"""Read-only event diagnosis of sealed Phase 4S. No simulation or selection.""" from pathlib import Path import json import hashlib import time import numpy as np import pandas as pd BASE = Path('/data/digital-drosophila') SOURCE = BASE/'experiments/phase4s-timing-credit' OUT = BASE/'experiments/phase4t-timing-stability/audit' MODEL = BASE/'models/male-cns-lif/timing-stability-v1' def sha(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 save(p,x): p.write_text(json.dumps(x,indent=2,sort_keys=True,allow_nan=False)+'\n') def verify(root): records=[] for line in (root/'checksums.sha256').read_text().splitlines(): expected,rel=line.split(' ',1) p=root/rel assert p.is_file() and sha(p)==expected, str(p) records.append({'path':rel,'sha256':expected}) return records def main(): OUT.mkdir(parents=True,exist_ok=True) assert not (OUT/'summary.json').exists(), 'Refusing overwrite' start=time.perf_counter() before=verify(SOURCE) prereg=json.loads((SOURCE/'protocol/preregistered.json').read_text()) chosen=json.loads((SOURCE/'calibration/selection.json').read_text())['chosen'] comp=prereg['config']['target_compartment'] identity=pd.read_parquet(SOURCE/'protocol/frozen-edge-identity.parquet') assert len(identity)==14232 target=identity[identity.compartment.eq(comp)] source=target.groupby('source_neuron_idx').agg(body_id=('source_body_id','first'),edge_count=('edge_id','size')) ns=source.index.to_numpy();multiplicity=source.edge_count.to_numpy(np.float32) dt=.1;steps=1200;us=round(chosen['US_ms']/dt);w=chosen['window_ms'];tau=chosen['tau_ms'] rows=[];hist=[];timelines=[];gates=[];repro=[] for p in sorted((SOURCE/'events').glob('*.parquet')): split,label,seed=p.stem.split('-');seed=int(seed) ev=pd.read_parquet(p) assert set(ev.columns)=={'step','neuron_idx'} assert ev.step.between(0,steps-1).all() and not ev.duplicated().any() t=ev[ev.neuron_idx.isin(ns)].sort_values(['step','neuron_idx']) ts=t.step.to_numpy(np.int32);nodes=t.neuron_idx.to_numpy(np.int32) last=np.full((steps,len(ns)),-1,np.int32) lookup={int(n):j for j,n in enumerate(ns)} for s,n in zip(ts,nodes):last[s,lookup[int(n)]]=s np.maximum.accumulate(last,axis=0,out=last) ages=(np.arange(steps)[:,None]-last).astype(np.float32)*np.float32(dt) valid=(last>=0)&(ages<=np.float32(w)) e=np.zeros_like(ages);e[valid]=np.exp(-ages[valid]/np.float32(tau)) curve=np.sum(e*multiplicity[None,:],axis=1,dtype=np.float64) # Independent single-gate oracle, one KC at a time, no prefix array. oracle=0. for n,m in zip(ns,multiplicity): past=ts[(nodes==n)&(ts<=us)] if len(past): age=np.float32(us-int(past.max()))*np.float32(dt) if age<=np.float32(w):oracle+=float(np.float32(np.exp(-age/np.float32(tau)))*m) assert oracle==float(curve[us]) oldpath=SOURCE/('heldout/results.parquet' if split=='held' else 'calibration/grid.parquet') old=pd.read_parquet(oldpath) old=old[(old.label==label)&(old.seed==seed)&(old.window_ms==w)&(old.US_ms==chosen['US_ms'])].iloc[0] assert float(curve[us])==float(old.matched) assert float(curve.mean())==float(old.random_expectation) recent=ts[(ts>=us-round(w/dt))&(ts<=us)] past=ts[ts<=us];future=ts[ts>us] if len(recent):reason='credit_present' elif not len(ev):reason='no_KC_events' elif not len(t):reason='KC_active_but_no_target_compartment_source_events' elif not len(past):reason='target_onset_after_US' elif not len(future):reason='target_activity_ended_before_gate' else:reason='target_activity_gap_at_gate' covered=source.loc[np.unique(nodes)].edge_count.sum() if len(nodes) else 0 gate_nodes=np.unique(nodes[(ts>=us-round(w/dt))&(ts<=us)]) buckets=np.bincount(ts//50,minlength=24) row={'split':split,'label':label,'seed':seed,'source_file':str(p),'events_sha256':sha(p), 'KC_events':len(ev),'KC_active':int(ev.neuron_idx.nunique()),'target_events':len(t), 'target_active_KC':int(t.neuron_idx.nunique()),'target_source_KC_total':len(source), 'target_edge_total':len(target),'edges_covered_any_time':int(covered), 'edge_coverage_fraction':float(covered/len(target)),'gate_active_KC':len(gate_nodes), 'gate_covered_edges':int(source.loc[gate_nodes].edge_count.sum()),'gate_events':len(recent), 'first_target_ms':float(ts.min()*dt) if len(ts) else None, 'last_target_ms':float(ts.max()*dt) if len(ts) else None, 'peak_bin_start_ms':int(buckets.argmax())*5 if len(ts) else None, 'last_before_US_ms':float(past.max()*dt) if len(past) else None, 'first_after_US_ms':float(future.min()*dt) if len(future) else None, 'matched_credit':float(curve[us]),'random_expectation':float(curve.mean()), 'reason':reason} rows.append(row);repro.append({'trial':p.stem,'matched_exact':True,'random_exact':True,'independent_oracle_exact':True}) for begin in range(0,steps,10): selected=(ts>=begin)&(ts0 and zero.reason.isin(['target_onset_after_US','target_activity_ended_before_gate','target_activity_gap_at_gate']).all()), 'reproduction':repro,'new_simulations':0,'weight_updates':0,'new_candidate':False, 'time_semantics':'Original gate uses emission times, including same-step KC. Equal +18 steps on KC and US preserves relative ages; no asymmetric arrival correction.', 'limits':['Descriptive reuse of known seeds, not independent validation.', 'Gate absence is diagnosed, upstream cause of onset variability is not established.', 'No new window or US selection on old heldout outcomes.'], 'implementation_note':'Initial report serialization stopped on pandas NaN for absent pre-US events; serialize original null-bearing records instead. No scientific result changed.', 'wall_seconds':time.perf_counter()-start} after=verify(SOURCE);assert before==after save(OUT/'source-integrity.json',{'pass':True,'checked':len(after),'records':after}) save(OUT/'summary.json',summary) lines=['# Phase 4T / P0 — 已封存事件时序诊断','', f'共审计 {len(rows)} 条 KC 事件序列,零 credit {len(zero)} 条。没有新脑模拟、权重更新或候选选择。','', '| split | odor | seed | KC active | target active | first target ms | peak bin ms | reason |', '|---|---|---:|---:|---:|---:|---:|---|'] for x in rows:lines.append(f"| {x['split']} | {x['label']} | {x['seed']} | {x['KC_active']} | {x['target_active_KC']} | {x['first_target_ms']} | {x['peak_bin_start_ms']} | {x['reason']} |") lines+=['','所有 matched/random credit 均精确复现 Phase 4S,并通过独立逐 KC 单点 oracle。', '描述性曲线不能用于把旧 heldout 当成新的验证集。下一实验必须先冻结规则,再使用全新种子。', '仅定位 gate 落空原因;不能把时序相关性推断为 APL 或某个回路的因果作用。'] (OUT/'REPORT.md').write_text('\n'.join(lines)+'\n',encoding='utf-8') print(json.dumps({k:v for k,v in summary.items() if k!='reproduction'},indent=2),flush=True) if __name__=='__main__':main()