Initial release
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user