Archive MaleCNS research sources, provenance, bibliography and immutable data inventory

This commit is contained in:
Digital Drosophila Archive
2026-09-29 03:38:32 +08:00
commit 3981628ff8
682 changed files with 161475 additions and 0 deletions
@@ -0,0 +1,5 @@
65004cfd7a8cc638a8c2176962a6deee344df530d16b9899f80272fbe5830288 phase4y-hypotheses.md
636139eaeafa981a0c54c59a7c69e2b52b8493aa798448fa86ae6d2c6baba319 phase4y.py
52f586ad0b4a12dbeb918f9453f9861a8dfc209118333eae5f46d27b7bce6011 phase4y.yaml
c800c4f30767e8492853b311fb36f06f020b0aee73abcfb14561300cc9a87952 phase4y_describe.py
53f8088d7572d5b63bc1ab623341bbab33dfdeeaf06421b775b2eaea1053ce0e manifest.json
@@ -0,0 +1,24 @@
{
"files": [
{
"bytes": 1899,
"path": "phase4y-hypotheses.md",
"sha256": "65004cfd7a8cc638a8c2176962a6deee344df530d16b9899f80272fbe5830288"
},
{
"bytes": 13859,
"path": "phase4y.py",
"sha256": "636139eaeafa981a0c54c59a7c69e2b52b8493aa798448fa86ae6d2c6baba319"
},
{
"bytes": 2107,
"path": "phase4y.yaml",
"sha256": "52f586ad0b4a12dbeb918f9453f9861a8dfc209118333eae5f46d27b7bce6011"
},
{
"bytes": 9823,
"path": "phase4y_describe.py",
"sha256": "c800c4f30767e8492853b311fb36f06f020b0aee73abcfb14561300cc9a87952"
}
]
}
@@ -0,0 +1,13 @@
# Independent DPM hypothesis branch
The user delegated selection after explicitly being asked about an independent physiological-hypothesis branch. The frozen reference remains read-only. No branch is automatically promoted.
Three preregistered conditions multiply only actual DPM outgoing effective currents by +1, 0 or -1. +1 is the frozen mathematical reference. 0 tests removal of the fast-current pathway, not removal of DPM neurons or all DPM functions. -1 is a sign-sensitivity experiment, not proof of GABA/serotonin receptor action on every target. No slow-receptor time constants, co-transmission model or dopamine concentration is invented.
The raw DPM bodies 11734 and 12569 have dopamine predictions and empty ground_truth, as verified in Phase4X. Prediction labels and the original sign array remain intact.
[Haynes et al., eLife 2015](https://pmc.ncbi.nlm.nih.gov/articles/PMC4305081/), doi:10.7554/eLife.03868, reports DPM GABA/5-HT signaling and inhibition in its tested preparation. It motivates the sign-sensitivity hypothesis but does not validate a global sign flip on these MaleCNS bodies. Full text was read during Phase4W; re-access during this phase returned a CAPTCHA. Prior literature and prediction caveats remain in the sealed Phase4W/4X notes.
The comparison asks whether an input dependency seen in isolated replay survives endogenous whole-brain feedback. It does not ask which condition looks most brain-like, has more activity or trains best. All results and failures are retained. There is no learning, candidate gate, reward sweep, extra stimulation or game interface.
Limitations: six previously studied odors, two fixed seeds (only one new), 1s observation, unchanged simplified LIF and frozen APL overlay. Formal biological transmitter identity, reward valence, receptor-specific physiology, association learning and long-term stability remain unproven.
@@ -0,0 +1,161 @@
"""Isolated in-memory hypothesis overlays; all frozen model/data files read-only."""
from pathlib import Path
import argparse,json,time
import numpy as np
import pandas as pd
import torch,yaml
import phase4p,phase4p_finalize,phase4s,phase4v,phase4x
from phase4p_revalidate import DeterministicAPLSim,door_inputs,make_drive
from phase4r import save,digest,arrhash
from phase4h_audit import verify_checksum
from transmitter_roles_v1 import roles
def checks(cfg):
xc=yaml.safe_load((Path(cfg['prior_model'])/'phase4x.yaml').read_text());out=phase4x.checks(xc)
for key in ('prior_root','prior_model'):
root=Path(cfg[key]);out['X_'+key]=verify_checksum(root,root/'checksums.sha256')
assert all(x['passed'] for x in out.values());return out
class CountObserver:
def __init__(self,rt,step):self.rt=rt;self.ext=rt.ext;self.step=step;self.before_late=None
def __getattr__(self,name):return getattr(self.ext,name)
def state_step(self,*args):
if self.rt.step_index==self.step:self.before_late=self.rt.counts.cpu().numpy().copy()
return self.ext.state_step(*args)
def run(cfg,exp,model):
for d in ('protocol','audit','events','states','observations','comparisons','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))
vc=yaml.safe_load((Path(cfg['input_model'])/'phase4v.yaml').read_text())
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,'input_config':vc,'panel':panel.to_dict('records'),
'sources':{p.name:digest(p) for p in model.iterdir() if p.is_file()},'before_simulation':True,
'no_candidate_selection':True,'new_seed_is_one_replication_not_generalization_gate':True})
pcfg=yaml.safe_load((Path(cfg['runtime_root'])/'phase4p.yaml').read_text());rt=phase4p.CanonicalRuntime(pcfg,Path(cfg['runtime_root']),exp);sim=DeterministicAPLSim(rt)
role=roles(sim.meta);dpm=np.flatnonzero(role.annotation_DPM).astype(np.int32)
assert sorted(sim.meta.iloc[dpm].body_id.astype(int).tolist())==cfg['perturbation']['expected_bodies']
ids=np.flatnonzero(np.isin(sim.sources,dpm)).astype(np.int64);eg=torch.as_tensor(ids,dtype=torch.int64,device=sim.device)
assert not set(ids)&set(sim.apl_out_ids)
sim.meta.iloc[dpm].to_parquet(exp/'audit/DPM-source-metadata.parquet',index=False)
pd.DataFrame({'edge_id':ids,'source_neuron_idx':sim.sources[ids],'target_neuron_idx':rt.indices_np[ids],
'raw_synapse_count':rt.raw_np[ids],'frozen_effective_weight':sim.base[eg].cpu().numpy()}).to_parquet(exp/'audit/perturbed-existing-edges.parquet',index=False)
original_install=sim.install;original_base_hash=arrhash(sim.base.cpu().numpy());weightchecks=[]
original_install(None);original_overlay=rt.weights.cpu().numpy().copy()
factor=1.
def install(E):
assert E is None,'Learning forbidden in diagnostic branch'
original_install(None)
rt.weights[eg]=sim.base[eg]*float(factor)
sim.install=install
for spec in cfg['conditions']:
factor=spec['factor'];sim.install(None);got=rt.weights.cpu().numpy()
expected=original_overlay.copy();expected[ids]=original_overlay[ids]*np.float32(factor)
assert np.array_equal(got,expected)
changed=np.flatnonzero(got!=original_overlay)
assert np.all(np.isin(changed,ids))
weightchecks.append({'condition':spec['name'],'factor':factor,'changed_effective_edges':len(changed),
'only_declared_DPM_edges':True,'edge_array_length':len(got),'raw_weight_changes':0})
save(exp/'regression/overlay-scope.json',weightchecks)
populations={'all':np.arange(sim.n),'KC':sim.kc,'PN_annotation':sim.pn,'MBON':sim.mbon,'DPM':dpm,
'DAN_annotation':np.flatnonzero(role.annotation_DAN),'MBON_downstream':sim.downstream}
for cls in ('descending','motor'):
populations[cls]=np.flatnonzero(sim.meta.annotation_superclass.eq(cls).to_numpy())
save(exp/'audit/populations.json',{k:{'count':len(v),'definition':('exact annotation_superclass' if k in ('descending','motor') else 'frozen IDs or exact annotation evidence')} for k,v in populations.items()})
target=np.unique(sim.identity.loc[sim.identity.compartment.eq(vc['target_compartment']),'source_neuron_idx'].to_numpy(np.int32))
reg=[];stats=[];watchrows=[];poprows=[];comparisons=[];inputs=[]
start=time.perf_counter();torch.cuda.reset_peak_memory_stats()
def trial(targets,forced,name):
observer=CountObserver(rt,round(cfg['late_start_ms']/sim.dt));rt.ext=observer
try:r=phase4v.observed(sim,targets,forced,target,vc)
finally:rt.ext=observer.ext
assert r['finite'],'Nonfinite state; stop branch analysis'
r['late_counts']=r['counts']-observer.before_late
assert np.all(r['late_counts']>=0)
return r
for oi in cfg['odor_indices']:
odor=panel.iloc[oi];targets,rates=make_drive(oc,panel,vectors,selection,mapping,odor.odor_id,'physiological')
for seed in cfg['seeds']:
actual=vc['seed_namespace']+oi*vc['odor_seed_offset']+seed
forced=phase4v.forcing(sim,targets,rates,actual,vc,'offset')
inputs.append({'odor':oi,'seed':seed,'actual_seed':actual,'forcing_hash':arrhash(forced.cpu().numpy()),'targets_rates_hash':arrhash(targets,rates)})
reference=None
for spec in cfg['conditions']:
factor=spec['factor'];condition=spec['name'];name=f'{oi}-{seed}-{condition}'
r=trial(targets,forced,name)
phase4s.persist_run(exp,name,r)
np.savez_compressed(exp/'states'/f'{name}.npz',**{k:r[k] for k in ('counts','late_counts','final_v','final_g','trace')})
pd.DataFrame(r['samples']).to_parquet(exp/'observations'/f'{name}.parquet',index=False)
if condition=='frozen_reference':
reference=r
if seed==cfg['old_replay_seed']:
oldname=f'{oi}-{seed}-offset';old=np.load(Path(cfg['input_experiment'])/'states'/f'{oldname}.npz')
eq={k:bool(np.array_equal(old[k],r[k])) for k in ('counts','final_v','final_g','trace')}
ev=pd.read_parquet(Path(cfg['input_experiment'])/'events'/f'{oldname}.parquet')
eq['KC_events']=arrhash(ev.step.to_numpy(),ev.neuron_idx.to_numpy())==arrhash(r['kc_event_step'],r['kc_event_node'])
assert all(eq.values());reg.append({'trial':name,'test':'frozen_V_reproduction',**eq})
assert reference is not None
if oi==cfg['odor_indices'][0]:
rr=trial(targets,forced,name)
keys=('counts','late_counts','final_v','final_g','kc_event_step','kc_event_node')
eq={k:bool(np.array_equal(r[k],rr[k])) for k in keys};assert all(eq.values())
reg.append({'trial':name,'test':'exact_repeat',**eq})
es=r['kc_event_step'];en=r['kc_event_node'];late=es>=round(cfg['late_start_ms']/sim.dt)
stats.append({'odor_index':oi,'odor_name':odor.odor_name,'seed':seed,'condition':condition,
'whole_brain_spikes':int(r['counts'].astype(np.int64).sum()),'whole_brain_active':int(np.count_nonzero(r['counts'])),
'late_whole_brain_spikes':int(r['late_counts'].astype(np.int64).sum()),'KC_spikes':len(es),'late_KC_spikes':int(late.sum()),
'late_KC_active':int(np.unique(en[late]).size),'finite':r['finite'],'wall_seconds':r['wall_seconds']})
for node in cfg['watch_neuron_idx']:
t=es[en==node];watchrows.append({'odor_index':oi,'seed':seed,'condition':condition,'neuron_idx':node,
'total_spikes':len(t),'late_spikes':int((t>=round(cfg['late_start_ms']/sim.dt)).sum()),
'first_ms':float(t.min()*sim.dt) if len(t) else None,'last_ms':float(t.max()*sim.dt) if len(t) else None})
for pop,idx in populations.items():
poprows.append({'odor_index':oi,'seed':seed,'condition':condition,'population':pop,'neurons':len(idx),
'total_spikes':int(r['counts'][idx].astype(np.int64).sum()),'active':int(np.count_nonzero(r['counts'][idx])),
'late_spikes':int(r['late_counts'][idx].astype(np.int64).sum()),'late_active':int(np.count_nonzero(r['late_counts'][idx]))})
comparisons.append({'odor_index':oi,'seed':seed,'condition':condition,
'neurons_with_changed_counts':int(np.count_nonzero(r['counts']!=reference['counts'])),
'max_abs_finalV_difference':float(np.abs(r['final_v'].astype(float)-reference['final_v']).max()),
'KC_event_sequence_equal':arrhash(es,en)==arrhash(reference['kc_event_step'],reference['kc_event_node'])})
save(exp/'regression/trials.json',reg)
print(json.dumps({'odor_complete':oi,'seed':seed,'trials':len(stats)}),flush=True)
sim.install=original_install;original_install(None)
assert arrhash(sim.base.cpu().numpy())==original_base_hash
assert np.array_equal(rt.weights.cpu().numpy(),original_overlay)
save(exp/'regression/restore.json',{'immutable_memory_base_hash_exact':True,'frozen_APL_overlay_restored_exact':True})
assert phase4s.evidence_regression(cfg,exp,rt,sim)
for name,data in [('trials',stats),('watched-KC',watchrows),('populations',poprows),('paired-differences',comparisons)]:pd.DataFrame(data).to_parquet(exp/'comparisons'/f'{name}.parquet',index=False)
save(exp/'protocol/input-hashes.json',inputs)
save(exp/'audit/results.json',{'phase':'4Y','status':'COMPLETE-EXPERIMENTAL-HYPOTHESIS-AUDIT','trials':len(stats),'repeat_trials':6,
'old_reference_reproductions':6,'frozen_reference_trials':5,'reference_pass':True,'all_finite':True,
'DPM_neurons':len(dpm),'DPM_existing_edges':len(ids),'weight_training':False,'candidate_selected':False,
'frozen_baseline_changed':False,'biological_sign_proven':False,'new_seed_count':1,'old_seed_count':1})
save(exp/'performance/summary.json',{'wall_seconds_excluding_build':time.perf_counter()-start,'peak_VRAM_bytes':torch.cuda.max_memory_allocated(),
'GPU':torch.cuda.get_device_name(0),'diagnostic_observation_overhead_included':True})
def seal(cfg,exp,model):
pre=json.loads((exp/'protocol/preregistered.json').read_text());now=checks(cfg);hist=phase4p_finalize.integrity()
ok=hist['all_static_checks_passed'] and now==json.loads((exp/'audit/preflight.json').read_text()) and all(digest(model/n)==h for n,h in pre['sources'].items())
save(exp/'audit/frozen-integrity.json',{'pass':ok,'history':hist,'current':now});assert ok
result=json.loads((exp/'audit/results.json').read_text());result['integrity_pass']=True;save(exp/'audit/results.json',result)
w=pd.read_parquet(exp/'comparisons/watched-KC.parquet');t=pd.read_parquet(exp/'comparisons/trials.parquet')
lines=['# Phase 4Y — independent DPM fast-current hypotheses','',
'Status: COMPLETE-EXPERIMENTAL-HYPOTHESIS-AUDIT. No candidate selection, training, game integration or frozen baseline changes.',
'All conditions use identical PN forcing for 120ms, then off to 1000ms. Six old odors, one old seed and one new seed. Not a population-level independent generalization gate.',
'Factors 0 and -1 are diagnostic model interventions, not corrections to raw labels or validated receptor-specific physiology. Only actual outgoing DPM effective currents change in RAM. Graph and raw counts stay intact.','',
'| KC | condition | late spike range (12 trials) |','|---:|---|---:|']
for (node,condition),g in w.groupby(['neuron_idx','condition']):lines.append(f'| {node} | {condition} | {g.late_spikes.min()}–{g.late_spikes.max()} |')
lines+=['','| condition | mean whole-brain spikes | mean late KC spikes | mean late active KC |','|---|---:|---:|---:|']
for condition,g in t.groupby('condition'):lines.append(f'| {condition} | {g.whole_brain_spikes.mean():.2f} | {g.late_KC_spikes.mean():.2f} | {g.late_KC_active.mean():.2f} |')
lines+=['','Exact controls: six Phase4V original trials, six repeats, five frozen reference trials, unchanged in-memory base hash and restored frozen overlay.',
'Finite states do not establish biological stability. No calibrated runaway threshold, biological sign conclusion, receptor kinetics or learning success is inferred.',
'Whole-brain intervention outcomes may differ from prior isolated replay because all feedback responds. Report this difference, do not select a condition by favorable activity.']
(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']))
@@ -0,0 +1,39 @@
phase: 4Y
experiment_root: /data/digital-drosophila/experiments/phase4y-dpm-hypothesis
model_root: /data/digital-drosophila/models/male-cns-lif/dpm-hypothesis-v1
prior_root: /data/digital-drosophila/experiments/phase4x-transmitter-provenance
prior_model: /data/digital-drosophila/models/male-cns-lif/transmitter-audit-v1
runtime_root: /data/digital-drosophila/models/male-cns-lif/deterministic-runtime-v2
phase4p_root: /data/digital-drosophila/experiments/phase4p-deterministic-runtime
input_model: /data/digital-drosophila/models/male-cns-lif/csus-audit-v1
input_experiment: /data/digital-drosophila/experiments/phase4v-csus-semantics
odor_indices: [0,1,2,3,4,5]
seeds: [131101,141101]
old_replay_seed: 131101
conditions:
- {name: frozen_reference, factor: 1.0}
- {name: DPM_fast_current_off, factor: 0.0}
- {name: DPM_fast_current_reversed, factor: -1.0}
perturbation:
exact_type: DPM
expected_bodies: [11734,12569]
scope: all and only outgoing effective currents of the two annotation-DPM neurons
no_fast_current: diagnostic ablation; not deletion of neurons or anatomical edges and not a complete slow-modulator model
reversed: sign-sensitivity hypothesis; not verified receptor-specific GABA/serotonin physiology
input_condition: same original 120ms PN forcing, then off to 1000ms
late_start_ms: 500
watch_neuron_idx: [59832,78836,82312]
repeat_check: first odor and both seeds, all three conditions
scope:
authorization: user delegated independent hypothesis-branch decision after explicit request for authorization
diagnostic_only: true
baseline_read_only: true
weight_training: false
slow_receptor_parameters_invented: false
candidate_selection: false
game_or_RL: false
reserved_novel_odors: untouched
interpretation:
report: all conditions and both seeds; do not pick the most active or best-looking result
causal_scope: effect of intervention on frozen mathematical model, not proof of biological neurotransmitter identity
stability: report nonfinite state, brain spike counts, active fractions, and KC late activity; no biological runaway threshold inferred
@@ -0,0 +1,106 @@
"""Descriptive paired effects only; no condition selection or parameter fitting."""
from pathlib import Path
import json
import pandas as pd
import numpy as np
import yaml
from phase4r import save
cfg=yaml.safe_load(Path(__file__).with_name('phase4y.yaml').read_text());exp=Path(cfg['experiment_root'])
assert not (exp/'checksums.sha256').exists(),'Sealed output is immutable'
w=pd.read_parquet(exp/'comparisons/watched-KC.parquet');t=pd.read_parquet(exp/'comparisons/trials.parquet')
p=pd.read_parquet(exp/'comparisons/populations.parquet');x=pd.read_parquet(Path(cfg['prior_root'])/'replay/results.parquet')
inputrows=[]
for _,r in t.iterrows():
obs=pd.read_parquet(exp/'observations'/f'{r.odor_index}-{r.seed}-{r.condition}.parquet').iloc[-1]
inputrows.append({'odor_index':int(r.odor_index),'seed':int(r.seed),'condition':r.condition,
'requested':int(obs.PN_requested_cumulative),'accepted_new_spikes':int(obs.PN_accepted_cumulative),
'preexisting_spikes':int(obs.PN_preexisting_cumulative),'refractory_blocked':int(obs.PN_blocked_cumulative)})
inp=pd.DataFrame(inputrows);assert (inp.groupby(['odor_index','seed']).requested.nunique()==1).all()
assert (inp.requested==inp.accepted_new_spikes+inp.preexisting_spikes+inp.refractory_blocked).all()
inp.to_parquet(exp/'comparisons/input-request-vs-acceptance.parquet',index=False)
save(exp/'audit/input-matching.json',{'scheduled_requests_matched_all_pairs':True,'partition_exact':True,
'pairs_with_changed_accepted_count':int((inp.groupby(['odor_index','seed']).accepted_new_spikes.nunique()>1).sum()),
'interpretation':'identical exogenous requests, not guaranteed identical accepted PN spikes; acceptance depends on endogenous refractory/spike state through the unchanged force interface'})
# Reporting-only schema correction. Preserve original tables and the registered run source.
# These exact superclass values are used by the frozen Phase4C propagation summaries.
meta_path=Path('/data/digital-drosophila/datasets/male-cns-v1.0/processed/neuron-only/neuron-index/unified-metadata.parquet')
sup=pd.read_parquet(meta_path,columns=['neuron_idx','annotation_superclass']).sort_values('neuron_idx')
definitions={'descending':['descending_neuron','descending_neuron_tbc'],'motor':['vnc_motor','cb_motor']}
corrected=[]
for _,r in t.iterrows():
name=f'{r.odor_index}-{r.seed}-{r.condition}'
with np.load(exp/'states'/f'{name}.npz') as z:
for pop,values in definitions.items():
idx=np.flatnonzero(sup.annotation_superclass.isin(values).to_numpy());assert len(idx)>0
c=z['counts'][idx];l=z['late_counts'][idx]
corrected.append({'odor_index':int(r.odor_index),'seed':int(r.seed),'condition':r.condition,'population':pop,
'neurons':len(idx),'total_spikes':int(c.astype(np.int64).sum()),'active':int(np.count_nonzero(c)),
'late_spikes':int(l.astype(np.int64).sum()),'late_active':int(np.count_nonzero(l))})
p=pd.concat([p[~p.population.isin(definitions)],pd.DataFrame(corrected)],ignore_index=True)
p.to_parquet(exp/'comparisons/populations-corrected.parquet',index=False)
save(exp/'audit/population-schema-correction.json',{'scope':'report aggregation only, no dynamics or registered conditions changed',
'issue':'initial literal superclass descending/motor matches zero rows; those zero-population rows are invalid readouts, not evidence of silence',
'definitions_from':'frozen phase4c.py summary definitions, verified against actual metadata',
'corrected_values':definitions,'corrected_counts':{k:int(sup.annotation_superclass.isin(v).sum()) for k,v in definitions.items()},
'source':str(meta_path),'original_tables_preserved':True,'recomputed_from':'saved complete per-neuron total and late counts','simulation_rerun':False})
(exp/'READ-ME-FIRST.md').write_text(
'# Phase 4Y evidence routing\n\n'
'Use audit/MECHANISM.md and comparisons/populations-corrected.parquet for population readouts.\n\n'
'The original comparisons/populations.parquet descending/motor rows used invalid literal superclass names and matched zero neurons. '
'Those rows are preserved for audit but must not be interpreted as no activity. Corrected rows use the frozen Phase4C definitions '
'(descending_neuron/descending_neuron_tbc; vnc_motor/cb_motor) and were recomputed from saved complete counts, without rerunning dynamics. '
'See audit/population-schema-correction.json. Other original population rows are unchanged.\n\n'
'All three hypotheses are reported. None is promoted to a learned or biological baseline.\n',encoding='utf-8')
effects=[];isolated=[]
for (oi,seed,node),g in w.groupby(['odor_index','seed','neuron_idx']):
baseline=int(g.loc[g.condition.eq('frozen_reference'),'late_spikes'].iloc[0])
for _,r in g.iterrows():
effects.append({'odor_index':int(oi),'seed':int(seed),'neuron_idx':int(node),'condition':r.condition,
'baseline_late_spikes':baseline,'condition_late_spikes':int(r.late_spikes),'paired_change':int(r.late_spikes)-baseline})
if seed==cfg['old_replay_seed']:
name=f'{oi}-{seed}-offset';z=x[(x.trial==name)&(x.neuron_idx==node)]
ib=int(z.loc[z['mode'].eq('base'),'late_spikes'].iloc[0]);io=int(z.loc[z['mode'].eq('omit_DPM_annotation'),'late_spikes'].iloc[0])
off=int(g.loc[g.condition.eq('DPM_fast_current_off'),'late_spikes'].iloc[0]);assert ib==baseline
isolated.append({'odor_index':int(oi),'neuron_idx':int(node),'baseline_late_spikes':baseline,
'isolated_DPM_off_late_spikes':io,'whole_brain_DPM_off_late_spikes':off,
'whole_minus_isolated':off-io,'interpretation':'different feedback assumptions; not a numerical regression failure'})
pd.DataFrame(effects).to_parquet(exp/'comparisons/watched-paired-effects.parquet',index=False)
pd.DataFrame(isolated).to_parquet(exp/'comparisons/isolated-vs-whole-brain.parquet',index=False)
new=[];loss=[]
metadata=pd.read_parquet(Path(cfg['prior_root'])/'roles/neuron-evidence-axes.parquet').set_index('neuron_idx')
for (oi,seed),group in t.groupby(['odor_index','seed']):
def late_set(condition):
e=pd.read_parquet(exp/'events'/f'{oi}-{seed}-{condition}.parquet');return set(map(int,e.loc[e.step>=5000,'neuron_idx']))
ref=late_set('frozen_reference')
for condition in ('DPM_fast_current_off','DPM_fast_current_reversed'):
s=late_set(condition)
for label,nodes in [('new',s-ref),('lost',ref-s)]:
for node in sorted(nodes):
row={'odor_index':int(oi),'seed':int(seed),'condition':condition,'change':label,'neuron_idx':node,
'body_id':int(metadata.loc[node,'body_id']),'annotation_type':metadata.loc[node,'annotation_type']}
(new if label=='new' else loss).append(row)
pd.DataFrame(new+loss,columns=['odor_index','seed','condition','change','neuron_idx','body_id','annotation_type']).to_parquet(exp/'comparisons/late-KC-membership-changes.parquet',index=False)
ep=pd.DataFrame(effects);summary=[]
for (node,condition),g in ep.groupby(['neuron_idx','condition']):
summary.append({'neuron_idx':int(node),'condition':condition,'n':len(g),'late_min':int(g.condition_late_spikes.min()),
'late_max':int(g.condition_late_spikes.max()),'change_min':int(g.paired_change.min()),'change_max':int(g.paired_change.max()),
'decreases':int((g.paired_change<0).sum()),'increases':int((g.paired_change>0).sum()),'unchanged':int((g.paired_change==0).sum())})
save(exp/'audit/descriptive-effects.json',{'scope':'post-run descriptive summaries; no promotion or new gate',
'watched':summary,'new_late_KC_memberships':len(new),'lost_late_KC_memberships':len(loss)})
lines=['# Phase 4Y — paired whole-brain effects','',
'All branches are diagnostic hypotheses; none is selected as a better biological model.','',
'| KC | condition | late spike range | paired change range | decreases / unchanged / increases |','|---:|---|---:|---:|---:|']
for r in summary:lines.append(f"| {r['neuron_idx']} | {r['condition']} | {r['late_min']}–{r['late_max']} | {r['change_min']}–{r['change_max']} | {r['decreases']} / {r['unchanged']} / {r['increases']} |")
lines+=['','| KC | isolated DPM-off range | whole-brain DPM-off range (same old seed) |','|---:|---:|---:|']
for node,g in pd.DataFrame(isolated).groupby('neuron_idx'):
lines.append(f'| {node} | {g.isolated_DPM_off_late_spikes.min()}–{g.isolated_DPM_off_late_spikes.max()} | {g.whole_brain_DPM_off_late_spikes.min()}–{g.whole_brain_DPM_off_late_spikes.max()} |')
lines+=['','| population | condition | mean late spikes | mean late active neurons |','|---|---|---:|---:|']
for (pop,condition),g in p.groupby(['population','condition']):
lines.append(f'| {pop} | {condition} | {g.late_spikes.mean():.2f} | {g.late_active.mean():.2f} |')
lines+=['','Whole-brain feedback is allowed to respond; isolated replay holds the other recorded arrivals fixed. A difference between them is the target of the experiment, not grounds for choosing one result.',
'Input matching is at the scheduled request level. Accepted new PN spikes can differ through feedback-dependent refractory state; see input-request-vs-acceptance.parquet. Do not claim identical delivered PN spike trains.',
'Descending/motor rows here use populations-corrected.parquet, reconstructed from saved full counts and verified actual superclass names. The original zero-population rows are retained but invalid; see audit/population-schema-correction.json.',
'No learning, no reward events, no parameter fitting, no promotion; smaller late activity is not itself success. Biological signs and long-term stability remain unproven.']
(exp/'audit/MECHANISM.md').write_text('\n'.join(lines)+'\n',encoding='utf-8')
print('\n'.join(lines[:20]))