Files
digital-drosophila/models/male-cns-lif/wholebrain-plastic-interface-v1/test_comparison.py
T
2026-09-30 16:11:14 +08:00

364 lines
22 KiB
Python

"""Pure temporary-file synthetic tests; never a brain run or old-file write.
Public integration tests mock only the full-case loader to use a tiny graph.
The sealed output parser, timing/queue reconstructor, overlay bridge, source AST
and receipt checks remain real. Production rejects these tiny cases unchanged.
"""
import copy
import hashlib
import json
from pathlib import Path
import shutil
import tempfile
import unittest
from unittest.mock import patch
import numpy as np
import comparison as c
def write_json(path, data):
Path(path).write_text(json.dumps(data, sort_keys=True, allow_nan=False), encoding='utf-8')
class ComparisonTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix='phase4am-comparison-synthetic-')
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name).resolve()
self.case_dir = self.root / 'case'
self.graph_dir = self.root / 'graph'
self.bundle_dir = self.root / 'bundle'
for p in (self.case_dir, self.graph_dir, self.bundle_dir):
p.mkdir()
self.helper = c._helper(set())
self.steps = 30
self.graph = dict(nodes=np.arange(4, dtype=np.int32), edge_id=np.arange(6, dtype=np.int64),
ptr=np.array([0, 2, 3, 5, 6], np.int64), src=np.array([0, 0, 1, 2, 2, 3], np.int32),
dst=np.array([3, 2, 2, 0, 3, 1], np.int32), signed_raw=np.array([1, 0, 2, -2, 3, 1], np.int64))
self.graph['weights'] = self.graph['signed_raw'] * (.275 * .001)
for key, value in self.graph.items():
np.save(self.graph_dir / (key + '.npy'), value)
self.bundle = {'kc_globals.npy': np.r_[np.array([0, 1], np.int32), np.arange(4, 4066, dtype=np.int32)],
'apl_globals.npy': np.array([2, 3], np.int32), 'input_matrix.npy': np.zeros((2, c.N), np.float32),
'output_matrix.npy': np.zeros((2, c.K), np.float32)}
for key, value in self.bundle.items():
np.save(self.bundle_dir / key, value)
self.requests = [(0, 0), (1, 1), (2, 0), (22, 0), (23, 3)]
np.savez(self.case_dir / 'requests.npz', tick=np.array([x[0] for x in self.requests], np.int32),
neuron_idx=np.array([x[1] for x in self.requests], np.int32))
write_json(self.case_dir / 'spec.json', {'name': 'synthetic', 'steps': self.steps,
'dt_ms': .1, 'duration_ms': 3., 'record_history': False, 'apl_delay_steps': 18})
write_json(self.root / 'coeff.json', {'synthetic': True})
self.case = dict(spec=json.loads((self.case_dir / 'spec.json').read_text()), graph=self.graph,
bundle=self.bundle, token=('synthetic', str(self.root)), graph_dir=self.graph_dir, bundle_dir=self.bundle_dir,
requested=self.requests, input_hashes={'spec.json': c.file_hash(self.case_dir / 'spec.json'),
'requests.npz': c.file_hash(self.case_dir / 'requests.npz'), 'base_weights.npy': c.file_hash(self.graph_dir / 'weights.npy'),
'coefficients.json': c.file_hash(self.root / 'coeff.json')})
self.ids = np.array([0, 2], np.int64)
self.base = np.array([.275, .55], np.float32)
np.save(self.root / 'mask.npy', self.ids)
np.save(self.root / 'base.npy', self.base)
self.delta = np.zeros(2, np.float32)
self.overlay_name = 'zero'
self._overlay_inputs()
def _overlay_inputs(self):
np.savez(self.root / 'overlay.npz', edge_id=self.ids, plastic_delta_mv=self.delta)
write_json(self.root / 'overlay-manifest.json', dict(schema='phase4am-overlay-v1', overlay_name=self.overlay_name,
overlay_sha256=c.file_hash(self.root / 'overlay.npz'), mask_path=str(self.root / 'mask.npy'),
mask_sha256=c.file_hash(self.root / 'mask.npy'), base_candidate_path=str(self.root / 'base.npy'),
base_candidate_sha256=c.file_hash(self.root / 'base.npy'),
aj_model_dir=str(Path(c.__file__).resolve().parent.parent / 'apl-timing-lab-v1')))
def run_files(self, name, wrapped):
directory = self.root / name
directory.mkdir()
ticks = np.array([0, 1, 18, 22, 23], np.int32)
nodes = np.array([0, 1, 2, 0, 3], np.int32)
state = dict(step=ticks, neuron_idx=nodes, counts=np.bincount(nodes, minlength=4).astype(np.int32),
v=np.full(4, -.052, np.float64), g=np.zeros(4, np.float64))
hashes, queues, requests = [], [], []
last = np.full(4, -1000000000, np.int64)
for tick in range(self.steps):
fired = nodes[ticks == tick]
for request_tick, target in self.requests:
if request_tick == tick:
ok = tick - last[target] >= 22
requests.append(dict(tick=tick, neuron_idx=target, attempted=1, accepted=int(ok), rejected=int(not ok)))
last[fired] = tick
ref = np.maximum(22 - (tick - last), 0).astype(np.int32)
hashes.append(dict(tick=tick, v_sha256=c.raw_hash(state['v']), g_sha256=c.raw_hash(state['g']), ref_sha256=c.raw_hash(ref)))
sources = nodes[ticks == tick - 18] if tick >= 18 else []
rows = np.array([eid for source in sources for eid in range(self.graph['ptr'][source], self.graph['ptr'][source + 1])], np.int64)
accepted = (ref[self.graph['dst'][rows]] == 0).astype(np.uint8)
queues.append(self.helper._queue_digest(tick, rows, accepted))
state['ref'] = ref
np.savez(directory / 'state.npz', **state)
for filename, rows in (('tick-state-hashes.jsonl', hashes), ('queue.jsonl', queues)):
(directory / filename).write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
(directory / 'requests.csv').write_text('tick,neuron_idx,attempted,accepted,rejected\n' +
''.join(','.join(str(row[key]) for key in c._helper(set()).REQUEST_FIELDS) + '\n' for row in requests), encoding='utf-8')
for field, width in (('drive', 2), ('state', 2), ('inhibition', c.K), ('applied', c.K)):
np.save(directory / ('apl-' + field + '.float32.npy'), np.zeros((self.steps, width), np.float32))
pending = np.zeros((18, c.K), np.float32)
np.save(directory / 'apl-pending-tail.float32.npy', pending)
timing = dict(apl_delay_steps=18, steps=self.steps, pending_generated_count=18, generated_rows=self.steps,
applied_generated_rows=self.steps - 18, pending_shape=[18, c.K], pending_initial_zero_prefix=0,
pending_application_tick_start=self.steps, pending_application_tick_stop_exclusive=self.steps + 18,
pending_generated_tick_start=self.steps - 18, pending_generated_tick_stop_exclusive=self.steps,
pending_raw_sha256=c.raw_hash(pending), final_apl_state_float32=[0., 0.], final_apl_state_u32_bits=[0, 0],
final_apl_state_nonzero=False, brain_tail_advanced=False, future_apl_decay_or_feedback_generated=False,
finite_generated_sequence_only=True, closed_loop_branch_integrals_claimed_equal=False,
biological_delay_measurement_claimed=False)
write_json(directory / 'apl-timing-audit.json', timing)
component = dict(ok=True, protocol='phase4ag-apl-v1', n_neurons=c.N, n_apl=2, n_kc=c.K, dt_ms=.1,
files_sha256={key: c.file_hash(self.bundle_dir / key) for key in self.bundle},
array_sha256={key: c.raw_hash(value) for key, value in self.bundle.items()},
decay_f32_bits=f'{int(np.float32(np.exp(-.1 / 20.)).view(np.uint32)):08x}',
gain_f32_bits=f'{int(np.float32(.01).view(np.uint32)):08x}',
environment=dict(device_name='NVIDIA GeForce RTX 4090', cuda_matmul_allow_tf32=False,
float32_matmul_precision='highest', precision_configuration_changed_by_component=False))
write_json(directory / 'apl-component.json', component)
(directory / 'sealed-gpu-source.diff').write_text('synthetic diff, not executed\n', encoding='utf-8')
provenance = dict(sealed_source_sha256=c.SEALED, candidate_sha256=c.AJ_SHA,
source_diff_sha256=c.file_hash(directory / 'sealed-gpu-source.diff'), module_globals_patched=False)
write_json(directory / 'source-audit.json', provenance)
_, effective_sha, apl_count = c._effective_hash(self.case, self.ids if wrapped else None, self.delta if wrapped else None)
summary = dict(case='synthetic', complete=True, pass_all=True, steps=self.steps, completed_ticks=self.steps,
nodes=4, edges=6, precision='float64', apl_precision='float32', state_units='SI volts', total_spikes=len(ticks),
requested=len(requests), accepted_requests=sum(row['accepted'] for row in requests),
rejected_requests=sum(row['rejected'] for row in requests), arriving_edges=sum(row['count'] for row in queues),
accepted_arriving_edges=sum(row['accepted_count'] for row in queues), counts_match_sparse_events=True,
all_finite=True, record_history=False, apl_fast_edges_zeroed=apl_count, kc_targets_present=c.K,
effective_weights_raw_sha256=effective_sha, apl_component=component,
apl_schedule='end, after threshold/reset and guarded arrivals, unconditional all KC',
apl_si_conversion='applied h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction',
apl_delay_steps=18, apl_timing=timing, source_provenance=provenance, pending_generated_count=18,
pending_initial_zero_prefix=0, pending_tail_shape=[18, c.K], future_generated_decay_tail_included=False,
build=dict(sources={'author_ext.cpp': c.CPP_SHA, 'author_ext.cu': c.CUDA_SHA}, arch_list='8.9',
cuda_flags=['-O2', '--fmad=false', '--ftz=false', '--prec-div=true', '--prec-sqrt=true']),
sealed_ae_backend_sha256=c.SEALED['ae_backend'], learning=False, input_sha256=self.case['input_hashes'],
**{key: 1.0 for key in c.PERFORMANCE})
write_json(directory / 'summary.json', summary)
if wrapped:
self._write_overlay(directory, summary)
return directory
def _write_overlay(self, directory, summary):
for source, target in (('overlay.npz', 'overlay-input.npz'), ('overlay-manifest.json', 'overlay-manifest.json')):
shutil.copyfile(self.root / source, directory / target)
input_rows = {}
for label, filename in (('overlay', 'overlay.npz'), ('manifest', 'overlay-manifest.json'), ('mask', 'mask.npy'), ('base_candidate', 'base.npy')):
input_rows[label + '_file'] = str(self.root / filename)
input_rows[label + '_sha256'] = c.file_hash(self.root / filename)
audit = dict(schema='phase4am-overlay-audit-v1', complete=True, overlay_name=self.overlay_name, input=input_rows,
arrays={key: dict(dtype=str(value.dtype), shape=list(value.shape), raw_sha256=c.raw_hash(value)) for key, value in
dict(edge_id=self.ids, plastic_delta_mv=self.delta, base_candidate_mv=self.base).items()},
candidate_count=2, nonzero_delta_count=int(np.count_nonzero(self.delta)), mapped_candidate_count=2,
missing_candidate_count=0, installed_nonzero_count=int(np.count_nonzero(self.delta)), delta_dtype='float32',
delta_units='mV', state_dtype='float64', bridge=c.BRIDGE, installation_schedule=c.SCHEDULE,
weights_updated_during_run=False, learning=False, zero_delta_no_write=True, frozen_mask_exact=True,
bounds_pass=True, apl_overlap_count=0, apl_fast_edges_zeroed=summary['apl_fast_edges_zeroed'],
base_graph_files_modified=False, untouched_weights_bitwise_preserved=True,
base_weights_file_sha256=c.file_hash(self.graph_dir / 'weights.npy'), base_weights_raw_sha256=c._base_raw_hash(self.case),
effective_weights_raw_sha256=summary['effective_weights_raw_sha256'],
expected_effective_weights_raw_sha256=summary['effective_weights_raw_sha256'],
copied_overlay_file='overlay-input.npz', copied_manifest_file='overlay-manifest.json',
inherited_aj_source_provenance=summary['source_provenance'],
inherited_source_audit_scope='AJ source provenance only; AM overlay semantics are recorded separately',
static25_is_exact_fraction_of_float64_base_claimed=False, arrival_weight_sampling='arrival time; delta fixed for entire trial')
write_json(directory / 'overlay-audit.json', audit)
model = Path(c.__file__).resolve().parent
receipt = dict(schema='phase4am-adapter-provenance-v1', complete=True,
scope='constant sparse overlay installation only; no within-trial plasticity or learning',
adapter_file=str(model / 'adapter.py'), adapter_sha256=c.file_hash(model / 'adapter.py'),
overlay_module_file=str(model / 'overlay.py'), overlay_module_sha256=c.file_hash(model / 'overlay.py'),
sealed_sources={relative: dict(path=str(model.parent / relative), sha256=sha) for relative, sha in c.ADAPTER_PINS.items()},
ast=c._expected_ast(model.parent / 'apl-timing-lab-v1/gpu_timing.py', model / 'adapter.py'),
installation_calls=1, weights_updated_during_run=False, learning=False, apl_delay_steps=18,
original_module_globals_patched=False, delta_constant_for_entire_trial=True,
inherited_source_audit_describes_AM_semantics=False, overlay_audit_sha256=c.file_hash(directory / 'overlay-audit.json'),
inherited_summary_sha256=c.file_hash(directory / 'summary.json'), effective_weights_raw_sha256=summary['effective_weights_raw_sha256'])
write_json(directory / 'adapter-provenance.json', receipt)
def compare(self, left, right, mode='zero', output=None):
with patch.object(c, '_load_case', return_value=self.case), patch.object(c, 'CANDIDATES', 2):
return c.compare_runs(self.case_dir, left, right, output, mode)
def assert_pass(self, report):
self.assertTrue(report['pass_all'], json.dumps({key: report.get(key) for key in ('error', 'failed_checks', 'differences')}))
self.assertFalse(report['learning_achieved'])
self.assertFalse(report['candidate_generated'])
self.assertTrue(report['read_manifest'])
def test_zero_complete_real_parsers(self):
left, right = self.run_files('left', False), self.run_files('right', True)
self.assert_pass(self.compare(left, right))
def test_static25_repeat_and_single_validation(self):
self.overlay_name = 'static25'
self.delta = np.multiply(self.base, np.float32(-.25))
self._overlay_inputs()
left, right = self.run_files('left', True), self.run_files('right', True)
self.assert_pass(self.compare(left, right, 'repeat'))
self.assert_pass(self.compare(left, left, 'self'))
def test_production_rejects_toy_dimensions(self):
with self.assertRaises(ValueError):
c._load_case(self.case_dir, set())
def test_missing_adapter_receipt_fails_despite_complete_summary(self):
left, right = self.run_files('left', False), self.run_files('right', True)
(right / 'adapter-provenance.json').unlink()
self.assertFalse(self.compare(left, right)['pass_all'])
def test_corrupt_adapter_receipt_ast_and_summary_binding_fail(self):
left, right = self.run_files('left', False), self.run_files('right', True)
path = right / 'adapter-provenance.json'
original = json.loads(path.read_text())
for change in ('ast', 'binding', 'complete'):
data = copy.deepcopy(original)
if change == 'ast':
data['ast']['entire_tick_loop_ast_identical'] = False
elif change == 'binding':
data['inherited_summary_sha256'] = '0' * 64
else:
data['complete'] = False
write_json(path, data)
self.assertFalse(self.compare(left, right)['pass_all'], change)
def test_per_neuron_count_corruption_not_hidden_by_equal_total(self):
left, right = self.run_files('left', False), self.run_files('right', True)
for folder in (left, right):
with np.load(folder / 'state.npz') as f:
state = {key: f[key].copy() for key in f.files}
state['counts'][0] -= 1
state['counts'][1] += 1
np.savez(folder / 'state.npz', **state)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_both_equal_wrong_queues_fail_independent_reconstruction(self):
left, right = self.run_files('left', False), self.run_files('right', True)
for folder in (left, right):
path = folder / 'queue.jsonl'
rows = [json.loads(row) for row in path.read_text().splitlines()]
rows[18]['ordered_edge_sha256'] = '0' * 64
path.write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
report = self.compare(left, right)
self.assertFalse(report['pass_all'])
self.assertIn('left_gpu.full_event_queue_request_accounting', report['failed_checks'])
def test_both_equal_wrong_requests_fail(self):
left, right = self.run_files('left', False), self.run_files('right', True)
for folder in (left, right):
path = folder / 'requests.csv'
path.write_text(path.read_text().replace('2,0,1,0,1', '2,0,1,1,0'), encoding='utf-8')
self.assertFalse(self.compare(left, right)['pass_all'])
def test_both_equal_wrong_ref_hash_fails(self):
left, right = self.run_files('left', False), self.run_files('right', True)
for folder in (left, right):
path = folder / 'tick-state-hashes.jsonl'
rows = [json.loads(row) for row in path.read_text().splitlines()]
rows[1]['ref_sha256'] = '0' * 64
path.write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
self.assertFalse(self.compare(left, right)['pass_all'])
def test_state_signed_zero_detected(self):
left, right = self.run_files('left', False), self.run_files('right', True)
with np.load(right / 'state.npz') as f:
state = {key: f[key].copy() for key in f.files}
state['g'][0] = -0.
np.savez(right / 'state.npz', **state)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_same_nonfinite_states_fail(self):
left, right = self.run_files('left', False), self.run_files('right', True)
for folder in (left, right):
with np.load(folder / 'state.npz') as f:
state = {key: f[key].copy() for key in f.files}
state['v'][0] = np.inf
np.savez(folder / 'state.npz', **state)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_apl_pending_signed_zero_fails(self):
left, right = self.run_files('left', False), self.run_files('right', True)
path = right / 'apl-pending-tail.float32.npy'
value = np.load(path)
value[0, 0] = np.float32(-0.)
np.save(path, value)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_missing_tick_or_extra_request_fails(self):
left, right = self.run_files('left', False), self.run_files('right', True)
path = right / 'queue.jsonl'
path.write_text('\n'.join(path.read_text().splitlines()[:-1]) + '\n', encoding='utf-8')
self.assertFalse(self.compare(left, right)['pass_all'])
def test_summary_only_whitelisted_performance_may_differ(self):
a = dict(dynamics='same', build={'source': 'same'}, wall_seconds=1)
b = dict(a, wall_seconds=5)
checks, differences = {}, {}
c._summary_pair(a, b, checks, differences)
self.assertTrue(all(checks.values()))
b['build'] = {'source': 'different'}
c._summary_pair(a, b, checks, differences)
self.assertFalse(all(checks.values()))
def test_overlay_bad_units_and_fake_complete_receipt_fail(self):
left, right = self.run_files('left', False), self.run_files('right', True)
path = right / 'overlay-audit.json'
data = json.loads(path.read_text()); data['delta_units'] = 'V'; write_json(path, data)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_source_or_build_mutation_fails(self):
left, right = self.run_files('left', False), self.run_files('right', True)
path = right / 'summary.json'
data = json.loads(path.read_text()); data['build']['cuda_flags'].append('--use_fast_math'); write_json(path, data)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_no_existing_output_overwrite_and_no_input_directory_write(self):
left, right = self.run_files('left', False), self.run_files('right', True)
output = self.root / 'report.json'
self.assert_pass(self.compare(left, right, output=output))
with self.assertRaises(ValueError):
self.compare(left, right, output=output)
with self.assertRaises(ValueError):
self.compare(left, right, output=right / 'report.json')
def test_fake_repeat_self_disallowed(self):
left = self.run_files('left', True)
self.assertFalse(self.compare(left, left, 'repeat')['pass_all'])
def test_static25_cannot_be_zero_reference(self):
self.overlay_name = 'static25'; self.delta = np.multiply(self.base, np.float32(-.25)); self._overlay_inputs()
left, right = self.run_files('left', False), self.run_files('right', True)
self.assertFalse(self.compare(left, right)['pass_all'])
def test_helper_source_pin_and_adapter_ast_contract(self):
model = Path(c.__file__).resolve().parent
self.assertEqual(c.file_hash(model.parent / 'apl-timing-lab-v1/compare_timing.py'), c.HELPER_SHA)
import adapter
_, owner = adapter.derive_ast((model.parent / 'apl-timing-lab-v1/gpu_timing.py').read_bytes())
independent = c._expected_ast(model.parent / 'apl-timing-lab-v1/gpu_timing.py', model / 'adapter.py')
for key, value in owner.items():
self.assertEqual(independent[key], value)
def test_fresh_sha_detects_same_size_write_even_with_identical_stat(self):
path = self.root / 'same-stat.bin'
path.write_bytes(b'first!')
identity = c._fingerprint(path)
with patch.object(c, '_fingerprint', return_value=identity):
before = c.file_hash(path)
path.write_bytes(b'other!')
after = c.file_hash(path)
self.assertEqual(before, hashlib.sha256(b'first!').hexdigest())
self.assertEqual(after, hashlib.sha256(b'other!').hexdigest())
self.assertNotEqual(before, after)
if __name__ == '__main__':
unittest.main()