From b7b76de1f3fdec309f69efb9c900829bdb6ba8bc Mon Sep 17 00:00:00 2001 From: rUv Date: Mon, 12 Oct 2026 16:22:53 -0700 Subject: [PATCH] feat(csi): freeze source-backed motion ablation and record rejected mean-score candidate --- .../espfi-motion-mean-20261012.json | 133 ++++++++++ scripts/csi_training/espfi_motion.py | 232 ++++++++++++++++++ scripts/tests/test_espfi_motion.py | 95 +++++++ 3 files changed, 460 insertions(+) create mode 100644 docs/validation/espfi-motion-mean-20261012.json create mode 100644 scripts/csi_training/espfi_motion.py create mode 100644 scripts/tests/test_espfi_motion.py diff --git a/docs/validation/espfi-motion-mean-20261012.json b/docs/validation/espfi-motion-mean-20261012.json new file mode 100644 index 000000000..bfd4084a9 --- /dev/null +++ b/docs/validation/espfi-motion-mean-20261012.json @@ -0,0 +1,133 @@ +{ + "status": "complete", + "evidence": "MEASURED", + "promotionAuthorized": false, + "development": { + "accuracy": 0.42857142857142855, + "macro_f1": 0.39720523837065186, + "per_class_f1": [ + 0.625, + 0.47619047619047616, + 0.2857142857142857, + 0.4166666666666667, + 0.3076923076923077, + 0.5263157894736842, + 0.14285714285714285 + ], + "samples": 70 + }, + "incumbent_development": { + "accuracy": 0.44285714285714284, + "macro_f1": 0.3839985224859174, + "per_class_f1": [ + 0.35294117647058826, + 0.6153846153846154, + 0, + 0.5882352941176471, + 0.56, + 0.5714285714285714, + 0 + ], + "samples": 70 + }, + "retention_checks": { + "macro_f1_gain": false, + "accuracy_nonregression": false, + "no_additional_zero_f1_classes": true, + "p95_under_1ms": true + }, + "retained_as_research_candidate": false, + "latency": { + "candidate": { + "p50_ms": 0.5314695, + "p95_ms": 0.7371814999999999 + }, + "incumbent": { + "p50_ms": 0.31542, + "p95_ms": 0.456288 + } + }, + "model_bytes": 496488, + "model_sha256": "c4bef32b0b4a563381883a705679637b188f30940e92c113ff21102aa1871d36", + "baseline_model_sha256": "8a2cafdc5e6df989a3d8fff5762e2287ac5051f0e8b1ca2868431d60274b31ca", + "baseline_receipt_sha256": "26bad1e0d7ce7a8ad525f8218e567dd88ba3f78404027a0795b2ab0e0faeb40d", + "source_sha256": { + "espfi_motion.py": "53fa62558d4ec0e357e0854f728f282ca2db6449c96f1dd9a1478de98ec8dd90", + "espfi_generalize.py": "956b3a5e918f7c263a2b9183b4a6a70c8f2157604262a4ec6cee2c324a69224f", + "espfi_kernel.py": "ae837cb2e3fac7f052b4bd371cd496e9636e5d5dbaf3a341526a4fc5aef23478", + "espfi_runtime.py": "8e100972b0deeb8a6586271264f335551cb5d1e9cc42a24bddf933dba064a80f" + }, + "scope": "24 configurations selected only on two training folds; development seen once after selection; 1000 alternating paired batch1 calls include features/checks/classifier, exclude pooling and IO. Candidate is float64 with explicit map, incumbent uses seeded float32 folded runtime; storage comparison includes different encodings. No test, hardware, pose or SOTA claim.", + "reload_max_abs_logit_error": 0, + "selected": { + "candidate": { + "family": "local_spectral", + "kind": "rff", + "dimensions": 128, + "gamma_multiplier": 0.1, + "alpha": 0.01 + }, + "mean_cv_macro_f1": 0.35559936248893814, + "scalar_count": 61999 + }, + "best_per_family": { + "relative_combined": { + "candidate": { + "family": "relative_combined", + "kind": "rff", + "dimensions": 128, + "gamma_multiplier": 0.1, + "alpha": 0.01 + }, + "mean_cv_macro_f1": 0.306028118879762, + "fold_macro_f1": [ + 0.30414628178603337, + 0.30790995597349063 + ] + }, + "motion_acf": { + "candidate": { + "family": "motion_acf", + "kind": "rff", + "dimensions": 128, + "gamma_multiplier": 0.1, + "alpha": 0.1 + }, + "mean_cv_macro_f1": 0.33402959890262357, + "fold_macro_f1": [ + 0.33211530016861246, + 0.3359438976366347 + ] + }, + "motion_invariant": { + "candidate": { + "family": "motion_invariant", + "kind": "rff", + "dimensions": 128, + "gamma_multiplier": 0.1, + "alpha": 0.1 + }, + "mean_cv_macro_f1": 0.3541747172155182, + "fold_macro_f1": [ + 0.31217774871662207, + 0.39617168571441436 + ] + }, + "local_spectral": { + "candidate": { + "family": "local_spectral", + "kind": "rff", + "dimensions": 128, + "gamma_multiplier": 0.1, + "alpha": 0.01 + }, + "mean_cv_macro_f1": 0.35559936248893814, + "fold_macro_f1": [ + 0.26600194570119384, + 0.44519677927668244 + ] + } + }, + "manifest_sha256": "74e0fe039acbed1b287bd11ee6b779becbb8f9b66c3c249d260ed66f5583f02a", + "full_receipt_sha256": "98e56df2181dcae17070b80002ca67fc7a53206742903f5f2c8c353747d3d8f1" +} diff --git a/scripts/csi_training/espfi_motion.py b/scripts/csi_training/espfi_motion.py new file mode 100644 index 000000000..bc788a4a7 --- /dev/null +++ b/scripts/csi_training/espfi_motion.py @@ -0,0 +1,232 @@ +"""Frozen source-only motion ablation inspired by published ACF and RuView STFT. + +Independent feature experiment, not a reproduction of an external model. +Only grouped training folds select a candidate; final test arrays are absent. +""" +import argparse +import json +from pathlib import Path +import time +import numpy as np +from safetensors.numpy import load, save_file +from .espfi_generalize import amplitudes, features, folds +from .espfi_kernel import fit, predict_features +from .espfi_runtime import BufferedKernelBundle +from .espfi_train import digest, scores, write_json + +WIDTHS = {'relative_combined': 468, 'motion_acf': 468, + 'motion_invariant': 45, 'local_spectral': 468} +GRID = [dict(family=family, kind='rff', dimensions=128, gamma_multiplier=gamma, alpha=alpha) + for family in WIDTHS for gamma in (.1, 1.) for alpha in (.01, .1, 1.)] +CONTRACT = {'seed': 42, 'grid': GRID, 'input': 'nonnegative_amplitude_N52T190', + 'selection': 'mean training-only grouped CV macro F1, then fewer scalars, then declaration order', + 'acf_lags': [1, 2, 4, 8, 16, 32], 'difference_lags': [1, 4, 16], + 'carrier_quantiles': [.1, .25, .5, .75, .9], + 'acf': 'centered log-amplitude biased autocorrelation divided by full channel energy floored at 190e-12', + 'stft': {'window': 32, 'starts': list(range(0, 159, 16))+[158], + 'taper': 'symmetric Hann', 'bands': [[1, 5], [5, 17]], + 'pooling': '4 consecutive frame groups via array_split; channel RMS appended'}, + 'frequency_units': 'normalized cycles/sample; no measured rate inferred', + 'retention_gate': {'development_macro_f1_absolute_gain': .03, + 'accuracy_nonregression': True, 'no_additional_zero_f1_classes': True, + 'paired_p95_limit_ms': 1.0}, + 'promotionAuthorized': False} + + +def motion_features(values, family): + if family not in WIDTHS: + raise ValueError('Unknown motion family') + if family == 'relative_combined': + return features(values, family) + x = amplitudes(values) + logged = np.log(np.maximum(x, 1e-6)) + centered = logged-logged.mean(-1, keepdims=True) + scale = np.maximum(np.sqrt(np.mean(centered**2, axis=(1, 2), keepdims=True)), 1e-6) + relative = centered/scale + if family == 'local_spectral': + rows = [] + for offset in range(0, len(x), 32): + part = relative[offset:offset+32] + windows = np.lib.stride_tricks.sliding_window_view(part, 32, axis=-1) + windows = windows[..., CONTRACT['stft']['starts'], :] * np.hanning(32) + power = np.abs(np.fft.rfft(windows, axis=-1))**2 / 32**2 + bands = np.stack((power[..., 1:5].sum(-1), power[..., 5:17].sum(-1)), axis=-1) + local = np.stack([b.mean(axis=-2) for b in np.array_split(bands, 4, axis=-2)], axis=-2) + rows.append(np.concatenate((local.reshape(len(part), -1), + np.sqrt(np.mean(part**2, axis=-1))), axis=1)) + result = np.concatenate(rows) + else: + energy = np.maximum(np.sum(centered**2, axis=-1), 190e-12) + acf = [np.sum(centered[..., lag:]*centered[..., :-lag], axis=-1)/energy + for lag in CONTRACT['acf_lags']] + changes = [np.mean(np.abs(relative[..., lag:]-relative[..., :-lag]), axis=-1) + for lag in CONTRACT['difference_lags']] + per_channel = np.stack(acf+changes, axis=-1) + if family == 'motion_invariant': + pooled = np.quantile(per_channel, CONTRACT['carrier_quantiles'], axis=1) + result = pooled.transpose(1, 0, 2).reshape(len(x), -1) + else: + result = per_channel.reshape(len(x), -1) + if result.shape != (len(x), WIDTHS[family]) or not np.isfinite(result).all(): + raise ValueError('Invalid motion features') + return np.ascontiguousarray(result) + + +def _read(path, limit): + path = Path(path) + if path.is_symlink() or not path.is_file(): + raise ValueError('Expected regular bounded artifact') + with path.open('rb') as handle: + data = handle.read(limit+1) + if len(data) > limit: + raise ValueError('Oversized artifact') + return data + + +class MotionBundle: + def __init__(self, directory): + root = Path(directory) + status = json.loads(_read(root/'status.json', 65536)) + report = json.loads(_read(root/'metrics.json', 16*1024**2)) + if (not isinstance(status, dict) or not isinstance(report, dict) + or status.get('status') != 'complete' or status.get('promotionAuthorized') is not False + or report.get('status') != 'complete' or report.get('promotionAuthorized') is not False + or report.get('contract') != CONTRACT or report.get('task') != 'public_motion_ablation'): + raise ValueError('Invalid completed motion artifact') + self.candidate = report['selected']['candidate'] + if self.candidate not in GRID: + raise ValueError('Unknown candidate') + data = _read(root/'model.safetensors', 1024**2) + import hashlib + if hashlib.sha256(data).hexdigest() != report.get('model_sha256'): + raise ValueError('Model hash mismatch') + self.state = load(data) + width = WIDTHS[self.candidate['family']] + expected = {'feature_mean': (width,), 'feature_scale': (width,), + 'projection': (width, 128), 'phase': (128,), 'output_mean': (128,), + 'weights': (128, 7), 'bias': (7,)} + if (set(self.state) != set(expected) or any(self.state[k].shape != shape or + self.state[k].dtype != np.float64 or not np.isfinite(self.state[k]).all() + for k, shape in expected.items()) or not (self.state['feature_scale'] > 0).all()): + raise ValueError('Invalid motion model state') + + def predict(self, values): + return predict_features(self.state, motion_features(values, self.candidate['family'])) + + +def _validate(parts): + if set(parts) != {'train', 'validation'}: + raise ValueError('Requires development-only partitions') + for name, part in parts.items(): + ids, y = np.asarray(part['identity']), np.asarray(part['y']) + if (y.dtype.kind not in 'iu' or y.ndim != 1 or len(y) != len(part['x']) + or not len(y) or set(y) != set(range(7)) or ids.dtype.kind not in 'iu' + or ids.shape != (len(y), 4) or len(np.unique(ids, axis=0)) != len(y) + or not np.array_equal(ids[:, 2]-1, y) or not np.all((ids[:, 3] >= 1) & (ids[:, 3] <= 10))): + raise ValueError('Invalid recording identities or labels') + if name == 'validation' and (not np.all(ids[:, 0] == 3) or not np.all(ids[:, 1] == 7)): + raise ValueError('Invalid development identities') + return folds(parts['train']['identity'], parts['train']['y']) + + +def search(parts, provenance, baseline_directory, baseline_receipt, output, max_seconds=600): + if not 1 <= max_seconds <= 3600: + raise ValueError('Invalid time budget') + fold_ids = _validate(parts) + baseline = BufferedKernelBundle(baseline_directory) + prior = json.loads(_read(baseline_receipt, 16*1024**2)) + if (not isinstance(prior, dict) or prior.get('provenance', {}).get('manifest_sha256') != provenance.get('manifest_sha256') + or not provenance.get('manifest_sha256') or prior.get('model_sha256') != digest(Path(baseline_directory)/'model.safetensors')): + raise ValueError('Baseline data or model provenance mismatch') + root = Path(output) + root.mkdir(exist_ok=False, parents=True) + write_json(root/'status.json', {'status': 'running', 'promotionAuthorized': False}) + write_json(root/'search_contract.json', CONTRACT) + start = time.monotonic() + try: + train, dev = parts['train'], parts['validation'] + cache = {family: motion_features(train['x'], family) for family in WIDTHS} + results, selected, rank = [], None, None + for candidate in GRID: + evaluations = [] + for fit_ids, eval_ids in fold_ids: + if time.monotonic()-start > max_seconds: + raise TimeoutError('Motion ablation budget exceeded') + state = fit(cache[candidate['family']][fit_ids], train['y'][fit_ids], candidate) + result = predict_features(state, cache[candidate['family']][eval_ids]) + evaluations.append(scores(train['y'][eval_ids], result.argmax(1), 7)) + row = {'candidate': candidate, 'folds': evaluations, + 'mean_cv_macro_f1': float(np.mean([v['macro_f1'] for v in evaluations])), + 'scalar_count': sum(v.size for v in state.values())} + results.append(row) + value = (row['mean_cv_macro_f1'], -row['scalar_count']) + if rank is None or value > rank: + selected, rank = row, value + write_json(root/'selection.json', {'selected': selected, 'candidates': results, 'contract': CONTRACT}) + chosen = selected['candidate'] + state = fit(cache[chosen['family']], train['y'], chosen) + save_file(state, str(root/'model.safetensors')) + actual = predict_features(state, motion_features(dev['x'], chosen['family'])) + measured = scores(dev['y'], actual.argmax(1), 7) + incumbent = scores(dev['y'], baseline.predict(dev['x']).argmax(1), 7) + replay = load(_read(root/'model.safetensors', 1024**2)) + if not np.array_equal(actual, predict_features(replay, motion_features(dev['x'], chosen['family']))): + raise ValueError('Reload changes logits') + timings = {'candidate': [], 'incumbent': []} + def predict(x): + return predict_features(state, motion_features(x, chosen['family'])) + for i in range(20): + sample = dev['x'][i % len(dev['x']):i % len(dev['x'])+1] + predict(sample); baseline.predict(sample) + for i in range(1000): + sample = dev['x'][i % len(dev['x']):i % len(dev['x'])+1] + calls = [('candidate', predict), ('incumbent', baseline.predict)] + if i % 2: + calls.reverse() + for name, fn in calls: + before = time.perf_counter_ns(); fn(sample) + timings[name].append((time.perf_counter_ns()-before)/1e6) + timing = {k: {'p50_ms': float(np.percentile(v, 50)), 'p95_ms': float(np.percentile(v, 95))} for k, v in timings.items()} + checks = {'macro_f1_gain': measured['macro_f1'] >= incumbent['macro_f1']+.03, + 'accuracy_nonregression': measured['accuracy'] >= incumbent['accuracy'], + 'no_additional_zero_f1_classes': all(b == 0 or a > 0 for a, b in zip(measured['per_class_f1'], incumbent['per_class_f1'])), + 'p95_under_1ms': timing['candidate']['p95_ms'] < 1.0} + report = {'status': 'complete', 'promotionAuthorized': False, 'task': 'public_motion_ablation', + 'contract': CONTRACT, 'selected': selected, 'candidates': results, + 'evidence': 'SYNTHETIC' if provenance.get('evidence') == 'SYNTHETIC' else 'MEASURED', + 'provenance': provenance, 'development': measured, 'incumbent_development': incumbent, + 'retention_checks': checks, 'retained_as_research_candidate': all(checks.values()), + 'latency': timing, 'reload_max_abs_logit_error': 0.0, + 'model_bytes': (root/'model.safetensors').stat().st_size, + 'model_sha256': digest(root/'model.safetensors'), + 'baseline_model_sha256': prior['model_sha256'], 'baseline_receipt_sha256': digest(baseline_receipt), + 'source_sha256': {name: digest(Path(__file__).with_name(name)) for name in + ('espfi_motion.py', 'espfi_generalize.py', 'espfi_kernel.py', 'espfi_runtime.py')}, + 'elapsed_seconds': time.monotonic()-start, + 'scope': '24 configurations selected only on two training folds; development seen once after selection; ' + '1000 alternating paired batch1 calls include features/checks/classifier, exclude pooling and IO. ' + 'Candidate is float64 with explicit map, incumbent uses seeded float32 folded runtime; ' + 'storage comparison includes different encodings. No test, hardware, pose or SOTA claim.'} + write_json(root/'metrics.json', report) + write_json(root/'status.json', {'status': 'complete', 'promotionAuthorized': False}) + return report + except Exception: + write_json(root/'status.json', {'status': 'failed', 'promotionAuthorized': False}) + raise + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for arg in ('manifest', 'baseline', 'baseline-receipt', 'output'): + parser.add_argument('--'+arg, required=True) + parser.add_argument('--max-seconds', type=int, default=600) + args = parser.parse_args() + from .espfi import load_development + parts, provenance = load_development(args.manifest) + report = search(parts, provenance, args.baseline, args.baseline_receipt, args.output, args.max_seconds) + print(json.dumps({k: report[k] for k in ('selected', 'development', 'incumbent_development', + 'retention_checks', 'retained_as_research_candidate', 'latency', 'model_bytes')}, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/scripts/tests/test_espfi_motion.py b/scripts/tests/test_espfi_motion.py new file mode 100644 index 000000000..4213dae94 --- /dev/null +++ b/scripts/tests/test_espfi_motion.py @@ -0,0 +1,95 @@ +"""Synthetic motion-ablation integrity; no sensing accuracy evidence.""" +import copy +import json +from pathlib import Path +import sys +import tempfile +import unittest +import numpy as np +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from csi_training.espfi_motion import CONTRACT, GRID, WIDTHS, MotionBundle, motion_features, search +from csi_training.espfi_generalize import features +from csi_training.espfi_kernel import fit +from csi_training.espfi_seeded import SPEC, export_seeded +from csi_training.espfi_train import write_json +from test_espfi_generalize import fixture + + +class MotionTests(unittest.TestCase): + def test_frozen_dimensions_gain_and_carrier_invariance(self): + rng = np.random.default_rng(10) + x = np.exp(rng.normal(size=(3, 52, 190))) + gain = np.exp(rng.normal(size=(3, 52, 1))) + self.assertEqual(len(GRID), 24) + for family, width in WIDTHS.items(): + actual = motion_features(x, family) + self.assertEqual(actual.shape, (3, width)) + np.testing.assert_allclose(actual, motion_features(x*gain, family), atol=1e-11, rtol=1e-11) + for value in (0., 1., 1e-20, 1e100): + self.assertTrue(np.isfinite(motion_features(np.full_like(x, value), family)).all()) + with self.assertRaises(ValueError): + motion_features(-x, family) + np.testing.assert_allclose(motion_features(x[:, ::-1], 'motion_invariant'), + motion_features(x, 'motion_invariant'), atol=1e-12, rtol=1e-12) + + def test_acf_definition_and_temporal_reversal(self): + x = np.exp(np.random.default_rng(12).normal(size=(2, 52, 190))) + values = motion_features(x, 'motion_acf').reshape(2, 52, 9) + logged = np.log(x); centered = logged-logged.mean(-1, keepdims=True) + for i, lag in enumerate(CONTRACT['acf_lags']): + expected = (centered[..., lag:]*centered[..., :-lag]).sum(-1)/(centered**2).sum(-1) + np.testing.assert_allclose(values[..., i], expected, atol=1e-12, rtol=1e-12) + self.assertTrue(np.all(np.abs(values[..., :6]) <= 1+1e-12)) + np.testing.assert_allclose(values.reshape(2, -1), motion_features(x[..., ::-1], 'motion_acf'), atol=1e-12) + + def test_stft_explicit_windows_and_tail(self): + x = np.ones((1, 52, 190)); x[..., -1] = 20 + actual = motion_features(x, 'local_spectral') + logged = np.log(x); relative = logged-logged.mean(-1, keepdims=True) + relative /= np.sqrt(np.mean(relative**2)) + windows = np.stack([relative[..., i:i+32] for i in CONTRACT['stft']['starts']], axis=-2)*np.hanning(32) + power = np.abs(np.fft.rfft(windows, axis=-1))**2/32**2 + bands = np.stack((power[..., 1:5].sum(-1), power[..., 5:17].sum(-1)), axis=-1) + local = np.stack([b.mean(-2) for b in np.array_split(bands, 4, axis=-2)], axis=-2) + expected = np.concatenate((local.reshape(1, -1), np.sqrt(np.mean(relative**2, axis=-1))), axis=1) + np.testing.assert_array_equal(actual, expected) + self.assertEqual(CONTRACT['stft']['starts'][-1]+32, 190) + + def test_selection_provenance_reload_and_lifecycle(self): + parts, provenance = fixture() + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + state = fit(features(parts['train']['x'], SPEC['family']), parts['train']['y'], dict(SPEC, kind='rff')) + original = export_seeded(state, root/'baseline', precision='float32') + receipt = root/'receipt.json' + write_json(receipt, {'model_sha256': original['model_sha256'], 'provenance': provenance}) + a = search(parts, provenance, root/'baseline', receipt, root/'a') + changed = copy.deepcopy(parts) + changed['validation']['x'] *= 1.5 + changed['validation']['y'] = (changed['validation']['y']+1) % 7 + changed['validation']['identity'][:, 2] = changed['validation']['y']+1 + b = search(changed, provenance, root/'baseline', receipt, root/'b') + self.assertEqual(a['selected'], b['selected']) + self.assertEqual(a['model_sha256'], b['model_sha256']) + self.assertFalse(a['promotionAuthorized']) + self.assertEqual(a['reload_max_abs_logit_error'], 0) + self.assertNotIn('test', a) + loaded = MotionBundle(root/'a') + self.assertEqual(loaded.predict(parts['validation']['x']).shape, (7, 7)) + with (root/'a/model.safetensors').open('ab') as handle: + handle.write(b'bad') + with self.assertRaisesRegex(ValueError, 'hash mismatch'): + MotionBundle(root/'a') + write_json(root/'b/status.json', {'status': 'running', 'promotionAuthorized': False}) + with self.assertRaisesRegex(ValueError, 'completed'): + MotionBundle(root/'b') + wrong = dict(provenance, manifest_sha256='wrong') + with self.assertRaisesRegex(ValueError, 'provenance'): + search(parts, wrong, root/'baseline', receipt, root/'bad') + parts['test'] = parts['validation'] + with self.assertRaisesRegex(ValueError, 'development-only'): + search(parts, provenance, root/'baseline', receipt, root/'test') + + +if __name__ == '__main__': + unittest.main()