research: Dütsch analyzer — horizon-limited coverage/events, Parseval HF weights

Coverage and no_donor/eq_only totals now stop at the horizon; HF energy
uses one-sided Parseval weights (interior bins x2, DC/Nyquist x1). Focused
regressions for both. run.py, src and thresholds unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-10-30 12:07:45 +00:00
co-authored by Claude Opus 5.5
parent 430bcf2fc3
commit dd37883d3c
2 changed files with 58 additions and 3 deletions
+16 -3
View File
@@ -107,13 +107,23 @@ def hf_component(case, ks):
spec = np.fft.rfft(resid)
high = spec.copy()
high[:21] = 0.0
energy = np.abs(spec) ** 2
energy = one_sided_energy(spec, T)
return dict(label="unqualified phase-locked HF force component, not checked",
cycles=list(ks), hf_rms_over_F_ref=float(np.sqrt(np.mean(np.fft.irfft(high, T) ** 2))),
hf_energy_fraction=float(energy[21:].sum() / max(energy.sum(), 1e-300)),
top_harmonics=[int(j) for j in np.argsort(energy[21:])[::-1][:5] + 21])
def one_sided_energy(spec, n):
"""Parseval-consistent energy per rfft bin: interior bins count twice, DC and
(for even n) Nyquist once."""
w = np.full(len(spec), 2.0)
w[0] = 1.0
if n % 2 == 0:
w[-1] = 1.0
return w * np.abs(spec) ** 2
def ledger(case, horizon=p.LAST_SCORED):
ks = [k for k in sorted(cycle_slices(case)) if k <= horizon]
pairs = {k: pair_metrics(case, k) for k in ks if k - 1 > p.DISCARD}
@@ -144,11 +154,14 @@ def main(argv):
horizon_mode="default-cap" if horizon == p.LAST_SCORED else
"extended duration diagnostic (declared amendment, D40/60D only)")
for case, (pairs, k_case) in zip(cases, ledgers):
limit = round((0.25 + horizon) * case["T"]) # events inside the horizon only
entry = {key: case[key] for key in ("path", "D", "T", "box", "n", "u_max", "nu", "tau", "A",
"steps", "cycles", "F_ref")}
entry.update(duration_T=case["steps"] / case["T"], K_case=k_case, pairs=pairs,
coverage={k: coverage(case, k) for k in sorted(cycle_slices(case)) if k > p.DISCARD},
no_donor=float(case["diag"][:, 13].sum()), eq_only=float(case["diag"][:, 12].sum()),
coverage={k: coverage(case, k) for k in sorted(cycle_slices(case))
if p.DISCARD < k <= horizon},
no_donor=float(case["diag"][:limit, 13].sum()),
eq_only=float(case["diag"][:limit, 12].sum()),
hf_component=hf_component(case, window or tuple(
[k for k in sorted(cycle_slices(case)) if p.DISCARD < k <= horizon][-3:])))
if window: # residual drift inside the scored window (pairs K..K+2)