558 lines
23 KiB
Lua
558 lines
23 KiB
Lua
-- ============================================================================
|
|
-- 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
|