94 lines
6.6 KiB
Python
94 lines
6.6 KiB
Python
"""Descriptive audit only; no parameter ranking, fitting or candidate selection."""
|
|
from pathlib import Path
|
|
import json
|
|
import numpy as np
|
|
import pandas as pd
|
|
import yaml
|
|
from phase4r import save
|
|
|
|
cfg=yaml.safe_load(Path(__file__).with_name('phase4v.yaml').read_text())
|
|
exp=Path(cfg['experiment_root']); prior=Path(cfg['prior_root'])
|
|
assert not (exp/'checksums.sha256').exists(),'Already sealed'
|
|
target=set(json.loads((exp/'audit/semantics.json').read_text())['target_KC_ids'])
|
|
old=[]
|
|
for p in sorted((prior/'events').glob('calibration-*.parquet')):
|
|
x=pd.read_parquet(p); t=x.loc[x.neuron_idx.isin(target),'step'].to_numpy()*.1
|
|
old.append({'trial':p.stem,'events':len(t),'first_ms':float(t.min()) if len(t) else None,
|
|
'fraction_after25ms':float((t>25).mean()) if len(t) else None,
|
|
'fraction_after60ms':float((t>60).mean()) if len(t) else None,
|
|
'fraction_after90ms':float((t>90).mean()) if len(t) else None})
|
|
assert len(old)==120
|
|
pd.DataFrame(old).to_parquet(exp/'audit/prior120-event-distribution.parquet',index=False)
|
|
trials=pd.read_parquet(exp/'audit/trials.parquet'); details=[]; bins=[]
|
|
for _,r in trials.iterrows():
|
|
name=f'{r.odor_index}-{r.seed}-{r.condition}'
|
|
x=pd.read_parquet(exp/'events'/f'{name}.parquet')
|
|
obs=pd.read_parquet(exp/'observations'/f'{name}.parquet')
|
|
assert (obs.PN_requested_cumulative == obs.PN_accepted_cumulative+obs.PN_preexisting_cumulative+obs.PN_blocked_cumulative).all()
|
|
if r.condition=='offset':
|
|
assert obs.loc[obs.time_ms>=cfg['short_duration_ms'],'PN_requested_cumulative'].nunique()==1
|
|
t=x.step.to_numpy()*.1; tt=x.loc[x.neuron_idx.isin(target),'step'].to_numpy()*.1
|
|
for start in np.arange(0,float(obs.time_ms.max()),cfg['activity_bin_ms']):
|
|
end=start+cfg['activity_bin_ms']; mask=(t>=start)&(t<end)
|
|
bins.append({'odor_index':int(r.odor_index),'seed':int(r.seed),'condition':r.condition,'start_ms':float(start),
|
|
'KC_events':int(mask.sum()),'KC_active':int(x.loc[mask,'neuron_idx'].nunique()),
|
|
'target_events':int(((tt>=start)&(tt<end)).sum())})
|
|
with np.load(exp/'states'/f'{name}.npz') as state:
|
|
count=state['counts']; total=int(count.astype(np.int64).sum()); active=int(np.count_nonzero(count))
|
|
row=r.to_dict(); row.update({'whole_brain_spikes':total,'whole_brain_active':active,
|
|
'KC_events_120_200ms':int(((t>=120)&(t<200)).sum()),
|
|
'KC_events_after200ms':int((t>=200).sum()),'KC_events_after500ms':int((t>=500).sum()),
|
|
'PN_requests':int(obs.iloc[-1].PN_requested_cumulative),'PN_new_spikes':int(obs.iloc[-1].PN_accepted_cumulative),
|
|
'PN_refractory_blocked':int(obs.iloc[-1].PN_blocked_cumulative),
|
|
'late_KC_g_mean':float(obs[obs.time_ms>=max(obs.time_ms)-100].KC_g_mean.mean()),
|
|
'late_APL_inhibition_mean':float(obs[obs.time_ms>=max(obs.time_ms)-100].APL_inhibition_mean.mean())})
|
|
details.append(row)
|
|
pd.DataFrame(bins).to_parquet(exp/'audit/activity-10ms-bins.parquet',index=False)
|
|
df=pd.DataFrame(details); df.to_parquet(exp/'audit/detailed-trials.parquet',index=False)
|
|
rows=[]
|
|
for (odor,condition),g in df.groupby(['odor_name','condition'],sort=False):
|
|
row={'odor':odor,'condition':condition,'n':len(g)}
|
|
for k in ('KC_events','target_events','KC_events_after120ms','KC_events_after200ms','KC_events_after500ms','whole_brain_spikes','whole_brain_active','PN_requests','PN_new_spikes','PN_refractory_blocked','late_KC_g_mean','late_APL_inhibition_mean'):
|
|
row[k+'_mean']=float(g[k].mean())
|
|
rows.append(row)
|
|
save(exp/'audit/descriptive-summary.json',{'groups':rows,'analysis':'post-run descriptive summaries, not hypothesis selection or tuning'})
|
|
us=pd.read_parquet(exp/'audit/US-acceptance.parquet')
|
|
us['scheduled_arrival_slot_ms']=us.first_edge_arrival_ms
|
|
us['output_spike_arrival_ms']=us.first_edge_arrival_ms.where(us.actual_output_spike)
|
|
us.to_parquet(exp/'audit/US-arrival-semantics.parquet',index=False)
|
|
save(exp/'audit/field-semantics.json',{
|
|
'PN_request_accounting_partition_exact':True,
|
|
'offset_no_external_requests_after120ms':True,
|
|
'US_original_first_edge_arrival_ms':'scheduled emission step + frozen delay; not evidence of arrival if force rejected and no actual spike',
|
|
'US_output_spike_arrival_ms':'nominal edge delivery time only for actual output spikes; not dopamine release/concentration',
|
|
'samples_time_ms':'state after step; (step+1)*dt',
|
|
'events_time_ms':'runtime spike convention; step*dt',
|
|
'analysis_source_role':'descriptive audit added after simulation preregistration; cannot select parameters or candidate'})
|
|
late=[]
|
|
for _,r in trials[trials.condition=='offset'].iterrows():
|
|
e=pd.read_parquet(exp/'events'/f'{r.odor_index}-{r.seed}-offset.parquet')
|
|
late.append((int(r.odor_index),set(map(int,e.loc[e.step>=5000,'neuron_idx']))))
|
|
pairs={'within_odor':[],'between_odor':[]}
|
|
for i,(oi,a) in enumerate(late):
|
|
for oj,b in late[i+1:]:
|
|
pairs['within_odor' if oi==oj else 'between_odor'].append(len(a&b)/len(a|b) if a|b else 1.)
|
|
save(exp/'audit/offset-late-KC-overlap.json',{
|
|
'scope':'posthoc descriptive >=500ms KC activity; no memory/learning gate',
|
|
'trials':len(late),'late_active_KC_counts':[len(x[1]) for x in late],
|
|
'common_KC_ids':sorted(set.intersection(*(x[1] for x in late))),
|
|
'pairwise_Jaccard':{k:{'pairs':len(v),'mean':float(np.mean(v)),'min':float(min(v)),'max':float(max(v))} for k,v in pairs.items()}})
|
|
lines=['# Mechanism observations (descriptive, no training)','',
|
|
'| Odor | Condition | KC events | target events | KC events >=200ms | late mean KC g |',
|
|
'|---|---|---:|---:|---:|---:|']
|
|
for r in rows:
|
|
if r['condition'] in ('short','sustained','offset'):
|
|
lines.append(f"| {r['odor']} | {r['condition']} | {r['KC_events_mean']:.2f} | {r['target_events_mean']:.2f} | {r['KC_events_after200ms_mean']:.2f} | {r['late_KC_g_mean_mean']:.6f} |")
|
|
lines += ['', 'The longer trials are diagnostics of the unchanged brain at unchanged input rates. Differences are not evidence of learning.',
|
|
'Counter observations add synchronization overhead and cannot be used as runtime speed benchmarks.',
|
|
'No baseline-activity input is added. A silent warmup is not a physiological ongoing-state initialization.',
|
|
'Turning external PN forcing off does not silence PN neurons or recurrent network activity. Persistent activity is not itself retained associative memory.',
|
|
'A scheduled teaching gate is an engineering signal, not measured dopamine release or receptor kinetics.',
|
|
'No new US policy or seconds-to-milliseconds conversion is selected from these observations.']
|
|
(exp/'audit/MECHANISM.md').write_text('\n'.join(lines)+'\n',encoding='utf-8')
|
|
print(json.dumps({'old_trials':len(old),'diagnostic_trials':len(df),'groups':rows},indent=2))
|