106 lines
4.1 KiB
Python
106 lines
4.1 KiB
Python
"""The similarity family for the NLMS step size, and what it settled.
|
|
|
|
Two corrections came from outside and both were right:
|
|
|
|
1. mu should be a SIMILARITY, not a variance estimate. The filter can only
|
|
remove what correlates with its input, so the quantity is the cosine
|
|
between the error and the filtered reference. It beats the variance
|
|
formulation on eleven of twelve cells.
|
|
|
|
2. Testing one similarity function is as incomplete here as it would have
|
|
been in the root-finding work. The whole family must be run, and when it
|
|
is, the best member is not the cosine.
|
|
|
|
And the law of the first domain - that the slope of (1 - S) at the origin
|
|
decides, not the shape of S - orders this family too, monotonically and
|
|
without exception. That is a prediction made in one domain and confirmed in
|
|
another.
|
|
|
|
What it did not settle: even the best member loses to a constant tuned per
|
|
scenario, on all twelve cells.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from anc.rules import CosineSimilarity, Fixed, Measured
|
|
from simroot.pure import PURE_SIMS
|
|
from stage20_prediction import FLAT_15, FLAT_30, STEP_25, sig_cabin, sig_tonal, \
|
|
score
|
|
|
|
SEEDS = 3
|
|
CELLS = [(sig_tonal, FLAT_30), (sig_tonal, STEP_25),
|
|
(sig_cabin, FLAT_15), (sig_cabin, STEP_25)]
|
|
|
|
|
|
def make(name):
|
|
r = CosineSimilarity()
|
|
r.S = None if name is None else PURE_SIMS[name]
|
|
return r
|
|
|
|
|
|
def mean(rule):
|
|
vals = [score(rule, gen, prof, seeds=SEEDS) for gen, prof in CELLS]
|
|
return sum(vals) / len(vals)
|
|
|
|
|
|
def test_the_similarity_formulation_beats_the_variance_one():
|
|
"""Correction 1. mu as 'how much of the error resembles what the filter
|
|
can remove' against mu as 'one minus the noise-power fraction'.
|
|
|
|
Stated carefully, because the first version of this test was not: what
|
|
beats the variance rule on eleven of twelve cells is the best member of
|
|
the FAMILY, not the raw cosine. The raw cosine can lose to it - on this
|
|
subset it does, 14.04 against 14.32 - which is precisely why correction 2
|
|
(run the whole family) was needed before the claim could be made at all.
|
|
"""
|
|
best_member = max(mean(make(n)) for n in (None, "ratio", "exp_abs",
|
|
"rational", "ratio_soft",
|
|
"sqrt", "cbrt"))
|
|
assert best_member > mean(Measured()), best_member
|
|
|
|
|
|
def test_the_raw_cosine_is_not_the_best_member():
|
|
"""Correction 2. A single member is not the family: the identity
|
|
S(d) = 1 - d is beaten by members with a steeper approach to 1."""
|
|
raw = mean(make(None))
|
|
best = max(mean(make(n)) for n in ("sqrt", "cbrt", "ratio_soft"))
|
|
assert best > raw, (best, raw)
|
|
|
|
|
|
def test_two_code_paths_for_the_same_function_agree():
|
|
"""S = None takes mu = |cos| directly; S = ratio computes
|
|
max(0, 1 - d) with d = 1 - |cos|. They are the same function on [0,1]
|
|
and reach it by different code, so they must score identically."""
|
|
a = mean(make(None))
|
|
b = mean(make("ratio"))
|
|
assert abs(a - b) < 1e-9, (a, b)
|
|
|
|
|
|
def test_the_law_of_the_first_domain_orders_this_family_too():
|
|
"""The prediction carried across domains: slope of (1 - S) at 0 decides.
|
|
Every slope-1 member must beat every slope-0 member."""
|
|
slope1 = [mean(make(n)) for n in ("ratio", "exp_abs", "rational")]
|
|
slope0 = [mean(make(n)) for n in ("rat_sq", "exp_sq", "cosine")]
|
|
assert min(slope1) > max(slope0), (slope1, slope0)
|
|
|
|
|
|
def test_and_it_still_loses_to_a_tuned_constant():
|
|
"""The half that did not work, pinned so the claim is never overstated.
|
|
The competitor knows the scenario; no deployable controller does - and it
|
|
is still ahead."""
|
|
losses = 0
|
|
for gen, prof in CELLS:
|
|
best_member = max(score(make(n), gen, prof, seeds=SEEDS)
|
|
for n in (None, "cbrt", "sqrt", "ratio"))
|
|
oracle = max(score(Fixed(mu), gen, prof, seeds=SEEDS)
|
|
for mu in (0.05, 0.1, 0.2, 0.5, 1.0))
|
|
losses += best_member < oracle
|
|
assert losses == len(CELLS), losses
|