"""Read-only CS/US semantics diagnostics; no learning or parameter selection.""" from pathlib import Path import argparse import json import math import time import numpy as np import pandas as pd import torch import yaml import phase4p import phase4p_finalize import phase4s import phase4u from phase4p_revalidate import DeterministicAPLSim, door_inputs, make_drive from phase4r import save, digest, arrhash from phase4h_audit import verify_checksum def checks(cfg): old = yaml.safe_load((Path(cfg['prior_model'])/'phase4u.yaml').read_text()) out = phase4u.checks(old) for key in ('prior_root', 'prior_model'): root = Path(cfg[key]); out['U_'+key] = verify_checksum(root, root/'checksums.sha256') assert all(x['passed'] for x in out.values()) return out def forcing(sim, targets, rates, seed, cfg, mode): block = round(cfg['forcing_block_ms']/sim.dt) steps = round((cfg['observation_duration_ms'] if mode in ('sustained','offset') else cfg['short_duration_ms'])/sim.dt) probs = torch.as_tensor(np.asarray(rates, np.float32)*sim.dt/1000., device=sim.device) parts = [] for b in range(math.ceil(steps/block)): gen = torch.Generator(device=sim.device); gen.manual_seed(int(seed+b*cfg['forcing_block_seed_stride'])) parts.append((torch.rand((block,len(targets)),generator=gen,device=sim.device)0).sum()),'KC_g_mean':float(rt.g[sim.kc_gpu].mean()), 'KC_V_mean':float(rt.v[sim.kc_gpu].mean()),'KC_threshold_margin_min':float((rt.threshold-rt.v[sim.kc_gpu]).min()), 'APL_state_0':float(apl[0]),'APL_state_1':float(apl[1]),'APL_inhibition_mean':float(inhibition.mean()), 'finite':bool(torch.isfinite(rt.v).all() and torch.isfinite(rt.g).all())}) torch.cuda.synchronize() counts=rt.counts.cpu().numpy().copy() return {'counts':counts,'kc_counts':counts[sim.kc], 'first':first.cpu().numpy().copy(), 'final_v':rt.v.cpu().numpy().copy(),'final_g':rt.g.cpu().numpy().copy(), 'trace':np.zeros(len(sim.kc),np.float32) if not trace_at else trace_at[-1].cpu().numpy().copy(), 'kc_event_step':np.concatenate(evs) if evs else np.empty(0,np.int32), 'kc_event_node':np.concatenate(evn) if evn else np.empty(0,np.int32), 'prefix':prefix,'samples':samples,'US':us,'wall_seconds':time.perf_counter()-start, 'finite':all(x['finite'] for x in samples)} def exact(a,b,keys): return {k:bool(np.array_equal(a[k],b[k])) for k in keys} def run(cfg,exp,model): for name in ('protocol','audit','events','states','observations','regression','performance'): (exp/name).mkdir(parents=True,exist_ok=True) assert not (exp/'protocol/preregistered.json').exists(),'Refusing overwrite' save(exp/'audit/preflight.json',checks(cfg)) oc,panel,vectors,selection,mapping=door_inputs(); panel=panel[panel.split=='calibration'].reset_index(drop=True) assert len(panel)==6 save(exp/'protocol/preregistered.json',{'config':cfg,'panel':panel.to_dict('records'), 'sources':{p.name:digest(p) for p in model.iterdir() if p.is_file()},'before_simulation':True,'no_parameter_selection':True}) pc=yaml.safe_load((Path(cfg['runtime_root'])/'phase4p.yaml').read_text()) rt=phase4p.CanonicalRuntime(pc,Path(cfg['runtime_root']),exp); sim=DeterministicAPLSim(rt) assert len(sim.identity)==14232 target=np.unique(sim.identity.loc[sim.identity.compartment.eq(cfg['target_compartment']),'source_neuron_idx'].to_numpy(np.int32)) dan=sim.dan_targets(cfg['matched_DAN'],cfg['target_compartment']) save(exp/'audit/semantics.json',{'CS':'frozen physiological DoOR-to-PN rates; Bernoulli force request per 0.1ms step; refractory may refuse', 'US':'one scheduled force request per confirmed PAM15 neuron; engineering teaching trace sampled at scheduled emission step regardless acceptance', 'dopamine_concentration_measured':False,'initial_state':'all V at rest; g/ref/queue/APL/trace zero', 'delay_steps':rt.delay_steps,'delay_ms':rt.delay_steps*sim.dt,'DAN_ids':dan.tolist(),'target_KC_ids':target.tolist(), 'long_input':'120ms RNG blocks with explicit seed stride; first block identical, later sequence not claimed to equal legacy long run', 'time_scaling_to_biological_seconds':'unknown; 1000ms is observation, not conditioning replication'}) keys=['counts','first','final_v','final_g','trace','kc_event_step','kc_event_node']; regress=[]; summaries=[]; usrows=[] start=time.perf_counter(); torch.cuda.reset_peak_memory_stats() def persist(name,r,oi,seed,mode): assert r['finite'],'Nonfinite state' phase4s.persist_run(exp,name,r) np.savez_compressed(exp/'states'/f'{name}.npz',**{k:r[k] for k in ('counts','final_v','final_g','trace')}) pd.DataFrame(r['samples']).to_parquet(exp/'observations'/f'{name}.parquet',index=False) steps=r['kc_event_step']; nodes=r['kc_event_node']; t=steps[np.isin(nodes,target)]*sim.dt row={'odor_index':oi,'odor_name':panel.iloc[oi].odor_name,'seed':seed,'condition':mode, 'KC_active':int(np.count_nonzero(r['kc_counts'])),'KC_events':len(steps),'target_events':len(t), 'first_target_ms':float(t.min()) if len(t) else None,'median_target_ms':float(np.median(t)) if len(t) else None, 'target_events_after120ms':int(np.count_nonzero(t>=120)), 'KC_events_after120ms':int(np.count_nonzero(steps*sim.dt>=120)), 'last_KC_event_ms':float(steps.max()*sim.dt) if len(steps) else None,'wall_seconds':r['wall_seconds'],'finite':r['finite']} summaries.append(row) for oi,odor in panel.iterrows(): targets,rates=make_drive(oc,panel,vectors,selection,mapping,odor.odor_id,'physiological') for si,seed in enumerate(cfg['seeds']): actual=cfg['seed_namespace']+oi*cfg['odor_seed_offset']+seed legacy=sim.run(targets,rates,actual,cfg['short_duration_ms'],collect_events=True) f=forcing(sim,targets,rates,actual,cfg,'short') short=observed(sim,targets,f,target,cfg) eq=exact(legacy,short,keys); regress.append({'odor':oi,'seed':seed,'test':'short-exact','fields':eq}) save(exp/'regression/instrumentation.json',regress); assert all(eq.values()),'Instrumentation diverges' persist(f'{oi}-{seed}-short',short,oi,seed,'short') for mode in ('sustained','offset'): ff=forcing(sim,targets,rates,actual,cfg,mode); assert torch.equal(ff[:len(f)],f) r=observed(sim,targets,ff,target,cfg) eq=exact(legacy,r['prefix'],['counts','final_v','final_g']) mask=r['kc_event_step']=120ms |', '|---|---|---:|---:|---:|'] for (odor,mode),g in df.groupby(['odor_name','condition'],sort=False): lines.append(f"| {odor} | {mode} | {g.KC_events.mean():.2f} | {g.target_events.mean():.2f} | {g.target_events_after120ms.mean():.2f} |") lines += ['',f"DAN requests: {r['DAN_requests']}; accepted new spikes: {r['DAN_accepted_new']}; preexisting: {r['DAN_preexisting']}; refused: {r['DAN_refused']}.", 'US is a force request, not measured dopamine. Teaching trace is sampled at the scheduled emission step; synaptic arrivals are 1.8ms later.', 'Observation does not establish biological time scaling or associative memory. Literature boundaries are in phase4v-literature.md.', 'No game, decoder, RL, visual input, gain/threshold changes, eligibility changes, or plastic-mask changes.'] (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)) check=verify_checksum(root,root/'checksums.sha256'); assert check['passed']; print(json.dumps(check),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']))