diff --git a/Complexity.lean b/Complexity.lean index fecdf887d9..11d05a552e 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -31,6 +31,7 @@ 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 @@ -38,10 +39,14 @@ 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.QueryDigits 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 @@ -53,6 +58,7 @@ public import Complexity.Algorithms.Reachability.InductiveCounting.Semantics public import Complexity.Algorithms.Reachability.Reingold public import Complexity.Algorithms.Reachability.ReingoldEval public import Complexity.Algorithms.Reachability.Savitch +public import Complexity.Algorithms.Sorting.Counting public import Complexity.Algorithms.Streaming.CountMin.Basic public import Complexity.Algorithms.Streaming.CountMin.LinearHash public import Complexity.Algorithms.Streaming.CountMin.Probability @@ -112,6 +118,28 @@ 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 @@ -122,7 +150,100 @@ 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.Product +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Admission +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Arguments +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Bands +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Catalogue +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Copy +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Cost +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Directory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Exec +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Flat +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Groups +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Header +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Input +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Layout +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.MatrixInput +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.MatrixTransfer +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Parameters +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Program +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Reconstruct +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Route +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Routing +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.SavedGroups +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Thin +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.ThinWidth +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Tile +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Tiles +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Transfer +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Width +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.MatrixMultiplication.Schonhage.Wanted.Cost +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Count +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.CountStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Exec +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Flat +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.GroupedRuns +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Groups +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Input +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Meaning +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Pack +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.PackProgram +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Prefix +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.PrefixStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Program +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Route +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Scatter +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.ScatterStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.ScatterWrite +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Setup +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Width public import Complexity.Algorithms.WordRAM.NatClog public import Complexity.Algorithms.WordRAM.NatLog public import Complexity.Algorithms.WordRAM.NatSqrt @@ -1105,6 +1226,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 @@ -1729,6 +1851,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 @@ -1739,6 +1862,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 @@ -1760,6 +1884,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 @@ -1767,6 +1892,7 @@ public import Complexity.Computability.WordRAM.Randomized.Expected public import Complexity.Computability.WordRAM.Randomized.LoopSegment public import Complexity.Computability.WordRAM.Randomized.Structured public import Complexity.Computability.WordRAM.RegisterFrame +public import Complexity.Computability.WordRAM.Sequence public import Complexity.Computability.WordRAM.Signed.Arithmetic public import Complexity.Computability.WordRAM.Signed.Comparison public import Complexity.Computability.WordRAM.Signed.Compile @@ -2222,6 +2348,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 @@ -2244,6 +2371,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 @@ -2279,6 +2407,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 by rw [(hπ k).1]; trivial) hu + filter_eq_of_forall_orderEmbOfFin Q hQ (fun k => by rw [(hπ k).1]; trivial) hu have hvQ : innerSetR v = Q := - filter_eq_of_forall_innerLevel Q hQ (fun k => by rw [(hπ k).2]; trivial) hv - have huout := ((filter_eq_iff_forall_level Q hQ).mp huQ).2 - have hvout := ((filter_eq_iff_forall_level Q hQ).mp hvQ).2 + filter_eq_of_forall_orderEmbOfFin Q hQ (fun k => by rw [(hπ k).2]; trivial) hv + have huout := ((filter_eq_iff_orderEmbOfFin_and_compl Q hQ).mp huQ).2 + have hvout := ((filter_eq_iff_orderEmbOfFin_and_compl Q hQ).mp hvQ).2 -- at a level ℓ ∉ Q we have w_ℓ = z_ij for some i, j […] so (u_ℓ, v_ℓ) = (x_i, y_j). have hout : ∀ k, u ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = .x (r k) ∧ v ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = .y (c k) := @@ -135,8 +136,8 @@ private theorem prod_gamma_eq_one {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card ∏ ℓ, coefficient (leftStrOf Q hQ r π ℓ) (rightStrOf Q hQ π c ℓ) (outStrOf Q hQ r c ℓ) = 1 := by have hone : ∀ ℓ, coefficient (leftStrOf Q hQ r π ℓ) (rightStrOf Q hQ π c ℓ) (outStrOf Q hQ r c ℓ) = 1 := by - simp only [forall_level_iff Q hQ, leftStrOf, rightStrOf, outStrOf, glue_innerLevel, - glue_outerLevel] + simp only [forall_iff_orderEmbOfFin_and_compl Q hQ, leftStrOf, rightStrOf, outStrOf, + glue_orderEmbOfFin, glue_orderEmbOfFin_compl] exact ⟨fun _ => (coefficient_z0 _ _).trans (ite_eq_left ⟨_, _, rfl, rfl⟩), fun _ => (coefficient_x_y_z _ _ _ _).trans (ite_eq_left ⟨rfl, rfl⟩)⟩ exact prod_eq_one fun ℓ _ => hone ℓ diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean index da18d521e0..74bb5a6a3a 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean @@ -187,35 +187,36 @@ theorem innerSetO_outStrOf (r c : OuterStr L m) : innerSetO (outStrOf Q hQ r c) theorem outerPartL_leftStrOf (r : OuterStr L m) (π : InnerStr m) (h : (innerSetL (leftStrOf Q hQ r π)).card = m) : outerPartL (leftStrOf Q hQ r π) h = r := funext fun k => congrArg LeftVar.outerIndex - (glue_outerLevel_of_eq Q hQ _ _ (innerSetL_leftStrOf Q hQ r π) h k) + (glue_orderEmbOfFin_compl_of_eq Q hQ _ _ (innerSetL_leftStrOf Q hQ r π) h k) /-- The inner part of `leftStrOf Q hQ r π` is `π`. -/ @[simp] theorem innerPartL_leftStrOf (r : OuterStr L m) (π : InnerStr m) (h : (innerSetL (leftStrOf Q hQ r π)).card = m) : innerPartL (leftStrOf Q hQ r π) h = π := funext fun k => congrArg LeftVar.innerIndex - (glue_innerLevel_of_eq Q hQ _ _ (innerSetL_leftStrOf Q hQ r π) h k) + (glue_orderEmbOfFin_of_eq Q hQ _ _ (innerSetL_leftStrOf Q hQ r π) h k) /-- The inner part of `rightStrOf Q hQ π c` is `π`. -/ @[simp] theorem innerPartR_rightStrOf (π : InnerStr m) (c : OuterStr L m) (h : (innerSetR (rightStrOf Q hQ π c)).card = m) : innerPartR (rightStrOf Q hQ π c) h = π := funext fun k => congrArg RightVar.innerIndex - (glue_innerLevel_of_eq Q hQ _ _ (innerSetR_rightStrOf Q hQ π c) h k) + (glue_orderEmbOfFin_of_eq Q hQ _ _ (innerSetR_rightStrOf Q hQ π c) h k) /-- The outer part of `rightStrOf Q hQ π c` is `c`. -/ @[simp] theorem outerPartR_rightStrOf (π : InnerStr m) (c : OuterStr L m) (h : (innerSetR (rightStrOf Q hQ π c)).card = m) : outerPartR (rightStrOf Q hQ π c) h = c := funext fun k => congrArg RightVar.outerIndex - (glue_outerLevel_of_eq Q hQ _ _ (innerSetR_rightStrOf Q hQ π c) h k) + (glue_orderEmbOfFin_compl_of_eq Q hQ _ _ (innerSetR_rightStrOf Q hQ π c) h k) /-- The variable of `outStrOf Q hQ r c` at the `k`-th lowest level outside its inner set. -/ private theorem outStrOf_outerLevel (r c : OuterStr L m) (h : (innerSetO (outStrOf Q hQ r c)).card = m) (k : Fin (L - m)) : outStrOf Q hQ r c (((innerSetO (outStrOf Q hQ r c))ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetO (outStrOf Q hQ r c)) h)) k) = .z (r k) (c k) := by - rw [outerLevel_congr Q hQ (innerSetO_outStrOf Q hQ r c) h, outStrOf, glue_outerLevel] + simp only [innerSetO_outStrOf] at h ⊢ + exact glue_orderEmbOfFin_compl Q hQ _ _ k /-- The row of `outStrOf Q hQ r c` is `r`. -/ @[simp] @@ -236,7 +237,8 @@ part. -/ theorem leftStr_eq_leftStrOf {u : LeftStr L} (hu : innerSetL u = Q) (h : (innerSetL u).card = m) : u = leftStrOf Q hQ (outerPartL u h) (innerPartL u h) := by subst hu - refine glue_index _ h LeftVar.IsInner (inner := fun π : Fin 2 × Fin 2 => .p π.1 π.2) ?_ ?_ rfl + refine eq_glue_of_reconstruct _ h LeftVar.IsInner + (fromSubset := fun π : Fin 2 × Fin 2 => .p π.1 π.2) ?_ ?_ rfl · rintro (i | ⟨i, j⟩) hs exacts [hs.elim, rfl] · rintro (i | ⟨i, j⟩) hs @@ -247,7 +249,8 @@ part. -/ theorem rightStr_eq_rightStrOf {v : RightStr L} (hv : innerSetR v = Q) (h : (innerSetR v).card = m) : v = rightStrOf Q hQ (innerPartR v h) (outerPartR v h) := by subst hv - refine glue_index _ h RightVar.IsInner (inner := fun π : Fin 2 × Fin 2 => .q π.1 π.2) ?_ ?_ rfl + refine eq_glue_of_reconstruct _ h RightVar.IsInner + (fromSubset := fun π : Fin 2 × Fin 2 => .q π.1 π.2) ?_ ?_ rfl · rintro (j | ⟨i, j⟩) ht exacts [ht.elim, rfl] · rintro (j | ⟨i, j⟩) ht @@ -257,9 +260,9 @@ theorem rightStr_eq_rightStrOf {v : RightStr L} (hv : innerSetR v = Q) theorem outStr_eq_outStrOf {w : OutStr L} (hw : innerSetO w = Q) (h : (innerSetO w).card = m) : w = outStrOf Q hQ (rowO w h) (colO w h) := by subst hw - refine glue_index _ h OutVar.IsInner (inner := fun _ : Unit => .z0) - (outer := fun rc : Fin 3 × Fin 3 => .z rc.1 rc.2) (innerIndex := fun _ => ()) - (outerIndex := fun z => (z.rowIndex, z.colIndex)) ?_ ?_ rfl + refine eq_glue_of_reconstruct _ h OutVar.IsInner (fromSubset := fun _ : Unit => .z0) + (fromCompl := fun rc : Fin 3 × Fin 3 => .z rc.1 rc.2) (toSubset := fun _ => ()) + (toCompl := fun z => (z.rowIndex, z.colIndex)) ?_ ?_ rfl · rintro (⟨i, j⟩ | _) hz exacts [hz.elim, rfl] · rintro (⟨i, j⟩ | _) hz 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/QueryDigits.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/QueryDigits.lean new file mode 100644 index 0000000000..f54c776e6f --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/QueryDigits.lean @@ -0,0 +1,55 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.MaskTable + +/-! +# Packing a wanted output's digits + +The binary mask selects inner positions. At each remaining position, consume one digit +from each compressed base-three offset and emit their row-major base-ten digit. +All three numberings put the first coordinate in the least significant place. + +## Main definitions + +* `QueryDigits.pack`: the arithmetic recurrence computed by the native query encoder. + +## Main results + +* `QueryDigits.pack_lt`: the packed word occupies at most the original number of digits. + +This recurrence and its elementary bounds are original implementation mathematics. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage.QueryDigits + +/-- Pack a binary level mask and two compressed base-three offsets into output digits. +Unused high bits and high offset digits are ignored after the specified depth. -/ +def pack : ℕ → ℕ → ℕ → ℕ → ℕ + | 0, _, _, _ => 0 + | L + 1, mask, row, col => + if mask % 2 = 0 then + 3 * (row % 3) + col % 3 + 10 * pack L (mask / 2) (row / 3) (col / 3) + else 9 + 10 * pack L (mask / 2) row col + +/-- Packing never needs a supplied bound on a partial accumulator or residual input. -/ +theorem pack_lt (L mask row col : ℕ) : pack L mask row col < 10 ^ L := by + induction L generalizing mask row col with + | zero => simp [pack] + | succ L ih => + have hr := Nat.mod_lt row (by decide : 0 < 3) + have hc := Nat.mod_lt col (by decide : 0 < 3) + have ho := ih (mask / 2) (row / 3) (col / 3) + have hi := ih (mask / 2) row col + simp only [pack, pow_succ] + split <;> omega + +end Cslib.Algorithms.MatrixMultiplication.Schonhage.QueryDigits 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/Sorting/Counting.lean b/Complexity/Algorithms/Sorting/Counting.lean new file mode 100644 index 0000000000..4b9c4a737f --- /dev/null +++ b/Complexity/Algorithms/Sorting/Counting.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.Init +public import Mathlib.Data.Finset.Card +public import Mathlib.Data.Finset.Range +public import Mathlib.Data.Nat.Count +public import Mathlib.Data.Fintype.EquivFin +public import Mathlib.Order.Interval.Finset.Nat + +/-! +# Stable counting-sort positions + +## Main definitions + +* `CountingSort.position`: stable scatter position of an original occurrence. + +## Main results + +* `CountingSort.count_lt_succ`: the prefix-sum update used by a native counting pass. +* `CountingSort.position_injective`: every original position has a distinct destination. +* `CountingSort.position_bijective`: scatter destinations form a permutation of the input. +* `CountingSort.scatter_span_image`: an actual scattered array's bucket span has exactly + the values whose original keys select that bucket. + +These finite counting identities are original supporting mathematics for the native router. +They reuse Mathlib's `Nat.count` and its prefix-count APIs directly; only the stable-scatter +construction and cross-bucket theory are added. They assert no program or execution cost. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.CountingSort + +open Finset + +/-- The stable destination of original position `i` in an input of length `n`. -/ +def position (key : ℕ → ℕ) (n i : ℕ) : ℕ := + Nat.count (fun k => key k < key i) n + Nat.count (fun k => key k = key i) i + +/-- Larger keys start no earlier. -/ +private theorem offset_mono (key : ℕ → ℕ) (n : ℕ) {b c : ℕ} (h : b ≤ c) : + Nat.count (fun k => key k < b) n ≤ Nat.count (fun k => key k < c) n := by + exact Nat.count_mono_left (fun _ _ hk => hk.trans_le h) + +/-- One prefix-sum step appends exactly the current bucket's cardinality. -/ +theorem count_lt_succ (key : ℕ → ℕ) (n b : ℕ) : + Nat.count (fun k => key k < b + 1) n = + Nat.count (fun k => key k < b) n + Nat.count (fun k => key k = b) n := by + have he : ((range n).filter fun i => key i < b + 1) = + ((range n).filter fun i => key i < b) ∪ ((range n).filter fun i => key i = b) := by + ext i + simp only [mem_filter, mem_union, mem_range] + omega + simp only [Nat.count_eq_card_filter_range] + rw [he, card_union_of_disjoint] + exact disjoint_left.mpr (by + intro i hi hj + have := (mem_filter.mp hi).2 + have := (mem_filter.mp hj).2 + omega) + +/-- Each occurrence lands before the next bucket, and after its own bucket's start. -/ +theorem position_mem (key : ℕ → ℕ) {n i : ℕ} (hi : i < n) : + Nat.count (fun k => key k < key i) n ≤ position key n i ∧ + position key n i < Nat.count (fun k => key k < key i + 1) n := by + have := Nat.count_strict_mono (p := fun j => key j = key i) rfl hi + rw [count_lt_succ] + exact ⟨Nat.le_add_right _ _, Nat.add_lt_add_left ‹_› _⟩ + +/-- Stable scatter destinations are valid original-length indices. -/ +theorem position_lt (key : ℕ → ℕ) {n i : ℕ} (hi : i < n) : position key n i < n := + (position_mem key hi).2.trans_le (Nat.count_le _) + +/-- Original order is retained inside a bucket. -/ +theorem position_lt_same (key : ℕ → ℕ) (n : ℕ) {i j : ℕ} (hij : i < j) + (hkey : key i = key j) : position key n i < position key n j := by + have := Nat.count_strict_mono (p := fun j => key j = key i) rfl hij + simp only [position, hkey] at * + omega + +/-- Every position in a smaller bucket precedes every position in a larger bucket. -/ +theorem position_lt_key (key : ℕ → ℕ) {n i j : ℕ} (hi : i < n) + (hkey : key i < key j) : position key n i < position key n j := + ((position_mem key hi).2.trans_le + (offset_mono key n (Nat.succ_le_of_lt hkey))).trans_le (Nat.le_add_right _ _) + +/-- Distinct original positions never overwrite one another during stable scatter. -/ +theorem position_injective (key : ℕ → ℕ) (n : ℕ) : + Function.Injective (fun i : Fin n => position key n i.val) := by + intro i j he + change position key n i.val = position key n j.val at he + rcases lt_trichotomy (key i) (key j) with hk | hk | hk + · have := position_lt_key key i.isLt hk + omega + · rcases lt_trichotomy i.val j.val with hij | hij | hij + · have := position_lt_same key n hij hk + omega + · exact Fin.ext hij + · have := position_lt_same key n hij hk.symm + omega + · have := position_lt_key key j.isLt hk + omega + +/-- All original occurrences, including equal keys, are a permutation of the output cells. -/ +theorem position_bijective (key : ℕ → ℕ) (n : ℕ) : + Function.Bijective (fun i : Fin n => + (⟨position key n i.val, position_lt key i.isLt⟩ : Fin n)) := by + have hi : Function.Injective (fun i : Fin n => + (⟨position key n i.val, position_lt key i.isLt⟩ : Fin n)) := + fun i j h => position_injective key n (congrArg (fun j : Fin n => j.val) h) + exact ⟨hi, Finite.surjective_of_injective hi⟩ + +/-- A stable destination belongs to a bucket's half-open span exactly when its key is that +bucket. Equal values or repeated original requests do not change the interval criterion. -/ +theorem position_mem_iff (key : ℕ → ℕ) {n i : ℕ} (hi : i < n) (b : ℕ) : + (Nat.count (fun k => key k < b) n ≤ position key n i ∧ + position key n i < Nat.count (fun k => key k < b + 1) n) ↔ + key i = b := by + constructor + · intro h + rcases lt_trichotomy (key i) b with hb | hb | hb + · have hp := (position_mem key hi).2.trans_le (offset_mono key n (Nat.succ_le_of_lt hb)) + omega + · exact hb + · have hp := (offset_mono key n (Nat.succ_le_of_lt hb)).trans (position_mem key hi).1 + change Nat.count (fun k => key k < b + 1) n ≤ position key n i at hp + omega + · intro h + subst b + exact position_mem key hi + +/-- A memory array satisfying the stable-scatter equations has precisely the original +values selected by each bucket. No sorted list or permutation certificate is a premise. -/ +theorem scatter_span_image {α : Type*} [DecidableEq α] (key : ℕ → ℕ) (n b : ℕ) + (stored values : ℕ → α) (h : ∀ i < n, stored (position key n i) = values i) : + (Ico (Nat.count (fun k => key k < b) n) (Nat.count (fun k => key k < b + 1) n)).image stored = + ((range n).filter fun i => key i = b).image values := by + ext v + constructor + · intro hv + obtain ⟨j, hj, hv⟩ := mem_image.mp hv + have hjn : j < n := (mem_Ico.mp hj).2.trans_le (Nat.count_le _) + obtain ⟨i, hi⟩ := (position_bijective key n).surjective ⟨j, hjn⟩ + have hpos : position key n i.val = j := congrArg (fun t : Fin n => t.val) hi + have hkey : key i.val = b := (position_mem_iff key i.isLt b).mp (by + rw [hpos] + exact mem_Ico.mp hj) + exact mem_image.mpr ⟨i.val, mem_filter.mpr ⟨mem_range.mpr i.isLt, hkey⟩, + (h i.val i.isLt).symm.trans (by simpa only [hpos] using hv)⟩ + · intro hv + obtain ⟨i, hi, hv⟩ := mem_image.mp hv + obtain ⟨hin, hik⟩ := mem_filter.mp hi + have hlt := mem_range.mp hin + exact mem_image.mpr ⟨position key n i, + mem_Ico.mpr ((position_mem_iff key hlt b).mpr hik), (h i hlt).trans hv⟩ + +end Cslib.Algorithms.CountingSort 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/Encoding.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean index 10afd12bbb..d9cf2eb322 100644 --- a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean @@ -57,7 +57,9 @@ 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. -/ +/-- Executable flat address digits for the seven left variables. `LeftVar.equiv` instead +exposes their mathematical sum carrier `Fin 3 ⊕ (Fin 2 × Fin 2)`. This dictionary gives a +single `Fin 7` digit for each variable, for the native array layout. -/ def leftDigits : Fin 7 ≃ LeftVar where toFun := leftAt invFun @@ -66,7 +68,9 @@ def leftDigits : Fin 7 ≃ LeftVar where left_inv := by decide right_inv := by decide -/-- Explicit numbering of the seven right variables. -/ +/-- Executable flat address digits for the seven right variables. `RightVar.equiv` instead +exposes their mathematical sum carrier `Fin 3 ⊕ (Fin 2 × Fin 2)`. This dictionary gives a +single `Fin 7` digit for each variable, in the same order as `leftDigits`. -/ def rightDigits : Fin 7 ≃ RightVar where toFun := rightAt invFun @@ -75,7 +79,9 @@ def rightDigits : Fin 7 ≃ RightVar where left_inv := by decide right_inv := by decide -/-- Explicit numbering of all ten terms. -/ +/-- Executable flat address digits for all ten terms. `Term.equiv` instead exposes their +mathematical carrier `Option (Fin 3 × Fin 3)`. The native dictionary uses `Fin 10`, with +row-major `P i j` at digit `3*i+j` and `P0` at digit 9. -/ def termDigits : Fin 10 ≃ Term where toFun := termAt invFun diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product.lean new file mode 100644 index 0000000000..e2531eb569 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product.lean @@ -0,0 +1,50 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.ThinWidth + +/-! +# Native queried thin-matrix products + +## Main definitions + +* `QueriedProduct.program`: fixed standard unsigned WordRAM code with 160 registers. +* `QueriedProduct.input`: original scalar headers, both row-major matrices and raw query pairs. + +## Main results + +* `QueriedProduct.exec`: complete execution from original words, with arbitrary header zero + and outside memory, exact `Matrix.mul` answers and input restoration. +* `QueriedProduct.runs_wide`: initialized flat runs, guaranteed input fit and a charged halt. +* `QueriedProduct.runs_thin`: the same native execution at `L=19m`, `D=4^m`, `D^18≤N`, + with instruction bound `4325376*(19m+1)*N²/D^(1/18)` when `Q*2^m≤N²`, and sufficient + physical width `128*clog₂(N+Q+A+2)` for entry bound `A`. +* `QueriedProduct.thin_timeBound`: distinct-set sparsity gives a saving with an additive + charge for every query occurrence, without imposing the stronger occurrence-count premise. + +The program computes its own valid-subset tables, shared band encodings, tile keys, query +codes, counts, prefix offsets, stable scatter, tile pointers, pruned traversals, epochs and +answer reconstruction. Original query order and duplicates are retained. Signed mode uses +canonical `Signed.encode` input words. Raw mode requires nonnegative entries and actually +doubles each word before calling the signed producer; this includes raw unsigned 0/1 inputs. +One timestamp directory is initialized and reused across all tiles. + +The component reuses the finite Section 2 tensor/pruning and thin-product mathematics from +[AlmanWilliams2026], with original programs and execution proofs against native WordRAM APIs. +It does not import the source Light machine or assume a solver, execution or cost contract. +Stage `exec_*` lemmas are internal implementation APIs shared between proof modules. Callers +use the results above; the initialized input contains no computed layout or query certificate. +This is a queried-product component, not a complete Exact Triangle or ordinary-3SUM algorithm. +The full endpoint and improved Section 4 route to exponent 1.9992 remain under #1394. + +## References + +* [AlmanWilliams2026], Section 2: finite tensor encodings and sparse queried products. +-/ diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Admission.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Admission.lean new file mode 100644 index 0000000000..cb02886417 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Admission.lean @@ -0,0 +1,60 @@ +/- +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.Product.Input + +/-! +# Deriving producer and tile-kernel admission from original inputs + +## Main results + +* `QueriedProduct.kernel_fits`: actual separated tile pointers and bounded fresh epochs fit + from the single original numerical allocation envelope. +* `QueriedProduct.magnitude_admissions`: the original entry envelope covers shared encodings, + every pruned partial sum and the actual raw-word doubling instruction. + +No intermediate-state fit contract is part of the final caller's input. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM + +/-- The shared original allocation bound admits every computed tile's actual pointer values. +Query counts retain occurrences; epoch bounds are established by the caller's loop invariant. -/ +public theorem kernel_fits {w L m N Q a b q o n E : ℕ} + (hfit : Fits w (wordBudget L m N Q)) + (ha : a ≤ BandPreparation.memoryEnd L m N) (hb : b ≤ BandPreparation.memoryEnd L m N) + (hq : q ≤ codeBase L m N Q + Q) (ho : o ≤ outputBase L m N Q) (hn : n ≤ Q) + (hE : E ≤ epochBound L m N) : + Fits w (PrunedQuery.kernelBudget L (scratchBase L m N Q) a b q o n E) := by + apply hfit.of_le + unfold wordBudget PrunedQuery.kernelBudget PrunedQuery.evaluationBudget + PrunedQuery.nodeBudget PrunedQuery.addressBound + omega + +/-- All signed and conversion admission follows from the original integer-entry magnitude. +The extra two physical bits are the existing native signed-kernel convention. -/ +public theorem magnitude_admissions {w L A : ℕ} (hw : 0 < w) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) : + (7 ^ L : ℕ) * (A : ℤ) < (2 : ℤ) ^ (w - 1) ∧ + PrunedQuery.magnitudeBound L (7 ^ L * A) (7 ^ L * A) < 2 ^ (w - 1) ∧ + Fits (w + 2) (2 * A) := by + have hk : PrunedQuery.magnitudeBound L (7 ^ L * A) (7 ^ L * A) < 2 ^ (w - 1) := by + unfold magnitudeBound at hmag + omega + have hp : 7 ^ L * A < 2 ^ (w - 1) := by + unfold PrunedQuery.magnitudeBound at hk + omega + refine ⟨by exact_mod_cast hp, hk, ?_⟩ + simpa only [entryWord, Bool.false_eq_true, ↓reduceIte, Signed.encode, + Int.natAbs_natCast, ite_eq_right (show ¬(A : ℤ) < 0 by omega), Nat.add_zero] using + entryWord_fits hw false (A : ℤ) (by simp) hmag + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Arguments.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Arguments.lean new file mode 100644 index 0000000000..0d593cc4bc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Arguments.lean @@ -0,0 +1,127 @@ +/- +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.Product.Transfer + +/-! +# Computing each tile's actual kernel arguments + +## Main results + +* `QueriedProduct.exec_tileArguments`: load real group endpoints, divide the tile index, + compute shared catalogue/query/output pointers and populate the existing native kernel ABI. + +The span values in this internal interface describe the actual saved offset words. The full +caller establishes their bounds from its executed counting/prefix/scatter passes. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +set_option maxRecDepth 4096 in +-- The fixed 160-register footprint check simplifies a 32-member persistent register set. +/-- Actual tile-index arithmetic and kernel-argument setup cost twenty-one instructions. +Only the two real offset words are read. Every unsigned arithmetic fit follows from the +original allocation bound and the computed span bounds. -/ +public theorem exec_tileArguments {w L m N Q t a b : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hx : x.regs index = t) (ht : t < numBands L m N ^ 2) + (ha : x.mem (offsetBase L m N Q + t) = a) + (hb : x.mem (offsetBase L m N Q + t + 1) = b) (hab : a ≤ b) (hbQ : b ≤ Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w tileArguments x y 21 ∧ Parameters L m N Q y ∧ y.mem = x.mem ∧ + y.regs index = t ∧ y.regs epoch = x.regs epoch ∧ + (y.comap bank).regs PrunedQuery.inputDepth = L ∧ + (y.comap bank).regs PrunedQuery.arrayA = + BandPreparation.catalogueBase L m N + t / numBands L m N * 10 ^ L ∧ + (y.comap bank).regs PrunedQuery.arrayB = + BandPreparation.catalogueBase L m N + + (numBands L m N + t % numBands L m N) * 10 ^ L ∧ + (y.comap bank).regs PrunedQuery.queries = codeBase L m N Q + a ∧ + (y.comap bank).regs PrunedQuery.queryCount = b - a ∧ + (y.comap bank).regs PrunedQuery.output = groupedBase L m N Q + a ∧ + (y.comap bank).regs PrunedQuery.scratch = scratchBase L m N Q ∧ + (y.comap bank).regs PrunedQuery.epoch = x.regs epoch := by + let B := numBands L m N + let H := 10 ^ L + let C := BandPreparation.catalogueBase L m N + have hB : 0 < B := by dsimp [B] at *; nlinarith only [ht, Nat.zero_le t] + have hH : 1 ≤ H := Nat.one_le_pow L _ (by decide) + have hr : t / B < B := (Nat.div_lt_iff_lt_mul hB).mpr (by simpa [B, pow_two] using ht) + have hc : t % B < B := Nat.mod_lt t hB + have hrH : t / B * H ≤ B * H := Nat.mul_le_mul_right H hr.le + have hcH : (t % B + B) * H ≤ 2 * B * H := Nat.mul_le_mul_right H (by omega) + have horder : C + 2 * B * H ≤ memoryEnd L m N Q := by + have h := (producers_le_snapshot L m N Q).1 + have h' := (transfer_bounds L m N Q).2.1 + dsimp only [C, B, H, BandPreparation.memoryEnd] at * + omega + have hcodes : codeBase L m N Q + Q ≤ memoryEnd L m N Q := by + have h10 := Nat.zero_le H + unfold memoryEnd stampBase scratchBase outputBase groupedBase originalBase + omega + have hgroups : groupedBase L m N Q + Q ≤ memoryEnd L m N Q := by + unfold memoryEnd stampBase scratchBase outputBase + have h10 := Nat.zero_le (10 ^ L) + omega + have htiles : offsetBase L m N Q + B ^ 2 + 1 ≤ memoryEnd L m N Q := by + dsimp only [B] + unfold codeBase at hcodes + omega + have foffset : Fits w (offsetBase L m N Q + t) := allocated_fits hfit (by + dsimp only [B] at * + omega) + have fnext : Fits w (offsetBase L m N Q + t + 1) := allocated_fits hfit (by + dsimp only [B] at * + omega) + have frow : Fits w (t / B) := allocated_fits hfit (by nlinarith only [hr, hH, horder]) + have fcol : Fits w (t % B) := allocated_fits hfit (by nlinarith only [hc, hH, horder]) + have frowH : Fits w (t / B * H) := allocated_fits hfit (by nlinarith only [hrH, horder]) + have fleft : Fits w (t / B * H + C) := allocated_fits hfit (by nlinarith only [hrH, horder]) + have fcb : Fits w (t % B + B) := allocated_fits hfit (by + nlinarith only [hcH, hH, horder, Nat.zero_le (t % B + B)]) + have fcbH : Fits w ((t % B + B) * H) := allocated_fits hfit (by omega) + have fright : Fits w ((t % B + B) * H + C) := allocated_fits hfit (by omega) + have fq : Fits w (codeBase L m N Q + a) := allocated_fits hfit (by omega) + have fo : Fits w (groupedBase L m N Q + a) := allocated_fits hfit (by omega) + have fn : Fits w (b - a) := allocated_fits hfit (by omega) + let y := (((((((((((((((((((((x.set address (offsetBase L m N Q + t)).set first a).set + address (offsetBase L m N Q + t + 1)).set stop b).set rowBand (t / B)).set colBand + (t % B)).set leftPointer (t / B * H)).set leftPointer (t / B * H + C)).set + rightPointer (t % B + B)).set rightPointer ((t % B + B) * H)).set rightPointer + ((t % B + B) * H + C)).set queryPointer (codeBase L m N Q + a)).set answerPointer + (groupedBase L m N Q + a)).set (bank PrunedQuery.inputDepth) L).set + (bank PrunedQuery.arrayA) (t / B * H + C)).set (bank PrunedQuery.arrayB) + ((t % B + B) * H + C)).set (bank PrunedQuery.queries) (codeBase L m N Q + a)).set + (bank PrunedQuery.queryCount) (b - a)).set (bank PrunedQuery.output) + (groupedBase L m N Q + a)).set (bank PrunedQuery.scratch) (scratchBase L m N Q)).set + (bank PrunedQuery.epoch) (x.regs epoch)) + have he : ExecWithin w tileArguments x y 21 := by + ram_eval [tileArguments, y, hp.offsets, hp.one, hp.bands, hp.leaves, hp.catalogue, + hp.codes, hp.grouped, hp.depth, hp.scratch, hx, ha, hb, foffset, fnext, frow, fcol, + frowH, fleft, fcb, fcbH, fright, fq, fo, fn, B, H, C, bank, Fin.val_ofNat, + PrunedQuery.inputDepth, PrunedQuery.arrayA, PrunedQuery.arrayB, PrunedQuery.queries, + PrunedQuery.queryCount, PrunedQuery.output, PrunedQuery.scratch, PrunedQuery.epoch, + address, offsets, index, first, stop, one, rowBand, colBand, bands, leftPointer, + rightPointer, leaves, catalogue, queryPointer, answerPointer, codes, grouped, depth, + scratch, epoch, Fin.mk.injEq] + have yp : Parameters L m N Q y := hp.of_frame (by + intro i hi + apply he.regs_eq + have h : ∀ i ∈ parameterRegisters, tileArguments.Preserves i := by decide + exact h i hi) + refine ⟨y, he, yp, rfl, ?_⟩ + simp +decide [y, Data.comap_regs, hx, B, H, C, bank, + PrunedQuery.inputDepth, PrunedQuery.arrayA, PrunedQuery.arrayB, PrunedQuery.queries, + PrunedQuery.queryCount, PrunedQuery.output, PrunedQuery.scratch, PrunedQuery.epoch, + address, first, stop, rowBand, colBand, leftPointer, rightPointer, queryPointer, + answerPointer, index, epoch, Nat.add_comm] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Bands.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Bands.lean new file mode 100644 index 0000000000..b6d4fa5569 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Bands.lean @@ -0,0 +1,85 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Admission +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandFlat + +/-! +# Running the actual shared catalogue producer in the product caller + +## Main results + +* `QueriedProduct.exec_bands`: invoke the complete actual band/encoding program in the low bank, + yielding the unchanged mathematical `encodingL/R` arrays and preserving the caller's scalars. + +The input memory equations are discharged by the preceding charged matrix adaptation. +No encoded catalogue, subset layout or producer execution is supplied to the program. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The actual native producer computes every shared band encoding, once per band. +Its complete outside frame protects the caller's original input and saved routing arrays. -/ +public theorem exec_bands {w L m N Q A : ℕ} (hw : 0 < w) (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (x : Data 160) (hp : Parameters L m N Q x) + (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) + (hmat : ∀ side r, r < N → ∀ k, k < D m → + x.mem (BandPreparation.matrixOffset m N side r k + 4) = + Signed.encode (BandPreparation.matrixValues X Y side r k)) + (hX : ∀ i k, |X i k| ≤ (A : ℤ)) (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N Q)) : + ∃ y, ExecWithin (w + 2) (BandPreparation.program.rename bank) x y + (BandPreparation.timeBound L m N) ∧ Parameters L m N Q y ∧ + (∀ β < numBands L m N, ∀ τ, + y.mem (BandPreparation.catalogueAddress L m N false β (Encoding.leafIndex τ).val) = + Signed.encode (encodingL (bandArrayL (MaskTable.layout hm) X β) τ)) ∧ + (∀ β < numBands L m N, ∀ τ, + y.mem (BandPreparation.catalogueAddress L m N true β (Encoding.leafIndex τ).val) = + Signed.encode (encodingR (bandArrayR (MaskTable.layout hm) Y β) τ)) ∧ + (∀ a, a < BandPreparation.inputEnd m N ∨ BandPreparation.memoryEnd L m N ≤ a → + y.mem a = x.mem a) := by + have hbound (side : Bool) (r : ℕ) (hr : r < N) (k : ℕ) (hk : k < D m) : + |BandPreparation.matrixValues X Y side r k| ≤ (A : ℤ) := by + cases side <;> + simp only [BandPreparation.matrixValues, dite_eq_left hr, dite_eq_left hk, + Bool.false_eq_true, ↓reduceIte] + · exact hX _ _ + · exact hY _ _ + obtain ⟨z, he, _, hv, _, _, hf⟩ := BandPreparation.exec hw hm (x.comap bank) hL hmemory hN + (BandPreparation.matrixValues X Y) hmat (A : ℤ) (by positivity) hbound + (magnitude_admissions hw hmag).1 (producer_fits hfit).1 + let y := z.extend bank x + have hframe (i : Fin 160) (hi : 96 ≤ i.val) : y.regs i = x.regs i := by + have hnot : ¬∃ j, bank j = i := by + rintro ⟨j, hj⟩ + have hv := congrArg (fun r : Fin 160 => r.val) hj + change j.val = i.val at hv + omega + exact Function.extend_apply' _ _ _ hnot + refine ⟨y, he.rename bank_injective, hp.reframe hframe, ?_, ?_, ?_⟩ + · intro β hβ τ + change z.mem _ = _ + simpa only [BandPreparation.catalogueValue_left hm X (BandPreparation.matrixValues X Y false) + (by intro i k; simp [BandPreparation.matrixValues])] using + hv false β hβ (Encoding.leafIndex τ).val (Encoding.leafIndex τ).isLt + · intro β hβ τ + change z.mem _ = _ + simpa only [BandPreparation.catalogueValue_right hm Y (BandPreparation.matrixValues X Y true) + (by intro k i; simp [BandPreparation.matrixValues])] using + hv true β hβ (Encoding.leafIndex τ).val (Encoding.leafIndex τ).isLt + · intro a ha + exact hf a (by rcases ha with ha | ha <;> omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Catalogue.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Catalogue.lean new file mode 100644 index 0000000000..8159062027 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Catalogue.lean @@ -0,0 +1,61 @@ +/- +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.Product.Bands + +/-! +# Preserving the actual shared catalogue between tile calls + +## Main definitions + +* `QueriedProduct.EncodedBands`: the unchanged producer's mathematical encodings in real memory. + +## Main results + +* `QueriedProduct.EncodedBands.reframe`: retain both catalogues through a readonly low-memory frame. + +This predicate contains only ordinary word equalities. `exec_bands` establishes them by +running the native producer; no execution, fit or cost assertion is bundled in the predicate. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Actual shared arrays, using the original band arrays and authoritative leaf numbering. -/ +public structure EncodedBands {L m N : ℕ} (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (μ : ℕ → ℕ) : Prop where + /-- The left encoding is computed once for each row band. -/ + left : ∀ β < numBands L m N, ∀ τ, + μ (BandPreparation.catalogueAddress L m N false β (Encoding.leafIndex τ).val) = + Signed.encode (encodingL (bandArrayL (MaskTable.layout hm) X β) τ) + /-- The right encoding is computed once for each column band. -/ + right : ∀ β < numBands L m N, ∀ τ, + μ (BandPreparation.catalogueAddress L m N true β (Encoding.leafIndex τ).val) = + Signed.encode (encodingR (bandArrayR (MaskTable.layout hm) Y β) τ) + +/-- The native tile kernel's low readonly frame preserves all shared encodings. -/ +public theorem EncodedBands.reframe {L m N : ℕ} {hm : m ≤ L} + {X : Matrix (Fin N) (Fin (D m)) ℤ} {Y : Matrix (Fin (D m)) (Fin N) ℤ} + {μ ν : ℕ → ℕ} (h : EncodedBands hm X Y μ) + (hf : ∀ a < BandPreparation.memoryEnd L m N, ν a = μ a) : EncodedBands hm X Y ν := by + have hread (side : Bool) (β : ℕ) (hβ : β < numBands L m N) (τ : Leaf L) : + ν (BandPreparation.catalogueAddress L m N side β (Encoding.leafIndex τ).val) = + μ (BandPreparation.catalogueAddress L m N side β (Encoding.leafIndex τ).val) := by + have hb := BandPreparation.catalogue_block_le side hβ + have ht := (Encoding.leafIndex τ).isLt + apply hf + unfold BandPreparation.catalogueAddress at hb ⊢ + omega + exact ⟨fun β hβ τ => (hread false β hβ τ).trans (h.left β hβ τ), + fun β hβ τ => (hread true β hβ τ).trans (h.right β hβ τ)⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Copy.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Copy.lean new file mode 100644 index 0000000000..3518f54316 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Copy.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.Product.Program + +/-! +# Charged copies across the producer/consumer boundary + +## Main results + +* `QueriedProduct.exec_copyCall`: real argument setup and the unchanged dynamic native copy, + with both address orders, equality, complete memory meaning and caller-register frame. + +These are internal execution interfaces. Complete product clients derive the numerical fits +and separation from the original input and the explicit computed layout. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM + +/-- Each existing 96-register callee has exactly its original register semantics. -/ +public theorem bank_injective : Function.Injective bank := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 160 => i.val) h) + +private theorem copyBank_injective : Function.Injective copyBank := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 160 => i.val) h) + +/-- Dynamic native argument setup and every copy instruction are charged. The caller's +source/destination/length registers, and every register beyond the seven-register callee, +are preserved. Source and destination separation is a numerical layout obligation. -/ +public theorem exec_copyCall {w : ℕ} (x : Data 160) (h1 : Fits w 1) + (hs : Fits w (x.regs source + x.regs length)) + (hd : Fits w (x.regs destination + x.regs length)) + (hsep : x.regs source + x.regs length ≤ x.regs destination ∨ + x.regs destination + x.regs length ≤ x.regs source ∨ + x.regs source = x.regs destination) : + ∃ y, ExecWithin w copyCall x y (7 * x.regs length + 7) ∧ + y.mem = ArrayCopy.copyPrefix x.mem (x.regs source) (x.regs destination) (x.regs length) ∧ + (∀ i, 7 ≤ i.val → y.regs i = x.regs i) := by + let x1 := x.set (0 : Fin 160) (x.regs source) + let x2 := x1.set (1 : Fin 160) (x.regs destination) + let x3 := x2.set (2 : Fin 160) (x.regs length) + have h0 : ExecWithin (B := .standard) w (.op (.copy (0 : Fin 160) source)) x x1 1 := + ExecWithin.copy + have h01 : ExecWithin (B := .standard) w (.op (.copy (1 : Fin 160) destination)) x1 x2 1 := by + ram_eval [x1, x2, source, destination] + have h02 : ExecWithin (B := .standard) w (.op (.copy (2 : Fin 160) length)) x2 x3 1 := by + ram_eval [x1, x2, x3, source, destination, length] + obtain ⟨z, hz, hm, _, _, _⟩ := ArrayCopy.exec_of_separated_or_eq (x3.comap copyBank) h1 + (by simpa [x1, x2, x3, copyBank, ArrayCopy.src, ArrayCopy.len] using hs) + (by simpa [x1, x2, x3, copyBank, ArrayCopy.dst, ArrayCopy.len] using hd) + (by simpa [x1, x2, x3, copyBank, ArrayCopy.src, ArrayCopy.dst, ArrayCopy.len] using hsep) + let y := z.extend copyBank x3 + have he := hz.rename copyBank_injective + refine ⟨y, ?_, ?_, ?_⟩ + · have hlen : (x3.comap copyBank).regs ArrayCopy.len = x.regs length := by + simp [x1, x2, x3, copyBank, ArrayCopy.len] + rw [hlen] at he + exact (h0.seq (h01.seq (h02.seq he))).mono (by omega) + · change z.mem = _ + simpa [x1, x2, x3, copyBank, ArrayCopy.src, ArrayCopy.dst, ArrayCopy.len] using hm + · intro i hi + have hnot : ¬∃ j, copyBank j = i := by + rintro ⟨j, hj⟩ + have hv := congrArg (fun r : Fin 160 => r.val) hj + change j.val = i.val at hv + omega + change Function.extend copyBank z.regs x3.regs i = x.regs i + rw [Function.extend_apply' _ _ _ hnot] + simp only [x3, x2, x1, Data.set_regs, + Function.update_of_ne (show i ≠ (2 : Fin 160) by omega), + Function.update_of_ne (show i ≠ (1 : Fin 160) by omega), + Function.update_of_ne (show i ≠ (0 : Fin 160) by omega)] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Cost.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Cost.lean new file mode 100644 index 0000000000..ae81685380 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Cost.lean @@ -0,0 +1,126 @@ +/- +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.Product.Routing +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Tiles + +/-! +# Complete native queried-product instruction ledger + +## Main definitions + +* `QueriedProduct.timeBound`: actual routing, copies, conversion, shared encoding, one directory + initialization, all tile calls, reconstruction and original-input restoration; halt is separate. + +Every term is derived from ordinary original inputs and the finite pruned trace definition. +No solver or running-time assertion is supplied as an input field. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open scoped BigOperators + +/-- A uniform instruction bound for either original signed inputs or raw nonnegative inputs. +The raw-conversion envelope charges a multiplication for every original matrix entry. -/ +@[expose] public def timeBound {L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) : ℕ := + routingTimeBound L m N qs.length + (8 * (2 * (N * D m)) + 10) + + BandPreparation.timeBound L m N + (5 * 10 ^ L + 5) + + ((∑ t ∈ Finset.range (numBands L m N ^ 2), tileCharge hm qs t) + 2) + + (9 * qs.length + 3) + (7 * inputEnd m N qs.length + 10) + +private theorem sum_bucket_counts (key : ℕ → ℕ) (Q T : ℕ) : + (∑ t ∈ Finset.range T, Nat.count (fun i => key i = t) Q) = + Nat.count (fun i => key i < T) Q := by + induction T with + | zero => simp + | succ T ih => rw [Finset.sum_range_succ, ih, CountingSort.count_lt_succ] + +private theorem sum_tile_indices (B : ℕ) (f : ℕ × ℕ → ℕ) : + (∑ t ∈ Finset.range (B ^ 2), f (t / B, t % B)) = + ∑ p ∈ Finset.range B ×ˢ Finset.range B, f p := by + have he := (finProdFinEquiv : Fin B × Fin B ≃ Fin (B * B)).symm.sum_comp + (fun p => f (p.1.val, p.2.val)) + change (∑ t : Fin (B * B), f (t.val / B, t.val % B)) = + ∑ p : Fin B × Fin B, f (p.1.val, p.2.val) at he + rw [pow_two, Finset.sum_product, + ← Fin.sum_univ_eq_sum_range (fun t => f (t / B, t % B)) (B * B), he, + Fintype.sum_prod_type, + Fin.sum_univ_eq_sum_range (fun i => ∑ j : Fin B, f (i, j.val)) B] + exact Finset.sum_congr rfl (fun i _ => Fin.sum_univ_eq_sum_range (fun j => f (i, j)) B) + +/-- The exact sum of occurrence-dependent call charges uses every original request once. +It uses no distinct-set bound on list multiplicity. -/ +public theorem sum_tileCharge {L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) : + (∑ t ∈ Finset.range (numBands L m N ^ 2), tileCharge hm qs t) = + (4 * L + 66) * numBands L m N ^ 2 + 29 * qs.length + + ∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N, + PrunedQuery.traceWeight (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2) := by + have hkey (i : ℕ) (hi : i < qs.length) : + (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) i < numBands L m N ^ 2 := + Wanted.tileKey_lt hm _ (Wanted.inputPairs_lt qs i hi).1 (Wanted.inputPairs_lt qs i hi).2 + have hcount := sum_bucket_counts (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length + (numBands L m N ^ 2) + rw [Nat.count_of_forall hkey] at hcount + simp only [tileCharge, Finset.sum_add_distrib, Finset.sum_const, + Finset.card_range, smul_eq_mul, ← Finset.mul_sum] + have htiles := sum_tile_indices (numBands L m N) (fun T => + PrunedQuery.traceWeight (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)) + rw [hcount, htiles] + unfold Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles + ring + +/-- Sum the actual native kernel trace charges using the existing contributing-leaf bound. -/ +public theorem sum_traceWeight_le {L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) : + (∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N, + PrunedQuery.traceWeight (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)) ≤ + 8192 * (L + 1) * + (∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N, + (Cslib.Algorithms.MatrixMultiplication.Schonhage.leaves + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)).card) + + 4096 * numBands L m N ^ 2 := by + have h := Finset.sum_le_sum (s := Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N) + (fun T _ => PrunedQuery.traceWeight_le + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)) + have hc : (Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N).card = + numBands L m N ^ 2 := by + simp only [Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles, + Finset.card_product, Finset.card_range, pow_two] + have heq (v : ℕ) : 4096 * (2 * (L + 1) * v + 1) = 8192 * (L + 1) * v + 4096 := by ring + simp_rw [heq] at h + simpa only [Finset.sum_add_distrib, ← Finset.mul_sum, Finset.sum_const, smul_eq_mul, + hc, Nat.mul_comm (numBands L m N ^ 2) 4096] using h + +/-- Every actual phase, including halt, is bounded by original input size, shared encodings, +tile count and contributing leaves. In particular, repeated requests retain a charged term. -/ +public theorem timeBound_le {L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) : + timeBound hm qs + 1 ≤ 65536 * (L + 1) * + (1 + N * D m + qs.length + 2 ^ L + (numBands L m N + 1) * 10 ^ L + + numBands L m N ^ 2 + + ∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N, + (Cslib.Algorithms.MatrixMultiplication.Schonhage.leaves + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)).card) := by + have htrace := sum_traceWeight_le hm qs + have hleaves := Nat.zero_le (∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N, + (Cslib.Algorithms.MatrixMultiplication.Schonhage.leaves + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)).card) + have hLleaves := Nat.zero_le (L * + (∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N, + (Cslib.Algorithms.MatrixMultiplication.Schonhage.leaves + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)).card)) + rw [timeBound, sum_tileCharge] + unfold routingTimeBound Wanted.timeBound BandPreparation.timeBound inputEnd + nlinarith only [htrace, hleaves, hLleaves, Nat.zero_le L, Nat.zero_le (2 ^ L), + Nat.zero_le (10 ^ L), Nat.zero_le (L * 10 ^ L), + Nat.zero_le (N * D m), Nat.zero_le (L * (N * D m)), Nat.zero_le qs.length, + Nat.zero_le (L * qs.length), 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)), + Nat.zero_le (numBands L m N ^ 2), Nat.zero_le (L * numBands L m N ^ 2)] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Directory.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Directory.lean new file mode 100644 index 0000000000..35c4a8fc05 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Directory.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.Product.Transfer + +/-! +# One charged timestamp initialization for all product tiles + +## Main results + +* `QueriedProduct.exec_initializeDirectory`: clear the actual shared directory once and set + the initial epoch and tile index, with a complete outside frame. + +Subsequent kernel calls propagate their returned epoch. This initialization is not repeated +per tile or request. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM + +/-- Actual directory stores and loop control, followed by the epoch/index setup. -/ +public theorem exec_initializeDirectory {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w initializeDirectory x y (5 * 10 ^ L + 5) ∧ + Parameters L m N Q y ∧ y.regs epoch = 0 ∧ y.regs index = 0 ∧ + (∀ j < 10 ^ L, y.mem (stampBase L m N Q + j) = 0) ∧ + (∀ a, a < stampBase L m N Q ∨ memoryEnd L m N Q ≤ a → y.mem a = x.mem a) := by + let H := 10 ^ L + let body : Code .standard 160 := ram[ + address ← stamps + index; *address ← zero; index ← index + one ] + let I (z : Data 160) : Prop := Parameters L m N Q z ∧ z.regs index ≤ H ∧ + (∀ j < z.regs index, z.mem (stampBase L m N Q + j) = 0) ∧ + (∀ a, a < stampBase L m N Q ∨ memoryEnd L m N Q ≤ a → z.mem a = x.mem a) + let start := x.set index 0 + have hs : ExecWithin (B := .standard) w (ram[index ← #0]) x start 1 := by + ram_eval [start] + have sp : Parameters L m N Q start := hp.set_work index (by decide) _ + have hstart : I start := ⟨sp, by simp [start], by simp [start], by simp [start]⟩ + have hbody (z : Data 160) (hz : I z) (hc : (Cond.lt index leaves).Holds z) : + ∃ u, ExecWithin w body z u 3 ∧ I u ∧ H - u.regs index < H - z.regs index := by + have hi : z.regs index < H := by simpa only [Cond.holds_lt, hz.1.leaves, H] using hc + have ha : Fits w (stampBase L m N Q + z.regs index) := allocated_fits hfit (by + unfold memoryEnd + dsimp only [H] at hi + omega) + have hn : Fits w (z.regs index + 1) := allocated_fits hfit (by + unfold memoryEnd + dsimp only [H] at hi + omega) + let u := ((z.set address (stampBase L m N Q + z.regs index)).write + (stampBase L m N Q + z.regs index) 0).set index (z.regs index + 1) + have he : ExecWithin w body z u 3 := by + ram_eval [body, u, hz.1.stamps, hz.1.zero, hz.1.one, ha, hn, + address, stamps, index, zero, one] + have up : Parameters L m N Q u := + ((hz.1.set_work address (by decide) _).write _ _).set_work index (by decide) _ + have hnext : u.regs index = z.regs index + 1 := by simp [u] + refine ⟨u, he, ⟨up, by rw [hnext]; omega, ?_, ?_⟩, by rw [hnext]; omega⟩ + · intro j hj + rw [hnext] at hj + change Function.update z.mem (stampBase L m N Q + z.regs index) 0 + (stampBase L m N Q + j) = 0 + by_cases h : j = z.regs index + · subst j + exact Function.update_self _ _ _ + · rw [Function.update_of_ne (by omega)] + exact hz.2.2.1 j (by omega) + · intro a ha + change Function.update z.mem (stampBase L m N Q + z.regs index) 0 a = _ + rw [Function.update_of_ne (by + unfold memoryEnd at ha + dsimp only [H] at hi + rcases ha with ha | ha <;> omega)] + exact hz.2.2.2 a ha + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => H - z.regs index) hbody hstart + have hn : y.regs index = H := by + have hi := hy.2.1 + simp only [Cond.holds_lt, hy.1.leaves] at hc + dsimp only [H] at hi ⊢ + omega + let z := (y.set epoch 0).set index 0 + have hz : ExecWithin (B := .standard) w (ram[epoch ← #0; index ← #0]) y z 2 := by + ram_eval [z] + refine ⟨z, ?_, (hy.1.set_work epoch (by decide) _).set_work index (by decide) _, + by simp [z, epoch, index], by simp [z], ?_, ?_⟩ + · apply (hs.seq (he.seq hz)).mono + have hzero : start.regs index = 0 := by simp [start] + rw [hzero, Nat.sub_zero] + dsimp only [H] + omega + · simpa only [z, Data.set_mem, hn, H] using hy.2.2.1 + · simpa only [z, Data.set_mem] using hy.2.2.2 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Exec.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Exec.lean new file mode 100644 index 0000000000..c746fc788f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Exec.lean @@ -0,0 +1,153 @@ +/- +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.Product.Cost +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.MatrixInput +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Directory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Reconstruct + +/-! +# Complete actual native queried matrix products + +## Main results + +* `QueriedProduct.exec`: original matrix/coordinate words suffice for actual finite native + execution, exact original-order product values, original-input restoration and outside frame. + +The program computes all tables, groups, catalogues, tile arguments, epochs and reporting +decisions. The original-word predicate permits arbitrary header zero and arbitrary outside +memory; initialized clients discharge it directly from the literal `input` list. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Actual composed execution from only ordinary original words, with every phase charged. +No table/layout/query/producer/run/time certificate or fast-solver assumption is an input. -/ +public theorem exec {w L m N A : ℕ} (hw : 0 < w) (hm : m ≤ L) (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (x : Data 160) + (hinput : ∀ a, 0 < a → a < inputEnd m N qs.length → x.mem a = + (State.init (input bits L X Y qs) : State 160).data.mem a) + (hX : ∀ i k, |X i k| ≤ (A : ℤ)) (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hbits : bits = true → (∀ i k, 0 ≤ X i k) ∧ (∀ k j, 0 ≤ Y k j)) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) : + ∃ y, ExecWithin (w + 2) (program bits) x y (timeBound hm qs) ∧ + Parameters L m N qs.length y ∧ + (∀ i : Fin qs.length, y.mem (outputBase L m N qs.length + i.val) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ a, ¬(inputEnd m N qs.length ≤ a ∧ a < memoryEnd L m N qs.length) → + y.mem a = x.mem a) := by + have h10 := Nat.zero_le (10 ^ L) + have hI : 5 + 2 * (N * D m) + 2 * qs.length = inputEnd m N qs.length := rfl + have hS := producers_le_snapshot L m N qs.length + have hOff : snapshotBase L m N qs.length + inputEnd m N qs.length = + offsetBase L m N qs.length := rfl + have hCode : offsetBase L m N qs.length + numBands L m N ^ 2 + 1 = + codeBase L m N qs.length := rfl + have hOrig : codeBase L m N qs.length + qs.length = originalBase L m N qs.length := rfl + have hGroup : originalBase L m N qs.length + qs.length = groupedBase L m N qs.length := rfl + have hOut : groupedBase L m N qs.length + qs.length = outputBase L m N qs.length := rfl + have hFree : outputBase L m N qs.length + qs.length = scratchBase L m N qs.length := rfl + have hStamp : scratchBase L m N qs.length + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L = + stampBase L m N qs.length := rfl + have hEnd : stampBase L m N qs.length + 10 ^ L = memoryEnd L m N qs.length := rfl + have hL : x.mem 1 = L := by + rw [hinput 1 (by decide) (by omega)] + simp [State.init, Data.init, input] + have hm' : x.mem 2 = m := by + rw [hinput 2 (by decide) (by omega)] + simp [State.init, Data.init, input] + have hN : x.mem 3 = N := by + rw [hinput 3 (by decide) (by omega)] + simp [State.init, Data.init, input] + have hQ : x.mem 4 = qs.length := by + rw [hinput 4 (by decide) (by omega)] + simp [State.init, Data.init, input] + have hqueries (i : ℕ) (hi : i < qs.length) : + x.mem (queryInputBase m N + 2 * i) = (Wanted.inputPairs qs i).1 ∧ + x.mem (queryInputBase m N + 2 * i + 1) = (Wanted.inputPairs qs i).2 := by + have hr := input_query_word bits L X Y qs ⟨i, hi⟩ 0 + have hc := input_query_word bits L X Y qs ⟨i, hi⟩ 1 + rw [hinput _ (by unfold queryInputBase; omega) (by + simpa only [Nat.add_zero] using query_address_lt hi (by decide : 0 < 2)), + hinput _ (by unfold queryInputBase; omega) (query_address_lt hi (by decide : 1 < 2))] + rw [Wanted.inputPairs_get qs ⟨i, hi⟩] + simpa only [Fin.val_zero, Fin.val_one, Nat.add_zero, ↓reduceIte, Fin.reduceEq] using + And.intro hr hc + obtain ⟨r, old, hr, rp, rg, rv, ro, rs, rf⟩ := + exec_prepareRouting hm qs x hL hm' hN hQ hqueries hfit + have rsource (j : ℕ) (hj : j < 2 * (N * D m)) : + r.mem (snapshotBase L m N qs.length + 5 + j) = + (State.init (input bits L X Y qs) : State 160).data.mem (5 + j) := by + rw [show snapshotBase L m N qs.length + 5 + j = + snapshotBase L m N qs.length + (5 + j) by omega, rs _ (by omega)] + exact hinput _ (by omega) (by omega) + obtain ⟨c, hc, cp, cm, cf⟩ := + exec_prepareInputMatrices hw bits X Y qs r rp rsource hX hY hbits hmag hfit + have cs (j : ℕ) (hj : j < inputEnd m N qs.length) : + c.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [cf _ (Or.inr (by omega))] + exact rs j hj + have cg : SavedGroups L m N qs.length old c.mem := + rg.reframe (fun a ha _ => cf a (Or.inr (by omega))) + obtain ⟨b, hb, bp, bl, br, bf⟩ := exec_bands hw hm X Y c cp + (by rw [cf 1 (Or.inl (by decide)), rf 1 (Or.inl (by decide))]; exact hL) + (by rw [cf 2 (Or.inl (by decide)), rf 2 (Or.inl (by decide))]; exact hm') + (by rw [cf 3 (Or.inl (by decide)), rf 3 (Or.inl (by decide))]; exact hN) + cm hX hY hmag hfit + have bg : SavedGroups L m N qs.length old b.mem := + cg.reframe (fun a ha _ => bf a (Or.inr (by omega))) + have be : EncodedBands hm X Y b.mem := ⟨bl, br⟩ + have bs (j : ℕ) (hj : j < inputEnd m N qs.length) : + b.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [bf _ (Or.inr (by omega))] + exact cs j hj + obtain ⟨d, hd, dp, de, di, dm, df⟩ := exec_initializeDirectory b bp hfit + have dg : SavedGroups L m N qs.length old d.mem := + bg.reframe (fun a _ ha => df a (Or.inl (by omega))) + have de' : EncodedBands hm X Y d.mem := be.reframe (fun a ha => df a (Or.inl (by omega))) + have ds (j : ℕ) (hj : j < inputEnd m N qs.length) : + d.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [df _ (Or.inl (by omega))] + exact bs j hj + obtain ⟨t, ht, tp, tg, _, tv, tf⟩ := exec_evaluateTiles hw hm X Y qs old d dp di de dg de' + (fun i => (rv i).1) ro dm hX hY hmag hfit + have ts (j : ℕ) (hj : j < inputEnd m N qs.length) : + t.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [tf _ (by omega) (by omega)] + exact ds j hj + obtain ⟨o, ho, op, ov, of⟩ := exec_reconstruct X Y qs old t tp tg (fun i => (rv i).2) tv hfit + have os (j : ℕ) (hj : j < inputEnd m N qs.length) : + o.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [of _ (Or.inl (by omega))] + exact ts j hj + obtain ⟨y, hy, yp, ym⟩ := exec_restoreInput o op hfit + refine ⟨y, ?_, yp, ?_, ?_⟩ + · apply (hr.seq (hc.seq (hb.seq (hd.seq (ht.seq (ho.seq hy)))))).mono + unfold timeBound + omega + · intro i + rw [ym, ArrayCopy.copyPrefix_outside (by omega)] + exact ov i + · intro a ha + by_cases hi : a < inputEnd m N qs.length + · rw [ym] + have hread := ArrayCopy.copyPrefix_read (mem := o.mem) + (src := snapshotBase L m N qs.length) (dst := 0) hi + simpa only [Nat.zero_add] using hread.trans (os a hi) + · have habove : memoryEnd L m N qs.length ≤ a := by omega + rw [ym, ArrayCopy.copyPrefix_outside (by omega), of _ (Or.inr (by omega)), + tf _ (by omega) (by omega), df _ (Or.inr habove), bf _ (Or.inr (by omega)), + cf _ (Or.inr (by omega)), rf _ (Or.inr (by omega))] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Flat.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Flat.lean new file mode 100644 index 0000000000..15c182148b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Flat.lean @@ -0,0 +1,83 @@ +/- +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.Product.Exec + +/-! +# Initialized flat native queried products + +## Main results + +* `QueriedProduct.runs`: actual `State.init` executions, exact original-order product values, + guaranteed input fit, complete frame and a charged halt tied to the same final memory. +* `QueriedProduct.bounded_reachable`: every initialized reachable prefix has fitting data words. +* `QueriedProduct.program_length_le`: one fixed finite 160-register program for each input mode. + +The code is independent of all dimensions, entry magnitudes, query counts and word widths. +These results are component results; they do not instantiate a complete Exact Triangle solver +or the ordinary-3SUM endpoint under #1394. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +set_option maxRecDepth 8192 in +-- This closed structural check traverses the two fixed native program trees and signed kernels. +/-- The full flattened instruction list, including halt, is fixed independently of the input. -/ +public theorem program_length_le (bits : Bool) : + (program bits |>.program output).length ≤ 32768 := by + cases bits <;> decide +kernel + +set_option maxRecDepth 8192 in +-- This is a finite syntactic check of the actual code, including each renamed producer/kernel. +/-- Neither mode reads the physical width; the same fixed code runs at every admitted width. -/ +public theorem not_readsWidth (bits : Bool) : ¬(program bits).ReadsWidth := by + cases bits <;> decide +kernel + +/-- Original matrices and coordinates initialize the real native run. The charged prefix +reaches the same memory that contains all product values, and its next instruction halts. -/ +public theorem runs {w L m N A : ℕ} (hw : 0 < w) (hm : m ≤ L) (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (hX : ∀ i k, |X i k| ≤ (A : ℤ)) + (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hbits : bits = true → (∀ i k, 0 ≤ X i k) ∧ (∀ k j, 0 ≤ Y k j)) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) : + InputFits (w + 2) (input bits L X Y qs) ∧ + ∃ y n, n ≤ timeBound hm qs ∧ + Runs (w + 2) ((program bits).program output) (State.init (input bits L X Y qs)) + ⟨(program bits).size, y⟩ n ∧ + Executes (w + 2) ((program bits).program output) (State.init (input bits L X Y qs)) + (outputBase L m N qs.length) (n + 1) ∧ y.Bounded (w + 2) ∧ + (∀ i : Fin qs.length, y.mem (outputBase L m N qs.length + i.val) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ a, ¬(inputEnd m N qs.length ≤ a ∧ a < memoryEnd L m N qs.length) → + y.mem a = (State.init (input bits L X Y qs) : State 160).data.mem a) := by + have hi := input_fits hw hm bits X Y qs hX hY hmag hfit + obtain ⟨y, he, hp, hv, hf⟩ := exec hw hm bits X Y qs + (State.init (input bits L X Y qs)).data (by intro _ _ _; rfl) hX hY hbits hmag hfit + have hy := he.bounded (Data.Bounded.init hi) + obtain ⟨n, hn, he⟩ := he + refine ⟨hi, y, n, hn, ?_, ?_, hy, hv, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := (program bits).program output) (Program.Contains.self _).append_left + · simpa only [hp.output, State.init] using he.executes output + +/-- Every reachable initialized prefix is bounded at the same physical width. -/ +public theorem bounded_reachable {w L m N A n : ℕ} (hw : 0 < w) (hm : m ≤ L) (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (hX : ∀ i k, |X i k| ≤ (A : ℤ)) + (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) {y : State 160} + (hy : Runs (w + 2) ((program bits).program output) (State.init (input bits L X Y qs)) y n) : + y.data.Bounded (w + 2) := hy.bounded_init (input_fits hw hm bits X Y qs hX hY hmag hfit) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Groups.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Groups.lean new file mode 100644 index 0000000000..a06d8cd4ef --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Groups.lean @@ -0,0 +1,237 @@ +/- +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.Product.Transfer +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Memory + +/-! +# Saving actual routing arrays across producer calls + +## Main results + +* `QueriedProduct.exec_saveOffsets`: save all computed offsets, including the terminal word. +* `QueriedProduct.exec_saveCodes`: save the actual grouped query codes. +* `QueriedProduct.exec_saveOriginals`: save the actual original-position permutation. + +Each internal interface proves its charged copy execution. The protected outputs are copied +from actual router memory, without constructing or supplying a routing certificate. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Every low routing source precedes the protected destinations. -/ +public theorem group_copy_bounds (L m N Q : ℕ) : + Wanted.offsetAddress L m N Q + numBands L m N ^ 2 + 1 ≤ Wanted.memoryLimit L m N Q ∧ + Wanted.codeAddress L m N Q + Q ≤ Wanted.memoryLimit L m N Q ∧ + Wanted.originalAddress L m N Q + Q ≤ Wanted.memoryLimit L m N Q ∧ + Wanted.memoryLimit L m N Q ≤ offsetBase L m N Q ∧ + offsetBase L m N Q + numBands L m N ^ 2 + 1 ≤ memoryEnd L m N Q ∧ + codeBase L m N Q + Q ≤ memoryEnd L m N Q ∧ + originalBase L m N Q + Q ≤ memoryEnd L m N Q := by + have h10 := Nat.zero_le (10 ^ L) + simp only [Wanted.memoryLimit, Wanted.originalAddress, Wanted.codeAddress, + Wanted.cursorAddressBase, + offsetBase, snapshotBase, memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase] + omega + +/-- Actual argument setup and native copying preserve the caller protocol and every other +register beyond the copy routine. This is an internal producer-consumer execution interface. -/ +public theorem exec_saveOffsets {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) + (hsrc : x.regs (bank Wanted.offsets) = Wanted.offsetAddress L m N Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w saveOffsets x y (7 * numBands L m N ^ 2 + 17) ∧ Parameters L m N Q y ∧ + y.mem = ArrayCopy.copyPrefix x.mem (Wanted.offsetAddress L m N Q) + (offsetBase L m N Q) (numBands L m N ^ 2 + 1) ∧ + (∀ i, 7 ≤ i.val → i ≠ source → i ≠ destination → i ≠ length → y.regs i = x.regs i) := by + have hb := group_copy_bounds L m N Q + have hdest : offsetBase L m N Q + (numBands L m N ^ 2 + 1) ≤ memoryEnd L m N Q := by + exact hb.2.2.2.2.1 + have hsource : Wanted.offsetAddress L m N Q + (numBands L m N ^ 2 + 1) ≤ Wanted.memoryLimit L m + N Q := + hb.1 + have hsep : Wanted.offsetAddress L m N Q + (numBands L m N ^ 2 + 1) ≤ offsetBase L m N Q := by + have horder : offsetBase L m N Q ≤ offsetBase L m N Q := by + exact le_rfl + omega + have hn : Fits w (numBands L m N ^ 2 + 1) := allocated_fits hfit (by omega) + have hs : Fits w (Wanted.offsetAddress L m N Q + (numBands L m N ^ 2 + 1)) := allocated_fits + hfit (by omega) + have hd : Fits w (offsetBase L m N Q + (numBands L m N ^ 2 + 1)) := allocated_fits hfit (by omega) + let z := ((x.set source (Wanted.offsetAddress L m N Q)).set destination + (offsetBase L m N Q)).set length (numBands L m N ^ 2 + 1) + have hz : ExecWithin (B := .standard) w (.seq (.op (.copy source (bank Wanted.offsets))) (ram[ + destination ← offsets; length ← tiles + one ])) x z 3 := by + ram_eval [z, hsrc, hp.offsets, hp.tiles, hp.one, hn, tiles, one, source, destination, length, + offsets] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) _).set_work destination (by decide) _).set_work + length (by decide) _ + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length] using hs) + (by simpa [z, source, destination, length] using hd) + (Or.inl (by simpa [z, source, destination, length] using hsep)) + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_, ?_⟩ + · have hlen : z.regs length = (numBands L m N ^ 2 + 1) := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · simpa [z, source, destination, length] using hm + · intro i hi hs hd hl + rw [hf i hi] + simp only [z, Data.set_regs, Function.update_of_ne hl, Function.update_of_ne hd, + Function.update_of_ne hs] + +/-- Actual argument setup and native copying preserve the caller protocol and every other +register beyond the copy routine. This is an internal producer-consumer execution interface. -/ +public theorem exec_saveCodes {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) + (hsrc : x.regs (bank Wanted.codes) = Wanted.codeAddress L m N Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w saveCodes x y (7 * Q + 10) ∧ Parameters L m N Q y ∧ + y.mem = ArrayCopy.copyPrefix x.mem (Wanted.codeAddress L m N Q) + (codeBase L m N Q) (Q) ∧ + (∀ i, 7 ≤ i.val → i ≠ source → i ≠ destination → i ≠ length → y.regs i = x.regs i) := by + have hb := group_copy_bounds L m N Q + have hdest : codeBase L m N Q + (Q) ≤ memoryEnd L m N Q := by + exact hb.2.2.2.2.2.1 + have hsource : Wanted.codeAddress L m N Q + (Q) ≤ Wanted.memoryLimit L m N Q := + hb.2.1 + have hsep : Wanted.codeAddress L m N Q + (Q) ≤ codeBase L m N Q := by + have horder : offsetBase L m N Q ≤ codeBase L m N Q := by + unfold codeBase + omega + omega + have hn : Fits w (Q) := allocated_fits hfit (by omega) + have hs : Fits w (Wanted.codeAddress L m N Q + (Q)) := allocated_fits hfit (by omega) + have hd : Fits w (codeBase L m N Q + (Q)) := allocated_fits hfit (by omega) + let z := ((x.set source (Wanted.codeAddress L m N Q)).set destination + (codeBase L m N Q)).set length (Q) + have hz : ExecWithin (B := .standard) w (.seq (.op (.copy source (bank Wanted.codes))) (ram[ + destination ← codes; length ← queryCount ])) x z 3 := by + ram_eval [z, hsrc, hp.codes, hp.queryCount, queryCount, source, destination, length, codes] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) _).set_work destination (by decide) _).set_work + length (by decide) _ + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length] using hs) + (by simpa [z, source, destination, length] using hd) + (Or.inl (by simpa [z, source, destination, length] using hsep)) + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_, ?_⟩ + · have hlen : z.regs length = (Q) := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · simpa [z, source, destination, length] using hm + · intro i hi hs hd hl + rw [hf i hi] + simp only [z, Data.set_regs, Function.update_of_ne hl, Function.update_of_ne hd, + Function.update_of_ne hs] + +/-- Actual argument setup and native copying preserve the caller protocol and every other +register beyond the copy routine. This is an internal producer-consumer execution interface. -/ +public theorem exec_saveOriginals {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) + (hsrc : x.regs (bank Wanted.originals) = Wanted.originalAddress L m N Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w saveOriginals x y (7 * Q + 10) ∧ Parameters L m N Q y ∧ + y.mem = ArrayCopy.copyPrefix x.mem (Wanted.originalAddress L m N Q) + (originalBase L m N Q) (Q) ∧ + (∀ i, 7 ≤ i.val → i ≠ source → i ≠ destination → i ≠ length → y.regs i = x.regs i) := by + have hb := group_copy_bounds L m N Q + have hdest : originalBase L m N Q + (Q) ≤ memoryEnd L m N Q := by + exact hb.2.2.2.2.2.2 + have hsource : Wanted.originalAddress L m N Q + (Q) ≤ Wanted.memoryLimit L m N Q := + hb.2.2.1 + have hsep : Wanted.originalAddress L m N Q + (Q) ≤ originalBase L m N Q := by + have horder : offsetBase L m N Q ≤ originalBase L m N Q := by + unfold originalBase codeBase + omega + omega + have hn : Fits w (Q) := allocated_fits hfit (by omega) + have hs : Fits w (Wanted.originalAddress L m N Q + (Q)) := allocated_fits hfit (by omega) + have hd : Fits w (originalBase L m N Q + (Q)) := allocated_fits hfit (by omega) + let z := ((x.set source (Wanted.originalAddress L m N Q)).set destination + (originalBase L m N Q)).set length (Q) + have hz : ExecWithin (B := .standard) w (.seq (.op (.copy source (bank Wanted.originals))) (ram[ + destination ← originals; length ← queryCount ])) x z 3 := by + ram_eval [z, hsrc, hp.originals, hp.queryCount, queryCount, source, destination, length, + originals] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) _).set_work destination (by decide) _).set_work + length (by decide) _ + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length] using hs) + (by simpa [z, source, destination, length] using hd) + (Or.inl (by simpa [z, source, destination, length] using hsep)) + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_, ?_⟩ + · have hlen : z.regs length = (Q) := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · simpa [z, source, destination, length] using hm + · intro i hi hs hd hl + rw [hf i hi] + simp only [z, Data.set_regs, Function.update_of_ne hl, Function.update_of_ne hd, + Function.update_of_ne hs] + +/-- Save all three actual routing arrays before the second producer reuses low memory. +Every protected value is read from the router's executed memory, and the complete outside +frame includes the protected original input and all untouched low cells. -/ +public theorem exec_saveGroups {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (wp : Wanted.Parameters L m N Q (x.comap bank)) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w saveGroups x y (7 * numBands L m N ^ 2 + 14 * Q + 37) ∧ + Parameters L m N Q y ∧ + (∀ t ≤ numBands L m N ^ 2, + y.mem (offsetBase L m N Q + t) = x.mem (Wanted.offsetAddress L m N Q + t)) ∧ + (∀ j < Q, y.mem (codeBase L m N Q + j) = x.mem (Wanted.codeAddress L m N Q + j)) ∧ + (∀ j < Q, y.mem (originalBase L m N Q + j) = + x.mem (Wanted.originalAddress L m N Q + j)) ∧ + (∀ a, a < offsetBase L m N Q ∨ originalBase L m N Q + Q ≤ a → y.mem a = x.mem a) := by + obtain ⟨a, ha, ap, am, af⟩ := exec_saveOffsets x hp wp.offsets hfit + have ac : a.regs (bank Wanted.codes) = Wanted.codeAddress L m N Q := + (af _ (by decide) (by decide) (by decide) (by decide)).trans wp.codes + obtain ⟨b, hb, bp, bm, bf⟩ := exec_saveCodes a ap ac hfit + have bo : b.regs (bank Wanted.originals) = Wanted.originalAddress L m N Q := + (bf _ (by decide) (by decide) (by decide) (by decide)).trans + ((af _ (by decide) (by decide) (by decide) (by decide)).trans wp.originals) + obtain ⟨y, hy, yp, ym, _⟩ := exec_saveOriginals b bp bo hfit + have hbounds := group_copy_bounds L m N Q + have horder : offsetBase L m N Q ≤ codeBase L m N Q ∧ + codeBase L m N Q ≤ originalBase L m N Q := by + simp only [codeBase, originalBase] + omega + refine ⟨y, (ha.seq (hb.seq hy)).mono (by omega), yp, ?_, ?_, ?_, ?_⟩ + · intro t ht + have hbefore : offsetBase L m N Q + t < codeBase L m N Q := by unfold codeBase; omega + rw [ym, ArrayCopy.copyPrefix_outside (by omega), bm, + ArrayCopy.copyPrefix_outside (by omega), am] + exact ArrayCopy.copyPrefix_read (by omega) + · intro j hj + have hbefore : codeBase L m N Q + j < originalBase L m N Q := by + unfold originalBase + omega + have hlow : Wanted.codeAddress L m N Q + j < offsetBase L m N Q := by omega + rw [ym, ArrayCopy.copyPrefix_outside (by omega), bm, ArrayCopy.copyPrefix_read hj, + am, ArrayCopy.copyPrefix_outside (by omega)] + · intro j hj + have hlow : Wanted.originalAddress L m N Q + j < offsetBase L m N Q := by omega + rw [ym, ArrayCopy.copyPrefix_read hj, bm, ArrayCopy.copyPrefix_outside (by omega), + am, ArrayCopy.copyPrefix_outside (by omega)] + · intro p hp + have hoff : offsetBase L m N Q + (numBands L m N ^ 2 + 1) = codeBase L m N Q := by + unfold codeBase + omega + have hcode : codeBase L m N Q + Q = originalBase L m N Q := rfl + rw [ym, ArrayCopy.copyPrefix_outside (by omega), bm, + ArrayCopy.copyPrefix_outside (by omega), am, ArrayCopy.copyPrefix_outside (by omega)] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Header.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Header.lean new file mode 100644 index 0000000000..304ea325e3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Header.lean @@ -0,0 +1,500 @@ +/- +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.Product.Memory +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Setup + +/-! +# Charged protected-buffer calculation + +## Main results + +* `QueriedProduct.exec_layout`: calculate every persistent scalar from the actual producer + header and original request count, with all intermediate fits derived from original inputs. + +This is an internal execution API. It receives ordinary header equalities and a numerical +input bound, not a supplied layout or intermediate-state certificate. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +-- Name the fifty-update state once; separating execution from admission keeps each proof +-- within the default heartbeat limit without changing the actual header code. +private def layoutState (x : Data 160) (L m N Q : ℕ) : Data 160 := + ((((((((((((((((((((((((((((((((((((((((((((((((((x.set depth (L)).set inner (m)).set + rows (N)).set address (4)).set queryCount (Q)).set columns (D m)).set masks (2 ^ L)).set + cells (7 ^ L)).set leaves (10 ^ L)).set bands (numBands L m N)).set catalogue + (BandPreparation.catalogueBase L m N)).set zero (0)).set one (1)).set two (2)).set three + (3)).set four (4)).set five (5)).set six (6)).set tiles ((numBands L m N) * (numBands L m + N))).set matrixWords ((N) * (D m))).set matrixWords (((N) * (D m)) * (2))).set queryOffset + ((((N) * (D m)) * (2)) + (5))).set inputSize ((Q) * (2))).set inputSize (((Q) * (2)) + + ((((N) * (D m)) * (2)) + (5)))).set bandEnd ((numBands L m N) * (10 ^ L))).set bandEnd + (((numBands L m N) * (10 ^ L)) * (2))).set bandEnd ((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N))).set wantedEnd ((Q) * (4))).set wantedEnd (((Q) * + (4)) + (6))).set temporary ((2 ^ L) * (2))).set wantedEnd ((((Q) * (4)) + (6)) + ((2 ^ L) * + (2)))).set temporary (((numBands L m N) * (numBands L m N)) * (3))).set wantedEnd (((((Q) * + (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * (3)))).set + snapshot (((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m N)) + + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3))))).set snapshot ((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5))))).set offsets (((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5))))).set codes ((((((((numBands L m N) * + (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) + * (2))) + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * + (numBands L m N)))).set codes (((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1))).set originals ((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q))).set grouped (((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q))).set output ((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q))).set scratch (((((((((((((numBands L m N) * (10 ^ L)) * + (2)) + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q))).set temporary ((L) + (1))).set temporary (((L) + + (1)) * (4))).set stamps ((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4)))).set temporary ((L) + + (1))).set temporary (((L) + (1)) * (10 ^ L))).set temporary ((((L) + (1)) * (10 ^ L)) * + (2))).set stamps (((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) + ((((L) + (1)) * (10 ^ L)) * + (2)))).set memoryLimit ((((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) + ((((L) + (1)) * (10 ^ L)) * + (2))) + (10 ^ L))) + +private theorem exec_layout_raw {w L m N Q : ℕ} (x : Data 160) + (hp : BandPreparation.BandParameters L m N (x.comap bank)) (hQ : x.mem 4 = Q) + (hf0 : Fits w (4)) + (hf1 : Fits w (0)) + (hf2 : Fits w (1)) + (hf3 : Fits w (2)) + (hf4 : Fits w (3)) + (hf5 : Fits w (5)) + (hf6 : Fits w (6)) + (hf7 : Fits w ((numBands L m N) * (numBands L m N))) + (hf8 : Fits w ((N) * (D m))) + (hf9 : Fits w (((N) * (D m)) * (2))) + (hf10 : Fits w ((((N) * (D m)) * (2)) + (5))) + (hf11 : Fits w ((Q) * (2))) + (hf12 : Fits w (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (hf13 : Fits w ((numBands L m N) * (10 ^ L))) + (hf14 : Fits w (((numBands L m N) * (10 ^ L)) * (2))) + (hf15 : Fits w ((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m + N))) + (hf16 : Fits w ((Q) * (4))) + (hf17 : Fits w (((Q) * (4)) + (6))) + (hf18 : Fits w ((2 ^ L) * (2))) + (hf19 : Fits w ((((Q) * (4)) + (6)) + ((2 ^ L) * (2)))) + (hf20 : Fits w (((numBands L m N) * (numBands L m N)) * (3))) + (hf21 : Fits w (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m + N)) * (3)))) + (hf22 : Fits w (((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m + N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3))))) + (hf23 : Fits w ((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m + N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5))))) + (hf24 : Fits w (((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L + m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5))))) + (hf25 : Fits w ((((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L + m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + ((numBands L m N) * (numBands L m N)))) + (hf26 : Fits w (((((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase + L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) + * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + ((numBands L m N) * (numBands L m N))) + (1))) + (hf27 : Fits w ((((((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase + L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) + * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + ((numBands L m N) * (numBands L m N))) + (1)) + (Q))) + (hf28 : Fits w (((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q))) + (hf29 : Fits w ((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q))) + (hf30 : Fits w (((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q))) + (hf31 : Fits w ((L) + (1))) + (hf32 : Fits w (((L) + (1)) * (4))) + (hf33 : Fits w ((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4)))) + (hf34 : Fits w (((L) + (1)) * (10 ^ L))) + (hf35 : Fits w ((((L) + (1)) * (10 ^ L)) * (2))) + (hf36 : Fits w (((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) + ((((L) + (1)) * (10 ^ L)) * + (2)))) + (hf37 : Fits w ((((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) + ((((L) + (1)) * (10 ^ L)) * + (2))) + (10 ^ L))) : + ExecWithin w layout x (layoutState x L m N Q) 50 := by + have hb0 : x.regs (bank BandPreparation.levels) = L := hp.fixed.levels + have hb1 : x.regs (bank BandPreparation.inner) = m := hp.fixed.inner + have hb2 : x.regs (bank BandPreparation.rows) = N := hp.fixed.rows + have hb3 : x.regs (bank BandPreparation.columns) = D m := hp.columns + have hb4 : x.regs (bank BandPreparation.masks) = 2 ^ L := hp.masks + have hb5 : x.regs (bank BandPreparation.cells) = 7 ^ L := hp.cells + have hb6 : x.regs (bank BandPreparation.leaves) = 10 ^ L := hp.leaves + have hb7 : x.regs (bank BandPreparation.bands) = numBands L m N := hp.bands + have hb8 : x.regs (bank BandPreparation.catalogue) = BandPreparation.catalogueBase L m N := + hp.catalogue + ram_eval [layout, layoutState, hQ, hf0, hf1, hf2, hf3, hf4, hf5, hf6, hf7, hf8, hf9, hf10, hf11, + hf12, + hf13, hf14, hf15, hf16, hf17, hf18, hf19, hf20, hf21, hf22, hf23, hf24, hf25, hf26, hf27, + hf28, hf29, hf30, hf31, hf32, hf33, hf34, hf35, hf36, hf37, depth, inner, rows, address, + queryCount, columns, masks, cells, leaves, bands, catalogue, zero, one, two, three, four, + five, six, tiles, matrixWords, queryOffset, inputSize, bandEnd, wantedEnd, temporary, + snapshot, offsets, codes, originals, grouped, output, scratch, stamps, memoryLimit, hb0, + hb1, hb2, hb3, hb4, hb5, hb6, hb7, hb8] + +/-- Actual native layout calculation, preserving all original memory. -/ +public theorem exec_layout {w L m N Q : ℕ} (x : Data 160) + (hp : BandPreparation.BandParameters L m N (x.comap bank)) (hQ : x.mem 4 = Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w layout x y 50 ∧ Parameters L m N Q y ∧ y.mem = x.mem := by + have hl := caller_fits hfit + have h2 : 2 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have hsmall (v : ℕ) (hv : v ≤ 16 * (L + N + Q + memoryEnd L m N Q + 16)) : + Fits w v := hl.of_le hv + have hf0 : Fits w (4) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf1 : Fits w (0) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf2 : Fits w (1) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf3 : Fits w (2) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf4 : Fits w (3) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf5 : Fits w (5) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf6 : Fits w (6) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf7 : Fits w ((numBands L m N) * (numBands L m N)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf8 : Fits w ((N) * (D m)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf9 : Fits w (((N) * (D m)) * (2)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf10 : Fits w ((((N) * (D m)) * (2)) + (5)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf11 : Fits w ((Q) * (2)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf12 : Fits w (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf13 : Fits w ((numBands L m N) * (10 ^ L)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf14 : Fits w (((numBands L m N) * (10 ^ L)) * (2)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf15 : Fits w ((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m + N)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf16 : Fits w ((Q) * (4)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf17 : Fits w (((Q) * (4)) + (6)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf18 : Fits w ((2 ^ L) * (2)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf19 : Fits w ((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf20 : Fits w (((numBands L m N) * (numBands L m N)) * (3)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf21 : Fits w (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m + N)) * (3))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf22 : Fits w (((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m + N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf23 : Fits w ((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L m + N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf24 : Fits w (((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L + m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf25 : Fits w ((((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase L + m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) * + (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + ((numBands L m N) * (numBands L m N))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf26 : Fits w (((((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase + L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) + * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + ((numBands L m N) * (numBands L m N))) + (1)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf27 : Fits w ((((((((((numBands L m N) * (10 ^ L)) * (2)) + (BandPreparation.catalogueBase + L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + (((numBands L m N) * (numBands L m N)) + * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * + (2)) + (5)))) + ((numBands L m N) * (numBands L m N))) + (1)) + (Q)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf28 : Fits w (((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf29 : Fits w ((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf30 : Fits w (((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf31 : Fits w ((L) + (1)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf32 : Fits w (((L) + (1)) * (4)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf33 : Fits w ((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf34 : Fits w (((L) + (1)) * (10 ^ L)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf35 : Fits w ((((L) + (1)) * (10 ^ L)) * (2)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf36 : Fits w (((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) + ((((L) + (1)) * (10 ^ L)) * + (2))) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + have hf37 : Fits w ((((((((((((((((numBands L m N) * (10 ^ L)) * (2)) + + (BandPreparation.catalogueBase L m N)) + (((((Q) * (4)) + (6)) + ((2 ^ L) * (2))) + + (((numBands L m N) * (numBands L m N)) * (3)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + + (5)))) + (((Q) * (2)) + ((((N) * (D m)) * (2)) + (5)))) + ((numBands L m N) * (numBands L m + N))) + (1)) + (Q)) + (Q)) + (Q)) + (Q)) + (((L) + (1)) * (4))) + ((((L) + (1)) * (10 ^ L)) * + (2))) + (10 ^ L)) := hsmall _ (by + simp only [memoryEnd, stampBase, scratchBase, outputBase, groupedBase, + originalBase, codeBase, offsetBase, snapshotBase, inputEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq] + ring_nf + omega) + let y := layoutState x L m N Q + have he : ExecWithin w layout x y 50 := + exec_layout_raw x hp hQ hf0 hf1 hf2 hf3 hf4 hf5 hf6 hf7 hf8 hf9 hf10 hf11 hf12 hf13 hf14 hf15 + hf16 hf17 hf18 hf19 hf20 hf21 hf22 hf23 hf24 hf25 hf26 hf27 hf28 hf29 hf30 hf31 hf32 hf33 + hf34 hf35 hf36 hf37 + refine ⟨y, he, ?_, rfl⟩ + constructor <;> + simp +decide only [y, layoutState, Data.set_regs, Function.update_apply, depth, inner, rows, + address, + queryCount, columns, masks, cells, leaves, bands, catalogue, zero, one, two, three, four, + five, six, tiles, matrixWords, queryOffset, inputSize, bandEnd, wantedEnd, temporary, + snapshot, offsets, codes, originals, grouped, output, scratch, stamps, memoryLimit, + ↓reduceIte] <;> + (try simp only [inputEnd, queryInputBase, snapshotBase, offsetBase, codeBase, + originalBase, groupedBase, outputBase, scratchBase, stampBase, memoryEnd, + BandPreparation.memoryEnd, Wanted.memoryLimit_eq, pow_two]) <;> ring + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Input.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Input.lean new file mode 100644 index 0000000000..cb840da6ef --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Input.lean @@ -0,0 +1,233 @@ +/- +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.Product.Program +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Input +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandInput +public import Complexity.Foundations.Data.List.RowMajor +import Mathlib.Data.List.GetD +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Transfer + +/-! +# Original matrix and coordinate inputs for queried products + +## Main definitions + +* `QueriedProduct.entryWord`: canonical signed words or original raw nonnegative words. +* `QueriedProduct.input`: four scalar headers, both row-major matrices and raw coordinates. + +The raw-input program performs actual doubling before calling the signed producer. +In particular, raw 0/1 entries become canonical signed 0/2 words. This input definition +neither supplies encodings nor performs that target conversion on the caller's behalf. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Ordinary source words. In raw mode only nonnegative source values are represented; +the native matrix-copy loop performs the subsequent signed-encoding conversion. -/ +@[expose] public def entryWord (bits : Bool) (z : ℤ) : ℕ := + if bits then z.toNat else Signed.encode z + +/-- Actual doubling converts every nonnegative raw entry into its canonical signed word. +In particular, the raw unsigned zero/one producer convention becomes signed zero/two. -/ +public theorem convert_entryWord (bits : Bool) (z : ℤ) (hz : bits = true → 0 ≤ z) : + (if bits then 2 * entryWord bits z else entryWord bits z) = Signed.encode z := by + cases bits + · rfl + · have hnonneg := hz rfl + cases z with + | ofNat n => simp [entryWord, Signed.encode] + | negSucc n => omega + +/-- Both original matrices in their ordinary row-major input order. -/ +@[expose] public def matrixInput {m N : ℕ} (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) : List ℕ := + (List.finRange N).flatMap (fun i => List.ofFn fun k => entryWord bits (X i k)) ++ + (List.finRange (D m)).flatMap (fun k => List.ofFn fun j => entryWord bits (Y k j)) + +/-- Actual initialized input, using exactly the existing router's raw coordinate convention. -/ +@[expose] public def input {m N : ℕ} (bits : Bool) (L : ℕ) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) : List ℕ := + [L, m, N, qs.length] ++ matrixInput bits X Y ++ (Wanted.input L m qs).drop 4 + +/-- The matrix block contains exactly both original input arrays. -/ +public theorem matrixInput_length {m N : ℕ} (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) : + (matrixInput bits X Y).length = 2 * (N * D m) := by + simp only [matrixInput, List.flatMap_ofFn_prod, List.length_append, List.length_ofFn] + ring + +/-- Every original scalar, matrix entry and coordinate lies within the snapshot prefix. -/ +public theorem input_length {m N : ℕ} (bits : Bool) (L : ℕ) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) : + (input bits L X Y qs).length + 1 = inputEnd m N qs.length := by + have hq := Wanted.input_length L m qs + simp only [input, List.length_append, matrixInput_length, List.length_cons, + List.length_nil, List.length_drop, Wanted.inputLimit, inputEnd] at * + omega + +/-- The complete initialized input retains the router's raw coordinate values; the matrices +only shift their original address. No query code or tile is supplied in these words. -/ +public theorem input_query_word {m N : ℕ} (bits : Bool) (L : ℕ) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (i : Fin qs.length) (s : Fin 2) : + (State.init (input bits L X Y qs) : State 160).data.mem + (queryInputBase m N + 2 * i.val + s.val) = + if s = 0 then (qs.get i).1.val else (qs.get i).2.val := by + have hw := Wanted.input_word L m qs i s + change (Data.init (Wanted.input L m qs) : Data 96).mem _ = _ at hw + rw [show 5 + 2 * i.val + s.val = (4 + 2 * i.val + s.val) + 1 by omega, + Data.init_mem_succ] at hw + change (Data.init (input bits L X Y qs) : Data 160).mem _ = _ + rw [show queryInputBase m N + 2 * i.val + s.val = + (4 + 2 * (N * D m) + 2 * i.val + s.val) + 1 by unfold queryInputBase; omega, + Data.init_mem_succ] + unfold input + rw [List.getD_append_right _ _ _ _ (by + simp only [List.length_append, matrixInput_length, List.length_cons, List.length_nil] + omega)] + simp only [List.length_append, matrixInput_length, List.length_cons, List.length_nil] + rw [show 4 + 2 * (N * D m) + 2 * i.val + s.val - + (1 + (1 + (1 + (1 + 0))) + 2 * (N * D m)) = 2 * i.val + s.val by omega] + simp only [List.getD_eq_getElem?_getD, List.getElem?_drop] at hw ⊢ + simpa only [Nat.add_assoc] using hw + +/-- Both original row-major matrix blocks contain precisely their supplied entry words, +before any copying, sign conversion or encoding takes place. -/ +public theorem input_matrix_word {L m N : ℕ} (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (side : Bool) (r k : ℕ) (hr : r < N) (hk : k < D m) : + (State.init (input bits L X Y qs) : State 160).data.mem + (BandPreparation.matrixOffset m N side r k + 5) = + entryWord bits (BandPreparation.matrixValues X Y side r k) := by + let xs := (List.finRange N).flatMap (fun i => List.ofFn fun k => entryWord bits (X i k)) + let ys := (List.finRange (D m)).flatMap (fun k => List.ofFn fun j => entryWord bits (Y k j)) + have hxs : xs.length = N * D m := by + simp only [xs, List.flatMap_ofFn_prod, List.length_ofFn] + have hys : ys.length = D m * N := by + simp only [ys, List.flatMap_ofFn_prod, List.length_ofFn] + have hoff : BandPreparation.matrixOffset m N side r k < (xs ++ ys).length := by + simp only [List.length_append, hxs, hys] + cases side + · have h := (finProdFinEquiv (⟨r, hr⟩, ⟨k, hk⟩)).isLt + simp only [finProdFinEquiv, Equiv.coe_fn_mk] at h + simp only [BandPreparation.matrixOffset, Bool.false_eq_true, ↓reduceIte] + nlinarith only [h, Nat.zero_le (N * D m)] + · have h := (finProdFinEquiv (⟨k, hk⟩, ⟨r, hr⟩)).isLt + simp only [finProdFinEquiv, Equiv.coe_fn_mk] at h + simp only [BandPreparation.matrixOffset, ↓reduceIte] + nlinarith only [h] + change (Data.init (input bits L X Y qs) : Data 160).mem _ = _ + rw [show BandPreparation.matrixOffset m N side r k + 5 = + (BandPreparation.matrixOffset m N side r k + 4) + 1 by omega, Data.init_mem_succ] + change (([L, m, N, qs.length] ++ (xs ++ ys) ++ (Wanted.input L m qs).drop 4).getD + (BandPreparation.matrixOffset m N side r k + 4) 0) = _ + rw [List.getD_append _ _ _ _ (by + simp only [List.length_append, List.length_cons, List.length_nil, hxs, hys] at hoff ⊢ + omega)] + rw [List.getD_append_right _ _ _ _ (by simp)] + simp only [List.length_cons, List.length_nil, Nat.reduceAdd, Nat.add_sub_cancel_right] + 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 [BandPreparation.matrixOffset, Bool.false_eq_true, ↓reduceIte] + rw [List.getD_append _ _ _ _ (by simpa only [hxs] using hi)] + simpa only [xs, BandPreparation.matrixValues, dite_eq_left hr, dite_eq_left hk, + Bool.false_eq_true, ↓reduceIte] using + List.getD_flatMap_ofFn (fun i k => entryWord bits (X i k)) 0 ⟨r, hr⟩ ⟨k, hk⟩ + · simp only [BandPreparation.matrixOffset, ↓reduceIte] + rw [List.getD_append_right _ _ _ _ (by rw [hxs]; omega), hxs, + Nat.add_sub_cancel_right] + simpa only [ys, BandPreparation.matrixValues, dite_eq_left hr, dite_eq_left hk, + ↓reduceIte] using + List.getD_flatMap_ofFn (fun k j => entryWord bits (Y k j)) 0 ⟨k, hk⟩ ⟨r, hr⟩ + +/-- The original magnitude envelope covers both canonical signed and raw unsigned words. +Raw-word fit does not itself assert nonnegativity of the represented source entries; that +extra semantic premise is used by the actual conversion proof. -/ +public theorem entryWord_fits {w L A : ℕ} (hw : 0 < w) (bits : Bool) (z : ℤ) + (hz : |z| ≤ (A : ℤ)) (hmag : magnitudeBound L A < 2 ^ (w - 1)) : + Fits (w + 2) (entryWord bits z) := by + have hA : A < 2 ^ (w - 1) := by + unfold magnitudeBound at hmag + omega + have hAi : (A : ℤ) < (2 : ℤ) ^ (w - 1) := by exact_mod_cast hA + have hzf : GenericRAM.Signed.Fits w z := + ⟨hw, (abs_lt.mp (hz.trans_lt hAi)).1.le, (abs_lt.mp (hz.trans_lt hAi)).2⟩ + cases bits + · exact (Signed.encode_fits hzf).mono (by omega) + · have hn : z.toNat ≤ A := Int.toNat_le.mpr ((le_abs_self z).trans hz) + exact ((show Fits (w - 1) A from hA).mono (show w - 1 ≤ w + 2 by omega)).of_le hn + +/-- Every actual initialized word fits from original dimensions and entry magnitudes. +No intermediate machine-state fit certificate is supplied. -/ +public theorem input_fits {w L m N A : ℕ} (hw : 0 < w) (hm : m ≤ L) (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (hX : ∀ i k, |X i k| ≤ (A : ℤ)) + (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) : + InputFits (w + 2) (input bits L X Y qs) := by + have hs := caller_fits hfit + have hb := transfer_bounds L m N qs.length + have hq := Wanted.input_fits hm qs (producer_fits hfit).2 + refine ⟨?_, ?_⟩ + · have hi := input_length bits L X Y qs + exact allocated_fits hfit (by omega) + · 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 | rfl | rfl) | hv) | hv + · exact hs.of_le (by omega) + · exact hs.of_le (by omega) + · exact hs.of_le (by omega) + · exact hs.of_le (by omega) + · simp only [matrixInput, List.mem_append] at hv + rcases hv with hv | hv + · obtain ⟨i, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨k, rfl⟩ := List.mem_ofFn.mp hv + exact entryWord_fits hw bits _ (hX i k) hmag + · obtain ⟨k, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨j, rfl⟩ := List.mem_ofFn.mp hv + exact entryWord_fits hw bits _ (hY k j) hmag + · exact hq.2 v (List.mem_of_mem_drop hv) + +/-- Original raw matrix words are bounded by the original absolute-entry bound, at every +flat position. This supplies the actual conversion loop without an intermediate fit premise. -/ +public theorem input_raw_matrix_le {L m N A : ℕ} + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (hX : ∀ i k, |X i k| ≤ (A : ℤ)) + (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) (j : ℕ) (hj : j < 2 * (N * D m)) : + (State.init (input true L X Y qs) : State 160).data.mem (5 + j) ≤ A := by + have hj' : j < (matrixInput true X Y).length := by rw [matrixInput_length]; exact hj + have hbound (v : ℕ) (hv : v ∈ matrixInput true X Y) : v ≤ A := by + simp only [matrixInput, List.mem_append] at hv + rcases hv with hv | hv + · obtain ⟨i, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨k, rfl⟩ := List.mem_ofFn.mp hv + exact Int.toNat_le.mpr ((le_abs_self _).trans (hX i k)) + · obtain ⟨k, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨i, rfl⟩ := List.mem_ofFn.mp hv + exact Int.toNat_le.mpr ((le_abs_self _).trans (hY k i)) + change (Data.init (input true L X Y qs) : Data 160).mem _ ≤ A + rw [show 5 + j = (4 + j) + 1 by omega, Data.init_mem_succ] + unfold input + rw [List.getD_append _ _ _ _ (by + simp only [List.length_append, List.length_cons, List.length_nil] + omega)] + rw [List.getD_append_right _ _ _ _ (by simp)] + simp only [List.length_cons, List.length_nil, Nat.reduceAdd, Nat.add_sub_cancel_left] + rw [List.getD_eq_get _ _ ⟨j, hj'⟩] + exact hbound _ (List.get_mem _ _) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Layout.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Layout.lean new file mode 100644 index 0000000000..ddc0617238 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Layout.lean @@ -0,0 +1,113 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandProgram +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Bounds + +/-! +# Protected buffers for native queried products + +## Main definitions + +* `QueriedProduct.inputEnd`: four original headers, both matrices and all coordinate pairs. +* `QueriedProduct.snapshotBase`: protected original input above both producers' work areas. +* `QueriedProduct.offsetBase`, `QueriedProduct.codeBase`, `QueriedProduct.originalBase`: + protected copies of the actual computed routing arrays. +* `QueriedProduct.outputBase`: answers in original request order. +* `QueriedProduct.scratchBase`: reusable pruned-kernel workspace after every protected array. + +These numeric specifications describe the addresses computed by the native header. +Neither a layout nor an execution/cost certificate is supplied to the program. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- One past the original initialized input, including the native length word. -/ +@[expose] public def inputEnd (m N Q : ℕ) : ℕ := 5 + 2 * (N * D m) + 2 * Q + +/-- Original coordinate pairs follow both row-major input matrices. -/ +@[expose] public def queryInputBase (m N : ℕ) : ℕ := 5 + 2 * (N * D m) + +/-- The sum reserves disjoint space above the input and both low producer allocations. +Using a sum permits direct arithmetic without a hidden maximum/layout operation. -/ +@[expose] public def snapshotBase (L m N Q : ℕ) : ℕ := + BandPreparation.memoryEnd L m N + Wanted.memoryLimit L m N Q + inputEnd m N Q + +/-- Actual prefix offsets are saved here before the band producer reuses low memory. -/ +@[expose] public def offsetBase (L m N Q : ℕ) : ℕ := + snapshotBase L m N Q + inputEnd m N Q + +/-- The grouped numerical codes follow all offsets, including their terminal word. -/ +@[expose] public def codeBase (L m N Q : ℕ) : ℕ := + offsetBase L m N Q + numBands L m N ^ 2 + 1 + +/-- The actual original-index permutation follows the grouped codes. -/ +@[expose] public def originalBase (L m N Q : ℕ) : ℕ := codeBase L m N Q + Q + +/-- Kernel reporting writes answers in grouped order into this separate array. -/ +@[expose] public def groupedBase (L m N Q : ℕ) : ℕ := originalBase L m N Q + Q + +/-- Reconstructed answers in original list order; repeated requests have separate cells. -/ +@[expose] public def outputBase (L m N Q : ℕ) : ℕ := groupedBase L m N Q + Q + +/-- Every source, saved routing array and answer block precedes reusable kernel scratch. -/ +@[expose] public def scratchBase (L m N Q : ℕ) : ℕ := outputBase L m N Q + Q + +/-- The shared timestamp directory follows the kernel's actual frames and depth rows. -/ +@[expose] public def stampBase (L m N Q : ℕ) : ℕ := + scratchBase L m N Q + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + +/-- One past the explicit whole-program allocation. -/ +@[expose] public def memoryEnd (L m N Q : ℕ) : ℕ := stampBase L m N Q + 10 ^ L + +/-- Every tile call increases its actual returned epoch by at most this total envelope. -/ +@[expose] public def epochBound (L m N : ℕ) : ℕ := + numBands L m N ^ 2 * (1 + PrunedQuery.epochAllowance L) + +/-- Ordinary scalar bound covering all producer, caller and kernel address arithmetic. +Signed arithmetic has its separate original-entry magnitude envelope. -/ +@[expose] public def wordBudget (L m N Q : ℕ) : ℕ := + 1024 * (BandPreparation.wordBudget L m N + Wanted.wordBudget L m N Q + + PrunedQuery.kernelBudget L (scratchBase L m N Q) (BandPreparation.memoryEnd L m N) + (BandPreparation.memoryEnd L m N) (codeBase L m N Q) (outputBase L m N Q) Q + (epochBound L m N) + memoryEnd L m N Q + inputEnd m N Q + L + N + Q + 16) + +/-- The source's magnitude bounds on shared encodings derive the kernel's operand bounds. -/ +@[expose] public def magnitudeBound (L A : ℕ) : ℕ := + PrunedQuery.magnitudeBound L (7 ^ L * A) (7 ^ L * A) + A + +/-- Both low producer work areas and the complete original input precede its snapshot. -/ +public theorem producers_le_snapshot (L m N Q : ℕ) : + BandPreparation.memoryEnd L m N ≤ snapshotBase L m N Q ∧ + Wanted.memoryLimit L m N Q ≤ snapshotBase L m N Q ∧ + inputEnd m N Q ≤ snapshotBase L m N Q := by + unfold snapshotBase + omega + +/-- The high buffers are adjacent and ordered, including zero-length answer arrays. -/ +public theorem buffer_order (L m N Q : ℕ) : + snapshotBase L m N Q + inputEnd m N Q = offsetBase L m N Q ∧ + offsetBase L m N Q + numBands L m N ^ 2 + 1 = codeBase L m N Q ∧ + codeBase L m N Q + Q = originalBase L m N Q ∧ + originalBase L m N Q + Q = groupedBase L m N Q ∧ + groupedBase L m N Q + Q = outputBase L m N Q ∧ + outputBase L m N Q + Q = scratchBase L m N Q := by + exact ⟨rfl, rfl, rfl, rfl, rfl, rfl⟩ + +/-- Real original query addresses lie strictly inside the original input prefix. -/ +public theorem query_address_lt {m N Q i s : ℕ} (hi : i < Q) (hs : s < 2) : + queryInputBase m N + 2 * i + s < inputEnd m N Q := by + unfold queryInputBase inputEnd + omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/MatrixInput.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/MatrixInput.lean new file mode 100644 index 0000000000..ea776dea0e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/MatrixInput.lean @@ -0,0 +1,83 @@ +/- +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.Product.MatrixTransfer +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Admission + +/-! +# Original matrices through the actual native input adapter + +## Main results + +* `QueriedProduct.exec_prepareInputMatrices`: real signed copies or raw-entry doubling + establish the exact input words required by the existing band producer. + +The source-word equalities come from the preceding native original-input snapshot. The +raw path represents nonnegative entries, including the ET frontend's raw zero/one matrices. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Every original matrix entry reaches the producer in its canonical signed convention. +The instruction envelope includes the actual conversion at every raw input entry. -/ +public theorem exec_prepareInputMatrices {w L m N A : ℕ} (hw : 0 < w) (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (x : Data 160) (hp : Parameters L m N qs.length x) + (hsource : ∀ j < 2 * (N * D m), x.mem (snapshotBase L m N qs.length + 5 + j) = + (State.init (input bits L X Y qs) : State 160).data.mem (5 + j)) + (hX : ∀ i k, |X i k| ≤ (A : ℤ)) (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hbits : bits = true → (∀ i k, 0 ≤ X i k) ∧ (∀ k j, 0 ≤ Y k j)) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) : + ∃ y, ExecWithin (w + 2) (prepareMatrices bits) x y (8 * (2 * (N * D m)) + 10) ∧ + Parameters L m N qs.length y ∧ + (∀ side r, r < N → ∀ k, k < D m → + y.mem (BandPreparation.matrixOffset m N side r k + 4) = + Signed.encode (BandPreparation.matrixValues X Y side r k)) ∧ + (∀ a, a < 4 ∨ 4 + 2 * (N * D m) ≤ a → y.mem a = x.mem a) := by + obtain ⟨y, he, yp, hy, hf⟩ : + ∃ y, ExecWithin (w + 2) (prepareMatrices bits) x y (8 * (2 * (N * D m)) + 10) ∧ + Parameters L m N qs.length y ∧ + (∀ j < 2 * (N * D m), y.mem (4 + j) = + if bits then 2 * x.mem (snapshotBase L m N qs.length + 5 + j) + else x.mem (snapshotBase L m N qs.length + 5 + j)) ∧ + (∀ a, a < 4 ∨ 4 + 2 * (N * D m) ≤ a → y.mem a = x.mem a) := by + cases bits + · obtain ⟨y, he, yp, hy, hf⟩ := exec_prepareSigned x hp hfit + exact ⟨y, he.mono (by omega), yp, hy, hf⟩ + · obtain ⟨y, he, yp, hy, hf⟩ := exec_prepareBits x hp hfit + (magnitude_admissions hw hmag).2.2 (by + intro j hj + rw [hsource j hj] + exact input_raw_matrix_le X Y qs hX hY j hj) + exact ⟨y, he.mono (by omega), yp, hy, hf⟩ + refine ⟨y, he, yp, ?_, hf⟩ + intro side r hr k hk + have hindex := BandPreparation.matrixAddress_lt_inverse (L := L) side hr hk + unfold BandPreparation.matrixAddress BandPreparation.inverseBase + BandPreparation.inputEnd at hindex + have hi : BandPreparation.matrixOffset m N side r k < 2 * (N * D m) := by omega + have hv := input_matrix_word (L := L) bits X Y qs side r k hr hk + rw [show BandPreparation.matrixOffset m N side r k + 4 = + 4 + BandPreparation.matrixOffset m N side r k by omega, hy _ hi, hsource _ hi, + show 5 + BandPreparation.matrixOffset m N side r k = + BandPreparation.matrixOffset m N side r k + 5 by omega, hv] + exact convert_entryWord bits _ (by + intro hb + have hnonneg := hbits hb + cases side <;> + simp only [BandPreparation.matrixValues, dite_eq_left hr, dite_eq_left hk, + Bool.false_eq_true, ↓reduceIte] + · exact hnonneg.1 _ _ + · exact hnonneg.2 _ _) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/MatrixTransfer.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/MatrixTransfer.lean new file mode 100644 index 0000000000..57038dd7c7 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/MatrixTransfer.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.Product.Transfer + +/-! +# Actual matrix-word adaptation between producer inputs + +## Main results + +* `QueriedProduct.exec_prepareBits`: copy each original raw unsigned entry while actually + doubling it for the canonical signed producer convention. + +The conversion uses a native load/multiply/store loop. Its original-entry bound derives +every arithmetic fit; its invariant protects unread source words and all caller scalars. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Source and destination blocks in the actual matrix adaptation are disjoint and fit. -/ +public theorem matrix_transfer_bounds (L m N Q : ℕ) : + 4 + 2 * (N * D m) ≤ snapshotBase L m N Q + 5 ∧ + snapshotBase L m N Q + 5 + 2 * (N * D m) ≤ memoryEnd L m N Q := by + have h := transfer_bounds L m N Q + unfold inputEnd at h + omega + +/-- Every raw entry is converted by an actual charged multiplication, with the complete +outside frame. The numerical bound concerns original source words, not intermediate states. -/ +public theorem exec_prepareBits {w L m N Q A : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hfit : Fits w (wordBudget L m N Q)) + (hA : Fits w (2 * A)) + (hentries : ∀ i < 2 * (N * D m), x.mem (snapshotBase L m N Q + 5 + i) ≤ A) : + ∃ y, ExecWithin w (prepareMatrices true) x y (8 * (2 * (N * D m)) + 4) ∧ + Parameters L m N Q y ∧ + (∀ i < 2 * (N * D m), + y.mem (4 + i) = 2 * x.mem (snapshotBase L m N Q + 5 + i)) ∧ + (∀ a, a < 4 ∨ 4 + 2 * (N * D m) ≤ a → y.mem a = x.mem a) := by + let M := 2 * (N * D m) + let S := snapshotBase L m N Q + 5 + have hb := matrix_transfer_bounds L m N Q + have hsource : Fits w S := allocated_fits hfit (by dsimp [S]; omega) + let start := (x.set source S).set index 0 + have hs : ExecWithin (B := .standard) w (ram[ source ← snapshot + five; index ← #0 ]) + x start 2 := by + ram_eval [start, hp.snapshot, hp.five, hsource, S, source, snapshot, five, index] + have sp : Parameters L m N Q start := + (hp.set_work source (by decide) _).set_work index (by decide) _ + let I (z : Data 160) : Prop := Parameters L m N Q z ∧ z.regs source = S ∧ + z.regs index ≤ M ∧ + (∀ j < z.regs index, z.mem (4 + j) = 2 * x.mem (S + j)) ∧ + (∀ a, a < 4 ∨ 4 + z.regs index ≤ a → z.mem a = x.mem a) + have hstart : I start := ⟨sp, by simp [start, source, index], by simp [start], + by simp [start], by simp [start]⟩ + have hbody (z : Data 160) (hz : I z) (hc : (Cond.lt index matrixWords).Holds z) : + ∃ u, ExecWithin w bitCopyStep z u 6 ∧ I u ∧ M - u.regs index < M - z.regs index := by + have hi : z.regs index < M := by + simpa only [Cond.holds_lt, hz.1.matrixWords, M] using hc + have hload : z.mem (S + z.regs index) = x.mem (S + z.regs index) := + hz.2.2.2.2 _ (Or.inr (by dsimp [S, M] at *; omega)) + have hsrc : Fits w (S + z.regs index) := allocated_fits hfit (by + dsimp [S, M] at * + omega) + have hdst : Fits w (4 + z.regs index) := allocated_fits hfit (by + dsimp [S, M] at * + omega) + have hnext : Fits w (z.regs index + 1) := allocated_fits hfit (by + dsimp [S, M] at * + omega) + have hvalue : Fits w (x.mem (S + z.regs index) * 2) := hA.of_le (by + have hv := hentries (z.regs index) hi + dsimp only [S] + omega) + let u := (((((z.set address (S + z.regs index)).set value + (x.mem (S + z.regs index))).set value (x.mem (S + z.regs index) * 2)).set address + (4 + z.regs index)).write (4 + z.regs index) (x.mem (S + z.regs index) * 2)).set + index (z.regs index + 1) + have he : ExecWithin w bitCopyStep z u 6 := by + ram_eval [bitCopyStep, u, hz.2.1, hz.1.two, hz.1.four, hz.1.one, hload, + hsrc, hdst, hnext, hvalue, source, address, index, value, two, four, one] + have up : Parameters L m N Q u := + (((((hz.1.set_work address (by decide) _).set_work value (by decide) _).set_work + value (by decide) _).set_work address (by decide) _).write _ _).set_work + index (by decide) _ + have hn : u.regs index = z.regs index + 1 := by simp [u] + refine ⟨u, he, ⟨up, ?_, by rw [hn]; omega, ?_, ?_⟩, by rw [hn]; omega⟩ + · simpa [u, source, address, index, value] using hz.2.1 + · intro j hj + rw [hn] at hj + change Function.update z.mem (4 + z.regs index) (x.mem (S + z.regs index) * 2) + (4 + j) = 2 * x.mem (S + j) + by_cases h : j = z.regs index + · subst j + simp only [Function.update_self, Nat.mul_comm] + · rw [Function.update_of_ne (by omega)] + exact hz.2.2.2.1 j (by omega) + · intro a ha + change Function.update z.mem (4 + z.regs index) (x.mem (S + z.regs index) * 2) a = _ + rw [Function.update_of_ne (by rw [hn] at ha; rcases ha with ha | ha <;> omega)] + apply hz.2.2.2.2 a + rw [hn] at ha + rcases ha with ha | ha <;> omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => M - z.regs index) hbody hstart + have hn : y.regs index = M := by + have hu := hy.2.2.1 + simp only [Cond.holds_lt, hy.1.matrixWords] at hc + dsimp only [M] at hu ⊢ + omega + refine ⟨y, (hs.seq he).mono ?_, hy.1, ?_, ?_⟩ + · have hzero : start.regs index = 0 := by simp [start] + rw [hzero, Nat.sub_zero] + dsimp [M] + omega + · simpa only [hn, M, S] using hy.2.2.2.1 + · simpa only [hn, M] using hy.2.2.2.2 + +/-- Canonical signed inputs use actual dynamic copying with no conversion arithmetic. -/ +public theorem exec_prepareSigned {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w (prepareMatrices false) x y (7 * (2 * (N * D m)) + 10) ∧ + Parameters L m N Q y ∧ + (∀ i < 2 * (N * D m), + y.mem (4 + i) = x.mem (snapshotBase L m N Q + 5 + i)) ∧ + (∀ a, a < 4 ∨ 4 + 2 * (N * D m) ≤ a → y.mem a = x.mem a) := by + have hb := matrix_transfer_bounds L m N Q + have hs : Fits w (snapshotBase L m N Q + 5) := allocated_fits hfit (by omega) + let z := ((x.set source (snapshotBase L m N Q + 5)).set destination 4).set + length (2 * (N * D m)) + have hz : ExecWithin (B := .standard) w (ram[ + source ← snapshot + five; destination ← four; length ← matrixWords ]) x z 3 := by + ram_eval [z, hp.snapshot, hp.five, hp.four, hp.matrixWords, hs, + source, snapshot, five, destination, four, length, matrixWords] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) _).set_work destination (by decide) _).set_work + length (by decide) _ + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length] using + allocated_fits hfit (show snapshotBase L m N Q + 5 + 2 * (N * D m) ≤ + memoryEnd L m N Q + 16 by omega)) + (by simpa [z, source, destination, length] using + allocated_fits hfit (show 4 + 2 * (N * D m) ≤ memoryEnd L m N Q + 16 by omega)) + (Or.inr (Or.inl (by simpa [z, source, destination, length] using hb.1))) + have hy : y.mem = ArrayCopy.copyPrefix x.mem (snapshotBase L m N Q + 5) 4 + (2 * (N * D m)) := by simpa [z, source, destination, length] using hm + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_, ?_⟩ + · have hlen : z.regs length = 2 * (N * D m) := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · intro i hi + rw [hy] + exact ArrayCopy.copyPrefix_read hi + · intro a ha + rw [hy] + exact ArrayCopy.copyPrefix_outside (by rcases ha with ha | ha <;> omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Memory.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Memory.lean new file mode 100644 index 0000000000..fd87249f54 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Memory.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.Product.Parameters + +/-! +# Persistent queried-product caller scalars + +## Main definitions + +* `QueriedProduct.Parameters`: ordinary equalities describing computed high-register values. + +## Main results + +* `QueriedProduct.Parameters.reframe`: preserve the scalar protocol through actual low-bank calls. + +The record contains no execution, cost, layout-validity or intermediate-state-fit assertion. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Persistent high-register contents after the native layout calculation. -/ +public structure Parameters (L m N Q : ℕ) (x : Data 160) : Prop where + /-- Original depth. -/ + depth : x.regs depth = L + /-- Original inner depth. -/ + inner : x.regs inner = m + /-- Original outer matrix dimension. -/ + rows : x.regs rows = N + /-- Original request count, including repetitions. -/ + queryCount : x.regs queryCount = Q + /-- Original thin dimension. -/ + columns : x.regs columns = D m + /-- Complete subset-scan size. -/ + masks : x.regs masks = 2 ^ L + /-- Original band-array size. -/ + cells : x.regs cells = 7 ^ L + /-- Shared encoding-array size. -/ + leaves : x.regs leaves = 10 ^ L + /-- Computed number of shared bands. -/ + bands : x.regs bands = numBands L m N + /-- Computed number of tile calls. -/ + tiles : x.regs tiles = numBands L m N ^ 2 + /-- Complete original prefix size. -/ + inputSize : x.regs inputSize = inputEnd m N Q + /-- Protected source snapshot. -/ + snapshot : x.regs snapshot = snapshotBase L m N Q + /-- Saved actual offsets. -/ + offsets : x.regs offsets = offsetBase L m N Q + /-- Saved actual query codes. -/ + codes : x.regs codes = codeBase L m N Q + /-- Saved original-position permutation. -/ + originals : x.regs originals = originalBase L m N Q + /-- Grouped-answer block. -/ + grouped : x.regs grouped = groupedBase L m N Q + /-- Original-order answer block. -/ + output : x.regs output = outputBase L m N Q + /-- Reusable native kernel scratch. -/ + scratch : x.regs scratch = scratchBase L m N Q + /-- Shared timestamp directory. -/ + stamps : x.regs stamps = stampBase L m N Q + /-- One past the complete allocation. -/ + memoryLimit : x.regs memoryLimit = memoryEnd L m N Q + /-- Constant zero. -/ + zero : x.regs zero = 0 + /-- Constant one. -/ + one : x.regs one = 1 + /-- Unsigned multiplier for signed encoding. -/ + two : x.regs two = 2 + /-- Constant three. -/ + three : x.regs three = 3 + /-- Constant four. -/ + four : x.regs four = 4 + /-- Original matrix payload offset. -/ + five : x.regs five = 5 + /-- Constant six. -/ + six : x.regs six = 6 + /-- Both original matrix payloads. -/ + matrixWords : x.regs matrixWords = 2 * (N * D m) + /-- Original coordinate-pair offset. -/ + queryOffset : x.regs queryOffset = queryInputBase m N + /-- Low routing allocation end. -/ + wantedEnd : x.regs wantedEnd = Wanted.memoryLimit L m N Q + /-- Low producer allocation end. -/ + bandEnd : x.regs bandEnd = BandPreparation.memoryEnd L m N + /-- Computed low shared catalogue base. -/ + catalogue : x.regs catalogue = BandPreparation.catalogueBase L m N + +/-- Registers holding the persistent caller protocol. -/ +@[expose] public def parameterRegisters : Finset (Fin 160) := + {depth, inner, rows, queryCount, columns, masks, cells, leaves, bands, tiles, inputSize, + snapshot, offsets, codes, originals, grouped, output, scratch, stamps, memoryLimit, zero, one, + two, three, four, five, six, matrixWords, queryOffset, wantedEnd, bandEnd, catalogue} + +/-- Preserve the protocol through operations on any of the other caller work registers. -/ +public theorem Parameters.of_frame {L m N Q : ℕ} {x y : Data 160} + (hx : Parameters L m N Q x) (h : ∀ i ∈ parameterRegisters, y.regs i = x.regs i) : + Parameters L m N Q y := by + constructor + · exact (h _ (by decide)).trans hx.depth + · exact (h _ (by decide)).trans hx.inner + · exact (h _ (by decide)).trans hx.rows + · exact (h _ (by decide)).trans hx.queryCount + · exact (h _ (by decide)).trans hx.columns + · 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.bands + · exact (h _ (by decide)).trans hx.tiles + · exact (h _ (by decide)).trans hx.inputSize + · exact (h _ (by decide)).trans hx.snapshot + · exact (h _ (by decide)).trans hx.offsets + · exact (h _ (by decide)).trans hx.codes + · exact (h _ (by decide)).trans hx.originals + · exact (h _ (by decide)).trans hx.grouped + · exact (h _ (by decide)).trans hx.output + · exact (h _ (by decide)).trans hx.scratch + · exact (h _ (by decide)).trans hx.stamps + · exact (h _ (by decide)).trans hx.memoryLimit + · exact (h _ (by decide)).trans hx.zero + · exact (h _ (by decide)).trans hx.one + · exact (h _ (by decide)).trans hx.two + · exact (h _ (by decide)).trans hx.three + · exact (h _ (by decide)).trans hx.four + · exact (h _ (by decide)).trans hx.five + · exact (h _ (by decide)).trans hx.six + · exact (h _ (by decide)).trans hx.matrixWords + · exact (h _ (by decide)).trans hx.queryOffset + · exact (h _ (by decide)).trans hx.wantedEnd + · exact (h _ (by decide)).trans hx.bandEnd + · exact (h _ (by decide)).trans hx.catalogue + +set_option maxRecDepth 2048 in +-- Simplifying the fixed 32-member register set unfolds its finite membership expressions. +/-- Existing native callees operate below register 96, leaving this caller protocol intact. -/ +public theorem Parameters.reframe {L m N Q : ℕ} {x y : Data 160} + (hx : Parameters L m N Q x) (h : ∀ i, 96 ≤ i.val → y.regs i = x.regs i) : + Parameters L m N Q y := hx.of_frame (by + intro i hi + have hs : ∀ i ∈ parameterRegisters, 96 ≤ i.val := by decide + exact h i (hs i hi)) + +/-- Assigning a working register does not change any persistent parameter. -/ +public theorem Parameters.set_work {L m N Q : ℕ} {x : Data 160} + (hx : Parameters L m N Q x) (i : Fin 160) (hi : i ∉ parameterRegisters) (v : ℕ) : + Parameters L m N Q (x.set i v) := hx.of_frame (by + intro j hj + have hji : j ≠ i := fun heq => hi (heq ▸ hj) + simp only [Data.set_regs, Function.update_of_ne hji]) + +/-- Memory writes preserve the scalar protocol. -/ +public theorem Parameters.write {L m N Q : ℕ} {x : Data 160} + (hx : Parameters L m N Q x) (a v : ℕ) : Parameters L m N Q (x.write a v) := + hx.of_frame (by intro i _; rfl) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Parameters.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Parameters.lean new file mode 100644 index 0000000000..4aabeed0a4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Parameters.lean @@ -0,0 +1,67 @@ +/- +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.Product.Copy +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandParameters + +/-! +# Caller scalars from actual native parameters + +## Main results + +* `QueriedProduct.producer_fits`: original numerical admission covers both producer kernels. +* `QueriedProduct.exec_parameters`: actual powers, mask counting, root and band count, + with a charge and complete high-register/memory frame. + +The parameter phase reads original headers only. No computed roots, subset tables or +intermediate-state fit assertions are supplied by the caller. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM + +/-- Numerical admission is on original scalar parameters, not machine states. -/ +public theorem producer_fits {w L m N Q : ℕ} (hfit : Fits w (wordBudget L m N Q)) : + Fits w (BandPreparation.wordBudget L m N) ∧ Fits w (Wanted.wordBudget L m N Q) := by + constructor <;> apply hfit.of_le <;> unfold wordBudget <;> omega + +/-- A common numerical margin covers caller counters, all allocation endpoints and constants. -/ +public theorem caller_fits {w L m N Q : ℕ} (hfit : Fits w (wordBudget L m N Q)) : + Fits w (16 * (L + N + Q + memoryEnd L m N Q + 16)) := by + apply hfit.of_le + unfold wordBudget + omega + +/-- All actual parameter computation is charged, and the original matrix/query memory is +untouched. Reusing the low bank preserves every high caller register. -/ +public theorem exec_parameters {w L m N Q : ℕ} (hm : m ≤ L) (x : Data 160) + (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w parameters x y + ((16 * L + 49) + ((6 * L + 16) * 2 ^ L + 14)) ∧ + BandPreparation.BandParameters L m N (y.comap bank) ∧ y.mem = x.mem ∧ + (∀ i, 96 ≤ i.val → y.regs i = x.regs i) := by + obtain ⟨h, hh, hp, hm⟩ := BandPreparation.exec_header hm (x.comap bank) hL hmemory hN + (producer_fits hfit).1 + obtain ⟨c, hc, cp, cm⟩ := BandPreparation.exec_countCode h hp (producer_fits hfit).1 + have he := (hh.seq hc).rename bank_injective + change ExecWithin w parameters x (c.extend bank x) + ((16 * L + 49) + ((6 * L + 16) * 2 ^ L + 14)) at he + refine ⟨c.extend bank x, he, ?_, cm.trans hm, ?_⟩ + · simpa only [Data.comap_extend bank_injective] using cp + · intro i hi + have hnot : ¬∃ j, bank j = i := by + rintro ⟨j, hj⟩ + have hv := congrArg (fun r : Fin 160 => r.val) hj + change j.val = i.val at hv + omega + exact Function.extend_apply' _ _ _ hnot + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Program.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Program.lean new file mode 100644 index 0000000000..ed765df7a3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Program.lean @@ -0,0 +1,289 @@ +/- +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.Product.Layout + +/-! +# Actual producer, tile-kernel and reconstruction code + +## Main definitions + +* `QueriedProduct.program`: fixed native composition, with signed or raw nonnegative matrices. +* `QueriedProduct.tileStep`: actual offset loads, catalogue pointers, epoch call and update. +* `QueriedProduct.reconstruct`: restore the original request order from actual stored indices. + +All three existing 96-register callees use the low bank. The caller's 64 additional +registers retain its scalar layout and counters. Copying, initial directory clearing, +header adaptation, raw-entry doubling, dispatch, reporting and restoration are real instructions. +Supporting code definitions are exposed for execution proofs; they are implementation APIs. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM + +/-- Reuse each existing native program in the same low 96-register bank. -/ +@[expose] public def bank (i : Fin 96) : Fin 160 := ⟨i.val, by omega⟩ +/-- The unchanged copy routine uses the first seven low registers. -/ +@[expose] public def copyBank (i : Fin 7) : Fin 160 := ⟨i.val, by omega⟩ + +/-- Persistent caller depth. -/ +public abbrev depth : Fin 160 := 96 +/-- Original inner depth, determining the shared dimension `4^m`. -/ +public abbrev inner : Fin 160 := 97 +/-- Original outer matrix dimension. -/ +public abbrev rows : Fin 160 := 98 +/-- Number of original request occurrences, including duplicates. -/ +public abbrev queryCount : Fin 160 := 99 +/-- Computed shared dimension `4^m`. -/ +public abbrev columns : Fin 160 := 100 +/-- Number of masks visited by the actual subset-table scan. -/ +public abbrev masks : Fin 160 := 101 +/-- Number of cells in each padded band array. -/ +public abbrev cells : Fin 160 := 102 +/-- Number of leaf entries in each shared encoding. -/ +public abbrev leaves : Fin 160 := 103 +/-- Computed number of row bands, also used for column bands. -/ +public abbrev bands : Fin 160 := 104 +/-- Number of tile calls, the square of the band count. -/ +public abbrev tiles : Fin 160 := 105 +/-- Length of the complete original memory prefix, including word zero. -/ +public abbrev inputSize : Fin 160 := 106 +/-- Protected snapshot pointer used to restore the original input. -/ +public abbrev snapshot : Fin 160 := 107 +/-- Protected copy of the router's immutable prefix offsets. -/ +public abbrev offsets : Fin 160 := 108 +/-- Protected copy of the actual grouped query codes. -/ +public abbrev codes : Fin 160 := 109 +/-- Protected permutation from grouped positions to original occurrence indices. -/ +public abbrev originals : Fin 160 := 110 +/-- Base of the grouped answers written by tile calls. -/ +public abbrev grouped : Fin 160 := 111 +/-- Base of the reconstructed original-order answers, returned by halt. -/ +public abbrev output : Fin 160 := 112 +/-- Base of reusable pruned-kernel stack and cache space. -/ +public abbrev scratch : Fin 160 := 113 +/-- Base of the timestamp directory shared by all tiles. -/ +public abbrev stamps : Fin 160 := 114 +/-- One past the complete caller and kernel allocation. -/ +public abbrev memoryLimit : Fin 160 := 115 +/-- Returned kernel epoch propagated to the next tile. -/ +public abbrev epoch : Fin 160 := 116 +/-- Current tile, directory-entry or reporting-loop index. -/ +public abbrev index : Fin 160 := 117 +/-- First grouped position of the current tile's actual span. -/ +public abbrev first : Fin 160 := 118 +/-- One past the last grouped position of the current tile. -/ +public abbrev stop : Fin 160 := 119 +/-- Row band computed by division of the current tile key. -/ +public abbrev rowBand : Fin 160 := 120 +/-- Column band computed by remainder of the current tile key. -/ +public abbrev colBand : Fin 160 := 121 +/-- Actual shared left-encoding pointer for the current tile. -/ +public abbrev leftPointer : Fin 160 := 122 +/-- Actual shared right-encoding pointer for the current tile. -/ +public abbrev rightPointer : Fin 160 := 123 +/-- Actual grouped-query pointer passed to the kernel. -/ +public abbrev queryPointer : Fin 160 := 124 +/-- Current grouped-answer pointer passed to the kernel. -/ +public abbrev answerPointer : Fin 160 := 125 +/-- Source pointer supplied to a real native copy. -/ +public abbrev source : Fin 160 := 126 +/-- Destination pointer supplied to a real native copy. -/ +public abbrev destination : Fin 160 := 127 +/-- Number of words supplied to a real native copy. -/ +public abbrev length : Fin 160 := 128 +/-- Temporary address for scalar loads, stores and reporting. -/ +public abbrev address : Fin 160 := 129 +/-- Temporary loaded matrix value, answer or original-position index. -/ +public abbrev value : Fin 160 := 130 +/-- Persistent unsigned zero. -/ +public abbrev zero : Fin 160 := 131 +/-- Persistent loop-increment constant one. -/ +public abbrev one : Fin 160 := 132 +/-- Persistent multiplier two for raw-to-signed conversion. -/ +public abbrev two : Fin 160 := 133 +/-- Persistent constant four for header offsets and kernel stack sizing. -/ +public abbrev four : Fin 160 := 134 +/-- First original matrix-payload address, immediately after the four headers. -/ +public abbrev five : Fin 160 := 135 +/-- Persistent constant six used in the computed router allocation. -/ +public abbrev six : Fin 160 := 136 +/-- Total word count of both original matrix payloads. -/ +public abbrev matrixWords : Fin 160 := 137 +/-- Original coordinate-pair block address. -/ +public abbrev queryOffset : Fin 160 := 138 +/-- One past the actual router allocation. -/ +public abbrev wantedEnd : Fin 160 := 139 +/-- One past the shared-band producer allocation. -/ +public abbrev bandEnd : Fin 160 := 140 +/-- Temporary product used while computing separated buffer addresses. -/ +public abbrev temporary : Fin 160 := 141 +/-- Base of the producer's shared two-sided encoding catalogue. -/ +public abbrev catalogue : Fin 160 := 145 +/-- Persistent constant three used in the router allocation. -/ +public abbrev three : Fin 160 := 146 + +/-- Compute genuine band scalars before selecting a protected caller area. No memory writes. -/ +@[expose] public def parameters : Code .standard 160 := + .seq (BandPreparation.header.rename bank) (BandPreparation.countCode.rename bank) + +/-- Preserve the computed original parameters and calculate every caller buffer address. -/ +@[expose] public def layout : Code .standard 160 := + let bLevels := bank BandPreparation.levels + let bInner := bank BandPreparation.inner + let bRows := bank BandPreparation.rows + let bColumns := bank BandPreparation.columns + let bMasks := bank BandPreparation.masks + let bCells := bank BandPreparation.cells + let bLeaves := bank BandPreparation.leaves + let bBands := bank BandPreparation.bands + let bCatalogue := bank BandPreparation.catalogue + ram[ + depth ← bLevels; inner ← bInner; rows ← bRows; + address ← #4; queryCount ← *address; + columns ← bColumns; masks ← bMasks; cells ← bCells; leaves ← bLeaves; + bands ← bBands; catalogue ← bCatalogue; + zero ← #0; one ← #1; two ← #2; three ← #3; four ← #4; five ← #5; six ← #6; + tiles ← bands * bands; + matrixWords ← rows * columns; matrixWords ← matrixWords * two; + queryOffset ← matrixWords + five; + inputSize ← queryCount * two; inputSize ← inputSize + queryOffset; + bandEnd ← bands * leaves; bandEnd ← bandEnd * two; bandEnd ← bandEnd + catalogue; + wantedEnd ← queryCount * four; wantedEnd ← wantedEnd + six; + temporary ← masks * two; wantedEnd ← wantedEnd + temporary; + temporary ← tiles * three; wantedEnd ← wantedEnd + temporary; + snapshot ← bandEnd + wantedEnd; snapshot ← snapshot + inputSize; + offsets ← snapshot + inputSize; + codes ← offsets + tiles; codes ← codes + one; + originals ← codes + queryCount; grouped ← originals + queryCount; + output ← grouped + queryCount; scratch ← output + queryCount; + temporary ← depth + one; temporary ← temporary * four; + stamps ← scratch + temporary; + temporary ← depth + one; temporary ← temporary * leaves; + temporary ← temporary * two; stamps ← stamps + temporary; + memoryLimit ← stamps + leaves ] + +/-- Real argument setup followed by the unchanged dynamic-length native copy. -/ +@[expose] public def copyCall : Code .standard 160 := + let copySrc : Fin 160 := 0 + let copyDst : Fin 160 := 1 + let copyLen : Fin 160 := 2 + ram[ + copySrc ← source; copyDst ← destination; copyLen ← length; + ⟪ArrayCopy.program.rename copyBank⟫ ] + +/-- Save the entire ordinary input, including its length word and coordinate list. -/ +@[expose] public def saveInput : Code .standard 160 := + .seq (ram[ source ← zero; destination ← snapshot; length ← inputSize ]) copyCall + +/-- Put the original raw coordinate pairs in the actual Wanted input convention. -/ +@[expose] public def prepareQueries : Code .standard 160 := + .seq (ram[ source ← snapshot + queryOffset; destination ← five; + length ← queryCount * two ]) copyCall + +/-- Save every computed tile offset, including the terminal offset. -/ +@[expose] public def saveOffsets : Code .standard 160 := + let wOffsets := bank Wanted.offsets + .seq (ram[ source ← wOffsets; destination ← offsets; length ← tiles + one ]) copyCall + +/-- Save the actual grouped query codes. -/ +@[expose] public def saveCodes : Code .standard 160 := + let wCodes := bank Wanted.codes + .seq (ram[ source ← wCodes; destination ← codes; length ← queryCount ]) copyCall + +/-- Save the actual original-position permutation. -/ +@[expose] public def saveOriginals : Code .standard 160 := + let wOriginals := bank Wanted.originals + .seq (ram[ source ← wOriginals; destination ← originals; length ← queryCount ]) copyCall + +/-- Preserve the computed arrays before the band producer reuses the low work area. -/ +@[expose] public def saveGroups : Code .standard 160 := + .seq saveOffsets (.seq saveCodes saveOriginals) + +/-- One actual load, unsigned doubling and store converts a raw 0/1 matrix entry. -/ +@[expose] public def bitCopyStep : Code .standard 160 := ram[ + address ← source + index; value ← *address; value ← value * two; + address ← four + index; *address ← value; index ← index + one ] + +/-- Copy real matrix words into the band producer's adjacent front input blocks. +The bit-input variant executes the conversion instruction at every entry. -/ +@[expose] public def prepareMatrices (bits : Bool) : Code .standard 160 := + if bits then + .seq (ram[ source ← snapshot + five; index ← #0 ]) + (.loop (.lt index matrixWords) bitCopyStep) + else + .seq (ram[ source ← snapshot + five; destination ← four; length ← matrixWords ]) copyCall + +/-- Initialize the one timestamp directory by actual stores, once for the entire product. -/ +@[expose] public def initializeDirectory : Code .standard 160 := ram[ + index ← #0; + while index < leaves do [ + address ← stamps + index; *address ← zero; index ← index + one ]; + epoch ← #0; index ← #0 ] + +/-- Load actual group endpoints and compute both shared catalogue pointers and kernel ABI. -/ +@[expose] public def tileArguments : Code .standard 160 := + let kDepth := bank PrunedQuery.inputDepth + let kLeft := bank PrunedQuery.arrayA + let kRight := bank PrunedQuery.arrayB + let kQueries := bank PrunedQuery.queries + let kCount := bank PrunedQuery.queryCount + let kOutput := bank PrunedQuery.output + let kScratch := bank PrunedQuery.scratch + let kEpoch := bank PrunedQuery.epoch + ram[ + address ← offsets + index; first ← *address; + address ← address + one; stop ← *address; + rowBand ← index / bands; colBand ← index % bands; + leftPointer ← rowBand * leaves; leftPointer ← leftPointer + catalogue; + rightPointer ← colBand + bands; rightPointer ← rightPointer * leaves; + rightPointer ← rightPointer + catalogue; + queryPointer ← codes + first; answerPointer ← grouped + first; + kDepth ← depth; kLeft ← leftPointer; kRight ← rightPointer; + kQueries ← queryPointer; kCount ← stop - first; kOutput ← answerPointer; + kScratch ← scratch; kEpoch ← epoch ] + +/-- One real pruned-kernel call, with the returned epoch propagated to the next tile. -/ +@[expose] public def tileStep : Code .standard 160 := + let kEpoch := bank PrunedQuery.epoch + .seq tileArguments (.seq (PrunedQuery.kernel.rename bank) + (ram[ epoch ← kEpoch; index ← index + one ])) + +/-- Compute all tiles from the actual group arrays; the zero-tile case never divides by zero. -/ +@[expose] public def evaluateTiles : Code .standard 160 := + .loop (.lt index tiles) tileStep + +/-- Read one actual original index and scatter its computed answer into request order. -/ +@[expose] public def reconstructStep : Code .standard 160 := ram[ + address ← originals + index; temporary ← *address; + address ← grouped + index; value ← *address; + address ← output + temporary; *address ← value; index ← index + one ] + +/-- Every original occurrence is reconstructed by charged native reads and stores. -/ +@[expose] public def reconstruct : Code .standard 160 := ram[ + index ← #0; while index < queryCount do [ ⟪reconstructStep⟫ ] ] + +/-- Restore the complete actual input after all private low-memory reuse has finished. -/ +@[expose] public def restoreInput : Code .standard 160 := + .seq (ram[ source ← snapshot; destination ← zero; length ← inputSize ]) copyCall + +/-- Complete actual routing preparation and protected preservation of the original input. -/ +@[expose] public def prepareRouting : Code .standard 160 := + .seq parameters (.seq layout (.seq saveInput (.seq prepareQueries + (.seq (Wanted.program.rename bank) saveGroups)))) + +/-- The complete fixed producer/consumer program. `bits=false` takes canonical signed matrix +words; `bits=true` takes raw unsigned 0/1 words and performs actual per-entry doubling. -/ +@[expose] public def program (bits : Bool) : Code .standard 160 := + .seq prepareRouting (.seq (prepareMatrices bits) + (.seq (BandPreparation.program.rename bank) (.seq initializeDirectory + (.seq evaluateTiles (.seq reconstruct restoreInput))))) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Reconstruct.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Reconstruct.lean new file mode 100644 index 0000000000..4c0b33e993 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Reconstruct.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.Product.SavedGroups + +/-! +# Actual reconstruction in original request order + +## Main results + +* `QueriedProduct.exec_reconstruct`: read the stored original indices and grouped answers, + then scatter every answer to its original request position, charging every instruction. + +The existing stable-position bijection proves that no earlier answer is overwritten. +Neither an inverse permutation nor a scatter execution is supplied to the program. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Actual reconstruction retains repeated input occurrences, input words and outside memory. +Only the designated original-order answer block is written. -/ +public theorem exec_reconstruct {w L m N : ℕ} + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (old : ℕ → ℕ) (x : Data 160) + (hp : Parameters L m N qs.length x) (hg : SavedGroups L m N qs.length old x.mem) + (horiginals : ∀ i : Fin qs.length, + old (Wanted.originalAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = i.val) + (hanswers : ∀ i : Fin qs.length, + x.mem (groupedBase L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) + (hfit : Fits w (wordBudget L m N qs.length)) : + ∃ y, ExecWithin w reconstruct x y (9 * qs.length + 3) ∧ + Parameters L m N qs.length y ∧ + (∀ i : Fin qs.length, y.mem (outputBase L m N qs.length + i.val) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ a, a < outputBase L m N qs.length ∨ scratchBase L m N qs.length ≤ a → + y.mem a = x.mem a) := by + let key := Wanted.tileKey L m N ∘ Wanted.inputPairs qs + let pos := CountingSort.position key qs.length + let I (z : Data 160) : Prop := Parameters L m N qs.length z ∧ z.regs index ≤ qs.length ∧ + (∀ i : Fin qs.length, pos i.val < z.regs index → + z.mem (outputBase L m N qs.length + i.val) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ a, a < outputBase L m N qs.length ∨ scratchBase L m N qs.length ≤ a → z.mem a = x.mem a) + have hout : outputBase L m N qs.length + qs.length = scratchBase L m N qs.length := rfl + have hgroup : groupedBase L m N qs.length + qs.length = outputBase L m N qs.length := rfl + have horig : originalBase L m N qs.length + qs.length = groupedBase L m N qs.length := rfl + have hfree : scratchBase L m N qs.length ≤ memoryEnd L m N qs.length := by + unfold memoryEnd stampBase + have h10 := Nat.zero_le (10 ^ L) + omega + let start := x.set index 0 + have hs : ExecWithin (B := .standard) w (ram[index ← #0]) x start 1 := by ram_eval [start] + have sp : Parameters L m N qs.length start := hp.set_work index (by decide) _ + have hstart : I start := ⟨sp, by simp [start], by simp [start, pos], by simp [start]⟩ + have hbody (z : Data 160) (hz : I z) (hc : (Cond.lt index queryCount).Holds z) : + ∃ u, ExecWithin w reconstructStep z u 7 ∧ I u ∧ + qs.length - u.regs index < qs.length - z.regs index := by + have hi : z.regs index < qs.length := by simpa only [Cond.holds_lt, hz.1.queryCount] using hc + obtain ⟨i, hip⟩ := (CountingSort.position_bijective key qs.length).surjective ⟨z.regs index, hi⟩ + have hpos := congrArg (fun j : Fin qs.length => j.val) hip + change pos i.val = z.regs index at hpos + have hreadIndex : z.mem (originalBase L m N qs.length + z.regs index) = i.val := by + rw [hz.2.2.2 _ (Or.inl (by omega)), hg.originals _ hi, ← hpos] + exact horiginals i + have hreadValue : z.mem (groupedBase L m N qs.length + z.regs index) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2) := by + rw [hz.2.2.2 _ (Or.inl (by omega)), ← hpos] + exact hanswers i + have fo : Fits w (originalBase L m N qs.length + z.regs index) := allocated_fits hfit (by omega) + have fg : Fits w (groupedBase L m N qs.length + z.regs index) := allocated_fits hfit (by omega) + have fy : Fits w (outputBase L m N qs.length + i.val) := allocated_fits hfit (by + have h := i.isLt + omega) + have fn : Fits w (z.regs index + 1) := allocated_fits hfit (by omega) + let answer := Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2) + let u := ((((((z.set address (originalBase L m N qs.length + z.regs index)).set + temporary i.val).set address (groupedBase L m N qs.length + z.regs index)).set value + answer).set address (outputBase L m N qs.length + i.val)).write + (outputBase L m N qs.length + i.val) answer).set index (z.regs index + 1) + have he : ExecWithin w reconstructStep z u 7 := by + ram_eval [reconstructStep, u, answer, hz.1.originals, hz.1.grouped, hz.1.output, + hz.1.one, hreadIndex, hreadValue, fo, fg, fy, fn, address, originals, index, + temporary, grouped, value, output, one] + have up : Parameters L m N qs.length u := + ((((((hz.1.set_work address (by decide) _).set_work temporary (by decide) _).set_work + address (by decide) _).set_work value (by decide) _).set_work address (by decide) _).write + _ _).set_work index (by decide) _ + have un : u.regs index = z.regs index + 1 := by simp [u] + refine ⟨u, he, ⟨up, by rw [un]; omega, ?_, ?_⟩, by rw [un]; omega⟩ + · intro j hj + rw [un] at hj + change Function.update z.mem (outputBase L m N qs.length + i.val) answer + (outputBase L m N qs.length + j.val) = _ + by_cases hij : j = i + · subst j + exact Function.update_self _ _ _ + · have hne : j.val ≠ i.val := fun heq => hij (Fin.ext heq) + rw [Function.update_of_ne (by omega)] + have hpne : pos j.val ≠ pos i.val := fun heq => hij + (CountingSort.position_injective key qs.length heq) + exact hz.2.2.1 j (by omega) + · intro a ha + change Function.update z.mem (outputBase L m N qs.length + i.val) answer a = _ + rw [Function.update_of_ne (by have h := i.isLt; rcases ha with ha | ha <;> omega)] + exact hz.2.2.2 a ha + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => qs.length - z.regs index) + hbody hstart + have hn : y.regs index = qs.length := by + have h := hy.2.1 + simp only [Cond.holds_lt, hy.1.queryCount] at hc + omega + refine ⟨y, ?_, hy.1, ?_, hy.2.2.2⟩ + · apply (hs.seq he).mono + have hzero : start.regs index = 0 := by simp [start] + rw [hzero, Nat.sub_zero] + omega + · intro i + apply hy.2.2.1 i + rw [hn] + exact CountingSort.position_lt key i.isLt + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Route.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Route.lean new file mode 100644 index 0000000000..7ff6d8b884 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Route.lean @@ -0,0 +1,73 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Exec +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Input + +/-! +# Actual routing in the product caller's register bank + +## Main results + +* `QueriedProduct.exec_wanted`: invoke the existing router on real coordinates while retaining + the caller's protected addresses, including its untouched original-input snapshot. + +No routing or permutation certificate is an input. The computed memory equations in this +internal interface are conclusions of the actual finite native program. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The existing actual router runs in the low bank and preserves every caller scalar. +Its stable permutation retains separate occurrences of duplicate requests. -/ +public theorem exec_wanted {w L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (x : Data 160) (hp : Parameters L m N qs.length x) + (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) + (hQ : x.mem 4 = qs.length) + (hinput : ∀ i < qs.length, + x.mem (5 + 2 * i) = (Wanted.inputPairs qs i).1 ∧ + x.mem (5 + 2 * i + 1) = (Wanted.inputPairs qs i).2) + (hfit : Fits w (wordBudget L m N qs.length)) : + ∃ y, ExecWithin w (Wanted.program.rename bank) x y (Wanted.timeBound L m N qs.length) ∧ + Parameters L m N qs.length y ∧ Wanted.Parameters L m N qs.length (y.comap bank) ∧ + (∀ i : Fin qs.length, + y.mem (Wanted.codeAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val ∧ + y.mem (Wanted.originalAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + i.val) ∧ + (∀ t ≤ numBands L m N ^ 2, + y.mem (Wanted.offsetAddress L m N qs.length + t) = + Nat.count (fun k => (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) k < t) qs.length) ∧ + y.mem (Wanted.offsetAddress L m N qs.length + numBands L m N ^ 2) = qs.length ∧ + (∀ a, a < Wanted.inputLimit qs.length ∨ Wanted.memoryLimit L m N qs.length ≤ a → + y.mem a = x.mem a) := by + obtain ⟨z, he, zp, zv, zo, zlast, zframe⟩ := Wanted.exec_program hm (Wanted.inputPairs qs) + (Wanted.inputPairs_lt qs) (x.comap bank) hL hmemory hN hQ hinput (producer_fits hfit).2 + let y := z.extend bank x + have hf (i : Fin 160) (hi : 96 ≤ i.val) : y.regs i = x.regs i := by + have hnot : ¬∃ j, bank j = i := by + rintro ⟨j, hj⟩ + have hv := congrArg (fun r : Fin 160 => r.val) hj + change j.val = i.val at hv + omega + exact Function.extend_apply' _ _ _ hnot + refine ⟨y, he.rename bank_injective, hp.reframe hf, ?_, ?_, zo, zlast, zframe⟩ + · simpa only [y, Data.comap_extend bank_injective] using zp + · intro i + change z.mem _ = _ ∧ z.mem _ = _ + simpa only [Wanted.inputPairs_get] using zv i.val i.isLt + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Routing.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Routing.lean new file mode 100644 index 0000000000..6d5f61239e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Routing.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.Product.Header +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Route +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.SavedGroups + +/-! +# Native routing preparation from original coordinates + +## Main results + +* `QueriedProduct.exec_prepareRouting`: run parameters, layout, original-input snapshot, + coordinate adaptation, actual routing and all protected group copies in sequence. + +The earlier router memory in the conclusion is an existential witness produced by its +actual execution. It is not a catalogue, layout or run certificate supplied by the caller. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The sum of the actual preparation phases, before matrix adaptation and encoding. -/ +@[expose] public def routingTimeBound (L m N Q : ℕ) : ℕ := + ((16 * L + 49) + ((6 * L + 16) * 2 ^ L + 14)) + 50 + + (7 * inputEnd m N Q + 10) + (14 * Q + 10) + Wanted.timeBound L m N Q + + (7 * numBands L m N ^ 2 + 14 * Q + 37) + +/-- Real original coordinates suffice to establish all routing invariants and a protected +copy of the entire original input. Header zero and outside memory may be arbitrary. -/ +public theorem exec_prepareRouting {w L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (x : Data 160) (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) + (hQ : x.mem 4 = qs.length) + (hqueries : ∀ i < qs.length, + x.mem (queryInputBase m N + 2 * i) = (Wanted.inputPairs qs i).1 ∧ + x.mem (queryInputBase m N + 2 * i + 1) = (Wanted.inputPairs qs i).2) + (hfit : Fits w (wordBudget L m N qs.length)) : + ∃ (y : Data 160) (old : ℕ → ℕ), + ExecWithin w prepareRouting x y (routingTimeBound L m N qs.length) ∧ + Parameters L m N qs.length y ∧ SavedGroups L m N qs.length old y.mem ∧ + (∀ i : Fin qs.length, + old (Wanted.codeAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val ∧ + old (Wanted.originalAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + i.val) ∧ + (∀ t ≤ numBands L m N ^ 2, + old (Wanted.offsetAddress L m N qs.length + t) = + Nat.count (fun k => (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) k < t) qs.length) ∧ + (∀ j < inputEnd m N qs.length, + y.mem (snapshotBase L m N qs.length + j) = x.mem j) ∧ + (∀ a, a < 5 ∨ groupedBase L m N qs.length ≤ a → y.mem a = x.mem a) := by + have hgeom : 5 ≤ inputEnd m N qs.length ∧ + 5 + 2 * qs.length ≤ inputEnd m N qs.length ∧ + inputEnd m N qs.length ≤ snapshotBase L m N qs.length ∧ + snapshotBase L m N qs.length + inputEnd m N qs.length ≤ groupedBase L m N qs.length := by + simp only [inputEnd, snapshotBase, groupedBase, originalBase, codeBase, offsetBase] + omega + obtain ⟨p, hp, pp, pm, _⟩ := exec_parameters hm x hL hmemory hN hfit + obtain ⟨l, hl, lp, lm⟩ := exec_layout p pp (by rw [pm]; exact hQ) hfit + have lmem : l.mem = x.mem := lm.trans pm + obtain ⟨s, hs, sp, sm⟩ := exec_saveInput l lp hfit + have ssnapshot (j : ℕ) (hj : j < inputEnd m N qs.length) : + s.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [sm, ArrayCopy.copyPrefix_read hj, Nat.zero_add, lmem] + have sframe (a : ℕ) (ha : a < snapshotBase L m N qs.length ∨ + snapshotBase L m N qs.length + inputEnd m N qs.length ≤ a) : s.mem a = x.mem a := by + rw [sm, ArrayCopy.copyPrefix_outside (by rcases ha with ha | ha <;> omega), lmem] + obtain ⟨q, hq, qp, qm⟩ := exec_prepareQueries s sp hfit + have qsnapshot (j : ℕ) (hj : j < inputEnd m N qs.length) : + q.mem (snapshotBase L m N qs.length + j) = x.mem j := by + rw [qm, ArrayCopy.copyPrefix_outside (by omega)] + exact ssnapshot j hj + have qframe (a : ℕ) (ha : a < 5 ∨ groupedBase L m N qs.length ≤ a) : q.mem a = x.mem a := by + rw [qm, ArrayCopy.copyPrefix_outside (by rcases ha with ha | ha <;> omega)] + exact sframe a (by rcases ha with ha | ha <;> omega) + have qinput (i : ℕ) (hi : i < qs.length) : + q.mem (5 + 2 * i) = (Wanted.inputPairs qs i).1 ∧ + q.mem (5 + 2 * i + 1) = (Wanted.inputPairs qs i).2 := by + have hr := ArrayCopy.copyPrefix_read (mem := s.mem) + (src := snapshotBase L m N qs.length + queryInputBase m N) (dst := 5) + (k := 2 * qs.length) (j := 2 * i) (by omega) + have hc := ArrayCopy.copyPrefix_read (mem := s.mem) + (src := snapshotBase L m N qs.length + queryInputBase m N) (dst := 5) + (k := 2 * qs.length) (j := 2 * i + 1) (by omega) + have hrow := ssnapshot (queryInputBase m N + 2 * i) (query_address_lt hi (by decide : 0 < 2)) + have hcol := ssnapshot (queryInputBase m N + 2 * i + 1) + (query_address_lt hi (by decide : 1 < 2)) + constructor + · rw [qm, hr] + simpa only [Nat.add_assoc] using hrow.trans (hqueries i hi).1 + · rw [qm, show 5 + 2 * i + 1 = 5 + (2 * i + 1) by omega, hc] + simpa only [Nat.add_assoc] using hcol.trans (hqueries i hi).2 + obtain ⟨w, hw, wp, wparams, wv, wo, _, wf⟩ := exec_wanted hm qs q qp + ((qframe 1 (Or.inl (by decide))).trans hL) + ((qframe 2 (Or.inl (by decide))).trans hmemory) + ((qframe 3 (Or.inl (by decide))).trans hN) + ((qframe 4 (Or.inl (by decide))).trans hQ) qinput hfit + obtain ⟨y, hy, yp, yo, yc, yi, yf⟩ := exec_saveGroups w wp wparams hfit + have hlow : Wanted.memoryLimit L m N qs.length ≤ snapshotBase L m N qs.length := + (producers_le_snapshot L m N qs.length).2.1 + have hoff : snapshotBase L m N qs.length + inputEnd m N qs.length = + offsetBase L m N qs.length := rfl + have hg : originalBase L m N qs.length + qs.length = groupedBase L m N qs.length := rfl + refine ⟨y, w.mem, ?_, yp, ⟨yo, yc, yi⟩, wv, wo, ?_, ?_⟩ + · apply (hp.seq (hl.seq (hs.seq (hq.seq (hw.seq hy))))).mono + unfold routingTimeBound + omega + · intro j hj + rw [yf _ (Or.inl (by omega)), wf _ (Or.inr (by omega))] + exact qsnapshot j hj + · intro a ha + rw [yf _ (by rcases ha with ha | ha <;> omega), wf _ (by + rcases ha with ha | ha + · exact Or.inl (by unfold Wanted.inputLimit; omega) + · exact Or.inr (by omega))] + exact qframe a ha + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/SavedGroups.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/SavedGroups.lean new file mode 100644 index 0000000000..fc6c0ff9a8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/SavedGroups.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.Product.Groups +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Groups + +/-! +# Memory invariants for the saved routing arrays + +## Main definitions + +* `QueriedProduct.SavedGroups`: protected words agree with an earlier actual router state. + +## Main results + +* `QueriedProduct.SavedGroups.reframe`: kernel and producer frames retain the saved arrays. +* `QueriedProduct.router_code_lt`: executed stable scatter gives a valid query code at every + grouped position, including duplicate requests. +* `QueriedProduct.SavedGroups.span_bounds`: actual saved offsets form admitted kernel spans. + +The earlier memory is a proof reference to an executed state, not program input. The native +copy theorem establishes these equalities before any low-memory reuse occurs. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Values copied from the actual router state into protected caller-owned arrays. -/ +public structure SavedGroups (L m N Q : ℕ) (old μ : ℕ → ℕ) : Prop where + /-- All actual offsets, including the terminal offset. -/ + offsets : ∀ t ≤ numBands L m N ^ 2, + μ (offsetBase L m N Q + t) = old (Wanted.offsetAddress L m N Q + t) + /-- All actual grouped codes. -/ + codes : ∀ j < Q, μ (codeBase L m N Q + j) = old (Wanted.codeAddress L m N Q + j) + /-- All actual original-position words. -/ + originals : ∀ j < Q, + μ (originalBase L m N Q + j) = old (Wanted.originalAddress L m N Q + j) + +/-- Preserving the protected readonly interval preserves the saved routing arrays. -/ +public theorem SavedGroups.reframe {L m N Q : ℕ} {old μ ν : ℕ → ℕ} + (h : SavedGroups L m N Q old μ) + (hf : ∀ a, offsetBase L m N Q ≤ a → a < groupedBase L m N Q → ν a = μ a) : + SavedGroups L m N Q old ν := by + have hc : offsetBase L m N Q + numBands L m N ^ 2 + 1 = codeBase L m N Q := by + unfold codeBase + omega + have ho : codeBase L m N Q + Q = originalBase L m N Q := rfl + have hg : originalBase L m N Q + Q = groupedBase L m N Q := rfl + constructor + · intro t ht + exact (hf _ (by omega) (by omega)).trans (h.offsets t ht) + · intro j hj + exact (hf _ (by omega) (by omega)).trans (h.codes j hj) + · intro j hj + exact (hf _ (by omega) (by omega)).trans (h.originals j hj) + +/-- Stable scatter covers every grouped cell, so every stored code is a valid query index. +The proof uses the existing permutation theorem rather than a supplied inverse table. -/ +public theorem router_code_lt {L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (old : ℕ → ℕ) + (hvalues : ∀ i : Fin qs.length, + old (Wanted.codeAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val) + (j : ℕ) (hj : j < qs.length) : + old (Wanted.codeAddress L m N qs.length + j) < 10 ^ L := by + obtain ⟨i, hi⟩ := (CountingSort.position_bijective + (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length).surjective ⟨j, hj⟩ + have hp := congrArg (fun k : Fin qs.length => k.val) hi + change CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i = j at hp + have hv := hvalues i + rw [hp] at hv + rw [hv] + exact (PrunedQuery.queryIndex _).isLt + +/-- The actual protected offset words satisfy the kernel's monotone span and count bounds. +These inequalities follow from the executed prefix-count meanings, not input certificates. -/ +public theorem SavedGroups.span_bounds {L m N Q : ℕ} {old μ : ℕ → ℕ} + (h : SavedGroups L m N Q old μ) (key : ℕ → ℕ) + (hoffsets : ∀ t ≤ numBands L m N ^ 2, + old (Wanted.offsetAddress L m N Q + t) = Nat.count (fun k => key k < t) Q) + {t : ℕ} (ht : t < numBands L m N ^ 2) : + μ (offsetBase L m N Q + t) ≤ μ (offsetBase L m N Q + t + 1) ∧ + μ (offsetBase L m N Q + t + 1) ≤ Q := by + rw [show offsetBase L m N Q + t + 1 = offsetBase L m N Q + (t + 1) by omega, + h.offsets t ht.le, h.offsets (t + 1) (by omega), hoffsets t ht.le, + hoffsets (t + 1) (by omega)] + exact ⟨Nat.count_mono_left (fun k _ hk => by omega), Nat.count_le _⟩ + +/-- A typed view of the actual saved code span requests exactly the original finite tile +set. This uses the existing router's span theorem; the native caller constructs the view +by decoding its real words, rather than receiving a query-set or routing certificate. -/ +public theorem SavedGroups.span_queries {L m N : ℕ} (hm : m ≤ L) + (qs : List (Fin N × Fin N)) {old μ : ℕ → ℕ} (h : SavedGroups L m N qs.length old μ) + (hvalues : ∀ i : Fin qs.length, + old (Wanted.codeAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val) + (hoffsets : ∀ t ≤ numBands L m N ^ 2, + old (Wanted.offsetAddress L m N qs.length + t) = + Nat.count (fun k => (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) k < t) qs.length) + (T : ℕ × ℕ) (hT : T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N) (a b : ℕ) + (ha : μ (offsetBase L m N qs.length + (T.1 * numBands L m N + T.2)) = a) + (hb : μ (offsetBase L m N qs.length + (T.1 * numBands L m N + T.2 + 1)) = b) + (queries : Fin (b - a) → OutStr L) + (hinput : ∀ i, μ (codeBase L m N qs.length + (a + i.val)) = + (PrunedQuery.queryIndex (queries i)).val) : + Finset.univ.image queries = wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2 := by + have hband : T.1 < numBands L m N ∧ T.2 < numBands L m N := by + simpa only [Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles, + Finset.mem_product, Finset.mem_range] using hT + have ht : T.1 * numBands L m N + T.2 < numBands L m N ^ 2 := by + nlinarith only [Nat.mul_le_mul_right (numBands L m N) (Nat.succ_le_of_lt hband.1), hband.2] + have has : old (Wanted.offsetAddress L m N qs.length + + (T.1 * numBands L m N + T.2)) = a := (h.offsets _ ht.le).symm.trans ha + have hbs : old (Wanted.offsetAddress L m N qs.length + + (T.1 * numBands L m N + T.2 + 1)) = b := + (h.offsets _ (by omega)).symm.trans hb + have habQ : a ≤ b ∧ b ≤ qs.length := by + have hs := h.span_bounds (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) hoffsets ht + simpa only [ha, show offsetBase L m N qs.length + + (T.1 * numBands L m N + T.2) + 1 = offsetBase L m N qs.length + + (T.1 * numBands L m N + T.2 + 1) by omega, hb] using hs + have hword (i : Fin (b - a)) : old (Wanted.codeAddress L m N qs.length + (a + i.val)) = + (PrunedQuery.queryIndex (queries i)).val := by + rw [← h.codes (a + i.val) (by have hi := i.isLt; omega)] + exact hinput i + have hspan := Wanted.code_span_eq_wantedStrings hm qs old T hT hvalues hoffsets + rw [has, hbs] at hspan + have hinj : Function.Injective (fun v : OutStr L => (PrunedQuery.queryIndex v).val) := by + intro u v he + simpa only [PrunedQuery.queryAt_queryIndex] using + congrArg PrunedQuery.queryAt (Fin.ext he) + ext w + constructor + · intro hw + obtain ⟨i, _, rfl⟩ := Finset.mem_image.mp hw + have hcode : (PrunedQuery.queryIndex (queries i)).val ∈ + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2).image + (fun w => (PrunedQuery.queryIndex w).val) := by + rw [← hspan] + exact Finset.mem_image.mpr ⟨a + i.val, Finset.mem_Ico.mpr + ⟨by omega, by have hi := i.isLt; omega⟩, hword i⟩ + obtain ⟨v, hv, he⟩ := Finset.mem_image.mp hcode + exact hinj he ▸ hv + · intro hw + have hcode : (PrunedQuery.queryIndex w).val ∈ + (Finset.Ico a b).image (fun j => old (Wanted.codeAddress L m N qs.length + j)) := by + rw [hspan] + exact Finset.mem_image.mpr ⟨w, hw, rfl⟩ + obtain ⟨j, hj, he⟩ := Finset.mem_image.mp hcode + have hj' := Finset.mem_Ico.mp hj + let i : Fin (b - a) := ⟨j - a, by omega⟩ + have hai : a + i.val = j := by dsimp [i]; omega + have hread := hword i + rw [hai] at hread + exact Finset.mem_image.mpr ⟨i, Finset.mem_univ _, hinj (hread.symm.trans he)⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Thin.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Thin.lean new file mode 100644 index 0000000000..634a55cd65 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Thin.lean @@ -0,0 +1,111 @@ +/- +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.Product.Width + +/-! +# Quantitative saving for actual native queried products + +## Main results + +* `QueriedProduct.thin_timeBound`: the native charge, including halt, has the proved thin + saving plus an explicit term for every original query occurrence. +* `QueriedProduct.thin_timeBound_sparse`: an occurrence-count sparsity condition absorbs + that term and gives the complete charged `N² / D^(1/18)` bound, with a depth factor. + +These results compose the actual native charge with the existing finite encoding/tile/leaf +bounds from [AlmanWilliams2026, Section 2]. The occurrence hypothesis in the second result is +stronger than the source's distinct-position cardinality condition when requests repeat. +No sparse solver or running-time assertion is assumed. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The source's distinct-position sparsity condition retains an explicit charge for list +multiplicity. Every actual copy, conversion, producer, tile, epoch and report is included. -/ +public theorem thin_timeBound (m N : ℕ) (qs : List (Fin N × Fin N)) + (hN : D m ^ 18 ≤ N) (hW : qs.toFinset.card * 2 ^ m ≤ N ^ 2) : + ((timeBound (show m ≤ 19 * m by omega) qs + 1 : ℕ) : ℝ) ≤ + 65536 * ((19 * m + 1 : ℕ) : ℝ) * + (65 * ((D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2) + qs.length) := by + have hm : m ≤ 19 * m := by omega + have hNpos : 0 < N := (Nat.pow_pos (show 0 < D m from Nat.pow_pos (by decide))).trans_le hN + have hB := numBands_pos hm hNpos + have hH : 10 ^ (19 * m) ≤ numBands (19 * m) m N * 10 ^ (19 * m) := + Nat.le_mul_of_pos_left _ hB + have hprep := preparation_shape_le hm hNpos + have henc := encodings_total_le m N hN + have htiles := minutiae_total_le m N qs.toFinset hN hW + have hleaves := total_leaves_le (MaskTable.layout hm) qs.toFinset hN hW + have hc : (Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles (19 * m) m N).card = + numBands (19 * m) m N ^ 2 := by + simp only [Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles, + Finset.card_product, Finset.card_range, pow_two] + rw [hc] at htiles + have hprepR : ((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 hprep + have hHR : ((10 ^ (19 * m) : ℕ) : ℝ) ≤ + ((numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) := by exact_mod_cast hH + have hshape : ((1 + N * D m + qs.length + 2 ^ (19 * m) + + (numBands (19 * m) m N + 1) * 10 ^ (19 * m) + numBands (19 * m) m N ^ 2 + + ∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles (19 * m) m N, + (Cslib.Algorithms.MatrixMultiplication.Schonhage.leaves + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)).card : ℕ) : ℝ) ≤ + 65 * ((D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2) + qs.length := by + have hnonneg : 0 ≤ (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by positivity + push_cast at hprepR hHR henc htiles hleaves ⊢ + nlinarith only [hprepR, hHR, henc, htiles, hleaves, hnonneg, + Nat.cast_nonneg (α := ℝ) (K (19 * m) m), Nat.cast_nonneg (α := ℝ) qs.toFinset.card, + Nat.cast_nonneg (α := ℝ) (N * D m), Nat.cast_nonneg (α := ℝ) (2 ^ (19 * m))] + have hcost := timeBound_le hm qs + have hcostR : ((timeBound hm qs + 1 : ℕ) : ℝ) ≤ + 65536 * ((19 * m + 1 : ℕ) : ℝ) * + ((1 + N * D m + qs.length + 2 ^ (19 * m) + + (numBands (19 * m) m N + 1) * 10 ^ (19 * m) + numBands (19 * m) m N ^ 2 + + ∑ T ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles (19 * m) m N, + (Cslib.Algorithms.MatrixMultiplication.Schonhage.leaves + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2)).card : ℕ) : ℝ) := by + exact_mod_cast hcost + exact hcostR.trans (mul_le_mul_of_nonneg_left hshape (by positivity)) + +/-- A bound on all original occurrences, rather than only distinct coordinates, absorbs +the actual list-input/reporting charge. The final halt is included. -/ +public theorem thin_timeBound_sparse (m N : ℕ) (qs : List (Fin N × Fin N)) + (hN : D m ^ 18 ≤ N) (hQ : qs.length * 2 ^ m ≤ N ^ 2) : + ((timeBound (show m ≤ 19 * m by omega) qs + 1 : ℕ) : ℝ) ≤ + 4325376 * ((19 * m + 1 : ℕ) : ℝ) * (N : ℝ) ^ 2 / (D m : ℝ) ^ (1 / 18 : ℝ) := by + have hW : qs.toFinset.card * 2 ^ m ≤ N ^ 2 := + (Nat.mul_le_mul_right _ (List.toFinset_card_le qs)).trans hQ + have hQ' : (qs.length : ℝ) * (2 : ℝ) ^ m ≤ (N : ℝ) ^ 2 := by exact_mod_cast hQ + have hp : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (2 : ℝ) ^ m := by + rw [← Real.rpow_natCast] + exact Real.rpow_le_rpow_of_exponent_le (by norm_num) + (by have := Nat.cast_nonneg (α := ℝ) m; linarith) + have hlen : (qs.length : ℝ) ≤ (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by + rw [← saving_eq_D_rpow] + calc (qs.length : ℝ) = saving m * (qs.length * (2 : ℝ) ^ ((m : ℝ) / 9)) := by + rw [mul_left_comm, saving_mul_two_rpow, mul_one] + _ ≤ saving m * (qs.length * (2 : ℝ) ^ m) := + mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_left hp (Nat.cast_nonneg _)) + (saving_pos m).le + _ ≤ saving m * (N : ℝ) ^ 2 := mul_le_mul_of_nonneg_left hQ' (saving_pos m).le + have h := thin_timeBound m N qs hN hW + have hfinal : ((timeBound (show m ≤ 19 * m by omega) qs + 1 : ℕ) : ℝ) ≤ + 4325376 * ((19 * m + 1 : ℕ) : ℝ) * + ((D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2) := by + have hL : 0 ≤ ((19 * m + 1 : ℕ) : ℝ) := Nat.cast_nonneg _ + nlinarith only [h, hlen, mul_nonneg hL (sub_nonneg.mpr hlen)] + rw [Real.rpow_neg (by positivity)] at hfinal + simpa only [div_eq_mul_inv, mul_assoc, mul_left_comm, mul_comm] using hfinal + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/ThinWidth.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/ThinWidth.lean new file mode 100644 index 0000000000..09a79d9c55 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/ThinWidth.lean @@ -0,0 +1,216 @@ +/- +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.Product.Thin + +/-! +# Source-regime logarithmic width for actual queried products + +## Main results + +* `QueriedProduct.thin_envelopes`: original dimensions give polynomial magnitude/address + envelopes and an explicit polynomial allocation bound. +* `QueriedProduct.thin_admissionWidth_le`: a positive constant times the logarithm of original + dimension, request count and entry magnitude suffices in the source's thin regime. + +These bounds concern the real native program and its actual allocation formulas. They assume +neither intermediate-state fits nor a polynomial-space certificate. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The source regime controls every original power, computed band count and address term. +The deliberately loose integer constants leave a uniform logarithmic-width margin. -/ +public theorem thin_envelopes (m N Q A : ℕ) (hN : D m ^ 18 ≤ N) : + magnitudeBound (19 * m) A + wordBudget (19 * m) m N Q + 1 ≤ + 2 ^ 40 * (N + Q + A + 2) ^ 10 ∧ + memoryEnd (19 * m) m N Q ≤ 128 * (N + Q + A + 2) ^ 4 := by + let S := N + Q + A + 2 + let Z := S ^ 2 + have hs : 2 ≤ S := by dsimp only [S]; omega + have hz : 1 ≤ Z := Nat.one_le_pow _ _ (by omega) + have hsz : S ≤ Z := Nat.le_self_pow (by decide) _ + have hNN : N ≤ S := by dsimp only [S]; omega + have hN0 : N ≤ Z := hNN.trans hsz + have hQ0 : Q ≤ Z := (show Q ≤ S by dsimp only [S]; omega).trans hsz + have hA0 : A ≤ Z := (show A ≤ S by dsimp only [S]; omega).trans hsz + have hp : 10 ^ (19 * m) ≤ N ^ 2 := by + calc 10 ^ (19 * m) = (10 ^ 19) ^ m := by rw [pow_mul] + _ ≤ (4 ^ 36) ^ m := Nat.pow_le_pow_left (by decide) m + _ = (D m ^ 18) ^ 2 := by + simp only [D, ← pow_mul] + congr 1 + omega + _ ≤ N ^ 2 := Nat.pow_le_pow_left hN 2 + have h10 : 10 ^ (19 * m) ≤ Z := hp.trans (Nat.pow_le_pow_left hNN 2) + have h2 : 2 ^ (19 * m) ≤ Z := (Nat.pow_le_pow_left (by decide : 2 ≤ 10) _).trans h10 + have h7 : 7 ^ (19 * m) ≤ Z := (Nat.pow_le_pow_left (by decide : 7 ≤ 10) _).trans h10 + have hL : 19 * m ≤ Z := (Nat.lt_pow_self (by decide : 1 < 10)).le.trans h10 + have hD : D m ≤ Z := ((Nat.le_self_pow (by decide : 18 ≠ 0) _).trans hN).trans hN0 + have hm : m ≤ 19 * m := by omega + have hbandpos : 0 < bandSize (19 * m) m := Nat.mul_pos (K0_pos hm) (N0_pos _ _) + have hbands : numBands (19 * m) m N ≤ N := by + apply Nat.le_of_lt_succ + change (N + bandSize (19 * m) m - 1) / bandSize (19 * m) m < N + 1 + apply (Nat.div_lt_iff_lt_mul hbandpos).mpr + have hmul := Nat.le_mul_of_pos_right N hbandpos + rw [Nat.add_mul, Nat.one_mul] + omega + have hB : numBands (19 * m) m N ≤ Z := hbands.trans hN0 + have hall : PrunedQuery.epochAllowance (19 * m) ≤ 2 * Z := by + unfold PrunedQuery.epochAllowance + omega + let bi := 4 + 2 * (Z * Z) + let bc := (bi + (2 + Z + Z) + Z) + bi + Z + Z + let be := bc + 2 * Z * Z + let we := 6 + 4 * Z + 2 * Z + 3 * Z ^ 2 + let ii := 5 + 2 * (Z * Z) + 2 * Z + let ss := be + we + ii + let oo := ss + ii + let cc := oo + Z ^ 2 + 1 + let ou := cc + Z + Z + Z + let ff := ou + Z + let ee := ff + 4 * (Z + 1) + 2 * (Z + 1) * Z + Z + let ep := Z ^ 2 * (1 + 2 * Z) + have hbc : BandPreparation.catalogueBase (19 * m) m N ≤ bc := by + dsimp only [bc, bi] + simp only [BandPreparation.catalogueBase, BandPreparation.tableBase, + BandPreparation.inverseBase, BandPreparation.snapshotBase, BandPreparation.inputEnd, + Encoding.scratchBase, Encoding.outputBase] + gcongr + have hbe : BandPreparation.memoryEnd (19 * m) m N ≤ be := by + dsimp only [be] + unfold BandPreparation.memoryEnd + gcongr + have hwe : Wanted.memoryLimit (19 * m) m N Q ≤ we := by + dsimp only [we] + rw [Wanted.memoryLimit_eq] + gcongr + have hii : inputEnd m N Q ≤ ii := by dsimp only [ii, inputEnd]; gcongr + have hss : snapshotBase (19 * m) m N Q ≤ ss := by + dsimp only [ss, snapshotBase] + gcongr + have hoo : offsetBase (19 * m) m N Q ≤ oo := by + dsimp only [oo, offsetBase] + gcongr + have hcc : codeBase (19 * m) m N Q ≤ cc := by + dsimp only [cc, codeBase] + gcongr + have hou : outputBase (19 * m) m N Q ≤ ou := by + dsimp only [ou, outputBase, groupedBase, originalBase] + gcongr + have hff : scratchBase (19 * m) m N Q ≤ ff := by + dsimp only [ff, scratchBase] + gcongr + have hee : memoryEnd (19 * m) m N Q ≤ ee := by + dsimp only [ee, memoryEnd, stampBase] + gcongr + have hep : epochBound (19 * m) m N ≤ ep := by + dsimp only [ep, epochBound] + gcongr + let bb := 256 * (Z + Z + Z * Z + Z + Z + 2 * Z * Z + 1) + let tab := Z * 2 + 5 + Z + let rb := 64 * (Z + Z + tab + Z ^ 2 + Z + Z + 1) + let wb := 256 * (bb + rb + we + Z + Z + Z + Z ^ 2 + Z + Z + 16) + let kb := (256 * (Z + 1) * Z + 32 * Z + 512) + + ((8 * ff + 64 * (Z + 1) * Z + be + be + 2 * Z + (ep + 1) + 2 * Z + 128) + + 4 * (cc + ou + Z + 1)) + let ab := 1024 * (bb + wb + kb + ee + ii + Z + Z + Z + 16) + let mg := Z * Z + Z * Z + Z * ((Z * Z) * (Z * Z)) + Z + have hbb : BandPreparation.wordBudget (19 * m) m N ≤ bb := by + dsimp only [bb, BandPreparation.wordBudget] + gcongr + have htab : Wanted.tableAddress (19 * m) Q ≤ tab := by + dsimp only [tab, Wanted.tableAddress, Wanted.inputLimit] + gcongr + have hrb : Wanted.routeBudget (19 * m) m N (Wanted.tableAddress (19 * m) Q) ≤ rb := by + dsimp only [rb, Wanted.routeBudget] + gcongr + have hwb : Wanted.wordBudget (19 * m) m N Q ≤ wb := by + dsimp only [wb, Wanted.wordBudget] + gcongr + have hkb : PrunedQuery.kernelBudget (19 * m) (scratchBase (19 * m) m N Q) + (BandPreparation.memoryEnd (19 * m) m N) (BandPreparation.memoryEnd (19 * m) m N) + (codeBase (19 * m) m N Q) (outputBase (19 * m) m N Q) Q (epochBound (19 * m) m N) ≤ kb := by + dsimp only [kb, PrunedQuery.kernelBudget, PrunedQuery.addressBound, + PrunedQuery.evaluationBudget, PrunedQuery.nodeBudget] + gcongr + have hab : wordBudget (19 * m) m N Q ≤ ab := by + dsimp only [ab, wordBudget] + gcongr + have hmg : magnitudeBound (19 * m) A ≤ mg := by + dsimp only [mg, magnitudeBound, PrunedQuery.magnitudeBound] + gcongr + have h0 : 1 ≤ Z ^ 5 := Nat.one_le_pow _ _ hz + have h1 : Z ≤ Z ^ 5 := Nat.le_self_pow (by decide) _ + have h2' : Z ^ 2 ≤ Z ^ 5 := Nat.pow_le_pow_right hz (by decide) + have h3 : Z ^ 3 ≤ Z ^ 5 := Nat.pow_le_pow_right hz (by decide) + have htotal : mg + ab + 1 ≤ 2 ^ 40 * Z ^ 5 := by + dsimp only [mg, ab, wb, bb, rb, tab, kb, ee, ff, ou, cc, oo, ss, be, bc, bi, we, ii, ep] + nlinarith only [h0, h1, h2', h3] + have hmem : ee ≤ 128 * Z ^ 2 := by + have hz2 := Nat.le_self_pow (by decide : 2 ≠ 0) Z + have h02 : 1 ≤ Z ^ 2 := Nat.one_le_pow _ _ hz + dsimp only [ee, ff, ou, cc, oo, ss, be, bc, bi, we, ii] + nlinarith only [hz2, h02] + constructor + · have h := (Nat.add_le_add_right (Nat.add_le_add hmg hab) 1).trans htotal + simpa only [Z, S, ← pow_mul, Nat.reduceMul] using h + · have h := hee.trans hmem + simpa only [Z, S, ← pow_mul, Nat.reduceMul] using h + +/-- A fixed positive logarithmic-width coefficient suffices throughout the thin regime. -/ +public theorem thin_admissionWidth_le (m N Q A : ℕ) (hN : D m ^ 18 ≤ N) : + admissionWidth (19 * m) m N Q A ≤ 128 * Nat.clog 2 (N + Q + A + 2) := by + let S := N + Q + A + 2 + have hs : 1 < S := by dsimp only [S]; omega + have hl := Nat.clog_pos (by decide : 1 < (2 : ℕ)) hs + have hp := Nat.le_pow_clog (by decide : 1 < (2 : ℕ)) S + have he := (thin_envelopes m N Q A hN).1 + have hpow : magnitudeBound (19 * m) A + wordBudget (19 * m) m N Q + 1 ≤ + 2 ^ (40 + 10 * Nat.clog 2 S) := by + calc _ ≤ 2 ^ 40 * S ^ 10 := he + _ ≤ 2 ^ 40 * (2 ^ Nat.clog 2 S) ^ 10 := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hp 10) + _ = _ := by rw [pow_add, ← pow_mul, Nat.mul_comm (Nat.clog 2 S) 10] + have hc := (Nat.clog_le_iff_le_pow (by decide : 1 < (2 : ℕ))).mpr hpow + unfold admissionWidth + dsimp only [S] at hc hl + omega + +/-- Complete initialized native thin-product execution at a fixed positive logarithmic-width +threshold. Every original occurrence is answered, including duplicates, and halt is charged. +The occurrence-count condition is stated explicitly rather than replaced by set cardinality. -/ +public theorem runs_thin {W m N A : ℕ} (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) (hQ : qs.length * 2 ^ m ≤ N ^ 2) + (hX : ∀ i k, |X i k| ≤ (A : ℤ)) (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hbits : bits = true → (∀ i k, 0 ≤ X i k) ∧ (∀ k j, 0 ≤ Y k j)) + (hW : 128 * Nat.clog 2 (N + qs.length + A + 2) ≤ W) : + InputFits W (input bits (19 * m) X Y qs) ∧ + ∃ y n, + Runs W ((program bits).program output) (State.init (input bits (19 * m) X Y qs)) + ⟨(program bits).size, y⟩ n ∧ + Executes W ((program bits).program output) (State.init (input bits (19 * m) X Y qs)) + (outputBase (19 * m) m N qs.length) (n + 1) ∧ + ((n + 1 : ℕ) : ℝ) ≤ 4325376 * ((19 * m + 1 : ℕ) : ℝ) * (N : ℝ) ^ 2 / + (D m : ℝ) ^ (1 / 18 : ℝ) ∧ y.Bounded W ∧ + (∀ i : Fin qs.length, y.mem (outputBase (19 * m) m N qs.length + i.val) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ a, ¬(inputEnd m N qs.length ≤ a ∧ a < memoryEnd (19 * m) m N qs.length) → + y.mem a = (State.init (input bits (19 * m) X Y qs) : State 160).data.mem a) := by + obtain ⟨hi, y, n, hn, hr, he, hy, hv, hf⟩ := runs_wide (show m ≤ 19 * m by omega) + bits X Y qs hX hY hbits ((thin_admissionWidth_le m N qs.length A hN).trans hW) + have hnR : ((n + 1 : ℕ) : ℝ) ≤ (timeBound (show m ≤ 19 * m by omega) qs + 1 : ℕ) := by + exact_mod_cast Nat.add_le_add_right hn 1 + exact ⟨hi, y, n, hr, he, hnR.trans (thin_timeBound_sparse m N qs hN hQ), hy, hv, hf⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Tile.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Tile.lean new file mode 100644 index 0000000000..019118421a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Tile.lean @@ -0,0 +1,243 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Arguments +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.Catalogue +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Product.SavedGroups +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Kernel +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Magnitude + +/-! +# One actual tile call on produced arrays and computed routing words + +## Main results + +* `QueriedProduct.exec_tileStep`: load the actual span, construct the kernel ABI, execute the + native pruned kernel and propagate its returned epoch while advancing the real tile index. + +The proof decodes typed queries from actual saved words; they are not program input. +The finite request set, encoding arrays, frames, arithmetic +fits and call charge are obtained from the actual producer contracts. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- One charged native tile, with the actual query set and a reusable timestamp directory. +The final memory witness contains every reported pruned value and is the same state whose +epoch is passed to the next tile. Its answers are the original signed matrix product. -/ +public theorem exec_tileStep {w L m N A t E : ℕ} (hw : 0 < w) (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (old : ℕ → ℕ) (x : Data 160) + (hp : Parameters L m N qs.length x) (hx : x.regs index = t) + (ht : t < numBands L m N ^ 2) (hE : x.regs epoch = E) (hEbound : E ≤ epochBound L m N) + (hg : SavedGroups L m N qs.length old x.mem) (he : EncodedBands hm X Y x.mem) + (hvalues : ∀ i : Fin qs.length, + old (Wanted.codeAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val) + (hoffsets : ∀ t ≤ numBands L m N ^ 2, + old (Wanted.offsetAddress L m N qs.length + t) = + Nat.count (fun k => (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) k < t) qs.length) + (hmarks : ∀ j < 10 ^ L, x.mem (stampBase L m N qs.length + j) ≤ E) + (hX : ∀ i k, |X i k| ≤ (A : ℤ)) (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) : + let a := x.mem (offsetBase L m N qs.length + t) + let b := x.mem (offsetBase L m N qs.length + t + 1) + let β := t / numBands L m N + let γ := t % numBands L m N + ∃ y, + ExecWithin (w + 2) tileStep x y + (4 * L + 29 * (b - a) + + PrunedQuery.traceWeight (wantedStrings (MaskTable.layout hm) qs.toFinset β γ) + 64) ∧ + Parameters L m N qs.length y ∧ y.regs index = t + 1 ∧ + E + 1 ≤ y.regs epoch ∧ y.regs epoch ≤ E + 1 + PrunedQuery.epochAllowance L ∧ + SavedGroups L m N qs.length old y.mem ∧ EncodedBands hm X Y y.mem ∧ + (∀ j < 10 ^ L, y.mem (stampBase L m N qs.length + j) ≤ y.regs epoch) ∧ + (∀ i : Fin qs.length, + (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) i.val = t → + y.mem (groupedBase L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ p, ¬(groupedBase L m N qs.length + a ≤ p ∧ + p < groupedBase L m N qs.length + b) → + ¬(scratchBase L m N qs.length ≤ p ∧ p < memoryEnd L m N qs.length) → + y.mem p = x.mem p) := by + dsimp only + let B := numBands L m N + let a := x.mem (offsetBase L m N qs.length + t) + let b := x.mem (offsetBase L m N qs.length + t + 1) + let β := t / B + let γ := t % B + let pa := BandPreparation.catalogueBase L m N + β * 10 ^ L + let pb := BandPreparation.catalogueBase L m N + (B + γ) * 10 ^ L + have habQ : a ≤ b ∧ b ≤ qs.length := + hg.span_bounds (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) hoffsets ht + have hB : 0 < B := by dsimp only [B] at *; nlinarith only [ht, Nat.zero_le t] + have hβ : β < B := (Nat.div_lt_iff_lt_mul hB).mpr (by simpa [B, pow_two] using ht) + have hγ : γ < B := Nat.mod_lt t hB + have hT : (β, γ) ∈ Cslib.Algorithms.MatrixMultiplication.Schonhage.tiles L m N := + Finset.mem_product.mpr ⟨Finset.mem_range.mpr hβ, Finset.mem_range.mpr hγ⟩ + have htnum : β * numBands L m N + γ = t := by + simpa only [β, γ, B, Nat.mul_comm] using Nat.div_add_mod t B + have hcode (j : ℕ) (hj : j < qs.length) : + x.mem (codeBase L m N qs.length + j) < 10 ^ L := by + rw [hg.codes j hj] + exact router_code_lt hm qs old hvalues j hj + let queries : Fin (b - a) → OutStr L := fun i => PrunedQuery.queryAt + ⟨x.mem (codeBase L m N qs.length + (a + i.val)), hcode _ (by have hi := i.isLt; omega)⟩ + have hinput (i : Fin (b - a)) : x.mem (codeBase L m N qs.length + (a + i.val)) = + (PrunedQuery.queryIndex (queries i)).val := by + simp only [queries, PrunedQuery.queryIndex_queryAt] + have hset : Finset.univ.image queries = + wantedStrings (MaskTable.layout hm) qs.toFinset β γ := + hg.span_queries hm qs hvalues hoffsets (β, γ) hT a b + (by rw [htnum]) + (by rw [htnum]; rfl) queries hinput + obtain ⟨arg, har, arp, arm, ari, _, arL, arA, arB, arq, arQ, aro, arf, arE⟩ := + exec_tileArguments x hp hx ht (rfl : x.mem (offsetBase L m N qs.length + t) = a) + (rfl : x.mem (offsetBase L m N qs.length + t + 1) = b) habQ.1 habQ.2 hfit + change (arg.comap bank).regs PrunedQuery.arrayA = pa at arA + change (arg.comap bank).regs PrunedQuery.arrayB = pb at arB + change (arg.comap bank).regs PrunedQuery.queries = codeBase L m N qs.length + a at arq + change (arg.comap bank).regs PrunedQuery.queryCount = b - a at arQ + change (arg.comap bank).regs PrunedQuery.output = groupedBase L m N qs.length + a at aro + have hpab : pa + 10 ^ L ≤ BandPreparation.memoryEnd L m N ∧ + pb + 10 ^ L ≤ BandPreparation.memoryEnd L m N := by + have hl := BandPreparation.catalogue_block_le false hβ + have hr := BandPreparation.catalogue_block_le true hγ + simpa only [BandPreparation.catalogueAddress, Bool.false_eq_true, ↓reduceIte, + Nat.zero_add, Nat.add_zero, pa, pb, B] using And.intro hl hr + have hlow : BandPreparation.memoryEnd L m N ≤ snapshotBase L m N qs.length ∧ + snapshotBase L m N qs.length ≤ offsetBase L m N qs.length ∧ + offsetBase L m N qs.length ≤ groupedBase L m N qs.length := by + refine ⟨(producers_le_snapshot L m N qs.length).1, ?_, ?_⟩ + · unfold offsetBase + omega + · unfold groupedBase originalBase codeBase + omega + have hout : groupedBase L m N qs.length + qs.length = outputBase L m N qs.length := rfl + have hqueries : codeBase L m N qs.length + qs.length ≤ groupedBase L m N qs.length := by + unfold groupedBase originalBase + omega + have hfree : outputBase L m N qs.length ≤ scratchBase L m N qs.length := by + unfold scratchBase + omega + have hbefore : BandPreparation.memoryEnd L m N ≤ scratchBase L m N qs.length := by omega + have hleft (τ : Leaf L) : arg.mem (pa + (Encoding.leafIndex τ).val) = + Signed.encode (encodingL (bandArrayL (MaskTable.layout hm) X β) τ) := by + rw [arm] + simpa only [pa, BandPreparation.catalogueAddress, Bool.false_eq_true, ↓reduceIte, + Nat.zero_add, Nat.add_assoc] using he.left β hβ τ + have hright (τ : Leaf L) : arg.mem (pb + (Encoding.leafIndex τ).val) = + Signed.encode (encodingR (bandArrayR (MaskTable.layout hm) Y γ) τ) := by + rw [arm] + simpa only [pb, B, BandPreparation.catalogueAddress, ↓reduceIte, Nat.add_assoc] using + he.right γ hγ τ + have habsL (τ : Leaf L) : |encodingL (bandArrayL (MaskTable.layout hm) X β) τ| ≤ + ((7 ^ L * A : ℕ) : ℤ) := by + exact_mod_cast abs_encodingL_le + (abs_bandArrayL_le (MaskTable.layout hm) (by positivity) hX β) τ + have habsR (τ : Leaf L) : |encodingR (bandArrayR (MaskTable.layout hm) Y γ) τ| ≤ + ((7 ^ L * A : ℕ) : ℤ) := by + exact_mod_cast abs_encodingR_le + (abs_bandArrayR_le (MaskTable.layout hm) (by positivity) hY γ) τ + have hpa := (Nat.le_add_right pa (10 ^ L)).trans hpab.1 + have hpb := (Nat.le_add_right pb (10 ^ L)).trans hpab.2 + have hkernel := kernel_fits hfit hpa hpb + (show codeBase L m N qs.length + a ≤ codeBase L m N qs.length + qs.length by omega) + (show groupedBase L m N qs.length + a ≤ outputBase L m N qs.length by omega) + (show b - a ≤ qs.length by omega) hEbound + obtain ⟨k, hk, kv, kf, _, kel, keu, km⟩ := PrunedQuery.exec_kernel + (L := L) (f := scratchBase L m N qs.length) (a0 := pa) (b0 := pb) + (q0 := codeBase L m N qs.length + a) (o0 := groupedBase L m N qs.length + a) + (Q := b - a) (E := E) + (encodingL (bandArrayL (MaskTable.layout hm) X β)) + (encodingR (bandArrayR (MaskTable.layout hm) Y γ)) queries (arg.comap bank) + arL arA arB arQ arq aro arf (arE.trans hE) + (by intro i; simpa only [Data.comap_mem, arm, Nat.add_assoc] using hinput i) + hleft hright (by omega) (by omega) (by omega) (by omega) + (by simpa only [Data.comap_mem, arm, stampBase] using hmarks) + habsL habsR hw (magnitude_admissions hw hmag).2.1 hkernel + rw [hset] at hk kv + let u := k.extend bank arg + have hhigh (i : Fin 160) (hi : 96 ≤ i.val) : u.regs i = arg.regs i := by + have hnot : ¬∃ j, bank j = i := by + rintro ⟨j, hj⟩ + have hv := congrArg (fun r : Fin 160 => r.val) hj + change j.val = i.val at hv + omega + exact Function.extend_apply' _ _ _ hnot + have up : Parameters L m N qs.length u := arp.reframe hhigh + have ui : u.regs index = t := (hhigh _ (by decide)).trans ari + have ue : u.regs (bank PrunedQuery.epoch) = k.regs PrunedQuery.epoch := + bank_injective.extend_apply _ _ _ + have hnext : Fits (w + 2) (t + 1) := allocated_fits hfit (by + unfold memoryEnd stampBase scratchBase outputBase groupedBase originalBase codeBase offsetBase + have h10 := Nat.zero_le (10 ^ L) + omega) + let y := (u.set epoch (k.regs PrunedQuery.epoch)).set index (t + 1) + have hpost : ExecWithin (B := .standard) (w + 2) + (.seq (.op (.copy epoch (bank PrunedQuery.epoch))) (ram[index ← index + one])) u y 2 := by + ram_eval [y, ue, ui, up.one, hnext, epoch, index, one] + have yp : Parameters L m N qs.length y := + (up.set_work epoch (by decide) _).set_work index (by decide) _ + have yepoch : y.regs epoch = k.regs PrunedQuery.epoch := by simp [y, epoch, index] + have yframe (p : ℕ) + (ho : ¬(groupedBase L m N qs.length + a ≤ p ∧ + p < groupedBase L m N qs.length + b)) + (hf : ¬(scratchBase L m N qs.length ≤ p ∧ p < memoryEnd L m N qs.length)) : + y.mem p = x.mem p := by + change k.mem p = x.mem p + rw [kf p (by omega) (by simpa only [memoryEnd, stampBase] using hf), + Data.comap_mem, arm] + refine ⟨y, ?_, yp, by simp [y], by simpa only [yepoch] using kel, + by simpa only [yepoch] using keu, ?_, ?_, ?_, ?_, yframe⟩ + · apply (har.seq ((hk.rename bank_injective).seq hpost)).mono + dsimp only [β, γ, B, a, b] + omega + · exact hg.reframe (fun p hp hbefore => yframe p (by omega) (by omega)) + · exact he.reframe (fun p hp => yframe p (by omega) (by omega)) + · intro j hj + change k.mem (stampBase L m N qs.length + j) ≤ y.regs epoch + simpa only [stampBase, yepoch] using km j hj + · intro i hkey + let p := CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i + have hp : p < qs.length := CountingSort.position_lt _ i.isLt + have hspan : a ≤ p ∧ p < b := by + have hs := (CountingSort.position_mem_iff + (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) i.isLt t).mpr hkey + have ha := (hg.offsets t ht.le).trans (hoffsets t ht.le) + have hb := (hg.offsets (t + 1) (by omega)).trans (hoffsets (t + 1) (by omega)) + change a = _ at ha + change b = _ at hb + simpa only [← ha, ← hb] using hs + let j : Fin (b - a) := ⟨p - a, by omega⟩ + have hap : a + j.val = p := by dsimp only [j]; omega + have hquery : queries j = outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2 := by + have hr := hinput j + rw [hap, hg.codes p hp, hvalues i] at hr + simpa only [PrunedQuery.queryAt_queryIndex] using + congrArg PrunedQuery.queryAt (Fin.ext hr.symm) + have htile : Wanted.tileKey L m N ((qs.get i).1.val, (qs.get i).2.val) = + β * numBands L m N + γ := by + simpa only [Function.comp_apply, Wanted.inputPairs_get, htnum] using hkey + have hbands := (Wanted.tileKey_eq_iff hm (qs.get i).1 (qs.get i).2 (β, γ) hT).mp htile + change bandOf L m (qs.get i).1.val = β ∧ bandOf L m (qs.get i).2.val = γ at hbands + have hr := kv j + rw [Nat.add_assoc, hap, hquery, ← hbands.1, ← hbands.2, + thin_product_spec (MaskTable.layout hm) X Y qs.toFinset (qs.get i).1 (qs.get i).2 + (List.mem_toFinset.mpr (List.get_mem _ _))] at hr + exact hr + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Tiles.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Tiles.lean new file mode 100644 index 0000000000..1e2cb00607 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Tiles.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.Product.Tile +import Mathlib.Algebra.BigOperators.Intervals + +/-! +# Actual all-tile execution with shared encodings and epochs + +## Main definitions + +* `QueriedProduct.tileCharge`: the charge of one actual tile call and its outer loop control. + +## Main results + +* `QueriedProduct.exec_evaluateTiles`: every original request receives its exact matrix-product + value in grouped order, while actual returned epochs retain one shared timestamp directory. + +The finite charge uses original coordinate occurrences and the existing pruned trace count. +The proof derives it from executed spans and calls. No sparse bound is assumed. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open scoped BigOperators + +/-- Per-tile analysis of the actual program, including call setup, returned epoch/index +updates and two outer-loop instructions. Duplicate coordinate occurrences remain charged. -/ +@[expose] public def tileCharge {L m N : ℕ} (hm : m ≤ L) + (qs : List (Fin N × Fin N)) (t : ℕ) : ℕ := + 4 * L + 29 * Nat.count (fun i => (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) i = t) + qs.length + PrunedQuery.traceWeight + (wantedStrings (MaskTable.layout hm) qs.toFinset (t / numBands L m N) + (t % numBands L m N)) + 66 + +private structure Progress {L m N : ℕ} (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (old initial : ℕ → ℕ) (z : Data 160) : Prop where + fixed : Parameters L m N qs.length z + index_le : z.regs index ≤ numBands L m N ^ 2 + epoch_le : z.regs epoch ≤ z.regs index * (1 + PrunedQuery.epochAllowance L) + groups : SavedGroups L m N qs.length old z.mem + catalogue : EncodedBands hm X Y z.mem + marks : ∀ j < 10 ^ L, z.mem (stampBase L m N qs.length + j) ≤ z.regs epoch + answers : ∀ i : Fin qs.length, + (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) i.val < z.regs index → + z.mem (groupedBase L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2) + frame : ∀ p, ¬(groupedBase L m N qs.length ≤ p ∧ p < outputBase L m N qs.length) → + ¬(scratchBase L m N qs.length ≤ p ∧ p < memoryEnd L m N qs.length) → z.mem p = initial p + +/-- Every real tile is evaluated by the same fixed program. The zero-band case exits without +division, and repeated requested coordinates retain their distinct grouped positions. -/ +public theorem exec_evaluateTiles {w L m N A : ℕ} (hw : 0 < w) (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (old : ℕ → ℕ) (x : Data 160) + (hp : Parameters L m N qs.length x) (hi : x.regs index = 0) (hE : x.regs epoch = 0) + (hg : SavedGroups L m N qs.length old x.mem) (he : EncodedBands hm X Y x.mem) + (hvalues : ∀ i : Fin qs.length, + old (Wanted.codeAddress L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val) + (hoffsets : ∀ t ≤ numBands L m N ^ 2, + old (Wanted.offsetAddress L m N qs.length + t) = + Nat.count (fun k => (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) k < t) qs.length) + (hmarks : ∀ j < 10 ^ L, x.mem (stampBase L m N qs.length + j) = 0) + (hX : ∀ i k, |X i k| ≤ (A : ℤ)) (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hmag : magnitudeBound L A < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N qs.length)) : + ∃ y, ExecWithin (w + 2) evaluateTiles x y + ((∑ t ∈ Finset.range (numBands L m N ^ 2), tileCharge hm qs t) + 2) ∧ + Parameters L m N qs.length y ∧ SavedGroups L m N qs.length old y.mem ∧ + EncodedBands hm X Y y.mem ∧ + (∀ i : Fin qs.length, + y.mem (groupedBase L m N qs.length + + CountingSort.position (Wanted.tileKey L m N ∘ Wanted.inputPairs qs) qs.length i) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ p, ¬(groupedBase L m N qs.length ≤ p ∧ p < outputBase L m N qs.length) → + ¬(scratchBase L m N qs.length ≤ p ∧ p < memoryEnd L m N qs.length) → + y.mem p = x.mem p) := by + let key := Wanted.tileKey L m N ∘ Wanted.inputPairs qs + let T := numBands L m N ^ 2 + let I := Progress hm X Y qs old x.mem + let Φ (z : Data 160) := ∑ t ∈ Finset.Ico (z.regs index) T, tileCharge hm qs t + have hout : groupedBase L m N qs.length + qs.length = outputBase L m N qs.length := rfl + have hfree : outputBase L m N qs.length ≤ scratchBase L m N qs.length := by + unfold scratchBase + omega + have hstart : I x := ⟨hp, by rw [hi]; omega, by rw [hi, hE]; simp, + hg, he, by intro j hj; rw [hmarks j hj, hE], + by intro i hk; rw [hi] at hk; omega, by intro p _ _; rfl⟩ + have hbody (z : Data 160) (hz : I z) (hc : (Cond.lt index tiles).Holds z) : + ∃ y C, ExecWithin (w + 2) tileStep z y C ∧ I y ∧ C + 2 + Φ y ≤ Φ z := by + have ht : z.regs index < T := by simpa only [Cond.holds_lt, hz.fixed.tiles, T] using hc + have hepoch : z.regs epoch ≤ epochBound L m N := by + have h := hz.epoch_le.trans + (Nat.mul_le_mul_right (1 + PrunedQuery.epochAllowance L) hz.index_le) + simpa only [epochBound] using h + obtain ⟨y, hr, yp, yi, _, yeu, yg, ye, ym, yvalues, yf⟩ := exec_tileStep hw hm X Y qs old z + hz.fixed rfl ht rfl hepoch hz.groups hz.catalogue hvalues hoffsets hz.marks hX hY hmag hfit + have ha := (hz.groups.offsets _ ht.le).trans (hoffsets _ ht.le) + have hb := (hz.groups.offsets _ (Nat.succ_le_of_lt ht)).trans + (hoffsets _ (Nat.succ_le_of_lt ht)) + have hdiff : z.mem (offsetBase L m N qs.length + z.regs index + 1) - + z.mem (offsetBase L m N qs.length + z.regs index) = + Nat.count (fun k => key k = z.regs index) qs.length := by + rw [show offsetBase L m N qs.length + z.regs index + 1 = + offsetBase L m N qs.length + (z.regs index + 1) by omega, hb, ha, + CountingSort.count_lt_succ] + dsimp only [key] + omega + rw [hdiff] at hr + refine ⟨y, _, hr, ?_, ?_⟩ + · refine ⟨yp, by rw [yi]; omega, ?_, yg, ye, ym, ?_, ?_⟩ + · rw [yi, Nat.add_mul, Nat.one_mul] + have h := hz.epoch_le + omega + · intro i hik + change key i.val < y.regs index at hik + by_cases hki : key i.val = z.regs index + · exact yvalues i hki + · have hold : key i.val < z.regs index := by rw [yi] at hik; omega + have hs := (CountingSort.position_mem key i.isLt).2 + have hmcount : Nat.count (fun k => key k < key i.val + 1) qs.length ≤ + Nat.count (fun k => key k < z.regs index) qs.length := + Nat.count_mono_left (fun k _ hk => by omega) + have hpos := CountingSort.position_lt key i.isLt + dsimp only [key] at hs hmcount hpos + rw [yf _ (by omega) (by omega)] + exact hz.answers i hold + · intro p ho hf + have hbQ := (hz.groups.span_bounds key hoffsets ht).2 + rw [yf p (by omega) hf] + exact hz.frame p ho hf + · have hsplit := Finset.sum_eq_sum_Ico_succ_bot ht (tileCharge hm qs) + dsimp only [Φ] + rw [yi, hsplit] + dsimp only [tileCharge, key] + omega + obtain ⟨y, hr, hy, hc⟩ := ExecWithin.loop_potential I Φ hbody hstart + have hlast : y.regs index = T := by + have h := hy.index_le + simp only [Cond.holds_lt, hy.fixed.tiles] at hc + dsimp only [T] at * + omega + refine ⟨y, ?_, hy.fixed, hy.groups, hy.catalogue, ?_, hy.frame⟩ + · simpa only [Φ, hi, Nat.Ico_zero_eq_range, T, evaluateTiles] using hr + · intro i + apply hy.answers i + rw [hlast] + have hk := Wanted.tileKey_lt hm (Wanted.inputPairs qs i.val) + (Wanted.inputPairs_lt qs i i.isLt).1 (Wanted.inputPairs_lt qs i i.isLt).2 + exact hk + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Transfer.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Transfer.lean new file mode 100644 index 0000000000..87b43502a6 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Transfer.lean @@ -0,0 +1,141 @@ +/- +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.Product.Memory + +/-! +# Protected original-input and coordinate transfers + +## Main results + +* `QueriedProduct.exec_saveInput`: snapshot the complete original input by actual native copying. +* `QueriedProduct.exec_prepareQueries`: adapt real coordinates to the router's input convention. + +These internal execution interfaces derive copy separation and arithmetic fits from the +original numerical parameters. Their memory conclusions describe the actual executed state. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM + +/-- Original input, its protected copy and the coordinate adaptation all fit the allocation. -/ +public theorem transfer_bounds (L m N Q : ℕ) : + inputEnd m N Q ≤ snapshotBase L m N Q ∧ + snapshotBase L m N Q + inputEnd m N Q ≤ memoryEnd L m N Q ∧ + 5 + 2 * Q ≤ snapshotBase L m N Q + queryInputBase m N ∧ + snapshotBase L m N Q + queryInputBase m N + 2 * Q ≤ memoryEnd L m N Q := by + have h10 := Nat.zero_le (10 ^ L) + unfold memoryEnd stampBase scratchBase outputBase groupedBase originalBase codeBase + offsetBase snapshotBase queryInputBase inputEnd + omega + +/-- Any allocated endpoint fits without assumptions about intermediate machine states. -/ +public theorem allocated_fits {w L m N Q a : ℕ} (hfit : Fits w (wordBudget L m N Q)) + (ha : a ≤ memoryEnd L m N Q + 16) : Fits w a := by + apply (caller_fits hfit).of_le + omega + +/-- The actual input snapshot uses three argument instructions and the native copy loop. -/ +public theorem exec_saveInput {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w saveInput x y (7 * inputEnd m N Q + 10) ∧ + Parameters L m N Q y ∧ + y.mem = ArrayCopy.copyPrefix x.mem 0 (snapshotBase L m N Q) (inputEnd m N Q) := by + let z := ((x.set source 0).set destination (snapshotBase L m N Q)).set length (inputEnd m N Q) + have hz : ExecWithin (B := .standard) w (ram[ + source ← zero; destination ← snapshot; length ← inputSize ]) x z 3 := by + ram_eval [z, hp.zero, hp.snapshot, hp.inputSize, source, destination, length, + zero, snapshot, inputSize] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) 0).set_work destination (by decide) _).set_work + length (by decide) _ + have hb := transfer_bounds L m N Q + have hi : Fits w (inputEnd m N Q) := allocated_fits hfit (by omega) + have hs : Fits w (snapshotBase L m N Q + inputEnd m N Q) := + allocated_fits hfit (by omega) + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length] using hi) + (by simpa [z, source, destination, length] using hs) + (Or.inl (by simpa [z, source, destination, length] using hb.1)) + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_⟩ + · have hlen : z.regs length = inputEnd m N Q := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · simpa [z, source, destination, length] using hm + +/-- Real coordinate words are copied from the protected original input to address five. +The caller's original prefix remains protected in its snapshot for final restoration. -/ +public theorem exec_prepareQueries {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w prepareQueries x y (14 * Q + 10) ∧ Parameters L m N Q y ∧ + y.mem = ArrayCopy.copyPrefix x.mem + (snapshotBase L m N Q + queryInputBase m N) 5 (2 * Q) := by + let z := ((x.set source (snapshotBase L m N Q + queryInputBase m N)).set destination 5).set + length (Q * 2) + have hb := transfer_bounds L m N Q + have hs : Fits w (snapshotBase L m N Q + queryInputBase m N) := + allocated_fits hfit (by omega) + have hn : Fits w (Q * 2) := allocated_fits hfit (by + have h10 := Nat.zero_le (10 ^ L) + unfold memoryEnd stampBase scratchBase outputBase groupedBase originalBase codeBase + offsetBase snapshotBase inputEnd + omega) + have hz : ExecWithin (B := .standard) w (ram[ + source ← snapshot + queryOffset; destination ← five; length ← queryCount * two ]) + x z 3 := by + ram_eval [z, hp.snapshot, hp.queryOffset, hp.five, hp.queryCount, hp.two, hs, hn, + source, destination, length, snapshot, queryOffset, five, queryCount, two] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) _).set_work destination (by decide) _).set_work + length (by decide) _ + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length, Nat.mul_comm] using + allocated_fits hfit (show snapshotBase L m N Q + queryInputBase m N + 2 * Q ≤ + memoryEnd L m N Q + 16 by omega)) + (by simpa [z, source, destination, length, Nat.mul_comm] using + allocated_fits hfit (show 5 + 2 * Q ≤ memoryEnd L m N Q + 16 by omega)) + (Or.inr (Or.inl (by + simpa [z, source, destination, length, Nat.mul_comm] using hb.2.2.1))) + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_⟩ + · have hlen : z.regs length = Q * 2 := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · simpa [z, source, destination, length, Nat.mul_comm] using hm + +/-- Restore the entire original prefix through the same actual copy routine. Answers and +all memory outside the prefix remain unchanged. -/ +public theorem exec_restoreInput {w L m N Q : ℕ} (x : Data 160) + (hp : Parameters L m N Q x) (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w restoreInput x y (7 * inputEnd m N Q + 10) ∧ + Parameters L m N Q y ∧ + y.mem = ArrayCopy.copyPrefix x.mem (snapshotBase L m N Q) 0 (inputEnd m N Q) := by + let z := ((x.set source (snapshotBase L m N Q)).set destination 0).set length (inputEnd m N Q) + have hz : ExecWithin (B := .standard) w (ram[ + source ← snapshot; destination ← zero; length ← inputSize ]) x z 3 := by + ram_eval [z, hp.zero, hp.snapshot, hp.inputSize, source, destination, length, + zero, snapshot, inputSize] + have zp : Parameters L m N Q z := + ((hp.set_work source (by decide) _).set_work destination (by decide) 0).set_work + length (by decide) _ + have hb := transfer_bounds L m N Q + have hi : Fits w (inputEnd m N Q) := allocated_fits hfit (by omega) + have hs : Fits w (snapshotBase L m N Q + inputEnd m N Q) := + allocated_fits hfit (by omega) + obtain ⟨y, he, hm, hf⟩ := exec_copyCall z (allocated_fits hfit (by omega)) + (by simpa [z, source, destination, length] using hs) + (by simpa [z, source, destination, length] using hi) + (Or.inr (Or.inl (by simpa [z, source, destination, length] using hb.1))) + refine ⟨y, ?_, zp.reframe (fun i hi => hf i (by omega)), ?_⟩ + · have hlen : z.regs length = inputEnd m N Q := by simp [z] + rw [hlen] at he + exact (hz.seq he).mono (by omega) + · simpa [z, source, destination, length] using hm + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Width.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Width.lean new file mode 100644 index 0000000000..b21437d12a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Product/Width.lean @@ -0,0 +1,80 @@ +/- +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.Product.Flat +public import Mathlib.Data.Nat.Log + +/-! +# Positive original-input width admission for native queried products + +## Main results + +* `QueriedProduct.admissionWidth`: sufficient physical width, derived only from original scalar + dimensions, entry magnitude and request count. +* `QueriedProduct.runs_wide`: actual initialized product execution at every sufficiently wide + admitted width, with guaranteed input fit and no physical-width reads by the code. + +The logarithm applies to the explicit original-input magnitude/address envelope. Relating it +to a polynomially bounded whole-3SUM input remains separate composition work under #1394. +-/ + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- An original-input-derived sufficient physical width, including the existing two-bit +native signed-kernel margin. It is not read by the native program. -/ +@[expose] public def admissionWidth (L m N Q A : ℕ) : ℕ := + Nat.clog 2 (magnitudeBound L A + wordBudget L m N Q + 1) + 3 + +/-- The physical threshold is strictly positive even for empty and zero-magnitude inputs. -/ +public theorem admissionWidth_pos (L m N Q A : ℕ) : 0 < admissionWidth L m N Q A := by + unfold admissionWidth + omega + +/-- Mathlib's ceiling logarithm supplies both numerical admission hypotheses. -/ +public theorem admission_bounds {W L m N Q A : ℕ} (hW : admissionWidth L m N Q A ≤ W) : + 0 < W - 2 ∧ magnitudeBound L A < 2 ^ ((W - 2) - 1) ∧ Fits W (wordBudget L m N Q) := by + have hl := Nat.le_pow_clog (by decide : 1 < (2 : ℕ)) + (magnitudeBound L A + wordBudget L m N Q + 1) + have he : Nat.clog 2 (magnitudeBound L A + wordBudget L m N Q + 1) ≤ (W - 2) - 1 := by + unfold admissionWidth at hW + omega + have hp := Nat.pow_le_pow_right (by decide : 1 ≤ (2 : ℕ)) he + refine ⟨by unfold admissionWidth at hW; omega, by omega, ?_⟩ + change wordBudget L m N Q < 2 ^ W + have hp' := Nat.pow_le_pow_right (by decide : 1 ≤ (2 : ℕ)) (by omega : + Nat.clog 2 (magnitudeBound L A + wordBudget L m N Q + 1) ≤ W) + omega + +/-- Both real input modes run at every sufficiently wide physical width. All product values, +the charged halt and complete restored-input/outside frame refer to the same actual run. -/ +public theorem runs_wide {W L m N A : ℕ} (hm : m ≤ L) (bits : Bool) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) (hX : ∀ i k, |X i k| ≤ (A : ℤ)) + (hY : ∀ k j, |Y k j| ≤ (A : ℤ)) + (hbits : bits = true → (∀ i k, 0 ≤ X i k) ∧ (∀ k j, 0 ≤ Y k j)) + (hW : admissionWidth L m N qs.length A ≤ W) : + InputFits W (input bits L X Y qs) ∧ + ∃ y n, n ≤ timeBound hm qs ∧ + Runs W ((program bits).program output) (State.init (input bits L X Y qs)) + ⟨(program bits).size, y⟩ n ∧ + Executes W ((program bits).program output) (State.init (input bits L X Y qs)) + (outputBase L m N qs.length) (n + 1) ∧ y.Bounded W ∧ + (∀ i : Fin qs.length, y.mem (outputBase L m N qs.length + i.val) = + Signed.encode ((X * Y) (qs.get i).1 (qs.get i).2)) ∧ + (∀ a, ¬(inputEnd m N qs.length ≤ a ∧ a < memoryEnd L m N qs.length) → + y.mem a = (State.init (input bits L X Y qs) : State 160).data.mem a) := by + obtain ⟨hw, hmagnitude, haddress⟩ := admission_bounds hW + have htwo : W - 2 + 2 = W := by unfold admissionWidth at hW; omega + have h := runs hw hm bits X Y qs hX hY hbits hmagnitude (by simpa only [htwo] using haddress) + simpa only [htwo] using h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct 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/Wanted/Cost.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Cost.lean new file mode 100644 index 0000000000..5bba089698 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Cost.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.Wanted.Memory +import Mathlib.Tactic.Linarith + +/-! +# Charged bound for native wanted-query grouping + +## Main definitions + +* `Wanted.timeBound`: sum of actual parameter/table, zero/count, prefix and scatter charges. + +## Main results + +* `Wanted.timeBound_add_one_le`: a bound polynomial in depth times query, tile and mask counts. + +These are original instruction bounds for the native implementation. Actual executions are +proved separately; no execution or complexity certificate is an input to the program. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Prefix instruction budget, before the final flat halt. All initialization and loops count. -/ +def timeBound (L m N Q : ℕ) : ℕ := + 16 * L + (12 * L + 33) * 2 ^ L + 15 * numBands L m N ^ 2 + (15 * L + 57) * Q + 98 + +/-- Including halt, routing is polynomial in depth times the original query, tile and +subset-table sizes. In particular it does not scan `10^L` cells per query or tile. -/ +theorem timeBound_add_one_le (L m N Q : ℕ) : + timeBound L m N Q + 1 ≤ + 128 * (L + 1) * (Q + numBands L m N ^ 2 + 2 ^ L + 1) := by + unfold timeBound + nlinarith only [Nat.zero_le (L * Q), Nat.zero_le (L * numBands L m N ^ 2), + Nat.zero_le (L * 2 ^ L), Nat.zero_le Q, Nat.zero_le (numBands L m N ^ 2), + Nat.zero_le (2 ^ L), Nat.zero_le L] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Count.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Count.lean new file mode 100644 index 0000000000..2d460459db --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Count.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.Wanted.CountStep + +/-! +# Complete native count pass for wanted positions + +## Main results + +* `Wanted.exec_clearCounts`: initialize exactly the actual tile-count cells. +* `Wanted.exec_countQueries`: all original occurrences, including repetitions, are counted + by the actual coordinate-loading and tile-index code in linear charged time. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Actual dense initialization of tile counts. It touches no query or computed table. -/ +theorem exec_clearCounts {w L m N Q : ℕ} (x : Data 96) (hx : Parameters L m N Q x) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w clearCounts x y (5 * numBands L m N ^ 2 + 3) ∧ + Parameters L m N Q y ∧ + (∀ t < numBands L m N ^ 2, y.mem (countAddress L Q + t) = 0) ∧ + (∀ a, a < countAddress L Q ∨ countAddress L Q + numBands L m N ^ 2 ≤ a → + y.mem a = x.mem a) := by + let T := numBands L m N ^ 2 + let body : Code .standard 96 := ram[ + pointer ← counts + index; *pointer ← BandPreparation.zero; + index ← index + BandPreparation.one ] + let I (z : Data 96) : Prop := Parameters L m N Q z ∧ z.regs index ≤ T ∧ + (∀ t < z.regs index, z.mem (countAddress L Q + t) = 0) ∧ + (∀ a, a < countAddress L Q ∨ countAddress L Q + T ≤ a → z.mem a = x.mem a) + let start := x.set index 0 + have hs : ExecWithin w (ram[index ← #0] : Code .standard 96) x start 1 := by + ram_eval [start] + have hp : Parameters L m N Q start := hx.reframe (by + intro i hi + apply hs.regs_eq + have h : ∀ i ∈ parameterRegisters, (ram[index ← #0] : Code .standard 96).Preserves i := by + decide +kernel + exact h i hi) + have hstart : I start := ⟨hp, by simp [start], by simp [start], by simp [start]⟩ + have hb (z : Data 96) (hz : I z) (hc : (Cond.lt index tileCount).Holds z) : + ∃ u, ExecWithin w body z u 3 ∧ I u ∧ T - u.regs index < T - z.regs index := by + have hn : z.regs index < T := by + simpa only [Cond.holds_lt, hz.1.tileCount] using hc + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have h10 := Nat.zero_le (10 ^ L) + have hfaddr : Fits w (countAddress L Q + z.regs index) := hl.of_le (by + unfold countAddress tableAddress inputLimit + dsimp [T] at hn + omega) + have hnext : Fits w (z.regs index + 1) := hl.of_le (by dsimp [T] at hn; omega) + let u := ((z.set pointer (countAddress L Q + z.regs index)).write + (countAddress L Q + z.regs index) 0).set index (z.regs index + 1) + have he : ExecWithin w body z u 3 := by + ram_eval [body, u, hz.1.counts, hz.1.tables.zero, hz.1.tables.one, hfaddr, hnext, + pointer, counts, index, BandPreparation.zero, BandPreparation.one, Fin.mk.injEq] + have hu : Parameters L m N Q u := hz.1.reframe (by + intro i hi + apply he.regs_eq + have h : ∀ i ∈ parameterRegisters, body.Preserves i := by decide +kernel + exact h i hi) + have hi : u.regs index = z.regs index + 1 := by simp [u, index] + refine ⟨u, he, ⟨hu, by rw [hi]; omega, ?_, ?_⟩, by rw [hi]; omega⟩ + · intro t ht + rw [hi] at ht + change Function.update z.mem (countAddress L Q + z.regs index) 0 + (countAddress L Q + t) = 0 + by_cases h : t = z.regs index + · subst t + exact Function.update_self _ _ _ + · rw [Function.update_of_ne (by omega)] + exact hz.2.2.1 t (by omega) + · intro a ha + change Function.update z.mem (countAddress L Q + z.regs index) 0 a = x.mem a + rw [Function.update_of_ne (by rcases ha with ha | ha <;> omega)] + exact hz.2.2.2 a ha + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => T - z.regs index) hb hstart + have hn : y.regs index = T := by + have hu := hy.2.1 + simp only [Cond.holds_lt, hy.1.tileCount] at hc + dsimp [T] at hu ⊢ + omega + refine ⟨y, ?_, hy.1, ?_, hy.2.2.2⟩ + · apply (hs.seq he).mono + have hzero : start.regs index = 0 := by simp [start] + rw [hzero, Nat.sub_zero] + dsimp [T] + omega + · intro t ht + exact hy.2.2.1 t (hn ▸ ht) + +/-- Every original query is loaded and its actual tile computed. Duplicate input pairs +increase counts independently; the readonly input and both computed tables are preserved. -/ +theorem exec_countQueries {w L m N Q : ℕ} (hm : m ≤ L) (pairs : ℕ → ℕ × ℕ) + (hpairs : ∀ i < Q, (pairs i).1 < N ∧ (pairs i).2 < N) (x : Data 96) + (hx : Parameters L m N Q x) + (hinput : ∀ i < Q, x.mem (5 + 2 * i) = (pairs i).1 ∧ + x.mem (5 + 2 * i + 1) = (pairs i).2) + (hzero : ∀ t < numBands L m N ^ 2, x.mem (countAddress L Q + t) = 0) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w countQueries x y (16 * Q + 3) ∧ Parameters L m N Q y ∧ + (∀ t < numBands L m N ^ 2, + y.mem (countAddress L Q + t) = Nat.count (fun k => (tileKey L m N ∘ pairs) k = t) Q) ∧ + (∀ a, a < countAddress L Q ∨ countAddress L Q + numBands L m N ^ 2 ≤ a → + y.mem a = x.mem a) := by + let key := tileKey L m N ∘ pairs + let T := numBands L m N ^ 2 + let I (z : Data 96) : Prop := Parameters L m N Q z ∧ z.regs index ≤ Q ∧ + (∀ i < Q, z.mem (5 + 2 * i) = (pairs i).1 ∧ + z.mem (5 + 2 * i + 1) = (pairs i).2) ∧ + (∀ t < T, z.mem (countAddress L Q + t) = Nat.count (fun k => key k = t) (z.regs index)) ∧ + (∀ a, a < countAddress L Q ∨ countAddress L Q + T ≤ a → z.mem a = x.mem a) + let start := x.set index 0 + have hs : ExecWithin w (ram[index ← #0] : Code .standard 96) x start 1 := by + ram_eval [start] + have hsp : Parameters L m N Q start := hx.reframe (by + intro i hi + apply hs.regs_eq + have h : ∀ i ∈ parameterRegisters, (ram[index ← #0] : Code .standard 96).Preserves i := by + decide +kernel + exact h i hi) + have hi : I start := by + refine ⟨hsp, by simp [start], hinput, ?_, by simp [start]⟩ + intro t ht + simpa only [start, Data.set_regs, Function.update_self, Data.set_mem, + Nat.count_zero] using hzero t ht + have hb (z : Data 96) (hz : I z) (hc : (Cond.lt index queryCount).Holds z) : + ∃ u, ExecWithin w countStep z u 14 ∧ I u ∧ + Q - u.regs index < Q - z.regs index := by + have hn : z.regs index < Q := by + simpa only [Cond.holds_lt, hz.1.queryCount] using hc + obtain ⟨u, he, hu, hidx, hcounts, hf⟩ := exec_countStep hm pairs hn (hpairs _ hn) + z hz.1 rfl (hz.2.2.1 _ hn) hz.2.2.2.1 hfit + refine ⟨u, he, ⟨hu, by rw [hidx]; omega, ?_, ?_, ?_⟩, by rw [hidx]; omega⟩ + · intro i hi + have ha : 5 + 2 * i + 1 < countAddress L Q := by + have h2 := Nat.zero_le (2 ^ L) + unfold countAddress tableAddress inputLimit + omega + rw [hf _ (Or.inl (by omega)), hf _ (Or.inl ha)] + exact hz.2.2.1 i hi + · intro t ht + rw [hidx] + exact hcounts t ht + · intro a ha + exact (hf a ha).trans (hz.2.2.2.2 a ha) + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => Q - z.regs index) hb hi + have hn : y.regs index = Q := by + have hu := hy.2.1 + simp only [Cond.holds_lt, hy.1.queryCount] at hc + omega + refine ⟨y, ?_, hy.1, ?_, hy.2.2.2.2⟩ + · apply (hs.seq he).mono + have hz : start.regs index = 0 := by simp [start] + rw [hz, Nat.sub_zero] + omega + · intro t ht + simpa only [hn] using hy.2.2.2.1 t ht + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/CountStep.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/CountStep.lean new file mode 100644 index 0000000000..4bf0ff97ac --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/CountStep.lean @@ -0,0 +1,131 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Setup + +/-! +# One charged native tile-count update + +## Main results + +* `Wanted.exec_countStep`: load the actual pair, compute its tile and increment exactly + that bucket while preserving original query memory and all other counts. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- A row-major tile key, computed from the two original coordinate words. -/ +def tileKey (L m N : ℕ) (q : ℕ × ℕ) : ℕ := + bandOf L m q.1 * numBands L m N + bandOf L m q.2 + +/-- Admitted original positions always name one of the actual tiles. -/ +theorem tileKey_lt {L m N : ℕ} (hm : m ≤ L) (q : ℕ × ℕ) + (hr : q.1 < N) (hc : q.2 < N) : tileKey L m N q < numBands L m N ^ 2 := by + have hβ := bandOf_lt_numBands hm N q.1 hr + have hγ := bandOf_lt_numBands hm N q.2 hc + unfold tileKey + nlinarith only [Nat.mul_le_mul_right (numBands L m N) (Nat.succ_le_of_lt hβ), hγ] + +private theorem countStep_parameters {w T L m N Q : ℕ} {x y : Data 96} + (hx : Parameters L m N Q x) (he : ExecWithin w countStep x y T) : + Parameters L m N Q y := hx.reframe (by + intro i hi + apply he.regs_eq + have hp : ∀ i ∈ parameterRegisters, countStep.Preserves i := by decide +kernel + exact hp i hi) + +/-- The full native count update, including loading both original coordinates. Its count +bound comes from the original prefix length, rather than a supplied intermediate-state fit. -/ +theorem exec_countStep {w L m N Q n : ℕ} (hm : m ≤ L) (pairs : ℕ → ℕ × ℕ) + (hn : n < Q) (hpair : (pairs n).1 < N ∧ (pairs n).2 < N) (x : Data 96) + (hx : Parameters L m N Q x) (hi : x.regs index = n) + (hinput : x.mem (5 + 2 * n) = (pairs n).1 ∧ + x.mem (5 + 2 * n + 1) = (pairs n).2) + (hcounts : ∀ t < numBands L m N ^ 2, + x.mem (countAddress L Q + t) = Nat.count (fun k => (tileKey L m N ∘ pairs) k = t) n) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w countStep x y 14 ∧ Parameters L m N Q y ∧ y.regs index = n + 1 ∧ + (∀ t < numBands L m N ^ 2, + y.mem (countAddress L Q + t) = + Nat.count (fun k => (tileKey L m N ∘ pairs) k = t) (n + 1)) ∧ + (∀ a, a < countAddress L Q ∨ countAddress L Q + numBands L m N ^ 2 ≤ a → + y.mem a = x.mem a) := by + let I := (pairs n).1 + let J := (pairs n).2 + let B := numBands L m N + let β := bandOf L m I + let γ := bandOf L m J + let key := tileKey L m N ∘ pairs + let c := Nat.count (fun k => key k = (key n)) n + have ht : key n < B ^ 2 := tileKey_lt hm _ hpair.1 hpair.2 + have hβ : β < B := bandOf_lt_numBands hm N I hpair.1 + have hγ : γ < B := bandOf_lt_numBands hm N J hpair.2 + have hB : B ≤ B ^ 2 := by nlinarith only [hβ] + have hBeq : B ^ 2 = numBands L m N ^ 2 := rfl + have h10 := Nat.zero_le (10 ^ L) + have hprod : β * B ≤ key n := by dsimp [key, tileKey, Function.comp_def, β, B, I]; omega + have hcle : c ≤ n := Nat.count_le _ + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have hf (v : ℕ) (hv : v ≤ memoryLimit L m N Q) : Fits w v := hl.of_le (by omega) + have hcn : Fits w (c + 1) := hf _ (by omega) + have hn1 : Fits w (n + 1) := hf _ (by omega) + have hn2 : Fits w (n * 2) := hf _ (by omega) + have ha : Fits w (n * 2 + 5) := hf _ (by omega) + have ha1 : Fits w (n * 2 + 5 + 1) := hf _ (by omega) + have hβf : Fits w β := hf _ (by omega) + have hγf : Fits w γ := hf _ (by omega) + have hpf : Fits w (β * B) := hf _ (by omega) + have hkf : Fits w (key n) := hf _ (by omega) + have hcf : Fits w (countAddress L Q + key n) := hf _ (by + unfold countAddress tableAddress inputLimit + omega) + have hload : x.mem (countAddress L Q + key n) = c := hcounts _ ht + have hI : x.mem (n * 2 + 5) = I := + (congrArg x.mem (by omega : n * 2 + 5 = 5 + 2 * n)).trans hinput.1 + have hJ : x.mem (n * 2 + 5 + 1) = J := + (congrArg x.mem (by omega : n * 2 + 5 + 1 = 5 + 2 * n + 1)).trans hinput.2 + let y := ((((((((((((((x.set pointer (n * 2)).set pointer (n * 2 + 5)).set queryRow I).set + pointer (n * 2 + 5 + 1)).set queryCol J).set rowBand β).set colBand γ).set + tile (β * B)).set tile (key n)).set pointer (countAddress L Q + key n)).set count c).set + count (c + 1)).write (countAddress L Q + key n) (c + 1)).set index (n + 1)) + have he : ExecWithin w countStep x y 14 := by + dsimp only [β, γ, B, I, J, bandOf] at hβf hγf hpf + dsimp only [I, J] at hI hJ + dsimp only [key, Function.comp_def, tileKey, bandOf] at hkf hcf hload + ram_eval [countStep, loadQuery, tileCode, y, hx.queries, hx.queryCount, hx.counts, + hx.route.bandWidth, hx.route.bands, hx.tables.one, hx.tables.two, hi, + hI, hJ, hload, hn2, ha, ha1, hβf, hγf, hpf, hkf, hcf, hcn, hn1, + I, J, β, γ, B, key, Function.comp_def, tileKey, bandOf, + pointer, index, queries, queryRow, queryCol, rowBand, colBand, tile, bandWidth, + counts, count, BandPreparation.bands, BandPreparation.one, BandPreparation.two, + Fin.mk.injEq] + refine ⟨y, he, countStep_parameters hx he, by simp [y, index], ?_, ?_⟩ + · intro t ht' + change y.mem (countAddress L Q + t) = Nat.count (fun k => key k = t) (n + 1) + rw [Nat.count_succ] + change (Function.update x.mem (countAddress L Q + key n) (c + 1)) + (countAddress L Q + t) = _ + by_cases h : key n = t + · subst t + simp only [Function.update_self, ↓reduceIte] + rfl + · have hc' : x.mem (countAddress L Q + t) = Nat.count (fun k => key k = t) n := hcounts t ht' + rw [Function.update_of_ne (by omega), hc', ite_eq_right h] + omega + · intro a ha + change Function.update x.mem (countAddress L Q + key n) (c + 1) a = x.mem a + rw [Function.update_of_ne (by rcases ha with ha | ha <;> omega)] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Exec.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Exec.lean new file mode 100644 index 0000000000..edd2d0b073 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Exec.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.Wanted.Count +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Prefix +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Scatter +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Cost + +/-! +# Complete native routing from original scalar and coordinate inputs + +## Main results + +* `Wanted.exec_program`: compute tables, counts, prefix offsets, query codes and stable + scatter positions in the fixed native program, retaining the original input and outside frame. + +All table and cursor premises of the internal stages are established by actual preceding code. +The caller supplies only the original scalar headers and the ordinary coordinate list. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The complete native program, with no table, query-code, prefix or permutation oracle. +Every original query occurrence has one computed stable destination and retains its index. -/ +theorem exec_program {w L m N Q : ℕ} (hm : m ≤ L) (pairs : ℕ → ℕ × ℕ) + (hpairs : ∀ i < Q, (pairs i).1 < N ∧ (pairs i).2 < N) (x : Data 96) + (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) (hQ : x.mem 4 = Q) + (hinput : ∀ i < Q, x.mem (5 + 2 * i) = (pairs i).1 ∧ + x.mem (5 + 2 * i + 1) = (pairs i).2) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w program x y (timeBound L m N Q) ∧ Parameters L m N Q y ∧ + (∀ i < Q, + y.mem (codeAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (pairs i).1 (pairs i).2)).val ∧ + y.mem (originalAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q i) = i) ∧ + (∀ t ≤ numBands L m N ^ 2, + y.mem (offsetAddress L m N Q + t) = Nat.count (fun k => (tileKey L m N ∘ pairs) k < t) Q) ∧ + y.mem (offsetAddress L m N Q + numBands L m N ^ 2) = Q ∧ + (∀ a, a < inputLimit Q ∨ memoryLimit L m N Q ≤ a → y.mem a = x.mem a) := by + let key := tileKey L m N ∘ pairs + have h2 := Nat.zero_le (2 ^ L) + have hT := Nat.zero_le (numBands L m N ^ 2) + have horder : inputLimit Q ≤ countAddress L Q ∧ + countAddress L Q + numBands L m N ^ 2 = offsetAddress L m N Q ∧ + offsetAddress L m N Q ≤ cursorAddressBase L m N Q ∧ + cursorAddressBase L m N Q ≤ codeAddress L m N Q ∧ + codeAddress L m N Q ≤ memoryLimit L m N Q := by + simp only [countAddress, tableAddress, offsetAddress, cursorAddressBase, + codeAddress, memoryLimit, originalAddress] + exact ⟨by omega, trivial, by omega, by omega, by omega⟩ + obtain ⟨a, ha, ap, atable, af⟩ := exec_tables hm x hL hmemory hN hQ hfit + obtain ⟨b, hb, bp, bz, bf⟩ := exec_clearCounts a ap hfit + have bin (i : ℕ) (hi : i < Q) : b.mem (5 + 2 * i) = (pairs i).1 ∧ + b.mem (5 + 2 * i + 1) = (pairs i).2 := by + have hbound : 5 + 2 * i + 1 < inputLimit Q := by unfold inputLimit; omega + rw [bf _ (Or.inl (by omega)), af _ (Or.inl (by omega)), + bf _ (Or.inl (by omega)), af _ (Or.inl hbound)] + exact hinput i hi + obtain ⟨c, hc, cp, ccounts, cf⟩ := exec_countQueries hm pairs hpairs b bp bin bz hfit + have hkeys : ∀ i < Q, key i < numBands L m N ^ 2 := by + intro i hi + exact tileKey_lt hm _ (hpairs i hi).1 (hpairs i hi).2 + obtain ⟨p, hp, pp, poff, pcur, plast, pf⟩ := exec_prefix key hkeys c cp ccounts hfit + have pinput (addr : ℕ) (hi : addr < inputLimit Q) : p.mem addr = x.mem addr := by + rw [pf _ (Or.inl (by omega)), cf _ (Or.inl (by omega)), + bf _ (Or.inl (by omega)), af _ (Or.inl hi)] + have pin (i : ℕ) (hi : i < Q) : p.mem (5 + 2 * i) = (pairs i).1 ∧ + p.mem (5 + 2 * i + 1) = (pairs i).2 := by + have hbound : 5 + 2 * i + 1 < inputLimit Q := by unfold inputLimit; omega + rw [pinput _ (by omega), pinput _ hbound] + exact hinput i hi + have ptable (gh : Fin (K0 L m) × Fin (K0 L m)) : + p.mem (tableAddress L Q + (MaskTable.pairRank gh).val) = (MaskTable.selectedMask gh).val := by + have hr : (MaskTable.pairRank gh).val < 2 ^ L := + (MaskTable.pairRank gh).isLt.trans_le (Nat.choose_le_two_pow L m) + have htable : tableAddress L Q + (MaskTable.pairRank gh).val < countAddress L Q := by + unfold countAddress + omega + rw [pf _ (Or.inl (by omega)), cf _ (Or.inl htable), bf _ (Or.inl htable)] + exact atable gh + obtain ⟨y, hy, yp, yout, _, yf⟩ := exec_scatter hm pairs hpairs p pp pin ptable pcur hfit + refine ⟨y, ?_, yp, yout, ?_, ?_, ?_⟩ + · have hrest := hb.seq (hc.seq (hp.seq hy)) + have hfull := ha.seq hrest + apply hfull.mono + unfold timeBound + nlinarith + · intro t ht + have hlow : offsetAddress L m N Q + t < cursorAddressBase L m N Q := by + unfold cursorAddressBase + omega + rw [yf _ (Or.inl hlow)] + exact poff t ht + · rw [yf _ (Or.inl (by unfold cursorAddressBase; omega))] + exact plast + · intro addr hi + rw [yf _ (by rcases hi with hi | hi <;> omega), + pf _ (by rcases hi with hi | hi <;> omega), + cf _ (by rcases hi with hi | hi <;> omega), + bf _ (by rcases hi with hi | hi <;> omega)] + apply af addr + rcases hi with hi | hi + · exact Or.inl hi + · exact Or.inr (by unfold countAddress at horder; omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Flat.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Flat.lean new file mode 100644 index 0000000000..b23cece5ad --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Flat.lean @@ -0,0 +1,66 @@ +/- +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.Wanted.Input + +/-! +# Initialized flat runs of native wanted-query grouping + +## Main results + +* `Wanted.runs`: one actual initialized native run returns grouped query codes, their + original-position permutation and exact tile spans; halt is charged on that same run. +* `Wanted.bounded_reachable`: every initialized reachable prefix has fitting data words. + +The returned unsigned pointer names the code array. Other addresses are explicit functions of +the original headers, and the complete original input and outside memory are preserved. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Actual initialized standard WordRAM runs, with the same final memory carrying all +answers, a charged halt, and the readonly-input/outside frame. -/ +theorem runs {w L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (hfit : Fits w (wordBudget L m N qs.length)) : + InputFits w (input L m qs) ∧ + ∃ y n, n ≤ timeBound L m N qs.length ∧ + Runs w (program.program codes) (State.init (input L m qs)) ⟨program.size, y⟩ n ∧ + Executes w (program.program codes) (State.init (input L m qs)) + (codeAddress L m N qs.length) (n + 1) ∧ y.Bounded w ∧ + (∀ i : Fin qs.length, + y.mem (codeAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val ∧ + y.mem (originalAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = i.val) ∧ + (∀ t ≤ numBands L m N ^ 2, y.mem (offsetAddress L m N qs.length + t) = + Nat.count (fun k => (tileKey L m N ∘ inputPairs qs) k < t) qs.length) ∧ + y.mem (offsetAddress L m N qs.length + numBands L m N ^ 2) = qs.length ∧ + (∀ a, a < inputLimit qs.length ∨ memoryLimit L m N qs.length ≤ a → + y.mem a = (State.init (input L m qs) : State 96).data.mem a) := by + obtain ⟨hi, y, ⟨n, hn, he⟩, hp, hb, hv, ho, ht, hf⟩ := exec_input hm qs hfit + refine ⟨hi, y, n, hn, ?_, ?_, hb, hv, ho, ht, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := program.program codes) (Program.Contains.self _).append_left + · simpa only [hp.codes, State.init] using he.executes codes + +/-- Ordinary initialized native safety holds at every reachable prefix, including the +parameter, table, count and scatter stages, from the same original-input fit guarantee. -/ +theorem bounded_reachable {w L m N n : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (hfit : Fits w (wordBudget L m N qs.length)) {y : State 96} + (hy : Runs w (program.program codes) (State.init (input L m qs)) y n) : + y.data.Bounded w := hy.bounded_init (input_fits hm qs hfit) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/GroupedRuns.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/GroupedRuns.lean new file mode 100644 index 0000000000..fa19a71a21 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/GroupedRuns.lean @@ -0,0 +1,91 @@ +/- +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.Wanted.Groups +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Width + +/-! +# Initialized runs with the finite wanted-set interface + +## Main results + +* `Wanted.runs_grouped`: one actual initialized native run computes each tile's query-code + span and a permutation retaining all original positions, within the charged bound. + +This is the routing interface for subsequent shared-band and pruned-kernel composition. +It does not evaluate the product or reconstruct answers. Stage execution lemmas in the +supporting modules are internal implementation APIs; consumers use this theorem or `runs`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Finset + +/-- Actual native grouping from the original raw coordinate list at every sufficiently +wide physical width. The same final memory supplies the tile spans, permutation and frame. +No layout, query-code, count or permutation certificate is supplied as input. -/ +theorem runs_grouped {W L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (hW : admissionWidth L m N qs.length ≤ W) : + InputFits W (input L m qs) ∧ + ∃ y n, n + 1 ≤ 128 * (L + 1) * + (qs.length + numBands L m N ^ 2 + 2 ^ L + 1) ∧ + Runs W (program.program codes) (State.init (input L m qs)) ⟨program.size, y⟩ n ∧ + Executes W (program.program codes) (State.init (input L m qs)) + (codeAddress L m N qs.length) (n + 1) ∧ y.Bounded W ∧ + (∀ i : Fin qs.length, + y.mem (codeAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val ∧ + y.mem (originalAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = i.val) ∧ + (∀ T ∈ tiles L m N, + (Ico (y.mem (offsetAddress L m N qs.length + (T.1 * numBands L m N + T.2))) + (y.mem (offsetAddress L m N qs.length + (T.1 * numBands L m N + T.2 + 1)))).image + (fun j => y.mem (codeAddress L m N qs.length + j)) = + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2).image + (fun w => (PrunedQuery.queryIndex w).val)) ∧ + (∀ j < qs.length, y.mem (codeAddress L m N qs.length + j) < 10 ^ L) ∧ + (∀ t < numBands L m N ^ 2, + y.mem (offsetAddress L m N qs.length + t) ≤ + y.mem (offsetAddress L m N qs.length + (t + 1)) ∧ + y.mem (offsetAddress L m N qs.length + (t + 1)) ≤ qs.length) ∧ + (∀ j < qs.length, y.mem (originalAddress L m N qs.length + j) < qs.length) ∧ + Function.Injective + (fun j : Fin qs.length => y.mem (originalAddress L m N qs.length + j.val)) ∧ + (range qs.length).image (fun j => y.mem (originalAddress L m N qs.length + j)) = + range qs.length ∧ + y.mem (offsetAddress L m N qs.length + numBands L m N ^ 2) = qs.length ∧ + (∀ a, a < inputLimit qs.length ∨ memoryLimit L m N qs.length ≤ a → + y.mem a = (State.init (input L m qs) : State 96).data.mem a) := by + obtain ⟨hi, y, n, hn, hr, he, hb, hv, ho, ht, hf⟩ := runs_wide hm qs hW + obtain ⟨hperm, hinj, hcover⟩ := originals_permutation qs y.mem (fun i => (hv i).2) + refine ⟨hi, y, n, (Nat.succ_le_succ hn).trans (timeBound_add_one_le L m N qs.length), + hr, he, hb, hv, ?_, ?_, ?_, hperm, hinj, hcover, ht, hf⟩ + · intro T hT + exact code_span_eq_wantedStrings hm qs y.mem T hT (fun i => (hv i).1) ho + · intro j hj + obtain ⟨i, hi⟩ := + (CountingSort.position_bijective (tileKey L m N ∘ inputPairs qs) qs.length).surjective + ⟨j, hj⟩ + have hp := congrArg (fun k : Fin qs.length => k.val) hi + change CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val = j at hp + have hvj := (hv i).1 + rw [hp] at hvj + rw [hvj] + exact (PrunedQuery.queryIndex _).isLt + · intro t ht + rw [ho t ht.le, ho (t + 1) (Nat.succ_le_of_lt ht)] + exact ⟨Nat.count_mono_left (fun _ _ hk => Nat.lt_succ_of_lt hk), Nat.count_le _⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Groups.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Groups.lean new file mode 100644 index 0000000000..9e289791b9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Groups.lean @@ -0,0 +1,155 @@ +/- +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.Wanted.Flat + +/-! +# Consumer meaning of the computed query groups + +## Main results + +* `Wanted.code_span_eq_wantedStrings`: the actual stored offsets select exactly the + original finite `wantedStrings` codes of each tile. +* `Wanted.originals_permutation`: the actual original-position words form a permutation, + retaining all occurrences, including duplicate pairs. + +These representation lemmas consume the memory equations returned by the initialized run. +They neither replace the native grouping code nor supply any input certificate to it. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Finset + +/-- An in-range tile key identifies exactly the original row and column bands. -/ +theorem tileKey_eq_iff {L m N : ℕ} (hm : m ≤ L) (i j : Fin N) (T : ℕ × ℕ) + (hT : T ∈ tiles L m N) : + tileKey L m N (i.val, j.val) = T.1 * numBands L m N + T.2 ↔ + bandOf L m i.val = T.1 ∧ bandOf L m j.val = T.2 := by + have hb : T.1 < numBands L m N ∧ T.2 < numBands L m N := by + simpa only [tiles, mem_product, mem_range] using hT + let u : Fin (numBands L m N) := ⟨bandOf L m i.val, bandOf_lt_numBands hm N i i.isLt⟩ + let v : Fin (numBands L m N) := ⟨bandOf L m j.val, bandOf_lt_numBands hm N j j.isLt⟩ + constructor + · intro hk + have he : finProdFinEquiv (u, v) = finProdFinEquiv + ((⟨T.1, hb.1⟩ : Fin (numBands L m N)), ⟨T.2, hb.2⟩) := by + apply Fin.ext + change bandOf L m j.val + numBands L m N * bandOf L m i.val = + T.2 + numBands L m N * T.1 + unfold tileKey at hk + nlinarith only [hk] + have hp := finProdFinEquiv.injective he + exact ⟨congrArg (fun p => p.1.val) hp, congrArg (fun p => p.2.val) hp⟩ + · rintro ⟨hi, hj⟩ + simp only [tileKey, hi, hj] + +/-- A tile's actual stored half-open span contains exactly the existing mathematical +`wantedStrings` codes. Duplicate list occurrences have the same set meaning and are +retained separately by the original-position permutation. -/ +theorem code_span_eq_wantedStrings {L m N : ℕ} (hm : m ≤ L) + (qs : List (Fin N × Fin N)) (μ : ℕ → ℕ) (T : ℕ × ℕ) (hT : T ∈ tiles L m N) + (hvalues : ∀ i : Fin qs.length, + μ (codeAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val) + (hoffsets : ∀ t ≤ numBands L m N ^ 2, + μ (offsetAddress L m N qs.length + t) = + Nat.count (fun k => (tileKey L m N ∘ inputPairs qs) k < t) qs.length) : + (Ico (μ (offsetAddress L m N qs.length + (T.1 * numBands L m N + T.2))) + (μ (offsetAddress L m N qs.length + (T.1 * numBands L m N + T.2 + 1)))).image + (fun j => μ (codeAddress L m N qs.length + j)) = + (wantedStrings (MaskTable.layout hm) qs.toFinset T.1 T.2).image + (fun w => (PrunedQuery.queryIndex w).val) := by + let key := tileKey L m N ∘ inputPairs qs + let values := fun i => (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (inputPairs qs i).1 (inputPairs qs i).2)).val + let t := T.1 * numBands L m N + T.2 + have hb : T.1 < numBands L m N ∧ T.2 < numBands L m N := by + simpa only [tiles, mem_product, mem_range] using hT + have ht : t < numBands L m N ^ 2 := by + dsimp [t] + nlinarith only [Nat.mul_le_mul_right (numBands L m N) (Nat.succ_le_of_lt hb.1), hb.2] + have hv (i : ℕ) (hi : i < qs.length) : + μ (codeAddress L m N qs.length + CountingSort.position key qs.length i) = values i := by + dsimp only [values] + rw [inputPairs_get qs ⟨i, hi⟩] + exact hvalues ⟨i, hi⟩ + rw [hoffsets _ ht.le, hoffsets _ (Nat.succ_le_of_lt ht)] + rw [CountingSort.scatter_span_image key qs.length t _ values hv] + simp only [wantedStrings, image_image, Function.comp_def] + ext code + constructor + · intro hc + obtain ⟨i, hi, hc⟩ := mem_image.mp hc + obtain ⟨hin, hik⟩ := mem_filter.mp hi + have hiQ := mem_range.mp hin + have hp : inputPairs qs i = ((qs.get ⟨i, hiQ⟩).1.val, (qs.get ⟨i, hiQ⟩).2.val) := + inputPairs_get qs ⟨i, hiQ⟩ + have hk : tileKey L m N ((qs.get ⟨i, hiQ⟩).1.val, (qs.get ⟨i, hiQ⟩).2.val) = t := by + simpa only [key, Function.comp_def, hp] using hik + have hband := (tileKey_eq_iff hm (qs.get ⟨i, hiQ⟩).1 (qs.get ⟨i, hiQ⟩).2 T hT).mp hk + exact mem_image.mpr ⟨qs.get ⟨i, hiQ⟩, + mem_filter.mpr ⟨List.mem_toFinset.mpr (List.get_mem _ _), hband⟩, + by simpa only [values, hp] using hc⟩ + · intro hc + obtain ⟨q, hq, hc⟩ := mem_image.mp hc + obtain ⟨hq, hband⟩ := mem_filter.mp hq + obtain ⟨i, rfl⟩ := List.mem_iff_get.mp (List.mem_toFinset.mp hq) + have hp := inputPairs_get qs i + have hk : key i.val = t := by + dsimp only [key, Function.comp_def] + rw [hp] + exact (tileKey_eq_iff hm (qs.get i).1 (qs.get i).2 T hT).mpr hband + exact mem_image.mpr ⟨i.val, mem_filter.mpr ⟨mem_range.mpr i.isLt, hk⟩, + by simpa only [values, hp] using hc⟩ + +/-- The actual stored original indices are in range, injective, and cover every input +position. Equal original coordinate pairs therefore retain their separate occurrences. -/ +theorem originals_permutation {L m N : ℕ} (qs : List (Fin N × Fin N)) (μ : ℕ → ℕ) + (h : ∀ i : Fin qs.length, + μ (originalAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = i.val) : + (∀ j < qs.length, μ (originalAddress L m N qs.length + j) < qs.length) ∧ + Function.Injective (fun j : Fin qs.length => μ (originalAddress L m N qs.length + j.val)) ∧ + (range qs.length).image (fun j => μ (originalAddress L m N qs.length + j)) = + range qs.length := by + let key := tileKey L m N ∘ inputPairs qs + have inverse (j : Fin qs.length) : ∃ i : Fin qs.length, + CountingSort.position key qs.length i.val = j.val ∧ + μ (originalAddress L m N qs.length + j.val) = i.val := by + obtain ⟨i, hi⟩ := (CountingSort.position_bijective key qs.length).surjective j + have hp := congrArg (fun k : Fin qs.length => k.val) hi + exact ⟨i, hp, hp ▸ h i⟩ + have hrange (j : ℕ) (hj : j < qs.length) : + μ (originalAddress L m N qs.length + j) < qs.length := by + obtain ⟨i, _, hi⟩ := inverse ⟨j, hj⟩ + exact hi ▸ i.isLt + refine ⟨hrange, ?_, ?_⟩ + · intro i j hij + obtain ⟨a, ha, hia⟩ := inverse i + obtain ⟨b, hb, hjb⟩ := inverse j + have hab : a.val = b.val := hia.symm.trans (hij.trans hjb) + exact Fin.ext (ha.symm.trans ((congrArg (CountingSort.position key qs.length) hab).trans hb)) + · ext i + constructor + · intro hi + obtain ⟨j, hj, rfl⟩ := mem_image.mp hi + exact mem_range.mpr (hrange j (mem_range.mp hj)) + · intro hi + have hin := mem_range.mp hi + exact mem_image.mpr ⟨CountingSort.position key qs.length i, + mem_range.mpr (CountingSort.position_lt key hin), h ⟨i, hin⟩⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Input.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Input.lean new file mode 100644 index 0000000000..9af69d9fc2 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Input.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.Wanted.Exec +public import Complexity.Foundations.Data.List.RowMajor +import Mathlib.Data.List.GetD + +/-! +# Ordinary initialized wanted-position inputs + +## Main definitions + +* `Wanted.input`: four raw headers followed by the ordinary interleaved coordinate words. +* `Wanted.inputPairs`: a zero extension of that original finite list for natural-index invariants. + +## Main results + +* `Wanted.input_fits`: the original header, length and coordinate words fit the admitted width. +* `Wanted.input_word`: initialized memory contains each original coordinate at its actual address. +* `Wanted.exec_input`: the fixed program computes the grouped arrays from actual `State.init`. + +No powers, valid masks, tile keys, offsets, query codes or permutation words occur in the input. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Four unsigned scalar headers followed by each row/column pair in original list order. +The standard `State.init` length word precedes this payload at address zero. -/ +def input {N : ℕ} (L m : ℕ) (qs : List (Fin N × Fin N)) : List ℕ := + [L, m, N, qs.length] ++ (List.finRange qs.length).flatMap (fun i => + List.ofFn (fun s : Fin 2 => if s = 0 then (qs.get i).1.val else (qs.get i).2.val)) + +/-- Ordinary input positions extended by zero outside the supplied list. This is a +mathematical view of original words, not a query-code or layout certificate. -/ +def inputPairs {N : ℕ} (qs : List (Fin N × Fin N)) (i : ℕ) : ℕ × ℕ := + if hi : i < qs.length then ((qs.get ⟨i, hi⟩).1.val, (qs.get ⟨i, hi⟩).2.val) else (0, 0) + +/-- Every in-range position is exactly its original pair. -/ +@[simp] +theorem inputPairs_get {N : ℕ} (qs : List (Fin N × Fin N)) (i : Fin qs.length) : + inputPairs qs i.val = ((qs.get i).1.val, (qs.get i).2.val) := by + simp [inputPairs, i.isLt] + +/-- The native length header and coordinate input end at the concrete allocation boundary. -/ +theorem input_length {N : ℕ} (L m : ℕ) (qs : List (Fin N × Fin N)) : + (input L m qs).length + 1 = inputLimit qs.length := by + simp only [input, List.flatMap_ofFn_prod, List.length_append, List.length_ofFn, + List.length_cons, List.length_nil, inputLimit] + omega + +/-- Both original coordinates are below the original matrix dimension. -/ +theorem inputPairs_lt {N : ℕ} (qs : List (Fin N × Fin N)) (i : ℕ) (hi : i < qs.length) : + (inputPairs qs i).1 < N ∧ (inputPairs qs i).2 < N := by + have he := inputPairs_get qs ⟨i, hi⟩ + rw [he] + exact ⟨(qs.get ⟨i, hi⟩).1.isLt, (qs.get ⟨i, hi⟩).2.isLt⟩ + +/-- The exact initialized row/column word, with address calculations in original list order. -/ +theorem input_word {N : ℕ} (L m : ℕ) (qs : List (Fin N × Fin N)) (i : Fin qs.length) + (s : Fin 2) : + (State.init (input L m qs) : State 96).data.mem (5 + 2 * i.val + s.val) = + if s = 0 then (qs.get i).1.val else (qs.get i).2.val := by + change (Data.init (input L m qs) : Data 96).mem _ = _ + rw [show 5 + 2 * i.val + s.val = (4 + (i.val * 2 + s.val)) + 1 by omega, + Data.init_mem_succ] + unfold input + rw [List.getD_append_right _ _ _ _ (by simp)] + simp only [List.length_cons, List.length_nil, Nat.reduceAdd, Nat.add_sub_cancel_left] + exact List.getD_flatMap_ofFn (fun j t => + if t = (0 : Fin 2) then (qs.get j).1.val else (qs.get j).2.val) 0 i s + +/-- Header, length and every coordinate word fit from the same original numerical budget. -/ +theorem input_fits {w L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (hfit : Fits w (wordBudget L m N qs.length)) : InputFits w (input L m qs) := by + have hl := local_fits hfit + have hend := memoryLimit_eq L m N qs.length + have h2 := Nat.zero_le (2 ^ L) + have h10 := Nat.zero_le (10 ^ L) + have hfN : Fits w N := hl.of_le (by omega) + refine ⟨?_, ?_⟩ + · have hi := input_length L m qs + apply hl.of_le + unfold inputLimit at hi + omega + · 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 | rfl | rfl) | hv + · exact hl.of_le (by omega) + · exact hl.of_le (by omega) + · exact hfN + · exact hl.of_le (by omega) + · obtain ⟨i, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨s, rfl⟩ := List.mem_ofFn.mp hv + split + · exact hfN.of_le (qs.get i).1.isLt.le + · exact hfN.of_le (qs.get i).2.isLt.le + +/-- Complete execution from the actual initialized list, with guaranteed input fit and a +bounded returned state. All auxiliary tables, counts, offsets and codes are computed natively. -/ +theorem exec_input {w L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (hfit : Fits w (wordBudget L m N qs.length)) : + InputFits w (input L m qs) ∧ + ∃ y, ExecWithin w program (State.init (input L m qs) : State 96).data y + (timeBound L m N qs.length) ∧ Parameters L m N qs.length y ∧ y.Bounded w ∧ + (∀ i : Fin qs.length, + y.mem (codeAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val ∧ + y.mem (originalAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = i.val) ∧ + (∀ t ≤ numBands L m N ^ 2, y.mem (offsetAddress L m N qs.length + t) = + Nat.count (fun k => (tileKey L m N ∘ inputPairs qs) k < t) qs.length) ∧ + y.mem (offsetAddress L m N qs.length + numBands L m N ^ 2) = qs.length ∧ + (∀ a, a < inputLimit qs.length ∨ memoryLimit L m N qs.length ≤ a → + y.mem a = (State.init (input L m qs) : State 96).data.mem a) := by + have hfitInput := input_fits hm qs hfit + let x : Data 96 := (State.init (input L m qs)).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 hQ : x.mem 4 = qs.length := by simp [x, State.init, Data.init, input] + have hwords (i : ℕ) (hi : i < qs.length) : + x.mem (5 + 2 * i) = (inputPairs qs i).1 ∧ + x.mem (5 + 2 * i + 1) = (inputPairs qs i).2 := by + have hr := input_word L m qs ⟨i, hi⟩ 0 + have hc := input_word L m qs ⟨i, hi⟩ 1 + rw [inputPairs_get qs ⟨i, hi⟩] + simpa only [Fin.val_zero, Fin.val_one, Nat.add_zero, + ↓reduceIte, Fin.isValue, Fin.reduceEq] using And.intro hr hc + obtain ⟨y, he, hp, hv, ho, hlast, hf⟩ := exec_program hm (inputPairs qs) + (inputPairs_lt qs) x hL hM hN hQ hwords hfit + refine ⟨hfitInput, y, he, hp, he.bounded (Data.Bounded.init hfitInput), ?_, ho, hlast, hf⟩ + intro i + simpa only [inputPairs_get] using hv i.val i.isLt + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Meaning.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Meaning.lean new file mode 100644 index 0000000000..af2e555cfc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Meaning.lean @@ -0,0 +1,127 @@ +/- +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.Wanted.Pack +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Codes +public import Complexity.Foundations.Data.Nat.OfDigits +public import Complexity.Foundations.Data.Finset.OrderedFilter + +/-! +# Wanted query codes and the existing output strings + +## Main results + +* `Wanted.pack_output_list`: packing the inner mask and compressed outer digits recovers + the existing pruned evaluator's output numbering, including repeated zero digits. +* `Wanted.pack_outStrOfPos`: the computed mask and the ordinary row/column offsets give + exactly the original thin-product query, without a caller-supplied output string. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open QueryDigits PrunedQuery + +/-- The pruned evaluator uses row-major digits for ordinary outer outputs. -/ +theorem outputDigits_outer (i j : Fin 3) : + (outputDigits.symm (.z i j)).val = 3 * i.val + j.val := by decide +revert + +/-- Its inner output is the existing private term numbered nine. -/ +theorem outputDigits_inner : (outputDigits.symm .z0).val = 9 := by decide + +/-- The arithmetic recurrence reconstructs every output list from its inner flags and +the two outer-coordinate lists. It does not need a supplied cardinality certificate. -/ +theorem pack_output_list (zs : List OutVar) : + pack zs.length (Nat.ofDigits 2 (zs.map fun z => if z.IsInner then 1 else 0)) + (Nat.ofDigits 3 ((zs.filter fun z => ¬z.IsInner).map fun z => z.rowIndex.val)) + (Nat.ofDigits 3 ((zs.filter fun z => ¬z.IsInner).map fun z => z.colIndex.val)) = + Nat.ofDigits 10 (zs.map fun z => (outputDigits.symm z).val) := by + induction zs with + | nil => simp [pack] + | cons z zs ih => + cases z with + | z i j => + have hi : i.val < 3 := i.isLt + have hj : j.val < 3 := j.isLt + simpa [pack, OutVar.IsInner, OutVar.rowIndex, OutVar.colIndex, Nat.ofDigits, + Nat.add_mul_mod_self_left, Nat.add_mul_div_left, Nat.mod_eq_of_lt hi, + Nat.div_eq_of_lt hi, Nat.mod_eq_of_lt hj, Nat.div_eq_of_lt hj, + outputDigits_outer] using congrArg (fun t => 3 * i.val + j.val + 10 * t) ih + | z0 => + simpa [pack, OutVar.IsInner, Nat.ofDigits, Nat.add_mul_mod_self_left, + Nat.add_mul_div_left, outputDigits_inner] using ih + +private theorem outer_list {L m : ℕ} (w : OutStr L) (h : (innerSetO w).card = m) + (f : OutVar → Fin 3) : + ((List.ofFn w).filter fun z => ¬z.IsInner).map (fun z => (f z).val) = + List.ofFn (fun k => (f (w ((innerSetO w)ᶜ.orderEmbOfFin + (Finset.card_compl_of_card_eq _ h) k))).val) := by + rw [Finset.ofFn_orderEmbOfFin (innerSetO w)ᶜ + (Finset.card_compl_of_card_eq _ h) (fun i => (f (w i)).val), + Finset.sort_eq_filter_finRange, + List.ofFn_eq_map, List.filter_map, List.map_map] + have hp : (fun i : Fin L => decide (¬(w i).IsInner)) = + (fun i => decide (i ∈ (innerSetO w)ᶜ)) := by + funext i + simp [innerSetO] + simp only [Function.comp_def, hp] + +/-- A packed mask and the ordered outer coordinates recover every original output code. -/ +theorem pack_queryIndex {L m : ℕ} (w : OutStr L) (h : (innerSetO w).card = m) : + pack L (MaskTable.maskCode (innerSetO w)).val + (finFunctionFinEquiv (rowO w h)).val (finFunctionFinEquiv (colO w h)).val = + (queryIndex w).val := by + have hm : Nat.ofDigits 2 ((List.ofFn w).map fun z => if z.IsInner then 1 else 0) = + (MaskTable.maskCode (innerSetO w)).val := by + rw [List.map_ofFn] + calc + _ = Nat.ofDigits 2 (List.ofFn fun i => + ((if i ∈ innerSetO w then 1 else 0) : Fin 2).val) := by + congr 2 + funext i + by_cases hi : (w i).IsInner <;> simp [innerSetO, hi] + _ = _ := Nat.ofDigits_finFunctionFinEquiv _ + have hr : Nat.ofDigits 3 (((List.ofFn w).filter fun z => ¬z.IsInner).map + fun z => z.rowIndex.val) = (finFunctionFinEquiv (rowO w h)).val := by + rw [outer_list w h OutVar.rowIndex, Nat.ofDigits_finFunctionFinEquiv] + rfl + have hc : Nat.ofDigits 3 (((List.ofFn w).filter fun z => ¬z.IsInner).map + fun z => z.colIndex.val) = (finFunctionFinEquiv (colO w h)).val := by + rw [outer_list w h OutVar.colIndex, Nat.ofDigits_finFunctionFinEquiv] + rfl + have ho : Nat.ofDigits 10 ((List.ofFn w).map fun z => (outputDigits.symm z).val) = + (queryIndex w).val := by + rw [List.map_ofFn] + exact Nat.ofDigits_finFunctionFinEquiv (fun i => outputDigits.symm (w i)) + simpa only [List.length_ofFn, hm, hr, hc, ho] using pack_output_list (List.ofFn w) + +/-- The actual deterministic table mask and the original offsets compute precisely +`outStrOfPos`, in the unchanged pruned evaluator's least-significant-first numbering. -/ +theorem pack_outStrOfPos {L m : ℕ} (hm : m ≤ L) (I J : ℕ) : + pack L (MaskTable.selectedMask + (blockOf (MaskTable.layout hm) I, blockOf (MaskTable.layout hm) J)).val + (I % N0 L m) (J % N0 L m) = + (queryIndex (outStrOfPos (MaskTable.layout hm) I J)).val := by + let lay := MaskTable.layout hm + let Q := lay.table (blockOf lay I, blockOf lay J) + have hQ : Q.card = m := lay.table_card _ + have h := pack_queryIndex (outStrOfPos lay I J) + (by simpa only [outStrOfPos, innerSetO_outStrOf] using hQ) + unfold outStrOfPos at h + rw [rowO_outStrOf, colO_outStrOf, innerSetO_outStrOf] at h + change pack L (MaskTable.maskCode (MaskTable.maskSet + (MaskTable.selectedMask (blockOf lay I, blockOf lay J)))).val + (finFunctionFinEquiv (finFunctionFinEquiv.symm (offsetOf L m I))).val + (finFunctionFinEquiv (finFunctionFinEquiv.symm (offsetOf L m J))).val = _ at h + simpa only [MaskTable.maskCode_maskSet, Equiv.apply_symm_apply, offsetOf, N0, + outStrOfPos, lay] using h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Memory.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Memory.lean new file mode 100644 index 0000000000..1154f251cc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Memory.lean @@ -0,0 +1,123 @@ +/- +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.Wanted.Route +public import Complexity.Algorithms.Sorting.Counting + +/-! +# Concrete memory layout of native wanted-query grouping + +## Main definitions + +* `Wanted.tableAddress`: computed selected-mask table after the readonly query input. +* `Wanted.Parameters`: scalar register equalities used by all three grouping passes. +* `Wanted.wordBudget`: an original-input-derived sufficient bound for unsigned words. + +All layout definitions are numeric specifications of actual address instructions. Neither +the layout nor the parameter record contains a run, time bound or assumed solver. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The original length word, four headers and two words per wanted position. -/ +def inputLimit (Q : ℕ) : ℕ := Q * 2 + 5 + +/-- The forward table follows a complete `2^L`-word inverse table. -/ +def tableAddress (L Q : ℕ) : ℕ := inputLimit Q + 2 ^ L + +/-- Tile counts follow both computed tables. -/ +def countAddress (L Q : ℕ) : ℕ := tableAddress L Q + 2 ^ L + +/-- One immutable prefix offset per tile, followed by one terminal offset. -/ +def offsetAddress (L m N Q : ℕ) : ℕ := countAddress L Q + numBands L m N ^ 2 + +/-- Mutable stable-scatter cursors follow the immutable prefix offsets. -/ +def cursorAddressBase (L m N Q : ℕ) : ℕ := offsetAddress L m N Q + numBands L m N ^ 2 + 1 + +/-- One actual query code per original occurrence. -/ +def codeAddress (L m N Q : ℕ) : ℕ := cursorAddressBase L m N Q + numBands L m N ^ 2 + +/-- Every code retains its original input position in the adjacent array. -/ +def originalAddress (L m N Q : ℕ) : ℕ := codeAddress L m N Q + Q + +/-- One past both grouped output arrays. -/ +def memoryLimit (L m N Q : ℕ) : ℕ := originalAddress L m N Q + Q + +/-- This sufficient budget covers the reused parameter kernels as well as all routing +and grouping arithmetic. It depends only on the original four scalar parameters. -/ +def wordBudget (L m N Q : ℕ) : ℕ := + 256 * (BandPreparation.wordBudget L m N + routeBudget L m N (tableAddress L Q) + + memoryLimit L m N Q + L + N + Q + numBands L m N ^ 2 + 10 ^ L + 2 ^ L + 16) + +/-- Persistent scalar register values after actual table relocation and allocation. -/ +structure Parameters (L m N Q : ℕ) (x : Data 96) : Prop where + /-- The reused table kernel's actual scalar contents. -/ + tables : BandPreparation.TableParameters L m (inputLimit Q) (tableAddress L Q) x + /-- The one-query router's actual scalar contents. -/ + route : RouteParameters L m N (tableAddress L Q) x + /-- Original query input starts after its four scalar headers. -/ + queries : x.regs queries = 5 + /-- Number of positions, retaining repetitions. -/ + queryCount : x.regs queryCount = Q + /-- Allocated tile counts. -/ + counts : x.regs counts = countAddress L Q + /-- Immutable prefix offsets. -/ + offsets : x.regs offsets = offsetAddress L m N Q + /-- Mutable cursor addresses. -/ + cursors : x.regs cursors = cursorAddressBase L m N Q + /-- Grouped query codes. -/ + codes : x.regs codes = codeAddress L m N Q + /-- Original-position permutation. -/ + originals : x.regs originals = originalAddress L m N Q + /-- Actual tile count. -/ + tileCount : x.regs tileCount = numBands L m N ^ 2 + /-- The entire readonly original prefix. -/ + inputEnd : x.regs inputEnd = inputLimit Q + /-- One past the allocated arrays. -/ + memoryEnd : x.regs memoryEnd = memoryLimit L m N Q + +/-- The exact scalar register footprint shared by the grouping passes. -/ +def parameterRegisters : Finset (Fin 96) := + {queries, queryCount, counts, offsets, cursors, codes, originals, tileCount, + bandWidth, inputEnd, memoryEnd, BandPreparation.levels, BandPreparation.inner, + BandPreparation.outerSize, BandPreparation.masks, BandPreparation.inverse, + BandPreparation.table, BandPreparation.root, BandPreparation.square, + BandPreparation.bands, BandPreparation.zero, BandPreparation.one, BandPreparation.two} + +/-- Persistent scalar contracts survive preservation of their concrete registers. -/ +theorem Parameters.reframe {L m N Q : ℕ} {x y : Data 96} (hx : Parameters L m N Q x) + (h : ∀ i ∈ parameterRegisters, y.regs i = x.regs i) : Parameters L m N Q y := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact ⟨(h _ (by decide)).trans hx.tables.levels, + (h _ (by decide)).trans hx.tables.inner, (h _ (by decide)).trans hx.tables.masks, + (h _ (by decide)).trans hx.tables.inverse, (h _ (by decide)).trans hx.tables.table, + (h _ (by decide)).trans hx.tables.square, (h _ (by decide)).trans hx.tables.zero, + (h _ (by decide)).trans hx.tables.one, (h _ (by decide)).trans hx.tables.two⟩ + · exact ⟨(h _ (by decide)).trans hx.route.levels, + (h _ (by decide)).trans hx.route.outerSize, (h _ (by decide)).trans hx.route.root, + (h _ (by decide)).trans hx.route.bandWidth, (h _ (by decide)).trans hx.route.bands, + (h _ (by decide)).trans hx.route.table⟩ + all_goals first + | exact (h _ (by decide)).trans hx.queries + | exact (h _ (by decide)).trans hx.queryCount + | exact (h _ (by decide)).trans hx.counts + | exact (h _ (by decide)).trans hx.offsets + | exact (h _ (by decide)).trans hx.cursors + | exact (h _ (by decide)).trans hx.codes + | exact (h _ (by decide)).trans hx.originals + | exact (h _ (by decide)).trans hx.tileCount + | exact (h _ (by decide)).trans hx.inputEnd + | exact (h _ (by decide)).trans hx.memoryEnd + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Pack.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Pack.lean new file mode 100644 index 0000000000..574eae7853 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Pack.lean @@ -0,0 +1,224 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.PackProgram +import Mathlib.Tactic.Linarith + +/-! +# Execution of the native wanted-output digit loop + +## Main results + +* `Wanted.exec_pack`: actual unsigned packing with an explicit linear instruction bound + and unchanged memory, for arbitrary initial scratch-register contents. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage.QueryDigits + +/-- Scalar contents retained throughout packing. These are only register equalities. -/ +structure PackParameters (L : ℕ) (x : Data 16) : Prop where + /-- Original depth. -/ + depth : x.regs depth = L + /-- Initialized unit. -/ + one : x.regs one = 1 + /-- Binary radix. -/ + two : x.regs two = 2 + /-- Compressed offset radix. -/ + three : x.regs three = 3 + /-- Output radix. -/ + ten : x.regs ten = 10 + /-- Inner output digit. -/ + nine : x.regs nine = 9 + +/-- A consumed prefix and the remaining arithmetic recurrence give the original output. -/ +structure PackProgress (L M R C : ℕ) (x : Data 16) : Prop where + /-- Persistent constants and depth. -/ + fixed : PackParameters L x + /-- A coordinate is visited at most once. -/ + level_le : x.regs level ≤ L + /-- Binary division decreases the mask. -/ + mask_le : x.regs mask ≤ M + /-- Ternary division decreases the row offset. -/ + row_le : x.regs row ≤ R + /-- Ternary division decreases the column offset. -/ + col_le : x.regs col ≤ C + /-- Actual next output place. -/ + power_eq : x.regs power = 10 ^ x.regs level + /-- Already emitted digits fit below the next place. -/ + code_lt : x.regs code < x.regs power + /-- Prefix and residual recurrence have the desired complete value. -/ + value : x.regs code + x.regs power * + pack (L - x.regs level) (x.regs mask) (x.regs row) (x.regs col) = pack L M R C + +private theorem parameters_after_body {L : ℕ} {x y : Data 16} + (hx : PackParameters L x) {w c : ℕ} (he : ExecWithin w packBody x y c) : + PackParameters L y := by + have hf (i : Fin 16) (hi : i ∈ ({depth, one, two, three, ten, nine} : Finset _)) : + y.regs i = x.regs i := by + apply he.regs_eq + have h : ∀ i ∈ ({depth, one, two, three, ten, nine} : Finset (Fin 16)), + packBody.Preserves i := by + simp only [packBody, Code.Preserves, Op.dest, Op.bin] + decide + exact h i hi + exact ⟨(hf _ (by decide)).trans hx.depth, (hf _ (by decide)).trans hx.one, + (hf _ (by decide)).trans hx.two, (hf _ (by decide)).trans hx.three, + (hf _ (by decide)).trans hx.ten, (hf _ (by decide)).trans hx.nine⟩ + +/-- One coordinate's actual test, arithmetic and register updates. The numeric budget is +derived from original inputs, rather than an assumed bound on intermediate states. -/ +theorem exec_packBody {w L M R C : ℕ} (x : Data 16) (hx : PackProgress L M R C x) + (hc : (Cond.lt level depth).Holds x) + (hfit : Fits w (M + R + C + L + 32 * 10 ^ L + 32)) : + ∃ y, ExecWithin w packBody x y 13 ∧ PackProgress L M R C 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.depth] using hc + have hr := Nat.mod_lt (x.regs row) (by decide : 0 < 3) + have hcol := Nat.mod_lt (x.regs col) (by decide : 0 < 3) + have hp : x.regs power ≤ 10 ^ L := by + rw [hx.power_eq] + exact Nat.pow_le_pow_right (by decide) hx.level_le + have hmfit : Fits w (x.regs mask / 2) := hfit.of_le + ((Nat.div_le_self _ _).trans (by have := hx.mask_le; omega)) + have hbit : Fits w (x.regs mask % 2) := hfit.of_le (by + have := Nat.mod_lt (x.regs mask) (by decide : 0 < 2) + omega) + have hpfit : Fits w (x.regs power * 10) := 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 hb : x.regs mask % 2 = 0 + · let d := (x.regs row % 3) * 3 + x.regs col % 3 + have hd : d < 9 := by dsimp [d]; omega + have hrd : Fits w (x.regs row % 3) := hfit.of_le (by omega) + have hr3 : Fits w (x.regs row % 3 * 3) := hfit.of_le (by omega) + have hcd : Fits w (x.regs col % 3) := hfit.of_le (by omega) + have hdf : Fits w d := hfit.of_le (by omega) + have hrq : Fits w (x.regs row / 3) := hfit.of_le + ((Nat.div_le_self _ _).trans (by have := hx.row_le; omega)) + have hcq : Fits w (x.regs col / 3) := hfit.of_le + ((Nat.div_le_self _ _).trans (by have := hx.col_le; omega)) + have hmul : Fits w (d * x.regs power) := hfit.of_le (by nlinarith) + have hadd : Fits w (x.regs code + d * x.regs power) := hfit.of_le (by + have := hx.code_lt + nlinarith) + let y := ((((((((((((x.set bit 0).set mask (x.regs mask / 2)).set digit + (x.regs row % 3)).set digit (x.regs row % 3 * 3)).set temporary + (x.regs col % 3)).set digit d).set row (x.regs row / 3)).set + col (x.regs col / 3)).set temporary (d * x.regs power)).set code + (x.regs code + d * x.regs power)).set power (x.regs power * 10)).set + level (x.regs level + 1)) + have he : ExecWithin w packBody x y 13 := by + ram_eval [packBody, y, d, hx.fixed.two, hx.fixed.three, hx.fixed.one, + hx.fixed.ten, hb, hmfit, hbit, hpfit, hjfit, hrd, hr3, hcd, hdf, + hrq, hcq, hmul, hadd, Nat.lt_one_iff, + mask, bit, row, col, digit, temporary, code, power, level, two, three, one, ten, + Fin.mk.injEq] + refine ⟨y, he, ⟨parameters_after_body hx.fixed he, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩, + rfl, ?_⟩ + · simp only [y, Data.set_regs, Function.update_self]; omega + · simpa [y, mask, bit, digit, row, col, temporary, code, power, level] using + (Nat.div_le_self (x.regs mask) 2).trans hx.mask_le + · simpa [y, mask, bit, digit, row, col, temporary, code, power, level] using + (Nat.div_le_self (x.regs row) 3).trans hx.row_le + · simpa [y, mask, bit, digit, row, col, temporary, code, power, level] using + (Nat.div_le_self (x.regs col) 3).trans hx.col_le + · simp [y, power, level, hx.power_eq, pow_succ] + · have := hx.code_lt + simp only [y, Data.set_regs, Function.update_self, code, power, level, + Function.update_of_ne (by decide : (4 : Fin 16) ≠ 5), + Function.update_of_ne (by decide : (4 : Fin 16) ≠ 6), + Function.update_of_ne (by decide : (6 : Fin 16) ≠ 5)] + nlinarith + · have hv := hx.value + rw [hrest, pack, ite_eq_left hb] at hv + simp only [y, Data.set_regs, Function.update_apply] + simp only [mask, row, col, code, power, level, digit, temporary, + Fin.isValue, ↓reduceIte] + dsimp [d] at * + nlinarith only [hv] + · change L - (x.regs level + 1) < L - x.regs level + omega + · have h9 : Fits w 9 := hfit.of_le (by omega) + have hmul : Fits w (9 * x.regs power) := hfit.of_le (by omega) + have hadd : Fits w (x.regs code + 9 * x.regs power) := hfit.of_le (by + have := hx.code_lt + omega) + let y := (((((((x.set bit (x.regs mask % 2)).set mask (x.regs mask / 2)).set + digit 9).set temporary (9 * x.regs power)).set code + (x.regs code + 9 * x.regs power)).set power (x.regs power * 10)).set + level (x.regs level + 1)) + have he : ExecWithin w packBody x y 13 := by + ram_eval [packBody, y, hx.fixed.two, hx.fixed.nine, hx.fixed.one, hx.fixed.ten, + hb, hmfit, hbit, hpfit, hjfit, h9, hmul, hadd, Nat.lt_one_iff, + mask, bit, row, col, digit, temporary, code, power, level, two, three, one, ten, nine, + Fin.mk.injEq] + refine ⟨y, he, ⟨parameters_after_body hx.fixed he, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩, + rfl, ?_⟩ + · simp only [y, Data.set_regs, Function.update_self]; omega + · simpa [y, mask, bit, digit, row, col, temporary, code, power, level] using + (Nat.div_le_self (x.regs mask) 2).trans hx.mask_le + · simpa [y, mask, bit, digit, row, col, temporary, code, power, level] using hx.row_le + · simpa [y, mask, bit, digit, row, col, temporary, code, power, level] using hx.col_le + · simp [y, power, level, hx.power_eq, pow_succ] + · have := hx.code_lt + change x.regs code + 9 * x.regs power < x.regs power * 10 + omega + · have hv := hx.value + rw [hrest, pack, ite_eq_right hb] at hv + simp only [bit, Fin.isValue, mask, digit, temporary, power, code, level, Data.set_regs, + ne_eq, Fin.reduceEq, not_false_eq_true, Function.update_of_ne, Function.update_self, + row, col, y] + nlinarith only [hv] + · change L - (x.regs level + 1) < L - x.regs level + omega + +/-- Execute the entire fixed packer from ordinary register inputs. Every branch, division, +remainder, index update and loop control is charged; memory and the depth are preserved. -/ +theorem exec_pack {w : ℕ} (L M R C : ℕ) (x : Data 16) + (hL : x.regs depth = L) (hM : x.regs mask = M) (hR : x.regs row = R) + (hC : x.regs col = C) (hfit : Fits w (M + R + C + L + 32 * 10 ^ L + 32)) : + ∃ y, ExecWithin w packCode x y (15 * L + 10) ∧ + y.regs code = pack L M R C ∧ y.regs depth = L ∧ y.mem = x.mem := by + have hf (v : ℕ) (hv : v ≤ 10) : Fits w v := hfit.of_le (by omega) + let start := ((((((((x.set one 1).set two 2).set three 3).set ten 10).set nine 9).set + code 0).set level 0).set power 1) + have hs : ExecWithin w packHead x start 8 := by + ram_eval [packHead, start, hf 0 (by decide), hf 1 (by decide), hf 2 (by decide), + hf 3 (by decide), hf 9 (by decide), hf 10 (by decide), + one, two, three, ten, nine, code, level, power, Fin.mk.injEq] + have hi : PackProgress L M R C start := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · constructor <;> simp [start, depth, one, two, three, ten, nine, code, level, power, hL] + all_goals simp [start, one, two, three, ten, nine, code, level, power, + mask, row, col, hM, hR, hC] + let I (z : Data 16) : Prop := PackProgress L M R C z ∧ z.mem = x.mem + have hb (z : Data 16) (hz : I z) (hc : (Cond.lt level depth).Holds z) : + ∃ u, ExecWithin w packBody z u 13 ∧ I u ∧ + L - u.regs level < L - z.regs level := by + obtain ⟨u, he, hu, hm, ht⟩ := exec_packBody z hz.1 hc hfit + exact ⟨u, he, ⟨hu, hm.trans hz.2⟩, ht⟩ + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun z => L - z.regs level) hb ⟨hi, rfl⟩ + have hend : y.regs level = L := by + simp only [Cond.holds_lt, hy.1.fixed.depth] at hc + have := hy.1.level_le + omega + refine ⟨y, ?_, ?_, hy.1.fixed.depth, hy.2⟩ + · apply (hs.seq he).mono + have hstart : start.regs level = 0 := by simp [start, level, power] + rw [hstart, Nat.sub_zero] + omega + · simpa only [hend, Nat.sub_self, pack, Nat.mul_zero, Nat.add_zero] using hy.1.value + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/PackProgram.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/PackProgram.lean new file mode 100644 index 0000000000..bc4010845d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/PackProgram.lean @@ -0,0 +1,83 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.QueryDigits + +/-! +# Native output-digit packing + +## Main definitions + +* `Wanted.packCode`: fixed unsigned code for a binary mask and compressed row/column offsets. + +The mask and both offsets are consumed in registers. No query string or dispatch table is +provided to this code, and no memory is read or written. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM + +/-- Register containing the original depth. -/ +abbrev depth : Fin 16 := 0 +/-- Remaining binary subset mask. -/ +abbrev mask : Fin 16 := 1 +/-- Remaining compressed row offset. -/ +abbrev row : Fin 16 := 2 +/-- Remaining compressed column offset. -/ +abbrev col : Fin 16 := 3 +/-- Accumulated output code. -/ +abbrev code : Fin 16 := 4 +/-- Number of consumed coordinates. -/ +abbrev level : Fin 16 := 5 +/-- Next base-ten place. -/ +abbrev power : Fin 16 := 6 +/-- Current binary mask digit. -/ +abbrev bit : Fin 16 := 7 +/-- Current output digit. -/ +abbrev digit : Fin 16 := 8 +/-- Arithmetic scratch register. -/ +abbrev temporary : Fin 16 := 9 +/-- Constant one. -/ +abbrev one : Fin 16 := 10 +/-- Constant two. -/ +abbrev two : Fin 16 := 11 +/-- Constant three. -/ +abbrev three : Fin 16 := 12 +/-- Constant ten. -/ +abbrev ten : Fin 16 := 13 +/-- Constant nine. -/ +abbrev nine : Fin 16 := 14 + +/-- Initialize loop counters and ordinary unsigned constants. -/ +def packHead : Code .standard 16 := ram[ + one ← #1; two ← #2; three ← #3; ten ← #10; nine ← #9; + code ← #0; level ← #0; power ← #1 +] + +/-- One actual digit decision, with row-major outer digits and inner digit nine. -/ +def packBody : Code .standard 16 := ram[ + bit ← mask % two; mask ← mask / two; + if bit < one then [ + digit ← row % three; digit ← digit * three; + temporary ← col % three; digit ← digit + temporary; + row ← row / three; col ← col / three + ] else [ digit ← nine ]; + temporary ← digit * power; code ← code + temporary; + power ← power * ten; level ← level + one +] + +/-- A single finite program handles every runtime depth and both compressed offsets. -/ +def packCode : Code .standard 16 := + .seq packHead (.loop (.lt level depth) packBody) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Prefix.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Prefix.lean new file mode 100644 index 0000000000..a55e9bf181 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Prefix.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.Wanted.PrefixStep +public import Complexity.Computability.WordRAM.Sequence + +/-! +# Complete native tile-prefix pass + +## Main results + +* `Wanted.exec_prefix`: compute every exact tile span and initialize its scatter cursor, + including the terminal query-count offset, with native loads, stores and loop controls. + +This is an internal implementation API of the complete routing program. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The actual prefix loop initializes every immutable span and mutable cursor. Neither +the offsets nor the total input length are supplied as precomputed memory certificates. -/ +theorem exec_prefix {w L m N Q : ℕ} (key : ℕ → ℕ) + (hkey : ∀ i < Q, key i < numBands L m N ^ 2) (x : Data 96) + (hx : Parameters L m N Q x) + (hcounts : ∀ t < numBands L m N ^ 2, + x.mem (countAddress L Q + t) = Nat.count (fun k => key k = t) Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w prefixCode x y (10 * numBands L m N ^ 2 + 6) ∧ + Parameters L m N Q y ∧ + (∀ t ≤ numBands L m N ^ 2, + y.mem (offsetAddress L m N Q + t) = Nat.count (fun k => key k < t) Q) ∧ + (∀ t < numBands L m N ^ 2, + y.mem (cursorAddressBase L m N Q + t) = Nat.count (fun k => key k < t) Q) ∧ + y.mem (offsetAddress L m N Q + numBands L m N ^ 2) = Q ∧ + (∀ a, a < offsetAddress L m N Q ∨ codeAddress L m N Q ≤ a → y.mem a = x.mem a) := by + let T := numBands L m N ^ 2 + let I (z : Data 96) : Prop := Parameters L m N Q z ∧ z.regs index ≤ T ∧ + z.regs total = Nat.count (fun k => key k < (z.regs index)) Q ∧ + (∀ t < z.regs index, + z.mem (offsetAddress L m N Q + t) = Nat.count (fun k => key k < t) Q ∧ + z.mem (cursorAddressBase L m N Q + t) = Nat.count (fun k => key k < t) Q) ∧ + (∀ t < T, z.mem (countAddress L Q + t) = Nat.count (fun k => key k = t) Q) ∧ + (∀ a, a < offsetAddress L m N Q ∨ codeAddress L m N Q ≤ a → z.mem a = x.mem a) + let start := (x.set index 0).set total 0 + have hs : ExecWithin w (ram[index ← #0; total ← #0] : Code .standard 96) x start 2 := by + ram_eval [start] + have hp : Parameters L m N Q start := hx.reframe (by + intro i hi + apply hs.regs_eq + have h : ∀ i ∈ parameterRegisters, + (ram[index ← #0; total ← #0] : Code .standard 96).Preserves i := by decide +kernel + exact h i hi) + have hi : I start := by + refine ⟨hp, by simp [start, index, total], ?_, by simp [start, index, total], + hcounts, by simp [start]⟩ + simp [start, index, total] + have hb (z : Data 96) (hz : I z) (hc : (Cond.lt index tileCount).Holds z) : + ∃ u, ExecWithin w prefixStep z u 8 ∧ I u ∧ + T - u.regs index < T - z.regs index := by + have hn : z.regs index < T := by + simpa only [Cond.holds_lt, hz.1.tileCount] using hc + obtain ⟨u, he, hu, hidx, hsum, hoff, hcur, hf⟩ := exec_prefixStep key hn z hz.1 rfl + hz.2.2.1 (hz.2.2.2.2.1 _ hn) hfit + refine ⟨u, he, ⟨hu, by rw [hidx]; omega, ?_, ?_, ?_, ?_⟩, by rw [hidx]; omega⟩ + · simpa only [hidx] using hsum + · intro t ht + rw [hidx] at ht + by_cases ht' : t = z.regs index + · subst t + exact ⟨hoff, hcur⟩ + · have hlt : t < z.regs index := by omega + have hsep : offsetAddress L m N Q + t < cursorAddressBase L m N Q := by + unfold cursorAddressBase + omega + have hsep' : offsetAddress L m N Q + z.regs index < cursorAddressBase L m N Q := by + unfold cursorAddressBase + dsimp [T] at hn + omega + rw [hf _ (by omega) (by omega), hf _ (by omega) (by omega)] + exact hz.2.2.2.1 t hlt + · intro t ht + have hcount : countAddress L Q + t < offsetAddress L m N Q := by + unfold offsetAddress + exact Nat.add_lt_add_left ht _ + have hcursor : offsetAddress L m N Q ≤ cursorAddressBase L m N Q := by + unfold cursorAddressBase + omega + rw [hf _ (by omega) (by omega)] + exact hz.2.2.2.2.1 t ht + · intro a ha + have hsep : cursorAddressBase L m N Q + z.regs index < codeAddress L m N Q := by + unfold codeAddress + exact Nat.add_lt_add_left hn _ + have hsep' : offsetAddress L m N Q + z.regs index < codeAddress L m N Q := by + unfold codeAddress cursorAddressBase + dsimp [T] at hn + omega + rw [hf _ (by rcases ha with ha | ha <;> omega) + (by rcases ha with ha | ha <;> unfold cursorAddressBase at * <;> omega)] + exact hz.2.2.2.2.2 a ha + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => T - z.regs index) hb hi + have hn : y.regs index = T := by + have := hy.2.1 + simp only [Cond.holds_lt, hy.1.tileCount] at hc + dsimp [T] at * + omega + have hsum : y.regs total = Nat.count (fun k => key k < T) Q := by + simpa only [hn] using hy.2.2.1 + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have h10 := Nat.zero_le (10 ^ L) + have haddr : Fits w (offsetAddress L m N Q + T) := hl.of_le (by + unfold offsetAddress countAddress tableAddress inputLimit + dsimp [T] + omega) + let z := (y.set pointer (offsetAddress L m N Q + T)).write + (offsetAddress L m N Q + T) (Nat.count (fun k => key k < T) Q) + have hz : ExecWithin w (ram[pointer ← offsets + tileCount; *pointer ← total] : + Code .standard 96) y z 2 := by + ram_eval [z, hy.1.offsets, hy.1.tileCount, hsum, haddr, T, + pointer, offsets, tileCount, total, Fin.mk.injEq] + have zp : Parameters L m N Q z := hy.1.reframe (by + intro i hi + apply hz.regs_eq + have h : ∀ i ∈ parameterRegisters, + (ram[pointer ← offsets + tileCount; *pointer ← total] : Code .standard 96).Preserves i := by + decide +kernel + exact h i hi) + have hlast : Nat.count (fun k => key k < T) Q = Q := Nat.count_of_forall hkey + refine ⟨z, ?_, zp, ?_, ?_, ?_, ?_⟩ + · apply (ExecWithin.seq_assoc.mp (hs.seq (he.seq hz))).mono + have h0 : start.regs index = 0 := by simp [start, index, total] + rw [h0, Nat.sub_zero] + dsimp [T] + omega + · intro t ht + by_cases h : t = T + · subst t + exact Function.update_self _ _ _ + · have ht' : t < y.regs index := by rw [hn]; omega + change Function.update y.mem (offsetAddress L m N Q + T) + (Nat.count (fun k => key k < T) Q) (offsetAddress L m N Q + t) = _ + rw [Function.update_of_ne (by omega)] + exact (hy.2.2.2.1 t ht').1 + · intro t ht + change Function.update y.mem (offsetAddress L m N Q + T) + (Nat.count (fun k => key k < T) Q) (cursorAddressBase L m N Q + t) = _ + rw [Function.update_of_ne (by unfold cursorAddressBase; omega)] + exact (hy.2.2.2.1 t (by rw [hn]; exact ht)).2 + · change Function.update y.mem (offsetAddress L m N Q + T) + (Nat.count (fun k => key k < T) Q) (offsetAddress L m N Q + T) = Q + rw [Function.update_self, hlast] + · intro a ha + change Function.update y.mem (offsetAddress L m N Q + T) + (Nat.count (fun k => key k < T) Q) a = x.mem a + rw [Function.update_of_ne (by + rcases ha with ha | ha + · omega + · unfold codeAddress cursorAddressBase at ha + omega)] + exact hy.2.2.2.2.2 a ha + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/PrefixStep.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/PrefixStep.lean new file mode 100644 index 0000000000..2e3a9d0023 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/PrefixStep.lean @@ -0,0 +1,93 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Setup + +/-! +# One native tile-prefix update + +## Main results + +* `Wanted.exec_prefixStep`: load the actual bucket count, store its immutable start and + mutable cursor, and advance the running sum with every instruction charged. + +This stage lemma is an internal implementation API for the complete grouping execution. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- One actual prefix update; the two stored cells are the only possible memory changes. -/ +theorem exec_prefixStep {w L m N Q t : ℕ} (key : ℕ → ℕ) + (ht : t < numBands L m N ^ 2) (x : Data 96) (hx : Parameters L m N Q x) + (hi : x.regs index = t) (hs : x.regs total = Nat.count (fun k => key k < t) Q) + (hc : x.mem (countAddress L Q + t) = Nat.count (fun k => key k = t) Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w prefixStep x y 8 ∧ Parameters L m N Q y ∧ + y.regs index = t + 1 ∧ y.regs total = Nat.count (fun k => key k < (t + 1)) Q ∧ + y.mem (offsetAddress L m N Q + t) = Nat.count (fun k => key k < t) Q ∧ + y.mem (cursorAddressBase L m N Q + t) = Nat.count (fun k => key k < t) Q ∧ + (∀ a, a ≠ offsetAddress L m N Q + t → a ≠ cursorAddressBase L m N Q + t → + y.mem a = x.mem a) := by + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have h10 := Nat.zero_le (10 ^ L) + have hf (v : ℕ) (hv : v ≤ memoryLimit L m N Q) : Fits w v := hl.of_le (by omega) + have hcount : Fits w (countAddress L Q + t) := hf _ (by + unfold countAddress tableAddress inputLimit + omega) + have hoff : Fits w (offsetAddress L m N Q + t) := hf _ (by + unfold offsetAddress countAddress tableAddress inputLimit + omega) + have hcursor : Fits w (cursorAddressBase L m N Q + t) := hf _ (by + unfold cursorAddressBase offsetAddress countAddress tableAddress inputLimit + omega) + have hadd : Fits w (Nat.count (fun k => key k < t) Q + Nat.count (fun k => key k = t) Q) := + hf _ (by + rw [← CountingSort.count_lt_succ] + have := Nat.count_le (fun i => key i < t + 1) (n := Q) + omega) + have hnext : Fits w (t + 1) := hf _ (by omega) + let s := Nat.count (fun k => key k < t) Q + let c := Nat.count (fun k => key k = t) Q + let y := ((((((((x.set pointer (countAddress L Q + t)).set count c).set + pointer (offsetAddress L m N Q + t)).write (offsetAddress L m N Q + t) s).set + pointer (cursorAddressBase L m N Q + t)).write (cursorAddressBase L m N Q + t) s).set + total (s + c)).set index (t + 1)) + have he : ExecWithin w prefixStep x y 8 := by + ram_eval [prefixStep, y, hx.counts, hx.offsets, hx.cursors, hx.tables.one, + hi, hs, hc, hcount, hoff, hcursor, hadd, hnext, s, c, + pointer, count, counts, offsets, cursors, total, index, BandPreparation.one, + Fin.mk.injEq] + have hp : Parameters L m N Q y := hx.reframe (by + intro i hi + apply he.regs_eq + have h : ∀ i ∈ parameterRegisters, prefixStep.Preserves i := by decide +kernel + exact h i hi) + have hsep : offsetAddress L m N Q + t ≠ cursorAddressBase L m N Q + t := by + unfold cursorAddressBase + omega + refine ⟨y, he, hp, by simp [y, index], ?_, ?_, ?_, ?_⟩ + · simpa only [y, Data.set_regs, Data.write_regs, total, index, + Function.update_of_ne (by decide : (30 : Fin 96) ≠ 23), Function.update_self, + s, c] using (CountingSort.count_lt_succ key Q t).symm + · change Function.update (Function.update x.mem (offsetAddress L m N Q + t) s) + (cursorAddressBase L m N Q + t) s (offsetAddress L m N Q + t) = s + rw [Function.update_of_ne hsep, Function.update_self] + · exact Function.update_self _ _ _ + · intro a ho hc + change Function.update (Function.update x.mem (offsetAddress L m N Q + t) s) + (cursorAddressBase L m N Q + t) s a = x.mem a + rw [Function.update_of_ne hc, Function.update_of_ne ho] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Program.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Program.lean new file mode 100644 index 0000000000..dd5b245ca1 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Program.lean @@ -0,0 +1,199 @@ +/- +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.Wanted.PackProgram +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandTables +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandParameters + +/-! +# Native routing and stable grouping of wanted positions + +The initialized input contains only `L,m,N,Q` and `Q` ordinary row/column pairs. The code +computes the valid-mask tables, band parameters, tile counts, prefix offsets and scatter +positions. Each scatter writes the unchanged pruned-query code and its original position. +No sorted request list, layout certificate or dispatch decision is supplied as input. + +## Main definitions + +* `Wanted.routeCode`: compute one tile and one query code using the computed table. +* `Wanted.program`: computed tables followed by counting, prefix and stable scatter passes. + +These programs use only the existing standard unsigned instructions. Correctness, width, +frame and resource theorems are separate from the definitions. Supporting `exec_*` lemmas +are internal implementation APIs exported across proof modules; consumers use `runs`, +`runs_grouped` or the generic-memory `exec_program` when composing a caller. + +## References + +The routing goal comes from [AlmanWilliams2026, Section 2.4.4] and Anthropic's pinned +[Wanted/Codes.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Wanted/Codes.lean) at revision +`16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. This is original native code with its own charged +proofs, using the existing finite output-string definitions. It does not import the source +Light machine, its sorting procedure or its instruction counts. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Register embedding for the unchanged sixteen-register digit packer. -/ +def packRegisters (i : Fin 16) : Fin 96 := ⟨i.val, by omega⟩ + +/-- Original row of the current query. -/ +abbrev queryRow : Fin 96 := 16 +/-- Original column of the current query. -/ +abbrev queryCol : Fin 96 := 17 +/-- Computed row band. -/ +abbrev rowBand : Fin 96 := 18 +/-- Computed column band. -/ +abbrev colBand : Fin 96 := 19 +/-- Row-major tile key. -/ +abbrev tile : Fin 96 := 20 +/-- Address of the first original row/column pair. -/ +abbrev queries : Fin 96 := 21 +/-- Number of original query positions, including duplicates. -/ +abbrev queryCount : Fin 96 := 22 +/-- Current original query position or tile position. -/ +abbrev index : Fin 96 := 23 +/-- Tile-count array base. -/ +abbrev counts : Fin 96 := 24 +/-- Prefix-offset array base, with one additional terminal cell. -/ +abbrev offsets : Fin 96 := 25 +/-- Current stable-scatter cursor array base. -/ +abbrev cursors : Fin 96 := 26 +/-- Grouped query-code array base. -/ +abbrev codes : Fin 96 := 27 +/-- Grouped original-position array base. -/ +abbrev originals : Fin 96 := 28 +/-- Current tile's query count. -/ +abbrev count : Fin 96 := 29 +/-- Running prefix sum. -/ +abbrev total : Fin 96 := 30 +/-- Number of tiles. -/ +abbrev tileCount : Fin 96 := 31 +/-- Number of original positions in one band. -/ +abbrev bandWidth : Fin 96 := 85 +/-- One past the readonly initialized query input. -/ +abbrev inputEnd : Fin 96 := 86 +/-- One past the grouping arrays. -/ +abbrev memoryEnd : Fin 96 := 87 +/-- Address scratch. -/ +abbrev pointer : Fin 96 := 88 +/-- Saved address of the current scatter cursor. -/ +abbrev cursorAddress : Fin 96 := 89 +/-- Current grouped output position. -/ +abbrev position : Fin 96 := 90 + +/-- Compute just the row-major tile key. No memory is touched. -/ +def tileCode : Code .standard 96 := ram[ + rowBand ← queryRow / bandWidth; colBand ← queryCol / bandWidth; + tile ← rowBand * BandPreparation.bands; tile ← tile + colBand +] + +/-- Compute the selected subset rank and load its actual mask from the computed table. -/ +def routeHead : Code .standard 96 := ram[ + ⟪tileCode⟫; + ⟪.op (.copy (packRegisters depth) BandPreparation.levels)⟫; + ⟪.op (.bin .mod (packRegisters row) queryRow BandPreparation.outerSize)⟫; + ⟪.op (.bin .mod (packRegisters col) queryCol BandPreparation.outerSize)⟫; + ⟪.op (.bin .div (packRegisters mask) queryRow BandPreparation.outerSize)⟫; + ⟪.op (.bin .mod (packRegisters mask) (packRegisters mask) BandPreparation.root)⟫; + ⟪.op (.bin .mul (packRegisters mask) (packRegisters mask) BandPreparation.root)⟫; + ⟪.op (.bin .div (packRegisters temporary) queryCol BandPreparation.outerSize)⟫; + ⟪.op (.bin .mod (packRegisters temporary) (packRegisters temporary) BandPreparation.root)⟫; + ⟪.op (.bin .add (packRegisters mask) (packRegisters mask) (packRegisters temporary))⟫; + ⟪.op (.bin .add (packRegisters temporary) BandPreparation.table (packRegisters mask))⟫; + ⟪.op (.load (packRegisters mask) (packRegisters temporary))⟫ +] + +/-- Compute a tile key and its output code, with actual digit decisions and table load. -/ +def routeCode : Code .standard 96 := .seq routeHead (packCode.rename packRegisters) + +/-- Relocate the computed tables and every grouping array after the initialized input. -/ +def setup : Code .standard 96 := ram[ + pointer ← #4; queryCount ← *pointer; queries ← #5; + inputEnd ← queryCount * BandPreparation.two; inputEnd ← inputEnd + queries; + BandPreparation.inverse ← inputEnd; + BandPreparation.table ← BandPreparation.inverse + BandPreparation.masks; + counts ← BandPreparation.table + BandPreparation.masks; + bandWidth ← BandPreparation.root * BandPreparation.outerSize; + tileCount ← BandPreparation.bands * BandPreparation.bands; + offsets ← counts + tileCount; cursors ← offsets + tileCount; + cursors ← cursors + BandPreparation.one; codes ← cursors + tileCount; + originals ← codes + queryCount; memoryEnd ← originals + queryCount +] + +/-- Zero each tile count once. -/ +def clearCounts : Code .standard 96 := ram[ + index ← #0; + while index < tileCount do [ + pointer ← counts + index; *pointer ← BandPreparation.zero; + index ← index + BandPreparation.one + ] +] + +/-- Load one original pair without changing its input words. -/ +def loadQuery : Code .standard 96 := ram[ + pointer ← index * BandPreparation.two; pointer ← pointer + queries; + queryRow ← *pointer; pointer ← pointer + BandPreparation.one; queryCol ← *pointer +] + +/-- Count one input position in its actual tile, retaining duplicates. -/ +def countStep : Code .standard 96 := ram[ + ⟪loadQuery⟫; ⟪tileCode⟫; pointer ← counts + tile; count ← *pointer; + count ← count + BandPreparation.one; *pointer ← count; + index ← index + BandPreparation.one +] + +/-- Scan the original list once to obtain tile counts. -/ +def countQueries : Code .standard 96 := + .seq (ram[index ← #0]) (.loop (.lt index queryCount) countStep) + +/-- Store both the immutable prefix offset and the initial scatter cursor of one tile. -/ +def prefixStep : Code .standard 96 := ram[ + pointer ← counts + index; count ← *pointer; + pointer ← offsets + index; *pointer ← total; + pointer ← cursors + index; *pointer ← total; + total ← total + count; index ← index + BandPreparation.one +] + +/-- Compute exact tile spans, including the terminal offset after the last tile. -/ +def prefixCode : Code .standard 96 := ram[ + index ← #0; total ← #0; + while index < tileCount do [ ⟪prefixStep⟫ ]; + pointer ← offsets + tileCount; *pointer ← total +] + +/-- Append one code and original position to its tile, advancing the actual cursor. -/ +def scatterWrite : Code .standard 96 := ram[ + cursorAddress ← cursors + tile; position ← *cursorAddress; + pointer ← codes + position; ⟪.op (.store pointer (packRegisters code))⟫; + pointer ← originals + position; *pointer ← index; + position ← position + BandPreparation.one; *cursorAddress ← position; + index ← index + BandPreparation.one +] + +/-- Recompute one query's code and scatter it stably into its counted tile. -/ +def scatterStep : Code .standard 96 := + .seq loadQuery (.seq routeCode scatterWrite) + +/-- The second original-order scan retains every occurrence and its original position. -/ +def scatter : Code .standard 96 := + .seq (ram[index ← #0]) (.loop (.lt index queryCount) scatterStep) + +/-- Complete initialized native routing; no mathematical layout is an input word. -/ +def program : Code .standard 96 := + .seq (.seq BandPreparation.header (.seq BandPreparation.countCode + (.seq setup BandPreparation.tablesCode))) + (.seq clearCounts (.seq countQueries (.seq prefixCode scatter))) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Route.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Route.lean new file mode 100644 index 0000000000..46624c5690 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Route.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.Wanted.Program +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Meaning +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling + +/-! +# Native routing of one wanted position + +## Main results + +* `Wanted.exec_route`: actual divisions, selected-mask lookup and digit packing return + exactly the tile and `outStrOfPos` code for an original matrix position. + +The table contract is an ordinary memory equation supplied by the actual computed-table +routine. It supplies neither an output string nor an execution or time certificate. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Scalar values read by the one-query router. The table may be relocated by its caller. -/ +structure RouteParameters (L m N tab : ℕ) (x : Data 96) : Prop where + /-- Original depth. -/ + levels : x.regs BandPreparation.levels = L + /-- Compressed outer dimension. -/ + outerSize : x.regs BandPreparation.outerSize = N0 L m + /-- Computed root of the valid-subset count. -/ + root : x.regs BandPreparation.root = K0 L m + /-- Computed band width. -/ + bandWidth : x.regs bandWidth = bandSize L m + /-- Computed band count. -/ + bands : x.regs BandPreparation.bands = numBands L m N + /-- Actual forward-table base. -/ + table : x.regs BandPreparation.table = tab + +/-- Numeric bound for all arithmetic performed by one router and its digit kernel. -/ +def routeBudget (L m N tab : ℕ) : ℕ := + 64 * (L + N + tab + numBands L m N ^ 2 + 10 ^ L + 2 ^ L + 1) + +/-- Only the packer bank and three computed routing registers are written. -/ +def RouteLocal (i : Fin 96) : Prop := i.val < 16 ∨ i = rowBand ∨ i = colBand ∨ i = tile + +instance (i : Fin 96) : Decidable (RouteLocal i) := + inferInstanceAs (Decidable (i.val < 16 ∨ i = rowBand ∨ i = colBand ∨ i = tile)) + +private theorem route_preserves (i : Fin 96) (hi : ¬RouteLocal i) : routeCode.Preserves i := by + revert i + decide +kernel + +/-- The register map embeds the packer without identifying any pair of registers. -/ +theorem packRegisters_injective : Function.Injective packRegisters := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 96 => i.val) h) + +/-- Actual routing on one admitted original position. Original row/column words, all +persistent parameters and all memory survive; only the declared local registers change. -/ +theorem exec_route {w L m N tab : ℕ} (hm : m ≤ L) (I J : Fin N) (x : Data 96) + (hx : RouteParameters L m N tab x) + (hI : x.regs queryRow = I.val) (hJ : x.regs queryCol = J.val) + (htable : ∀ gh : Fin (K0 L m) × Fin (K0 L m), + x.mem (tab + (MaskTable.pairRank gh).val) = (MaskTable.selectedMask gh).val) + (hfit : Fits w (routeBudget L m N tab)) : + ∃ y, ExecWithin w routeCode x y (15 * L + 25) ∧ + y.regs rowBand = bandOf L m I.val ∧ y.regs colBand = bandOf L m J.val ∧ + y.regs tile = bandOf L m I.val * numBands L m N + bandOf L m J.val ∧ + y.regs (packRegisters code) = + (PrunedQuery.queryIndex (outStrOfPos (MaskTable.layout hm) I J)).val ∧ + y.mem = x.mem ∧ (∀ i, ¬RouteLocal i → y.regs i = x.regs i) := by + let lay := MaskTable.layout hm + let g := blockOf lay I + let h := blockOf lay J + let a := (MaskTable.selectedMask (g, h)).val + let r := I.val % N0 L m + let c := J.val % N0 L m + let β := bandOf L m I.val + let γ := bandOf L m J.val + let b := numBands L m N + let k := β * b + γ + let u := I.val / N0 L m + let v := J.val / N0 L m + let rank := g.val * K0 L m + h.val + have hβ : β < b := bandOf_lt_numBands hm N I I.isLt + have hγ : γ < b := bandOf_lt_numBands hm N J J.isLt + have hk : k < b ^ 2 := by + dsimp [k] + nlinarith only [Nat.mul_le_mul_right b (Nat.succ_le_of_lt hβ), hγ] + have hbb : b ≤ b ^ 2 := by nlinarith only [hβ] + have h2 : 2 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have hu : u ≤ N := (Nat.div_le_self _ _).trans I.isLt.le + have hv : v ≤ N := (Nat.div_le_self _ _).trans J.isLt.le + have hg : g.val < K0 L m := g.isLt + have hh : h.val < K0 L m := h.isLt + have hrank : rank < 2 ^ L := by + have hp : rank = (MaskTable.pairRank (g, h)).val := by + change g.val * K0 L m + h.val = h.val + K0 L m * g.val + ring + rw [hp] + exact (MaskTable.pairRank (g, h)).isLt.trans_le (Nat.choose_le_two_pow L m) + have hmul : g.val * K0 L m ≤ rank := by dsimp [rank]; omega + have ha : a < 2 ^ L := (MaskTable.selectedMask (g, h)).isLt + have hr : r ≤ N := (Nat.mod_le _ _).trans I.isLt.le + have hc : c ≤ N := (Nat.mod_le _ _).trans J.isLt.le + have hbfit (v : ℕ) (hv : v ≤ L + N + tab + b ^ 2 + 10 ^ L + 2 ^ L + 1) : + Fits w v := hfit.of_le (by + change v ≤ 64 * (L + N + tab + b ^ 2 + 10 ^ L + 2 ^ L + 1) + omega) + have hfβ : Fits w β := hbfit _ (by omega) + have hfγ : Fits w γ := hbfit _ (by omega) + have hfprod : Fits w (β * b) := hbfit _ (by dsimp [k] at hk; omega) + have hfk : Fits w k := hbfit _ (by omega) + have hfr : Fits w r := hbfit _ (by omega) + have hfc : Fits w c := hbfit _ (by omega) + have hfu : Fits w u := hbfit _ (by omega) + have hfv : Fits w v := hbfit _ (by omega) + have hfg : Fits w g.val := hbfit _ (by + have hle : g.val ≤ u := Nat.mod_le _ _ + omega) + have hfh : Fits w h.val := hbfit _ (by + have hle : h.val ≤ v := Nat.mod_le _ _ + omega) + have hfgmul : Fits w (g.val * K0 L m) := hbfit _ (by omega) + have hfrank : Fits w rank := hbfit _ (by omega) + have hfaddr : Fits w (tab + rank) := hbfit _ (by omega) + have hload : x.mem (tab + rank) = a := by + convert htable (g, h) using 1 + congr 1 + change tab + rank = tab + (h.val + K0 L m * g.val) + dsimp [rank] + ring + let y := (((((((((((((((x.set rowBand β).set colBand γ).set tile (β * b)).set + tile k).set (packRegisters depth) L).set (packRegisters row) r).set + (packRegisters col) c).set (packRegisters mask) u).set (packRegisters mask) g.val).set + (packRegisters mask) (g.val * K0 L m)).set (packRegisters temporary) v).set + (packRegisters temporary) h.val).set (packRegisters mask) rank).set + (packRegisters temporary) (tab + rank)).set (packRegisters mask) a) + have he : ExecWithin w routeHead x y 15 := by + dsimp only [β, γ, b, k, bandOf] at hfβ hfγ hfprod hfk + dsimp only [r, c, u, v, g, h, blockOf, lay] at hfr hfc hfu hfv hfg hfh + dsimp only [rank, g, h, blockOf, lay] at hfgmul hfrank hfaddr hload + ram_eval [routeHead, tileCode, y, hx.bandWidth, hx.bands, hx.levels, hx.outerSize, + hx.root, hx.table, hI, hJ, hfβ, hfγ, hfprod, hfk, hfr, hfc, hfu, hfv, hfg, hfh, + hfgmul, hfrank, hfaddr, hload, β, γ, b, k, r, c, u, v, rank, g, h, blockOf, + lay, bandOf, packRegisters, depth, mask, row, col, temporary, + queryRow, queryCol, rowBand, colBand, tile, BandPreparation.levels, + BandPreparation.outerSize, BandPreparation.root, BandPreparation.table, + BandPreparation.bands, bandWidth, Fin.reduceFinMk, Fin.mk.injEq] + have hpackfit : Fits w (a + r + c + L + 32 * 10 ^ L + 32) := hfit.of_le (by + change a + r + c + L + 32 * 10 ^ L + 32 ≤ + 64 * (L + N + tab + b ^ 2 + 10 ^ L + 2 ^ L + 1) + omega) + obtain ⟨z, hz, hcode, _, hmem⟩ := exec_pack L a r c (y.comap packRegisters) + (by simp [y, packRegisters, depth, row, col, mask, temporary, rowBand, colBand, tile]) + (by simp [y, packRegisters, mask]) + (by simp [y, packRegisters, row, col, mask, temporary, depth]) + (by simp [y, packRegisters, col, mask, temporary, depth]) hpackfit + have hrun : ExecWithin w routeCode x (z.extend packRegisters y) (15 * L + 25) := by + exact (he.seq (hz.rename packRegisters_injective)).mono (by omega) + refine ⟨z.extend packRegisters y, hrun, ?_, ?_, ?_, ?_, hmem, ?_⟩ + · have hp : (packCode.rename packRegisters).Preserves rowBand := by decide +kernel + exact ((hz.rename packRegisters_injective).regs_eq hp).trans (by simp [y, rowBand, + colBand, tile, packRegisters, depth, row, col, mask, temporary, β]) + · have hp : (packCode.rename packRegisters).Preserves colBand := by decide +kernel + exact ((hz.rename packRegisters_injective).regs_eq hp).trans (by simp [y, rowBand, + colBand, tile, packRegisters, depth, row, col, mask, temporary, γ]) + · have hp : (packCode.rename packRegisters).Preserves tile := by decide +kernel + exact ((hz.rename packRegisters_injective).regs_eq hp).trans (by simp [y, rowBand, + colBand, tile, packRegisters, depth, row, col, mask, temporary]; rfl) + · have hout := congrArg (fun q : Data 16 => q.regs code) + (Data.comap_extend packRegisters_injective z y) + exact hout.trans (hcode.trans (pack_outStrOfPos hm I J)) + · intro i hi + exact hrun.regs_eq (route_preserves i hi) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Scatter.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Scatter.lean new file mode 100644 index 0000000000..15a26876e3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Scatter.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.Wanted.ScatterStep + +/-! +# Complete native wanted-query scatter + +## Main results + +* `Wanted.exec_scatter`: compute every original position's output code and stable destination, + retaining repetitions and writing the original-position permutation with a charged bound. + +This internal implementation API composes the actual router and stores. The finite scatter +permutation is proved from occurrence counts; it is neither supplied nor checked by an oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The actual original-order scan fills all grouped codes and the original-position +permutation. Its only writes are scatter cursors and the two grouped output arrays. -/ +theorem exec_scatter {w L m N Q : ℕ} (hm : m ≤ L) (pairs : ℕ → ℕ × ℕ) + (hpairs : ∀ i < Q, (pairs i).1 < N ∧ (pairs i).2 < N) (x : Data 96) + (hx : Parameters L m N Q x) + (hinput : ∀ i < Q, x.mem (5 + 2 * i) = (pairs i).1 ∧ + x.mem (5 + 2 * i + 1) = (pairs i).2) + (htable : ∀ gh : Fin (K0 L m) × Fin (K0 L m), + x.mem (tableAddress L Q + (MaskTable.pairRank gh).val) = (MaskTable.selectedMask gh).val) + (hcursors : ∀ t < numBands L m N ^ 2, + x.mem (cursorAddressBase L m N Q + t) = Nat.count (fun k => (tileKey L m N ∘ pairs) k < t) Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w scatter x y ((15 * L + 41) * Q + 3) ∧ Parameters L m N Q y ∧ + (∀ i < Q, + y.mem (codeAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (pairs i).1 (pairs i).2)).val ∧ + y.mem (originalAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q i) = i) ∧ + (∀ t < numBands L m N ^ 2, y.mem (cursorAddressBase L m N Q + t) = + Nat.count (fun k => (tileKey L m N ∘ pairs) k < (t + 1)) Q) ∧ + (∀ a, a < cursorAddressBase L m N Q ∨ memoryLimit L m N Q ≤ a → + y.mem a = x.mem a) := by + let key := tileKey L m N ∘ pairs + let I (z : Data 96) : Prop := Parameters L m N Q z ∧ z.regs index ≤ Q ∧ + (∀ i < Q, z.mem (5 + 2 * i) = (pairs i).1 ∧ + z.mem (5 + 2 * i + 1) = (pairs i).2) ∧ + (∀ gh : Fin (K0 L m) × Fin (K0 L m), + z.mem (tableAddress L Q + (MaskTable.pairRank gh).val) = (MaskTable.selectedMask gh).val) ∧ + (∀ t < numBands L m N ^ 2, z.mem (cursorAddressBase L m N Q + t) = + Nat.count (fun k => key k < t) Q + Nat.count (fun k => key k = t) (z.regs index)) ∧ + (∀ i < z.regs index, + z.mem (codeAddress L m N Q + CountingSort.position key Q i) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (pairs i).1 (pairs i).2)).val ∧ + z.mem (originalAddress L m N Q + CountingSort.position key Q i) = i) ∧ + (∀ a, a < cursorAddressBase L m N Q ∨ memoryLimit L m N Q ≤ a → z.mem a = x.mem a) + let start := x.set index 0 + have hs : ExecWithin w (ram[index ← #0] : Code .standard 96) x start 1 := by + ram_eval [start] + have hp : Parameters L m N Q start := hx.reframe (by + intro i hi + apply hs.regs_eq + have h : ∀ i ∈ parameterRegisters, (ram[index ← #0] : Code .standard 96).Preserves i := by + decide +kernel + exact h i hi) + have hi : I start := by + refine ⟨hp, by simp [start], hinput, htable, ?_, by simp [start], by simp [start]⟩ + intro t ht + simpa only [start, Data.set_regs, Function.update_self, Data.set_mem, + Nat.count_zero, Nat.add_zero] using hcursors t ht + have hb (z : Data 96) (hz : I z) (hc : (Cond.lt index queryCount).Holds z) : + ∃ u, ExecWithin w scatterStep z u (15 * L + 39) ∧ I u ∧ + Q - u.regs index < Q - z.regs index := by + have hn : z.regs index < Q := by + simpa only [Cond.holds_lt, hz.1.queryCount] using hc + obtain ⟨u, he, hu, hidx, hcur, hcode, horig, hf⟩ := exec_scatterStep hm pairs hn + (hpairs _ hn) z hz.1 rfl (hz.2.2.1 _ hn) hz.2.2.2.1 hz.2.2.2.2.1 hfit + change ∀ a, a ≠ cursorAddressBase L m N Q + key (z.regs index) → + a ≠ codeAddress L m N Q + CountingSort.position key Q (z.regs index) → + a ≠ originalAddress L m N Q + CountingSort.position key Q (z.regs index) → + u.mem a = z.mem a at hf + have hk : key (z.regs index) < numBands L m N ^ 2 := tileKey_lt hm _ (hpairs _ hn).1 + (hpairs _ hn).2 + have hpos : CountingSort.position key Q (z.regs index) < Q := + CountingSort.position_lt key hn + have hcursor : cursorAddressBase L m N Q + key (z.regs index) < codeAddress L m N Q := by + unfold codeAddress + omega + have hcodes : codeAddress L m N Q + CountingSort.position key Q (z.regs index) < + originalAddress L m N Q := by + unfold originalAddress + omega + have horiginals : originalAddress L m N Q + CountingSort.position key Q (z.regs index) < + memoryLimit L m N Q := by + unfold memoryLimit + omega + have horder : cursorAddressBase L m N Q ≤ codeAddress L m N Q ∧ + codeAddress L m N Q ≤ originalAddress L m N Q ∧ + originalAddress L m N Q ≤ memoryLimit L m N Q := by + simp only [codeAddress, originalAddress, memoryLimit] + omega + have hlow (a : ℕ) (ha : a < cursorAddressBase L m N Q) : u.mem a = z.mem a := + hf a (by omega) (by omega) (by omega) + refine ⟨u, he, ⟨hu, by rw [hidx]; omega, ?_, ?_, ?_, ?_, ?_⟩, by rw [hidx]; omega⟩ + · intro i hi + have h2 := Nat.zero_le (2 ^ L) + have ha : 5 + 2 * i + 1 < cursorAddressBase L m N Q := by + unfold cursorAddressBase offsetAddress countAddress tableAddress inputLimit + omega + rw [hlow _ (by omega), hlow _ ha] + exact hz.2.2.1 i hi + · intro gh + have hr : (MaskTable.pairRank gh).val < 2 ^ L := + (MaskTable.pairRank gh).isLt.trans_le (Nat.choose_le_two_pow L m) + have ha : tableAddress L Q + (MaskTable.pairRank gh).val < + cursorAddressBase L m N Q := by + unfold cursorAddressBase offsetAddress countAddress + omega + rw [hlow _ ha] + exact hz.2.2.2.1 gh + · intro t ht + rw [hidx] + exact hcur t ht + · intro i hi + rw [hidx] at hi + by_cases h : i = z.regs index + · subst i + exact ⟨hcode, horig⟩ + · have hip : i < z.regs index := by omega + have hiQ : i < Q := hip.trans hn + have hp : CountingSort.position key Q i < Q := CountingSort.position_lt key hiQ + have hne : CountingSort.position key Q i ≠ CountingSort.position key Q (z.regs index) := by + intro heq + have hfin : (⟨i, hiQ⟩ : Fin Q) = ⟨z.regs index, hn⟩ := + CountingSort.position_injective key Q heq + exact h (congrArg (fun j : Fin Q => j.val) hfin) + have hop : originalAddress L m N Q = codeAddress L m N Q + Q := rfl + rw [hf _ (by omega) (by omega) (by omega), hf _ (by omega) (by omega) (by omega)] + exact hz.2.2.2.2.2.1 i hip + · intro a ha + rw [hf _ (by rcases ha with ha | ha <;> omega) + (by rcases ha with ha | ha <;> omega) (by rcases ha with ha | ha <;> omega)] + exact hz.2.2.2.2.2.2 a ha + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun z => Q - z.regs index) hb hi + have hn : y.regs index = Q := by + have hle := hy.2.1 + simp only [Cond.holds_lt, hy.1.queryCount] at hc + omega + refine ⟨y, ?_, hy.1, ?_, ?_, hy.2.2.2.2.2.2⟩ + · apply (hs.seq he).mono + have h0 : start.regs index = 0 := by simp [start] + rw [h0, Nat.sub_zero] + nlinarith + · intro i hi + exact hy.2.2.2.2.2.1 i (hn ▸ hi) + · intro t ht + have hv := hy.2.2.2.2.1 t ht + rw [hn] at hv + exact hv.trans (CountingSort.count_lt_succ key Q t).symm + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/ScatterStep.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/ScatterStep.lean new file mode 100644 index 0000000000..26e9806f11 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/ScatterStep.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.Wanted.ScatterWrite +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.CountStep + +/-! +# One computed wanted-query scatter + +## Main results + +* `Wanted.exec_scatterStep`: the actual coordinate loads, table lookup, output-digit loop + and three scatter stores preserve the exact stable-counting cursor invariant. + +This stage contract is an internal implementation API; the query code is calculated from the +original pair, rather than supplied as a source memory word or an output certificate. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Load, compute and scatter one original query, preserving the original input and exact +per-tile cursors. Every loop in the digit computation and every address instruction is charged. -/ +theorem exec_scatterStep {w L m N Q n : ℕ} (hm : m ≤ L) (pairs : ℕ → ℕ × ℕ) + (hn : n < Q) (hpair : (pairs n).1 < N ∧ (pairs n).2 < N) (x : Data 96) + (hx : Parameters L m N Q x) (hi : x.regs index = n) + (hinput : x.mem (5 + 2 * n) = (pairs n).1 ∧ + x.mem (5 + 2 * n + 1) = (pairs n).2) + (htable : ∀ gh : Fin (K0 L m) × Fin (K0 L m), + x.mem (tableAddress L Q + (MaskTable.pairRank gh).val) = (MaskTable.selectedMask gh).val) + (hcursors : ∀ t < numBands L m N ^ 2, + x.mem (cursorAddressBase L m N Q + t) = Nat.count (fun k => (tileKey L m N ∘ pairs) k < t) Q + + Nat.count (fun k => (tileKey L m N ∘ pairs) k = t) n) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w scatterStep x y (15 * L + 39) ∧ Parameters L m N Q y ∧ + y.regs index = n + 1 ∧ + (∀ t < numBands L m N ^ 2, + y.mem (cursorAddressBase L m N Q + t) = + Nat.count (fun k => (tileKey L m N ∘ pairs) k < t) Q + + Nat.count (fun k => (tileKey L m N ∘ pairs) k = t) (n + 1)) ∧ + y.mem (codeAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q n) = + (PrunedQuery.queryIndex (outStrOfPos (MaskTable.layout hm) (pairs n).1 (pairs n).2)).val ∧ + y.mem (originalAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q n) = n ∧ + (∀ a, a ≠ cursorAddressBase L m N Q + (tileKey L m N ∘ pairs) n → + a ≠ codeAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q n → + a ≠ originalAddress L m N Q + CountingSort.position (tileKey L m N ∘ pairs) Q n → + y.mem a = x.mem a) := by + let key := tileKey L m N ∘ pairs + let p := CountingSort.position key Q n + let I : Fin N := ⟨(pairs n).1, hpair.1⟩ + let J : Fin N := ⟨(pairs n).2, hpair.2⟩ + have hk : key n < numBands L m N ^ 2 := tileKey_lt hm _ hpair.1 hpair.2 + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have h10 := Nat.zero_le (10 ^ L) + have h2 := Nat.zero_le (2 ^ L) + have hn2 : Fits w (n * 2) := hl.of_le (by omega) + have ha : Fits w (n * 2 + 5) := hl.of_le (by omega) + have ha1 : Fits w (n * 2 + 5 + 1) := hl.of_le (by omega) + have hI : x.mem (n * 2 + 5) = I.val := + (congrArg x.mem (by omega : n * 2 + 5 = 5 + 2 * n)).trans hinput.1 + have hJ : x.mem (n * 2 + 5 + 1) = J.val := + (congrArg x.mem (by omega : n * 2 + 5 + 1 = 5 + 2 * n + 1)).trans hinput.2 + let loaded := ((((x.set pointer (n * 2)).set pointer (n * 2 + 5)).set queryRow I.val).set + pointer (n * 2 + 5 + 1)).set queryCol J.val + have hload : ExecWithin w loadQuery x loaded 5 := by + ram_eval [loadQuery, loaded, hx.queries, hx.tables.two, hx.tables.one, hi, hI, hJ, + hn2, ha, ha1, pointer, index, queries, queryRow, queryCol, + BandPreparation.one, BandPreparation.two, Fin.mk.injEq] + have lp : Parameters L m N Q loaded := hx.reframe (by + intro i hi + apply hload.regs_eq + have hp : ∀ i ∈ parameterRegisters, loadQuery.Preserves i := by decide +kernel + exact hp i hi) + have hrfit : Fits w (routeBudget L m N (tableAddress L Q)) := hfit.of_le (by + unfold wordBudget + omega) + obtain ⟨routed, hr, _, _, htile, hvalue, hrmem, hrregs⟩ := exec_route hm I J loaded lp.route + (by simp [loaded, queryRow, queryCol, pointer]) (by simp [loaded]) htable hrfit + change routed.mem = x.mem at hrmem + have rp : Parameters L m N Q routed := lp.reframe (by + intro i hi + have hf : ∀ i ∈ parameterRegisters, ¬RouteLocal i := by decide +kernel + exact hrregs i (hf i hi)) + have hidx : routed.regs index = n := + (hrregs index (by decide)).trans (by simp [loaded, index, pointer, queryRow, queryCol, hi]) + have hcursor : routed.mem (cursorAddressBase L m N Q + key n) = p := by + rw [hrmem] + exact hcursors _ hk + obtain ⟨y, hy, yp, hiy, hcode, horig, hcur, hf⟩ := exec_scatterWrite key hn hk routed rp hidx + htile hvalue hcursor hfit + refine ⟨y, ?_, yp, hiy, ?_, hcode, horig, ?_⟩ + · exact (hload.seq (hr.seq hy)).mono (by omega) + · intro t ht + change y.mem (cursorAddressBase L m N Q + t) = + Nat.count (fun k => key k < t) Q + Nat.count (fun k => key k = t) (n + 1) + rw [Nat.count_succ] + by_cases h : key n = t + · subst t + rw [hcur] + simp only [↓reduceIte] + rfl + · have hlow : cursorAddressBase L m N Q + t < codeAddress L m N Q := by + unfold codeAddress + omega + have horiglow : codeAddress L m N Q ≤ originalAddress L m N Q := by + unfold originalAddress + omega + rw [hf _ (by omega) (by omega) (by omega), hrmem, hcursors t ht, ite_eq_right h] + rfl + · intro a hc hv ho + exact (hf a hc hv ho).trans (congrFun hrmem a) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/ScatterWrite.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/ScatterWrite.lean new file mode 100644 index 0000000000..5c86a4f551 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/ScatterWrite.lean @@ -0,0 +1,105 @@ +/- +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.Wanted.Setup + +/-! +# Native stable-scatter stores + +## Main results + +* `Wanted.exec_scatterWrite`: store one computed code and original position at the actual + cursor, then increment that cursor and the original-input counter. + +This is an internal implementation API. The scatter position is the existing counting-sort +formula, whose range follows from the original input length, rather than an execution oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Three actual memory stores suffice for one stable occurrence. Address fits and +separation follow from the original query length and the proved counting-sort position. -/ +theorem exec_scatterWrite {w L m N Q n v : ℕ} (key : ℕ → ℕ) (hn : n < Q) + (hk : key n < numBands L m N ^ 2) (x : Data 96) (hx : Parameters L m N Q x) + (hi : x.regs index = n) (ht : x.regs tile = key n) + (hv : x.regs (packRegisters code) = v) + (hc : x.mem (cursorAddressBase L m N Q + key n) = CountingSort.position key Q n) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w scatterWrite x y 9 ∧ Parameters L m N Q y ∧ y.regs index = n + 1 ∧ + y.mem (codeAddress L m N Q + CountingSort.position key Q n) = v ∧ + y.mem (originalAddress L m N Q + CountingSort.position key Q n) = n ∧ + y.mem (cursorAddressBase L m N Q + key n) = CountingSort.position key Q n + 1 ∧ + (∀ a, a ≠ cursorAddressBase L m N Q + key n → + a ≠ codeAddress L m N Q + CountingSort.position key Q n → + a ≠ originalAddress L m N Q + CountingSort.position key Q n → y.mem a = x.mem a) := by + let p := CountingSort.position key Q n + have hp : p < Q := CountingSort.position_lt key hn + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have h10 := Nat.zero_le (10 ^ L) + have h2 := Nat.zero_le (2 ^ L) + have hf (v : ℕ) (hv : v ≤ memoryLimit L m N Q) : Fits w v := hl.of_le (by omega) + have hcursor : Fits w (cursorAddressBase L m N Q + key n) := hf _ (by + unfold cursorAddressBase offsetAddress countAddress tableAddress inputLimit + omega) + have hcode : Fits w (codeAddress L m N Q + p) := hf _ (by + unfold memoryLimit originalAddress + omega) + have horig : Fits w (originalAddress L m N Q + p) := hf _ (by + unfold memoryLimit + omega) + have hnext : Fits w (p + 1) := hf _ (by omega) + have hnnext : Fits w (n + 1) := hf _ (by omega) + have hcurcode : cursorAddressBase L m N Q + key n < codeAddress L m N Q := by + unfold codeAddress + omega + have hcodeorig : codeAddress L m N Q + p < originalAddress L m N Q := by + unfold originalAddress + omega + let y := (((((((((x.set cursorAddress (cursorAddressBase L m N Q + key n)).set position p).set + pointer (codeAddress L m N Q + p)).write (codeAddress L m N Q + p) v).set + pointer (originalAddress L m N Q + p)).write (originalAddress L m N Q + p) n).set + position (p + 1)).write (cursorAddressBase L m N Q + key n) (p + 1)).set index (n + 1)) + have he : ExecWithin w scatterWrite x y 9 := by + dsimp only [p] at hcode horig hnext + simp only [packRegisters, code] at hv + ram_eval [scatterWrite, y, hx.cursors, hx.codes, hx.originals, hx.tables.one, + hi, ht, hv, hc, hcursor, hcode, horig, hnext, hnnext, p, + cursorAddress, position, pointer, cursors, codes, originals, tile, index, + packRegisters, code, BandPreparation.one, Fin.reduceFinMk, Fin.mk.injEq] + have hy : Parameters L m N Q y := hx.reframe (by + intro i hi + apply he.regs_eq + have h : ∀ i ∈ parameterRegisters, scatterWrite.Preserves i := by decide +kernel + exact h i hi) + refine ⟨y, he, hy, by simp [y, index], ?_, ?_, ?_, ?_⟩ + · change Function.update (Function.update + (Function.update x.mem (codeAddress L m N Q + p) v) + (originalAddress L m N Q + p) n) + (cursorAddressBase L m N Q + key n) (p + 1) (codeAddress L m N Q + p) = v + rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega), Function.update_self] + · change Function.update (Function.update + (Function.update x.mem (codeAddress L m N Q + p) v) + (originalAddress L m N Q + p) n) + (cursorAddressBase L m N Q + key n) (p + 1) (originalAddress L m N Q + p) = n + rw [Function.update_of_ne (by omega), Function.update_self] + · exact Function.update_self _ _ _ + · intro a hc hk ho + change Function.update (Function.update + (Function.update x.mem (codeAddress L m N Q + p) v) + (originalAddress L m N Q + p) n) + (cursorAddressBase L m N Q + key n) (p + 1) a = x.mem a + rw [Function.update_of_ne hc, Function.update_of_ne ho, Function.update_of_ne hk] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Setup.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Setup.lean new file mode 100644 index 0000000000..7c3b293d26 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Setup.lean @@ -0,0 +1,167 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.Memory +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.BandBounds + +/-! +# Actual initialization of wanted-query parameters + +## Main results + +* `Wanted.exec_setup`: read the query count and compute all relocated addresses natively. +* `Wanted.exec_tables`: scalar/header/count setup and actual valid-mask tables from the + original four input words, preserving every original query word. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Expanded allocated size, useful without hiding any arithmetic behind a layout record. -/ +theorem memoryLimit_eq (L m N Q : ℕ) : + memoryLimit L m N Q = 6 + 4 * Q + 2 * 2 ^ L + 3 * numBands L m N ^ 2 := by + simp only [memoryLimit, originalAddress, codeAddress, cursorAddressBase, + offsetAddress, countAddress, tableAddress, inputLimit] + omega + +/-- The original numerical word budget covers every scalar used by the grouping loops. -/ +theorem local_fits {w L m N Q : ℕ} (hfit : Fits w (wordBudget L m N Q)) : + Fits w (16 * (L + N + Q + memoryLimit L m N Q + 10 ^ L + 2 ^ L + + numBands L m N ^ 2 + 16)) := by + apply hfit.of_le + unfold wordBudget routeBudget + omega + +/-- Relocate the actual computed-table parameters after the input and allocate all +count/prefix/cursor/code/permutation arrays using charged native arithmetic. -/ +theorem exec_setup {w L m N Q : ℕ} (x : Data 96) + (hx : BandPreparation.BandParameters L m N x) (hQ : x.mem 4 = Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w setup x y 16 ∧ Parameters L m N Q y ∧ y.mem = x.mem := by + have hl := local_fits hfit + have hend := memoryLimit_eq L m N Q + have hf (v : ℕ) (hv : v ≤ memoryLimit L m N Q + 10 ^ L) : Fits w v := + hl.of_le (by omega) + have hB : bandSize L m ≤ 10 ^ L := + (bandSize_le_seven_pow L m).trans (Nat.pow_le_pow_left (by decide) _) + have h5 : Fits w 5 := hf _ (by omega) + have h4 : Fits w 4 := hf _ (by omega) + have hq2 : Fits w (Q * 2) := hf _ (by omega) + have hi : Fits w (inputLimit Q) := hf _ (by unfold inputLimit; omega) + have ht : Fits w (tableAddress L Q) := hf _ (by + unfold tableAddress inputLimit; omega) + have hc : Fits w (countAddress L Q) := hf _ (by + unfold countAddress tableAddress inputLimit; omega) + have hb : Fits w (bandSize L m) := hf _ (by omega) + have ht2 : Fits w (numBands L m N * numBands L m N) := hf _ (by + rw [← sq]; omega) + have ho : Fits w (offsetAddress L m N Q) := hf _ (by + unfold offsetAddress countAddress tableAddress inputLimit; omega) + have hcur0 : Fits w (offsetAddress L m N Q + numBands L m N ^ 2) := hf _ (by + unfold offsetAddress countAddress tableAddress inputLimit; omega) + have hcur : Fits w (cursorAddressBase L m N Q) := hf _ (by + unfold cursorAddressBase offsetAddress countAddress tableAddress inputLimit; omega) + have hco : Fits w (codeAddress L m N Q) := hf _ (by + unfold codeAddress cursorAddressBase offsetAddress countAddress tableAddress inputLimit; omega) + have horig : Fits w (originalAddress L m N Q) := hf _ (by + unfold originalAddress codeAddress cursorAddressBase offsetAddress countAddress + tableAddress inputLimit + omega) + have hefit : Fits w (memoryLimit L m N Q) := hf _ (by omega) + let y := ((((((((((((((((x.set pointer 4).set queryCount Q).set queries 5).set + inputEnd (Q * 2)).set inputEnd (inputLimit Q)).set BandPreparation.inverse + (inputLimit Q)).set BandPreparation.table (tableAddress L Q)).set counts + (countAddress L Q)).set bandWidth (bandSize L m)).set tileCount + (numBands L m N * numBands L m N)).set offsets (offsetAddress L m N Q)).set + cursors (offsetAddress L m N Q + numBands L m N ^ 2)).set cursors + (cursorAddressBase L m N Q)).set codes (codeAddress L m N Q)).set originals + (originalAddress L m N Q)).set memoryEnd (memoryLimit L m N Q)) + have he : ExecWithin w setup x y 16 := by + simp only [bandSize] at hb + simp only [inputLimit] at hi + simp only [tableAddress, inputLimit] at ht + simp only [countAddress, tableAddress, inputLimit] at hc + simp only [offsetAddress, countAddress, tableAddress, inputLimit] at ho hcur0 + simp only [cursorAddressBase, offsetAddress, countAddress, tableAddress, inputLimit] at hcur + simp only [codeAddress, cursorAddressBase, offsetAddress, countAddress, tableAddress, + inputLimit] at hco + simp only [originalAddress, codeAddress, cursorAddressBase, offsetAddress, countAddress, + tableAddress, inputLimit] at horig + simp only [memoryLimit, originalAddress, codeAddress, cursorAddressBase, offsetAddress, + countAddress, tableAddress, inputLimit] at hefit + simp only [pow_two] at ho hcur0 hcur hco horig hefit + ram_eval [setup, y, hQ, hx.fixed.two, hx.fixed.one, hx.masks, hx.root, hx.outerSize, + hx.bands, h4, h5, hq2, hi, ht, hc, hb, ht2, ho, hcur0, hcur, hco, horig, hefit, + memoryLimit, originalAddress, codeAddress, cursorAddressBase, offsetAddress, + countAddress, tableAddress, inputLimit, bandSize, pow_two, + pointer, queryCount, queries, inputEnd, counts, bandWidth, tileCount, offsets, + cursors, codes, originals, memoryEnd, BandPreparation.inverse, BandPreparation.table, + BandPreparation.root, BandPreparation.outerSize, BandPreparation.bands, + BandPreparation.masks, BandPreparation.two, BandPreparation.one, Fin.mk.injEq] + refine ⟨y, he, ?_, rfl⟩ + constructor + · constructor <;> simp [y, pointer, queryCount, queries, inputEnd, counts, bandWidth, + tileCount, offsets, cursors, codes, originals, memoryEnd, BandPreparation.levels, + BandPreparation.inner, BandPreparation.masks, BandPreparation.inverse, + BandPreparation.table, BandPreparation.square, BandPreparation.zero, + BandPreparation.one, BandPreparation.two, hx.fixed.levels, hx.fixed.inner, hx.masks, + hx.square, hx.fixed.zero, hx.fixed.one, hx.fixed.two] + · constructor <;> simp [y, pointer, queryCount, queries, inputEnd, counts, bandWidth, + tileCount, offsets, cursors, codes, originals, memoryEnd, BandPreparation.levels, + BandPreparation.outerSize, BandPreparation.root, BandPreparation.bands, + BandPreparation.inverse, BandPreparation.table, hx.fixed.levels, hx.outerSize, + hx.root, hx.bands] + all_goals simp [y, pointer, queryCount, queries, inputEnd, counts, bandWidth, + tileCount, offsets, cursors, codes, originals, memoryEnd, BandPreparation.inverse, + BandPreparation.table, pow_two] + +/-- The initialized producer of the tables used by routing, without invoking any encoders. +The only memory changes are the two relocated mask tables after the complete query input. -/ +theorem exec_tables {w L m N Q : ℕ} (hm : m ≤ L) (x : Data 96) + (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) (hQ : x.mem 4 = Q) + (hfit : Fits w (wordBudget L m N Q)) : + ∃ y, ExecWithin w (.seq BandPreparation.header (.seq BandPreparation.countCode + (.seq setup BandPreparation.tablesCode))) x y + (16 * L + (12 * L + 33) * 2 ^ L + 83) ∧ Parameters L m N Q y ∧ + (∀ gh : Fin (K0 L m) × Fin (K0 L m), + y.mem (tableAddress L Q + (MaskTable.pairRank gh).val) = + (MaskTable.selectedMask gh).val) ∧ + (∀ a, a < inputLimit Q ∨ tableAddress L Q + 2 ^ L ≤ a → y.mem a = x.mem a) := by + have h10 := Nat.zero_le (10 ^ L) + have h2 := Nat.zero_le (2 ^ L) + have hpfit : Fits w (BandPreparation.wordBudget L m N) := hfit.of_le (by + unfold wordBudget; omega) + obtain ⟨h, hh, hparams, hmem⟩ := BandPreparation.exec_header hm x hL hmemory hN hpfit + obtain ⟨c, hc, cp, cm⟩ := BandPreparation.exec_countCode h hparams hpfit + obtain ⟨s, hs, sp, sm⟩ := exec_setup c cp (by rw [cm, hmem]; exact hQ) hfit + have htfit : Fits w (inputLimit Q + tableAddress L Q + 2 ^ L + L + 4) := by + apply (local_fits hfit).of_le + have := memoryLimit_eq L m N Q + unfold tableAddress inputLimit + omega + obtain ⟨y, hy, _, _, hforward, hf⟩ := BandPreparation.exec_tables L m + (inputLimit Q) (tableAddress L Q) s sp.tables (by rfl) htfit + have yp : Parameters L m N Q y := sp.reframe (by + intro i hi + apply hy.regs_eq + have hp : ∀ i ∈ parameterRegisters, BandPreparation.tablesCode.Preserves i := by + decide +kernel + exact hp i hi) + refine ⟨y, ?_, yp, hforward, ?_⟩ + · apply (hh.seq (hc.seq (hs.seq hy))).mono + nlinarith only [Nat.zero_le ((6 * L + 16) * 2 ^ L)] + · intro a ha + rw [hf a (by rcases ha with ha | ha <;> unfold tableAddress at * <;> omega) + (by rcases ha with ha | ha <;> unfold tableAddress at * <;> omega), sm, cm, hmem] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Width.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Width.lean new file mode 100644 index 0000000000..6f32278004 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Wanted/Width.lean @@ -0,0 +1,83 @@ +/- +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.Wanted.Flat +public import Mathlib.Data.Nat.Log + +/-! +# Sufficient physical width for native wanted-query grouping + +## Main definitions + +* `Wanted.admissionWidth`: a positive ceiling-logarithmic threshold from original parameters. + +## Main results + +* `Wanted.runs_wide`: input fit and actual initialized runs at every admitted physical width. +* `Wanted.program_length_le`: a fixed finite code-size bound independent of the input. + +Coordinates and query codes are unsigned. No signed-kernel width adjustment is needed for +this routing component. Relating its parameters to a whole 3SUM input remains composition work. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Positive sufficient physical width, logarithmic in the explicit original-parameter budget. +The program does not read the width or this threshold. -/ +def admissionWidth (L m N Q : ℕ) : ℕ := Nat.clog 2 (wordBudget L m N Q + 1) + 1 + +/-- The width condition admits positive widths even for zero dimension and empty queries. -/ +theorem admissionWidth_pos (L m N Q : ℕ) : 0 < admissionWidth L m N Q := by + unfold admissionWidth + omega + +/-- A sufficiently large physical width guarantees the actual numerical fit premise. -/ +theorem admission_fit {W L m N Q : ℕ} (hW : admissionWidth L m N Q ≤ W) : + Fits W (wordBudget L m N Q) := by + have hc := Nat.le_pow_clog (by decide : 1 < (2 : ℕ)) (wordBudget L m N Q + 1) + have hpow := Nat.pow_le_pow_right (by decide : 1 ≤ (2 : ℕ)) + (by unfold admissionWidth at hW; omega : Nat.clog 2 (wordBudget L m N Q + 1) ≤ W) + change wordBudget L m N Q < 2 ^ W + omega + +/-- Actual initialized runs at every sufficiently wide physical width. The same returned +memory holds the original-index permutation, query codes, exact spans and readonly frame. -/ +theorem runs_wide {W L m N : ℕ} (hm : m ≤ L) (qs : List (Fin N × Fin N)) + (hW : admissionWidth L m N qs.length ≤ W) : + InputFits W (input L m qs) ∧ + ∃ y n, n ≤ timeBound L m N qs.length ∧ + Runs W (program.program codes) (State.init (input L m qs)) ⟨program.size, y⟩ n ∧ + Executes W (program.program codes) (State.init (input L m qs)) + (codeAddress L m N qs.length) (n + 1) ∧ y.Bounded W ∧ + (∀ i : Fin qs.length, + y.mem (codeAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = + (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout hm) (qs.get i).1 (qs.get i).2)).val ∧ + y.mem (originalAddress L m N qs.length + + CountingSort.position (tileKey L m N ∘ inputPairs qs) qs.length i.val) = i.val) ∧ + (∀ t ≤ numBands L m N ^ 2, y.mem (offsetAddress L m N qs.length + t) = + Nat.count (fun k => (tileKey L m N ∘ inputPairs qs) k < t) qs.length) ∧ + y.mem (offsetAddress L m N qs.length + numBands L m N ^ 2) = qs.length ∧ + (∀ a, a < inputLimit qs.length ∨ memoryLimit L m N qs.length ≤ a → + y.mem a = (State.init (input L m qs) : State 96).data.mem a) := + runs hm qs (admission_fit hW) + +/-- The code is one finite 96-register program; all data-dependent repetition is in loops. -/ +theorem program_length_le : (program.program codes).length ≤ 4096 := by decide +kernel + +/-- The native program is uniform across admitted widths. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide +kernel + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted 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/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/Computability/WordRAM/Sequence.lean b/Complexity/Computability/WordRAM/Sequence.lean new file mode 100644 index 0000000000..e4099331a0 --- /dev/null +++ b/Complexity/Computability/WordRAM/Sequence.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.Structured + +/-! +# Reassociating structured native execution proofs + +## Main results + +* `WordRAM.ExecWithin.seq_assoc`: compose an already proved instruction block with the + surrounding right-associated program, without repeating its execution proof. + +Reassociation preserves the same data states and charged bound. This narrow proof-rule +module avoids extending the interface of the widely imported structured-code core. +-/ + +@[expose] public section + +namespace Cslib.WordRAM.ExecWithin + +/-- Sequential blocks may be regrouped while preserving every instruction charge. -/ +theorem seq_assoc {B : Basis} {r w T : ℕ} {a b c : Code B r} {x y : Data r} : + ExecWithin w (.seq (.seq a b) c) x y T ↔ ExecWithin w (.seq a (.seq b c)) x y T := by + constructor + · rintro ⟨n, hn, he⟩ + cases he with + | seq hab hc => + cases hab with + | seq ha hb => exact ⟨_, by omega, .seq ha (.seq hb hc)⟩ + · rintro ⟨n, hn, he⟩ + cases he with + | seq ha hbc => + cases hbc with + | seq hb hc => exact ⟨_, by omega, .seq (.seq ha hb) hc⟩ + +end Cslib.WordRAM.ExecWithin diff --git a/Complexity/Foundations/Data/Finset/OrderGlue.lean b/Complexity/Foundations/Data/Finset/OrderGlue.lean index 9a61f690c7..7d2253b09d 100644 --- a/Complexity/Foundations/Data/Finset/OrderGlue.lean +++ b/Complexity/Foundations/Data/Finset/OrderGlue.lean @@ -15,11 +15,14 @@ public import Mathlib.Data.Finset.Sort ## Main definitions -* `Finset.glue`: join two strings along a subset and its complement. +* `Finset.glue`: join two functions along a subset and its complement, each enumerated + by Mathlib's `Finset.orderEmbOfFin`. ## Main results +* `Finset.forall_iff_orderEmbOfFin_and_compl`: split a predicate over the two enumerations. * `Finset.eq_glue_iff`, `Finset.glue_inj`: reconstruction and uniqueness. +* `Finset.eq_glue_of_reconstruct`: reconstruct values through maps on the two parts. ## References @@ -42,146 +45,126 @@ theorem card_compl_of_card_eq {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) variable {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) -/-! ### The levels of a set and of its complement, in order -/ +/-! ### Predicates on a subset and its complement -/ -/-- The `k`-th lowest level outside `Q` is not a level of `Q`. -/ -theorem outerLevel_notMem (k : Fin (L - m)) : (Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k ∉ - Q := - mem_compl.mp (Qᶜ.orderEmbOfFin_mem (card_compl_of_card_eq Q hQ) k) - -/-- Every level of `Q` is the `k`-th lowest level of `Q` for some `k`. -/ -theorem exists_innerLevel {ℓ : Fin L} (hℓ : ℓ ∈ Q) : ∃ k, Finset.orderEmbOfFin Q hQ k = ℓ := - (Q.range_orderEmbOfFin hQ).ge hℓ - -/-- Every level outside `Q` is the `k`-th lowest level outside `Q` for some `k`. -/ -theorem exists_outerLevel {ℓ : Fin L} (hℓ : ℓ ∉ Q) : ∃ k, (Qᶜ.orderEmbOfFin (card_compl_of_card_eq - Q hQ)) k = ℓ := - (Qᶜ.range_orderEmbOfFin (card_compl_of_card_eq Q hQ)).ge (mem_coe.mpr (mem_compl.mpr hℓ)) - -/-- The levels of `Q` in order depend only on the set `Q`. -/ -theorem innerLevel_congr {Q' : Finset (Fin L)} (h : Q' = Q) (hQ' : Q'.card = m) : - Finset.orderEmbOfFin Q' hQ' = Finset.orderEmbOfFin Q hQ := by - subst h - rfl - -/-- The levels outside `Q` in order depend only on the set `Q`. -/ -theorem outerLevel_congr {Q' : Finset (Fin L)} (h : Q' = Q) (hQ' : Q'.card = m) : - (Q'ᶜ.orderEmbOfFin (card_compl_of_card_eq Q' hQ')) = (Qᶜ.orderEmbOfFin (card_compl_of_card_eq - Q hQ)) := by - subst h - rfl - -/-- A property holds at every level if and only if it holds at the levels of `Q` and at the levels -outside `Q`. -/ -theorem forall_level_iff {P : Fin L → Prop} : +/-- A predicate holds everywhere if and only if it holds on the ordered enumerations of +`Q` and its complement. -/ +theorem forall_iff_orderEmbOfFin_and_compl {P : Fin L → Prop} : (∀ ℓ, P ℓ) ↔ (∀ k, P (Finset.orderEmbOfFin Q hQ k)) ∧ ∀ k, P ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) := by refine ⟨fun h => ⟨fun _ => h _, fun _ => h _⟩, fun ⟨hin, hout⟩ ℓ => ?_⟩ by_cases hℓ : ℓ ∈ Q - · obtain ⟨k, rfl⟩ := exists_innerLevel Q hQ hℓ + · obtain ⟨k, rfl⟩ := (Q.range_orderEmbOfFin hQ).ge hℓ exact hin k - · obtain ⟨k, rfl⟩ := exists_outerLevel Q hQ hℓ + · obtain ⟨k, rfl⟩ := (Qᶜ.range_orderEmbOfFin (card_compl_of_card_eq Q hQ)).ge + (mem_coe.mpr (mem_compl.mpr hℓ)) exact hout k -/-- A property cuts out the set `Q` if and only if it holds at the levels of `Q` and fails at the -other levels. -/ -theorem filter_eq_iff_forall_level {P : Fin L → Prop} [DecidablePred P] : +/-- A predicate cuts out `Q` if and only if it holds on its ordered enumeration and fails +on the ordered enumeration of its complement. -/ +theorem filter_eq_iff_orderEmbOfFin_and_compl {P : Fin L → Prop} [DecidablePred P] : univ.filter P = Q ↔ (∀ k, P (Finset.orderEmbOfFin Q hQ k)) ∧ ∀ k, ¬ P ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) := by - simp only [Finset.ext_iff, forall_level_iff Q hQ, mem_filter, mem_univ, true_and, - Finset.orderEmbOfFin_mem, outerLevel_notMem Q hQ, iff_true, iff_false] - -/-- A property that holds at the `m` levels of `Q`, and at `m` levels in all, cuts out `Q`. -/ -theorem filter_eq_of_forall_innerLevel {P : Fin L → Prop} [DecidablePred P] + have hcompl (k : Fin (L - m)) : + Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ) k ∉ Q := + mem_compl.mp (Qᶜ.orderEmbOfFin_mem (card_compl_of_card_eq Q hQ) k) + simp only [Finset.ext_iff, forall_iff_orderEmbOfFin_and_compl Q hQ, mem_filter, mem_univ, + true_and, Finset.orderEmbOfFin_mem, hcompl, iff_true, iff_false] + +/-- A predicate holding on every enumerated element of `Q`, and on `m` elements in all, +cuts out `Q`. -/ +theorem filter_eq_of_forall_orderEmbOfFin {P : Fin L → Prop} [DecidablePred P] (hin : ∀ k, P (Finset.orderEmbOfFin Q hQ k)) (hcard : (univ.filter P).card = m) : univ.filter P = Q := by refine (eq_of_subset_of_card_le (fun ℓ hℓ => ?_) (hcard.trans hQ.symm).le).symm - obtain ⟨k, rfl⟩ := exists_innerLevel Q hQ hℓ + obtain ⟨k, rfl⟩ := (Q.range_orderEmbOfFin hQ).ge hℓ exact mem_filter.mpr ⟨mem_univ _, hin k⟩ -/-! ### Gluing two strings along a set of levels -/ +/-! ### Gluing functions along an ordered subset -/ variable {α : Type*} -/-- The string that has the letters of `f` at the levels of `Q` and the letters of `g` at the other -levels, both in the order of the levels. -/ +/-- The function taking the values of `f` on `Q` and of `g` on its complement, with each +part enumerated in increasing order. -/ def glue (f : Fin m → α) (g : Fin (L - m) → α) : Fin L → α := fun ℓ => if hℓ : ℓ ∈ Q then f ((Q.orderIsoOfFin hQ).symm ⟨ℓ, hℓ⟩) else g ((Qᶜ.orderIsoOfFin (card_compl_of_card_eq Q hQ)).symm ⟨ℓ, mem_compl.mpr hℓ⟩) -/-- At the `k`-th lowest level of `Q`, the glued string has the `k`-th letter of `f`. -/ +/-- Evaluating a glued function on the ordered enumeration of `Q` reads `f`. -/ @[simp] -theorem glue_innerLevel (f : Fin m → α) (g : Fin (L - m) → α) (k : Fin m) : +theorem glue_orderEmbOfFin (f : Fin m → α) (g : Fin (L - m) → α) (k : Fin m) : glue Q hQ f g (Finset.orderEmbOfFin Q hQ k) = f k := by rw [glue, dite_eq_left (Finset.orderEmbOfFin_mem Q hQ k)] exact congrArg f ((OrderIso.symm_apply_eq _).mpr (Subtype.ext rfl)) -/-- At the `k`-th lowest level outside `Q`, the glued string has the `k`-th letter of `g`. -/ +/-- Evaluating a glued function on the ordered enumeration of the complement reads `g`. -/ @[simp] -theorem glue_outerLevel (f : Fin m → α) (g : Fin (L - m) → α) (k : Fin (L - m)) : +theorem glue_orderEmbOfFin_compl (f : Fin m → α) (g : Fin (L - m) → α) (k : Fin (L - m)) : glue Q hQ f g ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = g k := by - rw [glue, dite_eq_right (outerLevel_notMem Q hQ k)] + rw [glue, dite_eq_right (mem_compl.mp + (Qᶜ.orderEmbOfFin_mem (card_compl_of_card_eq Q hQ) k))] exact congrArg g ((OrderIso.symm_apply_eq _).mpr (Subtype.ext rfl)) -/-- At the `k`-th lowest level of a set `Q'` that is equal to `Q`, the string glued along `Q` has -the `k`-th letter of `f`. -/ -theorem glue_innerLevel_of_eq (f : Fin m → α) (g : Fin (L - m) → α) {Q' : Finset (Fin L)} +/-- Enumerating a subset equal to `Q` reads the same values from a glued function. -/ +theorem glue_orderEmbOfFin_of_eq (f : Fin m → α) (g : Fin (L - m) → α) {Q' : Finset (Fin L)} (h : Q' = Q) (hQ' : Q'.card = m) (k : Fin m) : glue Q hQ f g (Finset.orderEmbOfFin Q' hQ' k) = f k := by - rw [innerLevel_congr Q hQ h, glue_innerLevel] + subst h + apply glue_orderEmbOfFin -/-- At the `k`-th lowest level outside a set `Q'` that is equal to `Q`, the string glued along `Q` -has the `k`-th letter of `g`. -/ -theorem glue_outerLevel_of_eq (f : Fin m → α) (g : Fin (L - m) → α) {Q' : Finset (Fin L)} +/-- Enumerating the complement of a subset equal to `Q` reads the same values from a +function glued along `Q`. -/ +theorem glue_orderEmbOfFin_compl_of_eq (f : Fin m → α) (g : Fin (L - m) → α) {Q' : Finset (Fin L)} (h : Q' = Q) (hQ' : Q'.card = m) (k : Fin (L - m)) : glue Q hQ f g ((Q'ᶜ.orderEmbOfFin (card_compl_of_card_eq Q' hQ')) k) = g k := by subst h - apply glue_outerLevel + apply glue_orderEmbOfFin_compl variable {f f' : Fin m → α} {g g' : Fin (L - m) → α} -/-- The glued string is the only string with the letters of `f` at the levels of `Q` and the letters -of `g` at the other levels. -/ +/-- A function is the glued function exactly when its restrictions to both ordered +parts have the stated values. -/ theorem eq_glue_iff {u : Fin L → α} : u = glue Q hQ f g ↔ (∀ k, u (Finset.orderEmbOfFin Q hQ k) = f k) ∧ ∀ k, u ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = g k := by - simp only [funext_iff, forall_level_iff Q hQ, glue_innerLevel, glue_outerLevel] + simp only [funext_iff, forall_iff_orderEmbOfFin_and_compl Q hQ, glue_orderEmbOfFin, + glue_orderEmbOfFin_compl] -/-- The two strings can be read off the glued string. -/ +/-- Both component functions can be read off their glued function. -/ theorem glue_inj : glue Q hQ f g = glue Q hQ f' g' ↔ f = f' ∧ g = g' := by rw [eq_glue_iff] - simp only [glue_innerLevel, glue_outerLevel, funext_iff] + simp only [glue_orderEmbOfFin, glue_orderEmbOfFin_compl, funext_iff] -/-- A map applied to a glued string, letter by letter. -/ +/-- Applying a map to a glued function maps both of its components. -/ theorem map_glue {β : Type*} (φ : α → β) : (fun ℓ => φ (glue Q hQ f g ℓ)) = glue Q hQ (fun k => φ (f k)) (fun k => φ (g k)) := by - simp only [eq_glue_iff, glue_innerLevel, glue_outerLevel, implies_true, and_self] + simp only [eq_glue_iff, glue_orderEmbOfFin, glue_orderEmbOfFin_compl, implies_true, and_self] -/-! ### Alphabets with inner and outer letters -/ +/-! ### Predicates and reconstruction on the two parts -/ -variable (IsInner : α → Prop) [DecidablePred IsInner] +variable (P : α → Prop) [DecidablePred P] -/-- If the letters of `f` are inner and those of `g` are outer, then the glued string has its inner -letters exactly at the levels of `Q`. -/ -theorem filter_glue (hf : ∀ k, IsInner (f k)) (hg : ∀ k, ¬ IsInner (g k)) : - (univ.filter fun ℓ => IsInner (glue Q hQ f g ℓ)) = Q := by - simpa only [filter_eq_iff_forall_level Q hQ, glue_innerLevel, glue_outerLevel] using +/-- If `P` holds on the values of `f` and fails on the values of `g`, it holds on the +glued function exactly at the elements of `Q`. -/ +theorem filter_glue (hf : ∀ k, P (f k)) (hg : ∀ k, ¬ P (g k)) : + (univ.filter fun ℓ => P (glue Q hQ f g ℓ)) = Q := by + simpa only [filter_eq_iff_orderEmbOfFin_and_compl Q hQ, glue_orderEmbOfFin, + glue_orderEmbOfFin_compl] using And.intro hf hg -/-- Reconstruct `u` by extracting and reinserting its inner and outer indices. The hypotheses -require that both index projections have the stated reconstruction property on their -respective classes of letters, and that the inner letters of `u` occur exactly on the -size-`m` set `Q`. -/ -theorem glue_index {I O : Type*} {inner : I → α} {outer : O → α} {innerIndex : α → I} - {outerIndex : α → O} (hI : ∀ s, IsInner s → inner (innerIndex s) = s) - (hO : ∀ s, ¬ IsInner s → outer (outerIndex s) = s) {u : Fin L → α} - (hu : (univ.filter fun ℓ => IsInner (u ℓ)) = Q) : - u = glue Q hQ (fun k => inner (innerIndex (u (Finset.orderEmbOfFin Q hQ k)))) - (fun k => outer (outerIndex (u ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k)))) := by - obtain ⟨hin, hout⟩ := (filter_eq_iff_forall_level Q hQ).mp hu - exact (eq_glue_iff Q hQ).mpr ⟨fun k => (hI _ (hin k)).symm, fun k => (hO _ (hout k)).symm⟩ - +/-- Reconstruct `u` from maps that recover its values on the subset defined by `P` and +on its complement. The two reconstruction hypotheses apply on their respective parts. -/ +theorem eq_glue_of_reconstruct {β γ : Type*} {fromSubset : β → α} {fromCompl : γ → α} + {toSubset : α → β} {toCompl : α → γ} + (hsubset : ∀ a, P a → fromSubset (toSubset a) = a) + (hcompl : ∀ a, ¬ P a → fromCompl (toCompl a) = a) {u : Fin L → α} + (hu : (univ.filter fun ℓ => P (u ℓ)) = Q) : + u = glue Q hQ (fun k => fromSubset (toSubset (u (Finset.orderEmbOfFin Q hQ k)))) + (fun k => fromCompl (toCompl (u ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k)))) := by + obtain ⟨hin, hout⟩ := (filter_eq_iff_orderEmbOfFin_and_compl Q hQ).mp hu + exact (eq_glue_iff Q hQ).mpr + ⟨fun k => (hsubset _ (hin k)).symm, fun k => (hcompl _ (hout k)).symm⟩ end Finset 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/FinsetOrderGlue.lean b/ComplexityTests/FinsetOrderGlue.lean new file mode 100644 index 0000000000..6f1f7e9411 --- /dev/null +++ b/ComplexityTests/FinsetOrderGlue.lean @@ -0,0 +1,68 @@ +/- +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.Foundations.Data.Finset.OrderGlue +import Mathlib.Data.Fin.VecNotation + +namespace ComplexityTests.FinsetOrderGlue + +open Finset + +private def selected : Finset (Fin 5) := {1, 3} + +private theorem card_selected : selected.card = 2 := by decide + +private def values : Fin 2 → ℤ := ![-3, -7] + +private def otherValues : Fin 3 → ℤ := ![11, 0, 5] + +/-- Nonconsecutive positions and their complement are both enumerated in increasing order. -/ +example : glue selected card_selected values otherValues = ![11, -3, 0, -7, 5] := by + symm + apply (eq_glue_iff selected card_selected).mpr + have hs : (![1, 3] : Fin 2 → Fin 5) = selected.orderEmbOfFin card_selected := + orderEmbOfFin_unique card_selected (by decide +kernel) (by decide +kernel) + have hc : (![0, 2, 4] : Fin 3 → Fin 5) = + selectedᶜ.orderEmbOfFin (card_compl_of_card_eq selected card_selected) := + orderEmbOfFin_unique _ (by decide +kernel) (by decide +kernel) + constructor + · rw [← hs] + decide +kernel + · rw [← hc] + decide +kernel + +/-- Generic predicate recognition includes a zero value in the complement. -/ +example : (univ.filter fun i => glue selected card_selected values otherValues i < 0) = + selected := by + apply filter_glue selected card_selected (fun z : ℤ => z < 0) + · decide +kernel + · decide +kernel + +/-- The named evaluation rules recover values on both enumerations. -/ +example : glue selected card_selected values otherValues (selected.orderEmbOfFin card_selected 1) + = -7 ∧ + glue selected card_selected values otherValues + (selectedᶜ.orderEmbOfFin (card_compl_of_card_eq selected card_selected) 2) = 5 := by + simp only [glue_orderEmbOfFin, glue_orderEmbOfFin_compl] + exact ⟨rfl, rfl⟩ + +private def optionalValues : Fin 5 → Option ℤ := ![none, some (-3), none, some 7, none] + +/-- Reconstruction applies to a generic optional value, without tensor-alphabet vocabulary. -/ +example : optionalValues = glue selected card_selected + (fun k => some ((optionalValues (selected.orderEmbOfFin card_selected k)).getD 0)) + (fun _ => none) := by + refine eq_glue_of_reconstruct selected card_selected (fun a : Option ℤ => a ≠ none) + (fromSubset := some) (fromCompl := fun _ : Unit => none) + (toSubset := fun a => a.getD 0) (toCompl := fun _ => ()) ?_ ?_ ?_ + · rintro (_ | a) ha + · exact (ha rfl).elim + · rfl + · intro a ha + exact (not_not.mp ha).symm + · decide +kernel + +end ComplexityTests.FinsetOrderGlue diff --git a/ComplexityTests/SchonhageBandPreparation.lean b/ComplexityTests/SchonhageBandPreparation.lean new file mode 100644 index 0000000000..6ee815d440 --- /dev/null +++ b/ComplexityTests/SchonhageBandPreparation.lean @@ -0,0 +1,188 @@ +/- +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] + +private def asymmetricRead (side : Bool) (a : ℕ) : Code .standard 96 := + .seq header (.seq saveInput (.seq countCode (.seq tablesCode + (.seq (.op (.const band 0)) (.seq (.op (.const index a)) + (.seq splitCode (readCell side))))))) + +private def observeAsymmetricRead (side : Bool) (a : ℕ) : + Option (ℕ × ℕ × ℕ × ℕ × ℤ) := + let data : Data 96 := (State.init (input 4 1 29 + (fun i k => ((100 * i.val + k.val : ℕ) : ℤ) - 1000) + (fun k j => -((1000 * k.val + j.val + 7 : ℕ) : ℤ)))).data + ((asymmetricRead side a).eval 24 256 data).map fun (y, _) => + (y.regs root, y.regs maskAcc, y.regs block, y.regs row, decode (y.regs value)) + +-- At L=4,m=1, the computed root is 2. Masks 2 and 4 have ranks 1 and 2, so division +-- and remainder select different blocks. Both guarded rows (1 and 28) are real at N=29. +-- These runs compute headers/tables/snapshot and decode/load only, without an encoder run. +#guard observeAsymmetricRead false 36 == some (2, 2, 0, 1, -898) +#guard observeAsymmetricRead true 36 == some (2, 2, 1, 28, -2035) +#guard observeAsymmetricRead false 246 == some (2, 4, 1, 28, 1802) +#guard observeAsymmetricRead true 246 == some (2, 4, 0, 1, -2008) + +-- 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/SchonhageProduct.lean b/ComplexityTests/SchonhageProduct.lean new file mode 100644 index 0000000000..f191ea7c09 --- /dev/null +++ b/ComplexityTests/SchonhageProduct.lean @@ -0,0 +1,163 @@ +/- +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.Product + +-- Actual native interpreter regressions supplement the general execution proofs. +set_option linter.hashCommand false + +namespace ComplexityTests.SchonhageProduct + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.QueriedProduct + +private def observe {m N : ℕ} (bits : Bool) (L : ℕ) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (qs : List (Fin N × Fin N)) : Option (List ℤ × Bool) := + let data := (State.init (input bits L X Y qs) : State 160).data + let sentinel := memoryEnd L m N qs.length + 17 + let start := data.write sentinel 91 + ((program bits).eval 32 4096 start).map fun (y, _) => + ((List.range qs.length).map (fun i => decode (y.mem (y.regs output + i))), + decide ((∀ a < inputEnd m N qs.length, y.mem a = data.mem a) ∧ y.mem sentinel = 91)) + +-- The actual complete pipeline handles an empty original input without a tile division. +#guard observe false 0 (m := 0) (N := 0) (fun _ _ => 0) (fun _ _ => 0) [] == + some ([], true) + +-- Signed operands and repeated requests survive both producer calls and reconstruction. +#guard observe false 0 (m := 0) (N := 1) (fun _ _ => -3) (fun _ _ => 2) + [(0, 0), (0, 0), (0, 0)] == some ([-6, -6, -6], true) + +-- Raw unsigned entries are doubled by real code before signed encoding, not in the input. +#guard observe true 0 (m := 0) (N := 1) (fun _ _ => 1) (fun _ _ => 1) + [(0, 0), (0, 0)] == some ([1, 1], true) + +-- Distinct tile pointers and returned epochs are reused across all four padded band pairs. +#guard observe false 1 (m := 0) (N := 4) + (fun i _ => (i.val : ℤ) - 2) (fun _ j => (j.val : ℤ) - 1) + [(3, 0), (0, 3), (3, 3), (1, 0), (3, 0)] == some ([-1, -4, 2, 1, -1], true) + +-- A nonempty inner coordinate makes the pruned kernel visit all ten signed tensor terms. +#guard observe false 1 (m := 1) (N := 2) + (fun i k => (i.val : ℤ) + k.val - 1) (fun k j => (k.val : ℤ) + j.val - 2) + [(1, 0), (0, 1), (1, 1), (1, 0)] == some ([2, 6, 8, 2], true) + +-- Empty matrices also admit positive tensor depth without any tile division. +#guard observe false 1 (m := 1) (N := 0) (fun _ _ => 0) (fun _ _ => 0) [] == + some ([], true) + +private def signedX : Matrix (Fin 1) (Fin (D 0)) ℤ := fun _ _ => -3 +private def signedY : Matrix (Fin (D 0)) (Fin 1) ℤ := fun _ _ => 2 +private def repeated : List (Fin 1 × Fin 1) := [(0, 0), (0, 0), (0, 0)] + +-- Kernel-checked execution clients use the same final memory for prefix Runs, halt and values. +example : + InputFits (admissionWidth 0 0 1 3 3) (input false 0 signedX signedY repeated) ∧ + ∃ y n, n ≤ timeBound (by decide : 0 ≤ 0) repeated ∧ + Runs (admissionWidth 0 0 1 3 3) ((program false).program output) + (State.init (input false 0 signedX signedY repeated)) ⟨(program false).size, y⟩ n ∧ + Executes (admissionWidth 0 0 1 3 3) ((program false).program output) + (State.init (input false 0 signedX signedY repeated)) (outputBase 0 0 1 3) (n + 1) ∧ + (∀ i : Fin 3, y.mem (outputBase 0 0 1 3 + i.val) = encode (-6)) := by + obtain ⟨hi, y, n, hn, hr, he, _, hv, _⟩ := runs_wide (A := 3) (by decide : 0 ≤ 0) + false signedX signedY repeated (by intro i k; norm_num [signedX]) + (by intro k j; norm_num [signedY]) (by simp) le_rfl + refine ⟨hi, y, n, hn, hr, he, ?_⟩ + intro i + have h := hv i + fin_cases i <;> norm_num [signedX, signedY, repeated, Matrix.mul_apply, D, + Fin.sum_univ_succ] at h ⊢ <;> exact h + +private def thinN (m : ℕ) : ℕ := D m ^ 18 +private def thinIndex (m : ℕ) : Fin (thinN m) := + ⟨0, Nat.pow_pos (Nat.pow_pos (by decide))⟩ +private def thinQuery (m : ℕ) : List (Fin (thinN m) × Fin (thinN m)) := + [(thinIndex m, thinIndex m)] + +-- A nonempty, fully admitted thin family exists at every inner depth. The input contains +-- literal matrix words and a raw coordinate; the symbolic client does not execute a huge VM run. +example (m : ℕ) : + InputFits (128 * Nat.clog 2 (thinN m + 3)) + (input (m := m) true (19 * m) (fun _ _ => (0 : ℤ)) (fun _ _ => (0 : ℤ)) (thinQuery m)) ∧ + ∃ y n, + Runs (128 * Nat.clog 2 (thinN m + 3)) ((program true).program output) + (State.init (input (m := m) true (19 * m) (fun _ _ => (0 : ℤ)) (fun _ _ => (0 : ℤ)) + (thinQuery m))) ⟨(program true).size, y⟩ n ∧ + Executes (128 * Nat.clog 2 (thinN m + 3)) ((program true).program output) + (State.init (input (m := m) true (19 * m) (fun _ _ => (0 : ℤ)) (fun _ _ => (0 : ℤ)) + (thinQuery m))) (outputBase (19 * m) m (thinN m) 1) (n + 1) ∧ + ((n + 1 : ℕ) : ℝ) ≤ 4325376 * ((19 * m + 1 : ℕ) : ℝ) * (thinN m : ℝ) ^ 2 / + (D m : ℝ) ^ (1 / 18 : ℝ) ∧ y.mem (outputBase (19 * m) m (thinN m) 1) = 0 := by + have hQ : (thinQuery m).length * 2 ^ m ≤ thinN m ^ 2 := by + calc (thinQuery m).length * 2 ^ m = 2 ^ m := by simp [thinQuery] + _ ≤ D m := Nat.pow_le_pow_left (by decide : 2 ≤ 4) m + _ ≤ (D m) ^ 36 := Nat.le_self_pow (by decide : 36 ≠ 0) _ + _ = thinN m ^ 2 := by norm_num [thinN, ← pow_mul] + obtain ⟨hi, y, n, hr, he, hn, _, hv, _⟩ := runs_thin + (W := 128 * Nat.clog 2 (thinN m + 3)) (m := m) (A := 0) true + (fun _ _ => (0 : ℤ)) (fun _ _ => (0 : ℤ)) (thinQuery m) le_rfl hQ + (by simp) (by simp) (by simp) (by simp [thinQuery, Nat.add_assoc]) + refine ⟨hi, y, n, hr, he, hn, ?_⟩ + have h := hv ⟨0, by simp [thinQuery]⟩ + change y.mem (outputBase (19 * m) m (thinN m) 1) = encode + (((0 : Matrix (Fin (thinN m)) (Fin (D m)) ℤ) * + (0 : Matrix (Fin (D m)) (Fin (thinN m)) ℤ)) (thinIndex m) (thinIndex m)) at h + simpa only [Matrix.zero_mul, Matrix.zero_apply, encode, Int.natAbs_zero, + Int.lt_irrefl, ↓reduceIte, Nat.mul_zero, Nat.zero_add] using h + +-- The sufficient numerical budget has a strict boundary; this is not a necessary runtime width. +example : ¬Fits 29 (wordBudget 0 0 1 3) ∧ Fits 30 (wordBudget 0 0 1 3) := by + decide +kernel + +-- Repeated requests satisfy the distinct-set condition without satisfying occurrence sparsity. +example : repeated.toFinset.card * 2 ^ 0 ≤ 1 ^ 2 ∧ ¬repeated.length * 2 ^ 0 ≤ 1 ^ 2 := by + decide +kernel + +-- Negative integers require signed mode: raw input deliberately represents nonnegative values. +#guard observe false 0 (m := 0) (N := 1) (fun _ _ => -1) (fun _ _ => 2) + [(0, 0)] == some ([-2], true) +#guard observe true 0 (m := 0) (N := 1) (fun _ _ => -1) (fun _ _ => 2) + [(0, 0)] == some ([0], true) + +private def framedSigned : Data 160 := + ((State.init (input false 0 signedX signedY repeated) : State 160).data.write 0 77).write + (memoryEnd 0 0 1 3) 91 + +-- A real call accepts an unrelated length/header word and preserves a sentinel exactly at +-- the allocation boundary, while computing the signed products and restoring the whole input. +example : ∃ y : Data 160, ∃ n, n ≤ timeBound (by decide : 0 ≤ 0) repeated ∧ + Runs 34 ((program false).program output) ⟨0, framedSigned⟩ + ⟨(program false).size, y⟩ n ∧ + Executes 34 ((program false).program output) ⟨0, framedSigned⟩ + (outputBase 0 0 1 3) (n + 1) ∧ + y.mem 0 = 77 ∧ y.mem (memoryEnd 0 0 1 3) = 91 ∧ + y.mem (outputBase 0 0 1 3) = encode (-6) := by + obtain ⟨y, ⟨n, hn, he⟩, hp, hv, hf⟩ := exec (w := 32) (A := 3) (by decide) + (by decide : 0 ≤ 0) false signedX signedY repeated framedSigned + (by + intro a ha hb + have : a < 13 := by norm_num [inputEnd, repeated, D] at hb ⊢; exact hb + interval_cases a <;> decide +kernel) + (by intro i k; norm_num [signedX]) (by intro k j; norm_num [signedY]) + (by simp) (by decide +kernel) (by decide +kernel) + change Parameters 0 0 1 3 y at hp + change ∀ a, ¬(inputEnd 0 1 3 ≤ a ∧ a < memoryEnd 0 0 1 3) → + y.mem a = framedSigned.mem a at hf + refine ⟨y, n, hn, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [Nat.zero_add] using he.runs (pc := 0) + (p := (program false).program output) (Program.Contains.self _).append_left + · simpa only [hp.output] using he.executes output + · rw [hf 0 (by decide +kernel)] + decide +kernel + · rw [hf (memoryEnd 0 0 1 3) (by omega)] + rfl + · have h := hv ⟨0, by decide⟩ + norm_num [signedX, signedY, repeated, Matrix.mul_apply, D, Fin.sum_univ_succ] at h ⊢ + exact h + +end ComplexityTests.SchonhageProduct 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/ComplexityTests/SchonhageWanted.lean b/ComplexityTests/SchonhageWanted.lean new file mode 100644 index 0000000000..c3a40a6a06 --- /dev/null +++ b/ComplexityTests/SchonhageWanted.lean @@ -0,0 +1,140 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +-- upstream: local-only (tests) + +import Complexity.Init +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted.GroupedRuns +import Complexity.Algorithms.MatrixMultiplication.Schonhage.MaskTable + +set_option linter.hashCommand false + +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Wanted +open Cslib.Algorithms + +private def expectedCode {L m : ℕ} (hm : m ≤ L) (I J : ℕ) : ℕ := + (PrunedQuery.queryIndex (outStrOfPos (MaskTable.layout hm) I J)).val + +-- Distinct row/column block indices use rank 1 and rank 2, not their transposes. +#guard expectedCode (by decide : 1 ≤ 4) 2 28 == 97 +#guard expectedCode (by decide : 1 ≤ 4) 28 2 == 905 +example : QueryDigits.pack 4 2 2 1 ≠ QueryDigits.pack 4 4 1 2 := by decide +kernel + +private def packedInput : Data 16 where + regs i := if i = depth then 4 else if i = mask then 2 else + if i = row then 2 else if i = col then 1 else 81 + mem a := if a = 23 then 91 else 0 + +-- The arbitrary nonzero scratch bank is initialized by real code; the same returned state +-- carries the packed word and the unchanged memory assertion. +example : ∃ y, ExecWithin 19 packCode packedInput y 70 ∧ + y.regs code = 97 ∧ y.mem 23 = 91 := by + obtain ⟨y, he, hv, _, hf⟩ := exec_pack (w := 19) 4 2 2 1 packedInput + (by decide) (by decide) (by decide) (by decide) (by decide) + exact ⟨y, he, hv, congrFun hf 23⟩ + +private def routingInput (L m N : ℕ) (qs : List (ℕ × ℕ)) : List ℕ := + [L, m, N, qs.length] ++ qs.flatMap (fun q => [q.1, q.2]) + +private def observe (L m N : ℕ) (qs : List (ℕ × ℕ)) : + Option (List ℕ × List ℕ × List ℕ × Bool) := + let initial := (State.init (routingInput L m N qs) : State 96).data.write 100000 91 + (program.eval 25 1024 initial).map fun (y, _) => + ((List.range qs.length).map fun i => y.mem (y.regs codes + i), + (List.range qs.length).map fun i => y.mem (y.regs originals + i), + (List.range (y.regs tileCount + 1)).map fun i => y.mem (y.regs offsets + i), + decide ((∀ a < 5 + 2 * qs.length, y.mem a = initial.mem a) ∧ y.mem 100000 = 91)) + +-- Actual initialized empty and zero-depth runs, including the computed tables and prefix pass. +#guard observe 0 0 0 [] == some ([], [], [0], true) +#guard observe 0 0 2 [(1, 0), (0, 1), (1, 0)] == + some ([0, 0, 0], [1, 0, 2], [0, 0, 1, 3, 3], true) + +-- K0=2, three bands, asymmetric row/column digits and an original repeated query. +-- This exercises only routing; it never materializes a 10^4-leaf encoded array. +#guard observe 4 1 110 [(2, 28), (28, 2), (55, 109), (2, 28), (109, 55)] == + some ([97, 905, 97, 49, 49], [0, 1, 3, 2, 4], [0, 3, 3, 3, 3, 3, 4, 4, 5, 5], true) + +private def repeatedQueries : List (Fin 2 × Fin 2) := + [(1, 0), (0, 1), (1, 0)] + +-- The kernel-checked initialized run and its halt use the same memory witness. Both +-- equal coordinate pairs survive as separate occurrences in the original-index array. +example : InputFits 32 (input 0 0 repeatedQueries) ∧ + ∃ y n, n ≤ timeBound 0 0 2 3 ∧ + Runs 32 (program.program codes) (State.init (input 0 0 repeatedQueries)) + ⟨program.size, y⟩ n ∧ + Executes 32 (program.program codes) (State.init (input 0 0 repeatedQueries)) + (codeAddress 0 0 2 3) (n + 1) ∧ + y.mem (codeAddress 0 0 2 3 + 1) = 0 ∧ + y.mem (originalAddress 0 0 2 3 + 1) = 0 ∧ + y.mem (originalAddress 0 0 2 3 + 2) = 2 ∧ + y.mem 1 = 0 := by + obtain ⟨hi, y, n, hn, hr, he, _, hv, _, _, hf⟩ := + runs (w := 32) (by decide : 0 ≤ 0) repeatedQueries (by decide +kernel) + have h0 := hv ⟨0, by decide⟩ + have h2 := hv ⟨2, by decide⟩ + have hp0 : CountingSort.position (tileKey 0 0 2 ∘ inputPairs repeatedQueries) 3 0 = 1 := by + decide +kernel + have hp2 : CountingSort.position (tileKey 0 0 2 ∘ inputPairs repeatedQueries) 3 2 = 2 := by + decide +kernel + have hz : (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout (by decide : 0 ≤ 0)) + (repeatedQueries.get ⟨0, by decide⟩).1 + (repeatedQueries.get ⟨0, by decide⟩).2)).val = 0 := by + have hlt := (PrunedQuery.queryIndex + (outStrOfPos (MaskTable.layout (by decide : 0 ≤ 0)) + (repeatedQueries.get ⟨0, by decide⟩).1 (repeatedQueries.get ⟨0, by decide⟩).2)).isLt + change _ < 1 at hlt + omega + simp only [show repeatedQueries.length = 3 from rfl, hp0] at h0 + simp only [show repeatedQueries.length = 3 from rfl, hp2] at h2 + refine ⟨hi, y, n, hn, hr, he, h0.1.trans hz, h0.2, h2.2, ?_⟩ + exact hf 1 (Or.inl (by decide)) + +-- The entirely empty input is admitted too; the terminal offset is written by real code. +example : ∃ y n, Runs 32 (program.program codes) (State.init (input 0 0 + ([] : List (Fin 0 × Fin 0)))) ⟨program.size, y⟩ n ∧ + Executes 32 (program.program codes) (State.init (input 0 0 + ([] : List (Fin 0 × Fin 0)))) (codeAddress 0 0 0 0) (n + 1) ∧ + y.mem (offsetAddress 0 0 0 0) = 0 := by + obtain ⟨_, y, n, _, hr, he, _, _, _, ht, _⟩ := + runs (w := 32) (by decide : 0 ≤ 0) ([] : List (Fin 0 × Fin 0)) (by decide +kernel) + exact ⟨y, n, hr, he, ht⟩ + +-- A physical width can fail the numerical admission premise, even for zero depth. +example : ¬Fits 1 (wordBudget 0 0 0 0) := by decide +kernel +example : Fits 32 (wordBudget 0 0 0 0) := by decide +kernel +example : (program.program codes).length ≤ 4096 := program_length_le +example : ¬program.ReadsWidth := not_readsWidth + +private def asymmetricQueries : List (Fin 110 × Fin 110) := + [(2, 28), (28, 2), (55, 109), (2, 28), (109, 55)] + +-- All admission premises are discharged on the asymmetric positive-inner-depth family. +-- The code-set assertion uses the same y as the actual initialized Runs and charged halt. +example : InputFits 64 (input 4 1 asymmetricQueries) ∧ + ∃ y n, Runs 64 (program.program codes) (State.init (input 4 1 asymmetricQueries)) + ⟨program.size, y⟩ n ∧ + Executes 64 (program.program codes) (State.init (input 4 1 asymmetricQueries)) + (codeAddress 4 1 110 5) (n + 1) ∧ + n + 1 ≤ 128 * 5 * (5 + 3 ^ 2 + 2 ^ 4 + 1) ∧ + (Finset.Ico (y.mem (offsetAddress 4 1 110 5)) + (y.mem (offsetAddress 4 1 110 5 + 1))).image + (fun j => y.mem (codeAddress 4 1 110 5 + j)) = + (wantedStrings (MaskTable.layout (by decide : 1 ≤ 4)) + asymmetricQueries.toFinset 0 0).image (fun w => (PrunedQuery.queryIndex w).val) ∧ + Function.Injective (fun j : Fin 5 => y.mem (originalAddress 4 1 110 5 + j.val)) := by + obtain ⟨hi, y, n, hn, hr, he, _, _, hg, _, _, _, hj, _, _, _⟩ := + runs_grouped (W := 64) (by decide : 1 ≤ 4) asymmetricQueries (by decide +kernel) + have hg0 := hg (0, 0) (by decide +kernel) + have hbands : numBands 4 1 110 = 3 := by decide +kernel + simp only [show asymmetricQueries.length = 5 from rfl, hbands] at hn + simp only [Nat.zero_mul, Nat.zero_add, Nat.add_zero] at hg0 + exact ⟨hi, y, n, hr, he, hn, hg0, hj⟩ diff --git a/NOTICE b/NOTICE index 468bfa04bc..17fb0a9d8c 100644 --- a/NOTICE +++ b/NOTICE @@ -31,3 +31,11 @@ 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 c0f55bba8a..5d26ec56ec 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -438,9 +438,42 @@ 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)`. +- **Native wanted-query routing** + ([#1561](https://github.com/cslib-community/complexitylib/issues/1561)): computes the + selected-subset table, tile keys, query codes, counts, prefix offsets and stable scatter from + original raw coordinates. Its initialized run preserves every original occurrence and gives + each tile exactly its finite `wantedStrings` code set, within + `128*(L+1)*(Q+numBands²+2^L+1)` instructions including halt. It uses fixed 96-register code and + an explicit positive sufficient-width threshold. Joining these groups to shared band encoders, + invoking the pruned kernel and reconstructing queried products remain separate native work. +- **Native queried-product composition** + ([#1581](https://github.com/cslib-community/complexitylib/issues/1581)): joins the actual router + and band producers to the pointer/epoch kernel, restores the original input, and reconstructs + every requested signed product in original order. It charges raw-entry conversion, all copies, + dispatch, one shared directory initialization, epochs and reporting. At `L=19m`, `D=4^m`, + `D^18≤N` and occurrence sparsity `Q*2^m≤N²`, its initialized fixed 160-register program uses at + most `4325376*(19m+1)*N²/D^(1/18)` instructions including halt, with sufficient width + `128*clog₂(N+Q+A+2)` for entry bound `A`. The distinct-set sparsity theorem instead retains an + explicit additive charge for all `Q` occurrences. Native sparse-product and Exact Triangle + solving, full input/time/width/asymptotic composition, and the improved Section 4 route to + exponent 1.9992 remain under [#1394]. - **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 diff --git a/UPSTREAM.md b/UPSTREAM.md index 7803bd2dda..66e65cbe87 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -170,6 +170,21 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he 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), @@ -251,6 +266,60 @@ 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. + Native wanted-query routing ([#1561](https://github.com/cslib-community/complexitylib/issues/1561)) + uses actual count, prefix and stable-scatter passes after computing the subset table and each + query's tile/code. It reuses `MaskTable.layout`, `outStrOfPos`, `wantedStrings` and the existing + `PrunedQuery.queryIndex`/encoder leaf order; no parallel layout or output-string universe is + added. Primitive counts use Mathlib's `Nat.count` and its prefix/monotonicity APIs directly. + The generic `CountingSort` topic module adds stable positions, their permutation and + bucket-span meaning, without redundant count/offset definitions. The narrow `WordRAM.Sequence` + proof module reassociates existing + structured execution derivations without changing code or charges. Original scalar headers + and raw coordinate pairs are the entire initialized input. Fixed 96-register standard code + computes all tables and grouped arrays, preserves the original input/outside frame, and + charges at most `128*(L+1)*(Q+numBands²+2^L+1)` instructions including halt. Its positive + ceiling-logarithmic admission threshold depends only on the original numerical budget. + Within-tile order is stable input order; no sorted or duplicate-free query premise is needed + by the pruned kernel. + The band catalogue retains the same leaf ordering and canonical signed words. + Native queried-product composition ([#1581](https://github.com/cslib-community/complexitylib/issues/1581)) + now connects those actual programs. It computes all protected buffers and tile arguments, + calls the shared encoders and pruned pointer/epoch kernel, and reconstructs every requested + `Matrix.mul` value in original order, retaining duplicates. Original signed matrices and raw + coordinate pairs are the entire initialized input. Raw nonnegative mode actually doubles + every matrix word before signed encoding, covering the raw 0/1 producer ABI. Copies, + header adaptation, one directory initialization, dispatch, epochs, reporting and restoration + are all charged. Existing `MaskTable.layout`, `wantedStrings`, `encodingL/R`, leaf numbering, + `Pruned.result` and stable-position meanings are reused; the existing `Wanted.tileKey_eq_iff` + lemma is exposed for the concrete tile-correctness proof without changing its statement. + The fixed 160-register program has at most 32768 flat instructions including halt and reads + no physical-width instruction. At `L=19m`, `D=4^m`, `D^18≤N`, `Q*2^m≤N²`, the complete charge + including halt is `4325376*(19m+1)*N²/D^(1/18)`. Distinct-set sparsity retains an additive + occurrence charge instead. The sufficient physical width is `128*clog₂(N+Q+A+2)`; its + magnitude/address budget and allocation have explicit polynomial envelopes in the original + dimensions, request count and entry bound. These are original native execution/resource + proofs, not a transfer of the source Light semantics or instruction constants. Exact Triangle + parameter/instance preparation and outer solving, full ordinary-3SUM composition, and the + improved Section 4 route to exponent 1.9992 remain separate work under #1394. + 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`. @@ -329,7 +398,7 @@ agreement during upstream review. | `Foundations/Combinatorics/{FiniteCover,Hoeffding,Hypergeometric,PowersetTail,Sunflower}` | Consider Mathlib combinatorics; probabilistic-method lemmas belong near `Combinatorics/Probabilistic`. | | `Foundations/Data/Finset/Expect` | Product-type expectation helpers extracted from `Foundations/Combinatorics/Expander/Products` (#317); Mathlib candidates. | | `Foundations/Data/Finset/Markov` | Mathlib candidate: `Algebra/Order/BigOperators/Group/Finset` (`Finset.card_filter_mul_le_sum`, `Finset.mul_card_filter_le_sum`). | -| `Foundations/Data/Finset/OrderGlue` | Follow-up to #1496: rename the generic `forall_level_iff`, `exists_innerLevel`, `outerLevel_notMem` and `filter_glue` using order-embedding/complement terminology (for example `forall_iff_orderEmb_and_compl`), retaining this topic module. Migrate the concrete `Schonhage/Levels` and `Schonhage/Batch` clients together with the active native band-preparation client; defer that API migration until the latter is stable. | +| `Foundations/Data/Finset/OrderGlue` | #1545 resolves the generic naming follow-up from #1496 after native bands stabilized in #1536: order-embedding/subset/complement terminology, Mathlib's `orderEmbOfFin_mem`/`range_orderEmbOfFin` and ordinary substitution in place of five redundant wrappers, and migration of finite tensor clients with checked native band clients. Retain this focused topic module. | | `Foundations/Data/Matrix/Permanent` | Mathlib candidate: Laplace expansion of the permanent along row and column 0, beside `LinearAlgebra/Matrix/Permanent` (mirroring `Matrix.det_succ_row_zero`). | | `Foundations/LinearAlgebra/{Rank,Matrix/CutRank,Matrix/Lindsey}` | Mathlib candidates: `LinearAlgebra/Dimension/LinearMap` and `LinearAlgebra/Matrix/Rank`; placement of Lindsey's lemma needs agreement. | | `Probability/PairwiseIndependentHash/Shift` | Consider integration into `PairwiseIndependentHash`. |