"""Side-channel event/drive audit. Frozen run function and CUDA math unchanged.""" from pathlib import Path import argparse,json,time import numpy as np import pandas as pd import torch import yaml import phase4p,phase4p_finalize,phase4s,phase4v from phase4p_revalidate import DeterministicAPLSim,door_inputs,make_drive from phase4r import save,digest,arrhash from phase4h_audit import verify_checksum CATS=['PN_olfactory','KC','APL','MBON','DAN','other'] def checks(cfg): vc=yaml.safe_load((Path(cfg['prior_model'])/'phase4v.yaml').read_text()) out=phase4v.checks(vc) for key in ('prior_root','prior_model'): p=Path(cfg[key]);out['V_'+key]=verify_checksum(p,p/'checksums.sha256') assert all(x['passed'] for x in out.values());return out class Observer: def __init__(self,rt,watch): self.rt=rt;self.ext=rt.ext;self.fanout=rt.canonical_fanout self.watch=np.asarray(watch,np.int32);self.wg=torch.as_tensor(watch,dtype=torch.int64,device=rt.device) self.states=[];self.steps=[];self.nodes=[];self.forced=[] def __getattr__(self,name):return getattr(self.ext,name) def state_step(self,*a): w=self.wg pre=torch.stack((a[0][w],a[1][w],a[2][w].float(),a[3][w])) self.ext.state_step(*a) post=torch.stack((a[0][w],a[1][w],a[4][w].float())) self.states.append(torch.cat((pre,post)).cpu().numpy()) def force_step(self,*a): tg=a[4];req=a[5].bool() accepted=req & ~a[2][tg].bool() & (a[1][tg]==0) self.ext.force_step(*a) x=tg[accepted].cpu().numpy().astype(np.int32) if len(x):self.forced.extend((self.rt.step_index,int(i)) for i in x) def record_fanout(self,src,dst): x=src.cpu().numpy().astype(np.int32) self.nodes.append(x);self.steps.append(np.full(len(x),self.rt.step_index,np.int32)) return self.fanout(src,dst) def attach(self):self.rt.ext=self;self.rt.canonical_fanout=self.record_fanout def detach(self):self.rt.ext=self.ext;self.rt.canonical_fanout=self.fanout def events(self): return pd.DataFrame({'step':np.concatenate(self.steps),'neuron_idx':np.concatenate(self.nodes)}) def classify(meta): cls=meta.annotation_class.fillna('').astype(str);typ=meta.annotation_type.fillna('').astype(str) nt=meta.neurotransmitter.fillna('').astype(str).str.strip().str.lower() code=np.full(len(meta),5,np.int8) code[cls.isin(['ALPN','olfactory'])]=0;code[cls.eq('Kenyon_Cell')]=1 code[typ.eq('APL')]=2;code[cls.eq('MBON')]=3;code[nt.eq('dopamine')]=4 return code def replay(arrival,overlay,rt): # Explicit rounding matches CUDA __fadd_rn/__fmul_rn, not fused operations. f=np.float32;v=f(rt.rest);g=f(0);ref=0;out=[];vs=[];gs=[] for step,a in enumerate(arrival): g=f(f(g*rt.gd)+a);ref=max(ref-1,0) v=f(v+f(rt.alpha*f(g-f(v-rt.rest)))) if ref==0 else f(rt.resetv) fire=ref==0 and v>=rt.threshold if fire:v=f(rt.resetv);ref=rt.ref_steps;out.append(step) vs.append(v);gs.append(g) # Measured applied APL difference, fixed exogenous signal for isolated replay. g=f(np.float64(g)+overlay[step]) return np.asarray(out,np.int32),np.asarray(vs,np.float32),np.asarray(gs,np.float32),g def drive_audit(cfg,exp,name,sim,observer,result,edges,code): rt=sim.rt;st=np.asarray(observer.states);T=len(st);events=observer.events() assert np.array_equal(np.bincount(events.neuron_idx,minlength=sim.n),result['counts']) events.to_parquet(exp/'events'/f'{name}-whole-brain.parquet',index=False) pd.DataFrame(observer.forced,columns=['step','neuron_idx']).to_parquet(exp/'events'/f'{name}-accepted-input.parquet',index=False) np.savez_compressed(exp/'states'/f'{name}-watched.npz',state=st, fields=np.asarray(['pre_v','pre_g','pre_ref','arrival','post_v','post_g','natural_spike'])) # Only real events on actual incoming edges. Delay is exact, including inhibitory edges. arrivals=events.merge(edges,left_on='neuron_idx',right_on='source_neuron_idx',how='inner') arrivals=arrivals.rename(columns={'step':'emission_step'}).drop(columns='neuron_idx') arrivals['arrival_step']=arrivals.emission_step+rt.delay_steps arrivals=arrivals[arrivals.arrival_step=round(cfg['late_start_ms']/rt.dt) replayrows.append({'trial':name,'neuron_idx':node,'mode':mode,'spikes':len(rr[0]),'late_spikes':int(late.sum()), 'first_ms':float(rr[0][0]*rt.dt) if len(rr[0]) else None,'last_ms':float(rr[0][-1]*rt.dt) if len(rr[0]) else None, 'max_abs_V_difference':float(np.abs(rr[1].astype(float)-base[1]).max())}) np.savez_compressed(exp/'replay'/f'{name}-{node}-{mode}.npz',event_steps=rr[0],v=rr[1],g=rr[2]) for period,minstep in [('whole',0),('late',round(cfg['late_start_ms']/rt.dt))]: aa=a[a.arrival_step>=minstep] for cat,g in aa.groupby('source_category'): w=g.effective_weight.to_numpy(float) drives.append({'trial':name,'neuron_idx':node,'period':period,'source_category':cat, 'positive_arrival_sum':float(w[w>0].sum()),'negative_arrival_sum':float(w[w<0].sum()), 'edge_event_count':len(g),'unique_sources':int(g.source_neuron_idx.nunique())}) return checks_out,replayrows,drives,events def ancestry(cfg,sim,events,forced,csc,weights,code,name): rt=sim.rt;order,ptr=csc;by=events.sort_values(['neuron_idx','step'],kind='stable') times=by.step.to_numpy(np.int32);ep=np.r_[0,np.cumsum(np.bincount(by.neuron_idx,minlength=sim.n))] roots=set(forced);rows=[];summaries=[];gd=float(rt.gd) def node_info(node,prefix): m=sim.meta.iloc[node] return {prefix+'body_id':int(m.body_id),prefix+'type':None if pd.isna(m.annotation_type) else str(m.annotation_type), prefix+'class':None if pd.isna(m.annotation_class) else str(m.annotation_class),prefix+'category':CATS[code[node]]} for target in cfg['watch_neuron_idx']: tt=times[ep[target]:ep[target+1]] selected=[('first',int(tt[0]))] if len(tt) else [] late=tt[tt>=round(cfg['late_start_ms']/rt.dt)] if len(late):selected.append(('first_late',int(late[0]))) for kind,t in selected: node=target;path=[];status='hop_limit' for hop in range(cfg['ancestry']['max_hops']): if (t,node) in roots:status='accepted_input_root';break ee=order[ptr[node]:ptr[node+1]];ee=ee[weights[ee]>0] best=None for e in ee: src=int(sim.sources[e]);ts=times[ep[src]:ep[src+1]];j=np.searchsorted(ts,t-rt.delay_steps,side='right')-1 if j<0:continue emit=int(ts[j]);arrival=emit+rt.delay_steps;score=float(weights[e])*gd**(t-arrival) if best is None or score>best[0]:best=(score,int(e),src,emit,arrival) if best is None:status='unexplained_no_positive_event_predecessor';break score,e,src,emit,arrival=best row={'trial':name,'watched_neuron_idx':target,'endpoint':kind,'backward_hop':hop, 'source_neuron_idx':src,'target_neuron_idx':node,'source_step':emit,'arrival_step':arrival,'target_step':t, 'edge_id':e,'raw_synapse_count':int(rt.raw_np[e]),'effective_weight':float(weights[e]),'synaptic_sign':1, 'ranking_score':score,'arrival_age_ms':(t-arrival)*rt.dt,**node_info(src,'source_'),**node_info(node,'target_')} assert emit