22t: two-factor laws REFUTED by descent (quad Q->0, resrp rp->0 — geometry already in lvl=am/res); real render of sim-optimum 7.6/1.694 = 1.898 with group regressions, canon stays; error budget: dual = 62% of corpus abs-error -> inter-band acc/f6f8 consumer is priority #1
This commit is contained in:
@@ -245,6 +245,17 @@ static void process_band_structural(
|
||||
}
|
||||
}
|
||||
|
||||
// RT_RESPRP=1 (NOTES 22t): keep ONLY the res^rp factor of the warp cascade
|
||||
// while NOWARP skips the full kBand768*kWarp*res^rp blanket. Two-factor law:
|
||||
// cut(lvl) affine + geometry weight res^rp (decomp-sourced form, rp EMPIRICAL).
|
||||
static const int resrp_only = getenv("RT_RESPRP") ? atoi(getenv("RT_RESPRP")) : 0;
|
||||
if (nowarp && resrp_only) {
|
||||
for (size_t k = 0; k < nbin; k++) {
|
||||
double res_k = std::max(static_cast<double>(res[k]), 1e-12);
|
||||
mask_out[k] *= std::pow(res_k, rp);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9 (NOTES_LEVEL:830 + consumers_out.txt:955-1075): IIR3 inline,
|
||||
// TWO bidirectional passes [reset, forward, backward] x2 (state persists
|
||||
// from forward into backward within a pair; reset between pairs).
|
||||
@@ -330,6 +341,11 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
|
||||
res_.clear();
|
||||
track_.clear();
|
||||
|
||||
// RT_DUMPRESPATH=<file> (NOTES 22t): static twin-response spectra per band,
|
||||
// binary {int32 band, int32 nbin, float res[nbin]} records (append).
|
||||
FILE* rp_dump = nullptr;
|
||||
if (const char* dp = getenv("RT_DUMPRESPATH")) rp_dump = fopen(dp, "ab");
|
||||
|
||||
for (const auto& b : bands_) {
|
||||
std::vector<float> r(half + 1, 1.0f);
|
||||
float sens_lin = std::pow(10.0f, b.sens * SENS_SCALE / 20.0f);
|
||||
@@ -348,8 +364,16 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
|
||||
r[k] = std::sqrt(out[k].re * out[k].re + out[k].im * out[k].im);
|
||||
r[k] = std::max(r[k], 1e-12f);
|
||||
}
|
||||
if (rp_dump) {
|
||||
int32_t bi = static_cast<int32_t>(res_.size());
|
||||
int32_t nb = static_cast<int32_t>(r.size());
|
||||
fwrite(&bi, sizeof(int32_t), 1, rp_dump);
|
||||
fwrite(&nb, sizeof(int32_t), 1, rp_dump);
|
||||
fwrite(r.data(), sizeof(float), r.size(), rp_dump);
|
||||
}
|
||||
res_.push_back(std::move(r));
|
||||
}
|
||||
if (rp_dump) fclose(rp_dump);
|
||||
track_.assign(bands_.size(), std::vector<float>(half + 1, 1.0f));
|
||||
}
|
||||
|
||||
|
||||
@@ -2016,3 +2016,45 @@ x_i=−dA*/dS — точная эффективная координата ке
|
||||
не решается, подтверждено покейсовой бисекцией.
|
||||
3. Реплей-инфраструктура готова для быстрого перебора ЛЮБЫХ параметрических законов
|
||||
(2 c корпус) — использовать вместо реальных рендеров до финальной валидации.
|
||||
|
||||
## ============ UPDATE 2026-08-23b (22t): ДВУХФАКТОРНЫЕ ЗАКОНЫ ОТВЕРГНУТЫ — БЮДЖЕТ ОШИБКИ В DUAL ============
|
||||
|
||||
Инструменты расширены:
|
||||
- `RT_DUMPRESPATH=<file>` в FramedDetector::setParams — статические спектры twin-response
|
||||
res[band][bin] ({i32 band,i32 nbin,f32[]}, append); lawfit collect кладёт их в npz рядом с lvl.
|
||||
- `RT_RESPRP=1` в process_band_structural — применять res^rp при NOWARP (для реальных
|
||||
рендеров двухфакторного закона).
|
||||
- `lawfit22r.py fit`: координатный спуск по семействам законов на реплее (2 c/корпус).
|
||||
|
||||
### Результат фит-семейств (офлайн, старт 7.6/1.6)
|
||||
| семейство | A | S | Q | rp | TOTAL(sim) |
|
||||
|-----------|---|---|---|----|-----------|
|
||||
| scalar | 7.600 | 1.694 | 0 | 0 | **1.860** |
|
||||
| quad (Q·x²) | →scalar | | **→0** | | 1.860 |
|
||||
| resrp (mask·res^rp) | →scalar | | | **→0** | 1.860 |
|
||||
| quad+resrp | 7.452 | 1.540 | +0.069 | 0 | 1.865 |
|
||||
|
||||
ОБА вторых фактора отброшены спуском: кривизна не нужна, res-вес не нужен.
|
||||
Объяснение resrp: lvl = am/res УЖЕ содержит геометрию твин-юбки; домножение на
|
||||
res^rp сверху — двойной учёт (в каноне res^rp компенсировал другое — отсутствие
|
||||
res в xv-домене LUT).
|
||||
|
||||
### Реальный рендер скалярного оптимума (7.6, 1.694)
|
||||
t1kq 0.954 / t1k 0.842 / al 0.799 / res **0.117** / dual **3.246** / comb 4.855
|
||||
⇒ TOTAL **1.898** (sim 1.860 + стат. смещения реплея ~0.04). Против HEAD 1.931:
|
||||
выигрыш мизерный, t1kq/t1k/comb РЕГРЕССИРУЮТ ⇒ гейт не пройден, канон НЕ тронут.
|
||||
|
||||
### Бюджет ошибки — где лежит остаток (решающий факт)
|
||||
abs-сумма по корпусу при (7.6,1.694): 115.4 дБ·кейсов, из них:
|
||||
- **dual: 22 кейса × 3.246 = 71.4 (62%!)** — межполосный механизм (Шаг 2/9, консюмер acc/f6f8);
|
||||
- comb: 24.3 (21%) — мультиполосное взаимодействие;
|
||||
- всё прочее: ~20 (17%).
|
||||
Если бы dual был на уровне bridge (0.726), TOTAL упал бы до ~0.96. Никакая
|
||||
донастройка закона это не даст — ПРИОРИТЕТ №1 подтверждён количественно:
|
||||
искать точку потребления acc/f6f8 (межполосный каскад), а не крутить маску.
|
||||
|
||||
### NEXT
|
||||
1. dual/межполосный консюмер: декомп-охота вокруг FUN_180529fe0 callers
|
||||
(52e260 render-loop, multiband combine), трассировка acc/f6f8 в живом плагине.
|
||||
2. comb: после dual — пере-тест 4-полосного взаимодействия.
|
||||
3. Реплей-инфра готова; для любых новых гипотез сначала офлайн (2 c), потом рендер.
|
||||
|
||||
+102
-48
@@ -91,10 +91,14 @@ def collect():
|
||||
|
||||
store = {}
|
||||
traj_bin = '/tmp/opencode/lawfit_traj.bin'
|
||||
resp_bin = '/tmp/opencode/lawfit_res.bin'
|
||||
for key, u in sorted(uniq.items()):
|
||||
if os.path.exists(traj_bin):
|
||||
os.remove(traj_bin)
|
||||
env = {**os.environ, **BASE_ENV, 'RT_DUMP_ALL': traj_bin}
|
||||
if os.path.exists(resp_bin):
|
||||
os.remove(resp_bin)
|
||||
env = {**os.environ, **BASE_ENV, 'RT_DUMP_ALL': traj_bin,
|
||||
'RT_DUMPRESPATH': resp_bin}
|
||||
subprocess.run([corpus.RB, u['inp'], '/tmp/opencode/lawfit_out.wav'] + u['args'],
|
||||
capture_output=True, text=True, env=env,
|
||||
cwd='/home/m/re-tools')
|
||||
@@ -104,9 +108,26 @@ def collect():
|
||||
continue
|
||||
lvl = arr.reshape(-1, u['nb'], RBIN) # [frame][band][bin]
|
||||
store[key] = lvl
|
||||
print(f'{key}: {lvl.shape[0]} frames x {u["nb"]} bands')
|
||||
# static twin-response spectra per band ({i32 band, i32 nbin, f32[nbin]})
|
||||
res = None
|
||||
if os.path.exists(resp_bin):
|
||||
raw = open(resp_bin, 'rb').read()
|
||||
off = 0
|
||||
bands = []
|
||||
while off < len(raw):
|
||||
bi, nb = struct.unpack_from('<ii', raw, off)
|
||||
off += 8
|
||||
r = np.frombuffer(raw, dtype='<f4', count=nb, offset=off)
|
||||
off += 4 * nb
|
||||
bands.append(r.astype(np.float32))
|
||||
if len(bands) == u['nb'] and all(b.size == RBIN for b in bands):
|
||||
res = np.stack(bands)
|
||||
if res is not None:
|
||||
store[key + '|res'] = res
|
||||
print(f'{key}: {lvl.shape[0]} frames x {u["nb"]} bands'
|
||||
+ (' +res' if res is not None else ' NORES'))
|
||||
np.savez_compressed(TRAJ, **store)
|
||||
print(f'\nwrote {len(store)} trajectories -> {TRAJ}')
|
||||
print(f'\nwrote {len(store)} entries -> {TRAJ}')
|
||||
|
||||
|
||||
def load_traj():
|
||||
@@ -135,16 +156,24 @@ class CaseInput:
|
||||
return lvl.shape[0] == self.nf
|
||||
|
||||
|
||||
def masks_from_lvl(lvl, nb, A, S):
|
||||
"""Per-frame lower-half mask exactly as bare chain + LAWAFFINE produces."""
|
||||
def masks_from_lvl(lvl, nb, A, S, Q=0.0, res=None, rp=0.0):
|
||||
"""Per-frame lower-half mask exactly as bare chain + LAWAFFINE produces.
|
||||
|
||||
Law families (22t): scalar cut=A+S*log2(lvl); quad adds curvature Q*x^2;
|
||||
resrp multiplies per-band mask by res^rp (decomp warp-cascade factor,
|
||||
applied BEFORE cross-band min like the C++ warp section).
|
||||
"""
|
||||
nf = lvl.shape[0]
|
||||
lv64 = lvl.astype(np.float64)
|
||||
mm = np.exp2(-((A + S * np.log2(np.maximum(lv64, 1e-12))) / 6.0174))
|
||||
x = np.log2(np.maximum(lv64, 1e-12))
|
||||
mm = np.exp2(-((A + S * x + Q * x * x) / 6.0174))
|
||||
low = lv64 <= 1e-6 # C++ fallback: exp2(-level)
|
||||
if low.any():
|
||||
mm[low] = np.exp2(-lv64[low])
|
||||
if res is not None and rp:
|
||||
mm = mm * res[None].astype(np.float64) ** rp
|
||||
if nb > 1:
|
||||
lo = np.min(mm, axis=1)
|
||||
lo = np.min(mm, axis=1) # min across bands
|
||||
else:
|
||||
lo = mm[:, 0, :]
|
||||
full = np.ones((nf, NFFT))
|
||||
@@ -153,10 +182,10 @@ def masks_from_lvl(lvl, nb, A, S):
|
||||
return full
|
||||
|
||||
|
||||
def replay(ci, lvl, nb, A, S):
|
||||
"""Return trimmed 44.1k output for law (A,S) on prepared CaseInput ci."""
|
||||
def replay(ci, lvl, nb, A, S, Q=0.0, res=None, rp=0.0):
|
||||
"""Return trimmed 44.1k output for law params on prepared CaseInput ci."""
|
||||
assert ci.frames_ok(lvl), f'traj {lvl.shape[0]} != frames {ci.nf}'
|
||||
full = masks_from_lvl(lvl, nb, A, S)
|
||||
full = masks_from_lvl(lvl, nb, A, S, Q, res, rp)
|
||||
segs = ci.xext[ci.offs[:, None] + np.arange(NFFT)[None, :]] * _WIN[None, :]
|
||||
yspec = np.fft.fft(segs, axis=1) * full
|
||||
td = np.real(np.fft.ifft(yspec, axis=1))
|
||||
@@ -203,20 +232,18 @@ def build_eval_index(trajs):
|
||||
return idx
|
||||
|
||||
|
||||
def sim_errors(trajs, idx, A, S, ref_cache=None):
|
||||
def sim_errors(trajs, idx, A, S, Q=0.0, rp=0.0):
|
||||
errs = {}
|
||||
ycache = {}
|
||||
for e in idx:
|
||||
ck = (e['key'], e['nb'])
|
||||
ck = (e['key'], e['nb'], round(Q, 6), round(rp, 6))
|
||||
if ck not in ycache:
|
||||
ci = case_input(e['key'], e['inp'])
|
||||
ycache[ck] = replay(ci, trajs[e['key']], e['nb'], A, S)
|
||||
ycache[ck] = replay(ci, trajs[e['key']], e['nb'], A, S,
|
||||
Q, trajs.get(e['key'] + '|res'), rp)
|
||||
y44 = ycache[ck]
|
||||
if ref_cache is None:
|
||||
errs[e['name']] = corpus.db(corpus.ta(y44, e['f'])) - \
|
||||
corpus.db(corpus.ta(corpus.load_mono(e['ref']), e['f']))
|
||||
else:
|
||||
errs[e['name']] = corpus.db(corpus.ta(y44, e['f'])) - ref_cache[e['name']]
|
||||
return errs
|
||||
|
||||
|
||||
@@ -250,45 +277,72 @@ def sanity(A, S, json_path):
|
||||
def fit(A0, S0):
|
||||
trajs = load_traj()
|
||||
idx = build_eval_index(trajs)
|
||||
fit2(trajs, idx, A0, S0)
|
||||
|
||||
def objective(A, S, sub=None):
|
||||
|
||||
def fit2(trajs, idx, A0, S0):
|
||||
"""Law-family comparison: scalar / quad / resrp / quad+resrp (NOTES 22t)."""
|
||||
|
||||
def objective(p, sub=None):
|
||||
A, S, Q, rp = p
|
||||
ii = idx if sub is None else sub
|
||||
return group_stats(sim_errors(trajs, ii, A, S))
|
||||
return group_stats(sim_errors(trajs, ii, A, S, Q, rp))
|
||||
|
||||
best = (A0, S0)
|
||||
bst = objective(*best)
|
||||
print(f'start A={A0} S={S0}: TOTAL={bst["TOTAL"]:.3f}')
|
||||
stepA, stepS = 0.8, 0.25
|
||||
for it in range(4):
|
||||
def descend(p0, steps, sub=None, label=''):
|
||||
best = list(p0)
|
||||
bst = objective(tuple(best), sub)
|
||||
print(f'{label} start {[round(v, 4) for v in best]}: '
|
||||
f'TOTAL={bst["TOTAL"]:.3f}', flush=True)
|
||||
while all(s > 1e-4 for s in steps.values()):
|
||||
moved = False
|
||||
for A, S in [(best[0] + stepA, best[1]), (best[0] - stepA, best[1]),
|
||||
(best[0], best[1] + stepS), (best[0], best[1] - stepS)]:
|
||||
st = objective(A, S)
|
||||
mark = ''
|
||||
if st['TOTAL'] < bst['TOTAL'] - 1e-4:
|
||||
best, bst = (A, S), st
|
||||
for i, nm in enumerate(['A', 'S', 'Q', 'rp']):
|
||||
if nm not in steps:
|
||||
continue
|
||||
st = steps[nm]
|
||||
for d in (+st, -st):
|
||||
cand = list(best)
|
||||
cand[i] = round(cand[i] + d, 6)
|
||||
if cand[3] < 0 or (nm == 'Q' and abs(cand[2]) > 3):
|
||||
continue
|
||||
s2 = objective(tuple(cand), sub)
|
||||
if s2['TOTAL'] < bst['TOTAL'] - 1e-4:
|
||||
best, bst = cand, s2
|
||||
moved = True
|
||||
mark = ' *'
|
||||
print(f' [{it}] A={A:+7.3f} S={S:+6.3f}: TOTAL={st["TOTAL"]:.3f}{mark}')
|
||||
print(f' {label} {nm}{d:+.4g}: TOTAL={s2["TOTAL"]:.3f} '
|
||||
f'{[round(v, 4) for v in best]}', flush=True)
|
||||
if not moved:
|
||||
stepA /= 2
|
||||
stepS /= 2
|
||||
if stepA < 0.05:
|
||||
break
|
||||
print(f'\nBEST global: A={best[0]:.3f} S={best[1]:.3f} TOTAL={bst["TOTAL"]:.3f}')
|
||||
for k, v in bst.items():
|
||||
print(f' {k:>6}: {v:.3f}')
|
||||
for nm in steps:
|
||||
steps[nm] /= 2
|
||||
return best, bst
|
||||
|
||||
print('\n=== per-group greedy optima (grid around global best) ===')
|
||||
for g in ['t1kq', 't1k', 'al', 'res', 'dual', 'comb']:
|
||||
sub = [e for e in idx if e['grp'] == g]
|
||||
bA, bS, bval = None, None, 1e9
|
||||
for A in np.arange(best[0] - 2.5, best[0] + 2.51, 0.5):
|
||||
for S in np.arange(max(0.25, best[1] - 1.0), best[1] + 1.01, 0.25):
|
||||
st = objective(float(A), float(S), sub)
|
||||
if st[g] < bval:
|
||||
bA, bS, bval = float(A), float(S), st[g]
|
||||
print(f'{g:>6}: A={bA:6.2f} S={bS:5.2f} mean|e|={bval:.3f}', flush=True)
|
||||
res_all = {}
|
||||
|
||||
b, t = descend([A0, S0, 0.0, 0.0], {'A': 0.4, 'S': 0.15}, label='[scalar]')
|
||||
res_all['scalar'] = (list(b), dict(t))
|
||||
|
||||
bA, bS, _, _ = res_all['scalar'][0]
|
||||
b, t = descend([bA, bS, 0.0, 0.0], {'A': 0.3, 'S': 0.15, 'Q': 0.06},
|
||||
label='[quad]')
|
||||
res_all['quad'] = (list(b), dict(t))
|
||||
|
||||
b, t = descend([bA, bS, 0.0, 0.03], {'A': 0.3, 'S': 0.15, 'rp': 0.01},
|
||||
label='[resrp]')
|
||||
res_all['resrp'] = (list(b), dict(t))
|
||||
|
||||
bA, bS, bQ, _ = res_all['quad'][0]
|
||||
b, t = descend([bA, bS, bQ, 0.03], {'A': 0.25, 'S': 0.12, 'Q': 0.05,
|
||||
'rp': 0.008}, label='[quad+resrp]')
|
||||
res_all['quad+resrp'] = (list(b), dict(t))
|
||||
|
||||
print('\n================ LAW FAMILY SUMMARY ================')
|
||||
for fam, (p, st) in res_all.items():
|
||||
gs = ' '.join(f'{g}={st[g]:.3f}' for g in
|
||||
['t1kq', 't1k', 'al', 'res', 'dual', 'comb'])
|
||||
print(f'{fam:>12}: A={p[0]:7.3f} S={p[1]:6.3f} Q={p[2]:+6.3f} '
|
||||
f'rp={p[3]:5.3f} TOTAL={st["TOTAL"]:.3f}\n{"":>14}{gs}')
|
||||
json.dump({f: {'params': p, 'groups': s} for f, (p, s) in res_all.items()},
|
||||
open('/tmp/opencode/lawfit_fit2.json', 'w'), indent=1)
|
||||
print('\nwrote /tmp/opencode/lawfit_fit2.json')
|
||||
|
||||
|
||||
def freq_of(name):
|
||||
|
||||
Reference in New Issue
Block a user