Files
digital-drosophila/models/male-cns-lif/transmitter-audit-v1/phase4x.py
T

156 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Raw-to-runtime provenance audit and non-mutating audit-role repair."""
from pathlib import Path
import argparse,json,time
import numpy as np
import pandas as pd
import pyarrow as pa
import pyarrow.compute as pc
import pyarrow.feather as ft
import pyarrow.parquet as pq
import torch,yaml
import phase4p,phase4p_finalize,phase4s,phase4w
from phase4p_revalidate import DeterministicAPLSim
from phase4h_audit import verify_checksum
from phase4r import save,digest
from transmitter_roles_v1 import roles,self_test
def checks(cfg):
old=yaml.safe_load((Path(cfg['prior_model'])/'phase4w.yaml').read_text());out=phase4w.checks(old)
for k in ('prior_root','prior_model','prior_addendum'):
root=Path(cfg[k]);out[k]=verify_checksum(root,root/'checksums.sha256')
assert all(x['passed'] for x in out.values());return out
def column_equal(a,b):
if pa.types.is_dictionary(a.type):a=a.cast(a.type.value_type)
if pa.types.is_dictionary(b.type):b=b.cast(b.type.value_type)
return a.combine_chunks().equals(b.combine_chunks())
def run(cfg,exp,model):
for d in ('protocol','audit','roles','replay','regression','performance'):(exp/d).mkdir(parents=True,exist_ok=True)
assert not (exp/'protocol/preregistered.json').exists(),'Refusing overwrite'
save(exp/'audit/preflight.json',checks(cfg))
root=Path(cfg['dataset_root']);files={'annotation':root/'raw/annotations/body-annotations-male-cns-v1.0-minconf-0.5.feather',
'NT':root/'raw/annotations/body-neurotransmitters-male-cns-v1.0.feather',
'metadata':root/'processed/neuron-only/neuron-index/unified-metadata.parquet'}
save(exp/'protocol/preregistered.json',{'config':cfg,'sources':{p.name:digest(p) for p in model.iterdir() if p.is_file()},
'inputs':{k:{'path':str(p),'sha256':digest(p)} for k,p in files.items()},'before_analysis':True,'no_parameter_selection':True})
tabs={k:(pq.read_table(p) if p.suffix=='.parquet' else ft.read_table(p)) for k,p in files.items()}
save(exp/'audit/schema.json',{k:str(v.schema) for k,v in tabs.items()})
md=tabs['metadata'];assert md.num_rows==166700 and md['body_id'].type==pa.uint64() and md['neuron_idx'].type==pa.int32()
assert np.array_equal(md['neuron_idx'].to_numpy(),np.arange(166700,dtype=np.int32))
fields=[]
for key,idfield,prefix in [('annotation','bodyId','annotation_'),('NT','body','nt_')]:
table=tabs[key];assert table[idfield].null_count==0 and pc.count_distinct(table[idfield]).as_py()==table.num_rows
loc=pc.index_in(md['body_id'],value_set=table[idfield].cast(pa.uint64()));aligned=table.take(loc)
for col in table.column_names:
exact=column_equal(aligned[col],md[prefix+col]);fields.append({'table':key,'field':col,'exact':exact})
if key=='annotation':assert loc.null_count==0 and aligned['superclass'].null_count==0
save(exp/'audit/raw-column-alignment.json',fields);assert all(x['exact'] for x in fields),'Data join mismatch'
assert column_equal(md['neurotransmitter'],md['nt_consensus_nt'])
meta=md.to_pandas();mapped=pd.read_parquet(cfg['MB_mapping']).source_neuron_idx.unique();role=roles(meta,mapped)
role.to_parquet(exp/'roles/neuron-evidence-axes.parquet',index=False)
save(exp/'regression/classifier-tests.json',self_test())
meta[meta.annotation_type.eq('DPM')].to_parquet(exp/'audit/DPM-complete-metadata.parquet',index=False)
evidence=meta[['neuron_idx','body_id','annotation_class','annotation_type','neurotransmitter']+[c for c in meta if c.startswith('nt_')]].copy()
evidence['annotation_DAN']=role.annotation_DAN;evidence['metadata_dopamine']=role.metadata_dopamine
evidence[evidence.annotation_DAN!=evidence.metadata_dopamine].to_parquet(exp/'audit/DAN-NT-disagreements.parquet',index=False)
gt=role.has_raw_ground_truth.to_numpy();same=meta.nt_ground_truth.eq(meta.neurotransmitter)
conf=meta.nt_predicted_nt_confidence
summary={'neurons':len(meta),'raw_column_checks':len(fields),'raw_to_metadata_exact':True,'consensus_to_runtime_label_exact':True,
'ground_truth_present':int(gt.sum()),'ground_truth_absent':int((~gt).sum()),'ground_truth_vs_consensus_mismatch':int((gt & ~same).sum()),
'ground_truth_agreement_not_independent_accuracy':True,'confidence_null':int(conf.isna().sum()),
'confidence_outside_0_1':int(((conf<0)|(conf>1)).sum()),'confidence_probability_calibration':'not established; preserve raw values',
'metadata_dopamine':int(role.metadata_dopamine.sum()),'annotation_DAN':int(role.annotation_DAN.sum()),
'metadata_dopamine_not_annotation_DAN':int((role.metadata_dopamine & ~role.annotation_DAN).sum()),
'annotation_DAN_not_metadata_dopamine':int((role.annotation_DAN & ~role.metadata_dopamine).sum()),
'DPM_count':int(role.annotation_DPM.sum()),'identity_conflicts':int(role.identity_conflict.sum()),
'DPM_with_raw_ground_truth':int((role.annotation_DPM & role.has_raw_ground_truth).sum()),
'role_groups':role.role_group.value_counts().to_dict(),'compartment_mapping_unchanged':True}
save(exp/'audit/metadata-summary.json',summary)
pcfg=yaml.safe_load((Path(cfg['runtime_root'])/'phase4p.yaml').read_text())
policy=pcfg['sign_policy'];sign=[]
for value in meta.neurotransmitter:
key='' if pd.isna(value) else str(value).strip().lower()
sign.append(policy['unknown_sign'] if not key else policy['unclear_sign'] if key=='unclear' else policy['neurotransmitter_sign'][key])
sign=np.asarray(sign,np.int8);saved=np.load(cfg['saved_sign']);assert np.array_equal(sign,saved)
np.save(exp/'audit/reconstructed-sign.int8.npy',sign)
rt=phase4p.CanonicalRuntime(pcfg,Path(cfg['runtime_root']),exp);sim=DeterministicAPLSim(rt)
expected=rt.raw_np.astype(np.float32)*sign[sim.sources]*float(pcfg['model']['w_syn_mv_per_anatomical_synapse'])
actual=sim.base.cpu().numpy();assert np.array_equal(expected,actual)
save(exp/'regression/sign-weight-chain.json',{'neurons':len(sign),'edges':len(expected),'saved_sign_exact':True,
'runtime_effective_weights_exact':True,'base_weight_changes':0,'sign_changes':0,'raw_synapse_sum':int(rt.raw_np.sum()),
'sign_policy':policy,'fast_current_sign_is_model_assumption_not_receptor_measurement':True})
wc=yaml.safe_load((Path(cfg['prior_model'])/'phase4w.yaml').read_text());watch=wc['watch_neuron_idx'];rows=[];reg=[];groupdrive=[]
start=time.perf_counter();oldroot=Path(cfg['prior_root'])
for oi in wc['odor_indices']:
name=f'{oi}-{wc["seed"]}-offset';arr=pd.read_parquet(oldroot/'drive'/f'{name}-actual-edge-arrivals.parquet')
src=arr.source_neuron_idx.to_numpy(np.int32)
arr['role_group']=role.role_group.to_numpy()[src]
arr['metadata_dopamine']=role.metadata_dopamine.to_numpy()[src]
arr['annotation_DAN']=role.annotation_DAN.to_numpy()[src];arr['annotation_DPM']=role.annotation_DPM.to_numpy()[src]
arr.to_parquet(exp/'roles'/f'{name}-reclassified-arrivals.parquet',index=False)
for (target,group),g in arr[arr.arrival_step>=5000].groupby(['target_neuron_idx','role_group']):
w=g.effective_weight.to_numpy(float);groupdrive.append({'trial':name,'neuron_idx':int(target),'role_group':group,
'positive_sum':float(w[w>0].sum()),'negative_sum':float(w[w<0].sum()),'edge_events':len(g)})
with np.load(oldroot/'states'/f'{name}-watched.npz') as z:st=z['state']
for wi,node in enumerate(watch):
a=arr[arr.target_neuron_idx==node];by={int(t):g for t,g in a.groupby('arrival_step',sort=False)}
with np.load(oldroot/'replay'/f'{name}-{node}-base.npz') as b:
# Last-step overlay has no effect on recorded post-state V/g/events.
base={k:b[k].copy() for k in b.files}
overlay=np.r_[st[1:,1,wi].astype(float)-st[:-1,5,wi].astype(float),0.]
for mode in cfg['replay_modes']:
ar=np.zeros(len(st),np.float32)
for t,g in by.items():
omit=np.zeros(len(g),bool)
if mode=='omit_DPM_annotation':omit=g.annotation_DPM.to_numpy()
elif mode=='omit_DAN_annotation':omit=g.annotation_DAN.to_numpy()
elif mode=='omit_dopamine_without_DAN_or_DPM':omit=(g.metadata_dopamine & ~g.annotation_DAN & ~g.annotation_DPM).to_numpy()
elif mode=='omit_legacy_metadata_dopamine':omit=g.metadata_dopamine.to_numpy()
w=g.loc[~omit,'effective_weight'].to_numpy(np.float32)
if len(w):ar[t]=np.cumsum(w,dtype=np.float32)[-1]
rr=phase4w.replay(ar,overlay,rt)
if mode in ('base','omit_legacy_metadata_dopamine'):
oldmode='base' if mode=='base' else 'omit_DAN'
with np.load(oldroot/'replay'/f'{name}-{node}-{oldmode}.npz') as old:
eq={k:bool(np.array_equal(old[k],v)) for k,v in zip(('event_steps','v','g'),rr[:3])}
assert all(eq.values());reg.append({'trial':name,'neuron_idx':node,'mode':mode,**eq})
rows.append({'trial':name,'neuron_idx':node,'mode':mode,'spikes':len(rr[0]),'late_spikes':int((rr[0]>=5000).sum())})
np.savez_compressed(exp/'replay'/f'{name}-{node}-{mode}.npz',event_steps=rr[0],v=rr[1],g=rr[2])
print(json.dumps({'odor_complete':oi,'CPU_replays':len(rows)}),flush=True)
pd.DataFrame(rows).to_parquet(exp/'replay/results.parquet',index=False)
pd.DataFrame(groupdrive).to_parquet(exp/'roles/reclassified-drive.parquet',index=False)
save(exp/'regression/replay-compatibility.json',reg)
assert phase4s.evidence_regression(cfg,exp,rt,sim)
save(exp/'audit/results.json',{'phase':'4X','status':'AUDIT-CLASSIFICATION-REPAIR-PASS','metadata':summary,
'raw_import_bug_found':False,'audit_role_conflation_fixed':True,'replays':len(rows),'compatibility_checks':len(reg),
'reference_pass':True,'sign_weight_chain_exact':True,'frozen_brain_modified':False,'learning_candidate':False,
'biology_identity_issue_resolved':False,'limit':'conditional replay only; metadata DAN is annotation identity not ground-truth certification'})
save(exp/'performance/summary.json',{'CPU_replay_and_reference_seconds':time.perf_counter()-start,'GPU':torch.cuda.get_device_name(0)})
def seal(cfg,exp,model):
pre=json.loads((exp/'protocol/preregistered.json').read_text());now=checks(cfg);hist=phase4p_finalize.integrity()
ok=now==json.loads((exp/'audit/preflight.json').read_text()) and hist['all_static_checks_passed']
ok &= all(digest(model/n)==h for n,h in pre['sources'].items()) and all(digest(Path(v['path']))==v['sha256'] for v in pre['inputs'].values())
save(exp/'audit/frozen-integrity.json',{'pass':ok,'history':hist,'current':now});assert ok
r=json.loads((exp/'audit/results.json').read_text());r['integrity_pass']=True;save(exp/'audit/results.json',r)
df=pd.read_parquet(exp/'replay/results.parquet');lines=['# Phase 4X — transmitter provenance and audit-role repair','',
'Status: AUDIT-CLASSIFICATION-REPAIR-PASS. No frozen brain/sign/raw changes; no learning candidate.','',
'All original annotation and neurotransmitter columns match raw by body_id. Runtime labels equal raw consensus_nt; reconstructed sign and effective weights match frozen runtime.',
'DPM has raw predicted/consensus dopamine with no ground_truth. Its confidence is a supplied field, not established calibration probability.',
'Repair: separate transmitter metadata, exact DAN annotation class, exact DPM annotation type and frozen compartment-mapping evidence. Do not infer reward valence or DAN identity from transmitter/name alone.','',
'| KC neuron_idx | conditional replay | late spikes range (6 known odors) |','|---:|---|---:|']
for (node,mode),g in df.groupby(['neuron_idx','mode']):lines.append(f'| {node} | {mode} | {g.late_spikes.min()}–{g.late_spikes.max()} |')
lines+=['','Six old odors, one fixed seed per odor; no new blind generalization claim. Other recorded arrivals and APL remain fixed in isolated replay.',
'The last APL subtraction is not applied in CPU comparisons because stored V/g are post-state, pre-APL; it cannot affect any recorded event/state and no final post-APL g comparison is claimed.',
'Consensus/ground_truth agreement is a provenance check, not independent prediction accuracy. No confidence threshold, label correction, sign change or gain change is inferred.']
(exp/'REPORT.md').write_text('\n'.join(lines)+'\n',encoding='utf-8')
for root in (model,exp):
rows=[{'path':p.relative_to(root).as_posix(),'sha256':digest(p),'bytes':p.stat().st_size} for p in sorted(root.rglob('*')) if p.is_file() and p.name not in ('manifest.json','checksums.sha256')]
save(root/'manifest.json',{'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));q=verify_checksum(root,root/'checksums.sha256');assert q['passed'];print(json.dumps(q),flush=True)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('task',choices=['run','seal']);p.add_argument('--config',required=True);a=p.parse_args()
cfg=yaml.safe_load(Path(a.config).read_text());(run if a.task=='run' else seal)(cfg,Path(cfg['experiment_root']),Path(cfg['model_root']))