Files
digital-drosophila/models/male-cns-lif/odor-timing-generalization-v1/odor_coverage_diagnostics.py
T

46 lines
3.1 KiB
Python

"""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))