From b1066f31e060c7a23a00fd0a52b1bebdb00d59db Mon Sep 17 00:00:00 2001 From: Matiq Date: Tue, 18 Aug 2026 18:02:31 +0300 Subject: [PATCH] =?UTF-8?q?model:=20=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=20FUN=5F180529fe0=20(rmse=200.236=20dB):=20C?= =?UTF-8?q?(f)=3D1.221*LUT(log10(L0/res))=20+=200.358*warp(f)^3.143=20?= =?UTF-8?q?=E2=80=94=20dual2000-=D0=B3=D0=BB=D1=83=D0=B1=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=90=D0=94=D0=94=D0=98?= =?UTF-8?q?=D0=A2=D0=98=D0=92=D0=9D=D0=AB=D0=99=20=D0=B0=D0=BA=D0=BA=D1=83?= =?UTF-8?q?=D0=BC=D1=83=D0=BB=D1=8F=D1=82=D0=BE=D1=80=200x5407c8=20(=D0=BD?= =?UTF-8?q?=D0=B5=20warp*LUT,=20=D1=82=D0=BE=D1=82=20>10=20dB),=20res(500)?= =?UTF-8?q?=3D0.117=20Q-=D0=BD=D0=B5=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=20(C500-const),=20warp^3.14~pi=20=3D>=20=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D1=82=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=B0=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model_fir.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 model_fir.py diff --git a/model_fir.py b/model_fir.py new file mode 100644 index 0000000..469f0ec --- /dev/null +++ b/model_fir.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""model_fir.py — РЕЗУЛЬТАТ диагностического моста (2026-08-18). + +Вопрос: даёт ли РЕАЛЬНАЯ цепочка FUN_180529fe0 (masкa -> warp -> FIR/DC) глубину +dual_b1q с реDD-константами из декомпа? Ответ: ДА, через АДДИТИВНЫЙ аккумулятор. + +МОДЕЛЬ (rmse=0.236 dB на 36 точках): + C(f) = g·LUT(log10(L0/res_band(f))) + w·warp(f)^a + red = -20·log10(1-C) + g=1.221 w=0.358 a=3.143 (фит, bounds [0.9..1.5],[0.2..1.2],[2..6]) + LUT = PCHIP B.11 (заморожен), warp = 0.87·Kx/(K+x), K=exp(2.0723), x=f/2000 + +КЛЮЧЕВЫЕ ВЫВОДЫ: +1. dual500-константа объяснена БЕЗ tilt: res_band(500; fc=500)=0.117 не зависит от Q + (твин в центре полосы Q-независим); C500 = g·LUT(0.574)·warp^a(500≈0) = 0.692 -> 10.23 dB. +2. Глубина dual2000 НЕ мультипликативный warp·LUT (ratio warp 2000/500=3.66 -> rmse>10 dB); + её даёт добавка w·warp(f)^3.14: на 2000 Гц вклад 0.358·0.773^3.14=0.149, на 1000<=0.02 + (иначе t1k/t1kq рушатся) => точка фита фиксирована единственной. Экзотика a≈π: кандидат — + кратный каскад warp (freq-axis 0x540698 / ∏0x540688 / двойной FFT-проход). +3. Остаток dual2000 при Q=0.1 (14.4 vs 15.2) = тонкая форма LUT-колена 0.574 (0.7 dB) — + не закрывается per-bin формами; требует точного ур-я импульса (FFT-уровень 0x535a70). +4. Порядок измерений в фите: DUAL.ravel() (500,2000 попарно) | T1KQ | T1K. +5. res(2000) растёт 0.216(Q=0.1)..1.988(Q=10) и входит ЧЕРЕЗ LUT(xv), где xv=log10(L0/res): + xv(2000)=0.308(Q=0.1)..-0.656(Q=10) -> C2000-Q-зависимость совпадает. +""" +import numpy as np +from scipy.optimize import least_squares +from scipy.interpolate import PchipInterpolator + +FS = 44100.0 +GAIN = 4.132 + +QS = [0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0] +DUAL = np.array([(10.220, 15.224), (10.219, 13.963), (10.219, 12.947), + (10.219, 11.725), (10.218, 11.119), (10.216, 10.689), + (10.210, 10.412), (10.203, 10.305), (10.182, 10.225), + (10.113, 10.183), (9.822, 10.165)]) +FCS = [800.0, 900.0, 950.0, 1000.0, 1050.0, 1100.0, 1200.0] +T1KQ = np.array([7.868, 8.536, 8.726, 8.788, 8.729, 8.575, 8.115]) +T1K = np.array([14.548, 15.332, 15.553, 15.626, 15.557, 15.378, 14.840]) +L0_DUAL = 10 ** (-7.142 / 20) +L0_T1KQ = 10 ** (-18.063 / 20) +L0_T1K = 1.0 + +LUT_X = np.array([-0.750, -0.500, -0.250, 0.000, 0.250, 0.500, 0.574, 0.610, 0.750, 1.000]) +LUT_Y = np.array([0.4402, 0.4552, 0.4813, 0.5072, 0.5329, 0.5332, 0.5645, 0.6471, 0.6562, 0.6670]) +_LUT = PchipInterpolator(LUT_X, LUT_Y) + + +def lut(x): + return np.float64(np.clip(_LUT(float(x)), LUT_Y[0], LUT_Y[-1])) + + +def res_band(f, fc, Q): + w0 = fc * 2 * np.pi / FS + c, s = np.cos(w0), np.sin(w0) + p = (s * 0.5) / Q + a, a2 = p * GAIN, p / GAIN + A = [a + 1, -2 * c, 1 - a] + B = [a2 + 1, -2 * c, 1 - a2] + w = 2 * np.pi * f / FS + z = np.exp(-1j * w) + return np.abs(2.0 * (B[0] + B[1] * z + B[2] * z * z) / + (A[0] + A[1] * z + A[2] * z * z)) + + +def warp(f): + x = f / 2000.0 + return 0.87 * 7.942 * x / (7.942 + x) + + +def make_combos(): + combos = [] + for q in QS: + combos += [(500.0, 500.0, q, L0_DUAL), (2000.0, 500.0, q, L0_DUAL)] + for fc in FCS: + combos.append((1000.0, fc, 0.9999978, L0_T1KQ)) + for fc in FCS: + combos.append((1000.0, fc, 0.9999978, L0_T1K)) + return combos + + +COMBOS = make_combos() + + +def pred(P): + g, w, a = P + out = [] + for ft, fc, Q, L0 in COMBOS: + xv = np.log10(L0 / res_band(ft, fc, Q)) + m = g * lut(xv) + w * warp(ft) ** a + out.append(-20 * np.log10(1 - np.clip(m, 0, 0.999))) + return np.array(out) + + +def run(): + meas = np.concatenate([DUAL.ravel(), T1KQ, T1K]) + rng = np.random.default_rng(2) + best = None + for trial in range(40): + try: + p0 = rng.uniform([0.9, 0.2, 2.0], [1.5, 1.2, 6.0]) + r = least_squares(lambda p: pred(p) - meas, p0, + bounds=([0.9, 0.2, 2.0], [1.5, 1.2, 6.0]), + max_nfev=400, ftol=1e-11, x_scale='jac') + pr = pred(r.x) + rm = np.sqrt(np.mean((pr - meas) ** 2)) + if best is None or rm < best[0]: + best = (rm, pr, r.x) + except Exception: + pass + rm, pr, P = best + seg = lambda i0, i1: np.sqrt(np.mean((pr[i0:i1] - meas[i0:i1]) ** 2)) + print(f'rmse={rm:.4f} dual={seg(0,22):.3f} t1kq={seg(22,29):.3f} t1k={seg(29,36):.3f} ' + f'P(g,w,a)={np.round(P,4)} [warp^a: 2000={warp(2000)**P[2]:.3f}, 1000={warp(1000)**P[2]:.3f}]') + print('d500=' + ' '.join(f'{pr[2*i]:5.2f}/{meas[2*i]:5.2f}' for i in range(11))) + print('d2k =' + ' '.join(f'{pr[2*i+1]:5.2f}/{meas[2*i+1]:5.2f}' for i in range(11))) + print('t1kq=' + ' '.join(f'{pr[22+j]:5.2f}/{meas[22+j]:5.2f}' for j in range(7))) + print('t1k =' + ' '.join(f'{pr[29+j]:5.2f}/{meas[29+j]:5.2f}' for j in range(7))) + # числовая справка res/warp + print('\nres(500;fc500)=%.3f (Q-независим, объясняет C500-константу)' % res_band(500, 500, 1.0)) + print('res(2000): ' + ' '.join('%.3f' % res_band(2000, 500, q) for q in QS)) + print('xv(2000)=log10(L0D/res): ' + + ' '.join('%.3f' % np.log10(L0_DUAL / res_band(2000, 500, q)) for q in QS)) + + +if __name__ == '__main__': + run() \ No newline at end of file