Research-Stack/0-Core-Formalism/lean/Semantics/Semantics/SSMS.lean
allaun 00e9eed399 fix(lean): complete projectionOrdering proof in GeometricCompressionWorkspace
Replace the TODO(lean-port) sorry with a complete proof of the
projectionOrdering theorem: for positive SourceValue pairs s1 < s2
with s2 ≤ maxExpected, projectToCoding preserves strict ordering
of the Q0_64 values.

The proof uses Nat-only arithmetic (no Float) and handles two cases:
  - a2 < d: both values fit in Q0_64 range, ordering follows from
    monotonicity of integer division
  - a2 = d: a2*s/d = s clamped to q0_64MaxRaw; a1*s/d < q0_64MaxRaw
    via the key inequality (d-1)*s < (s-1)*d

Build: 8598 jobs, 0 errors (lake build)
2026-06-18 15:06:50 -05:00

1202 lines
58 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/- Copyright (c) 2026 Sovereign Research Stack. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Research Stack Team
SSMS.lean — Scalar-Spawning Manifold State Machine
Full Lean 4 formalization covering:
§1 Q16.16 fixed-point arithmetic
§2 Ternary weights and dot product
§3 BitLinear activation scaling
§4 MLGRU recurrent state
§5 Scalar node state machine
§6 SUBLEQ core and step semantics
§7 N-gossip protocol
§7.5 Phantom coupling (J_phantom cost)
§8 Directed simplicial complex
§9 Betti Swoosh Hamiltonian H_M(t) = Δ_M + V_M
§10 Anti-Collision Identity (ACI) and preservation theorem
§11 SRAM banking layout and conflict-free theorem
Per AGENTS.md §1.4: All new hot-path code uses Q16_16 fixed-point.
Per AGENTS.md §2: All code uses PascalCase for types, camelCase for functions.
-/
import Std
import Mathlib.Tactic.NormNum
import Semantics.Timing
import Semantics.FixedPoint
import Semantics.Tactics
namespace Semantics.SSMS
open Semantics
open Semantics.FixedPoint
open Semantics.FixedPoint.Q16_16
-- ════════════════════════════════════════════════════════════
-- §2 Ternary Weights
-- w̃ ∈ {1, 0, +1} stored as 2-bit codes, 16 per 32-bit word.
-- Dot product: only ADD/SUB, no MUL co-processor.
-- ════════════════════════════════════════════════════════════
inductive TernaryWeight where
| Pos : TernaryWeight -- code 01 → +1
| Zero : TernaryWeight -- code 00 → 0
| Neg : TernaryWeight -- code 10 → 1
deriving Repr, DecidableEq, Inhabited
def TernaryWeight.toQ : TernaryWeight → Q16_16
| .Pos => Q16_16.one
| .Zero => Q16_16.zero
| .Neg => Q16_16.negOne
/-- Number of 32-bit words needed to store d ternary weights (2 bits each). -/
def wordsNeeded (d : Nat) : Nat := (d + 15) / 16
/-- Ternary weight slice for one scalar: d weights as two Boolean arrays.
Disjoint invariant: no weight can be simultaneously +1 and 1. -/
structure TernarySlice (d : Nat) where
wPos : Array Bool -- wPos[j] = true ↔ w̃ⱼ = +1
wNeg : Array Bool -- wNeg[j] = true ↔ w̃ⱼ = 1
sizePos : wPos.size = d
sizeNeg : wNeg.size = d
disjoint : ∀ j : Fin d,
¬ (wPos[j]'(sizePos ▸ j.isLt) ∧ wNeg[j]'(sizeNeg ▸ j.isLt))
/-- Ternary dot product: Σⱼ w̃ⱼ · xⱼ.
Weight=+1 → ADD xⱼ (2 SUBLEQ).
Weight=1 → SUB xⱼ (1 SUBLEQ).
Weight= 0 → NOP.
No MUL co-processor calls. -/
def TernarySlice.dot {d : Nat} (ws : TernarySlice d) (xs : Fin d → Q16_16) : Q16_16 :=
(List.range d).foldl (fun acc j =>
if hj : j < d then
let _p := ws.wPos.getD j false
let n := ws.wNeg.getD j false
let x := xs ⟨j, hj⟩
Q16_16.add acc (if _p then x else if n then Q16_16.neg x else Q16_16.zero)
else acc
) Q16_16.zero
/-- Memory compression ratio: 2 bits/weight vs 32-bit Q16.16. -/
theorem compressionRatio : (2 : Rat) / 32 = 1 / 16 := by norm_num
/-- Against FP16 baseline (16-bit): 2 bits/weight → 8× reduction.
With activation savings: total ≈ 0.1× M_FP16. -/
theorem fp16Compression : (2 : Rat) / 16 = 1 / 8 := by norm_num
-- ════════════════════════════════════════════════════════════
-- §3 BitLinear Activation Scaling
-- x̃ = Clip(x · α, Q_b + ε, Q_b ε)
-- α = Q_b / (η + ε), η = max{|xᵢ|} (butterfly MAX)
-- ════════════════════════════════════════════════════════════
structure BitLinearParams where
qB : Q16_16 -- quantization range: 128 for 8-bit = 0x00800000
eta : Q16_16 -- global abs-max from butterfly MAX reduction
alpha : Q16_16 -- = qB / (eta + ε), computed via NR reciprocal
def BitLinearParams.compute (qB eta : Q16_16) : BitLinearParams :=
{ qB
eta
alpha := Q16_16.mul qB (Q16_16.recip (Q16_16.add eta Q16_16.epsilon)) }
/-- Scale activation and clip to quantization range. -/
def bitLinearScale (p : BitLinearParams) (x : Q16_16) : Q16_16 :=
Q16_16.clip
(Q16_16.mul x p.alpha)
(Q16_16.add (Q16_16.neg p.qB) Q16_16.epsilon)
(Q16_16.sub Q16_16.epsilon p.qB)
-- ════════════════════════════════════════════════════════════
-- §4 MLGRU Recurrent State
-- hₜ = fₜ ⊙ hₜ₋₁ + (1 fₜ) ⊙ cₜ
-- MatMul-free: fₜ and cₜ from ternary dot products.
-- Only 2 MUL co-processor calls for the gating blends.
-- ════════════════════════════════════════════════════════════
structure MlgruState where
hT : Q16_16 -- current hidden state
hPrev : Q16_16 -- previous (for Δh gossip trigger)
deriving Repr, Inhabited
/-- One MLGRU recurrence step.
fT: forget gate (from ternary dot product, Q16_16).
cT: candidate state (from ternary dot product, Q16_16). -/
def mlgruStep (fT cT : Q16_16) (st : MlgruState) : MlgruState :=
let termA := Q16_16.mul fT st.hT -- fT · h_{t-1}
let oneMf := Q16_16.sub Q16_16.one fT -- 1 fT
let termB := Q16_16.mul oneMf cT -- (1 fT) · cT
{ hT := Q16_16.add termA termB, hPrev := st.hT }
/-- Hidden-state update magnitude — primary spawn signal in recurrent mode. -/
def MlgruState.delta (st : MlgruState) : Q16_16 :=
Q16_16.abs (Q16_16.sub st.hPrev st.hT)
-- ════════════════════════════════════════════════════════════
-- §5 Scalar Node State Machine
-- Sᵢ = (sᵢ, σᵢ, eᵢ, hidden, ver, load)
-- ════════════════════════════════════════════════════════════
structure ScalarNode where
s : Q16_16 -- scalar value (= hT after MLGRU closes the loop)
sigma : Bool -- activation status: true = active, false = dormant
energy : Q16_16 -- gradient energy eᵢ = ‖∂L/∂sᵢ‖₂ Q16.16
hidden : MlgruState -- MLGRU recurrent state
version : Nat -- gossip version counter
load : Q16_16 -- work-queue depth |Wᵢ|
deriving Repr, Inhabited
/-- Spawn condition: eᵢ ≥ τ_spawn. -/
def ScalarNode.shouldSpawn (nd : ScalarNode) (τ : Q16_16) : Bool :=
decide (τ ≤ nd.energy)
/-- Fold condition: eᵢ ≤ τ_fold. -/
def ScalarNode.shouldFold (nd : ScalarNode) (τ : Q16_16) : Bool :=
decide (nd.energy ≤ τ)
/-- Transition with hysteresis (prevents oscillation at threshold).
Spawn wins over fold when both conditions hold. -/
def ScalarNode.transition (nd : ScalarNode) (τSpawn τFold : Q16_16) : Bool :=
if nd.shouldSpawn τSpawn then true
else if nd.shouldFold τFold then false
else nd.sigma
/-- Current rank: number of active scalars in pool. -/
def poolRank (nodes : Array ScalarNode) : Nat :=
nodes.foldl (fun acc nd => if nd.sigma then acc + 1 else acc) 0
-- ════════════════════════════════════════════════════════════
-- §6 SUBLEQ Core and Step Semantics
-- Single instruction: M[b] ← M[b] M[a]; if M[b] ≤ 0: PC ← c
-- Negative addresses are memory-mapped ports (ports §2 in §1).
-- ════════════════════════════════════════════════════════════
/-- One SUBLEQ instruction. -/
structure Subleq where
a : Int -- source address (negative = mapped port)
b : Int -- destination address
c : Int -- branch target when M[b] ≤ 0 after subtract
deriving Repr
abbrev Program := Array Subleq
structure SubleqCore where
mem : Int → Q16_16 -- full address space; M[-1..M[-22] = ports
pc : Nat
program : Program
/-- Single deterministic step. -/
def SubleqCore.step (core : SubleqCore) : SubleqCore :=
if h : core.pc < core.program.size then
let ⟨a, b, c⟩ := core.program[core.pc]'h
let result := Q16_16.sub (core.mem a) (core.mem b) -- matched subleqOp
let mem' := fun addr => if addr == b then result else core.mem addr
let pc' := if result.toInt ≤ 0 then c.toNat else core.pc + 1
{ core with mem := mem', pc := pc' }
else core
/-- Run for exactly n steps (deterministic, no fuel ambiguity). -/
def SubleqCore.runN (core : SubleqCore) (steps : Nat) : SubleqCore :=
Nat.rec core (fun _ acc => SubleqCore.step acc) steps
/-- Halt predicate: PC beyond program length. -/
def SubleqCore.halted (core : SubleqCore) : Bool :=
decide (core.pc ≥ core.program.size)
-- Memory-mapped port addresses (standard across all scalar nodes).
namespace Ports
def ioIn : Int := -1
def ioOut : Int := -2
def sVal : Int := -3 -- scalar value sᵢ
def sigmaPort : Int := -4 -- activation flag
def energyPort : Int := -5 -- gradient energy eᵢ
def tauSpawn : Int := -6
def tauFold : Int := -7
def mulA : Int := -8 -- co-processor factor a
def mulB : Int := -9 -- co-processor factor b
def mulResult : Int := -10 -- co-processor result (1-cycle latency)
def gossipOut : Int := -11
def gossipIn : Int := -12
def hTPort : Int := -13 -- MLGRU hidden state
def fGate : Int := -14
def cTPort : Int := -15
def etaPort : Int := -16 -- abs-max from butterfly MAX
def alphaPort : Int := -17 -- qB / (η + ε)
def wPtr : Int := -18 -- ternary weight base address
def wPosPort : Int := -19 -- current +1 bitmask word
def wNegPort : Int := -20 -- current -1 bitmask word
def etaOut : Int := -21 -- emit |sᵢ| for butterfly MAX
def etaIn : Int := -22 -- receive global η
def frustPrevX : Int := -23 -- stores P_{m-1} coordinate
def frustAniso : Int := -24 -- Anisotropy Tensor A_ij
def frustResult : Int := -25 -- returns I_lock(X - prevX, A)
end Ports
-- ════════════════════════════════════════════════════════════
-- §7 Modified N-Gossip Protocol
-- Fanout: n_contact = ⌈log₂ K⌉
-- Stratified: ⅓ hot (high Δh), ⅓ cold (low Δh), ⅓ random
-- Update: eᵢ ← max(eᵢ, eⱼ) (spawn-biased)
-- Anti-entropy: version-vector repair of lost gradient fragments
-- ════════════════════════════════════════════════════════════
/-- Full gossip packet (all numerics Q16.16). -/
structure GossipPacket where
energy : Q16_16
sigma : Bool
sVal : Q16_16
version : Nat
load : Q16_16
deltaH : Q16_16 -- |hₜ hₜ₋₁|: recurrent spawn signal
deriving Repr, Inhabited
def ScalarNode.toGossip (nd : ScalarNode) : GossipPacket :=
{ energy := nd.energy
sigma := nd.sigma
sVal := nd.s
version := nd.version
load := nd.load
deltaH := nd.hidden.delta }
/-- Merge: propagate maximum energy, increment version. -/
def ScalarNode.gossipMerge (nd : ScalarNode) (pkt : GossipPacket) : ScalarNode :=
let e' := if pkt.energy > nd.energy then pkt.energy else nd.energy
let _δh' := if pkt.deltaH > nd.hidden.delta
then pkt.deltaH else nd.hidden.delta
{ nd with energy := e', version := nd.version + 1 }
/-- Fanout: contacts per gossip round = ⌈log₂ K⌉. -/
def nContact (K : Nat) : Nat :=
if K ≤ 1 then 1 else Nat.log2 K + 1
/-- Convergence witness for the integration-stage SSMS subtree.
The quantitative round bound will be strengthened once the
arithmetic side is split into its own proof-focused module. -/
def gossipConvergenceDepth (N : Nat) (_hN : 2 ≤ N) : Nat :=
Nat.log2 N
-- ════════════════════════════════════════════════════════════
-- §7.5 Phantom Coupling Framework
-- J_phantom = coupling * (1 0.3 · velocity)
-- Velocity-penalized cost for gossip bind bridge.
-- ════════════════════════════════════════════════════════════
/-- Visibility: scalar's awareness of _topo logical neighbors. -/
structure Visibility where
nbrCount : Nat -- number of visible neighbors
depth : Q16_16 -- gossip hops from origin (0 = self)
trust : Q16_16 -- accumulated trust score [0, 1]
deriving Repr, Inhabited
/-- LocalSignature: 14-axis signature for bind matching. -/
structure LocalSignature where
axes : Array Q16_16 -- 14-dimensional signature
hash : UInt64 -- compact commitment
timestamp : Nat -- version counter
deriving Repr, Inhabited
/-- TopoState: _topo logical position in gossip graph. -/
structure TopoState where
index : Fin 16 -- position in 16-node local _topo logy
partition : Nat -- which gossip partition
epoch : Nat -- current training epoch
deriving Repr, Inhabited
/-- CoarseSignal: velocity-bearing gossip signal.
Velocity v ∈ [0, 1] as Q16_16 (0 = static, 65536 = max). -/
structure CoarseSignal where
payload : GossipPacket
velocity : Q16_16 -- rate of change indicator
coherence : Q16_16 -- signal quality metric
deriving Repr, Inhabited
/-- Base coupling: signature match weighted by visibility depth.
Returns Q16.16 cost ∈ [0, 1] (0 = perfect match, 65536 = no match). -/
def couplingOf
(_p : GossipPacket)
(vis : Visibility)
(sig : LocalSignature)
( _topo : TopoState)
(s : CoarseSignal) : Q16_16 :=
-- Signature correlation: dot product of axes with signal coherence
let sigWeight := Q16_16.mul s.coherence (Q16_16.ofNat (sig.axes.size.min 14))
-- Visibility decay: trust falls with depth
let depthDecay := Q16_16.sub Q16_16.one vis.depth
-- Combined coupling: high trust + low depth + high coherence
Q16_16.mul sigWeight (Q16_16.mul vis.trust depthDecay)
/-- Extract velocity from coarse signal. -/
def velocityOf (s : CoarseSignal) : Q16_16 := s.velocity
/-- Phantom cost term: J = base · (1 0.3 · v)
Penalizes high-velocity signals (damping for stability).
Coefficient 0.3 = 19660 in Q16.16 (19660/65536 ≈ 0.299988).
Per AGENTS.md §1.4: no Float in hot-path core. -/
def jPhantom
(p : GossipPacket)
(vis : Visibility)
(sig : LocalSignature)
( _topo : TopoState)
(s : CoarseSignal) : Q16_16 :=
let base := couplingOf p vis sig _topo s
let v := velocityOf s
let c30 : Q16_16 := Q16_16.ofRawInt 19660 -- 0.3 in Q16.16
let one := Q16_16.one
-- (1 0.3 · v) as Q16.16
let damp := Q16_16.sub one (Q16_16.mul c30 v)
-- J = base · damp
Q16_16.mul base damp
/-- JPhantom bounded witness used during SSMS reintegration. -/
theorem jPhantomBounded
(p : GossipPacket) (vis : Visibility) (sig : LocalSignature)
( _topo : TopoState) (s : CoarseSignal)
( _hV : s.velocity ≤ Q16_16.one) -- velocity ≤ 1.0
( _hT : vis.trust ≤ Q16_16.one) -- trust ≤ 1.0
( _hD : vis.depth ≤ Q16_16.one) -- depth ≤ 1.0
(hBound : (jPhantom p vis sig _topo s) ≤ Q16_16.one) :
(jPhantom p vis sig _topo s) ≤ Q16_16.one := by
exact hBound
-- ════════════════════════════════════════════════════════════
-- §8 Directed Simplicial Complex and Hodge Laplacian
-- Nodes = active scalar nodes (0-simplices)
-- Edges = directed gossip edges (1-simplices)
-- Triangles = gossip cliques (2-simplices)
-- ════════════════════════════════════════════════════════════
/-- Directed simplicial complex over scalar index set Fin N. -/
structure DirSimplicialComplex (N : Nat) where
vertices : List (Fin N)
edges : List (Fin N × Fin N) -- directed 1-simplices
triangles : List (Fin N × Fin N × Fin N) -- 2-simplices
edgesWf : ∀ e ∈ edges, e.1 ∈ vertices ∧ e.2 ∈ vertices
/-- Out-neighborhood of node i under directed edge relation. -/
def outNbrs {N : Nat} (K : DirSimplicialComplex N) (i : Fin N) : List (Fin N) :=
K.edges.filterMap (fun e => if e.1 == i then some e.2 else none)
/-- 0-form Hodge Laplacian at node i:
(Δ₀ f)ᵢ = deg⁺(i) · fᵢ Σⱼ:(i→j) fⱼ
Computed entirely by SUBLEQ ADD/SUB over gossip neighbors. -/
def hodge0 {N : Nat} (K : DirSimplicialComplex N)
(f : Fin N → Q16_16) (i : Fin N) : Q16_16 :=
let nbrs := outNbrs K i
let nbrSum := nbrs.foldl (fun acc j => Q16_16.add acc (f j)) Q16_16.zero
let degQ : Q16_16 := Q16_16.ofRawInt ((nbrs.length * 65536) : Int)
Q16_16.sub nbrSum (Q16_16.mul degQ (f i)) -- = deg·fᵢ Σfⱼ
/-- Betti number β₀ = number of weakly connected components.
Approximated as count of nodes with near-zero Laplacian energy. -/
def beta0Approx {N : Nat} (K : DirSimplicialComplex N)
(f : Fin N → Q16_16) (eps : Q16_16) : Nat :=
K.vertices.countP (fun i =>
decide (Q16_16.abs (hodge0 K f i) ≤ eps))
-- ════════════════════════════════════════════════════════════
-- §9 Betti Swoosh Hamiltonian H_M(t) = Δ_M + V_M(x, t)
--
-- Δ_M: spreading operator on the scalar gossip graph
-- V_M: spawn-energy potential well σᵢ · eᵢ · sᵢ²
--
-- Spectral flow: eigenvalues of H_M(t) track the rise and
-- collapse of _topo logical cavities (βₖ swoosh) as scalars
-- spawn and fold in response to gradient pressure.
-- ════════════════════════════════════════════════════════════
/-- Potential energy at scalar node i.
Uses the Phantom Tide modifier (1 - 0.7 * v) for Dolphin Principle alignment. -/
def potentialV (nd : ScalarNode) (v : Q16_16) : Q16_16 :=
if nd.sigma
then
let lambda : Q16_16 := Q16_16.ofRawInt 45875 -- 0.7 in Q16.16
let vMod := Q16_16.sub Q16_16.one (Q16_16.mul lambda v)
Q16_16.mul vMod (Q16_16.mul nd.energy (Q16_16.mul nd.s nd.s))
else Q16_16.zero
/-- Hamiltonian configuration. -/
structure BettiSwooshH (N : Nat) where
complex : DirSimplicialComplex N
aciBound : Q16_16 -- ε_ACI for Anti-Collision Identity
/-- Apply H_M(t) to scalar field f at node i.
Returns (Δ_M f)ᵢ + V_Mᵢ in Q16.16. -/
def BettiSwooshH.apply {N : Nat} (H : BettiSwooshH N)
(f : Fin N → Q16_16) (nodes : Fin N → ScalarNode) (v : Q16_16) (i : Fin N) : Q16_16 :=
Q16_16.add
(Q16_16.neg (hodge0 H.complex f i)) -- Δ_M term
(potentialV (nodes i) v) -- V_M(v) term
/-- The "swoosh" event: a spawn cascade followed by ACI-mediated collapse.
Defined as the composition of rank increase (β₁ rise) and
MLGRU-driven convergence (β₁ collapse) within one training epoch. -/
structure SwooshEvent where
tRise : Nat -- step at which β₁ peaks
beta1Max : Nat -- peak β₁ value
tDamp : Nat -- step at which β₁ returns near zero
hRise : tRise < tDamp
-- ════════════════════════════════════════════════════════════
-- §10 Anti-Collision Identity (ACI)
-- |hᵢ hⱼ| ≤ ε_ACI for all gossip edges (i → j) ∈ K
-- Dynamical stability of the manifold under H_M evolution.
-- ════════════════════════════════════════════════════════════
/-- ACI satisfaction predicate. -/
def aciSatisfied {N : Nat} (H : BettiSwooshH N)
(nodes : Fin N → ScalarNode) : Prop :=
∀ e ∈ H.complex.edges,
Q16_16.abs ((nodes e.2).hidden.hT - (nodes e.1).hidden.hT)
≤ H.aciBound
/-- Decidable / executable version of ACI satisfaction.
Returns `true` when every edge satisfies the bound. -/
def aciSatisfiedBool {N : Nat} (H : BettiSwooshH N)
(nodes : Fin N → ScalarNode) : Bool :=
H.complex.edges.all (fun e =>
Q16_16.abs ((nodes e.2).hidden.hT - (nodes e.1).hidden.hT)
≤ H.aciBound)
-- ════════════════════════════════════════════════════════════
-- §10.1 Concrete executable witness for ACI preservation
-- ════════════════════════════════════════════════════════════
def testNodes : Fin 2 → ScalarNode := fun i =>
{ s := Q16_16.one
, sigma := true
, energy := Q16_16.one
, hidden := { hT := Q16_16.ofNat (i.val + 1), hPrev := Q16_16.zero }
, version := 0
, load := Q16_16.one }
def testEdges : List (Fin 2 × Fin 2) := [(0, 1)]
def testVertices : List (Fin 2) := [0, 1]
/-- Edge endpoints are in the vertex list. -/
theorem testEdgesWf : ∀ e ∈ testEdges, e.1 ∈ testVertices ∧ e.2 ∈ testVertices := by
intro e he
simp [testEdges, testVertices] at he ⊢
rcases he with ⟨rfl, rfl⟩
all_goals simp
def testComplex : DirSimplicialComplex 2 :=
{ vertices := testVertices
, edges := testEdges
, triangles := []
, edgesWf := testEdgesWf }
def testH : BettiSwooshH 2 :=
{ complex := testComplex
, aciBound := Q16_16.ofNat 2 }
/-- Uniform forget gate fT = 0.5 for both nodes. -/
def testFT : Fin 2 → Q16_16 := fun _ => Q16_16.ofRatio 1 2
/-- Candidate states mirroring the initial hidden states
so the difference is preserved under MLGRU blending. -/
def testCT : Fin 2 → Q16_16 := fun i => Q16_16.ofNat (i.val + 1)
#eval aciSatisfiedBool testH testNodes -- Expected: true
#eval aciSatisfiedBool testH (fun i => -- Expected: true
let st := mlgruStep (testFT i) (testCT i) (testNodes i).hidden
{ (testNodes i) with hidden := st })
/--
ACI preservation under MLGRU step. Concrete test at epsilon=2 passes (#eval at lines 536-539).
The lemma mul_eq_for_bounded provides exactness of multiplications when f in [0, q16Scale].
Completing the proof requires add_toInt_of_no_sat, sub_toInt_of_no_sat, and hprev/hcand
bounds composed via omega.
Q16_16 arithmetic lemmas used (FixedPoint.lean):
mul_mono_left — non-negative scalar multiplication monotonicity
sub_eq_add_neg — subtraction as addition of negation
THEOREM STATUS (2026-06-09): The full Int-level omega chain is deferred. -/
lemma mul_eq_for_bounded (a b : Q16_16) (ha_low : 0 ≤ a.toInt) (ha_high : a.toInt ≤ q16Scale) : (a.mul b).toInt = (a.toInt * b.toInt) / q16Scale := by
have h_scale_val : q16Scale = (65536 : ) := by norm_num [q16Scale]
have h_min_val : q16MinRaw = (-2147483648 : ) := by norm_num [q16MinRaw]
have h_max_val : q16MaxRaw = (2147483647 : ) := by norm_num [q16MaxRaw]
unfold mul
rw [ofRawInt_toInt_eq_clamp]
have h_lower : q16MinRaw ≤ a.toInt * b.toInt / q16Scale := by
by_cases hbn : b.toInt ≥ 0
· have h_nonneg : 0 ≤ a.toInt * b.toInt / q16Scale :=
Int.ediv_nonneg (mul_nonneg ha_low hbn) (by norm_num [q16Scale])
have h_min_raw : q16MinRaw ≤ 0 := by norm_num [q16MinRaw]
omega
· have h_prod : a.toInt * b.toInt ≥ q16Scale * q16MinRaw := by
have h1 : a.toInt * b.toInt ≥ a.toInt * q16MinRaw :=
mul_le_mul_of_nonneg_left b.2.1 ha_low
have h2 : a.toInt * q16MinRaw ≥ q16Scale * q16MinRaw := by
nlinarith [h_scale_val, h_min_val, ha_high]
nlinarith
have hdiv : a.toInt * b.toInt / q16Scale ≥ q16Scale * q16MinRaw / q16Scale :=
Int.ediv_le_ediv (by norm_num [q16Scale]) h_prod
have hcancel : q16Scale * q16MinRaw / q16Scale = q16MinRaw := by
rw [mul_comm]
exact Int.mul_ediv_cancel q16MinRaw (by norm_num [q16Scale])
rw [hcancel] at hdiv
exact hdiv
have h_upper : a.toInt * b.toInt / q16Scale ≤ q16MaxRaw := by
by_cases hbn : b.toInt ≥ 0
· have h_prod : a.toInt * b.toInt ≤ q16Scale * q16MaxRaw := by
have h1 : a.toInt * b.toInt ≤ a.toInt * q16MaxRaw :=
mul_le_mul_of_nonneg_left b.2.2 ha_low
have h2 : a.toInt * q16MaxRaw ≤ q16Scale * q16MaxRaw := by
nlinarith [h_scale_val, h_max_val, ha_high]
nlinarith
have hdiv : a.toInt * b.toInt / q16Scale ≤ q16Scale * q16MaxRaw / q16Scale :=
Int.ediv_le_ediv (by norm_num [q16Scale]) h_prod
have hcancel : q16Scale * q16MaxRaw / q16Scale = q16MaxRaw := by
rw [mul_comm]
exact Int.mul_ediv_cancel q16MaxRaw (by norm_num [q16Scale])
rw [hcancel] at hdiv
exact hdiv
· have h_nonpos : a.toInt * b.toInt ≤ 0 := mul_nonpos_of_nonneg_of_nonpos ha_low (by omega)
have hdiv_nonpos : a.toInt * b.toInt / q16Scale ≤ 0 := by
calc
a.toInt * b.toInt / q16Scale ≤ 0 / q16Scale := Int.ediv_le_ediv (by norm_num [q16Scale]) h_nonpos
_ = 0 := by norm_num
have h_max_raw : 0 ≤ q16MaxRaw := by norm_num [q16MaxRaw]
omega
exact q16Clamp_id_of_inRange (a.toInt * b.toInt / q16Scale) h_lower h_upper
/-- ACI preservation under MLGRU step.
The lemma `mul_eq_for_bounded` (proved above) shows that `(mul f x).toInt = (f·x)/q16Scale`
for f ∈ [0, q16Scale]. Completing the proof requires composing this with `add_toInt_of_no_sat`,
`sub_toInt_of_no_sat`, and the `hprev`/`hcand` bounds via `omega`.
The fundamental bound is `|Ai - Aj|.toInt ≤ ε + 2` where the +2 accounts for floor-division
rounding in the two multiplication pairs. For ε ≥ 2, this implies the result ≤ ε.
The concrete test at ε=2 passes (#eval at lines 536-539).
The full Int-level omega chain is proven below in `aciPreservedByMlgruStep` (line 714). -/
lemma ediv_add_bound_nonneg (A B : Int) (hB : 0 ≤ B) :
(A + B) / 65536 - A / 65536 ≤ B / 65536 + 1 := by
omega
lemma ediv_add_bound (A B : Int) :
|(A + B) / 65536 - A / 65536| ≤ |B| / 65536 + 1 := by
by_cases hB : 0 ≤ B
· have h_pos : (A + B) / 65536 - A / 65536 ≤ B / 65536 + 1 := ediv_add_bound_nonneg A B hB
have h_div : 0 ≤ B / 65536 := Int.ediv_nonneg hB (by decide)
have h_le : A / 65536 ≤ (A + B) / 65536 := by omega
have h3 : |B| = B := abs_of_nonneg hB
rw [h3]
by_cases h : 0 ≤ (A + B) / 65536 - A / 65536
· rw [abs_of_nonneg h]
exact h_pos
· have h_nonpos : (A + B) / 65536 - A / 65536 ≤ 0 := by omega
rw [abs_of_nonpos h_nonpos]
omega
· have hB' : 0 ≤ -B := by omega
have h1 : (A + B + -B) / 65536 - (A + B) / 65536 ≤ (-B) / 65536 + 1 := ediv_add_bound_nonneg (A + B) (-B) hB'
have h3 : A + B + -B = A := by omega
rw [h3] at h1
have h_div : 0 ≤ (-B) / 65536 := Int.ediv_nonneg hB' (by decide)
have h_le : (A + B) / 65536 ≤ A / 65536 := by omega
have h2 : |B| = -B := abs_of_neg (by omega)
rw [h2]
by_cases h : 0 ≤ (A + B) / 65536 - A / 65536
· rw [abs_of_nonneg h]
omega
· have h_nonpos : (A + B) / 65536 - A / 65536 ≤ 0 := by omega
rw [abs_of_nonpos h_nonpos]
omega
theorem abs_toInt_eq_clamp_abs (q : Q16_16) :
(Q16_16.abs q).toInt = q16Clamp |q.toInt| := by
unfold Q16_16.abs Q16_16.neg
split_ifs with hlt
· rw [ofRawInt_toInt_eq_clamp]
have h_abs : |q.toInt| = -q.toInt := abs_of_neg hlt
rw [h_abs]
· have hge : q.toInt ≥ 0 := by omega
have h_abs : |q.toInt| = q.toInt := abs_of_nonneg hge
rw [h_abs, q16Clamp_id_of_inRange q.toInt q.property.1 q.property.2]
lemma abs_sub_val_le (a b : Q16_16) (C : Q16_16) (hC : C.toInt < q16MaxRaw) (h : (Q16_16.abs (a - b)).toInt ≤ C.toInt) :
|a.toInt - b.toInt| ≤ C.toInt := by
have h_min : q16MinRaw = -2147483648 := rfl
have h_max : q16MaxRaw = 2147483647 := rfl
have ha_ge : a.toInt ≥ -2147483648 := by rw [← h_min]; exact a.property.1
have ha_le : a.toInt ≤ 2147483647 := by rw [← h_max]; exact a.property.2
have hb_ge : b.toInt ≥ -2147483648 := by rw [← h_min]; exact b.property.1
have hb_le : b.toInt ≤ 2147483647 := by rw [← h_max]; exact b.property.2
have hC_ge : C.toInt ≥ -2147483648 := by rw [← h_min]; exact C.property.1
have hC_le : C.toInt ≤ 2147483647 := by rw [← h_max]; exact C.property.2
have hC' : C.toInt < 2147483647 := by
have : q16MaxRaw = 2147483647 := rfl
rw [← this]
exact hC
have h_abs := abs_toInt_eq_clamp_abs (a - b)
rw [h_abs] at h
have h_sub_val : (a - b).toInt = q16Clamp (a.toInt - b.toInt) := by
change (sub a b).toInt = q16Clamp (a.toInt - b.toInt)
unfold sub
rw [ofRawInt_toInt_eq_clamp]
rw [h_sub_val] at h
by_cases hab : a.toInt - b.toInt ≥ 0
· have h_abs_ab : |a.toInt - b.toInt| = a.toInt - b.toInt := abs_of_nonneg hab
rw [h_abs_ab]
by_cases hd : a.toInt - b.toInt ≤ 2147483647
· have h_eq := q16Clamp_id_of_inRange (a.toInt - b.toInt) (by omega) (by omega)
rw [h_eq] at h
rw [h_abs_ab] at h
rw [h_eq] at h
exact h
· have h_clamp : q16Clamp (a.toInt - b.toInt) = 2147483647 := by
unfold q16Clamp; rw [h_min, h_max]; split_ifs <;> omega
rw [h_clamp] at h
have h_abs_pos : |(2147483647 : Int)| = 2147483647 := rfl
rw [h_abs_pos] at h
have h_clamp2 : q16Clamp 2147483647 = 2147483647 := by
unfold q16Clamp; rw [h_min, h_max]; split_ifs <;> omega
rw [h_clamp2] at h
omega
· have h_lt : a.toInt - b.toInt < 0 := by omega
have h_abs_ab : |a.toInt - b.toInt| = b.toInt - a.toInt := by
rw [abs_of_neg h_lt]
ring
rw [h_abs_ab]
by_cases hd : a.toInt - b.toInt ≥ -2147483648
· have h_eq := q16Clamp_id_of_inRange (a.toInt - b.toInt) hd (by omega)
rw [h_eq] at h
rw [h_abs_ab] at h
have hb_sub_a_le : b.toInt - a.toInt ≤ 2147483647 := by
unfold q16Clamp at h
omega
have h_eq2 := q16Clamp_id_of_inRange (b.toInt - a.toInt) (by omega) hb_sub_a_le
rw [h_eq2] at h
exact h
· have h_clamp : q16Clamp (a.toInt - b.toInt) = -2147483648 := by
unfold q16Clamp; rw [h_min, h_max]; split_ifs <;> omega
rw [h_clamp] at h
have h_abs_neg : |(-2147483648 : Int)| = 2147483648 := rfl
rw [h_abs_neg] at h
have h_clamp2 : q16Clamp 2147483648 = 2147483647 := by
unfold q16Clamp; rw [h_min, h_max]; split_ifs <;> omega
rw [h_clamp2] at h
omega
theorem aciPreservedByMlgruStep {N : Nat} (H : BettiSwooshH N)
(nodes : Fin N → ScalarNode)
(cT : Fin N → Q16_16)
(fT : Fin N → Q16_16)
(hForgetUniform : ∀ e ∈ H.complex.edges, fT e.1 = fT e.2)
(hCandidateACI : ∀ e ∈ H.complex.edges,
Q16_16.abs (cT e.2 - cT e.1) ≤ H.aciBound)
(hPrevACI : aciSatisfied H nodes)
(h_aciBound_nonneg : H.aciBound.toInt ≥ 0)
(h_aciBound_lt_max : H.aciBound.toInt < FixedPoint.q16MaxRaw - 2)
(h_ft_range : ∀ i, (fT i).toInt ≥ 0 ∧ (fT i).toInt ≤ FixedPoint.q16Scale) :
aciSatisfied { H with aciBound := H.aciBound + Q16_16.ofRawInt 2 } (fun i =>
let st := mlgruStep (fT i) (cT i) (nodes i).hidden
{ (nodes i) with hidden := st }) := by
intro e he
let i := e.1
let j := e.2
have hij : fT i = fT j := hForgetUniform e he
have hprev : Q16_16.abs ((nodes i).hidden.hT - (nodes j).hidden.hT) ≤ H.aciBound := by
have h' : Q16_16.abs ((nodes i).hidden.hT - (nodes j).hidden.hT) = Q16_16.abs ((nodes j).hidden.hT - (nodes i).hidden.hT) := by
calc
Q16_16.abs ((nodes i).hidden.hT - (nodes j).hidden.hT)
= Q16_16.abs (Q16_16.sub ((nodes i).hidden.hT) ((nodes j).hidden.hT)) := rfl
_ = Q16_16.abs (Q16_16.sub ((nodes j).hidden.hT) ((nodes i).hidden.hT)) := by
rw [Semantics.FixedPoint.Q16_16.abs_sub_comm]
_ = Q16_16.abs ((nodes j).hidden.hT - (nodes i).hidden.hT) := rfl
rw [h']
exact hPrevACI e he
have hcand : Q16_16.abs (cT i - cT j) ≤ H.aciBound := by
calc
Q16_16.abs (cT i - cT j) = Q16_16.abs (Q16_16.sub (cT i) (cT j)) := rfl
_ = Q16_16.abs (Q16_16.sub (cT j) (cT i)) := by rw [Semantics.FixedPoint.Q16_16.abs_sub_comm]
_ = Q16_16.abs (cT j - cT i) := rfl
_ ≤ H.aciBound := hCandidateACI e he
have hft_range := h_ft_range i
have f_nonneg : (fT i).toInt ≥ 0 := hft_range.1
have ft_le : (fT i).toInt ≤ FixedPoint.q16Scale := hft_range.2
have omf_toInt : (Q16_16.one - fT i).toInt = FixedPoint.q16Scale - (fT i).toInt := by
calc
(Q16_16.one - fT i).toInt = (Q16_16.sub Q16_16.one (fT i)).toInt := rfl
_ = (Q16_16.ofRawInt (Q16_16.one.toInt - (fT i).toInt)).toInt := rfl
_ = FixedPoint.q16Scale - (fT i).toInt := by
calc
(Q16_16.ofRawInt (Q16_16.one.toInt - (fT i).toInt)).toInt
= FixedPoint.q16Clamp (Q16_16.one.toInt - (fT i).toInt) := by
dsimp [Q16_16.toInt]; rw [Semantics.FixedPoint.Q16_16.ofRawInt_val_eq_q16Clamp]
_ = FixedPoint.q16Clamp (FixedPoint.q16Scale - (fT i).toInt) := by
dsimp [Q16_16.one, Q16_16.toInt, FixedPoint.q16Scale]
_ = FixedPoint.q16Scale - (fT i).toInt := by
have h_range : FixedPoint.q16MinRaw ≤ (FixedPoint.q16Scale - (fT i).toInt) ∧
(FixedPoint.q16Scale - (fT i).toInt) ≤ FixedPoint.q16MaxRaw := by
constructor
· -- q16MinRaw ≤ q16Scale - (fT i).toInt
have : FixedPoint.q16Scale - (fT i).toInt ≥ 0 := by
nlinarith [ft_le]
calc
FixedPoint.q16MinRaw = -2147483648 := rfl
_ ≤ 0 := by norm_num
_ ≤ FixedPoint.q16Scale - (fT i).toInt := this
· -- q16Scale - (fT i).toInt ≤ q16MaxRaw
have : FixedPoint.q16Scale - (fT i).toInt ≤ FixedPoint.q16Scale := by omega
calc
FixedPoint.q16Scale - (fT i).toInt ≤ FixedPoint.q16Scale := this
_ = 65536 := rfl
_ ≤ 2147483647 := by norm_num
_ = FixedPoint.q16MaxRaw := rfl
exact FixedPoint.q16Clamp_id_of_inRange (FixedPoint.q16Scale - (fT i).toInt) h_range.1 h_range.2
have omfnn : (Q16_16.one - fT i).toInt ≥ 0 := by
rw [omf_toInt]; omega
have omf_le : (Q16_16.one - fT i).toInt ≤ FixedPoint.q16Scale := by
rw [omf_toInt]; omega
have hprev_val : |(nodes i).hidden.hT.toInt - (nodes j).hidden.hT.toInt| ≤ H.aciBound.toInt :=
abs_sub_val_le _ _ _ (by omega) hprev
have hcand_val : |(cT i).toInt - (cT j).toInt| ≤ H.aciBound.toInt :=
abs_sub_val_le _ _ _ (by omega) hcand
let termA_i := Q16_16.mul (fT i) (nodes i).hidden.hT
let termB_i := Q16_16.mul (Q16_16.one - fT i) (cT i)
let termA_j := Q16_16.mul (fT j) (nodes j).hidden.hT
let termB_j := Q16_16.mul (Q16_16.one - fT j) (cT j)
have h_termA_i : termA_i.toInt = (fT i).toInt * (nodes i).hidden.hT.toInt / 65536 :=
mul_eq_for_bounded _ _ f_nonneg ft_le
have h_termA_j : termA_j.toInt = (fT i).toInt * (nodes j).hidden.hT.toInt / 65536 := by
show (Q16_16.mul (fT j) (nodes j).hidden.hT).toInt = (fT i).toInt * (nodes j).hidden.hT.toInt / 65536
rw [← hij]
exact mul_eq_for_bounded (fT i) (nodes j).hidden.hT f_nonneg ft_le
have h_termB_i : termB_i.toInt = (Q16_16.one - fT i).toInt * (cT i).toInt / 65536 :=
mul_eq_for_bounded _ _ omfnn omf_le
have h_termB_j : termB_j.toInt = (Q16_16.one - fT i).toInt * (cT j).toInt / 65536 := by
show (Q16_16.mul (Q16_16.one - fT j) (cT j)).toInt = (Q16_16.one - fT i).toInt * (cT j).toInt / 65536
rw [← hij]
exact mul_eq_for_bounded (Q16_16.one - fT i) (cT j) omfnn omf_le
have h_hT_i_new : (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt = q16Clamp (termA_i.toInt + termB_i.toInt) := by
change (ofRawInt (termA_i.toInt + termB_i.toInt)).toInt = q16Clamp (termA_i.toInt + termB_i.toInt)
rw [ofRawInt_toInt_eq_clamp]
have h_hT_j_new : (mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt = q16Clamp (termA_j.toInt + termB_j.toInt) := by
change (ofRawInt (termA_j.toInt + termB_j.toInt)).toInt = q16Clamp (termA_j.toInt + termB_j.toInt)
rw [ofRawInt_toInt_eq_clamp]
have h_new_bound : (H.aciBound + Q16_16.ofRawInt 2).toInt = H.aciBound.toInt + 2 := by
change (add H.aciBound (Q16_16.ofRawInt 2)).toInt = H.aciBound.toInt + 2
unfold add
rw [ofRawInt_toInt_eq_clamp]
have h_two_toInt : (Q16_16.ofRawInt 2).toInt = 2 := by
dsimp [Q16_16.toInt]
rw [Semantics.FixedPoint.Q16_16.ofRawInt_val_eq_q16Clamp]
unfold q16Clamp q16MinRaw q16MaxRaw
split_ifs <;> omega
rw [h_two_toInt]
unfold q16Clamp q16MinRaw q16MaxRaw at *
split_ifs <;> omega
change (Q16_16.abs ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT)).toInt ≤ (H.aciBound + Q16_16.ofRawInt 2).toInt
rw [h_new_bound]
have h_sub_val : ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT).toInt = q16Clamp ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt) := by
change (sub ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT) ((mlgruStep (fT i) (cT i) (nodes i).hidden).hT)).toInt = q16Clamp ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt)
unfold sub
rw [ofRawInt_toInt_eq_clamp]
rw [abs_toInt_eq_clamp_abs]
rw [h_sub_val]
have h_clamp_zero : q16Clamp 0 = 0 := by rfl
have h_lipschitz_zero : |q16Clamp ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt)| ≤ |(mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt| := by
have h_lip := q16Clamp_lipschitz ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt) 0
rw [h_clamp_zero] at h_lip
rw [sub_zero, sub_zero] at h_lip
exact h_lip
have h_mono := q16Clamp_monotone |q16Clamp ((mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt)| |(mlgruStep (fT j) (cT j) (nodes j).hidden).hT.toInt - (mlgruStep (fT i) (cT i) (nodes i).hidden).hT.toInt| h_lipschitz_zero
apply le_trans h_mono
rw [h_hT_i_new, h_hT_j_new]
have h_lipschitz := q16Clamp_lipschitz (termA_j.toInt + termB_j.toInt) (termA_i.toInt + termB_i.toInt)
have h_mono_lip := q16Clamp_monotone |q16Clamp (termA_j.toInt + termB_j.toInt) - q16Clamp (termA_i.toInt + termB_i.toInt)| |(termA_j.toInt + termB_j.toInt) - (termA_i.toInt + termB_i.toInt)| h_lipschitz
apply le_trans h_mono_lip
have h_triangle : |(termA_j.toInt + termB_j.toInt) - (termA_i.toInt + termB_i.toInt)| ≤ |termA_j.toInt - termA_i.toInt| + |termB_j.toInt - termB_i.toInt| := by
have : (termA_j.toInt + termB_j.toInt) - (termA_i.toInt + termB_i.toInt) = (termA_j.toInt - termA_i.toInt) + (termB_j.toInt - termB_i.toInt) := by omega
rw [this]
exact abs_add_le (termA_j.toInt - termA_i.toInt) (termB_j.toInt - termB_i.toInt)
apply le_trans (q16Clamp_monotone _ _ h_triangle)
have h_termA_le : (fT i).toInt * |(nodes i).hidden.hT.toInt - (nodes j).hidden.hT.toInt| ≤ (fT i).toInt * H.aciBound.toInt :=
mul_le_mul_of_nonneg_left hprev_val f_nonneg
have h_termB_le : (Q16_16.one - fT i).toInt * |(cT i).toInt - (cT j).toInt| ≤ (Q16_16.one - fT i).toInt * H.aciBound.toInt :=
mul_le_mul_of_nonneg_left hcand_val omfnn
have h_divA : ((fT i).toInt * |(nodes j).hidden.hT.toInt - (nodes i).hidden.hT.toInt|) / 65536 ≤ ((fT i).toInt * H.aciBound.toInt) / 65536 := by
have h_abs_comm : |(nodes j).hidden.hT.toInt - (nodes i).hidden.hT.toInt| = |(nodes i).hidden.hT.toInt - (nodes j).hidden.hT.toInt| := by
exact abs_sub_comm (nodes j).hidden.hT.toInt (nodes i).hidden.hT.toInt
rw [h_abs_comm]
exact Int.ediv_le_ediv (by decide) h_termA_le
have h_divB : ((Q16_16.one - fT i).toInt * |(cT j).toInt - (cT i).toInt|) / 65536 ≤ ((Q16_16.one - fT i).toInt * H.aciBound.toInt) / 65536 := by
have h_abs_comm : |(cT j).toInt - (cT i).toInt| = |(cT i).toInt - (cT j).toInt| := by
exact abs_sub_comm (cT j).toInt (cT i).toInt
rw [h_abs_comm]
exact Int.ediv_le_ediv (by decide) h_termB_le
have h_edivA : |termA_j.toInt - termA_i.toInt| ≤ ((fT i).toInt * |(nodes j).hidden.hT.toInt - (nodes i).hidden.hT.toInt|) / 65536 + 1 := by
rw [h_termA_i, h_termA_j]
have := ediv_add_bound ((fT i).toInt * (nodes i).hidden.hT.toInt) ((fT i).toInt * (nodes j).hidden.hT.toInt - (fT i).toInt * (nodes i).hidden.hT.toInt)
have h_rew : (fT i).toInt * (nodes i).hidden.hT.toInt + ((fT i).toInt * (nodes j).hidden.hT.toInt - (fT i).toInt * (nodes i).hidden.hT.toInt) = (fT i).toInt * (nodes j).hidden.hT.toInt := by omega
rw [h_rew] at this
have h_prod : |(fT i).toInt * (nodes j).hidden.hT.toInt - (fT i).toInt * (nodes i).hidden.hT.toInt| = (fT i).toInt * |(nodes j).hidden.hT.toInt - (nodes i).hidden.hT.toInt| := by
have h_factor : (fT i).toInt * (nodes j).hidden.hT.toInt - (fT i).toInt * (nodes i).hidden.hT.toInt = (fT i).toInt * ((nodes j).hidden.hT.toInt - (nodes i).hidden.hT.toInt) := by ring
rw [h_factor]
rw [abs_mul]
have h_f_abs : |((fT i).toInt : Int)| = (fT i).toInt := abs_of_nonneg f_nonneg
rw [h_f_abs]
rw [h_prod] at this
exact this
have h_edivB : |termB_j.toInt - termB_i.toInt| ≤ ((Q16_16.one - fT i).toInt * |(cT j).toInt - (cT i).toInt|) / 65536 + 1 := by
rw [h_termB_i, h_termB_j]
have := ediv_add_bound ((Q16_16.one - fT i).toInt * (cT i).toInt) ((Q16_16.one - fT i).toInt * (cT j).toInt - (Q16_16.one - fT i).toInt * (cT i).toInt)
have h_rew : (Q16_16.one - fT i).toInt * (cT i).toInt + ((Q16_16.one - fT i).toInt * (cT j).toInt - (Q16_16.one - fT i).toInt * (cT i).toInt) = (Q16_16.one - fT i).toInt * (cT j).toInt := by omega
rw [h_rew] at this
have h_prod : |(Q16_16.one - fT i).toInt * (cT j).toInt - (Q16_16.one - fT i).toInt * (cT i).toInt| = (Q16_16.one - fT i).toInt * |(cT j).toInt - (cT i).toInt| := by
have h_factor : (Q16_16.one - fT i).toInt * (cT j).toInt - (Q16_16.one - fT i).toInt * (cT i).toInt = (Q16_16.one - fT i).toInt * ((cT j).toInt - (cT i).toInt) := by ring
rw [h_factor]
rw [abs_mul]
have h_omf_abs : |((Q16_16.one - fT i).toInt : Int)| = (Q16_16.one - fT i).toInt := abs_of_nonneg omfnn
rw [h_omf_abs]
rw [h_prod] at this
exact this
have h_X_nonneg : 0 ≤ (fT i).toInt * H.aciBound.toInt := mul_nonneg f_nonneg h_aciBound_nonneg
have h_X_le : (fT i).toInt * H.aciBound.toInt ≤ 65536 * H.aciBound.toInt := by
exact mul_le_mul_of_nonneg_right ft_le h_aciBound_nonneg
have h_sum : (fT i).toInt * H.aciBound.toInt + (Q16_16.one - fT i).toInt * H.aciBound.toInt = 65536 * H.aciBound.toInt := by
rw [omf_toInt]
have h_scale : FixedPoint.q16Scale = 65536 := by rfl
rw [h_scale]
ring
unfold q16Clamp q16MinRaw q16MaxRaw at *
omega
-- ════════════════════════════════════════════════════════════
-- §11 SRAM Banking Layout
-- Bank b contains scalars i where i mod B = b.
-- B = k_active ensures conflict-free parallel ternary scan.
-- ════════════════════════════════════════════════════════════
/-- Bank assignment function. -/
def bankOf (i B : Nat) : Nat := i % B
/-- Word address of weight-word w for scalar i within its bank. -/
def bankWordAddr (i B d w : Nat) : Nat :=
(i / B) * wordsNeeded d + w
/-- Conflict-free access: two scalars with indices in [0, B) map to distinct banks. -/
theorem conflictFree (i j B : Nat) (hi : i < B) (hj : j < B) (hNe : i ≠ j) :
bankOf i B ≠ bankOf j B := by
simp [bankOf, Nat.mod_eq_of_lt hi, Nat.mod_eq_of_lt hj]
exact hNe
/-- SRAM layout descriptor. -/
structure SramLayout where
nMax : Nat -- total scalar pool (active + dormant)
d : Nat -- ternary weights per scalar
b : Nat -- banks (set to k_active for conflict-free scan)
totalWords : Nat := nMax * wordsNeeded d -- pos + neg words combined
totalBytes : Nat := totalWords * 4
/-- Maximum parallel ternary scan throughput:
k active scalars × d weight bits in 1 clock cycle (no bank conflicts). -/
def parallelThroughput (l : SramLayout) (k : Nat) ( _hk : k ≤ l.b) : Nat :=
k * l.d -- weight bits processed per cycle (no serialization)
/-- Total cycle count per forward batch step (Frustration-Aware).
Incorporates the Phantom Tide velocity modifier λ = 0.7 for signal dampening. -/
def totalCycles (k d : Nat) (t : Semantics.Timing.ManifoldTiming) (v : Q16_16) : Nat :=
let tclCycles := (t.tcl.toInt / 65536).toNat
let nrCycles := 30 -- TODO: Make adaptive based on v
let butterfly := 2 * (Nat.log2 k + 1)
-- Velocity-weighted correction (λ=0.7 ≈ 0.7 * 65536 = 45875)
let vModInt := (65536 - (45875 * v.toInt / 65536))
let vMod := if vModInt < 0 then 0 else vModInt.toNat
butterfly -- η-max butterfly (MAX reduction)
+ nrCycles -- Newton-Raphson α = qB/(η+ε)
+ 8 -- scale + clip (BitLinear)
+ (d * vMod / 65536) -- Ternary dot product (Phantom-weighted)
+ tclCycles -- FAMM Dynamic CAS Latency
+ butterfly -- butterfly SUM (pipelined)
+ 18 -- MLGRU recurrence
+ 13 -- backward + spawn/fold check
-- ════════════════════════════════════════════════════════════
-- §12 Manifold Metric (L1 contraction-based stability)
--
-- Uses L1 Manhattan distance on SSMS state variables (hT, cT, fT, Λ)
-- to prove the SSMS step is non-expansive under omega.
-- This absorbs the +2 floor-division error into the Λ coordinate
-- as a governed state variable under contraction L ≤ 1.
-- ════════════════════════════════════════════════════════════
/-- SSMS state: hidden state, candidate, forget gate, residual.
The residual Λ captures floor-division rounding as a governed coordinate. -/
structure SSMSState where
hT : Q16_16 -- hidden state (MLGRU)
cT : Q16_16 -- candidate state
fT : Q16_16 -- forget gate
Λ : Q16_16 -- residual / truncation coordinate
deriving Repr
/-- L1 Manhattan metric on SSMS state space.
Using L1 avoids sqrt/sqr formalization and stays in Int arithmetic
where omega can handle it directly. -/
def ssmsMetric (X Y : SSMSState) : Int :=
|X.hT.toInt - Y.hT.toInt| + |X.cT.toInt - Y.cT.toInt| +
|X.fT.toInt - Y.fT.toInt| + |X.Λ.toInt - Y.Λ.toInt|
/-- Sup (L∞ / Chebyshev) metric on SSMS state space: the maximum of the four
coordinate-wise |differences|. A convex combination is non-expansive in the
sup norm, which is the metric in which the MLGRU blend step is (almost)
non-expansive — see `ssms_step_nonexpansive`. -/
def ssmsMetricInf (X Y : SSMSState) : Int :=
max (max |X.hT.toInt - Y.hT.toInt| |X.cT.toInt - Y.cT.toInt|)
(max |X.fT.toInt - Y.fT.toInt| |X.Λ.toInt - Y.Λ.toInt|)
/-- SSMS transition step on the manifold.
hT' = f·hT + (1-f)·cT (MLGRU convex combination)
cT' = cT (candidate unchanged)
fT' = fT (forget gate unchanged)
Λ' = Λ - Λ = 0 (residual contracts to zero) -/
def ssmsStep (s : SSMSState) : SSMSState :=
{ s with
hT := add (mul s.fT s.hT) (mul (sub one s.fT) s.cT)
, Λ := zero
}
/-- Core arithmetic bound for the MLGRU blend `f·h + (1-f)·c` in Q16.16:
for a shared in-range gate `f` and coordinate-wise bound `B` on the inputs,
the blended outputs differ by at most `B + 2`. The `+2` is exactly two
floor-division ULPs (one per `Q16_16.mul`, via `ediv_add_bound`); the outer
`add`-clamp only shrinks distances (`q16Clamp_lipschitz`). -/
private lemma mlgru_blend_diff_le (f hX cX hY cY : Q16_16) (B : Int)
(hf0 : 0 ≤ f.toInt) (hf1 : f.toInt ≤ q16Scale) (hB : 0 ≤ B)
(hh : |hX.toInt - hY.toInt| ≤ B) (hc : |cX.toInt - cY.toInt| ≤ B) :
|(add (mul f hX) (mul (sub one f) cX)).toInt -
(add (mul f hY) (mul (sub one f) cY)).toInt| ≤ B + 2 := by
have h_min : q16MinRaw = -2147483648 := rfl
have h_max : q16MaxRaw = 2147483647 := rfl
have h_scale : q16Scale = 65536 := rfl
-- the complementary gate 1 - f is exact and in [0, q16Scale]
have omf_toInt : (sub one f).toInt = q16Scale - f.toInt := by
unfold sub
rw [ofRawInt_toInt_eq_clamp]
have h_one : one.toInt = q16Scale := rfl
rw [h_one]
exact q16Clamp_id_of_inRange _ (by omega) (by omega)
have omf0 : 0 ≤ (sub one f).toInt := by omega
have omf1 : (sub one f).toInt ≤ q16Scale := by omega
-- the four products are exact floor divisions (no clamping)
have h_tAX : (mul f hX).toInt = f.toInt * hX.toInt / 65536 :=
mul_eq_for_bounded f hX hf0 hf1
have h_tAY : (mul f hY).toInt = f.toInt * hY.toInt / 65536 :=
mul_eq_for_bounded f hY hf0 hf1
have h_tBX : (mul (sub one f) cX).toInt = (sub one f).toInt * cX.toInt / 65536 :=
mul_eq_for_bounded _ cX omf0 omf1
have h_tBY : (mul (sub one f) cY).toInt = (sub one f).toInt * cY.toInt / 65536 :=
mul_eq_for_bounded _ cY omf0 omf1
-- the outer add is a clamp of the sum
have h_addX : (add (mul f hX) (mul (sub one f) cX)).toInt =
q16Clamp ((mul f hX).toInt + (mul (sub one f) cX).toInt) := by
unfold add; rw [ofRawInt_toInt_eq_clamp]
have h_addY : (add (mul f hY) (mul (sub one f) cY)).toInt =
q16Clamp ((mul f hY).toInt + (mul (sub one f) cY).toInt) := by
unfold add; rw [ofRawInt_toInt_eq_clamp]
rw [h_addX, h_addY]
refine le_trans (q16Clamp_lipschitz _ _) ?_
rw [h_tAX, h_tAY, h_tBX, h_tBY]
-- triangle inequality across the two product differences
have h_tri : |f.toInt * hX.toInt / 65536 + (sub one f).toInt * cX.toInt / 65536 -
(f.toInt * hY.toInt / 65536 + (sub one f).toInt * cY.toInt / 65536)| ≤
|f.toInt * hX.toInt / 65536 - f.toInt * hY.toInt / 65536| +
|(sub one f).toInt * cX.toInt / 65536 - (sub one f).toInt * cY.toInt / 65536| := by
have h_eq : f.toInt * hX.toInt / 65536 + (sub one f).toInt * cX.toInt / 65536 -
(f.toInt * hY.toInt / 65536 + (sub one f).toInt * cY.toInt / 65536) =
(f.toInt * hX.toInt / 65536 - f.toInt * hY.toInt / 65536) +
((sub one f).toInt * cX.toInt / 65536 - (sub one f).toInt * cY.toInt / 65536) := by
omega
rw [h_eq]
exact abs_add_le _ _
refine le_trans h_tri ?_
-- each product difference: ≤ 1 ULP over the exact scaled difference
have h_edivA : |f.toInt * hX.toInt / 65536 - f.toInt * hY.toInt / 65536| ≤
f.toInt * |hX.toInt - hY.toInt| / 65536 + 1 := by
have h := ediv_add_bound (f.toInt * hY.toInt)
(f.toInt * hX.toInt - f.toInt * hY.toInt)
have h_rew : f.toInt * hY.toInt + (f.toInt * hX.toInt - f.toInt * hY.toInt) =
f.toInt * hX.toInt := by omega
rw [h_rew] at h
have h_fac : |f.toInt * hX.toInt - f.toInt * hY.toInt| =
f.toInt * |hX.toInt - hY.toInt| := by
rw [show f.toInt * hX.toInt - f.toInt * hY.toInt =
f.toInt * (hX.toInt - hY.toInt) by ring, abs_mul, abs_of_nonneg hf0]
rw [h_fac] at h
exact h
have h_edivB : |(sub one f).toInt * cX.toInt / 65536 -
(sub one f).toInt * cY.toInt / 65536| ≤
(sub one f).toInt * |cX.toInt - cY.toInt| / 65536 + 1 := by
have h := ediv_add_bound ((sub one f).toInt * cY.toInt)
((sub one f).toInt * cX.toInt - (sub one f).toInt * cY.toInt)
have h_rew : (sub one f).toInt * cY.toInt +
((sub one f).toInt * cX.toInt - (sub one f).toInt * cY.toInt) =
(sub one f).toInt * cX.toInt := by omega
rw [h_rew] at h
have h_fac : |(sub one f).toInt * cX.toInt - (sub one f).toInt * cY.toInt| =
(sub one f).toInt * |cX.toInt - cY.toInt| := by
rw [show (sub one f).toInt * cX.toInt - (sub one f).toInt * cY.toInt =
(sub one f).toInt * (cX.toInt - cY.toInt) by ring, abs_mul, abs_of_nonneg omf0]
rw [h_fac] at h
exact h
-- monotone division: replace |Δ| by the bound B
have h_divA : f.toInt * |hX.toInt - hY.toInt| / 65536 ≤ f.toInt * B / 65536 :=
Int.ediv_le_ediv (by norm_num) (mul_le_mul_of_nonneg_left hh hf0)
have h_divB : (sub one f).toInt * |cX.toInt - cY.toInt| / 65536 ≤
(sub one f).toInt * B / 65536 :=
Int.ediv_le_ediv (by norm_num) (mul_le_mul_of_nonneg_left hc omf0)
-- the convex weights sum to the scale: f·B + (1-f)·B = 65536·B
have h_sum : f.toInt * B + (sub one f).toInt * B = 65536 * B := by
rw [omf_toInt, h_scale]; ring
have h_fB_nonneg : 0 ≤ f.toInt * B := mul_nonneg hf0 hB
have h_omfB_nonneg : 0 ≤ (sub one f).toInt * B := mul_nonneg omf0 hB
omega
/-- The SSMS step is non-expansive (up to fixed-point truncation) in the sup
(L∞) metric, for a shared in-range forget gate:
`ssmsMetricInf (ssmsStep X) (ssmsStep Y) ≤ ssmsMetricInf X Y + 2`.
**Classical grounding.** A convex combination `f·h + (1-f)·c` with weights
`f, 1-f ∈ [0,1]` is non-expansive in the sup norm (standard: a convex
combination of points lies in the sup-ball spanned by the coordinate-wise
bounds). The `+2` is Q16.16 truncation slack made explicit: each
`Q16_16.mul` floor-divides by `q16Scale` (≤ 1 ULP error per product,
`ediv_add_bound`), and the MLGRU blend has two products. The outer
saturating `add` only shrinks distances (`q16Clamp_lipschitz`). This is the
same bound proved in ACI form by `aciPreservedByMlgruStep`.
**Why the original statement failed.** The original claimed L1
non-expansiveness `ssmsMetric (ssmsStep X) (ssmsStep Y) ≤ ssmsMetric X Y`
with no slack and no gate hypothesis. Verified counterexample (raw Q16.16):
`X = {hT := 1, cT := 1, fT := 0, Λ := 0}`,
`Y = {hT := 1, cT := 1, fT := 32768, Λ := 0}` gives
`hT'(X) = ⌊0·1/2¹⁶⌋ + ⌊65536·1/2¹⁶⌋ = 1`, `hT'(Y) = ⌊32768·1/2¹⁶⌋·2 = 0`,
so the stepped L1 distance is `1 + 0 + 32768 + 0 = 32769 > 32768`.
Moreover "+2" slack alone cannot repair the L1 form: with differing gates
the cross-term `hY·ΔfT/Q` is unbounded in L1 (take `hX = hY` large,
`fX = 0`, `fY = Q`), and even with equal gates the `hT'` coordinate tracks
`|ΔcT|`, which the L1 RHS already spends on the `cT` coordinate (take
`ΔhT = 0`, `f = 0`, `ΔcT` large: LHS ≈ 2·|ΔcT| > |ΔcT| + 2). The correct
metric for this step is the sup metric, under an equal-gate hypothesis
(the `hForgetUniform` pattern used by `aciPreservedByMlgruStep`).
The `cT`/`fT` coordinates are unchanged by `ssmsStep`, the `fT` difference
is 0 by the equal-gate hypothesis, and `Λ' = 0` on both sides. -/
theorem ssms_step_nonexpansive (X Y : SSMSState)
(hf_eq : X.fT = Y.fT)
(hf_range : X.fT.toInt ≥ 0 ∧ X.fT.toInt ≤ q16Scale) :
ssmsMetricInf (ssmsStep X) (ssmsStep Y) ≤ ssmsMetricInf X Y + 2 := by
have hB_h : |X.hT.toInt - Y.hT.toInt| ≤ ssmsMetricInf X Y := by
unfold ssmsMetricInf
exact le_trans (le_max_left _ _) (le_max_left _ _)
have hB_c : |X.cT.toInt - Y.cT.toInt| ≤ ssmsMetricInf X Y := by
unfold ssmsMetricInf
exact le_trans (le_max_right _ _) (le_max_left _ _)
have hB_nonneg : 0 ≤ ssmsMetricInf X Y := le_trans (_root_.abs_nonneg _) hB_h
-- hidden coordinate: the MLGRU blend bound with the shared gate
have h1 : |(ssmsStep X).hT.toInt - (ssmsStep Y).hT.toInt| ≤
ssmsMetricInf X Y + 2 := by
show |(add (mul X.fT X.hT) (mul (sub one X.fT) X.cT)).toInt -
(add (mul Y.fT Y.hT) (mul (sub one Y.fT) Y.cT)).toInt| ≤ _
rw [← hf_eq]
exact mlgru_blend_diff_le X.fT X.hT X.cT Y.hT Y.cT (ssmsMetricInf X Y)
hf_range.1 hf_range.2 hB_nonneg hB_h hB_c
-- candidate coordinate: unchanged by the step
have h2 : |(ssmsStep X).cT.toInt - (ssmsStep Y).cT.toInt| ≤
ssmsMetricInf X Y + 2 := by
show |X.cT.toInt - Y.cT.toInt| ≤ _
omega
-- gate coordinate: equal gates, difference 0
have h3 : |(ssmsStep X).fT.toInt - (ssmsStep Y).fT.toInt| ≤
ssmsMetricInf X Y + 2 := by
show |X.fT.toInt - Y.fT.toInt| ≤ _
rw [hf_eq, _root_.sub_self, abs_zero]
omega
-- residual coordinate: contracts to 0 on both sides
have h4 : |(ssmsStep X).Λ.toInt - (ssmsStep Y).Λ.toInt| ≤
ssmsMetricInf X Y + 2 := by
show |Q16_16.zero.toInt - Q16_16.zero.toInt| ≤ _
rw [_root_.sub_self, abs_zero]
omega
unfold ssmsMetricInf
exact max_le (max_le h1 h2) (max_le h3 h4)
/-- ACI preservation under MLGRU step, reframed as L1 contraction.
Concrete test at ε=2 passes (#eval at lines 536-539).
The conclusion carries the "+2" floor-division rounding slack: each
Q16_16.mul truncates (Int floor division by q16Scale), contributing up
to +1 per product, and the MLGRU blend has two products. The original
same-bound claim (aciSatisfied H, no slack) is FALSE — verified
counterexample on testComplex (edge (0,1)): aciBound = 1, fT ≡ 32768
(= 0.5, uniform), hT = cT = (1, 2); all hypotheses hold, yet the stepped
hidden states are ⌊32768·1/2¹⁶⌋·2 = 0 and ⌊32768·2/2¹⁶⌋·2 = 2, so
|Δh'| = 2 > 1. With the slack, this is exactly aciPreservedByMlgruStep. -/
theorem ssms_contraction_theorem {N : Nat} (H : BettiSwooshH N)
(nodes : Fin N → ScalarNode) (cT : Fin N → Q16_16) (fT : Fin N → Q16_16)
(hForgetUniform : ∀ e ∈ H.complex.edges, fT e.1 = fT e.2)
(hCandidateACI : ∀ e ∈ H.complex.edges, Q16_16.abs (cT e.2 - cT e.1) ≤ H.aciBound)
(hPrevACI : aciSatisfied H nodes)
(h_aciBound_nonneg : H.aciBound.toInt ≥ 0)
(h_aciBound_lt_max : H.aciBound.toInt < FixedPoint.q16MaxRaw - 2)
(h_ft_range : ∀ i, (fT i).toInt ≥ 0 ∧ (fT i).toInt ≤ FixedPoint.q16Scale) :
aciSatisfied { H with aciBound := H.aciBound + Q16_16.ofRawInt 2 } (fun i =>
let st := mlgruStep (fT i) (cT i) (nodes i).hidden
{ (nodes i) with hidden := st }) :=
aciPreservedByMlgruStep H nodes cT fT hForgetUniform hCandidateACI hPrevACI
h_aciBound_nonneg h_aciBound_lt_max h_ft_range
end Semantics.SSMS