"""Read-only frozen-brain timing audit. All outputs are confined to Phase 4R.""" from pathlib import Path import argparse import hashlib import json import os import resource import sys import time import numpy as np import pandas as pd import torch import yaml import phase4p import phase4p_finalize from phase4p_revalidate import DeterministicAPLSim, door_inputs, make_drive from phase4h_audit import verify_checksum from phase4q import last_spike_trace, edge_elig, random_us_steps def save(p, obj): p.parent.mkdir(parents=True, exist_ok=True) p.write_text(json.dumps(obj, indent=2, sort_keys=True, allow_nan=False) + '\n') def digest(p): h=hashlib.sha256() with p.open('rb') as f: for b in iter(lambda:f.read(8<<20), b''): h.update(b) return h.hexdigest() def arrhash(*a): h=hashlib.sha256() for x in a: h.update(np.ascontiguousarray(x).tobytes()) return h.hexdigest() def quick_integrity(cfg): specs=[('P',Path(cfg['phase4p_root']),'manifests/checksums.sha256'), ('P_candidate',Path(cfg['runtime_root'])/'candidate','checksums.sha256'), ('Q',Path(cfg['phase4q_root']),'manifests/checksums.sha256'), ('Q_model',Path(cfg['phase4q_model']),'checksums.sha256')] return {k:verify_checksum(root,root/rel) for k,root,rel in specs} def oracle(steps,nodes,lookup,us,window,tau,dt): # Independent oracle: sort event times separately for every KC, binary-search at US. out=np.zeros(int(lookup.max())+1,np.float32) for node in np.unique(nodes): ts=np.sort(steps[nodes==node]); pos=np.searchsorted(ts,us,side='right')-1 if pos>=0: age=np.float32(us-int(ts[pos]))*np.float32(dt) if age<=np.float32(window): out[lookup[node]]=np.exp(-age/np.float32(tau)) return out def curves(run,lookup,multiplicity,windows,dt,nsteps,shift=0): last=np.full((nsteps,len(multiplicity)),-1,np.int32) steps=run['kc_event_step']+shift; nodes=run['kc_event_node'];valid=steps=0)&(age<=np.float32(w)) e[ok]=np.exp(-age[ok]/np.float32(w/3)) rows.append(np.sum(e*multiplicity[None,:],axis=1,dtype=np.float64)) return rows def run_audit(cfg,exp,model): for name in ('audit','protocol','events','timing','fixed-event','network-effects','regression','performance','figures','manifests'): (exp/name).mkdir(parents=True,exist_ok=True) if (exp/'audit/preflight.json').exists(): raise RuntimeError('Existing run: refusing to overwrite. Review it explicitly.') pre=quick_integrity(cfg);save(exp/'audit/preflight.json',pre) if not all(x['passed'] for x in pre.values()): raise RuntimeError('Frozen P/Q integrity failed') pcfg=yaml.safe_load((Path(cfg['runtime_root'])/'phase4p.yaml').read_text()) qcfg=yaml.safe_load((Path(cfg['phase4q_model'])/'phase4q.yaml').read_text()) ocfg,panel,vectors,selection,mapping=door_inputs() cal=panel[panel.split=='calibration'].iloc[:2] assert not set(cal.odor_id)&{selection['ODOR_A']['odor_id'],selection['ODOR_B']['odor_id']} inputs=[];drives={};nsteps=round(cfg['duration_ms']/pcfg['model']['dt_ms']);dt=pcfg['model']['dt_ms'] for oi,(_,odor) in enumerate(cal.iterrows()): label='CAL_'+chr(65+oi); t,r=make_drive(ocfg,panel,vectors,selection,mapping,str(odor.odor_id),'physiological');drives[label]=(t,r) pd.DataFrame({'neuron_idx':t,'rate_hz':r}).to_parquet(exp/'protocol'/f'{label}-PN-drive.parquet',index=False) for split in ('calibration','heldout'): for seed in cfg[split+'_seeds']: actual=seed+oi*100 qs=int(random_us_steps(cfg['random_q_seed_base'],actual,1,nsteps)[0]) ns=round(float(np.random.default_rng(cfg['random_n_seed_base']+actual).uniform(30,110))/dt) inputs.append({'label':label,'odor_id':str(odor.odor_id),'odor_name':str(odor.odor_name), 'split':split,'seed':seed,'actual_seed':actual,'matched_step':round(cfg['matched_us_ms']/dt), 'q_random_step':qs,'n_random_step':ns,'target_count':len(t),'rate_sum_hz':float(r.sum()),'drive_hash':arrhash(t,r)}) save(exp/'protocol/preregistered-inputs.json',{'config':cfg,'inputs':inputs,'formal_AB_used':False, 'saved_before_simulation':True,'selected_timing':None}) sourcefiles=[Path(cfg['runtime_root'])/n for n in ('phase4p.py','phase4p_ext.cpp','phase4p_ext.cu','phase4p.yaml','phase4p_revalidate.py')] sourcefiles += [Path(cfg['phase4q_model'])/n for n in ('phase4q.py','phase4q.yaml')] sourcefiles += list(model.glob('phase4r.*')) save(exp/'protocol/source-hashes.json',{str(p):digest(p) for p in sourcefiles}) static_findings={ 'odor_forcing':'constant PN rate vectors over all 120 ms, Bernoulli events per timestep; no brief CS pulse in this runtime', 'matched_US':'one forced spike per selected DAN at 90 ms; external teaching gate sampled at same emission timestep', 'random_Q':'one discrete uniform step over [0,120) ms; unchanged selected DANs/amplitude', 'random_N':'continuous uniform [30,110] ms then rounded to a timestep', 'Q_KC_timing':'KC soma/emission event time is called terminal time; does not add 18-step anatomical delay', 'equal_delay_note':'shifting BOTH KC and US by 18 steps preserves relative timing; shifting only KC changes the teaching contract', 'Q_control_scope':'D0/unrelated/far-before/far-after are algebraic eligibility tests, not physiological control simulations', 'Q_ten_repeats':'same NumPy function called ten times on a synthetic event, not full-brain runtime repeats', 'Q_checkpoint':'reconstructs trace from in-memory last-spike array, no disk serialize/restore', 'Q_timing_matrix':'synchronous hypothetical activity on all in-compartment terminals, not measured odor-driven edge-group credit', 'Q_future_far_case':'for window 250 ms, synthetic far-before KC step is negative; this is not an actual runtime event', 'Q_independence':'calibration split selected independently of formal odors; frozen-stimuli file was written after simulations', 'Q_comparison':'matched and random timing were run separately, so KC streams may differ through DAN/network feedback', 'Q_claim_limit':'calibration failure does not prove universal credit-rule failure or associative-memory failure'} save(exp/'audit/static-protocol-findings.json',static_findings) rt=phase4p.CanonicalRuntime(pcfg,Path(cfg['runtime_root']),exp);sim=DeterministicAPLSim(rt) assert rt.n==166700 and len(rt.indices_np)==25582938 identity=sim.identity;comp=identity.compartment.astype(str).to_numpy()==cfg['target_compartment'] lookup=np.full(sim.n,-1,np.int32);lookup[sim.kc]=np.arange(len(sim.kc),dtype=np.int32) source_local=lookup[identity.source_neuron_idx.to_numpy(np.int32)] assert np.all(source_local>=0) multi=np.bincount(source_local[comp],minlength=len(sim.kc)).astype(np.float32) dan=sim.dan_targets(cfg['matched_dan_type'],cfg['target_compartment']) identity.to_parquet(exp/'protocol/frozen-edge-identity.parquet',index=False) pd.DataFrame({'neuron_idx':dan,'body_id':sim.meta.body_id.to_numpy()[dan]}).to_parquet(exp/'protocol/DAN-targets.parquet',index=False) ids=identity.edge_id.to_numpy(np.int64) assert np.array_equal(sim.sources[ids],identity.source_neuron_idx.to_numpy()) assert np.array_equal(rt.indices_np[ids],identity.target_neuron_idx.to_numpy()) # Real DAN->KC/MBON edges document arrival provenance; no inferred compartment relations. dk=np.isin(sim.sources,dan)&np.isin(rt.indices_np,np.r_[sim.kc,sim.mbon]) realids=np.flatnonzero(dk) pd.DataFrame({'edge_id':realids,'source_neuron_idx':sim.sources[realids],'target_neuron_idx':rt.indices_np[realids], 'raw_synapse_count':rt.raw_np[realids],'delay_steps':rt.delay_steps}).to_parquet(exp/'protocol/real-DAN-to-KC-MBON-edges.parquet',index=False) telemetry=[];hist=[];fixed=[];grid=[];effects=[];edge_rows=[];oracle_rows=[];q_repro=[];repeat_rows=[] qold=pd.read_parquet(Path(cfg['phase4q_root'])/'calibration/frozen-stimuli.parquet') qmatrix=pd.read_parquet(Path(cfg['phase4q_root'])/'calibration/window-matrix.parquet') walls=[];t0=time.perf_counter();cpu0=resource.getrusage(resource.RUSAGE_SELF);torch.cuda.reset_peak_memory_stats() for ix,spec in enumerate(inputs): tag=f"{spec['split']}-{spec['label']}-{spec['seed']}";targets,rates=drives[spec['label']] runs={}; gates={'no_external_US':None,'matched_90ms':spec['matched_step'], 'random_Q_0_120ms':spec['q_random_step'],'random_N_30_110ms':spec['n_random_step']} for condition in cfg['conditions']: u=gates[condition];run=sim.run(targets,rates,spec['actual_seed'],cfg['duration_ms'], dan_targets=dan,dan_pulses=[] if u is None else [u*dt],collect_events=True) assert run['finite'];runs[condition]=run;walls.append(run['wall_seconds']) ev=run['kc_event_step'];nodes=run['kc_event_node'];loc=lookup[nodes];incomp=multi[loc]>0 frame=pd.DataFrame({'emission_step':ev,'arrival_step':ev+rt.delay_steps,'neuron_idx':nodes, 'body_id':sim.meta.body_id.to_numpy()[nodes]}) frame.to_parquet(exp/'events'/f'{tag}-{condition}.parquet',index=False) np.savez_compressed(exp/'events'/f'{tag}-{condition}-state.npz',counts=run['counts'],v=run['final_v'],g=run['final_g']) telemetry.append({**spec,'condition':condition,'external_US_step':u,'external_US_event_count':0 if u is None else len(dan), 'KC_events':len(ev),'KC_active':int(np.count_nonzero(run['kc_counts'])),'compartment_source_events':int(incomp.sum()), 'KC_fraction_first_30ms':float(np.mean(ev<300)) if len(ev) else 0., 'compartment_fraction_first_30ms':float(np.mean(ev[incomp]<300)) if incomp.any() else 0., 'compartment_events_85_to_90ms':int(np.count_nonzero(incomp&(ev>=850)&(ev<=900))), 'KC_events_sha256':arrhash(ev,nodes),'wall_seconds':run['wall_seconds']}) for begin in range(0,nsteps,50): select=(ev>=begin)&(ev=begin)&(ev+rt.delay_steps=0 else None, 'age_at_US_ms':float((900-ls)*dt) if ls>=0 else None,'eligibility':float(trace[source_local[j]])}) # Actual full-brain repeats: nine additional runs + first matched run = ten. if ix==0: ref=runs['matched_90ms'] for rep in range(1,cfg['runtime_repeats']): rr=sim.run(targets,rates,spec['actual_seed'],cfg['duration_ms'],dan_targets=dan,dan_pulses=[90],collect_events=True) exact={k:bool(np.array_equal(rr[k],ref[k])) for k in ('kc_event_step','kc_event_node','counts','final_v','final_g')} repeat_rows.append({'repeat':rep+1,'exact':exact,'all_exact':all(exact.values()),'wall_seconds':rr['wall_seconds']}) assert all(exact.values()) print(json.dumps({'completed_trials':ix+1,'of':len(inputs),'label':spec['label'],'seed':spec['seed']}),flush=True) for name,rows in [('events/trials',telemetry),('timing/KC-histograms',hist),('fixed-event/ratios',fixed), ('fixed-event/time-grid',grid),('network-effects/credit-decomposition',effects),('timing/edge-age-at-90ms',edge_rows)]: pd.DataFrame(rows).to_parquet(exp/(name+'.parquet'),index=False) save(exp/'regression/phase4q-reproduction.json',{'rows':q_repro,'all_event_and_credit_exact':all(x['event_hash_exact'] and x['credit_exact'] for x in q_repro)}) save(exp/'regression/independent-oracle.json',{'rows':oracle_rows,'all_exact':all(x['exact'] for x in oracle_rows)}) save(exp/'regression/full-runtime-10-repeat.json',{'scope':'one calibration odor/seed, 120ms, unchanged APL and runtime-v2', 'runs':10,'rows':repeat_rows,'all_exact':all(x['all_exact'] for x in repeat_rows)}) # Runtime weights are restored before comparing full frozen five-trial references. rt.weights.copy_(sim.base);reg=[] for spec in pcfg['reference_trials']: refroot=Path(cfg['phase4p_root'])/'phase4c-v2/reference'/spec['name'] ref_events=pd.read_parquet(refroot/'spike-events.parquet') ref={'step':ref_events.step.to_numpy(),'neuron_idx':ref_events.neuron_idx.to_numpy(), 'counts':np.load(refroot/'per-neuron-spike-counts.int32.npy'),'v':np.load(refroot/'final-voltage.float32.npy'),'g':np.load(refroot/'final-g.float32.npy')} for rep in range(cfg['reference_repeats']): r=rt.run(np.flatnonzero(rt.pops[spec['population']]),spec['rate_hz'],spec['seed'],spec['duration_ms']) exact={k:bool(np.array_equal(r[k],ref[k])) for k in ref} reg.append({'trial':spec['name'],'repeat':rep+1,'exact':exact,'all_exact':all(exact.values()),'wall_seconds':r['metrics']['wall_seconds']}) assert all(exact.values()), f'Frozen regression failed: {spec}' print(json.dumps({'regression':spec['name'],'exact':True}),flush=True) save(exp/'regression/five-frozen-trials.json',{'all_exact':all(x['all_exact'] for x in reg),'rows':reg,'reference':'Phase4P phase4c-v2 frozen reference'}) # Eligibility controls operate on actual recorded events, and are explicitly offline. base=runs['no_external_US'];maxstep=int(base['kc_event_step'].max()) if len(base['kc_event_step']) else 0 ctr=[] for w in cfg['windows_ms']: before,_=last_spike_trace(base['kc_event_step'],base['kc_event_node'],lookup,-10000,w,w/3,dt) after,_=last_spike_trace(base['kc_event_step'],base['kc_event_node'],lookup,maxstep+10000,w,w/3,dt) at,last=last_spike_trace(base['kc_event_step'],base['kc_event_node'],lookup,900,w,w/3,dt) path=exp/'regression'/f'checkpoint-window-{w}.npz';np.savez_compressed(path,last=last) restored=np.load(path)['last'];age=(900-restored).astype(np.float32)*np.float32(dt) reconstructed=np.zeros(len(last),np.float32);ok=(restored>=0)&(age<=np.float32(w));reconstructed[ok]=np.exp(-age[ok]/np.float32(w/3)) ctr.append({'window_ms':w,'far_before_zero':not np.any(before),'far_after_zero':not np.any(after), 'D0_zero':not np.any(edge_elig(at,source_local,comp,False)), 'unrelated_compartment_zero_on_scored_edges':not np.any(edge_elig(at,source_local,identity.compartment.astype(str).to_numpy()=="y2a'1")[comp]), 'disk_checkpoint_restore_exact':bool(np.array_equal(at,reconstructed)), 'future_append_invariant':bool(np.array_equal(at,oracle(base['kc_event_step'],base['kc_event_node'],lookup,900,w,w/3,dt)))}) save(exp/'regression/offline-controls.json',{'scope':'actual events, offline eligibility only; not biological controls','rows':ctr}) cpu1=resource.getrusage(resource.RUSAGE_SELF);wall=time.perf_counter()-t0 save(exp/'performance/summary.json',{'wall_seconds_audit_and_regression':wall,'brain_runs_main':len(inputs)*4, 'extra_full_runtime_repeats':9,'frozen_reference_runs':len(reg),'main_brain_wall_seconds':sum(walls), 'CPU_percent_one_core':100*(cpu1.ru_utime+cpu1.ru_stime-cpu0.ru_utime-cpu0.ru_stime)/wall, 'peak_RAM_bytes':resource.getrusage(resource.RUSAGE_SELF).ru_maxrss*1024,'peak_VRAM_bytes':torch.cuda.max_memory_allocated(), 'GPU_name':torch.cuda.get_device_name(0),'GPU_utilization':'not sampled; not inferred from timing'}) analyze(cfg,exp) def analyze(cfg,exp): fixed=pd.read_parquet(exp/'fixed-event/ratios.parquet');ev=pd.read_parquet(exp/'events/trials.parquet') eff=pd.read_parquet(exp/'network-effects/credit-decomposition.parquet');age=pd.read_parquet(exp/'timing/edge-age-at-90ms.parquet') ratio=[] for (split,w),f in fixed.groupby(['split','window_ms']): m=float(f.matched_same_events.mean());q=float(f.random_q_exact_expectation.mean());n=float(f.random_n_exact_expectation.mean()) ratio.append({'split':split,'window_ms':int(w),'matched':m,'random_Q_exact':q,'random_N_exact':n, 'ratio_Q':m/q if q else None,'ratio_N':m/n if n else None}) groups=age.groupby(['split','window_ms','frozen_group'],as_index=False).agg(total_eligibility=('eligibility','sum'),credited_observations=('eligibility',lambda x:int((x>0).sum()))) groups.to_parquet(exp/'audit/frozen-group-credit.parquet',index=False) report={'phase':'4R','status':'COMPLETED-DIAGNOSTIC-NO-CANDIDATE','learning_run':False,'next_phase_started':False, 'new_timing_selected':None,'new_eligibility_rule_selected':None,'formal_AB_evaluated':False, 'same_event_ratios':ratio,'symmetric_delay_exact':bool(fixed.symmetric_delay_exact.all()), 'activity_by_condition':ev.groupby(['split','condition'],as_index=False)[['KC_events','KC_active','compartment_fraction_first_30ms','compartment_events_85_to_90ms']].mean().to_dict('records'), 'network_effects':eff.groupby(['split','condition','window_ms'],as_index=False)[['fixed_KC_credit','rerun_KC_credit','network_feedback_credit_difference','KC_count_L1_vs_noUS','MBON_count_L1_vs_noUS']].mean().to_dict('records'), 'limitations':['Same two calibration odors with independent heldout seeds; not heldout odors or generalization proof.', 'No APL ablation; early transient cannot be uniquely attributed to APL.', 'Emission-time vs arrival-time semantics audited without changing frozen teaching semantics.', 'No training or functional associative-memory gate in this audit.', 'Frozen-group labels belong to formal A/B; calibration credit on these groups is not calibration odor specificity.']} save(exp/'audit/results.json',report) import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt hist=pd.read_parquet(exp/'timing/KC-histograms.parquet');grid=pd.read_parquet(exp/'fixed-event/time-grid.parquet') fig,axs=plt.subplots(2,2,figsize=(13,8),layout='constrained') for oi,label in enumerate(('CAL_A','CAL_B')): for split,style in [('calibration','-'),('heldout','--')]: h=hist[(hist.label==label)&(hist.split==split)&(hist.condition=='no_external_US')].groupby('start_ms').compartment_source_spikes.mean() axs[0,oi].plot(h.index+2.5,h.values,style,label=split) axs[0,oi].axvline(90,color='black',ls=':',label='US 90 ms');axs[0,oi].set(title=label+' target-compartment KC activity',xlabel='Time (ms)',ylabel='KC spikes / 5 ms');axs[0,oi].legend() for w in cfg['windows_ms']: g=grid[(grid.label==label)&(grid.split=='heldout')&(grid.window_ms==w)].groupby('US_step').credit_same_KC_events.mean() axs[1,oi].plot(g.index*0.1,g.values,label=f'{w} ms') axs[1,oi].axvline(90,color='black',ls=':');axs[1,oi].set(title='Fixed-event credit vs US time (heldout seeds)',xlabel='US time (ms)',ylabel='Summed edge eligibility');axs[1,oi].legend(ncol=2) fig.savefig(exp/'figures/timing-audit.png',dpi=180);plt.close(fig) print(json.dumps({'completed':True,'ratios':ratio}),flush=True) def seal(cfg,exp,model): frozen=phase4p_finalize.integrity();after=quick_integrity(cfg) before=json.loads((exp/'audit/preflight.json').read_text()) source=json.loads((exp/'protocol/source-hashes.json').read_text()) same=all(digest(Path(p))==h for p,h in source.items()) rep={'Phase1_4O':frozen,'Phase4P_4Q':after,'same_before_after':before==after,'source_hashes_unchanged':same, 'all_passed':frozen['all_static_checks_passed'] and all(x['passed'] for x in after.values()) and before==after and same} save(exp/'audit/frozen-integrity.json',rep) for root in (model,exp): paths=[p for p in sorted(root.rglob('*')) if p.is_file() and p.name not in ('checksums.sha256','manifest.json')] rows=[{'path':p.relative_to(root).as_posix(),'sha256':digest(p),'bytes':p.stat().st_size} for p in paths] save(root/'manifest.json',{'root':str(root),'files':rows}) rows.append({'path':'manifest.json','sha256':digest(root/'manifest.json')}) (root/'checksums.sha256').write_text(''.join(x['sha256']+' '+x['path']+'\n' for x in rows)) own=verify_checksum(root,root/'checksums.sha256');assert own['passed'];print(json.dumps(own),flush=True) print(json.dumps({'frozen_integrity':rep['all_passed']}),flush=True) if __name__=='__main__': parser=argparse.ArgumentParser();parser.add_argument('task',choices=['audit','seal']);parser.add_argument('--config',required=True) args=parser.parse_args();cfg=yaml.safe_load(Path(args.config).read_text());exp=Path(cfg['experiment_root']);model=Path(cfg['model_root']) if args.task=='audit':run_audit(cfg,exp,model) else:seal(cfg,exp,model)