257 lines
18 KiB
Python
257 lines
18 KiB
Python
"""Evidence-only audit and unchanged-dynamics real motif tests; no learning."""
|
|
import argparse
|
|
import ast
|
|
from datetime import datetime, timezone
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import unittest
|
|
import urllib.request
|
|
import numpy as np
|
|
import pandas as pd
|
|
import yaml
|
|
from math_reference import params, single, state, cpu_motif
|
|
|
|
CFG = yaml.safe_load(Path(__file__).with_name('config.yaml').read_text())
|
|
ROOT = Path(CFG['root']); MODEL = ROOT/CFG['model']; EXP = ROOT/CFG['experiment']
|
|
sys.path.insert(0, str(ROOT/'models/male-cns-lif/apl-pn-scope-audit-v1'))
|
|
from audit import save, digest, immutable_check, seal
|
|
sys.path.insert(0, str(ROOT/'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
|
from diagnostic import manifest_check, queues
|
|
|
|
|
|
def integrity():
|
|
result = {'original': immutable_check(ROOT, ROOT/CFG['old_snapshot'], 4)}
|
|
for name in CFG['additional_frozen']: result[name] = manifest_check(ROOT/name, digest)
|
|
assert all(x['pass'] for x in result.values()), result
|
|
return result
|
|
|
|
|
|
def prepare():
|
|
if EXP.exists() and any(EXP.iterdir()): raise RuntimeError('Refusing nonempty output')
|
|
EXP.mkdir(exist_ok=True)
|
|
for d in ['protocol', 'external-resources', 'audit', 'synthetic', 'motifs', 'regression', 'performance']:
|
|
(EXP/d).mkdir()
|
|
save(EXP/'protocol/preregistered.json', {'config': CFG, 'before_analysis': True,
|
|
'timestamp_utc': datetime.now(timezone.utc).isoformat(),
|
|
'parent_commit': subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip(),
|
|
'sources': {p.name: digest(p) for p in sorted(MODEL.iterdir()) if p.is_file()}})
|
|
save(EXP/'regression/preflight.json', integrity())
|
|
external = EXP/'external-resources'; commit = CFG['author']['commit']; records = []
|
|
for filename in ['model.py', 'LICENSE', 'environment_full.yml']:
|
|
url = f'https://raw.githubusercontent.com/philshiu/Drosophila_brain_model/{commit}/{filename}'
|
|
with urllib.request.urlopen(url, timeout=60) as response: data = response.read()
|
|
target = external/filename
|
|
with target.open('xb') as out: out.write(data)
|
|
records.append({'path': filename, 'url': url, 'bytes': len(data), 'sha256': digest(target), 'license': 'MIT'})
|
|
assert digest(external/'model.py') == CFG['author']['model_sha256']
|
|
assert 'MIT License' in (external/'LICENSE').read_text()
|
|
text = (external/'model.py').read_text(); ast.parse(text)
|
|
clauses = ['dg/dt = -g / tau', '(unless refractory)', 'v = v_rst; w = 0; g = 0 * mV',
|
|
"method='linear'", "'v > v_th'", 'neu[i].rfc = 0 * ms']
|
|
assert all(clause in text for clause in clauses)
|
|
lines = text.splitlines()
|
|
save(EXP/'audit/author-source-clauses.json', [{'clause': clause, 'line_numbers_1based': [i+1 for i, line in enumerate(lines) if clause in line]} for clause in clauses])
|
|
save(external/'provenance.json', {'files': records, 'commit': commit, 'paper': CFG['author']['paper'],
|
|
'downloaded_code_executed': False, 'new_dependencies_installed': False})
|
|
can = ROOT/CFG['canonical']; metadata = pd.read_parquet(can/'neuron-index/unified-metadata.parquet').sort_values('neuron_idx').reset_index(drop=True)
|
|
ptr = np.load(can/'gpu/csr-indptr.int64.npy', mmap_mode='r'); dst = np.load(can/'gpu/csr-indices.int32.npy', mmap_mode='r'); raw = np.load(can/'gpu/csr-weights.int64.npy', mmap_mode='r')
|
|
assert len(metadata) == 166700 and len(dst) == 25582938 and int(raw.sum()) == 124177617
|
|
nodes = np.array(CFG['selected_nodes'], np.int32)
|
|
meta = metadata.iloc[nodes].copy(); assert np.array_equal(meta.neuron_idx, nodes)
|
|
assert meta.neurotransmitter.eq('acetylcholine').all(), 'No sign inference or reassignment'
|
|
alln = meta[meta.annotation_class.eq('ALLN')]; assert alln.annotation_type.eq('lLN1_bc').all() and alln.nt_ground_truth.eq('acetylcholine').all()
|
|
ids = np.concatenate([np.arange(ptr[n], ptr[n+1], dtype=np.int64) for n in nodes])
|
|
src = np.concatenate([np.full(ptr[n+1]-ptr[n], n, np.int32) for n in nodes])
|
|
keep = np.isin(dst[ids], nodes); ids = ids[keep]; src = src[keep]
|
|
edges = pd.DataFrame({'edge_id': ids, 'source_neuron_idx': src, 'target_neuron_idx': dst[ids], 'raw_count': raw[ids]})
|
|
edges['source_body_id'] = metadata.body_id.to_numpy()[src]; edges['target_body_id'] = metadata.body_id.to_numpy()[dst[ids]]
|
|
assert len(edges) == 13
|
|
meta.to_parquet(EXP/'audit/selected-metadata.parquet', index=False); edges.to_csv(EXP/'audit/selected-real-edges.csv', index=False)
|
|
# Explicit metadata mapping only, not physiological parameter assignment.
|
|
graded = metadata[metadata.annotation_type.isin(['lLN2P_a', 'lLN2P_b', 'lLN2P_c', 'lLN2F_b'])].copy()
|
|
cols = ['neuron_idx','body_id','annotation_type','annotation_hemibrainType','annotation_flywireType','annotation_matchingNotes','neurotransmitter','nt_ground_truth']
|
|
graded[cols].to_csv(EXP/'audit/local-inhibition-crosswalk.csv', index=False)
|
|
frozen_yaml = ROOT/CFG['runtime']/'phase4p.yaml'; pc = yaml.safe_load(frozen_yaml.read_text())
|
|
save(EXP/'protocol/frozen-input-hashes.json', {'runtime_config': digest(frozen_yaml),
|
|
'runtime_cpp': digest(ROOT/CFG['runtime']/'phase4p_ext.cpp'), 'runtime_cuda': digest(ROOT/CFG['runtime']/'phase4p_ext.cu'),
|
|
'baseline_phase4b': digest(ROOT/'models/male-cns-lif/phase4b.py'),
|
|
'metadata': digest(can/'neuron-index/unified-metadata.parquet')})
|
|
save(EXP/'protocol/dynamics.json', pc['model'])
|
|
evidence = [
|
|
['spike reset g', 'reset to zero', 'retain g'],
|
|
['g during refractory', 'ODE paused; synaptic writes blocked', 'decay and accept arrivals'],
|
|
['integration', 'linear exact state updater', 'forward Euler with decayed g'],
|
|
['threshold', 'strict >', '>='],
|
|
['within-step arrivals', 'after integration, before reset', 'before integration'],
|
|
['stimulated neurons', 'voltage Poisson input; refractory=0', 'forced spikes subject to 22-step refractory'],
|
|
['W_syn', 'free parameter calibrated on FlyWire feeding response', 'same value transferred to MaleCNS; not independently measured']]
|
|
pd.DataFrame(evidence, columns=['item', 'author', 'frozen_adapted']).to_csv(EXP/'audit/semantics-differences.csv', index=False)
|
|
prepared = ['protocol/dynamics.json','audit/selected-real-edges.csv','audit/selected-metadata.parquet',
|
|
'protocol/frozen-input-hashes.json','audit/local-inhibition-crosswalk.csv']
|
|
save(EXP/'protocol/prepared-input-hashes.json', {name:digest(EXP/name) for name in prepared})
|
|
print('PREPARED: pinned author source, 13 actual motif edges, frozen integrity PASS', flush=True)
|
|
|
|
|
|
def synthetic():
|
|
m = json.loads((EXP/'protocol/dynamics.json').read_text()); p = params(m)
|
|
edges = pd.read_csv(EXP/'audit/selected-real-edges.csv'); counts = np.unique(np.r_[1, edges.raw_count.to_numpy(np.int64)])
|
|
results, traces = [], {}
|
|
cases = {}
|
|
for count in counts:
|
|
a = np.zeros(round(100/m['dt_ms']), np.float32); a[0] = np.float32(count)*np.float32(m['w_syn_mv_per_anatomical_synapse'])
|
|
cases[f'one_impulse_contacts_{count}'] = a
|
|
# The separate refractory boundary fixture is a synthetic unit test, not a biological stimulation.
|
|
a = np.zeros(round(20/m['dt_ms']), np.float32)
|
|
a[0] = np.float32(2000) # enough for a spike at t=0 in every component policy
|
|
a[[1,10,20,22,23]] += np.float32(10)
|
|
cases['refractory_boundary'] = a
|
|
a = np.zeros(round(200/m['dt_ms']), np.float32); a[:round(100/m['dt_ms'])] = np.float32(10)
|
|
cases['sustained_then_off'] = a
|
|
cases['zero_input'] = np.zeros(round(100/m['dt_ms']), np.float32)
|
|
for name, a in cases.items():
|
|
for policy in CFG['synthetic_policies']:
|
|
events, history = single(a, p, policy)
|
|
repeat = single(a, p, policy)
|
|
assert np.array_equal(events, repeat[0]) and np.array_equal(history, repeat[1])
|
|
if policy in ('reset_g_only', 'reset_and_refractory'): assert np.all(history[events,1] == 0)
|
|
if name.startswith('one_impulse') and policy in ('reset_g_only', 'reset_and_refractory'): assert len(events) <= 1
|
|
if name == 'zero_input': assert not len(events)
|
|
if name == 'refractory_boundary' and policy == 'reset_and_refractory':
|
|
assert np.all(history[:22,1] == 0) and history[22,1] > 0
|
|
results.append({'case': name, 'policy': policy, 'spikes': len(events),
|
|
'spike_times_ms': json.dumps((events.astype(float)*m['dt_ms']).tolist()),
|
|
'post_input_spikes': int((events >= 1000).sum()) if name == 'sustained_then_off' else None,
|
|
'final_g': float(history[-1,1]), 'finite': bool(np.isfinite(history).all()), 'exact_repeat': True,
|
|
'not_full_Brian2_replication': True})
|
|
traces[name+'__'+policy] = history
|
|
traces[name+'__input'] = a
|
|
pd.DataFrame(results).to_csv(EXP/'synthetic/results.csv', index=False)
|
|
np.savez_compressed(EXP/'synthetic/traces.npz', **traces)
|
|
save(EXP/'regression/synthetic.json', {'pass': True, 'cases': len(cases), 'policies': 4,
|
|
'proves_only_operator_difference': True, 'brain_model_modified': False, 'Brian2_executed': False})
|
|
|
|
|
|
def gpu_motif(rt, nodes, edges, weights, p, steps, seed):
|
|
import torch
|
|
n = len(nodes); lookup = {int(node): i for i, node in enumerate(nodes)}
|
|
src = np.array([lookup[int(x)] for x in edges.source_neuron_idx], np.int32)
|
|
dst = np.array([lookup[int(x)] for x in edges.target_neuron_idx], np.int32)
|
|
d = rt.device; qlen = p['delay_steps']+1
|
|
v = torch.full((n,), float(p['rest']), dtype=torch.float32, device=d); g = torch.zeros_like(v)
|
|
ref = torch.zeros(n, dtype=torch.int32, device=d); spike = torch.zeros(n, dtype=torch.uint8, device=d); counts = torch.zeros_like(ref)
|
|
queue = torch.zeros((qlen,n), dtype=torch.float32, device=d); wg = torch.as_tensor(weights, device=d)
|
|
history = np.empty((steps,3,n), np.float32); ts, ns = [], []
|
|
for t in range(steps):
|
|
rt.ext.state_step(v,g,ref,queue[t%qlen],spike,counts,float(p['gd']),float(p['alpha']),float(p['rest']),float(p['reset']),float(p['threshold']),p['ref_steps'])
|
|
if t == 0 and seed is not None:
|
|
targets = torch.tensor([lookup[seed]], dtype=torch.int64, device=d); force = torch.ones(1,dtype=torch.uint8,device=d)
|
|
rt.ext.force_step(v,ref,spike,counts,targets,force,float(p['reset']),p['ref_steps'])
|
|
active = spike.cpu().numpy().astype(bool)
|
|
indices = np.flatnonzero(active); ts.extend([t]*len(indices)); ns.extend(nodes[indices].tolist())
|
|
selected = np.flatnonzero(active[src]); selected = selected[np.argsort(dst[selected],kind='stable')]
|
|
if len(selected):
|
|
st = dst[selected]; starts = np.flatnonzero(np.r_[True,st[1:] != st[:-1]])
|
|
rt.ext.segmented_sum(torch.as_tensor(selected,dtype=torch.int64,device=d),
|
|
torch.as_tensor(starts,dtype=torch.int64,device=d), torch.as_tensor(st[starts],dtype=torch.int32,device=d),
|
|
wg,queue[(t+p['delay_steps'])%qlen])
|
|
history[t] = torch.stack([v,g,ref.float()]).cpu().numpy()
|
|
return {'step': np.asarray(ts,np.int32),'neuron_idx':np.asarray(ns,np.int32),'counts':counts.cpu().numpy(),
|
|
'v':v.cpu().numpy(),'g':g.cpu().numpy(),'ref':ref.cpu().numpy(),'history':history,'queue':queue.cpu().numpy()}
|
|
|
|
|
|
def run():
|
|
pre = json.loads((EXP/'protocol/preregistered.json').read_text())
|
|
assert all(digest(MODEL/name) == h for name,h in pre['sources'].items())
|
|
assert not (EXP/'regression/run-start.json').exists(), 'Refusing overwrite'
|
|
prepared = json.loads((EXP/'protocol/prepared-input-hashes.json').read_text())
|
|
assert all(digest(EXP/name) == h for name,h in prepared.items()), 'Prepared-input drift'
|
|
runtime = ROOT/CFG['runtime']; pc = yaml.safe_load((runtime/'phase4p.yaml').read_text())
|
|
assert pc['model'] == json.loads((EXP/'protocol/dynamics.json').read_text())
|
|
suite = unittest.defaultTestLoader.discover(str(MODEL), pattern='test_math.py')
|
|
test_result = unittest.TextTestRunner(verbosity=2).run(suite)
|
|
assert test_result.wasSuccessful() and test_result.testsRun == 7
|
|
save(EXP/'regression/unit-tests.json', {'pass': True, 'tests': test_result.testsRun})
|
|
queues(); os.environ['CUDA_VISIBLE_DEVICES'] = pc['device']['visible_gpu_uuid']
|
|
os.environ['CUDA_HOME'] = '/usr/local/cuda'; os.environ['MAX_JOBS'] = '2'; os.environ['TORCH_CUDA_ARCH_LIST'] = '8.9'
|
|
os.environ['PATH'] = str(ROOT/'models/male-cns-lif/.venv/bin')+':/usr/local/cuda/bin:'+os.environ['PATH']
|
|
sys.path.insert(0,str(ROOT/'models/male-cns-lif')); sys.path.insert(0,str(runtime))
|
|
import torch, phase4p
|
|
free,total = torch.cuda.mem_get_info(); assert free > 2*1024**3
|
|
save(EXP/'regression/run-start.json', {'free_VRAM': free,'device':torch.cuda.get_device_name(),
|
|
'whole_brain_uses_only_frozen_reference':True,'alternative_policies_only_synthetic_single_cell':True})
|
|
start = time.perf_counter(); torch.cuda.reset_peak_memory_stats()
|
|
rt = phase4p.CanonicalRuntime(pc,runtime,EXP); p = params(pc['model'])
|
|
synthetic()
|
|
edges = pd.read_csv(EXP/'audit/selected-real-edges.csv').sort_values('edge_id')
|
|
eid=edges.edge_id.to_numpy(np.int64); src=edges.source_neuron_idx.to_numpy(np.int32)
|
|
assert np.all(eid >= rt.indptr_np[src]) and np.all(eid < rt.indptr_np[src+1])
|
|
assert np.array_equal(rt.indices_np[eid],edges.target_neuron_idx)
|
|
assert np.array_equal(rt.raw_np[eid],edges.raw_count)
|
|
save(EXP/'regression/anatomical-edge-validation.json', {'pass':True,'edges':len(edges),
|
|
'checks':['canonical edge_id in source CSR row','exact target','exact raw int64 count','exact effective float32 weight']})
|
|
effective = rt.weights.cpu().numpy(); rows, regress = [], []
|
|
assert np.array_equal(effective[edges.edge_id], edges.raw_count.to_numpy(np.float32)*np.float32(pc['model']['w_syn_mv_per_anatomical_synapse']))
|
|
for spec in CFG['motifs']:
|
|
queues(); nodes = np.sort(np.array(spec['nodes'],np.int32)); subset = edges[edges.source_neuron_idx.isin(nodes)&edges.target_neuron_idx.isin(nodes)].copy()
|
|
if 'pairs' in spec:
|
|
pairs = {tuple(x) for x in spec['pairs']}
|
|
subset = subset[[tuple(x) in pairs for x in subset[['source_neuron_idx','target_neuron_idx']].to_numpy()]]
|
|
assert len(subset) == len(pairs)
|
|
w = effective[subset.edge_id]; seed = CFG['seed_node'] if spec.get('force',True) else None
|
|
steps = round(CFG['duration_ms']/pc['model']['dt_ms']); t0 = time.perf_counter()
|
|
cpu = cpu_motif(nodes,subset,w,p,steps,seed); gpu = gpu_motif(rt,nodes,subset,w,p,steps,seed)
|
|
eq = {key:bool(np.array_equal(value,gpu[key])) for key,value in cpu.items()}
|
|
save(EXP/'regression'/(spec['name']+'.json'), {'exact':eq,'pass':all(eq.values())}); assert all(eq.values()),eq
|
|
if seed is None: assert len(cpu['step']) == 0
|
|
subset.to_csv(EXP/'motifs'/(spec['name']+'-edges.csv'),index=False)
|
|
pd.DataFrame({'step':cpu['step'],'neuron_idx':cpu['neuron_idx']}).to_parquet(EXP/'motifs'/(spec['name']+'-events.parquet'),index=False)
|
|
np.savez_compressed(EXP/'motifs'/(spec['name']+'-state.npz'), nodes=nodes, **{k:v for k,v in cpu.items() if k not in ('step','neuron_idx')})
|
|
late = cpu['step'] >= round(CFG['late_start_ms']/pc['model']['dt_ms'])
|
|
for node in nodes:
|
|
chosen = cpu['neuron_idx'] == node
|
|
rows.append({'motif':spec['name'],'neuron_idx':int(node),'spikes':int(chosen.sum()),
|
|
'late_spikes':int((chosen&late).sum()),'first_ms':float(cpu['step'][chosen][0]*pc['model']['dt_ms']) if chosen.any() else None,
|
|
'last_ms':float(cpu['step'][chosen][-1]*pc['model']['dt_ms']) if chosen.any() else None,
|
|
'external_force_requests':int(seed == node),'frozen_math_unchanged':True,'whole_brain_necessity_proven':False})
|
|
regress.append({'motif':spec['name'],'CPU_GPU_exact':True,'wall_seconds':time.perf_counter()-t0})
|
|
print(json.dumps({'motif':spec['name'],'events':len(cpu['step']),'late_events':int(late.sum()),'exact':True}),flush=True)
|
|
pd.DataFrame(rows).to_csv(EXP/'motifs/summary.csv',index=False)
|
|
save(EXP/'regression/motifs.json',regress)
|
|
refs=[]
|
|
for spec in pc['reference_trials']:
|
|
queues(); base = ROOT/'experiments/phase4p-deterministic-runtime/phase4c-v2/reference'/spec['name']
|
|
ev=pd.read_parquet(base/'spike-events.parquet')
|
|
expected={'step':ev.step.to_numpy(),'neuron_idx':ev.neuron_idx.to_numpy(),
|
|
'counts':np.load(base/'per-neuron-spike-counts.int32.npy'),'v':np.load(base/'final-voltage.float32.npy'),'g':np.load(base/'final-g.float32.npy')}
|
|
r=rt.run(np.flatnonzero(rt.pops[spec['population']]),spec['rate_hz'],spec['seed'],spec['duration_ms'])
|
|
eq={k:bool(np.array_equal(r[k],v)) for k,v in expected.items()};assert all(eq.values()),eq
|
|
refs.append({'trial':spec['name'],'exact':eq,'wall_seconds':r['metrics']['wall_seconds']})
|
|
print(json.dumps({'frozen_trial':spec['name'],'exact':True}),flush=True)
|
|
save(EXP/'regression/frozen-five.json',{'pass':True,'trials':refs,'oracle':'frozen deterministic-v2, not Brian2'})
|
|
save(EXP/'performance/summary.json',{'wall_seconds':time.perf_counter()-start,'peak_allocated_VRAM':int(torch.cuda.max_memory_allocated()),
|
|
'not_runtime_benchmark':True,'ComfyUI_unloaded':False})
|
|
save(EXP/'audit/run-complete.json',{'synthetic_policies':4,'real_motifs_frozen_only':5,'all_CPU_GPU_exact':True,'learning_achieved':False,
|
|
'alternative_MaleCNS_dynamics_run':False,'original_author_code_executed':False})
|
|
|
|
|
|
def finalize():
|
|
assert (EXP/'audit/run-complete.json').exists()
|
|
post=integrity();assert post==json.loads((EXP/'regression/preflight.json').read_text())
|
|
save(EXP/'regression/final-integrity.json',post)
|
|
pre=json.loads((EXP/'protocol/preregistered.json').read_text());assert all(digest(MODEL/n)==h for n,h in pre['sources'].items())
|
|
from report import write_report
|
|
write_report(EXP)
|
|
save(EXP/'audit/final-status.json',{'status':'COMPLETE-SEMANTICS-AUDIT / LEARNING-NOT-ACHIEVED','frozen_integrity_pass':True,
|
|
'documented_adaptation_not_GPU_regression':True,'new_model_authorization_separate':True,'goal_remains_active':True})
|
|
for folder in (MODEL,EXP):
|
|
print('SEALED',folder,seal(folder),flush=True);assert manifest_check(folder,digest)['pass']
|
|
|
|
|
|
if __name__=='__main__':
|
|
parser=argparse.ArgumentParser();parser.add_argument('task',choices=['prepare','run','finalize']);a=parser.parse_args();globals()[a.task]()
|