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 @@
309ccee0bae28539e4d3ba0ebee6ebdfddfbe64e48c40347a594888de06a1306 odor_coverage_diagnostics.py
ece8e13d98de562c3450771ca9d330e6d849a69aa136054710e5d49a68216d10 phase4u-literature.md
c97b908f38619a52a291c63fe90056f69d966f4138f50a3489f54e6e0ff8683e phase4u.py
66da0009e213681ae159fac35e69bb374f5789e8554579e4e7a5f67d97486c7a phase4u.yaml
49256cd0b3682d7c622882effc90bbbc605ae8c7479c805ebaf53a88f13f3d5d manifest.json
@@ -0,0 +1,24 @@
{
"files": [
{
"bytes": 3126,
"path": "odor_coverage_diagnostics.py",
"sha256": "309ccee0bae28539e4d3ba0ebee6ebdfddfbe64e48c40347a594888de06a1306"
},
{
"bytes": 3534,
"path": "phase4u-literature.md",
"sha256": "ece8e13d98de562c3450771ca9d330e6d849a69aa136054710e5d49a68216d10"
},
{
"bytes": 16466,
"path": "phase4u.py",
"sha256": "c97b908f38619a52a291c63fe90056f69d966f4138f50a3489f54e6e0ff8683e"
},
{
"bytes": 2242,
"path": "phase4u.yaml",
"sha256": "66da0009e213681ae159fac35e69bb374f5789e8554579e4e7a5f67d97486c7a"
}
]
}
@@ -0,0 +1,45 @@
"""Post-calibration set intersection audit. Never selects a replacement policy."""
from pathlib import Path
from itertools import combinations
import json
import pandas as pd
root=Path('/data/digital-drosophila/experiments/phase4u-odor-generalization')
data=json.loads((root/'calibration/selection.json').read_text());grid=data['grid']
pre=json.loads((root/'protocol/preregistered.json').read_text())
names={p['odor_id']:p['odor_name'] for p in pre['panel']}
ids=[p['odor_id'] for p in grid[0]['odors']]
sets={oid:{i for i,z in enumerate(grid) if next(x for x in z['odors'] if x['odor_id']==oid)['pass']} for oid in ids}
assert all(sets.values()),'At least one odor has no individually passing grid point; different mechanism'
empty=[]
for size in range(2,len(ids)+1):
for subset in combinations(ids,size):
if not set.intersection(*(sets[o] for o in subset)):
empty.append({'odor_ids':list(subset),'odor_names':[names[o] for o in subset]})
if empty:break
coverage=[]
for z in grid:
coverage.append({'window_ms':z['window_ms'],'US_ms':z['US_ms'],'passing_odors':sum(x['pass'] for x in z['odors']),
'failing_odors':[names[x['odor_id']] for x in z['odors'] if not x['pass']]})
report={'scope':'Post-calibration descriptive intersections within the preregistered finite grid only.',
'candidate_grid_cells':len(grid),'max_passing_odors':max(x['passing_odors'] for x in coverage),
'all_six_intersection_count':len(set.intersection(*sets.values())),
'per_odor':[{'odor_id':o,'odor_name':names[o],'passing_count':len(sets[o]),
'passing_cells':[{'window_ms':grid[i]['window_ms'],'US_ms':grid[i]['US_ms']} for i in sorted(sets[o])]} for o in ids],
'minimum_empty_intersection_subset_size':size,'minimum_conflicts':empty,
'coverage':coverage,'new_parameter_selected':False,'new_odor_test_run':False,
'limits':['No claim of impossibility outside this finite grid, encoder and brain model.',
'No reward-triggering, per-odor timing, threshold or trace modifications performed.',
'Per-odor cells are descriptive, not approved odor-specific policies.']}
(root/'audit/coverage-intersections.json').write_text(json.dumps(report,indent=2)+'\n')
f=pd.DataFrame(coverage);matrix=f.pivot(index='window_ms',columns='US_ms',values='passing_odors')
lines=['# 校准覆盖交集审计','',
'每个单元格是通过的校准气味数(满分 6)。这是事后描述,不选择替代参数。','',
'| window / US ms | '+' | '.join(map(str,matrix.columns))+' |',
'|---|'+'---:|'*len(matrix.columns)]
for w,row in matrix.iterrows():lines.append('| '+str(w)+' | '+' | '.join(map(str,row.astype(int)))+' |')
lines+=['',f'最小无共同解子集包含 {size} 个气味:','']
for x in empty:lines.append('- '+' / '.join(x['odor_names']))
lines+=['','局限:仅对预注册有限网格成立。没有证明所有时序模型无解;也没有证明真实果蝇无法学习这些气味。']
(root/'audit/COVERAGE.md').write_text('\n'.join(lines)+'\n',encoding='utf-8')
print(json.dumps({k:v for k,v in report.items() if k!='coverage'},indent=2))
@@ -0,0 +1,23 @@
# Phase 4U — 文献依据与适用边界
核对日期:2026-09-29。以下为原始来源的简短归纳,不转载论文。Google 搜索结果不等于论文证据;Google Research、Google DeepMind 与其他作者分别标明。
| 来源 | 可支持的范围 | 本项目采取的措施 / 不允许的外推 |
|---|---|---|
| [Google Research:MaleCNS 发布说明,2026-09-03](https://research.google/blog/a-connectomics-milestone-mapping-the-complete-male-fruit-fly-brain/) | Google、Janelia 与合作方发布雄蝇 CNS 连接组;这是结构数据基础 | 继续使用本地经校验 canonical IDs;不把结构完整等同于学习功能完整,不替换数据版本 |
| [Shiu et al., Nature 2024](https://www.nature.com/articles/s41586-024-07763-9),doi:10.1038/s41586-024-07763-9 | 连接组约束的计算模型用于感觉—运动处理 | 保留传播/数值回归作为独立门槛;不以该论文作为当前 plasticity 已通过的依据。此次全文页面访问受限,未据此提取新参数 |
| [Vaxenburg et al., Nature 2025](https://www.nature.com/articles/s41586-025-09029-4),doi:10.1038/s41586-025-09029-4 | 包括 Google DeepMind 作者的果蝇身体仿真,使用数据驱动端到端 RL 控制步行/飞行 | 身体控制研究与冻结 MaleCNS 内部关联学习并非同一问题;本轮不引入外部 RL、不接身体或游戏 |
| [Handler et al., Cell 2019](https://pmc.ncbi.nlm.nih.gov/articles/PMC9012144/),doi:10.1016/j.cell.2019.05.040 | 感觉与多巴胺的相对时序、受体通路与双向 KC–MBON 可塑性有关 | 当前单一因果 LTD gate 是工程简化,不是完整分子模型;不能将未来放电反向计入过去 US。本轮未改变规则或从文献移植时间常数。全文直连受限,依据可访问的作者摘要/引言索引 |
| [Münch & Galizia, Scientific Reports 2016 / DoOR 2.0](https://pmc.ncbi.nlm.nih.gov/articles/PMC4766438/),doi:10.1038/srep21841 | 合并多实验来源的气味响应数据库 | 使用已冻结 DoOR.data v2.0.1 和已有 PN mapping 扩大刺激面板;归一化响应值不是 MaleCNS 的实测时序,400 Hz 映射仍是工程假设 |
| [Wong et al., Nature Communications 2023](https://www.nature.com/articles/s41467-023-38487-5),doi:10.1038/s41467-023-38487-5 | 特定 αβ core KC 的亚阈整合和嗅觉决策时间研究 | 依据该工作的启发,将响应潜伏期/亚阈动力学与气味幅度区分。不能外推到所有 KC,也不改变当前阈值或刺激电流 |
## 对本轮实验的实际影响
1. 优先修复实验覆盖和数据独立性,而非把 US 反复挪到失败样本的峰值。
2. 校准使用冻结面板全部 6 个 calibration odors,排除 formal A/B 和此前 4 个 timing-heldout odors。
3. 新测试气味从官方 DoOR 已有资源中选取,排除旧 12 气味的 ID 和名称。按静态 mapped-response-sum 分层选样,不查看新脑响应。
4. 向量构造与 PN rates 必须逐位复现冻结 12 气味,先验证,再对新气味使用同一函数。刺激率上限、映射和脑参数不变。
5. 候选范围、选择规则与门槛沿用 Phase 4T;只扩大校准与独立测试面板。保留旧失败结果,不改旧 heldout 身份来冒充独立验证。
6. timing 与 credit specificity、记忆、行为分别验收。本轮即使 timing PASS,也不自动声称学习成功。
局限:该研究仍是同一连接组、同一工程输入变换的模型内测试;跨气味泛化不等于跨动物或实验生理泛化。
@@ -0,0 +1,198 @@
"""Literature-informed panel coverage test; frozen brain and encoder, no learning."""
from pathlib import Path
import argparse
import json
import time
import numpy as np
import pandas as pd
import torch
import yaml
import phase4p
import phase4p_finalize
import phase4s
import phase4t
from phase4p_revalidate import DeterministicAPLSim,door_inputs,make_drive
from phase4l import load_external
from phase4r import save,digest,arrhash,curves
from phase4q import last_spike_trace,v2_trace
from phase4h_audit import verify_checksum
def checks(cfg):
tc=yaml.safe_load((Path(cfg['prior_model'])/'phase4t.yaml').read_text())
out=phase4t.checks(tc)
for key in ('prior_root','prior_model'):
root=Path(cfg[key]);out[key]=verify_checksum(root,root/'checksums.sha256')
assert all(x['passed'] for x in out.values());return out
def prepare(cfg,exp):
oc,old,oldvec,selection,mapping=door_inputs()
_,response,odors,provenance=load_external(oc,exp)
assert 'InChIKey' in odors.columns and 'Name' in odors.columns
usable=mapping[mapping.mapping_confidence.isin(['exact','alias-normalized'])&mapping.neuron_idx.notna()]
cg=usable[['odor_channel','glomerulus']].drop_duplicates();gloms=selection['glomeruli_order'];gi={g:i for i,g in enumerate(gloms)}
assert sorted(cg.glomerulus.unique())==gloms
names=odors.dropna(subset=['InChIKey']).drop_duplicates('InChIKey').set_index('InChIKey')['Name'].to_dict()
vectors={};rows=[]
for key,row in response.iterrows():
if str(key)=='SFR':continue
name=names.get(key)
if pd.isna(name) or not str(name).strip():continue
arr=np.zeros(len(gloms),np.float32)
for _,q in cg.iterrows():
value=row.get(q.odor_channel)
if pd.notna(value):arr[gi[q.glomerulus]]=max(arr[gi[q.glomerulus]],max(float(value),0.))
allsum=float(np.maximum(np.nan_to_num(row.to_numpy(np.float64),nan=0.),0.).sum())
mapped=float(arr.sum());strong=int(np.count_nonzero(arr>=float(oc['odor_encoder']['strong_response_threshold'])))
if mapped<=0 or strong<int(oc['odor_encoder']['minimum_mapped_strong_channels']):continue
oid=str(key);vectors[oid]=arr
rows.append({'odor_id':oid,'odor_name':str(name),'mapped_response_sum':mapped,'all_response_sum':allsum,
'mapped_signal_fraction':mapped/allsum if allsum else 0.,'mapped_strong_glomeruli':strong,'mapped_nonzero_glomeruli':int(np.count_nonzero(arr))})
candidates=pd.DataFrame(rows)
tests=[]
for oid in old.odor_id:
assert np.array_equal(vectors[oid],oldvec[oid]),'Frozen vector mismatch: '+oid
# make_drive is the unchanged encoder; physiological mode never rescales to the panel median.
t,r=make_drive(oc,old,vectors,selection,mapping,oid,'physiological')
tt,rr=make_drive(oc,old,oldvec,selection,mapping,oid,'physiological')
assert np.array_equal(t,tt) and np.array_equal(r,rr)
tests.append({'odor_id':oid,'vector_exact':True,'PN_targets_rates_exact':True,'rate_sum_hz':float(r.sum())})
oldnames=set(old.odor_name.astype(str).str.strip().str.casefold())
pool=candidates[~candidates.odor_id.isin(old.odor_id)&~candidates.odor_name.str.strip().str.casefold().isin(oldnames)&(candidates.mapped_signal_fraction>=.9)].copy()
pool=pool.sort_values(['mapped_response_sum','odor_id'],kind='stable').drop_duplicates('odor_name').reset_index(drop=True)
n=int(cfg['panel']['novel_count']);assert len(pool)>=n,'Not enough independent physiology odors'
bins=np.array_split(np.arange(len(pool)),n);selected=[int(b[(len(b)-1)//2]) for b in bins]
novel=pool.iloc[selected].copy();novel['split']='new_odor_validation'
cal=old[old.split=='calibration'].drop(columns=['formal_label']).copy();cal['split']='calibration';assert len(cal)==6
panel=pd.concat([cal,novel],ignore_index=True)
formal={selection['ODOR_A']['odor_id'],selection['ODOR_B']['odor_id']}
assert not set(panel.odor_id)&formal and not set(novel.odor_id)&set(old.odor_id)
pool.to_parquet(exp/'protocol/eligible-new-odor-pool.parquet',index=False)
panel.to_parquet(exp/'protocol/frozen-panel.parquet',index=False)
np.savez_compressed(exp/'protocol/frozen-vectors.npz',glomeruli=np.asarray(gloms),**{oid:vectors[oid] for oid in panel.odor_id})
save(exp/'regression/frozen-encoder-exact.json',{'pass':True,'rows':tests})
save(exp/'protocol/panel-selection.json',{'eligible_new_odors':len(pool),'rank_bins':[list(map(int,b)) for b in bins],
'selected_ranks':selected,'new_odor_ID_overlap_old12':0,'input_response_only':True,'runtime_responses_used':False,
'source':provenance,'frozen_mapping_sha256':digest(Path(oc['paths']['phase4l_root'])/'receptor-mapping/olfactory-mapping.parquet')})
return oc,old,panel,vectors,selection,mapping
def run(cfg,exp,model):
for d in ('protocol','external-resources','calibration','validation','events','regression','audit','performance'):(exp/d).mkdir(parents=True,exist_ok=True)
assert not (exp/'protocol/preregistered.json').exists(),'Refusing overwrite'
before=checks(cfg);save(exp/'audit/preflight.json',before)
oc,old,panel,vectors,selection,mapping=prepare(cfg,exp)
cal=panel[panel.split=='calibration'];nc=len(cal)
save(exp/'protocol/preregistered.json',{'config':cfg,'panel':panel.to_dict('records'),
'sources':{p.name:digest(p) for p in model.glob('phase4u*') if p.is_file()},
'created_before_simulation':True,'old12_excluded_from_new_test':True,'no_new_dynamic_results_seen':True,
'method':'Expanded calibration coverage; fixed Phase4T grid, score and gate. Not posthoc retest of Phase4T failures.'})
print(json.dumps({'panel':panel[['odor_id','odor_name','split','mapped_response_sum']].to_dict('records')}),flush=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)
lookup=np.full(sim.n,-1,np.int32);lookup[sim.kc]=np.arange(len(sim.kc))
identity=sim.identity;assert len(identity)==14232
mask=identity.compartment.eq(cfg['target_compartment']).to_numpy();loc=lookup[identity.source_neuron_idx.to_numpy(np.int32)]
multi=np.bincount(loc[mask],minlength=len(sim.kc)).astype(np.float32)
dan=sim.dan_targets(cfg['matched_DAN'],cfg['target_compartment']);duration=cfg['duration_ms'];nsteps=round(duration/sim.dt)
records=[];repeats=[];live=[];times=[];start=time.perf_counter();torch.cuda.reset_peak_memory_stats()
def trial(oi,seed,split,ws,us):
odor=panel.iloc[oi];targets,rates=make_drive(oc,old,vectors,selection,mapping,odor.odor_id,'physiological')
actual=cfg['seed_namespace_offset']+oi*cfg['odor_seed_offset']+seed
r=sim.run(targets,rates,actual,duration,collect_events=True);assert r['finite']
phase4s.persist_run(exp,f'{split}-{oi}-{seed}',r)
cs=curves(r,lookup,multi,ws,sim.dt,nsteps)
for wi,w in enumerate(ws):
den=float(cs[wi].mean())
for u in us:
m=float(cs[wi][round(u/sim.dt)])
records.append({'split':split,'odor_id':odor.odor_id,'odor_name':odor.odor_name,'odor_index':oi,'seed':seed,'actual_seed':actual,
'window_ms':w,'US_ms':u,'matched':m,'random_expectation':den,'ratio':m/den if den else 0.})
s=r['kc_event_step'];nodes=r['kc_event_node'];target=multi[lookup[nodes]]>0;ts=s[target]
times.append({'split':split,'odor_index':oi,'seed':seed,'KC_active':int(np.count_nonzero(r['kc_counts'])),
'target_active_KC':int(np.unique(nodes[target]).size),'target_events':len(ts),
'first_target_ms':float(ts.min()*sim.dt) if len(ts) else None,
'median_target_ms':float(np.median(ts)*sim.dt) if len(ts) else None})
return r,targets,rates,actual
for oi in range(nc):
for si,seed in enumerate(cfg['calibration_seeds']):
r,t,rates,actual=trial(oi,seed,'calibration',cfg['windows_ms'],cfg['candidate_US_ms'])
if si==0:
rr=sim.run(t,rates,actual,duration,collect_events=True);assert rr['finite']
repeats.append({'split':'calibration','odor_index':oi,'seed':seed,
'events_exact':arrhash(r['kc_event_step'],r['kc_event_node'])==arrhash(rr['kc_event_step'],rr['kc_event_node']),
'states_exact':arrhash(r['final_v'],r['final_g'])==arrhash(rr['final_v'],rr['final_g'])})
print(json.dumps({'stage':'calibration','odor_index':oi,'seeds':len(cfg['calibration_seeds'])}),flush=True)
grid=[phase4t.gate(records,cfg,w,u) for w in cfg['windows_ms'] for u in cfg['candidate_US_ms']]
good=[x for x in grid if x['pass']];chosen=sorted(good,key=lambda x:(-x['score'],x['window_ms'],x['US_ms']))[0] if good else None
pd.DataFrame(records).to_parquet(exp/'calibration/grid.parquet',index=False)
save(exp/'calibration/selection.json',{'grid':grid,'chosen':chosen,'saved_before_validation':True})
print(json.dumps({'selected':chosen}),flush=True)
summary={'phase':'4U','calibration':chosen,'seed_validation':None,'new_odor_validation':None,'timing_gate_pass':False,
'weight_updates':0,'memory_claim':False,'calibration_odors':nc,'new_test_odors':len(panel)-nc,'formal_AB_used':False}
if chosen:
w=chosen['window_ms'];u=chosen['US_ms'];us=round(u/sim.dt);phase4t.controls(w,u,sim.dt,exp/'regression')
for oi in range(len(panel)):
split='seed_validation' if oi<nc else 'new_odor_validation'
for si,seed in enumerate(cfg['validation_seeds']):
r,t,rates,actual=trial(oi,seed,split,[w],[u])
if si<cfg['live_US_checks']['seeds_per_validation_odor']:
for pulse in (us,int(np.random.default_rng(actual+cfg['live_US_checks']['random_seed_offset']).integers(nsteps))):
rr=sim.run(t,rates,actual,duration,dan_targets=dan,dan_pulses=[pulse*sim.dt],collect_events=True);assert rr['finite']
phase4s.persist_run(exp,f'live-{oi}-{seed}-{pulse}',rr)
a,_=last_spike_trace(r['kc_event_step'],r['kc_event_node'],lookup,pulse,w,w/3,sim.dt)
b,_=last_spike_trace(rr['kc_event_step'],rr['kc_event_node'],lookup,pulse,w,w/3,sim.dt)
v=v2_trace(rr['kc_event_step'],rr['kc_event_node'],lookup,pulse,sim.dt)
live.append({'odor_index':oi,'seed':seed,'US_step':pulse,'trace_exact':bool(np.array_equal(a,b)),
'v2_trace_exact':bool(np.array_equal(v,rr['trace']))})
print(json.dumps({'stage':split,'odor_index':oi,'seeds':len(cfg['validation_seeds'])}),flush=True)
validation=[x for x in records if x['split']!='calibration'];pd.DataFrame(validation).to_parquet(exp/'validation/results.parquet',index=False)
for split in ('seed_validation','new_odor_validation'):summary[split]=phase4t.gate([x for x in validation if x['split']==split],cfg,w,u)
summary['live_controls_pass']=all(x['trace_exact'] and x['v2_trace_exact'] for x in live)
summary['timing_gate_pass']=summary['seed_validation']['pass'] and summary['new_odor_validation']['pass'] and summary['live_controls_pass']
save(exp/'regression/live-US-checks.json',live);save(exp/'regression/repeats.json',repeats)
summary['repeats_pass']=all(x['events_exact'] and x['states_exact'] for x in repeats)
summary['reference_pass']=phase4s.evidence_regression(cfg,exp,rt,sim)
summary['timing_gate_pass'] &= summary['reference_pass'] and summary['repeats_pass']
summary['status']='PASS-INDEPENDENT-TIMING-ONLY' if summary['timing_gate_pass'] else ('FAIL-CALIBRATION-COVERAGE' if chosen is None else 'FAIL-INDEPENDENT-TIMING-GENERALIZATION')
pd.DataFrame(times).to_parquet(exp/'audit/event-timing.parquet',index=False)
save(exp/'audit/results.json',summary)
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),'GPU_utilization':'not sampled','base_short_trials':len(times),'live_US_trials':len(live),'repeat_short_trials':len(repeats),'frozen_reference_trials':5})
print(json.dumps(summary,indent=2),flush=True)
def seal(cfg,exp,model):
now=checks(cfg);old=phase4p_finalize.integrity();pre=json.loads((exp/'protocol/preregistered.json').read_text())
same=all(digest(model/n)==h for n,h in pre['sources'].items())
integrity={'history':old,'current':now,'before_after_equal':now==json.loads((exp/'audit/preflight.json').read_text()),'source_unchanged':same}
integrity['pass']=old['all_static_checks_passed'] and same and integrity['before_after_equal']
save(exp/'audit/frozen-integrity.json',integrity);assert integrity['pass']
r=json.loads((exp/'audit/results.json').read_text());r['integrity_pass']=True;save(exp/'audit/results.json',r)
if r['timing_gate_pass']:save(model/'MaleCNS-OdorTiming-v1-candidate.json',{'scope':'engineering timing only; no credit-specificity or memory claim','results':r})
names={x['odor_id']:x['odor_name'] for x in pre['panel']}
lines=['# Phase 4U — 文献约束与扩展气味时序验证','',f"状态:`{r['status']}`",'',
'文献与模型边界见模型目录 phase4u-literature.md;官方 DoOR 数据 provenance 与哈希见 external-resources/provenance.json。',
'扩大校准集至原始六种 calibration odors;新增六种测试气味与旧十二种 ID/名称均不重叠,按照静态响应强度分层选取。',
'十二种旧气味的 glomerular vectors、PN targets/rates 均逐位复现。固定脑、encoder 与 14,232-edge mask 未更改;无权重更新。','']
for key in ('calibration','seed_validation','new_odor_validation'):
if r[key]:
g=r[key];lines +=[f"## {key}",'',f"US={g['US_ms']} ms;window={g['window_ms']} ms。",'',
'| 气味 | 达标 seeds | mean ratio | q20 ratio | 零 credit | PASS |','|---|---:|---:|---:|---:|---|']
for x in g['odors']:lines.append(f"| {names[x['odor_id']]} | {x['qualifying']}/{x['n']} | {x['ratio_of_means']} | {x['q20_ratio']:.4f} | {x['zero_credit']} | {x['pass']} |")
if r['calibration'] is None:
grid=json.loads((exp/'calibration/selection.json').read_text())['grid'];counts=[sum(x['pass'] for x in z['odors']) for z in grid]
lines+=['没有共同达标的候选;未访问新测试气味的动态结果,也未选择最不差的参数作为正式方案。',
f"共 {len(grid)} 个候选;单个候选最多通过 {max(counts)}/{r['calibration_odors']} 个校准气味。所有逐候选结果已保留。"]
perf=json.loads((exp/'performance/summary.json').read_text())
lines+=['',f"参考回归:{r['reference_pass']};重复:{r['repeats_pass']};历史清单完整性:{r['integrity_pass']}。",'',
f"本轮耗时(不含编译/封存):{perf['wall_seconds_excluding_build']:.2f}s;peak allocated VRAM:{perf['peak_VRAM_bytes']/2**20:.2f}MiB。",'',
'没有 formal A/B credit 或 conditioning,不接游戏、不引入 RL/decoder。文献只指导覆盖、因果时序与推断边界,未提供当前模型的 MaleCNS 实测参数。',
'结果仅适用于当前 DoOR→PN 工程输入与 LIF 模型。失败不能证明所有时序规则无效;成功也不是关联记忆成功。']
(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))
result=verify_checksum(root,root/'checksums.sha256');assert result['passed'];print(json.dumps(result),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,41 @@
phase: 4U
experiment_root: /data/digital-drosophila/experiments/phase4u-odor-generalization
model_root: /data/digital-drosophila/models/male-cns-lif/odor-timing-generalization-v1
runtime_root: /data/digital-drosophila/models/male-cns-lif/deterministic-runtime-v2
phase4p_root: /data/digital-drosophila/experiments/phase4p-deterministic-runtime
prior_root: /data/digital-drosophila/experiments/phase4t-timing-stability
prior_model: /data/digital-drosophila/models/male-cns-lif/timing-stability-v1
duration_ms: 120
target_compartment: "y5B'2a"
matched_DAN: PAM15
candidate_US_ms: [10, 15, 20, 25, 30, 40, 60, 90]
windows_ms: [5, 10, 20, 50, 100, 250]
calibration_seeds: [118101,118102,118103,118104,118105,118106,118107,118108,118109,118110,118111,118112,118113,118114,118115,118116,118117,118118,118119,118120]
validation_seeds: [119101,119102,119103,119104,119105,119106,119107,119108,119109,119110,119111,119112,119113,119114,119115,119116,119117,119118,119119,119120]
odor_seed_offset: 2000000
seed_namespace_offset: 50000000
panel:
calibration: all six frozen Phase4L calibration odors
novel_count: 6
exclusion: all old twelve odor IDs and names
quality_filter: same minimum five strong mapped glomeruli and mapped_response>0 as frozen encoder; mapped_signal_fraction at least 0.9
selection: stable sort eligible new odors by mapped_response_sum then odor_id; six equal rank bins; choose lower median rank of each bin
no_selection_on_runtime_activity: true
timing_gate:
ratio: 3.0
qualifying_fraction_each_odor: 0.8
mean_ratio_each_odor: 3.0
selection: maximize minimum across six calibration odors of q20 per-seed ratio; ties shortest window then earliest US
random: exact uniform expectation over 1200 timestep gates on same KC events
unchanged_from_4T: true
on_no_qualifying_calibration: stop; do not access new odor dynamic trials or choose a least-bad parameter
live_US_checks:
seeds_per_validation_odor: 1
random_seed_offset: 29000000
scope:
weight_updates: false
formal_AB_used: false
per_odor_US_or_event_triggered_US: false
change_encoder_rates_dynamics_or_signs: false
new_candidate_requires_all_validation_odors_pass: true
gate_scope: engineering fixed-US timing, not biological memory