Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
View File
+984
View File
@@ -0,0 +1,984 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- ============================================================================
local C = require("md_solver_commons")
-- MD Confluence V4 -- STORM / Trajectory Anchor hybrid solver
-- MDMAchine | A&E Concepts (c) 2026
--
-- V4: Commons integration + relational velocity decomposition.
-- Tonal ramp, look-back floor, RMS default on, anchor_blend 0.12.
--
-- owns_loop = true. Forks STORM's stiffness-gated multi-order dispatch AND
-- Trajectory Anchor's full 13-stage stateful correction stack into one loop,
-- blending their two x_next candidates per step via disagreement- and
-- inertia-modulated mixing.
--
-- CANDIDATE MODEL:
-- Both candidates are x_next (post-advance latents), NOT vt.
-- v_curr is computed ONCE per step and shared by both candidates.
--
-- STATE-FEEDBACK RULE:
-- STORM's v_cache stores velocity (v_curr, shared) -- no desync possible.
-- Anchor's latent state (_anc_prev_out, _anc_history) is OVERWRITTEN with
-- x_final (the blended result) so its memory/inertia/concept-lock math
-- believes the blended trajectory is what happened. One-shot references
-- (identity anchor snapshot, tonal anchor capture) fire against x_final too
-- since they read whatever the actual trajectory is at anchor_sigma.
-- ============================================================================
solver = {
name = "md_confluence_v4",
display = "MD Confluence V4",
description = "STORM / Trajectory Anchor hybrid with batch-aware routing. Disagreement- and inertia-modulated latent blend. Per-batch tonal anchor, spectral guard, and RMS servo.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
owns_loop = true,
params = {
-- ── Mix ──────────────────────────────────────────────────────────────
{ key = "mix_amount", type = "slider", label = "Mix Amount",
default = 50, min = 0, max = 100, step = 1,
hint = "Base blend: 0 = pure STORM, 100 = pure Trajectory Anchor. Modulated at runtime by disagreement mode and inertia gating -- effective mix moves around this value, not on it." },
{ key = "disagreement_mode", type = "select", label = "Disagreement Mode",
default = "adaptive",
options = {
{ value = "damp", label = "Damp (consensus)" },
{ value = "amplify", label = "Amplify (instability)" },
{ value = "adaptive", label = "Adaptive (damp early, amplify late)" },
},
hint = "How blend reacts when STORM and Anchor candidates disagree. Damp = pull toward consensus. Amplify = disagreement becomes controlled texture. Adaptive = damp during structure, amplify during detail." },
{ key = "damp_strength", type = "slider", label = "Damp Strength",
default = 0.4, min = 0, max = 1, step = 0.05,
hint = "How hard disagreement pulls mix toward consensus (damp/adaptive mode)." },
{ key = "chaos_strength", type = "slider", label = "Chaos Strength",
default = 0.3, min = 0, max = 1, step = 0.05,
hint = "How hard disagreement pushes mix further from center (amplify/adaptive mode)." },
{ key = "inertia_influence", type = "slider", label = "Inertia Influence",
default = 0.7, min = 0, max = 1, step = 0.05,
hint = "How much Anchor's inertia state gates the mix. 0 = pure user mix. 1 = full auto-gating (low inertia collapses toward STORM)." },
{ key = "inertia_gate_low", type = "slider", label = "Inertia Gate Low",
default = 0.15, min = 0, max = 1, step = 0.01,
hint = "Smoothstep floor: inertia magnitude below this = mix fully gated toward STORM." },
{ key = "inertia_gate_high", type = "slider", label = "Inertia Gate High",
default = 0.6, min = 0, max = 1, step = 0.01,
hint = "Smoothstep ceiling: inertia magnitude above this = user's stated mix takes over fully." },
-- ── STORM params ──────────────────────────────────────────────────
{ key = "stiffness_threshold", type = "slider", label = "STORM: Detail Sensitivity",
default = 0.15, min = 0.05, max = 0.50, step = 0.01,
hint = "Stiffness threshold. Lower = more careful on transients." },
{ key = "rk_order", type = "select", label = "STORM: Precision Level",
default = "auto",
options = {
{ value = "auto", label = "Auto" },
{ value = "2", label = "RK2" }, { value = "3", label = "RK3" },
{ value = "4", label = "RK4" }, { value = "5", label = "RK5" },
},
hint = "STORK solver order when stiff." },
{ key = "cache_depth", type = "slider", label = "STORM: History Memory",
default = 5, min = 2, max = 10, step = 1,
hint = "Velocity cache depth for STORM's multi-order dispatch." },
{ key = "look_back_lambda_storm", type = "slider", label = "STORM: Look-Back Lambda",
default = 0.15, min = 0, max = 1, step = 0.01,
hint = "STORM's own look-back smoother weight. 0 = off." },
{ key = "look_back_snr_power_storm", type = "slider", label = "STORM: Look-Back SNR Power",
default = 1.5, min = 0.5, max = 3, step = 0.1,
hint = "STORM look-back falloff exponent." },
-- ── Anchor params ──────────────────────────────────────────────────
{ key = "warmup_steps", type = "slider", label = "Anchor: Warmup Steps",
default = 2, min = 0, max = 6, step = 1,
hint = "Skip Anchor stateful features for first N steps. Also gates inertia toward 0 during warmup." },
{ key = "inertia_alpha", type = "slider", label = "Anchor: Inertia Alpha",
default = 0.15, min = 0.0, max = 0.5, step = 0.01,
hint = "Anchor velocity carry-over coefficient. Entropy-modulated at runtime." },
{ key = "memory_blend", type = "slider", label = "Anchor: Memory Blend",
default = 0.12, min = 0.0, max = 0.5, step = 0.01,
hint = "3-step ring buffer blend fraction." },
{ key = "concept_lock", type = "toggle", label = "Anchor: Concept Lock",
default = true, hint = "Stability mask on settled regions." },
{ key = "concept_sigma_power", type = "slider", label = "Anchor: Concept Sigma Power",
default = 1.0, min = 0.25, max = 3.0, step = 0.25,
hint = "Concept lock fade curve across sigma." },
{ key = "identity_anchor", type = "toggle", label = "Anchor: Identity Anchor",
default = false, hint = "Snapshot pull-back at anchor_sigma." },
{ key = "anchor_sigma", type = "slider", label = "Anchor: Anchor Sigma",
default = 0.5, min = 0.1, max = 0.9, step = 0.05,
hint = "Sigma fraction for identity/tonal anchor capture." },
{ key = "anchor_blend", type = "slider", label = "Anchor: Anchor Blend",
default = 0.08, min = 0.01, max = 0.30, step = 0.01,
hint = "Pull strength toward identity anchor." },
{ key = "tonal_anchor", type = "toggle", label = "Anchor: Tonal Anchor",
default = true, hint = "Spectral centroid drift correction." },
{ key = "tonal_strength", type = "slider", label = "Anchor: Tonal Strength",
default = 0.15, min = 0.0, max = 1.0, step = 0.05,
hint = "Tonal correction scale (hard-capped 0.1%/step regardless)." },
{ key = "look_back_enabled_anchor", type = "toggle", label = "Anchor: Look-Back Smoother",
default = false, hint = "SNR-adaptive latent EMA." },
{ key = "look_back_lambda_anchor", type = "slider", label = "Anchor: Look-Back Lambda",
default = 0.15, min = 0.05, max = 1.0, step = 0.05,
hint = "Max look-back weight at high sigma." },
{ key = "look_back_snr_power_anchor", type = "slider", label = "Anchor: Look-Back SNR Power",
default = 1.3, min = 0.5, max = 3.0, step = 0.1,
hint = "Look-back falloff exponent." },
{ key = "rms_servo", type = "toggle", label = "Anchor: RMS Servo",
default = false, hint = "Downward-only RMS ceiling." },
{ key = "rms_target_min", type = "slider", label = "Anchor: RMS Target Min",
default = 1.2, min = 0.1, max = 3.0, step = 0.05, hint = "RMS ceiling at low sigma." },
{ key = "rms_target_max", type = "slider", label = "Anchor: RMS Target Max",
default = 2.5, min = 0.5, max = 5.0, step = 0.05, hint = "RMS ceiling at high sigma." },
{ key = "rms_servo_gain", type = "slider", label = "Anchor: RMS Servo Gain",
default = 0.6, min = 0.1, max = 1.0, step = 0.05, hint = "Servo correction aggressiveness." },
{ key = "latent_pressure", type = "toggle", label = "Anchor: Latent Pressure",
default = false, hint = "Entropy x RMS target correction (off by default)." },
{ key = "pressure_target_rms", type = "slider", label = "Anchor: Pressure Target RMS",
default = 2.0, min = 0.5, max = 4.0, step = 0.1, hint = "RMS component of pressure target." },
{ key = "pressure_target_entropy", type = "slider", label = "Anchor: Pressure Target Entropy",
default = 7.5, min = 1.0, max = 15.0, step = 0.5, hint = "Shannon entropy target." },
-- ── Post-Blend Shearing Control ──────────────────────────────────
{ key = "post_blend_lookback", type = "slider", label = "Post-Blend Look-Back",
default = 0.0, min = 0.0, max = 0.7, step = 0.05,
hint = "SNR-adaptive EMA on x_final AFTER the blend. Neither sub-solver's look-back covers the blend seam -- this does. 0 = off. 0.25 = subtle anti-shear. Fades with sigma like anchor's look-back." },
{ key = "post_blend_snr_power", type = "slider", label = "Post-Blend SNR Power",
default = 1.0, min = 0.5, max = 3.0, step = 0.1,
hint = "Falloff exponent for post-blend look-back. 1.0 = linear fade (more late-step smoothing than anchor's 1.3 default). Lower = more smoothing persists into detail steps." },
{ key = "spectral_guard", type = "slider", label = "Spectral Blend Guard",
default = 0.4, min = 0.0, max = 1.0, step = 0.05,
hint = "Frequency-aware blend correction. When STORM and anchor disagree, their delta concentrates in high-freq (metallic) components. This attenuates the blend delta in the upper latent bands proportional to disagreement. 0 = off (flat blend). 0.4 = moderate HF damping. 1.0 = aggressive." },
{ key = "late_damp_override", type = "slider", label = "Late Damp Override",
default = 0.7, min = 0.0, max = 1.0, step = 0.05,
hint = "In adaptive mode, overrides amplify with damp for the final portion of the run. 0.7 = last 30% of steps forced to damp. 0 = no override (pure adaptive all the way). Prevents late-step disagreement amplification causing metallic ringing." },
-- ── SDE / Safety ──────────────────────────────────────────────────
{ key = "eta", type = "slider", label = "Noise Injection (0 = ODE)",
default = 0.0, min = 0.0, max = 1.0, step = 0.05,
hint = "Post-blend SDE noise: scale = sigma_next * eta." },
{ key = "seed", type = "slider", label = "Seed",
default = 42, min = 0, max = 999999, step = 1,
hint = "RNG seed for SDE noise." },
{ key = "safety_clamp", type = "slider", label = "Safety Clamp",
default = 2.5, min = 1.0, max = 5.0, step = 0.1,
hint = "Max abs latent value post-blend." },
{ key = "verbose", type = "toggle", label = "Verbose Logging",
default = false,
hint = "Per-step blend diagnostics: agreement, inertia, effective_mix, STORM mode." },
{ key = "relational_weight", type = "slider", label = "Relational Weight",
default = 0.0, min = 0.0, max = 1.0, step = 0.05,
hint = "Barbour Best Matching velocity decomposition. 0 = off." },
{ key = "relational_sigma_power", type = "slider", label = "Relational Sigma Decay",
default = 1.0, min = 0.25, max = 4.0, step = 0.25,
hint = "How fast relational weight fades." },
},
}
-- ─────────────────────────────────────────────────────────────────────────────
-- HELPERS (aliased from md_solver_commons)
-- ─────────────────────────────────────────────────────────────────────────────
local EPSILON = C.EPSILON
local PRESSURE_CAP = 5e-4
local clamp = C.clamp
local smoothstep = C.smoothstep
local fa_to_tbl = C.fa_to_tbl
local tbl_to_fa = C.tbl_to_fa
local vec_norm = C.vec_norm
local vec_sub_norm = C.vec_sub_norm
local vec_dot = C.vec_dot
local vec_clone = C.vec_clone
local cosine_sim = C.cosine_sim
local has_nan_inf = C.has_nan_inf
local rms_range = C.rms_range
local rms = C.rms
local function shannon_entropy(a, n)
local sum = 0.0
for i = 0, n - 1 do sum = sum + math.abs(a[i]) + 1e-7 end
local inv_sum = 1.0 / (sum + 1e-8)
local H = 0.0
for i = 0, n - 1 do
local p = (math.abs(a[i]) + 1e-7) * inv_sum
H = H - p * math.log(p + EPSILON) / math.log(2.0)
end
H = math.max(0.05, H)
if H ~= H or H == math.huge or H == -math.huge then H = 5.0 end
return H
end
local spectral_centroid = C.spectral_centroid
local band_energy = C.band_energy
local make_rng = C.make_rng
local normal = C.normal
local bool_param = C.bool_param
local num_param = C.num_param
-- ─────────────────────────────────────────────────────────────────────────────
-- STORM INTERNALS (ported verbatim from storm_sampler_core.lua)
-- ─────────────────────────────────────────────────────────────────────────────
local function compute_stiffness(v_curr, v_cache, step_idx, baseline, threshold, ema_alpha, n_calib, n)
threshold = threshold or 0.15
ema_alpha = ema_alpha or 0.3
n_calib = n_calib or 4
if #v_cache < 1 then return true, baseline, nil end
local v_prev = v_cache[#v_cache].v
local norm_delta = vec_sub_norm(v_curr, v_prev, n)
local norm_curr = vec_norm(v_curr, n) + 1e-8
local raw_ratio = norm_delta / norm_curr
local prev_ema = baseline.ema or raw_ratio
local smoothed = ema_alpha * raw_ratio + (1.0 - ema_alpha) * prev_ema
baseline.ema = smoothed
local dot = vec_dot(v_curr, v_prev, n)
local nc = vec_norm(v_curr, n)
local np_ = vec_norm(v_prev, n)
local cos_sim_val = dot / (nc * np_ + 1e-8)
if step_idx < n_calib then
baseline.sum = (baseline.sum or 0.0) + smoothed
baseline.count = (baseline.count or 0) + 1
baseline.last_ratio = smoothed
return true, baseline, cos_sim_val
end
local bmean = baseline.sum / math.max(baseline.count, 1)
local adap_thr = threshold * (bmean / 0.15)
adap_thr = clamp(adap_thr, 0.05, 0.50)
local stiff = smoothed > adap_thr
baseline.last_ratio = smoothed
baseline.last_threshold = adap_thr
return stiff, baseline, cos_sim_val
end
local function stork_step(v_cache, x, sigma_curr, sigma_next, v_curr, rk_order, n)
local dt = sigma_next - sigma_curr
local n_cache = #v_cache
local actual_order
if rk_order == "auto" then
actual_order = (n_cache >= 1) and math.min(n_cache + 1, 5) or 1
else
actual_order = (n_cache >= 1) and math.min(tonumber(rk_order), n_cache + 1) or 1
end
actual_order = math.max(actual_order, 1)
if n_cache < 1 or actual_order <= 1 then
local x_next = {}
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
return x_next, 1
end
local e0 = v_cache[#v_cache]
local v_prev_0 = e0.v
local sigma_prev = e0.sigma
local dot = vec_dot(v_curr, v_prev_0, n)
local nc = vec_norm(v_curr, n)
local np_ = vec_norm(v_prev_0, n)
local cos_sim_val = dot / (nc * np_ + 1e-8)
local damping = clamp(cos_sim_val, 0.0, 1.0)
local denom = sigma_curr - sigma_prev
if math.abs(denom) < 1e-8 then
local x_next = {}
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
return x_next, 2
end
local alpha = (sigma_next - sigma_curr) / denom
local x_next = {}
if actual_order == 2 then
for i = 0, n - 1 do
local v_extrap = v_curr[i] + (alpha * damping) * (v_curr[i] - v_prev_0[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * v_extrap)
end
elseif actual_order == 3 and n_cache >= 2 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 then
for i = 0, n - 1 do
local ve = v_curr[i] + (alpha * damping) * (v_curr[i] - v1[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * ve)
end
actual_order = 2
else
local c0 = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do
local v_pred = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (v_pred - v_curr[i]))
end
end
elseif actual_order == 4 and n_cache >= 3 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local v3, s3 = v_cache[#v_cache - 2].v, v_cache[#v_cache - 2].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
local h2 = s2 - s3
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 or math.abs(h2) < 1e-8 then
local c0 = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 3
else
local c0 = 1.0 + dt / (2.0 * h) + dt ^ 2 / (3.0 * h * h1) + dt ^ 3 / (4.0 * h * h1 * h2)
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1 + dt ^ 2 / (2.0 * h1 * h2))
local c2 = (dt ^ 2 / (3.0 * h * h1)) * (1.0 + dt / (2.0 * h2))
local c3 = -(dt ^ 3) / (4.0 * h * h1 * h2)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i] + c3 * v3[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
end
elseif actual_order >= 5 and n_cache >= 4 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local v3, s3 = v_cache[#v_cache - 2].v, v_cache[#v_cache - 2].sigma
local v4, s4 = v_cache[#v_cache - 3].v, v_cache[#v_cache - 3].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
local h2 = s2 - s3
local h3 = s3 - s4
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 or math.abs(h2) < 1e-8 or math.abs(h3) < 1e-8 then
local c0 = 1.0 + dt / (2.0 * h) + dt ^ 2 / (3.0 * h * h1) + dt ^ 3 / (4.0 * h * h1 * h2)
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1 + dt ^ 2 / (2.0 * h1 * h2))
local c2 = (dt ^ 2 / (3.0 * h * h1)) * (1.0 + dt / (2.0 * h2))
local c3 = -(dt ^ 3) / (4.0 * h * h1 * h2)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i] + c3 * v3[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 4
else
local c0 = 1.0 + dt / (2.0 * h) + dt ^ 2 / (3.0 * h * h1) + dt ^ 3 / (4.0 * h * h1 * h2) + dt ^ 4 / (5.0 * h * h1 * h2 * h3)
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1 + dt ^ 2 / (2.0 * h1 * h2) + dt ^ 3 / (3.0 * h1 * h2 * h3))
local c2 = (dt ^ 2 / (3.0 * h * h1)) * (1.0 + dt / (2.0 * h2) + dt ^ 2 / (3.0 * h2 * h3))
local c3 = -(dt ^ 3 / (4.0 * h * h1 * h2)) * (1.0 + dt / (2.0 * h3))
local c4 = dt ^ 4 / (5.0 * h * h1 * h2 * h3)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i] + c3 * v3[i] + c4 * v4[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 5
end
else
-- Fallback AB2
for i = 0, n - 1 do
local ve = v_curr[i] + (alpha * damping) * (v_curr[i] - v_prev_0[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * ve)
end
actual_order = 2
end
return x_next, actual_order
end
local function dpmpp3m_step(v_cache, x, sigma_curr, sigma_next, v_curr, n)
local dt = sigma_next - sigma_curr
local x_next = {}
if #v_cache >= 2 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 then
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
else
local cc = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do x_next[i] = x[i] + dt * (cc * v_curr[i] + c1 * v1[i] + c2 * v2[i]) end
end
elseif #v_cache >= 1 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local h = sigma_curr - s1
if math.abs(h) < 1e-8 then
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
else
for i = 0, n - 1 do x_next[i] = x[i] + dt * (v_curr[i] + (dt / (2.0 * h)) * (v_curr[i] - v1[i])) end
end
else
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
end
return x_next
end
-- STORM's own look-back (operates on its x_next candidate independently)
local function storm_look_back(x_curr, x_prev, sigma_curr, sigma_max, lambda_base, snr_power, n)
if x_prev == nil then return x_curr, 0.0 end
local ratio = math.min(sigma_curr / math.max(sigma_max, 1e-8), 1.0)
local lam = lambda_base * (ratio ^ snr_power)
local out = {}
for i = 0, n - 1 do out[i] = (1.0 - lam) * x_curr[i] + lam * x_prev[i] end
return out, lam
end
-- ─────────────────────────────────────────────────────────────────────────────
-- ANCHOR STATE (module-level, reset per generation)
-- ─────────────────────────────────────────────────────────────────────────────
local _anc_sigma_max = nil
local _anc_has_prev = false
local _anc_has_velocity = false
local _anc_has_anchor = false
local _anc_tonal_ref_cent = {} -- per-batch
local _anc_tonal_ref_bands = {} -- per-batch
local _anc_tonal_captured = false
local _anc_last_entropy = 7.5
local _anc_hist_head = 1
local _anc_hist_count = 0
-- Hoisted buffers (resized on n change)
local _anc_out = {}
local _anc_fallback = {}
local _anc_vel_old = {}
local _anc_vel_raw = {}
local _anc_id_buf = {}
local _anc_prev_out = {}
local _anc_hist_mean = {}
local _anc_history = { {}, {}, {} }
local function reset_anchor_state(n)
_anc_sigma_max = nil
_anc_has_prev = false
_anc_has_velocity = false
_anc_has_anchor = false
_anc_tonal_ref_cent = {} -- per-batch: [b] = centroid
_anc_tonal_ref_bands = {} -- per-batch: [b] = {band1..4}
_anc_tonal_captured = false
_anc_last_entropy = 7.5
_anc_hist_head = 1
_anc_hist_count = 0
for i = 0, n - 1 do
_anc_out[i] = 0.0
_anc_fallback[i] = 0.0
_anc_vel_old[i] = 0.0
_anc_vel_raw[i] = 0.0
_anc_id_buf[i] = 0.0
_anc_prev_out[i] = 0.0
_anc_hist_mean[i] = 0.0
_anc_history[1][i] = 0.0
_anc_history[2][i] = 0.0
_anc_history[3][i] = 0.0
end
end
-- ─────────────────────────────────────────────────────────────────────────────
-- ANCHOR CANDIDATE (full 13-stage pipeline from md_trajectory_anchor.lua)
-- Input: x (Lua table, current latent), v_curr (velocity), sigma_curr, sigma_next, n
-- Reads/writes _anc_* state. Returns x_next_anchor as Lua table.
-- ─────────────────────────────────────────────────────────────────────────────
local function anchor_candidate(x, v_curr, sigma_curr, sigma_next, step_idx, n, p, B, NPB)
local warmup = math.floor(num_param(p, "warmup_steps", 2))
local f_inertia = true -- always on in confluence (inertia_alpha=0 to disable)
local inertia_a = num_param(p, "inertia_alpha", 0.15)
local f_memory = true -- always on (memory_blend=0 to disable)
local mem_blend = num_param(p, "memory_blend", 0.12)
local f_concept = bool_param(p, "concept_lock", true)
local concept_power = num_param(p, "concept_sigma_power", 1.0)
local f_anchor = bool_param(p, "identity_anchor", false)
local anchor_sigma = num_param(p, "anchor_sigma", 0.5)
local anchor_blend = num_param(p, "anchor_blend", 0.08)
local f_tonal = bool_param(p, "tonal_anchor", true)
local tonal_str = num_param(p, "tonal_strength", 0.15)
local f_lookback = bool_param(p, "look_back_enabled_anchor", false)
local lb_lambda = num_param(p, "look_back_lambda_anchor", 0.15)
local lb_snr_power = num_param(p, "look_back_snr_power_anchor", 1.3)
local f_rms = bool_param(p, "rms_servo", false)
local rms_tgt_min = num_param(p, "rms_target_min", 1.2)
local rms_tgt_max = num_param(p, "rms_target_max", 2.5)
local rms_gain = num_param(p, "rms_servo_gain", 0.6)
local f_pressure = bool_param(p, "latent_pressure", false)
local p_tgt_rms = num_param(p, "pressure_target_rms", 2.0)
local p_tgt_entropy = num_param(p, "pressure_target_entropy", 7.5)
local sclamp = num_param(p, "safety_clamp", 2.5)
if _anc_sigma_max == nil then _anc_sigma_max = sigma_curr end
local sigma_ratio = clamp(sigma_curr / math.max(_anc_sigma_max, EPSILON), 0.0, 1.0)
local past_warmup = (step_idx >= warmup)
-- 2. Entropy (from input x)
_anc_last_entropy = shannon_entropy(x, n)
-- 3. Euler advance: dt = sigma_next - sigma_curr (negative in flow-matching)
local dt = sigma_next - sigma_curr
for i = 0, n - 1 do
local v = x[i] + dt * v_curr[i]
_anc_out[i] = v
_anc_fallback[i] = v
end
-- 4. Latent Pressure
if f_pressure then
local cur_rms = rms(_anc_out, n)
local target_product = p_tgt_entropy * p_tgt_rms
local cur_product = _anc_last_entropy * cur_rms
local correction = clamp(
(target_product - cur_product) / (target_product + EPSILON),
-PRESSURE_CAP, PRESSURE_CAP)
if math.abs(correction) > 1e-6 then
for i = 0, n - 1 do _anc_out[i] = _anc_out[i] * (1.0 + correction) end
end
end
-- 5. Memory Buffer
if mem_blend > 0 and past_warmup and _anc_hist_count > 0 then
for i = 0, n - 1 do _anc_hist_mean[i] = 0.0 end
local hw = 1.0 / _anc_hist_count
for h = 1, _anc_hist_count do
for i = 0, n - 1 do _anc_hist_mean[i] = _anc_hist_mean[i] + _anc_history[h][i] end
end
for i = 0, n - 1 do
_anc_out[i] = (1.0 - mem_blend) * _anc_out[i] + mem_blend * (_anc_hist_mean[i] * hw)
end
end
-- 6. Inertia Engine
if inertia_a > 0 and past_warmup and _anc_has_prev then
for i = 0, n - 1 do _anc_vel_raw[i] = _anc_out[i] - _anc_prev_out[i] end
if _anc_has_velocity then
for i = 0, n - 1 do
_anc_vel_old[i] = 0.8 * _anc_vel_old[i] + 0.2 * _anc_vel_raw[i]
end
else
for i = 0, n - 1 do _anc_vel_old[i] = _anc_vel_raw[i] end
_anc_has_velocity = true
end
local alpha = inertia_a * clamp(_anc_last_entropy / 7.5, 0.0, 1.5)
for i = 0, n - 1 do _anc_out[i] = _anc_out[i] + alpha * _anc_vel_old[i] end
end
-- 7. Concept Lock
if f_concept and past_warmup and _anc_has_prev then
local sigma_mod = sigma_ratio ^ concept_power
if sigma_mod > 1e-4 then
for i = 0, n - 1 do
local delta = math.abs(_anc_out[i] - _anc_prev_out[i])
local lock_w = (1.0 / (1.0 + math.exp(delta * 40.0 - 2.0))) * sigma_mod
_anc_out[i] = (1.0 - lock_w) * _anc_out[i] + lock_w * _anc_prev_out[i]
end
end
end
-- 8. Identity Anchor
if f_anchor and past_warmup then
if not _anc_has_anchor and sigma_ratio <= anchor_sigma then
for i = 0, n - 1 do _anc_id_buf[i] = _anc_out[i] end
_anc_has_anchor = true
elseif _anc_has_anchor then
for i = 0, n - 1 do
_anc_out[i] = (1.0 - anchor_blend) * _anc_out[i] + anchor_blend * _anc_id_buf[i]
end
end
end
-- 9. Tonal Anchor (per-batch centroid + band correction)
if f_tonal and past_warmup then
if not _anc_tonal_captured and sigma_ratio <= anchor_sigma then
for b = 0, B - 1 do
local off = b * NPB
_anc_tonal_ref_cent[b] = spectral_centroid(_anc_out, off, NPB)
_anc_tonal_ref_bands[b] = band_energy(_anc_out, off, NPB)
end
_anc_tonal_captured = true
elseif _anc_tonal_captured then
local eff_str = tonal_str * sigma_ratio
if eff_str > 1e-6 then
for b = 0, B - 1 do
local off = b * NPB
local curr_centroid = spectral_centroid(_anc_out, off, NPB)
local curr_bands = band_energy(_anc_out, off, NPB)
local drift_norm_val = (curr_centroid - _anc_tonal_ref_cent[b]) /
(math.abs(_anc_tonal_ref_cent[b]) + EPSILON)
local tilt = clamp(-drift_norm_val * eff_str, -1e-3, 1e-3)
local center = (NPB - 1) / 2.0
for i = off, off + NPB - 1 do
local dist_w = ((i - off) - center) / (center + EPSILON)
_anc_out[i] = _anc_out[i] + tilt * dist_w * math.abs(_anc_out[i])
end
local ref_total, curr_total = 0.0, 0.0
for bb = 1, 4 do
ref_total = ref_total + _anc_tonal_ref_bands[b][bb]
curr_total = curr_total + curr_bands[bb]
end
if ref_total > EPSILON and curr_total > EPSILON then
local bsize = math.floor(NPB / 4)
for bb = 0, 3 do
local ref_ratio = _anc_tonal_ref_bands[b][bb + 1] / ref_total
local curr_ratio = curr_bands[bb + 1] / curr_total
local band_corr = clamp((ref_ratio - curr_ratio) * eff_str, -1e-3, 1e-3)
local blo = off + bb * bsize
local bhi = (bb == 3) and (off + NPB - 1) or (blo + bsize - 1)
for i = blo, bhi do
_anc_out[i] = _anc_out[i] + band_corr * math.abs(_anc_out[i])
end
end
end
end
end
end
end
-- 10. Look-Back Smoother
if f_lookback and past_warmup and _anc_has_prev then
local lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
if lb_w > 1e-6 then
for i = 0, n - 1 do
_anc_out[i] = (1.0 - lb_w) * _anc_out[i] + lb_w * _anc_prev_out[i]
end
end
end
-- 11. RMS Servo (per-batch)
if f_rms then
local rms_target = rms_tgt_min + (sigma_ratio ^ 0.6) * (rms_tgt_max - rms_tgt_min)
for b = 0, B - 1 do
local off = b * NPB
local cur_rms = rms_range(_anc_out, off, NPB)
if cur_rms > rms_target then
local servo_rms = cur_rms + rms_gain * (rms_target - cur_rms)
local scale = servo_rms / cur_rms
for i = off, off + NPB - 1 do _anc_out[i] = _anc_out[i] * scale end
end
end
end
-- 12. Safety Clamp + NaN Guard
if has_nan_inf(_anc_out, n) then
for i = 0, n - 1 do _anc_out[i] = _anc_fallback[i] end
end
for i = 0, n - 1 do _anc_out[i] = clamp(_anc_out[i], -sclamp, sclamp) end
-- Return candidate (state feedback happens in main loop AFTER blend)
local result = {}
for i = 0, n - 1 do result[i] = _anc_out[i] end
return result
end
-- Feed blended x_final back into anchor's state so its memory believes
-- the blended trajectory is what happened
local function anchor_state_feedback(x_final, step_idx, past_warmup, n)
if past_warmup then
for i = 0, n - 1 do _anc_prev_out[i] = x_final[i] end
_anc_has_prev = true
-- Ring buffer push
for i = 0, n - 1 do _anc_history[_anc_hist_head][i] = x_final[i] end
_anc_hist_head = _anc_hist_head + 1
if _anc_hist_head > 3 then _anc_hist_head = 1 end
if _anc_hist_count < 3 then _anc_hist_count = _anc_hist_count + 1 end
end
end
-- ─────────────────────────────────────────────────────────────────────────────
-- CONFLUENCE BLEND
-- ─────────────────────────────────────────────────────────────────────────────
local function compute_effective_mix(user_mix, disagreement, inertia_mag, inertia_influence,
gate_low, gate_high, mode, damp_str, chaos_str, t_frac)
local base = user_mix / 100.0
local gate = smoothstep(inertia_mag, gate_low, gate_high)
local gated_low = base * 0.3
local gated_full = base
local gate_mixed = gated_low * (1 - gate) + gated_full * gate
local gated = base * (1 - inertia_influence) + gate_mixed * inertia_influence
local function damp_term()
return gated * (1 - disagreement * damp_str)
end
local function amplify_term()
local push = disagreement * chaos_str
local sign = (gated >= 0.5) and 1.0 or -1.0
return clamp(gated + push * sign, 0, 1)
end
local effective
if mode == "damp" then
effective = damp_term()
elseif mode == "amplify" then
effective = amplify_term()
else -- adaptive
effective = damp_term() * (1 - t_frac) + amplify_term() * t_frac
end
return clamp(effective, 0, 1)
end
-- ─────────────────────────────────────────────────────────────────────────────
-- SAMPLE -- full loop
-- ─────────────────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
local p = params or {}
local mix_amount = num_param(p, "mix_amount", 50)
local disagreement_mode = p.disagreement_mode or "adaptive"
local damp_str = num_param(p, "damp_strength", 0.4)
local chaos_str = num_param(p, "chaos_strength", 0.3)
local inertia_influence = num_param(p, "inertia_influence", 0.7)
local gate_low = num_param(p, "inertia_gate_low", 0.15)
local gate_high = num_param(p, "inertia_gate_high", 0.6)
local stiffness_thr = num_param(p, "stiffness_threshold", 0.15)
local rk_order = p.rk_order or "auto"
local depth_max = math.floor(num_param(p, "cache_depth", 5))
local lb_lambda_storm = num_param(p, "look_back_lambda_storm", 0.15)
local lb_snr_storm = num_param(p, "look_back_snr_power_storm", 1.5)
local warmup = math.floor(num_param(p, "warmup_steps", 2))
local pb_lb_lambda = num_param(p, "post_blend_lookback", 0.0)
local pb_lb_snr = num_param(p, "post_blend_snr_power", 1.0)
local spec_guard = num_param(p, "spectral_guard", 0.4)
local late_damp_at = num_param(p, "late_damp_override", 0.7)
local eta = num_param(p, "eta", 0.0)
local seed = math.floor(num_param(p, "seed", 42))
local sclamp = num_param(p, "safety_clamp", 2.5)
local verbose = bool_param(p, "verbose", false)
local rw = num_param(p, "relational_weight", 0.0)
local rw_sig_pow = num_param(p, "relational_sigma_power", 1.0)
local ns = #schedule
-- Engine schedule has NO trailing 0 (fix ported from 46c081e): iterate all ns
-- entries so the last iteration gets sigma_next = 0.0 and the terminal branch
-- performs the final x0 projection. With ns - 1 that branch is dead code and
-- the output keeps ~final-sigma noise.
local n_steps = ns
if n_steps < 1 then return end
-- Batch routing: engine exposes batch_n and n_per as globals
local B = (batch_n and batch_n > 0) and batch_n or 1
local NPB = (n_per and n_per > 0) and n_per or n
if B * NPB ~= n then B = 1; NPB = n end
-- Reset both sub-solver states
local v_cache = {}
local baseline = { sum = 0.0, count = 0 }
local hyst = 0.05
local ema_a = 0.3
local n_calib = math.max(2, math.min(5, math.floor(n_steps * 0.12)))
reset_anchor_state(n)
local sigma_max = schedule[1]
local x = fa_to_tbl(xt, n)
-- STORM look-back state
local storm_lb_prev = nil
local lb_storm_enabled = (lb_lambda_storm > 0)
-- Post-blend look-back state
local pb_prev = nil
local pb_enabled = (pb_lb_lambda > 0)
if verbose then
print(string.format("[CONFLUENCE V4] Schedule: %d steps | B=%d NPB=%d | Mix: %d | Mode: %s | RK: %s",
n_steps, B, NPB, mix_amount, disagreement_mode, tostring(rk_order)))
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
local step_idx = i - 1
-- Terminal step: plain Euler, no blend
if sigma_next == 0.0 then
tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_final = fa_to_tbl(vt_buf, n)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
if verbose then print(string.format("[CONFLUENCE] Step %02d: TERMINAL (Euler)", step_idx)) end
break
end
-- Single model call, shared by both candidates
tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_curr = fa_to_tbl(vt_buf, n)
-- Relational decomposition
if rw > 0 then
local sr = clamp(sigma_curr / math.max(sigma_max, EPSILON), 0.0, 1.0)
C.apply_relational(v_curr, n, B, NPB, sr, sigma_max,
rw, rw_sig_pow, false, 0.85, x)
end
-- Save pre-step x for STORM look-back
local x_before_storm = nil
if lb_storm_enabled then x_before_storm = vec_clone(x, n) end
-- ── CANDIDATE A: STORM ──────────────────────────────────────────
local stiff, cos_sim_out
if #v_cache >= 1 then
stiff, baseline, cos_sim_out = compute_stiffness(
v_curr, v_cache, step_idx, baseline, stiffness_thr, ema_a, n_calib, n)
else
stiff, cos_sim_out = true, nil
end
-- Hysteresis
local prev_mode = baseline.prev_mode or "STORK"
if prev_mode == "DPM++" and not stiff then
if (baseline.last_ratio or 0) > (baseline.last_threshold or stiffness_thr) + hyst then
stiff = true
end
end
local x_next_storm, actual_order, storm_mode
if stiff then
x_next_storm, actual_order = stork_step(v_cache, x, sigma_curr, sigma_next, v_curr, rk_order, n)
storm_mode = "STORK"
else
x_next_storm = dpmpp3m_step(v_cache, x, sigma_curr, sigma_next, v_curr, n)
storm_mode = "DPM++"
actual_order = 3
end
-- STORM NaN guard
if has_nan_inf(x_next_storm, n) then
local dt = sigma_next - sigma_curr
x_next_storm = {}
for j = 0, n - 1 do x_next_storm[j] = x[j] + dt * v_curr[j] end
v_cache = {}
actual_order = 1
end
-- STORM look-back (its own, independent of anchor's)
if lb_storm_enabled then
x_next_storm = storm_look_back(x_next_storm, storm_lb_prev, sigma_curr, sigma_max, lb_lambda_storm, lb_snr_storm, n)
storm_lb_prev = x_before_storm
end
baseline.prev_mode = storm_mode
-- Update STORM v_cache (stores velocity, not latent -- no desync)
table.insert(v_cache, { v = v_curr, sigma = sigma_curr })
while #v_cache > depth_max do table.remove(v_cache, 1) end
-- ── CANDIDATE B: ANCHOR ─────────────────────────────────────────
local x_next_anchor = anchor_candidate(x, v_curr, sigma_curr, sigma_next, step_idx, n, p, B, NPB)
-- ── DISAGREEMENT + INERTIA ──────────────────────────────────────
local agreement = cosine_sim(x_next_storm, x_next_anchor, n)
local disagreement = 1.0 - agreement
local mag_ratio = vec_norm(x_next_anchor, n) / (vec_norm(x_next_storm, n) + EPSILON)
local inertia_mag = vec_norm(_anc_vel_old, n) / (vec_norm(v_curr, n) + EPSILON)
inertia_mag = clamp(inertia_mag, 0, 1.5)
local t_frac = step_idx / math.max(n_steps - 1, 1)
local past_warmup = (step_idx >= warmup)
-- Late damp override: force damp mode past late_damp_at fraction
local active_mode = disagreement_mode
if active_mode == "adaptive" and late_damp_at > 0 and t_frac >= late_damp_at then
active_mode = "damp"
end
local effective_mix = compute_effective_mix(
mix_amount, disagreement, inertia_mag, inertia_influence,
gate_low, gate_high, active_mode, damp_str, chaos_str, t_frac)
-- ── BLEND (with spectral guard) ──────────────────────────────────
local x_final = {}
if spec_guard > 0 and disagreement > 0.01 then
-- Frequency-aware blend: attenuate the blend delta in upper bands
-- proportional to disagreement. Per-batch band assignment.
local bsize = math.floor(NPB / 4)
local atten = disagreement * spec_guard
for j = 0, n - 1 do
local local_idx = j % NPB
local band = math.floor(local_idx / bsize)
if band > 3 then band = 3 end
-- band 0 (low) = no attenuation, band 3 (high) = full attenuation
local band_atten = (band / 3.0) * atten
local local_mix = effective_mix * (1.0 - clamp(band_atten, 0.0, 0.8))
x_final[j] = (1.0 - local_mix) * x_next_storm[j] + local_mix * x_next_anchor[j]
end
else
for j = 0, n - 1 do
x_final[j] = (1.0 - effective_mix) * x_next_storm[j] + effective_mix * x_next_anchor[j]
end
end
-- Post-blend NaN guard
if has_nan_inf(x_final, n) then
if verbose then print(string.format("[CONFLUENCE] NaN post-blend step %d, using STORM", step_idx)) end
for j = 0, n - 1 do x_final[j] = x_next_storm[j] end
end
for j = 0, n - 1 do x_final[j] = clamp(x_final[j], -sclamp, sclamp) end
-- ── POST-BLEND LOOK-BACK ─────────────────────────────────────────
-- SNR-adaptive EMA on x_final itself. Covers the blend seam that
-- neither sub-solver's own look-back touches.
if pb_enabled and pb_prev ~= nil then
local ratio = clamp(sigma_curr / math.max(sigma_max, EPSILON), 0.0, 1.0)
local pb_w = pb_lb_lambda * (ratio ^ pb_lb_snr)
if pb_w > 1e-6 then
for j = 0, n - 1 do
x_final[j] = (1.0 - pb_w) * x_final[j] + pb_w * pb_prev[j]
end
end
end
if pb_enabled then pb_prev = vec_clone(x_final, n) end
-- ── STATE FEEDBACK ───────────────────────────────────────────────
-- Anchor gets the blended result, not its own unblended candidate
anchor_state_feedback(x_final, step_idx, past_warmup, n)
-- ── SDE NOISE (post-blend, same convention as anchor) ────────────
if eta > 0.0 and sigma_next > EPSILON then
local rng = make_rng(seed + step_idx * 7919)
local scale = sigma_next * eta
for j = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
x_final[j] = x_final[j] + normal(u1, u2) * scale
end
end
-- ── VERBOSE ──────────────────────────────────────────────────────
if verbose then
print(string.format(
"[CONFLUENCE] step %02d %-5s RK%d | agree=%.3f mag=%.3f inertia=%.3f mix=%d->%.3f mode=%s t=%.2f",
step_idx, storm_mode, actual_order, agreement, mag_ratio,
inertia_mag, mix_amount, effective_mix, active_mode, t_frac))
end
x = x_final
tbl_to_fa(x, xt, n)
tbl_to_fa(v_curr, vt_buf, n)
if on_step(step_idx, sigma_curr, sigma_next) then return end
x = fa_to_tbl(xt, n)
end
tbl_to_fa(x, xt, n)
end
+249
View File
@@ -0,0 +1,249 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
-- ============================================================================
-- MD Dual-Time V2 -- Inner Convergence Loop Sampler
-- MDMAchine | A&E Concepts (c) 2026
--
-- Zero-NFE iterative refinement using cached (v, sigma, x) tuples.
-- Inverse-distance + sigma-proximity velocity interpolation. Sigma-adaptive
-- inner blend protects early vocal separation. owns_loop = true. Single NFE.
-- ============================================================================
local C = require("md_solver_commons")
-- ── VELOCITY INTERPOLATION (per-batch) ──────────────────────────────────────
local function interpolate_velocity_batch(x_cand, off, cnt, cache, cache_len, sigma_curr, sigma_wt)
local v_interp = {}
for i = 0, cnt - 1 do v_interp[i] = 0.0 end
local total_weight = 0.0
for k = 1, cache_len do
local entry = cache[k]
local dist_sq = 0.0
for i = 0, cnt - 1 do
local d = x_cand[off + i] - entry.x[off + i]
dist_sq = dist_sq + d * d
end
local pos_dist = math.sqrt(dist_sq / math.max(cnt, 1) + C.EPSILON)
local sig_factor = 1.0 / (1.0 + sigma_wt * math.abs(sigma_curr - entry.sigma))
local w = sig_factor / (pos_dist + C.EPSILON)
total_weight = total_weight + w
for i = 0, cnt - 1 do v_interp[i] = v_interp[i] + w * entry.v[off + i] end
end
if total_weight > C.EPSILON then
local inv_w = 1.0 / total_weight
for i = 0, cnt - 1 do v_interp[i] = v_interp[i] * inv_w end
end
return v_interp
end
-- ── INNER CONVERGENCE LOOP ──────────────────────────────────────────────────
local function inner_loop(x_start, x_euler, v_curr, dt, n, B, NPB,
cache, cache_len, sigma_curr, sigma_wt,
max_inner, conv_thresh, relaxation)
local x_cand = C.vec_clone(x_euler, n)
local init_resid, final_resid = 0.0, 0.0
local converged = false
local iters_used = 0
for k = 1, max_inner do
iters_used = k
local v_interp_full = C.vec_clone(v_curr, n)
for b = 0, B - 1 do
local off = b * NPB
local v_batch = interpolate_velocity_batch(
x_cand, off, NPB, cache, cache_len, sigma_curr, sigma_wt)
for j = 0, NPB - 1 do v_interp_full[off + j] = v_batch[j] end
end
local x_new = {}
for j = 0, n - 1 do x_new[j] = x_start[j] + dt * v_interp_full[j] end
local corr_rms = 0.0
for j = 0, n - 1 do
local c = x_new[j] - x_cand[j]
corr_rms = corr_rms + c * c
end
corr_rms = math.sqrt(corr_rms / math.max(n, 1))
if k == 1 then init_resid = corr_rms end
final_resid = corr_rms
for j = 0, n - 1 do
x_cand[j] = x_cand[j] + relaxation * (x_new[j] - x_cand[j])
end
if C.has_nan_inf(x_cand, n) then
for j = 0, n - 1 do x_cand[j] = x_euler[j] end
break
end
if corr_rms < conv_thresh then converged = true; break end
end
return x_cand, iters_used, converged, init_resid, final_resid
end
-- ── SOLVER DEFINITION ───────────────────────────────────────────────────────
solver = {
name = "md_dual_time_v2",
display = "MD Dual-Time V2",
description = "Inner convergence loop sampler. Zero-NFE velocity history interpolation. Sigma-adaptive inner blend. Batch-aware, shared anchor stack.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
owns_loop = true,
params = {
{ key = "max_inner", type = "slider", label = "Max Inner Iterations",
default = 3, min = 1, max = 10, step = 1,
hint = "Pseudo-time iterations per step." },
{ key = "cache_depth", type = "slider", label = "Cache Depth",
default = 6, min = 2, max = 12, step = 1,
hint = "Number of (v, sigma, x) tuples stored." },
{ key = "convergence_threshold", type = "slider", label = "Convergence Threshold",
default = 0.005, min = 0.0005, max = 0.1, step = 0.0005,
hint = "Per-element RMS for early exit." },
{ key = "sigma_weight", type = "slider", label = "Sigma Proximity Weight",
default = 4.0, min = 0.0, max = 8.0, step = 0.25,
hint = "Sigma proximity influence. Higher = less phase ghosting." },
{ key = "relaxation", type = "slider", label = "Relaxation Factor",
default = 0.45, min = 0.1, max = 1.0, step = 0.05,
hint = "Inner loop step size. Lower = less phase ghosting." },
{ key = "sigma_gate", type = "slider", label = "Sigma Gate",
default = 0.9, min = 0.5, max = 1.0, step = 0.05,
hint = "Sigma fraction above which inner loop is disabled." },
{ key = "inner_blend", type = "slider", label = "Inner Blend",
default = 0.4, min = 0.0, max = 1.0, step = 0.05,
hint = "Max Euler/converged mix. Sigma-adaptive: near-zero early, ramps quadratically." },
},
}
C.append_common_params(solver.params)
-- ── SAMPLE ──────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
local p = params or {}
local B, NPB = C.get_batch_routing(n)
local max_inner = math.floor(C.num_param(p, "max_inner", 3))
local cache_depth = math.floor(C.num_param(p, "cache_depth", 6))
local conv_thresh = C.num_param(p, "convergence_threshold", 0.005)
local sigma_wt = C.num_param(p, "sigma_weight", 4.0)
local relaxation = C.num_param(p, "relaxation", 0.45)
local sigma_gate = C.num_param(p, "sigma_gate", 0.9)
local inner_blend = C.num_param(p, "inner_blend", 0.4)
local opts = C.read_common_opts(p)
local state = C.new_state()
-- Engine schedule has NO trailing 0 (fix ported from 46c081e): iterate all ns
-- entries so the last iteration gets sigma_next = 0.0 and the terminal branch
-- performs the final x0 projection. With ns - 1 that branch is dead code and
-- the output keeps ~final-sigma noise.
local ns, n_steps = #schedule, #schedule
if n_steps < 1 then return end
local sigma_max = schedule[1]
local cache, cache_len, cache_pos, cache_max = {}, 0, 0, cache_depth
for k = 1, cache_depth do cache[k] = nil end
local x = C.fa_to_tbl(xt, n)
if opts.verbose then
print(string.format("[DUAL-TIME V2] Schedule: %d steps | B=%d NPB=%d | inner=%d cache=%d",
n_steps, B, NPB, max_inner, cache_depth))
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
local step_idx = i - 1
local sigma_ratio = C.clamp(sigma_curr / math.max(sigma_max, C.EPSILON), 0.0, 1.0)
if sigma_next == 0.0 then
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_final = C.fa_to_tbl(vt_buf, n)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
break
end
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_curr = C.fa_to_tbl(vt_buf, n)
local dt = sigma_next - sigma_curr
-- Relational decomposition
if opts.rw > 0 then
C.apply_relational(v_curr, n, B, NPB, sigma_ratio, sigma_max,
opts.rw, opts.rw_sigma_pow, opts.drift_on, opts.drift_thr, x)
end
local x_euler = {}
for j = 0, n - 1 do x_euler[j] = x[j] + dt * v_curr[j] end
local x_new = x_euler
local iters_used, converged, init_resid, final_resid = 0, false, 0.0, 0.0
if cache_len >= 2 and sigma_ratio < sigma_gate then
local cache_ordered = {}
for k = 1, cache_len do
local idx = ((cache_pos - cache_len + k - 1) % cache_max) + 1
cache_ordered[k] = cache[idx]
end
x_new, iters_used, converged, init_resid, final_resid = inner_loop(
x, x_euler, v_curr, dt, n, B, NPB,
cache_ordered, cache_len, sigma_curr, sigma_wt,
max_inner, conv_thresh, relaxation)
-- Sigma-adaptive inner blend
local blend_ramp = (1.0 - sigma_ratio) * (1.0 - sigma_ratio)
local eff_blend = inner_blend * blend_ramp
if eff_blend > 1e-6 and eff_blend < 1.0 - 1e-6 then
for j = 0, n - 1 do
x_new[j] = (1.0 - eff_blend) * x_euler[j] + eff_blend * x_new[j]
end
elseif eff_blend <= 1e-6 then
for j = 0, n - 1 do x_new[j] = x_euler[j] end
end
end
-- Cache push
cache_pos = (cache_pos % cache_max) + 1
cache[cache_pos] = { v = C.vec_clone(v_curr, n), sigma = sigma_curr, x = C.vec_clone(x, n) }
if cache_len < cache_max then cache_len = cache_len + 1 end
if C.has_nan_inf(x_new, n) then
for j = 0, n - 1 do x_new[j] = x_euler[j] end
end
opts.sigma_next = sigma_next
opts.step_idx = step_idx
C.post_advance(x_new, n, B, NPB, sigma_ratio, opts, state)
if opts.verbose then
print(string.format("[DUAL-TIME V2] step %02d | inner=%d/%d %s | resid %.5f->%.5f | rms=%.3f",
step_idx, iters_used, max_inner,
converged and "CONV" or (iters_used > 0 and "max" or "skip"),
init_resid, final_resid, C.rms(x_new, n)))
end
x = x_new
C.tbl_to_fa(x, xt, n)
C.tbl_to_fa(v_curr, vt_buf, n)
if on_step(step_idx, sigma_curr, sigma_next) then return end
x = C.fa_to_tbl(xt, n)
end
C.tbl_to_fa(x, xt, n)
end
+251
View File
@@ -0,0 +1,251 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
-- ============================================================================
-- MD Eigenflow V1 -- PCA Trajectory Filtering Sampler
-- MDMAchine | A&E Concepts (c) 2026
--
-- PCA trajectory filtering via power iteration on velocity history window.
-- Separates dominant denoising direction from oscillatory noise.
-- Euler advance with filtered velocity. owns_loop = true. Single NFE.
-- ============================================================================
local C = require("md_solver_commons")
-- ── POWER ITERATION (per-batch) ─────────────────────────────────────────────
local function power_iteration_batch(window, win_len, off, cnt, M, iters)
local eigvecs, eigvals = {}, {}
for m = 1, M do
local q = {}
for i = 0, cnt - 1 do q[i] = window[1][off + i] end
for prev = 1, m - 1 do
local d = 0.0
for i = 0, cnt - 1 do d = d + q[i] * eigvecs[prev][i] end
for i = 0, cnt - 1 do q[i] = q[i] - d * eigvecs[prev][i] end
end
for _iter = 1, iters do
local Cq = {}
for i = 0, cnt - 1 do Cq[i] = 0.0 end
for k = 1, win_len do
local dot = 0.0
for i = 0, cnt - 1 do dot = dot + window[k][off + i] * q[i] end
local scale = dot / win_len
for i = 0, cnt - 1 do Cq[i] = Cq[i] + window[k][off + i] * scale end
end
for prev = 1, m - 1 do
local d = 0.0
for i = 0, cnt - 1 do d = d + Cq[i] * eigvecs[prev][i] end
for i = 0, cnt - 1 do Cq[i] = Cq[i] - d * eigvecs[prev][i] end
end
local nrm = 0.0
for i = 0, cnt - 1 do nrm = nrm + Cq[i] * Cq[i] end
nrm = math.sqrt(nrm + C.EPSILON)
for i = 0, cnt - 1 do q[i] = Cq[i] / nrm end
end
local lam = 0.0
for k = 1, win_len do
local dot = 0.0
for i = 0, cnt - 1 do dot = dot + window[k][off + i] * q[i] end
lam = lam + dot * dot
end
eigvecs[m] = q
eigvals[m] = lam / win_len
end
return eigvecs, eigvals
end
local function filter_velocity_batch(v_curr, off, cnt, eigvecs, M, ratio)
local projections = {}
for m = 1, M do
local dot = 0.0
for i = 0, cnt - 1 do dot = dot + v_curr[off + i] * eigvecs[m][i] end
projections[m] = dot
end
local filtered = {}
for i = 0, cnt - 1 do
local dominant = 0.0
for m = 1, M do dominant = dominant + projections[m] * eigvecs[m][i] end
filtered[i] = dominant + ratio * (v_curr[off + i] - dominant)
end
return filtered
end
-- ── SOLVER DEFINITION ───────────────────────────────────────────────────────
solver = {
name = "md_eigenflow_v1",
display = "MD Eigenflow V1",
description = "PCA trajectory filtering sampler. Power iteration on velocity history, batch-aware, shared anchor stack.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
owns_loop = true,
params = {
{ key = "window_size", type = "slider", label = "Velocity Window Size",
default = 6, min = 3, max = 12, step = 1,
hint = "Velocity snapshots in sliding window." },
{ key = "num_modes", type = "slider", label = "Principal Modes",
default = 2, min = 1, max = 4, step = 1,
hint = "Dominant eigenvectors to keep. 1 = aggressive, 3+ = conservative." },
{ key = "power_iterations", type = "slider", label = "Power Iterations",
default = 4, min = 2, max = 8, step = 1,
hint = "Convergence iterations for power method." },
{ key = "eigenflow_ratio", type = "slider", label = "Eigenflow Ratio",
default = 0.3, min = 0.0, max = 1.0, step = 0.05,
hint = "Residual to keep. 0 = pure dominant mode. 1 = passthrough (Euler)." },
{ key = "sigma_warmup", type = "slider", label = "Sigma Warmup",
default = 0.85, min = 0.5, max = 1.0, step = 0.05,
hint = "Sigma fraction above which filtering is disabled." },
{ key = "adaptive_ratio", type = "toggle", label = "Adaptive Ratio",
default = true,
hint = "Modulates eigenflow_ratio by dominance ratio." },
},
}
C.append_common_params(solver.params)
-- ── SAMPLE ──────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
local p = params or {}
local B, NPB = C.get_batch_routing(n)
local win_size = math.floor(C.num_param(p, "window_size", 6))
local num_modes = math.floor(C.num_param(p, "num_modes", 2))
local pw_iters = math.floor(C.num_param(p, "power_iterations", 4))
local ef_ratio = C.num_param(p, "eigenflow_ratio", 0.3)
local sigma_warmup = C.num_param(p, "sigma_warmup", 0.85)
local f_adaptive = C.bool_param(p, "adaptive_ratio", true)
local opts = C.read_common_opts(p)
local state = C.new_state()
-- Engine schedule has NO trailing 0 (fix ported from 46c081e): iterate all ns
-- entries so the last iteration gets sigma_next = 0.0 and the terminal branch
-- performs the final x0 projection. With ns - 1 that branch is dead code and
-- the output keeps ~final-sigma noise.
local ns, n_steps = #schedule, #schedule
if n_steps < 1 then return end
local sigma_max = schedule[1]
local v_window, v_win_len, v_win_pos = {}, 0, 0
for k = 1, win_size do v_window[k] = nil end
local x = C.fa_to_tbl(xt, n)
if opts.verbose then
print(string.format("[EIGENFLOW V1] Schedule: %d steps | B=%d NPB=%d n=%d | win=%d modes=%d ratio=%.2f",
n_steps, B, NPB, n, win_size, num_modes, ef_ratio))
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
local step_idx = i - 1
local sigma_ratio = C.clamp(sigma_curr / math.max(sigma_max, C.EPSILON), 0.0, 1.0)
if sigma_next == 0.0 then
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_final = C.fa_to_tbl(vt_buf, n)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
break
end
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_curr = C.fa_to_tbl(vt_buf, n)
local dt = sigma_next - sigma_curr
-- Relational decomposition (shape/scale cleanup on velocity)
if opts.rw > 0 then
C.apply_relational(v_curr, n, B, NPB, sigma_ratio, sigma_max,
opts.rw, opts.rw_sigma_pow, opts.drift_on, opts.drift_thr, x)
end
-- Push into ring buffer
v_win_pos = (v_win_pos % win_size) + 1
v_window[v_win_pos] = C.vec_clone(v_curr, n)
if v_win_len < win_size then v_win_len = v_win_len + 1 end
-- Build ordered window
local win_ordered = {}
for k = 1, v_win_len do
local idx = ((v_win_pos - v_win_len + k - 1) % win_size) + 1
win_ordered[k] = v_window[idx]
end
-- Eigenflow filtering
local v_use = v_curr
local filtered = false
local dominance_ratio = 0.0
if v_win_len >= win_size and sigma_ratio < sigma_warmup then
local actual_modes = math.min(num_modes, win_size - 1)
local v_filtered = C.vec_clone(v_curr, n)
for b = 0, B - 1 do
local off = b * NPB
local eigvecs, eigvals = power_iteration_batch(
win_ordered, v_win_len, off, NPB, actual_modes, pw_iters)
if actual_modes >= 2 and eigvals[2] > C.EPSILON then
local dr = eigvals[1] / eigvals[2]
if dr > dominance_ratio then dominance_ratio = dr end
end
local eff_ratio = ef_ratio
if f_adaptive and dominance_ratio > 1.0 then
eff_ratio = ef_ratio * C.clamp(1.0 / math.sqrt(dominance_ratio), 0.1, 1.0)
end
local batch_filtered = filter_velocity_batch(
v_curr, off, NPB, eigvecs, actual_modes, eff_ratio)
for j = 0, NPB - 1 do v_filtered[off + j] = batch_filtered[j] end
end
if not C.has_nan_inf(v_filtered, n) then
v_use = v_filtered
filtered = true
end
end
-- Euler advance
local x_new = {}
for j = 0, n - 1 do x_new[j] = x[j] + dt * v_use[j] end
if C.has_nan_inf(x_new, n) then
for j = 0, n - 1 do x_new[j] = x[j] + dt * v_curr[j] end
end
-- Post-advance stack
opts.sigma_next = sigma_next
opts.step_idx = step_idx
C.post_advance(x_new, n, B, NPB, sigma_ratio, opts, state)
if opts.verbose then
print(string.format("[EIGENFLOW V1] step %02d | %s | dom=%.2f | rms=%.3f",
step_idx, filtered and "FILTERED" or "raw", dominance_ratio, C.rms(x_new, n)))
end
x = x_new
C.tbl_to_fa(x, xt, n)
C.tbl_to_fa(v_curr, vt_buf, n)
if on_step(step_idx, sigma_curr, sigma_next) then return end
x = C.fa_to_tbl(xt, n)
end
C.tbl_to_fa(x, xt, n)
end
+286
View File
@@ -0,0 +1,286 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
-- ============================================================================
-- MD Hamiltonian V2 -- Energy-Conserving Momentum-Augmented Sampler
-- MDMAchine | A&E Concepts (c) 2026
--
-- Euler-primary architecture with momentum correction layer, sigma-adaptive
-- decay, confidence gating, spectral momentum, Hamiltonian energy tracking.
-- Two look-backs (primary + post-step). owns_loop = true. Single NFE.
-- ============================================================================
local C = require("md_solver_commons")
-- ── HAMILTONIAN ENERGY ──────────────────────────────────────────────────────
local function kinetic_energy(p, mass, n)
local s = 0.0
for i = 0, n - 1 do s = s + p[i] * p[i] end
return 0.5 * s / mass
end
local function potential_energy(x, v_curr, sigma_ratio, n)
return -C.vec_dot(v_curr, x, n) * sigma_ratio
end
-- ── SOLVER DEFINITION ───────────────────────────────────────────────────────
solver = {
name = "md_hamiltonian_v2",
display = "MD Hamiltonian V2",
description = "Energy-conserving momentum-augmented sampler. Euler + momentum correction, spectral weighting, Hamiltonian tracking. Shared anchor stack.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
owns_loop = true,
params = {
-- Momentum
{ key = "momentum_weight", type = "slider", label = "Momentum Weight",
default = 0.20, min = 0.0, max = 0.8, step = 0.05,
hint = "Momentum blend. 0 = pure Euler. Scaled by confidence gating and sigma fadeout." },
{ key = "momentum_decay", type = "slider", label = "Momentum Decay",
default = 0.85, min = 0.0, max = 0.99, step = 0.01,
hint = "Step-to-step carry-over. Sigma-adaptive." },
{ key = "momentum_ema_alpha", type = "slider", label = "Momentum EMA Alpha",
default = 0.3, min = 0.05, max = 0.8, step = 0.05,
hint = "Velocity absorption rate. Sigma-adaptive." },
{ key = "mass", type = "slider", label = "Particle Mass",
default = 1.0, min = 0.1, max = 5.0, step = 0.1,
hint = "Inertial mass." },
-- Energy
{ key = "energy_tolerance", type = "slider", label = "Energy Tolerance",
default = 0.05, min = 0.005, max = 0.5, step = 0.005,
hint = "Hamiltonian drift before Metropolis correction." },
{ key = "correction_strength", type = "slider", label = "Correction Strength",
default = 0.7, min = 0.0, max = 1.0, step = 0.05,
hint = "Metropolis momentum rescale. 0 = monitor only." },
{ key = "energy_tracking", type = "select", label = "Energy Tracking",
default = "adaptive",
options = {
{ value = "fixed", label = "Fixed" },
{ value = "adaptive", label = "Adaptive" },
{ value = "monitor", label = "Monitor Only" },
},
hint = "How H reference evolves." },
-- Confidence
{ key = "confidence_floor", type = "slider", label = "Confidence Floor",
default = 0.2, min = 0.0, max = 0.8, step = 0.05, hint = "Min alignment for momentum." },
{ key = "confidence_ceiling", type = "slider", label = "Confidence Ceiling",
default = 0.7, min = 0.3, max = 1.0, step = 0.05, hint = "Full momentum alignment." },
-- Spectral momentum
{ key = "spectral_momentum", type = "toggle", label = "Spectral Momentum",
default = true, hint = "Per-batch 4-band momentum weighting." },
{ key = "spectral_hi_boost", type = "slider", label = "Spectral HF Boost",
default = 1.4, min = 1.0, max = 4.0, step = 0.1, hint = "HF momentum multiplier." },
{ key = "spectral_mid_cut", type = "slider", label = "Spectral Mid Cut",
default = 0.6, min = 0.1, max = 1.0, step = 0.05, hint = "Mid momentum multiplier." },
-- Post-step look-back (secondary)
{ key = "post_look_back", type = "slider", label = "Post-Step Look-Back",
default = 0.0, min = 0.0, max = 0.6, step = 0.05, hint = "Additional SNR-adaptive EMA. 0 = off (default)." },
{ key = "post_look_back_snr", type = "slider", label = "Post-Step LB SNR Power",
default = 1.0, min = 0.5, max = 3.0, step = 0.1, hint = "Falloff." },
},
}
C.append_common_params(solver.params)
-- ── SAMPLE ──────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
local p = params or {}
local B, NPB = C.get_batch_routing(n)
local mom_weight = C.num_param(p, "momentum_weight", 0.20)
local mom_decay = C.num_param(p, "momentum_decay", 0.85)
local mom_alpha = C.num_param(p, "momentum_ema_alpha", 0.3)
local mass = C.num_param(p, "mass", 1.0)
local energy_tol = C.num_param(p, "energy_tolerance", 0.05)
local corr_str = C.num_param(p, "correction_strength", 0.7)
local energy_mode = p.energy_tracking or "adaptive"
local conf_floor = C.num_param(p, "confidence_floor", 0.2)
local conf_ceil = C.num_param(p, "confidence_ceiling", 0.7)
local f_spec_mom = C.bool_param(p, "spectral_momentum", true)
local spec_hi = C.num_param(p, "spectral_hi_boost", 1.4)
local spec_mid = C.num_param(p, "spectral_mid_cut", 0.6)
local post_lb_lam = C.num_param(p, "post_look_back", 0.0)
local post_lb_snr = C.num_param(p, "post_look_back_snr", 1.0)
local opts = C.read_common_opts(p)
local state = C.new_state()
-- Engine schedule has NO trailing 0 (fix ported from 46c081e): iterate all ns
-- entries so the last iteration gets sigma_next = 0.0 and the terminal branch
-- performs the final x0 projection. With ns - 1 that branch is dead code and
-- the output keeps ~final-sigma noise.
local ns, n_steps = #schedule, #schedule
if n_steps < 1 then return end
local sigma_max = schedule[1]
local momentum = nil
local H_ref = nil
local post_lb_enabled = (post_lb_lam > 0)
local x = C.fa_to_tbl(xt, n)
if opts.verbose then
print(string.format("[HAMILTONIAN V2] Schedule: %d steps | B=%d NPB=%d | weight=%.2f decay=%.2f mass=%.1f",
n_steps, B, NPB, mom_weight, mom_decay, mass))
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
local step_idx = i - 1
local sigma_ratio = C.clamp(sigma_curr / math.max(sigma_max, C.EPSILON), 0.0, 1.0)
if sigma_next == 0.0 then
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_final = C.fa_to_tbl(vt_buf, n)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
break
end
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_curr = C.fa_to_tbl(vt_buf, n)
local dt = sigma_next - sigma_curr
-- Relational decomposition
if opts.rw > 0 then
C.apply_relational(v_curr, n, B, NPB, sigma_ratio, sigma_max,
opts.rw, opts.rw_sigma_pow, opts.drift_on, opts.drift_thr, x)
end
-- 1. Euler advance
local x_euler = {}
for j = 0, n - 1 do x_euler[j] = x[j] + dt * v_curr[j] end
-- 2. Momentum update (sigma-adaptive)
local decay_power = 1.0 + 2.0 * (1.0 - sigma_ratio)
local effective_decay = mom_decay ^ decay_power
local effective_alpha = mom_alpha + (1.0 - mom_alpha) * 0.5 * (1.0 - sigma_ratio)
if momentum == nil then
momentum = {}
for j = 0, n - 1 do momentum[j] = v_curr[j] * mass end
else
for j = 0, n - 1 do momentum[j] = momentum[j] * effective_decay end
for j = 0, n - 1 do
momentum[j] = (1.0 - effective_alpha) * momentum[j] + effective_alpha * v_curr[j] * mass
end
end
-- 3. Momentum-predicted position
local x_mom = {}
local inv_mass = 1.0 / mass
for j = 0, n - 1 do x_mom[j] = x[j] + dt * momentum[j] * inv_mass end
-- 4. Confidence gating + sigma fadeout (linear)
local mom_norm = C.vec_norm(momentum, n)
local v_norm = C.vec_norm(v_curr, n)
local alignment = 0.0
if mom_norm > C.EPSILON and v_norm > C.EPSILON then
alignment = C.vec_dot(momentum, v_curr, n) / (mom_norm * v_norm)
end
local confidence = C.smoothstep(alignment, conf_floor, conf_ceil)
local sigma_fade = sigma_ratio
local eff_weight = mom_weight * confidence * sigma_fade
-- Re-alignment when fighting
if mom_norm > C.EPSILON and v_norm > C.EPSILON and alignment < 0.3 then
local blend = 0.3 * (1.0 - alignment)
for j = 0, n - 1 do
momentum[j] = (1.0 - blend) * momentum[j] + blend * v_curr[j] * mass * math.abs(dt)
end
end
-- 5. Blend (per-batch spectral awareness)
local x_new = {}
if f_spec_mom and eff_weight > 1e-6 then
local band_mults = { 1.0, spec_mid, spec_mid, spec_hi }
local bsize = math.floor(NPB / 4)
for j = 0, n - 1 do
local local_idx = j % NPB
local band = math.min(math.floor(local_idx / bsize), 3)
local local_w = C.clamp(eff_weight * band_mults[band + 1], 0.0, 0.95)
x_new[j] = (1.0 - local_w) * x_euler[j] + local_w * x_mom[j]
end
else
for j = 0, n - 1 do
x_new[j] = (1.0 - eff_weight) * x_euler[j] + eff_weight * x_mom[j]
end
end
if C.has_nan_inf(x_new, n) then
for j = 0, n - 1 do x_new[j] = x_euler[j] end
end
-- 6. Hamiltonian energy tracking
local T = kinetic_energy(momentum, mass, n)
local V = potential_energy(x_new, v_curr, sigma_ratio, n)
local H = T + V
local corrected = false
if H_ref == nil then
H_ref = H
else
local rel_drift = math.abs(H - H_ref) / (math.abs(H_ref) + C.EPSILON)
if energy_mode ~= "monitor" and rel_drift > energy_tol and corr_str > 0 then
local T_target = H_ref - V
if T_target < 0.01 then T_target = 0.01 end
local scale = math.sqrt(T_target / (T + C.EPSILON))
scale = 1.0 + corr_str * (scale - 1.0)
scale = C.clamp(scale, 0.5, 2.0)
for j = 0, n - 1 do momentum[j] = momentum[j] * scale end
corrected = true
end
if energy_mode == "adaptive" then H_ref = 0.95 * H_ref + 0.05 * H end
end
-- 7-8. Identity + tonal anchor (via commons)
if opts.f_id_anchor then
C.apply_identity_anchor(x_new, n, sigma_ratio, opts.anchor_sigma, opts.anchor_blend, state)
end
if opts.f_tonal then
C.apply_tonal_anchor(x_new, n, B, NPB, sigma_ratio, opts.anchor_sigma, opts.tonal_str, state)
end
-- 9. Primary look-back (via commons)
if opts.f_lookback then
C.apply_look_back(x_new, n, sigma_ratio, opts.lb_lambda, opts.lb_snr_power, state, "lb_prev")
end
-- 10. RMS servo (via commons)
if opts.f_rms then
C.apply_rms_servo(x_new, n, B, NPB, sigma_ratio, opts.rms_tgt_min, opts.rms_tgt_max, opts.rms_gain)
end
-- 11. Post-step look-back (secondary, via commons)
if post_lb_enabled then
C.apply_look_back(x_new, n, sigma_ratio, post_lb_lam, post_lb_snr, state, "lb2_prev")
end
-- 12. SDE noise + safety clamp (via commons)
C.apply_sde_noise(x_new, n, sigma_next, opts.eta, opts.seed, step_idx)
C.apply_safety_clamp(x_new, n, opts.sclamp)
if opts.verbose then
print(string.format(
"[HAMILTONIAN V2] step %02d | H=%.2f %s | align=%.3f conf=%.2f ew=%.3f | rms=%.3f",
step_idx, H, corrected and "CORR" or "ok",
alignment, confidence, eff_weight, C.rms(x_new, n)))
end
x = x_new
C.tbl_to_fa(x, xt, n)
C.tbl_to_fa(v_curr, vt_buf, n)
if on_step(step_idx, sigma_curr, sigma_next) then return end
x = C.fa_to_tbl(xt, n)
end
C.tbl_to_fa(x, xt, n)
end
+280
View File
@@ -0,0 +1,280 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
-- ============================================================================
-- MD OmniRelational Solver v3.0 -- Barbour Best Matching + Sigma Decay + Look-Back
-- MDMAchine | A&E Concepts 2026
--
-- WHAT IS NEW IN V3:
--
-- V1/V2 applied the same relational weight (rw) at every step regardless
-- of where you are in the denoising schedule. This is suboptimal:
--
-- High sigma (early steps): structure formation phase. The latent is still
-- mostly noise. Shape-preserving relational geometry matters most here --
-- normalizing direction prevents any single component dominating.
--
-- Low sigma (late steps): detail refinement phase. The latent is close to
-- x0. Raw velocity is more accurate. Relational re-injection here over-
-- smooths fine detail.
--
-- SIGMA-ADAPTIVE RELATIONAL WEIGHT:
-- rw_eff = rw * (t_curr / sigma_max) ^ sigma_power
-- At t=sigma_max (first step): rw_eff = rw (full effect)
-- At t=0 (final step): rw_eff = 0 (pure Euler)
-- sigma_power controls the decay curve. 1.0 = linear, 2.0 = quadratic.
--
-- LOOK-BACK SNR SMOOTHER (arXiv:2602.09449):
-- lambda_eff = lb_lambda * (t_curr / sigma_max) ^ lb_snr_power
-- x_smooth = (1 - lambda_eff) * x_next + lambda_eff * x_prev
-- Heavy at high sigma (blends structure), fades at low sigma (preserves
-- detail). Same mechanism as STORM and PingPong. Off by default.
--
-- GENERATION STATE RESET:
-- Module-level state (sigma_max, x_prev for look-back) resets on
-- step_idx_==0 so same-size consecutive generations don't bleed.
--
-- DRIFT GUARD kept from V1 -- simple, clean, no hoisted buffer.
solver = {
name = "md_omni_relational_V3",
display = "MD OmniRelational V3 (Sigma Adaptive)",
description = "Barbour Best Matching with sigma-adaptive relational weight and look-back smoother. rw fades toward pure Euler at low sigma. Proper state reset between generations.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
params = {
{
key = "relational_weight",
type = "slider",
label = "Relational Weight",
default = 0.5,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Base relational weight at high sigma. Fades toward 0 at low sigma. 0=pure Euler always. 0.5=balanced at structure phase.",
},
{
key = "sigma_power",
type = "slider",
label = "Sigma Decay Power",
default = 1.0,
min = 0.25,
max = 4.0,
step = 0.25,
hint = "Controls how fast rw fades with sigma. 1.0=linear decay. 2.0=quadratic (faster fade). 0.5=slow fade. Higher = relational effect concentrated earlier.",
},
{
key = "look_back_lambda",
type = "slider",
label = "Look-Back Lambda",
default = 0.0,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Look-back coherence smoother. 0=off. Blends current step with previous, fading out at low sigma. Suppresses trajectory shear. Start at 0.05-0.15.",
},
{
key = "look_back_snr_power",
type = "slider",
label = "Look-Back SNR Power",
default = 1.5,
min = 0.5,
max = 3.0,
step = 0.1,
hint = "Controls how fast look-back fades with sigma. Higher = smoothing concentrated on early structure steps only.",
},
{
key = "drift_guard",
type = "toggle",
label = "Drift Guard (AOS)",
default = false,
hint = "Project shape_vec onto orthogonal complement of x when cos_sim exceeds threshold. Prevents update reinforcing existing latent structure.",
},
{
key = "drift_threshold",
type = "slider",
label = "Drift Threshold",
default = 0.85,
min = 0.1,
max = 1.0,
step = 0.05,
hint = "Cosine similarity ceiling before Gram-Schmidt projection fires. 0.85=standard. Only active when Drift Guard is on.",
},
{
key = "eta",
type = "slider",
label = "Eta (SDE Noise)",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Ancestral noise injection. 0=deterministic ODE. Scales with t_prev.",
},
{
key = "seed",
type = "slider",
label = "Seed",
default = 42,
min = 0,
max = 999999,
step = 1,
hint = "RNG seed for SDE noise.",
},
},
}
local EPSILON = 1e-8
-- Module state -- reset on step_idx_==0
local _sigma_max = nil
local _x_prev_lb = nil -- look-back previous x (before update)
local function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
local function make_rng(seed)
local state = math.floor(seed) % 2147483647
if state <= 0 then state = state + 2147483646 end
return function()
state = (state * 1664525 + 1013904223) % 2147483648
return state / 2147483648.0
end
end
local function normal(u1, u2)
return math.sqrt(-2.0 * math.log(math.max(u1, EPSILON))) * math.cos(2.0 * math.pi * u2)
end
local function l2_norm(arr, n)
local s = 0.0
for i = 0, n - 1 do s = s + arr[i] * arr[i] end
return math.sqrt(s + EPSILON)
end
-- Shape decomposition: returns unit direction table + L2 norm (mean_scale)
local function decompose_shape(vt, n)
local norm = l2_norm(vt, n)
local inv = norm > EPSILON and (1.0 / norm) or 0.0
local shape = {}
for i = 0, n - 1 do shape[i] = vt[i] * inv end
return shape, norm
end
-- Gram-Schmidt drift guard: projects shape onto orthogonal complement of xt
local function apply_drift_guard(shape, xt, n, threshold)
local norm_x = l2_norm(xt, n)
if norm_x < EPSILON then return shape end
local inv_x = 1.0 / norm_x
local dot_sx = 0.0
for i = 0, n - 1 do dot_sx = dot_sx + shape[i] * (xt[i] * inv_x) end
if math.abs(dot_sx) <= threshold then return shape end
local proj = {}
for i = 0, n - 1 do
proj[i] = shape[i] - dot_sx * (xt[i] * inv_x)
end
local proj_norm = l2_norm(proj, n)
if proj_norm < EPSILON then return shape end
local inv_proj = 1.0 / proj_norm
for i = 0, n - 1 do proj[i] = proj[i] * inv_proj end
return proj
end
-- Look-back SNR smoother: blend x_curr toward x_prev, lambda fades with sigma
local function look_back_smooth(x_curr, x_prev, t_curr, sigma_max, lb_lambda, snr_power, n)
if x_prev == nil or lb_lambda < EPSILON then return x_curr, 0.0 end
local ratio = math.min(t_curr / math.max(sigma_max, EPSILON), 1.0)
local lam = lb_lambda * (ratio ^ snr_power)
local out = {}
for i = 0, n - 1 do
out[i] = (1.0 - lam) * x_curr[i] + lam * x_prev[i]
end
return out, lam
end
-- ── step() function ───────────────────────────────────────────────────────────
function step(xt, vt, t_curr, t_prev, n)
local rw = clamp((params and params.relational_weight) or 0.5, 0.0, 1.0)
local sig_power = clamp((params and params.sigma_power) or 1.0, 0.25, 4.0)
local lb_lambda = clamp((params and params.look_back_lambda) or 0.0, 0.0, 0.5)
local lb_snr_pow = clamp((params and params.look_back_snr_power) or 1.5, 0.5, 3.0)
local drift_on = (params and params.drift_guard) or false
local drift_thr = clamp((params and params.drift_threshold) or 0.85, 0.1, 1.0)
local eta = clamp((params and params.eta) or 0.0, 0.0, 1.0)
local seed = math.floor((params and params.seed) or 42)
local step_idx_ = step_index or 0
-- Reset state at start of each generation
if step_idx_ == 0 then
_sigma_max = t_curr
_x_prev_lb = nil
end
if _sigma_max == nil then _sigma_max = t_curr end
-- Snapshot xt before update for look-back (copy to plain table)
local x_curr_snapshot = nil
if lb_lambda > EPSILON then
x_curr_snapshot = {}
for i = 0, n - 1 do x_curr_snapshot[i] = xt[i] end
end
-- ── 1. Shape decomposition ────────────────────────────────────────────────
local shape_vec, mean_scale = decompose_shape(vt, n)
-- ── 2. Optional drift guard ───────────────────────────────────────────────
if drift_on and drift_thr < 1.0 then
-- xt is a plain table in step() -- pass directly
local xt_tbl = {}
for i = 0, n - 1 do xt_tbl[i] = xt[i] end
shape_vec = apply_drift_guard(shape_vec, xt_tbl, n, drift_thr)
end
-- ── 3. Sigma-adaptive relational weight ───────────────────────────────────
-- rw_eff = rw * (t_curr / sigma_max) ^ sigma_power
-- At high sigma: rw_eff = rw (full relational). At low sigma: fades to 0.
local sigma_ratio = math.min(t_curr / math.max(_sigma_max, EPSILON), 1.0)
local rw_eff = rw * (sigma_ratio ^ sig_power)
-- ── 4. Blend + Euler update ───────────────────────────────────────────────
local dt = t_prev - t_curr -- negative in flow-matching
local x_next = {}
for i = 0, n - 1 do
local rel_i = shape_vec[i] * mean_scale
local eff_vt_i = rw_eff * rel_i + (1.0 - rw_eff) * vt[i]
x_next[i] = xt[i] + dt * eff_vt_i
end
-- ── 5. Look-back smoother ─────────────────────────────────────────────────
if lb_lambda > EPSILON then
local lam
x_next, lam = look_back_smooth(x_next, _x_prev_lb, t_curr, _sigma_max, lb_lambda, lb_snr_pow, n)
_x_prev_lb = x_curr_snapshot
end
-- ── 6. Write x_next back to xt ───────────────────────────────────────────
for i = 0, n - 1 do xt[i] = x_next[i] end
-- ── 7. Optional SDE noise ─────────────────────────────────────────────────
if eta > 0.0 and t_prev > EPSILON then
local rng = make_rng(seed + step_idx_ * 7919)
local scale = t_prev * eta
for i = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
xt[i] = xt[i] + normal(u1, u2) * scale
end
end
end
+360
View File
@@ -0,0 +1,360 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details: https://www.gnu.org/licenses/
-- ============================================================================
-- MD PingPong Simple v1.1 — Ancestral Euler + Momentum + Look-Back Smoother
-- MDMAchine | A&E Concepts © 2026
--
-- Port of MD_PingPong_Samplers.py (single-branch path) to HOT-Step-CPP Lua solver.
--
-- WHAT THIS DOES:
-- Standard ODE solvers (Euler, Heun, STORM) advance the latent deterministically.
-- PingPong injects ancestral (stochastic) noise at each step — the "ping" is
-- the clean denoising step, the "pong" is the noise re-injection that keeps
-- the trajectory alive and stochastic.
--
-- CORE STEP MATH:
-- dt = t_prev - t_curr (negative — stepping down)
-- x_euler = xt + dt * vt (standard Euler, matches STORM/OmniRelational)
-- x_next = x_euler + noise * |dt| * ancestral_strength
--
-- MOMENTUM:
-- Latent velocity (x - x_prev) carried forward at each step. Maintains
-- flow continuity across the ODE trajectory — reduces erratic jumps between
-- steps, especially at low step counts.
--
-- NOISE COHERENCE:
-- Blends fresh Gaussian noise with the previous step's noise at ratio
-- noise_coherence. 0=fully fresh, 1=fully carried. Useful for temporal
-- smoothness in audio; keep low (0-0.2) to avoid spectral smearing.
--
-- LOOK-BACK SNR SMOOTHER:
-- λ(σ) = lambda_base * (σ/σ_max)^snr_power — heavy at high sigma, fades to
-- zero at low sigma. Suppresses ODE manifold shearing and harmonic hum.
-- Reference: arXiv:2602.09449
--
-- RMS SERVO:
-- Downward-only energy ceiling that follows a smooth curve from rms_max
-- (high sigma) to rms_min (low sigma). Domain-tunable: image latents
-- typically sit around 0.75-0.97; audio latents around 0.3-0.7.
-- Servo is DOWNWARD ONLY — never boosts energy, only clamps excess.
--
-- SOLVER API NOTE:
-- HOT-Step-CPP passes velocity vt where dt = t_prev - t_curr is NEGATIVE
-- (stepping from high sigma to low sigma). Euler update is:
-- x_next = xt + dt * vt (same convention as STORM and OmniRelational)
-- Ancestral noise is added as: noise * |dt| * strength
-- Do NOT use xt - t_curr * vt for denoised — sign convention mismatch.
--
-- PARAMS:
-- ancestral_strength — noise injection scale. 1.0=standard ancestral, 0=pure ODE
-- noise_coherence — step-to-step noise carry. 0=fresh, 0.2=subtle temporal link
-- momentum_strength — latent velocity carry-over. 0.15=subtle, 0.3=strong
-- look_back_enabled — SNR smoother toggle
-- look_back_lambda — max smoothing weight (0.55=25-step, 0.35=35-step)
-- look_back_snr_power — falloff exponent (1.3=25-step, 1.5=35-step)
-- rms_servo — energy ceiling toggle
-- rms_target_min — servo floor at low sigma (audio: ~0.3, image: ~0.75)
-- rms_target_max — servo ceiling at high sigma (audio: ~0.7, image: ~0.97)
-- rms_servo_gain — correction aggressiveness (0.6=default, 1.0=hard snap)
-- seed — RNG seed
-- ============================================================================
solver = {
name = "md_pingpong_simple",
display = "MD PingPong Simple (Ancestral)",
description = "Ancestral Euler with momentum, noise coherence, look-back SNR smoother, and domain-tunable RMS servo. Single-branch stochastic sampler. Port of MD_PingPong_Samplers v3.5.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
params = {
{
key = "ancestral_strength",
type = "slider",
label = "Ancestral Strength",
default = 0.2,
min = 0.0,
max = 1.5,
step = 0.05,
hint = "Noise injection strength. 1.0=standard ancestral. 0=pure ODE (no noise).",
},
{
key = "noise_coherence",
type = "slider",
label = "Noise Coherence",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Step-to-step noise correlation. 0=fresh noise each step. 0.2=subtle temporal link. Keep low for audio to avoid smearing.",
},
{
key = "momentum_strength",
type = "slider",
label = "Momentum",
default = 0.1,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Latent velocity carry-over. 0.1=subtle flow continuity. 0.3=strong.",
},
{
key = "look_back_enabled",
type = "toggle",
label = "Look-Back Smoother",
default = true,
hint = "SNR-adaptive latent EMA. Suppresses ODE manifold shearing and harmonic hum. arXiv:2602.09449.",
},
{
key = "look_back_lambda",
type = "slider",
label = "Look-Back Lambda",
default = 0.55,
min = 0.1,
max = 1.0,
step = 0.05,
hint = "Max smoothing weight. Active when Look-Back Smoother is on. 0.55=25-step, 0.35=35-step.",
},
{
key = "look_back_snr_power",
type = "slider",
label = "SNR Power",
default = 1.3,
min = 0.5,
max = 3.0,
step = 0.1,
hint = "Falloff exponent. Active when Look-Back Smoother is on. Higher=smoother fade at low sigma.",
},
{
key = "rms_servo",
type = "toggle",
label = "RMS Servo",
default = true,
hint = "Downward-only energy ceiling. Prevents latent energy accumulation. Off by default — tune min/max for your domain before enabling.",
},
{
key = "rms_target_min",
type = "slider",
label = "RMS Target Min",
default = 1.0,
min = 0.1,
max = 3.0,
step = 0.05,
hint = "RMS ceiling at low sigma (late steps). Active when RMS Servo is on. ACE-Step latents ~2.0 RMS. Start at 1.2-1.8.",
},
{
key = "rms_target_max",
type = "slider",
label = "RMS Target Max",
default = 2.2,
min = 0.5,
max = 4.0,
step = 0.05,
hint = "RMS ceiling at high sigma (early steps). Active when RMS Servo is on. ACE-Step latents ~2.0 RMS. Start at 2.0-2.5.",
},
{
key = "rms_servo_gain",
type = "slider",
label = "Servo Gain",
default = 0.75,
min = 0.1,
max = 1.0,
step = 0.05,
hint = "Servo correction aggressiveness. Active when RMS Servo is on. 0.6=soft, 1.0=hard snap.",
},
{
key = "seed",
type = "slider",
label = "Seed",
default = 42,
min = 0,
max = 999999,
step = 1,
hint = "RNG seed for noise generation.",
},
},
}
-- ── State (file-level locals, reset when n changes) ──────────────────────────
local _prev_x = nil -- for momentum
local _prev_noise = nil -- for noise coherence
local _look_back_xp = nil -- for look-back smoother
local _sigma_max = nil -- captured at step 0
local _last_n = 0
-- Hoisted scratch tables — reused every step to avoid GC pressure
-- Initialized on first step or when n changes
local _noise_buf = {} -- reusable noise array
local _x_next_buf = {} -- reusable output array
local _x_copy_buf = {} -- reusable momentum copy
local EPSILON = 1e-8
-- ── Helpers ──────────────────────────────────────────────────────────────────
local function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
-- Seeded LCG RNG — deterministic, no dependency on math.random state
local function make_rng(seed)
local state = math.floor(seed) % 2147483647
if state <= 0 then state = state + 2147483646 end
return function()
state = (state * 1664525 + 1013904223) % 2147483648
return state / 2147483648.0
end
end
-- Box-Muller: two uniform [0,1] → one standard normal sample
local function normal(u1, u2)
return math.sqrt(-2.0 * math.log(math.max(u1, EPSILON))) * math.cos(2.0 * math.pi * u2)
end
-- Array RMS
local function rms(arr, n)
local s = 0.0
for i = 0, n - 1 do s = s + arr[i] * arr[i] end
return math.sqrt(s / n + EPSILON)
end
-- ── Required step() function ──────────────────────────────────────────────────
function step(xt, vt, t_curr, t_prev, n)
local step_idx_ = step_index or 0
-- Reset state on new generation: n change OR step 0 of any new run.
-- Must check step_idx_==0 because same-length generations won't trigger n change,
-- causing momentum/look-back to bleed finished audio from the previous run into
-- the noise of the new one — explosive velocity on step 1.
if n ~= _last_n or step_idx_ == 0 then
_prev_x = nil
_prev_noise = nil
_look_back_xp = nil
_sigma_max = nil
_last_n = n
-- Pre-size scratch tables for this n
for i = 0, n - 1 do
_noise_buf[i] = 0.0
_x_next_buf[i] = 0.0
_x_copy_buf[i] = 0.0
end
end
-- Read params with safe fallbacks
local anc_strength = (params and params.ancestral_strength) or 1.0
local noise_coh = (params and params.noise_coherence) or 0.0
local mom_str = (params and params.momentum_strength) or 0.1
local lb_enabled = (params and params.look_back_enabled) or false
local lb_lambda = (params and params.look_back_lambda) or 0.55
local lb_snr_power = (params and params.look_back_snr_power) or 1.3
local rms_servo_on = (params and params.rms_servo) or true
local rms_tgt_min = (params and params.rms_target_min) or 1.2
local rms_tgt_max = (params and params.rms_target_max) or 2.2
local rms_servo_gain = (params and params.rms_servo_gain) or 0.6
local seed = math.floor((params and params.seed) or 42)
-- Capture sigma_max on first step for ratio computation
if _sigma_max == nil then _sigma_max = t_curr end
local sigma_max = _sigma_max
local sigma_ratio = clamp(t_curr / math.max(sigma_max, EPSILON), 0.0, 1.0)
-- dt = t_prev - t_curr. In flow-matching, t steps DOWN (1→0),
-- HOT-Step API: t_curr=high sigma, t_prev=lower target. t_curr > t_prev. dt=t_prev-t_curr is NEGATIVE.
local dt = t_prev - t_curr
-- Save current xt for momentum (reuse hoisted buffer)
for i = 0, n - 1 do _x_copy_buf[i] = xt[i] end
-- ── NOISE GENERATION ──────────────────────────────────────────────────────
-- Write into hoisted buffer — no table allocation per step
local rng = make_rng(seed + step_idx_ * 7919)
for i = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
_noise_buf[i] = normal(u1, u2)
end
-- Noise coherence: blend with carried noise from previous step
if noise_coh > 0.0 and _prev_noise ~= nil then
for i = 0, n - 1 do
_noise_buf[i] = _noise_buf[i] * (1.0 - noise_coh) + _prev_noise[i] * noise_coh
end
end
-- Store for next step — reuse _prev_noise table if same size
if _prev_noise == nil then _prev_noise = {} end
for i = 0, n - 1 do _prev_noise[i] = _noise_buf[i] end
-- ── ANCESTRAL STEP ────────────────────────────────────────────────────────
-- Variance-preserving SDE noise for flow matching:
-- noise_scale = sqrt(t_prev^2 - t_curr^2) * anc_strength
-- t_curr > t_prev, so t_curr^2 - t_prev^2 > 0. Confirmed numerically.
local noise_scale = math.sqrt(math.max(t_curr * t_curr - t_prev * t_prev, 0.0)) * anc_strength
if noise_scale > EPSILON then
for i = 0, n - 1 do
_x_next_buf[i] = xt[i] + dt * vt[i] + _noise_buf[i] * noise_scale
end
else
for i = 0, n - 1 do _x_next_buf[i] = xt[i] + dt * vt[i] end
end
-- ── MOMENTUM ──────────────────────────────────────────────────────────────
if mom_str > 0.0 and _prev_x ~= nil then
for i = 0, n - 1 do
local vel = _x_copy_buf[i] - _prev_x[i]
_x_next_buf[i] = _x_next_buf[i] + vel * mom_str
end
end
-- ── LOOK-BACK SNR SMOOTHER ────────────────────────────────────────────────
-- λ(σ) = lb_lambda * (σ/σ_max)^lb_snr_power — heavy early, fades late.
if lb_enabled then
local lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
if _look_back_xp == nil then
_look_back_xp = {}
for i = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
_look_back_xp[i] = _x_next_buf[i] + normal(u1, u2) * sigma_max * 0.1
end
end
for i = 0, n - 1 do
_x_next_buf[i] = _x_next_buf[i] * (1.0 - lb_w) + _look_back_xp[i] * lb_w
end
-- Update look-back buffer in-place
for i = 0, n - 1 do _look_back_xp[i] = _x_next_buf[i] end
end
-- ── RMS SERVO (DOWNWARD ONLY) ─────────────────────────────────────────────
if rms_servo_on then
local rms_target = rms_tgt_min + (sigma_ratio ^ 0.6) * (rms_tgt_max - rms_tgt_min)
local cur_rms = rms(_x_next_buf, n)
if cur_rms > rms_target then
local servo_rms = cur_rms + rms_servo_gain * (rms_target - cur_rms)
local scale = servo_rms / cur_rms
for i = 0, n - 1 do _x_next_buf[i] = _x_next_buf[i] * scale end
end
end
-- ── UPDATE STATE & WRITE OUTPUT ───────────────────────────────────────────
-- Store momentum reference — reuse table, copy values
if _prev_x == nil then _prev_x = {} end
for i = 0, n - 1 do _prev_x[i] = _x_copy_buf[i] end
for i = 0, n - 1 do xt[i] = _x_next_buf[i] end
end
+557
View File
@@ -0,0 +1,557 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- ============================================================================
-- md_solver_commons.lua -- Shared infrastructure for MD solver plugins
-- MDMAchine | A&E Concepts (c) 2026
--
-- Provides:
-- Level 1: Pure helpers (stateless math, array ops, param readers)
-- Level 2: Stateful stages (identity anchor, tonal anchor, look-back,
-- RMS servo, SDE noise, safety clamp) -- each takes state table
-- Level 3: post_advance() convenience -- calls all stages in order
-- Param defs: standard param definitions solvers can append
--
-- DEFAULTS REVISED (2026-07-14 listening tests, Rob/scragnog):
-- Look-back floor REMOVED -- the 0.15 floor never faded out and smeared the
-- final detail steps (main garble source). Look-back now fades to zero.
-- look_back_enabled, identity_anchor, rms_servo: default OFF (opt-in).
-- anchor_blend back to 0.08 (was 0.12). Tonal ramp kept:
-- 0.3 + 0.7 * (1 - sigma_ratio)
-- ============================================================================
local C = {}
C.EPSILON = 1e-8
-- ═══════════════════════════════════════════════════════════════════════════
-- LEVEL 1: PURE HELPERS
-- ═══════════════════════════════════════════════════════════════════════════
function C.clamp(x, lo, hi) return math.max(lo, math.min(hi, x)) end
function C.smoothstep(x, lo, hi)
if hi <= lo then return (x >= hi) and 1.0 or 0.0 end
local t = C.clamp((x - lo) / (hi - lo), 0.0, 1.0)
return t * t * (3.0 - 2.0 * t)
end
function C.vec_norm(v, n)
local s = 0.0
for i = 0, n - 1 do s = s + v[i] * v[i] end
return math.sqrt(s)
end
function C.vec_dot(a, b, n)
local s = 0.0
for i = 0, n - 1 do s = s + a[i] * b[i] end
return s
end
function C.vec_sub_norm(a, b, n)
local s = 0.0
for i = 0, n - 1 do local d = a[i] - b[i]; s = s + d * d end
return math.sqrt(s)
end
function C.cosine_sim(a, b, n)
local dot, na, nb = 0.0, 0.0, 0.0
for i = 0, n - 1 do
dot = dot + a[i] * b[i]
na = na + a[i] * a[i]
nb = nb + b[i] * b[i]
end
return dot / (math.sqrt(na) * math.sqrt(nb) + C.EPSILON)
end
function C.shannon_entropy(a, n)
local sum_abs = 0.0
for i = 0, n - 1 do sum_abs = sum_abs + math.abs(a[i]) end
if sum_abs < C.EPSILON then return 0.0 end
local H = 0.0
for i = 0, n - 1 do
local p = math.abs(a[i]) / sum_abs
if p > C.EPSILON then H = H - p * math.log(p) end
end
return H
end
function C.vec_clone(v, n)
local c = {}
for i = 0, n - 1 do c[i] = v[i] end
return c
end
function C.fa_to_tbl(fa, n)
local t = {}
for i = 0, n - 1 do t[i] = fa[i] end
return t
end
function C.tbl_to_fa(t, fa, n)
for i = 0, n - 1 do fa[i] = t[i] end
end
function C.has_nan_inf(v, n)
for i = 0, n - 1 do
if v[i] ~= v[i] or math.abs(v[i]) == math.huge then return true end
end
return false
end
function C.rms_range(a, off, cnt)
local s = 0.0
for i = off, off + cnt - 1 do s = s + a[i] * a[i] end
return math.sqrt(s / math.max(cnt, 1) + C.EPSILON)
end
function C.rms(a, n) return C.rms_range(a, 0, n) end
function C.spectral_centroid(a, off, cnt)
local sum_mag, sum_w = 0.0, 0.0
for i = 0, cnt - 1 do
local m = math.abs(a[off + i])
sum_mag = sum_mag + m
sum_w = sum_w + m * i
end
if sum_mag < C.EPSILON then return 0.0 end
return sum_w / sum_mag
end
function C.band_energy(a, off, cnt)
local bands = {0.0, 0.0, 0.0, 0.0}
local bsize = math.floor(cnt / 4)
for b = 0, 3 do
local s = 0.0
local lo = off + b * bsize
local hi = (b == 3) and (off + cnt - 1) or (lo + bsize - 1)
for i = lo, hi do s = s + math.abs(a[i]) end
bands[b + 1] = s / math.max(hi - lo + 1, 1)
end
return bands
end
function C.make_rng(seed)
local state = math.floor(seed) % 2147483647
if state <= 0 then state = state + 2147483646 end
return function()
state = (state * 1664525 + 1013904223) % 2147483648
return state / 2147483648.0
end
end
function C.normal(u1, u2)
return math.sqrt(-2.0 * math.log(math.max(u1, C.EPSILON))) * math.cos(2.0 * math.pi * u2)
end
function C.num_param(p, key, default)
if p == nil or p[key] == nil then return default end
return tonumber(p[key]) or default
end
function C.bool_param(p, key, default)
if p == nil or p[key] == nil then return default end
return p[key]
end
-- Batch routing: reads engine globals, returns B, NPB with sanity fallback
function C.get_batch_routing(n)
local B = (batch_n and batch_n > 0) and batch_n or 1
local NPB = (n_per and n_per > 0) and n_per or n
if B * NPB ~= n then B = 1; NPB = n end
return B, NPB
end
-- ═══════════════════════════════════════════════════════════════════════════
-- RELATIONAL DECOMPOSITION (from OmniRelational V3)
-- Barbour Best Matching: separates velocity into unit direction (shape)
-- and magnitude (scale). Blends shape-recomposed velocity with raw velocity
-- using sigma-adaptive weight. Prevents any single latent component from
-- dominating. Optional Gram-Schmidt drift guard.
-- ═══════════════════════════════════════════════════════════════════════════
-- Per-batch shape decomposition: returns unit direction + L2 norm
local function decompose_shape_batch(v, off, cnt)
local s = 0.0
for i = off, off + cnt - 1 do s = s + v[i] * v[i] end
local norm = math.sqrt(s + C.EPSILON)
local inv = (norm > C.EPSILON) and (1.0 / norm) or 0.0
local shape = {}
for i = 0, cnt - 1 do shape[i] = v[off + i] * inv end
return shape, norm
end
-- Optional drift guard: projects shape onto orthogonal complement of x
local function drift_guard_batch(shape, x, off, cnt, threshold)
local norm_x = 0.0
for i = off, off + cnt - 1 do norm_x = norm_x + x[i] * x[i] end
norm_x = math.sqrt(norm_x + C.EPSILON)
if norm_x < C.EPSILON then return shape end
local inv_x = 1.0 / norm_x
local dot_sx = 0.0
for i = 0, cnt - 1 do dot_sx = dot_sx + shape[i] * (x[off + i] * inv_x) end
if math.abs(dot_sx) <= threshold then return shape end
local proj = {}
for i = 0, cnt - 1 do proj[i] = shape[i] - dot_sx * (x[off + i] * inv_x) end
local proj_norm = 0.0
for i = 0, cnt - 1 do proj_norm = proj_norm + proj[i] * proj[i] end
proj_norm = math.sqrt(proj_norm + C.EPSILON)
if proj_norm < C.EPSILON then return shape end
local inv_proj = 1.0 / proj_norm
for i = 0, cnt - 1 do proj[i] = proj[i] * inv_proj end
return proj
end
-- Apply relational decomposition to velocity (in-place, per-batch).
-- v_out is modified: blends shape-recomposed velocity with raw velocity.
-- x_curr needed only when drift_guard is enabled.
function C.apply_relational(v_out, n, B, NPB, sigma_ratio, sigma_max,
rw, sigma_power, drift_on, drift_thr, x_curr)
if rw < 1e-6 then return v_out end
-- Sigma-adaptive relational weight: fades toward pure raw at low sigma
local sr = math.min(sigma_ratio, 1.0)
local rw_eff = rw * (sr ^ sigma_power)
if rw_eff < 1e-6 then return v_out end
for b = 0, B - 1 do
local off = b * NPB
-- Decompose into shape (unit direction) and scale (L2 norm)
local shape, scale = decompose_shape_batch(v_out, off, NPB)
-- Optional drift guard
if drift_on and drift_thr < 1.0 and x_curr ~= nil then
shape = drift_guard_batch(shape, x_curr, off, NPB, drift_thr)
end
-- Blend: rw_eff * (shape * scale) + (1 - rw_eff) * raw
for i = 0, NPB - 1 do
local rel_v = shape[i] * scale
v_out[off + i] = rw_eff * rel_v + (1.0 - rw_eff) * v_out[off + i]
end
end
return v_out
end
-- ═══════════════════════════════════════════════════════════════════════════
-- STATE MANAGEMENT
-- ═══════════════════════════════════════════════════════════════════════════
-- Creates a fresh state table for one solver run.
-- Solvers call this once at the top of sample() and pass it to stage functions.
function C.new_state()
return {
-- Identity anchor
has_anchor = false,
id_buf = {},
-- Tonal anchor
tonal_captured = false,
tonal_ref_cent = {},
tonal_ref_bands = {},
-- Look-back (primary)
lb_prev = nil,
-- Look-back (secondary, for solvers that need two)
lb2_prev = nil,
}
end
-- ═══════════════════════════════════════════════════════════════════════════
-- LEVEL 2: STATEFUL STAGES
-- Each operates on x_new (table, 0-indexed), modifies in place, returns x_new.
-- ═══════════════════════════════════════════════════════════════════════════
-- Identity anchor: captures snapshot at anchor_sigma, then pulls back
function C.apply_identity_anchor(x_new, n, sigma_ratio, anchor_sigma, anchor_blend, state)
if not state.has_anchor and sigma_ratio <= anchor_sigma then
state.id_buf = C.vec_clone(x_new, n)
state.has_anchor = true
elseif state.has_anchor then
for j = 0, n - 1 do
x_new[j] = (1.0 - anchor_blend) * x_new[j] + anchor_blend * state.id_buf[j]
end
end
return x_new
end
-- Tonal anchor: per-batch spectral centroid + 4-band energy ratio correction
-- Uses detail-phase ramp: 0.3 + 0.7 * (1 - sigma_ratio)
function C.apply_tonal_anchor(x_new, n, B, NPB, sigma_ratio, anchor_sigma, tonal_str, state)
if not state.tonal_captured and sigma_ratio <= anchor_sigma then
for b = 0, B - 1 do
local off = b * NPB
state.tonal_ref_cent[b] = C.spectral_centroid(x_new, off, NPB)
state.tonal_ref_bands[b] = C.band_energy(x_new, off, NPB)
end
state.tonal_captured = true
elseif state.tonal_captured then
local tonal_ramp = 0.3 + 0.7 * (1.0 - sigma_ratio)
local eff_str = tonal_str * tonal_ramp
if eff_str > 1e-6 then
for b = 0, B - 1 do
local off = b * NPB
-- Centroid drift correction
local curr_centroid = C.spectral_centroid(x_new, off, NPB)
local drift_norm_val = (curr_centroid - state.tonal_ref_cent[b]) /
(math.abs(state.tonal_ref_cent[b]) + C.EPSILON)
local tilt = C.clamp(-drift_norm_val * eff_str, -1e-3, 1e-3)
local center = (NPB - 1) / 2.0
for j = off, off + NPB - 1 do
local dist_w = ((j - off) - center) / (center + C.EPSILON)
x_new[j] = x_new[j] + tilt * dist_w * math.abs(x_new[j])
end
-- Band energy ratio correction
local curr_bands = C.band_energy(x_new, off, NPB)
local ref_total, curr_total = 0.0, 0.0
for bb = 1, 4 do
ref_total = ref_total + state.tonal_ref_bands[b][bb]
curr_total = curr_total + curr_bands[bb]
end
if ref_total > C.EPSILON and curr_total > C.EPSILON then
local bsize = math.floor(NPB / 4)
for bb = 0, 3 do
local ref_ratio = state.tonal_ref_bands[b][bb + 1] / ref_total
local curr_ratio = curr_bands[bb + 1] / curr_total
local band_corr = C.clamp((ref_ratio - curr_ratio) * eff_str, -1e-3, 1e-3)
local blo = off + bb * bsize
local bhi = (bb == 3) and (off + NPB - 1) or (blo + bsize - 1)
for j = blo, bhi do
x_new[j] = x_new[j] + band_corr * math.abs(x_new[j])
end
end
end
end
end
end
return x_new
end
-- Look-back smoother, SNR-adaptive, fades to zero at low sigma
-- slot: "lb_prev" (primary) or "lb2_prev" (secondary)
function C.apply_look_back(x_new, n, sigma_ratio, lb_lambda, lb_snr_power, state, slot)
slot = slot or "lb_prev"
local prev = state[slot]
if prev ~= nil then
local lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
if lb_w > 1e-6 then
for j = 0, n - 1 do
x_new[j] = (1.0 - lb_w) * x_new[j] + lb_w * prev[j]
end
end
end
state[slot] = C.vec_clone(x_new, n)
return x_new
end
-- RMS servo: per-batch downward-only ceiling
function C.apply_rms_servo(x_new, n, B, NPB, sigma_ratio, rms_tgt_min, rms_tgt_max, rms_gain)
local rms_target = rms_tgt_min + (sigma_ratio ^ 0.6) * (rms_tgt_max - rms_tgt_min)
for b = 0, B - 1 do
local off = b * NPB
local cur_rms = C.rms_range(x_new, off, NPB)
if cur_rms > rms_target then
local servo_rms = cur_rms + rms_gain * (rms_target - cur_rms)
local scale = servo_rms / cur_rms
for j = off, off + NPB - 1 do x_new[j] = x_new[j] * scale end
end
end
return x_new
end
-- SDE noise injection
function C.apply_sde_noise(x_new, n, sigma_next, eta, seed, step_idx)
if eta > 0.0 and sigma_next > C.EPSILON then
local rng = C.make_rng(seed + step_idx * 7919)
local scale = sigma_next * eta
for j = 0, n - 1 do
local u1 = math.max(rng(), C.EPSILON)
local u2 = rng()
x_new[j] = x_new[j] + C.normal(u1, u2) * scale
end
end
return x_new
end
-- Safety clamp
function C.apply_safety_clamp(x_new, n, sclamp)
for j = 0, n - 1 do x_new[j] = C.clamp(x_new[j], -sclamp, sclamp) end
return x_new
end
-- ═══════════════════════════════════════════════════════════════════════════
-- LEVEL 3: POST-ADVANCE CONVENIENCE
-- Calls all stages in standard order. opts table keys:
-- f_id_anchor, anchor_sigma, anchor_blend,
-- f_tonal, tonal_str,
-- f_lookback, lb_lambda, lb_snr_power,
-- f_rms, rms_tgt_min, rms_tgt_max, rms_gain,
-- eta, seed, step_idx, sigma_next,
-- sclamp
-- ═══════════════════════════════════════════════════════════════════════════
function C.post_advance(x_new, n, B, NPB, sigma_ratio, opts, state)
-- Identity anchor
if opts.f_id_anchor then
C.apply_identity_anchor(x_new, n, sigma_ratio,
opts.anchor_sigma, opts.anchor_blend, state)
end
-- Tonal anchor
if opts.f_tonal then
C.apply_tonal_anchor(x_new, n, B, NPB, sigma_ratio,
opts.anchor_sigma, opts.tonal_str, state)
end
-- Look-back
if opts.f_lookback then
C.apply_look_back(x_new, n, sigma_ratio,
opts.lb_lambda, opts.lb_snr_power, state, "lb_prev")
end
-- RMS servo
if opts.f_rms then
C.apply_rms_servo(x_new, n, B, NPB, sigma_ratio,
opts.rms_tgt_min, opts.rms_tgt_max, opts.rms_gain)
end
-- SDE noise
C.apply_sde_noise(x_new, n, opts.sigma_next, opts.eta, opts.seed, opts.step_idx)
-- Safety clamp
C.apply_safety_clamp(x_new, n, opts.sclamp)
return x_new
end
-- ═══════════════════════════════════════════════════════════════════════════
-- PARAM DEFINITIONS
-- Solvers call C.append_common_params(params_table) to add these.
-- Defaults reflect cross-cutting fixes (anchor_blend=0.12, rms=true).
-- ═══════════════════════════════════════════════════════════════════════════
C.RELATIONAL_PARAMS = {
{ key = "relational_weight", type = "slider", label = "Relational Weight",
default = 0.0, min = 0.0, max = 1.0, step = 0.05,
hint = "Barbour Best Matching: shape/scale decomposition on velocity. 0 = off (raw velocity). 0.3-0.5 = balanced. Sigma-adaptive: fades to raw at low sigma." },
{ key = "relational_sigma_power", type = "slider", label = "Relational Sigma Decay",
default = 1.0, min = 0.25, max = 4.0, step = 0.25,
hint = "How fast relational weight fades. 1.0 = linear. 2.0 = quadratic (faster fade)." },
{ key = "drift_guard", type = "toggle", label = "Drift Guard",
default = false,
hint = "Gram-Schmidt projection prevents velocity reinforcing existing latent structure." },
{ key = "drift_threshold", type = "slider", label = "Drift Threshold",
default = 0.85, min = 0.1, max = 1.0, step = 0.05,
hint = "Cosine similarity ceiling before drift guard fires." },
}
C.ANCHOR_PARAMS = {
{ key = "identity_anchor", type = "toggle", label = "Identity Anchor",
default = false,
hint = "Captures latent snapshot at anchor_sigma. Gently pulls output back." },
{ key = "anchor_sigma", type = "slider", label = "Anchor Sigma",
default = 0.5, min = 0.1, max = 0.9, step = 0.05,
hint = "Sigma fraction for identity/tonal anchor capture." },
{ key = "anchor_blend", type = "slider", label = "Anchor Blend",
default = 0.08, min = 0.01, max = 0.30, step = 0.01,
hint = "Pull strength toward identity anchor snapshot." },
{ key = "tonal_anchor", type = "toggle", label = "Tonal Anchor",
default = true,
hint = "Per-batch spectral centroid + 4-band energy ratio drift correction. Ramps up in detail phase." },
{ key = "tonal_strength", type = "slider", label = "Tonal Strength",
default = 0.20, min = 0.0, max = 1.0, step = 0.05,
hint = "Tonal correction scale. Detail-phase ramp built in. Hard-capped 0.1%/step." },
}
C.LOOKBACK_PARAMS = {
{ key = "look_back_enabled", type = "toggle", label = "Look-Back Smoother",
default = false, hint = "SNR-adaptive latent EMA. Fades to zero at low sigma." },
{ key = "look_back_lambda", type = "slider", label = "Look-Back Lambda",
default = 0.15, min = 0.05, max = 1.0, step = 0.05, hint = "Max smoothing at high sigma." },
{ key = "look_back_snr_power", type = "slider", label = "Look-Back SNR Power",
default = 1.3, min = 0.5, max = 3.0, step = 0.1, hint = "Falloff exponent." },
}
C.RMS_PARAMS = {
{ key = "rms_servo", type = "toggle", label = "RMS Servo",
default = false, hint = "Per-batch downward-only RMS ceiling. ACE-Step latents run ~2.0 RMS -- calibrate targets before enabling." },
{ key = "rms_target_min", type = "slider", label = "RMS Target Min",
default = 1.2, min = 0.1, max = 3.0, step = 0.05, hint = "RMS ceiling at low sigma." },
{ key = "rms_target_max", type = "slider", label = "RMS Target Max",
default = 2.5, min = 0.5, max = 5.0, step = 0.05, hint = "RMS ceiling at high sigma." },
{ key = "rms_servo_gain", type = "slider", label = "RMS Servo Gain",
default = 0.6, min = 0.1, max = 1.0, step = 0.05, hint = "Servo correction aggressiveness." },
}
C.SDE_PARAMS = {
{ key = "eta", type = "slider", label = "Noise Injection (0 = ODE)",
default = 0.0, min = 0.0, max = 1.0, step = 0.05, hint = "Post-step SDE noise." },
{ key = "seed", type = "slider", label = "Seed",
default = 42, min = 0, max = 999999, step = 1, hint = "RNG seed." },
{ key = "safety_clamp", type = "slider", label = "Safety Clamp",
default = 2.5, min = 1.0, max = 5.0, step = 0.1, hint = "Max abs latent value." },
}
C.VERBOSE_PARAM = {
{ key = "verbose", type = "toggle", label = "Verbose Logging",
default = false, hint = "Per-step diagnostics." },
}
-- Appends param definitions to a solver's params table.
-- Usage: C.append_common_params(solver.params)
-- Adds: anchor, look-back, RMS, SDE, verbose (in that order)
function C.append_common_params(params_table)
local sets = { C.RELATIONAL_PARAMS, C.ANCHOR_PARAMS, C.LOOKBACK_PARAMS, C.RMS_PARAMS, C.SDE_PARAMS, C.VERBOSE_PARAM }
for _, set in ipairs(sets) do
for _, p in ipairs(set) do
params_table[#params_table + 1] = p
end
end
end
-- Reads all common params from the params global into an opts table
-- suitable for passing to post_advance().
function C.read_common_opts(p)
return {
-- Relational
rw = C.num_param(p, "relational_weight", 0.0),
rw_sigma_pow = C.num_param(p, "relational_sigma_power", 1.0),
drift_on = C.bool_param(p, "drift_guard", false),
drift_thr = C.num_param(p, "drift_threshold", 0.85),
-- Anchors
f_id_anchor = C.bool_param(p, "identity_anchor", false),
anchor_sigma = C.num_param(p, "anchor_sigma", 0.5),
anchor_blend = C.num_param(p, "anchor_blend", 0.08),
f_tonal = C.bool_param(p, "tonal_anchor", true),
tonal_str = C.num_param(p, "tonal_strength", 0.20),
f_lookback = C.bool_param(p, "look_back_enabled", false),
lb_lambda = C.num_param(p, "look_back_lambda", 0.15),
lb_snr_power = C.num_param(p, "look_back_snr_power", 1.3),
f_rms = C.bool_param(p, "rms_servo", false),
rms_tgt_min = C.num_param(p, "rms_target_min", 1.2),
rms_tgt_max = C.num_param(p, "rms_target_max", 2.5),
rms_gain = C.num_param(p, "rms_servo_gain", 0.6),
eta = C.num_param(p, "eta", 0.0),
seed = math.floor(C.num_param(p, "seed", 42)),
sclamp = C.num_param(p, "safety_clamp", 2.5),
verbose = C.bool_param(p, "verbose", false),
-- These are set per-step by the solver before calling post_advance:
sigma_next = 0.0,
step_idx = 0,
}
end
return C
+574
View File
@@ -0,0 +1,574 @@
--[[
md_storm_core.lua
STORM -- Stabilized Taylor Oscillation with Runge-Kutta Memory
V4: Commons integration + relational decomposition
© 2026 Alexander Allan (MDMAchine) | A&E Concepts
GPL v3
--]]
local C = require("md_solver_commons")
solver = {
name = "md_storm_V4",
display = "MD STORM V4",
description = "Adaptive STORK/DPM++3M hybrid with relational velocity decomposition, pseudo-LTE error estimation, look-back SNR smoother, stiffness dispatch.",
accent = "cyan",
nfe = 0,
order = 5,
needs_model = false,
stateful = true,
stochastic = false,
owns_loop = true,
params = {
-- ── Pseudo-LTE Research Controls ──
{ key = "attenuation_k", type = "slider", label = "Attenuation K",
default = 25.0, min = 1.0, max = 30.0, step = 0.5,
hint = "Exponential decay factor for highest-order memory term in pseudo-LTE estimation." },
{ key = "hyst_downgrade", type = "slider", label = "Downgrade Threshold",
default = 0.40, min = 0.10, max = 0.80, step = 0.02,
hint = "Pseudo-LTE above which solver drops order. Lower = more cautious." },
{ key = "hyst_upgrade", type = "slider", label = "Upgrade Threshold",
default = 0.25, min = 0.05, max = 0.40, step = 0.01,
hint = "Pseudo-LTE below which solver regains trust and considers upgrading order." },
{ key = "stability_window", type = "slider", label = "Stability Window",
default = 4, min = 1, max = 8, step = 1,
hint = "Consecutive stable steps required before order upgrade. Higher = more conservative." },
-- ── Stiffness Detection ──
{ key = "stiffness_threshold", type = "slider", label = "Stiffness Threshold",
default = 0.15, min = 0.05, max = 0.50, step = 0.01,
hint = "Base threshold for STORK/DPM++ dispatch. Lower = more STORK (precise), higher = more DPM++ (smooth)." },
{ key = "stiffness_hysteresis", type = "slider", label = "Stiffness Hysteresis",
default = 0.05, min = 0.0, max = 0.20, step = 0.01,
hint = "Dead zone preventing rapid mode switching. Higher = stickier dispatch." },
{ key = "stiffness_ema", type = "slider", label = "Stiffness EMA",
default = 0.4, min = 0.05, max = 0.8, step = 0.05,
hint = "EMA smoothing for stiffness ratio. Lower = more reactive, higher = more stable." },
-- ── Look-Back Smoother ──
{ key = "look_back_lambda", type = "slider", label = "Look-Back Lambda",
default = 0.15, min = 0.0, max = 1.0, step = 0.01,
hint = "Inter-step smoothing strength. 0=off (raw). 0.15=standard. Higher = smoother but softer detail." },
{ key = "look_back_snr_power", type = "slider", label = "Look-Back SNR Power",
default = 1.2, min = 0.5, max = 3.0, step = 0.1,
hint = "Concentrates smoothing on early noisy steps. Higher = heavier early smoothing, leaves late detail alone." },
-- ── Solver Order & Cache ──
{ key = "rk_order", type = "select", label = "Precision Level",
default = "auto",
options = {
{ value = "auto", label = "Auto (Recommended)" },
{ value = "2", label = "Low (RK2)" },
{ value = "3", label = "Medium (RK3)" },
{ value = "4", label = "High (RK4)" },
{ value = "5", label = "Maximum (RK5)" },
},
hint = "Max STORK order. Auto ramps up as cache fills. DPM++3M always uses order 3." },
{ key = "cache_depth", type = "slider", label = "Cache Depth",
default = 5, min = 2, max = 10, step = 1,
hint = "Velocity history size. More = higher order available, diminishing returns past 5." },
-- ── Diagnostics ──
{ key = "telemetry", type = "toggle", label = "Output Telemetry",
default = false,
hint = "Print JSON diagnostic logs to console at generation end." },
{ key = "verbose", type = "toggle", label = "Verbose Logging",
default = false,
hint = "Print per-step solver decisions to console (debug)." },
{ key = "relational_weight", type = "slider", label = "Relational Weight",
default = 0.0, min = 0.0, max = 1.0, step = 0.05,
hint = "Barbour Best Matching velocity decomposition. 0 = off. 0.3-0.5 = balanced." },
{ key = "relational_sigma_power", type = "slider", label = "Relational Sigma Decay",
default = 1.0, min = 0.25, max = 4.0, step = 0.25,
hint = "How fast relational weight fades. 1.0 = linear." },
},
}
-- ─────────────────────────────────────────────────────────────────────────────
-- HELPERS (aliased from md_solver_commons)
-- ─────────────────────────────────────────────────────────────────────────────
local EPSILON = C.EPSILON
local fa_to_tbl = C.fa_to_tbl
local tbl_to_fa = C.tbl_to_fa
local vec_norm = C.vec_norm
local vec_sub_norm = C.vec_sub_norm
local vec_dot = C.vec_dot
local vec_clone = C.vec_clone
local has_nan_inf_tbl = C.has_nan_inf
local clamp = C.clamp
-- ─────────────────────────────────────────────────────────────────────────────
-- LOOK-BACK SMOOTHER (arXiv:2602.09449)
-- ─────────────────────────────────────────────────────────────────────────────
local function look_back_smooth(x_curr, x_prev, sigma_curr, sigma_max, lambda_base, snr_power, n)
if x_prev == nil then return x_curr, 0.0 end
local ratio = math.min(sigma_curr / math.max(sigma_max, 1e-8), 1.0)
local lam = lambda_base * math.max(ratio ^ snr_power, 0.15)
local out = {}
for i = 0, n - 1 do out[i] = (1.0 - lam) * x_curr[i] + lam * x_prev[i] end
return out, lam
end
-- ─────────────────────────────────────────────────────────────────────────────
-- STIFFNESS DETECTION (from deployed STORM v3.0)
-- ─────────────────────────────────────────────────────────────────────────────
local function compute_stiffness(v_curr, v_cache, step_idx, baseline, threshold, ema_alpha, n_calib, n)
if #v_cache < 1 then return true, baseline, nil end
local v_prev = v_cache[#v_cache].v
local norm_delta = vec_sub_norm(v_curr, v_prev, n)
local norm_curr = vec_norm(v_curr, n) + 1e-8
local raw_ratio = norm_delta / norm_curr
local prev_ema = baseline.ema or raw_ratio
local smoothed = ema_alpha * raw_ratio + (1.0 - ema_alpha) * prev_ema
baseline.ema = smoothed
local dot = vec_dot(v_curr, v_prev, n)
local nc = vec_norm(v_curr, n)
local np_ = vec_norm(v_prev, n)
local cos_sim = dot / (nc * np_ + 1e-8)
if step_idx < n_calib then
baseline.sum = (baseline.sum or 0.0) + smoothed
baseline.count = (baseline.count or 0) + 1
baseline.last_ratio = smoothed
return true, baseline, cos_sim
end
local bmean = baseline.sum / math.max(baseline.count, 1)
local adap_thr = threshold * (bmean / 0.15)
adap_thr = clamp(adap_thr, 0.05, 0.50)
local stiff = smoothed > adap_thr
baseline.last_ratio = smoothed
baseline.last_threshold = adap_thr
return stiff, baseline, cos_sim
end
-- ─────────────────────────────────────────────────────────────────────────────
-- STORK MULTI-ORDER (AB2-AB5, cosine-similarity damping, from deployed STORM)
-- ─────────────────────────────────────────────────────────────────────────────
local function stork_step(v_cache, x, sigma_curr, sigma_next, v_curr, rk_order, n)
local dt = sigma_next - sigma_curr
local n_cache = #v_cache
local actual_order
if rk_order == "auto" then
actual_order = (n_cache >= 1) and math.min(n_cache + 1, 5) or 1
else
actual_order = (n_cache >= 1) and math.min(tonumber(rk_order), n_cache + 1) or 1
end
actual_order = math.max(actual_order, 1)
-- Euler fallback
if n_cache < 1 or actual_order <= 1 then
local x_next = {}
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
return x_next, 1
end
local e0 = v_cache[#v_cache]
local v_prev_0 = e0.v
local sigma_prev = e0.sigma
-- Cosine-similarity damping (deployed STORM's approach)
local dot = vec_dot(v_curr, v_prev_0, n)
local nc = vec_norm(v_curr, n)
local np_ = vec_norm(v_prev_0, n)
local cos_sim = dot / (nc * np_ + 1e-8)
local damping = clamp(cos_sim, 0.0, 1.0)
local denom = sigma_curr - sigma_prev
if math.abs(denom) < 1e-8 then
local x_next = {}
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
return x_next, 2
end
local alpha = (sigma_next - sigma_curr) / denom
local x_next = {}
if actual_order == 2 then
for i = 0, n - 1 do
local v_extrap = v_curr[i] + (alpha * damping) * (v_curr[i] - v_prev_0[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * v_extrap)
end
elseif actual_order == 3 and n_cache >= 2 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 then
for i = 0, n - 1 do
local ve = v_curr[i] + (alpha * damping) * (v_curr[i] - v1[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * ve)
end
actual_order = 2
else
local c0 = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do
local v_pred = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (v_pred - v_curr[i]))
end
end
elseif actual_order == 4 and n_cache >= 3 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local v3, s3 = v_cache[#v_cache - 2].v, v_cache[#v_cache - 2].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
local h2 = s2 - s3
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 or math.abs(h2) < 1e-8 then
local c0 = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 3
else
local c0 = 1.0 + dt/(2.0*h) + dt^2/(3.0*h*h1) + dt^3/(4.0*h*h1*h2)
local c1 = -(dt/(2.0*h)) * (1.0 + dt/h1 + dt^2/(2.0*h1*h2))
local c2 = (dt^2/(3.0*h*h1)) * (1.0 + dt/(2.0*h2))
local c3 = -(dt^3) / (4.0*h*h1*h2)
for i = 0, n - 1 do
local vp = c0*v_curr[i] + c1*v1[i] + c2*v2[i] + c3*v3[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
end
elseif actual_order >= 5 and n_cache >= 4 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local v3, s3 = v_cache[#v_cache - 2].v, v_cache[#v_cache - 2].sigma
local v4, s4 = v_cache[#v_cache - 3].v, v_cache[#v_cache - 3].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
local h2 = s2 - s3
local h3 = s3 - s4
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 or math.abs(h2) < 1e-8 or math.abs(h3) < 1e-8 then
local c0 = 1.0 + dt/(2.0*h) + dt^2/(3.0*h*h1) + dt^3/(4.0*h*h1*h2)
local c1 = -(dt/(2.0*h)) * (1.0 + dt/h1 + dt^2/(2.0*h1*h2))
local c2 = (dt^2/(3.0*h*h1)) * (1.0 + dt/(2.0*h2))
local c3 = -(dt^3) / (4.0*h*h1*h2)
for i = 0, n - 1 do
local vp = c0*v_curr[i] + c1*v1[i] + c2*v2[i] + c3*v3[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 4
else
local c0 = 1.0 + dt/(2.0*h) + dt^2/(3.0*h*h1) + dt^3/(4.0*h*h1*h2) + dt^4/(5.0*h*h1*h2*h3)
local c1 = -(dt/(2.0*h)) * (1.0 + dt/h1 + dt^2/(2.0*h1*h2) + dt^3/(3.0*h1*h2*h3))
local c2 = (dt^2/(3.0*h*h1)) * (1.0 + dt/(2.0*h2) + dt^2/(3.0*h2*h3))
local c3 = -(dt^3/(4.0*h*h1*h2)) * (1.0 + dt/(2.0*h3))
local c4 = dt^4 / (5.0*h*h1*h2*h3)
for i = 0, n - 1 do
local vp = c0*v_curr[i] + c1*v1[i] + c2*v2[i] + c3*v3[i] + c4*v4[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 5
end
else
-- Fallback AB2
for i = 0, n - 1 do
local ve = v_curr[i] + (alpha * damping) * (v_curr[i] - v_prev_0[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * ve)
end
actual_order = 2
end
return x_next, actual_order
end
-- ─────────────────────────────────────────────────────────────────────────────
-- DPM++3M (smooth schedule path, from deployed STORM)
-- ─────────────────────────────────────────────────────────────────────────────
local function dpmpp3m_step(v_cache, x, sigma_curr, sigma_next, v_curr, n)
local dt = sigma_next - sigma_curr
local x_next = {}
if #v_cache >= 2 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 then
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
else
local cc = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do x_next[i] = x[i] + dt * (cc * v_curr[i] + c1 * v1[i] + c2 * v2[i]) end
end
elseif #v_cache >= 1 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local h = sigma_curr - s1
if math.abs(h) < 1e-8 then
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
else
for i = 0, n - 1 do x_next[i] = x[i] + dt * (v_curr[i] + (dt / (2.0 * h)) * (v_curr[i] - v1[i])) end
end
else
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
end
return x_next
end
-- ─────────────────────────────────────────────────────────────────────────────
-- PSEUDO-LTE ESTIMATION (Phase 1 research addition)
-- Computes kinetic-floored relative error from the highest-order AB term.
-- Returns rel_epsilon and attenuation weight w_t for telemetry.
-- ─────────────────────────────────────────────────────────────────────────────
local function estimate_pseudo_lte(v_cache, v_curr, ema_vel, k_atten, n)
local n_cache = #v_cache
if n_cache < 1 then return 0.0, 1.0 end
-- Use the oldest cached velocity as the "highest order contribution" proxy
local v_oldest = v_cache[1].v
local oldest_norm = vec_norm(v_oldest, n)
-- Extrapolation norm: current velocity (proxy for full polynomial magnitude)
local extrap_norm = vec_norm(v_curr, n)
local safe_den = math.max(extrap_norm, ema_vel * 0.5)
local rel_epsilon = oldest_norm / (safe_den + EPSILON)
local w_t = math.exp(-k_atten * rel_epsilon)
return rel_epsilon, w_t
end
-- ─────────────────────────────────────────────────────────────────────────────
-- SAMPLE — Full-loop entry point
-- ─────────────────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
local p = params or {}
-- Pseudo-LTE research params
local k_atten = p.attenuation_k or 10.0
local thr_down = p.hyst_downgrade or 0.40
local thr_up = p.hyst_upgrade or 0.15
local stab_window = math.floor(p.stability_window or 3)
-- Stiffness detection params
local stiff_thr = p.stiffness_threshold or 0.15
local hyst = p.stiffness_hysteresis or 0.05
local ema_a = p.stiffness_ema or 0.3
-- Look-back smoother params
local lb_lambda = p.look_back_lambda or 0.15
local lb_snr_pow = p.look_back_snr_power or 1.5
-- Solver order & cache params
local rk_order = p.rk_order or "auto"
local depth_max = math.floor(p.cache_depth or 5)
-- Diagnostics
local do_tele = p.telemetry or false
local verbose = p.verbose or false
local rw = C.num_param(p, "relational_weight", 0.0)
local rw_sig_pow = C.num_param(p, "relational_sigma_power", 1.0)
-- Derived constants
local calib_frac = 0.12
local ns = #schedule
-- Engine schedule has NO trailing 0 (fix ported from 46c081e): iterate all ns
-- entries so the last iteration gets sigma_next = 0.0 and the terminal branch
-- performs the final x0 projection. With ns - 1 that branch is dead code and
-- the output keeps ~final-sigma noise.
local n_steps = ns
if n_steps < 1 then return end
local v_cache = {}
local baseline = { sum = 0.0, count = 0 }
local sigma_max = schedule[1]
local n_calib = math.max(2, math.min(5, math.floor(n_steps * calib_frac)))
local lb_enabled = (lb_lambda > 0)
-- Pseudo-LTE state
local current_order = 1
local stability_counter = 0
local ema_vel = 0.0
local telemetry_data = {}
if verbose then
print(string.format("[STORM] Schedule: %d steps | Calib: %d | RK: %s | Cache: %d | LB: %.2f^%.1f",
n_steps, n_calib, tostring(rk_order), depth_max, lb_lambda, lb_snr_pow))
end
-- Working copy
local x = fa_to_tbl(xt, n)
-- Seed x_prev for look-back (jittered copy, same as deployed STORM)
local x_prev_lb = nil
if lb_enabled then
x_prev_lb = {}
for i = 0, n - 1 do
local u1 = math.max(1e-12, math.random())
local u2 = math.random()
local r = (sigma_max * 0.1) * math.sqrt(-2.0 * math.log(u1))
x_prev_lb[i] = x[i] + r * math.cos(2 * math.pi * u2)
end
end
-- Model eval helper
local function eval_at(x_tbl, t_val)
tbl_to_fa(x_tbl, xt, n)
model_fn(xt, t_val)
return fa_to_tbl(vt_buf, n)
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
-- Terminal step: Euler denoise to x0
if sigma_next == 0.0 then
local v_final = eval_at(x, sigma_curr)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
if verbose then
print(string.format("[STORM] Step %02d: FINAL (Euler terminal)", i - 1))
end
break
end
-- Snapshot for look-back (before this step modifies x)
local x_prev_lb_before = nil
if lb_enabled then x_prev_lb_before = vec_clone(x, n) end
-- Evaluate velocity
local v_curr = eval_at(x, sigma_curr)
-- Relational decomposition
local sigma_ratio = clamp(sigma_curr / math.max(sigma_max, EPSILON), 0.0, 1.0)
if rw > 0 then
C.apply_relational(v_curr, n, 1, n, sigma_ratio, sigma_max,
rw, rw_sig_pow, false, 0.85, x)
end
local cur_vel_norm = vec_norm(v_curr, n)
-- Update kinetic floor EMA
if i == 1 then ema_vel = cur_vel_norm
else ema_vel = 0.8 * ema_vel + 0.2 * cur_vel_norm end
-- ── Stiffness detection (deployed STORM) ──
local stiff, cos_sim_out
if #v_cache >= 1 then
stiff, baseline, cos_sim_out = compute_stiffness(
v_curr, v_cache, i - 1, baseline, stiff_thr, ema_a, n_calib, n)
else
stiff, cos_sim_out = true, nil
end
-- Hysteresis: prevent rapid STORK↔DPM++ switching
local prev_mode = baseline.prev_mode or "STORK"
if prev_mode == "DPM++" and not stiff then
if (baseline.last_ratio or 0) > (baseline.last_threshold or stiff_thr) + hyst then
stiff = true
end
end
-- ── Dispatch: STORK (stiff) or DPM++3M (smooth) ──
local x_next, actual_order, mode
if stiff then
x_next, actual_order = stork_step(v_cache, x, sigma_curr, sigma_next, v_curr, rk_order, n)
mode = "STORK"
else
x_next = dpmpp3m_step(v_cache, x, sigma_curr, sigma_next, v_curr, n)
mode = "DPM++"
actual_order = 3
end
-- ── Pseudo-LTE estimation (Phase 1 research) ──
local rel_epsilon, w_t = estimate_pseudo_lte(v_cache, v_curr, ema_vel, k_atten, n)
-- Asymmetric hysteresis order management
if rel_epsilon > thr_down then
current_order = math.max(1, current_order - 1)
stability_counter = 0
elseif rel_epsilon < thr_up then
stability_counter = stability_counter + 1
if stability_counter >= stab_window then
current_order = math.min(current_order + 1, 5)
stability_counter = 0
end
else
stability_counter = 0
end
-- Verbose logging
if verbose then
local lr = baseline.last_ratio or 0.0
local lt = baseline.last_threshold or stiff_thr
local cs = cos_sim_out and string.format("%.4f", cos_sim_out) or "N/A"
local tag = (stiff and prev_mode == "DPM++") and " -> CURVATURE SPIKE" or ""
print(string.format("[STORM] Step %02d: %-5s RK%d | Stiff: %.3f/%.3f | cos: %s | LTE: %.4f w=%.3f ord=%d%s",
i - 1, mode, actual_order, lr, lt, cs, rel_epsilon, w_t, current_order, tag))
end
-- Telemetry
if do_tele then
table.insert(telemetry_data, string.format(
'{"step":%d,"sigma":%.4f,"mode":"%s","rk_order":%d,"stiff_ratio":%.5f,"stiff_thr":%.5f,"cos_sim":%s,"rel_eps":%.5f,"w_t":%.5f,"lte_order":%d,"vel_norm":%.5f}',
i, sigma_curr, mode, actual_order,
baseline.last_ratio or 0, baseline.last_threshold or stiff_thr,
cos_sim_out and string.format("%.5f", cos_sim_out) or "null",
rel_epsilon, w_t, current_order, cur_vel_norm))
end
-- ── NaN guard ──
if has_nan_inf_tbl(x_next, n) then
print(string.format("[STORM] NaN/Inf at step %d. Flushing cache, Euler fallback.", i - 1))
local dt = sigma_next - sigma_curr
x_next = {}
for j = 0, n - 1 do x_next[j] = x[j] + dt * v_curr[j] end
v_cache = {}
baseline.prev_mode = "STORK"
current_order = 1
stability_counter = 0
end
-- Update cache
table.insert(v_cache, { v = v_curr, sigma = sigma_curr })
while #v_cache > depth_max do table.remove(v_cache, 1) end
baseline.prev_mode = mode
x = x_next
-- ── Look-Back smoothing ──
if lb_enabled and x_prev_lb ~= nil then
local lam
x, lam = look_back_smooth(x, x_prev_lb, sigma_curr, sigma_max, lb_lambda, lb_snr_pow, n)
if verbose then
print(string.format("[STORM] LookBack lambda=%.4f @ sigma=%.3f", lam, sigma_curr))
end
end
x_prev_lb = x_prev_lb_before
-- Write back for on_step hooks (DCW, repaint)
tbl_to_fa(x, xt, n)
tbl_to_fa(v_curr, vt_buf, n)
-- Report step
if on_step(i - 1, sigma_curr, sigma_next) then return end
-- Re-read in case hooks modified xt
x = fa_to_tbl(xt, n)
end
-- Write final x0
tbl_to_fa(x, xt, n)
if do_tele then
print("\nSTORM_DATA:[" .. table.concat(telemetry_data, ",") .. "]\n")
end
end
+739
View File
@@ -0,0 +1,739 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details: https://www.gnu.org/licenses/
-- ============================================================================
-- MD Trajectory Anchor v2.0 — Latent Path Stabilizer
-- MDMAchine | A&E Concepts © 2026
--
-- A stateful trajectory stabilization solver for HOT-Step-CPP.
-- Runs via step() — NOT owns_loop. The guider pipeline (APG, ADG, PMG, etc.)
-- remains fully active. Receives the pre-guided vt from the engine and applies
-- stateful corrections on top of the advancing latent.
--
-- WHY step() INSTEAD OF owns_loop:
-- All features here (inertia, concept lock, anchors, look-back) only need
-- xt — the latent tensor — which step() provides directly. owns_loop was
-- used in V1/V2 because guidance features needed cond/uncond, but those
-- have been removed. step() is the correct, minimal contract for this work.
-- Guiders run normally alongside this solver.
--
-- PIPELINE PER STEP:
-- xt → Euler advance (xt + dt * vt) → _out_buf
-- → [entropy measurement] — Shannon H from xt (step 0+)
-- → [latent pressure] — entropy×RMS correction (toggle, off)
-- → [memory buffer] — 3-step ring buffer smoothing
-- → [inertia engine] — EMA velocity carry-over
-- → [concept lock] — stability mask, sigma-adaptive
-- → [identity anchor] — mid-sigma snapshot pull-back
-- → [tonal anchor] — spectral centroid correction, sigma-adaptive
-- → [look-back smoother] — SNR-adaptive EMA (arXiv:2602.09449)
-- → [RMS servo] — descending RMS ceiling (toggle, off)
-- → [safety clamp + NaN guard] — abs ceiling + Euler rollback on NaN
-- → write _out_buf to xt
--
-- STATE RESET:
-- All module-level state resets on step_index == 0 OR n change.
-- Same-length consecutive generations do not bleed state.
--
-- INERTIA EMA FIX (V2 regression):
-- V2 computed: vel = 0.8 * vel + 0.2 * vel (no-op, same buffer).
-- V1.0 uses two separate buffers: _vel_old_buf (EMA) and _vel_raw_buf (delta).
-- EMA: _vel_old_buf[i] = 0.8 * _vel_old_buf[i] + 0.2 * _vel_raw_buf[i]
--
-- SIGMA-ADAPTIVE FEATURES (from OmniRelational V3 pattern):
-- concept lock strength = full * (sigma_ratio ^ concept_sigma_power)
-- tonal correction scale = tonal_strength * sigma_ratio
-- look-back weight = lb_lambda * (sigma_ratio ^ lb_snr_power)
-- All three are heavy at high sigma (structure phase), fade to zero at sigma=0.
--
-- PARAMS:
-- warmup_steps — skip stateful features for first N steps
-- inertia_engine — EMA latent velocity carry-over
-- inertia_alpha — base velocity coefficient, entropy-modulated
-- memory_buffer — 3-step ring buffer output smoothing
-- memory_blend — history blend fraction
-- concept_lock — stability mask on settled regions
-- concept_sigma_power — how fast lock fades with sigma
-- identity_anchor — captures xt snapshot at anchor_sigma, pulls back
-- anchor_sigma — sigma fraction at which anchors are captured
-- anchor_blend — pull strength toward identity anchor
-- tonal_anchor — spectral centroid drift correction
-- tonal_strength — correction scale (per-element hard cap 0.1%)
-- look_back_enabled — SNR-adaptive latent EMA smoother
-- look_back_lambda — max smoothing weight at high sigma
-- look_back_snr_power — falloff exponent
-- rms_servo — descending RMS ceiling (off by default)
-- rms_target_min — RMS ceiling at low sigma
-- rms_target_max — RMS ceiling at high sigma
-- rms_servo_gain — servo correction aggressiveness
-- latent_pressure — entropy×RMS target correction (off by default)
-- pressure_target_rms — RMS target for pressure correction
-- pressure_target_entropy — entropy target for pressure weighting
-- safety_clamp — max absolute latent value
-- ============================================================================
solver = {
name = "md_trajectory_anchor",
display = "MD Trajectory Anchor",
description = "Latent path stabilizer. step() solver — guiders stay active. Inertia engine, concept lock, identity anchor, tonal anchor, memory buffer, look-back smoother, RMS servo. All stateful. State resets cleanly between generations. Eta=0 for pure ODE, eta>0 for SDE noise.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
params = {
-- ── Warmup ────────────────────────────────────────────────────────────
{
key = "warmup_steps",
type = "slider",
label = "Warmup Steps",
default = 2,
min = 0,
max = 6,
step = 1,
hint = "Skip stateful features (inertia, concept lock, anchors) for first N steps. Latent is mostly noise at high sigma — anchoring into chaos makes things worse. 2=recommended. 0=always active.",
},
-- ── Inertia Engine ────────────────────────────────────────────────────
{
key = "inertia_engine",
type = "toggle",
label = "Inertia Engine",
default = true,
hint = "EMA-smoothed latent velocity carry-over. Adds step-to-step momentum — reduces abrupt trajectory direction changes. Alpha is entropy-modulated: less inertia when latent is already structured (low entropy).",
},
{
key = "inertia_alpha",
type = "slider",
label = "Inertia Alpha",
default = 0.15,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Base velocity carry-over coefficient. 0.10=subtle. 0.20=noticeable. 0.30+=strong. Scaled down at runtime when entropy is low (structured latent needs less push).",
},
-- ── Memory Buffer ─────────────────────────────────────────────────────
{
key = "memory_buffer",
type = "toggle",
label = "Memory Buffer",
default = true,
hint = "Blends last 3 step outputs into the current step output. Suppresses step-to-step jitter without redirecting the trajectory. Ring buffer, zero-alloc.",
},
{
key = "memory_blend",
type = "slider",
label = "Memory Blend",
default = 0.12,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Fraction of 3-step history mean blended into each step output. 0.12=subtle. 0.25+=heavy smoothing (may soften transients in audio).",
},
-- ── Concept Lock ──────────────────────────────────────────────────────
{
key = "concept_lock",
type = "toggle",
label = "Concept Lock",
default = true,
hint = "Stability mask: elements with small step-to-step delta are pulled back toward their previous state. Protects settled structure from noise. Sigma-adaptive — full strength at high sigma, fades at low sigma (detail phase).",
},
{
key = "concept_sigma_power",
type = "slider",
label = "Concept Lock Sigma Power",
default = 1.0,
min = 0.25,
max = 3.0,
step = 0.25,
hint = "Controls how fast concept lock fades as sigma decreases. 1.0=linear decay. 2.0=quadratic (lock concentrated on early structure steps only). 0.5=slow fade (lock persists into detail steps).",
},
-- ── Identity Anchor ───────────────────────────────────────────────────
{
key = "identity_anchor",
type = "toggle",
label = "Identity Anchor",
default = true,
hint = "Captures a snapshot of xt at anchor_sigma, then gently pulls toward it on all subsequent steps. Prevents late-stage structural drift. Tonal anchor fires at the same sigma.",
},
{
key = "anchor_sigma",
type = "slider",
label = "Anchor Sigma",
default = 0.5,
min = 0.1,
max = 0.9,
step = 0.05,
hint = "Sigma level (as fraction of sigma_max) at which the identity and tonal anchors are captured. 0.5=mid-generation. Lower=locks in more detail. Higher=locks coarser structure only.",
},
{
key = "anchor_blend",
type = "slider",
label = "Anchor Blend",
default = 0.08,
min = 0.01,
max = 0.30,
step = 0.01,
hint = "Pull strength toward identity anchor per step. 0.08=gentle (recommended). 0.15=noticeable. Setting too high constrains creative refinement after anchor capture.",
},
-- ── Tonal Anchor ──────────────────────────────────────────────────────
{
key = "tonal_anchor",
type = "toggle",
label = "Tonal Anchor",
default = true,
hint = "Captures spectral centroid and band energy ratios at anchor_sigma. Applies centroid drift correction and band ratio correction on subsequent steps. Sigma-adaptive — correction strength fades proportionally with sigma.",
},
{
key = "tonal_strength",
type = "slider",
label = "Tonal Strength",
default = 0.15,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Tonal correction scale. Each per-element correction is hard-capped at 0.1% per step regardless of this value. 0.10-0.20=recommended for audio. Higher values widen the correction window but the cap still applies.",
},
-- ── Look-Back Smoother ────────────────────────────────────────────────
{
key = "look_back_enabled",
type = "toggle",
label = "Look-Back Smoother",
default = true,
hint = "SNR-adaptive latent EMA. Blends current output toward previous step output, weighted heavily at high sigma (structure), fading to zero at low sigma (detail). Suppresses ODE manifold shearing and harmonic hum. arXiv:2602.09449.",
},
{
key = "look_back_lambda",
type = "slider",
label = "Look-Back Lambda",
default = 0.55,
min = 0.05,
max = 1.0,
step = 0.05,
hint = "Max smoothing weight at sigma=sigma_max. 0.55=25-step DDIM (default). 0.35=35-step simple. Always fades to zero at sigma=0 regardless of this value.",
},
{
key = "look_back_snr_power",
type = "slider",
label = "Look-Back SNR Power",
default = 1.3,
min = 0.5,
max = 3.0,
step = 0.1,
hint = "Falloff exponent for look-back weight. 1.3=25-step DDIM. 1.5=35-step simple. Higher = smoothing concentrated on early structure steps only.",
},
-- ── RMS Servo ─────────────────────────────────────────────────────────
{
key = "rms_servo",
type = "toggle",
label = "RMS Servo",
default = false,
hint = "Downward-only RMS ceiling. Prevents latent energy runaway without hard clipping. Off by default — calibrate target_min and target_max for your domain before enabling. ACE-Step latents run ~2.0 RMS.",
},
{
key = "rms_target_min",
type = "slider",
label = "RMS Target Min",
default = 1.2,
min = 0.1,
max = 3.0,
step = 0.05,
hint = "RMS ceiling at low sigma (late/detail steps). ACE-Step latents ~2.0 RMS at x0. Start at 1.2-1.8 and observe results.",
},
{
key = "rms_target_max",
type = "slider",
label = "RMS Target Max",
default = 2.5,
min = 0.5,
max = 5.0,
step = 0.05,
hint = "RMS ceiling at high sigma (early/structure steps). Should be >= target_min. ACE-Step early sigma ~2.5-3.5. Servo only fires downward.",
},
{
key = "rms_servo_gain",
type = "slider",
label = "RMS Servo Gain",
default = 0.6,
min = 0.1,
max = 1.0,
step = 0.05,
hint = "Servo correction aggressiveness. 0.6=gradual correction. 1.0=hard snap to target each step. Lower is smoother but slower to converge.",
},
-- ── Latent Pressure ───────────────────────────────────────────────────
{
key = "latent_pressure",
type = "toggle",
label = "Latent Pressure",
default = false,
hint = "Applies a small per-step RMS correction weighted by Shannon entropy. Nudges latent toward a healthy entropy×RMS product. Off by default — tune target params before enabling. Correction capped at 0.05% per step.",
},
{
key = "pressure_target_rms",
type = "slider",
label = "Pressure Target RMS",
default = 2.0,
min = 0.5,
max = 4.0,
step = 0.1,
hint = "RMS component of pressure target. ACE-Step ~2.0. Correction direction flips if current entropy×RMS is above target.",
},
{
key = "pressure_target_entropy",
type = "slider",
label = "Pressure Target Entropy",
default = 7.5,
min = 1.0,
max = 15.0,
step = 0.5,
hint = "Shannon entropy component of pressure target. 7.5=image-domain default. Audio domain may differ — run with verbose output and measure entropy distribution before setting this.",
},
-- ── SDE Noise ─────────────────────────────────────────────────────────
{
key = "eta",
type = "slider",
label = "Noise Injection (0 = ODE)",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "0 = pure deterministic ODE (default, recommended). >0 = SDE mode — injects ancestral noise scaled by t_prev × eta each step. Try 0.05-0.15 for subtle stochasticity. Higher values may overpower the stabilization features.",
},
{
key = "seed",
type = "slider",
label = "Seed",
default = 42,
min = 0,
max = 999999,
step = 1,
hint = "RNG seed for SDE noise (only used when Noise Injection > 0). Deterministic per-step via seed + step_index × 7919.",
},
-- ── Safety ────────────────────────────────────────────────────────────
{
key = "safety_clamp",
type = "slider",
label = "Safety Clamp",
default = 2.5,
min = 1.0,
max = 5.0,
step = 0.1,
hint = "Max absolute latent value after all corrections. NaN/Inf triggers a full rollback to raw Euler output before clamping. 2.5=standard. Raise to 4.0+ if clamping is audible.",
},
},
}
-- ── Constants ─────────────────────────────────────────────────────────────────
local EPSILON = 1e-8
local PRESSURE_CAP = 5e-4 -- max pressure correction per step (0.05%)
local function make_rng(seed)
local state = math.floor(seed) % 2147483647
if state <= 0 then state = state + 2147483646 end
return function()
state = (state * 1664525 + 1013904223) % 2147483648
return state / 2147483648.0
end
end
local function normal(u1, u2)
return math.sqrt(-2.0 * math.log(math.max(u1, EPSILON))) * math.cos(2.0 * math.pi * u2)
end
-- ── Hoisted Buffers (Zero Allocation Hot Loop) ────────────────────────────────
-- Sized on first run or n-change. Reused every step — no GC pressure.
local _last_n = 0
local _out_buf = {} -- working output for this step
local _fallback_buf = {} -- raw Euler output (NaN rollback)
local _vel_old_buf = {} -- EMA velocity (carries across steps)
local _vel_raw_buf = {} -- raw velocity delta (computed this step)
local _anchor_buf = {} -- identity anchor snapshot (frozen at anchor_sigma)
local _prev_out_buf = {} -- previous step final output (inertia + concept lock + look-back)
local _hist_mean_buf = {} -- history mean scratch
local _history = { {}, {}, {} } -- ring buffer (3 slots, 0-indexed elements)
-- ── Module State (reset on n change or step_index == 0) ───────────────────────
local _sigma_max = nil
local _has_prev = false -- true after first step output is stored
local _has_velocity = false -- true after first EMA velocity is initialized
local _has_anchor = false -- true after identity anchor is captured
local _tonal_ref_centroid = nil
local _tonal_ref_bands = nil
local _last_entropy = 7.5
local _hist_head = 1
local _hist_count = 0
-- ── Helpers ───────────────────────────────────────────────────────────────────
local function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
local function bool_param(p, key, default)
if p == nil or p[key] == nil then return default end
return p[key]
end
local function num_param(p, key, default)
if p == nil or p[key] == nil then return default end
return tonumber(p[key]) or default
end
local function rms(a, n)
local s = 0.0
for i = 0, n - 1 do s = s + a[i] * a[i] end
return math.sqrt(s / n + EPSILON)
end
local function shannon_entropy(a, n)
local sum = 0.0
for i = 0, n - 1 do sum = sum + math.abs(a[i]) + 1e-7 end
local inv_sum = 1.0 / (sum + 1e-8)
local H = 0.0
for i = 0, n - 1 do
local p = (math.abs(a[i]) + 1e-7) * inv_sum
H = H - p * math.log(p + EPSILON) / math.log(2.0)
end
H = math.max(0.05, H)
if H ~= H or H == math.huge or H == -math.huge then H = 5.0 end
return H
end
local function spectral_centroid(a, n)
local sum_mag, sum_w = 0.0, 0.0
for i = 0, n - 1 do
local m = math.abs(a[i])
sum_mag = sum_mag + m
sum_w = sum_w + m * i
end
if sum_mag < EPSILON then return 0.0 end
return sum_w / sum_mag
end
local function band_energy(a, n)
local bands = {0.0, 0.0, 0.0, 0.0}
local bsize = math.floor(n / 4)
for b = 0, 3 do
local s = 0.0
local lo = b * bsize
local hi = (b == 3) and (n - 1) or (lo + bsize - 1)
for i = lo, hi do s = s + math.abs(a[i]) end
bands[b + 1] = s / math.max(hi - lo + 1, 1)
end
return bands
end
local function is_safe(a, n)
for i = 0, n - 1 do
local v = a[i]
if v ~= v or v == math.huge or v == -math.huge then return false end
end
return true
end
-- ── step() ────────────────────────────────────────────────────────────────────
function step(xt, vt, t_curr, t_prev, n)
-- ── 0. Read params ────────────────────────────────────────────────────────
local warmup = math.floor(num_param(params, "warmup_steps", 2))
local f_inertia = bool_param(params, "inertia_engine", true)
local inertia_a = num_param(params, "inertia_alpha", 0.15)
local f_memory = bool_param(params, "memory_buffer", true)
local mem_blend = num_param(params, "memory_blend", 0.12)
local f_concept = bool_param(params, "concept_lock", true)
local concept_power = num_param(params, "concept_sigma_power", 1.0)
local f_anchor = bool_param(params, "identity_anchor", true)
local anchor_sigma = num_param(params, "anchor_sigma", 0.5)
local anchor_blend = num_param(params, "anchor_blend", 0.08)
local f_tonal = bool_param(params, "tonal_anchor", true)
local tonal_str = num_param(params, "tonal_strength", 0.15)
local f_lookback = bool_param(params, "look_back_enabled", true)
local lb_lambda = num_param(params, "look_back_lambda", 0.55)
local lb_snr_power = num_param(params, "look_back_snr_power", 1.3)
local f_rms = bool_param(params, "rms_servo", false)
local rms_tgt_min = num_param(params, "rms_target_min", 1.2)
local rms_tgt_max = num_param(params, "rms_target_max", 2.5)
local rms_gain = num_param(params, "rms_servo_gain", 0.6)
local f_pressure = bool_param(params, "latent_pressure", false)
local p_tgt_rms = num_param(params, "pressure_target_rms", 2.0)
local p_tgt_entropy = num_param(params, "pressure_target_entropy", 7.5)
local eta = num_param(params, "eta", 0.0)
local seed = math.floor(num_param(params, "seed", 42))
local sclamp = num_param(params, "safety_clamp", 2.5)
local step_idx = step_index or 0
-- ── 1. State reset (generation start or n change) ─────────────────────────
-- n change: new latent shape (different duration/channels)
-- step_idx == 0: new generation with same shape — must reset or prev
-- generation's final state bleeds into next run's step 1
if n ~= _last_n or step_idx == 0 then
_sigma_max = nil
_has_prev = false
_has_velocity = false
_has_anchor = false
_tonal_ref_centroid = nil
_tonal_ref_bands = nil
_last_entropy = 7.5
_hist_head = 1
_hist_count = 0
-- Resize hoisted buffers
for i = 0, n - 1 do
_out_buf[i] = 0.0
_fallback_buf[i] = 0.0
_vel_old_buf[i] = 0.0
_vel_raw_buf[i] = 0.0
_anchor_buf[i] = 0.0
_prev_out_buf[i] = 0.0
_hist_mean_buf[i] = 0.0
_history[1][i] = 0.0
_history[2][i] = 0.0
_history[3][i] = 0.0
end
_last_n = n
end
-- Capture sigma_max on first step of this generation
if _sigma_max == nil then _sigma_max = t_curr end
-- sigma_ratio: 1.0 at high sigma (early), 0.0 at sigma=0 (final step)
local sigma_ratio = clamp(t_curr / math.max(_sigma_max, EPSILON), 0.0, 1.0)
-- Warmup gate: stateful features are skipped for first `warmup` steps
local past_warmup = (step_idx >= warmup)
-- ── 2. Entropy measurement (always, from step 0) ──────────────────────────
-- Measured from xt (input), not the output. Represents current latent state.
_last_entropy = shannon_entropy(xt, n)
-- ── 3. Euler advance ──────────────────────────────────────────────────────
-- dt = t_prev - t_curr. t decrements each step, so dt < 0 (standard).
-- x_next = xt + dt * vt
local dt = t_prev - t_curr
for i = 0, n - 1 do
local v = xt[i] + dt * vt[i]
_out_buf[i] = v
_fallback_buf[i] = v -- save raw Euler for NaN rollback
end
-- ── 4. Latent Pressure (always if enabled, from step 0) ───────────────────
-- Nudges latent RMS toward pressure_target_rms, weighted by entropy proximity
-- to pressure_target_entropy. Correction hard-capped at PRESSURE_CAP per step.
if f_pressure then
local cur_rms = rms(_out_buf, n)
local target_product = p_tgt_entropy * p_tgt_rms
local cur_product = _last_entropy * cur_rms
local correction = clamp(
(target_product - cur_product) / (target_product + EPSILON),
-PRESSURE_CAP, PRESSURE_CAP
)
if math.abs(correction) > 1e-6 then
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] * (1.0 + correction) end
end
end
-- ── Stateful features below: all gated on past_warmup AND _has_prev ────────
-- ── 5. Memory Buffer ──────────────────────────────────────────────────────
-- Blends mean of last 3 step outputs into current output.
-- Ring buffer: _hist_head cycles 1→2→3→1. _hist_count tracks fill level.
if f_memory and past_warmup and _hist_count > 0 then
for i = 0, n - 1 do _hist_mean_buf[i] = 0.0 end
local hw = 1.0 / _hist_count
for h = 1, _hist_count do
for i = 0, n - 1 do _hist_mean_buf[i] = _hist_mean_buf[i] + _history[h][i] end
end
for i = 0, n - 1 do
_out_buf[i] = (1.0 - mem_blend) * _out_buf[i] + mem_blend * (_hist_mean_buf[i] * hw)
end
end
-- ── 6. Inertia Engine ─────────────────────────────────────────────────────
-- EMA velocity = smoothed step-to-step output delta.
-- Velocity raw this step: _out_buf - _prev_out_buf (output delta).
-- EMA update: vel_old = 0.8 * vel_old + 0.2 * vel_raw (two separate buffers)
-- Alpha entropy-modulated: less inertia when latent is structured (low H).
if f_inertia and past_warmup and _has_prev then
-- Compute raw velocity delta into _vel_raw_buf
for i = 0, n - 1 do _vel_raw_buf[i] = _out_buf[i] - _prev_out_buf[i] end
-- EMA update or initialization
if _has_velocity then
for i = 0, n - 1 do
_vel_old_buf[i] = 0.8 * _vel_old_buf[i] + 0.2 * _vel_raw_buf[i]
end
else
for i = 0, n - 1 do _vel_old_buf[i] = _vel_raw_buf[i] end
_has_velocity = true
end
-- Alpha modulated by entropy: low entropy (structured) → less inertia
local alpha = inertia_a * clamp(_last_entropy / 7.5, 0.0, 1.5)
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] + alpha * _vel_old_buf[i] end
end
-- ── 7. Concept Lock ───────────────────────────────────────────────────────
-- Stability mask: elements with small step-to-step delta get pulled back
-- toward their previous state. Sigmoid-shaped lock weight per element.
-- Sigma-adaptive: lock_w scaled by (sigma_ratio ^ concept_sigma_power)
-- → full effect at high sigma, fades to zero at sigma=0.
if f_concept and past_warmup and _has_prev then
local sigma_mod = sigma_ratio ^ concept_power
if sigma_mod > 1e-4 then
for i = 0, n - 1 do
local delta = math.abs(_out_buf[i] - _prev_out_buf[i])
-- Sigmoid: regions with delta < ~0.05 get near-full lock
local lock_w = (1.0 / (1.0 + math.exp(delta * 40.0 - 2.0))) * sigma_mod
_out_buf[i] = (1.0 - lock_w) * _out_buf[i] + lock_w * _prev_out_buf[i]
end
end
end
-- ── 8. Identity Anchor ────────────────────────────────────────────────────
-- Captures _out_buf snapshot when sigma_ratio crosses anchor_sigma threshold.
-- On subsequent steps: gentle pull back toward the captured snapshot.
-- Anchor sigma is a ratio of sigma_max (same as OmniRelational pattern).
if f_anchor and past_warmup then
if not _has_anchor and sigma_ratio <= anchor_sigma then
-- Capture snapshot
for i = 0, n - 1 do _anchor_buf[i] = _out_buf[i] end
_has_anchor = true
elseif _has_anchor then
for i = 0, n - 1 do
_out_buf[i] = (1.0 - anchor_blend) * _out_buf[i] + anchor_blend * _anchor_buf[i]
end
end
end
-- ── 9. Tonal Anchor ───────────────────────────────────────────────────────
-- Captures spectral centroid and 4-band energy ratios at anchor_sigma.
-- Correction: per-element tilt for centroid drift + per-band ratio correction.
-- Each per-element correction hard-capped at ±0.1% regardless of tonal_str.
-- Sigma-adaptive: effective_str = tonal_str * sigma_ratio
-- → full correction just after capture, fades to zero at sigma=0.
if f_tonal and past_warmup then
if _tonal_ref_centroid == nil and sigma_ratio <= anchor_sigma then
-- Capture reference (fires same step as identity anchor)
_tonal_ref_centroid = spectral_centroid(_out_buf, n)
_tonal_ref_bands = band_energy(_out_buf, n)
elseif _tonal_ref_centroid ~= nil then
-- Sigma-adaptive correction scale
local eff_str = tonal_str * sigma_ratio
if eff_str > 1e-6 then
local curr_centroid = spectral_centroid(_out_buf, n)
local curr_bands = band_energy(_out_buf, n)
-- Centroid drift: linear tilt across elements, capped at 0.1%
local drift_norm = (curr_centroid - _tonal_ref_centroid) /
(math.abs(_tonal_ref_centroid) + EPSILON)
local tilt = clamp(-drift_norm * eff_str, -1e-3, 1e-3)
local center = (n - 1) / 2.0
for i = 0, n - 1 do
local dist_w = (i - center) / (center + EPSILON)
_out_buf[i] = _out_buf[i] + tilt * dist_w * math.abs(_out_buf[i])
end
-- Band energy ratio correction, capped at 0.1% per band
local ref_total, curr_total = 0.0, 0.0
for b = 1, 4 do
ref_total = ref_total + _tonal_ref_bands[b]
curr_total = curr_total + curr_bands[b]
end
if ref_total > EPSILON and curr_total > EPSILON then
local bsize = math.floor(n / 4)
for b = 0, 3 do
local ref_ratio = _tonal_ref_bands[b + 1] / ref_total
local curr_ratio = curr_bands[b + 1] / curr_total
local band_corr = clamp((ref_ratio - curr_ratio) * eff_str, -1e-3, 1e-3)
local lo = b * bsize
local hi = (b == 3) and (n - 1) or (lo + bsize - 1)
for i = lo, hi do
_out_buf[i] = _out_buf[i] + band_corr * math.abs(_out_buf[i])
end
end
end
end
end
end
-- ── 10. Look-Back Smoother ────────────────────────────────────────────────
-- SNR-adaptive EMA: lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
-- Blends current output toward previous step output.
-- Heavy at high sigma (structure coherence), zero at sigma=0 (preserve detail).
-- Pattern from MD PingPong. arXiv:2602.09449.
if f_lookback and past_warmup and _has_prev then
local lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
if lb_w > 1e-6 then
for i = 0, n - 1 do
_out_buf[i] = (1.0 - lb_w) * _out_buf[i] + lb_w * _prev_out_buf[i]
end
end
end
-- ── 11. RMS Servo ─────────────────────────────────────────────────────────
-- Downward-only RMS ceiling: fires only when cur_rms > rms_target.
-- Target descends from rms_target_max (high sigma) to rms_target_min (low sigma).
-- Curve: target = min + sigma_ratio^0.6 * (max - min) (from PingPong).
-- Pattern from MD PingPong.
if f_rms then
local rms_target = rms_tgt_min + (sigma_ratio ^ 0.6) * (rms_tgt_max - rms_tgt_min)
local cur_rms = rms(_out_buf, n)
if cur_rms > rms_target then
local servo_rms = cur_rms + rms_gain * (rms_target - cur_rms)
local scale = servo_rms / cur_rms
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] * scale end
end
end
-- ── 12. Safety Clamp + NaN Guard ──────────────────────────────────────────
-- NaN/Inf in output: roll back to raw Euler result before clamping.
-- Abs ceiling applied regardless.
if not is_safe(_out_buf, n) then
for i = 0, n - 1 do _out_buf[i] = _fallback_buf[i] end
end
for i = 0, n - 1 do _out_buf[i] = clamp(_out_buf[i], -sclamp, sclamp) end
-- ── 13. Update state ──────────────────────────────────────────────────────
-- Store this step's output as prev_out_buf for next step.
-- Also push to memory ring buffer.
if past_warmup then
for i = 0, n - 1 do _prev_out_buf[i] = _out_buf[i] end
_has_prev = true
-- Ring buffer push
if f_memory then
for i = 0, n - 1 do _history[_hist_head][i] = _out_buf[i] end
_hist_head = _hist_head + 1
if _hist_head > 3 then _hist_head = 1 end
if _hist_count < 3 then _hist_count = _hist_count + 1 end
end
end
-- ── 14. Write output ──────────────────────────────────────────────────────
for i = 0, n - 1 do xt[i] = _out_buf[i] end
-- ── 15. SDE Noise Injection ───────────────────────────────────────────────
-- Applied after write-back, outside the safety clamp, matching OmniRelational
-- convention. scale = t_prev * eta — noise magnitude tracks current sigma level,
-- naturally fades to zero as generation converges.
if eta > 0.0 and t_prev > EPSILON then
local rng = make_rng(seed + step_idx * 7919)
local scale = t_prev * eta
for i = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
xt[i] = xt[i] + normal(u1, u2) * scale
end
end
end
+776
View File
@@ -0,0 +1,776 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details: https://www.gnu.org/licenses/
-- ============================================================================
local C = require("md_solver_commons")
-- MD Trajectory Anchor V3 — Latent Path Stabilizer
-- MDMAchine | A&E Concepts © 2026
--
-- A stateful trajectory stabilization solver for HOT-Step-CPP.
-- Runs via step() — NOT owns_loop. The guider pipeline (APG, ADG, PMG, etc.)
-- remains fully active. Receives the pre-guided vt from the engine and applies
-- stateful corrections on top of the advancing latent.
--
-- WHY step() INSTEAD OF owns_loop:
-- All features here (inertia, concept lock, anchors, look-back) only need
-- xt — the latent tensor — which step() provides directly. owns_loop was
-- used in V1/V2 because guidance features needed cond/uncond, but those
-- have been removed. step() is the correct, minimal contract for this work.
-- Guiders run normally alongside this solver.
--
-- PIPELINE PER STEP:
-- xt → Euler advance (xt + dt * vt) → _out_buf
-- → [entropy measurement] — Shannon H from xt (step 0+)
-- → [latent pressure] — entropy×RMS correction (toggle, off)
-- → [memory buffer] — 3-step ring buffer smoothing
-- → [inertia engine] — EMA velocity carry-over
-- → [concept lock] — stability mask, sigma-adaptive
-- → [identity anchor] — mid-sigma snapshot pull-back
-- → [tonal anchor] — spectral centroid correction, sigma-adaptive
-- → [look-back smoother] — SNR-adaptive EMA (arXiv:2602.09449)
-- → [RMS servo] — descending RMS ceiling (toggle, off)
-- → [safety clamp + NaN guard] — abs ceiling + Euler rollback on NaN
-- → write _out_buf to xt
--
-- STATE RESET:
-- All module-level state resets on step_index == 0 OR n change.
-- Same-length consecutive generations do not bleed state.
--
-- INERTIA EMA FIX (V2 regression):
-- V2 computed: vel = 0.8 * vel + 0.2 * vel (no-op, same buffer).
-- V1.0 uses two separate buffers: _vel_old_buf (EMA) and _vel_raw_buf (delta).
-- EMA: _vel_old_buf[i] = 0.8 * _vel_old_buf[i] + 0.2 * _vel_raw_buf[i]
--
-- SIGMA-ADAPTIVE FEATURES (from OmniRelational V3 pattern):
-- concept lock strength = full * (sigma_ratio ^ concept_sigma_power)
-- tonal correction scale = tonal_strength * sigma_ratio
-- look-back weight = lb_lambda * (sigma_ratio ^ lb_snr_power)
-- All three are heavy at high sigma (structure phase), fade to zero at sigma=0.
--
-- PARAMS:
-- warmup_steps — skip stateful features for first N steps
-- inertia_engine — EMA latent velocity carry-over
-- inertia_alpha — base velocity coefficient, entropy-modulated
-- memory_buffer — 3-step ring buffer output smoothing
-- memory_blend — history blend fraction
-- concept_lock — stability mask on settled regions
-- concept_sigma_power — how fast lock fades with sigma
-- identity_anchor — captures xt snapshot at anchor_sigma, pulls back
-- anchor_sigma — sigma fraction at which anchors are captured
-- anchor_blend — pull strength toward identity anchor
-- tonal_anchor — spectral centroid drift correction
-- tonal_strength — correction scale (per-element hard cap 0.1%)
-- look_back_enabled — SNR-adaptive latent EMA smoother
-- look_back_lambda — max smoothing weight at high sigma
-- look_back_snr_power — falloff exponent
-- rms_servo — descending RMS ceiling (off by default)
-- rms_target_min — RMS ceiling at low sigma
-- rms_target_max — RMS ceiling at high sigma
-- rms_servo_gain — servo correction aggressiveness
-- latent_pressure — entropy×RMS target correction (off by default)
-- pressure_target_rms — RMS target for pressure correction
-- pressure_target_entropy — entropy target for pressure weighting
-- safety_clamp — max absolute latent value
-- ============================================================================
solver = {
name = "md_trajectory_anchor_V3",
display = "MD Trajectory Anchor V3",
description = "Latent path stabilizer. step() solver — guiders stay active. Inertia engine, concept lock, identity anchor, tonal anchor, memory buffer, look-back smoother, RMS servo. All stateful. State resets cleanly between generations.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
params = {
-- ── Warmup ────────────────────────────────────────────────────────────
{
key = "warmup_steps",
type = "slider",
label = "Warmup Steps",
default = 2,
min = 0,
max = 6,
step = 1,
hint = "Skip stateful features (inertia, concept lock, anchors) for first N steps. Latent is mostly noise at high sigma — anchoring into chaos makes things worse. 2=recommended. 0=always active.",
},
-- ── Inertia Engine ────────────────────────────────────────────────────
{
key = "inertia_engine",
type = "toggle",
label = "Inertia Engine",
default = true,
hint = "EMA-smoothed latent velocity carry-over. Adds step-to-step momentum — reduces abrupt trajectory direction changes. Alpha is entropy-modulated: less inertia when latent is already structured (low entropy).",
},
{
key = "inertia_alpha",
type = "slider",
label = "Inertia Alpha",
default = 0.15,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Base velocity carry-over coefficient. 0.10=subtle. 0.20=noticeable. 0.30+=strong. Scaled down at runtime when entropy is low (structured latent needs less push).",
},
-- ── Memory Buffer ─────────────────────────────────────────────────────
{
key = "memory_buffer",
type = "toggle",
label = "Memory Buffer",
default = false,
hint = "Blends last 3 step outputs into the current step output. Suppresses step-to-step jitter without redirecting the trajectory. Ring buffer, zero-alloc.",
},
{
key = "memory_blend",
type = "slider",
label = "Memory Blend",
default = 0.12,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Fraction of 3-step history mean blended into each step output. 0.12=subtle. 0.25+=heavy smoothing (may soften transients in audio).",
},
-- ── Concept Lock ──────────────────────────────────────────────────────
{
key = "concept_lock",
type = "toggle",
label = "Concept Lock",
default = true,
hint = "Stability mask: elements with small step-to-step delta are pulled back toward their previous state. Protects settled structure from noise. Sigma-adaptive — full strength at high sigma, fades at low sigma (detail phase).",
},
{
key = "concept_sigma_power",
type = "slider",
label = "Concept Lock Sigma Power",
default = 1.0,
min = 0.25,
max = 3.0,
step = 0.25,
hint = "Controls how fast concept lock fades as sigma decreases. 1.0=linear decay. 2.0=quadratic (lock concentrated on early structure steps only). 0.5=slow fade (lock persists into detail steps).",
},
-- ── Identity Anchor ───────────────────────────────────────────────────
{
key = "identity_anchor",
type = "toggle",
label = "Identity Anchor",
default = false,
hint = "Captures a snapshot of xt at anchor_sigma, then gently pulls toward it on all subsequent steps. Prevents late-stage structural drift. Tonal anchor fires at the same sigma.",
},
{
key = "anchor_sigma",
type = "slider",
label = "Anchor Sigma",
default = 0.5,
min = 0.1,
max = 0.9,
step = 0.05,
hint = "Sigma level (as fraction of sigma_max) at which the identity and tonal anchors are captured. 0.5=mid-generation. Lower=locks in more detail. Higher=locks coarser structure only.",
},
{
key = "anchor_blend",
type = "slider",
label = "Anchor Blend",
default = 0.08,
min = 0.01,
max = 0.30,
step = 0.01,
hint = "Pull strength toward identity anchor per step. 0.08=gentle (recommended). 0.15=noticeable. Setting too high constrains creative refinement after anchor capture.",
},
-- ── Tonal Anchor ──────────────────────────────────────────────────────
{
key = "tonal_anchor",
type = "toggle",
label = "Tonal Anchor",
default = true,
hint = "Captures spectral centroid and band energy ratios at anchor_sigma. Applies centroid drift correction and band ratio correction on subsequent steps. Sigma-adaptive — correction strength fades proportionally with sigma.",
},
{
key = "tonal_strength",
type = "slider",
label = "Tonal Strength",
default = 0.15,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Tonal correction scale. Each per-element correction is hard-capped at 0.1% per step regardless of this value. 0.10-0.20=recommended for audio. Higher values widen the correction window but the cap still applies.",
},
-- ── Look-Back Smoother ────────────────────────────────────────────────
{
key = "look_back_enabled",
type = "toggle",
label = "Look-Back Smoother",
default = false,
hint = "SNR-adaptive latent EMA. Blends current output toward previous step output, weighted heavily at high sigma (structure), fading to zero at low sigma (detail). Suppresses ODE manifold shearing and harmonic hum. arXiv:2602.09449.",
},
{
key = "look_back_lambda",
type = "slider",
label = "Look-Back Lambda",
default = 0.15,
min = 0.05,
max = 1.0,
step = 0.05,
hint = "Max smoothing weight at sigma=sigma_max. 0.55=25-step DDIM (default). 0.35=35-step simple. Always fades to zero at sigma=0 regardless of this value.",
},
{
key = "look_back_snr_power",
type = "slider",
label = "Look-Back SNR Power",
default = 1.3,
min = 0.5,
max = 3.0,
step = 0.1,
hint = "Falloff exponent for look-back weight. 1.3=25-step DDIM. 1.5=35-step simple. Higher = smoothing concentrated on early structure steps only.",
},
-- ── RMS Servo ─────────────────────────────────────────────────────────
{
key = "rms_servo",
type = "toggle",
label = "RMS Servo",
default = false,
hint = "Downward-only RMS ceiling. Prevents latent energy runaway without hard clipping. Off by default — calibrate target_min and target_max for your domain before enabling. ACE-Step latents run ~2.0 RMS.",
},
{
key = "rms_target_min",
type = "slider",
label = "RMS Target Min",
default = 1.2,
min = 0.1,
max = 3.0,
step = 0.05,
hint = "RMS ceiling at low sigma (late/detail steps). ACE-Step latents ~2.0 RMS at x0. Start at 1.2-1.8 and observe results.",
},
{
key = "rms_target_max",
type = "slider",
label = "RMS Target Max",
default = 2.5,
min = 0.5,
max = 5.0,
step = 0.05,
hint = "RMS ceiling at high sigma (early/structure steps). Should be >= target_min. ACE-Step early sigma ~2.5-3.5. Servo only fires downward.",
},
{
key = "rms_servo_gain",
type = "slider",
label = "RMS Servo Gain",
default = 0.6,
min = 0.1,
max = 1.0,
step = 0.05,
hint = "Servo correction aggressiveness. 0.6=gradual correction. 1.0=hard snap to target each step. Lower is smoother but slower to converge.",
},
-- ── Latent Pressure ───────────────────────────────────────────────────
{
key = "latent_pressure",
type = "toggle",
label = "Latent Pressure",
default = false,
hint = "Applies a small per-step RMS correction weighted by Shannon entropy. Nudges latent toward a healthy entropy×RMS product. Off by default — tune target params before enabling. Correction capped at 0.05% per step.",
},
{
key = "pressure_target_rms",
type = "slider",
label = "Pressure Target RMS",
default = 2.0,
min = 0.5,
max = 4.0,
step = 0.1,
hint = "RMS component of pressure target. ACE-Step ~2.0. Correction direction flips if current entropy×RMS is above target.",
},
{
key = "pressure_target_entropy",
type = "slider",
label = "Pressure Target Entropy",
default = 7.5,
min = 1.0,
max = 15.0,
step = 0.5,
hint = "Shannon entropy component of pressure target. 7.5=image-domain default. Audio domain may differ — run with verbose output and measure entropy distribution before setting this.",
},
-- ── SDE Noise ─────────────────────────────────────────────────────────
{
key = "relational_weight",
type = "slider",
label = "Relational Weight",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Barbour Best Matching velocity decomposition. 0 = off. 0.3-0.5 = balanced.",
},
{
key = "relational_sigma_power",
type = "slider",
label = "Relational Sigma Decay",
default = 1.0,
min = 0.25,
max = 4.0,
step = 0.25,
hint = "How fast relational weight fades. 1.0 = linear.",
},
{
key = "eta",
type = "slider",
label = "Eta (SDE Noise)",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Ancestral noise injection. 0=deterministic ODE (default). Scales with t_prev each step. Low values (0.05-0.15) add subtle stochasticity without overwhelming the stabilization features.",
},
{
key = "seed",
type = "slider",
label = "Seed",
default = 42,
min = 0,
max = 999999,
step = 1,
hint = "RNG seed for SDE noise. Deterministic per-step via seed + step_index * 7919.",
},
-- ── Safety ────────────────────────────────────────────────────────────
{
key = "safety_clamp",
type = "slider",
label = "Safety Clamp",
default = 2.5,
min = 1.0,
max = 5.0,
step = 0.1,
hint = "Max absolute latent value after all corrections. NaN/Inf triggers a full rollback to raw Euler output before clamping. 2.5=standard. Raise to 4.0+ if clamping is audible.",
},
},
}
-- ── Constants ─────────────────────────────────────────────────────────────────
local EPSILON = 1e-8
local PRESSURE_CAP = 5e-4 -- max pressure correction per step (0.05%)
local function make_rng(seed)
local state = math.floor(seed) % 2147483647
if state <= 0 then state = state + 2147483646 end
return function()
state = (state * 1664525 + 1013904223) % 2147483648
return state / 2147483648.0
end
end
local function normal(u1, u2)
return math.sqrt(-2.0 * math.log(math.max(u1, EPSILON))) * math.cos(2.0 * math.pi * u2)
end
-- ── Hoisted Buffers (Zero Allocation Hot Loop) ────────────────────────────────
-- Sized on first run or n-change. Reused every step — no GC pressure.
local _last_n = 0
local _out_buf = {} -- working output for this step
local _fallback_buf = {} -- raw Euler output (NaN rollback)
local _vel_old_buf = {} -- EMA velocity (carries across steps)
local _vel_raw_buf = {} -- raw velocity delta (computed this step)
local _anchor_buf = {} -- identity anchor snapshot (frozen at anchor_sigma)
local _prev_out_buf = {} -- previous step final output (inertia + concept lock + look-back)
local _hist_mean_buf = {} -- history mean scratch
local _history = { {}, {}, {} } -- ring buffer (3 slots, 0-indexed elements)
-- ── Module State (reset on n change or step_index == 0) ───────────────────────
local _sigma_max = nil
local _has_prev = false -- true after first step output is stored
local _has_velocity = false -- true after first EMA velocity is initialized
local _has_anchor = false -- true after identity anchor is captured
local _tonal_ref_centroid = nil
local _tonal_ref_bands = nil
local _last_entropy = 7.5
local _hist_head = 1
local _hist_count = 0
-- ── Helpers ───────────────────────────────────────────────────────────────────
local function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
local function bool_param(p, key, default)
if p == nil or p[key] == nil then return default end
return p[key]
end
local function num_param(p, key, default)
if p == nil or p[key] == nil then return default end
return tonumber(p[key]) or default
end
local function rms(a, n)
local s = 0.0
for i = 0, n - 1 do s = s + a[i] * a[i] end
return math.sqrt(s / n + EPSILON)
end
local function shannon_entropy(a, n)
local sum = 0.0
for i = 0, n - 1 do sum = sum + math.abs(a[i]) + 1e-7 end
local inv_sum = 1.0 / (sum + 1e-8)
local H = 0.0
for i = 0, n - 1 do
local p = (math.abs(a[i]) + 1e-7) * inv_sum
H = H - p * math.log(p + EPSILON) / math.log(2.0)
end
H = math.max(0.05, H)
if H ~= H or H == math.huge or H == -math.huge then H = 5.0 end
return H
end
local function spectral_centroid(a, n)
local sum_mag, sum_w = 0.0, 0.0
for i = 0, n - 1 do
local m = math.abs(a[i])
sum_mag = sum_mag + m
sum_w = sum_w + m * i
end
if sum_mag < EPSILON then return 0.0 end
return sum_w / sum_mag
end
local function band_energy(a, n)
local bands = {0.0, 0.0, 0.0, 0.0}
local bsize = math.floor(n / 4)
for b = 0, 3 do
local s = 0.0
local lo = b * bsize
local hi = (b == 3) and (n - 1) or (lo + bsize - 1)
for i = lo, hi do s = s + math.abs(a[i]) end
bands[b + 1] = s / math.max(hi - lo + 1, 1)
end
return bands
end
local function is_safe(a, n)
for i = 0, n - 1 do
local v = a[i]
if v ~= v or v == math.huge or v == -math.huge then return false end
end
return true
end
-- ── step() ────────────────────────────────────────────────────────────────────
function step(xt, vt, t_curr, t_prev, n)
-- ── 0. Read params ────────────────────────────────────────────────────────
local warmup = math.floor(num_param(params, "warmup_steps", 2))
local f_inertia = bool_param(params, "inertia_engine", true)
local inertia_a = num_param(params, "inertia_alpha", 0.15)
local f_memory = bool_param(params, "memory_buffer", false)
local mem_blend = num_param(params, "memory_blend", 0.12)
local f_concept = bool_param(params, "concept_lock", true)
local concept_power = num_param(params, "concept_sigma_power", 1.0)
local f_anchor = bool_param(params, "identity_anchor", false)
local anchor_sigma = num_param(params, "anchor_sigma", 0.5)
local anchor_blend = num_param(params, "anchor_blend", 0.08)
local f_tonal = bool_param(params, "tonal_anchor", true)
local tonal_str = num_param(params, "tonal_strength", 0.15)
local f_lookback = bool_param(params, "look_back_enabled", false)
local lb_lambda = num_param(params, "look_back_lambda", 0.15)
local lb_snr_power = num_param(params, "look_back_snr_power", 1.3)
local f_rms = bool_param(params, "rms_servo", false)
local rms_tgt_min = num_param(params, "rms_target_min", 1.2)
local rms_tgt_max = num_param(params, "rms_target_max", 2.5)
local rms_gain = num_param(params, "rms_servo_gain", 0.6)
local f_pressure = bool_param(params, "latent_pressure", false)
local p_tgt_rms = num_param(params, "pressure_target_rms", 2.0)
local p_tgt_entropy = num_param(params, "pressure_target_entropy", 7.5)
local eta = num_param(params, "eta", 0.0)
local seed = math.floor(num_param(params, "seed", 42))
local sclamp = num_param(params, "safety_clamp", 2.5)
local rw = num_param(params, "relational_weight", 0.0)
local rw_sig_pow = num_param(params, "relational_sigma_power", 1.0)
local step_idx = step_index or 0
-- ── 1. State reset (generation start or n change) ─────────────────────────
-- n change: new latent shape (different duration/channels)
-- step_idx == 0: new generation with same shape — must reset or prev
-- generation's final state bleeds into next run's step 1
if n ~= _last_n or step_idx == 0 then
_sigma_max = nil
_has_prev = false
_has_velocity = false
_has_anchor = false
_tonal_ref_centroid = nil
_tonal_ref_bands = nil
_last_entropy = 7.5
_hist_head = 1
_hist_count = 0
-- Resize hoisted buffers
for i = 0, n - 1 do
_out_buf[i] = 0.0
_fallback_buf[i] = 0.0
_vel_old_buf[i] = 0.0
_vel_raw_buf[i] = 0.0
_anchor_buf[i] = 0.0
_prev_out_buf[i] = 0.0
_hist_mean_buf[i] = 0.0
_history[1][i] = 0.0
_history[2][i] = 0.0
_history[3][i] = 0.0
end
_last_n = n
end
-- Capture sigma_max on first step of this generation
if _sigma_max == nil then _sigma_max = t_curr end
-- sigma_ratio: 1.0 at high sigma (early), 0.0 at sigma=0 (final step)
local sigma_ratio = clamp(t_curr / math.max(_sigma_max, EPSILON), 0.0, 1.0)
-- Warmup gate: stateful features are skipped for first `warmup` steps
local past_warmup = (step_idx >= warmup)
-- ── 2. Entropy measurement (always, from step 0) ──────────────────────────
-- Measured from xt (input), not the output. Represents current latent state.
_last_entropy = shannon_entropy(xt, n)
-- ── 2b. Relational velocity decomposition ──────────────────────────────
-- vt is read-only FloatArray, so we create a local velocity reference
local vel = vt -- default: use vt directly (no copy overhead when rw=0)
if rw > 0 and _sigma_max ~= nil then
local v_tbl = {}
for i = 0, n - 1 do v_tbl[i] = vt[i] end
local x_tbl = {}
for i = 0, n - 1 do x_tbl[i] = xt[i] end
C.apply_relational(v_tbl, n, 1, n, sigma_ratio, _sigma_max,
rw, rw_sig_pow, false, 0.85, x_tbl)
vel = v_tbl
end
-- ── 3. Euler advance ──────────────────────────────────────────────────────
-- dt = t_prev - t_curr. t decrements each step, so dt < 0 (standard).
-- x_next = xt + dt * vel
local dt = t_prev - t_curr
for i = 0, n - 1 do
local v = xt[i] + dt * vel[i]
_out_buf[i] = v
_fallback_buf[i] = v -- save raw Euler for NaN rollback
end
-- ── 4. Latent Pressure (always if enabled, from step 0) ───────────────────
-- Nudges latent RMS toward pressure_target_rms, weighted by entropy proximity
-- to pressure_target_entropy. Correction hard-capped at PRESSURE_CAP per step.
if f_pressure then
local cur_rms = rms(_out_buf, n)
local target_product = p_tgt_entropy * p_tgt_rms
local cur_product = _last_entropy * cur_rms
local correction = clamp(
(target_product - cur_product) / (target_product + EPSILON),
-PRESSURE_CAP, PRESSURE_CAP
)
if math.abs(correction) > 1e-6 then
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] * (1.0 + correction) end
end
end
-- ── Stateful features below: all gated on past_warmup AND _has_prev ────────
-- ── 5. Memory Buffer ──────────────────────────────────────────────────────
-- Blends mean of last 3 step outputs into current output.
-- Ring buffer: _hist_head cycles 1→2→3→1. _hist_count tracks fill level.
if f_memory and past_warmup and _hist_count > 0 then
for i = 0, n - 1 do _hist_mean_buf[i] = 0.0 end
local hw = 1.0 / _hist_count
for h = 1, _hist_count do
for i = 0, n - 1 do _hist_mean_buf[i] = _hist_mean_buf[i] + _history[h][i] end
end
for i = 0, n - 1 do
_out_buf[i] = (1.0 - mem_blend) * _out_buf[i] + mem_blend * (_hist_mean_buf[i] * hw)
end
end
-- ── 6. Inertia Engine ─────────────────────────────────────────────────────
-- EMA velocity = smoothed step-to-step output delta.
-- Velocity raw this step: _out_buf - _prev_out_buf (output delta).
-- EMA update: vel_old = 0.8 * vel_old + 0.2 * vel_raw (two separate buffers)
-- Alpha entropy-modulated: less inertia when latent is structured (low H).
if f_inertia and past_warmup and _has_prev then
-- Compute raw velocity delta into _vel_raw_buf
for i = 0, n - 1 do _vel_raw_buf[i] = _out_buf[i] - _prev_out_buf[i] end
-- EMA update or initialization
if _has_velocity then
for i = 0, n - 1 do
_vel_old_buf[i] = 0.8 * _vel_old_buf[i] + 0.2 * _vel_raw_buf[i]
end
else
for i = 0, n - 1 do _vel_old_buf[i] = _vel_raw_buf[i] end
_has_velocity = true
end
-- Alpha modulated by entropy: low entropy (structured) → less inertia
local alpha = inertia_a * clamp(_last_entropy / 7.5, 0.0, 1.5)
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] + alpha * _vel_old_buf[i] end
end
-- ── 7. Concept Lock ───────────────────────────────────────────────────────
-- Stability mask: elements with small step-to-step delta get pulled back
-- toward their previous state. Sigmoid-shaped lock weight per element.
-- Sigma-adaptive: lock_w scaled by (sigma_ratio ^ concept_sigma_power)
-- → full effect at high sigma, fades to zero at sigma=0.
if f_concept and past_warmup and _has_prev then
local sigma_mod = sigma_ratio ^ concept_power
if sigma_mod > 1e-4 then
for i = 0, n - 1 do
local delta = math.abs(_out_buf[i] - _prev_out_buf[i])
-- Sigmoid: regions with delta < ~0.05 get near-full lock
local lock_w = (1.0 / (1.0 + math.exp(delta * 40.0 - 2.0))) * sigma_mod
_out_buf[i] = (1.0 - lock_w) * _out_buf[i] + lock_w * _prev_out_buf[i]
end
end
end
-- ── 8. Identity Anchor ────────────────────────────────────────────────────
-- Captures _out_buf snapshot when sigma_ratio crosses anchor_sigma threshold.
-- On subsequent steps: gentle pull back toward the captured snapshot.
-- Anchor sigma is a ratio of sigma_max (same as OmniRelational pattern).
if f_anchor and past_warmup then
if not _has_anchor and sigma_ratio <= anchor_sigma then
-- Capture snapshot
for i = 0, n - 1 do _anchor_buf[i] = _out_buf[i] end
_has_anchor = true
elseif _has_anchor then
for i = 0, n - 1 do
_out_buf[i] = (1.0 - anchor_blend) * _out_buf[i] + anchor_blend * _anchor_buf[i]
end
end
end
-- ── 9. Tonal Anchor ───────────────────────────────────────────────────────
-- Captures spectral centroid and 4-band energy ratios at anchor_sigma.
-- Correction: per-element tilt for centroid drift + per-band ratio correction.
-- Each per-element correction hard-capped at ±0.1% regardless of tonal_str.
-- Sigma-adaptive: effective_str = tonal_str * sigma_ratio
-- → full correction just after capture, fades to zero at sigma=0.
if f_tonal and past_warmup then
if _tonal_ref_centroid == nil and sigma_ratio <= anchor_sigma then
-- Capture reference (fires same step as identity anchor)
_tonal_ref_centroid = spectral_centroid(_out_buf, n)
_tonal_ref_bands = band_energy(_out_buf, n)
elseif _tonal_ref_centroid ~= nil then
-- Sigma-adaptive correction scale
local eff_str = tonal_str * sigma_ratio
if eff_str > 1e-6 then
local curr_centroid = spectral_centroid(_out_buf, n)
local curr_bands = band_energy(_out_buf, n)
-- Centroid drift: linear tilt across elements, capped at 0.1%
local drift_norm = (curr_centroid - _tonal_ref_centroid) /
(math.abs(_tonal_ref_centroid) + EPSILON)
local tilt = clamp(-drift_norm * eff_str, -1e-3, 1e-3)
local center = (n - 1) / 2.0
for i = 0, n - 1 do
local dist_w = (i - center) / (center + EPSILON)
_out_buf[i] = _out_buf[i] + tilt * dist_w * math.abs(_out_buf[i])
end
-- Band energy ratio correction, capped at 0.1% per band
local ref_total, curr_total = 0.0, 0.0
for b = 1, 4 do
ref_total = ref_total + _tonal_ref_bands[b]
curr_total = curr_total + curr_bands[b]
end
if ref_total > EPSILON and curr_total > EPSILON then
local bsize = math.floor(n / 4)
for b = 0, 3 do
local ref_ratio = _tonal_ref_bands[b + 1] / ref_total
local curr_ratio = curr_bands[b + 1] / curr_total
local band_corr = clamp((ref_ratio - curr_ratio) * eff_str, -1e-3, 1e-3)
local lo = b * bsize
local hi = (b == 3) and (n - 1) or (lo + bsize - 1)
for i = lo, hi do
_out_buf[i] = _out_buf[i] + band_corr * math.abs(_out_buf[i])
end
end
end
end
end
end
-- ── 10. Look-Back Smoother ────────────────────────────────────────────────
-- SNR-adaptive EMA: lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
-- Blends current output toward previous step output.
-- Heavy at high sigma (structure coherence), zero at sigma=0 (preserve detail).
-- Pattern from MD PingPong. arXiv:2602.09449.
if f_lookback and past_warmup and _has_prev then
local lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
if lb_w > 1e-6 then
for i = 0, n - 1 do
_out_buf[i] = (1.0 - lb_w) * _out_buf[i] + lb_w * _prev_out_buf[i]
end
end
end
-- ── 11. RMS Servo ─────────────────────────────────────────────────────────
-- Downward-only RMS ceiling: fires only when cur_rms > rms_target.
-- Target descends from rms_target_max (high sigma) to rms_target_min (low sigma).
-- Curve: target = min + sigma_ratio^0.6 * (max - min) (from PingPong).
-- Pattern from MD PingPong.
if f_rms then
local rms_target = rms_tgt_min + (sigma_ratio ^ 0.6) * (rms_tgt_max - rms_tgt_min)
local cur_rms = rms(_out_buf, n)
if cur_rms > rms_target then
local servo_rms = cur_rms + rms_gain * (rms_target - cur_rms)
local scale = servo_rms / cur_rms
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] * scale end
end
end
-- ── 12. Safety Clamp + NaN Guard ──────────────────────────────────────────
-- NaN/Inf in output: roll back to raw Euler result before clamping.
-- Abs ceiling applied regardless.
if not is_safe(_out_buf, n) then
for i = 0, n - 1 do _out_buf[i] = _fallback_buf[i] end
end
for i = 0, n - 1 do _out_buf[i] = clamp(_out_buf[i], -sclamp, sclamp) end
-- ── 13. Update state ──────────────────────────────────────────────────────
-- Store this step's output as prev_out_buf for next step.
-- Also push to memory ring buffer.
if past_warmup then
for i = 0, n - 1 do _prev_out_buf[i] = _out_buf[i] end
_has_prev = true
-- Ring buffer push
if f_memory then
for i = 0, n - 1 do _history[_hist_head][i] = _out_buf[i] end
_hist_head = _hist_head + 1
if _hist_head > 3 then _hist_head = 1 end
if _hist_count < 3 then _hist_count = _hist_count + 1 end
end
end
-- ── 14. Write output ──────────────────────────────────────────────────────
for i = 0, n - 1 do xt[i] = _out_buf[i] end
-- ── 15. SDE Noise Injection ───────────────────────────────────────────────
-- Applied after write-back, outside the safety clamp, matching OmniRelational
-- convention. scale = t_prev * eta — noise magnitude tracks current sigma level,
-- naturally fades to zero as generation converges.
if eta > 0.0 and t_prev > EPSILON then
local rng = make_rng(seed + step_idx * 7919)
local scale = t_prev * eta
for i = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
xt[i] = xt[i] + normal(u1, u2) * scale
end
end
end
+856
View File
@@ -0,0 +1,856 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details: https://www.gnu.org/licenses/
-- ============================================================================
local C = require("md_solver_commons")
-- MD Trajectory Anchor V5 — Latent Path Stabilizer
-- MDMAchine | A&E Concepts © 2026
--
-- Plugin version: V5 (HOT-Step UI / filename — what users see)
-- Internal version: v5.0 (math/changelog — what developers track)
--
-- A stateful trajectory stabilization solver for HOT-Step-CPP.
-- Runs via step() — NOT owns_loop. The guider pipeline (APG, ADG, PMG, etc.)
-- remains fully active. Receives the pre-guided vt from the engine and applies
-- stateful corrections on top of the advancing latent.
--
-- WHY step() INSTEAD OF owns_loop:
-- All features here (inertia, concept lock, anchors, look-back) only need
-- xt — the latent tensor — which step() provides directly. owns_loop was
-- used in V1/V2 because guidance features needed cond/uncond, but those
-- have been removed. step() is the correct, minimal contract for this work.
--
-- CHANGELOG:
-- V5 (v5.0): Step-budget auto-scaling — all system strengths adapt to step
-- count via sqrt(35/num_steps). 12-step turbo pushes harder per
-- step, 150-step runs back off automatically. Zero new params.
-- Anti-ringing on identity anchor — when velocity is already
-- moving toward the anchor, blend is reduced to prevent overshoot
-- oscillation. Zero new params.
-- V4 (v4.0): Version bump for batch consistency. Functionally identical to V3.
-- V3 (v3.0): Terminal branch fix era. step()-based, unaffected.
--
-- FUTURE IDEAS (not yet implemented):
-- - Velocity-aware memory buffer: weight ring buffer entries by cosine
-- similarity to current velocity. Steps going the same direction get
-- full weight, steps from before a direction change get discounted.
-- Prevents smoothing across trajectory corners.
-- - Sigma-adaptive concept lock threshold: scale lock_threshold with sigma
-- so "settled" means different things at different noise levels.
-- effective_threshold = lock_threshold * (1 + sigma_ratio * scale_factor)
-- - Trajectory confidence metric: per-step quality signal (velocity
-- stability EMA, magnitude variance, direction consistency) that
-- modulates all system strengths globally. Borrow from STORM V5 LTE.
-- Guiders run normally alongside this solver.
--
-- PIPELINE PER STEP:
-- xt → Euler advance (xt + dt * vt) → _out_buf
-- → [entropy measurement] — Shannon H from xt (step 0+)
-- → [latent pressure] — entropy×RMS correction (toggle, off)
-- → [memory buffer] — 3-step ring buffer smoothing
-- → [inertia engine] — EMA velocity carry-over
-- → [concept lock] — stability mask, sigma-adaptive
-- → [identity anchor] — mid-sigma snapshot pull-back
-- → [tonal anchor] — spectral centroid correction, sigma-adaptive
-- → [look-back smoother] — SNR-adaptive EMA (arXiv:2602.09449)
-- → [RMS servo] — descending RMS ceiling (toggle, off)
-- → [safety clamp + NaN guard] — abs ceiling + Euler rollback on NaN
-- → write _out_buf to xt
--
-- STATE RESET:
-- All module-level state resets on step_index == 0 OR n change.
-- Same-length consecutive generations do not bleed state.
--
-- INERTIA EMA FIX (V2 regression):
-- V2 computed: vel = 0.8 * vel + 0.2 * vel (no-op, same buffer).
-- V1.0 uses two separate buffers: _vel_old_buf (EMA) and _vel_raw_buf (delta).
-- EMA: _vel_old_buf[i] = 0.8 * _vel_old_buf[i] + 0.2 * _vel_raw_buf[i]
--
-- SIGMA-ADAPTIVE FEATURES (from OmniRelational V3 pattern):
-- concept lock strength = full * (sigma_ratio ^ concept_sigma_power)
-- tonal correction scale = tonal_strength * sigma_ratio
-- look-back weight = lb_lambda * (sigma_ratio ^ lb_snr_power)
-- All three are heavy at high sigma (structure phase), fade to zero at sigma=0.
--
-- PARAMS:
-- warmup_steps — skip stateful features for first N steps
-- inertia_engine — EMA latent velocity carry-over
-- inertia_alpha — base velocity coefficient, entropy-modulated
-- memory_buffer — 3-step ring buffer output smoothing
-- memory_blend — history blend fraction
-- concept_lock — stability mask on settled regions
-- concept_sigma_power — how fast lock fades with sigma
-- identity_anchor — captures xt snapshot at anchor_sigma, pulls back
-- anchor_sigma — sigma fraction at which anchors are captured
-- anchor_blend — pull strength toward identity anchor
-- tonal_anchor — spectral centroid drift correction
-- tonal_strength — correction scale (per-element hard cap 0.1%)
-- look_back_enabled — SNR-adaptive latent EMA smoother
-- look_back_lambda — max smoothing weight at high sigma
-- look_back_snr_power — falloff exponent
-- rms_servo — descending RMS ceiling (off by default)
-- rms_target_min — RMS ceiling at low sigma
-- rms_target_max — RMS ceiling at high sigma
-- rms_servo_gain — servo correction aggressiveness
-- latent_pressure — entropy×RMS target correction (off by default)
-- pressure_target_rms — RMS target for pressure correction
-- pressure_target_entropy — entropy target for pressure weighting
-- safety_clamp — max absolute latent value
-- ============================================================================
solver = {
name = "md_trajectory_anchor_V5",
display = "MD Trajectory Anchor V5",
description = "Latent path stabilizer. step() solver — guiders stay active. Inertia engine, concept lock, identity anchor, tonal anchor, memory buffer, look-back smoother, RMS servo. All stateful. State resets cleanly between generations.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
params = {
-- ── Warmup ────────────────────────────────────────────────────────────
{
key = "warmup_steps",
type = "slider",
label = "Warmup Steps",
default = 2,
min = 0,
max = 6,
step = 1,
hint = "Skip stateful features (inertia, concept lock, anchors) for first N steps. Latent is mostly noise at high sigma — anchoring into chaos makes things worse. 2=recommended. 0=always active.",
},
-- ── Inertia Engine ────────────────────────────────────────────────────
{
key = "inertia_engine",
type = "toggle",
label = "Inertia Engine",
default = true,
hint = "EMA-smoothed latent velocity carry-over. Adds step-to-step momentum — reduces abrupt trajectory direction changes. Alpha is entropy-modulated: less inertia when latent is already structured (low entropy).",
},
{
key = "inertia_alpha",
type = "slider",
label = "Inertia Alpha",
default = 0.15,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Base velocity carry-over coefficient. 0.10=subtle. 0.20=noticeable. 0.30+=strong. Scaled down at runtime when entropy is low (structured latent needs less push).",
},
-- ── Memory Buffer ─────────────────────────────────────────────────────
{
key = "memory_buffer",
type = "toggle",
label = "Memory Buffer",
default = false,
hint = "Blends last 3 step outputs into the current step output. Suppresses step-to-step jitter without redirecting the trajectory. Ring buffer, zero-alloc.",
},
{
key = "memory_blend",
type = "slider",
label = "Memory Blend",
default = 0.12,
min = 0.0,
max = 0.5,
step = 0.01,
hint = "Fraction of 3-step history mean blended into each step output. 0.12=subtle. 0.25+=heavy smoothing (may soften transients in audio).",
},
-- ── Concept Lock ──────────────────────────────────────────────────────
{
key = "concept_lock",
type = "toggle",
label = "Concept Lock",
default = true,
hint = "Stability mask: elements with small step-to-step delta are pulled back toward their previous state. Protects settled structure from noise. Sigma-adaptive — full strength at high sigma, fades at low sigma (detail phase).",
},
{
key = "concept_sigma_power",
type = "slider",
label = "Concept Lock Sigma Power",
default = 1.0,
min = 0.25,
max = 3.0,
step = 0.25,
hint = "Controls how fast concept lock fades as sigma decreases. 1.0=linear decay. 2.0=quadratic (lock concentrated on early structure steps only). 0.5=slow fade (lock persists into detail steps).",
},
-- ── Identity Anchor ───────────────────────────────────────────────────
{
key = "identity_anchor",
type = "toggle",
label = "Identity Anchor",
default = false,
hint = "Captures a snapshot of xt at anchor_sigma, then gently pulls toward it on all subsequent steps. Prevents late-stage structural drift. Tonal anchor fires at the same sigma.",
},
{
key = "anchor_sigma",
type = "slider",
label = "Anchor Sigma",
default = 0.5,
min = 0.1,
max = 0.9,
step = 0.05,
hint = "Sigma level (as fraction of sigma_max) at which the identity and tonal anchors are captured. 0.5=mid-generation. Lower=locks in more detail. Higher=locks coarser structure only.",
},
{
key = "anchor_blend",
type = "slider",
label = "Anchor Blend",
default = 0.08,
min = 0.01,
max = 0.30,
step = 0.01,
hint = "Pull strength toward identity anchor per step. 0.08=gentle (recommended). 0.15=noticeable. Setting too high constrains creative refinement after anchor capture.",
},
-- ── Tonal Anchor ──────────────────────────────────────────────────────
{
key = "tonal_anchor",
type = "toggle",
label = "Tonal Anchor",
default = true,
hint = "Captures spectral centroid and band energy ratios at anchor_sigma. Applies centroid drift correction and band ratio correction on subsequent steps. Sigma-adaptive — correction strength fades proportionally with sigma.",
},
{
key = "tonal_strength",
type = "slider",
label = "Tonal Strength",
default = 0.15,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Tonal correction scale. Each per-element correction is hard-capped at 0.1% per step regardless of this value. 0.10-0.20=recommended for audio. Higher values widen the correction window but the cap still applies.",
},
-- ── Look-Back Smoother ────────────────────────────────────────────────
{
key = "look_back_enabled",
type = "toggle",
label = "Look-Back Smoother",
default = false,
hint = "SNR-adaptive latent EMA. Blends current output toward previous step output, weighted heavily at high sigma (structure), fading to zero at low sigma (detail). Suppresses ODE manifold shearing and harmonic hum. arXiv:2602.09449.",
},
{
key = "look_back_lambda",
type = "slider",
label = "Look-Back Lambda",
default = 0.15,
min = 0.05,
max = 1.0,
step = 0.05,
hint = "Max smoothing weight at sigma=sigma_max. 0.55=25-step DDIM (default). 0.35=35-step simple. Always fades to zero at sigma=0 regardless of this value.",
},
{
key = "look_back_snr_power",
type = "slider",
label = "Look-Back SNR Power",
default = 1.3,
min = 0.5,
max = 3.0,
step = 0.1,
hint = "Falloff exponent for look-back weight. 1.3=25-step DDIM. 1.5=35-step simple. Higher = smoothing concentrated on early structure steps only.",
},
-- ── RMS Servo ─────────────────────────────────────────────────────────
{
key = "rms_servo",
type = "toggle",
label = "RMS Servo",
default = false,
hint = "Downward-only RMS ceiling. Prevents latent energy runaway without hard clipping. Off by default — calibrate target_min and target_max for your domain before enabling. ACE-Step latents run ~2.0 RMS.",
},
{
key = "rms_target_min",
type = "slider",
label = "RMS Target Min",
default = 1.2,
min = 0.1,
max = 3.0,
step = 0.05,
hint = "RMS ceiling at low sigma (late/detail steps). ACE-Step latents ~2.0 RMS at x0. Start at 1.2-1.8 and observe results.",
},
{
key = "rms_target_max",
type = "slider",
label = "RMS Target Max",
default = 2.5,
min = 0.5,
max = 5.0,
step = 0.05,
hint = "RMS ceiling at high sigma (early/structure steps). Should be >= target_min. ACE-Step early sigma ~2.5-3.5. Servo only fires downward.",
},
{
key = "rms_servo_gain",
type = "slider",
label = "RMS Servo Gain",
default = 0.6,
min = 0.1,
max = 1.0,
step = 0.05,
hint = "Servo correction aggressiveness. 0.6=gradual correction. 1.0=hard snap to target each step. Lower is smoother but slower to converge.",
},
-- ── Latent Pressure ───────────────────────────────────────────────────
{
key = "latent_pressure",
type = "toggle",
label = "Latent Pressure",
default = false,
hint = "Applies a small per-step RMS correction weighted by Shannon entropy. Nudges latent toward a healthy entropy×RMS product. Off by default — tune target params before enabling. Correction capped at 0.05% per step.",
},
{
key = "pressure_target_rms",
type = "slider",
label = "Pressure Target RMS",
default = 2.0,
min = 0.5,
max = 4.0,
step = 0.1,
hint = "RMS component of pressure target. ACE-Step ~2.0. Correction direction flips if current entropy×RMS is above target.",
},
{
key = "pressure_target_entropy",
type = "slider",
label = "Pressure Target Entropy",
default = 7.5,
min = 1.0,
max = 15.0,
step = 0.5,
hint = "Shannon entropy component of pressure target. 7.5=image-domain default. Audio domain may differ — run with verbose output and measure entropy distribution before setting this.",
},
-- ── SDE Noise ─────────────────────────────────────────────────────────
{
key = "relational_weight",
type = "slider",
label = "Relational Weight",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Barbour Best Matching velocity decomposition. 0 = off. 0.3-0.5 = balanced.",
},
{
key = "relational_sigma_power",
type = "slider",
label = "Relational Sigma Decay",
default = 1.0,
min = 0.25,
max = 4.0,
step = 0.25,
hint = "How fast relational weight fades. 1.0 = linear.",
},
{
key = "eta",
type = "slider",
label = "Eta (SDE Noise)",
default = 0.0,
min = 0.0,
max = 1.0,
step = 0.05,
hint = "Ancestral noise injection. 0=deterministic ODE (default). Scales with t_prev each step. Low values (0.05-0.15) add subtle stochasticity without overwhelming the stabilization features.",
},
{
key = "seed",
type = "slider",
label = "Seed",
default = 42,
min = 0,
max = 999999,
step = 1,
hint = "RNG seed for SDE noise. Deterministic per-step via seed + step_index * 7919.",
},
-- ── Safety ────────────────────────────────────────────────────────────
{
key = "safety_clamp",
type = "slider",
label = "Safety Clamp",
default = 2.5,
min = 1.0,
max = 5.0,
step = 0.1,
hint = "Max absolute latent value after all corrections. NaN/Inf triggers a full rollback to raw Euler output before clamping. 2.5=standard. Raise to 4.0+ if clamping is audible.",
},
},
}
-- ── Constants ─────────────────────────────────────────────────────────────────
local EPSILON = 1e-8
local PRESSURE_CAP = 5e-4 -- max pressure correction per step (0.05%)
local function make_rng(seed)
local state = math.floor(seed) % 2147483647
if state <= 0 then state = state + 2147483646 end
return function()
state = (state * 1664525 + 1013904223) % 2147483648
return state / 2147483648.0
end
end
local function normal(u1, u2)
return math.sqrt(-2.0 * math.log(math.max(u1, EPSILON))) * math.cos(2.0 * math.pi * u2)
end
-- ── Hoisted Buffers (Zero Allocation Hot Loop) ────────────────────────────────
-- Sized on first run or n-change. Reused every step — no GC pressure.
local _last_n = 0
local _out_buf = {} -- working output for this step
local _fallback_buf = {} -- raw Euler output (NaN rollback)
local _vel_old_buf = {} -- EMA velocity (carries across steps)
local _vel_raw_buf = {} -- raw velocity delta (computed this step)
local _anchor_buf = {} -- identity anchor snapshot (frozen at anchor_sigma)
local _prev_out_buf = {} -- previous step final output (inertia + concept lock + look-back)
local _hist_mean_buf = {} -- history mean scratch
local _history = { {}, {}, {} } -- ring buffer (3 slots, 0-indexed elements)
-- ── Module State (reset on n change or step_index == 0) ───────────────────────
local _sigma_max = nil
local _has_prev = false -- true after first step output is stored
local _has_velocity = false -- true after first EMA velocity is initialized
local _has_anchor = false -- true after identity anchor is captured
local _tonal_ref_centroid = nil
local _tonal_ref_bands = nil
local _last_entropy = 7.5
local _hist_head = 1
local _hist_count = 0
-- ── Helpers ───────────────────────────────────────────────────────────────────
local function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
local function bool_param(p, key, default)
if p == nil or p[key] == nil then return default end
return p[key]
end
local function num_param(p, key, default)
if p == nil or p[key] == nil then return default end
return tonumber(p[key]) or default
end
local function rms(a, n)
local s = 0.0
for i = 0, n - 1 do s = s + a[i] * a[i] end
return math.sqrt(s / n + EPSILON)
end
local function shannon_entropy(a, n)
local sum = 0.0
for i = 0, n - 1 do sum = sum + math.abs(a[i]) + 1e-7 end
local inv_sum = 1.0 / (sum + 1e-8)
local H = 0.0
for i = 0, n - 1 do
local p = (math.abs(a[i]) + 1e-7) * inv_sum
H = H - p * math.log(p + EPSILON) / math.log(2.0)
end
H = math.max(0.05, H)
if H ~= H or H == math.huge or H == -math.huge then H = 5.0 end
return H
end
local function spectral_centroid(a, n)
local sum_mag, sum_w = 0.0, 0.0
for i = 0, n - 1 do
local m = math.abs(a[i])
sum_mag = sum_mag + m
sum_w = sum_w + m * i
end
if sum_mag < EPSILON then return 0.0 end
return sum_w / sum_mag
end
local function band_energy(a, n)
local bands = {0.0, 0.0, 0.0, 0.0}
local bsize = math.floor(n / 4)
for b = 0, 3 do
local s = 0.0
local lo = b * bsize
local hi = (b == 3) and (n - 1) or (lo + bsize - 1)
for i = lo, hi do s = s + math.abs(a[i]) end
bands[b + 1] = s / math.max(hi - lo + 1, 1)
end
return bands
end
local function is_safe(a, n)
for i = 0, n - 1 do
local v = a[i]
if v ~= v or v == math.huge or v == -math.huge then return false end
end
return true
end
local function dot_product(a, b, n)
local s = 0.0
for i = 0, n - 1 do s = s + a[i] * b[i] end
return s
end
local function vec_norm(a, n)
return math.sqrt(dot_product(a, a, n) + EPSILON)
end
-- ── step() ────────────────────────────────────────────────────────────────────
function step(xt, vt, t_curr, t_prev, n)
-- ── 0. Read params ────────────────────────────────────────────────────────
local warmup = math.floor(num_param(params, "warmup_steps", 2))
local f_inertia = bool_param(params, "inertia_engine", true)
local inertia_a = num_param(params, "inertia_alpha", 0.15)
local f_memory = bool_param(params, "memory_buffer", false)
local mem_blend = num_param(params, "memory_blend", 0.12)
local f_concept = bool_param(params, "concept_lock", true)
local concept_power = num_param(params, "concept_sigma_power", 1.0)
local f_anchor = bool_param(params, "identity_anchor", false)
local anchor_sigma = num_param(params, "anchor_sigma", 0.5)
local anchor_blend = num_param(params, "anchor_blend", 0.08)
local f_tonal = bool_param(params, "tonal_anchor", true)
local tonal_str = num_param(params, "tonal_strength", 0.15)
local f_lookback = bool_param(params, "look_back_enabled", false)
local lb_lambda = num_param(params, "look_back_lambda", 0.15)
local lb_snr_power = num_param(params, "look_back_snr_power", 1.3)
local f_rms = bool_param(params, "rms_servo", false)
local rms_tgt_min = num_param(params, "rms_target_min", 1.2)
local rms_tgt_max = num_param(params, "rms_target_max", 2.5)
local rms_gain = num_param(params, "rms_servo_gain", 0.6)
local f_pressure = bool_param(params, "latent_pressure", false)
local p_tgt_rms = num_param(params, "pressure_target_rms", 2.0)
local p_tgt_entropy = num_param(params, "pressure_target_entropy", 7.5)
local eta = num_param(params, "eta", 0.0)
local seed = math.floor(num_param(params, "seed", 42))
local sclamp = num_param(params, "safety_clamp", 2.5)
local rw = num_param(params, "relational_weight", 0.0)
local rw_sig_pow = num_param(params, "relational_sigma_power", 1.0)
local step_idx = step_index or 0
-- ── 0b. Step-budget auto-scaling ──────────────────────────────────────────
-- All system strengths adapt to step count. At 12 steps each step matters
-- more, so systems push harder. At 150 steps each step matters less, so
-- systems back off. Reference = 35 steps (standard schedule).
-- sqrt gives diminishing returns — doubling steps halves to 0.71, not 0.5.
local total_steps = num_steps or 35
if total_steps < 1 then total_steps = 35 end
local budget_scale = math.sqrt(35.0 / total_steps)
-- Apply to all system strengths (modifies local copies, not params)
inertia_a = inertia_a * budget_scale
mem_blend = mem_blend * budget_scale
anchor_blend = anchor_blend * budget_scale
tonal_str = tonal_str * budget_scale
lb_lambda = lb_lambda * budget_scale
-- ── 1. State reset (generation start or n change) ─────────────────────────
-- n change: new latent shape (different duration/channels)
-- step_idx == 0: new generation with same shape — must reset or prev
-- generation's final state bleeds into next run's step 1
if n ~= _last_n or step_idx == 0 then
_sigma_max = nil
_has_prev = false
_has_velocity = false
_has_anchor = false
_tonal_ref_centroid = nil
_tonal_ref_bands = nil
_last_entropy = 7.5
_hist_head = 1
_hist_count = 0
-- Resize hoisted buffers
for i = 0, n - 1 do
_out_buf[i] = 0.0
_fallback_buf[i] = 0.0
_vel_old_buf[i] = 0.0
_vel_raw_buf[i] = 0.0
_anchor_buf[i] = 0.0
_prev_out_buf[i] = 0.0
_hist_mean_buf[i] = 0.0
_history[1][i] = 0.0
_history[2][i] = 0.0
_history[3][i] = 0.0
end
_last_n = n
end
-- Capture sigma_max on first step of this generation
if _sigma_max == nil then _sigma_max = t_curr end
-- sigma_ratio: 1.0 at high sigma (early), 0.0 at sigma=0 (final step)
local sigma_ratio = clamp(t_curr / math.max(_sigma_max, EPSILON), 0.0, 1.0)
-- Warmup gate: stateful features are skipped for first `warmup` steps
local past_warmup = (step_idx >= warmup)
-- ── 2. Entropy measurement (always, from step 0) ──────────────────────────
-- Measured from xt (input), not the output. Represents current latent state.
_last_entropy = shannon_entropy(xt, n)
-- ── 2b. Relational velocity decomposition ──────────────────────────────
-- vt is read-only FloatArray, so we create a local velocity reference
local vel = vt -- default: use vt directly (no copy overhead when rw=0)
if rw > 0 and _sigma_max ~= nil then
local v_tbl = {}
for i = 0, n - 1 do v_tbl[i] = vt[i] end
local x_tbl = {}
for i = 0, n - 1 do x_tbl[i] = xt[i] end
C.apply_relational(v_tbl, n, 1, n, sigma_ratio, _sigma_max,
rw, rw_sig_pow, false, 0.85, x_tbl)
vel = v_tbl
end
-- ── 3. Euler advance ──────────────────────────────────────────────────────
-- dt = t_prev - t_curr. t decrements each step, so dt < 0 (standard).
-- x_next = xt + dt * vel
local dt = t_prev - t_curr
for i = 0, n - 1 do
local v = xt[i] + dt * vel[i]
_out_buf[i] = v
_fallback_buf[i] = v -- save raw Euler for NaN rollback
end
-- ── 4. Latent Pressure (always if enabled, from step 0) ───────────────────
-- Nudges latent RMS toward pressure_target_rms, weighted by entropy proximity
-- to pressure_target_entropy. Correction hard-capped at PRESSURE_CAP per step.
if f_pressure then
local cur_rms = rms(_out_buf, n)
local target_product = p_tgt_entropy * p_tgt_rms
local cur_product = _last_entropy * cur_rms
local correction = clamp(
(target_product - cur_product) / (target_product + EPSILON),
-PRESSURE_CAP, PRESSURE_CAP
)
if math.abs(correction) > 1e-6 then
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] * (1.0 + correction) end
end
end
-- ── Stateful features below: all gated on past_warmup AND _has_prev ────────
-- ── 5. Memory Buffer ──────────────────────────────────────────────────────
-- Blends mean of last 3 step outputs into current output.
-- Ring buffer: _hist_head cycles 1→2→3→1. _hist_count tracks fill level.
if f_memory and past_warmup and _hist_count > 0 then
for i = 0, n - 1 do _hist_mean_buf[i] = 0.0 end
local hw = 1.0 / _hist_count
for h = 1, _hist_count do
for i = 0, n - 1 do _hist_mean_buf[i] = _hist_mean_buf[i] + _history[h][i] end
end
for i = 0, n - 1 do
_out_buf[i] = (1.0 - mem_blend) * _out_buf[i] + mem_blend * (_hist_mean_buf[i] * hw)
end
end
-- ── 6. Inertia Engine ─────────────────────────────────────────────────────
-- EMA velocity = smoothed step-to-step output delta.
-- Velocity raw this step: _out_buf - _prev_out_buf (output delta).
-- EMA update: vel_old = 0.8 * vel_old + 0.2 * vel_raw (two separate buffers)
-- Alpha entropy-modulated: less inertia when latent is structured (low H).
if f_inertia and past_warmup and _has_prev then
-- Compute raw velocity delta into _vel_raw_buf
for i = 0, n - 1 do _vel_raw_buf[i] = _out_buf[i] - _prev_out_buf[i] end
-- EMA update or initialization
if _has_velocity then
for i = 0, n - 1 do
_vel_old_buf[i] = 0.8 * _vel_old_buf[i] + 0.2 * _vel_raw_buf[i]
end
else
for i = 0, n - 1 do _vel_old_buf[i] = _vel_raw_buf[i] end
_has_velocity = true
end
-- Alpha modulated by entropy: low entropy (structured) → less inertia
local alpha = inertia_a * clamp(_last_entropy / 7.5, 0.0, 1.5)
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] + alpha * _vel_old_buf[i] end
end
-- ── 7. Concept Lock ───────────────────────────────────────────────────────
-- Stability mask: elements with small step-to-step delta get pulled back
-- toward their previous state. Sigmoid-shaped lock weight per element.
-- Sigma-adaptive: lock_w scaled by (sigma_ratio ^ concept_sigma_power)
-- → full effect at high sigma, fades to zero at sigma=0.
if f_concept and past_warmup and _has_prev then
local sigma_mod = sigma_ratio ^ concept_power
if sigma_mod > 1e-4 then
for i = 0, n - 1 do
local delta = math.abs(_out_buf[i] - _prev_out_buf[i])
-- Sigmoid: regions with delta < ~0.05 get near-full lock
local lock_w = (1.0 / (1.0 + math.exp(delta * 40.0 - 2.0))) * sigma_mod
_out_buf[i] = (1.0 - lock_w) * _out_buf[i] + lock_w * _prev_out_buf[i]
end
end
end
-- ── 8. Identity Anchor ────────────────────────────────────────────────────
-- Captures _out_buf snapshot when sigma_ratio crosses anchor_sigma threshold.
-- On subsequent steps: gentle pull back toward the captured snapshot.
-- Anti-ringing (V5): when velocity is already pointing toward the anchor,
-- reduce the pull to prevent overshoot oscillation. If moving away from
-- anchor, keep full pull. Zero new params — purely automatic.
if f_anchor and past_warmup then
if not _has_anchor and sigma_ratio <= anchor_sigma then
-- Capture snapshot
for i = 0, n - 1 do _anchor_buf[i] = _out_buf[i] end
_has_anchor = true
elseif _has_anchor then
local eff_blend = anchor_blend
-- Anti-ringing: check if velocity aligns with anchor direction
if _has_prev then
-- direction_to_anchor = anchor - current
-- velocity = current - prev
local dot_va = 0.0
local norm_v_sq = 0.0
local norm_a_sq = 0.0
for i = 0, n - 1 do
local v_i = _out_buf[i] - _prev_out_buf[i]
local a_i = _anchor_buf[i] - _out_buf[i]
dot_va = dot_va + v_i * a_i
norm_v_sq = norm_v_sq + v_i * v_i
norm_a_sq = norm_a_sq + a_i * a_i
end
local norm_v = math.sqrt(norm_v_sq + EPSILON)
local norm_a = math.sqrt(norm_a_sq + EPSILON)
local cos_sim = dot_va / (norm_v * norm_a)
-- cos_sim > 0: already moving toward anchor → reduce pull
-- cos_sim < 0: moving away from anchor → keep full pull
if cos_sim > 0.0 then
eff_blend = anchor_blend * (1.0 - clamp(cos_sim, 0.0, 0.8))
end
end
for i = 0, n - 1 do
_out_buf[i] = (1.0 - eff_blend) * _out_buf[i] + eff_blend * _anchor_buf[i]
end
end
end
-- ── 9. Tonal Anchor ───────────────────────────────────────────────────────
-- Captures spectral centroid and 4-band energy ratios at anchor_sigma.
-- Correction: per-element tilt for centroid drift + per-band ratio correction.
-- Each per-element correction hard-capped at ±0.1% regardless of tonal_str.
-- Sigma-adaptive: effective_str = tonal_str * sigma_ratio
-- → full correction just after capture, fades to zero at sigma=0.
if f_tonal and past_warmup then
if _tonal_ref_centroid == nil and sigma_ratio <= anchor_sigma then
-- Capture reference (fires same step as identity anchor)
_tonal_ref_centroid = spectral_centroid(_out_buf, n)
_tonal_ref_bands = band_energy(_out_buf, n)
elseif _tonal_ref_centroid ~= nil then
-- Sigma-adaptive correction scale
local eff_str = tonal_str * sigma_ratio
if eff_str > 1e-6 then
local curr_centroid = spectral_centroid(_out_buf, n)
local curr_bands = band_energy(_out_buf, n)
-- Centroid drift: linear tilt across elements, capped at 0.1%
local drift_norm = (curr_centroid - _tonal_ref_centroid) /
(math.abs(_tonal_ref_centroid) + EPSILON)
local tilt = clamp(-drift_norm * eff_str, -1e-3, 1e-3)
local center = (n - 1) / 2.0
for i = 0, n - 1 do
local dist_w = (i - center) / (center + EPSILON)
_out_buf[i] = _out_buf[i] + tilt * dist_w * math.abs(_out_buf[i])
end
-- Band energy ratio correction, capped at 0.1% per band
local ref_total, curr_total = 0.0, 0.0
for b = 1, 4 do
ref_total = ref_total + _tonal_ref_bands[b]
curr_total = curr_total + curr_bands[b]
end
if ref_total > EPSILON and curr_total > EPSILON then
local bsize = math.floor(n / 4)
for b = 0, 3 do
local ref_ratio = _tonal_ref_bands[b + 1] / ref_total
local curr_ratio = curr_bands[b + 1] / curr_total
local band_corr = clamp((ref_ratio - curr_ratio) * eff_str, -1e-3, 1e-3)
local lo = b * bsize
local hi = (b == 3) and (n - 1) or (lo + bsize - 1)
for i = lo, hi do
_out_buf[i] = _out_buf[i] + band_corr * math.abs(_out_buf[i])
end
end
end
end
end
end
-- ── 10. Look-Back Smoother ────────────────────────────────────────────────
-- SNR-adaptive EMA: lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
-- Blends current output toward previous step output.
-- Heavy at high sigma (structure coherence), zero at sigma=0 (preserve detail).
-- Pattern from MD PingPong. arXiv:2602.09449.
if f_lookback and past_warmup and _has_prev then
local lb_w = lb_lambda * (sigma_ratio ^ lb_snr_power)
if lb_w > 1e-6 then
for i = 0, n - 1 do
_out_buf[i] = (1.0 - lb_w) * _out_buf[i] + lb_w * _prev_out_buf[i]
end
end
end
-- ── 11. RMS Servo ─────────────────────────────────────────────────────────
-- Downward-only RMS ceiling: fires only when cur_rms > rms_target.
-- Target descends from rms_target_max (high sigma) to rms_target_min (low sigma).
-- Curve: target = min + sigma_ratio^0.6 * (max - min) (from PingPong).
-- Pattern from MD PingPong.
if f_rms then
local rms_target = rms_tgt_min + (sigma_ratio ^ 0.6) * (rms_tgt_max - rms_tgt_min)
local cur_rms = rms(_out_buf, n)
if cur_rms > rms_target then
local servo_rms = cur_rms + rms_gain * (rms_target - cur_rms)
local scale = servo_rms / cur_rms
for i = 0, n - 1 do _out_buf[i] = _out_buf[i] * scale end
end
end
-- ── 12. Safety Clamp + NaN Guard ──────────────────────────────────────────
-- NaN/Inf in output: roll back to raw Euler result before clamping.
-- Abs ceiling applied regardless.
if not is_safe(_out_buf, n) then
for i = 0, n - 1 do _out_buf[i] = _fallback_buf[i] end
end
for i = 0, n - 1 do _out_buf[i] = clamp(_out_buf[i], -sclamp, sclamp) end
-- ── 13. Update state ──────────────────────────────────────────────────────
-- Store this step's output as prev_out_buf for next step.
-- Also push to memory ring buffer.
if past_warmup then
for i = 0, n - 1 do _prev_out_buf[i] = _out_buf[i] end
_has_prev = true
-- Ring buffer push
if f_memory then
for i = 0, n - 1 do _history[_hist_head][i] = _out_buf[i] end
_hist_head = _hist_head + 1
if _hist_head > 3 then _hist_head = 1 end
if _hist_count < 3 then _hist_count = _hist_count + 1 end
end
end
-- ── 14. Write output ──────────────────────────────────────────────────────
for i = 0, n - 1 do xt[i] = _out_buf[i] end
-- ── 15. SDE Noise Injection ───────────────────────────────────────────────
-- Applied after write-back, outside the safety clamp, matching OmniRelational
-- convention. scale = t_prev * eta — noise magnitude tracks current sigma level,
-- naturally fades to zero as generation converges.
if eta > 0.0 and t_prev > EPSILON then
local rng = make_rng(seed + step_idx * 7919)
local scale = t_prev * eta
for i = 0, n - 1 do
local u1 = math.max(rng(), EPSILON)
local u2 = rng()
xt[i] = xt[i] + normal(u1, u2) * scale
end
end
end
+218
View File
@@ -0,0 +1,218 @@
-- ============================================================================
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright (C) 2026 Alexander Allan (MDMAchine) -- A&E Concepts
-- ============================================================================
-- MD Vortex V1 -- Vorticity Damping Sampler
-- MDMAchine | A&E Concepts (c) 2026
--
-- Multi-scale curl proxy detects rotational velocity energy. Enstrophy EMA
-- triggers targeted vortex shedding (direct damping of vortical component).
-- Euler advance with cleaned velocity. owns_loop = true. Single NFE.
-- ============================================================================
local C = require("md_solver_commons")
-- ── CURL PROXY (per-batch) ──────────────────────────────────────────────────
local function compute_curl_proxy(v_curr, v_prev, off, cnt, multi_scale)
local curl = {}
for i = 0, cnt - 1 do curl[i] = 0.0 end
for i = 1, cnt - 2 do
local grad_curr = (v_curr[off + i + 1] - v_curr[off + i - 1]) * 0.5
local grad_prev = (v_prev[off + i + 1] - v_prev[off + i - 1]) * 0.5
curl[i] = curl[i] + (grad_curr - grad_prev)
end
if multi_scale then
for i = 2, cnt - 3 do
local grad_curr = (v_curr[off + i + 2] - v_curr[off + i - 2]) * 0.25
local grad_prev = (v_prev[off + i + 2] - v_prev[off + i - 2]) * 0.25
curl[i] = curl[i] + 0.5 * (grad_curr - grad_prev)
end
for i = 4, cnt - 5 do
local grad_curr = (v_curr[off + i + 4] - v_curr[off + i - 4]) * 0.125
local grad_prev = (v_prev[off + i + 4] - v_prev[off + i - 4]) * 0.125
curl[i] = curl[i] + 0.25 * (grad_curr - grad_prev)
end
end
local enstrophy, curl_max = 0.0, 0.0
for i = 0, cnt - 1 do
enstrophy = enstrophy + curl[i] * curl[i]
local ac = math.abs(curl[i])
if ac > curl_max then curl_max = ac end
end
return curl, enstrophy / math.max(cnt, 1), curl_max
end
local function apply_shedding(v_out, off, cnt, curl, enstrophy_ema,
threshold, strength, progressive)
if strength < 1e-6 or enstrophy_ema <= threshold then return false end
local eff_strength = strength
if progressive then
local overshoot = C.clamp((enstrophy_ema - threshold) / (threshold + C.EPSILON), 0.0, 2.0)
eff_strength = strength * (overshoot / 2.0)
end
if eff_strength < 1e-6 then return false end
local correction = {}
correction[0] = 0.0
for i = 1, cnt - 1 do correction[i] = correction[i - 1] + curl[i] end
local corr_energy = 0.0
for i = 0, cnt - 1 do corr_energy = corr_energy + correction[i] * correction[i] end
corr_energy = math.sqrt(corr_energy / math.max(cnt, 1) + C.EPSILON)
local scale = eff_strength / (corr_energy + C.EPSILON)
local v_rms = 0.0
for i = 0, cnt - 1 do v_rms = v_rms + v_out[off + i] * v_out[off + i] end
v_rms = math.sqrt(v_rms / math.max(cnt, 1) + C.EPSILON)
local max_corr = 0.05 * v_rms
for i = 0, cnt - 1 do
v_out[off + i] = v_out[off + i] - C.clamp(correction[i] * scale, -max_corr, max_corr)
end
return true
end
-- ── SOLVER DEFINITION ───────────────────────────────────────────────────────
solver = {
name = "md_vortex_v1",
display = "MD Vortex V1",
description = "Vorticity damping sampler. Multi-scale curl proxy, enstrophy tracking, targeted shedding. Batch-aware, shared anchor stack.",
nfe = 1,
order = 1,
needs_model = false,
stateful = true,
stochastic = true,
owns_loop = true,
params = {
{ key = "shedding_strength", type = "slider", label = "Shedding Strength",
default = 0.30, min = 0.0, max = 1.0, step = 0.05,
hint = "Vortical energy damping. 0 = monitor only." },
{ key = "enstrophy_threshold", type = "slider", label = "Enstrophy Threshold",
default = 0.02, min = 0.001, max = 0.2, step = 0.001,
hint = "EMA level triggering shedding. Calibrate with verbose=true." },
{ key = "enstrophy_ema_alpha", type = "slider", label = "Enstrophy EMA Alpha",
default = 0.1, min = 0.02, max = 0.5, step = 0.02,
hint = "Tracker responsiveness." },
{ key = "multi_scale", type = "toggle", label = "Multi-Scale Curl",
default = true, hint = "Adjacent + skip-2 + skip-4 gradient changes." },
{ key = "sigma_gate", type = "slider", label = "Sigma Gate",
default = 0.9, min = 0.5, max = 1.0, step = 0.05,
hint = "Sigma fraction above which shedding is disabled." },
{ key = "progressive_shedding", type = "toggle", label = "Progressive Shedding",
default = true, hint = "Strength scales with enstrophy overshoot." },
},
}
C.append_common_params(solver.params)
-- ── SAMPLE ──────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
local p = params or {}
local B, NPB = C.get_batch_routing(n)
local shed_str = C.num_param(p, "shedding_strength", 0.30)
local enst_thresh = C.num_param(p, "enstrophy_threshold", 0.02)
local enst_alpha = C.num_param(p, "enstrophy_ema_alpha", 0.1)
local f_multi = C.bool_param(p, "multi_scale", true)
local sigma_gate = C.num_param(p, "sigma_gate", 0.9)
local f_prog = C.bool_param(p, "progressive_shedding", true)
local opts = C.read_common_opts(p)
local state = C.new_state()
-- Engine schedule has NO trailing 0 (fix ported from 46c081e): iterate all ns
-- entries so the last iteration gets sigma_next = 0.0 and the terminal branch
-- performs the final x0 projection. With ns - 1 that branch is dead code and
-- the output keeps ~final-sigma noise.
local ns, n_steps = #schedule, #schedule
if n_steps < 1 then return end
local sigma_max = schedule[1]
local v_prev = nil
local enst_ema = {}
for b = 0, B - 1 do enst_ema[b] = 0.0 end
local x = C.fa_to_tbl(xt, n)
if opts.verbose then
print(string.format("[VORTEX V1] Schedule: %d steps | B=%d NPB=%d | shed=%.2f thresh=%.4f",
n_steps, B, NPB, shed_str, enst_thresh))
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
local step_idx = i - 1
local sigma_ratio = C.clamp(sigma_curr / math.max(sigma_max, C.EPSILON), 0.0, 1.0)
if sigma_next == 0.0 then
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_final = C.fa_to_tbl(vt_buf, n)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
break
end
C.tbl_to_fa(x, xt, n)
model_fn(xt, sigma_curr)
local v_curr = C.fa_to_tbl(vt_buf, n)
local dt = sigma_next - sigma_curr
-- Relational decomposition
if opts.rw > 0 then
C.apply_relational(v_curr, n, B, NPB, sigma_ratio, sigma_max,
opts.rw, opts.rw_sigma_pow, opts.drift_on, opts.drift_thr, x)
end
local shedding_active, max_enst, max_curl = false, 0.0, 0.0
if v_prev ~= nil and sigma_ratio < sigma_gate then
local v_shed = C.vec_clone(v_curr, n)
for b = 0, B - 1 do
local off = b * NPB
local curl, enstrophy, curl_max = compute_curl_proxy(v_curr, v_prev, off, NPB, f_multi)
enst_ema[b] = (1.0 - enst_alpha) * enst_ema[b] + enst_alpha * enstrophy
if enst_ema[b] > max_enst then max_enst = enst_ema[b] end
if curl_max > max_curl then max_curl = curl_max end
if apply_shedding(v_shed, off, NPB, curl, enst_ema[b], enst_thresh, shed_str, f_prog) then
shedding_active = true
end
end
if not C.has_nan_inf(v_shed, n) then v_curr = v_shed end
end
v_prev = C.vec_clone(v_curr, n)
local x_new = {}
for j = 0, n - 1 do x_new[j] = x[j] + dt * v_curr[j] end
if C.has_nan_inf(x_new, n) then
local v_raw = C.fa_to_tbl(vt_buf, n)
for j = 0, n - 1 do x_new[j] = x[j] + dt * v_raw[j] end
end
opts.sigma_next = sigma_next
opts.step_idx = step_idx
C.post_advance(x_new, n, B, NPB, sigma_ratio, opts, state)
if opts.verbose then
print(string.format("[VORTEX V1] step %02d | enst_ema=%.5f %s | curl_max=%.4f | rms=%.3f",
step_idx, enst_ema[0] or 0, shedding_active and "SHEDDING" or "quiet",
max_curl, C.rms(x_new, n)))
end
x = x_new
C.tbl_to_fa(x, xt, n)
C.tbl_to_fa(v_curr, vt_buf, n)
if on_step(step_idx, sigma_curr, sigma_next) then return end
x = C.fa_to_tbl(xt, n)
end
C.tbl_to_fa(x, xt, n)
end
+518
View File
@@ -0,0 +1,518 @@
--[[
storm_sampler_core.lua
STORM -- Stabilized Taylor Oscillation with Runge-Kutta Memory
Adaptive hybrid solver: STORK (stiff) + DPM++3M (stable), per-step dispatch
© 2026 Alexander Allan (MDMAchine) | A&E Concepts
GPL v3 -- Public version. Gradient norm stiffness detection only.
Adapted for HOT-Step full-loop plugin API (owns_loop = true).
All data uses 0-indexed FloatArray or 0-indexed Lua tables.
Version: 3.0.0 (HOT-Step plugin port from v2.1.0)
--]]
solver = {
name = "storm",
display = "STORM",
description = "Adaptive STORK/DPM++3M hybrid with stiffness detection",
accent = "cyan",
nfe = 0,
order = 5,
needs_model = false,
stateful = true,
stochastic = false,
owns_loop = true,
params = {
{ key = "stiffness_threshold", type = "slider", label = "Detail Sensitivity",
default = 0.15, min = 0.05, max = 0.50, step = 0.01,
hint = "How aggressively complex passages get extra precision. Lower = more careful on transients and busy sections, higher = faster but looser" },
{ key = "look_back_lambda", type = "slider", label = "Coherence Smoothing",
default = 0.15, min = 0, max = 1, step = 0.01,
hint = "Blends each step with previous ones for smoother output. 0 = off (raw), higher = more coherent but softer detail" },
{ key = "look_back_snr_power", type = "slider", label = "Early-Step Focus",
default = 1.5, min = 0.5, max = 3, step = 0.1,
hint = "Concentrates smoothing on early noisy steps (structure). Higher = smooths structure more, leaves fine detail alone" },
{ key = "rk_order", type = "select", label = "Precision Level",
default = "auto",
options = {
{ value = "auto", label = "Auto (Recommended)" },
{ value = "2", label = "Low (RK2)" },
{ value = "3", label = "Medium (RK3)" },
{ value = "4", label = "High (RK4)" },
{ value = "5", label = "Maximum (RK5)" },
},
hint = "Solver accuracy per step. Auto ramps up gradually. Higher = cleaner but more compute per step" },
{ key = "cache_depth", type = "slider", label = "History Memory",
default = 5, min = 2, max = 10, step = 1,
hint = "How many previous steps the solver remembers. More = smoother multi-step blending, but diminishing returns past 5" },
{ key = "verbose", type = "toggle", label = "Verbose Logging",
default = false,
hint = "Print per-step solver decisions to the console (debug)" },
},
}
-- ─────────────────────────────────────────────────────────────────────────────
-- HELPERS: FloatArray ↔ Lua table (0-indexed)
-- ─────────────────────────────────────────────────────────────────────────────
local function fa_to_tbl(fa, n)
local t = {}
for i = 0, n - 1 do t[i] = fa[i] end
return t
end
local function tbl_to_fa(t, fa, n)
for i = 0, n - 1 do fa[i] = t[i] end
end
-- ─────────────────────────────────────────────────────────────────────────────
-- MATH HELPERS (0-indexed Lua tables)
-- ─────────────────────────────────────────────────────────────────────────────
local function vec_norm(v, n)
local s = 0.0
for i = 0, n - 1 do s = s + v[i] * v[i] end
return math.sqrt(s)
end
local function vec_sub_norm(a, b, n)
local s = 0.0
for i = 0, n - 1 do local d = a[i] - b[i]; s = s + d * d end
return math.sqrt(s)
end
local function vec_dot(a, b, n)
local s = 0.0
for i = 0, n - 1 do s = s + a[i] * b[i] end
return s
end
local function vec_clone(v, n)
local c = {}
for i = 0, n - 1 do c[i] = v[i] end
return c
end
local function has_nan_inf_tbl(v, n)
for i = 0, n - 1 do
if v[i] ~= v[i] or math.abs(v[i]) == math.huge then return true end
end
return false
end
local function clamp(x, lo, hi) return math.max(lo, math.min(hi, x)) end
local function randn_iso(n, scale)
local out = {}
for i = 0, n - 1, 2 do
local u1 = math.max(1e-12, math.random())
local u2 = math.random()
local r = scale * math.sqrt(-2.0 * math.log(u1))
out[i] = r * math.cos(2 * math.pi * u2)
if i + 1 < n then
out[i + 1] = r * math.sin(2 * math.pi * u2)
end
end
return out
end
-- ─────────────────────────────────────────────────────────────────────────────
-- LOOK-BACK SMOOTHER (arXiv:2602.09449)
-- ─────────────────────────────────────────────────────────────────────────────
local function look_back_smooth(x_curr, x_prev, sigma_curr, sigma_max, lambda_base, snr_power, n)
if x_prev == nil then return x_curr, 0.0 end
local ratio = math.min(sigma_curr / math.max(sigma_max, 1e-8), 1.0)
local lam = lambda_base * (ratio ^ snr_power)
local out = {}
for i = 0, n - 1 do out[i] = (1.0 - lam) * x_curr[i] + lam * x_prev[i] end
return out, lam
end
-- ─────────────────────────────────────────────────────────────────────────────
-- STIFFNESS DETECTION
-- ─────────────────────────────────────────────────────────────────────────────
local function compute_stiffness(v_curr, v_cache, step_idx, baseline, threshold, ema_alpha, n_calib, n)
threshold = threshold or 0.15
ema_alpha = ema_alpha or 0.3
n_calib = n_calib or 4
if #v_cache < 1 then return true, baseline, nil end
local v_prev = v_cache[#v_cache].v
local norm_delta = vec_sub_norm(v_curr, v_prev, n)
local norm_curr = vec_norm(v_curr, n) + 1e-8
local raw_ratio = norm_delta / norm_curr
local prev_ema = baseline.ema or raw_ratio
local smoothed = ema_alpha * raw_ratio + (1.0 - ema_alpha) * prev_ema
baseline.ema = smoothed
local dot = vec_dot(v_curr, v_prev, n)
local nc = vec_norm(v_curr, n)
local np_ = vec_norm(v_prev, n)
local cos_sim = dot / (nc * np_ + 1e-8)
if step_idx < n_calib then
baseline.sum = (baseline.sum or 0.0) + smoothed
baseline.count = (baseline.count or 0) + 1
baseline.last_ratio = smoothed
return true, baseline, cos_sim
end
local bmean = baseline.sum / math.max(baseline.count, 1)
local adap_thr = threshold * (bmean / 0.15)
adap_thr = clamp(adap_thr, 0.05, 0.50)
local stiff = smoothed > adap_thr
baseline.last_ratio = smoothed
baseline.last_threshold = adap_thr
return stiff, baseline, cos_sim
end
-- ─────────────────────────────────────────────────────────────────────────────
-- STORK MULTI-ORDER (AB2-AB5, single NFE, cached derivatives)
-- ─────────────────────────────────────────────────────────────────────────────
local function stork_step(v_cache, x, sigma_curr, sigma_next, v_curr, rk_order, n)
local dt = sigma_next - sigma_curr
local n_cache = #v_cache
local actual_order
if rk_order == "auto" then
actual_order = (n_cache >= 1) and math.min(n_cache + 1, 5) or 1
else
actual_order = (n_cache >= 1) and math.min(tonumber(rk_order), n_cache + 1) or 1
end
actual_order = math.max(actual_order, 1)
if n_cache < 1 or actual_order <= 1 then
local x_next = {}
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
return x_next, 1
end
local e0 = v_cache[#v_cache]
local v_prev_0 = e0.v
local sigma_prev = e0.sigma
-- Curvature damping
local dot = vec_dot(v_curr, v_prev_0, n)
local nc = vec_norm(v_curr, n)
local np_ = vec_norm(v_prev_0, n)
local cos_sim = dot / (nc * np_ + 1e-8)
local damping = clamp(cos_sim, 0.0, 1.0)
local denom = sigma_curr - sigma_prev
if math.abs(denom) < 1e-8 then
local x_next = {}
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
return x_next, 2
end
local alpha = (sigma_next - sigma_curr) / denom
local x_next = {}
if actual_order == 2 then
for i = 0, n - 1 do
local v_extrap = v_curr[i] + (alpha * damping) * (v_curr[i] - v_prev_0[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * v_extrap)
end
elseif actual_order == 3 and n_cache >= 2 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 then
for i = 0, n - 1 do
local ve = v_curr[i] + (alpha * damping) * (v_curr[i] - v1[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * ve)
end
actual_order = 2
else
local c0 = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do
local v_pred = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (v_pred - v_curr[i]))
end
end
elseif actual_order == 4 and n_cache >= 3 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local v3, s3 = v_cache[#v_cache - 2].v, v_cache[#v_cache - 2].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
local h2 = s2 - s3
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 or math.abs(h2) < 1e-8 then
local c0 = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 3
else
local c0 = 1.0 + dt / (2.0 * h) + dt ^ 2 / (3.0 * h * h1) + dt ^ 3 / (4.0 * h * h1 * h2)
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1 + dt ^ 2 / (2.0 * h1 * h2))
local c2 = (dt ^ 2 / (3.0 * h * h1)) * (1.0 + dt / (2.0 * h2))
local c3 = -(dt ^ 3) / (4.0 * h * h1 * h2)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i] + c3 * v3[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
end
elseif actual_order >= 5 and n_cache >= 4 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local v3, s3 = v_cache[#v_cache - 2].v, v_cache[#v_cache - 2].sigma
local v4, s4 = v_cache[#v_cache - 3].v, v_cache[#v_cache - 3].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
local h2 = s2 - s3
local h3 = s3 - s4
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 or math.abs(h2) < 1e-8 or math.abs(h3) < 1e-8 then
local c0 = 1.0 + dt / (2.0 * h) + dt ^ 2 / (3.0 * h * h1) + dt ^ 3 / (4.0 * h * h1 * h2)
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1 + dt ^ 2 / (2.0 * h1 * h2))
local c2 = (dt ^ 2 / (3.0 * h * h1)) * (1.0 + dt / (2.0 * h2))
local c3 = -(dt ^ 3) / (4.0 * h * h1 * h2)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i] + c3 * v3[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 4
else
local c0 = 1.0 + dt / (2.0 * h) + dt ^ 2 / (3.0 * h * h1) + dt ^ 3 / (4.0 * h * h1 * h2) + dt ^ 4 / (5.0 * h * h1 * h2 * h3)
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1 + dt ^ 2 / (2.0 * h1 * h2) + dt ^ 3 / (3.0 * h1 * h2 * h3))
local c2 = (dt ^ 2 / (3.0 * h * h1)) * (1.0 + dt / (2.0 * h2) + dt ^ 2 / (3.0 * h2 * h3))
local c3 = -(dt ^ 3 / (4.0 * h * h1 * h2)) * (1.0 + dt / (2.0 * h3))
local c4 = dt ^ 4 / (5.0 * h * h1 * h2 * h3)
for i = 0, n - 1 do
local vp = c0 * v_curr[i] + c1 * v1[i] + c2 * v2[i] + c3 * v3[i] + c4 * v4[i]
x_next[i] = x[i] + dt * (v_curr[i] + damping * (vp - v_curr[i]))
end
actual_order = 5
end
else
-- Fallback AB2
for i = 0, n - 1 do
local ve = v_curr[i] + (alpha * damping) * (v_curr[i] - v_prev_0[i])
x_next[i] = x[i] + dt * (0.5 * v_curr[i] + 0.5 * ve)
end
actual_order = 2
end
return x_next, actual_order
end
-- ─────────────────────────────────────────────────────────────────────────────
-- DPM++3M -- smooth schedule path
-- ─────────────────────────────────────────────────────────────────────────────
local function dpmpp3m_step(v_cache, x, sigma_curr, sigma_next, v_curr, n)
local dt = sigma_next - sigma_curr
local x_next = {}
if #v_cache >= 2 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local v2, s2 = v_cache[#v_cache - 1].v, v_cache[#v_cache - 1].sigma
local h = sigma_curr - s1
local h1 = s1 - s2
if math.abs(h) < 1e-8 or math.abs(h1) < 1e-8 then
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
else
local cc = 1.0 + (dt / (2.0 * h)) + (dt ^ 2 / (3.0 * h * h1))
local c1 = -(dt / (2.0 * h)) * (1.0 + dt / h1)
local c2 = (dt ^ 2) / (3.0 * h * h1)
for i = 0, n - 1 do x_next[i] = x[i] + dt * (cc * v_curr[i] + c1 * v1[i] + c2 * v2[i]) end
end
elseif #v_cache >= 1 then
local v1, s1 = v_cache[#v_cache].v, v_cache[#v_cache].sigma
local h = sigma_curr - s1
if math.abs(h) < 1e-8 then
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
else
for i = 0, n - 1 do x_next[i] = x[i] + dt * (v_curr[i] + (dt / (2.0 * h)) * (v_curr[i] - v1[i])) end
end
else
for i = 0, n - 1 do x_next[i] = x[i] + dt * v_curr[i] end
end
return x_next
end
-- ─────────────────────────────────────────────────────────────────────────────
-- SAMPLE — Full-loop entry point
-- ─────────────────────────────────────────────────────────────────────────────
function sample(xt, vt_buf, schedule, n, model_fn)
-- Read params
local p = params or {}
local thr = p.stiffness_threshold or 0.15
local lb_lambda = p.look_back_lambda or 0.35
local lb_snr_pow = p.look_back_snr_power or 1.5
local rk_order = p.rk_order or "auto"
local depth_max = p.cache_depth or 5
local verbose = p.verbose or false
local hyst = 0.05
local ema_a = 0.3
local calib_frac = 0.12
local ns = #schedule
-- The engine's schedule table has NO trailing 0 (unlike ComfyUI sigmas):
-- iterate all ns entries so the last iteration gets sigma_next = 0.0 and
-- the terminal branch performs the final x0 projection. With ns - 1 the
-- terminal branch is dead code and the output keeps ~final-sigma noise.
local n_steps = ns
if n_steps < 1 then return end
local v_cache = {}
local baseline = { sum = 0.0, count = 0 }
local sigma_max = schedule[1]
local n_calib = math.max(2, math.min(5, math.floor(n_steps * calib_frac)))
local lb_enabled = (lb_lambda > 0)
if verbose then
print(string.format("[STORM] Schedule: %d steps | Calib: %d | RK: %s | Cache: %d",
n_steps, n_calib, tostring(rk_order), depth_max))
end
-- Working copy of xt as a Lua table (we write back to FloatArray at each step)
local x = fa_to_tbl(xt, n)
-- Seed x_prev for look-back
local x_prev_lb = nil
if lb_enabled then
x_prev_lb = {}
for i = 0, n - 1 do
local u1 = math.max(1e-12, math.random())
local u2 = math.random()
local r = (sigma_max * 0.1) * math.sqrt(-2.0 * math.log(u1))
x_prev_lb[i] = x[i] + r * math.cos(2 * math.pi * u2)
end
end
-- Helper: evaluate model and return velocity as Lua table
local function eval_model(x_tbl)
tbl_to_fa(x_tbl, xt, n)
model_fn(xt, 0) -- dummy t, we'll set it properly below
return fa_to_tbl(vt_buf, n)
end
-- Proper eval: writes x_tbl to xt, calls model at t_val, returns velocity table
local function eval_at(x_tbl, t_val)
tbl_to_fa(x_tbl, xt, n)
model_fn(xt, t_val)
return fa_to_tbl(vt_buf, n)
end
for i = 1, n_steps do
local sigma_curr = schedule[i]
local sigma_next = (i < ns) and schedule[i + 1] or 0.0
-- Terminal step
if sigma_next == 0.0 then
local v_final = eval_at(x, sigma_curr)
for j = 0, n - 1 do x[j] = x[j] - v_final[j] * sigma_curr end
if verbose then
print(string.format("[STORM] Step %02d: FINAL (Euler terminal)", i - 1))
end
break
end
local x_prev_lb_before = nil
if lb_enabled then x_prev_lb_before = vec_clone(x, n) end
-- Evaluate velocity
local v_curr = eval_at(x, sigma_curr)
-- Stiffness detection
local stiff, cos_sim_out
if #v_cache >= 1 then
stiff, baseline, cos_sim_out = compute_stiffness(
v_curr, v_cache, i - 1, baseline, thr, ema_a, n_calib, n)
else
stiff, cos_sim_out = true, nil
end
-- Hysteresis
local prev_mode = baseline.prev_mode or "STORK"
if prev_mode == "DPM++" and not stiff then
if (baseline.last_ratio or 0) > (baseline.last_threshold or thr) + hyst then
stiff = true
end
end
-- Dispatch
local x_next, actual_order, mode
if stiff then
x_next, actual_order = stork_step(v_cache, x, sigma_curr, sigma_next, v_curr, rk_order, n)
mode = "STORK"
else
x_next = dpmpp3m_step(v_cache, x, sigma_curr, sigma_next, v_curr, n)
mode = "DPM++"
actual_order = 3
end
-- Verbose
if verbose then
local lr = baseline.last_ratio or 0.0
local lt = baseline.last_threshold or thr
local cs = cos_sim_out and string.format("%.4f", cos_sim_out) or "N/A"
local tag = (stiff and prev_mode == "DPM++") and " -> CURVATURE SPIKE" or ""
print(string.format("[STORM] Step %02d: %-5s RK%d | Ratio: %.3f | Thr: %.3f | cos: %s%s",
i - 1, mode, actual_order, lr, lt, cs, tag))
end
-- NaN guard
if has_nan_inf_tbl(x_next, n) then
print(string.format("[STORM] NaN/Inf at step %d. Flushing cache.", i - 1))
local dt = sigma_next - sigma_curr
x_next = {}
for j = 0, n - 1 do x_next[j] = x[j] + dt * v_curr[j] end
v_cache = {}
baseline.prev_mode = "STORK"
actual_order = 1
end
-- Update cache
table.insert(v_cache, { v = v_curr, sigma = sigma_curr })
while #v_cache > depth_max do table.remove(v_cache, 1) end
baseline.prev_mode = mode
x = x_next
-- Look-Back smoothing
if lb_enabled and x_prev_lb ~= nil then
local lam
x, lam = look_back_smooth(x, x_prev_lb, sigma_curr, sigma_max, lb_lambda, lb_snr_pow, n)
if verbose then
print(string.format("[STORM] LookBack λ=%.4f @ σ=%.3f", lam, sigma_curr))
end
end
x_prev_lb = x_prev_lb_before
-- Write x back to xt FloatArray for on_step hooks (DCW, repaint)
tbl_to_fa(x, xt, n)
-- Write velocity to vt_buf for DCW
tbl_to_fa(v_curr, vt_buf, n)
-- Report step (engine hooks: DCW, repaint, progress)
if on_step(i - 1, sigma_curr, sigma_next) then return end
-- Re-read xt in case hooks modified it (DCW, repaint)
x = fa_to_tbl(xt, n)
end
-- Write final x0 to xt
tbl_to_fa(x, xt, n)
end