"""Post-hoc interpretation only; no further trials, selection or parameter changes.""" from pathlib import Path import sys import numpy as np import pandas as pd ROOT = Path('/data/digital-drosophila'); MODEL = ROOT/'models/male-cns-lif/terminal-scope-diagnostic-v1' EXP = ROOT/'experiments/phase4ab-terminal-scope' sys.path.insert(0, str(MODEL)) import diagnostic cfg, _, _, _, save, digest, _, _ = diagnostic.load_context() assert (EXP/'audit/run-complete.json').exists() m = pd.read_parquet(ROOT/cfg['canonical']/'neuron-index/unified-metadata.parquet').sort_values('neuron_idx').reset_index(drop=True) trials = pd.read_csv(EXP/'audit/trials.csv'); watched = pd.read_csv(EXP/'audit/watched-counts.csv') remaining, comparisons = [], [] for oi in cfg['odor_indices']: names = {c: f'{oi}-{cfg["seed"]}-{c}' for c in cfg['conditions']} narrow = pd.read_parquet(EXP/'events'/f'{names["calyx_PN_KC_contact_block"]}.parquet') allkc = pd.read_parquet(EXP/'events'/f'{names["all_PN_KC_block"]}.parquet') a = np.load(EXP/'states'/f'{names["calyx_PN_KC_contact_block"]}.npz') b = np.load(EXP/'states'/f'{names["all_PN_KC_block"]}.npz') comparisons.append({'odor_index': oi, 'terminal_vs_allKC_events_exact': narrow.equals(allkc), 'counts_exact': bool(np.array_equal(a['counts'], b['counts'])), 'max_V_difference': float(np.abs(a['final_v'].astype(float)-b['final_v']).max()), 'max_g_difference': float(np.abs(a['final_g'].astype(float)-b['final_g']).max())}) kc = np.flatnonzero(m.annotation_class.eq('Kenyon_Cell')) late = narrow[(narrow.step >= 5000) & narrow.neuron_idx.isin(kc)] for node, group in late.groupby('neuron_idx'): row = m.iloc[node] remaining.append({'odor_index': oi, 'neuron_idx': int(node), 'body_id': int(row.body_id), 'type': str(row.annotation_type), 'late_spikes': len(group), 'first_late_spike_ms': float(group.step.min()/10), 'last_spike_ms': float(group.step.max()/10), 'source_identity_not_inferred_from_this_count': True}) pd.DataFrame(remaining).to_csv(EXP/'audit/remaining-late-KCs.csv', index=False) pd.DataFrame(comparisons).to_csv(EXP/'audit/narrow-vs-allKC.csv', index=False) rows = [] for name, g in trials.groupby('condition', sort=False): row = {'condition': name} for key in ['whole_spikes', 'KC_active', 'KC_spikes', 'KC_late_spikes', 'KC82312_late_spikes', 'VA6_late_spikes', 'wall_seconds']: row[key+'_min'] = float(g[key].min()); row[key+'_max'] = float(g[key].max()) rows.append(row) pd.DataFrame(rows).to_csv(EXP/'audit/condition-ranges.csv', index=False) ca = trials[trials.condition == 'calyx_PN_KC_contact_block'] wide = trials[trials.condition == 'whole_PN_output_block'] local = watched[(watched.population == 'ALLN') & watched.trial.str.endswith('calyx_PN_KC_contact_block')] result = {'posthoc_summary_not_new_experiment': True, 'all_six_CA_block_targetKC_late_zero': bool(ca.KC82312_late_spikes.eq(0).all()), 'all_six_CA_block_VA6_late_persists': bool(ca.VA6_late_spikes.gt(0).all()), 'all_six_broad_block_KC_and_VA6_late_zero': bool((wide.KC_late_spikes.eq(0) & wide.VA6_late_spikes.eq(0)).all()), 'CA_block_active_KC_range': [int(ca.KC_active.min()), int(ca.KC_active.max())], 'CA_block_ALLN_watched_late_rate_range_hz': [float(local.late_rate_hz.min()), float(local.late_rate_hz.max())], 'CA_vs_allKC_spike_sequence_exact_for_all_odors': bool(all(x['terminal_vs_allKC_events_exact'] for x in comparisons)), 'not_a_physiological_APL_gain_estimate': True, 'not_a_memory_or_separability_success': True, 'new_candidate': False, 'source_sha256': digest(Path(__file__))} save(EXP/'audit/mechanism-interpretation.json', result) def span(frame, key): lo, hi = frame[key].min(), frame[key].max() return str(int(lo)) if lo == hi else f'{int(lo)}–{int(hi)}' ref = trials[trials.condition == 'frozen_reference'] lines = ['# Phase 4AB 机制解读', '', '正式状态:**COMPLETE-SCOPE-COUNTERFACTUAL / NO-DYNAMICS-CANDIDATE**。', '', '这一轮区分了“PN→KC 传入位置”与“维持 PN 放电的上游反馈”。没有修复或批准新的 APL 生理模型,也没有训练。', '', '| 诊断范围 | 活跃 KC(6 个旧气味) | 目标 KC 晚期 spikes | VA6 PN 晚期 spikes |', '|---|---:|---:|---:|'] labels = {'frozen_reference': '原模型', 'calyx_PN_KC_contact_block': '仅明确 calyx 接触', 'all_PN_KC_block': '同批 PN 的全部 KC 输出', 'whole_PN_output_block': '同批 PN 的全部快速输出'} for name in cfg['conditions']: g = trials[trials.condition == name] lines.append(f'| {labels[name]} | {span(g,"KC_active")} | {span(g,"KC82312_late_spikes")} | {span(g,"VA6_late_spikes")} |') lines += ['', '晚期窗口是 500–1,000 ms。每个气味沿用 seed 131101;总计 24 组诊断、4 次精确重复,不是 5-seed 学习验证。', '', '## 能说明什么', '', f'仅 calyx 接触阻断后,目标 KC82312 的晚期 spikes 范围为 {span(ca,"KC82312_late_spikes")},VA6 仍为 {span(ca,"VA6_late_spikes")}。', f'同时 KC 总活跃数降为 {span(ca,"KC_active")},原模型为 {span(ref,"KC_active")}。不能把这种接近静默的结果包装成编码或记忆改善。', '即使两个局部阻断条件的事件序列相同,其电压/g 仍可能不同;逐气味比较见 narrow-vs-allKC.csv。', '宽范围 PN 快速输出阻断影响反馈路径,不等价于局部 calyx 调制;它也没有关闭 PN 胞体或 frozen graded APL 的 PN 输入。', '', '## 仍不能说明什么', '', '完全阻断只是工程极端对照,不是 APL 的实测释放抑制比例。没有估计 receptor、bouton、局部扩散范围或时间常数。', '未使用 held-out 气味,未检验学习、保持、反转或动作输出。不得选取这次“让活动更少”的操作作为正式脑模型。', '剩余 late KC 的 ID/type 已单独保存;其来源不能仅凭残余计数猜测。', '', '## 下一步边界', '', '先继续 upstream feedback / compartment-specific physiology 的证据约束,区分 calyx 信号传递与触角叶持续放电,再确定可检验的局部动力学假设。', '不能靠增加整体抑制、调 eta/threshold、扩大 plastic mask 或让 KC 全静默宣称修复。当前不进入学习、游戏或视觉。', '', '相关原始研究:[Prisco et al., 2021](https://doi.org/10.7554/eLife.74172)、[Amin et al., 2020](https://doi.org/10.7554/eLife.56954)。它们支持研究局部 APL 作用,不直接验证本实验完全阻断幅度。', '', '顶层验收仍为 5/10(50%);本轮推进的是机制定位与可重复诊断能力。最终完整性状态见 regression/final-integrity.json。'] with (EXP/'INTERPRETATION.zh.md').open('x', encoding='utf-8') as out: out.write('\n'.join(lines)+'\n') print(result)