baseline: the published sim-numerics artifact as it stands (490 tests, verify_article.py checks all 72 paper numbers against sweep_full.json)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Active noise control: the step size of an NLMS canceller.
|
||||
|
||||
The third domain, and the third instance of the same pattern. What is pinned
|
||||
here is the pattern and the mistake, not a victory.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from anc.bench import SCENARIOS, make_primary_path, make_secondary_path, run_nlms
|
||||
from anc.rules import Fixed, InSampleMeasured, KwongJohnston, Measured
|
||||
|
||||
N, SEEDS = 8000, 4
|
||||
|
||||
|
||||
SNRS = (30.0, 15.0)
|
||||
|
||||
|
||||
def sweep(rule, snrs=SNRS):
|
||||
"""Both noise levels, as the experiment uses.
|
||||
|
||||
Measuring at 30 dB alone is the wrong test and was: with almost no
|
||||
measurement noise there is nothing for a safety factor to hedge against,
|
||||
the exact step mu = 1 is near optimal, and any hedging costs a little.
|
||||
The whole point of the rule appears at 15 dB. A claim about the grid has
|
||||
to be tested on the grid."""
|
||||
vals = []
|
||||
for snr in snrs:
|
||||
for s in range(SEEDS):
|
||||
for _sname, gen in SCENARIOS:
|
||||
rng = np.random.default_rng(1000 + s)
|
||||
noise = gen(N, rng)
|
||||
nr = run_nlms(noise, make_primary_path(rng),
|
||||
make_secondary_path(rng), rule, snr_db=snr,
|
||||
seed=s)
|
||||
if math.isfinite(nr):
|
||||
vals.append(nr)
|
||||
return sum(vals) / len(vals), min(vals), len(vals)
|
||||
|
||||
|
||||
def test_the_in_sample_observable_is_uninformative():
|
||||
"""The mistake, pinned. NLMS with mu = 1 drives the a posteriori error of
|
||||
the current sample to exactly zero, so a rule that measures it reads 'the
|
||||
model was perfect' and proposes mu = 1 for ever. It is therefore the
|
||||
fixed mu = 1 rule under another name, and the numbers say so."""
|
||||
a, _lo, _n = sweep(InSampleMeasured())
|
||||
b, _lo2, _n2 = sweep(Fixed(1.0))
|
||||
assert abs(a - b) < 0.1, (a, b)
|
||||
|
||||
|
||||
def test_measuring_out_of_sample_is_better_than_measuring_in_sample():
|
||||
"""The fix: what mu hedges against is the part of the error the filter
|
||||
cannot cancel, and that is observable as the floor the error keeps
|
||||
hitting - not as the residual of the sample just fitted."""
|
||||
good, _lo, _n = sweep(Measured())
|
||||
bad, _lo2, _n2 = sweep(InSampleMeasured())
|
||||
assert good > bad, (good, bad)
|
||||
|
||||
|
||||
def test_the_measured_rule_beats_the_four_constant_scheme():
|
||||
"""Kwong-Johnston is the classical variable-step-size rule and carries
|
||||
four constants. This is the comparison the transfer is about."""
|
||||
ours, _lo, _n = sweep(Measured())
|
||||
kj, _lo2, _n2 = sweep(KwongJohnston())
|
||||
assert ours > kj + 3.0, (ours, kj)
|
||||
|
||||
|
||||
def test_and_loses_to_one_well_chosen_constant():
|
||||
"""The other half, and the same in all three domains: measuring the
|
||||
constant beats the elaborate multi-constant schemes and does not beat a
|
||||
single constant chosen correctly for the benchmark."""
|
||||
ours, _lo, _n = sweep(Measured())
|
||||
best_fixed, _lo2, _n2 = sweep(Fixed(0.5))
|
||||
assert best_fixed > ours, (best_fixed, ours)
|
||||
assert best_fixed - ours < 3.0, (best_fixed, ours)
|
||||
|
||||
|
||||
def test_no_rule_diverges():
|
||||
"""A step size that is too large makes the filter blow up; none of these
|
||||
may."""
|
||||
for rule in (Fixed(0.1), Fixed(0.5), Fixed(1.0), KwongJohnston(),
|
||||
Measured()):
|
||||
_m, _lo, n = sweep(rule)
|
||||
assert n == SEEDS * len(SCENARIOS) * len(SNRS), (rule.name, n)
|
||||
Reference in New Issue
Block a user