Files
sim-numerics/tests/test_similarity_family_anc.py
T

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