Files
sim-numerics/stage17_anc.py
T

82 lines
3.0 KiB
Python

"""Stage 17 - the third domain: the step size of an adaptive noise canceller.
Root finding: the method lands second behind Chandrupatla 1997.
ODE step control: level with a five-constant PI controller, behind the
three-constant classical one.
Softening somebody else's acceptance test: fails, three times over.
Active noise control is the domain where the object we have - a step size
that is a safety factor on an exact step - is exactly the object the field
argues about, and where the standard answers are openly constant-ridden.
The acoustic paths, the noise regimes and the scenarios come from our earlier
ANC benchmark (SEM repository, branch feature/sem-anc-benchmark), so the
environment is one that existed before this question was asked.
Metric: noise reduction in dB on the second half of each run, so that the
adaptation transient is not counted. Higher is better. Ten seeds per cell.
"""
from __future__ import annotations
import math
import numpy as np
from anc.bench import (FS, SCENARIOS, make_primary_path, make_secondary_path,
run_nlms)
from anc.rules import ALL
N = 8000 # one second
SEEDS = 10
SNRS = (30.0, 15.0)
def main():
print(f"{len(SCENARIOS)} scenarios x {len(SNRS)} noise levels x {SEEDS} "
f"seeds, {N / FS:g} s each\n")
grand = {r.name: [] for r in ALL}
for snr in SNRS:
print("=" * 88)
print(f"reference SNR {snr:g} dB noise reduction, dB (higher is "
f"better)")
print("=" * 88)
print(f"{'scenario':22s}" + "".join(f"{r.name[:13]:>13s}" for r in ALL))
print("-" * 88)
for sname, gen in SCENARIOS:
row = {r.name: [] for r in ALL}
for s in range(SEEDS):
rng = np.random.default_rng(1000 + s)
noise = gen(N, rng)
prim = make_primary_path(rng)
sec = make_secondary_path(rng)
for r in ALL:
nr = run_nlms(noise, prim, sec, r, snr_db=snr, seed=s)
if math.isfinite(nr):
row[r.name].append(nr)
grand[r.name].append(nr)
cells = []
for r in ALL:
v = row[r.name]
cells.append(f"{(sum(v) / len(v)) if v else float('nan'):>13.2f}")
print(f"{sname:22s}" + "".join(cells), flush=True)
print()
print("=" * 88)
print("OVERALL")
print("=" * 88)
print(f"{'rule':26s}{'constants':>11s}{'mean NR dB':>13s}"
f"{'worst cell':>13s}{'diverged':>11s}")
print("-" * 88)
for r in ALL:
v = grand[r.name]
n_bad = SEEDS * len(SCENARIOS) * len(SNRS) - len(v)
print(f"{r.name:26s}{r.constants:>11d}"
f"{(sum(v) / len(v)) if v else float('nan'):>13.2f}"
f"{min(v) if v else float('nan'):>13.2f}{n_bad:>11d}")
print("\n'diverged' counts runs whose residual power was not finite - the "
"failure mode\na step size that is too large actually produces.")
if __name__ == "__main__":
main()