Archive MaleCNS research sources, provenance, bibliography and immutable data inventory
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""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)&(ts<begin+10)
|
||||
hist.append({'split':split,'label':label,'seed':seed,'start_ms':begin*dt,
|
||||
'target_spikes':int(selected.sum()),'target_active_KC':int(np.unique(nodes[selected]).size)})
|
||||
for s,n in zip(ts,nodes):timelines.append({'split':split,'label':label,'seed':seed,
|
||||
'emission_step':int(s),'arrival_step':int(s)+18,'neuron_idx':int(n),
|
||||
'body_id':int(source.loc[n,'body_id']),'target_edge_multiplicity':int(source.loc[n,'edge_count'])})
|
||||
# Full time curve is descriptive only. It cannot select a new parameter on old heldout data.
|
||||
for s,c in enumerate(curve):gates.append({'split':split,'label':label,'seed':seed,'US_step':s,'credit':float(c)})
|
||||
frame=pd.DataFrame(rows)
|
||||
frame.to_parquet(OUT/'per-trial-diagnosis.parquet',index=False)
|
||||
pd.DataFrame(hist).to_parquet(OUT/'target-activity-1ms.parquet',index=False)
|
||||
pd.DataFrame(timelines).to_parquet(OUT/'target-KC-events.parquet',index=False)
|
||||
pd.DataFrame(gates).to_parquet(OUT/'descriptive-gate-curves.parquet',index=False)
|
||||
zero=frame[frame.matched_credit==0]
|
||||
summary={'stage':'P0 event diagnosis','phase':'4T','source_phase':'4S','trials':len(rows),
|
||||
'target_compartment':comp,'target_source_KC_total':len(source),'target_edge_total':len(target),
|
||||
'zero_credit_trials':[x for x in rows if x['matched_credit']==0],
|
||||
'reason_counts':frame.groupby(['split','label','reason']).size().reset_index(name='count').to_dict('records'),
|
||||
'all_zero_trials_explained_by_event_timing':bool(len(zero)>0 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()
|
||||
Reference in New Issue
Block a user