82 lines
3.0 KiB
Python
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()
|