#!/usr/bin/env python3 """Check a candidate implementation against VECTORS-1.0, and diagnose failures. A third party points this at their own function and learns, per vector, whether they conform -- and where they do not, which clause of PCIST-1.0 they most likely mis-implemented, by matching what they returned against the recorded value for each single-clause mutation. python spec/vectors/verify.py nooscope.pcist_b:pcist_b The target is `module:function`, called as f(signal, times, baseline_window, response_window, k=, min_snr=, max_var=, n_steps=, resample=) and returning a float. Exits non-zero on any failure. """ from __future__ import annotations from pathlib import Path as _Path _REPO = _Path(__file__).resolve().parents[2] _WS = _REPO.parent import importlib import json import sys import numpy as np sys.path.insert(0, f"{_REPO}/spec/vectors") sys.path.insert(0, f"{_REPO}/pipeline") from build import generate # noqa: E402 VECTORS = f"{_REPO}/spec/vectors/VECTORS-1.0.json" def main() -> int: if len(sys.argv) < 2: print(__doc__) return 2 mod, fn = sys.argv[1].split(":") f = getattr(importlib.import_module(mod), fn) spec = json.load(open(VECTORS)) # The candidate's full profile across all vectors is what identifies a # mis-implementation. Matching vector by vector is ambiguous: several broken # clauses return zero on the noise vector, so a per-vector match reports two # or three confident diagnoses for one error. A profile match disambiguates, # because the mutations differ on the vectors where they do not collide. observed, failures = [], 0 for v in spec["vectors"]: g, par = v["generator"], v["parameters"] y, times = generate(g["kind"], g["channels"], g["seed"], g["source_fs_hz"], g["t_start_ms"], g["t_end_ms"], amp=g["amplitude"]) got = float(f(y, times, tuple(par["baseline_window"]), tuple(par["response_window"]), k=par["k"], min_snr=par["min_snr"], max_var=par["max_var"], n_steps=par["n_steps"], resample=par["resample"])) observed.append(got) exp, tol = v["expected"], v["tolerance_relative"] rel = abs(got - exp) / max(abs(exp), 1e-12) if rel <= tol: print(f" {v['id']} PASS {got:12.4f}") else: failures += 1 print(f" {v['id']} FAIL got {got:.4f}, expected {exp:.4f} " f"(relative {rel:.2e})") print(f"\n{len(spec['vectors']) - failures}/{len(spec['vectors'])} vectors pass") if not failures: print("Conformant to PCIST-1.0.") return 0 print("Not conformant to PCIST-1.0.\n") # profile match: how many vectors each single-clause mutation explains obs = np.array(observed, dtype=float) scores = [] for name in spec["mutations"]: prof = np.array([(v["mutation_values"] or {}).get(name, np.nan) for v in spec["vectors"]], dtype=float) if np.any(np.isnan(prof)): continue hits = int(np.sum(np.abs(obs - prof) <= 1e-4 * np.maximum(np.abs(prof), 1.0))) scores.append((hits, name)) scores.sort(reverse=True) n_v = len(spec["vectors"]) if not scores or scores[0][0] < n_v * 0.5: print("No single known mis-implementation explains this profile. The " "candidate probably has more than one clause wrong, or an error " "the vector set does not model. Compare intermediate quantities " "clause by clause against PCIST-1.0.") return 1 top = [n for h, n in scores if h == scores[0][0]] if len(top) > 1: print(f"Ambiguous: {len(top)} mis-implementations explain " f"{scores[0][0]}/{n_v} vectors equally — {', '.join(top)}. " f"They are not separated by this set.") for name in top: m = spec["mutations"][name] print(f"Most likely: clause {m['clause']} — {m['description']} " f"(explains {scores[0][0]}/{n_v} vectors)") runner = [(h, n) for h, n in scores if n not in top] if runner: m = spec["mutations"][runner[0][1]] print(f"Next best: clause {m['clause']} ({runner[0][0]}/{n_v})") return 1 if __name__ == "__main__": sys.exit(main())