Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9c5918445 | |||
| d54f0b3ef1 | |||
| b33e7ea047 | |||
| 0bcce98332 | |||
| 8e9f8f4d56 | |||
| 4ff241bdc9 | |||
| c187d7d5a6 | |||
| aeccb7d0cc | |||
| de61cf14b1 | |||
| 6d5f66bcc3 | |||
| c6444255fe | |||
| cb6937666f | |||
| 8d6b063aaf | |||
| b2cd81c7b9 | |||
| 9437bbff79 | |||
| ff91a74193 | |||
| 7dfe5d2d87 | |||
| 1eb4c52cf7 | |||
| 92241aea02 |
+73
-41
@@ -1,55 +1,87 @@
|
||||
# 当前研究状态 — Phase 4AC
|
||||
# Digital Drosophila — 当前状态
|
||||
|
||||
最新结果:**COMPLETE-SEMANTICS-AUDIT / LEARNING-NOT-ACHIEVED**。
|
||||
学习可用的总目标尚未达到,不把审计 PASS 当成学习 PASS。
|
||||
初始化 README/registry 保留原字节,最新结论从本页进入。
|
||||
更新:2026-09-30。独立分支:`codex/lif-author-semantics-lab`。
|
||||
|
||||
- [中文机制结论及限制](docs/progress/phase4ac-interpretation.md)
|
||||
- [完整小回路/单细胞结果](docs/progress/phase4ac-lif-semantics.md)
|
||||
- [预先声明的诊断方案](models/male-cns-lif/lif-semantics-audit-v1/PROTOCOL.md)
|
||||
- [新阶段校验清单](registry/phase4ac-lif-semantics/seal-verification.json)
|
||||
- [上一轮末梢作用范围诊断](docs/progress/phase4ab-terminal-scope.md)
|
||||
- [小回路与全脑背景的只读复核](docs/progress/feedback-context-review-20260930.md)
|
||||
## 最近已封存结果
|
||||
|
||||
## 后续只读复核的重要限制
|
||||
Phase 4AP:**PASS-EXACT-PROCESS-ISOLATED-DELTA-INTERFACE-NOT-LEARNING**。
|
||||
|
||||
所选五节点只贡献三个 ALLN 晚期正到达权重的 7.57%–8.99%;达到各自80%正输入需要49–52个来源。
|
||||
因此孤立小回路可以自持,不意味着它是全脑唯一原因。回路外输入也可能受该回路反馈影响,不是已证明独立的外源驱动。
|
||||
对旧数据的派生分解没有新模拟或训练;正输入比例不能当因果责任比例。
|
||||
独立新模型分支尚待用户确认,不把持续目标自动当作改变冻结动力学的许可。
|
||||
12/12 个槽位完成(明确导入 1 条 AO 轨迹,新增 11 次神经运行);复用原两组输入,未重新抽样。
|
||||
6/6 reference/light 比较与 6/6 跨 arm 对照精确通过;D0/frozen 保持零更新,D1 两轮分别有 90/79 条目标 compartment 边更新,跨 compartment 为 0。第二轮实际安装第一轮 90 条非零 delta。旧 28 套完整性与 AO/诊断/AN/AM 封存前后通过,原 AO 失败记录不变。
|
||||
|
||||
## 新增机制证据
|
||||
40/40 服务器接口测试及 14/14 发布工具测试通过;无 NaN/Inf。仍没有 A/B 功能学习评估或 learned candidate,两个 A epoch 的总放电数也没有展示对照差异。下一步集中完成 retention 更新边界、完整学习门与端到端预算准备,再进入真实正常 acquisition;不再以接口 PASS 替代学习。
|
||||
|
||||
只施加一次初始 spike,使用原有冻结动力学:
|
||||
[AP 正式结果与下一步](docs/progress/phase4ap-interface-status.md) · [正式状态](registry/phase4ap-conditioning-interface-v2/evidence/audit/final-status.json)
|
||||
|
||||
| 真实小回路 | 1 s 内总 spike | 500–1,000 ms spike |
|
||||
|---|---:|---:|
|
||||
| 2 个 ALLN | 473 | 238 |
|
||||
| 3 个 ALLN | 1,064 | 535 |
|
||||
| 3 ALLN + VA6 PN + KC | 1,483 | 747 |
|
||||
| 真实前向边对照 | 3 | 0 |
|
||||
| 无初始刺激 | 0 | 0 |
|
||||
## 前一项接口中止
|
||||
|
||||
最小回路:body 11478 → 11620(235 contacts),11620 → 11478(283 contacts),两者为 lLN1_bc / ACh。
|
||||
单次输入足以在隔离小回路中产生持续到观察期末的活动;不代表已证明全脑必要性,也不代表真实果蝇该回路必然如此。
|
||||
Phase 4AO:**FAIL-POSTPROCESS-MODULE-IDENTITY**,学习效果尚未判断。
|
||||
|
||||
## 原作者实现与本项目适配差异
|
||||
登记 12 条两轮 delta 衔接验证,仅第一条 reference 神经轨迹完成并封存(120 ms、36,539 spikes、无 NaN/Inf);随后因两个同源 loader 的缓存归属冲突停止,完整流程完成数为 0/12。未重跑轨迹、未覆盖源码、未启动完整 conditioning。
|
||||
|
||||
固定作者代码在 spike 后清零 g,对不应期内 g 更新/输入设限,并有不同积分、阈值和刺激时序。
|
||||
本项目冻结版本明确保留 g,继续衰减/接收输入。这是建模语义差异,不是本轮发现的 GPU 回归错误。
|
||||
单细胞组件测试证明这些选择会改变响应,但真实连接强度的一次输入下,reset-only 并未改变 spike 数。
|
||||
因此 **g reset 不得直接称为学习修复**,也不能仅据放电减少自动选择新模型。
|
||||
独立 CPU 进程后处理诊断已通过:同一轨迹产生 90 条目标 compartment 非零 delta,跨 compartment 改动为 0;未安装新权重,不能据此认定学习成功。AN/AM 封存与 28 套旧完整性再次通过。下一步单独登记进程隔离协调器,不能恢复或改写本次已封存 AO。
|
||||
|
||||
## 验证与下一步
|
||||
[AO 结果、原因与后续边界](docs/progress/phase4ao-interface-status.md) · [正式失败状态](registry/phase4ao-conditioning-interface/evidence/audit/final-status.json)
|
||||
|
||||
7 项单元测试、17×4 单细胞 fixture、5 个真实小回路 CPU/GPU 全状态/事件精确对照通过。
|
||||
五组 deterministic-v2 全脑冻结参考 trial 精确一致。
|
||||
原快照 230,239 项及 Phase 4AA / 4AB 模型与实验前后校验无变化。
|
||||
新产物有独立 manifest/SHA-256;未训练、未生成 learned candidate。
|
||||
ComfyUI 保持驻留,不停用、不卸载、不升级依赖。
|
||||
## 前一项工程验证
|
||||
|
||||
后续需要明确授权的独立动力学模型分支:先验证原作者 oracle 和局部机制,再检查输入区别与学习功能。
|
||||
仍要求独立 evaluation seeds、A 对 B 的特异性、MBON/downstream readout 和完整 conditioning controls。
|
||||
不调整学习率/阈值或扩大 mask,不进入游戏、RL、decoder、视觉;不修改已冻结版本。
|
||||
Phase 4AN:**PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING**。
|
||||
|
||||
Git 仅同步代码、小报告、官方小型源代码及校验清单。大数据/完整 spike 输出仍在服务器,不等于完整异机备份。
|
||||
固定 zero/static25 输入下,32/32 次运行及 74 项比较通过;分段续跑保留精确事件、末态和延迟队列,独立 CSR 审计覆盖真实非零跨段到达。AM 双封存及 28 套旧完整性记录再次通过。没有训练,未生成 learned candidate;结论只覆盖注册的 100 ms 输入及固定切点,不是普遍动力学证明。
|
||||
|
||||
light 的 100 ms 核心模拟约 2.15–2.19 s,单条输出约 2.60 MB,尚未达到实时。整个 32-call 执行流程约 433.97 s;首次编译、校验和保存都包含在该流程时间内。
|
||||
|
||||
[AN 结果及预算边界](docs/progress/phase4an-interpretation.md)。注册 SHA:`7317441126f191ccc6e6d28438870b3921537ebe282a6acb163716a690057945`;实验 manifest SHA:`e8952a48cd3ea6a93c811a1183a1f7b79e19646299924e7688e5f393737a9aa2`。
|
||||
|
||||
## 全脑固定权重接口
|
||||
|
||||
Phase 4AM:**PASS-EXACT-WHOLEBRAIN-PLASTIC-INTERFACE-NOT-LEARNING**。
|
||||
|
||||
独立建立 D18 whole-brain plastic-overlay 接口,不覆盖或替换旧 reference/runtime。
|
||||
75/75 服务器单元测试通过;完整性链 28 个根运行前后全部通过,五组旧参考重新运行全部精确。
|
||||
预注册 SHA-256:`465886b78ada08092c2c83de278844707499718977fb6dc6a7608391da0e5304`。
|
||||
|
||||
- 零 delta 门完成:120 个固定主输入及 24 次技术重复,共 **144/144 与 Phase 4AK D18 精确一致**。
|
||||
- 同一库存 static25 的 144 次闭环运行完成,24 次技术重复精确一致;真实反馈重新计算,不钳制源事件。
|
||||
- static25 是原 float32 候选权重的 25% floor 经明确 SI 精度桥接,不是学习结果,也不声称等于 float64 基础权重的精确 25%。
|
||||
- 120/120 主试验的 MBON、实际 downstream、descending、motor 事件序列及逐细胞计数有变化;12 个 mask 目标的计数变化为 119/120、事件变化为 120/120;KC 事件变化 120/120、计数变化 32/120。这不是每个细胞都变化,也不是学习或行为 PASS。
|
||||
- 两阶段都不训练,未产生 learned candidate。zero/static25 阶段墙时分别约 1665.34/1471.04 秒;主试验最大已分配显存 526,523,392 bytes。
|
||||
|
||||
运行目录:`experiments/phase4am-wholebrain-plastic-interface/`。
|
||||
[AM 结果解释](docs/progress/phase4am-interpretation.md) · [正式状态](registry/phase4am-wholebrain-plastic-interface/evidence/audit/final-status.json) · [预注册设计](models/male-cns-lif/wholebrain-plastic-interface-v1/PROTOCOL.md)
|
||||
|
||||
## 前序结论保持不变
|
||||
|
||||
Phase 4AL:**PASS-CONDITIONAL-CREDIT-TRANSFER-AUDIT-NOT-LEARNING**。
|
||||
|
||||
固定 D18 的 120 条主轨迹及 24 次重复全部 MBON 基线逐位复现;60 组气味对都有各自特有的 KC/候选边。固定 25% 减弱在 120/120 主试验中改变了 MBON count 或首次 latency,但那是钳制源事件的条件性回放,不是真实全脑反馈、不代表气味特异记忆或响应上界。
|
||||
|
||||
14,232 条 mask 边、12 个 mask-recipient MBON 和全部 97 个 MBON 保留。
|
||||
Phase 4AK D18 表征验证 PASS 原样保留。1.8 ms 为工程 APL 时序假设,不是实测延迟;matched_energy 匹配预期而非每次实际事件数;旧 1212 条非 KC fast-APL 出边未被 graded 路径替代的限制仍存在。
|
||||
|
||||
[AL 结果解释](docs/progress/phase4al-interpretation.md) · [AL 正式状态](registry/phase4al-mb-credit-transfer/evidence/audit/final-status.json) · [AK 结果解释](docs/progress/phase4ak-interpretation.md)
|
||||
|
||||
## 当前准备与学习边界
|
||||
|
||||
Phase 4AN 的有限工程门已封存;21 份实际使用源码已按 SHA 锁定。AO 进一步登记了包含科学准备代码的 51 份源快照,实验以接口失败封存。AP 使用独立目录的 5 份新源码完成进程隔离门,旧 51 源不变;lab 整个目录仍未封存,完整科学条件化协议尚未登记执行,没有学习候选。
|
||||
|
||||
评估清单和完整条件化政策仍是科学草案;完整 63 位输入、单轮更新桥及 fresh-reset 跨轮 delta 实际安装已通过有限 AO/AP 证据链,不能等同于完整学习。历史准备记录见[条件化准备进度与限制](docs/progress/conditioning-preparation-2026-09-30.md),当前接口结果以 AP 报告为准。
|
||||
|
||||
服务器 CPU 合成测试 119/119 通过,0 跳过;21 个 AN 注册源测试前后 SHA 均未变。当前续跑器禁止父子段更换 overlay,因此“保留末态并使用新 delta”的 retention 更新边界仍需新增接口验证,不是现有工程 PASS 已覆盖的能力。
|
||||
|
||||
完整 conditioning 草案为 68,000 epochs、10,760 生物秒(含 idle),新显式评估政策再列出 9,440 次 probes / 1,132.8 生物秒,总计 11,892.8 生物秒。这是未执行的条件性计划库存,不是工期预算。真实 CUDA 输入检查在 AO 通过,后处理冲突由独立 AP 接口解决。仍需反转/连续保持功能门、实际末态与新 delta 衔接及完整端到端资源预算,不直接启动大批量训练。
|
||||
|
||||
目标仍是“学习可以使用”,当前没有达到。接口验证或静态权重效果不能替代真实条件化。
|
||||
后续须预注册固定 olfactory A/B、具体 DAN type/compartment、至少五个互不重叠训练种子流及独立 evaluation streams;保留原 eligibility-v2、人工 US 与实际 DAN 放电的明确区分,以及配对/控制、反转、延迟奖励、保持等功能验证。
|
||||
|
||||
继续冻结 raw/base CSR/W_syn/sign、既有 APL 参数、eligibility-v2、14,232-edge mask 和全部已封存产物。不调参挽救结果,不接游戏、视觉、decoder、RL 或 Web。
|
||||
|
||||
AM 已正式封存并在服务器发布 3,020 份小证据;本地已按发布收据逐项验收 3,040 个文件(含控制与清单文件)。AN 的 599 个小证据文件已逐项本地验收;620 个实际 Git blobs 校验通过并推送为 `aeccb7d0ccdcf6788e0a8de60e7ab96f70dbe734`。Git 同步范围仅为源码、文档及小证据。大型轨迹保留在服务器,小证据同步不等于全量异机备份。
|
||||
|
||||
模型 manifest SHA-256:`7edeb251ecbeac5a64805fefe171a0a0bf8bba2675919ef922cc1a329c203ea8`。
|
||||
实验 manifest SHA-256:`9e24b682935a9b18e05db7a933080c5f63ddbeb8a0c85cc0df1cfe1d06d88ab4`。
|
||||
正式状态 SHA-256:`18b12c832e156bd6e5ba950f48a49fb908c62104b91bb3e78213f523c8fdc8f9`。
|
||||
|
||||
AP 新登记源码 commit:`0bcce983324ecfe1c75826a23e94ff932ad5e883`。
|
||||
AP 实验 manifest SHA:`a40e433541a6c02f7ad95622e96646da9a90a93e4e43cb3abfde3846b2554f00`。
|
||||
AP registry 共 356 份小文件已本地逐项 SHA 验收。Git 只同步源码、文档和小证据,不包含服务器大型轨迹。
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# 独立条件化分支:准备进度与边界
|
||||
|
||||
状态:**未注册的科学实现草案,不是 learning PASS,不是 learned candidate**。
|
||||
|
||||
已完成的 Phase 4AN 工程证据另行封存并推送至
|
||||
`aeccb7d0ccdcf6788e0a8de60e7ab96f70dbe734`:32/32 次 GPU 运行、74 项精确比较通过。
|
||||
599 个小证据文件已下载到本地逐项验收,共 5,508,335 bytes;620 个实际 Git blobs
|
||||
(含 21 个注册源文件)核对通过。大体积轨迹仍在服务器,不是全量异机备份。
|
||||
|
||||
## 本次补齐的实现
|
||||
|
||||
1. 评估清单:完整重建训练计划和独立评估种子来源;同一训练重复内复用 A/B
|
||||
评估输入作配对比较,不能把反复 probe 当成更多训练重复。
|
||||
2. 显式评估政策:CS 为 0–100 ms,观察到 120 ms;保留 0/1/2/5/10/20/50
|
||||
检查点。控制组核对实际有序 CS 前缀及源种子,不只核对数量。
|
||||
3. 输入接口:保留原离散 float32 Bernoulli 抽样、PN 列顺序和完整 63 位种子;
|
||||
必须指定 CUDA 设备和抽样上限。CS/US 重叠默认报错,显式布尔合并才允许并记录冲突。
|
||||
4. 单轮更新桥:复用固定 SHA 的 AM/J 原实现,从本轮真实稀疏事件计算 eligibility,
|
||||
在完整 epoch 结束后返回下一轮 delta;只计算,不安装权重,不改 base。
|
||||
|
||||
这里的输入单元测试使用 CPU/mock,不代表已经实测 CUDA 随机序列或高位种子的作用。
|
||||
单轮更新测试使用合成事件,不是已经运行条件化。人工 D 指令与实际 DAN 放电仍是两项证据。
|
||||
attempted update、实际 delta 改变和已达 depression floor 分别统计。
|
||||
|
||||
服务器 CPU 合成测试:**119/119 通过,0 跳过**,测试框架记录耗时 241.205 s。
|
||||
其中包含原 Torch CPU eligibility 表达式与 NumPy 路径的合成数值比较,不是 CUDA 比较。
|
||||
运行时隐藏全部 CUDA 设备;没有真实脑输入、网络模拟或 conditioning。21 个 AN 注册源在
|
||||
测试前后均另行校验保持原 SHA。
|
||||
|
||||
## 对齐不等于所有条件相同
|
||||
|
||||
非配对控制的中间奖励次数、经过时间可以与配对组不同;随机奖励的中间奖励次数也可能不同。
|
||||
这些差异单独记录,不称为全量匹配。DAN-only 的 CS 被有意移除,不能声称 CS exposure 相同。
|
||||
反转两臂各按自己的强化机会取点,但保留原始 acquisition 的气味方向与 pre baseline。
|
||||
所有检查点都在完整 epoch 及其边界更新之后,不是在 US tick 当场插入 probe。
|
||||
|
||||
该政策条件性列出 9,440 次评估(1,132.8 生物秒),加已有训练/控制/连续 idle 共
|
||||
11,892.8 生物秒;这是完整草案的计划库存,不是已执行数量,也不是工期预算。
|
||||
完整批量训练尚未启动。
|
||||
|
||||
## 下一步仍然缺少
|
||||
|
||||
- 把每个真实输入、delta-before/after、状态和队列绑定外部 SHA 与祖先记录的完整协调器。
|
||||
- 在指定 4090 上预先登记有限输入/更新衔接验证,实测完整种子、参考一致性和实际开销。
|
||||
- 完成反转与连续 retention 的功能门实现,并核算完整存储、时间上限和停止条件。
|
||||
- retention 还有明确接口缺口:当前冻结续跑器要求子段 overlay 与父检查点完全相同。
|
||||
最后训练 epoch 的末态属于 delta-before,边界更新却产生 delta-after;不能把这组
|
||||
“原神经末态 + 新权重”伪装成已经验证的同权重续跑。必须在新文件中明确实现并有限验证
|
||||
更新边界的状态交接,不能修改封存续跑器来绕过约束。
|
||||
- 以上通过后才登记执行 acquisition 及全部控制;若失败,保留失败证据,不通过改参数补救。
|
||||
|
||||
全部新增科学代码仍是 DRAFT;源文件清单与测试记录保存于
|
||||
`models/male-cns-lif/olfactory-conditioning-lab-v1/draft-source-inventory.json`。
|
||||
该记录不是实验注册或目录封存。既有 21 个 AN 注册源按其原清单单独核验,不纳入新草案清单。
|
||||
不运行游戏、RL、decoder、视觉、Web 或 3D;不改 frozen reference/runtime、图、符号、
|
||||
W_syn、APL 标定、eligibility-v2 或 14,232-edge mask。
|
||||
@@ -0,0 +1,71 @@
|
||||
# Phase 4AD 作者动力学语义实验室
|
||||
|
||||
状态:`COMPLETE-ORACLE-VALIDATED-LAB-ONLY`。
|
||||
|
||||
本轮仅有预声明的小回路与算子实验;没有整脑替代动力学运行、conditioning、学习成功或新学习候选。
|
||||
检查以原始事件、状态轨迹和预注册误差界限重算。缺失、失败、非有限值或初始事件丢失均不能通过;放电减少或静默本身不构成成功。
|
||||
|
||||
核查通过 58/58 项;失败或不完整 0 项。
|
||||
冻结历史完整性:通过。
|
||||
源码与输入预注册一致性:通过。
|
||||
|
||||
| 回路 | 语义 | 精度 | 总spikes | late spikes |
|
||||
|---|---|---|---:|---:|
|
||||
| pair | frozen | float32 | 473 | 238 |
|
||||
| pair | reset_g_only | float32 | 223 | 111 |
|
||||
| pair | refractory_gate_only | float32 | 635 | 321 |
|
||||
| pair | reset_and_refractory | float32 | 223 | 111 |
|
||||
| trio | frozen | float32 | 1064 | 535 |
|
||||
| trio | reset_g_only | float32 | 565 | 283 |
|
||||
| trio | refractory_gate_only | float32 | 1100 | 552 |
|
||||
| trio | reset_and_refractory | float32 | 469 | 235 |
|
||||
| five | frozen | float32 | 1483 | 747 |
|
||||
| five | reset_g_only | float32 | 868 | 433 |
|
||||
| five | refractory_gate_only | float32 | 1510 | 763 |
|
||||
| five | reset_and_refractory | float32 | 634 | 318 |
|
||||
| feedforward | frozen | float32 | 3 | 0 |
|
||||
| feedforward | reset_g_only | float32 | 3 | 0 |
|
||||
| feedforward | refractory_gate_only | float32 | 3 | 0 |
|
||||
| feedforward | reset_and_refractory | float32 | 3 | 0 |
|
||||
| no_input | frozen | float32 | 0 | 0 |
|
||||
| no_input | reset_g_only | float32 | 0 | 0 |
|
||||
| no_input | refractory_gate_only | float32 | 0 | 0 |
|
||||
| no_input | reset_and_refractory | float32 | 0 | 0 |
|
||||
| pair | author_complete | float64 | 213 | 106 |
|
||||
| pair | author_complete | float32 | 213 | 106 |
|
||||
| trio | author_complete | float64 | 448 | 224 |
|
||||
| trio | author_complete | float32 | 448 | 224 |
|
||||
| five | author_complete | float64 | 610 | 306 |
|
||||
| five | author_complete | float32 | 610 | 306 |
|
||||
| feedforward | author_complete | float64 | 3 | 0 |
|
||||
| feedforward | author_complete | float32 | 3 | 0 |
|
||||
| no_input | author_complete | float64 | 0 | 0 |
|
||||
| no_input | author_complete | float32 | 0 | 0 |
|
||||
|
||||
作者Brian2与独立NumPy比较:
|
||||
|
||||
| 实验 | 结果 | 事件一致 | 最大V/g误差 mV | 首次事件分歧step |
|
||||
|---|---|---|---:|---:|
|
||||
| author:pair:float64 | 通过 | True | 7.105427357601002e-14 | None |
|
||||
| author:pair:float32 | 通过 | True | 2.41696834564209e-05 | None |
|
||||
| author:trio:float64 | 通过 | True | 8.526512829121202e-14 | None |
|
||||
| author:trio:float32 | 通过 | True | 4.25875186920166e-05 | None |
|
||||
| author:five:float64 | 通过 | True | 3.836930773104541e-13 | None |
|
||||
| author:five:float32 | 通过 | True | 0.0002123713493347168 | None |
|
||||
| author:feedforward:float64 | 通过 | True | 3.765876499528531e-13 | None |
|
||||
| author:feedforward:float32 | 通过 | True | 0.00020179152488708496 | None |
|
||||
| author:no_input:float64 | 通过 | True | 7.105427357601002e-15 | None |
|
||||
| author:no_input:float32 | 通过 | True | 1.0728836059570312e-06 | None |
|
||||
|
||||
float32与float64交叉比较仅记录精度影响,允许保留分歧,不能用于掩盖各自与独立oracle的失败。
|
||||
- precision_cross_record:pair:事件一致=True,最大误差=3.733467101341148e-05 mV,首次事件分歧step=None。
|
||||
- precision_cross_record:trio:事件一致=True,最大误差=3.733467101341148e-05 mV,首次事件分歧step=None。
|
||||
- precision_cross_record:five:事件一致=True,最大误差=0.00019546693161487383 mV,首次事件分歧step=None。
|
||||
- precision_cross_record:feedforward:事件一致=True,最大误差=0.0005722045887850413 mV,首次事件分歧step=None。
|
||||
- precision_cross_record:no_input:事件一致=True,最大误差=0.0 mV,首次事件分歧step=None。
|
||||
|
||||
所有失败轨迹和错误文件原样保留;本简报没有选择获胜变体。
|
||||
小回路缺少外部整脑输入与graded APL,不能证明整脑因果必要性、生物真实性或信息保持。初始诊断seed也不是作者的感官Poisson接口。
|
||||
环境为Linux宿主,作者记录为Windows;Python补丁版本与环境差异见environment记录,不能称为二进制相同或原论文整脑复现。
|
||||
后续整脑嗅觉比较仍须核查固定输入、实际送达事件、持续活动和信息保持;可用学习仍需至少5个训练seed、独立评估、MBON/downstream的A对中性B特异性及acquisition/reversal/retention/delayed-reward对照。
|
||||
封存排除 .venv 与 __pycache__,保留环境安装报告;manifest列出payload哈希,checksums另含manifest,不存在自哈希循环。
|
||||
@@ -0,0 +1,54 @@
|
||||
# 独立模型分支首轮结果:实现正确,不等于学习修复
|
||||
|
||||
用户明确授权独立新模型分支测试动力学假设。分支为 `codex/lif-author-semantics-lab`,
|
||||
模型目录 `models/male-cns-lif/author-semantics-lab-v1/`,实验目录 `experiments/phase4ad-author-semantics/`。
|
||||
Phase 1–4AC 保持原样;没有替换已有候选、训练、调学习率、扩大mask或连接游戏。
|
||||
|
||||
## 已验证的结果
|
||||
|
||||
正式状态 `COMPLETE-ORACLE-VALIDATED-LAB-ONLY`,58/58审计项通过。
|
||||
实际运行固定作者代码的 `create_model`,并与独立解析积分实现对照,而非仅比较两份自写程序。
|
||||
8项算子/边界fixture各测试float64与float32;5个固定真实motif各测试两种精度,分别重复。
|
||||
各精度内的spike事件、计数和不应期逐步一致,重复运行逐数组精确一致。
|
||||
五个motif的float32与float64事件也一致,但不能外推到全脑高入度累加。
|
||||
float64最大V/g差约3.84e-13 mV;float32独立实现相对Brian2最大差约2.12e-4 mV。
|
||||
|
||||
| 真实回路 | 旧冻结模型1秒总spike | 完整作者语义总spike | 作者语义后500ms spike |
|
||||
|---|---:|---:|---:|
|
||||
| 两个ALLN | 473 | 213 | 106 |
|
||||
| 三个ALLN | 1,064 | 448 | 224 |
|
||||
| 三ALLN + VA6 PN + KC | 1,483 | 610 | 306 |
|
||||
| 前向真实边对照 | 3 | 3 | 0 |
|
||||
| 无刺激 | 0 | 0 | 0 |
|
||||
|
||||
**持续循环没有因恢复作者语义而消失。** 放电减少不是学习成功,也没有据此挑选获胜参数。
|
||||
单独限制不应期内g更新甚至提高了这些回路的放电;不能把单一算子改动当作通用修复。
|
||||
小回路省略了大部分全脑输入和graded APL,不能证明全脑问题完全由这些连接引起。
|
||||
此前全脑背景复核显示,三个ALLN的晚期正到达权重约91%以上来自所选五节点之外;这也不是因果责任百分比。
|
||||
|
||||
## 作者参考及边界
|
||||
|
||||
- 官方代码:`philshiu/Drosophila_brain_model`,commit `91bdd1e7dcf193f3e7ca5a8933497fcef63b7960`,MIT。
|
||||
- [原作者论文](https://www.nature.com/articles/s41586-024-07763-9);[固定代码版本](https://github.com/philshiu/Drosophila_brain_model/tree/91bdd1e7dcf193f3e7ca5a8933497fcef63b7960)。
|
||||
- Brian2 2.5.1,独立Linux环境;作者记录为Windows/Python3.10.11,此处Linux/Python3.10.12,不能称为二进制相同。
|
||||
- 原始g单位为volt,是加性输入状态,不是电导;reset中的局部`w=0`不会清零真实Synapses权重。
|
||||
- 原`poi`接口另做了零时长编译审计:向v加入68.75mV、150Hz、N=1,并将目标不应期置零。它不是本轮共同初始seed,也不是已验证的新嗅觉接口。
|
||||
- 安装只发生在新实验`.venv`,保存下载URL与归档SHA-256;MarkupSafe1.1.1使用源归档,其他核心包使用wheel。未改旧科学环境或ComfyUI。
|
||||
|
||||
## 下一关
|
||||
|
||||
1. 独立GPU实现须补足高入度、正负抵消、逐边加法顺序、非零初始g、阈值临界值与延迟队列边界测试;禁止用`g+sum(w)`擅自代替作者逐边写入。
|
||||
2. 在短时全图上对照事件及状态,再开展固定DoOR嗅觉CAL。保持既有输入请求,不顺手切换为作者的rfc=0输入接口;分别统计请求、因不应期拒绝与实际放电。
|
||||
3. CAL通过后才能使用独立seed及新odor验证。不能靠低活跃度或held-out调参授予成功。
|
||||
4. 真正conditioning须重新执行D(t)=0、frozen与随机奖励路径,并真正推进retention等待时间;旧脚本中复用基线/新建状态的检查不能替代这些端到端验证。
|
||||
5. 全部学习结论仍以MBON/downstream的刺激特异性、acquisition/reversal/retention/delayed reward为准,至少5个training seeds与独立evaluation seeds。
|
||||
|
||||
## 完整性和保存
|
||||
|
||||
原快照230,239项及4AA、4AB、4AC、背景复核产物前后SHA-256无变化。
|
||||
旧deterministic-v2五个全脑冻结trial重新执行,事件、计数、最终V/g逐数组相同。
|
||||
4090回归峰值已分配显存285,994,496字节;这不是新动力学全脑性能benchmark。
|
||||
新模型11个payload、新实验565个payload已独立封存,环境目录不纳入payload。
|
||||
Git同步代码、小报告和校验清单;大数据及完整状态轨迹仍在服务器,不能称为完整异机备份。
|
||||
|
||||
详见[完整结果](phase4ad-author-semantics.md)、[封存核验](../../registry/phase4ad-author-semantics/seal-verification.json)。
|
||||
@@ -0,0 +1,37 @@
|
||||
# Phase 4AE GPU作者语义数值门禁
|
||||
|
||||
状态:`PASS-ISOLATED-GPU-NUMERICAL-GATE`。
|
||||
|
||||
仅验证固定小网络,尚未证明全脑等价、生物学正确或学习可用。未训练、未修改冻结输入或旧模型。
|
||||
|
||||
| case | precision | events | max V/g error V | exact whole history | pass |
|
||||
|---|---|---:|---:|---|---|
|
||||
| fanin_bursts | float64 | 1539 | 0.0 | True | True |
|
||||
| fanin_bursts | float32 | 1539 | 0.0 | True | True |
|
||||
| highest_indegree | float64 | 101 | 0.0 | True | True |
|
||||
| highest_indegree | float32 | 101 | 0.0 | True | True |
|
||||
| mixed_high_indegree | float64 | 106 | 0.0 | True | True |
|
||||
| mixed_high_indegree | float32 | 106 | 0.0 | True | True |
|
||||
| motif_feedforward | float64 | 3 | 0.0 | True | True |
|
||||
| motif_feedforward | float32 | 3 | 0.0 | True | True |
|
||||
| motif_five | float64 | 60 | 0.0 | True | True |
|
||||
| motif_five | float32 | 60 | 0.0 | True | True |
|
||||
| motif_no_input | float64 | 0 | 0.0 | True | True |
|
||||
| motif_no_input | float32 | 0 | 0.0 | True | True |
|
||||
| motif_pair | float64 | 22 | 0.0 | True | True |
|
||||
| motif_pair | float32 | 22 | 0.0 | True | True |
|
||||
| motif_trio | float64 | 45 | 0.0 | True | True |
|
||||
| motif_trio | float32 | 45 | 0.0 | True | True |
|
||||
| ordered_cancellation | float64 | 32 | 0.0 | True | True |
|
||||
| ordered_cancellation | float32 | 32 | 0.0 | True | True |
|
||||
| release_selfloop | float64 | 8 | 0.0 | True | True |
|
||||
| release_selfloop | float32 | 8 | 0.0 | True | True |
|
||||
| signed_random | float64 | 400 | 0.0 | True | True |
|
||||
| signed_random | float32 | 400 | 0.0 | True | True |
|
||||
| threshold_rounding | float64 | 4 | 0.0 | True | True |
|
||||
| threshold_rounding | float32 | 3 | 0.0 | True | True |
|
||||
|
||||
每项GPU执行3次,检查原始事件、输入请求实际送达、逐目标到达顺序及guard、计数、不应期和完整状态。
|
||||
大整数、随机图、重复/零边属于纯数值夹具,不是新增MaleCNS连接或生理假设。真实子图使用全部既有诱导边、原contact count与冻结transmitter sign。
|
||||
原始冻结完整性与旧五组全脑deterministic-v2回归再次核验;该旧回归PASS不等于新动力学全脑PASS。
|
||||
没有根据活动强度选择参数;尚须短时全图对照及嗅觉信息保持测试。
|
||||
@@ -0,0 +1,51 @@
|
||||
# Phase 4AE:独立GPU后端通过固定数值压力集
|
||||
|
||||
状态:`PASS-ISOLATED-GPU-NUMERICAL-GATE`。独立分支仍为 `codex/lif-author-semantics-lab`。
|
||||
这不是 learned candidate,也不是全脑或长期动力学等价声明。
|
||||
|
||||
## 具体推进
|
||||
|
||||
- 新增双精度CUDA后端,按已验证作者表达式逐次舍入,禁用FMA和fast-math。
|
||||
- 保留精确18-step delay与22-step refractory;只展开当时放电源的真实出边,不逐步扫描全部edge。
|
||||
- 突触从目标当前g开始逐条累加,不能先求边权和再加;刚放电的目标拒绝同tick输入。
|
||||
- 将实际请求、允许注入、不应期拒绝分开统计。没有采用作者poi接口的rfc=0策略。
|
||||
- Brian2实际队列由只读代理观测;每个oracle再做无代理复跑,证明观测工具没有改变输出。
|
||||
|
||||
固定12组case × float64/float32 × 3次GPU执行,**72/72通过**。
|
||||
全部状态history逐bit一致,V/g最大绝对差为0;事件、计数、不应期、请求送达、逐目标到达顺序和接受标记均一致。
|
||||
三次GPU重复逐数组相同,24组Brian oracle的观测/无观测对照均通过。
|
||||
这些是整个短轨迹的对照,不是仅比较总spike数或末态。
|
||||
|
||||
## 覆盖范围和未覆盖项
|
||||
|
||||
5个合成夹具覆盖有序正负抵消、256源fan-in、同tick输入与不应期释放、自环/队列回绕、阈值可表示数邻域,以及固定seed随机有符号网络。
|
||||
极大整数、重复/零边只用于明确标记的数值夹具,不是真实MaleCNS新连接或生理参数。
|
||||
|
||||
2个真实128-neuron诱导子图按入度和原contact count选取,保持所有真实内部边与sign。
|
||||
两者中心都是全图最高入度节点neuron_idx=5/body_id=10009/type=CT1,入度11,526;伙伴组成不同。
|
||||
这是数值压力测试,不是两个独立中心的证据,也不是视觉编码实验。
|
||||
另有原4AD五个固定真实motif的100ms前缀。还没有覆盖所有全图累加场景和长时间反馈。
|
||||
|
||||
## 精度差异不能隐藏
|
||||
|
||||
作者float64与作者float32在阈值临界夹具产生4次和3次事件,首次分歧在tick0。
|
||||
两套GPU分别精确复制了各自参考,但**float32不能因此称为float64的等价加速**。
|
||||
本轮没有放宽事件门槛,没有根据活动强度选模型。下一次全图首先使用作者默认float64,以减少精度混淆。
|
||||
[完整交叉精度记录](../../registry/phase4ae-author-runtime/cross-precision-description.json)。
|
||||
|
||||
## 下一步与学习门槛
|
||||
|
||||
下一关:166,700节点/25,582,938边完整图的短时float64逐步对照:无输入10ms、固定单seed50ms、固定五seed50ms。
|
||||
采用连续运行和流式状态记录,不保存dense spike矩阵;比较实际队列摘要及所有细胞的每tick状态。
|
||||
通过后才进行固定DoOR嗅觉CAL,并独立审计原graded APL与新语义的组合。
|
||||
只有信息保持、held-out区分和真实conditioning功能/对照通过,才能讨论学习候选;不能从本轮数值PASS跳到学习PASS。
|
||||
|
||||
## 完整性与运行信息
|
||||
|
||||
原230,239项及4AA–4AD、背景复核前后hash检查通过,旧五个deterministic-v2全脑参考再次精确一致。
|
||||
新模型11个payload、新实验916个payload独立封存。ComfyUI未修改,科学环境未升级。
|
||||
72次GPU诊断总wall约15.93s;诊断峰值allocated VRAM约5.45MB。
|
||||
这些数值来自小图并包含诊断同步,**不是全脑性能或实时性承诺**;旧全脑回归峰值约286MB也不是新动力学性能。
|
||||
大数据/完整状态只留服务器;Git同步源码、小报告、出处与checksum,不等于完整异机备份。
|
||||
|
||||
详见[完整实验报告](phase4ae-author-runtime.md)、[预声明](../../models/male-cns-lif/author-runtime-lab-v1/PROTOCOL.md)、[封存校验](../../registry/phase4ae-author-runtime/seal-verification.json)。
|
||||
@@ -0,0 +1,53 @@
|
||||
# 下一关:author LIF + frozen legacy graded APL 的显式兼容审计
|
||||
|
||||
本文件为只读设计审计,未执行新APL试验、CAL或训练,不属于4AF数值PASS的覆盖范围。
|
||||
|
||||
## 已核源码与历史参数
|
||||
|
||||
旧实现:`models/male-cns-lif/phase4p_revalidate.py`,SHA256
|
||||
`c7e693a2da70acdce4f53af5c2853e52e8333acc93f88a46a5e8083eac639346`。
|
||||
固定配置:`models/male-cns-lif/phase4l.yaml`,SHA256
|
||||
`9b8bcb7e031bf9063033a1854a439a7cd0bf6730301c2d2d02ca99a76f098d6e`。
|
||||
|
||||
根代理只读检查服务器历史锁文件:
|
||||
`experiments/phase4j-mb-physiology-repair/calibration/locked-parameters.json`,574B,SHA256
|
||||
`1894cc4b4082ba9b2d069edf87c5eccb75292aa11947ad9c48acf661037ff3e7`。
|
||||
文件明确calibration_only=true、formal_or_heldout_used_for_selection=false、parameters_locked=true。
|
||||
旧72格CAL选择gain=.01、normalization=1、tau=20ms;3个配置有效,选中mean_fraction=.0400467519685。
|
||||
本次只核锁文件及来源,没有重新运行其历史CAL,不声称这些参数在新动力学中仍已生理验证。
|
||||
|
||||
## 旧overlay实际合同
|
||||
|
||||
APL由实际KC/ALPN spike事件驱动,不读取注入请求,不以APL点神经元自身spike驱动。
|
||||
输入矩阵是KC∪ALPN→APL的正raw count;除以全部有效输入raw总和/具有任意APL输入连接的唯一source数。
|
||||
输出矩阵是APL→KC raw count,除以非零矩阵元素均值。
|
||||
两矩阵、APL状态及旧torch.mv/matmul均为float32。decay=f32(exp(-.1/20));每tick乘decay再加当前实际spikes的输入;输出乘.01,旧单位是mV/state/step。
|
||||
没有额外delay、dt/tau驱动缩放或state截顶。
|
||||
|
||||
旧顺序是LIF→实际放电/接受forcing→快边排队→KC trace→APL→无条件减KC的g。
|
||||
所有5,845条APL源快出边都被overlay清零;graded仅回填KC。已有4Z审计中1,212条非KC出边/13,712contacts/999targets没有graded回填。
|
||||
兼容审计不得顺手缩窄该mask或恢复非KC边,解剖base文件永远只读;该已知范围限制必须保留在解释中。
|
||||
|
||||
## 与新author语义的交界不能静默处理
|
||||
|
||||
最小移植假设是:完成author groups/thresholds/synapses/resets之后,在end无条件执行原APL写入,再采样状态。
|
||||
这不是作者原始APL模型,也不意味着新组合已被4AF验证。
|
||||
|
||||
1. 刚spike的KC先reset g=0,随后APL可以写入负g。
|
||||
2. refractory KC仍接收APL,而author g在refractory期间冻结,因此负g可能累积到释放时。旧后端此时g仍衰减,行为不等价。
|
||||
3. 改成guarded on_pre、检查not_refractory或挪到reset前,都属于另一个假设,不可称旧overlay原样移植。
|
||||
4. float64主状态不授权APL自动变float64。须保留旧float32矩阵/状态/算术并明确最后mV→SI转换;归约次序或精度变化另立数值合同,不能假定torch与NumPy逐bit相同。
|
||||
5. Brian赋值API是否真正实现上述无条件写入,必须以边界fixture实测;不能从API名称推断。
|
||||
|
||||
## CAL前固定fixture与门禁
|
||||
|
||||
- 零输入和独立trial reset,无跨trial残留。
|
||||
- 各一个PN/KC脉冲与双APL汇合,核对当前tick驱动、下一tick膜效应和20ms尾迹。
|
||||
- 自然spike/接受请求/拒绝请求/两者重合:每个实际事件只计一次。
|
||||
- KC reset与refractory age0/1/21/22:保存overlay前后g/ref与实际写入行为。
|
||||
- 快突触到达与APL同tick:快边guard原样、APL顺序独立。
|
||||
- KC/非KC范围、双APL高fan-in、非整数规范化和SI转换,防止1000倍单位错误。
|
||||
|
||||
真实子图保留全图归一化常数,不按截取子图重算。保存APL/KC/ALPN有序身份、全部fast edge mask、原矩阵与规范化float32字节hash、归一化常数、参数/dtype/调度及基础/overlay权重hash。
|
||||
逐tick比较实际事件、请求接受拒绝、APL drive/state/inhibition、overlay前后g/ref。新组合需要独立oracle和重复运行检查;不是要求新整网轨迹与旧模型相同。
|
||||
先语义fixture,再固定同输入CAL;不碰heldout、不调gain、不以安静或稀疏作为成功标准。只有信息保持与功能性学习门禁通过才能推进学习候选。
|
||||
@@ -0,0 +1,21 @@
|
||||
# Phase 4AF:短时完整图数值对照
|
||||
|
||||
状态:`PASS-SHORT-FULLGRAPH-NUMERICAL-GATE`。
|
||||
|
||||
完整166,700节点/25,582,938边,原synaptic-contact总数124,177,617。
|
||||
作者默认float64;新分支只读复用4AE CUDA算术与未修改作者Brian2 create_model。
|
||||
无APL叠加、无Poisson forcing、无训练。只有tick0固定请求,所有原始边/符号/W_syn保留。
|
||||
|
||||
| trial | biological ms | events | active neurons | per-tick exact gate |
|
||||
|---|---:|---:|---:|---|
|
||||
| no_input | 10 | 0 | 0 | True |
|
||||
| single | 50 | 24398 | 9225 | True |
|
||||
| five | 50 | 24983 | 9170 | True |
|
||||
|
||||
每tick比较所有细胞原始V/g bit、ref、spike events、实际输入以及原队列edge/guard顺序SHA256。
|
||||
完整CPU状态保留,GPU逐tick状态hash由独立文件审计再次重算核对;未保存dense spike matrix。
|
||||
未执行:[];执行异常:[]。
|
||||
GPU wall/VRAM含逐tick同步、CPU拷贝和hash,仅是诊断成本,不是实时性能benchmark。
|
||||
原冻结校验与旧五组deterministic-v2参考回归另列;旧回归PASS不代表新动力学学习PASS。
|
||||
三个短轨迹一致也不证明长期全脑行为、生物学正确、嗅觉区分或学习成功。
|
||||
下一步:Explicit fixed APL/author semantics integration audit, then fixed DoOR CAL without learning
|
||||
@@ -0,0 +1,57 @@
|
||||
# Phase 4AF:短时全脑作者语义数值门禁通过
|
||||
|
||||
正式状态:`PASS-SHORT-FULLGRAPH-NUMERICAL-GATE`。
|
||||
这是独立 `codex/lif-author-semantics-lab` 分支的数值验证,不是学习候选。
|
||||
|
||||
## 实际结果
|
||||
|
||||
完整166,700节点、25,582,938条原始有向边、124,177,617个synaptic contacts。
|
||||
217个孤立节点与101条self-loop保留;sign未变,包括178个NULL与2,999个unclear的sign=0。
|
||||
W_syn=.275mV/contact,作者默认float64,所有动力学常量保持本分支预注册值。
|
||||
|
||||
| 固定试验 | 生物时间 | 放电事件 | 活跃神经元 | 实际边到达 | 接受的到达 | 逐tick精确对照 |
|
||||
|---|---:|---:|---:|---:|---:|---|
|
||||
| 无输入 | 10ms | 0 | 0 | 0 | 0 | PASS |
|
||||
| tick0请求1381 | 50ms | 24,398 | 9,225 | 9,501,728 | 8,173,086 | PASS |
|
||||
| tick0请求固定五节点 | 50ms | 24,983 | 9,170 | 9,776,853 | 8,442,581 | PASS |
|
||||
|
||||
五节点是1197、1381、1511、82312、133666。全试验只有tick0请求,不是持续Poisson驱动。
|
||||
所有请求均实际触发放电。CPU与GPU完整事件序列、每细胞计数、每tick V/g原始bit、不应期、原始queue edge/guard顺序均一致;状态最大差0,无NaN/Inf。
|
||||
1,100个tick ×166,700个细胞逐一比较,不是仅检查末态或总spike数。
|
||||
独立文件审计重新从CPU完整memmap计算hash,与GPU每tick原始状态摘要核对。
|
||||
|
||||
真实输入graph保留canonical CSR slot作为edge_id。作者create_model实际加载的全部i/j与全部SI权重逐块验证;GPU复用实测作者权重及实际生成的系数。
|
||||
GPU仅fan-out实际放电源的出边,稳定按target分组并保留目标内原顺序,从已有g逐条累加,不另行求和。
|
||||
|
||||
## 解释边界
|
||||
|
||||
- 只有三个短时轨迹,不能外推长期全脑等价或生理正确。
|
||||
- 本轮明确未启用APL overlay、未训练、未触碰heldout,不能当作正式嗅觉CAL。
|
||||
- 两个刺激条件的输入数量不同且不是odor对照,不能据活动差异声称separability。
|
||||
- fullgraph streaming queue proxy保留原对象/顺序,并核对完整delay与计数;没有另跑全图无代理重复。4AE的小图wrapped/unwrapped控制已通过,但不是全图复跑证据。
|
||||
- float32不被宣称为float64等价加速;4AE阈值临界夹具已有跨精度事件差异。
|
||||
|
||||
## 诊断成本,不是性能候选
|
||||
|
||||
GPU含逐tickCPU拷贝、hash和同步的simulation wall:0.566s/3.383s/3.414s。
|
||||
最大allocated VRAM320,670,208B,约305.8MiB。首次构建约58.79s,单列记录。
|
||||
此实现为严格诊断,不是实时运行benchmark;不能把上述时间换成正式runtime实时性结论。
|
||||
完整状态与图留服务器,新增payload约5.11GB;Git仅同步源码、小证据和checksum,不等于异机全量备份。
|
||||
|
||||
## 完整性和异常
|
||||
|
||||
原230,239项冻结字节、4AA–4AE及背景复核的前后hash全部PASS;旧五组deterministic-v2 frozen参考重新运行,事件/计数/末V/g全部精确一致。
|
||||
新model11项、experiment129项payload已封存,publication再核manifest通过。
|
||||
实验manifest SHA256:`0646b5fc232ee44912f1c3a60d600355f5a6d97dff7cf59fccea612d822fb899`。
|
||||
|
||||
部署时新experiment父目录不可写,首次prepare尚未生成protocol即中止;仅为本次新leaf目录配置所有权后重试,未改旧目录权限或数据。
|
||||
Windows本地测试遇UTF-8配置默认GBK读取问题,已在预注册前修复。4项协议单测通过,纯queue摘要另有14项本地检查通过。
|
||||
旧参考代码出现PyTorch对只读NumPy tensor构造的既有warning;未修改旧代码。GPU复制运行、冻结checksum和参考结果均通过。
|
||||
科学试验无失败,无参数/输入/容差重选。
|
||||
发布时CSV的标准CRLF被Git默认whitespace检查提示;保留封存原始字节,使用允许CR-at-EOL的检查,其余空白检查仍启用,未重写证据文件。
|
||||
|
||||
## 下一关
|
||||
|
||||
先明确并验证author LIF与frozen legacy graded APL的组合调度、refractory写入和float32→SI单位合同,再进行固定DoOR嗅觉CAL。
|
||||
之后才允许独立seed、新odor以及credit/functional learning gate。仍没有learning candidate,未进入游戏。
|
||||
详见[完整报告](phase4af-author-fullgraph.md)、[下一关只读设计审计](phase4af-apl-next-gate.md)、[封存校验](../../registry/phase4af-author-fullgraph/seal-verification.json)。
|
||||
@@ -0,0 +1,14 @@
|
||||
# Phase 4AG: author LIF + unchanged legacy graded APL
|
||||
|
||||
Status: `FAIL-FIXED-CAL-REPRESENTATION`.
|
||||
|
||||
Numerical closed-loop gate: True; original complete fixed six-CAL gate: False.
|
||||
The independent Brian LIF and candidate use independent state but share the APL arithmetic component. Original frozen APL AST replay and a float64 error audit are separately recorded.
|
||||
Author LIF uses float64; APL retains float32, tau20ms/gain0.01/normalization1, full original matrices and order. No learning/parameter search.
|
||||
The explicitly hypothesized unconditional end-APL write includes refractory KC. Original fast APL mask leaves1,212 non-KC edges unreplaced; this scope limitation was not silently fixed.
|
||||
Old checksums and five frozen reference trials: True.
|
||||
Successfully compared cases: [{'case': 'apl_only', 'pass_all': True}, {'case': 'arrival_collision', 'pass_all': True}, {'case': 'cal-matched_energy-00-s51101', 'pass_all': True}, {'case': 'cal-physiological-00-s51101', 'pass_all': True}, {'case': 'high_fanin', 'pass_all': True}, {'case': 'kc_ref_boundary', 'pass_all': True}, {'case': 'natural_request', 'pass_all': True}, {'case': 'pn_pulse', 'pass_all': True}, {'case': 'two_sources', 'pass_all': True}, {'case': 'zero', 'pass_all': True}].
|
||||
Detailed CAL, exact comparisons, component errors, event/request traces and execution exceptions are linked from audit/final-status.json.
|
||||
Wall/VRAM here include per-tick CPU synchronization, IPC and hashes. They are diagnostic costs, not runtime-performance benchmarks.
|
||||
No heldout dynamic outputs, formal A/B, learning, game, reward, decoder or new candidate.
|
||||
Next gate: Audit first failure in this explicitly declared hypothesis; no gain search, no heldout tuning, no conditioning
|
||||
@@ -0,0 +1,14 @@
|
||||
# Phase 4AG: author LIF + unchanged legacy graded APL
|
||||
|
||||
Status: `INCOMPLETE-NUMERICAL-EXECUTION`.
|
||||
|
||||
Numerical closed-loop gate: False; original complete fixed six-CAL gate: False.
|
||||
The independent Brian LIF and candidate use independent state but share the APL arithmetic component. Original frozen APL AST replay and a float64 error audit are separately recorded.
|
||||
Author LIF uses float64; APL retains float32, tau20ms/gain0.01/normalization1, full original matrices and order. No learning/parameter search.
|
||||
The explicitly hypothesized unconditional end-APL write includes refractory KC. Original fast APL mask leaves1,212 non-KC edges unreplaced; this scope limitation was not silently fixed.
|
||||
Old checksums and five frozen reference trials: True.
|
||||
Successfully compared cases: [].
|
||||
Detailed CAL, exact comparisons, component errors, event/request traces and execution exceptions are linked from audit/final-status.json.
|
||||
Wall/VRAM here include per-tick CPU synchronization, IPC and hashes. They are diagnostic costs, not runtime-performance benchmarks.
|
||||
No heldout dynamic outputs, formal A/B, learning, game, reward, decoder or new candidate.
|
||||
Next gate: Audit first failure in this explicitly declared hypothesis; no gain search, no heldout tuning, no conditioning
|
||||
@@ -0,0 +1,91 @@
|
||||
# Phase 4AG:设计、证据范围与限制
|
||||
|
||||
本文是独立于已登记模型和实验文件的解释性文档,不是预注册协议的修订,也不是最终实验报告。状态快照日期:2026-09-30。
|
||||
|
||||
在本说明对应的检查节点,r2 的 8 个固定小型测试场景已完成每场景 3 次 GPU 运行及 1 次 Brian 对照,数值门禁通过;两个预先指定的全图 100 ms Brian 对照也已通过。后续 60 次 CAL 基础运行和 12 次重复检查仍在进行中。本文不宣布 CAL 通过或失败,更不宣布学习成功。正式状态应以各阶段原始审计记录及最终封存报告为准。
|
||||
|
||||
## 本阶段到底测试什么
|
||||
|
||||
Phase 4AG 测试一个明确的新组合:在 MaleCNS 原有全图上,将已经核对过作者语义的 float64 LIF 动力学,与项目旧版 float32 graded APL 模块连接起来,先检查实现是否符合声明,再检查固定旧 CAL 输入下的嗅觉表征是否满足完整原门槛。
|
||||
|
||||
全图仍为 166,700 个神经元、25,582,938 条规范化有向边。原始连接计数、递质符号、W_syn、旧 APL 参数以及 eligibility-v2 的 14,232 边掩码不修改。本阶段不训练,不搜索增益、阈值、刺激强度或时间窗,不新增连接,不做行为、奖励或游戏演示。
|
||||
|
||||
作者语义来源是已归档的 Shiu 等人代码,而不是重新选取的最新版本:GitHub commit `91bdd1e7dcf193f3e7ca5a8933497fcef63b7960`,归档 `model.py` 的 SHA-256 为 `fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e`。[固定提交的作者代码](https://github.com/philshiu/Drosophila_brain_model/blob/91bdd1e7dcf193f3e7ca5a8933497fcef63b7960/model.py) 对应 Shiu 等人的论文 [A Drosophila computational brain model reveals sensorimotor processing](https://www.nature.com/articles/s41586-024-07763-9)(Nature,2024)。本项目使用不同的连接组及额外的旧 APL 工程模块,因此不能称为该论文或作者完整模型的严格复现。
|
||||
|
||||
## 混合精度与每一步的明确顺序
|
||||
|
||||
LIF 的 `v`、`g` 和解剖边权使用 float64、SI 伏特;这里的 `g` 是作者方程中以伏特表示的状态量,不应仅凭变量名解释成电导。LIF 核心复用已封存的 CUDA 运算,不修改数学语义。固定步长为 0.1 ms,解剖传递延迟为 18 步,不应期为 22 步。
|
||||
|
||||
APL 则保留旧环境中的 float32 矩阵、状态和运算序列。输入矩阵始终是 `2 × 166700`,输出矩阵始终是 `2 × 4064`;即使测试小型诱导子图,也不裁剪矩阵或重算归一化。每次独立运行从零 APL 状态开始。APL 输入是本步实际产生的二值放电,而不是外部请求:被不应期拒绝的请求不能被当作放电反馈。
|
||||
|
||||
每步先完成 LIF 核心、请求处理、真实延迟到达和复位的声明调度,再在 `end` 执行旧 APL。APL 衰减与增加保持分开的 eager 操作:
|
||||
|
||||
```text
|
||||
s32.mul_(float(float32(exp(-0.1 / 20.0))))
|
||||
drive32 = torch.mv(I32, actual_spikes32)
|
||||
s32.add_(drive32)
|
||||
h32 = torch.matmul(s32, O32) * float32(0.01)
|
||||
|
||||
delta64 = h32.to(float64) * float64(0.001)
|
||||
g64[KC] = g64[KC] - delta64
|
||||
```
|
||||
|
||||
其中 `h32` 沿用旧模块的 mV 语义。必须先拓宽为 float64,再乘 0.001 转成伏特,最后单独减去;不能先在 float32 中乘 0.001,也不能将 `mul_`、`add_` 或最后的转换与减法融合成另一条运算路径。tau=20 ms、gain=0.01、normalization=1 均不调整,也不额外加入 APL 延迟、裁剪或 `dt/tau` 缩放。
|
||||
|
||||
APL 不改变原环境的默认 TF32/矩阵乘精度/CuBLAS 设置,而是记录实际设置、版本和设备,并核对组件输出。保留“数学上等价”的表达式并不足以保证逐位相同;PyTorch 官方说明明确指出,浮点运算顺序、后端和版本都可能影响结果。[PyTorch 数值精度说明](https://docs.pytorch.org/docs/2.14/notes/numerical_accuracy.html)、[可复现性说明](https://docs.pytorch.org/docs/2.14/notes/randomness.html) 在此用于解释工程风险,不作为改变冻结环境的理由;文档不能替代实验记录中的实际安装版本和设备信息。
|
||||
|
||||
## 不应期中的 APL 写入是被检验的假设
|
||||
|
||||
本组合在复位后的 `end` 阶段,无条件向所有对应 KC 的 `g` 写入 APL 抑制,包括刚放电或仍处于不应期的 KC。这里没有补加 `ref == 0` 条件。
|
||||
|
||||
这必须与作者核心的普通解剖输入区分:作者方程中带有 `unless refractory` 的状态在不应期冻结,普通突触更新受相应保护;本分支的 APL 是显式的末尾直接状态写入,不是把它伪装成普通受保护突触。[Brian 2.5.1 不应期文档](https://brian2.readthedocs.io/en/2.5.1/user/refractoriness.html) 解释了不应期和状态冻结的区别;具体执行依据仍是已归档作者代码及本项目保存的实际生成代码。
|
||||
|
||||
由这套代码顺序可以推得:APL 写入的负 `g` 可能在不应期内继续累积,而该期间作者核心不会按通常的 `g` 方程衰减它。这个组合效应是此次被检验的显式工程假设,不是已确认的生理机制,也不是作者原模型的主张。若改成不应期屏蔽、写入后再次清零或更换写入阶段,就已经是另一项假设,不能作为此次结果不好时的即时“修复”。
|
||||
|
||||
## 5,845 条 fast APL 掩码及 1,212 条未替换边
|
||||
|
||||
旧 APL 模块的兼容规则是:将全部 5,845 条原始 APL fast 出边的有效权重置零,然后仅向 KC 提供 graded 替代。置零只发生在新运行时的内存副本中;磁盘上的原始图、计数、符号和基础权重不改写,边身份也不删除。
|
||||
|
||||
其中 1,212 条出边的目标不是 KC,因此其 fast 作用被关闭后,没有获得本模块的 graded 替代。这是已知且保留的覆盖缺口。不能把该组合描述成“所有 APL 输出均被等价替换”,也不能为了得到更好的结果悄悄恢复这些边或新增非 KC 的 graded 通路。这两个数字描述的是本项目图中的有向边计数,不是新增的生物学结论。
|
||||
|
||||
## 三类数值证据分别能说明什么
|
||||
|
||||
| 检查 | 能支持的结论 | 不能支持的结论 |
|
||||
| --- | --- | --- |
|
||||
| Brian 闭环对照 | Brian 自己推进 LIF、产生实际放电,独立进程中的 APL 状态据此更新;与 GPU 比较逐步状态、事件、请求、队列顺序与接受掩码、APL drive/state/h | APL 算法本身完全独立实现,或所有输入下的普遍等价 |
|
||||
| 原代码 AST 影子重放 | 从冻结旧初始化代码重建矩阵,并提取旧更新语句;用已记录的真实放电重放,检查组件字节是否保持一致 | 新的独立全脑闭环证明,或摆脱共同 Torch/CUDA 底层原语的验证 |
|
||||
| float64 算术测量与误差界 | 在明确的非负矩阵、正常有限运算等前提下,量化 float32 组件相对独立 float64 测量的偏差,检查预先定义的误差界 | 用 float64 替换 APL、事后放宽容差,或用误差界替代逐位旧组件一致性 |
|
||||
|
||||
Brian 的 APL worker 不接收候选 GPU 的抑制轨迹,而只接收 Brian 自己产生的实际放电;它有独立的零初始状态,并通过握手核对矩阵哈希与运行环境。但候选与 worker 共享 APL 组件实现,所以准确名称是“共享 APL 组件的闭环 Brian 对照”,不能写成“全部组件独立的 CPU oracle”。
|
||||
|
||||
AST 影子又提供一层不同的代码来源检查,但仍使用相同的 Torch/CUDA 原语。float64 测量不反馈到任一脑模型,其误差界不根据观测误差拟合。各证据应并列阅读,不能互相替代。
|
||||
|
||||
8 个固定小型场景检验零输入、真实 PN/KC 输入、不应期边界、自然放电与请求重合、延迟到达、APL mask 和高 fan-in 等情形;每场景 3 次 GPU 运行检查精确重复。两个全图 probe 是预先指定的第一个旧 CAL 气味、首个原始 seed、两种编码模式,各 100 ms。通过这些检查只说明已测试场景符合本组合的声明,不证明所有气味、时间尺度或后续学习任务都有效。同步、IPC、逐步哈希和诊断保存的耗时也不是性能基准。
|
||||
|
||||
## CAL 必须通过完整原 4L 门槛
|
||||
|
||||
CAL 设计固定为原 6 个校准气味 × 5 个原始 seed(51101–51105)× 2 种模式(`physiological`、`matched_energy`),共 60 次基础运行,每次 100 ms;每个气味/模式的首个 seed 再运行一次,共 12 次重复。它们不是额外的训练 seed。
|
||||
|
||||
输入在任何轨迹运行前保存,保留旧 PN 编码、目标/随机数列顺序。matched-energy 的参考值仍取原冻结 12 气味 panel 的中位数,不能换成 6 CAL 的中位数,也不能换成后续其他 panel。读取这份静态参考数据不等于读取 heldout 动态结果。
|
||||
|
||||
每种模式都必须同时满足以下原始条件,两种模式都通过才满足表征门槛:
|
||||
|
||||
- KC 活跃比例的平均值在闭区间 3%–9%。
|
||||
- 单次运行的最小活跃比例严格大于 0,最大值严格小于 50%。
|
||||
- 同气味跨 seed 的平均活跃集 Jaccard,严格大于不同气味同 seed 的平均 Jaccard。
|
||||
- 不同气味同 seed 的平均欧氏距离,严格大于同气味跨 seed 的平均欧氏距离。
|
||||
- 至少 3 对气味通过成对分离检查:该对气味在相同 seed 下的 5 个距离均值,严格大于这两个气味各自全部 10 个跨 seed 距离合并后的 20 项均值。
|
||||
|
||||
距离来自原始 KC spike-count 向量,欧氏距离先转 float64,不另做向量归一化;Jaccard 使用是否放电的布尔活跃集。6 CAL 每种模式有 15 对气味、60 项 within 距离和 75 项 between 距离。成对比较的基线不是全部气味混在一起的平均 within 距离;只检查平均稀疏率与“至少 3 对”的旧简化 `r4` 不能代替完整原 4L 门槛。
|
||||
|
||||
本阶段另加输出完整性、有限值和 12 次精确重复要求;缺失文件、失败运行、NaN 或全静默都不能当成通过。原 4L 虽记录 finite,但没有将它写进原最终 gate,因此这里应明确称为新增的完整性前置检查,而非改写历史。
|
||||
|
||||
即使最终 CAL 通过,含义也仅限于这组固定校准输入下的表征检查。它不等于 heldout 泛化、刺激特异性信用分配、记忆、条件化或可用学习;本阶段没有训练或生成新的候选模型结论。后续学习验证仍需在授权范围内另行登记并通过专门门槛,不能由本阶段的数值一致性推导出来。
|
||||
|
||||
## 为什么保留 v1,并单独运行 r2
|
||||
|
||||
第一次登记的 v1 在启动 APL worker 时,对科学虚拟环境的 Python 路径调用了 `resolve()`,跟随符号链接到了系统解释器,丢失预期包环境。异常发生在首个 Brian tick 之前,错误记录的 `last_complete_tick` 为 -1;v1 已保留源代码、部分输出和故障记录,并以 `INCOMPLETE-NUMERICAL-EXECUTION` 封存。
|
||||
|
||||
这不是已观测到的动力学或科学假设失败,也不能反过来称作数值通过。r2 使用新模型目录 `author-apl-lab-v2` 和新实验目录 `phase4ag-author-apl-r2`,保留虚拟环境的调用入口路径,只修执行入口及相应记录/检查,不更改方程、运算精度、掩码、刺激、门槛、参数或测试选择。全部声明流程从新状态重跑,不覆盖或挑选性省略 v1 的记录。
|
||||
|
||||
本文所述已通过的 r2 数值门禁与仍在进行的 CAL 是两件事。是否完成、是否通过,以及允许得出的结论,最终都必须回到对应原始证据和封存状态,而不是依据本解释文档升级。
|
||||
@@ -0,0 +1,85 @@
|
||||
# Phase 4AG:独立动力学分支的正式结果
|
||||
|
||||
更新:2026-09-30。实验:`phase4ag-author-apl-r2`;模型:`author-apl-lab-v2`;Git 分支:`codex/lif-author-semantics-lab`。
|
||||
|
||||
## 结论
|
||||
|
||||
正式状态:**FAIL-FIXED-CAL-REPRESENTATION**。
|
||||
|
||||
本轮完成作者 float64 LIF 与冻结旧 float32 graded APL 的显式组合测试。数值一致性、数据完整性和精确重复检查通过;固定 CAL 的完整原门槛未通过。没有训练,没有新学习候选,不升级原 reference/runtime,也不修改旧模型。
|
||||
|
||||
两种输入模式唯一未达标条件都是 KC 平均活跃比例低于预先固定的 3%–9%。这不证明约 1.2% 的活跃比例在生物学上错误,也不能据此认定 APL 是原因。
|
||||
|
||||
## 固定 CAL:60 次基础运行及 12 次重复
|
||||
|
||||
使用原 6 个 CAL 气味、原 5 个 seed、两种输入模式,每次 100 ms;每个气味/模式首个 seed 再运行一次。未读取 held-out 动态结果,未做正式 A/B conditioning。
|
||||
|
||||
| 指标 | physiological | matched_energy |
|
||||
| --- | ---: | ---: |
|
||||
| KC 平均活跃比例 | 1.271325% | 1.233596% |
|
||||
| 单次最少/最多活跃 KC(共 4,064) | 38 / 79 | 36 / 72 |
|
||||
| 同气味跨 seed 平均 Jaccard | 0.509072 | 0.516431 |
|
||||
| 不同气味同 seed 平均 Jaccard | 0.460955 | 0.481702 |
|
||||
| 同气味平均欧氏距离 | 5.811443 | 5.671828 |
|
||||
| 不同气味平均欧氏距离 | 6.205091 | 5.932021 |
|
||||
| 成对距离判据通过数 | 12 / 15 | 13 / 15 |
|
||||
| 完整原 CAL 门槛 | FAIL | FAIL |
|
||||
|
||||
其余原条件全部通过:单次最小活跃比例大于零、最大值小于 50%、同气味 Jaccard 大于不同气味、不同气味欧氏距离大于同气味、至少 3 对气味满足原成对距离判据。该判据使用每对气味自己的 20 项 within 距离与 5 项 same-seed between 距离,不是全气味混合基线。
|
||||
|
||||
60/60 基础运行完整、有限;12/12 重复的事件、状态、逐 tick 哈希、APL 轨迹、请求及队列均精确一致。未见 NaN/Inf。12/15 和 13/15 不是学习成功率、统计显著性或 held-out 泛化结论。
|
||||
|
||||
matched-energy 保持原冻结 12 气味静态 panel 中位数及原随机输入算法;它匹配期望输入量,不保证每次随机运行实际接受事件相等。其期望事件数 CV 约 6.48e-8,实际接受事件 CV 为 0.134355;不得称作实际输入事件数严格匹配实验。
|
||||
|
||||
## 数值证据与完整性
|
||||
|
||||
- 8 个固定真实诱导子图场景,各 3 次 GPU 和 1 次 Brian 闭环对照;16 个 GPU 重复比较通过。
|
||||
- 两个预指定完整全图 100 ms probe,逐 tick 的 v/g/ref、放电事件、请求、延迟队列及 APL drive/state/h 精确一致。
|
||||
- 10 组原 APL AST 组件重放/误差审计通过。两个 Python 环境分别通过 29 项协议/汇总/解释器入口单元测试。
|
||||
- 全图保留 166,700 神经元、25,582,938 条边,原始 contact weight sum 为 124,177,617;原始磁盘 CSR 不改。
|
||||
- 原 230,239 项冻结清单及后续已封存目录全部校验通过;旧五组 deterministic-v2 参考的事件、计数、最终 v/g 再次精确通过。
|
||||
- 新 r2 模型 17 项、新实验 1,718 项已封存并再次验证。实验封存前 payload 为 2,956,599,228 字节(约 2.75 GiB)。
|
||||
|
||||
Brian 独立推进 LIF,但 APL worker 与候选共享 APL 算术组件;AST 重放也使用相同底层 Torch/CUDA 原语。因此是“共享 APL 组件的闭环对照”,不是全部组件独立的 CPU oracle,也不是任意输入/长期全脑普遍等价证明。float64 误差测量不反馈到模型。
|
||||
|
||||
诊断包含逐步 CPU 同步、哈希、IPC 和保存,耗时不代表正式 runtime 性能;没有由本轮产生新的实时运行候选。
|
||||
|
||||
## 已声明的假设与保留的问题
|
||||
|
||||
APL 保留旧 float32 矩阵、tau=20 ms、gain=0.01、normalization=1;LIF 为作者 float64 语义。APL 在 reset 后的 end 阶段无条件写入 KC g,包括不应期,先拓宽 float64 再由 mV 转 SI。作者核心在不应期冻结 g,与末尾 APL 写入结合可能产生累积,但本轮尚未定位低 KC 覆盖率的原因。
|
||||
|
||||
旧 APL 规则在独立运行的内存权重副本中关闭 5,845 条 fast APL 出边,只向 KC 提供 graded 替代。其中 1,212 条非 KC 边(13,712 contacts)未获替代。该覆盖缺口原样披露,不静默恢复边或修改归一化。新分支不是作者原始完整模型的严格复现。
|
||||
|
||||
首次 v1 因虚拟环境 Python 符号链接被错误解析到系统解释器而在首个 Brian tick 前中止,正式保存为 `INCOMPLETE-NUMERICAL-EXECUTION`。r2 只修执行入口及检查,另建目录从头运行;全部 68 组输入请求与 v1 一致。失败代码、日志和结果保留,没有覆盖或删除。
|
||||
|
||||
## 下一步:先解释失败,不追逐通过率
|
||||
|
||||
只用现有 60 次 CAL 的记录,先登记一份只读影子记账诊断:
|
||||
|
||||
1. 分解请求数、实际接受量和 PN 实际放电。
|
||||
2. 按真实边及 18-step delay 重建 PN→KC 兴奋性投递、拒绝、不应期和 reset。
|
||||
3. 区分活跃/静默 KC,审计 APL 写入时相、累积量与 g 的关系。
|
||||
4. 先验证重建与已保存事件/最终状态一致,再讨论机制;不足以重建的证据明确标记缺失。
|
||||
|
||||
这不是一次新脑模拟或参数搜索。不改 gain、刺激、阈值、APL 参数、mask 或通过条件;不以本次 CAL 选择后续最佳参数。任何新的动力学假设须另立协议和目录。表征、独立 seed/新气味、信用分配、隔离功能及全脑功能检查通过之后,才有条件进入真正学习验证。
|
||||
|
||||
## 封存与证据入口
|
||||
|
||||
- [正式 r2 报告](phase4ag-author-apl-r2.md)
|
||||
- [原始最终状态](../../registry/phase4ag-author-apl-r2/evidence/audit/final-status.json)
|
||||
- [CAL 原始汇总](../../registry/phase4ag-author-apl-r2/evidence/calibration/summary.json)
|
||||
- [旧数据完整性](../../registry/phase4ag-author-apl-r2/evidence/regression/old-integrity-after.json)
|
||||
- [旧五组参考回归](../../registry/phase4ag-author-apl-r2/evidence/regression/frozen-five.json)
|
||||
- [新封存校验](../../registry/phase4ag-author-apl-r2/seal-verification.json)
|
||||
- [含来源链接的设计/限制历史快照](phase4ag-design-and-limits.md)
|
||||
- [保留的 v1 执行失败报告](phase4ag-author-apl.md)
|
||||
|
||||
r2 模型 manifest SHA-256:`208c00961b9d895d14ac050bc23fbc18db49d0df1fcf5c20d4c3eaa8e15ff7ff`。
|
||||
|
||||
r2 实验 manifest SHA-256:`21593297c40bee58516d4d7fd16e9d8d20ffdea5f8e0652fa5dd4e903b4d96ee`。
|
||||
|
||||
Git 发布代码、文档及小型证据;完整数组和事件文件仍在服务器实验目录。Git 同步不等于全量异机数据备份。
|
||||
|
||||
## 仓库发布检查
|
||||
|
||||
原仓库 681 项文件校验零差异,仓库审计单元测试通过,暂存内容的数据/密钥/大文件检查通过。标准空白检查发现两个已封存 `oracle_apl.py` 各有一个文件末尾空行;为保留登记字节未做格式改写。仅关闭末尾空行规则并允许标准 CSV CRLF 后,其余空白检查通过。这是格式例外,不是数值容差或科学门槛例外。
|
||||
@@ -0,0 +1,75 @@
|
||||
# Phase 4AH:KC 输入记账把问题缩小到了哪里
|
||||
|
||||
更新:2026-09-30。正式状态:**PASS-CONDITIONAL-KC-DRIVE-AUDIT**。
|
||||
|
||||
这里的 PASS 仅表示对既有轨迹的条件重建和输入账目通过验证。Phase 4AG 的 `FAIL-FIXED-CAL-REPRESENTATION` 不变,仍没有学习候选。本阶段不运行新脑实验、不训练、不调参数、不使用 held-out,也未使用 GPU。
|
||||
|
||||
## 关键结果
|
||||
|
||||
全部 8 个小型场景、60 次 CAL 基础记录和 12 次重复,共 80 份记录通过重建。KC 放电由重建代码独立计算,未用原 KC 放电强制复位;80 份事件、计数及最终 V/g/ref 精确一致,8 个小型场景的逐 tick pre/post-APL 状态也精确一致。12 份重复的诊断统计相同。
|
||||
|
||||
完整 CAL 没有逐 KC 状态历史,只有全脑每 tick 哈希。因此不能声称完整 CAL 的每 tick KC V/g 都与独立保存的原历史逐位验证。记录中的上游放电和 APL h 是重建的条件输入,不是重新模拟出来的独立证据。
|
||||
|
||||
下表每列汇总 30 次 CAL:6 气味 × 5 seed。KC 比例按“神经元×试验”计数,时间比例按“神经元×试验×tick”计数,不是跨试验去重后的神经元数。
|
||||
|
||||
| 指标 | physiological | matched_energy |
|
||||
| --- | ---: | ---: |
|
||||
| 外部请求 / 接受 | 3,203 / 2,195 | 2,936 / 2,038 |
|
||||
| 实际全部 ALPN 放电 | 154,514 | 153,049 |
|
||||
| 当次非设计目标 ALPN 放电 | 138,679(89.75%) | 137,431(89.80%) |
|
||||
| 静默 KC 中收到 PN 兴奋输入的比例 | 93.3455% | 93.3481% |
|
||||
| 静默 KC 中收到任意兴奋输入的比例 | 100% | 100% |
|
||||
| PN 占已接受兴奋增量总量的比例 | 89.1612% | 89.4183% |
|
||||
| PN→KC 到达边事件拒绝率 | 0.03649% | 0.03731% |
|
||||
| 从未放电 KC 的不应期 tick 数 | 0 | 0 |
|
||||
| APL 累计写入量 / 已接受兴奋增量 | 3.0516 | 3.0539 |
|
||||
| KC 末尾状态 g 为负的时间比例 | 91.8452% | 91.8721% |
|
||||
|
||||
每个气味的设计目标为 33–55 个 ALPN;六气味设计目标并集为 76 个,实际放电 ALPN 并集为 556 个(两种模式均如此)。设计目标、实际请求、接受请求和实际放电是不同集合,均已分别保存。没有把实际输入事件数强行匹配,也没有把“未与请求同 tick 的放电”直接命名为噪声或独立自然活动。
|
||||
|
||||
## 哪些解释已被排除,哪些还没有
|
||||
|
||||
在这些已记录的 100 ms CAL 中,可排除字面意义的“KC 完全没收到兴奋输入”。也可排除“从未放电的 KC 因自身不应期拒收或不应期内 APL 累积,才不能首次放电”:它们没有进入过不应期。
|
||||
|
||||
不应期内 APL 写入仅占全体 APL 累计写入约 0.0193% / 0.0192%。这不支持把“屏蔽不应期 APL 写入”当成本轮覆盖率失败的直接主要解释;但没有做干预,不能排除已活跃细胞的时序或网络间接效应。
|
||||
|
||||
尚不能排除:兴奋输入过晚、不够同步、缺少气味特异结构、广泛反馈再招募,或它们与持续 APL 写入的相互作用。静默 KC 最接近阈值时的距离中位数约 6.697 / 6.703 mV;该值描述当前轨迹,不用于调整阈值。
|
||||
|
||||
APL 的约 3.05 倍是跨时间、神经元和试验累计状态增量的记账比值,不是瞬时 E/I 比、生物学抑制强度比例,也不是可据此倒算的新 gain。`g` 在该作者方程中以伏特表示,不是电导。源分账采用真实 availability/reset 条件;不能将其当作移除 APL 后的全脑反事实。
|
||||
|
||||
## 身份、浮点和证据范围
|
||||
|
||||
源类别从实际 schema 建立互斥分组:KC 4,064、APL 2、ALPN 686、MBON 97、解剖 DAN 340、仅 dopamine 标注的其他类别 52、other 160,143、unclassified 1,316。两枚 DPM 在 other 中明确标记,不因 dopamine 标注被归作 DAN;340 个 DAN 中有 2 个的当前 NT 字段不是 dopamine。没有依据名称前缀猜身份或删除未知项。
|
||||
|
||||
重建保留原始浮点位、18-step 延迟、22-step 不应期、严格阈值及逐边累加顺序。APL fast-edge 掩码原样保留在独立内存副本;磁盘原边权不改。按来源和符号分账不反馈到状态更新;舍入残差单独记录。
|
||||
|
||||
- 47 项合成测试在本地与服务器通过。
|
||||
- 实际 g 阶段账目的最大闭合残差为 2.17e-19 V,在记录的运算误差界内。
|
||||
- 来源状态分账最大残差为 6.11e-16 V;积分最大残差为 7.46e-14 V·ms。重建事件/最终状态自身仍要求逐位一致,没有放宽容差。
|
||||
- 原 230,239 项及全部后续已封存目录校验通过。五组旧参考本阶段未重新运行;引用的是上一轮已封存且本次校验未变的 PASS 记录。
|
||||
- CPU 条件记账合计约 133.34 s;这是审计耗时,不是脑模型实时性能。
|
||||
- 新模型 11 项、新实验 415 项已封存;封存前实验 payload 81,395,186 字节。完整分账表留在服务器,Git 只发布代码、小型证据及 SHA-256。
|
||||
|
||||
## 下一步,不从比值反推参数
|
||||
|
||||
优先检验可证伪的机制问题:**非设计目标 ALPN 的广泛再招募是否先于 KC 响应,并通过 PN→APL / PN→KC 形成跨气味共享输入?**
|
||||
|
||||
先继续使用已有 60 次 CAL,做 ALPN 的局部条件重建、真实先后时序和设计/非设计来源的 PN→APL 分账。若时序或贡献不支持该假设,明确否定,不挑选支持案例。
|
||||
|
||||
旧 APL 的单位意图已核实:[原 4J 配置](../../models/male-cns-lif/phase4j.yaml) 明写 `gain_grid_mv_per_state_step`,[原 4J 实现](../../models/male-cns-lif/phase4j.py) 有意每 tick 把滤波后的抑制写入 g,4P 延续这一行为。所以不能称它为“遗漏 dt 的实现错误”,也不能偷偷把 .01 换解释成 tonic mV。后续可研究不同时间算子的结构假设,但需新单位契约和不依赖 CAL 的解析匹配对照,不能只是把累计抑制改小。[研究边界与来源](phase4ah-next-hypotheses.md)
|
||||
|
||||
这仍不是学习训练或新的全脑动力学实验。任何因果干预或新的 APL 动力学语义必须另立独立分支协议,保留原结果,提前固定对照和判据,不做 gain 搜索,不改旧模型、输入、mask、eligibility 或阈值。
|
||||
|
||||
## 证据入口
|
||||
|
||||
- [正式审计报告](phase4ah-kc-drive-audit.md)
|
||||
- [原始最终状态](../../registry/phase4ah-kc-drive-audit/evidence/audit/final-status.json)
|
||||
- [全部基础 CAL 输入账目](../../registry/phase4ah-kc-drive-audit/evidence/audit/calibration-drive-summary.csv)
|
||||
- [可重复计算的解释性统计及输入 SHA](../../registry/phase4ah-kc-drive-audit/interpretation-metrics.json)
|
||||
- [解释性统计生成代码](../../tools/report_phase4ah.py)
|
||||
- [冻结文件校验](../../registry/phase4ah-kc-drive-audit/evidence/regression/old-integrity-after.json)
|
||||
- [新封存校验](../../registry/phase4ah-kc-drive-audit/seal-verification.json)
|
||||
|
||||
模型 manifest SHA-256:`fa89cb0a6e2dc77462057b672ffbcfabbe74b893d8f70e4b41d8b5202de15530`。
|
||||
|
||||
实验 manifest SHA-256:`fb30ca09068d5d8dc1dc9972e00f674c5dc3aa49c7117d630607b5d9067927db`。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Phase 4AH — conditional KC drive audit
|
||||
|
||||
Status: `PASS-CONDITIONAL-KC-DRIVE-AUDIT`.
|
||||
|
||||
80 existing records reconstructed; 8 fixture pre/post histories and 72 CAL event/final states exact.
|
||||
12 repeated CAL records produced identical accounting. No new brain simulation, GPU use, tuning or learning.
|
||||
Full prior frozen integrity passed. Five reference simulations were not rerun; the sealed prior PASS is referenced by SHA.
|
||||
Previous CAL failure remains unchanged. Conditional replay is not an APL-removal counterfactual.
|
||||
|
||||
|Mode|Mean active KC fraction|Requests|Accepted requests|ALPN accepted excitatory increment sum (V)|APL write sum (V)|
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
|physiological|0.01271325|3203|2195|46379.7565|158735.25|
|
||||
|matched_energy|0.01233596|2936|2038|45928.6416|156859.684|
|
||||
|
||||
Increment sums span 30 trials × 4064 KC × time; not instantaneous conductance or causal percentages.
|
||||
See audit/final-status.json, audit/calibration-drive-summary.csv and trials/*/per-kc.parquet for detailed evidence.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Phase 4AH 后的研究边界:先定位,再做独立干预
|
||||
|
||||
日期:2026-09-30。本页是研究备忘,不是已执行或已通过的新实验协议。Phase 4AH 的条件审计 PASS 和 Phase 4AG 的固定 CAL FAIL 均不改变。
|
||||
|
||||
## 立即优先:既有轨迹中的 ALPN 再招募
|
||||
|
||||
可证伪的问题是:非设计 ALPN 再招募的时序是否先于 KC 响应;这些源在当前 PN→APL / PN→KC 输入中是否占主要部分。先对全部 60 次原 CAL 做来源和时序分账,不能仅拿全程累计比例作为答案。仍使用既有输入/放电,不生成新全脑轨迹;零贡献、反向时序或跨气味不同的结果也完整报告。
|
||||
|
||||
## 值得研究但尚未执行:APL 时间算子
|
||||
|
||||
旧模型的设计不是缺少一个 dt 因子。[phase4j.yaml](../../models/male-cns-lif/phase4j.yaml) 使用 `gain_grid_mv_per_state_step`;[phase4j.py](../../models/male-cns-lif/phase4j.py) 每步将 20 ms 滤波状态产生的抑制加到 g;[phase4p_revalidate.py](../../models/male-cns-lif/deterministic-runtime-v2/phase4p_revalidate.py) 保持该语义。原 4J 文件 SHA-256 分别为 `b35e4332a91b8489f69348cedf5ac675e9c3902c7e8c7beec5d053773c6529a3` 和 `abb4574da85f00bd9ef61024c65993ef81d42651ff8d8d63c15dd69c6222471e`,与 frozen 子目录副本相同。
|
||||
|
||||
一个未来的独立结构假设可以比较“滤波后的逐步 g 增量”与“独立连续驱动状态”的时间响应,而不是把 gain 调小。两者变量的单位及调度不同,不能把旧 `.01 mV/state/step` 直接当作 `.01 mV/state`,再宣称只改变了算法实现。
|
||||
|
||||
根据已冻结的离散更新式,在无放电、无不应期、恒定每步抑制增量 H 的线性诊断条件下,由代码推导(不是文献实测):
|
||||
|
||||
```text
|
||||
g_next = gd*g - H
|
||||
V_next = c0 + cg*g + cv*V
|
||||
g_steady = -H/(1-gd)
|
||||
V_base = c0/(1-cv)
|
||||
V_steady - V_base = -cg*H/((1-cv)*(1-gd))
|
||||
```
|
||||
|
||||
若以后选用 DC 匹配,应在新协议中预先固定这一匹配规则,使用冻结系数而不是重算常数,并先通过零输入、阶跃、脉冲、有限脉冲列、reset/ref22 边界的独立验证。不能看到 CAL 后再换成更有利的匹配标准。连续驱动可能使早期抑制更强,不能预设其改善编码。固定 trace 对照也不能代替闭环干预。
|
||||
|
||||
## 生物学依据能支持到哪里
|
||||
|
||||
Shiu 等人的 LIF 模型明确忽略非脉冲神经元和受体动力学。因此本项目的 graded APL 不是论文提供的“作者 APL 算子”,也不能由该论文直接获得 MaleCNS APL 参数。[Shiu et al., Nature 2024](https://pmc.ncbi.nlm.nih.gov/articles/PMC11446845/)
|
||||
|
||||
Amin 等人的局部刺激与成像显示 APL 活动和对 KC 的抑制具有空间局部性,为研究局部 APL 模型提供依据,但不能直接指定当前 MaleCNS 的每条边强度。[Amin et al., eLife 2020](https://pmc.ncbi.nlm.nih.gov/articles/PMC7541083/)
|
||||
|
||||
Prisco 等人结合 EM 和体内成像,研究了 calyx 中 APL 对 PN bouton 与 KC dendrite 的作用及区域化响应。这支持区分末梢与全神经元作用范围;不等于允许把 PN 整个细胞的输出统一抑制。[Prisco et al., eLife 2021](https://pmc.ncbi.nlm.nih.gov/articles/PMC8741211/)
|
||||
|
||||
这些证据不提供本数据集逐边受体、局部时间常数和 mV 转换系数。缺项继续标记 unknown,不人工补齐。本次主代理核验了上述公开原始来源;eLife 直连出现浏览器验证时使用其 PMC 原文/作者摘要,不把搜索结果中的其他论文当作替代数据。
|
||||
|
||||
## 不重走的路
|
||||
|
||||
- 不因累计 APL/兴奋比值约 3.05 就将 gain 除以 3;那是结果导向调参。
|
||||
- 不因静默比例高就改 KC/MBON 阈值、提高输入或降低验收标准。
|
||||
- 不将 APL→PN 的解剖接触直接解释为 whole-PN 抑制或同 bouton 的有效抑制。
|
||||
- 不顺手恢复/改写 1,212 条未被 graded 替代的非 KC fast APL 边;若测试其作用,另立明确机制对照。
|
||||
- 不根据少放电、多放电或稀疏率单项选择候选。表征、泛化、信用分配和功能性记忆仍须分别验证。
|
||||
|
||||
本页没有新增边、模型参数、learned weights 或行为成功声明。
|
||||
@@ -0,0 +1,89 @@
|
||||
# Phase 4AI:PN 再招募与 APL 来源审计
|
||||
|
||||
正式状态:`PASS-CONDITIONAL-PN-APL-SOURCE-AUDIT`。这是审计通过,不是表征或学习通过。
|
||||
|
||||
## 核验范围
|
||||
|
||||
74 项合成测试通过;固定 8 个小场景、60 个 CAL 主记录、12 个技术重复,共 80 份既有 Phase 4AG-r2 记录。PN 的完整放电事件、计数、最终 V/g/ref 逐位一致;8 个小场景的每步 pre/post 状态一致;12 对统计重复一致。CAL 没有独立保存逐 PN 的每步状态,不能把最终状态/事件验证称为完整逐步历史验证。
|
||||
|
||||
额外独立复核了每个首发前真实边事件的 source spike、18-step delay、真实 canonical edge、权重位值、到达时刻、接受状态、严格首发前条件。跨批唯一性与从原始 source events 重新推导的逐边期望数量均一致,无漏项或重复。
|
||||
|
||||
没有运行新全脑实验、使用 GPU、改参数、改权重、训练或访问 held-out 动态结果。全程只对既有记录做条件重建与非反馈分账;这不是独立全脑 oracle,也不是删掉某条通路的反事实。
|
||||
|
||||
## 1. 非设计 PN 的活动先于 KC
|
||||
|
||||
“非设计”仅表示该 trial 静态刺激目标集合以外,不等于噪声、错误神经元或污染。
|
||||
|
||||
|指标|physiological|matched_energy|
|
||||
|---|---:|---:|
|
||||
|非设计 PN 首次活动早于首个 KC 的主记录|30/30|30/30|
|
||||
|首个非设计 PN tick 范围|40–223|45–216|
|
||||
|首个 KC tick 范围|111–322|126–294|
|
||||
|首次活动前已有非设计 PN 活动的 active KC neuron-trials|1550/1550|1504/1504|
|
||||
|首次活动前已有真实非设计 PN→KC 到达边事件|1539/1550|1496/1504|
|
||||
|非设计 PN 放电 / 全部 PN 放电|138679/154514|137431/153049|
|
||||
|
||||
tick 是保存记录的整数时间索引,dt=0.1 ms;这里给出的是记录内时间,不是相对每个外部刺激 onset 的延迟。上表多 trial 合计不能当作不同神经元数量。
|
||||
|
||||
## 2. 首发前 PN 输入来自哪里
|
||||
|
||||
限定对象为已经放电的非设计 PN:14967 / 14939 neuron-trials,两种模式各涉及 547 个不同 PN。
|
||||
|
||||
|首发前接受的正权重增量来源|physiological|matched_energy|
|
||||
|---|---:|---:|
|
||||
|ALLN|65.7040%|65.7683%|
|
||||
|ALPN|27.7791%|27.8722%|
|
||||
|其他已注释神经元|5.2174%|5.0328%|
|
||||
|KC|0.00124%|0.00105%|
|
||||
|
||||
ALLN 是 metadata 中精确的 `annotation_class == ALLN`,不是 ALIN;没有根据类型前缀补齐身份。来源表同时保留负权重与零权重。
|
||||
|
||||
这些占比是首发前**名义正增量的累计份额**,不是净电流、净去极化或因果贡献。非反馈 V 账本中,ALLN 在首发阈值检查前的平均正/负项约为 +8.93/−5.85 mV(净约 +3.08 mV),而 ALPN 约为 +4.43/−0.11 mV(净约 +4.32 mV)。不能因 ALLN 正增量占比最大就声称其净去极化最大。
|
||||
|
||||
这个统计窗口截至每个 PN 自身首发,不是全部截至全网首个 KC,不能拿 66% 直接解释最早一批 PN 的启动。最早非设计 PN 先于首个 KC,排除了此前 KC spikes 启动这一最早群体的解释;之后的再招募与反馈仍可存在。当前只直接覆盖 ALLN→PN 来边,不是 PN→ALLN→PN 动态闭环、必要性或唯一因果路径证明;后续必须保留负项、时间过滤、其他通路和从未激活 PN 对照。
|
||||
|
||||
## 3. graded APL 的来源
|
||||
|
||||
冻结 APL 输入矩阵是非负 raw-contact 归一化工程映射,不是实际 signed PN→APL LIF 电流,不能称为测得的 dopamine/电导或生理量。
|
||||
|
||||
|non-design ALPN 来源份额|physiological|matched_energy|
|
||||
|---|---:|---:|
|
||||
|整个记录累计 h 写入|73.9038%|73.9797%|
|
||||
|每个 active KC 首发严格之前的 h 合计|66.5249%|66.7294%|
|
||||
|全网首个 KC 严格之前、全部 KC 的 h 合计|62.8646%|62.4176%|
|
||||
|
||||
全网首个 KC 之前的 KC 来源为 0,其余来自 design ALPN。在零初态、由 KC spikes 驱动的算子下,这是结构性结果,不能当成独立证明“KC 反馈无作用”。整窗 KC 来源仍占约 4.72% / 4.64%,每只 active KC 自身首发前占约 7.53% / 7.32%。这些均不证明 PN/APL 是 KC 稀疏度失败的原因。
|
||||
|
||||
三组 float64 合计与保存的 float32 drive/state/h 在预先推导的逐 tick 数值误差界内;这是数学来源分账,不是 bitwise CUDA APL replay。累计份额仍是描述统计,未构造累计来源份额的误差区间,不宣称“主导份额经严格误差界证明”。
|
||||
|
||||
## 4. 共同活动很强,但不能据此宣布气味信息为零
|
||||
|
||||
主比较固定全部 686 个 ALPN 维度;次比较固定六种 CAL 刺激目标并集 76 个 PN 之外的共同 610 维。每模式固定 60 within-seed-pair 与 75 between-odor/same-seed 比较;技术重复不充当新 seed。
|
||||
|
||||
|固定 686 维指标:within / between|physiological|matched_energy|
|
||||
|---|---:|---:|
|
||||
|Cosine distance|0.002812 / 0.003146|0.002936 / 0.003291|
|
||||
|Pearson correlation|0.993240 / 0.992438|0.992964 / 0.992107|
|
||||
|Active-set Jaccard|0.982743 / 0.980222|0.982590 / 0.979699|
|
||||
|Euclidean distance|22.7359 / 26.6852|25.3697 / 25.2796|
|
||||
|
||||
共同活动与小的气味差异可以并存。尤其 matched-energy 的 Euclidean between 不高于 within;不同指标并不一致。这不是无信息的统计证明,也不是学习可用性、held-out 泛化、分类器或显著性检验。
|
||||
|
||||
## 结论与下一步
|
||||
|
||||
原 Phase 4AG `FAIL-FIXED-CAL-REPRESENTATION` 完全不变,未产生学习候选。本轮减少了机制不确定性,没有把“出现差异”当作成功。
|
||||
|
||||
下一步先用固定 60 CAL 的已有记录,核对最早非设计 PN 群及首个 KC 前窗口,分开 design/non-design ALPN 与 ALLN 的正负输入,并查 PN→ALLN 方向的真实带时延先行边。不能挑出现所需结论的 cohort,也不能把 shortest path 当动态路径。
|
||||
|
||||
再按独立预声明协议检验一个时间算子假设:原 graded APL 当步末写入,与固定平移 18 步的工程对照。不能称为实测生理修正,不能调 gain、threshold 或 eta,不能扫描最佳 delay。先验证固定序列的按位平移与末端样本完整性,再决定是否进入闭环;详细边界见 [下一步假设](phase4ai-next-hypotheses.md)。
|
||||
|
||||
## 完整性和保存位置
|
||||
|
||||
- 原始登记 230239 个文件及后续 Phase 4AA–4AH 各冻结目录 checksum 全通过。旧五组 reference 本轮未重跑;上一轮封存 PASS 记录按 SHA 校验未变。
|
||||
- 新模型目录:`models/male-cns-lif/pn-recruitment-audit-v1/`,16 个 payload 文件,170860 bytes。
|
||||
- 新实验目录:`experiments/phase4ai-pn-recruitment-audit/`,734 个 payload 文件,1101594284 bytes,约 1.10 GB。纯 CPU 审计时间合计 726.84 秒,不是脑实时性能 benchmark。
|
||||
- 模型 manifest SHA-256:`a53443ac124e9bebc813867a5015d77276c2bd3edf89cd606a57d54756fe0dd7`。
|
||||
- 实验 manifest SHA-256:`12c6f2e9929b3f7084e8b39b90c8851185febbc0afbb7031f9eebb4c41d2731d`。
|
||||
- Git 仅发布代码、257 份小证据(约 5.85 MB)、文档与校验;完整新事件数据仍在服务器,不是异机全量备份。
|
||||
|
||||
解释指标由 `tools/report_phase4ai.py` 从已封存表只读导出,保存于 `registry/phase4ai-pn-recruitment-audit/interpretation-metrics.json` 并附独立 SHA;不修改原封存 manifest 或科学 gate。
|
||||
@@ -0,0 +1,36 @@
|
||||
# Phase 4AI 后的最小时间算子假设(尚未执行)
|
||||
|
||||
这是后续独立分支的设计建议,不是已经注册或完成的实验,也不是生理正确性结论。Phase 1–4AI 的代码、数据、参数与结论不因此改变;学习门仍关闭。
|
||||
|
||||
先补一项窄范围只读时序检查:在固定 60 CAL 中保留同时首发的全部最早非设计 PN,窗口截至首个 KC,分开 design/non-design PN 与 ALLN 的正负来源;核实 PN→ALLN 方向的真实先行边,必要时仅条件重建 ALLN 的接受状态。Phase 4AI 已验证 ALLN→PN 输入,但未证明动态 PN→ALLN→PN 闭环。此检查不运行新全脑实验,也不用于选择“最佳延迟”。
|
||||
|
||||
## 当前可以由代码确认的时间契约
|
||||
|
||||
现有 graded APL 组件在 PN/KC 于 tick `s` 放电后,当步更新其状态并在 `s` 末写入 KC 的 `g`。这次写入最早影响 `s+1` 的 threshold。真实直接 PN→KC 边在 `s+18` 的 threshold 之后到达,最早影响 `s+19` 的 threshold。
|
||||
|
||||
这是两个算子的调度差异,不足以证明该差异就是失败原因。APL 原参数的单位是每状态每步增量,不是“漏乘 dt”;不能把原数值直接重新解释为 tonic 电位、也不能根据累计抑制/兴奋比反推 gain。
|
||||
|
||||
## 唯一建议的最小对照
|
||||
|
||||
在新的独立目录和预声明协议中,只比较原 `delay=0` 与固定 `delay=18 steps`:
|
||||
|
||||
`h_apply[t] = 0 (t<18), otherwise h_generated[t-18]`。
|
||||
|
||||
其余全部保持原样:APL 状态递推、float32 位值、I/O 映射与归一化、gain、fast-APL mask、LIF 参数、connectome、sign、外部请求文件。不扫描“最佳延迟”,不选效果好的 seed,不训练。18 步来自现有解剖事件队列的工程对齐,不是测得的 MaleCNS APL 生理延迟。
|
||||
|
||||
注意:整条 `h` 延迟也会延迟 KC→APL 反馈,不是 PN-only 干预。解剖边到达仍受 refractory 保护,APL 写入仍不受该保护;时间对齐不等于两种作用完全等价。
|
||||
|
||||
## 两层验证不可混淆
|
||||
|
||||
1. 固定现有上游 spike/h 序列:按位平移 h,前补零并显式保留记录末端再延迟的 18 步。验证记录内每一个 h 样本无损、无漏、不循环移位,保持完整已生成序列的累计抑制量;不得把它称为未知未来的完整衰减尾部。
|
||||
2. 如继续全脑闭环:只能声明同一请求与同一算子参数。放电变化会改变后续 APL drive/state/h,不能声称两条件实际抑制总量仍相同,更不能事后重新归一化。报告末端状态和未排空事件,保留原固定 CAL 与后续学习门。
|
||||
|
||||
先完成零输入、单 PN 脉冲、有限脉冲列、末端脉冲及 reset/refractory 碰撞的 CPU/GPU oracle 检查。`delay=0` 必须重现原记录;任何差异均先停,不进入 CAL。若唯一 delay 改动后完整 CAL 仍失败,则“单独消除这项调度领先足以修复 CAL”的假设不成立;即使通过,也不等于生理正确、泛化通过或学习成功。
|
||||
|
||||
## 一手文献边界
|
||||
|
||||
- [Shiu et al., Nature 2024](https://www.nature.com/articles/s41586-024-07763-9) 使用固定 1.8 ms 的突触延迟,并讨论统一 spiking LIF 对 non-spiking 生理过程的限制。这不等于解剖上删除 APL,不能写成 MaleCNS/APL 实测值。
|
||||
- [Amin et al., eLife 2020](https://elifesciences.org/articles/56954.pdf) 支持 APL 非脉冲活动及局部 KC 抑制;约 5 Hz 成像不足以解析其推测的毫秒级传播,不能据此指定精确 MaleCNS 延迟。
|
||||
- [Prisco et al., eLife 2021](https://pmc.ncbi.nlm.nih.gov/articles/PMC8741211/) 支持 calyx 中 PN/APL/KC 的局部关系和响应归一化,不给出这些 MaleCNS 路径的毫秒延迟。
|
||||
|
||||
因此 0、1.8、3.6 ms 均不得宣称是由这些文献确定的 MaleCNS APL 生理参数。此建议只用于可证伪、单因素、可回归的工程动力学分支。
|
||||
@@ -0,0 +1,79 @@
|
||||
# Phase 4AJ:固定 graded-APL 时序分支
|
||||
|
||||
日期:2026-09-30。正式状态:`PASS-FIXED-CAL-APL-TIMING-HYPOTHESIS`。
|
||||
|
||||
这表示固定校准门通过,不是 held-out 泛化、关联记忆或“学习已经可用”。没有产生候选模型,也没有替换正式 runtime。
|
||||
|
||||
## 唯一干预与独立性
|
||||
|
||||
以封存的 Phase 4AG-r2 为对照,只比较完整 graded-APL 输出 h 在生成当步施加(D0),或延迟固定 18 tick / 1.8 ms 施加(D18)。这是工程时序假设,不是测得的 MaleCNS APL 生理延迟;PN 与 KC 来源的 h 一起延迟。
|
||||
|
||||
未改变 raw、CSR、原始 contact count、W_syn、符号、LIF 参数、APL tau/gain、eligibility-v2 或 14232-edge plastic mask。独立分支继续保留既有 5845 条 fast-APL 输出置零约定,其中 1212 条非 KC 边仍未被 graded 算子替代。没有静默修补这一局限。
|
||||
|
||||
两臂同一 case 的输入请求字节相同;受不应期影响,实际接受事件和闭环生成的 h 可不同。`matched_energy` 匹配的是预期输入量,并非不同气味的实际接受事件数完全相等。
|
||||
|
||||
## 固定 CAL 结果
|
||||
|
||||
每臂 6 气味 × 2 模式 × 5 seeds = 60 条主轨迹,另有 12 次精确重复;每条为 100 ms。两个臂均完整执行,未按结果删去条件。
|
||||
|
||||
| 指标 | D0 生理输入 | D18 生理输入 | D0 匹配预期输入 | D18 匹配预期输入 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 平均 KC 活跃比例 | 1.2713% | 3.4482% | 1.2336% | 3.4072% |
|
||||
| 最小–最大活跃比例 | 0.9350–1.9439% | 2.7067–4.6752% | 0.8858–1.7717% | 2.7067–4.6014% |
|
||||
| 同/异气味 Jaccard | .509072 / .460955 | .516363 / .480098 | .516431 / .481702 | .529681 / .495694 |
|
||||
| 同/异气味 Euclidean distance | 5.811443 / 6.205091 | 9.493907 / 9.972922 | 5.671828 / 5.932021 | 9.263882 / 9.684475 |
|
||||
| 通过的气味对 | 12/15 | 11/15 | 13/15 | 11/15 |
|
||||
| 原六项门槛 | FAIL:平均活跃比例 | PASS | FAIL:平均活跃比例 | PASS |
|
||||
|
||||
原门槛保持:平均 KC fraction 在 3%–9%,所有运行非零且小于 50%,同气味 Jaccard 高于异气味,异气味欧氏距离高于同气味,至少 3 个气味对通过。平均门槛不要求每一条轨迹都达到 3%。每模式 60 个 within 比较、75 个 between 比较;每个气味对使用自己的 within 基准。
|
||||
|
||||
**不能把本结果解释为分离能力全面提高。** 可分气味对数量反而由 12/15、13/15 降为 11/15;欧氏距离还受活跃维度与计数影响。通过的是预先固定的完整 CAL 判据,不是显著性、记忆或生物学真实性证明。
|
||||
|
||||
顶层 `calibration.pass_all` 为 false,因为它要求两个臂的表示门都通过;D0 的已知表示失败没有改变。实际执行看 `execution_gate_pass=true`;正式时序假设结论由完整数值门与 D18 门决定,没有把 D0 失败改写为成功。
|
||||
|
||||
## 数值与完整性
|
||||
|
||||
- 模型合成测试本地和服务器均 65/65;发布工具 12 项、描述工具 6 项,合计 18/18,在本地和服务器均通过。
|
||||
- 60 份封存对照中,420 个 ALLN 的事件、计数及最终 V/g/ref 条件重建通过。
|
||||
- 固定生成序列 68 × 2 = 136 项平移检查通过。初始正零与未消费尾部样本按位保留,不额外推进脑来清空尾部。
|
||||
- 两臂各 8 个 fixture 与 2 个全图 probe,共 20 组 Brian LIF/GPU 与 APL 组件检查通过;32 组 fixture 技术重复通过。
|
||||
- 两臂 120 条 CAL 主轨迹及 24 次重复完整,D0 的 72 次 CAL 对照精确复现旧结果。
|
||||
- 旧五组冻结参考新运行的事件序列、计数和最终 V/g 均逐位一致。原登记 230239 项与后续 Phase 4AA–4AI 各封存目录均校验通过,无 mismatch。
|
||||
|
||||
Brian 独立执行 LIF、产生自己的 spikes 并维护独立 FIFO,但 APL 算术仍共享已冻结 Torch 组件;另有 AST replay/舍入界组件审计。不能称全组件独立 CPU 验证或普遍等价证明。当前仅验证了固定短轨迹,不代表任意时间长度的稳定性。
|
||||
|
||||
旧 reference 加载只读 NumPy 数组时发出 PyTorch 警告;未为此修改封存代码。五组回归仍精确,冻结文件校验通过。诊断耗时包含同步、哈希和进程通信,不是生产运行时速度。
|
||||
|
||||
仓库格式审查保留 3 处非功能性提醒:已封存 `compare_timing.py:611`、`gpu_timing.py:453` 的末尾空行,以及 `test_calibration.py:206` 的行末空格。没有为了通过格式检查改写已登记/封存源代码;这些提醒不是数值失败,也不应报告成“格式检查零提醒”。
|
||||
|
||||
## 最早非设计 PN:补齐的是带时序先行证据
|
||||
|
||||
以下只来自旧 AG-r2 的 60 条对照轨迹,**不是新 D18 的来源归因**。窗口严格早于 earliest cohort 各自首发以及全网首个 KC;所有同 tick 首发 PN 均纳入。
|
||||
|
||||
| 指标 | 生理输入 | 匹配预期输入 |
|
||||
|---|---:|---:|
|
||||
| 最早非设计 PN 早于首个 KC | 30/30 | 30/30 |
|
||||
| 最早 cohort neuron-occurrences | 36 | 34 |
|
||||
| 接受到该 cohort 的 ALLN→PN edge-events | 85 | 91 |
|
||||
| 其中 ALLN 本次放电前、上次 reset 后有 design PN→ALLN 接受事件 | 85 | 91 |
|
||||
| 保存的时序 join pairs | 274 | 320 |
|
||||
| design ALPN 名义正增量合计(SI V) | 2.546500 | 2.189825 |
|
||||
| ALLN 名义正增量合计(SI V) | 1.411025 | 1.446500 |
|
||||
| ALLN 名义负增量绝对值合计(SI V) | .191950 | .209550 |
|
||||
|
||||
这里既有直接 PN 来边,也有符合实际延迟/接受条件的 PN→ALLN→PN 先行链。名义正增量最大的并非 ALLN;不能沿用 4AI 另一群体/窗口的约 66% 数字。85/91 是带时间的边事件数,不是唯一边数;274/320 是可共享先行事件的 join pairs,不是独立因果重复。负权重事件不构成兴奋性招募证据。
|
||||
|
||||
这些结果排除“尚未发生的 KC spike 启动最早 PN”这一解释,但不证明某条边的必要性、充分性、唯一原因,亦不证明气味信息全部丢失。ALLN 是 annotation class,不是 nonspiking 身份。
|
||||
|
||||
## 保存与下一关
|
||||
|
||||
- 新模型:`models/male-cns-lif/apl-timing-lab-v1/`。
|
||||
- 新实验:`experiments/phase4aj-apl-timing/`。
|
||||
- 模型 payload 21 文件、275408 bytes;实验 payload 4954 文件、6991917799 bytes(约 6.99 GB)。这不包含 manifest/checksums 两个控制文件。
|
||||
- 模型 manifest SHA-256:`52b05dd1ff30b6b51ff960fe95904532f5c147731a83f0b5c31f98ca7e53c216`。
|
||||
- 实验 manifest SHA-256:`7f490ada0db30785c61954d8b92147378ddc8bc16b229fe3808bbe501caff44a`。
|
||||
- 发布复制 1957 个小证据/控制文件、8673739 bytes(约 8.67 MB);另有发布收据、索引与只读解释文件,不将它们混入上述复制数量。
|
||||
- [正式状态](../../registry/phase4aj-apl-timing/evidence/audit/final-status.json)、[CAL](../../registry/phase4aj-apl-timing/evidence/calibration/summary.json)、[旧回归](../../registry/phase4aj-apl-timing/evidence/regression/frozen-five.json)、[封存校验](../../registry/phase4aj-apl-timing/seal-verification.json)。
|
||||
- [只读解释指标](../../registry/phase4aj-apl-timing/interpretation-metrics.json)另附 SHA,不修改科学 manifest 或原 publication receipt。
|
||||
|
||||
完整事件数据留在服务器;Git 只同步代码、小报告和校验,不是全量异机备份。下一步见 [独立验证计划](phase4aj-next-validation.md),本阶段没有执行其轨迹,没有训练/条件化、held-out 动态访问、游戏、decoder、RL 或 Web。
|
||||
@@ -0,0 +1,43 @@
|
||||
# Phase 4AJ 后:冻结 D18 的独立验证计划
|
||||
|
||||
状态:**待新阶段预注册与执行**。本文件是计划,不是已通过的结果。
|
||||
|
||||
Phase 4AJ 固定 CAL 已通过,下一关应检验外推,不能继续扫描 delay/gain 或直接宣称学习可用。
|
||||
|
||||
## 固定设计
|
||||
|
||||
只使用已封存 D18;动力学、100 ms 窗口、输入编码、APL 算术/队列、符号、CSR、mask 与原判据全部固定。
|
||||
|
||||
| 验证块 | 固定输入 | 主运行 | 技术重复 |
|
||||
|---|---|---:|---:|
|
||||
| 独立种子 | 原 6 个 CAL 气味 × 5 个新 seeds × 2 模式 | 60 | 12 |
|
||||
| 校准未用气味 | 静态面板后 6 个气味 × 同 5 个新 seeds × 2 模式 | 60 | 12 |
|
||||
|
||||
在任何新脑轨迹之前,锁定两个块的输入、种子、分析、停止规则和 SHA。先完成独立种子块,只有完整通过才解锁第二块;失败就封存,不换 seed/气味重试。第二块固定首气味/首种子的两种模式作新增 Brian 全图对照,不能按结果挑选。
|
||||
|
||||
每个“验证块 × 模式”分别通过原六项完整 CAL 门,四项全部通过;不许合并 12 个气味的平均值来挽救失败。逐位技术重复、有限 h 平移/pending、数值完整性和旧冻结回归仍必须通过。重复不是新随机样本;跨气味共享种子用于原配对统计,两块不应描述为独立随机流或生物重复。
|
||||
|
||||
## 静态输入与泄漏边界
|
||||
|
||||
允许读取原 `phase4l-olfactory-grounding` 的以下静态文件并核对已登记 SHA:
|
||||
|
||||
- `odor-encoder/frozen-odor-panel.parquet`
|
||||
- `odor-encoder/mapped-glomerular-vectors.npz`
|
||||
- `odor-encoder/selection.json`
|
||||
- `receptor-mapping/olfactory-mapping.parquet`
|
||||
|
||||
原行序、split、气味身份、glomeruli 顺序、PN 映射、RNG 列序和零响应规则保持不变。匹配输入基准继续用全 12 气味静态中位数 `2.556107997894287`,不得对任一六气味子集重算。
|
||||
|
||||
禁止提前读取旧 `kc-representation`、整个 12 气味动态输出、`eligible-edges`、`plasticity-magnitude`、`conditioning` 或混合这些结果的综合报告;不能先打开混合表再过滤。静态 A/B 身份也不能用于挑选有利气味替代完整验证块。
|
||||
|
||||
旧 4L 代码曾遍历全 12 气味。因此后六个只叫**“未参与 AG/AJ 校准的气味”**,不能说项目历史从未模拟过。新分支验证是对固定新模型的外推测试,不是完全未被研究过的生物样本。
|
||||
|
||||
先通过仅配置/输入清单的审计确定新 seed namespace,连同显式派生规则检查重用。`52101–52105` 已在 4Q/4R 使用,`51301–51305` 是旧学习评估种子,`512xx` 存在 train_seed+pairing 派生范围;不指定未经查证的新值,不无依据承诺全历史绝对独立。
|
||||
|
||||
## 实现约束与结果范围
|
||||
|
||||
旧 AG `validate_inventory` 硬编码原 CAL seeds/IDs,`read_trial` 记录也硬编码 calibration。新阶段须新增明确 cohort 的适配器,复用来源固定的纯指标与数值校验;不能改旧 globals、伪装成原 CAL 或改封存源码。
|
||||
|
||||
计划依据:仓库 `models/male-cns-lif/phase4l.py` 的静态编码/原指标(约 149、199、204 行),`phase4p_revalidate.py` 的 PN drive(约 226 行),`author-apl-lab-v2/calibration.py` 的指标/inventory(约 77、96、243、268 行),以及封存的 AG `protocol/encoder-audit.json`。行号仅帮助定位,实际以 SHA 登记版本为准。
|
||||
|
||||
即使全部通过,下一关仍是 credit specificity、真实 KC→MBON 功能传递、全脑输出与带控制的 conditioning;不产生 learned candidate,不接游戏、RL 或 decoder。1.8 ms 仍是工程假设,不因此成为实测生物学参数。
|
||||
@@ -0,0 +1,120 @@
|
||||
# Phase 4AK:冻结 D18 的验证与解释边界
|
||||
|
||||
状态:**已完成并封存,`PASS-FROZEN-D18-INDEPENDENT-VALIDATION`。** 两块共 120 个主运行、24 次精确重复及全部预声明门禁通过。本文依据本地已下载、哈希校验的新 AK 最终证据解释结果;这是冻结 D18 分支的表征验证 PASS,不是学习成功或 learned candidate。
|
||||
|
||||
## 本阶段检验什么
|
||||
|
||||
检验已经固定的 AJ D18 分支,能否在预声明的新种子和未参与 AG/AJ 校准的六个气味上,继续满足完整原 4L 表征判据。本阶段没有再次选择 delay 或调整 gain,也没有训练、更新塑性权重或产生 learned candidate。
|
||||
|
||||
两块输入、稀疏请求、源码 SHA、分析规则与停止规则已在任何新脑轨迹前锁定:
|
||||
|
||||
| 验证块 | 固定输入 | 主运行 | 技术重复 |
|
||||
|---|---|---:|---:|
|
||||
| `seed_validation` | 原六个 CAL 气味 × 五个预声明种子 × 两种输入模式 | 60 | 12 |
|
||||
| `novel_to_aj` | 原静态面板后六个气味 × 同五个种子 × 两种模式 | 60 | 12 |
|
||||
|
||||
每次仍为 100 ms;模式为 `physiological` 与 `matched_energy`。每气味、每模式重复首种子,重复用于逐位完整性检查,不是额外随机样本。只有种子块的数值、重复及全部原表征门同时通过,才允许启动第二块。未解锁、未执行的条件不记为 PASS;失败不换种子或气味重试。
|
||||
|
||||
冻结的动力学包括:float64 LIF、float32 legacy graded APL、`dt=0.1 ms`、普通突触延迟 18 步、不应期 22 步、原图与符号/权重/阈值,以及 APL `tau=20 ms`、`gain=0.01`、`normalization=1`。整条生成的 graded-APL `h` 延后 18 步应用,包含其依赖的 PN/KC 实际放电反馈;不是只延后某一种来源。
|
||||
|
||||
APL 仍在步末、reset 之后无条件写入 KC 的 `g`,不新增不应期守卫;仍先把 `h32` 提升为 float64,再乘 SI 换算系数,最后单独相减。保存起始正零填充及窗口末尾尚未应用的队列槽,不推进额外脑时间来清空尾部。这里的 1.8 ms 是被冻结的工程假设,不是经本阶段测量的生物学参数。
|
||||
|
||||
## 原六项门禁,不以汇总平均值补救失败
|
||||
|
||||
每个“验证块 × 模式”分别保留以下六项条件:
|
||||
|
||||
1. 平均 KC 活跃比例处于闭区间 `[3%, 9%]`。
|
||||
2. 所有主运行的 KC 活跃比例最小值严格大于 0。
|
||||
3. 所有主运行的最大值严格小于 50%。
|
||||
4. 同气味 Jaccard 均值大于异气味 Jaccard 均值。
|
||||
5. 异气味欧氏距离均值大于同气味欧氏距离均值。
|
||||
6. 至少 3/15 对气味通过各自的分离判据。
|
||||
|
||||
第六项不是用全体气味的共同基线:每对气味的五个同种子跨气味距离,对照这两个气味各十个种子间距离,合计二十个 within 距离。每个模式共 60 个 within 比较、75 个 between 比较;这些成对统计不是 60/75 个独立生物样本。
|
||||
|
||||
四个分组须分别通过,不合并十二气味均值来挽救某一分组。原数值完整性、逐位技术重复、generated/applied/pending 时序检查、固定对照、oracle/component 检查、旧冻结完整性及五个参考回归仍是必要条件。科学判据失败与执行不完整分别报告,不调阈值、不换观察窗口、不扫描参数。
|
||||
|
||||
## 种子与气味的“新”,各有明确范围
|
||||
|
||||
五个种子为 `1073741825–1073741829`,即 `2^30` 后的首五个正整数。选择依据是预声明静态 namespace 审计与固定算术规则,不依据活动量、可分性或运行成败。
|
||||
|
||||
静态审计列出了 **217 个本地文件快照**及其哈希;其中 **43 项**有明确的远端路径和 SHA 映射。封存 `prepare.py` 在写入预注册文件前,遍历 `seed-audit.json.inspected_files` 中非空的 `remote_path`,逐项断言远端文件哈希等于该行 `sha256`,再将其合入 `preregistered.json.inputs`。本次本地交叉复核确认:43 项全部出现在预注册的 51 项输入清单中,`path/sha256` **43/43 匹配、0 项不符**;预注册同时记录 `before_new_requests_and_brain_trajectories=true`。没有臆造独立的“43 项 PASS”字段或远端复查日志。
|
||||
|
||||
对应的 SHA-256 为:`model/seed-audit.json` → `e14c0deb94432787aa1cac7618254aeb3db0bcd5b1d8328e20428496d38b220a`;`model/prepare.py` → `d8b8315f8800b3efdc7548620f0fae85da21a12b753b9985dd623bd529e2c50f`;`protocol/preregistered.json` → `c9cda32a74b45df192824405e65b98571b6febcddd5f63d78e5edefac619b300`。43 项不是完整远端历史,217 项也不是所有历史执行的输入注册表。
|
||||
|
||||
审计考虑了列出的配置与已解释派生规则,但不覆盖未列出的 server-only 文件、CLI 覆写、终端历史、其他分支/版本、外部调用者或其他用户运行。因此准确说法是“在声明的静态审计范围内未发现重用”,不是“全历史绝对独立”。新整数也不证明 PRNG 流在统计上独立。跨气味、模式及两块继续共享同一组种子,是配对设计,不是彼此独立的生物重复。
|
||||
|
||||
旧 4L 代码曾遍历完整十二气味面板。因此后六气味只称 **“未参与 AG/AJ 校准的气味”**,不能称“项目历史从未模拟的气味”。本阶段可读取固定面板、外部响应向量、静态 selection 与 PN 映射;不能提前解析旧 heldout 动态结果或混合结果表来挑选输入。完整性哈希读取不透明文件字节,也不授权解析那些旧结果。
|
||||
|
||||
## matched energy 匹配的是预期输入
|
||||
|
||||
输入编码、PN 目标及 RNG 列顺序沿用原规则。`matched_energy` 仍使用完整原十二气味面板的固定中位数 `2.556107997894287`,不对任一六气味子集重算,也不按脑活动重新缩放。
|
||||
|
||||
此模式匹配的是编码器定义下的**预期请求输入量**,不是每次实际生成的请求数、实际接受的请求数或闭环全脑总活动。随机抽样会造成实现值变化;不应期状态也影响请求接受。不能把“matched”解释为各次实际事件、有效输入或抑制积分严格相同,更不能事后补发请求以强制相同。
|
||||
|
||||
## 数值验证能证明到哪里
|
||||
|
||||
新阶段直接调用 SHA 固定的封存 AJ 数值实现,不修改旧模块 globals 或动力学。本阶段完成了两个原 D18 全图输入的逐位身份对照,以及每个验证块首气味、首种子、两种模式的四个 Brian 全图 probes,均通过;六份对应的 component 审计全部通过。probe 的 GPU 运行计入对应六十个主运行,不能重复算为独立样本。
|
||||
|
||||
Brian 一侧独立推进 LIF、实际放电和 FIFO,但 graded APL 算术有意共享冻结的 legacy 组件,并另做原始操作的 component/AST 审计。有限 probes 不能被表述为所有输入、所有硬件或全组件独立 CPU/GPU 等价证明。旧八夹具属于源代码固定的继承证据,不声称本阶段重新运行了八夹具。
|
||||
|
||||
旧 fast-APL 屏蔽仍覆盖 5,845 条出边,graded 替代只作用于 KC;其中 **1,212 条非 KC 出边没有 graded 替代**。本阶段没有修复这一作用范围缺口。即使验证通过,也不证明 upstream AL 环路已得到生物学确认,不证明单一来源具有因果必要性。
|
||||
|
||||
## 最终结果
|
||||
|
||||
以下四模式表来自封存的 `validation/<cohort>/summary.json` 及模式摘要。显示值作舍入,门禁按未舍入数据计算。四个分组均通过原六项条件;3–9% 约束是各分组的**均值**范围,不要求每个运行都至少 3%。
|
||||
|
||||
| 验证块 | 模式 | KC 比例均值 / 最小 / 最大 | 同 / 异气味 Jaccard | within / between 欧氏距离 | 通过气味对 | 完整六门 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `seed_validation` | physiological | 3.4670% / 2.4360% / 4.4291% | .543794 / .487243 | 9.106586 / 9.867235 | 12/15 | PASS |
|
||||
| `seed_validation` | matched_energy | 3.4564% / 2.5098% / 4.2815% | .523960 / .498134 | 9.350120 / 9.710761 | 11/15 | PASS |
|
||||
| `novel_to_aj` | physiological | 3.2767% / 2.4114% / 4.2077% | .511063 / .444272 | 9.307078 / 10.158885 | 14/15 | PASS |
|
||||
| `novel_to_aj` | matched_energy | 3.2579% / 2.4114% / 4.2077% | .523955 / .449042 | 9.102812 / 10.059135 | 14/15 | PASS |
|
||||
|
||||
- 最终状态为 `PASS-FROZEN-D18-INDEPENDENT-VALIDATION`;`numerical_gate_pass`、`seed_validation_pass`、`novel_to_aj_pass` 均为 true。
|
||||
- 计划、实际完成及有效聚合的主运行均为 **120**;精确重复 **24/24**。两块各完成 60+12,没有以技术重复增加种子样本数。
|
||||
- `novel_dynamics_executed=true`、`novel_unlock_policy_violation=false`,`stopped=[]`:种子块通过后解锁第二块,没有已记录的违规解锁或停止事件。
|
||||
- D18 身份对照 **2/2**、Brian probes **4/4**、component 审计 **6/6**、独立重算的重复检查 **24/24** 通过。此处数量仍受前述有限 probes、共享 APL 算术的证明范围约束。
|
||||
- `regression/old-integrity-after.json` 对原清单 **230,239** 个文件及后续 **AA–AJ** 纳入链条的模型、实验和相关 registry 全部给出 `pass=true`、空 `mismatches`。按原完整性契约排除缓存、venv 等项目;不能把它改述为整台服务器所有文件均被检查。
|
||||
- `regression/frozen-five.json` 的 `pass_all=true`;五个旧 deterministic-v2 参考为 auditory-10hz、auditory-100hz、olfactory-50hz、mechanosensory-50hz、gustatory-50hz。每项的 `step/neuron_idx/counts/v/g` 均 `exact=true`,记录的 dtype/shape 一致。这验证旧参考未漂移,不把旧 float32 参考误说成 D18 新动力学的额外 oracle。
|
||||
|
||||
### 测试及保留的警告
|
||||
|
||||
封存的服务器 `regression/unit-tests.log` 明确记录 **48 tests / OK**,配套 JSON 为 `exit_code=0`、`pass_all=true`;日志 SHA 为 `5206333f1a31fc62671ebe6bdba02f3b2b2f0303f6009e905d7ca94bdc2a666f`。
|
||||
|
||||
本轮执行会话另外记录:本地模型测试 **48/48**,本地与服务器发布/下载工具测试各 **51/51**(本地 28.230 s、服务器 1.642 s)。这些本地及工具测试结果只有会话控制台记录,**未纳入科学 seal,也没有对应的独立仓库日志文件**;不得虚构证据路径。这些是操作与合成测试,不替代科学门禁。
|
||||
|
||||
同一轮 `verify_frozen.py` 控制台仍出现旧 `phase4c.py:308` 的 `torch.as_tensor` 读取只读 NumPy 数组警告;原源码未因本阶段而修改,警告保留,不写成“无警告”。同一进程的五个参考检查均逐位通过。警告的原始记录在本轮执行会话中,未纳入独立封存日志;逐位参考结果则保存在上述封存 JSON。
|
||||
|
||||
本轮 Git 格式检查也有非数值提醒:默认规则返回 2,报告 16,237 项,其中 16,236 项是封存 `requests.csv` 的 CRLF 换行被识别为尾空白。以一次性 `core.whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol` 识别 CRLF 后,仅剩已登记 `models/male-cns-lif/apl-timing-validation-v1/seed-audit.json:9314` 的末尾空行。没有修改仓库配置或任何封存字节来消除提醒;不能写成“格式检查零提醒”。其余新模型、工具及文档无空白格式异常。实际暂存的 1,305 个模型/registry Git blobs 已按外部回执锚与封存清单逐一校验通过,格式提醒不改变科学 PASS。
|
||||
|
||||
### 封存、发布与本地证据
|
||||
|
||||
发布器记录对模型与实验完整 payload 在复制前后重新验证:模型 **17 个文件、526,864 B**;实验 **2,899 个文件、6,154,144,988 B**。这是 manifest payload 数量和字节数,按封存契约不含自引用 seal 控制文件。
|
||||
|
||||
发布回执 `copied_evidence_files=1283`、`copied_evidence_bytes=4895616`,即复制了 **1,283 个小证据/控制文件、4,895,616 B**。随后本地下载校验覆盖 **1,302 个文件,另加发布回执**;`selected_bytes=6591818`,该字节数包含回执。两份 manifest 自校验和每个实际下载文件的 SHA 均通过。发布/下载的这些计数口径不同,不能相加当作科学样本数。
|
||||
|
||||
| 证据 | SHA-256 |
|
||||
|---|---|
|
||||
| 最终 `audit/final-status.json` | `c49181086ecbd19511b9f1e783afea8f66d0f74348a7b70d7f6c5fa99b4337fc` |
|
||||
| 模型 `model/manifest.json` | `3206a8666718b4a99e09b8384c87dd25895b5d7038d23fad7b93e40379183ccb` |
|
||||
| 实验 `manifest.json` | `25eb7e467eb17c3fa83ffee2e4e82b754f161fd8891e515bed23eeb500c5656d` |
|
||||
| 发布回执 `registry/publication-receipt.json`,亦为下载器必填的外部锚 | `37e85d7f264b844d481aa290918a10c56dfbecdfc7786862166f91d361bf4e62` |
|
||||
|
||||
这些哈希已在本地重新计算并与回执/下载校验记录交叉核对。五参考报告 SHA 为 `020fc362e70c4c0670e200783a8d1682f167f862e59840e8b8d80a4688e22433`;旧完整性报告 SHA 为 `273d4581cb15fb5da851ce19ee005abc15b265fbad4bd0a5bd8738e307fec2d0`。
|
||||
|
||||
发布成功与小证据下载校验不是科学 PASS;小文件证据包不是完整轨迹备份。本地没有下载并重新哈希全部大型轨迹,完整远端 payload 的前后验证由发布回执记录。正式科学 PASS 来自新阶段原始证据重算和最终门禁,而不是复制成功。
|
||||
|
||||
本阶段所有预声明验证块已执行并通过;没有用合并均值挽救失败模式,也没有因结果替换气味、种子或参数。结果支持冻结 D18 在这组新种子及校准未用气味上的表征外推,不支持更宽的全输入、全部硬件、真实生理机制或学习能力声明。
|
||||
|
||||
## 即使全部通过,也不等于学习完成
|
||||
|
||||
本阶段未更新 eligibility-v2,未改变 14,232 条塑性边 mask,未执行 conditioning,未生成 learned candidate,也未连接游戏、decoder、RL 或 reward 接口。最终记录中 `learning_achieved=false`、`candidate_generated=false`、`full_scientific_goal_achieved=false`。
|
||||
|
||||
**下一关 credit specificity 与真实 KC→MBON 功能传递尚未执行。** 后续还须独立验证全脑刺激特异输出,以及带控制的 conditioning;这些是后续门禁,不是本阶段已经取得的结果,不因本轮 PASS 而自动解锁为学习成功。
|
||||
|
||||
## 稳定依据与填表来源
|
||||
|
||||
稳定设计依据为模型目录 `models/male-cns-lif/apl-timing-validation-v1/` 下的 `PROTOCOL.md`、`config.json`、`SEED-AUDIT.md`、`seed-audit.json` 与 `validation.py`。原指标定义来自固定 SHA 的 AG `calibration.py` 与旧 4L `representation`;时序及重复检查来自固定 SHA 的 AJ 实现。
|
||||
|
||||
表格与封存结论仅使用新实验 `experiments/phase4ak-apl-timing-validation/` 下的 `validation/<cohort>/summary.json`、各模式摘要、相关 audit/regression 证据及最终 seal。发布凭据在 `registry/phase4ak-apl-timing-validation/`,已校验的本地小证据位于 `23-ai-gpu/reports/phase4ak-apl-timing-validation/`;工具测试和旧 NumPy 警告的会话来源另行明确标注。以封存哈希和外部回执锚确认版本,不以文件名、阶段名称或本文替代证据。
|
||||
@@ -0,0 +1,74 @@
|
||||
# Phase 4AL — KC credit / MBON 条件性传递审计
|
||||
|
||||
日期:2026-09-30。上一提交:`c6444255fe90fe1bdd5b862e63265587c15df442`。
|
||||
|
||||
正式状态:`PASS-CONDITIONAL-CREDIT-TRANSFER-AUDIT-NOT-LEARNING`。
|
||||
这里的 PASS 指算术、身份和覆盖审计通过,不是学习通过。
|
||||
|
||||
## 实际完成
|
||||
|
||||
- 保留固定 D18,读取全部 120 条 AK 主轨迹和 24 条技术重复,不挑气味、种子或 compartment。
|
||||
- 全部 144 次 MBON 条件基线重构的事件、逐神经元计数、最终 V/g/ref 与封存 AK 逐位一致。
|
||||
- 对实际 KC 发放只读重放冻结 float32 eligibility-v2,在 tick 900 更新后采样,包含该 tick 发放;这是虚拟 gate,没有输入 DAN,没有写入 plastic_delta。
|
||||
- 全部基线通过后,才执行一次预定的全既有 mask 25% 减弱。只改临时权重副本;所有上游/反馈发放钳制,目标 MBON 的阈值、复位和 refractory 独立重算。
|
||||
- 24 次技术重复的基线、credit、分解和条件性结果全部精确一致;不把它们当成额外独立种子。
|
||||
|
||||
## 真实边范围
|
||||
|
||||
全图 166,700 neurons / 25,582,938 edges。4,064 KC、97 MBON;MBON 共 86,039 条入边,其中全部 KC→MBON 61,210 条。冻结候选 mask 为 14,232 条,目标只有 12 个 MBON。
|
||||
|
||||
| compartment 原始字符串 | 候选边 | MBON 目标 |
|
||||
|---|---:|---:|
|
||||
| `B2B'2a` | 4,346 | 2 |
|
||||
| `a1` | 3,651 | 4 |
|
||||
| `y2a'1` | 4,126 | 4 |
|
||||
| `y5B'2a` | 2,109 | 2 |
|
||||
|
||||
95 条 DAN→MBON 边具有已封存的显式 compartment 原文匹配。这是 annotation token 与实际边的依据,不是突触 ROI 的独立定位。其他身份没有凭名称补齐;120 条 fast-APL→MBON 入边仍依冻结分支约定置零,未在本轮修正。
|
||||
|
||||
## 结果与边界
|
||||
|
||||
| 分组 | 各自特有 KC/候选边的气味对 | 候选边 active-set Jaccard 均值 | 25% 条件性试验改变 count 或首次 latency |
|
||||
|---|---:|---:|---:|
|
||||
| seed_validation / physiological | 15/15 | 0.57575 | 30/30 |
|
||||
| seed_validation / matched_energy | 15/15 | 0.61094 | 30/30 |
|
||||
| novel_to_aj / physiological | 15/15 | 0.54014 | 30/30 |
|
||||
| novel_to_aj / matched_energy | 15/15 | 0.57905 | 30/30 |
|
||||
|
||||
分类使用各条件五种子中至少三次 trace>0。Jaccard 比较完整 A-active/B-active 集合,包含 shared,不再把互斥 specific 集合的零交集误当特异性证据。60 对均非空;共享成分仍明显。这里只验证每侧存在 specific KC/边,没有设新的整体学习成功阈值。
|
||||
|
||||
候选边占全部 97 个 MBON 的已接受正向名义输入增量,四组逐试验比例均值为 **1.075%–1.115%**。这个全 MBON 分母不能当作 12 个塑性目标的输入比例。
|
||||
|
||||
补充的封存后描述性复算:固定这 12 个既有候选目标,以每个 trial 的 `sum(accepted_plastic_positive_V)/sum(accepted_total_positive_V)` 求比例,再按组取平均,得到 **11.14%–11.46%**。12×120=1,440 个目标/试验单元中,964 个 count 或首次 latency 改变;其余 85 个 MBON 的这些指标没有改变。此复算只解释分母,不是新增或替代门槛,未改动封存报告。
|
||||
|
||||
该复算输入为 `experiments/phase4al-mb-credit-transfer/audit/per-MBON-primary.parquet`,SHA-256 `fd194e5af084527798b2a651bbc0b16eb772f208ccbf1aae5b1ef3e7520c7b30`。使用 `candidate_target == true` 的全部行,按 `cohort, mode, name` 分组;没有择优子集。
|
||||
|
||||
这些量是模型中已接受边增量或 source-state 的记账,不是能量、真实电导或 dopamine 浓度。分组 g 记账的最大舍入残差为 5.274e-16 SI V,未回写状态或归给某个神经元来源。
|
||||
|
||||
**可作出的判断:** 在这个固定条件回放中,14,232 边的改变能够影响 MBON 功能性发放;不能说 MBON 对它们完全无响应。
|
||||
|
||||
**尚不能作出的判断:** 25% 一致减弱不是气味特异训练得到的 delta,不是响应上界,且 MBON→DAN/MBON/其他反馈没有在反事实中闭环重算。因此不能声称关联记忆、行为学习或完整全脑 credit 已通过,也不能自动解锁 conditioning。
|
||||
|
||||
## 完整性与工程交付
|
||||
|
||||
- 29/29 服务器合成测试通过,包括冻结 NumPy AST / Torch CPU trace 的逐位比较;本地此前缺 Torch 的跳过项在服务器已执行。
|
||||
- 五组旧 deterministic-v2 参考重新运行,事件/count/final V/g 全部逐位一致。这不是宣称新 D18 与旧动力学等价。
|
||||
- 原有 230,239 个登记文件和完整性链共 26 个根全部通过;旧 frozen/reference 产物零差异。
|
||||
- 封存前重新核验所有输出清单/哈希,144 次基线与原始 AK、24 次重复、60 对身份和全部汇总绑定。
|
||||
- 模型 payload 14 文件 / 109,946 bytes;实验 payload 2,166 文件 / 1,336,493,035 bytes。
|
||||
- 发布/下载/Git blob 验证工具 51/51 合成测试通过(本地和服务器)。Git 和本地证据同步不含大型轨迹,**不是全量异机备份**。
|
||||
- 准备阶段仅遇到新实验目录创建权限,已在任何注册/科学计算前处理,并记录于模型 `PREPARATION.md`;旧目录权限未改变。
|
||||
- 原样保存的编译日志有两行行尾空格,导致 `git diff --check` 返回 2;不为排版修改封存字节。它不是数值回归失败;实际 Git blob 已逐个对照 SHA 校验。
|
||||
- 旧 `phase4c.py:308` 仍提示只读 NumPy 数组转 Torch 的警告;本轮没有改旧实现。五组输出仍逐位一致,旧文件校验无变化,警告原样保留。
|
||||
|
||||
模型 manifest SHA-256:`72e4b4e2c53c258939ca355f31eeba2cd6381e126a61efeea651d8bac8df5328`。
|
||||
|
||||
实验 manifest SHA-256:`a22de7d85cbfe760070ff55f5666e74475a3335c63bbd37b41241a94be7e3eea`。
|
||||
|
||||
## 下一关
|
||||
|
||||
先建立单独、预注册的 whole-brain plastic-overlay 验证:零更新必须逐位等同 D18;使用真实 compartment mapping 和固定 eligibility-v2;明确人工 reinforcement 接口,记录实际 KC/DAN 状态与每条边的信用。随后才在新分支评价独立种子的 CS+/CS− 前后 MBON/downstream 差异、完整反馈、零更新/未配对等控制与保持。不能由本轮条件性敏感性直接宣布学习可用。
|
||||
|
||||
保持 raw/base CSR/W_syn/sign、APL 设置、eligibility-v2、mask 和所有既有封存字节只读。没有游戏、视觉、decoder、RL 或 Web。
|
||||
|
||||
文献支持、工程假设和不适用边界见 [EVIDENCE.md](../../models/male-cns-lif/mb-credit-transfer-audit-v1/EVIDENCE.md)。正式结果见 [final-status](../../registry/phase4al-mb-credit-transfer/evidence/audit/final-status.json)。上一轮 D18 表征结果仍见 [Phase 4AK](phase4ak-interpretation.md)。
|
||||
@@ -0,0 +1,54 @@
|
||||
# Phase 4AM — D18 全脑塑性权重接口
|
||||
|
||||
日期:2026-09-30。正式状态:`PASS-EXACT-WHOLEBRAIN-PLASTIC-INTERFACE-NOT-LEARNING`。
|
||||
|
||||
本轮通过的是固定权重接口、闭环运行与记录的一致性验证,不是学习或行为验证。实际运行和正式封存已完成;服务器发布了 3,020 份小证据,本地已按发布收据逐项验收 3,040 个文件(含控制与清单文件)。Git 发布范围仅为源码、文档及小证据。
|
||||
|
||||
## 已完成的固定门
|
||||
|
||||
- zero:120 个固定主输入及 24 次技术重复,共 144/144 与封存 Phase 4AK D18 精确一致;覆盖状态、事件、计数、延迟队列、请求接受及 APL generated/applied/pending 记录。
|
||||
- static25:同一完整库存共 144 次闭环运行完成,24 次技术重复精确一致。每条轨迹的上游、反馈和目标发放都重新计算,没有沿用 AL 的钳制源事件。
|
||||
- 五组旧 deterministic-v2 参考重新运行全部精确;28 个旧完整性根运行前后全部通过。旧参考回归不表示新 D18 与旧动力学等价。
|
||||
- zero / static25 阶段墙时分别为 1665.339973549002 / 1471.0373302119988 秒。主试验报告的最大已分配显存为 526,523,392 bytes;不是全机内存或全部进程峰值。
|
||||
|
||||
每阶段统计样本仍为 120 个主试验,24 次重复不增加独立样本数。两种输入模式和两个原定 cohort 均保留;`novel_to_aj` 仅指相对于 AJ 新增,不宣称历史从未见过。`matched_energy` 仍匹配预期随机输入,不保证每次实际事件数相同。
|
||||
|
||||
## 静态干预的实际含义
|
||||
|
||||
候选 mask 保持 14,232 条边,目标为 12 个 MBON;报告同时覆盖全部 97 个 MBON。原 raw/base CSR、sign、W_syn、APL 参数、eligibility-v2 与 mask 均未改变。
|
||||
|
||||
static25 使用原 float32 候选 base 的 `-.25 * base_candidate_mv` floor,安装时仅对非零 delta 行按独立运算加入 `float64(delta32) * .001` SI 增量。它不是 float64 D18 基础权重的精确 25%,不是训练所得 delta,也不是最大功能效应上界。每个试验开始前固定 delta,试验内不更新权重。
|
||||
|
||||
以下比较均为 static25 相对于 zero,在全部 120 个主试验上的描述性结果:
|
||||
|
||||
| 群体 | 事件序列改变的试验 | 至少一个细胞计数改变的试验 |
|
||||
|---|---:|---:|
|
||||
| 全部 MBON | 120/120 | 120/120 |
|
||||
| 实际 MBON downstream | 120/120 | 120/120 |
|
||||
| descending | 120/120 | 120/120 |
|
||||
| motor | 120/120 | 120/120 |
|
||||
| 12 个 mask-recipient MBON | 120/120 | 119/120 |
|
||||
| KC | 120/120 | 32/120 |
|
||||
|
||||
这些数值不是“每个细胞都改变”,也不必表示群体总 spike count 改变。它们说明固定候选边干预能在本模型的真实全脑反馈中影响发放;不能推出气味特异关联记忆、正确行为方向、感知或运动能力。AL 的非零条件回放使用不同精度干预并钳制反馈,因此不能充当本轮非零结果的严格算术 oracle。
|
||||
|
||||
## 未跨越的边界
|
||||
|
||||
D18 的 1.8 ms APL 延迟仍是工程时序假设,不是实测生理延迟。既有 1,212 条非 KC fast-APL 出边置零、而未由 graded 路径替代的限制未修正。早期 Brian 闭环对照共享 APL 组件,不是所有组件独立实现的 CPU 等价证明。本轮没有重新标定这些假设。
|
||||
|
||||
没有训练、没有 learned candidate、没有学习 PASS;不接游戏、视觉、decoder、RL 或 Web。条件化仍需单独预注册实际 cue/US、冻结 eligibility-v2、独立评估种子、未配对等控制及真实时间保持,不能把静态响应当作已获得记忆。
|
||||
|
||||
下一项 AN 目前仅有 **UNREGISTERED** 工程计划:32 calls、11,074 ticks,用于精简报告运行与 checkpoint 续跑的精确对照;实际脑运行数为 0。学习草案也未登记,没有由本轮结果自动解锁训练或产生候选。
|
||||
|
||||
## 证据与交付
|
||||
|
||||
[正式状态](../../registry/phase4am-wholebrain-plastic-interface/evidence/audit/final-status.json) · [科学汇总](../../registry/phase4am-wholebrain-plastic-interface/evidence/audit/scientific-summary.json) · [固定协议](../../models/male-cns-lif/wholebrain-plastic-interface-v1/PROTOCOL.md) · [发布收据](../../registry/phase4am-wholebrain-plastic-interface/publication-receipt.json)
|
||||
|
||||
- 预注册 SHA-256:`465886b78ada08092c2c83de278844707499718977fb6dc6a7608391da0e5304`
|
||||
- 科学汇总 SHA-256:`03f229fc0768d38bb7c2a3c558dbd68c2c3a622cbf96b1b16aef2819b6b67b77`
|
||||
- 正式状态 SHA-256:`18b12c832e156bd6e5ba950f48a49fb908c62104b91bb3e78213f523c8fdc8f9`
|
||||
- 模型 manifest SHA-256:`7edeb251ecbeac5a64805fefe171a0a0bf8bba2675919ef922cc1a329c203ea8`
|
||||
- 实验 manifest SHA-256:`9e24b682935a9b18e05db7a933080c5f63ddbeb8a0c85cc0df1cfe1d06d88ab4`
|
||||
- 发布收据 SHA-256:`7bb7c8edc1d1c09e9c5aa37c0fb367431ee9b8afe83dbcaff0f3cec5d22017f7`
|
||||
|
||||
大型轨迹保留在服务器。小证据发布不等于全量异机备份;Git 暂存载荷及提交载荷须另外使用发布收据 SHA 校验,不能由本地下载成功推断远端 Git 已同步。
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 4AN — 有限轻记录与连续状态工程验证
|
||||
|
||||
日期:2026-09-30。正式状态:`PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING`。
|
||||
|
||||
这是独立 D18 分支上的有限运行器验证,不是学习、记忆保持或生理模型通过。全部注册的 32 次 GPU 调用已执行,74 项比较通过;最终重新计算分析结果完全相同,AM 双封存与 28 套旧完整性记录运行后通过。AN 没有重新执行五组旧 reference,而是保留 AM 本轮已重新运行并通过的封存证据。
|
||||
|
||||
## 固定范围
|
||||
|
||||
166,700 neurons、25,582,938 edges,原图和 mask 不变。只使用 AM 原计划第一条 primary 的 100 ms 输入及其精确切片,分别保持 `zero`、`static25` 固定 overlay;没有新抽气味/seed、训练或试验内权重更新。
|
||||
|
||||
每个 overlay 包括完整 reference、两次完整 light、三个 reference 前缀,以及 reference/light 各一条段长 `[5,13,1,481,500]` 的连续链。共 11,074 ticks、1.1074 累计生物秒。完整执行流程墙时 433.968245 秒,包括首次编译、校验、进程启动、保存与续跑封存,不包括之后的分析和最终封存。
|
||||
|
||||
## 一致性与非空覆盖
|
||||
|
||||
两种 overlay 的完整新 reference 与各自 AM 输出精确一致。完整 light、技术重复和分段链的事件、计数、最终 V/g/ref、APL 末态、规范化 spike ring 和 APL pending 均符合注册比较。reference 另比较完整逐步状态哈希、APL history 和队列,并用原 CSR 独立重建队列顺序、接受掩码、局部时钟哈希和不应期状态。
|
||||
|
||||
两种 overlay 均有真实跨切点且非零权重、被接受的突触到达。例如 canonical edge 783780:source neuron_idx 522 在 tick 7 发放,target neuron_idx 82 在 tick 25 接收到达,跨过 tick 18 切点;原 SI effective weight 为 0.006875 V。该记录仅证明测试覆盖真实 carry,不证明唯一因果或生物学必要性。另核实同一预定切点存在 spike carry 与非零 APL pending。
|
||||
|
||||
light 不记录完整逐步 V/g/ref/APL/队列 history,未将缺失观测补为零或假称逐步浮点全量独立验证。该结论只适用于本次固定输入、固定 overlay 和预定切点,不是所有气味、任意长度或任意 parent 状态的普遍等价证明。
|
||||
|
||||
## 实测成本
|
||||
|
||||
| 100 ms 完整试验 | reference | light |
|
||||
|---|---:|---:|
|
||||
| 核心模拟墙时 | 5.456–5.509 s | 2.153–2.192 s |
|
||||
| 每条封存输出 | 35.688–35.697 MB | 2.597–2.606 MB |
|
||||
| 分配显存峰值 | 518,003,712 bytes | 518,003,712 bytes |
|
||||
|
||||
light 的运行加即时检查点封存约 5.827–5.854 s;这个数不含随后进程外核验。首次 reference 编译约 59.231 s,后续 build/cache 约 0.46 s。以上是小规模顺序试验,未测 GPU/CPU utilization,不能写成零。一次只读 CPU 比较与早期 light 运行有时间重叠,因此这不是隔离式或普适性能 benchmark,也未达到实时运行。
|
||||
|
||||
`state.npz` 与 terminal queue 的 ZIP entry 均实际检查为无损压缩。封存前输出预算计数为 221,057,911 bytes,低于注册的 8 GiB 上限;该数字不包含随后生成的最终 manifest/checksum 控制文件。
|
||||
|
||||
## 继续学习前的边界
|
||||
|
||||
仅本工程实验被封存;lab 整个目录未封存,实际使用的 21 份源码/测试/协议已按 SHA 锁定并保存快照。后续不得改写它们后沿用此结论。scientific conditioning 草案尚未登记、没有训练、没有 learned candidate。
|
||||
|
||||
当前草案已有 68,000 epochs:30,000×120 ms、30,000×180 ms 和 8,000×120 ms,总计 9,960 生物秒;另有 80 条 10 s 连续 idle,总计 10,760 生物秒。evaluation/probe inventory 尚未完整,所以这仍不是全量预算。仅按本次 100 ms 样本线性算术外推,核心模拟约 64.35–65.52 小时,运行及即时封存约 174.16–174.97 小时;气味活动、长度、缓存和 I/O 不同可能改变成本,不能当作可靠工期承诺。
|
||||
|
||||
下一步须先补齐 evaluation/reversal/retention 集成与有限预算,再登记小规模机制验证。小规模验证不能代替五个训练 seeds、独立 evaluation seeds、全部控制及完整科学门。不得调整参数挽救 PASS;没有自动解锁游戏、RL、decoder 或视觉。
|
||||
|
||||
## 锚点
|
||||
|
||||
- 注册:`7317441126f191ccc6e6d28438870b3921537ebe282a6acb163716a690057945`
|
||||
- 分析:`d5ad47a85e4c22e8a482b26330957a821e394022658531a914c9080187454d03`
|
||||
- 正式状态:`16630d0d666a2467c56b647d021cbdb627a553795f6afcd7f7eb496a2737d815`
|
||||
- 实验 manifest:`e8952a48cd3ea6a93c811a1183a1f7b79e19646299924e7688e5f393737a9aa2`
|
||||
- checksums:`7c22aee142affae52dd5792d655e5839db14380ae84993f99a50bdce53a4135c`
|
||||
|
||||
服务器目录:`experiments/phase4an-engineering-validation/`。原始大数组和轨迹仍保存在服务器;小证据/Git 交付不等于全量异机备份。
|
||||
@@ -0,0 +1,65 @@
|
||||
# Phase 4AO:两轮权重接口中止,后处理隔离诊断通过
|
||||
|
||||
更新:2026-09-30。独立分支 `codex/lif-author-semantics-lab`。
|
||||
|
||||
正式结果为 **FAIL-POSTPROCESS-MODULE-IDENTITY**。这是执行接口失败,不是
|
||||
嗅觉学习或动力学假设的否定证据。完整 conditioning 没有启动,没有 learned candidate。
|
||||
|
||||
## 实际完成与未完成
|
||||
|
||||
预登记 12 条短调用:D1 / D0 / frozen × reference / light × 两个 120 ms fresh-reset epoch。
|
||||
master 为 20260930,固定第一 PAM15 block;所选两个连续 epoch 均为 A 气味。
|
||||
D0/frozen 仍共享同一 CS 和人工 DAN 请求,只改变边界更新命令。没有选择性重抽种子。
|
||||
|
||||
- 新增协调器的服务器 CPU 合成测试 61/61 通过,0 跳过。
|
||||
- 实际 CUDA 抽样与固定原 L 的抽样表达式一致;完整 63 位种子的高位影响已检查。
|
||||
- 两轮固定输入分别有 78/65 个 CS 请求,各有 4 个 US 请求。
|
||||
- 第一条 reference 全脑轨迹完成:166,700 neurons、25,582,938 edges、1200 ticks、
|
||||
36,539 spikes,所有有限值检查通过。82 个请求中 55 个接受、27 个因实际状态拒绝。
|
||||
- 核心 reference 模拟 6.514 s;该次完整 runtime 66.935 s,其中编译/缓存准备 59.222 s。
|
||||
最大已分配显存 518,003,712 bytes。不可把这条含首次编译结果称为优化基准。
|
||||
- 轨迹已立即封存,但其后处理失败。因此是 **1 条神经轨迹完成、0/12 条完整流程完成**,
|
||||
不是 1/12 整体门通过;其余神经调用没有运行。
|
||||
|
||||
## 原因和隔离诊断
|
||||
|
||||
普通 `runtime_light` 与 continuation 内部加载的另一个同源实例各有私有模块缓存,
|
||||
却共享同一全局 helper 名。第二实例正确拒绝了未由其缓存认证的现有模块。
|
||||
没有绕过该安全检查、修改冻结 loader 或重跑已完成轨迹。
|
||||
|
||||
原 61 个测试没有覆盖“两个真实 loader 同进程组合”这一边界;修复诊断新增实际 loader
|
||||
进程隔离测试,能复现冲突,并验证干净进程加载成功、伪模块和错误 SHA 仍被拒绝。
|
||||
|
||||
在独立 CPU 进程、独立目录 `experiments/phase4ao-postprocess-isolation/` 中,原 E
|
||||
后处理使用完全相同的 sealed 轨迹成功。实际 KC events 为 137;从完整 14,232-edge
|
||||
mask 中,有 90 条属于目标 compartment 的边产生非零 delta,跨 compartment 改动为 0。
|
||||
这些 delta **未安装到脑、未用于下一轮、不是学习成功证据**。原 AO 状态保持 FAIL。
|
||||
后处理隔离与发布工具的服务器合成测试最终共 19/19 通过;发布测试另修正了仅适配本地
|
||||
目录的 fixture 路径错误,未修改生产模型或已登记源码。
|
||||
|
||||
## 冻结完整性
|
||||
|
||||
AN 与 AM 的完整封存重新校验通过;继承的 28 套旧完整性检查通过。
|
||||
原 21 个 AN 源、前一批 20 个科学准备源和本次登记的全部 51 源文件均未修改。
|
||||
五组旧参考使用已封存 AM 证据,本次没有重新执行,不混称为重新回归。
|
||||
|
||||
## 下一步
|
||||
|
||||
为独立进程后处理建立新的有限预登记;保留这次中止与诊断结果,不续写或重启已封存 AO,
|
||||
不因结果调整 seed、输入、eta、阈值或脑参数。先验证跨 epoch 的 delta 实际安装和所有
|
||||
reference/light/零更新对照,再处理新权重与连续 retention 状态的交接。之后才有条件
|
||||
登记至少五个训练种子、独立评估种子及完整功能控制。游戏、RL、decoder、视觉和 Web
|
||||
仍不在范围内。
|
||||
|
||||
## 来源与保存范围
|
||||
|
||||
代码版本:`4ff241bdc96319823f5fc83f80312468f5208a5c`。
|
||||
注册 SHA:`9e2ed05879ce279da7dc0b4cac09a44d67665c230b56db94e2ca39c645bdc250`。
|
||||
实验 manifest SHA:`dcfb24338a39ce0bb4d0bf5fe3c92a1907fb63536b4c57856c2f40203996f979`。
|
||||
正式状态 SHA:`f8fff2cb7d4eb0abfc9653c989781c0310b6ad7ec42cce4949bae1a15fd793d8`。
|
||||
隔离诊断 manifest SHA:`202efff603a05016b3f530df9eb1d3c52e1c4119dbc4372ad7b75c6ca69013a2`。
|
||||
|
||||
AO 小证据 registry 已发布 110 份来源文件,连同控制与索引共 117 个文件,本地逐项
|
||||
SHA-256 验收通过;隔离诊断的完整 10 个小文件也已本地验收。Git 同步源码、文档、
|
||||
AO 小证据与诊断 JSON,原始大型轨迹和完整约 83.8 MB 紧凑语义计划仍以服务器保存为准,
|
||||
不得把 Git 小证据同步称为全量异机备份。
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"CUDA_VISIBLE_DEVICES": "",
|
||||
"after_overlay_sha256": "b89e407a5d93b5f1901f901b00428dc19bd20cee2d81e57b117a2fa68d200a48",
|
||||
"authority_sha256": "9c86c505d6156ca4ba0496105a7fde0556c0a05bfbd68dec6e7ec7c1865cdb60",
|
||||
"before_overlay_sha256": "0b219eab09c30dc3af31fef7e6395a9bec1234090abbb72db096da833fed82c5",
|
||||
"candidate_generated": false,
|
||||
"checkpoint_manifest_sha256": "e2ad8068bcc8450840ff4c5fb5fbe6a39a1960cd5869b5e16096fb579df28a3a",
|
||||
"counts": {
|
||||
"actual_KC_events": 137,
|
||||
"actual_bitwise_changed_edges": 90,
|
||||
"actual_off_compartment_bitwise_changed_edges": 0,
|
||||
"actual_value_changed_edges": 90,
|
||||
"candidates": 14232,
|
||||
"events": 36539,
|
||||
"original_apply_v2_executed": true,
|
||||
"original_updated_edges_attempted": 90,
|
||||
"sampled_eligible_selected_already_at_floor": 0,
|
||||
"sampled_eligible_selected_edges": 90,
|
||||
"selected_at_floor_after": 0,
|
||||
"selected_at_floor_before": 0,
|
||||
"selected_compartment_edges": 2109,
|
||||
"selected_newly_at_floor": 0
|
||||
},
|
||||
"diagnostic_script_sha256": "b1070733f70adee166bb7ae0da31f60c75dcaeaf31436ba1dc7d575b91ac5d81",
|
||||
"interpretation": "The same pinned E succeeds in a fresh CPU process without the competing ordinary runtime loader cache. This is post-hoc diagnosis, not completion of AO.",
|
||||
"learning_success_claimed": false,
|
||||
"new_brain_runs": 0,
|
||||
"old_trajectory_rerun": false,
|
||||
"original_12_call_gate_passed": false,
|
||||
"original_protocol_remains_stopped": true,
|
||||
"postprocess_source_sha256": "757935a4815d9f625640df685184ed3fe44a8845323562796da7aa43cbb194e1",
|
||||
"registration_sha256": "9e2ed05879ce279da7dc0b4cac09a44d67665c230b56db94e2ca39c645bdc250",
|
||||
"result_files_sha256": {
|
||||
"/data/digital-drosophila/experiments/phase4ao-postprocess-isolation/postprocess/chain-receipt.json": "874e932264f1f6a84b0724f97e9c2833c16a9da744e6c9171c8a7587ce1cef58",
|
||||
"/data/digital-drosophila/experiments/phase4ao-postprocess-isolation/postprocess/delta-after.npz": "b89e407a5d93b5f1901f901b00428dc19bd20cee2d81e57b117a2fa68d200a48",
|
||||
"/data/digital-drosophila/experiments/phase4ao-postprocess-isolation/postprocess/overlay-manifest.json": "38dabc55ea0569843ee70f13cf570832ecaa8f1ee7f26a7420e7ebc3ea86f6bd",
|
||||
"/data/digital-drosophila/experiments/phase4ao-postprocess-isolation/postprocess/update-receipt.json": "d0a660b4e87a786275852c1d759e2750f42d18b354780a2e4a1036b3b3be5056"
|
||||
},
|
||||
"status": "PASS-ISOLATED-POSTPROCESS-DIAGNOSTIC-ONLY",
|
||||
"stopped_sha256": "45b096d034cc171039b95de69a97538eaf61a9bf4068a47e4e0a2cb16d0a9736",
|
||||
"wall_seconds": 4.321050373000617,
|
||||
"weights_installed": false
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"bytes": 4209,
|
||||
"path": "diagnose_postprocess_isolation.py",
|
||||
"sha256": "b1070733f70adee166bb7ae0da31f60c75dcaeaf31436ba1dc7d575b91ac5d81"
|
||||
},
|
||||
{
|
||||
"bytes": 2578,
|
||||
"path": "diagnostic-receipt.json",
|
||||
"sha256": "cefe84ac1642285b2c815a1f2a03677be064dd38125024f638eda4892cf477b3"
|
||||
},
|
||||
{
|
||||
"bytes": 8118,
|
||||
"path": "finalize_failed_phase4ao.py",
|
||||
"sha256": "a68f25ac6dc9e7b1aff1f8999aad9ee7531576696dd16b6ceb8e847b66dcb6d1"
|
||||
},
|
||||
{
|
||||
"bytes": 44415,
|
||||
"path": "postprocess/chain-receipt.json",
|
||||
"sha256": "874e932264f1f6a84b0724f97e9c2833c16a9da744e6c9171c8a7587ce1cef58"
|
||||
},
|
||||
{
|
||||
"bytes": 32303,
|
||||
"path": "postprocess/delta-after.npz",
|
||||
"sha256": "b89e407a5d93b5f1901f901b00428dc19bd20cee2d81e57b117a2fa68d200a48"
|
||||
},
|
||||
{
|
||||
"bytes": 1085,
|
||||
"path": "postprocess/overlay-manifest.json",
|
||||
"sha256": "38dabc55ea0569843ee70f13cf570832ecaa8f1ee7f26a7420e7ebc3ea86f6bd"
|
||||
},
|
||||
{
|
||||
"bytes": 7637,
|
||||
"path": "postprocess/update-receipt.json",
|
||||
"sha256": "d0a660b4e87a786275852c1d759e2750f42d18b354780a2e4a1036b3b3be5056"
|
||||
},
|
||||
{
|
||||
"bytes": 2728,
|
||||
"path": "test_postprocess_isolation.py",
|
||||
"sha256": "93e1552bf26b7da0799812d2b38e5ce99c8a3b5811fe97527f99d2ef19a4071d"
|
||||
}
|
||||
],
|
||||
"root": "/data/digital-drosophila/experiments/phase4ao-postprocess-isolation"
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# Phase 4AP:进程隔离与两轮 delta 安装精确通过
|
||||
|
||||
更新:2026-09-30。独立分支 `codex/lif-author-semantics-lab`。
|
||||
|
||||
正式封存状态:**PASS-EXACT-PROCESS-ISOLATED-DELTA-INTERFACE-NOT-LEARNING**。
|
||||
这是训练执行接口通过,不是嗅觉记忆、行为学习或 learned candidate。
|
||||
|
||||
## 完成范围
|
||||
|
||||
12/12 个登记槽位完成:1 条明确导入的 AO 已封存神经轨迹,加 11 次新神经运行。
|
||||
第一条仅在新的 CPU 子进程重算后处理,没有重跑大脑;原 AO 继续保持
|
||||
`FAIL-POSTPROCESS-MODULE-IDENTITY`,没有被恢复、覆盖或改判。
|
||||
|
||||
两组已封存输入逐字节复用,新增抽样次数为 0。覆盖 D1/D0/frozen × reference/light
|
||||
× 两个 120 ms fresh-reset epoch;两个 epoch 均为固定 A 气味、同一 PAM15 compartment。
|
||||
新神经运行合计 1.32 生物秒,连同导入轨迹覆盖 1.44 生物秒。
|
||||
epoch 间只继承本 arm/mode 的 delta,不继承神经末态。
|
||||
|
||||
- 40/40 服务器 CPU/实际 loader 子进程契约测试通过;发布工具另 14/14 通过。
|
||||
- 6/6 reference/light 比较通过:实际事件序列、计数、可观测末态、终端延迟队列和 delta 精确一致。
|
||||
- 6/6 已登记跨 arm 对照通过;D0/frozen 的所有 delta 保持精确正零。
|
||||
- 第一轮 D1 有 90 条、第二轮有 79 条边实际更新;每轮跨 compartment 改动均为 0。
|
||||
- 第二轮 reference 和 light 均实际安装本模式第一轮的 90 条非零 delta。
|
||||
- 有效权重 SHA 从冻结 base、fast-APL mask 和实际 delta 独立计算,不采用报告自证。
|
||||
- 所有运行有限值检查通过;未出现 NaN/Inf。各轮 4 个 DAN 请求均接受,实际对应 DAN 放电 4 次。
|
||||
|
||||
第一轮 36,539 spikes / 7,409 active neurons;第二轮 33,390 spikes / 7,080 active neurons。
|
||||
三个 arm 在这两轮的总 spike/active 数相同;不能把非零 delta 或下一轮安装成功解释为
|
||||
气味特异学习。没有 A/B 评估、acquisition、反转、延迟奖励或 retention 结果。
|
||||
|
||||
## 实现和边界
|
||||
|
||||
新 brain 与 update 均为真正新启动的 Python 进程。CPU update 在导入科学模块前隐藏
|
||||
CUDA,且只加载原 E 入口;不清除模块缓存,不绕过冻结 loader 的身份验证。
|
||||
worker 的输入、输出全量 SHA 清单、实际退出码、日志、checkpoint、authority 和父 delta
|
||||
共同绑定。错误、超时或部分产物均禁止自动重试。原 51 份冻结源没有改动。
|
||||
|
||||
人工 scheduled D 仍是 unconditioned-signal 接口,不是实测 dopamine 浓度,也没有用
|
||||
实际 DAN 请求接受与否乘改更新命令。原 eligibility-v2、14,232-edge mask、全脑图、
|
||||
sign、LIF、W_syn 和既有 APL 参数均未修改。原 fast-APL 非 KC 出边限制仍存在。
|
||||
light 未记录的逐 timestep 中间 V/g/ref 不声称已逐位观察。
|
||||
|
||||
## 性能和成本口径
|
||||
|
||||
同一 120 ms 输入,light 核心模拟约 2.44–2.58 s;含 runtime 内准备约 4.04–4.14 s。
|
||||
六个 light 槽位记录的流程时间约 18.51–18.82 s,包括独立 brain/update 子进程和当轮
|
||||
验收,但不包含槽位计时开始前的父链检查以及整个最终审计,不能当作完整训练预算。
|
||||
新 reference 首次运行含约一分钟编译,亦不可当作稳态模拟耗时。
|
||||
|
||||
全部槽位流程约 344.82 s(依据启动 UTC 与完成文件时间戳估计,非核心计时器;不包含
|
||||
prepare 和最终完整性审计)。最大已分配显存 518,003,712 bytes,约 494 MiB。
|
||||
只使用登记的单张 RTX 4090,未停止或驱逐三个 ComfyUI 服务。
|
||||
|
||||
后续必须从父进程启动前计到子进程退出和验收后,计入哈希、图加载、CUDA 启动、CPU
|
||||
更新与压缩。worker 自身计时开始于输入验证之后,不可漏算前置成本。当前数字不能直接
|
||||
外推全部训练,也不是实时性能 PASS。
|
||||
|
||||
## 完整性与保存
|
||||
|
||||
AO、独立隔离诊断、AN、AM 封存以及 28 套继承的旧完整性检查,前后均通过。
|
||||
五组旧 reference 使用 AM 已封存证据;本次没有重新执行这五组,不混称为新回归。
|
||||
|
||||
AP 实验封存 472 份 payload、237,871,144 bytes(另有 manifest/checksums)。
|
||||
发布 348 份小型来源证据;registry 共 356 文件、9,402,548 bytes,本地逐项 SHA 验收通过。
|
||||
大型神经轨迹、二进制状态和编译缓存留在服务器;Git 小证据同步不是全量异机备份。
|
||||
|
||||
源码 commit:`0bcce983324ecfe1c75826a23e94ff932ad5e883`。
|
||||
注册 SHA:`02a03b536a9f14196ad4b92fa9565b592732c6c863e72eeaee89b3b0ea8ed37d`。
|
||||
实验 manifest SHA:`a40e433541a6c02f7ad95622e96646da9a90a93e4e43cb3abfde3846b2554f00`。
|
||||
正式状态 SHA:`26de8371867d55253358a4be698de896445712e49e6eee8d99ce852fd0bf43ab`。
|
||||
|
||||
## 下一步:集中准备真实学习验证
|
||||
|
||||
不继续反复扩展两轮接口试验。将剩余准备收拢为一个有界登记包:
|
||||
|
||||
1. 新增独立的“真实末态 + 唯一已验证 delta-after”衔接器,旧 continuation 不改。
|
||||
保留电压、电流、不应期、spike/APL 队列和绝对时钟;base + delta 只安装一次。
|
||||
零更新与旧续跑精确一致;非零边界用连续 reference 与恢复/light 路径比较,并确认
|
||||
实际非空 pending arrival 覆盖变化候选边。没有覆盖就报告 NOT-COVERED。
|
||||
2. 在观察学习结果前完成 reversal/retention 判定,并把磁盘回执、训练/评估种子、
|
||||
完整控制臂和 population 权威接入已有 acquisition 判定函数。
|
||||
3. 用有限实测给完整科学计划登记 walltime/输出容量/资源停机界限,再进入正常 acquisition。
|
||||
正常阶段库存为 30,000 epochs + 3,920 probes;这不是本次已执行工作。
|
||||
禁止用核心模拟时间替代端到端成本,不新增付费资源或无上限运行。
|
||||
|
||||
目标仍是“学习可以使用”,尚未达到。不因这次接口 PASS 宣称成功,不调参或换 seed
|
||||
挽救结果,不接游戏、视觉、decoder、RL、Web 或 3D。
|
||||
@@ -0,0 +1,72 @@
|
||||
# Phase 4AJ: a single graded-APL timing hypothesis
|
||||
|
||||
Independent branch authorized by the human. All earlier models and data remain
|
||||
read-only. The learning goal remains unmet; neither an audit nor fixed-CAL PASS
|
||||
constitutes usable learning.
|
||||
|
||||
## Fixed intervention
|
||||
|
||||
Use the exact sealed AG author-style float64 LIF and float32 legacy graded APL.
|
||||
Compare h applied at its generating tick with the identical generated operator
|
||||
applied 18 steps later. No other change: dt=0.1 ms, anatomical delay=18 steps,
|
||||
refractory=22 steps, tau_m=20 ms, tau_syn=5 ms, W_syn=0.275 mV, original signs,
|
||||
threshold/rest/reset, all canonical IDs/edges, legacy APL tau=20 ms and gain=0.01.
|
||||
The unchanged fast-APL zero mask includes 1,212 non-KC edges not replaced by the
|
||||
graded operator. The branch does not silently fix this limitation.
|
||||
|
||||
This is engineering scheduling parity, not evidence of a measured 1.8 ms graded
|
||||
APL delay. Whole h is delayed, including KC feedback; it is not a PN-only
|
||||
intervention. APL still subtracts unconditionally at end-of-step, including
|
||||
refractory KC, whereas ordinary synaptic arrivals are guarded by refractory.
|
||||
Equal arrival time is not equivalent biological action.
|
||||
|
||||
## Stages and gates
|
||||
|
||||
1. Exact conditional reconstruction of all 420 metadata-defined ALLN in all
|
||||
60 sealed CAL r0 records. Audit every simultaneous earliest non-design PN,
|
||||
before first KC, and duration-matched never-active PN controls. PN→ALLN→PN
|
||||
witnesses must be actual accepted timed edges; distinguish signed weights,
|
||||
reset windows and zeroed edges. No shortest paths, unique-cause or necessity
|
||||
claims. Unknown antecedents are retained and do not falsify numerical audit.
|
||||
2. Fixed stored h trace shift audit: no loss, duplication, amplitude arithmetic
|
||||
or brain simulation. Initial +0 padding and chronological unconsumed tail
|
||||
explicitly retained. No extra brain/APL tail advance or future-decay claim.
|
||||
3. Both arms: eight fixed real-edge fixtures, three fresh GPU repeats, independent
|
||||
Brian LIF/own spikes/own FIFO, legacy APL component audit. Delay0 must reproduce
|
||||
sealed AG scientific outputs bitwise.
|
||||
4. Both arms: the same two fullgraph first-seed CAL probes, independent Brian
|
||||
comparison and APL component audit. Numerical failure stops later execution.
|
||||
5. Both arms: every original six-odor × two-mode × five-seed CAL, plus first-seed
|
||||
repeat for each odor/mode. Exact source input files, not regenerated random
|
||||
inputs. All results retained. Known baseline representation failure does not
|
||||
skip treatment; no seed selection, threshold/gain search or gate relaxation.
|
||||
6. Recompute comparisons from raw evidence, old checksums and five frozen
|
||||
reference regressions. Seal truthful outcome with SHA-256 and source version.
|
||||
|
||||
## Interpretation limits
|
||||
|
||||
Generated h depends on changed closed-loop spikes, so branch inhibition integrals
|
||||
need not match. Fixed-trace conservation is a transport check, not proof of
|
||||
equal closed-loop dose. Accepted request counts can also change with refractory
|
||||
state, despite byte-identical requested input schedules.
|
||||
|
||||
Brian state integration is independent but APL arithmetic is intentionally a
|
||||
shared frozen component. Original operator AST replay and rounding audit remain
|
||||
separate; no universal CPU/GPU equivalence is claimed from sampled fixtures.
|
||||
|
||||
CAL gate retains original 3–9% mean KC fraction, all-run limits, within/between
|
||||
Jaccard and Euclidean gates, and minimum odor-pair count in both input modes.
|
||||
Passing fixed CAL is not heldout generalization or associative memory. Further
|
||||
progress requires independent seeds/novel odors, credit specificity, isolated and
|
||||
whole-brain functional readouts, then conditioning with proper controls.
|
||||
|
||||
No training, eta/threshold/sign tuning, plastic mask expansion, heldout dynamic
|
||||
access, visual input, decoder, RL, game, Web or new candidate in this experiment.
|
||||
|
||||
## Literature scope
|
||||
|
||||
Shiu et al. 2024, DOI 10.1038/s41586-024-07763-9, supplies the spiking-LIF
|
||||
scheduling reference, not a measured MaleCNS graded-APL delay. Amin et al. 2020,
|
||||
DOI 10.7554/eLife.56954, and Prisco et al. 2021 (PMC8741211) motivate local graded
|
||||
APL/calyx interaction but do not determine this millisecond delay value. Prior
|
||||
sealed source/literature audits retain versions and provenance.
|
||||
@@ -0,0 +1,513 @@
|
||||
"""Conditional audit of the earliest non-design ALPN cohort in 60 sealed CALs.
|
||||
|
||||
CPU only. No new network trajectory, learning, parameter change, heldout input,
|
||||
or shortest-path attribution. Full ALLN inputs and compact outgoing witnesses
|
||||
are retained separately so a potentially large temporal join is not expanded.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
import traceback
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
N = 166700
|
||||
E = 25582938
|
||||
DELAY = 18
|
||||
BUDGET = 2 * 1024**3
|
||||
MODES = ('physiological', 'matched_energy')
|
||||
SEEDS = tuple(range(51101, 51106))
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
return a.dtype == b.dtype and a.shape == b.shape and a.tobytes() == b.tobytes()
|
||||
|
||||
|
||||
def first_ticks(ticks, nodes, targets, n=N):
|
||||
first = np.full(n, np.iinfo(np.int32).max, np.int32)
|
||||
np.minimum.at(first, nodes, ticks)
|
||||
first[first == np.iinfo(np.int32).max] = -1
|
||||
return first[targets]
|
||||
|
||||
|
||||
def select_cohort(pn, first_pn, design, first_kc, steps):
|
||||
"""Static-design complement, ALL ties, never choose by antecedent outcome."""
|
||||
require(pn.dtype == np.int32 and first_pn.dtype == np.int32 and pn.shape == first_pn.shape,
|
||||
'PN identity/first-tick schema mismatch')
|
||||
require(np.all(np.diff(pn) > 0) and np.isin(design, pn).all(), 'Invalid PN/design IDs')
|
||||
require(np.all((first_pn >= -1) & (first_pn < steps)), 'PN first tick outside trial')
|
||||
require(first_kc == -1 or 0 <= first_kc < steps, 'KC first tick outside trial')
|
||||
non_design = ~np.isin(pn, design)
|
||||
observed = first_pn[non_design & (first_pn >= 0)]
|
||||
earliest = int(observed.min()) if len(observed) else -1
|
||||
cohort = pn[non_design & (first_pn == earliest)] if earliest >= 0 else np.empty(0, np.int32)
|
||||
cutoff = int(first_kc) if first_kc >= 0 else int(steps)
|
||||
return dict(cohort=cohort, earliest_tick=earliest, pre_kc_stop_exclusive=cutoff,
|
||||
pre_kc_window_censored=first_kc < 0,
|
||||
matched_stop_exclusive=min(cutoff, earliest) if earliest >= 0 else cutoff,
|
||||
cohort_before_first_KC=bool(earliest >= 0 and first_kc >= 0 and earliest < first_kc),
|
||||
cohort_relation_to_first_KC=('no_non_design_spike' if earliest < 0 else
|
||||
'KC_unobserved' if first_kc < 0 else 'before' if earliest < first_kc else
|
||||
'same_tick' if earliest == first_kc else 'after'))
|
||||
|
||||
|
||||
def predecessor_counts(incoming_ticks, incoming_weights, source_tick, previous_source_tick):
|
||||
"""Strict temporal antecedents. Old-reset history is reported separately."""
|
||||
ticks, weights = np.asarray(incoming_ticks), np.asarray(incoming_weights)
|
||||
require(ticks.ndim == weights.ndim == 1 and ticks.shape == weights.shape,
|
||||
'Predecessor arrays differ')
|
||||
require(np.isfinite(weights).all() and np.all(np.diff(ticks) >= 0), 'Bad predecessor sequence')
|
||||
require(-1 <= previous_source_tick < source_tick, 'Bad previous/source spike order')
|
||||
prior = ticks < source_tick
|
||||
surviving = prior & (ticks > previous_source_tick)
|
||||
return dict(all_prior_design_PN_events=int(prior.sum()),
|
||||
since_last_reset_design_PN_events=int(surviving.sum()),
|
||||
since_last_reset_nonzero_design_PN_events=int(np.count_nonzero(surviving & (weights != 0))),
|
||||
since_last_reset_positive_events=int(np.count_nonzero(surviving & (weights > 0))),
|
||||
since_last_reset_negative_events=int(np.count_nonzero(surviving & (weights < 0))),
|
||||
since_last_reset_zero_events=int(np.count_nonzero(surviving & (weights == 0))),
|
||||
since_last_reset_positive_weight_SI=float(np.maximum(weights[surviving], 0).sum(dtype=np.longdouble)),
|
||||
since_last_reset_negative_abs_weight_SI=float(np.maximum(-weights[surviving], 0).sum(dtype=np.longdouble)),
|
||||
prior_reset_excluded_events=int(np.count_nonzero(prior & ~surviving)))
|
||||
|
||||
|
||||
def source_labels(global_ids, role, design_mask):
|
||||
result = role[global_ids].astype(object).copy()
|
||||
alpn = result == 'ALPN'
|
||||
result[alpn & design_mask[global_ids]] = 'design_ALPN'
|
||||
result[alpn & ~design_mask[global_ids]] = 'non_design_ALPN'
|
||||
return result
|
||||
|
||||
|
||||
def file_budget(root):
|
||||
size = sum(p.stat().st_size for p in root.rglob('*') if p.is_file())
|
||||
require(size <= BUDGET, 'Origin audit exceeds 2 GiB; no truncation/resampling allowed')
|
||||
return size
|
||||
|
||||
|
||||
class Sink:
|
||||
def __init__(self, path, schema, budget_root):
|
||||
import pyarrow.parquet as pq
|
||||
require(not path.exists(), 'Output exists: ' + str(path))
|
||||
self.writer = pq.ParquetWriter(path, schema, compression='zstd')
|
||||
self.schema, self.rows, self.root = schema, 0, budget_root
|
||||
|
||||
def write(self, columns):
|
||||
import pyarrow as pa
|
||||
table = pa.Table.from_pydict(columns, schema=self.schema)
|
||||
if table.num_rows:
|
||||
self.writer.write_table(table)
|
||||
self.rows += table.num_rows
|
||||
file_budget(self.root)
|
||||
|
||||
def close(self):
|
||||
self.writer.close()
|
||||
file_budget(self.root)
|
||||
|
||||
|
||||
def event_schema():
|
||||
import pyarrow as pa
|
||||
return pa.schema([
|
||||
('edge_id', pa.int64()), ('source_neuron_idx', pa.int32()), ('target_neuron_idx', pa.int32()),
|
||||
('source_body_id', pa.uint64()), ('target_body_id', pa.uint64()),
|
||||
('source_spike_tick', pa.int32()), ('arrival_tick', pa.int32()),
|
||||
('source_event_key', pa.int64()), ('weight_SI', pa.float64()), ('weight_sign', pa.int32()),
|
||||
('source_group', pa.string()), ('accepted', pa.bool_()), ('ref_at_arrival', pa.int32()),
|
||||
('target_previous_spike_tick', pa.int32()), ('before_first_any_KC', pa.bool_())])
|
||||
|
||||
|
||||
class ALLNObserver:
|
||||
"""Save every accepted incoming event, including frozen-zero edges."""
|
||||
def __init__(self, targets, incoming, role, design_mask, body, steps, cutoff, sink):
|
||||
self.targets, self.incoming = targets, incoming
|
||||
self.role, self.design, self.body = role, design_mask, body
|
||||
self.steps, self.cutoff, self.sink = steps, cutoff, sink
|
||||
self.last = np.full(len(targets), -1, np.int32)
|
||||
self.callbacks = self.due = self.accepted = self.rejected = 0
|
||||
self.groups = defaultdict(lambda: dict(due=0, accepted=0, rejected=0, positive=0, negative=0, zero=0))
|
||||
self.design_ticks = defaultdict(list)
|
||||
self.design_weights = defaultdict(list)
|
||||
|
||||
def __call__(self, tick, stage):
|
||||
require(tick == self.callbacks and tick < self.steps, 'ALLN callback coverage mismatch')
|
||||
require(np.all(stage['h32'] == 0) and np.all(stage['delta_si'] == 0), 'ALLN received graded APL')
|
||||
require(not np.any(stage['request']), 'CAL request targeted ALLN rather than PN')
|
||||
require(bit_equal(stage['g'], stage['g_pre_apl']), 'Unexpected ALLN post-APL change')
|
||||
due, accepted = stage['due_edge_indices'], stage['arrival_accepted']
|
||||
require(np.array_equal(accepted, stage['ref_after_reset'][self.incoming.dst_local[due]] == 0),
|
||||
'ALLN arrival acceptance differs from post-reset ref guard')
|
||||
require(tick >= DELAY or not len(due), 'Premature ALLN event')
|
||||
self.last[stage['fired']] = tick
|
||||
labels = source_labels(self.incoming.src_global[due], self.role, self.design)
|
||||
weight = self.incoming.weights[due]
|
||||
for label in np.unique(labels):
|
||||
mask = labels == label
|
||||
item = self.groups[str(label)]
|
||||
item['due'] += int(mask.sum())
|
||||
item['accepted'] += int(np.count_nonzero(mask & accepted))
|
||||
item['rejected'] += int(np.count_nonzero(mask & ~accepted))
|
||||
for name, select in (('positive', weight > 0), ('negative', weight < 0), ('zero', weight == 0)):
|
||||
item[name] += int(np.count_nonzero(mask & accepted & select))
|
||||
selected = due[accepted]
|
||||
src = self.incoming.src_global[selected]
|
||||
local = self.incoming.dst_local[selected]
|
||||
dst = self.targets[local]
|
||||
w = self.incoming.weights[selected]
|
||||
selected_labels = labels[accepted]
|
||||
count = len(selected)
|
||||
self.sink.write(dict(edge_id=self.incoming.edge_id[selected], source_neuron_idx=src,
|
||||
target_neuron_idx=dst, source_body_id=self.body[src], target_body_id=self.body[dst],
|
||||
source_spike_tick=np.full(count, tick - DELAY, np.int32),
|
||||
arrival_tick=np.full(count, tick, np.int32),
|
||||
source_event_key=np.int64(tick - DELAY) * N + src.astype(np.int64),
|
||||
weight_SI=w, weight_sign=np.sign(w).astype(np.int32), source_group=selected_labels,
|
||||
accepted=np.ones(count, bool), ref_at_arrival=stage['ref_after_reset'][local],
|
||||
target_previous_spike_tick=self.last[local], before_first_any_KC=np.full(count, tick < self.cutoff, bool)))
|
||||
# At most all accepted design-PN events are retained in RAM. Full event
|
||||
# rows are already streamed; this compact index never expands path pairs.
|
||||
for target in np.unique(dst[selected_labels == 'design_ALPN']):
|
||||
mask = (dst == target) & (selected_labels == 'design_ALPN')
|
||||
self.design_ticks[int(target)].extend([tick] * int(mask.sum()))
|
||||
self.design_weights[int(target)].extend(w[mask].tolist())
|
||||
self.callbacks += 1
|
||||
self.due += len(due)
|
||||
self.accepted += count
|
||||
self.rejected += int(np.count_nonzero(~accepted))
|
||||
|
||||
def report(self):
|
||||
require(self.callbacks == self.steps and self.due == self.accepted + self.rejected,
|
||||
'Incomplete ALLN replay observer')
|
||||
require(self.accepted == self.sink.rows, 'ALLN event sink row mismatch')
|
||||
return dict(callbacks=self.callbacks, ALLN_targets=len(self.targets), due_events=self.due,
|
||||
accepted_events=self.accepted, rejected_events=self.rejected,
|
||||
source_groups=dict(self.groups), graded_APL_h_zero=True,
|
||||
accepted_zero_weight_rows_preserved=True)
|
||||
|
||||
|
||||
def _window_add(stats, frame, mask, labels):
|
||||
for label in np.unique(labels[mask]):
|
||||
chosen = mask & (labels == label)
|
||||
weights = frame.weight_SI.to_numpy()[chosen]
|
||||
item = stats.setdefault(str(label), dict(events=0, positive_events=0, negative_events=0,
|
||||
positive_weight_SI=0.0, negative_abs_weight_SI=0.0))
|
||||
item['events'] += len(weights)
|
||||
item['positive_events'] += int(np.count_nonzero(weights > 0))
|
||||
item['negative_events'] += int(np.count_nonzero(weights < 0))
|
||||
item['positive_weight_SI'] += float(np.maximum(weights, 0).sum(dtype=np.longdouble))
|
||||
item['negative_abs_weight_SI'] += float(np.maximum(-weights, 0).sum(dtype=np.longdouble))
|
||||
|
||||
|
||||
def _read_npz(path):
|
||||
with np.load(path, allow_pickle=False) as file:
|
||||
return {key: file[key].copy() for key in file.files}
|
||||
|
||||
|
||||
def main():
|
||||
# Lazy import makes all selection/join helpers testable without a server.
|
||||
import pyarrow as pa
|
||||
import pyarrow.parquet as pq
|
||||
from context import (ROOT, EXP, AG, AG_MODEL, AI, AI_MODEL, AH_MODEL, COEFF,
|
||||
load_module, read, save, digest, check)
|
||||
check()
|
||||
require(read(AI / 'audit/final-status.json')['status'] == 'PASS-CONDITIONAL-PN-APL-SOURCE-AUDIT',
|
||||
'AI is not successfully sealed')
|
||||
require(read(AI / 'audit/final-status.json')['pass_all'] is True, 'AI seal failed')
|
||||
outroot = EXP / 'origin-audit'
|
||||
outroot.mkdir(parents=True, exist_ok=False)
|
||||
receipts = []
|
||||
try:
|
||||
sealed_roots = (AI, AI_MODEL, AG, AG_MODEL, AH_MODEL)
|
||||
sealed_inventory = {root: {r['path']: r for r in read(root / 'manifest.json')['files']}
|
||||
for root in sealed_roots}
|
||||
verified_hashes = {}
|
||||
def input_hash(path):
|
||||
path = Path(path)
|
||||
if path in verified_hashes:
|
||||
return verified_hashes[path]
|
||||
actual = digest(path)
|
||||
for root in sealed_roots:
|
||||
if path.is_relative_to(root) and path.name not in ('manifest.json', 'checksums.sha256'):
|
||||
key = path.relative_to(root).as_posix()
|
||||
require(key in sealed_inventory[root], 'Input absent from frozen seal: ' + str(path))
|
||||
frozen = sealed_inventory[root][key]
|
||||
require(actual == frozen['sha256'] and path.stat().st_size == frozen['bytes'],
|
||||
'Input differs from sealed digest/bytes: ' + str(path))
|
||||
break
|
||||
verified_hashes[path] = actual
|
||||
return actual
|
||||
# Verify entry-point metadata and code before importing the frozen code.
|
||||
for path in (AI / 'protocol/inventory.json', AI / 'protocol/neuron-source-groups.parquet',
|
||||
AG / 'protocol/calibration-cases.json', AH_MODEL / 'replay_core.py',
|
||||
AI_MODEL / 'validate_events.py', AG_MODEL / 'calibration.py'):
|
||||
input_hash(path)
|
||||
inventory = [row for row in read(AI / 'protocol/inventory.json')
|
||||
if row['kind'] == 'calibration' and row['repeat'] == 0]
|
||||
require(len(inventory) == 60 and len({r['case'] for r in inventory}) == 60,
|
||||
'Expected exactly the fixed 60 distinct CAL r0 inputs')
|
||||
reference_cases = read(AG / 'protocol/calibration-cases.json')
|
||||
require({r['case'] for r in inventory} == {r['name'] for r in reference_cases},
|
||||
'AI CAL inventory differs from AG CAL inventory')
|
||||
specifications = {r['case']: read(AG / 'cases' / r['case'] / 'spec.json') for r in inventory}
|
||||
cells = {(s['mode'], s['odor_id'], s['seed']) for s in specifications.values()}
|
||||
odors = sorted({s['odor_id'] for s in specifications.values()})
|
||||
require(len(odors) == 6 and cells == {(m, o, seed) for m in MODES for o in odors for seed in SEEDS},
|
||||
'CAL identity grid incomplete or includes repeats/heldout')
|
||||
meta_path = AI / 'protocol/neuron-source-groups.parquet'
|
||||
meta = pd.read_parquet(meta_path)
|
||||
require(len(meta) == N and np.array_equal(meta.neuron_idx.to_numpy(), np.arange(N)),
|
||||
'Metadata global identity mismatch')
|
||||
require(meta.audit_source_group.notna().all() and meta.body_id.dtype == np.uint64,
|
||||
'Invalid frozen role/body metadata')
|
||||
role, body = meta.audit_source_group.to_numpy(), meta.body_id.to_numpy()
|
||||
alln = np.flatnonzero(role == 'ALLN').astype(np.int32)
|
||||
pn = np.load(AG / 'apl-bundle/pn_globals.npy')
|
||||
kc = np.load(AG / 'apl-bundle/kc_globals.npy')
|
||||
apl = np.load(AG / 'apl-bundle/apl_globals.npy')
|
||||
require(len(alln) == 420 and len(pn) == 686 and len(kc) == 4064 and len(apl) == 2,
|
||||
'Frozen role population sizes differ')
|
||||
require(np.array_equal(pn, np.flatnonzero(role == 'ALPN').astype(np.int32)), 'ALPN role IDs differ')
|
||||
require((meta.iloc[alln].annotation_class == 'ALLN').all(), 'ALLN role is not exact annotation class')
|
||||
graph = AG / 'graphs/full'
|
||||
arrays = {k: np.load(graph / (k + '.npy'), mmap_mode='r')
|
||||
for k in ('nodes', 'src', 'dst', 'edge_id', 'weights')}
|
||||
require(bit_equal(arrays['nodes'], np.arange(N, dtype=np.int32)), 'Full graph global node order differs')
|
||||
require(len(arrays['edge_id']) == E, 'Full graph edge count differs')
|
||||
for start in range(0, E, 1000000):
|
||||
stop = min(E, start + 1000000)
|
||||
require(np.array_equal(arrays['edge_id'][start:stop], np.arange(start, stop, dtype=np.int64)),
|
||||
'Full graph edge IDs not canonical identity')
|
||||
core = load_module('frozen_ah_alln_replay_aj', AH_MODEL / 'replay_core.py')
|
||||
validate = load_module('sealed_ai_event_validation_aj', AI_MODEL / 'validate_events.py')
|
||||
cal = load_module('sealed_ag_state_validation_aj', AG_MODEL / 'calibration.py')
|
||||
lookup = np.full(N, -1, np.int32)
|
||||
lookup[alln] = np.arange(len(alln), dtype=np.int32)
|
||||
selected = np.flatnonzero(lookup[arrays['dst']] >= 0)
|
||||
src = arrays['src'][selected]
|
||||
weights = arrays['weights'][selected].copy()
|
||||
weights[np.isin(src, apl)] = 0.0
|
||||
incoming = core.IncomingIndex(src, lookup[arrays['dst'][selected]], arrays['edge_id'][selected], weights, len(alln))
|
||||
# Only incoming PN graph views are required to independently validate AI rows.
|
||||
pnedges = np.flatnonzero(np.isin(arrays['dst'], pn))
|
||||
pnweights = arrays['weights'][pnedges].copy()
|
||||
pnweights[np.isin(arrays['src'][pnedges], apl)] = 0.0
|
||||
shared_inputs = [meta_path, COEFF, AH_MODEL / 'replay_core.py', AI_MODEL / 'validate_events.py',
|
||||
AG_MODEL / 'calibration.py', AI / 'manifest.json', AI / 'checksums.sha256',
|
||||
AG / 'manifest.json', AG / 'checksums.sha256']
|
||||
shared_inputs += [graph / (key + '.npy') for key in arrays]
|
||||
shared_inputs += [AG / 'apl-bundle' / (name + '_globals.npy') for name in ('pn', 'kc', 'apl')]
|
||||
save(outroot / 'shared-inputs.json', [dict(path=str(p.relative_to(ROOT)), bytes=p.stat().st_size,
|
||||
sha256=input_hash(p)) for p in shared_inputs])
|
||||
save(outroot / 'inventory.json', [dict(**r, mode=specifications[r['case']]['mode'],
|
||||
odor_id=specifications[r['case']]['odor_id'], seed=specifications[r['case']]['seed']) for r in inventory])
|
||||
for row in inventory:
|
||||
name = row['case']
|
||||
require(Path(name).name == name and '/' not in name and '\\' not in name, 'Invalid case path')
|
||||
case = AG / 'cases' / name
|
||||
source = AG / 'gpu' / (name + '-r0')
|
||||
require(ROOT / row['source'] == source, 'Inventory source differs from fixed CAL r0 source')
|
||||
previous = AI / 'trials' / (name + '-r0')
|
||||
dest = outroot / name
|
||||
dest.mkdir(exist_ok=False)
|
||||
spec = specifications[name]
|
||||
for path in (source / 'state.npz', source / 'summary.json', case / 'spec.json', case / 'requests.npz',
|
||||
case / 'pn-drive.npz', previous / 'accepted-prefirst-edge-events.parquet',
|
||||
previous / 'per-pn.parquet', previous / 'summary.json'):
|
||||
input_hash(path)
|
||||
require(spec['steps'] == 1000 and spec['duration_ms'] == 100.0, 'CAL duration changed')
|
||||
require(not (case / 'arrays.npz').exists(), 'Unexpected CAL initial-state override')
|
||||
source_summary = read(source / 'summary.json')
|
||||
require(source_summary['complete'] is True and source_summary['completed_ticks'] == spec['steps'],
|
||||
'Incomplete source simulation')
|
||||
ai_summary = read(previous / 'summary.json')
|
||||
require(ai_summary['pass_all'] is True and all(ai_summary['exact'].values()), 'AI exactness failed')
|
||||
state = _read_npz(source / 'state.npz')
|
||||
event_keys = cal.validate_state(state, N, spec['steps'])
|
||||
requests = _read_npz(case / 'requests.npz')
|
||||
drive = _read_npz(case / 'pn-drive.npz')
|
||||
design = np.sort(drive['targets'])
|
||||
require(design.dtype == np.int32 and len(np.unique(design)) == len(design) and
|
||||
np.isin(design, pn).all(), 'Design identities invalid')
|
||||
require(len(design) == spec['target_count'] and drive['rates_hz'].dtype == np.float32 and
|
||||
drive['rates_hz'].shape == design.shape and np.all(drive['rates_hz'] > 0),
|
||||
'Static PN design count/rate schema differs')
|
||||
require(np.isin(requests['neuron_idx'], design).all(), 'Request outside static odor design')
|
||||
design_mask = np.zeros(N, bool)
|
||||
design_mask[design] = True
|
||||
first_pn = first_ticks(state['step'], state['neuron_idx'], pn)
|
||||
observed_kc = first_ticks(state['step'], state['neuron_idx'], kc)
|
||||
first_kc = int(observed_kc[observed_kc >= 0].min()) if np.any(observed_kc >= 0) else -1
|
||||
selection = select_cohort(pn, first_pn, design, first_kc, spec['steps'])
|
||||
cohort = selection.pop('cohort')
|
||||
cohort_mask = np.zeros(N, bool)
|
||||
cohort_mask[cohort] = True
|
||||
never_mask = np.zeros(N, bool)
|
||||
never_mask[pn[first_pn < 0]] = True
|
||||
saved_pn = pd.read_parquet(previous / 'per-pn.parquet')
|
||||
require(np.array_equal(saved_pn.neuron_idx, pn) and np.array_equal(saved_pn.first_spike_tick, first_pn),
|
||||
'Sealed AI PN first-spike identity mismatch')
|
||||
cutoff = selection['pre_kc_stop_exclusive']
|
||||
window_stats = {key: {} for key in ('cohort_pre_KC_and_pre_first', 'never_active_pre_KC',
|
||||
'never_active_non_design_pre_KC', 'never_active_matched_cohort_window',
|
||||
'never_active_non_design_matched_cohort_window')}
|
||||
outgoing_chunks = []
|
||||
cohort_chunks = []
|
||||
rows_seen, previous_event_key = 0, -1
|
||||
counts_per_pn = np.zeros(len(pn), np.int64)
|
||||
for batch in pq.ParquetFile(previous / 'accepted-prefirst-edge-events.parquet').iter_batches(batch_size=65536):
|
||||
frame = batch.to_pandas()
|
||||
columns = {k: frame[k].to_numpy() for k in validate.COLUMN_DTYPES}
|
||||
audit = validate.validate_batch(columns, edge_ids=arrays['edge_id'][pnedges],
|
||||
srcglobals=arrays['src'][pnedges], dstglobals=arrays['dst'][pnedges], weights=pnweights,
|
||||
target_globals=pn, target_first=first_pn, source_event_keys=event_keys)
|
||||
key = frame.arrival_tick.to_numpy().astype(np.int64) * E + frame.edge_id.to_numpy()
|
||||
require(not len(key) or (key[0] > previous_event_key and np.all(np.diff(key) > 0)),
|
||||
'AI event order/uniqueness violated across batches')
|
||||
if len(key):
|
||||
previous_event_key = int(key[-1])
|
||||
require(np.all(frame.arrival_tick.to_numpy() < spec['steps']), 'AI event beyond trial')
|
||||
labels = source_labels(frame.source_neuron_idx.to_numpy(), role, design_mask)
|
||||
require(np.array_equal(frame.source_group, role[frame.source_neuron_idx.to_numpy()]), 'AI source role differs')
|
||||
require(np.array_equal(frame.target_is_design, design_mask[frame.target_neuron_idx.to_numpy()]),
|
||||
'AI target design differs')
|
||||
targets = frame.target_neuron_idx.to_numpy()
|
||||
pre = frame.arrival_tick.to_numpy() < cutoff
|
||||
matched = frame.arrival_tick.to_numpy() < selection['matched_stop_exclusive']
|
||||
cohort_sel = cohort_mask[targets] & pre
|
||||
masks = dict(cohort_pre_KC_and_pre_first=cohort_sel,
|
||||
never_active_pre_KC=never_mask[targets] & pre,
|
||||
never_active_non_design_pre_KC=never_mask[targets] & ~design_mask[targets] & pre,
|
||||
never_active_matched_cohort_window=never_mask[targets] & matched,
|
||||
never_active_non_design_matched_cohort_window=never_mask[targets] & ~design_mask[targets] & matched)
|
||||
for key, mask in masks.items():
|
||||
_window_add(window_stats[key], frame, mask, labels)
|
||||
# The fixed earliest cohort normally makes these small, but the
|
||||
# on-disk ceiling remains hard and no row may be sampled away.
|
||||
if np.any(cohort_sel):
|
||||
selected_frame = frame.loc[cohort_sel].copy()
|
||||
selected_frame['audit_source_group'] = labels[cohort_sel]
|
||||
cohort_chunks.append(selected_frame)
|
||||
out_sel = cohort_sel & (role[frame.source_neuron_idx.to_numpy()] == 'ALLN')
|
||||
if np.any(out_sel):
|
||||
outgoing_chunks.append(frame.loc[out_sel].copy())
|
||||
counts_per_pn += audit['per_target_count']
|
||||
rows_seen += audit['count']
|
||||
require(rows_seen == ai_summary['observer']['first_arrival_record_count'], 'AI antecedent rows incomplete')
|
||||
require(np.array_equal(counts_per_pn, saved_pn[[g + '_accepted_nonzero_before_first_edges'
|
||||
for g in ai_summary['observer']['source_group_names']]].sum(axis=1).to_numpy()),
|
||||
'AI per-PN antecedent completeness differs')
|
||||
saved_event_schema = pq.ParquetFile(previous / 'accepted-prefirst-edge-events.parquet').schema_arrow
|
||||
cohort_table = (pd.concat(cohort_chunks, ignore_index=True) if cohort_chunks else
|
||||
pa.Table.from_batches([], schema=saved_event_schema).to_pandas().assign(audit_source_group=pd.Series(dtype=str)))
|
||||
cohort_table.to_parquet(dest / 'cohort-all-source-antecedents.parquet', index=False, compression='zstd')
|
||||
output = (pd.concat(outgoing_chunks, ignore_index=True) if outgoing_chunks else
|
||||
pa.Table.from_batches([], schema=saved_event_schema).to_pandas())
|
||||
sink = Sink(dest / 'ALLN-all-accepted-incoming.parquet', event_schema(), outroot)
|
||||
observer = ALLNObserver(alln, incoming, role, design_mask, body, spec['steps'], cutoff, sink)
|
||||
try:
|
||||
result = core.replay(np.full(len(alln), spec['model']['resting_potential_mv'], np.float64) * np.float64(.001),
|
||||
np.zeros(len(alln), np.float64), read(COEFF), spec['steps'], alln, requests,
|
||||
dict(step=state['step'], neuron_idx=state['neuron_idx']), incoming,
|
||||
np.zeros((spec['steps'], len(alln)), np.float32), observer)
|
||||
finally:
|
||||
sink.close()
|
||||
observed = np.isin(state['neuron_idx'], alln)
|
||||
exact = {k: bit_equal(result[k], state[k][alln]) for k in ('v', 'g', 'ref', 'counts')}
|
||||
exact.update({k: bit_equal(result[k], state[k][observed]) for k in ('step', 'neuron_idx')})
|
||||
require(all(exact.values()), 'Conditional ALLN replay differs from full AG events/final states: ' + str(exact))
|
||||
with (dest / 'ALLN-replay-final.npz').open('xb') as stream:
|
||||
np.savez_compressed(stream, **result)
|
||||
join_rows = []
|
||||
alln_source_spikes = {int(node): state['step'][state['neuron_idx'] == node]
|
||||
for node in np.unique(output.source_neuron_idx.to_numpy())}
|
||||
for record in output.to_dict('records'):
|
||||
target = int(record['source_neuron_idx']) # Intermediate ALLN.
|
||||
tick = int(record['source_spike_tick'])
|
||||
spikes = alln_source_spikes[target]
|
||||
pos = int(np.searchsorted(spikes, tick))
|
||||
require(pos < len(spikes) and spikes[pos] == tick, 'ALLN outgoing source event absent')
|
||||
last = int(spikes[pos - 1]) if pos else -1
|
||||
predecessors = predecessor_counts(np.asarray(observer.design_ticks[target], np.int32),
|
||||
np.asarray(observer.design_weights[target], np.float64), tick, last)
|
||||
join_rows.append(dict(**record, ALLN_previous_spike_tick=last,
|
||||
ALLN_source_event_key=tick * N + target, source_body_id=int(body[target]),
|
||||
target_body_id=int(body[int(record['target_neuron_idx'])]), **predecessors,
|
||||
witness_status=('timed_design_PN_antecedent_since_last_reset' if
|
||||
predecessors['since_last_reset_nonzero_design_PN_events'] else 'no_direct_design_PN_antecedent_since_last_reset')))
|
||||
# Add declared columns even if the fixed cohort has no ALLN antecedent.
|
||||
join_extra = dict(ALLN_previous_spike_tick='int32', ALLN_source_event_key='int64',
|
||||
source_body_id='uint64', target_body_id='uint64', all_prior_design_PN_events='int64',
|
||||
since_last_reset_design_PN_events='int64', since_last_reset_positive_events='int64',
|
||||
since_last_reset_nonzero_design_PN_events='int64',
|
||||
since_last_reset_negative_events='int64', since_last_reset_zero_events='int64',
|
||||
since_last_reset_positive_weight_SI='float64', since_last_reset_negative_abs_weight_SI='float64',
|
||||
prior_reset_excluded_events='int64', witness_status='object')
|
||||
witnesses = pd.DataFrame(join_rows) if join_rows else output.copy()
|
||||
for key, dtype in join_extra.items():
|
||||
witnesses[key] = witnesses[key].astype(dtype) if key in witnesses else pd.Series(dtype=dtype)
|
||||
witnesses.to_parquet(dest / 'ALLN-to-cohort-PN-witness-join.parquet', index=False, compression='zstd')
|
||||
per_pn = meta.iloc[pn].copy()
|
||||
per_pn['first_spike_tick'] = first_pn
|
||||
per_pn['design_target'] = design_mask[pn]
|
||||
per_pn['earliest_non_design_cohort'] = cohort_mask[pn]
|
||||
per_pn['never_active'] = first_pn < 0
|
||||
per_pn.to_parquet(dest / 'PN-population-and-cohort.parquet', index=False, compression='zstd')
|
||||
per_alln = meta.iloc[alln].copy()
|
||||
per_alln['first_spike_tick'] = result['first_spike']
|
||||
per_alln['spike_count'] = result['counts']
|
||||
per_alln.to_parquet(dest / 'ALLN-population.parquet', index=False, compression='zstd')
|
||||
witness_rows = int((witnesses.since_last_reset_nonzero_design_PN_events > 0).sum())
|
||||
inputs = [source / 'state.npz', source / 'summary.json', case / 'spec.json', case / 'requests.npz',
|
||||
case / 'pn-drive.npz', previous / 'accepted-prefirst-edge-events.parquet',
|
||||
previous / 'per-pn.parquet', previous / 'summary.json']
|
||||
report = dict(case=name, repeat=0, mode=spec['mode'], odor_id=spec['odor_id'], seed=spec['seed'],
|
||||
pass_all=True, exact=exact, ALLN_replay=observer.report(), first_any_KC_tick=first_kc,
|
||||
**selection, cohort_neurons=int(len(cohort)), cohort_neuron_idx=cohort.tolist(),
|
||||
cohort_body_id=[int(x) for x in body[cohort]], never_active_PN=int(never_mask.sum()),
|
||||
never_active_non_design_PN=int(np.count_nonzero(never_mask & ~design_mask)),
|
||||
window_source_accounting=window_stats, AI_antecedent_rows_checked=rows_seen,
|
||||
cohort_ALLN_outgoing_events=len(output), outgoing_events_with_since_reset_design_antecedent=witness_rows,
|
||||
outgoing_events_without_since_reset_design_antecedent=len(output) - witness_rows,
|
||||
compact_join_pair_count=int(witnesses.since_last_reset_nonzero_design_PN_events.sum()),
|
||||
no_direct_antecedent_is_not_unexplained_wholebrain=True,
|
||||
join_contract='same trial; incoming.target_neuron_idx=outgoing.source_neuron_idx; incoming.source_group=design_ALPN; '
|
||||
'incoming.weight_SI != 0; '
|
||||
'outgoing.ALLN_previous_spike_tick < incoming.arrival_tick < outgoing.source_spike_tick; '
|
||||
'outgoing.arrival_tick < cohort first spike and first-any-KC cutoff; both legs exactly 18 ticks; '
|
||||
'all incoming rows accepted at ref=0; signs retained; zero legs not causal transmission',
|
||||
accounting_scope='Accepted nominal SI weight sums, not membrane contribution, necessity or unique cause; '
|
||||
'prior-reset antecedents retained only as history; since-reset nonzero weights can still cancel/round away',
|
||||
window_scope='Cohort first-spike antecedents are censored at their first spike; never-active pre-KC windows are longer. '
|
||||
'Use matched_cohort_window for duration-matched comparisons. If KC never fires, full trial is marked censored.',
|
||||
new_brain_simulation=False, GPU_used=False, candidate_generated=False,
|
||||
CAL_independent_ALLN_per_tick_history_available=False,
|
||||
input_sha256={str(p.relative_to(ROOT)): input_hash(p) for p in inputs})
|
||||
save(dest / 'summary.json', report)
|
||||
save(dest / 'output-hashes.json', [dict(path=p.name, bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(dest.iterdir()) if p.is_file()])
|
||||
receipts.append(dict(case=name, repeat=0, mode=spec['mode'], odor_id=spec['odor_id'],
|
||||
seed=spec['seed'], ALLN_targets=420, exact=exact, pass_all=True,
|
||||
summary_sha256=digest(dest / 'summary.json')))
|
||||
file_budget(outroot)
|
||||
print(name, '420 ALLN conditional exact; earliest cohort', len(cohort), 'PASS', flush=True)
|
||||
require(len(receipts) == 60, 'Incomplete origin audit inventory')
|
||||
save(outroot / 'summary.json', dict(pass_all=True, status='PASS-CONDITIONAL-EARLIEST-AL-ORIGIN-AUDIT',
|
||||
trials=receipts, fixed_CAL_r0_count=60, ALLN_per_trial=420, new_brain_simulation=False,
|
||||
GPU_used=False, no_cartesian_witness_expansion=True, truncated=False,
|
||||
unique_cause_proven=False, necessity_proven=False, output_budget_bytes=BUDGET,
|
||||
meaning='Exact conditional ALLN reconstruction and timed anatomical antecedent audit, not a biological causal gate'))
|
||||
file_budget(outroot)
|
||||
except Exception as error:
|
||||
save(outroot / 'failure.json', dict(pass_all=False, error=type(error).__name__, message=str(error),
|
||||
traceback=traceback.format_exc(), completed=receipts, new_brain_simulation=False))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,368 @@
|
||||
"""Read-only two-arm CAL aggregation using the sealed, full original 4L gate.
|
||||
|
||||
No trajectory is generated here. Representation failure never selects an arm
|
||||
or suppresses the other arm. Timing checks use direct slices, not either
|
||||
runtime's queue, and old scientific outputs are never modified.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
LEGACY_SHA256 = "edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c"
|
||||
STATE_DTYPES = {"step": np.int32, "neuron_idx": np.int32, "counts": np.int32,
|
||||
"v": np.float64, "g": np.float64, "ref": np.int32}
|
||||
APL_FILES = {"apl-drive.float32.npy": 2, "apl-state.float32.npy": 2,
|
||||
"apl-inhibition.float32.npy": 4064}
|
||||
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
|
||||
_DEFAULT_OUTPUT = object()
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def read_json(path):
|
||||
return json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
value = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b""):
|
||||
value.update(block)
|
||||
return value.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
return bool(a.dtype == b.dtype and a.shape == b.shape and
|
||||
np.array_equal(np.ascontiguousarray(a).view(np.uint8),
|
||||
np.ascontiguousarray(b).view(np.uint8)))
|
||||
|
||||
|
||||
def load_legacy(path=None):
|
||||
"""Load the sealed pure functions, without rebinding any old global/path."""
|
||||
if path is None:
|
||||
from context import AG_MODEL
|
||||
path = AG_MODEL / "calibration.py"
|
||||
path = Path(path)
|
||||
require(file_hash(path) == LEGACY_SHA256, "Sealed AG calibration source changed")
|
||||
spec = importlib.util.spec_from_file_location("sealed_ag_calibration_for_aj", path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def _jsonl(path):
|
||||
with Path(path).open(encoding="utf-8") as stream:
|
||||
return [json.loads(line) for line in stream if line.strip()]
|
||||
|
||||
|
||||
def _request_rows(path):
|
||||
with Path(path).open(encoding="utf-8", newline="") as stream:
|
||||
reader = csv.DictReader(stream)
|
||||
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, "Request schema mismatch")
|
||||
return [{key: int(row[key]) for key in REQUEST_FIELDS} for row in reader]
|
||||
|
||||
|
||||
def _component(directory):
|
||||
gpu, oracle = directory / "apl-component.json", directory / "apl-worker-handshake.json"
|
||||
require(gpu.is_file() != oracle.is_file(), "Exactly one backend APL provenance file is required")
|
||||
return read_json(gpu) if gpu.is_file() else read_json(oracle)["handshake"]
|
||||
|
||||
|
||||
def control_identity(newdir, olddir, case):
|
||||
"""Compare delay0 scientific evidence with sealed AG, for fixtures or CAL.
|
||||
|
||||
case is the NEW case directory. Summary paths, budgets, build/wall times and
|
||||
new adapter metadata are deliberately not compared. Missing evidence raises;
|
||||
numerical differences return pass_all=False with each failed check retained.
|
||||
"""
|
||||
newdir, olddir, case = Path(newdir), Path(olddir), Path(case)
|
||||
spec = read_json(case / "spec.json")
|
||||
require(type(spec.get("apl_delay_steps")) is int and spec["apl_delay_steps"] == 0,
|
||||
"Control identity is defined only for explicit delay0")
|
||||
fresh, old = read_json(newdir / "summary.json"), read_json(olddir / "summary.json")
|
||||
require(all(row.get("complete") is True and row.get("pass_all") is True for row in (fresh, old)),
|
||||
"Cannot compare incomplete/failed control evidence")
|
||||
require(all(row.get("steps") == spec["steps"] and row.get("completed_ticks") == spec["steps"] and
|
||||
row.get("case", row.get("name")) == spec["name"] for row in (fresh, old)),
|
||||
"Control case identity/completion changed")
|
||||
checks = {}
|
||||
with np.load(newdir / "state.npz", allow_pickle=False) as aa, np.load(olddir / "state.npz", allow_pickle=False) as bb:
|
||||
require(set(aa.files) == set(bb.files) == set(STATE_DTYPES), "Control state schema mismatch")
|
||||
for name, dtype in STATE_DTYPES.items():
|
||||
first, second = aa[name], bb[name]
|
||||
require(first.dtype == second.dtype == dtype and first.ndim == second.ndim == 1 and
|
||||
np.isfinite(first).all() and np.isfinite(second).all(), "Invalid control state: " + name)
|
||||
checks["state/" + name] = bit_equal(first, second)
|
||||
for name, width in APL_FILES.items():
|
||||
first, second = (np.load(folder / name, mmap_mode="r", allow_pickle=False) for folder in (newdir, olddir))
|
||||
require(first.dtype == second.dtype == np.float32 and first.shape == second.shape == (spec["steps"], width)
|
||||
and np.isfinite(first).all() and np.isfinite(second).all(), "Invalid control APL trace: " + name)
|
||||
checks[name] = bit_equal(first, second)
|
||||
for name in ("tick-state-hashes.jsonl", "queue.jsonl"):
|
||||
first, second = _jsonl(newdir / name), _jsonl(olddir / name)
|
||||
require(len(first) == len(second) == spec["steps"] and all(
|
||||
[row.get("tick") for row in rows] == list(range(spec["steps"])) for rows in (first, second)),
|
||||
"Incomplete control tick record: " + name)
|
||||
checks[name] = first == second
|
||||
checks["requests.csv"] = _request_rows(newdir / "requests.csv") == _request_rows(olddir / "requests.csv")
|
||||
checks["APL_component"] = _component(newdir) == _component(olddir)
|
||||
require(isinstance(old.get("effective_weights_raw_sha256"), str) and
|
||||
len(old["effective_weights_raw_sha256"]) == 64, "Missing old effective-weight evidence")
|
||||
checks["effective_weights_raw_sha256"] = fresh.get("effective_weights_raw_sha256") == old["effective_weights_raw_sha256"]
|
||||
for key in ("nodes", "edges", "precision", "apl_fast_edges_zeroed", "kc_targets_present"):
|
||||
require(key in fresh and key in old, "Missing control summary scientific field: " + key)
|
||||
checks["summary/" + key] = fresh[key] == old[key]
|
||||
if spec["record_history"]:
|
||||
for prefix in ("", "preAPL-"):
|
||||
for field, dtype in (("v", "float64"), ("g", "float64"), ("ref", "int32")):
|
||||
name = f"{prefix}{field}-history.{dtype}.npy" if not prefix else f"{prefix}{field}.{dtype}.npy"
|
||||
first, second = (np.load(folder / name, mmap_mode="r", allow_pickle=False) for folder in (newdir, olddir))
|
||||
checks[name] = bit_equal(first, second)
|
||||
# CPU oracle also saves actual effective Brian weights. GPU has only the
|
||||
# raw effective-weight digest, already compared above.
|
||||
if (newdir / "weights.npy").exists() or (olddir / "weights.npy").exists():
|
||||
require((newdir / "weights.npy").is_file() and (olddir / "weights.npy").is_file(),
|
||||
"Control backend weight-file presence differs")
|
||||
checks["weights.npy"] = bit_equal(np.load(newdir / "weights.npy", mmap_mode="r"),
|
||||
np.load(olddir / "weights.npy", mmap_mode="r"))
|
||||
return {"pass_all": bool(all(checks.values())), "case": spec["name"], "checks": checks,
|
||||
"scope": "Six state arrays, all tick hashes/queues/requests, generated APL, component, effective weights and fixture pre/post histories",
|
||||
"adapter_paths_budgets_and_runtime_metadata_compared": False}
|
||||
|
||||
|
||||
def validate_timing(output, spec, trial):
|
||||
"""Independently verify raw timing files and recorded audit, without a queue."""
|
||||
output = Path(output)
|
||||
steps, delay = spec["steps"], spec["apl_delay_steps"]
|
||||
require(type(delay) is int and delay in (0, 18), "Invalid timing arm")
|
||||
generated = trial["apl"]["apl-inhibition.float32.npy"]
|
||||
applied = np.load(output / "apl-applied.float32.npy", mmap_mode="r", allow_pickle=False)
|
||||
pending = np.load(output / "apl-pending-tail.float32.npy", mmap_mode="r", allow_pickle=False)
|
||||
for name, value, shape in (("generated", generated, (steps, 4064)),
|
||||
("applied", applied, (steps, 4064)), ("pending", pending, (delay, 4064))):
|
||||
require(value.dtype == np.float32 and value.shape == shape and np.isfinite(value).all() and
|
||||
np.all(value >= 0), "Invalid timing array " + name)
|
||||
warmup, consumed, prefix = min(steps, delay), max(steps - delay, 0), max(delay - steps, 0)
|
||||
require(np.all(applied[:warmup].view(np.uint32) == 0) and np.all(pending[:prefix].view(np.uint32) == 0),
|
||||
"Timing initial padding must be positive zero")
|
||||
require(bit_equal(applied[warmup:], generated[:consumed]) and bit_equal(pending[prefix:], generated[consumed:]),
|
||||
"Timing transport is not an exact finite generated-sequence partition")
|
||||
audit = read_json(output / "apl-timing-audit.json")
|
||||
summary = read_json(output / "summary.json")
|
||||
expected = {"pass_all": True, "apl_delay_steps": delay, "steps": steps,
|
||||
"pending_generated_count": min(steps, delay), "pending_initial_zero_prefix": prefix,
|
||||
"pending_application_tick_start": steps, "pending_application_tick_stop_exclusive": steps + delay,
|
||||
"pending_generated_tick_start": consumed, "pending_generated_tick_stop_exclusive": steps,
|
||||
"initial_padding_positive_zero": True, "sample_bits_preserved": True, "brain_tail_advanced": False,
|
||||
"generated_trace": "apl-inhibition.float32.npy", "applied_trace": "apl-applied.float32.npy",
|
||||
"pending_trace": "apl-pending-tail.float32.npy", "pending_raw_sha256": raw_hash(pending),
|
||||
"pending_queue_drained": delay == 0, "finite_generated_sequence_only": True,
|
||||
"future_apl_decay_or_feedback_generated": False, "closed_loop_branch_integrals_claimed_equal": False,
|
||||
"biological_delay_measurement_claimed": False}
|
||||
final_state = trial["apl"]["apl-state.float32.npy"][-1]
|
||||
expected.update(final_apl_state_float32=final_state.tolist(),
|
||||
final_apl_state_u32_bits=final_state.view(np.uint32).tolist(),
|
||||
final_apl_state_nonzero=bool(np.any(final_state != 0)))
|
||||
for key, value in expected.items():
|
||||
require(key in audit and audit[key] == value and type(audit[key]) is type(value),
|
||||
"Timing audit disagrees with independent data: " + key)
|
||||
require(summary.get("apl_delay_steps") == delay and summary.get("apl_timing") == audit,
|
||||
"Timing audit differs from GPU summary")
|
||||
return {"applied": applied, "pending": pending, "audit": audit,
|
||||
"report": {"pass_all": True, "apl_delay_steps": delay, "steps": steps,
|
||||
"generated_applied_pending_bits_exact": True, "pending_generated_count": min(steps, delay),
|
||||
"pending_initial_zero_prefix": prefix, "brain_tail_advanced": False}}
|
||||
|
||||
|
||||
def timing_repeat(first, repeat, legacy):
|
||||
result = legacy.exact_repeat(first, repeat)
|
||||
result["checks"].update(apl_applied=bit_equal(first["timing"]["applied"], repeat["timing"]["applied"]),
|
||||
apl_pending=bit_equal(first["timing"]["pending"], repeat["timing"]["pending"]),
|
||||
timing_audit=first["timing"]["audit"] == repeat["timing"]["audit"])
|
||||
result["pass_all"] = bool(all(result["checks"].values()))
|
||||
result["scope"] += "; plus delayed applied h, finite pending tail and timing audit"
|
||||
return result
|
||||
|
||||
|
||||
def _case_spec(case, old_case, row, delay):
|
||||
spec = read_json(case / "spec.json")
|
||||
old = read_json(old_case / "spec.json")
|
||||
require(type(spec.get("apl_delay_steps")) is int and spec["apl_delay_steps"] == delay,
|
||||
"Case arm/delay mismatch")
|
||||
require(set(spec) == set(old) | {"apl_delay_steps"}, "Unexpected case fields")
|
||||
require(all(spec[key] == value for key, value in old.items() if key not in ("budget_root", "output_budget_bytes")),
|
||||
"A scientific case field changed in addition to APL output delay")
|
||||
require(spec["name"] == row["name"] and spec["kind"] == "calibration" and spec["steps"] == 1000 and
|
||||
spec["duration_ms"] == 100.0 and spec["dt_ms"] == .1 and spec["training"] is False and
|
||||
all(spec[key] == row[key] for key in ("mode", "odor_id", "odor_index", "seed")), "Invalid fixed CAL case")
|
||||
for name in ("requests.npz", "pn-drive.npz"):
|
||||
require(file_hash(case / name) == file_hash(old_case / name), "Frozen CAL input bytes changed: " + name)
|
||||
return spec
|
||||
|
||||
|
||||
def _read_new(legacy, output, case, spec, kc_globals, cfg):
|
||||
trial = legacy.read_trial(output, case, spec, kc_globals, cfg["neurons"], cfg["edges"])
|
||||
trial["timing"] = validate_timing(output, spec, trial)
|
||||
return trial
|
||||
|
||||
|
||||
def _provenance(output, name, repeat):
|
||||
return {"case": name, "repeat": repeat, "sha256": {fname: file_hash(output / fname) for fname in
|
||||
("state.npz", "summary.json", "tick-state-hashes.jsonl", "queue.jsonl", "requests.csv", "apl-component.json",
|
||||
*APL_FILES, "apl-applied.float32.npy", "apl-pending-tail.float32.npy", "apl-timing-audit.json")}}
|
||||
|
||||
|
||||
def collect_arm(arm, delay, records, odors, legacy, cfg, exp, ag, kc_globals):
|
||||
failures, runs, rows, repeats, controls, provenance, timings, modes = [], {}, [], [], [], [], [], {}
|
||||
for row in records:
|
||||
name, first, spec = row["name"], None, None
|
||||
case = exp / "cases" / arm / name
|
||||
try:
|
||||
spec = _case_spec(case, ag / "cases" / name, row, delay)
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="case_spec", case=name, type=type(error).__name__, message=str(error)))
|
||||
for repeat_index in ([0, 1] if row["seed"] == legacy.ORIGINAL_SEEDS[0] else [0]):
|
||||
output = exp / "gpu" / arm / (name + "-r" + str(repeat_index))
|
||||
try:
|
||||
require(spec is not None, "Case spec unavailable")
|
||||
trial = _read_new(legacy, output, case, spec, kc_globals, cfg)
|
||||
require(trial["record"]["requested"] == row["requests"], "Inventory request count changed")
|
||||
timings.append(dict(case=name, repeat=repeat_index, **trial["timing"]["report"]))
|
||||
provenance.append(_provenance(output, name, repeat_index))
|
||||
if repeat_index == 0:
|
||||
first = trial
|
||||
runs[(row["mode"], row["odor_id"], row["seed"])] = {key: first[key] for key in
|
||||
("kc_counts", "expected_external_events", "external_events")}
|
||||
rows.append(first["record"])
|
||||
else:
|
||||
require(first is not None, "Baseline unavailable for exact repeat")
|
||||
report = dict(case=name, **timing_repeat(first, trial, legacy))
|
||||
repeats.append(report)
|
||||
require(report["pass_all"], "Exact repetition differs: " + ", ".join(
|
||||
key for key, value in report["checks"].items() if not value))
|
||||
if delay == 0:
|
||||
control = control_identity(output, ag / "gpu" / (name + "-r" + str(repeat_index)), case)
|
||||
controls.append(dict(repeat=repeat_index, **control))
|
||||
require(control["pass_all"], "Delay0 differs from sealed AG: " + ", ".join(
|
||||
key for key, value in control["checks"].items() if not value))
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="raw_trial" if repeat_index == 0 else "repeat_trial", case=name,
|
||||
repeat=repeat_index, type=type(error).__name__, message=str(error)))
|
||||
# Each complete mode is still described even if the other mode/arm failed.
|
||||
for mode in legacy.ORIGINAL_MODES:
|
||||
needed = {(mode, odor, seed) for odor in odors for seed in legacy.ORIGINAL_SEEDS}
|
||||
if needed.issubset(runs):
|
||||
try:
|
||||
modes[mode] = legacy.summarize_mode(mode, odors, legacy.ORIGINAL_SEEDS, runs, cfg["gates"])
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="representation_calculation", mode=mode, type=type(error).__name__, message=str(error)))
|
||||
numerical = bool(len(runs) == 60 and len(timings) == 72 and not any(
|
||||
row["stage"] != "representation_calculation" for row in failures))
|
||||
repeat_pass = bool(len(repeats) == 12 and all(row["pass_all"] for row in repeats))
|
||||
control_pass = bool(delay != 0 or (len(controls) == 72 and all(row["pass_all"] for row in controls)))
|
||||
representation = bool(len(modes) == 2 and all(row["gate"] for row in modes.values()))
|
||||
execution = bool(numerical and repeat_pass and control_pass)
|
||||
return {"arm": arm, "apl_delay_steps": delay, "pass_all": bool(execution and representation),
|
||||
"execution_gate_pass": execution, "numerical_integrity_pass": numerical, "exact_repeat_pass": repeat_pass,
|
||||
"control_identity_applicable": delay == 0, "delay0_control_identity_pass": control_pass if delay == 0 else None,
|
||||
"original_representation_gate_pass": representation, "expected_base_trials": 60, "valid_base_trials": len(runs),
|
||||
"expected_repeats": 12, "checked_repeats": len(repeats), "checked_timing_trials": len(timings),
|
||||
"modes": modes, "runs": rows, "repeats": repeats, "timing_checks": timings,
|
||||
"control_checks": controls, "failures": failures, "input_output_provenance": provenance}
|
||||
|
||||
|
||||
def summarize(out_json=_DEFAULT_OUTPUT):
|
||||
"""Default writes new evidence exclusively; out_json=None is pure recomputation."""
|
||||
from context import EXP, AG, ROOT, CFG, check
|
||||
target = EXP / "calibration/summary.json" if out_json is _DEFAULT_OUTPUT else (
|
||||
None if out_json is None else Path(out_json))
|
||||
require(target is None or not target.exists(), "Refusing existing calibration summary")
|
||||
arms, failures, odors, encoder = {}, [], [], None
|
||||
try:
|
||||
check()
|
||||
legacy = load_legacy()
|
||||
require(CFG["delays_steps"] == [0, 18], "Fixed timing arms changed")
|
||||
records = read_json(EXP / "protocol/calibration-cases.json")
|
||||
require(file_hash(EXP / "protocol/calibration-cases.json") == file_hash(AG / "protocol/calibration-cases.json"),
|
||||
"Original CAL inventory bytes changed")
|
||||
odors = legacy.validate_inventory(records, CFG)
|
||||
encoder = read_json(EXP / "protocol/encoder-audit.json")
|
||||
require(file_hash(EXP / "protocol/encoder-audit.json") == file_hash(AG / "protocol/encoder-audit.json"),
|
||||
"Frozen encoder audit changed")
|
||||
require(encoder["panel_count"] == 12 and encoder["cal_count"] == 6 and
|
||||
encoder["seeds"] == list(legacy.ORIGINAL_SEEDS) and encoder["requested_before_any_dynamics"] is True and
|
||||
encoder["formal_AB_run"] is False and encoder["heldout_run"] is False, "Invalid frozen encoder scope")
|
||||
import pandas as pd
|
||||
panel = pd.read_parquet(ROOT / "experiments/phase4l-olfactory-grounding/odor-encoder/frozen-odor-panel.parquet")
|
||||
require(len(panel) == 12 and set(panel[panel.split == "calibration"].odor_id) == legacy.ORIGINAL_CAL_IDS and
|
||||
float(np.median(panel.mapped_response_sum)) == encoder["median_mapped_response_sum"],
|
||||
"Static original 12-panel median/identities changed; no heldout trajectories are read")
|
||||
kc = np.load(AG / "apl-bundle/kc_globals.npy", allow_pickle=False)
|
||||
require(kc.dtype == np.int32 and kc.shape == (4064,) and np.all(np.diff(kc) > 0) and
|
||||
np.all((kc >= 0) & (kc < CFG["neurons"])), "Invalid frozen KC identity")
|
||||
for delay in (0, 18):
|
||||
arm = "delay" + str(delay)
|
||||
try:
|
||||
arms[arm] = collect_arm(arm, delay, records, odors, legacy, CFG, EXP, AG, kc)
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="arm", arm=arm, type=type(error).__name__, message=str(error)))
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="prerequisites", type=type(error).__name__, message=str(error)))
|
||||
for arm, data in arms.items():
|
||||
failures.extend(dict(arm=arm, **row) for row in data["failures"])
|
||||
complete = set(arms) == {"delay0", "delay18"}
|
||||
numerical = bool(complete and all(row["numerical_integrity_pass"] for row in arms.values()) and not failures)
|
||||
repeated = bool(complete and all(row["exact_repeat_pass"] for row in arms.values()))
|
||||
control = bool("delay0" in arms and arms["delay0"]["delay0_control_identity_pass"])
|
||||
represented = bool(complete and all(row["original_representation_gate_pass"] for row in arms.values()))
|
||||
execution = bool(numerical and repeated and control)
|
||||
result = {"phase": "4AJ", "pass_all": bool(execution and represented), "execution_gate_pass": execution,
|
||||
"numerical_integrity_pass": numerical, "exact_repeat_pass": repeated, "delay0_control_identity_pass": control,
|
||||
"original_representation_gate_pass": represented,
|
||||
"original_representation_gate_pass_by_arm": {arm: row["original_representation_gate_pass"] for arm, row in arms.items()},
|
||||
"expected_base_trials_per_arm": 60, "expected_repeats_per_arm": 12,
|
||||
"expected_delay0_AG_comparisons": 72, "arms": arms, "failures": failures, "odor_ids": odors,
|
||||
"encoder_audit": encoder, "legacy_calibration_sha256": LEGACY_SHA256,
|
||||
"scope": "Both fixed timing arms, full original 4L CAL gate; no heldout or learning claim",
|
||||
"representation_failure_is_not_numerical_failure": True,
|
||||
"selection_or_tuning_performed": False, "both_arms_retained_regardless_of_representation": True,
|
||||
"closed_loop_generated_h_integrals_forced_equal": False,
|
||||
"pending_is_finite_generated_sequence_not_complete_future_decay": True,
|
||||
"candidate_generated": False, "training": False, "heldout_dynamic_access": False,
|
||||
"shared_apl_component_oracle_limit": True, "independent_oracle_equivalence_claim": False}
|
||||
if target is not None:
|
||||
outputs = {target: result}
|
||||
for arm, data in arms.items():
|
||||
directory = target.parent / arm
|
||||
outputs[directory / "summary.json"] = data
|
||||
for mode, metrics in data["modes"].items():
|
||||
outputs[directory / (mode + ".json")] = metrics
|
||||
require(all(not path.exists() for path in outputs), "Refusing existing arm/mode summary")
|
||||
for path, value in outputs.items():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
stream.write("\n")
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = summarize()
|
||||
print(json.dumps({key: result[key] for key in ("pass_all", "execution_gate_pass",
|
||||
"delay0_control_identity_pass", "original_representation_gate_pass_by_arm")}), flush=True)
|
||||
@@ -0,0 +1,22 @@
|
||||
d96aef0107f7b0791d16e475b749da418b8a38d2c56a94e0779de0cd6581a7de PROTOCOL.md
|
||||
7201871477a29ede31c7d6c6bf1c92700da3db5505e67ccfa7cb43126b8464b9 al_origin_audit.py
|
||||
fb15ddeaa108305c8de697bbae36b23ab0414a7c58db51c66dce9feb4a048a0d calibration.py
|
||||
9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734 compare_timing.py
|
||||
0b720e23aa7988b2b443e14951082b1ce9b061a183b7f858c0e0f3c0fc6fb25e config.json
|
||||
06d4994af3729d653263391ff3a3604f7c919e78562604e5c86acfa292d60ba0 context.py
|
||||
0a2dbf9e5f889e9dd3491d036e6f92c17417a84482c5e1e49134c0993f7d68f2 finalize.py
|
||||
af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b gpu_timing.py
|
||||
39d8c47c94bc2faddf016864a43e7715907fb987f56f7b27098060a88851f6be oracle_timing.py
|
||||
bba71bcd52dcb78d9ae0fa93a8a43173a4669ecef038dce18ce7d82edd9bce50 prepare.py
|
||||
d93e4fe2df542ce5a0f0adb5aceada583dc8c921f0159f426e50428d79d67a1d run_case.py
|
||||
6f26c626acf9ef8e6fae7902af0cbf0bd176eb322529ca8aa203a9700716c889 run_suite.py
|
||||
46f380dac340acc91f226968bb493475a31777f8290fb20f9145a97c9009d845 shift_audit.py
|
||||
4b75a39d36090b0a66cc7ad4b370ac785713fb58c366df2efcc55152d612fc2b test_al_origin_audit.py
|
||||
307a2848bf6d05aaa8105bb3558be2a74425894f96a4a2bd20af8b82cb8b6e25 test_calibration.py
|
||||
cbae93c993ac9aa99bcce68ae2131c7f938f8a63e89779f8d3d79e36bf268dee test_protocol.py
|
||||
bfc0d9e17410c2c52ea1d9a5259c92964f16b118dbae8cecad258697b67e4bdc tests/__init__.py
|
||||
71876b36eef88c198aa8e833af547dbe772001c71b010707b656c165c8b54031 tests/test_compare_timing.py
|
||||
4a375444dc732c3538df8aa4b81e43b5418754fcebebb0d02c16faad03d43195 tests/test_timing_queue.py
|
||||
54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e timing_queue.py
|
||||
97d8a240b08baaca935f9c0278978883005dcd6fdbb4ca0d948074ef79fd349c verify_frozen.py
|
||||
52b05dd1ff30b6b51ff960fe95904532f5c147731a83f0b5c31f98ca7e53c216 manifest.json
|
||||
@@ -0,0 +1,611 @@
|
||||
"""Fail-closed byte-exact audit of fixed-delay shared-APL closed-loop runs.
|
||||
|
||||
Comparison is independent of either backend's stored PASS. No tolerance is
|
||||
used, wall-clock performance fields are excluded, and reports contain no wall
|
||||
timestamp. Scientific delay/tick metadata are checked explicitly.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
N_GLOBAL = 166700
|
||||
STATE_KEYS = ('step', 'neuron_idx', 'counts', 'v', 'g', 'ref')
|
||||
REQUEST_FIELDS = ('tick', 'neuron_idx', 'attempted', 'accepted', 'rejected')
|
||||
HASH_FIELDS = {'tick', 'v_sha256', 'g_sha256', 'ref_sha256'}
|
||||
QUEUE_FIELDS = {'tick', 'count', 'accepted_count', 'ordered_edge_sha256',
|
||||
'accepted_mask_sha256', 'joint_sha256'}
|
||||
BUNDLE_FILES = ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')
|
||||
COEFFICIENTS = ('gd', 'c0', 'cg', 'cv', 'threshold', 'reset', 'seed_volt')
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(array, dtype=None):
|
||||
return hashlib.sha256(np.ascontiguousarray(array, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def save(path, report):
|
||||
if path is None:
|
||||
return
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as target:
|
||||
json.dump(report, target, indent=2, sort_keys=True, allow_nan=False)
|
||||
target.write('\n')
|
||||
|
||||
|
||||
def _read_json(path, used):
|
||||
used.add(Path(path))
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def _read_jsonl(path, used):
|
||||
used.add(Path(path))
|
||||
with Path(path).open(encoding='utf-8') as source:
|
||||
return [json.loads(line) for line in source if line.strip()]
|
||||
|
||||
|
||||
def _read_array(path, used):
|
||||
used.add(Path(path))
|
||||
return np.load(path, mmap_mode='r', allow_pickle=False)
|
||||
|
||||
|
||||
def _check(checks, name, condition):
|
||||
checks[name] = bool(condition)
|
||||
|
||||
|
||||
def _bit_mask(a, b):
|
||||
if a.dtype.kind == 'f':
|
||||
unsigned = np.dtype('uint'+str(a.dtype.itemsize*8))
|
||||
return np.ascontiguousarray(a).view(unsigned) != np.ascontiguousarray(b).view(unsigned)
|
||||
return a != b
|
||||
|
||||
|
||||
def _compare_array(name, a, b, checks, differences, dtype=None, shape=None):
|
||||
a, b = np.asarray(a), np.asarray(b)
|
||||
schema = a.shape == b.shape and a.dtype == b.dtype and \
|
||||
(dtype is None or a.dtype == np.dtype(dtype)) and (shape is None or a.shape == shape)
|
||||
_check(checks, name+'.schema', schema)
|
||||
if not schema:
|
||||
differences[name] = {'left_shape': list(a.shape), 'right_shape': list(b.shape),
|
||||
'left_dtype': str(a.dtype), 'right_dtype': str(b.dtype), 'expected_shape': None if shape is None else list(shape),
|
||||
'expected_dtype': None if dtype is None else str(np.dtype(dtype))}
|
||||
_check(checks, name+'.bitwise_exact', False)
|
||||
return False
|
||||
finite = bool(np.isfinite(a).all() and np.isfinite(b).all())
|
||||
_check(checks, name+'.finite', finite)
|
||||
mask = _bit_mask(a, b)
|
||||
exact = not bool(np.any(mask))
|
||||
_check(checks, name+'.bitwise_exact', exact)
|
||||
if not exact or not finite:
|
||||
flat = np.flatnonzero(mask.ravel())
|
||||
detail = {'mismatching_elements': int(len(flat)),
|
||||
'first_positions': [list(map(int, np.unravel_index(int(index), a.shape))) for index in flat[:5]],
|
||||
'left_nonfinite': int(np.count_nonzero(~np.isfinite(a))),
|
||||
'right_nonfinite': int(np.count_nonzero(~np.isfinite(b)))}
|
||||
if finite and a.size:
|
||||
error = np.abs(a.astype(np.float64)-b.astype(np.float64))
|
||||
detail.update(max_abs_error=float(error.max()), mean_abs_error=float(error.mean()))
|
||||
if a.dtype.kind == 'f' and len(flat):
|
||||
unsigned = np.dtype('uint'+str(a.dtype.itemsize*8))
|
||||
av = np.ascontiguousarray(a).view(unsigned).ravel()
|
||||
bv = np.ascontiguousarray(b).view(unsigned).ravel()
|
||||
detail['first_bit_patterns'] = [{'left': hex(int(av[index])), 'right': hex(int(bv[index]))} for index in flat[:5]]
|
||||
differences[name] = detail
|
||||
return exact and finite
|
||||
|
||||
|
||||
def _sha(value):
|
||||
return isinstance(value, str) and re.fullmatch('[0-9a-f]{64}', value) is not None
|
||||
|
||||
|
||||
def _read_requests(path, steps, used):
|
||||
used.add(Path(path))
|
||||
with Path(path).open(encoding='utf-8', newline='') as source:
|
||||
reader = csv.DictReader(source)
|
||||
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, 'Request CSV schema mismatch: '+str(path))
|
||||
rows = [{key: int(row[key]) for key in REQUEST_FIELDS} for row in reader]
|
||||
previous = (-1, -1)
|
||||
for row in rows:
|
||||
key = row['tick'], row['neuron_idx']
|
||||
require(previous < key and 0 <= key[0] < steps and 0 <= key[1] < N_GLOBAL,
|
||||
'Request rows must be sorted, unique, and in range: '+str(path))
|
||||
require(row['attempted'] == 1 and row['accepted'] in (0, 1) and row['rejected'] == 1-row['accepted'],
|
||||
'Request accounting is incomplete: '+str(path))
|
||||
previous = key
|
||||
return rows
|
||||
|
||||
|
||||
def _load_run(directory, role, n, edges, steps, record_history, name, delay, used, checks):
|
||||
directory = Path(directory)
|
||||
summary = _read_json(directory/'summary.json', used)
|
||||
_check(checks, role+'.summary_complete', summary.get('complete') is True and summary.get('pass_all') is True and
|
||||
summary.get('steps') == steps and summary.get('completed_ticks') == steps and
|
||||
summary.get('nodes') == n and summary.get('edges') == edges and summary.get('precision') == 'float64')
|
||||
is_cpu = role == 'cpu'
|
||||
_check(checks, role+'.case_name', summary.get('name' if is_cpu else 'case') == name)
|
||||
_check(checks, role+'.history_mode', summary.get('history_recorded' if is_cpu else 'record_history') is record_history)
|
||||
_check(checks, role+'.apl_precision', summary.get('APL_precision' if is_cpu else 'apl_precision') == 'float32')
|
||||
_check(checks, role+'.no_error_marker', not (directory/'error.json').exists())
|
||||
used.add(directory/'state.npz')
|
||||
with np.load(directory/'state.npz', allow_pickle=False) as source:
|
||||
require(set(source.files) == set(STATE_KEYS), 'State NPZ keys changed: '+str(directory))
|
||||
state = {key: source[key].copy() for key in STATE_KEYS}
|
||||
for key, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32'), ('counts', 'int32')):
|
||||
require(state[key].shape == (n,) and state[key].dtype == np.dtype(dtype), 'Invalid final '+key+': '+str(directory))
|
||||
require(state['step'].dtype == state['neuron_idx'].dtype == np.int32 and state['step'].ndim == 1 and
|
||||
state['step'].shape == state['neuron_idx'].shape, 'Invalid sparse event schema: '+str(directory))
|
||||
require(bool(np.all((state['step'] >= 0) & (state['step'] < steps))) and
|
||||
bool(np.all((state['neuron_idx'] >= 0) & (state['neuron_idx'] < N_GLOBAL))), 'Sparse event range error')
|
||||
event_keys = state['step'].astype(np.int64)*N_GLOBAL+state['neuron_idx']
|
||||
_check(checks, role+'.events_sorted_unique', len(event_keys) < 2 or np.all(np.diff(event_keys) > 0))
|
||||
_check(checks, role+'.count_nonnegative_total', np.all(state['counts'] >= 0) and int(state['counts'].sum()) == len(event_keys))
|
||||
_check(checks, role+'.ref_range', np.all((state['ref'] >= 0) & (state['ref'] <= 22)))
|
||||
_check(checks, role+'.reported_events', summary.get('spikes' if is_cpu else 'total_spikes') == len(event_keys))
|
||||
hashes = _read_jsonl(directory/'tick-state-hashes.jsonl', used)
|
||||
require(len(hashes) == steps and [row.get('tick') for row in hashes] == list(range(steps)), 'Incomplete tick hash coverage')
|
||||
require(all(set(row) == HASH_FIELDS and all(_sha(row[key]) for key in HASH_FIELDS-{'tick'}) for row in hashes),
|
||||
'Invalid tick state hash schema')
|
||||
queues = _read_jsonl(directory/'queue.jsonl', used)
|
||||
require(len(queues) == steps and [row.get('tick') for row in queues] == list(range(steps)), 'Incomplete queue coverage')
|
||||
require(all(set(row) == QUEUE_FIELDS and type(row['count']) is int and type(row['accepted_count']) is int and
|
||||
0 <= row['accepted_count'] <= row['count'] <= edges and
|
||||
all(_sha(row[key]) for key in QUEUE_FIELDS-{'tick', 'count', 'accepted_count'}) for row in queues),
|
||||
'Invalid queue row schema/count/hash')
|
||||
for key in ('v', 'g', 'ref'):
|
||||
_check(checks, role+'.last_hash_'+key, hashes[-1][key+'_sha256'] == raw_hash(state[key], '<i4' if key == 'ref' else '<f8'))
|
||||
requests = _read_requests(directory/'requests.csv', steps, used)
|
||||
requested, accepted = len(requests), sum(row['accepted'] for row in requests)
|
||||
_check(checks, role+'.request_summary', summary.get('requests' if is_cpu else 'requested') == requested and
|
||||
summary.get('applicable_requests' if is_cpu else 'accepted_requests') == accepted and
|
||||
summary.get('ref_rejected_requests' if is_cpu else 'rejected_requests') == requested-accepted)
|
||||
apl = {field: _read_array(directory/('apl-'+field+'.float32.npy'), used) for field in ('drive', 'state', 'inhibition', 'applied')}
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', 4064), ('applied', 4064)):
|
||||
require(apl[field].dtype == np.float32 and apl[field].shape == (steps, width), 'Invalid APL '+field+' history schema')
|
||||
_check(checks, role+'.apl_positive_'+field, np.all(apl[field] >= 0))
|
||||
pending = _read_array(directory/'apl-pending-tail.float32.npy', used)
|
||||
require(pending.dtype == np.float32 and pending.shape == (delay, 4064), 'Invalid pending APL queue schema')
|
||||
timing = _read_json(directory/'apl-timing-audit.json', used)
|
||||
_check(checks, role+'.delay_summary', type(summary.get('apl_delay_steps')) is int and summary['apl_delay_steps'] == delay)
|
||||
histories, pre = {}, {}
|
||||
for field, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32')):
|
||||
files = (directory/(field+'-history.'+dtype+'.npy'), directory/('preAPL-'+field+'.'+dtype+'.npy'))
|
||||
if record_history:
|
||||
histories[field], pre[field] = (_read_array(path, used) for path in files)
|
||||
require(all(array.dtype == np.dtype(dtype) and array.shape == (steps, n) for array in (histories[field], pre[field])),
|
||||
'Small pre/post history schema mismatch')
|
||||
_check(checks, role+'.history_hashes_'+field, all(row[field+'_sha256'] == raw_hash(histories[field][tick])
|
||||
for tick, row in enumerate(hashes)))
|
||||
_check(checks, role+'.history_final_'+field, not np.any(_bit_mask(histories[field][-1], state[field])))
|
||||
else:
|
||||
_check(checks, role+'.no_dense_'+field, not any(path.exists() for path in files))
|
||||
if is_cpu:
|
||||
component = _read_json(directory/'apl-worker-handshake.json', used)['handshake']
|
||||
else:
|
||||
component = _read_json(directory/'apl-component.json', used)
|
||||
_check(checks, role+'.component_summary_consistent', summary.get('apl_component') == component)
|
||||
return dict(directory=directory, summary=summary, state=state, hashes=hashes, queues=queues,
|
||||
requests=requests, apl=apl, pending=pending, timing=timing,
|
||||
histories=histories, pre=pre, component=component)
|
||||
|
||||
|
||||
def _pair_outputs(left, right, checks, differences, n, steps, record_history):
|
||||
for field in STATE_KEYS:
|
||||
shape = None if field in ('step', 'neuron_idx') else (n,)
|
||||
dtype = 'float64' if field in ('v', 'g') else 'int32'
|
||||
_compare_array('state.'+field, left['state'][field], right['state'][field], checks, differences, dtype, shape)
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', 4064), ('applied', 4064)):
|
||||
_compare_array('apl.'+field, left['apl'][field], right['apl'][field], checks, differences, 'float32', (steps, width))
|
||||
_compare_array('apl.pending', left['pending'], right['pending'], checks, differences, 'float32', left['pending'].shape)
|
||||
for field in ('hashes', 'queues', 'requests', 'component'):
|
||||
same = left[field] == right[field]
|
||||
_check(checks, field+'.parsed_exact', same)
|
||||
if not same:
|
||||
if isinstance(left[field], list):
|
||||
first = next((i for i, (a, b) in enumerate(zip(left[field], right[field])) if a != b), None)
|
||||
differences[field] = {'left_count': len(left[field]), 'right_count': len(right[field]),
|
||||
'first_differing_row': first,
|
||||
'left_row': None if first is None else left[field][first],
|
||||
'right_row': None if first is None else right[field][first]}
|
||||
else:
|
||||
differences[field] = {'left': left[field], 'right': right[field]}
|
||||
if record_history:
|
||||
for stage in ('histories', 'pre'):
|
||||
for field, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32')):
|
||||
_compare_array(stage+'.'+field, left[stage][field], right[stage][field], checks, differences, dtype, (steps, n))
|
||||
|
||||
|
||||
def _queue_digest(tick, ids, accepted):
|
||||
ids = np.ascontiguousarray(ids, dtype='<i8').tobytes()
|
||||
mask = np.ascontiguousarray(accepted, dtype=np.uint8).tobytes()
|
||||
return {'tick': tick, 'count': len(mask), 'accepted_count': int(np.count_nonzero(accepted)),
|
||||
'ordered_edge_sha256': hashlib.sha256(ids).hexdigest(),
|
||||
'accepted_mask_sha256': hashlib.sha256(mask).hexdigest(),
|
||||
'joint_sha256': hashlib.sha256(b'phase4af-queue-v1\0'+struct.pack('<qq', tick, len(mask))+ids+mask).hexdigest()}
|
||||
|
||||
|
||||
def _independent_event_accounting(run, role, graph, requested, steps, checks, differences):
|
||||
nodes, ptr = graph['nodes'], graph['ptr']
|
||||
n = len(nodes)
|
||||
state = run['state']
|
||||
local = np.searchsorted(nodes, state['neuron_idx'])
|
||||
require(np.all(local < n) and np.array_equal(nodes[local], state['neuron_idx']), 'Actual spike outside graph identity')
|
||||
_check(checks, role+'.events_per_neuron_counts', np.array_equal(np.bincount(local, minlength=n), state['counts']))
|
||||
actual_request_pairs = [(row['tick'], row['neuron_idx']) for row in run['requests']]
|
||||
_check(checks, role+'.requests_match_frozen_input', actual_request_pairs == requested)
|
||||
require(actual_request_pairs == requested, 'Actual request population/time differs from frozen input')
|
||||
bounds = np.searchsorted(state['step'], np.arange(steps+1))
|
||||
request_ticks = np.asarray([row['tick'] for row in run['requests']], np.int32)
|
||||
rbounds = np.searchsorted(request_ticks, np.arange(steps+1))
|
||||
request_nodes = np.asarray([row['neuron_idx'] for row in run['requests']], np.int32)
|
||||
request_local = np.searchsorted(nodes, request_nodes)
|
||||
require(np.all(request_local < n) and np.array_equal(nodes[request_local], request_nodes), 'Request outside graph')
|
||||
last = np.full(n, -1_000_000_000, np.int64)
|
||||
degrees = np.diff(ptr)
|
||||
expected_counts = np.zeros(steps, np.int64)
|
||||
arrival_tick = state['step'].astype(np.int64)+18
|
||||
valid = arrival_tick < steps
|
||||
np.add.at(expected_counts, arrival_tick[valid], degrees[local[valid]])
|
||||
_check(checks, role+'.delayed_all_edges_count', np.array_equal(expected_counts, [row['count'] for row in run['queues']]))
|
||||
request_ok, delivered_ok, ref_ok, queue_ok, reset_ok = True, True, True, True, True
|
||||
first = {}
|
||||
for tick in range(steps):
|
||||
fired = local[bounds[tick]:bounds[tick+1]]
|
||||
lo, hi = rbounds[tick:tick+2]
|
||||
targets = request_local[lo:hi]
|
||||
accepted = (tick-last[targets]) >= 22
|
||||
for index, ok in zip(range(lo, hi), accepted):
|
||||
row = run['requests'][index]
|
||||
if row['accepted'] != int(ok) or row['rejected'] != int(not ok):
|
||||
request_ok = False
|
||||
first.setdefault('request_ref_mismatch', {'tick': tick, 'neuron_idx': row['neuron_idx']})
|
||||
if not np.isin(targets[accepted], fired).all() or np.isin(targets[~accepted], fired).any():
|
||||
delivered_ok = False
|
||||
first.setdefault('request_delivery_mismatch', tick)
|
||||
last[fired] = tick
|
||||
ref = np.maximum(22-(tick-last), 0).astype(np.int32)
|
||||
if raw_hash(ref, '<i4') != run['hashes'][tick]['ref_sha256']:
|
||||
ref_ok = False
|
||||
first.setdefault('ref_hash_mismatch', tick)
|
||||
if run['pre']:
|
||||
if len(fired) and not np.array_equal(np.ascontiguousarray(run['pre']['g'][tick, fired]).view(np.uint64), np.zeros(len(fired), np.uint64)):
|
||||
reset_ok = False
|
||||
first.setdefault('pre_apl_spike_reset_mismatch', tick)
|
||||
due = local[bounds[tick-18]:bounds[tick-17]] if tick >= 18 else np.empty(0, np.int64)
|
||||
chunks = [np.arange(ptr[source], ptr[source+1], dtype=np.int64) for source in due]
|
||||
indices = np.concatenate(chunks) if chunks else np.empty(0, np.int64)
|
||||
expected_queue = _queue_digest(tick, graph['edge_id'][indices], (ref[graph['dst'][indices]] == 0).astype(np.uint8))
|
||||
if expected_queue != run['queues'][tick]:
|
||||
queue_ok = False
|
||||
first.setdefault('queue_reconstruction_mismatch', tick)
|
||||
_check(checks, role+'.request_refractory_gate', request_ok)
|
||||
_check(checks, role+'.accepted_spikes_rejected_absent', delivered_ok)
|
||||
_check(checks, role+'.all_ref_hashes_from_actual_events', ref_ok)
|
||||
if run['pre']:
|
||||
_check(checks, role+'.small_queue_independent_reconstruction', queue_ok)
|
||||
_check(checks, role+'.spike_g_reset_before_apl', reset_ok)
|
||||
if first:
|
||||
differences[role+'.independent_accounting'] = first
|
||||
|
||||
|
||||
def _check_apl_application(run, role, nodes, kc, checks, differences):
|
||||
if not run['pre']:
|
||||
return
|
||||
common, local_kc, bundle_kc = np.intersect1d(nodes, kc, assume_unique=True, return_indices=True)
|
||||
_compare_array(role+'.apl_keeps_voltage', run['pre']['v'], run['histories']['v'], checks, differences, 'float64')
|
||||
_compare_array(role+'.apl_keeps_ref', run['pre']['ref'], run['histories']['ref'], checks, differences, 'int32')
|
||||
expected = np.array(run['pre']['g'], copy=True)
|
||||
delta = run['apl']['applied'][:, bundle_kc].astype(np.float64)*np.float64(.001)
|
||||
expected[:, local_kc] = expected[:, local_kc]-delta
|
||||
_compare_array(role+'.apl_unconditional_g_write', expected, run['histories']['g'], checks, differences, 'float64')
|
||||
|
||||
|
||||
def _timing_sequence_checks(generated, applied, pending, delay, role, checks, differences):
|
||||
"""Independent slice-based reconstruction; does not import either queue."""
|
||||
require(type(delay) is int and delay in (0, 18), 'Invalid fixed APL delay')
|
||||
for key, value in (('generated', generated), ('applied', applied), ('pending', pending)):
|
||||
require(isinstance(value, np.ndarray) and value.ndim == 2 and value.dtype == np.float32,
|
||||
'Invalid timing array '+key)
|
||||
steps, width = generated.shape
|
||||
require(width > 0 and applied.shape == (steps, width) and pending.shape == (delay, width),
|
||||
'Timing sequence dimensions do not partition the declared window')
|
||||
_check(checks, role+'.timing_finite_nonnegative',
|
||||
all(np.isfinite(value).all() and np.all(value >= 0) for value in (generated, applied, pending)))
|
||||
consumed = max(steps-delay, 0)
|
||||
expected_applied = np.zeros_like(generated)
|
||||
if consumed:
|
||||
expected_applied[delay:] = generated[:consumed]
|
||||
expected_pending = np.zeros((delay, width), dtype=np.float32)
|
||||
generated_pending = min(steps, delay)
|
||||
if generated_pending:
|
||||
expected_pending[delay-generated_pending:] = generated[consumed:]
|
||||
_compare_array(role+'.fixed_delay_applied', applied, expected_applied, checks, differences,
|
||||
'float32', (steps, width))
|
||||
_compare_array(role+'.fixed_delay_pending', pending, expected_pending, checks, differences,
|
||||
'float32', (delay, width))
|
||||
_check(checks, role+'.finite_sequence_row_conservation', consumed+generated_pending == steps)
|
||||
|
||||
|
||||
def _timing_metadata_checks(run, role, delay, steps, checks):
|
||||
"""Validate declared limits against actual queue and generated APL state."""
|
||||
timing, summary = run['timing'], run['summary']
|
||||
count, prefix = min(steps, delay), max(delay-steps, 0)
|
||||
expected = {'apl_delay_steps': delay, 'steps': steps, 'pending_generated_count': count,
|
||||
'generated_rows': steps, 'applied_generated_rows': max(steps-delay, 0),
|
||||
'pending_initial_zero_prefix': prefix, 'pending_application_tick_start': steps,
|
||||
'pending_application_tick_stop_exclusive': steps+delay,
|
||||
'pending_generated_tick_start': max(steps-delay, 0),
|
||||
'pending_generated_tick_stop_exclusive': steps}
|
||||
_check(checks, role+'.timing_integer_metadata',
|
||||
all(type(timing.get(key)) is int and timing[key] == value for key, value in expected.items()))
|
||||
_check(checks, role+'.pending_hash_metadata', timing.get('pending_raw_sha256') == raw_hash(run['pending'], '<f4'))
|
||||
_check(checks, role+'.pending_shape_metadata', timing.get('pending_shape') == [delay, 4064])
|
||||
_check(checks, role+'.pending_summary_counts',
|
||||
type(summary.get('pending_generated_count')) is int and summary['pending_generated_count'] == count and
|
||||
type(summary.get('pending_initial_zero_prefix')) is int and summary['pending_initial_zero_prefix'] == prefix and
|
||||
summary.get('pending_tail_shape') == [delay, 4064] and
|
||||
summary.get('future_generated_decay_tail_included') is False)
|
||||
state = np.asarray(run['apl']['state'][-1], dtype=np.float32)
|
||||
state_values, state_bits = timing.get('final_apl_state_float32'), timing.get('final_apl_state_u32_bits')
|
||||
valid_state = isinstance(state_values, list) and len(state_values) == 2 and isinstance(state_bits, list) and len(state_bits) == 2
|
||||
if valid_state:
|
||||
try:
|
||||
value = np.asarray(state_values, dtype=np.float32)
|
||||
valid_state = np.array_equal(value.view(np.uint32), state.view(np.uint32)) and \
|
||||
all(type(x) is int for x in state_bits) and state_bits == state.view(np.uint32).tolist()
|
||||
except (ValueError, TypeError, OverflowError):
|
||||
valid_state = False
|
||||
_check(checks, role+'.final_apl_state_metadata', valid_state and
|
||||
timing.get('final_apl_state_nonzero') is bool(np.any(state != 0)))
|
||||
if role == 'cpu':
|
||||
_check(checks, role+'.no_extra_brain_or_apl_ticks',
|
||||
type(timing.get('brain_steps_after_window')) is int and timing['brain_steps_after_window'] == 0 and
|
||||
type(timing.get('APL_generation_steps_after_window')) is int and timing['APL_generation_steps_after_window'] == 0 and
|
||||
timing.get('future_generated_decay_tail_included') is False and
|
||||
summary.get('future_generated_decay_tail_included') is False and
|
||||
summary.get('APL_worker_ticks') == steps)
|
||||
_check(checks, role+'.timing_summary_metadata',
|
||||
summary.get('pending_generated_count') == count and
|
||||
summary.get('pending_initial_zero_prefix') == prefix and
|
||||
summary.get('pending_tail_shape') == [delay, 4064])
|
||||
else:
|
||||
_check(checks, role+'.no_extra_brain_or_apl_ticks',
|
||||
timing.get('brain_tail_advanced') is False and
|
||||
timing.get('future_apl_decay_or_feedback_generated') is False and
|
||||
timing.get('finite_generated_sequence_only') is True and
|
||||
timing.get('closed_loop_branch_integrals_claimed_equal') is False and
|
||||
timing.get('biological_delay_measurement_claimed') is False)
|
||||
_check(checks, role+'.timing_summary_metadata', summary.get('apl_timing') == timing)
|
||||
|
||||
|
||||
def _finish(report, checks, differences, used, out_json):
|
||||
report['checks'] = {name: bool(value) for name, value in checks.items()}
|
||||
report['differences'] = differences
|
||||
report['pass_all'] = bool(checks) and all(checks.values()) and 'error' not in report
|
||||
report['failed_checks'] = sorted(name for name, value in checks.items() if not value)
|
||||
report['evidence_sha256'] = {str(path): file_hash(path) for path in sorted(used) if path.is_file()}
|
||||
save(out_json, report)
|
||||
return report
|
||||
|
||||
|
||||
def compare(case_dir, gpu_dir, cpu_dir, out_json=None):
|
||||
"""Strict independent closed-loop comparison; out_json=None is read-only."""
|
||||
case_dir, gpu_dir, cpu_dir = Path(case_dir), Path(gpu_dir), Path(cpu_dir)
|
||||
if out_json is not None:
|
||||
require(not Path(out_json).exists(), 'Refusing existing comparison report')
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'shared-APL-component closed-loop byte-exact comparison', 'pass_all': False,
|
||||
'case_dir': str(case_dir), 'gpu_dir': str(gpu_dir), 'cpu_dir': str(cpu_dir),
|
||||
'tolerance': None, 'signed_zero_included': True, 'timing_fields_compared': False,
|
||||
'independent_CPU_all_components_claim': False, 'finite_generated_sequence_accounting_only': True,
|
||||
'cross_delay_closed_loop_integrals_claimed_equal': False, 'biological_delay_measurement_claimed': False,
|
||||
'scientific_apl_delay_fields_compared': True}
|
||||
try:
|
||||
spec = _read_json(case_dir/'spec.json', used)
|
||||
steps, history = spec['steps'], spec['record_history']
|
||||
delay = spec['apl_delay_steps']
|
||||
require(type(delay) is int and delay in (0, 18), 'Explicit fixed delay 0 or 18 required')
|
||||
require(type(steps) is int and steps > 0 and type(history) is bool and spec['dt_ms'] == .1,
|
||||
'Invalid fixed case clock/history schema')
|
||||
graph_dir, bundle_dir = Path(spec['graph_dir']), Path(spec['apl_bundle_dir'])
|
||||
graph = {field: _read_array(graph_dir/(field+'.npy'), used) for field in
|
||||
('nodes', 'edge_id', 'src', 'dst', 'ptr', 'signed_raw', 'weights')}
|
||||
nodes = graph['nodes']
|
||||
n, edges = len(nodes), len(graph['edge_id'])
|
||||
require(n > 0 and edges > 0 and nodes.dtype == np.int32 and nodes.ndim == 1 and np.all((nodes >= 0) & (nodes < N_GLOBAL)) and
|
||||
np.all(np.diff(nodes) > 0), 'Invalid canonical node identities')
|
||||
require(graph['ptr'].dtype == np.int64 and graph['ptr'].shape == (n+1,) and graph['ptr'][0] == 0 and
|
||||
graph['ptr'][-1] == edges and np.all(np.diff(graph['ptr']) >= 0), 'Invalid CSR')
|
||||
for key, dtype in (('edge_id', 'int64'), ('src', 'int32'), ('dst', 'int32'), ('signed_raw', 'int64'), ('weights', 'float64')):
|
||||
require(graph[key].dtype == np.dtype(dtype) and graph[key].shape == (edges,), 'Invalid graph '+key)
|
||||
require(np.array_equal(graph['src'], np.repeat(np.arange(n, dtype=np.int32), np.diff(graph['ptr']))) and
|
||||
np.all((graph['dst'] >= 0) & (graph['dst'] < n)), 'Invalid source/destination row mapping')
|
||||
require(history == (n < 512) and (n < 512 or n == N_GLOBAL), 'Invalid small/full history declaration')
|
||||
require(np.all(graph['edge_id'] >= 0) and np.all(np.diff(graph['edge_id']) > 0),
|
||||
'Original canonical edge IDs must be unique and increasing')
|
||||
if n == N_GLOBAL:
|
||||
require(edges == 25582938 and np.array_equal(nodes, np.arange(n, dtype=np.int32)) and
|
||||
np.array_equal(graph['edge_id'], np.arange(edges, dtype=np.int64)), 'Full canonical graph identity changed')
|
||||
bundle = {name: _read_array(bundle_dir/name, used) for name in BUNDLE_FILES}
|
||||
kc, apl_ids = bundle['kc_globals.npy'], bundle['apl_globals.npy']
|
||||
require(kc.dtype == apl_ids.dtype == np.int32 and kc.shape == (4064,) and apl_ids.shape == (2,), 'Invalid APL/KC identities')
|
||||
require(np.all((kc >= 0) & (kc < N_GLOBAL)) and np.all(np.diff(kc) > 0) and
|
||||
np.all((apl_ids >= 0) & (apl_ids < N_GLOBAL)) and np.all(np.diff(apl_ids) > 0) and
|
||||
not np.intersect1d(kc, apl_ids).size, 'APL/KC identities overlap or are invalid')
|
||||
for key, shape in (('input_matrix.npy', (2, N_GLOBAL)), ('output_matrix.npy', (2, 4064))):
|
||||
require(bundle[key].dtype == np.float32 and bundle[key].shape == shape and
|
||||
np.isfinite(bundle[key]).all() and np.all(bundle[key] >= 0), 'Invalid normalized APL matrix')
|
||||
gpu = _load_run(gpu_dir, 'gpu', n, edges, steps, history, spec['name'], delay, used, checks)
|
||||
cpu = _load_run(cpu_dir, 'cpu', n, edges, steps, history, spec['name'], delay, used, checks)
|
||||
_pair_outputs(gpu, cpu, checks, differences, n, steps, history)
|
||||
used.add(case_dir/'requests.npz')
|
||||
with np.load(case_dir/'requests.npz', allow_pickle=False) as source:
|
||||
require(set(source.files) == {'tick', 'neuron_idx'} and source['tick'].dtype == source['neuron_idx'].dtype == np.int32,
|
||||
'Fixed request NPZ schema changed')
|
||||
require(source['tick'].shape == source['neuron_idx'].shape and source['tick'].ndim == 1, 'Invalid sparse request shape')
|
||||
requested = list(zip(map(int, source['tick']), map(int, source['neuron_idx'])))
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
_independent_event_accounting(run, role, graph, requested, steps, checks, differences)
|
||||
_check_apl_application(run, role, nodes, kc, checks, differences)
|
||||
_timing_sequence_checks(run['apl']['inhibition'], run['apl']['applied'], run['pending'],
|
||||
delay, role, checks, differences)
|
||||
_timing_metadata_checks(run, role, delay, steps, checks)
|
||||
|
||||
# Independently derive the effective clone. Original graph weights stay read-only.
|
||||
cpu_weights = _read_array(cpu_dir/'weights.npy', used)
|
||||
require(cpu_weights.dtype == np.float64 and cpu_weights.shape == (edges,), 'CPU effective weight schema mismatch')
|
||||
expected_hash, base_hash, mask_hash = hashlib.sha256(), hashlib.sha256(), hashlib.sha256()
|
||||
weight_ok, mask_count = True, 0
|
||||
for start in range(0, edges, 1 << 20):
|
||||
stop = min(edges, start+(1 << 20))
|
||||
original = np.asarray(graph['weights'][start:stop])
|
||||
require(np.isfinite(original).all(), 'Nonfinite original weights')
|
||||
raw_expected = graph['signed_raw'][start:stop]*(.275*.001)
|
||||
require(not np.any(_bit_mask(original, raw_expected)), 'Original SI weights differ from unaltered signed contacts')
|
||||
mask = np.isin(nodes[graph['src'][start:stop]], apl_ids)
|
||||
expected = original.copy()
|
||||
expected[mask] = np.float64(0)
|
||||
mask_count += int(mask.sum())
|
||||
expected_hash.update(np.ascontiguousarray(expected, dtype='<f8').tobytes())
|
||||
base_hash.update(np.ascontiguousarray(original, dtype='<f8').tobytes())
|
||||
mask_hash.update(np.ascontiguousarray(graph['edge_id'][start:stop][mask], dtype='<i8').tobytes())
|
||||
weight_ok &= not np.any(_bit_mask(expected, cpu_weights[start:stop]))
|
||||
expected_sha = expected_hash.hexdigest()
|
||||
_check(checks, 'cpu.effective_weights_independent_bytes', weight_ok)
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
_check(checks, role+'.effective_weights_raw_sha', run['summary'].get('effective_weights_raw_sha256') == expected_sha)
|
||||
_check(checks, role+'.apl_fast_edge_count', run['summary'].get('apl_fast_edges_zeroed') == mask_count)
|
||||
_check(checks, role+'.kc_target_count', run['summary'].get('kc_targets_present') == len(np.intersect1d(nodes, kc)))
|
||||
mask_audit = _read_json(cpu_dir/'apl-mask-audit.json', used)
|
||||
_check(checks, 'cpu.mask_provenance', mask_audit['count'] == mask_count and
|
||||
mask_audit['ordered_original_edge_id_sha256'] == mask_hash.hexdigest() and
|
||||
mask_audit['base_weight_raw_bytes_sha256'] == base_hash.hexdigest() and
|
||||
mask_audit['effective_weights_raw_sha256'] == expected_sha and mask_audit['base_graph_files_modified'] is False)
|
||||
|
||||
case_hashes = {name: file_hash(case_dir/name) for name in ('spec.json', 'requests.npz')}
|
||||
if (case_dir/'arrays.npz').exists():
|
||||
case_hashes['arrays.npz'] = file_hash(case_dir/'arrays.npz')
|
||||
used.add(case_dir/'arrays.npz')
|
||||
baseline_coeff = _read_json(Path(spec['coefficients_path']), used)
|
||||
actual_coeff = _read_json(cpu_dir/'coefficients.json', used)
|
||||
_check(checks, 'cpu.actual_author_coefficients_exact', all(float(actual_coeff[key]).hex() == float(baseline_coeff[key]).hex()
|
||||
for key in COEFFICIENTS))
|
||||
audit = _read_json(cpu_dir/'input-audit.json', used)
|
||||
expected_graph_hashes = {name: file_hash(graph_dir/name) for name in
|
||||
tuple(field+'.npy' for field in graph)+('completeness.csv', 'connectivity.parquet')}
|
||||
bundle_hashes = {name: file_hash(bundle_dir/name) for name in BUNDLE_FILES}
|
||||
_check(checks, 'cpu.read_actual_case', audit['spec'] == spec and audit['case_hashes'] == case_hashes)
|
||||
_check(checks, 'cpu.graph_hashes', audit['graph_hashes'] == expected_graph_hashes)
|
||||
_check(checks, 'cpu.bundle_hashes', audit['bundle_hashes'] == bundle_hashes)
|
||||
_check(checks, 'cpu.no_candidate_trace_input', audit.get('candidate_trace_used_as_input') is False and
|
||||
cpu['summary'].get('candidate_trace_used_as_input') is False)
|
||||
_check(checks, 'cpu.worker_source_hash', audit['legacy_apl_worker_sha256'] == file_hash(spec['legacy_apl_worker']))
|
||||
_check(checks, 'cpu.coefficient_input_hash', audit['coefficients_sha256'] == file_hash(spec['coefficients_path']))
|
||||
gpu_hashes = dict(case_hashes, **{'base_weights.npy': file_hash(graph_dir/'weights.npy'),
|
||||
'coefficients.json': file_hash(spec['coefficients_path'])})
|
||||
_check(checks, 'gpu.input_hashes', gpu['summary']['input_sha256'] == gpu_hashes)
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
component = run['component']
|
||||
_check(checks, role+'.actual_component_bundle_bytes', component['files_sha256'] == bundle_hashes and
|
||||
component['array_sha256'] == {name: raw_hash(value) for name, value in bundle.items()})
|
||||
_check(checks, role+'.component_dimensions_clock', component.get('ok') is True and
|
||||
component['protocol'] == 'phase4ag-apl-v1' and component['n_neurons'] == N_GLOBAL and
|
||||
component['n_apl'] == 2 and component['n_kc'] == 4064 and component['dt_ms'] == .1)
|
||||
decay = np.float32(math.exp(-.1/20.0))
|
||||
gain = np.float32(.01)
|
||||
_check(checks, role+'.frozen_apl_constants', component['decay_f32_bits'] == f'{int(decay.view(np.uint32)):08x}' and
|
||||
component['gain_f32_bits'] == f'{int(gain.view(np.uint32)):08x}')
|
||||
env = component['environment']
|
||||
_check(checks, role+'.component_precision_environment', env['cuda_matmul_allow_tf32'] is False and
|
||||
env['float32_matmul_precision'] == 'highest' and env['precision_configuration_changed_by_component'] is False and
|
||||
'4090' in env['device_name'])
|
||||
environment = _read_json(cpu_dir/'environment.json', used)
|
||||
_check(checks, 'cpu.pinned_environment', environment['packages']['brian2'] == '2.5.1' and
|
||||
environment['packages']['numpy'] == '1.22.3' and environment['packages']['sympy'] == '1.12' and
|
||||
environment['Torch_imported_into_Brian'] is False and environment['author_precision'] == 'float64')
|
||||
queue_audit = _read_json(cpu_dir/'queue-audit.json', used)
|
||||
_check(checks, 'cpu.queue_observer_coverage', queue_audit['peek_calls'] == queue_audit['advance_calls'] == steps and
|
||||
queue_audit['same_peek_object_returned'] is True and queue_audit['zero_weight_and_refractory_rejected_edges_retained'] is True)
|
||||
_check(checks, 'cpu.sidecar_complete', cpu['summary']['APL_worker_exit_status'] == 0 and cpu['summary']['APL_worker_ticks'] == steps)
|
||||
report.update(case=spec['name'], steps=steps, nodes=n, edges=edges, record_history=history, apl_delay_steps=delay,
|
||||
total_spikes=len(gpu['state']['step']), active_neurons=int(np.count_nonzero(gpu['state']['counts'])),
|
||||
requested=len(requested), accepted_requests=sum(row['accepted'] for row in gpu['requests']),
|
||||
rejected_requests=sum(row['rejected'] for row in gpu['requests']),
|
||||
arriving_edges=sum(row['count'] for row in gpu['queues']),
|
||||
accepted_arriving_edges=sum(row['accepted_count'] for row in gpu['queues']),
|
||||
apl_fast_edges_zeroed=mask_count, effective_weights_raw_sha256=expected_sha,
|
||||
component_environment=gpu['component']['environment'],
|
||||
limits=['APL Torch primitives are shared; original AST component validation is a separate required gate.',
|
||||
'Small cases preserve and independently hash all pre/post states; full cases compare all-tick instrumented state hashes without saving dense whole-brain histories.',
|
||||
'For full cases intermediate finite checks are performed by the instrumented backends; this file independently inspects final states and all APL traces.',
|
||||
'Fixed-delay checks conserve this run\'s finite generated sample sequence, including pending initial zeros; no tail brain advancement or equality of different closed-loop branches is claimed.',
|
||||
'This is exact agreement for the declared trajectory, not physiological validity or learning.'])
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return _finish(report, checks, differences, used, out_json)
|
||||
|
||||
|
||||
def compare_gpu_repeats(first, second, out_json=None):
|
||||
"""Repeat equality only; the first run's independent oracle gate is separate."""
|
||||
first, second = Path(first), Path(second)
|
||||
if out_json is not None:
|
||||
require(not Path(out_json).exists(), 'Refusing existing repeat report')
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'fresh-GPU-run exact repeat comparison', 'first': str(first), 'second': str(second),
|
||||
'pass_all': False, 'tolerance': None, 'signed_zero_included': True, 'timing_fields_compared': False,
|
||||
'scientific_apl_delay_fields_compared': True,
|
||||
'scope': 'Scientific outputs, recording integrity and input hashes; independent case/graph audit belongs to compare()'}
|
||||
try:
|
||||
summary = _read_json(first/'summary.json', used)
|
||||
n, edges, steps, name, history = (summary[key] for key in ('nodes', 'edges', 'steps', 'case', 'record_history'))
|
||||
delay = summary['apl_delay_steps']
|
||||
require(type(delay) is int and delay in (0, 18), 'Explicit repeat APL delay required')
|
||||
require(type(n) is int and type(edges) is int and type(steps) is int and n > 0 and edges > 0 and steps > 0 and
|
||||
type(history) is bool and history == (n < 512), 'Invalid repeat dimensions')
|
||||
left = _load_run(first, 'first_gpu', n, edges, steps, history, name, delay, used, checks)
|
||||
right = _load_run(second, 'second_gpu', n, edges, steps, history, name, delay, used, checks)
|
||||
_pair_outputs(left, right, checks, differences, n, steps, history)
|
||||
for role, run in (('first_gpu', left), ('second_gpu', right)):
|
||||
_timing_sequence_checks(run['apl']['inhibition'], run['apl']['applied'], run['pending'],
|
||||
delay, role, checks, differences)
|
||||
_timing_metadata_checks(run, role, delay, steps, checks)
|
||||
stable = ('case', 'complete', 'pass_all', 'steps', 'completed_ticks', 'nodes', 'edges', 'precision',
|
||||
'apl_precision', 'state_units', 'total_spikes', 'requested', 'accepted_requests', 'rejected_requests',
|
||||
'arriving_edges', 'accepted_arriving_edges', 'counts_match_sparse_events', 'all_finite', 'record_history',
|
||||
'apl_fast_edges_zeroed', 'kc_targets_present', 'effective_weights_raw_sha256', 'apl_schedule',
|
||||
'apl_si_conversion', 'build', 'sealed_ae_backend_sha256', 'learning', 'input_sha256',
|
||||
'apl_delay_steps', 'apl_timing', 'source_provenance', 'pending_generated_count',
|
||||
'pending_initial_zero_prefix', 'pending_tail_shape', 'future_generated_decay_tail_included')
|
||||
for key in stable:
|
||||
_check(checks, 'summary.'+key, key in left['summary'] and key in right['summary'] and
|
||||
left['summary'][key] == right['summary'][key])
|
||||
if not checks['summary.'+key]:
|
||||
differences['summary.'+key] = {'first': left['summary'].get(key), 'second': right['summary'].get(key)}
|
||||
report.update(case=name, steps=steps, nodes=n, edges=edges, record_history=history, apl_delay_steps=delay,
|
||||
total_spikes=len(left['state']['step']),
|
||||
explicit_exclusions=['wall_seconds', 'diagnostic_simulation_seconds', 'build_or_cache_seconds',
|
||||
'peak_allocated_vram_bytes', 'peak_reserved_vram_bytes'])
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return _finish(report, checks, differences, used, out_json)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"phase": "4AJ",
|
||||
"name": "apl-timing-lab-v1",
|
||||
"human_authorization": "允许独立新模型分支测试动力学假设",
|
||||
"gpu_uuid": "GPU-79f3a85d-adce-7833-7003-6488140ca596",
|
||||
"neurons": 166700,
|
||||
"edges": 25582938,
|
||||
"raw_weight_sum": 124177617,
|
||||
"delays_steps": [0, 18],
|
||||
"dt_ms": 0.1,
|
||||
"apl": {"tau_ms": 20.0, "gain": 0.01, "normalization": 1.0},
|
||||
"fixtures": ["zero", "pn_pulse", "kc_ref_boundary", "two_sources", "arrival_collision", "natural_request", "apl_only", "high_fanin"],
|
||||
"fixture_repeats": 3,
|
||||
"calibration": {"split": "calibration", "odor_count": 6, "modes": ["physiological", "matched_energy"], "seeds": [51101,51102,51103,51104,51105], "duration_ms": 100.0, "median_scope": "original frozen 12-odor panel", "full_oracle_probes": ["cal-physiological-00-s51101", "cal-matched_energy-00-s51101"], "repeat_first_seed_each_odor": true},
|
||||
"gates": {"sparse_min_fraction": 0.03, "sparse_max_fraction": 0.09, "max_fraction_strict": 0.5, "min_fraction_strict": 0.0, "minimum_separable_odor_pairs": 3, "same_jaccard_gt_between": true, "between_euclidean_gt_within": true, "both_modes_required": true, "finite_required": true, "repeat_exact_required": true},
|
||||
"output_budget_bytes": 25769803776,
|
||||
"origin_audit_budget_bytes": 2147483648,
|
||||
"training": false,
|
||||
"heldout_dynamic_access": false,
|
||||
"candidate_generation": false,
|
||||
"hypothesis": "Only delay the entire generated graded-APL h by 18 exact steps; zero delay is a fresh replay of sealed AG control. Engineering scheduling parity, not measured MaleCNS physiology. No change to I/O, gain, state update, fast-APL zero mask, neuron coefficients, inputs or biological observation window.",
|
||||
"tail_contract": "pending tail is the next D queue slots, shape(D,4064); if T<D its initial zero prefix is not generated future output. Do not advance the brain or APL after the original window. Closed-loop h totals are not forced to match.",
|
||||
"selection_policy": "Both arms and all fixed CAL cases retained regardless of representation outcome. Stop later scientific execution on numerical/oracle/integrity failure. No scan, tuning, favorable seed or result selection."
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Independent APL scheduling hypothesis; every earlier artifact is read-only."""
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
MODEL = Path(__file__).resolve().parent
|
||||
EXP = ROOT / 'experiments/phase4aj-apl-timing'
|
||||
AI_MODEL = ROOT / 'models/male-cns-lif/pn-recruitment-audit-v1'
|
||||
AI = ROOT / 'experiments/phase4ai-pn-recruitment-audit'
|
||||
AH_MODEL = ROOT / 'models/male-cns-lif/kc-drive-audit-v1'
|
||||
AG_MODEL = ROOT / 'models/male-cns-lif/author-apl-lab-v2'
|
||||
AG = ROOT / 'experiments/phase4ag-author-apl-r2'
|
||||
META = ROOT / 'datasets/male-cns-v1.0/processed/neuron-only/neuron-index/unified-metadata.parquet'
|
||||
COEFF = ROOT / 'experiments/phase4af-author-fullgraph/oracle/no_input/coefficients.json'
|
||||
CFG = json.loads((MODEL / 'config.json').read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def load_module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def digest(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as f:
|
||||
for block in iter(lambda: f.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as f:
|
||||
json.dump(value, f, indent=2, ensure_ascii=False, allow_nan=False)
|
||||
f.write('\n')
|
||||
|
||||
|
||||
def sources():
|
||||
return {p.relative_to(MODEL).as_posix(): digest(p) for p in sorted(MODEL.rglob('*')) if p.is_file()
|
||||
and '__pycache__' not in p.parts and p.name not in ('manifest.json', 'checksums.sha256')}
|
||||
|
||||
|
||||
def integrity():
|
||||
previous = load_module('sealed_ai_context_aj', AI_MODEL / 'context.py')
|
||||
result = previous.integrity()
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import manifest_check
|
||||
for path in (AI_MODEL, AI):
|
||||
result[str(path.relative_to(ROOT))] = manifest_check(path, digest)
|
||||
assert all(r['pass'] for r in result.values())
|
||||
return result
|
||||
|
||||
|
||||
def check():
|
||||
registered = read(EXP / 'protocol/preregistered.json')
|
||||
assert registered['sources'] == sources(), 'Registered sources changed'
|
||||
for row in registered['inputs']:
|
||||
assert digest(ROOT / row['path']) == row['sha256'], row['path']
|
||||
for row in read(EXP / 'protocol/prepared-hashes.json'):
|
||||
assert digest(EXP / row['path']) == row['sha256'], row['path']
|
||||
return registered
|
||||
|
||||
|
||||
def budget():
|
||||
size = sum(p.stat().st_size for p in EXP.rglob('*') if p.is_file())
|
||||
assert size < CFG['output_budget_bytes'], 'Output budget exceeded; preserve partial work'
|
||||
return size
|
||||
|
||||
|
||||
def seal(root):
|
||||
rows = [dict(path=str(p.relative_to(root)), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(root.rglob('*')) if p.is_file() and '__pycache__' not in p.parts]
|
||||
save(root / 'manifest.json', dict(root=str(root), files=rows))
|
||||
with (root / 'checksums.sha256').open('x') as f:
|
||||
for row in rows:
|
||||
f.write(row['sha256'] + ' ' + row['path'] + '\n')
|
||||
f.write(digest(root / 'manifest.json') + ' manifest.json\n')
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
return a.dtype == b.dtype and a.shape == b.shape and a.tobytes() == b.tobytes()
|
||||
@@ -0,0 +1,156 @@
|
||||
"""Recheck raw evidence and seal this explicit scheduling hypothesis, not learning."""
|
||||
from context import ROOT, MODEL, EXP, AG, CFG, check, save, read, digest, integrity, budget, seal
|
||||
|
||||
|
||||
def optional(path):
|
||||
return read(path) if path.exists() else None
|
||||
|
||||
|
||||
def main():
|
||||
from compare_timing import compare, compare_gpu_repeats
|
||||
from calibration import control_identity, summarize
|
||||
check(); budget()
|
||||
arms = ['delay' + str(delay) for delay in CFG['delays_steps']]
|
||||
expected_cases = CFG['fixtures'] + CFG['calibration']['full_oracle_probes']
|
||||
comparisons, repeats, controls, components = {}, {}, {}, {}
|
||||
for arm in arms:
|
||||
for name in expected_cases:
|
||||
path = EXP / 'audit' / f'compare-{arm}-{name}.json'
|
||||
if path.exists():
|
||||
actual = compare(EXP / 'cases' / arm / name, EXP / 'gpu' / arm / (name + '-r0'), EXP / 'oracle' / arm / name, None)
|
||||
assert actual == read(path), 'Changed raw oracle evidence: ' + arm + '/' + name
|
||||
comparisons[arm + '/' + name] = actual['pass_all']
|
||||
path = EXP / 'component-audit' / arm / (name + '.json')
|
||||
if path.exists():
|
||||
report = read(path)
|
||||
assert set(report.get('input_sha256', {})) == {'state.npz', 'summary.json',
|
||||
'apl-drive.float32.npy', 'apl-state.float32.npy', 'apl-inhibition.float32.npy'}
|
||||
for filename, sha in report['input_sha256'].items():
|
||||
assert digest(EXP / 'gpu' / arm / (name + '-r0') / filename) == sha
|
||||
assert set(report.get('matrix_checks', {})) == {'input_matrix', 'output_matrix', 'input_raw', 'output_raw',
|
||||
'kc_globals', 'apl_globals', 'pn_globals', 'apl_out_ids', 'input_scale', 'output_scale'}
|
||||
assert set(report.get('ast_replay_bitwise_exact', {})) == {'drive', 'state', 'inhibition'}
|
||||
assert set(report.get('float64_measurement', {})) == {'drive', 'state', 'inhibition'}
|
||||
actual_pass = bool(not report.get('error') and all(report['matrix_checks'].values()) and
|
||||
all(report['ast_replay_bitwise_exact'].values()) and report['theoretical_bound']['applicable'] and
|
||||
all(metric['bound_violations'] == 0 for metric in report['float64_measurement'].values()))
|
||||
assert report['pass_all'] == actual_pass
|
||||
components[arm + '/' + name] = actual_pass
|
||||
for name in CFG['fixtures']:
|
||||
for repeat in range(1, CFG['fixture_repeats']):
|
||||
path = EXP / 'audit' / f'repeat-{arm}-{name}-r{repeat}.json'
|
||||
if path.exists():
|
||||
actual = compare_gpu_repeats(EXP / 'gpu' / arm / (name + '-r0'), EXP / 'gpu' / arm / f'{name}-r{repeat}', None)
|
||||
assert actual == read(path), 'Changed raw repeat evidence'
|
||||
repeats[f'{arm}/{name}-r{repeat}'] = actual['pass_all']
|
||||
for name in expected_cases:
|
||||
path = EXP / 'audit' / f'control-{name}.json'
|
||||
if path.exists():
|
||||
actual = control_identity(EXP / 'gpu/delay0' / (name + '-r0'), AG / 'gpu' / (name + '-r0'), EXP / 'cases/delay0' / name)
|
||||
assert actual == read(path), 'Changed delay0 control evidence'
|
||||
controls[name] = actual['pass_all']
|
||||
origin = optional(EXP / 'origin-audit/summary.json')
|
||||
cal_records = read(EXP / 'protocol/calibration-cases.json')
|
||||
origin_complete = False
|
||||
if origin:
|
||||
expected_origin = {row['name']: row for row in cal_records}
|
||||
assert len(expected_origin) == len(cal_records) == 60
|
||||
assert len(origin['trials']) == 60 and {row['case'] for row in origin['trials']} == set(expected_origin)
|
||||
for row in origin['trials']:
|
||||
original = expected_origin[row['case']]
|
||||
assert row['repeat'] == 0 and row['ALLN_targets'] == 420 and row['pass_all']
|
||||
assert set(row['exact']) == {'step', 'neuron_idx', 'counts', 'v', 'g', 'ref'} and all(row['exact'].values())
|
||||
assert all(row[key] == original[key] for key in ('mode', 'odor_id', 'seed'))
|
||||
origin_complete = True
|
||||
for row in origin['trials']:
|
||||
trial = EXP / 'origin-audit' / row['case']
|
||||
assert digest(trial / 'summary.json') == row['summary_sha256']
|
||||
for payload in read(trial / 'output-hashes.json'):
|
||||
assert digest(trial / payload['path']) == payload['sha256']
|
||||
for filename, sha in read(trial / 'summary.json')['input_sha256'].items():
|
||||
assert digest(ROOT / filename) == sha
|
||||
shift = optional(EXP / 'audit/shift-gate.json')
|
||||
shift_complete = False
|
||||
if shift:
|
||||
expected_shift = {(name, delay) for name in CFG['fixtures'] + [row['name'] for row in cal_records] for delay in CFG['delays_steps']}
|
||||
assert len(shift['records']) == len(expected_shift) == 136
|
||||
assert {(row['case'], row['delay_steps']) for row in shift['records']} == expected_shift
|
||||
assert all(row['pass_all'] for row in shift['records'])
|
||||
shift_complete = True
|
||||
from shift_audit import transport
|
||||
import numpy as np
|
||||
for row in shift['records']:
|
||||
source = AG / 'gpu' / (row['case'] + '-r0') / 'apl-inhibition.float32.npy'
|
||||
assert digest(source) == row['input_sha256']
|
||||
_, _, actual = transport(np.load(source, mmap_mode='r', allow_pickle=False), row['delay_steps'])
|
||||
assert all(row[key] == value for key, value in actual.items())
|
||||
cal = optional(EXP / 'calibration/summary.json')
|
||||
if cal is not None:
|
||||
assert summarize(out_json=None) == cal, 'CAL scientific evidence changed'
|
||||
expected = {arm + '/' + name for arm in arms for name in expected_cases}
|
||||
expected_repeats = {f'{arm}/{name}-r{repeat}' for arm in arms for name in CFG['fixtures'] for repeat in range(1, CFG['fixture_repeats'])}
|
||||
numerical = bool(origin_complete and origin['pass_all'] and shift_complete and shift['pass_all'] and
|
||||
set(comparisons) == set(components) == expected and set(repeats) == expected_repeats and
|
||||
set(controls) == set(expected_cases) and all(comparisons.values()) and all(components.values()) and
|
||||
all(repeats.values()) and all(controls.values()))
|
||||
gates = {name: optional(EXP / 'audit' / (name + '-gate.json')) for name in ('fixtures', 'probes', 'calibration')}
|
||||
numerical = numerical and all(gates[name] and gates[name]['pass_all'] for name in ('fixtures', 'probes'))
|
||||
cal_execution = bool(cal and cal['execution_gate_pass'] and gates['calibration'] and gates['calibration']['pass_all'])
|
||||
treatment_pass = bool(cal and cal.get('arms', {}).get('delay18', {}).get('original_representation_gate_pass', False))
|
||||
baseline_pass = bool(cal and cal.get('arms', {}).get('delay0', {}).get('original_representation_gate_pass', False))
|
||||
frozen = read(EXP / 'regression/frozen-five.json')
|
||||
after = integrity()
|
||||
save(EXP / 'regression/old-integrity-after.json', after)
|
||||
oldpass = bool(frozen['pass_all'] and all(row['pass'] for row in after.values()))
|
||||
if not oldpass:
|
||||
status = 'FAIL-FROZEN-INTEGRITY-OR-REGRESSION'
|
||||
elif numerical and cal_execution:
|
||||
status = 'PASS-FIXED-CAL-APL-TIMING-HYPOTHESIS' if treatment_pass else 'FAIL-FIXED-CAL-APL-TIMING-HYPOTHESIS'
|
||||
elif (any(not passed for group in (comparisons, components, repeats, controls) for passed in group.values()) or
|
||||
any(report is not None and not report['pass_all'] for report in (origin, shift, *gates.values())) or
|
||||
cal is not None and not cal['execution_gate_pass']):
|
||||
status = 'FAIL-NUMERICAL-OR-CONTROL-GATE'
|
||||
else:
|
||||
status = 'INCOMPLETE-NUMERICAL-OR-CAL-EXECUTION'
|
||||
measurements = []
|
||||
for path in sorted((EXP / 'gpu').glob('*/*/summary.json')):
|
||||
summary = read(path)
|
||||
measurements.append(dict(arm=path.parent.parent.name, run=path.parent.name,
|
||||
**{key: summary[key] for key in ('case', 'total_spikes', 'accepted_requests', 'nodes', 'edges',
|
||||
'diagnostic_simulation_seconds', 'peak_allocated_vram_bytes')}))
|
||||
result = dict(status=status, numerical_gate_pass=bool(numerical), full_CAL_execution_gate_pass=cal_execution,
|
||||
delay0_representation_pass=baseline_pass, delay18_representation_pass=treatment_pass,
|
||||
origin_audit=origin, fixed_trace_shift=shift, gates=gates, comparisons=comparisons,
|
||||
repeats=repeats, controls=controls, components=components, calibration=cal,
|
||||
stopped=[dict(path=str(path.relative_to(EXP)), report=read(path)) for path in sorted((EXP / 'audit').glob('stopped-*.json'))],
|
||||
old_frozen_integrity_and_five_reference_pass=oldpass, measurements=measurements,
|
||||
unchanged_APL_parameters=CFG['apl'], explicit_hypothesis=CFG['hypothesis'],
|
||||
delay_steps=CFG['delays_steps'], biological_delay_measured=False,
|
||||
APL_nonKC_fast_edges_unreplaced=1212, shared_APL_arithmetic_oracle_limitation=True,
|
||||
whole_brain_equal_inhibition_integral_claim=False, universal_equivalence_claim=False,
|
||||
heldout_generalization_tested=False, learning_achieved=False, candidate_generated=False,
|
||||
payload_bytes_before_manifest=budget(),
|
||||
next_gate='Independent seeds and novel-odor representation; then credit specificity and functional learning' if numerical and cal_execution and treatment_pass
|
||||
else 'Report the first failed gate for this fixed hypothesis; no tuning or conditioning promotion')
|
||||
save(EXP / 'audit/final-status.json', result)
|
||||
with (EXP / 'REPORT.md').open('x', encoding='utf-8') as stream:
|
||||
stream.write('\n'.join(['# Phase 4AJ — fixed graded-APL timing branch', '', f'Status: `{status}`.', '',
|
||||
f'Numerical gate: {numerical}. Complete both CAL arms: {cal_execution}.',
|
||||
f'Original representation gate: delay0={baseline_pass}; delay18={treatment_pass}.',
|
||||
'Only generated h application time changes: zero versus 18 steps. No brain gain, input, learning or mask change.',
|
||||
'This is an engineering scheduling hypothesis, not measured MaleCNS APL delay. It delays PN- and KC-derived h together.',
|
||||
'Finite stored generated samples are preserved in applied output or pending tail; the brain is not advanced to flush the tail.',
|
||||
'Closed-loop generated h and accepted requests can differ between arms; equal total inhibition is not assumed.',
|
||||
'Independent Brian LIF and candidate share legacy Torch APL arithmetic; component and separate delay-queue audits qualify this limit.',
|
||||
'Original fast APL mask still leaves 1,212 non-KC edges unreplaced. No silent scope change.',
|
||||
'The origin audit identifies timed real-edge antecedents, not unique or necessary biological causes.',
|
||||
f'Frozen checksums and five reference trials: {oldpass}.',
|
||||
'Costs include diagnostic synchronization, IPC, hashes and tracing; these are not production runtime benchmarks.',
|
||||
'No heldout trajectories, conditioning, learned model, reward interface, game, decoder or candidate.',
|
||||
'Next gate: ' + result['next_gate'], '']))
|
||||
check(); seal(MODEL); seal(EXP)
|
||||
print(status, flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,453 @@
|
||||
"""Phase 4AJ isolated fixed-delay graded-APL timing branch.
|
||||
|
||||
Copied from sealed AG-r2 gpu_apl.py; only dynamics change is a 0/18-step
|
||||
float32 output delay before the unchanged unconditional end-stage g write.
|
||||
The sealed LegacyAPL and AE CUDA operators are loaded explicitly, unmodified.
|
||||
Generated h is still logged separately. No extra brain/tail ticks are run.
|
||||
This is engineering schedule parity, not a measured biological delay claim.
|
||||
"""
|
||||
from functools import lru_cache
|
||||
import csv
|
||||
import difflib
|
||||
import importlib.util
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import time
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from timing_queue import audit_sequence
|
||||
|
||||
|
||||
SOURCE_ROOT = Path(__file__).resolve().parent
|
||||
AE_MODEL = SOURCE_ROOT.parent / "author-runtime-lab-v1"
|
||||
AF_MODEL = SOURCE_ROOT.parent / "author-fullgraph-lab-v1"
|
||||
AG_MODEL = SOURCE_ROOT.parent / "author-apl-lab-v2"
|
||||
N_NEURONS = 166700
|
||||
SEALED_SHA256 = {
|
||||
"ag_gpu": "69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a",
|
||||
"ag_worker": "21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291",
|
||||
"ae_backend": "03758d07572f4b44d272059d9308fac534268ad201b57bdd77d69fee95e67278",
|
||||
"af_queue": "d72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961",
|
||||
}
|
||||
COEFFICIENT_NAMES = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def write_json(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as output:
|
||||
json.dump(value, output, indent=2, allow_nan=False)
|
||||
output.write("\n")
|
||||
|
||||
|
||||
def raw_hash(value, dtype):
|
||||
return hashlib.sha256(np.ascontiguousarray(value, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def file_sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def load_source(path, name, expected_hash):
|
||||
"""Load a sealed source under a unique name; never patch its globals."""
|
||||
path = Path(path)
|
||||
require(file_sha256(path) == expected_hash, "Sealed source changed: " + str(path))
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
require(spec is not None and spec.loader is not None, "Cannot load sealed module")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
except BaseException:
|
||||
sys.modules.pop(name, None)
|
||||
raise
|
||||
return module
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def sealed_apl():
|
||||
return load_source(AG_MODEL / "legacy_apl_worker.py", "phase4aj_sealed_ag_worker",
|
||||
SEALED_SHA256["ag_worker"])
|
||||
|
||||
|
||||
def source_audit(out_dir):
|
||||
sources = {"ag_gpu": AG_MODEL / "gpu_apl.py", "ag_worker": AG_MODEL / "legacy_apl_worker.py",
|
||||
"ae_backend": AE_MODEL / "gpu_backend.py", "af_queue": AF_MODEL / "oracle_full.py"}
|
||||
actual = {key: file_sha256(path) for key, path in sources.items()}
|
||||
require(actual == SEALED_SHA256, "Sealed implementation hash mismatch")
|
||||
diff_path = out_dir / "sealed-gpu-source.diff"
|
||||
with diff_path.open("x", encoding="utf-8", newline="\n") as output:
|
||||
output.writelines(difflib.unified_diff(
|
||||
sources["ag_gpu"].read_text(encoding="utf-8").splitlines(keepends=True),
|
||||
Path(__file__).read_text(encoding="utf-8").splitlines(keepends=True),
|
||||
fromfile="sealed-author-apl-lab-v2/gpu_apl.py",
|
||||
tofile="new-apl-timing-lab-v1/gpu_timing.py"))
|
||||
audit = {"sealed_source_sha256": actual, "candidate_sha256": file_sha256(__file__),
|
||||
"source_diff_sha256": file_sha256(diff_path),
|
||||
"module_globals_patched": False,
|
||||
"only_dynamics_change": "fixed zero-initialized 0/18-step h32 delay before unchanged end g write",
|
||||
"diagnostic_additions": ["generated/applied/pending trace split", "fixed-sequence bitwise audit",
|
||||
"source provenance and final APL state disclosure"]}
|
||||
write_json(out_dir / "source-audit.json", audit)
|
||||
return audit
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def sealed_backend():
|
||||
return load_source(AE_MODEL / "gpu_backend.py", "phase4aj_sealed_ae_backend", SEALED_SHA256["ae_backend"])
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def queue_function():
|
||||
return load_source(AF_MODEL / "oracle_full.py", "phase4aj_sealed_af_queue", SEALED_SHA256["af_queue"]).queue_summary
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def load_graph(graph_directory):
|
||||
root = Path(graph_directory)
|
||||
require(root.is_absolute(), "graph_dir must be absolute")
|
||||
graph = {}
|
||||
for name, dtype in (("nodes", np.int32), ("edge_id", np.int64), ("src", np.int32),
|
||||
("dst", np.int32), ("signed_raw", np.int64), ("ptr", np.int64),
|
||||
("weights", np.float64)):
|
||||
value = np.load(root / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
require(value.dtype == dtype and value.ndim == 1, "Graph dtype/rank mismatch: " + name)
|
||||
graph[name] = value
|
||||
nodes, edges = graph["nodes"], graph["edge_id"]
|
||||
n, e = len(nodes), len(edges)
|
||||
require(0 < n <= N_NEURONS and bool(np.all((nodes >= 0) & (nodes < N_NEURONS))) and
|
||||
bool(np.all(np.diff(nodes) > 0)), "Global node IDs must be sorted unique")
|
||||
require(n < 512 or (n == N_NEURONS and np.array_equal(nodes, np.arange(N_NEURONS, dtype=np.int32))),
|
||||
"Only small fixtures or the intact frozen full graph are permitted")
|
||||
require(n != N_NEURONS or e == 25582938, "Full graph edge count changed")
|
||||
for name in ("src", "dst", "signed_raw", "weights"):
|
||||
require(graph[name].shape == (e,), "Graph edge array length mismatch: " + name)
|
||||
ptr = graph["ptr"]
|
||||
require(ptr.shape == (n + 1,) and ptr[0] == 0 and ptr[-1] == e and
|
||||
bool(np.all(np.diff(ptr) >= 0)), "Invalid graph CSR")
|
||||
require(np.array_equal(graph["src"], np.repeat(np.arange(n, dtype=np.int32), np.diff(ptr))),
|
||||
"Source order does not match CSR")
|
||||
# Global edge IDs need not equal local row indices in a fixture.
|
||||
for start in range(0, e, 1 << 20):
|
||||
stop = min(start + (1 << 20), e)
|
||||
require(bool(np.all((graph["dst"][start:stop] >= 0) & (graph["dst"][start:stop] < n))),
|
||||
"Local destination out of range")
|
||||
require(bool(np.isfinite(graph["weights"][start:stop]).all()), "Nonfinite base SI weights")
|
||||
require(np.array_equal(np.sign(graph["weights"][start:stop]), np.sign(graph["signed_raw"][start:stop])),
|
||||
"Base weight sign/zero pattern differs from frozen raw contacts")
|
||||
lo = max(0, start - 1)
|
||||
require(bool(np.all(edges[start:stop] >= 0)) and bool(np.all(np.diff(edges[lo:stop]) > 0)),
|
||||
"Global edge IDs must retain canonical increasing source/edge order")
|
||||
return graph
|
||||
|
||||
|
||||
def load_inputs(case_dir):
|
||||
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
|
||||
for name in ("name", "steps", "dt_ms", "duration_ms", "model", "graph_dir", "apl",
|
||||
"apl_bundle_dir", "coefficients_path", "record_history", "apl_delay_steps"):
|
||||
require(name in spec, "Missing case field: " + name)
|
||||
require(type(spec["apl_delay_steps"]) is int and spec["apl_delay_steps"] in (0, 18),
|
||||
"apl_delay_steps must be integer 0 or 18")
|
||||
steps = spec["steps"]
|
||||
require(type(steps) is int and 0 < steps <= 1000 and float(spec["dt_ms"]) == .1 and
|
||||
float(spec["duration_ms"]) == steps * .1, "Unexpected steps/dt/duration")
|
||||
require(type(spec["record_history"]) is bool, "record_history must be Boolean")
|
||||
require(spec["apl"] == {"tau_ms": 20, "gain": .01, "normalization": 1}, "Frozen APL parameters changed")
|
||||
for name in ("graph_dir", "apl_bundle_dir", "coefficients_path"):
|
||||
require(Path(spec[name]).is_absolute(), "Input path must be absolute: " + name)
|
||||
model = spec["model"]
|
||||
for name in ("resting_potential_mv", "threshold_mv", "reset_potential_mv", "synaptic_delay_ms",
|
||||
"refractory_ms", "w_syn_mv_per_anatomical_synapse"):
|
||||
require(name in model and math.isfinite(float(model[name])), "Invalid model field: " + name)
|
||||
require(float(model["synaptic_delay_ms"]) == 1.8 and float(model["refractory_ms"]) == 2.2 and
|
||||
float(model["w_syn_mv_per_anatomical_synapse"]) == .275, "Frozen delay/ref/W_syn changed")
|
||||
coeff = json.loads(Path(spec["coefficients_path"]).read_text(encoding="utf-8"))
|
||||
for name in COEFFICIENT_NAMES:
|
||||
require(name in coeff and math.isfinite(float(coeff[name])), "Invalid author coefficient: " + name)
|
||||
require(coeff["threshold"] == float(model["threshold_mv"]) * .001 and
|
||||
coeff["reset"] == float(model["reset_potential_mv"]) * .001, "Coefficient threshold/reset mismatch")
|
||||
graph = load_graph(str(Path(spec["graph_dir"]).resolve()))
|
||||
n = len(graph["nodes"])
|
||||
require(spec["record_history"] == (n < 512), "Fixtures need dense history; full graph must not duplicate dense histories")
|
||||
with np.load(case_dir / "requests.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"tick", "neuron_idx"}, "Request sparse schema changed")
|
||||
ticks, requested = source["tick"].copy(), source["neuron_idx"].copy()
|
||||
require(ticks.dtype == np.int32 and requested.dtype == np.int32 and ticks.ndim == 1 and
|
||||
ticks.shape == requested.shape, "Invalid sparse request dtype/shape")
|
||||
require(bool(np.all((ticks >= 0) & (ticks < steps))), "Request tick outside trial")
|
||||
if len(ticks) > 1:
|
||||
require(bool(np.all((ticks[1:] > ticks[:-1]) |
|
||||
((ticks[1:] == ticks[:-1]) & (requested[1:] > requested[:-1])))),
|
||||
"Requests must be sorted unique by tick/global neuron")
|
||||
local = np.searchsorted(graph["nodes"], requested)
|
||||
require(bool(np.all(local < n)) and np.array_equal(graph["nodes"][local], requested),
|
||||
"Requested neuron absent from graph")
|
||||
initial = {"initial_v_mv": np.full(n, float(model["resting_potential_mv"]), dtype=np.float64),
|
||||
"initial_g_mv": np.zeros(n, dtype=np.float64)}
|
||||
initial_path = case_dir / "arrays.npz"
|
||||
if initial_path.exists():
|
||||
with np.load(initial_path, allow_pickle=False) as source:
|
||||
require(set(source.files).issubset(initial), "Unexpected initial-state array")
|
||||
for name in source.files:
|
||||
value = source[name]
|
||||
require(value.dtype == np.float64 and value.shape == (n,) and bool(np.isfinite(value).all()),
|
||||
"Invalid initial state: " + name)
|
||||
initial[name] = value.copy()
|
||||
return spec, graph, coeff, ticks, requested, local, initial
|
||||
|
||||
|
||||
def new_array(path, dtype, shape):
|
||||
require(not Path(path).exists(), "Refusing existing output: " + str(path))
|
||||
return np.lib.format.open_memmap(path, mode="w+", dtype=dtype, shape=shape)
|
||||
|
||||
|
||||
def run_gpu(case_dir, out_dir, build_root):
|
||||
"""Run fresh candidate state; never consume oracle/candidate APL traces.
|
||||
|
||||
pass_all denotes internal recording/finite integrity only. Independent
|
||||
comparison and the original complete representation gate are separate.
|
||||
"""
|
||||
case_dir, out_dir = Path(case_dir), Path(out_dir)
|
||||
require(not out_dir.exists(), "Refusing existing output directory: " + str(out_dir))
|
||||
out_dir.mkdir(parents=True, exist_ok=False)
|
||||
start, tick, completed = time.perf_counter(), -1, 0
|
||||
maps = {}
|
||||
try:
|
||||
source_provenance = source_audit(out_dir)
|
||||
spec, graph, coeff, request_ticks, request_nodes, request_local, initial = load_inputs(case_dir)
|
||||
require(torch.cuda.is_available() and torch.cuda.device_count() == 1 and
|
||||
"4090" in torch.cuda.get_device_name(), "Only the declared single visible RTX4090 is permitted")
|
||||
ext, build = sealed_backend().build_extension(build_root)
|
||||
build_done = time.perf_counter()
|
||||
apl = sealed_apl().LegacyAPL(spec["apl_bundle_dir"], spec["dt_ms"])
|
||||
write_json(out_dir / "apl-component.json", apl.metadata)
|
||||
device = apl.device
|
||||
torch.cuda.reset_peak_memory_stats(device)
|
||||
n, e, steps = len(graph["nodes"]), len(graph["edge_id"]), spec["steps"]
|
||||
|
||||
def upload(value, dtype):
|
||||
return torch.from_numpy(np.array(value, copy=True, order="C")).to(device=device, dtype=dtype).contiguous()
|
||||
|
||||
indptr, dst = upload(graph["ptr"], torch.int64), upload(graph["dst"], torch.int32)
|
||||
global_nodes_gpu = upload(graph["nodes"], torch.int64)
|
||||
base_weights = upload(graph["weights"], torch.float64)
|
||||
weights = base_weights.clone()
|
||||
del base_weights
|
||||
local_apl = np.flatnonzero(np.isin(graph["nodes"], apl.apl_globals)).astype(np.int64)
|
||||
apl_out_mask = np.isin(graph["src"], local_apl)
|
||||
if bool(np.any(apl_out_mask)):
|
||||
weights[upload(apl_out_mask, torch.bool)] = 0.0
|
||||
effective_hash = hashlib.sha256()
|
||||
for offset in range(0, e, 1 << 20):
|
||||
effective_hash.update(weights[offset:offset + (1 << 20)].cpu().numpy().astype("<f8", copy=False).tobytes())
|
||||
common_kc, local_kc, full_kc = np.intersect1d(graph["nodes"], apl.kc_globals, assume_unique=True, return_indices=True)
|
||||
kc_local_gpu, kc_full_gpu = upload(local_kc, torch.int64), upload(full_kc, torch.int64)
|
||||
v = upload(initial["initial_v_mv"] * .001, torch.float64)
|
||||
g = upload(initial["initial_g_mv"] * .001, torch.float64)
|
||||
ref = torch.zeros(n, device=device, dtype=torch.int32)
|
||||
counts = torch.zeros(n, device=device, dtype=torch.int32)
|
||||
requests = torch.zeros(n, device=device, dtype=torch.uint8)
|
||||
ring = torch.zeros((19, n), device=device, dtype=torch.uint8)
|
||||
apl_delay = spec["apl_delay_steps"]
|
||||
apl_ring = torch.zeros((apl_delay, 4064), device=device, dtype=torch.float32)
|
||||
maps["apl_applied"] = new_array(out_dir / "apl-applied.float32.npy", np.float32, (steps, 4064))
|
||||
for name, width in (("drive", 2), ("state", 2), ("inhibition", 4064)):
|
||||
maps["apl_" + name] = new_array(out_dir / ("apl-" + name + ".float32.npy"), np.float32, (steps, width))
|
||||
if spec["record_history"]:
|
||||
for name, dtype in (("v", np.float64), ("g", np.float64), ("ref", np.int32)):
|
||||
maps[name] = new_array(out_dir / (name + "-history." + np.dtype(dtype).name + ".npy"), dtype, (steps, n))
|
||||
maps["pre_" + name] = new_array(out_dir / ("preAPL-" + name + "." + np.dtype(dtype).name + ".npy"), dtype, (steps, n))
|
||||
event_steps, event_nodes = [], []
|
||||
accepted_requests_total, due_total, accepted_due_total = 0, 0, 0
|
||||
queue_summary = queue_function()
|
||||
simulation_start = time.perf_counter()
|
||||
with (out_dir / "queue.jsonl").open("x", encoding="utf-8", buffering=1) as queue_output, \
|
||||
(out_dir / "tick-state-hashes.jsonl").open("x", encoding="utf-8", buffering=1) as hash_output, \
|
||||
(out_dir / "requests.csv").open("x", encoding="utf-8", newline="", buffering=1) as request_output:
|
||||
writer = csv.DictWriter(request_output, fieldnames=REQUEST_FIELDS)
|
||||
writer.writeheader()
|
||||
for tick in range(steps):
|
||||
requests.zero_()
|
||||
begin, end = np.searchsorted(request_ticks, (tick, tick + 1))
|
||||
if end > begin:
|
||||
requested_local_gpu = upload(request_local[begin:end], torch.int64)
|
||||
requests[requested_local_gpu] = 1
|
||||
accepted = (ref[requested_local_gpu] <= 1).to(torch.uint8).cpu().numpy()
|
||||
accepted_requests_total += int(accepted.sum())
|
||||
writer.writerows({"tick": tick, "neuron_idx": int(node), "attempted": 1,
|
||||
"accepted": int(ok), "rejected": 1 - int(ok)}
|
||||
for node, ok in zip(request_nodes[begin:end], accepted))
|
||||
spikes = ring[tick % 19]
|
||||
ext.state_step(v, g, ref, requests, spikes, counts,
|
||||
*[float(coeff[name]) for name in COEFFICIENT_NAMES], 22)
|
||||
expanded_global = np.empty(0, dtype=np.int64)
|
||||
accepted_cpu = np.empty(0, dtype=np.uint8)
|
||||
if tick >= 18:
|
||||
due = ring[(tick - 18) % 19]
|
||||
sources = torch.nonzero(due, as_tuple=False).flatten().contiguous()
|
||||
if sources.numel():
|
||||
degrees = indptr[sources + 1] - indptr[sources]
|
||||
offsets = (torch.cumsum(degrees, dim=0) - degrees).contiguous()
|
||||
edge_count = int(degrees.sum().item())
|
||||
expanded = torch.empty(edge_count, device=device, dtype=torch.int64)
|
||||
ext.fill_edges(indptr, sources, offsets, expanded)
|
||||
if edge_count:
|
||||
destination = dst[expanded]
|
||||
# Actual active fanout order, BEFORE stable target grouping.
|
||||
expanded_global = graph["edge_id"][expanded.cpu().numpy()].copy()
|
||||
accepted_cpu = (ref[destination.long()] == 0).to(torch.uint8).cpu().numpy().copy()
|
||||
order = torch.argsort(destination, stable=True)
|
||||
ordered_edges, ordered_targets = expanded[order].contiguous(), destination[order]
|
||||
boundary = torch.ones(edge_count, device=device, dtype=torch.bool)
|
||||
boundary[1:] = ordered_targets[1:] != ordered_targets[:-1]
|
||||
starts = torch.nonzero(boundary, as_tuple=False).flatten().contiguous()
|
||||
targets = ordered_targets[starts].contiguous()
|
||||
ext.apply_arrivals(ordered_edges, starts, targets, weights, g, ref)
|
||||
queue = queue_summary(tick, expanded_global, accepted_cpu)
|
||||
queue_output.write(json.dumps(queue, allow_nan=False) + "\n")
|
||||
due_total += queue["count"]
|
||||
accepted_due_total += queue["accepted_count"]
|
||||
if spec["record_history"]:
|
||||
for name, value in (("v", v), ("g", g), ("ref", ref)):
|
||||
maps["pre_" + name][tick] = value.cpu().numpy()
|
||||
if n == N_NEURONS:
|
||||
full_spikes = spikes
|
||||
else:
|
||||
apl.spikes.zero_()
|
||||
apl.spikes[global_nodes_gpu] = spikes
|
||||
full_spikes = apl.spikes
|
||||
drive, apl_state, h = apl.advance(full_spikes)
|
||||
if apl_delay == 0:
|
||||
applied_h = h # Exact legacy identity; no extra arithmetic.
|
||||
else:
|
||||
slot = tick % apl_delay
|
||||
applied_h = apl_ring[slot].clone()
|
||||
apl_ring[slot].copy_(h)
|
||||
# Mixed precision contract: widen f32 h EXACTLY, THEN multiply
|
||||
# in f64, THEN subtract in f64. Do not guard refractory KCs.
|
||||
delta_si = applied_h[kc_full_gpu].to(torch.float64) * 0.001
|
||||
g[kc_local_gpu] = g[kc_local_gpu] - delta_si
|
||||
apl_cpu = {"drive": drive.cpu().numpy().copy(), "state": apl_state.cpu().numpy().copy(),
|
||||
"inhibition": h.cpu().numpy().copy(), "applied": applied_h.cpu().numpy().copy()}
|
||||
for name, value in apl_cpu.items():
|
||||
maps["apl_" + name][tick] = value
|
||||
actual = {"v": v.cpu().numpy().copy(), "g": g.cpu().numpy().copy(),
|
||||
"ref": ref.cpu().numpy().copy()}
|
||||
finite = all(bool(np.isfinite(value).all()) for value in list(actual.values()) + list(apl_cpu.values()))
|
||||
row = {"tick": tick, **{name + "_sha256": raw_hash(value, "<i4" if name == "ref" else "<f8")
|
||||
for name, value in actual.items()}}
|
||||
hash_output.write(json.dumps(row, allow_nan=False) + "\n")
|
||||
if spec["record_history"]:
|
||||
for name, value in actual.items():
|
||||
maps[name][tick] = value
|
||||
fired_local = torch.nonzero(spikes, as_tuple=False).flatten().cpu().numpy()
|
||||
if len(fired_local):
|
||||
event_steps.append(np.full(len(fired_local), tick, dtype=np.int32))
|
||||
event_nodes.append(graph["nodes"][fired_local].copy())
|
||||
completed += 1
|
||||
if not finite:
|
||||
with (out_dir / "first-failure-state.npz").open("xb") as target:
|
||||
np.savez_compressed(target, tick=np.array(tick, dtype=np.int32), **actual,
|
||||
**{"apl_" + name: value for name, value in apl_cpu.items()})
|
||||
require(False, "Nonfinite state; first failure saved")
|
||||
torch.cuda.synchronize(device)
|
||||
|
||||
def joined(values):
|
||||
return np.concatenate(values).astype(np.int32, copy=False) if values else np.empty(0, dtype=np.int32)
|
||||
|
||||
state = {"step": joined(event_steps), "neuron_idx": joined(event_nodes), "counts": counts.cpu().numpy(),
|
||||
"v": v.cpu().numpy(), "g": g.cpu().numpy(), "ref": ref.cpu().numpy()}
|
||||
with (out_dir / "state.npz").open("xb") as target:
|
||||
np.savez_compressed(target, **state)
|
||||
event_locals = np.searchsorted(graph["nodes"], state["neuron_idx"])
|
||||
counts_exact = np.array_equal(np.bincount(event_locals, minlength=n), state["counts"])
|
||||
require(counts_exact and completed == steps, "Sparse events/counts/completed ticks mismatch")
|
||||
for value in maps.values():
|
||||
value.flush()
|
||||
# Save the queue state, not an invented future APL/brain decay trace.
|
||||
if apl_delay:
|
||||
head = steps % apl_delay
|
||||
ring_cpu = apl_ring.cpu().numpy()
|
||||
pending = np.concatenate((ring_cpu[head:], ring_cpu[:head]), axis=0)
|
||||
else:
|
||||
pending = np.empty((0, 4064), dtype=np.float32)
|
||||
with (out_dir / "apl-pending-tail.float32.npy").open("xb") as target:
|
||||
np.save(target, pending, allow_pickle=False)
|
||||
timing = audit_sequence(maps["apl_inhibition"], maps["apl_applied"], pending, apl_delay)
|
||||
final_apl_state = apl.state.cpu().numpy().copy()
|
||||
timing.update({
|
||||
"generated_rows": steps,
|
||||
"applied_generated_rows": max(steps - apl_delay, 0),
|
||||
"pending_shape": [apl_delay, 4064],
|
||||
"pending_application_tick_start": steps,
|
||||
"pending_application_tick_stop_exclusive": steps + apl_delay,
|
||||
"pending_generated_tick_start": max(0, steps - apl_delay),
|
||||
"pending_generated_tick_stop_exclusive": steps,
|
||||
"generated_trace": "apl-inhibition.float32.npy",
|
||||
"applied_trace": "apl-applied.float32.npy",
|
||||
"pending_trace": "apl-pending-tail.float32.npy",
|
||||
"pending_raw_sha256": raw_hash(pending, "<f4"),
|
||||
"final_apl_state_float32": final_apl_state.tolist(),
|
||||
"final_apl_state_u32_bits": final_apl_state.view(np.uint32).tolist(),
|
||||
"final_apl_state_nonzero": bool(np.any(final_apl_state != 0)),
|
||||
"pending_queue_drained": apl_delay == 0,
|
||||
"finite_generated_sequence_only": True,
|
||||
"future_apl_decay_or_feedback_generated": False,
|
||||
"closed_loop_branch_integrals_claimed_equal": False,
|
||||
"biological_delay_measurement_claimed": False,
|
||||
})
|
||||
write_json(out_dir / "apl-timing-audit.json", timing)
|
||||
summary = {"case": spec["name"], "complete": True, "pass_all": True,
|
||||
"pass_all_scope": "internal finite/recording/event-count integrity only; independent equivalence not claimed",
|
||||
"steps": steps, "completed_ticks": completed, "nodes": n, "edges": e, "precision": "float64",
|
||||
"apl_precision": "float32", "state_units": "SI volts", "total_spikes": len(state["step"]),
|
||||
"requested": len(request_nodes), "accepted_requests": accepted_requests_total,
|
||||
"rejected_requests": len(request_nodes) - accepted_requests_total,
|
||||
"arriving_edges": due_total, "accepted_arriving_edges": accepted_due_total,
|
||||
"counts_match_sparse_events": bool(counts_exact), "all_finite": True,
|
||||
"record_history": spec["record_history"], "apl_fast_edges_zeroed": int(apl_out_mask.sum()),
|
||||
"kc_targets_present": len(common_kc), "effective_weights_raw_sha256": effective_hash.hexdigest(),
|
||||
"apl_component": apl.metadata, "apl_schedule": "end, after threshold/reset and guarded arrivals, unconditional all KC",
|
||||
"apl_si_conversion": "applied h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction",
|
||||
"apl_delay_steps": apl_delay, "apl_timing": timing, "source_provenance": source_provenance,
|
||||
"pending_generated_count": min(steps, apl_delay),
|
||||
"pending_initial_zero_prefix": max(apl_delay - steps, 0),
|
||||
"pending_tail_shape": [apl_delay, 4064], "future_generated_decay_tail_included": False,
|
||||
"build": build, "sealed_ae_backend_sha256": file_sha256(AE_MODEL / "gpu_backend.py"),
|
||||
"shared_apl_component_oracle_limitation": True, "oracle_validation_claim": False,
|
||||
"learning": False, "performance_runtime_claim": False, "cpu_sync_each_tick": True,
|
||||
"build_or_cache_seconds": build_done - start,
|
||||
"diagnostic_simulation_seconds": time.perf_counter() - simulation_start,
|
||||
"wall_seconds": time.perf_counter() - start,
|
||||
"peak_allocated_vram_bytes": int(torch.cuda.max_memory_allocated(device)),
|
||||
"peak_reserved_vram_bytes": int(torch.cuda.max_memory_reserved(device)),
|
||||
"input_sha256": {"spec.json": file_sha256(case_dir / "spec.json"),
|
||||
"requests.npz": file_sha256(case_dir / "requests.npz"),
|
||||
"base_weights.npy": file_sha256(Path(spec["graph_dir"]) / "weights.npy"),
|
||||
"coefficients.json": file_sha256(spec["coefficients_path"])}}
|
||||
if (case_dir / "arrays.npz").exists():
|
||||
summary["input_sha256"]["arrays.npz"] = file_sha256(case_dir / "arrays.npz")
|
||||
write_json(out_dir / "summary.json", summary)
|
||||
return summary
|
||||
except Exception as error:
|
||||
for value in maps.values():
|
||||
value.flush()
|
||||
write_json(out_dir / "error.json", {"complete": False, "pass_all": False,
|
||||
"tick_in_progress": tick, "completed_ticks": completed,
|
||||
"type": type(error).__name__, "message": str(error), "traceback": traceback.format_exc()})
|
||||
raise
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"root": "/data/digital-drosophila/models/male-cns-lif/apl-timing-lab-v1",
|
||||
"files": [
|
||||
{
|
||||
"path": "PROTOCOL.md",
|
||||
"bytes": 4272,
|
||||
"sha256": "d96aef0107f7b0791d16e475b749da418b8a38d2c56a94e0779de0cd6581a7de"
|
||||
},
|
||||
{
|
||||
"path": "al_origin_audit.py",
|
||||
"bytes": 33399,
|
||||
"sha256": "7201871477a29ede31c7d6c6bf1c92700da3db5505e67ccfa7cb43126b8464b9"
|
||||
},
|
||||
{
|
||||
"path": "calibration.py",
|
||||
"bytes": 22786,
|
||||
"sha256": "fb15ddeaa108305c8de697bbae36b23ab0414a7c58db51c66dce9feb4a048a0d"
|
||||
},
|
||||
{
|
||||
"path": "compare_timing.py",
|
||||
"bytes": 41221,
|
||||
"sha256": "9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734"
|
||||
},
|
||||
{
|
||||
"path": "config.json",
|
||||
"bytes": 2149,
|
||||
"sha256": "0b720e23aa7988b2b443e14951082b1ce9b061a183b7f858c0e0f3c0fc6fb25e"
|
||||
},
|
||||
{
|
||||
"path": "context.py",
|
||||
"bytes": 3436,
|
||||
"sha256": "06d4994af3729d653263391ff3a3604f7c919e78562604e5c86acfa292d60ba0"
|
||||
},
|
||||
{
|
||||
"path": "finalize.py",
|
||||
"bytes": 11254,
|
||||
"sha256": "0a2dbf9e5f889e9dd3491d036e6f92c17417a84482c5e1e49134c0993f7d68f2"
|
||||
},
|
||||
{
|
||||
"path": "gpu_timing.py",
|
||||
"bytes": 27083,
|
||||
"sha256": "af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b"
|
||||
},
|
||||
{
|
||||
"path": "oracle_timing.py",
|
||||
"bytes": 55279,
|
||||
"sha256": "39d8c47c94bc2faddf016864a43e7715907fb987f56f7b27098060a88851f6be"
|
||||
},
|
||||
{
|
||||
"path": "prepare.py",
|
||||
"bytes": 5970,
|
||||
"sha256": "bba71bcd52dcb78d9ae0fa93a8a43173a4669ecef038dce18ce7d82edd9bce50"
|
||||
},
|
||||
{
|
||||
"path": "run_case.py",
|
||||
"bytes": 2855,
|
||||
"sha256": "d93e4fe2df542ce5a0f0adb5aceada583dc8c921f0159f426e50428d79d67a1d"
|
||||
},
|
||||
{
|
||||
"path": "run_suite.py",
|
||||
"bytes": 6161,
|
||||
"sha256": "6f26c626acf9ef8e6fae7902af0cbf0bd176eb322529ca8aa203a9700716c889"
|
||||
},
|
||||
{
|
||||
"path": "shift_audit.py",
|
||||
"bytes": 2114,
|
||||
"sha256": "46f380dac340acc91f226968bb493475a31777f8290fb20f9145a97c9009d845"
|
||||
},
|
||||
{
|
||||
"path": "test_al_origin_audit.py",
|
||||
"bytes": 9998,
|
||||
"sha256": "4b75a39d36090b0a66cc7ad4b370ac785713fb58c366df2efcc55152d612fc2b"
|
||||
},
|
||||
{
|
||||
"path": "test_calibration.py",
|
||||
"bytes": 14734,
|
||||
"sha256": "307a2848bf6d05aaa8105bb3558be2a74425894f96a4a2bd20af8b82cb8b6e25"
|
||||
},
|
||||
{
|
||||
"path": "test_protocol.py",
|
||||
"bytes": 10402,
|
||||
"sha256": "cbae93c993ac9aa99bcce68ae2131c7f938f8a63e89779f8d3d79e36bf268dee"
|
||||
},
|
||||
{
|
||||
"path": "tests/__init__.py",
|
||||
"bytes": 76,
|
||||
"sha256": "bfc0d9e17410c2c52ea1d9a5259c92964f16b118dbae8cecad258697b67e4bdc"
|
||||
},
|
||||
{
|
||||
"path": "tests/test_compare_timing.py",
|
||||
"bytes": 9226,
|
||||
"sha256": "71876b36eef88c198aa8e833af547dbe772001c71b010707b656c165c8b54031"
|
||||
},
|
||||
{
|
||||
"path": "tests/test_timing_queue.py",
|
||||
"bytes": 5825,
|
||||
"sha256": "4a375444dc732c3538df8aa4b81e43b5418754fcebebb0d02c16faad03d43195"
|
||||
},
|
||||
{
|
||||
"path": "timing_queue.py",
|
||||
"bytes": 4443,
|
||||
"sha256": "54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e"
|
||||
},
|
||||
{
|
||||
"path": "verify_frozen.py",
|
||||
"bytes": 2725,
|
||||
"sha256": "97d8a240b08baaca935f9c0278978883005dcd6fdbb4ca0d948074ef79fd349c"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,890 @@
|
||||
"""Phase 4AJ graded-APL output-timing adapter of the sealed Phase 4AG oracle.
|
||||
|
||||
Brian has no Torch dependency. A separate, pinned science-Python process
|
||||
executes the unchanged legacy float32 APL operations from Brian's own spikes.
|
||||
This is independent LIF/scheduling validation with a shared APL numerical
|
||||
component, NOT an all-components-independent CPU oracle. Only generated h
|
||||
transport is changed: h[t-D] is written at end(t), D in {0,18}. The original
|
||||
Brian equations, anatomical delay, refractory policy and queue proxy stay as
|
||||
in the sealed source. No future brain or APL generation ticks are run.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import queue
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
BLOCK_BYTES = 8 << 20
|
||||
BASE_ORACLE_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-apl-lab-v2/oracle_apl.py")
|
||||
BASE_ORACLE_SHA256 = "357a7d3debb5abc34a8714c0233b456547a154d9d5855d28661b4f01019a80ea"
|
||||
LEGACY_WORKER_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-apl-lab-v2/legacy_apl_worker.py")
|
||||
LEGACY_WORKER_SHA256 = "21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291"
|
||||
PROTOCOL = "phase4ag-apl-v1"
|
||||
AUTHOR_PATH = Path("/data/digital-drosophila/experiments/phase4ad-author-semantics/external-resources/model.py")
|
||||
AUTHOR_SHA256 = "fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e"
|
||||
AE_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-runtime-lab-v1/oracle_stress.py")
|
||||
AE_SHA256 = "32eb1c42383068ce52701d2139a376ce155ad7072cde019949f8ca2d55492409"
|
||||
AF_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-fullgraph-lab-v1/oracle_full.py")
|
||||
AF_SHA256 = "d72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961"
|
||||
EVENT_DTYPE = np.dtype([("step", "<i4"), ("neuron_idx", "<i4")])
|
||||
BUNDLE_FILES = ("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")
|
||||
_PROCESS_USED = False
|
||||
|
||||
|
||||
def _check(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def _save(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as handle:
|
||||
json.dump(value, handle, indent=2, sort_keys=True, allow_nan=False)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def _digest(path):
|
||||
result = hashlib.sha256()
|
||||
with Path(path).open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(BLOCK_BYTES), b""):
|
||||
result.update(block)
|
||||
return result.hexdigest()
|
||||
|
||||
|
||||
def _array_hash(array, dtype):
|
||||
return hashlib.sha256(np.ascontiguousarray(array, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def _load_checked(path, expected, name):
|
||||
data = Path(path).read_bytes()
|
||||
_check(hashlib.sha256(data).hexdigest() == expected, f"Pinned source hash mismatch: {path}")
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(data, str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def _python_entry(path):
|
||||
"""A venv is selected by its invocation path, not the resolved ELF target."""
|
||||
entry = Path(path).absolute()
|
||||
_check(entry.is_file(), f"Missing declared Python entry: {entry}")
|
||||
return entry
|
||||
|
||||
|
||||
def _new_memmap(path, dtype, shape):
|
||||
_check(not Path(path).exists(), f"Refusing to overwrite output: {path}")
|
||||
return np.lib.format.open_memmap(path, mode="w+", dtype=dtype, shape=shape)
|
||||
|
||||
|
||||
def _budget(root, limit, reserve=0):
|
||||
total = 0
|
||||
for directory, _, filenames in os.walk(root, followlinks=False):
|
||||
for filename in filenames:
|
||||
total += (Path(directory) / filename).stat(follow_symlinks=False).st_size
|
||||
_check(total + int(reserve) <= limit,
|
||||
f"Output budget exceeded: {total}+{reserve}>{limit}; inputs and duration were not adjusted")
|
||||
return total
|
||||
|
||||
|
||||
class _APLDelayFIFO:
|
||||
"""Independent list transport; no Brian/Torch import, arithmetic, or feedback.
|
||||
|
||||
Start with D distinct positive-zero vectors. On tick t, append a copy of
|
||||
generated h[t], then pop the oldest vector. The remaining D vectors are
|
||||
pending applications at ticks T..T+D-1 after T steps, NOT new future output.
|
||||
"""
|
||||
|
||||
def __init__(self, delay_steps):
|
||||
_check(type(delay_steps) is int and delay_steps in (0, 18),
|
||||
"apl_delay_steps must be an explicit integer 0 or 18")
|
||||
self.delay_steps = delay_steps
|
||||
self.tick = 0
|
||||
self.finished = False
|
||||
self.queue = [np.zeros(4064, dtype=np.float32) for _ in range(delay_steps)]
|
||||
|
||||
def step(self, tick, generated):
|
||||
_check(not self.finished and type(tick) is int and tick == self.tick,
|
||||
"APL FIFO ticks must be consecutive and cannot follow finish")
|
||||
value = np.asarray(generated)
|
||||
_check(value.dtype == np.dtype("float32") and value.shape == (4064,) and
|
||||
np.isfinite(value).all() and np.all(value >= 0),
|
||||
"APL FIFO requires one finite nonnegative float32 full-KC vector")
|
||||
self.queue.append(value.copy())
|
||||
applied = self.queue.pop(0)
|
||||
_check(len(self.queue) == self.delay_steps, "APL FIFO length changed")
|
||||
self.tick += 1
|
||||
return applied
|
||||
|
||||
def finish(self, steps):
|
||||
_check(not self.finished and type(steps) is int and steps == self.tick,
|
||||
"APL FIFO did not cover every requested tick")
|
||||
self.finished = True
|
||||
if not self.queue:
|
||||
return np.empty((0, 4064), dtype=np.float32)
|
||||
return np.stack(self.queue).astype(np.float32, copy=False)
|
||||
|
||||
|
||||
class _PipeReader:
|
||||
"""One bounded reader thread, with timeout/EOF checks for every frame."""
|
||||
|
||||
def __init__(self, pipe, timeout):
|
||||
self.pipe, self.timeout = pipe, float(timeout)
|
||||
self.chunks = queue.Queue(maxsize=8)
|
||||
self.buffer = bytearray()
|
||||
self.thread = threading.Thread(target=self._pump, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def _pump(self):
|
||||
try:
|
||||
while True:
|
||||
chunk = os.read(self.pipe.fileno(), 65536)
|
||||
self.chunks.put(chunk)
|
||||
if not chunk:
|
||||
return
|
||||
except BaseException as error:
|
||||
self.chunks.put(error)
|
||||
|
||||
def _fill(self, deadline):
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError("APL sidecar response timed out")
|
||||
try:
|
||||
chunk = self.chunks.get(timeout=remaining)
|
||||
except queue.Empty as error:
|
||||
raise TimeoutError("APL sidecar response timed out") from error
|
||||
if isinstance(chunk, BaseException):
|
||||
raise RuntimeError("APL sidecar stdout reader failed") from chunk
|
||||
if not chunk:
|
||||
raise EOFError("APL sidecar ended before a complete response")
|
||||
self.buffer.extend(chunk)
|
||||
|
||||
def read(self, size):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
while len(self.buffer) < size:
|
||||
self._fill(deadline)
|
||||
result = bytes(self.buffer[:size])
|
||||
del self.buffer[:size]
|
||||
return result
|
||||
|
||||
def line(self):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
while b"\n" not in self.buffer:
|
||||
_check(len(self.buffer) < 1 << 20, "APL sidecar handshake exceeds 1 MiB")
|
||||
self._fill(deadline)
|
||||
end = self.buffer.index(b"\n") + 1
|
||||
result = bytes(self.buffer[:end])
|
||||
del self.buffer[:end]
|
||||
return result
|
||||
|
||||
|
||||
class _APLWorker:
|
||||
def __init__(self, python, script, bundle_dir, stderr, timeout=180):
|
||||
self.command = [str(python), "-B", str(script), "--bundle-dir", str(bundle_dir), "--dt-ms", "0.1"]
|
||||
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=stderr, bufsize=0, shell=False)
|
||||
self.reader = _PipeReader(self.process.stdout, timeout)
|
||||
self.tick = 0
|
||||
try:
|
||||
self.hello = json.loads(self.reader.line().decode("utf-8"))
|
||||
_check(self.hello.get("ok") is True, "APL sidecar did not acknowledge initialization")
|
||||
_check(self.hello.get("protocol") == PROTOCOL, "Unexpected APL sidecar protocol")
|
||||
for key, value in (("n_neurons", 166700), ("n_apl", 2), ("n_kc", 4064)):
|
||||
_check(self.hello.get(key) == value, f"APL sidecar changed {key}")
|
||||
_check(self.hello.get("dt_ms") == .1, "APL sidecar changed dt")
|
||||
expected_decay = f"{int(np.float32(math.exp(-.1 / 20.)).view(np.uint32)):08x}"
|
||||
expected_gain = f"{int(np.float32(.01).view(np.uint32)):08x}"
|
||||
_check(self.hello.get("decay_f32_bits") == expected_decay and self.hello.get("gain_f32_bits") == expected_gain,
|
||||
"APL sidecar decay/gain differs from the frozen float32 scalars")
|
||||
_check(self.hello.get("files_sha256") == {name: _digest(bundle_dir / name) for name in BUNDLE_FILES},
|
||||
"APL sidecar bundle hashes differ from the independently read bundle")
|
||||
except BaseException:
|
||||
self.abort()
|
||||
raise
|
||||
|
||||
def step(self, tick, global_spikes):
|
||||
_check(tick == self.tick, "APL sidecar request ticks must be consecutive")
|
||||
nodes = np.asarray(global_spikes)
|
||||
_check(nodes.ndim == 1 and nodes.dtype == np.dtype("int32"), "APL input spikes must be int32 globals")
|
||||
_check(not len(nodes) or (nodes[0] >= 0 and nodes[-1] < 166700 and np.all(np.diff(nodes) > 0)),
|
||||
"APL input spikes must be sorted, unique, and in the frozen global range")
|
||||
packet = struct.pack("<qQ", int(tick), len(nodes)) + np.ascontiguousarray(nodes, dtype="<i4").tobytes()
|
||||
view = memoryview(packet)
|
||||
while view:
|
||||
written = self.process.stdin.write(view)
|
||||
if not written:
|
||||
raise BrokenPipeError("APL sidecar stdin failed")
|
||||
view = view[written:]
|
||||
self.process.stdin.flush()
|
||||
response_tick, byte_count = struct.unpack("<qQ", self.reader.read(16))
|
||||
_check(response_tick == tick and byte_count == (2 + 2 + 4064) * 4,
|
||||
"APL sidecar frame tick/length mismatch")
|
||||
values = np.frombuffer(self.reader.read(byte_count), dtype="<f4")
|
||||
_check(np.isfinite(values).all() and np.all(values >= 0), "APL sidecar returned invalid nonnegative f32 states")
|
||||
self.tick += 1
|
||||
return values[:2], values[2:4], values[4:]
|
||||
|
||||
def finish(self, steps):
|
||||
_check(self.tick == steps, "APL sidecar did not process every tick")
|
||||
self.process.stdin.close()
|
||||
status = self.process.wait(timeout=30)
|
||||
_check(status == 0, f"APL sidecar exited unsuccessfully: {status}")
|
||||
self.reader.thread.join(timeout=5)
|
||||
_check(not self.reader.thread.is_alive(), "APL stdout did not reach EOF")
|
||||
extra = bytes(self.reader.buffer)
|
||||
while not self.reader.chunks.empty():
|
||||
chunk = self.reader.chunks.get_nowait()
|
||||
_check(not isinstance(chunk, BaseException), "APL stdout reader failed after the last frame")
|
||||
extra += chunk
|
||||
_check(not extra, "APL sidecar emitted unrequested trailing stdout bytes")
|
||||
self.process.stdout.close()
|
||||
return status
|
||||
|
||||
def abort(self):
|
||||
if self.process.poll() is None:
|
||||
self.process.terminate()
|
||||
try:
|
||||
self.process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.process.kill()
|
||||
self.process.wait(timeout=5)
|
||||
for handle in (self.process.stdin, self.process.stdout):
|
||||
if handle is not None and not handle.closed:
|
||||
handle.close()
|
||||
|
||||
|
||||
def _graph_inputs(directory):
|
||||
arrays = {}
|
||||
definitions = {"nodes": "int32", "edge_id": "int64", "src": "int32",
|
||||
"dst": "int32", "signed_raw": "int64", "ptr": "int64", "weights": "float64"}
|
||||
for name, dtype in definitions.items():
|
||||
value = np.load(directory / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
_check(value.ndim == 1 and value.dtype == np.dtype(dtype), f"Invalid graph array {name}")
|
||||
arrays[name] = value
|
||||
nodes, edge_ids = arrays["nodes"], arrays["edge_id"]
|
||||
n, edges = len(nodes), len(edge_ids)
|
||||
_check(0 < n <= 166700 and edges > 0, "A case needs real global nodes and explicit nonempty connectivity")
|
||||
_check(nodes[0] >= 0 and nodes[-1] < 166700 and np.all(np.diff(nodes) > 0), "Invalid global node order")
|
||||
for name in ("src", "dst", "signed_raw", "weights"):
|
||||
_check(arrays[name].shape == (edges,), f"Graph array {name} has the wrong shape")
|
||||
pointer = arrays["ptr"]
|
||||
_check(pointer.shape == (n + 1,) and pointer[0] == 0 and pointer[-1] == edges and
|
||||
np.all(np.diff(pointer) >= 0), "Invalid graph CSR pointer")
|
||||
source_counts = np.zeros(n, np.int64)
|
||||
identity_edges = True
|
||||
previous_source, previous_edge = -1, -1
|
||||
width = BLOCK_BYTES // 8
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
source, target, ids = arrays["src"][begin:stop], arrays["dst"][begin:stop], edge_ids[begin:stop]
|
||||
_check(np.all((source >= 0) & (source < n)) and np.all((target >= 0) & (target < n)),
|
||||
"Graph edge endpoint is outside the local node array")
|
||||
_check(np.all(ids >= 0), "Graph edge IDs must be nonnegative")
|
||||
_check(source[0] > previous_source or (source[0] == previous_source and ids[0] > previous_edge),
|
||||
"Graph edge blocks are not in canonical source/edge order")
|
||||
_check(np.all((source[1:] > source[:-1]) |
|
||||
((source[1:] == source[:-1]) & (ids[1:] > ids[:-1]))),
|
||||
"Graph edges are not in canonical source/edge order")
|
||||
previous_source, previous_edge = int(source[-1]), int(ids[-1])
|
||||
identity_edges &= np.array_equal(ids, np.arange(begin, stop, dtype=np.int64))
|
||||
source_counts += np.bincount(source, minlength=n)
|
||||
_check(np.isfinite(arrays["weights"][begin:stop]).all(), "Nonfinite graph weights")
|
||||
_check(np.array_equal(source_counts, np.diff(pointer)), "CSR pointer and source rows disagree")
|
||||
if not identity_edges:
|
||||
_check(len(np.unique(edge_ids)) == edges, "Original edge IDs must be unique")
|
||||
for name in ("completeness.csv", "connectivity.parquet"):
|
||||
_check((directory / name).is_file(), f"Missing unchanged author input: {name}")
|
||||
if n == 166700:
|
||||
_check(edges == 25582938 and identity_edges and np.array_equal(nodes, np.arange(n, dtype=np.int32)),
|
||||
"A full probe must use the unchanged complete canonical graph")
|
||||
return arrays, identity_edges
|
||||
|
||||
|
||||
def _requests(case_dir, nodes, steps):
|
||||
with np.load(case_dir / "requests.npz", allow_pickle=False) as archive:
|
||||
_check(set(archive.files) == {"tick", "neuron_idx"}, "Unexpected sparse request schema")
|
||||
ticks, requested = archive["tick"].copy(), archive["neuron_idx"].copy()
|
||||
_check(ticks.dtype == requested.dtype == np.dtype("int32") and ticks.ndim == requested.ndim == 1 and
|
||||
ticks.shape == requested.shape, "Requests must be equal-length int32 vectors")
|
||||
_check(np.all((ticks >= 0) & (ticks < steps)), "Request time outside the case")
|
||||
_check(not len(ticks) or np.all((ticks[1:] > ticks[:-1]) |
|
||||
((ticks[1:] == ticks[:-1]) & (requested[1:] > requested[:-1]))),
|
||||
"Requests must be sorted and unique by tick/global node")
|
||||
local = np.searchsorted(nodes, requested)
|
||||
_check(np.all(local < len(nodes)) and np.array_equal(nodes[local], requested),
|
||||
"Request references a global neuron absent from this case")
|
||||
counts = np.bincount(ticks, minlength=steps).astype(np.int32)
|
||||
pointer = np.concatenate((np.zeros(1, np.int64), np.cumsum(counts, dtype=np.int64)))
|
||||
return requested, local.astype(np.int32), pointer, counts
|
||||
|
||||
|
||||
class _QueueProxy:
|
||||
def __init__(self, original, get_tick, available, arrays, identity_edges,
|
||||
due_counts, spike_ring, delay_steps, handle, records, summarize):
|
||||
self._original_queue, self._get_tick = original, get_tick
|
||||
self._available, self._arrays, self._identity_edges = available, arrays, identity_edges
|
||||
self._due_counts, self._spike_ring, self._delay_steps = due_counts, spike_ring, delay_steps
|
||||
self._handle, self._records, self._summarize = handle, records, summarize
|
||||
|
||||
def peek(self, *args, **kwargs):
|
||||
original = self._original_queue.peek(*args, **kwargs)
|
||||
local_edges = np.asarray(original)
|
||||
tick = self._get_tick()
|
||||
_check(tick == len(self._records["rows"]), "Queue peek ticks must be consecutive")
|
||||
_check(local_edges.ndim == 1 and np.all((local_edges >= 0) & (local_edges < len(self._arrays["edge_id"]))),
|
||||
"Original queue contains an invalid local edge index")
|
||||
_check(len(local_edges) == self._due_counts[tick % len(self._due_counts)],
|
||||
"Queue arrivals differ from the fixed-delay outgoing degree of actual spikes")
|
||||
fired_then = np.zeros(len(self._arrays["nodes"]), dtype=bool)
|
||||
if tick >= self._delay_steps:
|
||||
fired_then[self._spike_ring[(tick - self._delay_steps) % len(self._spike_ring)]] = True
|
||||
|
||||
def accepted(begin, stop):
|
||||
part = local_edges[begin:stop]
|
||||
_check(np.all(fired_then[self._arrays["src"][part]]), "Arrival lacks a presynaptic spike at author delay")
|
||||
return self._available[self._arrays["dst"][part]]
|
||||
|
||||
# Only the observed ID view changes; the queue is returned verbatim.
|
||||
edge_ids = local_edges if self._identity_edges else self._arrays["edge_id"][local_edges]
|
||||
summary = self._summarize(tick, edge_ids, accepted)
|
||||
self._handle.write(json.dumps(summary, sort_keys=True) + "\n")
|
||||
self._records["rows"].append(summary)
|
||||
self._records["peek_calls"] += 1
|
||||
return original
|
||||
|
||||
def advance(self, *args, **kwargs):
|
||||
self._records["advance_calls"] += 1
|
||||
return self._original_queue.advance(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._original_queue, name)
|
||||
|
||||
|
||||
def run_case(case_dir: Path, out_dir: Path) -> dict:
|
||||
"""Run one continuous case, with exclusive outputs and no candidate trace input.
|
||||
|
||||
Graph inputs are the AF array/author-table schema plus original weights.npy.
|
||||
requests.npz contains sparse tick/neuron_idx int32 vectors. Optional
|
||||
arrays.npz contains initial_v_mv/initial_g_mv float64 vectors only.
|
||||
Full probes write state hashes, sparse events and APL traces, not dense
|
||||
whole-brain histories. Each case requires a fresh process.
|
||||
"""
|
||||
global _PROCESS_USED
|
||||
_check(not _PROCESS_USED, "Every oracle case requires a fresh process")
|
||||
_PROCESS_USED = True
|
||||
case_dir, out_dir = Path(case_dir).resolve(strict=True), Path(out_dir).resolve()
|
||||
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
|
||||
graph_dir = Path(spec["graph_dir"]).resolve(strict=True)
|
||||
bundle_dir = Path(spec["apl_bundle_dir"]).resolve(strict=True)
|
||||
worker_path = Path(spec["legacy_apl_worker"]).resolve(strict=True)
|
||||
_check(worker_path == LEGACY_WORKER_PATH.resolve(strict=True),
|
||||
"The APL worker must remain the sealed Phase4AG-v2 worker")
|
||||
_check(_digest(worker_path) == LEGACY_WORKER_SHA256, "Frozen Phase4AG-v2 APL worker changed")
|
||||
_check(_digest(BASE_ORACLE_PATH) == BASE_ORACLE_SHA256, "Sealed oracle source baseline changed")
|
||||
science_python = _python_entry(spec["science_python"])
|
||||
author_path = Path(spec.get("author_path", AUTHOR_PATH)).resolve(strict=True)
|
||||
ae_path = Path(spec.get("ae_oracle_path", AE_PATH)).resolve(strict=True)
|
||||
af_path = Path(spec.get("af_oracle_path", AF_PATH)).resolve(strict=True)
|
||||
coefficients_path = Path(spec["coefficients_path"]).resolve(strict=True)
|
||||
steps = int(spec["steps"])
|
||||
apl_delay_steps = spec["apl_delay_steps"]
|
||||
_check(type(apl_delay_steps) is int and apl_delay_steps in (0, 18),
|
||||
"apl_delay_steps must be an explicit integer 0 or 18")
|
||||
_check(steps == spec["steps"] and 0 < steps <= np.iinfo(np.int32).max, "Invalid number of steps")
|
||||
_check(float(spec["dt_ms"]) == .1 and np.isclose(float(spec["duration_ms"]), steps * .1, rtol=0, atol=1e-12),
|
||||
"The frozen APL protocol requires dt_ms=0.1 and a matching duration")
|
||||
_check(spec.get("precision", "float64") == "float64", "Author state must be float64")
|
||||
_check(spec.get("training", False) is False and spec.get("poi_used", False) is False,
|
||||
"Training and original poi are outside this gate")
|
||||
_check(spec["apl"] == {"tau_ms": 20, "gain": .01, "normalization": 1}, "Frozen APL parameters changed")
|
||||
record_history = spec["record_history"]
|
||||
_check(isinstance(record_history, bool), "record_history must be a boolean")
|
||||
budget_root = Path(spec.get("budget_root", out_dir.parent)).resolve()
|
||||
budget_limit = int(spec.get("output_budget_bytes", 15 * 1024 ** 3))
|
||||
_check(budget_limit > 0 and out_dir.is_relative_to(budget_root), "Invalid output budget/root")
|
||||
out_dir.mkdir(parents=True, exist_ok=False)
|
||||
mapped_files = []
|
||||
saved_preferences, old_dt, worker = None, None, None
|
||||
last_complete_tick = -1
|
||||
try:
|
||||
arrays, identity_edges = _graph_inputs(graph_dir)
|
||||
nodes, n, edges = arrays["nodes"], len(arrays["nodes"]), len(arrays["edge_id"])
|
||||
_check(n < 166700 or not record_history, "Full probes must not save dense V/g/ref histories")
|
||||
bundle = {name: np.load(bundle_dir / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
for name in ("input_matrix", "output_matrix", "kc_globals", "apl_globals")}
|
||||
for name, shape in (("input_matrix", (2, 166700)), ("output_matrix", (2, 4064))):
|
||||
value = bundle[name]
|
||||
_check(value.dtype == np.dtype("float32") and value.shape == shape and value.flags.c_contiguous,
|
||||
f"Frozen APL {name} shape/dtype/layout changed")
|
||||
_check(np.isfinite(value).all() and np.all(value >= 0), f"Invalid positive raw-normalized APL {name}")
|
||||
for name, count in (("kc_globals", 4064), ("apl_globals", 2)):
|
||||
value = bundle[name]
|
||||
_check(value.dtype == np.dtype("int32") and value.shape == (count,) and value[0] >= 0 and
|
||||
value[-1] < 166700 and np.all(np.diff(value) > 0), f"Frozen APL identity {name} changed")
|
||||
_check(not np.intersect1d(bundle["kc_globals"], bundle["apl_globals"]).size, "APL and KC identities overlap")
|
||||
kc_positions = np.searchsorted(nodes, bundle["kc_globals"])
|
||||
present = kc_positions < n
|
||||
present[present] &= nodes[kc_positions[present]] == bundle["kc_globals"][present]
|
||||
kc_local, kc_in_bundle = kc_positions[present], np.flatnonzero(present)
|
||||
requested_globals, request_local, request_ptr, request_count = _requests(case_dir, nodes, steps)
|
||||
initial = None
|
||||
if (case_dir / "arrays.npz").exists():
|
||||
with np.load(case_dir / "arrays.npz", allow_pickle=False) as archive:
|
||||
_check(set(archive.files) == {"initial_v_mv", "initial_g_mv"}, "Unexpected initial-state schema")
|
||||
initial = {name: archive[name].copy() for name in archive.files}
|
||||
for name, value in initial.items():
|
||||
_check(value.dtype == np.dtype("float64") and value.shape == (n,) and np.isfinite(value).all(),
|
||||
f"Invalid diagnostic initial state: {name}")
|
||||
history_bytes = steps * n * 40 if record_history else 0
|
||||
_budget(budget_root, budget_limit, history_bytes + steps * (2 + 2 + 4064 + 4064) * 4 +
|
||||
apl_delay_steps * 4064 * 4 + edges * 8 + (64 << 20))
|
||||
versions = {name: importlib.metadata.version(name) for name in
|
||||
("brian2", "numpy", "pandas", "pyarrow", "sympy", "scipy", "cython")}
|
||||
_save(out_dir / "environment.json", {"python": sys.version, "platform": platform.platform(),
|
||||
"packages": versions, "codegen": "numpy", "author_precision": "float64",
|
||||
"APL_precision": "float32 in separate science-Python process", "Torch_imported_into_Brian": False})
|
||||
for name, required in (("brian2", "2.5.1"), ("numpy", "1.22.3"), ("sympy", "1.12")):
|
||||
_check(versions[name] == required, f"{name} must remain pinned to {required}")
|
||||
import brian2 as b2
|
||||
import pandas as pd
|
||||
from brian2.core.base import BrianObject
|
||||
|
||||
ae = _load_checked(ae_path, AE_SHA256, "phase4ag_sealed_ae_helpers")
|
||||
af = _load_checked(af_path, AF_SHA256, "phase4ag_sealed_af_helpers")
|
||||
author = _load_checked(author_path, AUTHOR_SHA256, "phase4ag_unchanged_author")
|
||||
_check("torch" not in sys.modules, "Torch must not be imported into the pinned Brian process")
|
||||
comp = pd.read_csv(graph_dir / "completeness.csv", index_col=0)
|
||||
_check(np.array_equal(comp.index.to_numpy(), nodes) and
|
||||
np.array_equal(comp["local_index"].to_numpy(), np.arange(n)), "Author CSV neuron identity differs")
|
||||
del comp
|
||||
graph_hashes = {name: _digest(graph_dir / name) for name in
|
||||
tuple(key + ".npy" for key in arrays) + ("completeness.csv", "connectivity.parquet")}
|
||||
bundle_hashes = {name: _digest(bundle_dir / name) for name in BUNDLE_FILES}
|
||||
case_hashes = {name: _digest(case_dir / name) for name in ("spec.json", "requests.npz")}
|
||||
if initial is not None:
|
||||
case_hashes["arrays.npz"] = _digest(case_dir / "arrays.npz")
|
||||
worker_hash = _digest(worker_path)
|
||||
coefficient_hash = _digest(coefficients_path)
|
||||
baseline_coefficients = json.loads(coefficients_path.read_text(encoding="utf-8"))
|
||||
if "legacy_apl_worker_sha256" in spec:
|
||||
_check(worker_hash == spec["legacy_apl_worker_sha256"], "APL worker differs from preregistered source")
|
||||
_save(out_dir / "input-audit.json", {"spec": spec, "case_hashes": case_hashes,
|
||||
"graph_hashes": graph_hashes, "bundle_hashes": bundle_hashes,
|
||||
"author_sha256": AUTHOR_SHA256, "ae_sha256": AE_SHA256, "af_sha256": AF_SHA256,
|
||||
"legacy_apl_worker_sha256": worker_hash, "coefficients_sha256": coefficient_hash,
|
||||
"copied_oracle_source": str(BASE_ORACLE_PATH), "copied_oracle_sha256": BASE_ORACLE_SHA256,
|
||||
"apl_delay_steps": apl_delay_steps, "only_scientific_change": "transport generated graded-APL h before the unchanged end write",
|
||||
"candidate_trace_used_as_input": False,
|
||||
"scope": "shared-APL-component closed-loop oracle, not all-components-independent CPU equivalence"})
|
||||
saved_preferences = {key: b2.prefs[key] for key in
|
||||
("codegen.target", "core.default_float_dtype", "legacy.refractory_timing")}
|
||||
old_dt = b2.defaultclock.dt
|
||||
b2.start_scope()
|
||||
b2.prefs["codegen.target"] = "numpy"
|
||||
b2.prefs["core.default_float_dtype"] = np.float64
|
||||
b2.prefs["legacy.refractory_timing"] = False
|
||||
b2.defaultclock.dt = .1 * b2.ms
|
||||
parameters = dict(author.default_params)
|
||||
_save(out_dir / "parameter-checks.json", ae._parameters_match(parameters, spec["model"], b2))
|
||||
neu, syn, spikes = author.create_model(graph_dir / "completeness.csv", graph_dir / "connectivity.parquet", parameters)
|
||||
_check(len(neu) == n and len(syn) == edges, "Author loaded an unexpected graph size")
|
||||
_check(all(np.dtype(neu.variables[key].dtype) == np.float64 for key in ("v", "g")) and
|
||||
np.dtype(syn.variables["w"].dtype) == np.float64, "Author states/weights are not float64")
|
||||
effective = _new_memmap(out_dir / "weights.npy", "<f8", (edges,))
|
||||
mapped_files.append(effective)
|
||||
width, w_unit, mask_count = BLOCK_BYTES // 8, float(parameters["w_syn"] / b2.volt), 0
|
||||
mask_hasher = hashlib.sha256()
|
||||
base_hasher = hashlib.sha256()
|
||||
effective_hasher = hashlib.sha256()
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
np.testing.assert_array_equal(np.asarray(syn.i[begin:stop]), arrays["src"][begin:stop])
|
||||
np.testing.assert_array_equal(np.asarray(syn.j[begin:stop]), arrays["dst"][begin:stop])
|
||||
original = np.asarray(syn.w_[begin:stop], dtype=np.float64).copy()
|
||||
raw_expected = arrays["signed_raw"][begin:stop] * w_unit
|
||||
_check(np.array_equal(original.view(np.uint64), raw_expected.view(np.uint64)) and
|
||||
np.array_equal(original.view(np.uint64), arrays["weights"][begin:stop].view(np.uint64)),
|
||||
"Actual original Brian weights differ from unchanged signed raw or AF base weights")
|
||||
base_hasher.update(np.ascontiguousarray(original, dtype="<f8").tobytes())
|
||||
mask = np.isin(nodes[arrays["src"][begin:stop]], bundle["apl_globals"])
|
||||
mask_count += int(mask.sum())
|
||||
mask_hasher.update(np.ascontiguousarray(arrays["edge_id"][begin:stop][mask], dtype="<i8").tobytes())
|
||||
original[mask] = np.float64(0.0)
|
||||
effective_hasher.update(np.ascontiguousarray(original, dtype="<f8").tobytes())
|
||||
effective[begin:stop] = original
|
||||
syn.w_[begin:stop] = original
|
||||
if n == 166700:
|
||||
_check(mask_count == 5845, "Full legacy APL fast-edge mask must contain exactly 5845 existing edges")
|
||||
effective.flush()
|
||||
_save(out_dir / "apl-mask-audit.json", {"count": mask_count, "ordered_original_edge_id_sha256": mask_hasher.hexdigest(),
|
||||
"base_weight_raw_bytes_sha256": base_hasher.hexdigest(), "effective_weights_file_sha256": _digest(out_dir / "weights.npy"),
|
||||
"effective_weights_raw_sha256": effective_hasher.hexdigest(),
|
||||
"rule": "zero +0 all existing fast edges whose source GLOBAL neuron is in apl_globals; no graph edge removal",
|
||||
"graded_targets": "only KC identities present in this LIF case", "KC_targets_in_case": len(kc_local),
|
||||
"base_graph_files_modified": False, "fullgraph_required_mask_count": 5845})
|
||||
if initial is not None:
|
||||
neu.v = initial["initial_v_mv"] * b2.mV
|
||||
neu.g = initial["initial_g_mv"] * b2.mV
|
||||
v_raw = neu.variables["v"].get_value()
|
||||
g_raw = neu.variables["g"].get_value()
|
||||
last_raw = neu.variables["lastspike"].get_value()
|
||||
available_raw = neu.variables["not_refractory"].get_value()
|
||||
histories = {}
|
||||
pre_apl_histories = {}
|
||||
if record_history:
|
||||
for name, dtype in (("v", "float64"), ("g", "float64"), ("ref", "int32")):
|
||||
histories[name] = _new_memmap(out_dir / f"{name}-history.{dtype}.npy", np.dtype(dtype), (steps, n))
|
||||
mapped_files.append(histories[name])
|
||||
pre_apl_histories[name] = _new_memmap(out_dir / f"preAPL-{name}.{dtype}.npy", np.dtype(dtype), (steps, n))
|
||||
mapped_files.append(pre_apl_histories[name])
|
||||
apl_history = {}
|
||||
for name, count in (("drive", 2), ("state", 2), ("inhibition", 4064)):
|
||||
apl_history[name] = _new_memmap(out_dir / f"apl-{name}.float32.npy", "<f4", (steps, count))
|
||||
mapped_files.append(apl_history[name])
|
||||
applied_history = _new_memmap(out_dir / "apl-applied.float32.npy", "<f4", (steps, 4064))
|
||||
mapped_files.append(applied_history)
|
||||
apl_fifo = _APLDelayFIFO(apl_delay_steps)
|
||||
_save(out_dir / "output-schema.json", {"history_recorded": record_history, "history_shape": [steps, n],
|
||||
"state_hash_file": "tick-state-hashes.jsonl", "state_hash_encoding": "SHA256 of contiguous little-endian raw f64 V/g and i32 ref",
|
||||
"APL_dtype": "float32", "APL_h_units": "mV; gain already applied in float32",
|
||||
"APL_SI_conversion": "h.astype(float64) * float64(0.001), then separate float64 g subtraction",
|
||||
"APL_write_schedule": "end, order=0, after original reset, unconditional raw g write; recorder end/order=1",
|
||||
"APL_input": "Brian's own actual global spikes, never candidate spikes or candidate inhibition",
|
||||
"APL_shapes": {"drive": [steps, 2], "state": [steps, 2], "inhibition": [steps, 4064],
|
||||
"applied": [steps, 4064], "pending_tail": [apl_delay_steps, 4064]},
|
||||
"apl_delay_steps": apl_delay_steps,
|
||||
"APL_transport": "applied[t]=generated_h[t-D] for t>=D, positive zero otherwise; separate independent list FIFO",
|
||||
"APL_inhibition_file_means": "generated h at the actual closed-loop Brian spike tick, not the delayed applied value",
|
||||
"APL_pending_file": "apl-pending-tail.float32.npy",
|
||||
"APL_pending_scope": "next D scheduled applications from recorded h plus unconsumed initial zero slots; not future generated decay",
|
||||
"APL_pending_initial_zero_prefix": max(apl_delay_steps - steps, 0),
|
||||
"APL_pending_generated_count": min(steps, apl_delay_steps),
|
||||
"request_columns": ["tick", "neuron_idx", "attempted", "accepted", "rejected"],
|
||||
"queue_protocol": "sealed Phase4AF queue_summary; original global edge IDs, including zero-weight/ref-rejected arrivals",
|
||||
"state_npz": ["step", "neuron_idx", "counts", "v", "g", "ref"],
|
||||
"complete_marker": "summary.json complete=true only after worker exit and all checks"})
|
||||
delay_steps = int(round(float(parameters["t_dly"] / b2.ms) / .1))
|
||||
ref_steps = int(round(float(parameters["t_rfc"] / b2.ms) / .1))
|
||||
_check(delay_steps == 18 and ref_steps == 22, "Author delay/refractory changed")
|
||||
queue_len = delay_steps + 1
|
||||
due_counts = np.zeros(queue_len, np.int64)
|
||||
spike_ring = [np.empty(0, np.int32) for _ in range(queue_len)]
|
||||
outdegree = np.diff(arrays["ptr"])
|
||||
request_seen = np.zeros(steps, np.uint8)
|
||||
apl_seen = np.zeros(steps, np.uint8)
|
||||
applicable_count = np.zeros(steps, np.int32)
|
||||
rejected_count = np.zeros(steps, np.int32)
|
||||
spike_count = np.zeros(steps, np.int32)
|
||||
total_counts = np.zeros(n, np.int32)
|
||||
records = {"rows": [], "peek_calls": 0, "advance_calls": 0, "installations": []}
|
||||
last_ticks = np.zeros(n, np.int64)
|
||||
final_ref = np.zeros(n, np.int32)
|
||||
permitted = np.empty(0, np.int32)
|
||||
total_events = 0
|
||||
|
||||
def tick_now():
|
||||
return int(round(float(neu.clock.t / b2.second) / .0001))
|
||||
|
||||
def current_ref(tick):
|
||||
seen = np.isfinite(last_raw) & (last_raw >= 0)
|
||||
last_ticks.fill(0)
|
||||
last_ticks[seen] = np.rint(last_raw[seen] / .0001).astype(np.int64)
|
||||
result = np.where(seen, np.maximum(ref_steps - (tick - last_ticks), 0), 0).astype(np.int32)
|
||||
_check(np.array_equal(result == 0, available_raw), "Ref history disagrees with author not_refractory")
|
||||
return result
|
||||
|
||||
with ExitStack() as stack:
|
||||
stderr = stack.enter_context((out_dir / "apl-worker.stderr.log").open("xb"))
|
||||
worker = _APLWorker(science_python, worker_path, bundle_dir, stderr, float(spec.get("worker_timeout_seconds", 180)))
|
||||
_save(out_dir / "apl-worker-handshake.json", {"command": worker.command, "handshake": worker.hello})
|
||||
queue_file = stack.enter_context((out_dir / "queue.jsonl").open("x", encoding="utf-8"))
|
||||
event_file = stack.enter_context((out_dir / "events.bin").open("xb"))
|
||||
hash_file = stack.enter_context((out_dir / "tick-state-hashes.jsonl").open("x", encoding="utf-8"))
|
||||
tick_file = stack.enter_context((out_dir / "ticks.jsonl").open("x", encoding="utf-8"))
|
||||
requests_file = stack.enter_context((out_dir / "requests.csv").open("x", encoding="utf-8", newline=""))
|
||||
request_writer = csv.DictWriter(requests_file, fieldnames=["tick", "neuron_idx", "attempted", "accepted", "rejected"])
|
||||
request_writer.writeheader()
|
||||
|
||||
def request_operation():
|
||||
nonlocal permitted
|
||||
tick = tick_now()
|
||||
_check(0 <= tick < steps and not request_seen[tick], "Invalid/duplicate common request tick")
|
||||
request_seen[tick] = 1
|
||||
begin, stop = request_ptr[tick:tick + 2]
|
||||
local = request_local[begin:stop]
|
||||
available = available_raw[local].copy()
|
||||
permitted = local[available]
|
||||
applicable_count[tick] = len(permitted)
|
||||
rejected_count[tick] = len(local) - len(permitted)
|
||||
for node, accepted in zip(requested_globals[begin:stop], available):
|
||||
request_writer.writerow({"tick": tick, "neuron_idx": int(node), "attempted": 1,
|
||||
"accepted": int(accepted), "rejected": int(not accepted)})
|
||||
if len(permitted):
|
||||
neu.v[permitted] = parameters["v_th"] + 1 * b2.mV
|
||||
|
||||
def apl_operation():
|
||||
tick = tick_now()
|
||||
_check(0 <= tick < steps and not apl_seen[tick] and request_seen[tick], "Invalid/duplicate APL tick")
|
||||
active = np.asarray(neu.spikes, dtype=np.int32)
|
||||
_check(not len(active) or np.all(np.diff(active) > 0), "Brian actual spikes are not canonical/unique")
|
||||
_check(np.all(g_raw[active] == 0.0), "APL ran before the original author g reset")
|
||||
if record_history:
|
||||
pre_apl_histories["v"][tick] = v_raw
|
||||
pre_apl_histories["g"][tick] = g_raw
|
||||
pre_apl_histories["ref"][tick] = current_ref(tick)
|
||||
drive, state, inhibition = worker.step(tick, np.asarray(nodes[active], dtype=np.int32))
|
||||
apl_history["drive"][tick] = drive
|
||||
apl_history["state"][tick] = state
|
||||
apl_history["inhibition"][tick] = inhibition
|
||||
applied = apl_fifo.step(tick, inhibition)
|
||||
applied_history[tick] = applied
|
||||
# These are separate array operations. Only h transport is new.
|
||||
# Do not fuse conversion/subtraction or add a refractory guard.
|
||||
delta_si = applied[kc_in_bundle].astype(np.float64) * np.float64(.001)
|
||||
g_raw[kc_local] = g_raw[kc_local] - delta_si
|
||||
apl_seen[tick] = 1
|
||||
|
||||
def record_end():
|
||||
nonlocal last_complete_tick, total_events, final_ref
|
||||
tick = tick_now()
|
||||
_check(tick == last_complete_tick + 1 and tick < steps and apl_seen[tick], "End tick is incomplete/out of order")
|
||||
_check(len(records["rows"]) == tick + 1, "End state lacks queue observation")
|
||||
_check(np.isfinite(v_raw).all() and np.isfinite(g_raw).all(), "Nonfinite author/APL state")
|
||||
final_ref = current_ref(tick)
|
||||
if record_history:
|
||||
histories["v"][tick], histories["g"][tick], histories["ref"][tick] = v_raw, g_raw, final_ref
|
||||
active = np.asarray(neu.spikes, dtype=np.int32)
|
||||
_check(np.isin(permitted, active).all(), "An applicable request did not produce a real spike")
|
||||
spike_count[tick] = len(active)
|
||||
total_counts[active] += 1
|
||||
if len(active):
|
||||
events = np.empty(len(active), EVENT_DTYPE)
|
||||
events["step"], events["neuron_idx"] = tick, nodes[active]
|
||||
event_file.write(memoryview(events).cast("B"))
|
||||
total_events += len(active)
|
||||
spike_ring[tick % queue_len] = active.copy()
|
||||
due_counts[(tick + delay_steps) % queue_len] = int(outdegree[active].sum(dtype=np.int64))
|
||||
hash_file.write(json.dumps({"tick": tick, "v_sha256": _array_hash(v_raw, "<f8"),
|
||||
"g_sha256": _array_hash(g_raw, "<f8"), "ref_sha256": _array_hash(final_ref, "<i4")}, sort_keys=True) + "\n")
|
||||
tick_file.write(json.dumps({"tick": tick, "spikes": len(active), "requests": int(request_count[tick]),
|
||||
"applicable_requests": int(applicable_count[tick]), "ref_rejected_requests": int(rejected_count[tick]),
|
||||
"event_sha256": _array_hash(nodes[active], "<i4"),
|
||||
"due_arrivals": records["rows"][tick]["count"], "accepted_arrivals": records["rows"][tick]["accepted_count"],
|
||||
"APL_drive_sha256": _array_hash(apl_history["drive"][tick], "<f4"),
|
||||
"APL_state_sha256": _array_hash(apl_history["state"][tick], "<f4"),
|
||||
"APL_inhibition_sha256": _array_hash(apl_history["inhibition"][tick], "<f4"),
|
||||
"APL_applied_sha256": _array_hash(applied_history[tick], "<f4"),
|
||||
"APL_applied_generated_tick": tick - apl_delay_steps if tick >= apl_delay_steps else None}, sort_keys=True) + "\n")
|
||||
_budget(budget_root, budget_limit, reserve=1 << 20)
|
||||
last_complete_tick = tick
|
||||
if (tick + 1) % 100 == 0 or tick + 1 == steps:
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
for handle in (queue_file, event_file, hash_file, tick_file, requests_file):
|
||||
handle.flush()
|
||||
print(json.dumps({"oracle": spec["name"], "completed_ticks": tick + 1,
|
||||
"spikes": total_events, "APL_worker_ticks": worker.tick}), flush=True)
|
||||
|
||||
class QueueInstaller(BrianObject):
|
||||
def __init__(self):
|
||||
super().__init__(clock=neu.clock, when="end", order=1_000_000, name="zz_apl_queue_installer")
|
||||
|
||||
def before_run(self, run_namespace):
|
||||
installed = []
|
||||
for code in syn.pre.code_objects:
|
||||
if "_queue" not in code.namespace or "_queue.peek()" not in str(code.code):
|
||||
continue
|
||||
original = code.namespace["_queue"]
|
||||
if isinstance(original, _QueueProxy):
|
||||
original = original._original_queue
|
||||
code.namespace["_queue"] = _QueueProxy(original, tick_now, available_raw, arrays, identity_edges,
|
||||
due_counts, spike_ring, delay_steps, queue_file, records, af.queue_summary)
|
||||
installed.append(code.name)
|
||||
_check(len(installed) == 1, "Expected one original anatomical queue consumer")
|
||||
records["installations"].append(installed[0])
|
||||
|
||||
objects = [neu, syn, spikes,
|
||||
b2.NetworkOperation(request_operation, clock=neu.clock, when="before_thresholds", order=0, name="oracle_common_requests"),
|
||||
b2.NetworkOperation(apl_operation, clock=neu.clock, when="end", order=0, name="oracle_legacy_apl_unconditional"),
|
||||
b2.NetworkOperation(record_end, clock=neu.clock, when="end", order=1, name="oracle_after_apl_record"), QueueInstaller()]
|
||||
network = b2.Network(*objects)
|
||||
_save(out_dir / "schedule.json", {"schedule": list(network.schedule), "summary": str(network.scheduling_summary())})
|
||||
network.run(0 * b2.ms, namespace={})
|
||||
_save(out_dir / "compiled-code.json", ae._code_records(network))
|
||||
compiled_coefficients = ae._coefficients(neu, parameters, b2, .0001)
|
||||
coefficient_keys = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
_check(all(float(compiled_coefficients[key]).hex() == float(baseline_coefficients[key]).hex()
|
||||
for key in coefficient_keys), "Compiled Brian coefficients differ from sealed AF")
|
||||
_check(last_complete_tick == -1 and worker.tick == 0 and not records["rows"] and not request_seen.any(),
|
||||
"Zero-duration compilation unexpectedly advanced state")
|
||||
network.run(steps * .1 * b2.ms, namespace={})
|
||||
_save(out_dir / "generated-code.json", ae._code_records(network))
|
||||
worker_status = worker.finish(steps)
|
||||
worker = None
|
||||
|
||||
_check(last_complete_tick == steps - 1 and np.all(request_seen == 1) and np.all(apl_seen == 1), "Incomplete trial")
|
||||
pending_tail = apl_fifo.finish(steps)
|
||||
warmup = min(steps, apl_delay_steps)
|
||||
consumed = max(steps - apl_delay_steps, 0)
|
||||
initial_zero_prefix = max(apl_delay_steps - steps, 0)
|
||||
_check(np.all(applied_history[:warmup].view(np.uint32) == 0),
|
||||
"Delayed APL warmup must contain positive zeros")
|
||||
_check(np.array_equal(applied_history[warmup:].view(np.uint32),
|
||||
apl_history["inhibition"][:consumed].view(np.uint32)),
|
||||
"Applied h is not the exact delayed generated float32 sequence")
|
||||
_check(pending_tail.shape == (apl_delay_steps, 4064) and pending_tail.dtype == np.float32,
|
||||
"Pending APL tail shape/dtype changed")
|
||||
_check(np.all(pending_tail[:initial_zero_prefix].view(np.uint32) == 0),
|
||||
"Short-window pending prefix must contain unconsumed initial positive zeros")
|
||||
_check(np.array_equal(pending_tail[initial_zero_prefix:].view(np.uint32),
|
||||
apl_history["inhibition"][consumed:].view(np.uint32)),
|
||||
"Pending tail does not exactly partition the finite generated h sequence")
|
||||
with (out_dir / "apl-pending-tail.float32.npy").open("xb") as handle:
|
||||
np.save(handle, pending_tail, allow_pickle=False)
|
||||
_save(out_dir / "apl-timing-audit.json", {
|
||||
"apl_delay_steps": apl_delay_steps, "steps": steps,
|
||||
"generated_rows": steps, "applied_generated_rows": consumed,
|
||||
"pending_generated_count": min(steps, apl_delay_steps),
|
||||
"initial_zero_prefix": initial_zero_prefix,
|
||||
"pending_initial_zero_prefix": initial_zero_prefix,
|
||||
"pending_shape": [apl_delay_steps, 4064],
|
||||
"pending_application_tick_start": steps,
|
||||
"pending_application_tick_stop_exclusive": steps + apl_delay_steps,
|
||||
"pending_generated_tick_start": consumed,
|
||||
"pending_generated_tick_stop_exclusive": steps,
|
||||
"positive_zero_warmup_exact": True, "transport_shift_exact": True,
|
||||
"finite_generated_sequence_partition_exact": True,
|
||||
"pending_raw_sha256": _array_hash(pending_tail, "<f4"),
|
||||
"pending_file_sha256": _digest(out_dir / "apl-pending-tail.float32.npy"),
|
||||
"brain_steps_after_window": 0, "APL_generation_steps_after_window": 0,
|
||||
"brain_tail_advanced": False,
|
||||
"final_apl_state_float32": np.asarray(apl_history["state"][-1], dtype=np.float32).tolist(),
|
||||
"final_apl_state_u32_bits": np.asarray(apl_history["state"][-1], dtype=np.float32).view(np.uint32).tolist(),
|
||||
"final_apl_state_nonzero": bool(np.any(apl_history["state"][-1] != 0)),
|
||||
"future_generated_decay_tail_included": False,
|
||||
"future_apl_decay_or_feedback_generated": False,
|
||||
"closed_loop_branch_integrals_claimed_equal": False,
|
||||
"delay_implementation": "independent list FIFO; no candidate or timing_queue imports",
|
||||
"meaning": "Tail contains only already generated h and any remaining initial-zero padding; never a complete future closed-loop or decay tail",
|
||||
})
|
||||
_check(records["peek_calls"] == records["advance_calls"] == steps, "Incomplete queue audit")
|
||||
_check(total_events == int(spikes.num_spikes) and np.array_equal(total_counts, np.asarray(spikes.count[:])),
|
||||
"Sparse events disagree with original SpikeMonitor")
|
||||
_check(np.array_equal(request_count, applicable_count + rejected_count), "Incomplete request accounting")
|
||||
_check(np.all(np.asarray(neu.rfc[:] / b2.ms) == float(parameters["t_rfc"] / b2.ms)), "Author refractory duration changed")
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
_check(np.array_equal(np.asarray(syn.w_[begin:stop]).view(np.uint64), effective[begin:stop].view(np.uint64)),
|
||||
"Effective synaptic weights changed during the trial")
|
||||
event_values = np.memmap(out_dir / "events.bin", dtype=EVENT_DTYPE, mode="r", shape=(total_events,)) if total_events else np.empty(0, EVENT_DTYPE)
|
||||
_check((out_dir / "events.bin").stat().st_size == total_events * 8, "Sparse event byte count mismatch")
|
||||
for begin in range(0, total_events, width):
|
||||
stop = min(total_events, begin + width)
|
||||
spike_local = np.asarray(spikes.i[begin:stop], dtype=np.int32)
|
||||
np.testing.assert_array_equal(event_values["neuron_idx"][begin:stop], nodes[spike_local])
|
||||
times = np.rint(np.asarray(spikes.t[begin:stop] / b2.second) / .0001).astype(np.int32)
|
||||
np.testing.assert_array_equal(event_values["step"][begin:stop], times)
|
||||
_budget(budget_root, budget_limit, total_events * 8 + n * 24 + (8 << 20))
|
||||
with (out_dir / "state.npz").open("xb") as handle:
|
||||
np.savez_compressed(handle, step=event_values["step"], neuron_idx=event_values["neuron_idx"],
|
||||
counts=total_counts, v=v_raw, g=g_raw, ref=final_ref)
|
||||
with (out_dir / "tick-summary.npz").open("xb") as handle:
|
||||
np.savez_compressed(handle, request_count=request_count, applicable_count=applicable_count,
|
||||
rejected_count=rejected_count, spike_count=spike_count,
|
||||
arrival_count=np.asarray([row["count"] for row in records["rows"]], np.int64),
|
||||
accepted_arrival_count=np.asarray([row["accepted_count"] for row in records["rows"]], np.int64))
|
||||
coefficients = ae._coefficients(neu, parameters, b2, .0001)
|
||||
keys = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
_check(all(float(coefficients[key]).hex() == float(baseline_coefficients[key]).hex() for key in keys),
|
||||
"Actual Brian coefficients differ from sealed AF coefficients")
|
||||
_save(out_dir / "coefficients.json", coefficients)
|
||||
_save(out_dir / "queue-audit.json", {"peek_calls": records["peek_calls"], "advance_calls": records["advance_calls"],
|
||||
"installations": records["installations"], "same_peek_object_returned": True,
|
||||
"original_edge_ids_not_sorted_or_filtered": True, "zero_weight_and_refractory_rejected_edges_retained": True,
|
||||
"post_threshold_pre_synaptic_guard_observed": True, "forwarding_contract": "sealed AE/AF design",
|
||||
"new_unwrapped_repeat_performed": False, "limit": "Current wrapper was not independently rerun without the queue proxy"})
|
||||
for path, expected in ((author_path, AUTHOR_SHA256), (ae_path, AE_SHA256), (af_path, AF_SHA256),
|
||||
(worker_path, worker_hash), (BASE_ORACLE_PATH, BASE_ORACLE_SHA256)):
|
||||
_check(_digest(path) == expected, f"Source changed during the run: {path}")
|
||||
_check(_digest(coefficients_path) == coefficient_hash, "Sealed coefficient reference changed during the run")
|
||||
for directory, hashes in ((graph_dir, graph_hashes), (bundle_dir, bundle_hashes), (case_dir, case_hashes)):
|
||||
for name, expected in hashes.items():
|
||||
_check(_digest(directory / name) == expected, f"Read-only input changed during the run: {name}")
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
_check("torch" not in sys.modules, "Pinned Brian process unexpectedly imported Torch")
|
||||
output_bytes = _budget(budget_root, budget_limit, 1 << 20)
|
||||
summary = {"name": spec["name"], "complete": True, "precision": "float64", "APL_precision": "float32",
|
||||
"backend": "shared-APL-component closed-loop Brian2 oracle", "steps": steps, "completed_ticks": last_complete_tick + 1,
|
||||
"nodes": n, "edges": edges, "spikes": total_events, "requests": int(request_count.sum()),
|
||||
"applicable_requests": int(applicable_count.sum()), "ref_rejected_requests": int(rejected_count.sum()),
|
||||
"APL_fast_edges_zeroed": mask_count, "APL_worker_exit_status": worker_status, "APL_worker_ticks": steps,
|
||||
"apl_fast_edges_zeroed": mask_count, "effective_weights_raw_sha256": effective_hasher.hexdigest(),
|
||||
"kc_targets_present": len(kc_local), "apl_delay_steps": apl_delay_steps,
|
||||
"APL_transport_shift_exact": True, "APL_finite_sequence_partition_exact": True,
|
||||
"pending_generated_count": min(steps, apl_delay_steps),
|
||||
"initial_zero_prefix": max(apl_delay_steps - steps, 0),
|
||||
"pending_initial_zero_prefix": max(apl_delay_steps - steps, 0),
|
||||
"pending_tail_shape": [apl_delay_steps, 4064], "future_generated_decay_tail_included": False,
|
||||
"APL_applied_sha256": _digest(out_dir / "apl-applied.float32.npy"),
|
||||
"APL_pending_tail_sha256": _digest(out_dir / "apl-pending-tail.float32.npy"),
|
||||
"APL_timing_audit_sha256": _digest(out_dir / "apl-timing-audit.json"),
|
||||
"candidate_trace_used_as_input": False, "independent_CPU_all_components_claim": False,
|
||||
"history_recorded": record_history, "single_continuous_run": True, "no_training": True, "no_poi": True,
|
||||
"source_and_inputs_unchanged": True, "pass_all": True,
|
||||
"pass_all_scope": "Oracle construction, protocol and internal self-checks only; candidate comparison is a separate gate",
|
||||
"scope": "Only this fixed trajectory; legacy Torch APL is a shared numerical component, not an independent CPU APL implementation",
|
||||
"output_budget_bytes": budget_limit, "output_bytes_before_summary": output_bytes,
|
||||
"state_hashes_sha256": _digest(out_dir / "tick-state-hashes.jsonl"), "queue_sha256": _digest(out_dir / "queue.jsonl"),
|
||||
"events_sha256": _digest(out_dir / "events.bin"), "weights_sha256": _digest(out_dir / "weights.npy")}
|
||||
_save(out_dir / "summary.json", summary)
|
||||
return summary
|
||||
except Exception as error:
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
_save(out_dir / "error.json", {"complete": False, "last_complete_tick": last_complete_tick,
|
||||
"type": type(error).__name__, "message": str(error), "partial_files_preserved": True,
|
||||
"inputs_or_duration_adjusted": False})
|
||||
raise
|
||||
finally:
|
||||
if worker is not None:
|
||||
worker.abort()
|
||||
if saved_preferences is not None:
|
||||
for key, value in saved_preferences.items():
|
||||
b2.prefs[key] = value
|
||||
b2.defaultclock.dt = old_dt
|
||||
|
||||
|
||||
def run_oracle(case_dir: Path, out_dir: Path) -> dict:
|
||||
"""Public timing-adapter alias; preserves the sealed run_case call shape."""
|
||||
return run_case(case_dir, out_dir)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("case_dir", type=Path)
|
||||
parser.add_argument("out_dir", type=Path)
|
||||
args = parser.parse_args()
|
||||
result = run_oracle(args.case_dir, args.out_dir)
|
||||
print(json.dumps(result, sort_keys=True, allow_nan=False), flush=True)
|
||||
if not result.get("complete") or not result.get("pass_all"):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Register a two-arm timing intervention before new trajectory outcomes."""
|
||||
from datetime import datetime, timezone
|
||||
import difflib
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import numpy as np
|
||||
from context import (ROOT, MODEL, EXP, AG, AG_MODEL, AI, AI_MODEL, AH_MODEL, META,
|
||||
COEFF, CFG, digest, read, save, sources, integrity, budget)
|
||||
|
||||
|
||||
def copy_exact(src, dst):
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
with src.open('rb') as a, dst.open('xb') as b:
|
||||
shutil.copyfileobj(a, b)
|
||||
assert digest(src) == digest(dst)
|
||||
|
||||
|
||||
def main():
|
||||
assert not EXP.exists() or not any(EXP.iterdir()), 'Never reuse a populated experiment'
|
||||
assert shutil.disk_usage(EXP.parent).free > CFG['output_budget_bytes'] + 10 * 1024**3
|
||||
assert read(AI / 'audit/final-status.json')['status'] == 'PASS-CONDITIONAL-PN-APL-SOURCE-AUDIT'
|
||||
assert CFG['apl'] == read(AG_MODEL / 'config.json')['apl'] and CFG['delays_steps'] == [0, 18]
|
||||
for sub in ('protocol', 'cases', 'gpu', 'oracle', 'audit', 'logs', 'regression', 'component-audit', 'calibration', 'extension-build'):
|
||||
(EXP / sub).mkdir(parents=True, exist_ok=False)
|
||||
command = [sys.executable, '-B', '-m', 'unittest', 'discover', '-v', '-s', str(MODEL), '-p', 'test_*.py']
|
||||
tested = subprocess.run(command, cwd=MODEL, capture_output=True, text=True)
|
||||
with (EXP / 'regression/unit-tests.log').open('x') as f:
|
||||
f.write(tested.stdout + tested.stderr)
|
||||
save(EXP / 'regression/unit-tests.json', dict(command=command, exit_code=tested.returncode,
|
||||
pass_all=tested.returncode == 0, sha256=digest(EXP / 'regression/unit-tests.log')))
|
||||
assert tested.returncode == 0, 'Unit tests failed; stop before registration'
|
||||
inputs = [AI / 'manifest.json', AI / 'checksums.sha256', AI_MODEL / 'manifest.json',
|
||||
AG / 'manifest.json', AG_MODEL / 'manifest.json', META, COEFF,
|
||||
AH_MODEL / 'replay_core.py', AG_MODEL / 'legacy_apl_worker.py', AG_MODEL / 'gpu_apl.py',
|
||||
AG_MODEL / 'oracle_apl.py', AG_MODEL / 'compare.py', AG_MODEL / 'calibration.py',
|
||||
AG_MODEL / 'validate_components.py', AI / 'protocol/neuron-source-groups.parquet']
|
||||
save(EXP / 'protocol/preregistered.json', dict(timestamp_utc=datetime.now(timezone.utc).isoformat(),
|
||||
sources=sources(), config=CFG, inputs=[dict(path=str(p.relative_to(ROOT)), sha256=digest(p)) for p in inputs],
|
||||
parent_commit=subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip(),
|
||||
before_new_trajectories=True, prior_CAL_and_AI_outcomes_known=True,
|
||||
single_factor='generated graded-APL h applied at t versus t+18; nothing else',
|
||||
fixed_input_counts_and_amplitudes=True, actual_accepted_requests_may_change_due_to_closed_loop=True,
|
||||
formal_learning_claim=False, oracle_limit='Independent Brian LIF state with shared legacy Torch APL arithmetic; separate component audit',
|
||||
delay_biological_measurement=False, whole_brain_equal_total_inhibition_claim=False,
|
||||
timing_test_no_full_future_tail_claim=True, source_diffs_are_review_material_not_equivalence_proof=True,
|
||||
scope='Origin audit -> exact shift conservation -> two-arm fixtures -> fullgraph oracle probes -> complete two-arm fixed CAL -> frozen regression',
|
||||
python=sys.version, numpy=np.__version__, machine=platform.machine()))
|
||||
save(EXP / 'regression/old-integrity-before.json', integrity())
|
||||
for old, new in (('gpu_apl.py', 'gpu_timing.py'), ('oracle_apl.py', 'oracle_timing.py'), ('compare.py', 'compare_timing.py')):
|
||||
difference = ''.join(difflib.unified_diff((AG_MODEL / old).read_text().splitlines(True),
|
||||
(MODEL / new).read_text().splitlines(True), fromfile=str(AG_MODEL / old), tofile=str(MODEL / new)))
|
||||
with (EXP / 'protocol' / (new + '.diff')).open('x') as f:
|
||||
f.write(difference)
|
||||
records = read(AG / 'protocol/calibration-cases.json')
|
||||
assert len(records) == 60 and len({r['name'] for r in records}) == 60
|
||||
copy_exact(AG / 'protocol/calibration-cases.json', EXP / 'protocol/calibration-cases.json')
|
||||
copy_exact(AG / 'protocol/encoder-audit.json', EXP / 'protocol/encoder-audit.json')
|
||||
names = CFG['fixtures'] + [r['name'] for r in records]
|
||||
inventory = []
|
||||
for delay in CFG['delays_steps']:
|
||||
arm = 'delay' + str(delay)
|
||||
for sub in ('cases', 'gpu', 'oracle', 'component-audit', 'calibration'):
|
||||
(EXP / sub / arm).mkdir(exist_ok=False)
|
||||
for name in names:
|
||||
src = AG / 'cases' / name
|
||||
dst = EXP / 'cases' / arm / name
|
||||
dst.mkdir(exist_ok=False)
|
||||
spec = read(src / 'spec.json')
|
||||
old_spec = dict(spec)
|
||||
spec.update(apl_delay_steps=delay, budget_root=str(EXP), output_budget_bytes=CFG['output_budget_bytes'])
|
||||
save(dst / 'spec.json', spec)
|
||||
for key in set(old_spec) - {'budget_root', 'output_budget_bytes'}:
|
||||
assert old_spec[key] == spec[key]
|
||||
files = []
|
||||
for fname in ('requests.npz', 'arrays.npz', 'pn-drive.npz'):
|
||||
if (src / fname).exists():
|
||||
copy_exact(src / fname, dst / fname)
|
||||
files.append(dict(file=fname, sha256=digest(src / fname)))
|
||||
inventory.append(dict(arm=arm, delay_steps=delay, name=name, kind=spec['kind'],
|
||||
source_case=str(src.relative_to(ROOT)), unchanged_input_files=files))
|
||||
save(EXP / 'protocol/case-inventory.json', inventory)
|
||||
save(EXP / 'protocol/prepared-hashes.json', [dict(path=str(p.relative_to(EXP)), sha256=digest(p), bytes=p.stat().st_size)
|
||||
for sub in ('protocol', 'cases') for p in sorted((EXP / sub).rglob('*')) if p.is_file() and p.name != 'prepared-hashes.json'])
|
||||
budget()
|
||||
print('REGISTERED 2 delays, 8 fixtures + 60 CAL each; exact original inputs; no new trajectory yet', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Exclusive-output fresh-process runner; original cases, one science GPU job."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
from context import ROOT, MODEL, EXP, AG, AG_MODEL, CFG, check, budget, read, load_module
|
||||
|
||||
|
||||
def preflight():
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = CFG['gpu_uuid']
|
||||
os.environ['CUDA_HOME'] = '/usr/local/cuda'
|
||||
os.environ['PATH'] = str(ROOT / 'models/male-cns-lif/.venv/bin') + ':/usr/local/cuda/bin:' + os.environ['PATH']
|
||||
os.environ['MAX_JOBS'] = '2'
|
||||
os.environ['TORCH_CUDA_ARCH_LIST'] = '8.9'
|
||||
for port in (8188, 8189, 8190):
|
||||
with urllib.request.urlopen(f'http://192.168.10.51:{port}/queue', timeout=5) as response:
|
||||
queue = json.load(response)
|
||||
assert not queue['queue_running'] and not queue['queue_pending'], 'ComfyUI busy: defer, never evict'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('backend', choices=['gpu', 'oracle', 'component'])
|
||||
parser.add_argument('arm', choices=['delay0', 'delay18'])
|
||||
parser.add_argument('case')
|
||||
parser.add_argument('--repeat', type=int, default=0)
|
||||
args = parser.parse_args()
|
||||
assert '/' not in args.case and '..' not in args.case and 0 <= args.repeat < 3
|
||||
check(); budget()
|
||||
assert read(EXP / 'origin-audit/summary.json')['pass_all']
|
||||
assert read(EXP / 'audit/shift-gate.json')['pass_all']
|
||||
preflight()
|
||||
case = EXP / 'cases' / args.arm / args.case
|
||||
assert case.is_dir() and read(case / 'spec.json')['apl_delay_steps'] == int(args.arm[5:])
|
||||
if args.case not in CFG['fixtures']:
|
||||
assert read(EXP / 'audit/fixtures-gate.json')['pass_all'], 'Fixture gate required before fullgraph execution'
|
||||
if args.case not in CFG['calibration']['full_oracle_probes'] or args.repeat > 0:
|
||||
assert read(EXP / 'audit/probes-gate.json')['pass_all'], 'Both fullgraph probe gates required before CAL'
|
||||
out = EXP / 'gpu' / args.arm / f'{args.case}-r{args.repeat}'
|
||||
if args.backend == 'gpu':
|
||||
from gpu_timing import run_gpu
|
||||
result = run_gpu(case, out, EXP / 'extension-build')
|
||||
elif args.backend == 'oracle':
|
||||
assert args.repeat == 0
|
||||
from oracle_timing import run_case
|
||||
result = run_case(case, EXP / 'oracle' / args.arm / args.case)
|
||||
else:
|
||||
assert args.repeat == 0
|
||||
component = load_module('sealed_ag_component_aj', AG_MODEL / 'validate_components.py')
|
||||
result = component.validate(out, AG / 'apl-bundle', EXP / 'component-audit' / args.arm / (args.case + '.json'))
|
||||
budget()
|
||||
print(json.dumps(dict(backend=args.backend, arm=args.arm, case=args.case, repeat=args.repeat,
|
||||
complete=result.get('complete'), pass_all=result.get('pass_all')), allow_nan=False), flush=True)
|
||||
if not result.get('pass_all', False):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,108 @@
|
||||
"""Strict ordered numerical gates; both complete CAL arms even if representation fails."""
|
||||
import argparse
|
||||
import subprocess
|
||||
import time
|
||||
import traceback
|
||||
from context import ROOT, MODEL, EXP, AG, CFG, check, save, read, budget
|
||||
|
||||
SCIENCE = ROOT / 'models/male-cns-lif/.venv/bin/python'
|
||||
ORACLE = ROOT / 'experiments/phase4ad-author-semantics/environment/.venv/bin/python'
|
||||
|
||||
|
||||
def child(backend, arm, name, repeat=0):
|
||||
log = EXP / 'logs' / f'{backend}-{arm}-{name}-r{repeat}.log'
|
||||
command = [str(ORACLE if backend == 'oracle' else SCIENCE), '-B', str(MODEL / 'run_case.py'),
|
||||
backend, arm, name, '--repeat', str(repeat)]
|
||||
print('START', backend, arm, name, repeat, flush=True)
|
||||
started = time.perf_counter()
|
||||
with log.open('x', encoding='utf-8') as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT, timeout=1800, check=False)
|
||||
print('FINISH', backend, arm, name, repeat, 'exit', result.returncode,
|
||||
'wall', round(time.perf_counter() - started, 2), flush=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f'{backend} {arm}/{name} failed; preserve and inspect {log}')
|
||||
|
||||
|
||||
def gate_case(arm, name, repeats):
|
||||
from compare_timing import compare, compare_gpu_repeats
|
||||
from calibration import control_identity
|
||||
child('gpu', arm, name)
|
||||
for repeat in range(1, repeats):
|
||||
child('gpu', arm, name, repeat)
|
||||
child('oracle', arm, name)
|
||||
result = compare(EXP / 'cases' / arm / name, EXP / 'gpu' / arm / (name + '-r0'),
|
||||
EXP / 'oracle' / arm / name, EXP / 'audit' / f'compare-{arm}-{name}.json')
|
||||
assert result['pass_all'], f'Closed-loop exact comparison failed: {arm}/{name}'
|
||||
for repeat in range(1, repeats):
|
||||
report = compare_gpu_repeats(EXP / 'gpu' / arm / (name + '-r0'),
|
||||
EXP / 'gpu' / arm / f'{name}-r{repeat}', EXP / 'audit' / f'repeat-{arm}-{name}-r{repeat}.json')
|
||||
assert report['pass_all'], f'Exact repeat failed: {arm}/{name}/{repeat}'
|
||||
if arm == 'delay0':
|
||||
report = control_identity(EXP / 'gpu' / arm / (name + '-r0'), AG / 'gpu' / (name + '-r0'),
|
||||
EXP / 'cases' / arm / name)
|
||||
save(EXP / 'audit' / f'control-{name}.json', report)
|
||||
assert report['pass_all'], f'Zero-delay control changed relative to sealed AG: {name}'
|
||||
child('component', arm, name)
|
||||
return dict(arm=arm, case=name, pass_all=True, gpu_repeats=repeats)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('stage', choices=['fixtures', 'probes', 'calibration'])
|
||||
args = parser.parse_args()
|
||||
check(); budget()
|
||||
receipt = EXP / 'audit' / (args.stage + '-gate.json')
|
||||
assert not receipt.exists()
|
||||
assert read(EXP / 'origin-audit/summary.json')['pass_all']
|
||||
assert read(EXP / 'audit/shift-gate.json')['pass_all']
|
||||
arms = ['delay' + str(delay) for delay in CFG['delays_steps']]
|
||||
try:
|
||||
if args.stage == 'fixtures':
|
||||
rows = [gate_case(arm, name, CFG['fixture_repeats']) for arm in arms for name in CFG['fixtures']]
|
||||
save(receipt, dict(pass_all=True, cases=rows, scope='Both arms; fixed real-edge induced fixtures only'))
|
||||
elif args.stage == 'probes':
|
||||
assert read(EXP / 'audit/fixtures-gate.json')['pass_all']
|
||||
rows = [gate_case(arm, name, 1) for arm in arms for name in CFG['calibration']['full_oracle_probes']]
|
||||
save(receipt, dict(pass_all=True, cases=rows, scope='Both arms; same two original first-CAL fullgraph cases'))
|
||||
else:
|
||||
assert read(EXP / 'audit/fixtures-gate.json')['pass_all']
|
||||
assert read(EXP / 'audit/probes-gate.json')['pass_all']
|
||||
from calibration import summarize, control_identity
|
||||
from compare_timing import compare_gpu_repeats
|
||||
records = read(EXP / 'protocol/calibration-cases.json')
|
||||
assert len(records) == 60
|
||||
for arm in arms:
|
||||
for record in records:
|
||||
if record['name'] not in CFG['calibration']['full_oracle_probes']:
|
||||
child('gpu', arm, record['name'])
|
||||
name = record['name']
|
||||
if arm == 'delay0':
|
||||
control = control_identity(EXP / 'gpu' / arm / (name + '-r0'),
|
||||
AG / 'gpu' / (name + '-r0'), EXP / 'cases' / arm / name)
|
||||
save(EXP / 'audit' / f'cal-control-{name}-r0.json', control)
|
||||
assert control['pass_all'], 'Zero-delay CAL control diverged: ' + name
|
||||
if record['seed'] == CFG['calibration']['seeds'][0]:
|
||||
child('gpu', arm, record['name'], 1)
|
||||
repeat = compare_gpu_repeats(EXP / 'gpu' / arm / (name + '-r0'),
|
||||
EXP / 'gpu' / arm / (name + '-r1'), EXP / 'audit' / f'cal-repeat-{arm}-{name}.json')
|
||||
assert repeat['pass_all'], 'CAL exact repeat diverged: ' + arm + '/' + name
|
||||
if arm == 'delay0':
|
||||
control = control_identity(EXP / 'gpu' / arm / (name + '-r1'),
|
||||
AG / 'gpu' / (name + '-r1'), EXP / 'cases' / arm / name)
|
||||
save(EXP / 'audit' / f'cal-control-{name}-r1.json', control)
|
||||
assert control['pass_all'], 'Zero-delay CAL repeat control diverged: ' + name
|
||||
result = summarize()
|
||||
save(receipt, dict(pass_all=result['execution_gate_pass'],
|
||||
representation_pass_all=result['pass_all'], scope='Complete both fixed CAL arms; not heldout or learning',
|
||||
summary='calibration/summary.json'))
|
||||
assert result['execution_gate_pass'], 'CAL numerical/control/repeat gate failed'
|
||||
except Exception as error:
|
||||
save(EXP / 'audit' / ('stopped-' + args.stage + '.json'), dict(stage=args.stage,
|
||||
type=type(error).__name__, message=str(error), traceback=traceback.format_exc(),
|
||||
later_stages_not_permitted=True, partial_outputs_preserved=True, parameters_changed=False))
|
||||
raise
|
||||
budget()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Fixed sealed-trace transport check; no closed-loop dynamics or gain search."""
|
||||
import hashlib
|
||||
import numpy as np
|
||||
from context import AG, EXP, CFG, check, read, save, digest, budget
|
||||
from timing_queue import TimingQueue, audit_sequence
|
||||
|
||||
|
||||
def transport(generated, delay):
|
||||
if generated.ndim != 2 or generated.dtype != np.float32:
|
||||
raise ValueError('Expected stored float32 generated h')
|
||||
queue = TimingQueue(delay, generated.shape[1])
|
||||
applied = np.empty_like(generated)
|
||||
for tick, row in enumerate(generated):
|
||||
applied[tick] = queue.push(row, tick)
|
||||
pending = queue.pending()
|
||||
report = audit_sequence(generated, applied, pending, delay)
|
||||
report.update(queue.metadata())
|
||||
report['applied_bytes_sha256'] = hashlib.sha256(applied.tobytes()).hexdigest()
|
||||
report['pending_bytes_sha256'] = hashlib.sha256(pending.tobytes()).hexdigest()
|
||||
return applied, pending, report
|
||||
|
||||
|
||||
def main():
|
||||
check()
|
||||
assert read(EXP / 'origin-audit/summary.json')['pass_all']
|
||||
names = CFG['fixtures'] + [r['name'] for r in read(EXP / 'protocol/calibration-cases.json')]
|
||||
records = []
|
||||
for name in names:
|
||||
path = AG / 'gpu' / (name + '-r0') / 'apl-inhibition.float32.npy'
|
||||
generated = np.load(path, mmap_mode='r', allow_pickle=False)
|
||||
assert generated.shape == (read(AG / 'cases' / name / 'spec.json')['steps'], 4064)
|
||||
for delay in CFG['delays_steps']:
|
||||
_, _, report = transport(generated, delay)
|
||||
records.append(dict(case=name, delay_steps=delay, input_sha256=digest(path), **report))
|
||||
save(EXP / 'audit/shift-gate.json', dict(pass_all=all(r['pass_all'] for r in records),
|
||||
records=records, input_trace_count=len(names), no_new_brain_trajectory=True,
|
||||
no_future_APL_advance=True, closed_loop_equal_integral_claim=False,
|
||||
preservation='Every generated finite-window sample retained bitwise in applied or chronological pending slots; initial padding is +0'))
|
||||
budget()
|
||||
print('PASS: 68 fixed generated traces, 2 exact shifts; no new brain trajectories', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,196 @@
|
||||
"""Pure synthetic selection, temporal joining, and streaming observer tests."""
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
import importlib.util
|
||||
import ast
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from al_origin_audit import (select_cohort, predecessor_counts, first_ticks,
|
||||
source_labels, ALLNObserver, _window_add, bit_equal)
|
||||
|
||||
|
||||
class MemorySink:
|
||||
def __init__(self):
|
||||
self.rows = 0
|
||||
self.calls = []
|
||||
|
||||
def write(self, data):
|
||||
self.rows += len(data['edge_id'])
|
||||
self.calls.append({k: np.asarray(v).copy() for k, v in data.items()})
|
||||
|
||||
|
||||
class OriginTests(unittest.TestCase):
|
||||
def test_main_does_not_shadow_callable_helpers(self):
|
||||
tree = ast.parse((Path(__file__).parent / 'al_origin_audit.py').read_text(encoding='utf-8'))
|
||||
helpers = {node.name for node in tree.body if isinstance(node, (ast.FunctionDef, ast.ClassDef))}
|
||||
main = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == 'main')
|
||||
assigned = {node.id for node in ast.walk(main) if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)}
|
||||
self.assertEqual(helpers & assigned, set())
|
||||
|
||||
def test_all_earliest_ties_and_static_design(self):
|
||||
ids = np.arange(5, dtype=np.int32)
|
||||
result = select_cohort(ids, np.array([1, 5, 5, 8, -1], np.int32),
|
||||
np.array([0], np.int32), 20, 30)
|
||||
np.testing.assert_array_equal(result['cohort'], [1, 2])
|
||||
self.assertEqual(result['matched_stop_exclusive'], 5)
|
||||
self.assertEqual(result['cohort_relation_to_first_KC'], 'before')
|
||||
|
||||
def test_no_non_design_activity_does_not_choose_never_active(self):
|
||||
result = select_cohort(np.arange(3, dtype=np.int32), np.array([0, -1, -1], np.int32),
|
||||
np.array([0], np.int32), 20, 30)
|
||||
self.assertEqual(len(result['cohort']), 0)
|
||||
self.assertEqual(result['earliest_tick'], -1)
|
||||
|
||||
def test_same_tick_is_not_before(self):
|
||||
result = select_cohort(np.arange(2, dtype=np.int32), np.array([0, 20], np.int32),
|
||||
np.array([0], np.int32), 20, 30)
|
||||
self.assertFalse(result['cohort_before_first_KC'])
|
||||
self.assertEqual(result['cohort_relation_to_first_KC'], 'same_tick')
|
||||
|
||||
def test_KC_absent_is_censored(self):
|
||||
result = select_cohort(np.arange(2, dtype=np.int32), np.array([0, 20], np.int32),
|
||||
np.array([0], np.int32), -1, 30)
|
||||
self.assertTrue(result['pre_kc_window_censored'])
|
||||
self.assertEqual(result['pre_kc_stop_exclusive'], 30)
|
||||
self.assertFalse(result['cohort_before_first_KC'])
|
||||
|
||||
def test_after_KC_retains_cohort_without_selecting_a_different_one(self):
|
||||
result = select_cohort(np.arange(2, dtype=np.int32), np.array([0, 20], np.int32),
|
||||
np.array([0], np.int32), 10, 30)
|
||||
self.assertEqual(result['cohort'].tolist(), [1])
|
||||
self.assertEqual(result['cohort_relation_to_first_KC'], 'after')
|
||||
self.assertEqual(result['matched_stop_exclusive'], 10)
|
||||
|
||||
def test_temporal_join_excludes_both_boundaries_and_prior_reset(self):
|
||||
result = predecessor_counts(np.array([3, 5, 6, 9, 10, 11]),
|
||||
np.array([1., 2., -3., 4., 5., 6.]), 10, 5)
|
||||
self.assertEqual(result['all_prior_design_PN_events'], 4)
|
||||
self.assertEqual(result['since_last_reset_design_PN_events'], 2)
|
||||
self.assertEqual(result['prior_reset_excluded_events'], 2)
|
||||
self.assertEqual(result['since_last_reset_positive_weight_SI'], 4.)
|
||||
self.assertEqual(result['since_last_reset_negative_abs_weight_SI'], 3.)
|
||||
|
||||
def test_zero_weight_not_transmission(self):
|
||||
result = predecessor_counts(np.array([2]), np.array([0.]), 3, -1)
|
||||
self.assertEqual(result['since_last_reset_design_PN_events'], 1)
|
||||
self.assertEqual(result['since_last_reset_nonzero_design_PN_events'], 0)
|
||||
|
||||
def test_no_direct_antecedent_valid_empty(self):
|
||||
result = predecessor_counts(np.array([], np.int32), np.array([], np.float64), 20, -1)
|
||||
self.assertEqual(result['since_last_reset_design_PN_events'], 0)
|
||||
|
||||
def test_reject_bad_temporal_order(self):
|
||||
with self.assertRaises(ValueError):
|
||||
predecessor_counts(np.array([3, 1]), np.ones(2), 10, 5)
|
||||
with self.assertRaises(ValueError):
|
||||
predecessor_counts(np.array([3]), np.ones(1), 10, 10)
|
||||
|
||||
def test_first_ticks_missing_and_repeated_events(self):
|
||||
values = first_ticks(np.array([1, 3, 3], np.int32), np.array([2, 1, 2], np.int32),
|
||||
np.arange(4, dtype=np.int32), n=4)
|
||||
np.testing.assert_array_equal(values, [-1, 3, 1, -1])
|
||||
|
||||
def test_exact_role_mapping_unknown_stays_unknown(self):
|
||||
labels = source_labels(np.arange(4), np.array(['ALPN', 'ALPN', 'ALLN', 'unclassified']),
|
||||
np.array([True, False, False, False]))
|
||||
self.assertEqual(labels.tolist(), ['design_ALPN', 'non_design_ALPN', 'ALLN', 'unclassified'])
|
||||
|
||||
def test_window_aggregation_preserves_sign_and_exclusion(self):
|
||||
stats = {}
|
||||
_window_add(stats, pd.DataFrame({'weight_SI': [1., -2., 9.]}),
|
||||
np.array([True, True, False]), np.array(['ALLN'] * 3))
|
||||
self.assertEqual(stats['ALLN']['events'], 2)
|
||||
self.assertEqual(stats['ALLN']['positive_weight_SI'], 1.)
|
||||
self.assertEqual(stats['ALLN']['negative_abs_weight_SI'], 2.)
|
||||
|
||||
def test_bit_equal_rejects_signed_zero(self):
|
||||
self.assertFalse(bit_equal(np.array([0.]), np.array([-0.])))
|
||||
|
||||
@staticmethod
|
||||
def observer():
|
||||
sink = MemorySink()
|
||||
incoming = SimpleNamespace(src_global=np.array([0, 1, 2], np.int32),
|
||||
dst_local=np.array([0, 0, 1], np.int32), edge_id=np.arange(3, dtype=np.int64),
|
||||
weights=np.array([.1, 0., -.2], np.float64))
|
||||
observer = ALLNObserver(np.array([3, 4], np.int32), incoming,
|
||||
np.array(['ALPN', 'APL', 'ALLN', 'ALLN', 'ALLN']),
|
||||
np.array([True, False, False, False, False]), np.arange(5, dtype=np.uint64),
|
||||
19, 19, sink)
|
||||
return observer, sink
|
||||
|
||||
@staticmethod
|
||||
def stage(due, accepted, ref=None):
|
||||
return dict(h32=np.zeros(2, np.float32), delta_si=np.zeros(2), request=np.zeros(2, bool),
|
||||
g=np.zeros(2), g_pre_apl=np.zeros(2), due_edge_indices=np.asarray(due, np.int64),
|
||||
arrival_accepted=np.asarray(accepted, bool),
|
||||
ref_after_reset=np.zeros(2, np.int32) if ref is None else ref,
|
||||
fired=np.zeros(2, bool))
|
||||
|
||||
def test_observer_all_accepted_signs_and_zeros(self):
|
||||
observer, sink = self.observer()
|
||||
for tick in range(18):
|
||||
observer(tick, self.stage([], []))
|
||||
observer(18, self.stage([0, 1, 2], [True, True, True]))
|
||||
report = observer.report()
|
||||
self.assertEqual(sink.rows, 3)
|
||||
self.assertEqual(report['source_groups']['APL']['zero'], 1)
|
||||
self.assertEqual(sink.calls[-1]['source_spike_tick'].tolist(), [0, 0, 0])
|
||||
self.assertEqual(observer.design_ticks[3], [18])
|
||||
self.assertEqual(sink.calls[-1]['weight_sign'].tolist(), [1, 0, -1])
|
||||
|
||||
def test_observer_ref_guard_and_no_rejected_records(self):
|
||||
observer, sink = self.observer()
|
||||
for tick in range(18):
|
||||
observer(tick, self.stage([], []))
|
||||
stage = self.stage([0, 1, 2], [False, False, True], np.array([22, 0], np.int32))
|
||||
stage['fired'][0] = True
|
||||
observer(18, stage)
|
||||
self.assertEqual(sink.rows, 1)
|
||||
self.assertEqual(observer.report()['rejected_events'], 2)
|
||||
self.assertEqual(observer.last.tolist(), [18, -1])
|
||||
|
||||
def test_observer_bad_acceptance_raises(self):
|
||||
observer, _ = self.observer()
|
||||
for tick in range(18):
|
||||
observer(tick, self.stage([], []))
|
||||
with self.assertRaises(ValueError):
|
||||
observer(18, self.stage([0], [True], np.array([22, 0], np.int32)))
|
||||
|
||||
def test_observer_rejects_unexpected_input_and_graded_APL(self):
|
||||
observer, _ = self.observer()
|
||||
stage = self.stage([], [])
|
||||
stage['request'][0] = True
|
||||
with self.assertRaises(ValueError):
|
||||
observer(0, stage)
|
||||
stage['request'][0] = False
|
||||
stage['h32'][0] = 1.
|
||||
with self.assertRaises(ValueError):
|
||||
observer(0, stage)
|
||||
|
||||
def test_frozen_replay_core_synthetic_integration(self):
|
||||
module_spec = importlib.util.spec_from_file_location('origin_test_frozen_core',
|
||||
Path(__file__).resolve().parent.parent / 'kc-drive-audit-v1/replay_core.py')
|
||||
core = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(core)
|
||||
incoming = core.IncomingIndex(np.array([0], np.int32), np.array([0], np.int32),
|
||||
np.array([0], np.int64), np.array([1.], np.float64), 2)
|
||||
sink = MemorySink()
|
||||
observer = ALLNObserver(np.array([3, 4], np.int32), incoming,
|
||||
np.array(['ALPN', 'APL', 'ALLN', 'ALLN', 'ALLN']),
|
||||
np.array([True, False, False, False, False]), np.arange(5, dtype=np.uint64), 40, 30, sink)
|
||||
coeff = dict(gd=.98, c0=-.00052, cg=.005, cv=.99, threshold=-.045, reset=-.052, seed_volt=-.044)
|
||||
result = core.replay(np.full(2, -.052), np.zeros(2), coeff, 40, np.array([3, 4], np.int32),
|
||||
dict(tick=np.array([], np.int32), neuron_idx=np.array([], np.int32)),
|
||||
dict(step=np.array([0], np.int32), neuron_idx=np.array([0], np.int32)), incoming,
|
||||
np.zeros((40, 2), np.float32), observer)
|
||||
self.assertEqual(observer.report()['accepted_events'], 1)
|
||||
self.assertGreater(int(result['first_spike'][0]), 18)
|
||||
self.assertEqual(int(result['first_spike'][1]), -1)
|
||||
self.assertEqual(observer.design_ticks[3], [18])
|
||||
nonempty = [call for call in sink.calls if len(call['edge_id'])]
|
||||
self.assertEqual(nonempty[0]['target_previous_spike_tick'].tolist(), [-1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,244 @@
|
||||
"""Synthetic CPU/file-fixture tests only; no real records, Torch, or Brian."""
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import calibration as cal
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
LEGACY = cal.load_legacy(HERE.parent / "author-apl-lab-v2/calibration.py")
|
||||
CFG = json.loads((HERE / "config.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def save_json(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, allow_nan=False), encoding="utf-8")
|
||||
|
||||
|
||||
def records():
|
||||
odors = sorted(LEGACY.ORIGINAL_CAL_IDS)
|
||||
return [dict(name=f"cal-{mode}-{index:02d}-s{seed}", mode=mode, odor_index=index,
|
||||
odor_id=odor, seed=seed, requests=0)
|
||||
for mode in LEGACY.ORIGINAL_MODES for index, odor in enumerate(odors) for seed in LEGACY.ORIGINAL_SEEDS]
|
||||
|
||||
|
||||
def timing_files(directory, delay, steps=20):
|
||||
generated = np.arange(steps * 4064, dtype=np.float32).reshape(steps, 4064) / np.float32(10000)
|
||||
stream = np.concatenate((np.zeros((delay, 4064), np.float32), generated))
|
||||
applied, pending = stream[:steps].copy(), stream[steps:].copy()
|
||||
state = np.arange(steps * 2, dtype=np.float32).reshape(steps, 2)
|
||||
np.save(directory / "apl-applied.float32.npy", applied)
|
||||
np.save(directory / "apl-pending-tail.float32.npy", pending)
|
||||
audit = dict(pass_all=True, apl_delay_steps=delay, steps=steps, pending_generated_count=min(steps, delay),
|
||||
pending_initial_zero_prefix=max(delay-steps, 0), pending_application_tick_start=steps,
|
||||
pending_application_tick_stop_exclusive=steps+delay, pending_generated_tick_start=max(0, steps-delay),
|
||||
pending_generated_tick_stop_exclusive=steps, initial_padding_positive_zero=True, sample_bits_preserved=True,
|
||||
brain_tail_advanced=False, generated_trace="apl-inhibition.float32.npy", applied_trace="apl-applied.float32.npy",
|
||||
pending_trace="apl-pending-tail.float32.npy", pending_raw_sha256=cal.raw_hash(pending),
|
||||
pending_queue_drained=delay == 0, finite_generated_sequence_only=True,
|
||||
future_apl_decay_or_feedback_generated=False, closed_loop_branch_integrals_claimed_equal=False,
|
||||
biological_delay_measurement_claimed=False, final_apl_state_float32=state[-1].tolist(),
|
||||
final_apl_state_u32_bits=state[-1].view(np.uint32).tolist(), final_apl_state_nonzero=bool(np.any(state[-1] != 0)))
|
||||
save_json(directory / "apl-timing-audit.json", audit)
|
||||
save_json(directory / "summary.json", dict(apl_delay_steps=delay, apl_timing=audit))
|
||||
trial = dict(apl={"apl-inhibition.float32.npy": generated, "apl-state.float32.npy": state})
|
||||
return dict(steps=steps, apl_delay_steps=delay), trial
|
||||
|
||||
|
||||
def control_files(root, record_history=True):
|
||||
new, old, case = root / "new", root / "old", root / "case"
|
||||
for directory in (new, old, case):
|
||||
directory.mkdir()
|
||||
spec = dict(name="synthetic", steps=3, apl_delay_steps=0, record_history=record_history)
|
||||
save_json(case / "spec.json", spec)
|
||||
for index, directory in enumerate((new, old)):
|
||||
summary = dict(case="synthetic", steps=3, completed_ticks=3, complete=True, pass_all=True,
|
||||
nodes=2, edges=3, precision="float64", apl_fast_edges_zeroed=1, kc_targets_present=1,
|
||||
effective_weights_raw_sha256="a" * 64, build_wall_seconds=index,
|
||||
input_sha256={"spec.json": str(index)}, output_budget_bytes=123+index)
|
||||
save_json(directory / "summary.json", summary)
|
||||
state = {key: np.zeros(0 if key in ("step", "neuron_idx") else 2, dtype) for key, dtype in cal.STATE_DTYPES.items()}
|
||||
np.savez(directory / "state.npz", **state)
|
||||
for name, width in cal.APL_FILES.items():
|
||||
np.save(directory / name, np.zeros((3, width), np.float32))
|
||||
for name in ("tick-state-hashes.jsonl", "queue.jsonl"):
|
||||
(directory / name).write_text("\n".join(json.dumps(dict(tick=t, same=True)) for t in range(3)), encoding="utf-8")
|
||||
(directory / "requests.csv").write_text(",".join(cal.REQUEST_FIELDS) + "\n", encoding="utf-8")
|
||||
save_json(directory / "apl-component.json", dict(ok=True, environment={"synthetic": True}))
|
||||
if record_history:
|
||||
for key, dtype in (("v", "float64"), ("g", "float64"), ("ref", "int32")):
|
||||
for name in (f"{key}-history.{dtype}.npy", f"preAPL-{key}.{dtype}.npy"):
|
||||
np.save(directory / name, np.zeros((3, 2), dtype))
|
||||
return new, old, case
|
||||
|
||||
|
||||
def fake_trial(output, row, delay):
|
||||
state = {name: np.zeros(1, dtype) for name, dtype in cal.STATE_DTYPES.items()}
|
||||
counts = np.zeros(1000, np.int32)
|
||||
if delay == 18:
|
||||
counts[row["odor_index"] * 50:(row["odor_index"] + 1) * 50] = 1
|
||||
return dict(state=state, apl={name: np.zeros((1, width), np.float32) for name, width in cal.APL_FILES.items()},
|
||||
hashes=[], queues=[], requests=[], component={}, kc_counts=counts, expected_external_events=10., external_events=0,
|
||||
record=dict(case=row["name"], requested=0), output=output,
|
||||
timing=dict(applied=np.zeros((1, 4064), np.float32), pending=np.zeros((delay, 4064), np.float32),
|
||||
audit=dict(synthetic=True), report=dict(pass_all=True)))
|
||||
|
||||
|
||||
class CalibrationTimingTests(unittest.TestCase):
|
||||
def test_all_original_inventory_and_pair_specific_gates_are_reused(self):
|
||||
rows = records()
|
||||
odors = LEGACY.validate_inventory(rows, CFG)
|
||||
runs = {(r["mode"], r["odor_id"], r["seed"]): fake_trial(Path("synthetic"), r, 18) for r in rows}
|
||||
report = LEGACY.summarize_mode(LEGACY.ORIGINAL_MODES[0], odors, LEGACY.ORIGINAL_SEEDS, runs, CFG["gates"])
|
||||
self.assertTrue(report["gate"])
|
||||
self.assertEqual((report["within_distance_count"], report["between_distance_count"], report["pair_count"]), (60, 75, 15))
|
||||
self.assertTrue(all(r["within_distance_count"] == 20 and r["between_distance_count"] == 5 for r in report["pairs"]))
|
||||
self.assertFalse(hasattr(LEGACY, "EXP"))
|
||||
with self.assertRaises(ValueError):
|
||||
LEGACY.validate_inventory(rows[:-1], CFG)
|
||||
|
||||
def test_direct_slices_cover_zero_delay_boundary_and_short_window(self):
|
||||
for delay in (0, 18):
|
||||
for steps in (1, 17, 18, 19, 37):
|
||||
with self.subTest(delay=delay, steps=steps), tempfile.TemporaryDirectory() as tmp:
|
||||
spec, trial = timing_files(Path(tmp), delay, steps)
|
||||
result = cal.validate_timing(Path(tmp), spec, trial)
|
||||
try:
|
||||
self.assertTrue(result["report"]["pass_all"])
|
||||
self.assertEqual(result["pending"].shape, (delay, 4064))
|
||||
finally:
|
||||
result["applied"]._mmap.close()
|
||||
result["pending"]._mmap.close()
|
||||
|
||||
def test_tail_or_application_bit_corruption_fails(self):
|
||||
for filename in ("apl-applied.float32.npy", "apl-pending-tail.float32.npy"):
|
||||
with self.subTest(filename=filename), tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); spec, trial = timing_files(root, 18)
|
||||
value = np.load(root / filename)
|
||||
value[-1, 1] = np.nextafter(value[-1, 1], np.float32(np.inf))
|
||||
np.save(root / filename, value)
|
||||
with self.assertRaisesRegex(ValueError, "partition"):
|
||||
cal.validate_timing(root, spec, trial)
|
||||
|
||||
def test_negative_zero_padding_and_fabricated_future_audit_fail(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); spec, trial = timing_files(root, 18, 1)
|
||||
value = np.load(root / "apl-applied.float32.npy")
|
||||
value[0, 0] = np.float32(-0.)
|
||||
np.save(root / "apl-applied.float32.npy", value)
|
||||
with self.assertRaisesRegex(ValueError, "positive zero"):
|
||||
cal.validate_timing(root, spec, trial)
|
||||
for key, value in (("brain_tail_advanced", True), ("final_apl_state_nonzero", False),
|
||||
("pending_initial_zero_prefix", 1), ("pass_all", 1)):
|
||||
with self.subTest(key=key), tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp); spec, trial = timing_files(root, 18, 20)
|
||||
audit = cal.read_json(root / "apl-timing-audit.json"); audit[key] = value
|
||||
save_json(root / "apl-timing-audit.json", audit)
|
||||
with self.assertRaisesRegex(ValueError, "audit disagrees"):
|
||||
cal.validate_timing(root, spec, trial)
|
||||
|
||||
def test_delay0_control_ignores_runtime_and_budget_metadata(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
new, old, case = control_files(Path(tmp))
|
||||
result = cal.control_identity(new, old, case)
|
||||
self.assertTrue(result["pass_all"])
|
||||
self.assertFalse(result["adapter_paths_budgets_and_runtime_metadata_compared"])
|
||||
self.assertIn("preAPL-g.float64.npy", result["checks"])
|
||||
|
||||
def test_control_signed_zero_history_and_missing_trace_fail(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
new, old, case = control_files(Path(tmp))
|
||||
with np.load(new / "state.npz") as archive:
|
||||
state = {key: archive[key].copy() for key in archive.files}
|
||||
state["v"][0] = -0.
|
||||
np.savez(new / "state.npz", **state)
|
||||
result = cal.control_identity(new, old, case)
|
||||
self.assertFalse(result["checks"]["state/v"])
|
||||
self.assertFalse(result["pass_all"])
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
new, old, case = control_files(Path(tmp))
|
||||
np.save(new / "preAPL-g.float64.npy", np.ones((3, 2), np.float64))
|
||||
self.assertFalse(cal.control_identity(new, old, case)["pass_all"])
|
||||
(new / "apl-drive.float32.npy").unlink()
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
cal.control_identity(new, old, case)
|
||||
|
||||
def test_control_rejects_delay18_and_incomplete_results(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
new, old, case = control_files(Path(tmp), False)
|
||||
spec = cal.read_json(case / "spec.json"); spec["apl_delay_steps"] = 18
|
||||
save_json(case / "spec.json", spec)
|
||||
with self.assertRaisesRegex(ValueError, "only for explicit delay0"):
|
||||
cal.control_identity(new, old, case)
|
||||
spec["apl_delay_steps"] = 0; save_json(case / "spec.json", spec)
|
||||
summary = cal.read_json(new / "summary.json"); summary["complete"] = False
|
||||
save_json(new / "summary.json", summary)
|
||||
with self.assertRaisesRegex(ValueError, "incomplete"):
|
||||
cal.control_identity(new, old, case)
|
||||
|
||||
def test_repeat_includes_applied_pending_and_audit(self):
|
||||
trial = fake_trial(Path("synthetic"), records()[0], 18)
|
||||
self.assertTrue(cal.timing_repeat(trial, copy.deepcopy(trial), LEGACY)["pass_all"])
|
||||
for key in ("applied", "pending", "audit"):
|
||||
bad = copy.deepcopy(trial)
|
||||
if key == "audit": bad["timing"][key]["synthetic"] = False
|
||||
else: bad["timing"][key][0, 0] = 1
|
||||
self.assertFalse(cal.timing_repeat(trial, bad, LEGACY)["pass_all"])
|
||||
|
||||
def test_both_arms_keep_full_inventory_despite_control_repr_failure(self):
|
||||
rows = records(); odors = LEGACY.validate_inventory(rows, CFG)
|
||||
by_name = {row["name"]: row for row in rows}
|
||||
calls = []
|
||||
def read_new(legacy, output, case, spec, kc, cfg):
|
||||
calls.append(str(output))
|
||||
return fake_trial(output, by_name[case.name], spec["apl_delay_steps"])
|
||||
with patch.object(cal, "_case_spec", side_effect=lambda case, old, row, delay: dict(apl_delay_steps=delay)), \
|
||||
patch.object(cal, "_read_new", side_effect=read_new), \
|
||||
patch.object(cal, "_provenance", return_value={}), \
|
||||
patch.object(cal, "control_identity", return_value=dict(case="synthetic", pass_all=True, checks={})):
|
||||
reports = [cal.collect_arm("delay"+str(delay), delay, rows, odors, LEGACY, CFG,
|
||||
Path("synthetic-exp"), Path("synthetic-ag"), np.arange(4064, dtype=np.int32)) for delay in (0,18)]
|
||||
self.assertEqual(len(calls), 144)
|
||||
self.assertTrue(all(r["execution_gate_pass"] for r in reports))
|
||||
self.assertFalse(reports[0]["original_representation_gate_pass"])
|
||||
self.assertTrue(reports[1]["original_representation_gate_pass"])
|
||||
self.assertEqual([r["valid_base_trials"] for r in reports], [60, 60])
|
||||
self.assertEqual([r["checked_repeats"] for r in reports], [12, 12])
|
||||
self.assertEqual(len(reports[0]["control_checks"]), 72)
|
||||
|
||||
def test_none_recomputation_is_deterministic_and_never_writes(self):
|
||||
rows = records(); odors = sorted(LEGACY.ORIGINAL_CAL_IDS)
|
||||
panel = pd.DataFrame(dict(split=["calibration"]*6+["heldout"]*6,
|
||||
odor_id=odors+["static-only-"+str(n) for n in range(6)], mapped_response_sum=[1.]*12))
|
||||
encoder = dict(panel_count=12, cal_count=6, seeds=list(LEGACY.ORIGINAL_SEEDS),
|
||||
requested_before_any_dynamics=True, formal_AB_run=False, heldout_run=False, median_mapped_response_sum=1.)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
ctx = types.ModuleType("context")
|
||||
ctx.EXP, ctx.AG, ctx.ROOT, ctx.CFG, ctx.check = Path(tmp)/"exp", Path(tmp)/"ag", Path(tmp), CFG, lambda: None
|
||||
def fake_arm(arm, *args):
|
||||
return dict(arm=arm, numerical_integrity_pass=True, exact_repeat_pass=True,
|
||||
delay0_control_identity_pass=True if arm == "delay0" else None,
|
||||
original_representation_gate_pass=False, failures=[], modes={"physiological": {"gate": False}})
|
||||
with patch.dict("sys.modules", {"context": ctx}), patch.object(cal, "load_legacy", return_value=LEGACY), \
|
||||
patch.object(cal, "read_json", side_effect=lambda p: rows if Path(p).name == "calibration-cases.json" else encoder), \
|
||||
patch.object(cal, "file_hash", return_value="same"), patch.object(pd, "read_parquet", return_value=panel), \
|
||||
patch.object(cal.np, "load", return_value=np.arange(4064, dtype=np.int32)), \
|
||||
patch.object(cal, "collect_arm", side_effect=fake_arm):
|
||||
first, second = cal.summarize(None), cal.summarize(None)
|
||||
self.assertEqual(first, second)
|
||||
self.assertTrue(first["execution_gate_pass"])
|
||||
self.assertFalse(first["pass_all"])
|
||||
json.dumps(first, allow_nan=False)
|
||||
self.assertEqual(list(Path(tmp).iterdir()), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,199 @@
|
||||
"""Synthetic orchestration/producer-contract checks; never launch a brain."""
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
from types import ModuleType
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
|
||||
import compare_timing
|
||||
import oracle_timing
|
||||
import run_suite
|
||||
from shift_audit import transport
|
||||
from timing_queue import audit_sequence
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def fake_calibration_records():
|
||||
return [dict(name=f'cal-{mode}-{odor:02d}-s{seed}', mode=mode, odor_id='odor'+str(odor), seed=seed)
|
||||
for mode in ('physiological', 'matched_energy') for odor in range(6)
|
||||
for seed in range(51101, 51106)]
|
||||
|
||||
|
||||
def oracle_timing_dict(delay, generated, pending, final_state):
|
||||
"""Evaluate only the actual producer's explicit metadata dict, no run call."""
|
||||
tree = ast.parse((HERE / 'oracle_timing.py').read_text(encoding='utf-8'))
|
||||
calls = [n for n in ast.walk(tree) if isinstance(n, ast.Call) and isinstance(n.func, ast.Name)
|
||||
and n.func.id == '_save' and len(n.args) > 1 and isinstance(n.args[1], ast.Dict)
|
||||
and any(isinstance(s, ast.Constant) and s.value == 'apl-timing-audit.json'
|
||||
for s in ast.walk(n.args[0]))]
|
||||
if len(calls) != 1:
|
||||
raise AssertionError('Expected exactly one explicit oracle timing-metadata producer')
|
||||
steps = len(generated)
|
||||
namespace = dict(np=np, min=min, max=max, bool=bool, apl_delay_steps=delay, steps=steps,
|
||||
consumed=max(steps-delay, 0), initial_zero_prefix=max(delay-steps, 0), pending_tail=pending,
|
||||
apl_history={'state': np.asarray([final_state], np.float32)}, out_dir=Path('.'),
|
||||
_array_hash=oracle_timing._array_hash, _digest=lambda _: '0'*64)
|
||||
return eval(compile(ast.Expression(calls[0].args[1]), '<only metadata literal>', 'eval'), namespace)
|
||||
|
||||
|
||||
class ProtocolTests(unittest.TestCase):
|
||||
def test_two_independent_queues_match_both_arms_and_short_windows(self):
|
||||
for delay in (0, 18):
|
||||
for steps in (0, 1, 17, 18, 19, 37):
|
||||
with self.subTest(delay=delay, steps=steps):
|
||||
generated = ((np.arange(steps * 4064).reshape(steps, 4064) % 31)
|
||||
.astype(np.float32) * np.float32(.001))
|
||||
if steps:
|
||||
generated[0, 0] = np.float32(-0.)
|
||||
applied, pending, report = transport(generated, delay)
|
||||
independent = oracle_timing._APLDelayFIFO(delay)
|
||||
expected = np.empty_like(generated)
|
||||
for tick, row in enumerate(generated):
|
||||
expected[tick] = independent.step(tick, row)
|
||||
tail = independent.finish(steps)
|
||||
np.testing.assert_array_equal(applied.view(np.uint32), expected.view(np.uint32))
|
||||
np.testing.assert_array_equal(pending.view(np.uint32), tail.view(np.uint32))
|
||||
self.assertTrue(report['pass_all'])
|
||||
|
||||
def test_queue_rejects_off_by_one_and_lost_pending(self):
|
||||
values = np.ones((19, 2), np.float32)
|
||||
applied, pending, _ = transport(values, 18)
|
||||
corrupt = applied.copy()
|
||||
corrupt[17] = 1
|
||||
with self.assertRaises(ValueError):
|
||||
audit_sequence(values, corrupt, pending, 18)
|
||||
with self.assertRaises(ValueError):
|
||||
audit_sequence(values, applied, pending[:-1], 18)
|
||||
|
||||
def test_actual_oracle_metadata_matches_compare_contract(self):
|
||||
for delay in (0, 18):
|
||||
for steps in (1, 17, 18, 19, 37):
|
||||
with self.subTest(delay=delay, steps=steps):
|
||||
generated = np.full((steps, 4064), .25, np.float32)
|
||||
_, pending, _ = transport(generated, delay)
|
||||
final = np.array([.125, -0.], np.float32)
|
||||
timing = oracle_timing_dict(delay, generated, pending, final)
|
||||
summary = dict(pending_generated_count=min(delay, steps),
|
||||
pending_initial_zero_prefix=max(delay-steps, 0), pending_tail_shape=[delay, 4064],
|
||||
future_generated_decay_tail_included=False, APL_worker_ticks=steps)
|
||||
run = dict(timing=timing, summary=summary, pending=pending,
|
||||
apl={'state': np.asarray([final], np.float32)})
|
||||
checks = {}
|
||||
compare_timing._timing_metadata_checks(run, 'cpu', delay, steps, checks)
|
||||
self.assertTrue(all(checks.values()), checks)
|
||||
|
||||
def test_bad_metadata_must_fail_without_fitted_tolerance(self):
|
||||
generated = np.ones((3, 4064), np.float32)
|
||||
_, pending, _ = transport(generated, 18)
|
||||
final = np.array([.125, 0.], np.float32)
|
||||
timing = oracle_timing_dict(18, generated, pending, final)
|
||||
timing['pending_initial_zero_prefix'] += 1
|
||||
run = dict(timing=timing, summary={}, pending=pending, apl={'state': np.asarray([final])})
|
||||
checks = {}
|
||||
compare_timing._timing_metadata_checks(run, 'cpu', 18, 3, checks)
|
||||
self.assertFalse(checks['cpu.timing_integer_metadata'])
|
||||
|
||||
def _suite(self, stage, *, failed_read=None, execution=True, representation=False,
|
||||
control=True, repeat=True):
|
||||
records = fake_calibration_records()
|
||||
saved, children = {}, []
|
||||
def read(path):
|
||||
path = str(path).replace('\\', '/')
|
||||
if path.endswith('calibration-cases.json'):
|
||||
return records
|
||||
return {'pass_all': not (failed_read and path.endswith(failed_read))}
|
||||
fake = ModuleType('calibration')
|
||||
fake.summarize = lambda: dict(execution_gate_pass=execution, pass_all=representation)
|
||||
fake.control_identity = lambda *args: dict(pass_all=control)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
with patch.object(run_suite, 'EXP', Path(directory)), patch.object(run_suite, 'check'), \
|
||||
patch.object(run_suite, 'budget'), patch.object(run_suite, 'read', side_effect=read), \
|
||||
patch.object(run_suite, 'save', side_effect=lambda path, result: saved.update({Path(path).name: result})), \
|
||||
patch.object(run_suite, 'child', side_effect=lambda *args: children.append(args)), \
|
||||
patch.object(run_suite, 'gate_case', side_effect=lambda arm, name, repeats:
|
||||
dict(arm=arm, case=name, pass_all=True, gpu_repeats=repeats)), \
|
||||
patch.object(compare_timing, 'compare_gpu_repeats', return_value=dict(pass_all=repeat)), \
|
||||
patch.dict(sys.modules, {'calibration': fake}), patch.object(sys, 'argv', ['run_suite.py', stage]):
|
||||
error = None
|
||||
try:
|
||||
run_suite.main()
|
||||
except AssertionError as caught:
|
||||
error = caught
|
||||
return saved, children, error
|
||||
|
||||
def test_fixture_gate_covers_both_arms_all_cases(self):
|
||||
saved, children, error = self._suite('fixtures')
|
||||
self.assertIsNone(error)
|
||||
self.assertEqual(children, [])
|
||||
rows = saved['fixtures-gate.json']['cases']
|
||||
self.assertEqual(len(rows), 16)
|
||||
self.assertEqual({r['arm'] for r in rows}, {'delay0', 'delay18'})
|
||||
self.assertTrue(all(r['gpu_repeats'] == 3 for r in rows))
|
||||
|
||||
def test_probe_stage_cannot_bypass_fixture_failure(self):
|
||||
saved, children, error = self._suite('probes', failed_read='fixtures-gate.json')
|
||||
self.assertIsNotNone(error)
|
||||
self.assertEqual(children, [])
|
||||
self.assertNotIn('probes-gate.json', saved)
|
||||
|
||||
def test_CAL_cannot_bypass_probe_failure(self):
|
||||
saved, children, error = self._suite('calibration', failed_read='probes-gate.json')
|
||||
self.assertIsNotNone(error)
|
||||
self.assertEqual(children, [])
|
||||
self.assertNotIn('calibration-gate.json', saved)
|
||||
|
||||
def test_representation_failure_does_not_select_or_stop_an_arm(self):
|
||||
saved, children, error = self._suite('calibration', execution=True, representation=False)
|
||||
self.assertIsNone(error)
|
||||
self.assertEqual(len(children), 140) # (60 - 2 already-probed + 12 repeat) per arm.
|
||||
self.assertEqual({row[1] for row in children}, {'delay0', 'delay18'})
|
||||
self.assertTrue(saved['calibration-gate.json']['pass_all'])
|
||||
self.assertFalse(saved['calibration-gate.json']['representation_pass_all'])
|
||||
for arm in ('delay0', 'delay18'):
|
||||
self.assertEqual(sum(row[1] == arm and len(row) == 4 and row[3] == 1 for row in children), 12)
|
||||
|
||||
def test_CAL_numerical_failure_stops_later_execution(self):
|
||||
saved, _, error = self._suite('calibration', execution=False, representation=False)
|
||||
self.assertIsNotNone(error)
|
||||
self.assertFalse(saved['calibration-gate.json']['pass_all'])
|
||||
self.assertTrue(saved['stopped-calibration.json']['later_stages_not_permitted'])
|
||||
|
||||
def test_control_divergence_stops_before_next_trial_or_other_arm(self):
|
||||
saved, children, error = self._suite('calibration', control=False)
|
||||
self.assertIsNotNone(error)
|
||||
# The first r0 is a completed fullgraph probe; do not even run its r1.
|
||||
self.assertEqual(children, [])
|
||||
self.assertNotIn('calibration-gate.json', saved)
|
||||
self.assertTrue(saved['stopped-calibration.json']['later_stages_not_permitted'])
|
||||
|
||||
def test_repeat_divergence_stops_before_next_trial_or_other_arm(self):
|
||||
saved, children, error = self._suite('calibration', repeat=False)
|
||||
self.assertIsNotNone(error)
|
||||
self.assertEqual(len(children), 1)
|
||||
self.assertEqual(children[0][1], 'delay0')
|
||||
self.assertEqual(children[0][-1], 1)
|
||||
self.assertNotIn('calibration-gate.json', saved)
|
||||
self.assertTrue(saved['stopped-calibration.json']['later_stages_not_permitted'])
|
||||
|
||||
def test_origin_failure_prevents_any_new_fixture_run(self):
|
||||
saved, children, error = self._suite('fixtures', failed_read='origin-audit/summary.json')
|
||||
self.assertIsNotNone(error)
|
||||
self.assertEqual(saved, {})
|
||||
self.assertEqual(children, [])
|
||||
|
||||
def test_shift_failure_prevents_any_new_fixture_run(self):
|
||||
saved, children, error = self._suite('fixtures', failed_read='shift-gate.json')
|
||||
self.assertIsNotNone(error)
|
||||
self.assertEqual(saved, {})
|
||||
self.assertEqual(children, [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1 @@
|
||||
"""Discoverable synthetic tests; no scientific execution during imports."""
|
||||
@@ -0,0 +1,169 @@
|
||||
"""Synthetic array-only comparator tests: no backend or scientific inputs."""
|
||||
from pathlib import Path
|
||||
import copy
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
import compare_timing as compare
|
||||
|
||||
|
||||
def traces(delay, steps, width=3):
|
||||
generated = np.arange(steps * width, dtype=np.float32).reshape(steps, width) * np.float32(.125)
|
||||
full = np.concatenate((np.zeros((delay, width), dtype=np.float32), generated))
|
||||
return generated, full[:steps].copy(), full[steps:].copy()
|
||||
|
||||
|
||||
def sequence_result(generated, applied, pending, delay):
|
||||
checks, differences = {}, {}
|
||||
compare._timing_sequence_checks(generated, applied, pending, delay, 'test', checks, differences)
|
||||
return checks, differences
|
||||
|
||||
|
||||
def metadata_run(delay, steps, cpu=False):
|
||||
generated, applied, pending = traces(delay, steps, 4064)
|
||||
state = np.tile(np.array([.25, 1.125], np.float32), (steps, 1))
|
||||
timing = dict(apl_delay_steps=delay, steps=steps, pending_generated_count=min(steps, delay),
|
||||
generated_rows=steps, applied_generated_rows=max(steps-delay, 0), pending_shape=[delay, 4064],
|
||||
pending_initial_zero_prefix=max(delay-steps, 0),
|
||||
pending_application_tick_start=steps, pending_application_tick_stop_exclusive=steps+delay,
|
||||
pending_generated_tick_start=max(steps-delay, 0), pending_generated_tick_stop_exclusive=steps,
|
||||
pending_raw_sha256=compare.raw_hash(pending, '<f4'),
|
||||
final_apl_state_float32=state[-1].tolist(),
|
||||
final_apl_state_u32_bits=state[-1].view(np.uint32).tolist(), final_apl_state_nonzero=True,
|
||||
brain_tail_advanced=False, future_apl_decay_or_feedback_generated=False,
|
||||
finite_generated_sequence_only=True, closed_loop_branch_integrals_claimed_equal=False,
|
||||
biological_delay_measurement_claimed=False, brain_steps_after_window=0,
|
||||
APL_generation_steps_after_window=0, future_generated_decay_tail_included=False)
|
||||
summary = dict(apl_timing=copy.deepcopy(timing), pending_generated_count=min(steps, delay),
|
||||
pending_initial_zero_prefix=max(delay-steps, 0), pending_tail_shape=[delay, 4064],
|
||||
future_generated_decay_tail_included=False, APL_worker_ticks=steps)
|
||||
return dict(apl={'inhibition': generated, 'applied': applied, 'state': state}, pending=pending,
|
||||
timing=timing, summary=summary)
|
||||
|
||||
|
||||
class TimingComparisonTests(unittest.TestCase):
|
||||
def test_delay_zero_and_18_partition(self):
|
||||
for delay in (0, 18):
|
||||
for steps in (1, 7, 18, 19, 54):
|
||||
with self.subTest(delay=delay, steps=steps):
|
||||
checks, differences = sequence_result(*traces(delay, steps), delay)
|
||||
self.assertTrue(all(checks.values()), differences)
|
||||
|
||||
def test_one_tick_shift_rejected_independently(self):
|
||||
generated, applied, pending = traces(18, 40)
|
||||
applied[18:] = generated[1:23]
|
||||
checks, _ = sequence_result(generated, applied, pending, 18)
|
||||
self.assertFalse(checks['test.fixed_delay_applied.bitwise_exact'])
|
||||
|
||||
def test_pending_wrong_chronology_rejected(self):
|
||||
generated, applied, pending = traces(18, 40)
|
||||
checks, _ = sequence_result(generated, applied, pending[::-1].copy(), 18)
|
||||
self.assertFalse(checks['test.fixed_delay_pending.bitwise_exact'])
|
||||
|
||||
def test_initial_zero_must_be_positive_bits(self):
|
||||
generated, applied, pending = traces(18, 4)
|
||||
pending.view(np.uint32)[0, 0] = 0x80000000
|
||||
checks, _ = sequence_result(generated, applied, pending, 18)
|
||||
self.assertFalse(checks['test.fixed_delay_pending.bitwise_exact'])
|
||||
generated, applied, pending = traces(18, 22)
|
||||
applied.view(np.uint32)[0, 0] = 0x80000000
|
||||
checks, _ = sequence_result(generated, applied, pending, 18)
|
||||
self.assertFalse(checks['test.fixed_delay_applied.bitwise_exact'])
|
||||
|
||||
def test_generated_signed_zero_preserved(self):
|
||||
generated, applied, pending = traces(0, 1)
|
||||
generated.view(np.uint32)[0, 0] = 0x80000000
|
||||
applied[:] = generated
|
||||
checks, _ = sequence_result(generated, applied, pending, 0)
|
||||
self.assertTrue(all(checks.values()))
|
||||
applied.view(np.uint32)[0, 0] = 0
|
||||
checks, _ = sequence_result(generated, applied, pending, 0)
|
||||
self.assertFalse(checks['test.fixed_delay_applied.bitwise_exact'])
|
||||
|
||||
def test_schema_and_delay_fail_closed(self):
|
||||
generated, applied, pending = traces(18, 4)
|
||||
for delay in (True, 1, 18.0):
|
||||
with self.assertRaises(ValueError):
|
||||
sequence_result(generated, applied, pending, delay)
|
||||
with self.assertRaises(ValueError):
|
||||
sequence_result(generated.astype(np.float64), applied, pending, 18)
|
||||
with self.assertRaises(ValueError):
|
||||
sequence_result(generated, applied, pending[-4:], 18)
|
||||
|
||||
def test_nonfinite_or_negative_generated_fails_even_if_shift_matches(self):
|
||||
for value in (np.inf, np.nan, -1):
|
||||
generated, applied, pending = traces(0, 2)
|
||||
generated[0, 0] = value
|
||||
applied[:] = generated
|
||||
checks, _ = sequence_result(generated, applied, pending, 0)
|
||||
self.assertFalse(checks['test.timing_finite_nonnegative'])
|
||||
|
||||
def test_both_backend_metadata_valid(self):
|
||||
for role in ('gpu', 'cpu', 'first_gpu', 'second_gpu'):
|
||||
for delay, steps in ((0, 25), (18, 7), (18, 25)):
|
||||
run = metadata_run(delay, steps, cpu=role == 'cpu')
|
||||
checks = {}
|
||||
compare._timing_metadata_checks(run, role, delay, steps, checks)
|
||||
self.assertTrue(all(checks.values()), checks)
|
||||
|
||||
def test_metadata_rejects_wrong_count_tail_advance_and_final_state(self):
|
||||
for role in ('gpu', 'cpu'):
|
||||
for change in ('count', 'tail', 'final', 'prefix', 'hash'):
|
||||
run = metadata_run(18, 25, cpu=role == 'cpu')
|
||||
if change == 'count':
|
||||
run['timing']['pending_generated_count'] = 17
|
||||
elif change == 'tail':
|
||||
run['timing']['brain_steps_after_window' if role == 'cpu' else 'brain_tail_advanced'] = 1
|
||||
elif change == 'final':
|
||||
run['timing']['final_apl_state_u32_bits'][0] += 1
|
||||
elif change == 'prefix':
|
||||
run['timing']['pending_initial_zero_prefix'] = False
|
||||
else:
|
||||
run['timing']['pending_raw_sha256'] = '0' * 64
|
||||
checks = {}
|
||||
compare._timing_metadata_checks(run, role, 18, 25, checks)
|
||||
self.assertFalse(all(checks.values()), (role, change))
|
||||
|
||||
def test_g_write_uses_applied_and_is_unconditional_during_ref(self):
|
||||
nodes, kc = np.array([10, 12], np.int32), np.array([10], np.int32)
|
||||
v = np.full((3, 2), -.06, np.float64)
|
||||
ref = np.array([[22, 0], [21, 0], [20, 0]], np.int32)
|
||||
before = np.array([[0, -.001], [-0., .001], [.002, -.003]], np.float64)
|
||||
applied = np.array([[.25], [0], [1]], np.float32)
|
||||
after = before.copy()
|
||||
after[:, 0] -= applied[:, 0].astype(np.float64) * np.float64(.001)
|
||||
run = dict(pre={'v': v, 'g': before, 'ref': ref}, histories={'v': v.copy(), 'g': after, 'ref': ref.copy()},
|
||||
apl={'applied': applied, 'inhibition': np.full((3, 1), 99, np.float32)})
|
||||
checks, differences = {}, {}
|
||||
compare._check_apl_application(run, 'test', nodes, kc, checks, differences)
|
||||
self.assertTrue(all(checks.values()), differences)
|
||||
run['histories']['g'] = before.copy() # Hypothetical refractory guard must fail.
|
||||
checks, differences = {}, {}
|
||||
compare._check_apl_application(run, 'test', nodes, kc, checks, differences)
|
||||
self.assertFalse(checks['test.apl_unconditional_g_write.bitwise_exact'])
|
||||
|
||||
def test_pair_comparison_includes_applied_and_pending(self):
|
||||
def output():
|
||||
generated, applied, pending = traces(18, 20)
|
||||
return dict(state={'step': np.empty(0, np.int32), 'neuron_idx': np.empty(0, np.int32),
|
||||
'counts': np.zeros(1, np.int32), 'ref': np.zeros(1, np.int32),
|
||||
'v': np.zeros(1, np.float64), 'g': np.zeros(1, np.float64)},
|
||||
apl={'drive': np.zeros((20, 2), np.float32), 'state': np.zeros((20, 2), np.float32),
|
||||
'inhibition': np.pad(generated, ((0, 0), (0, 4061))),
|
||||
'applied': np.pad(applied, ((0, 0), (0, 4061)))},
|
||||
pending=np.pad(pending, ((0, 0), (0, 4061))),
|
||||
hashes=[], queues=[], requests=[], component={}, histories={}, pre={})
|
||||
left, right = output(), output()
|
||||
right['apl']['applied'][0, 0] = 1
|
||||
right['pending'][0, 0] += 1
|
||||
checks, differences = {}, {}
|
||||
compare._pair_outputs(left, right, checks, differences, 1, 20, False)
|
||||
self.assertFalse(checks['apl.applied.bitwise_exact'])
|
||||
self.assertFalse(checks['apl.pending.bitwise_exact'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,129 @@
|
||||
"""Synthetic CPU-only checks; no Torch, data bundle, or scientific trial."""
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from timing_queue import TimingQueue, audit_sequence
|
||||
|
||||
|
||||
class TimingQueueTests(unittest.TestCase):
|
||||
def sequence(self, delay, steps, width=3):
|
||||
generated = np.arange(1, steps * width + 1, dtype=np.float32).reshape(steps, width)
|
||||
queue = TimingQueue(delay, width)
|
||||
applied = np.empty_like(generated)
|
||||
for tick, row in enumerate(generated):
|
||||
applied[tick] = queue.push(row, tick=tick)
|
||||
return queue, generated, applied, queue.pending()
|
||||
|
||||
def test_delay_zero_identity(self):
|
||||
queue, generated, applied, pending = self.sequence(0, 40)
|
||||
self.assertTrue(np.array_equal(generated.view(np.uint32), applied.view(np.uint32)))
|
||||
self.assertEqual(pending.shape, (0, 3))
|
||||
self.assertTrue(audit_sequence(generated, applied, pending, 0)["pass_all"])
|
||||
self.assertEqual(queue.metadata()["pending_generated_count"], 0)
|
||||
|
||||
def test_delay_18_boundary(self):
|
||||
_, generated, applied, pending = self.sequence(18, 39)
|
||||
self.assertTrue(np.all(applied[:18].view(np.uint32) == 0))
|
||||
np.testing.assert_array_equal(applied[18], generated[0])
|
||||
np.testing.assert_array_equal(applied[36], generated[18])
|
||||
np.testing.assert_array_equal(pending, generated[-18:])
|
||||
audit_sequence(generated, applied, pending, 18)
|
||||
|
||||
def test_short_sequence_keeps_initial_pending_slots(self):
|
||||
for steps in (0, 1, 7, 17, 18, 19):
|
||||
with self.subTest(steps=steps):
|
||||
queue, generated, applied, pending = self.sequence(18, steps)
|
||||
meta = audit_sequence(generated, applied, pending, 18)
|
||||
self.assertEqual(pending.shape, (18, 3))
|
||||
self.assertEqual(meta["pending_initial_zero_prefix"], max(18 - steps, 0))
|
||||
self.assertEqual(meta["pending_generated_count"], min(18, steps))
|
||||
self.assertEqual(queue.steps, steps)
|
||||
self.assertTrue(np.all(pending[:max(18 - steps, 0)].view(np.uint32) == 0))
|
||||
|
||||
def test_no_alias_to_input_or_return_or_pending(self):
|
||||
queue = TimingQueue(18, 1)
|
||||
row = np.array([3], dtype=np.float32)
|
||||
first = queue.push(row)
|
||||
row[0] = 9
|
||||
first[0] = 8
|
||||
pending = queue.pending()
|
||||
pending[:] = 12
|
||||
for _ in range(17):
|
||||
queue.push(np.zeros(1, dtype=np.float32))
|
||||
self.assertEqual(queue.push(np.zeros(1, dtype=np.float32))[0], 3)
|
||||
|
||||
def test_zero_delay_return_owned(self):
|
||||
row = np.array([2], dtype=np.float32)
|
||||
value = TimingQueue(0, 1).push(row)
|
||||
value[0] = 99
|
||||
self.assertEqual(row[0], 2)
|
||||
|
||||
def test_signed_zero_subnormal_and_extreme_bits(self):
|
||||
row = np.array([0x80000000, 1, 0x7f7fffff], dtype=np.uint32).view(np.float32)
|
||||
for delay in (0, 18):
|
||||
queue = TimingQueue(delay, 3)
|
||||
generated = np.stack([row.copy() for _ in range(23)])
|
||||
applied = np.stack([queue.push(x) for x in generated])
|
||||
audit_sequence(generated, applied, queue.pending(), delay)
|
||||
|
||||
def test_reject_invalid_configuration(self):
|
||||
for delay in (True, 1, -18, 18.0, np.int32(18)):
|
||||
with self.assertRaises(ValueError):
|
||||
TimingQueue(delay)
|
||||
for width in (0, -1, True, 2.0):
|
||||
with self.assertRaises(ValueError):
|
||||
TimingQueue(18, width)
|
||||
|
||||
def test_reject_invalid_sample_without_advance(self):
|
||||
queue = TimingQueue(18, 2)
|
||||
bad = [np.ones(2, dtype=np.float64), np.ones((1, 2), dtype=np.float32),
|
||||
np.ones(3, dtype=np.float32), [1, 2],
|
||||
np.array([np.nan, 1], dtype=np.float32), np.array([np.inf, 1], dtype=np.float32),
|
||||
np.array([-1, 1], dtype=np.float32)]
|
||||
for row in bad:
|
||||
with self.assertRaises(ValueError):
|
||||
queue.push(row)
|
||||
self.assertEqual(queue.steps, 0)
|
||||
|
||||
def test_tick_must_be_consecutive(self):
|
||||
queue = TimingQueue(0, 1)
|
||||
row = np.zeros(1, dtype=np.float32)
|
||||
for tick in (1, True, -1, .0):
|
||||
with self.assertRaises(ValueError):
|
||||
queue.push(row, tick=tick)
|
||||
queue.push(row, tick=0)
|
||||
with self.assertRaises(ValueError):
|
||||
queue.push(row, tick=0)
|
||||
self.assertEqual(queue.steps, 1)
|
||||
|
||||
def test_audit_detects_shift_or_initial_negative_zero(self):
|
||||
_, generated, applied, pending = self.sequence(18, 40)
|
||||
applied[18, 0] += 1
|
||||
with self.assertRaises(ValueError):
|
||||
audit_sequence(generated, applied, pending, 18)
|
||||
_, generated, applied, pending = self.sequence(18, 40)
|
||||
applied.view(np.uint32)[0, 0] = 0x80000000
|
||||
with self.assertRaises(ValueError):
|
||||
audit_sequence(generated, applied, pending, 18)
|
||||
|
||||
def test_audit_rejects_invalid_schema(self):
|
||||
_, generated, applied, pending = self.sequence(18, 2)
|
||||
for g, a, p in ((generated.astype(np.float64), applied, pending),
|
||||
(generated, applied[:1], pending), (generated, applied, pending[:2])):
|
||||
with self.assertRaises(ValueError):
|
||||
audit_sequence(g, a, p, 18)
|
||||
|
||||
def test_finite_sequence_sum_preserved_without_brain_tail(self):
|
||||
queue, generated, applied, pending = self.sequence(18, 53)
|
||||
np.testing.assert_array_equal(applied.sum(axis=0, dtype=np.float64) + pending.sum(axis=0, dtype=np.float64),
|
||||
generated.sum(axis=0, dtype=np.float64))
|
||||
self.assertEqual(queue.steps, 53)
|
||||
self.assertFalse(queue.metadata()["brain_tail_advanced"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,87 @@
|
||||
"""Pure NumPy reference for the fixed 0/18-step generated-APL output delay.
|
||||
|
||||
No brain dynamics, gain, float arithmetic or implicit tail advancement occurs
|
||||
here. Pending rows are the next D application slots, including initial +0
|
||||
padding if fewer than D samples have been generated.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
|
||||
class TimingQueue:
|
||||
def __init__(self, delay_steps, width=4064):
|
||||
if type(delay_steps) is not int or delay_steps not in (0, 18):
|
||||
raise ValueError("delay_steps must be integer 0 or 18")
|
||||
if type(width) is not int or width <= 0:
|
||||
raise ValueError("width must be a positive integer")
|
||||
self.delay_steps = delay_steps
|
||||
self.width = width
|
||||
self.steps = 0
|
||||
self._ring = np.zeros((delay_steps, width), dtype=np.float32)
|
||||
|
||||
def push(self, generated, tick=None):
|
||||
"""Return an owned f32 application row, without changing its bits."""
|
||||
if tick is not None and (type(tick) is not int or tick != self.steps):
|
||||
raise ValueError("tick must be the next consecutive integer tick")
|
||||
if not isinstance(generated, np.ndarray) or generated.dtype != np.float32:
|
||||
raise ValueError("generated must be a float32 ndarray")
|
||||
if generated.shape != (self.width,) or not np.isfinite(generated).all():
|
||||
raise ValueError("generated shape or finiteness invalid")
|
||||
if np.any(generated < 0):
|
||||
raise ValueError("generated inhibition must be nonnegative")
|
||||
if self.delay_steps == 0:
|
||||
applied = generated.copy()
|
||||
else:
|
||||
slot = self.steps % self.delay_steps
|
||||
applied = self._ring[slot].copy()
|
||||
self._ring[slot] = generated
|
||||
self.steps += 1
|
||||
return applied
|
||||
|
||||
def pending(self):
|
||||
"""Owned rows in next-application order; always shape (D, width)."""
|
||||
if self.delay_steps == 0:
|
||||
return self._ring.copy()
|
||||
head = self.steps % self.delay_steps
|
||||
return np.concatenate((self._ring[head:], self._ring[:head]), axis=0)
|
||||
|
||||
def metadata(self):
|
||||
return {"apl_delay_steps": self.delay_steps, "steps": self.steps,
|
||||
"pending_generated_count": min(self.steps, self.delay_steps),
|
||||
"pending_initial_zero_prefix": max(self.delay_steps - self.steps, 0),
|
||||
"pending_application_tick_start": self.steps,
|
||||
"pending_application_tick_stop_exclusive": self.steps + self.delay_steps,
|
||||
"brain_tail_advanced": False}
|
||||
|
||||
|
||||
def audit_sequence(generated, applied, pending, delay_steps):
|
||||
"""Bitwise finite-sequence accounting; not a closed-loop integral claim.
|
||||
|
||||
Checks concat(applied, pending) == concat(D initial +0 rows, generated).
|
||||
This preserves signed zeros and every stored sample, without assuming the
|
||||
generated sequences of two different closed-loop branches are identical.
|
||||
"""
|
||||
if type(delay_steps) is not int or delay_steps not in (0, 18):
|
||||
raise ValueError("delay_steps must be integer 0 or 18")
|
||||
for name, value in (("generated", generated), ("applied", applied), ("pending", pending)):
|
||||
if not isinstance(value, np.ndarray) or value.dtype != np.float32 or value.ndim != 2:
|
||||
raise ValueError(name + " must be a rank-2 float32 ndarray")
|
||||
if not np.isfinite(value).all() or np.any(value < 0):
|
||||
raise ValueError(name + " must be finite nonnegative inhibition")
|
||||
if generated.shape != applied.shape or pending.shape != (delay_steps, generated.shape[1]):
|
||||
raise ValueError("Sequence shapes disagree")
|
||||
steps = len(generated)
|
||||
# Rowwise comparison avoids allocating a second T-by-K trace.
|
||||
for tick in range(steps + delay_steps):
|
||||
actual = applied[tick] if tick < steps else pending[tick - steps]
|
||||
actual_bits = actual.view(np.uint32)
|
||||
if tick < delay_steps:
|
||||
ok = np.all(actual_bits == 0)
|
||||
else:
|
||||
ok = np.array_equal(actual_bits, generated[tick - delay_steps].view(np.uint32))
|
||||
if not ok:
|
||||
raise ValueError("Fixed-delay bitwise accounting failed at application tick " + str(tick))
|
||||
return {"pass_all": True, "apl_delay_steps": delay_steps, "steps": steps,
|
||||
"pending_generated_count": min(steps, delay_steps),
|
||||
"pending_initial_zero_prefix": max(delay_steps - steps, 0),
|
||||
"initial_padding_positive_zero": True, "sample_bits_preserved": True,
|
||||
"brain_tail_advanced": False}
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Run unchanged frozen reference into new output; never rewrite old evidence."""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import ROOT, EXP, CFG, save, check, bit_equal
|
||||
|
||||
|
||||
def main():
|
||||
check()
|
||||
assert not (EXP / 'regression/frozen-five.json').exists()
|
||||
rtpath = ROOT / 'models/male-cns-lif/deterministic-runtime-v2'
|
||||
pc = yaml.safe_load((rtpath / 'phase4p.yaml').read_text())
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = CFG['gpu_uuid']
|
||||
os.environ['CUDA_HOME'] = '/usr/local/cuda'
|
||||
os.environ['PATH'] = str(ROOT / 'models/male-cns-lif/.venv/bin') + ':/usr/local/cuda/bin:' + os.environ['PATH']
|
||||
os.environ['MAX_JOBS'] = '2'
|
||||
os.environ['TORCH_CUDA_ARCH_LIST'] = '8.9'
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import queues
|
||||
queues()
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif'))
|
||||
sys.path.insert(0, str(rtpath))
|
||||
import torch
|
||||
import phase4p
|
||||
assert torch.cuda.mem_get_info()[0] > 2 * 1024**3
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
started = time.perf_counter()
|
||||
runtime = phase4p.CanonicalRuntime(pc, rtpath, EXP)
|
||||
rows = []
|
||||
for spec in pc['reference_trials']:
|
||||
queues()
|
||||
base = ROOT / 'experiments/phase4p-deterministic-runtime/phase4c-v2/reference' / spec['name']
|
||||
events = pd.read_parquet(base / 'spike-events.parquet')
|
||||
reference = dict(step=events.step.to_numpy(), neuron_idx=events.neuron_idx.to_numpy(),
|
||||
counts=np.load(base / 'per-neuron-spike-counts.int32.npy'),
|
||||
v=np.load(base / 'final-voltage.float32.npy'), g=np.load(base / 'final-g.float32.npy'))
|
||||
result = runtime.run(np.flatnonzero(runtime.pops[spec['population']]), spec['rate_hz'], spec['seed'], spec['duration_ms'])
|
||||
row = dict(trial=spec['name'], exact={key: bool(bit_equal(value, result[key])) for key, value in reference.items()},
|
||||
dtype_shape={key: dict(reference_dtype=str(value.dtype), actual_dtype=str(result[key].dtype),
|
||||
reference_shape=list(value.shape), actual_shape=list(result[key].shape)) for key, value in reference.items()},
|
||||
wall_seconds=result['metrics']['wall_seconds'])
|
||||
rows.append(row)
|
||||
print(row, flush=True)
|
||||
passed = all(all(row['exact'].values()) for row in rows)
|
||||
save(EXP / 'regression/frozen-five.json', dict(pass_all=passed, trials=rows,
|
||||
wall_seconds=time.perf_counter() - started, peak_allocated_vram=int(torch.cuda.max_memory_allocated()),
|
||||
oracle='Old frozen deterministic-v2 only; not this independent dynamics branch'))
|
||||
assert passed
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,81 @@
|
||||
# Phase 4AK — frozen D18 validation, not learning
|
||||
|
||||
Prerequisite: the entire Phase 4AJ fixed-CAL experiment was completed, sealed
|
||||
and published at cb6937666f96b00ae8d435a2cfb2f417ad25c040.
|
||||
Its PASS is not evidence of usable learning. This stage tests the next necessary
|
||||
representation-generalization gate without adjusting the successful branch.
|
||||
|
||||
## No new brain dynamics
|
||||
|
||||
Call the exact SHA-pinned AJ GPU, Brian oracle, comparison and timing code from
|
||||
their original absolute paths. Do not copy or patch their numerical functions,
|
||||
globals, signs, weights, thresholds, APL gain/tau, refractory or delay queue.
|
||||
Keep dt=0.1 ms, anatomical delay=18 steps, and whole generated graded-APL h
|
||||
applied 18 steps later. Preserve positive-zero initialization and pending tail;
|
||||
never advance brain time to drain the tail. Shared APL arithmetic limits the
|
||||
oracle independence claim. The 5845 fast-APL output mask, including 1212
|
||||
unreplaced non-KC edges, is unchanged. No biological measurement claim.
|
||||
|
||||
## Input registration and data boundary
|
||||
|
||||
Read only the four SHA-pinned original static 4L encoder files (panel, vectors,
|
||||
selection, mapping) and pinned encoder configuration/source. Extract only the
|
||||
unchanged pn_drive function AST; do not import or execute unrelated learning
|
||||
experiments. Validate the extraction and request generator against all 60 old
|
||||
AG CAL request/PN-drive arrays before generating any new brain trajectory.
|
||||
Keep the original 12-odor median 2.556107997894287 and original target/RNG column
|
||||
order. Matched energy is expected input, not exactly equal realized events.
|
||||
|
||||
Predeclare both 6-odor x2-mode x5-seed blocks, every sparse request schedule,
|
||||
source hash, gate, and stop rule before any new brain trajectory. Seeds are the
|
||||
fixed first five integers above 2^30, subject to the enumerated static collision
|
||||
audit. They are not chosen using simulation outcomes. Do not claim exhaustive
|
||||
historical uniqueness, statistical independence of RNG streams, or biological
|
||||
replication. Same seed across odors preserves the existing paired comparisons.
|
||||
|
||||
The second six odors are novel to AG/AJ calibration, not never simulated in
|
||||
project history: old 4L traversed the full panel. Never open old heldout dynamic
|
||||
results, mixed old result tables or conditioning/plasticity reports to choose
|
||||
inputs. Full frozen-integrity hashing may read opaque bytes for checksums; that
|
||||
is not permission to parse those outcomes. Static A/B identities are not used
|
||||
to select a favorable subset. No new resource download.
|
||||
|
||||
## Ordered execution
|
||||
|
||||
1. Freshly run the two original AJ D18 fullgraph probe inputs. Verify every
|
||||
scientific output against sealed AJ, allowing only output budget/path
|
||||
differences in spec. Do not fake D0 to use a D0-only helper. Old eight-fixture
|
||||
proofs are source-pinned inherited evidence, not newly rerun fixture claims.
|
||||
2. For original CAL odors on new seeds, fixed first odor/first seed in each mode
|
||||
is a Brian fullgraph probe, with independent LIF/own spikes/FIFO and the
|
||||
existing shared-APL component audit. Any numerical failure stops.
|
||||
3. Complete the 60 seed-validation trajectories plus 12 exact repeats. Reuse
|
||||
the two probe GPU runs rather than duplicate them as independent samples.
|
||||
4. Only if the entire seed block passes numerical, repeat and original
|
||||
representation gates, unlock the second block. Run the first static novel
|
||||
odor/first seed in each mode as two additional Brian probes, then complete
|
||||
that block's 60 trajectories and 12 repeats. All planned conditions remain
|
||||
visible; gated unexecuted cases are NOT PASS and are reported as such.
|
||||
5. Recompute raw evidence, rerun the five old frozen reference trials into new
|
||||
output, verify every frozen artifact, and seal a truthful result.
|
||||
|
||||
Each cohort/mode separately retains every original six-part 4L gate: mean KC
|
||||
fraction in [3%,9%], all runs >0 and <50%, same-odor Jaccard > different-odor,
|
||||
between-odor Euclidean > within-odor, and at least 3 of 15 odor pairs passing
|
||||
their own two-odor within baseline. Every mode has 60 within comparisons and
|
||||
75 between comparisons; each pair has 20 within and 5 between. No pooled
|
||||
12-odor rescue, seed replacement, post-hoc metric/window/gate changes or scans.
|
||||
|
||||
## Outcome scope
|
||||
|
||||
PASS-FROZEN-D18-INDEPENDENT-VALIDATION requires all four cohort/mode gates,
|
||||
complete data, fresh controls, oracle/component checks, exact repeats and old
|
||||
frozen integrity/reference PASS. Scientific failure remains FAIL; incomplete
|
||||
execution is not re-labelled scientific success. Passing does not generate a
|
||||
candidate, establish learning, prove delay biology or repair upstream AL loops.
|
||||
Next required gates remain credit specificity, real KC-to-MBON functional
|
||||
transfer, whole-brain stimulus-specific response, and controlled conditioning.
|
||||
|
||||
All outputs are new under phase4ak-apl-timing-validation. Previous Phase 1–4AJ,
|
||||
base CSR/W_syn/signs, eligibility-v2 and the 14232-edge plastic mask remain
|
||||
read-only. No training, reward interface, game, vision, decoder, RL or Web.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Phase 4AK:仅静态种子审计
|
||||
|
||||
状态:`NO-COLLISION-FOUND-IN-DECLARED-STATIC-SCOPE`。这是输入准备审计,不是科学运行、表征验证或学习 PASS。
|
||||
|
||||
## 固定选择
|
||||
|
||||
使用 `2^30` 后的首五个正整数:
|
||||
|
||||
```text
|
||||
1073741825, 1073741826, 1073741827, 1073741828, 1073741829
|
||||
```
|
||||
|
||||
它们均小于 `2^31`。选择只依据静态声明的历史 namespace 与固定算术规则;没有生成随机输入来挑选、没有检查活动或 separability,也没有读取旧 held-out 动态结果。
|
||||
|
||||
新运行直接把这些整数传给原始 CUDA generator,不增加 odor、mode 或验证块偏移。两验证块、气味及模式继续按已提交计划共享同一组 seeds,以保留配对设计;技术重复重用同一 seed。这不意味着跨气味、模式或验证块的随机流彼此独立,也不是生物重复。运行后不得因结果不利换 seeds。
|
||||
|
||||
## 本次覆盖范围
|
||||
|
||||
先阅读已提交的 `phase4aj-next-validation.md`,随后只审计本地静态源码、配置及 AG/AJ 输入清单。机器记录见同目录 `seed-audit.json`:
|
||||
|
||||
- 217 个静态文件,每个记录 workspace-relative 路径、字节数和 SHA-256。
|
||||
- 1,432 条 seed/RNG 相关源码或配置行保留准确行号;词法证据包括测试与非 RNG 的 `seed_node`,不把每一数字都当成真正 RNG seed。
|
||||
- 检索范围为文件中列出的 12 个 source 根目录内 `.py/.yaml/.yml/.json`,排除名字含 manifest/checksum 的文件;另纳入已提交计划与明确允许的 AG/AJ protocol 静态文件。
|
||||
- 完整文件文本未发现五个新种子的十进制字面值;已检查的配置及派生规则也未发现重用。
|
||||
- AG 和 AJ 的 `protocol/calibration-cases.json` 均为 60 项,seeds 为 `51101–51105`,odor indices 为 `0–5`,两份文件 SHA 完全相同:`d91994c77db82cb40a258c92b222e0d7b28338ffe98764bd600988dae6f72754`。
|
||||
|
||||
没有导入或执行旧源码,没有运行 GPU/脑实验,没有访问网络或远程服务器,没有打开旧 `kc-representation`、`eligible-edges`、`plasticity-magnitude`、`conditioning` 或包含它们结果的综合报告。源码中的旧路径字符串是静态文本,不代表这些路径的输出被读取。
|
||||
|
||||
## 不能漏掉的派生规则
|
||||
|
||||
| 静态来源 | 已声明规则/占用范围 |
|
||||
|---|---|
|
||||
| 4A–4D | 配置中的 `41xxx–44xxx`;4C visual seed 加 `1/2/3/4/10+j`;4D 还内联 `44301–44303`。 |
|
||||
| 4E | `45000 + SHA256(layer)前4字节模1000000`,再加 amplitude/rate/sparsity/window 派生偏移;保守保留完整 hash 输出范围,不读取旧最优条件。 |
|
||||
| 4F/4G | `46101–46103`、`47001–47005`、`47101–47103`、`47201–47205`、`47300`、`47400`、`47901/47902`。4G 的 `stable_seed` 定义被记录,但其源码没有调用,不能宣称已执行。 |
|
||||
| 4H–4K | 训练 seed 加 trial index;4J calibration base 加 odor index、evaluation seed 加 `odor_index*100`;4I 固定随机集合 seed `49301`;若干 DAN helper 接收 `seed+991`,显式 pulse 路径不实际抽样,仍保守记录。 |
|
||||
| 4L–4Q | `51201–51205 + pairing(1..100)`,包括 `ts+pairing` 别名;评估 `51301–51305`;4N random-US 再加 `700000`。`512xx/513xx` 不可当作空白 namespace。 |
|
||||
| 4P/4Q/4R | 旧 odor seed 有 `seed+odor_index*100`;`52101–52105` 和 `52201–52205` 已明确声明。random-US 包括 `930000/950000+actual`;4Q formal 还使用 `940000+train_seed*1000+pairing`。 |
|
||||
| 4S | `54101–54105` 或 `55101–55105 + odor_index*100000`;control 加 `975000`;formal `[61000,62000,63000,64000,65000]+pairing`,评估 `71001–71005`。 |
|
||||
| 4T | `86101–86120` 或 `87101–87120 + odor_index*1000000`;live-US RNG 加 `9700000`。 |
|
||||
| 4U | `50000000 + odor_index*2000000 + seed`,seed 为 `118101–118120` 或 `119101–119120`;control 另加 `29000000`。按已声明最多 12 气味取保守 index `0..11`,最大列出参数为 `101119120`。 |
|
||||
| 4V/W/Y/Z、4AB terminal scope | `80000000 + odor_index*1000000 + seed + block*1000003`;seed 包括 `131101–131103/141101`。1000 ms 以 120 ms 分块,保守含 `block=0..8`;offset 模式也先生成这些块再清零,不能忽略 RNG 使用。 |
|
||||
| 4AE | 数值夹具固定 seed `410301`。 |
|
||||
| AG/AJ、AH/AI | AG/AJ 每 case 直接使用 `51101–51105`,没有旧 4P 的 odor 偏移;AH/AI 使用既有轨迹重建,并非新 RNG 流。AF/motif 的 `seed_node(s)` 是 neuron IDs,不是 RNG seeds。 |
|
||||
|
||||
这些固定配置和已解释公式保守保留在 `[0, 200000000]` 排除区间;这不是说区间内所有整数都实际用过。新种子在该区间之外。完整规则、源码路径和原始行见 JSON。
|
||||
|
||||
## SHA 与远端核验契约
|
||||
|
||||
本地 `phase4l.yaml` 的 SHA 与 AG-r2 已登记 `olfactory-grounding-v1/phase4l.yaml` 相同;本地 `phase4p_revalidate.py` 与已登记 `deterministic-runtime-v2/phase4p_revalidate.py` 相同。
|
||||
|
||||
JSON 的 `inspected_files` 提供 `local_path/sha256/remote_path`。仅对有确切模型相对路径且 SHA 匹配预注册的 AG/AJ sources、两个上述输入 pin,以及明确的 experiment/protocol 副本填写 remote_path,共 43 项。remote_path 均相对于 `/data/digital-drosophila`。其他本地平铺副本一律为 null,不能猜服务器子目录。
|
||||
|
||||
主流程应在注册前对非 null 远端路径再次校验 SHA,并执行自身的旧文件及 manifest-chain 完整性检查;本静态审计不替代服务器完整性验证。
|
||||
|
||||
## 严格限制
|
||||
|
||||
1. 未审计未列出的 server-only 文件、终端历史、CLI 覆写、其他分支/历史版本、外部包或其他用户的运行;因此不能说全历史绝对未用过。
|
||||
2. 配置中允许某 seed 不等于历史实际执行过;反之,任意 `reset(seed)` 或外部 caller 可以传入未在配置里列出的整数。词法检索与命名调用点复核不是完整 Python 数据流证明。
|
||||
3. 没有完整历史执行输入注册表;只审计 AG/AJ 两份明确静态 CAL 清单,没有读取旧动态输出来反推使用情况。
|
||||
4. 新整数不构成 PRNG 流统计独立性的数学证明。后六气味仅称“未用于 AG/AJ 校准”,不是项目历史从未运行过。
|
||||
5. 主代理同时创建的 Phase 4AK 文件不属于历史检索范围,避免候选自身造成字面碰撞;本记录必须在新轨迹前纳入预注册。
|
||||
|
||||
结论仅为:这五个值在所列静态证据及配置派生范围内未发现重用,符合本轮预声明选择规则。未开始验证块、conditioning、参数扫描或候选升级。
|
||||
@@ -0,0 +1,18 @@
|
||||
2d3996933b6e5a31e6a737ade5f3ae6869ffd03e01002c793eccf9b4f71e987d PROTOCOL.md
|
||||
35e8bfe1bff62f3a5d9295c785e1b8cec8a2393eaa67d6c683046e48f30feb56 SEED-AUDIT.md
|
||||
1718d8b01ece24930ff138f25c88f2f0e0f1cf69ad0ba655e5a0de5083e64df2 config.json
|
||||
5e4e500d5ec777bb8ae7ff77ac9a7279bfe995b1a17be2baededdbe065c98b73 context.py
|
||||
0061689abb1832e963718bd836bc8a50009ee38b258143a753b4d59e63414925 encoder_inputs.py
|
||||
7eb638c357cec7f5f85032312c50a616402b5a8a9ab5aa0268c181f6c3ae0ce5 finalize.py
|
||||
d8b8315f8800b3efdc7548620f0fae85da21a12b753b9985dd623bd529e2c50f prepare.py
|
||||
bba6c4a98c6edf1eaba60b8030a9eca646da7dd30b74b2a0c16e374efd022bfa run_case.py
|
||||
3d9890f11f299c07956faa5c0a9ebe2ea28ade58d8acf01a8631c17025073345 run_suite.py
|
||||
8a6629f8cd991cf309b508c670b3009737f9b5f5e1f72560fa5bd4b1ff0bc64d runtime_bridge.py
|
||||
e14c0deb94432787aa1cac7618254aeb3db0bcd5b1d8328e20428496d38b220a seed-audit.json
|
||||
f1b3b8a0ca9bc41152485c05fd5f24fb7bb1f76c6ea111ffb941ae1225d914fe test_encoder_inputs.py
|
||||
a85466891352c15b3103d30e7229734ad6694c8a1347d5e0747da3315223c203 test_final_status.py
|
||||
bc01a5ff38d79a7b66ab2eb9e6731164bb8a1e15b5b5c0c1655161c24e2c37cf test_runtime_bridge.py
|
||||
03f5aacb5a1f9460d10b30744100a43c57e5488d32dada79c888529943c82cf0 test_validation.py
|
||||
3464091a4cc2cba18946cb9c9b3d12a69bd3f79f1001096234b3a8467dc602c5 validation.py
|
||||
a736b774e39ff0ad863e8efc720bd5f9ed7d30261776bb57f0a716a7052dba5a verify_frozen.py
|
||||
3206a8666718b4a99e09b8384c87dd25895b5d7038d23fad7b93e40379183ccb manifest.json
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"phase": "4AK",
|
||||
"name": "apl-timing-validation-v1",
|
||||
"human_authorization": "允许独立新模型分支测试动力学假设",
|
||||
"gpu_uuid": "GPU-79f3a85d-adce-7833-7003-6488140ca596",
|
||||
"neurons": 166700,
|
||||
"edges": 25582938,
|
||||
"raw_weight_sum": 124177617,
|
||||
"apl_delay_steps": 18,
|
||||
"dt_ms": 0.1,
|
||||
"apl": {"tau_ms": 20.0, "gain": 0.01, "normalization": 1.0},
|
||||
"validation": {
|
||||
"seeds": [1073741825,1073741826,1073741827,1073741828,1073741829],
|
||||
"modes": ["physiological", "matched_energy"],
|
||||
"duration_ms": 100.0,
|
||||
"odor_count": 6,
|
||||
"repeat_first_seed_each_odor": true,
|
||||
"cohorts": ["seed_validation", "novel_to_aj"],
|
||||
"median_scope": "unchanged original frozen 12-odor panel",
|
||||
"median_mapped_response_sum": 2.556107997894287,
|
||||
"full_oracle_policy": "first static odor, first seed, each mode, for each cohort",
|
||||
"unlock_novel": "Complete seed_validation execution, representation and repeat gates must pass before any novel-to-AJ brain trajectory"
|
||||
},
|
||||
"gates": {"sparse_min_fraction": 0.03, "sparse_max_fraction": 0.09, "max_fraction_strict": 0.5, "min_fraction_strict": 0.0, "minimum_separable_odor_pairs": 3, "same_jaccard_gt_between": true, "between_euclidean_gt_within": true, "both_modes_required": true, "finite_required": true, "repeat_exact_required": true},
|
||||
"control_cases": ["cal-physiological-00-s51101", "cal-matched_energy-00-s51101"],
|
||||
"output_budget_bytes": 25769803776,
|
||||
"training": false,
|
||||
"candidate_generation": false,
|
||||
"old_heldout_dynamic_access": false,
|
||||
"new_validation_dynamic_execution": true,
|
||||
"historical_never_simulated_odor_claim": false,
|
||||
"universal_seed_independence_claim": false,
|
||||
"selection_policy": "Fixed source panel order, both cohorts and input schedules registered before any new trajectory. Stop on numerical failure or seed-block representation failure; retain all results, no replacement seeds/odors, no tuning, no pooled gate rescue.",
|
||||
"hypothesis": "The already frozen AJ D18 operator generalizes to new audited seeds and six odors not used in AG/AJ calibration. No change whatsoever to dynamics, input transform, temporal window, graph, signs, APL mask or metric definitions."
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
"""A new validation envelope; sealed brain implementations remain read-only."""
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
MODEL = Path(__file__).resolve().parent
|
||||
EXP = ROOT / 'experiments/phase4ak-apl-timing-validation'
|
||||
AJ_MODEL = ROOT / 'models/male-cns-lif/apl-timing-lab-v1'
|
||||
AJ = ROOT / 'experiments/phase4aj-apl-timing'
|
||||
AG_MODEL = ROOT / 'models/male-cns-lif/author-apl-lab-v2'
|
||||
AG = ROOT / 'experiments/phase4ag-author-apl-r2'
|
||||
COEFF = ROOT / 'experiments/phase4af-author-fullgraph/oracle/no_input/coefficients.json'
|
||||
STATIC = ROOT / 'experiments/phase4l-olfactory-grounding'
|
||||
CFG = json.loads((MODEL / 'config.json').read_text(encoding='utf-8'))
|
||||
PINS = {
|
||||
'models/male-cns-lif/apl-timing-lab-v1/manifest.json': '52b05dd1ff30b6b51ff960fe95904532f5c147731a83f0b5c31f98ca7e53c216',
|
||||
'experiments/phase4aj-apl-timing/manifest.json': '7f490ada0db30785c61954d8b92147378ddc8bc16b229fe3808bbe501caff44a',
|
||||
'models/male-cns-lif/phase4l.py': 'e97d58d8c1f44920d0e953f8f5831ddc86fa0198784e86460e315de4116ac553',
|
||||
'models/male-cns-lif/olfactory-grounding-v1/phase4l.yaml': '9b8bcb7e031bf9063033a1854a439a7cd0bf6730301c2d2d02ca99a76f098d6e',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/gpu_timing.py': 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/oracle_timing.py': '39d8c47c94bc2faddf016864a43e7715907fb987f56f7b27098060a88851f6be',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/compare_timing.py': '9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/timing_queue.py': '54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e',
|
||||
'models/male-cns-lif/apl-timing-lab-v1/calibration.py': 'fb15ddeaa108305c8de697bbae36b23ab0414a7c58db51c66dce9feb4a048a0d',
|
||||
'models/male-cns-lif/author-apl-lab-v2/calibration.py': 'edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c',
|
||||
'models/male-cns-lif/author-apl-lab-v2/validate_components.py': '5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3',
|
||||
'experiments/phase4l-olfactory-grounding/odor-encoder/frozen-odor-panel.parquet': '907f16a56e024a8e2112b5751e53b0e6037b329fa110098af3d31b119c10a1a0',
|
||||
'experiments/phase4l-olfactory-grounding/odor-encoder/mapped-glomerular-vectors.npz': 'f057563152e0fa22060080af13019f319e7f36563a775eff829bbc1929c451b0',
|
||||
'experiments/phase4l-olfactory-grounding/odor-encoder/selection.json': '1b832becf9d08935716fe9ba5e3cd3e619e7d208dfb43c4fa197c969d095f113',
|
||||
'experiments/phase4l-olfactory-grounding/receptor-mapping/olfactory-mapping.parquet': 'a01d27000533e9844458a4c559cac084f1c34ca20a1104e97e6a0f72b0cc93b1'
|
||||
}
|
||||
|
||||
|
||||
def digest(path):
|
||||
result = hashlib.sha256()
|
||||
with Path(path).open('rb') as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b''):
|
||||
result.update(block)
|
||||
return result.hexdigest()
|
||||
|
||||
|
||||
def load_module(name, path, expected_hash=None):
|
||||
path = Path(path)
|
||||
data = path.read_bytes()
|
||||
if expected_hash is not None:
|
||||
assert hashlib.sha256(data).hexdigest() == expected_hash, str(path)
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
exec(compile(data, str(path), 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def pinned(name, relative):
|
||||
return load_module(name, ROOT / relative, PINS[relative])
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as stream:
|
||||
json.dump(value, stream, indent=2, ensure_ascii=False, allow_nan=False)
|
||||
stream.write('\n')
|
||||
|
||||
|
||||
def sources():
|
||||
return {p.relative_to(MODEL).as_posix(): digest(p) for p in sorted(MODEL.rglob('*')) if p.is_file()
|
||||
and '__pycache__' not in p.parts and p.name not in ('manifest.json', 'checksums.sha256')}
|
||||
|
||||
|
||||
def integrity():
|
||||
old = load_module('sealed_aj_context_ak', AJ_MODEL / 'context.py')
|
||||
result = old.integrity()
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import manifest_check
|
||||
for path in (AJ_MODEL, AJ):
|
||||
result[str(path.relative_to(ROOT))] = manifest_check(path, digest)
|
||||
assert all(row['pass'] for row in result.values())
|
||||
return result
|
||||
|
||||
|
||||
def check():
|
||||
registered = read(EXP / 'protocol/preregistered.json')
|
||||
assert registered['sources'] == sources(), 'Registered validation source changed'
|
||||
assert registered['config'] == CFG
|
||||
for row in registered['inputs']:
|
||||
assert digest(ROOT / row['path']) == row['sha256'], row['path']
|
||||
for row in read(EXP / 'protocol/prepared-hashes.json'):
|
||||
assert digest(EXP / row['path']) == row['sha256'], row['path']
|
||||
return registered
|
||||
|
||||
|
||||
def budget():
|
||||
size = sum(p.stat().st_size for p in EXP.rglob('*') if p.is_file())
|
||||
assert size < CFG['output_budget_bytes'], 'Output budget exceeded; preserve partial evidence'
|
||||
return size
|
||||
|
||||
|
||||
def seal(root):
|
||||
rows = [dict(path=p.relative_to(root).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(root.rglob('*')) if p.is_file() and '__pycache__' not in p.parts]
|
||||
save(root / 'manifest.json', dict(root=str(root), files=rows))
|
||||
with (root / 'checksums.sha256').open('x', encoding='utf-8') as stream:
|
||||
for row in rows:
|
||||
stream.write(row['sha256'] + ' ' + row['path'] + '\n')
|
||||
stream.write(digest(root / 'manifest.json') + ' manifest.json\n')
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
return a.dtype == b.dtype and a.shape == b.shape and a.tobytes() == b.tobytes()
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Static-only frozen encoder extraction and exact request-schedule generation."""
|
||||
import ast
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import numpy as np
|
||||
|
||||
|
||||
def frozen_pn_drive(path, expected_sha):
|
||||
"""Execute the exact one pure function, not unrelated historical experiments."""
|
||||
import pandas as pd
|
||||
source = Path(path).read_bytes()
|
||||
assert hashlib.sha256(source).hexdigest() == expected_sha
|
||||
tree = ast.parse(source, filename=str(path))
|
||||
nodes = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == 'pn_drive']
|
||||
assert len(nodes) == 1 and not nodes[0].decorator_list
|
||||
module = ast.Module(body=nodes, type_ignores=[])
|
||||
namespace = {'np': np, 'pd': pd, 'Any': Any}
|
||||
exec(compile(module, str(path), 'exec'), namespace)
|
||||
return namespace['pn_drive'], hashlib.sha256(ast.dump(nodes[0], include_attributes=False).encode()).hexdigest()
|
||||
|
||||
|
||||
def requests_from_forced(forced, targets):
|
||||
targets = np.asarray(targets)
|
||||
assert targets.dtype == np.int32 and targets.ndim == 1 and len(np.unique(targets)) == len(targets)
|
||||
assert forced.dtype == np.bool_ and forced.ndim == 2 and forced.shape[1] == len(targets)
|
||||
ticks, columns = np.nonzero(forced)
|
||||
requests = np.column_stack([ticks, targets[columns]]).astype(np.int32)
|
||||
if len(requests):
|
||||
requests = requests[np.lexsort((requests[:, 1], requests[:, 0]))]
|
||||
assert np.all(np.diff(requests[:, 0].astype(np.int64) * 166700 + requests[:, 1]) > 0)
|
||||
return requests
|
||||
|
||||
|
||||
def make_requests(targets, rates, seed, steps=1000):
|
||||
import torch
|
||||
assert rates.dtype == np.float32 and rates.shape == targets.shape
|
||||
assert np.isfinite(rates).all() and np.all(rates >= 0) and steps == 1000
|
||||
original = rates * .1 / 1000.0
|
||||
explicit = rates * np.float32(.1) / np.float32(1000.)
|
||||
assert original.dtype == np.float32 and np.array_equal(original.view(np.uint32), explicit.view(np.uint32))
|
||||
assert np.all(original <= 1)
|
||||
generator = torch.Generator(device='cuda')
|
||||
generator.manual_seed(int(seed))
|
||||
probabilities = torch.as_tensor(original, dtype=torch.float32, device='cuda')
|
||||
forced = (torch.rand((steps, len(targets)), generator=generator, device='cuda') < probabilities[None, :]).cpu().numpy()
|
||||
return requests_from_forced(forced, targets)
|
||||
|
||||
|
||||
def write_npz(path, **arrays):
|
||||
with Path(path).open('xb') as stream:
|
||||
np.savez_compressed(stream, **arrays)
|
||||
@@ -0,0 +1,170 @@
|
||||
"""Recompute the new evidence, preserve gated non-execution, then seal truthfully."""
|
||||
from context import ROOT, MODEL, EXP, AJ, AJ_MODEL, AG, CFG, read, save, digest, check, integrity, budget, seal
|
||||
|
||||
|
||||
def optional(path):
|
||||
return read(path) if path.exists() else None
|
||||
|
||||
|
||||
def decide_status(*, numerical, seed, novel, integrity_pass, reference_pass, reference_present,
|
||||
known_numerical_failure=False, unauthorized_novel=False):
|
||||
if not integrity_pass or (reference_present and not reference_pass):
|
||||
return 'FAIL-FROZEN-INTEGRITY-OR-REGRESSION'
|
||||
if known_numerical_failure or unauthorized_novel:
|
||||
return 'FAIL-NUMERICAL-OR-EXECUTION-POLICY'
|
||||
if not numerical or not reference_present or not seed or not seed['execution_gate_pass']:
|
||||
return 'INCOMPLETE-VALIDATION-EXECUTION'
|
||||
if not seed['representation_gate_pass']:
|
||||
return 'FAIL-SEED-VALIDATION-REPRESENTATION'
|
||||
if not novel or not novel['execution_gate_pass']:
|
||||
return 'INCOMPLETE-VALIDATION-EXECUTION'
|
||||
if not novel['representation_gate_pass']:
|
||||
return 'FAIL-NOVEL-ODOR-REPRESENTATION'
|
||||
return 'PASS-FROZEN-D18-INDEPENDENT-VALIDATION'
|
||||
|
||||
|
||||
def component_report(group, name):
|
||||
path = EXP / 'component-audit' / group / (name + '.json')
|
||||
if not path.exists():
|
||||
return None
|
||||
report = read(path)
|
||||
output = EXP / 'gpu' / group / (name + '-r0')
|
||||
assert set(report.get('input_sha256', {})) == {'state.npz', 'summary.json',
|
||||
'apl-drive.float32.npy', 'apl-state.float32.npy', 'apl-inhibition.float32.npy'}
|
||||
for filename, checksum in report['input_sha256'].items():
|
||||
assert digest(output / filename) == checksum
|
||||
assert set(report.get('matrix_checks', {})) == {'input_matrix', 'output_matrix', 'input_raw', 'output_raw',
|
||||
'kc_globals', 'apl_globals', 'pn_globals', 'apl_out_ids', 'input_scale', 'output_scale'}
|
||||
assert set(report.get('ast_replay_bitwise_exact', {})) == {'drive', 'state', 'inhibition'}
|
||||
assert set(report.get('float64_measurement', {})) == {'drive', 'state', 'inhibition'}
|
||||
actual = bool(not report.get('error') and all(report['matrix_checks'].values()) and
|
||||
all(report['ast_replay_bitwise_exact'].values()) and report['theoretical_bound']['applicable'] and
|
||||
all(value['bound_violations'] == 0 for value in report['float64_measurement'].values()))
|
||||
assert report['pass_all'] == actual
|
||||
return actual
|
||||
|
||||
|
||||
def main():
|
||||
from pathlib import Path
|
||||
import runtime_bridge as bridge
|
||||
from validation import summarize
|
||||
check(); budget()
|
||||
controls_inventory, records = bridge.inventories(EXP, CFG)
|
||||
controls, comparisons, components, repeats, summaries = {}, {}, {}, {}, {}
|
||||
for row in controls_inventory:
|
||||
name = row['name']
|
||||
path = EXP / 'audit' / ('control-' + name + '.json')
|
||||
if path.exists():
|
||||
actual = bridge.control_identity(EXP / 'gpu/controls' / (name + '-r0'), Path(row['source_output']),
|
||||
EXP / 'cases/controls' / name, Path(row['source_case']), None)
|
||||
assert actual == read(path), 'Raw control evidence changed'
|
||||
controls[name] = actual['pass_all']
|
||||
component = component_report('controls', name)
|
||||
if component is not None:
|
||||
components['controls/' + name] = component
|
||||
gates = {stage: optional(EXP / 'audit' / (stage + '-gate.json')) for stage in bridge.STAGES}
|
||||
for cohort in CFG['validation']['cohorts']:
|
||||
summary_path = EXP / 'validation' / cohort / 'summary.json'
|
||||
if summary_path.exists():
|
||||
actual = summarize(cohort, out_json=None)
|
||||
assert actual == read(summary_path), 'Raw validation evidence changed: ' + cohort
|
||||
summaries[cohort] = actual
|
||||
for row in bridge.probe_rows(records, CFG, cohort):
|
||||
name = row['name']
|
||||
path = EXP / 'audit' / ('compare-' + cohort + '-' + name + '.json')
|
||||
if path.exists():
|
||||
actual = bridge.compare(EXP / 'cases' / cohort / name, EXP / 'gpu' / cohort / (name + '-r0'),
|
||||
EXP / 'oracle' / cohort / name, None)
|
||||
assert actual == read(path), 'Raw oracle evidence changed'
|
||||
comparisons[cohort + '/' + name] = actual['pass_all']
|
||||
component = component_report(cohort, name)
|
||||
if component is not None:
|
||||
components[cohort + '/' + name] = component
|
||||
for row in bridge.cohort_rows(records, CFG, cohort):
|
||||
if row['seed'] != CFG['validation']['seeds'][0]:
|
||||
continue
|
||||
name = row['name']
|
||||
path = EXP / 'audit' / ('repeat-' + cohort + '-' + name + '.json')
|
||||
if path.exists():
|
||||
actual = bridge.compare_gpu_repeats(EXP / 'gpu' / cohort / (name + '-r0'),
|
||||
EXP / 'gpu' / cohort / (name + '-r1'), None)
|
||||
assert actual == read(path), 'Raw repeat evidence changed'
|
||||
repeats[cohort + '/' + name] = actual['pass_all']
|
||||
seed, novel = summaries.get('seed_validation'), summaries.get('novel_to_aj')
|
||||
seed_pass = bool(seed and seed['execution_gate_pass'] and seed['representation_gate_pass'])
|
||||
novel_paths = [str(path.relative_to(EXP)) for root in ('gpu', 'oracle', 'component-audit')
|
||||
for path in (EXP / root / 'novel_to_aj').glob('*')]
|
||||
unauthorized = bool(novel_paths and not seed_pass)
|
||||
required_cohorts = ['seed_validation', 'novel_to_aj'] if seed_pass else ['seed_validation']
|
||||
expected_controls = {row['name'] for row in controls_inventory}
|
||||
expected_comparisons = {cohort + '/' + row['name'] for cohort in required_cohorts
|
||||
for row in bridge.probe_rows(records, CFG, cohort)}
|
||||
expected_components = expected_comparisons | {'controls/' + name for name in expected_controls}
|
||||
expected_repeats = {cohort + '/' + row['name'] for cohort in required_cohorts
|
||||
for row in bridge.cohort_rows(records, CFG, cohort) if row['seed'] == CFG['validation']['seeds'][0]}
|
||||
required_stages = ['controls', 'seed-probes', 'seed-validation'] + (['novel-probes', 'novel-validation'] if seed_pass else [])
|
||||
numerical = bool(set(controls) == expected_controls and set(comparisons) == expected_comparisons and
|
||||
set(components) == expected_components and set(repeats) == expected_repeats and
|
||||
all(all(values.values()) for values in (controls, comparisons, components, repeats)) and
|
||||
all(gates[stage] and gates[stage]['pass_all'] for stage in required_stages) and
|
||||
all(cohort in summaries and summaries[cohort]['execution_gate_pass'] for cohort in required_cohorts))
|
||||
for cohort, summary in summaries.items():
|
||||
stage = 'seed-validation' if cohort == 'seed_validation' else 'novel-validation'
|
||||
if gates[stage]:
|
||||
assert gates[stage]['execution_gate_pass'] == summary['execution_gate_pass']
|
||||
assert gates[stage]['representation_gate_pass'] == summary['representation_gate_pass']
|
||||
frozen = optional(EXP / 'regression/frozen-five.json')
|
||||
integrity_result = integrity()
|
||||
save(EXP / 'regression/old-integrity-after.json', integrity_result)
|
||||
oldpass = all(row['pass'] for row in integrity_result.values())
|
||||
failures = [read(path) for path in sorted((EXP / 'audit').glob('stopped-*.json'))]
|
||||
known_failure = bool(any(not value for group in (controls, comparisons, components, repeats) for value in group.values()) or
|
||||
any(gate is not None and not gate['pass_all'] for gate in gates.values()) or
|
||||
any(not summary['execution_gate_pass'] for summary in summaries.values()) or
|
||||
any(row.get('stop_kind') == 'execution_or_numerical_failure' for row in failures))
|
||||
status = decide_status(numerical=numerical, seed=seed, novel=novel, integrity_pass=oldpass,
|
||||
reference_present=frozen is not None, reference_pass=bool(frozen and frozen['pass_all']),
|
||||
known_numerical_failure=known_failure, unauthorized_novel=unauthorized)
|
||||
present_runs = []
|
||||
for path in sorted((EXP / 'gpu').glob('*/*/summary.json')):
|
||||
value = read(path)
|
||||
present_runs.append(dict(group=path.parent.parent.name, run=path.parent.name,
|
||||
complete=value.get('complete', False), pass_all=value.get('pass_all', False),
|
||||
**{key: value.get(key) for key in ('case', 'nodes', 'edges', 'total_spikes',
|
||||
'diagnostic_simulation_seconds', 'peak_allocated_vram_bytes')}))
|
||||
completed_primary = [row for row in present_runs if row['group'] in CFG['validation']['cohorts']
|
||||
and row['run'].endswith('-r0') and row['complete']]
|
||||
result = dict(status=status, numerical_gate_pass=numerical, summaries=summaries, gates=gates,
|
||||
controls=controls, comparisons=comparisons, components=components, repeats=repeats, stopped=failures,
|
||||
seed_validation_pass=seed_pass, novel_to_aj_pass=bool(novel and novel['pass_all']),
|
||||
novel_dynamics_executed=bool(novel_paths), novel_unlock_policy_violation=unauthorized,
|
||||
planned_primary_cases=120, actual_primary_cases=len(completed_primary), measurements=present_runs,
|
||||
aggregated_valid_primary_cases=sum(summary['valid_base_trials'] for summary in summaries.values()),
|
||||
actual_checked_repeats=sum(summary['checked_repeats'] for summary in summaries.values()),
|
||||
old_frozen_integrity_pass=oldpass, frozen_five_reference_pass=bool(frozen and frozen['pass_all']),
|
||||
unchanged_sealed_runtime=True, dynamics_changed=False, old_heldout_dynamic_results_parsed=False,
|
||||
novel_scope='Unused for AG/AJ calibration, not historically never simulated',
|
||||
shared_APL_arithmetic_oracle_limitation=True, biological_delay_measured=False,
|
||||
APL_nonKC_fast_edges_unreplaced=1212, learning_achieved=False, candidate_generated=False,
|
||||
full_scientific_goal_achieved=False, payload_bytes_before_manifest=budget(),
|
||||
next_gate='Credit specificity and real KC-to-MBON functional transfer before conditioning' if status.startswith('PASS-')
|
||||
else 'Inspect the first failed gate without tuning this frozen branch or replacing validation inputs')
|
||||
save(EXP / 'audit/final-status.json', result)
|
||||
with (EXP / 'REPORT.md').open('x', encoding='utf-8') as stream:
|
||||
stream.write('\n'.join(['# Phase 4AK — frozen D18 independent validation', '', f'Status: `{status}`.', '',
|
||||
f'Numerical gate: {numerical}. Seed-validation pass: {seed_pass}. Novel-to-AJ pass: {result["novel_to_aj_pass"]}.',
|
||||
f'Novel dynamics executed: {bool(novel_paths)}. Unauthorized unlock: {unauthorized}.',
|
||||
'Both cohorts and all requested inputs were locked before trajectories; no tuning or gate changes.',
|
||||
'Original sealed AJ runtime functions are used unchanged, including 1,212 unreplaced non-KC APL fast edges.',
|
||||
'The second six odors were not used for AG/AJ calibration; old 4L had simulated the full panel.',
|
||||
'Static seed audit is bounded, not proof of universally unused or statistically independent RNG streams.',
|
||||
'Brian LIF/FIFO is independent; APL arithmetic is intentionally shared and separately component-audited.',
|
||||
f'Old frozen integrity: {oldpass}. Five old reference trials: {result["frozen_five_reference_pass"]}.',
|
||||
'No learning, plasticity update, candidate, game, decoder, RL or Web. This is not completion of the learning goal.',
|
||||
'Next gate: ' + result['next_gate'], '']))
|
||||
check(); seal(MODEL); seal(EXP)
|
||||
print(status, flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"root": "/data/digital-drosophila/models/male-cns-lif/apl-timing-validation-v1",
|
||||
"files": [
|
||||
{
|
||||
"path": "PROTOCOL.md",
|
||||
"bytes": 5002,
|
||||
"sha256": "2d3996933b6e5a31e6a737ade5f3ae6869ffd03e01002c793eccf9b4f71e987d"
|
||||
},
|
||||
{
|
||||
"path": "SEED-AUDIT.md",
|
||||
"bytes": 6540,
|
||||
"sha256": "35e8bfe1bff62f3a5d9295c785e1b8cec8a2393eaa67d6c683046e48f30feb56"
|
||||
},
|
||||
{
|
||||
"path": "config.json",
|
||||
"bytes": 2183,
|
||||
"sha256": "1718d8b01ece24930ff138f25c88f2f0e0f1cf69ad0ba655e5a0de5083e64df2"
|
||||
},
|
||||
{
|
||||
"path": "context.py",
|
||||
"bytes": 5834,
|
||||
"sha256": "5e4e500d5ec777bb8ae7ff77ac9a7279bfe995b1a17be2baededdbe065c98b73"
|
||||
},
|
||||
{
|
||||
"path": "encoder_inputs.py",
|
||||
"bytes": 2431,
|
||||
"sha256": "0061689abb1832e963718bd836bc8a50009ee38b258143a753b4d59e63414925"
|
||||
},
|
||||
{
|
||||
"path": "finalize.py",
|
||||
"bytes": 11542,
|
||||
"sha256": "7eb638c357cec7f5f85032312c50a616402b5a8a9ab5aa0268c181f6c3ae0ce5"
|
||||
},
|
||||
{
|
||||
"path": "prepare.py",
|
||||
"bytes": 12405,
|
||||
"sha256": "d8b8315f8800b3efdc7548620f0fae85da21a12b753b9985dd623bd529e2c50f"
|
||||
},
|
||||
{
|
||||
"path": "run_case.py",
|
||||
"bytes": 3382,
|
||||
"sha256": "bba6c4a98c6edf1eaba60b8030a9eca646da7dd30b74b2a0c16e374efd022bfa"
|
||||
},
|
||||
{
|
||||
"path": "run_suite.py",
|
||||
"bytes": 7914,
|
||||
"sha256": "3d9890f11f299c07956faa5c0a9ebe2ea28ade58d8acf01a8631c17025073345"
|
||||
},
|
||||
{
|
||||
"path": "runtime_bridge.py",
|
||||
"bytes": 22443,
|
||||
"sha256": "8a6629f8cd991cf309b508c670b3009737f9b5f5e1f72560fa5bd4b1ff0bc64d"
|
||||
},
|
||||
{
|
||||
"path": "seed-audit.json",
|
||||
"bytes": 376926,
|
||||
"sha256": "e14c0deb94432787aa1cac7618254aeb3db0bcd5b1d8328e20428496d38b220a"
|
||||
},
|
||||
{
|
||||
"path": "test_encoder_inputs.py",
|
||||
"bytes": 2771,
|
||||
"sha256": "f1b3b8a0ca9bc41152485c05fd5f24fb7bb1f76c6ea111ffb941ae1225d914fe"
|
||||
},
|
||||
{
|
||||
"path": "test_final_status.py",
|
||||
"bytes": 2040,
|
||||
"sha256": "a85466891352c15b3103d30e7229734ad6694c8a1347d5e0747da3315223c203"
|
||||
},
|
||||
{
|
||||
"path": "test_runtime_bridge.py",
|
||||
"bytes": 23574,
|
||||
"sha256": "bc01a5ff38d79a7b66ab2eb9e6731164bb8a1e15b5b5c0c1655161c24e2c37cf"
|
||||
},
|
||||
{
|
||||
"path": "test_validation.py",
|
||||
"bytes": 17829,
|
||||
"sha256": "03f5aacb5a1f9460d10b30744100a43c57e5488d32dada79c888529943c82cf0"
|
||||
},
|
||||
{
|
||||
"path": "validation.py",
|
||||
"bytes": 21631,
|
||||
"sha256": "3464091a4cc2cba18946cb9c9b3d12a69bd3f79f1001096234b3a8467dc602c5"
|
||||
},
|
||||
{
|
||||
"path": "verify_frozen.py",
|
||||
"bytes": 2417,
|
||||
"sha256": "a736b774e39ff0ad863e8efc720bd5f9ed7d30261776bb57f0a716a7052dba5a"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
"""Lock both validation cohorts before outcomes; read no old novel-odor dynamics."""
|
||||
from datetime import datetime, timezone
|
||||
import ast
|
||||
import json
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import (ROOT, MODEL, EXP, AJ, AJ_MODEL, AG, CFG, PINS, STATIC,
|
||||
read, save, digest, sources, integrity, budget, bit_equal)
|
||||
from encoder_inputs import frozen_pn_drive, make_requests, write_npz
|
||||
|
||||
|
||||
def copy_exact(source, target):
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with source.open('rb') as source_stream, target.open('xb') as target_stream:
|
||||
shutil.copyfileobj(source_stream, target_stream)
|
||||
assert digest(source) == digest(target)
|
||||
|
||||
|
||||
def main():
|
||||
assert not EXP.exists() or not any(EXP.iterdir()), 'Never reuse a populated experiment'
|
||||
assert shutil.disk_usage(EXP.parent).free > CFG['output_budget_bytes'] + 10 * 1024**3
|
||||
for relative, expected in PINS.items():
|
||||
assert digest(ROOT / relative) == expected, relative
|
||||
previous = read(AJ / 'audit/final-status.json')
|
||||
assert previous['status'] == 'PASS-FIXED-CAL-APL-TIMING-HYPOTHESIS'
|
||||
assert previous['delay18_representation_pass'] and previous['old_frozen_integrity_and_five_reference_pass']
|
||||
assert CFG['apl'] == read(AJ_MODEL / 'config.json')['apl']
|
||||
assert CFG['gates'] == read(AJ_MODEL / 'config.json')['gates']
|
||||
assert CFG['apl_delay_steps'] == 18 and not CFG['training'] and not CFG['candidate_generation']
|
||||
seed_audit = read(MODEL / 'seed-audit.json')
|
||||
assert seed_audit['proposed_seeds'] == CFG['validation']['seeds'] and seed_audit['collision_found'] is False
|
||||
seed_sources = []
|
||||
for row in seed_audit['inspected_files']:
|
||||
if row.get('remote_path'):
|
||||
path = ROOT / row['remote_path']
|
||||
assert path.is_relative_to(ROOT) and '..' not in path.parts
|
||||
assert digest(path) == row['sha256'], row['remote_path']
|
||||
seed_sources.append(dict(path=path.relative_to(ROOT).as_posix(), sha256=row['sha256']))
|
||||
assert seed_sources, 'At least exact-mapped static seed sources must be checked on server'
|
||||
for name in ('protocol', 'cases', 'gpu', 'oracle', 'component-audit', 'audit', 'validation', 'regression', 'logs', 'extension-build'):
|
||||
(EXP / name).mkdir(parents=True, exist_ok=False)
|
||||
command = [sys.executable, '-B', '-m', 'unittest', 'discover', '-v', '-s', str(MODEL), '-p', 'test_*.py']
|
||||
test = subprocess.run(command, cwd=MODEL, text=True, capture_output=True)
|
||||
with (EXP / 'regression/unit-tests.log').open('x', encoding='utf-8') as stream:
|
||||
stream.write(test.stdout + test.stderr)
|
||||
save(EXP / 'regression/unit-tests.json', dict(command=command, exit_code=test.returncode,
|
||||
pass_all=test.returncode == 0, sha256=digest(EXP / 'regression/unit-tests.log')))
|
||||
assert test.returncode == 0
|
||||
inputs = {relative: expected for relative, expected in PINS.items()}
|
||||
for row in seed_sources:
|
||||
inputs[row['path']] = row['sha256']
|
||||
for path in (AJ / 'audit/final-status.json', AJ / 'protocol/preregistered.json',
|
||||
AG / 'protocol/calibration-cases.json', AG / 'protocol/encoder-audit.json'):
|
||||
inputs[path.relative_to(ROOT).as_posix()] = digest(path)
|
||||
save(EXP / 'protocol/preregistered.json', dict(timestamp_utc=datetime.now(timezone.utc).isoformat(),
|
||||
sources=sources(), config=CFG, inputs=[dict(path=path, sha256=sha) for path, sha in sorted(inputs.items())],
|
||||
parent_commit=subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip(),
|
||||
prior_AJ_CAL_outcomes_known=True, before_new_requests_and_brain_trajectories=True,
|
||||
training=False, candidate_generated=False, historical_never_simulated_odor_claim=False,
|
||||
seed_collision_audit_scope='Only enumerated static sources and explicitly evaluated derivations; no universal independence claim',
|
||||
shared_seed_across_conditions=True, new_dynamic_scope='seed_validation then conditionally novel_to_aj',
|
||||
source_engine='Unmodified SHA-pinned AJ backend functions, using original __file__ and globals',
|
||||
oracle_limit='Independent Brian LIF and delay FIFO, shared frozen Torch APL component',
|
||||
python=sys.version, machine=platform.machine(), numpy=np.__version__))
|
||||
save(EXP / 'regression/old-integrity-before.json', integrity())
|
||||
from run_case import preflight
|
||||
preflight()
|
||||
import torch
|
||||
cfg_path = ROOT / 'models/male-cns-lif/olfactory-grounding-v1/phase4l.yaml'
|
||||
oc = yaml.safe_load(cfg_path.read_text())
|
||||
assert oc['odor_encoder']['duration_ms'] == CFG['validation']['duration_ms']
|
||||
assert oc['odor_encoder']['modes'] == CFG['validation']['modes']
|
||||
panel = pd.read_parquet(STATIC / 'odor-encoder/frozen-odor-panel.parquet')
|
||||
assert len(panel) == 12 and panel.odor_id.is_unique
|
||||
assert panel.split.tolist() == ['calibration'] * 6 + ['heldout'] * 6
|
||||
median = float(np.median(panel.mapped_response_sum))
|
||||
assert median == CFG['validation']['median_mapped_response_sum']
|
||||
selection = read(STATIC / 'odor-encoder/selection.json')
|
||||
mapping = pd.read_parquet(STATIC / 'receptor-mapping/olfactory-mapping.parquet')
|
||||
with np.load(STATIC / 'odor-encoder/mapped-glomerular-vectors.npz', allow_pickle=False) as source:
|
||||
assert set(source.files) == set(panel.odor_id) | {'glomeruli'}
|
||||
assert source['glomeruli'].tolist() == selection['glomeruli_order']
|
||||
vectors = {oid: source[oid].copy() for oid in panel.odor_id}
|
||||
encoder_source = ROOT / 'models/male-cns-lif/phase4l.py'
|
||||
drive, ast_sha = frozen_pn_drive(encoder_source, PINS[str(encoder_source.relative_to(ROOT))])
|
||||
cached = {}
|
||||
static_rows = {}
|
||||
for cohort, frame in (('seed_validation', panel.iloc[:6]), ('novel_to_aj', panel.iloc[6:])):
|
||||
static_rows[cohort] = [dict(odor_id=str(odor.odor_id), odor_index=index,
|
||||
source_panel_split=str(odor.split)) for index, (_, odor) in enumerate(frame.iterrows())]
|
||||
for mode in CFG['validation']['modes']:
|
||||
for index, (_, odor) in enumerate(frame.iterrows()):
|
||||
targets, rates, ignored_frame = drive(str(odor.odor_id), vectors[odor.odor_id], mapping,
|
||||
selection['glomeruli_order'], mode, median, oc)
|
||||
assert targets.dtype == np.int32 and rates.dtype == np.float32 and len(targets) > 0
|
||||
assert len(np.unique(targets)) == len(targets) and np.all((targets >= 0) & (targets < CFG['neurons']))
|
||||
assert np.isfinite(rates).all() and np.all(rates > 0)
|
||||
cached[(cohort, mode, index)] = (targets, rates)
|
||||
# Reproduce all 60 known CAL input arrays, without any old heldout trajectory.
|
||||
control_rows = []
|
||||
old_inventory = read(AG / 'protocol/calibration-cases.json')
|
||||
assert len(old_inventory) == 60
|
||||
for row in old_inventory:
|
||||
targets, rates = cached[('seed_validation', row['mode'], row['odor_index'])]
|
||||
old_case = AG / 'cases' / row['name']
|
||||
spec = read(old_case / 'spec.json')
|
||||
assert spec['odor_id'] == static_rows['seed_validation'][row['odor_index']]['odor_id']
|
||||
with np.load(old_case / 'pn-drive.npz', allow_pickle=False) as old:
|
||||
assert bit_equal(targets, old['targets']) and bit_equal(rates, old['rates_hz'])
|
||||
requests = make_requests(targets, rates, row['seed'])
|
||||
with np.load(old_case / 'requests.npz', allow_pickle=False) as old:
|
||||
assert bit_equal(requests[:, 0], old['tick']) and bit_equal(requests[:, 1], old['neuron_idx'])
|
||||
control_rows.append(dict(case=row['name'], pass_all=True, seed=row['seed'],
|
||||
old_requests_sha256=digest(old_case / 'requests.npz'), old_drive_sha256=digest(old_case / 'pn-drive.npz'),
|
||||
target_rate_and_request_arrays_bitwise=True))
|
||||
save(EXP / 'protocol/encoder-control.json', dict(pass_all=True, cases=control_rows,
|
||||
old_heldout_dynamic_access=False, comparison='All original 60 CAL request/drive arrays; no trajectory or RNG-column reorder'))
|
||||
template = read(AJ / 'cases/delay18' / CFG['control_cases'][0] / 'spec.json')
|
||||
assert template['apl_delay_steps'] == 18 and template['steps'] == 1000 and template['record_history'] is False
|
||||
control_cases = []
|
||||
for name in CFG['control_cases']:
|
||||
source = AJ / 'cases/delay18' / name
|
||||
target = EXP / 'cases/controls' / name
|
||||
target.mkdir(parents=True, exist_ok=False)
|
||||
spec = read(source / 'spec.json')
|
||||
spec.update(budget_root=str(EXP), output_budget_bytes=CFG['output_budget_bytes'])
|
||||
save(target / 'spec.json', spec)
|
||||
for filename in ('requests.npz', 'pn-drive.npz'):
|
||||
copy_exact(source / filename, target / filename)
|
||||
control_cases.append(dict(name=name, source_case=str(source), source_output=str(AJ / 'gpu/delay18' / (name + '-r0'))))
|
||||
save(EXP / 'protocol/control-cases.json', control_cases)
|
||||
records = []
|
||||
for cohort, frame in (('seed_validation', panel.iloc[:6]), ('novel_to_aj', panel.iloc[6:])):
|
||||
for mode in CFG['validation']['modes']:
|
||||
for index, (_, odor) in enumerate(frame.iterrows()):
|
||||
targets, rates = cached[(cohort, mode, index)]
|
||||
for seed in CFG['validation']['seeds']:
|
||||
requests = make_requests(targets, rates, seed)
|
||||
name = f'{cohort}-{mode}-{index:02d}-s{seed}'
|
||||
target = EXP / 'cases' / cohort / name
|
||||
target.mkdir(parents=True, exist_ok=False)
|
||||
expected = float(rates.sum()) * 1000 * .1 / 1000.
|
||||
spec = dict(template, name=name, kind='fullgraph', validation_cohort=cohort,
|
||||
source_panel_split=str(odor.split), source_panel_row=int(odor.name),
|
||||
odor_id=str(odor.odor_id), odor_name=str(odor.odor_name), mode=mode, seed=seed,
|
||||
odor_index=index, expected_external_events=expected, target_count=len(targets),
|
||||
rate_sum_hz=float(rates.sum()), budget_root=str(EXP), output_budget_bytes=CFG['output_budget_bytes'])
|
||||
save(target / 'spec.json', spec)
|
||||
write_npz(target / 'requests.npz', tick=requests[:, 0], neuron_idx=requests[:, 1])
|
||||
write_npz(target / 'pn-drive.npz', targets=targets, rates_hz=rates)
|
||||
records.append(dict(name=name, cohort=cohort, source_panel_split=str(odor.split),
|
||||
source_panel_row=int(odor.name), mode=mode, odor_id=str(odor.odor_id), odor_index=index,
|
||||
seed=seed, targets=len(targets), requests=len(requests), expected_external_events=expected,
|
||||
rates_sum=float(rates.sum())))
|
||||
assert len(records) == 120 and len({row['name'] for row in records}) == 120
|
||||
save(EXP / 'protocol/validation-cases.json', records)
|
||||
save(EXP / 'protocol/encoder-audit.json', dict(panel_count=12, cal_count=6, novel_to_aj_count=6,
|
||||
median_mapped_response_sum=median, seeds=CFG['validation']['seeds'],
|
||||
seeds_static_audit_sha256=digest(MODEL / 'seed-audit.json'), cohorts_static_panel_rows=static_rows,
|
||||
requested_before_any_dynamics=True, historical_never_simulated_claim=False,
|
||||
old_heldout_dynamic_access=False, seed_namespace_universally_independent_claim=False,
|
||||
source_function='Unmodified AST node pn_drive from pinned phase4l.py; only np/pd/typing dependencies',
|
||||
source_sha256=digest(encoder_source), function_ast_sha256=ast_sha, torch=torch.__version__, numpy=np.__version__,
|
||||
control_60_original_CAL_arrays_exact=True, target_rng_column_order='Original function output, unchanged',
|
||||
generation='Original float32 rates*.1/1000 GPU Bernoulli; same seed across conditions; sort only resulting sparse requests',
|
||||
expected_not_realized_input_energy_matched=True, no_learning=True))
|
||||
save(EXP / 'protocol/prepared-hashes.json', [dict(path=p.relative_to(EXP).as_posix(), sha256=digest(p), bytes=p.stat().st_size)
|
||||
for directory in ('protocol', 'cases') for p in sorted((EXP / directory).rglob('*'))
|
||||
if p.is_file() and p.name != 'prepared-hashes.json'])
|
||||
budget()
|
||||
print('REGISTERED 2 controls +120 fixed validation cases; 60 original input arrays exact; no brain trajectories', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,66 @@
|
||||
"""One registered AK action per fresh process; no changes to frozen runtimes."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
from context import ROOT, EXP, CFG, check, budget, read
|
||||
import runtime_bridge as bridge
|
||||
|
||||
|
||||
def preflight():
|
||||
# Establish visibility before the lazy runtime/component Torch import.
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = CFG['gpu_uuid']
|
||||
os.environ['CUDA_HOME'] = '/usr/local/cuda'
|
||||
os.environ['PATH'] = str(ROOT / 'models/male-cns-lif/.venv/bin') + ':/usr/local/cuda/bin:' + os.environ['PATH']
|
||||
os.environ['MAX_JOBS'] = '2'
|
||||
os.environ['TORCH_CUDA_ARCH_LIST'] = '8.9'
|
||||
for port in (8188, 8189, 8190):
|
||||
with urllib.request.urlopen(f'http://192.168.10.51:{port}/queue', timeout=5) as response:
|
||||
queue = json.load(response)
|
||||
bridge.require(not queue['queue_running'] and not queue['queue_pending'], 'ComfyUI busy: defer, never evict')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('backend', choices=['gpu', 'oracle', 'component'])
|
||||
parser.add_argument('group', choices=bridge.GROUPS)
|
||||
parser.add_argument('case')
|
||||
parser.add_argument('--repeat', type=int, default=0)
|
||||
args = parser.parse_args()
|
||||
check(); budget()
|
||||
row = bridge.authorize_case(EXP, CFG, args.group, args.case, args.backend, args.repeat)
|
||||
case = EXP / 'cases' / args.group / args.case
|
||||
spec = bridge.case_spec(case)
|
||||
bridge.require(spec['name'] == args.case and spec['budget_root'] == str(EXP) and
|
||||
spec['output_budget_bytes'] == CFG['output_budget_bytes'], 'Case identity/new budget differs')
|
||||
if args.group == 'controls':
|
||||
bridge.control_inputs(case, row['source_case'])
|
||||
else:
|
||||
bridge.require(spec.get('kind') == 'fullgraph' and spec.get('validation_cohort') == args.group and
|
||||
all(spec[key] == row[key] for key in ('mode', 'odor_id', 'odor_index', 'seed')),
|
||||
'New fullgraph case differs from its registered cohort cell')
|
||||
output = EXP / 'gpu' / args.group / f'{args.case}-r{args.repeat}'
|
||||
if args.backend == 'component':
|
||||
summary = read(output / 'summary.json')
|
||||
bridge.require(summary.get('complete') is True and summary.get('pass_all') is True and
|
||||
summary.get('case') == args.case and summary.get('apl_delay_steps') == 18,
|
||||
'Component audit needs a complete matching D18 GPU run')
|
||||
preflight()
|
||||
def execute():
|
||||
if args.backend == 'gpu':
|
||||
result = bridge.run_gpu(case, output, EXP / 'extension-build')
|
||||
elif args.backend == 'oracle':
|
||||
result = bridge.run_oracle(case, EXP / 'oracle' / args.group / args.case)
|
||||
else:
|
||||
result = bridge.component(output, spec['apl_bundle_dir'], EXP / 'component-audit' / args.group / (args.case + '.json'))
|
||||
budget()
|
||||
bridge.require(result.get('pass_all') is True, 'Runtime/component evidence did not pass its own integrity checks')
|
||||
return result
|
||||
result = bridge.guarded_action(EXP, args.backend, args.group, args.case, args.repeat, execute)
|
||||
print(json.dumps(dict(backend=args.backend, group=args.group, case=args.case, repeat=args.repeat,
|
||||
complete=result.get('complete'), pass_all=result.get('pass_all')), allow_nan=False), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,151 @@
|
||||
"""Ordered fixed-D18 validation; representation failure never becomes execution failure.
|
||||
|
||||
Only the two predeclared first-odor/first-seed mode probes use Brian per cohort.
|
||||
Their GPU r0 outputs are reused in the full 60-case block. No novel-to-AJ
|
||||
trajectory can start until the complete seed block and its repeats pass.
|
||||
"""
|
||||
import argparse
|
||||
import subprocess
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from context import ROOT, MODEL, EXP, CFG, check, save, read, budget
|
||||
import runtime_bridge as bridge
|
||||
|
||||
|
||||
SCIENCE = ROOT / 'models/male-cns-lif/.venv/bin/python'
|
||||
ORACLE = ROOT / 'experiments/phase4ad-author-semantics/environment/.venv/bin/python'
|
||||
|
||||
|
||||
class RepresentationGateFailed(RuntimeError):
|
||||
"""Completed valid trajectories did not pass the predeclared representation gate."""
|
||||
|
||||
|
||||
def child(backend, group, name, repeat=0):
|
||||
# Do not resolve either interpreter: the venv invocation path is meaningful.
|
||||
interpreter = ORACLE if backend == 'oracle' else SCIENCE
|
||||
bridge.require(interpreter.is_file(), 'Missing fixed runtime interpreter: ' + str(interpreter))
|
||||
bridge.authorize_case(EXP, CFG, group, name, backend, repeat)
|
||||
log = EXP / 'logs' / f'{backend}-{group}-{name}-r{repeat}.log'
|
||||
log.parent.mkdir(parents=True, exist_ok=True)
|
||||
command = [str(interpreter), '-B', str(MODEL / 'run_case.py'), backend, group, name, '--repeat', str(repeat)]
|
||||
print('START', backend, group, name, repeat, flush=True)
|
||||
started = time.perf_counter()
|
||||
with log.open('x', encoding='utf-8') as stream:
|
||||
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT, timeout=1800, check=False)
|
||||
print('FINISH', backend, group, name, repeat, 'exit', result.returncode,
|
||||
'wall', round(time.perf_counter() - started, 2), flush=True)
|
||||
bridge.require(result.returncode == 0, f'{backend} {group}/{name} failed; preserve partial evidence and inspect {log}')
|
||||
|
||||
|
||||
def control(row):
|
||||
name = row['name']
|
||||
child('gpu', 'controls', name)
|
||||
case = EXP / 'cases/controls' / name
|
||||
output = EXP / 'gpu/controls' / (name + '-r0')
|
||||
report = bridge.control_identity(output, row['source_output'], case, row['source_case'],
|
||||
EXP / 'audit' / ('control-' + name + '.json'))
|
||||
bridge.require(report['pass_all'], 'Fresh fixed-D18 control differs from sealed AJ: ' + name)
|
||||
child('component', 'controls', name)
|
||||
return dict(case=name, group='controls', pass_all=True, fresh_gpu_runs=1, new_oracle_runs=0)
|
||||
|
||||
|
||||
def probe(row):
|
||||
name, group = row['name'], row['cohort']
|
||||
child('gpu', group, name)
|
||||
child('oracle', group, name)
|
||||
result = bridge.compare(EXP / 'cases' / group / name, EXP / 'gpu' / group / (name + '-r0'),
|
||||
EXP / 'oracle' / group / name, EXP / 'audit' / ('compare-' + group + '-' + name + '.json'))
|
||||
bridge.require(result['pass_all'], 'Closed-loop fixed-D18 comparison failed: ' + name)
|
||||
child('component', group, name)
|
||||
return dict(case=name, group=group, pass_all=True, fresh_gpu_runs=1, new_oracle_runs=1)
|
||||
|
||||
|
||||
def validation_block(cohort, records):
|
||||
from validation import summarize
|
||||
rows = bridge.cohort_rows(records, CFG, cohort)
|
||||
probes = {row['name'] for row in bridge.probe_rows(records, CFG, cohort)}
|
||||
repeated = []
|
||||
for row in rows:
|
||||
name = row['name']
|
||||
if name not in probes:
|
||||
child('gpu', cohort, name)
|
||||
else:
|
||||
comparison = read(EXP / 'audit' / ('compare-' + cohort + '-' + name + '.json'))
|
||||
bridge.require(comparison.get('pass_all') is True and comparison.get('case') == name,
|
||||
'Missing successful probe evidence for reused r0: ' + name)
|
||||
if row['seed'] == CFG['validation']['seeds'][0]:
|
||||
child('gpu', cohort, name, 1)
|
||||
result = bridge.compare_gpu_repeats(EXP / 'gpu' / cohort / (name + '-r0'),
|
||||
EXP / 'gpu' / cohort / (name + '-r1'), EXP / 'audit' / ('repeat-' + cohort + '-' + name + '.json'))
|
||||
bridge.require(result['pass_all'], 'Fixed-input GPU repeat diverged: ' + name)
|
||||
repeated.append(name)
|
||||
bridge.require(len(rows) == 60 and len(repeated) == 12, 'Incomplete registered base/repeat block')
|
||||
result = summarize(cohort)
|
||||
return result, repeated
|
||||
|
||||
|
||||
def run(stage):
|
||||
bridge.require(stage in bridge.STAGES, 'Unknown stage')
|
||||
check(); budget()
|
||||
bridge.ensure_unstopped(EXP)
|
||||
receipt = EXP / 'audit' / (stage + '-gate.json')
|
||||
bridge.require(not receipt.exists() and not (EXP / 'audit' / ('stopped-' + stage + '.json')).exists(),
|
||||
'Stage already has completion/stop evidence; do not rerun or overwrite')
|
||||
controls, records = bridge.inventories(EXP, CFG)
|
||||
try:
|
||||
if stage == 'controls':
|
||||
rows = [control(row) for row in controls]
|
||||
result = dict(stage=stage, pass_all=True, cases=rows, scope='Same-input fresh D18 GPU identity with sealed AJ; no new model or Brian run')
|
||||
else:
|
||||
bridge.gate_pass(EXP, 'controls')
|
||||
cohort = 'seed_validation' if stage.startswith('seed-') else 'novel_to_aj'
|
||||
if cohort == 'novel_to_aj':
|
||||
bridge.gate_pass(EXP, 'seed-validation', representation=True, cfg=CFG)
|
||||
if stage.endswith('-probes'):
|
||||
rows = [probe(row) for row in bridge.probe_rows(records, CFG, cohort)]
|
||||
result = dict(stage=stage, pass_all=True, cases=rows,
|
||||
scope='Two fixed first-odor first-seed mode probes; independent LIF with shared unchanged Torch APL component')
|
||||
else:
|
||||
bridge.gate_pass(EXP, 'seed-probes' if cohort == 'seed_validation' else 'novel-probes')
|
||||
summary, repeats = validation_block(cohort, records)
|
||||
execution = summary.get('execution_gate_pass') is True
|
||||
representation = summary.get('representation_gate_pass') is True
|
||||
if execution:
|
||||
bridge.validate_summary_coverage(summary, EXP, CFG, cohort)
|
||||
result = dict(stage=stage, cohort=cohort, pass_all=execution,
|
||||
execution_gate_pass=execution, representation_gate_pass=representation,
|
||||
base_count=60, repeat_count=len(repeats), repeat_cases=repeats,
|
||||
summary='validation/' + cohort + '/summary.json',
|
||||
summary_sha256=bridge.digest(EXP / 'validation' / cohort / 'summary.json'),
|
||||
validation_inventory_sha256=bridge.digest(EXP / 'protocol/validation-cases.json'),
|
||||
scope='Complete fixed representation block; no pooled rescue, learning, tuning, or historically-unseen-odor claim')
|
||||
save(receipt, result)
|
||||
if stage.endswith('-validation'):
|
||||
bridge.require(result['execution_gate_pass'], 'Validation execution/numerical/integrity gate failed')
|
||||
if not result['representation_gate_pass']:
|
||||
raise RepresentationGateFailed('Completed fixed validation block failed representation; no later cohort is authorized')
|
||||
budget()
|
||||
return result
|
||||
except Exception as error:
|
||||
save(EXP / 'audit' / ('stopped-' + stage + '.json'), dict(stage=stage,
|
||||
type=type(error).__name__, message=str(error), traceback=traceback.format_exc(),
|
||||
stop_kind='representation_failure' if isinstance(error, RepresentationGateFailed) else 'execution_or_numerical_failure',
|
||||
representation_failure_is_execution_failure=False,
|
||||
later_stages_not_permitted=True, partial_outputs_preserved=True,
|
||||
parameters_changed=False, replacement_seeds_or_odors_selected=False))
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('stage', choices=bridge.STAGES)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
run(args.stage)
|
||||
except RepresentationGateFailed:
|
||||
raise SystemExit(2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,396 @@
|
||||
"""Phase 4AK orchestration only: call hash-pinned, unchanged AJ functions.
|
||||
|
||||
No LIF, APL, queue, or input-generation equation is implemented here. Fresh
|
||||
processes resolve AJ's one unqualified import to the pinned AJ timing_queue.
|
||||
The D18 control adapter compares evidence; it never changes a case to D0.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
AJ_PINS = {
|
||||
'gpu_timing.py': 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b',
|
||||
'oracle_timing.py': '39d8c47c94bc2faddf016864a43e7715907fb987f56f7b27098060a88851f6be',
|
||||
'compare_timing.py': '9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734',
|
||||
'timing_queue.py': '54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e',
|
||||
}
|
||||
COMPONENT_SHA256 = '5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3'
|
||||
COHORTS = ('seed_validation', 'novel_to_aj')
|
||||
GROUPS = ('controls',) + COHORTS
|
||||
STAGES = ('controls', 'seed-probes', 'seed-validation', 'novel-probes', 'novel-validation')
|
||||
_BUDGET_KEYS = frozenset(('budget_root', 'output_budget_bytes'))
|
||||
_LOADED = {}
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def digest(path):
|
||||
result = hashlib.sha256()
|
||||
with Path(path).open('rb') as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b''):
|
||||
result.update(block)
|
||||
return result.hexdigest()
|
||||
|
||||
|
||||
def save(path, report):
|
||||
if path is None:
|
||||
return
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as stream:
|
||||
json.dump(report, stream, ensure_ascii=False, indent=2, sort_keys=True, allow_nan=False)
|
||||
stream.write('\n')
|
||||
|
||||
|
||||
def load_checked(name, path, expected_hash):
|
||||
"""Execute the verified bytes with importlib metadata, without old pyc writes."""
|
||||
path = Path(path).absolute()
|
||||
data = path.read_bytes()
|
||||
require(hashlib.sha256(data).hexdigest() == expected_hash, 'Sealed source SHA mismatch: ' + str(path))
|
||||
if name in sys.modules:
|
||||
existing = sys.modules[name]
|
||||
require(Path(getattr(existing, '__file__', '')).absolute() == path and
|
||||
_LOADED.get(name) == (path, expected_hash, existing),
|
||||
'Refusing an existing unverified module name: ' + name)
|
||||
return existing
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
require(spec is not None and spec.loader is not None, 'Cannot create sealed module specification')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
try:
|
||||
exec(compile(data, str(path), 'exec'), module.__dict__)
|
||||
except BaseException:
|
||||
sys.modules.pop(name, None)
|
||||
raise
|
||||
# Keep verification metadata here, not in the sealed module's globals.
|
||||
_LOADED[name] = (path, expected_hash, module)
|
||||
return module
|
||||
|
||||
|
||||
def sealed(filename, aj_model=None):
|
||||
require(filename in AJ_PINS, 'Unregistered AJ module')
|
||||
if aj_model is None:
|
||||
from context import AJ_MODEL
|
||||
aj_model = AJ_MODEL
|
||||
directory = Path(aj_model)
|
||||
if filename == 'gpu_timing.py':
|
||||
load_checked('timing_queue', directory / 'timing_queue.py', AJ_PINS['timing_queue.py'])
|
||||
name = 'phase4ak_sealed_aj_' + Path(filename).stem
|
||||
return load_checked(name, directory / filename, AJ_PINS[filename])
|
||||
|
||||
|
||||
def case_spec(case_dir):
|
||||
spec = read(Path(case_dir) / 'spec.json')
|
||||
require(isinstance(spec.get('name'), str) and re.fullmatch(r'[A-Za-z0-9_-]+', spec['name']), 'Unsafe case name')
|
||||
require(type(spec.get('apl_delay_steps')) is int and spec['apl_delay_steps'] == 18, 'AK permits fixed D18 only')
|
||||
require(spec.get('steps') == 1000 and type(spec['steps']) is int and spec.get('dt_ms') == .1 and
|
||||
spec.get('duration_ms') == 100.0 and spec.get('record_history') is False,
|
||||
'AK requires the unchanged fullgraph 100 ms window')
|
||||
require(spec.get('training') is False and spec.get('poi_used', False) is False and
|
||||
spec.get('precision') == 'float64' and spec.get('apl_precision') == 'float32',
|
||||
'Frozen no-learning / no-poi / precision scope changed')
|
||||
require(spec.get('apl') == {'tau_ms': 20, 'gain': .01, 'normalization': 1}, 'Frozen APL parameters changed')
|
||||
for field in ('graph_dir', 'apl_bundle_dir', 'coefficients_path', 'science_python', 'legacy_apl_worker'):
|
||||
require(field in spec and Path(spec[field]).is_absolute(), 'Missing absolute path: ' + field)
|
||||
require(isinstance(spec.get('model'), dict), 'Missing original author parameter dictionary')
|
||||
return spec
|
||||
|
||||
|
||||
def _new_output(case_dir, out_dir):
|
||||
spec = case_spec(case_dir)
|
||||
output = Path(out_dir).absolute()
|
||||
budget_root = Path(spec['budget_root']).absolute()
|
||||
require(type(spec.get('output_budget_bytes')) is int and spec['output_budget_bytes'] > 0,
|
||||
'Missing positive output budget')
|
||||
require(output.is_relative_to(budget_root) and output != budget_root and not output.exists(),
|
||||
'Output must be a new exclusive child of this case budget root')
|
||||
return spec
|
||||
|
||||
|
||||
def _receipt(output, backend, filename, aj_model):
|
||||
module = sealed(filename, aj_model)
|
||||
require(digest(module.__file__) == AJ_PINS[filename], 'Sealed runtime changed during invocation')
|
||||
receipt = {'backend': backend, 'sealed_runtime': str(module.__file__),
|
||||
'sealed_runtime_sha256': AJ_PINS[filename], 'apl_delay_steps': 18,
|
||||
'old_module_scientific_globals_changed': False, 'dynamics_copied_or_reimplemented': False,
|
||||
'scope': 'Unchanged AJ runtime called with new case/output/build arguments; not a new numerical model'}
|
||||
save(Path(output) / 'runtime-bridge.json', receipt)
|
||||
|
||||
|
||||
def run_gpu(case_dir, out_dir, build_root, *, aj_model=None):
|
||||
spec = _new_output(case_dir, out_dir)
|
||||
build_root = Path(build_root).absolute()
|
||||
require(build_root.is_relative_to(Path(spec['budget_root']).absolute()) and
|
||||
build_root != Path(spec['budget_root']).absolute(), 'Build cache must stay in the new experiment')
|
||||
module = sealed('gpu_timing.py', aj_model)
|
||||
result = module.run_gpu(Path(case_dir), Path(out_dir), build_root)
|
||||
_receipt(out_dir, 'gpu', 'gpu_timing.py', aj_model)
|
||||
return result
|
||||
|
||||
|
||||
def run_oracle(case_dir, out_dir, *, aj_model=None):
|
||||
_new_output(case_dir, out_dir)
|
||||
require('torch' not in sys.modules, 'Pinned Brian process must not import Torch')
|
||||
module = sealed('oracle_timing.py', aj_model)
|
||||
result = module.run_case(Path(case_dir), Path(out_dir))
|
||||
_receipt(out_dir, 'oracle', 'oracle_timing.py', aj_model)
|
||||
return result
|
||||
|
||||
|
||||
def compare(case_dir, gpu_dir, cpu_dir, out_json=None, *, aj_model=None):
|
||||
case_spec(case_dir)
|
||||
return sealed('compare_timing.py', aj_model).compare(case_dir, gpu_dir, cpu_dir, out_json)
|
||||
|
||||
|
||||
def compare_gpu_repeats(first, second, out_json=None, *, aj_model=None):
|
||||
require(all(read(Path(path) / 'summary.json').get('apl_delay_steps') == 18 for path in (first, second)),
|
||||
'Repeat evidence must be fixed D18')
|
||||
return sealed('compare_timing.py', aj_model).compare_gpu_repeats(first, second, out_json)
|
||||
|
||||
|
||||
def component(output, bundle, out_json, *, ag_model=None):
|
||||
if ag_model is None:
|
||||
from context import AG_MODEL
|
||||
ag_model = AG_MODEL
|
||||
module = load_checked('phase4ak_sealed_ag_components', Path(ag_model) / 'validate_components.py', COMPONENT_SHA256)
|
||||
return module.validate(Path(output), Path(bundle), Path(out_json))
|
||||
|
||||
|
||||
def control_inputs(newcase, oldcase):
|
||||
"""Only budget metadata may differ; all saved request/drive bytes are identical."""
|
||||
newcase, oldcase = Path(newcase), Path(oldcase)
|
||||
fresh, old = case_spec(newcase), case_spec(oldcase)
|
||||
require({k: v for k, v in fresh.items() if k not in _BUDGET_KEYS} ==
|
||||
{k: v for k, v in old.items() if k not in _BUDGET_KEYS},
|
||||
'D18 control scientific specification changed beyond the two budget fields')
|
||||
evidence = {str(folder / 'spec.json'): digest(folder / 'spec.json') for folder in (newcase, oldcase)}
|
||||
files = ['requests.npz', 'pn-drive.npz']
|
||||
require((newcase / 'arrays.npz').exists() == (oldcase / 'arrays.npz').exists(), 'Control initial-state presence changed')
|
||||
if (newcase / 'arrays.npz').exists():
|
||||
files.append('arrays.npz')
|
||||
for name in files:
|
||||
first, second = digest(newcase / name), digest(oldcase / name)
|
||||
require(first == second, 'Control input byte mismatch: ' + name)
|
||||
evidence[str(newcase / name)], evidence[str(oldcase / name)] = first, second
|
||||
return fresh, old, evidence
|
||||
|
||||
|
||||
def control_identity(newdir, olddir, newcase, oldcase, out_json=None, *, aj_model=None):
|
||||
"""D18 scientific identity using unchanged AJ comparison primitives.
|
||||
|
||||
Unlike the AJ D0-vs-AG adapter, this checks real D18 applied/pending files.
|
||||
Input SHA fields are checked against each real spec, not compared after
|
||||
deleting metadata. Only build/cache paths and the two budget fields are
|
||||
permitted to differ; all numerical/timing/component evidence stays exact.
|
||||
"""
|
||||
require(out_json is None or not Path(out_json).exists(), 'Refusing existing control report')
|
||||
aj = sealed('compare_timing.py', aj_model)
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'fixed-D18 same-input sealed-AJ control identity', 'pass_all': False,
|
||||
'new_output': str(newdir), 'old_output': str(olddir), 'new_case': str(newcase), 'old_case': str(oldcase),
|
||||
'apl_delay_steps': 18, 'tolerance': None, 'signed_zero_included': True,
|
||||
'dynamics_copied_or_reimplemented': False, 'old_globals_patched': False,
|
||||
'independent_CPU_all_components_claim': False,
|
||||
'allowed_case_metadata_differences': sorted(_BUDGET_KEYS),
|
||||
'build_and_wall_metadata_compared': False}
|
||||
try:
|
||||
spec, old_spec, inputs = control_inputs(newcase, oldcase)
|
||||
used.update(Path(path) for path in inputs)
|
||||
checks['control_input_spec_and_bytes'] = True
|
||||
steps, n, edges = spec['steps'], 166700, 25582938
|
||||
left = aj._load_run(Path(newdir), 'fresh_gpu', n, edges, steps, False, spec['name'], 18, used, checks)
|
||||
right = aj._load_run(Path(olddir), 'sealed_gpu', n, edges, steps, False, spec['name'], 18, used, checks)
|
||||
aj._pair_outputs(left, right, checks, differences, n, steps, False)
|
||||
for role, run, case, case_input in (('fresh_gpu', left, Path(newcase), spec),
|
||||
('sealed_gpu', right, Path(oldcase), old_spec)):
|
||||
aj._timing_sequence_checks(run['apl']['inhibition'], run['apl']['applied'], run['pending'],
|
||||
18, role, checks, differences)
|
||||
aj._timing_metadata_checks(run, role, 18, steps, checks)
|
||||
expected = {name: digest(case / name) for name in ('spec.json', 'requests.npz')}
|
||||
if (case / 'arrays.npz').exists():
|
||||
expected['arrays.npz'] = digest(case / 'arrays.npz')
|
||||
weight, coeff = Path(case_input['graph_dir']) / 'weights.npy', Path(case_input['coefficients_path'])
|
||||
used.update((weight, coeff))
|
||||
expected.update({'base_weights.npy': digest(weight), 'coefficients.json': digest(coeff)})
|
||||
checks[role + '.actual_input_hashes'] = run['summary'].get('input_sha256') == expected
|
||||
stable = ('case', 'complete', 'pass_all', 'steps', 'completed_ticks', 'nodes', 'edges', 'precision',
|
||||
'apl_precision', 'state_units', 'total_spikes', 'requested', 'accepted_requests', 'rejected_requests',
|
||||
'arriving_edges', 'accepted_arriving_edges', 'counts_match_sparse_events', 'all_finite', 'record_history',
|
||||
'apl_fast_edges_zeroed', 'kc_targets_present', 'effective_weights_raw_sha256', 'apl_schedule',
|
||||
'apl_si_conversion', 'sealed_ae_backend_sha256', 'learning', 'apl_delay_steps', 'apl_timing',
|
||||
'source_provenance', 'pending_generated_count', 'pending_initial_zero_prefix', 'pending_tail_shape',
|
||||
'future_generated_decay_tail_included')
|
||||
for key in stable:
|
||||
checks['summary.' + key] = key in left['summary'] and key in right['summary'] and left['summary'][key] == right['summary'][key]
|
||||
checks['timing_audit.parsed_exact'] = left['timing'] == right['timing']
|
||||
report.update(case=spec['name'], steps=steps, nodes=n, edges=edges,
|
||||
scope='Six state arrays, every tick state/queue/request, generated/applied/pending APL, component and timing metadata')
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return aj._finish(report, checks, differences, used, out_json)
|
||||
|
||||
|
||||
def inventories(exp, cfg):
|
||||
"""Validate static cells before choosing any dynamic case; no old dynamic read."""
|
||||
exp = Path(exp)
|
||||
controls = read(exp / 'protocol/control-cases.json')
|
||||
records = read(exp / 'protocol/validation-cases.json')
|
||||
require(isinstance(controls, list) and len(controls) == 2 and
|
||||
[row['name'] for row in controls] == cfg['control_cases'], 'Unexpected control inventory')
|
||||
for row in controls:
|
||||
require(re.fullmatch(r'[A-Za-z0-9_-]+', row['name']) and
|
||||
all(Path(row[key]).is_absolute() for key in ('source_case', 'source_output')), 'Invalid control identity/path')
|
||||
design = cfg['validation']
|
||||
require(design['cohorts'] == list(COHORTS) and len(design['seeds']) == 5 and len(set(design['seeds'])) == 5 and
|
||||
design['modes'] == ['physiological', 'matched_energy'] and design['odor_count'] == 6 and
|
||||
design['repeat_first_seed_each_odor'] is True, 'Fixed validation design changed')
|
||||
require(isinstance(records, list) and len(records) == 120, 'Expected 120 predeclared cases')
|
||||
cells, names = set(), set()
|
||||
for row in records:
|
||||
key = (row['cohort'], row['mode'], row['odor_index'], row['seed'])
|
||||
require(re.fullmatch(r'[A-Za-z0-9_-]+', row['name']) and row['name'] not in names and key not in cells,
|
||||
'Duplicate/unsafe case identity')
|
||||
require(type(row['odor_index']) is int and type(row['seed']) is int, 'Noninteger odor/seed')
|
||||
cells.add(key); names.add(row['name'])
|
||||
expected = {(cohort, mode, oi, seed) for cohort in COHORTS for mode in design['modes']
|
||||
for oi in range(6) for seed in design['seeds']}
|
||||
require(cells == expected, 'Missing or substituted validation cell')
|
||||
return controls, records
|
||||
|
||||
|
||||
def cohort_rows(records, cfg, cohort):
|
||||
require(cohort in COHORTS, 'Unknown validation cohort')
|
||||
design = cfg['validation']
|
||||
return sorted((row for row in records if row['cohort'] == cohort),
|
||||
key=lambda row: (design['modes'].index(row['mode']), row['odor_index'], design['seeds'].index(row['seed'])))
|
||||
|
||||
|
||||
def probe_rows(records, cfg, cohort):
|
||||
rows = [row for row in cohort_rows(records, cfg, cohort)
|
||||
if row['odor_index'] == 0 and row['seed'] == cfg['validation']['seeds'][0]]
|
||||
require(len(rows) == 2 and [row['mode'] for row in rows] == cfg['validation']['modes'], 'Fixed probe selection changed')
|
||||
return rows
|
||||
|
||||
|
||||
def ensure_unstopped(exp):
|
||||
markers = sorted((Path(exp) / 'audit').glob('stopped-*.json'))
|
||||
require(not markers, 'A prior stop marker forbids all new scientific actions: ' + ', '.join(path.name for path in markers))
|
||||
|
||||
|
||||
def guarded_action(exp, backend, group, name, repeat, action):
|
||||
"""Persist failure after invoking an authorized scientific action, even standalone.
|
||||
|
||||
The caller performs input authorization and external idle preflight first.
|
||||
A failure here is an execution/audit failure, not evidence of instability.
|
||||
"""
|
||||
require(backend in ('gpu', 'oracle', 'component') and group in GROUPS and
|
||||
isinstance(name, str) and re.fullmatch(r'[A-Za-z0-9_-]+', name) and
|
||||
type(repeat) is int and repeat in (0, 1), 'Invalid guarded action identity')
|
||||
ensure_unstopped(exp)
|
||||
try:
|
||||
return action()
|
||||
except Exception as error:
|
||||
save(Path(exp) / 'audit' / f'stopped-case-{backend}-{group}-{name}-r{repeat}.json', {
|
||||
'stage': 'case', 'backend': backend, 'group': group, 'case': name, 'repeat': repeat,
|
||||
'type': type(error).__name__, 'message': str(error), 'traceback': traceback.format_exc(),
|
||||
'stop_kind': 'execution_or_numerical_failure', 'scientific_action_invoked': True,
|
||||
'numerical_instability_inferred_from_execution_error': False,
|
||||
'later_stages_not_permitted': True, 'partial_outputs_preserved': True,
|
||||
'parameters_changed': False, 'replacement_seeds_or_odors_selected': False})
|
||||
raise
|
||||
|
||||
|
||||
def validate_summary_coverage(summary, exp, cfg, cohort, *, representation=False):
|
||||
"""Bind the unlock to this cohort's exact 60 base / 12 repeat / 72 timing cells."""
|
||||
_, records = inventories(exp, cfg)
|
||||
rows = cohort_rows(records, cfg, cohort)
|
||||
expected = {row['name'] for row in rows}
|
||||
repeated = {row['name'] for row in rows if row['seed'] == cfg['validation']['seeds'][0]}
|
||||
timing = {(name, 0) for name in expected} | {(name, 1) for name in repeated}
|
||||
require(summary.get('cohort') == cohort and summary.get('phase') == '4AK' and
|
||||
summary.get('apl_delay_steps') == 18 and summary.get('seeds') == cfg['validation']['seeds'] and
|
||||
summary.get('duration_ms') == 100.0, 'Validation summary identity/window differs')
|
||||
require(summary.get('expected_base_trials') == summary.get('valid_base_trials') == 60 and
|
||||
summary.get('expected_repeats') == summary.get('checked_repeats') == 12 and
|
||||
summary.get('checked_timing_trials') == 72, 'Incomplete validation coverage')
|
||||
base_rows, repeat_rows, timing_rows = (summary.get(key, []) for key in ('runs', 'repeats', 'timing_checks'))
|
||||
require(len(base_rows) == 60 and {row['case'] for row in base_rows} == expected and
|
||||
all(row.get('cohort') == cohort and row.get('validation_cohort') == cohort for row in base_rows),
|
||||
'Base-trial identities differ from the fixed cohort inventory')
|
||||
require(len(repeat_rows) == 12 and {row['case'] for row in repeat_rows} == repeated and
|
||||
all(row.get('pass_all') is True for row in repeat_rows), 'Exact-repeat identities/coverage failed')
|
||||
require(len(timing_rows) == 72 and {(row['case'], row['repeat']) for row in timing_rows} == timing and
|
||||
all(type(row['repeat']) is int and row.get('pass_all') is True and row.get('apl_delay_steps') == 18
|
||||
and row.get('steps') == 1000 for row in timing_rows), 'Timing identities/coverage failed')
|
||||
proof = summary.get('input_output_provenance', [])
|
||||
require(len(proof) == 72 and {(row['case'], row['repeat']) for row in proof} == timing,
|
||||
'Per-run evidence provenance does not cover exactly the registered cells')
|
||||
require(summary.get('prerequisite_input_sha256', {}).get('validation_inventory') ==
|
||||
digest(Path(exp) / 'protocol/validation-cases.json'), 'Summary was derived from a different validation inventory')
|
||||
require(summary.get('execution_gate_pass') is True and summary.get('numerical_integrity_pass') is True and
|
||||
summary.get('exact_repeat_pass') is True, 'Validation execution/numerical/repeat gate failed')
|
||||
require(set(summary.get('modes', {})) == set(cfg['validation']['modes']), 'Incomplete mode summaries')
|
||||
if representation:
|
||||
require(summary.get('representation_gate_pass') is True and summary.get('pass_all') is True and
|
||||
all(summary['modes'][mode].get('gate') is True for mode in cfg['validation']['modes']),
|
||||
'Seed representation summary does not authorize novel dynamics')
|
||||
|
||||
|
||||
def gate_pass(exp, stage, *, representation=False, cfg=None):
|
||||
report = read(Path(exp) / 'audit' / (stage + '-gate.json'))
|
||||
require(report.get('stage') == stage and report.get('pass_all') is True, 'Required execution gate failed: ' + stage)
|
||||
if representation:
|
||||
require(stage == 'seed-validation', 'Only the complete seed block unlocks novel trajectories')
|
||||
require(report.get('execution_gate_pass') is True and report.get('representation_gate_pass') is True,
|
||||
'Complete seed representation/execution gate required before novel dynamics')
|
||||
if cfg is None:
|
||||
from context import CFG
|
||||
cfg = CFG
|
||||
path = Path(exp) / 'validation/seed_validation/summary.json'
|
||||
require(report.get('cohort') == 'seed_validation' and
|
||||
report.get('summary') == 'validation/seed_validation/summary.json' and
|
||||
report.get('summary_sha256') == digest(path), 'Seed gate is not bound to this exact summary')
|
||||
require(report.get('validation_inventory_sha256') == digest(Path(exp) / 'protocol/validation-cases.json'),
|
||||
'Seed gate inventory digest differs')
|
||||
validate_summary_coverage(read(path), exp, cfg, 'seed_validation', representation=True)
|
||||
return report
|
||||
|
||||
|
||||
def authorize_case(exp, cfg, group, name, backend, repeat):
|
||||
ensure_unstopped(exp)
|
||||
require(group in GROUPS and backend in ('gpu', 'oracle', 'component') and type(repeat) is int and repeat in (0, 1),
|
||||
'Invalid runtime action')
|
||||
controls, records = inventories(exp, cfg)
|
||||
rows = controls if group == 'controls' else cohort_rows(records, cfg, group)
|
||||
selected = [row for row in rows if row['name'] == name]
|
||||
require(len(selected) == 1, 'Case not in the registered group inventory')
|
||||
row = selected[0]
|
||||
if group == 'controls':
|
||||
require(repeat == 0 and backend in ('gpu', 'component'), 'Controls are single fresh GPU identity runs')
|
||||
else:
|
||||
gate_pass(exp, 'controls')
|
||||
if group == 'novel_to_aj':
|
||||
gate_pass(exp, 'seed-validation', representation=True, cfg=cfg)
|
||||
probes = {item['name'] for item in probe_rows(records, cfg, group)}
|
||||
probe_stage = 'seed-probes' if group == 'seed_validation' else 'novel-probes'
|
||||
if name not in probes or repeat:
|
||||
gate_pass(exp, probe_stage)
|
||||
if backend in ('oracle', 'component'):
|
||||
require(name in probes and repeat == 0, 'Oracle/component scope is the two fixed probes per cohort')
|
||||
if repeat:
|
||||
require(backend == 'gpu' and row['seed'] == cfg['validation']['seeds'][0], 'Only declared first-seed repeats')
|
||||
return row
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
from encoder_inputs import frozen_pn_drive, requests_from_forced, write_npz
|
||||
|
||||
|
||||
class EncoderInputTests(unittest.TestCase):
|
||||
def test_exact_frozen_encoder_ast_on_synthetic_static_mapping(self):
|
||||
import pandas as pd
|
||||
local = Path(__file__).resolve().parents[1] / 'models/male-cns-lif/phase4l.py'
|
||||
source = local if local.exists() else Path('/data/digital-drosophila/models/male-cns-lif/phase4l.py')
|
||||
pin = 'e97d58d8c1f44920d0e953f8f5831ddc86fa0198784e86460e315de4116ac553'
|
||||
function, ast_sha = frozen_pn_drive(source, pin)
|
||||
self.assertEqual(len(ast_sha), 64)
|
||||
mapping = pd.DataFrame(dict(glomerulus=['G2', 'G2', 'G1', 'G1'],
|
||||
mapping_confidence=['exact', 'alias-normalized', 'exact', 'unknown'], neuron_idx=[3, 1, 7, 999]))
|
||||
cfg = {'odor_encoder': {'max_total_rate_per_glomerulus_hz': 400}}
|
||||
target, rates, _ = function('synthetic', np.array([.5, .25], np.float32), mapping,
|
||||
['G2', 'G1'], 'matched_energy', 1.5, cfg)
|
||||
np.testing.assert_array_equal(target, np.array([3, 1, 7], np.int32))
|
||||
np.testing.assert_array_equal(rates, np.array([200, 200, 200], np.float32))
|
||||
with self.assertRaises(AssertionError):
|
||||
frozen_pn_drive(source, '0' * 64)
|
||||
|
||||
def test_rng_column_order_preserved_until_request_sort(self):
|
||||
targets = np.array([10, 1, 3], np.int32)
|
||||
forced = np.array([[True, True, False], [False, True, True]], np.bool_)
|
||||
actual = requests_from_forced(forced, targets)
|
||||
np.testing.assert_array_equal(actual, np.array([[0, 1], [0, 10], [1, 1], [1, 3]], np.int32))
|
||||
np.testing.assert_array_equal(targets, np.array([10, 1, 3], np.int32))
|
||||
|
||||
def test_duplicate_targets_rejected_not_merged(self):
|
||||
with self.assertRaises(AssertionError):
|
||||
requests_from_forced(np.ones((2, 2), np.bool_), np.array([1, 1], np.int32))
|
||||
|
||||
def test_empty_sparse_schedule_has_exact_shape_and_dtype(self):
|
||||
result = requests_from_forced(np.zeros((3, 2), np.bool_), np.array([2, 1], np.int32))
|
||||
self.assertEqual(result.shape, (0, 2))
|
||||
self.assertEqual(result.dtype, np.int32)
|
||||
|
||||
def test_npz_creation_is_exclusive(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / 'schedule.npz'
|
||||
write_npz(path, tick=np.array([1], np.int32))
|
||||
with np.load(path, allow_pickle=False) as stored:
|
||||
np.testing.assert_array_equal(stored['tick'], np.array([1], np.int32))
|
||||
with self.assertRaises(FileExistsError):
|
||||
write_npz(path, tick=np.array([2], np.int32))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,39 @@
|
||||
import unittest
|
||||
from finalize import decide_status
|
||||
|
||||
|
||||
class FinalStatusTests(unittest.TestCase):
|
||||
def decide(self, **overrides):
|
||||
args = dict(numerical=True, seed=dict(execution_gate_pass=True, representation_gate_pass=True),
|
||||
novel=dict(execution_gate_pass=True, representation_gate_pass=True), integrity_pass=True,
|
||||
reference_pass=True, reference_present=True)
|
||||
args.update(overrides)
|
||||
return decide_status(**args)
|
||||
|
||||
def test_complete_only_representation_pass_is_not_named_learning(self):
|
||||
self.assertEqual(self.decide(), 'PASS-FROZEN-D18-INDEPENDENT-VALIDATION')
|
||||
|
||||
def test_seed_failure_without_novel_is_completed_scientific_failure(self):
|
||||
self.assertEqual(self.decide(seed=dict(execution_gate_pass=True, representation_gate_pass=False), novel=None),
|
||||
'FAIL-SEED-VALIDATION-REPRESENTATION')
|
||||
|
||||
def test_novel_gate_failure_is_preserved(self):
|
||||
self.assertEqual(self.decide(novel=dict(execution_gate_pass=True, representation_gate_pass=False)),
|
||||
'FAIL-NOVEL-ODOR-REPRESENTATION')
|
||||
|
||||
def test_missing_execution_or_reference_is_not_scientific_pass(self):
|
||||
for changes in (dict(numerical=False), dict(novel=None), dict(reference_present=False), dict(seed=None)):
|
||||
with self.subTest(changes=changes):
|
||||
self.assertEqual(self.decide(**changes), 'INCOMPLETE-VALIDATION-EXECUTION')
|
||||
|
||||
def test_numerical_and_unlock_failure_are_not_representation_results(self):
|
||||
self.assertEqual(self.decide(known_numerical_failure=True), 'FAIL-NUMERICAL-OR-EXECUTION-POLICY')
|
||||
self.assertEqual(self.decide(unauthorized_novel=True), 'FAIL-NUMERICAL-OR-EXECUTION-POLICY')
|
||||
|
||||
def test_old_integrity_or_reference_fail_overrides_new_pass(self):
|
||||
self.assertEqual(self.decide(integrity_pass=False), 'FAIL-FROZEN-INTEGRITY-OR-REGRESSION')
|
||||
self.assertEqual(self.decide(reference_pass=False), 'FAIL-FROZEN-INTEGRITY-OR-REGRESSION')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,409 @@
|
||||
"""CPU-only synthetic tests: no Torch/Brian import, GPU, remote, or brain run."""
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
import runtime_bridge as bridge
|
||||
|
||||
|
||||
HERE = Path(__file__).absolute().parent
|
||||
AJ = HERE.parent / 'apl-timing-lab-v1'
|
||||
|
||||
|
||||
def write_json(path, data):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data), encoding='utf-8')
|
||||
|
||||
|
||||
def spec(root, name='cal-physiological-00-s51101'):
|
||||
return dict(name=name, kind='calibration', steps=1000, dt_ms=.1, duration_ms=100.,
|
||||
model={'fixed': True}, graph_dir=str(root / 'graph'), apl_bundle_dir=str(root / 'bundle'),
|
||||
coefficients_path=str(root / 'coefficients.json'), science_python=str(root / 'venv/python'),
|
||||
legacy_apl_worker=str(root / 'legacy_apl_worker.py'),
|
||||
apl={'tau_ms': 20., 'gain': .01, 'normalization': 1.}, apl_delay_steps=18,
|
||||
record_history=False, training=False, precision='float64', apl_precision='float32',
|
||||
budget_root=str(root / 'old-exp'), output_budget_bytes=1024)
|
||||
|
||||
|
||||
def inventory(root):
|
||||
cfg = dict(control_cases=['control-phys', 'control-matched'], validation={
|
||||
'cohorts': list(bridge.COHORTS), 'seeds': [101, 102, 103, 104, 105],
|
||||
'modes': ['physiological', 'matched_energy'], 'odor_count': 6,
|
||||
'repeat_first_seed_each_odor': True})
|
||||
controls = [dict(name=name, source_case=str(root / 'old-cases' / name),
|
||||
source_output=str(root / 'old-gpu' / name)) for name in cfg['control_cases']]
|
||||
rows = [dict(cohort=cohort, mode=mode, odor_index=oi, seed=seed,
|
||||
name=f'{cohort}-{mode}-{oi:02d}-s{seed}') for cohort in bridge.COHORTS
|
||||
for mode in cfg['validation']['modes'] for oi in range(6) for seed in cfg['validation']['seeds']]
|
||||
write_json(root / 'protocol/control-cases.json', controls)
|
||||
write_json(root / 'protocol/validation-cases.json', rows)
|
||||
return cfg, rows
|
||||
|
||||
|
||||
def gate(root, stage, **values):
|
||||
write_json(root / 'audit' / (stage + '-gate.json'), dict(stage=stage, pass_all=True, **values))
|
||||
|
||||
|
||||
def seed_summary(root, cfg, rows):
|
||||
base = bridge.cohort_rows(rows, cfg, 'seed_validation')
|
||||
repeated = [row for row in base if row['seed'] == cfg['validation']['seeds'][0]]
|
||||
pairs = [(row['name'], 0) for row in base] + [(row['name'], 1) for row in repeated]
|
||||
return dict(phase='4AK', cohort='seed_validation', apl_delay_steps=18, duration_ms=100.,
|
||||
seeds=cfg['validation']['seeds'], pass_all=True, execution_gate_pass=True,
|
||||
representation_gate_pass=True, numerical_integrity_pass=True, exact_repeat_pass=True,
|
||||
expected_base_trials=60, valid_base_trials=60, expected_repeats=12, checked_repeats=12,
|
||||
checked_timing_trials=72, runs=[dict(case=row['name'], cohort='seed_validation',
|
||||
validation_cohort='seed_validation') for row in base],
|
||||
repeats=[dict(case=row['name'], pass_all=True) for row in repeated],
|
||||
timing_checks=[dict(case=name, repeat=repeat, pass_all=True, apl_delay_steps=18, steps=1000)
|
||||
for name, repeat in pairs], input_output_provenance=[dict(case=name, repeat=repeat) for name, repeat in pairs],
|
||||
modes={mode: dict(gate=True) for mode in cfg['validation']['modes']},
|
||||
prerequisite_input_sha256={'validation_inventory': bridge.digest(root / 'protocol/validation-cases.json')})
|
||||
|
||||
|
||||
def bind_seed_gate(root, summary):
|
||||
path = root / 'validation/seed_validation/summary.json'
|
||||
write_json(path, summary)
|
||||
gate(root, 'seed-validation', cohort='seed_validation', execution_gate_pass=True,
|
||||
representation_gate_pass=True, summary='validation/seed_validation/summary.json',
|
||||
summary_sha256=bridge.digest(path), validation_inventory_sha256=bridge.digest(root / 'protocol/validation-cases.json'))
|
||||
|
||||
|
||||
class LoaderTests(unittest.TestCase):
|
||||
def test_verified_import_preserves_file_and_globals_without_pyc(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / 'sealed.py'
|
||||
data = b'VALUE = 37\n'
|
||||
path.write_bytes(data)
|
||||
name = '_test_phase4ak_sealed'
|
||||
try:
|
||||
module = bridge.load_checked(name, path, hashlib.sha256(data).hexdigest())
|
||||
self.assertEqual(module.VALUE, 37)
|
||||
self.assertEqual(Path(module.__file__), path)
|
||||
self.assertIs(bridge.load_checked(name, path, hashlib.sha256(data).hexdigest()), module)
|
||||
self.assertNotIn('__phase4ak_loaded_sha256__', module.__dict__)
|
||||
self.assertFalse((path.parent / '__pycache__').exists())
|
||||
finally:
|
||||
sys.modules.pop(name, None); bridge._LOADED.pop(name, None)
|
||||
|
||||
def test_sha_checked_before_execution(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / 'sealed.py'
|
||||
path.write_text('raise RuntimeError("must not execute")', encoding='utf-8')
|
||||
with self.assertRaisesRegex(ValueError, 'SHA mismatch'):
|
||||
bridge.load_checked('_test_bad_pin', path, '0' * 64)
|
||||
self.assertNotIn('_test_bad_pin', sys.modules)
|
||||
|
||||
def test_existing_name_collision_is_not_silently_replaced(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / 'sealed.py'
|
||||
path.write_text('VALUE = 1', encoding='utf-8')
|
||||
old = types.ModuleType('_test_collision')
|
||||
sys.modules[old.__name__] = old
|
||||
try:
|
||||
with self.assertRaisesRegex(ValueError, 'unverified module'):
|
||||
bridge.load_checked(old.__name__, path, bridge.digest(path))
|
||||
self.assertIs(sys.modules[old.__name__], old)
|
||||
finally:
|
||||
sys.modules.pop(old.__name__, None)
|
||||
|
||||
def test_real_pure_sealed_module_load_does_not_import_torch_or_brian(self):
|
||||
self.assertNotIn('torch', sys.modules)
|
||||
self.assertNotIn('brian2', sys.modules)
|
||||
compare = bridge.sealed('compare_timing.py', AJ)
|
||||
self.assertEqual(bridge.digest(compare.__file__), bridge.AJ_PINS['compare_timing.py'])
|
||||
self.assertTrue(callable(compare.compare))
|
||||
self.assertNotIn('torch', sys.modules)
|
||||
self.assertNotIn('brian2', sys.modules)
|
||||
|
||||
|
||||
class InputTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name)
|
||||
self.old, self.new = self.root / 'old', self.root / 'new'
|
||||
self.old.mkdir(); self.new.mkdir()
|
||||
self.old_spec = spec(self.root)
|
||||
self.new_spec = dict(self.old_spec, budget_root=str(self.root / 'new-exp'), output_budget_bytes=2048)
|
||||
write_json(self.old / 'spec.json', self.old_spec)
|
||||
write_json(self.new / 'spec.json', self.new_spec)
|
||||
for name in ('requests.npz', 'pn-drive.npz'):
|
||||
for directory in (self.old, self.new):
|
||||
(directory / name).write_bytes(b'identical synthetic saved input bytes')
|
||||
|
||||
def tearDown(self):
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_control_allows_only_budget_difference(self):
|
||||
fresh, old, evidence = bridge.control_inputs(self.new, self.old)
|
||||
self.assertEqual(fresh['apl_delay_steps'], old['apl_delay_steps'])
|
||||
self.assertEqual(len(evidence), 6)
|
||||
changed = dict(self.new_spec, mode='changed')
|
||||
write_json(self.new / 'spec.json', changed)
|
||||
with self.assertRaisesRegex(ValueError, 'beyond the two budget fields'):
|
||||
bridge.control_inputs(self.new, self.old)
|
||||
|
||||
def test_drive_and_request_bytes_both_checked(self):
|
||||
for name in ('requests.npz', 'pn-drive.npz'):
|
||||
with self.subTest(name=name):
|
||||
original = (self.new / name).read_bytes()
|
||||
(self.new / name).write_bytes(original + b'changed')
|
||||
with self.assertRaisesRegex(ValueError, name):
|
||||
bridge.control_inputs(self.new, self.old)
|
||||
(self.new / name).write_bytes(original)
|
||||
|
||||
def test_real_d18_required_no_fake_d0(self):
|
||||
for value in (0, True, 18.0):
|
||||
with self.subTest(value=value):
|
||||
write_json(self.new / 'spec.json', dict(self.new_spec, apl_delay_steps=value))
|
||||
with self.assertRaisesRegex(ValueError, 'D18'):
|
||||
bridge.control_inputs(self.new, self.old)
|
||||
|
||||
def test_initial_state_presence_and_bytes_checked(self):
|
||||
(self.new / 'arrays.npz').write_bytes(b'a')
|
||||
with self.assertRaisesRegex(ValueError, 'presence'):
|
||||
bridge.control_inputs(self.new, self.old)
|
||||
(self.old / 'arrays.npz').write_bytes(b'b')
|
||||
with self.assertRaisesRegex(ValueError, 'arrays.npz'):
|
||||
bridge.control_inputs(self.new, self.old)
|
||||
|
||||
def test_exclusive_output_and_new_budget_containment(self):
|
||||
root = Path(self.new_spec['budget_root'])
|
||||
root.mkdir()
|
||||
out = root / 'gpu' / 'run'
|
||||
bridge._new_output(self.new, out)
|
||||
out.mkdir(parents=True)
|
||||
with self.assertRaisesRegex(ValueError, 'exclusive'):
|
||||
bridge._new_output(self.new, out)
|
||||
with self.assertRaisesRegex(ValueError, 'exclusive'):
|
||||
bridge._new_output(self.new, self.root / 'elsewhere')
|
||||
|
||||
def test_control_refuses_overwrite_before_reading_outputs(self):
|
||||
report = self.root / 'report.json'
|
||||
report.write_text('keep me', encoding='utf-8')
|
||||
with self.assertRaisesRegex(ValueError, 'existing control'):
|
||||
bridge.control_identity('missing-new', 'missing-old', self.new, self.old, report)
|
||||
self.assertEqual(report.read_text(encoding='utf-8'), 'keep me')
|
||||
|
||||
|
||||
class GateTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name)
|
||||
self.cfg, self.rows = inventory(self.root)
|
||||
|
||||
def tearDown(self):
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_exact_120_cells_and_fixed_two_probes(self):
|
||||
controls, rows = bridge.inventories(self.root, self.cfg)
|
||||
self.assertEqual(len(controls), 2)
|
||||
for cohort in bridge.COHORTS:
|
||||
probes = bridge.probe_rows(rows, self.cfg, cohort)
|
||||
self.assertEqual(len(probes), 2)
|
||||
self.assertTrue(all(row['odor_index'] == 0 and row['seed'] == 101 for row in probes))
|
||||
|
||||
def test_duplicate_or_replaced_cell_rejected(self):
|
||||
rows = copy.deepcopy(self.rows)
|
||||
rows[-1] = rows[0]
|
||||
write_json(self.root / 'protocol/validation-cases.json', rows)
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.inventories(self.root, self.cfg)
|
||||
|
||||
def test_controls_do_not_run_brian_or_repeats(self):
|
||||
name = self.cfg['control_cases'][0]
|
||||
bridge.authorize_case(self.root, self.cfg, 'controls', name, 'gpu', 0)
|
||||
for backend, repeat in (('oracle', 0), ('gpu', 1)):
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.authorize_case(self.root, self.cfg, 'controls', name, backend, repeat)
|
||||
|
||||
def test_probes_before_other_seed_runs(self):
|
||||
gate(self.root, 'controls')
|
||||
probe = bridge.probe_rows(self.rows, self.cfg, 'seed_validation')[0]
|
||||
bridge.authorize_case(self.root, self.cfg, 'seed_validation', probe['name'], 'oracle', 0)
|
||||
later = next(row for row in self.rows if row['cohort'] == 'seed_validation' and row['seed'] == 102)
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
bridge.authorize_case(self.root, self.cfg, 'seed_validation', later['name'], 'gpu', 0)
|
||||
gate(self.root, 'seed-probes')
|
||||
bridge.authorize_case(self.root, self.cfg, 'seed_validation', later['name'], 'gpu', 0)
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.authorize_case(self.root, self.cfg, 'seed_validation', later['name'], 'oracle', 0)
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.authorize_case(self.root, self.cfg, 'seed_validation', later['name'], 'gpu', 1)
|
||||
|
||||
def test_novel_denied_on_representation_failure_even_execution_passes(self):
|
||||
gate(self.root, 'controls')
|
||||
gate(self.root, 'seed-validation', execution_gate_pass=True, representation_gate_pass=False)
|
||||
probe = bridge.probe_rows(self.rows, self.cfg, 'novel_to_aj')[0]
|
||||
with self.assertRaisesRegex(ValueError, 'representation'):
|
||||
bridge.authorize_case(self.root, self.cfg, 'novel_to_aj', probe['name'], 'gpu', 0)
|
||||
|
||||
def test_novel_requires_both_gate_and_underlying_summary(self):
|
||||
gate(self.root, 'controls')
|
||||
probe = bridge.probe_rows(self.rows, self.cfg, 'novel_to_aj')[0]
|
||||
summary = seed_summary(self.root, self.cfg, self.rows)
|
||||
summary['representation_gate_pass'] = False
|
||||
bind_seed_gate(self.root, summary)
|
||||
with self.assertRaisesRegex(ValueError, 'summary'):
|
||||
bridge.authorize_case(self.root, self.cfg, 'novel_to_aj', probe['name'], 'gpu', 0)
|
||||
summary['representation_gate_pass'] = True
|
||||
bind_seed_gate(self.root, summary)
|
||||
bridge.authorize_case(self.root, self.cfg, 'novel_to_aj', probe['name'], 'gpu', 0)
|
||||
|
||||
def test_novel_rejects_summary_hash_or_cohort_misbinding(self):
|
||||
gate(self.root, 'controls')
|
||||
summary = seed_summary(self.root, self.cfg, self.rows)
|
||||
bind_seed_gate(self.root, summary)
|
||||
probe = bridge.probe_rows(self.rows, self.cfg, 'novel_to_aj')[0]
|
||||
summary['cohort'] = 'novel_to_aj'
|
||||
write_json(self.root / 'validation/seed_validation/summary.json', summary)
|
||||
with self.assertRaisesRegex(ValueError, 'exact summary'):
|
||||
bridge.authorize_case(self.root, self.cfg, 'novel_to_aj', probe['name'], 'gpu', 0)
|
||||
bind_seed_gate(self.root, summary)
|
||||
with self.assertRaisesRegex(ValueError, 'identity'):
|
||||
bridge.authorize_case(self.root, self.cfg, 'novel_to_aj', probe['name'], 'gpu', 0)
|
||||
|
||||
def test_novel_rejects_replaced_case_repeat_or_timing_even_with_correct_counts(self):
|
||||
gate(self.root, 'controls')
|
||||
probe = bridge.probe_rows(self.rows, self.cfg, 'novel_to_aj')[0]
|
||||
for key in ('runs', 'repeats', 'timing_checks', 'input_output_provenance'):
|
||||
with self.subTest(key=key):
|
||||
summary = seed_summary(self.root, self.cfg, self.rows)
|
||||
summary[key][0]['case'] = 'not-a-registered-case'
|
||||
bind_seed_gate(self.root, summary)
|
||||
with self.assertRaises(ValueError):
|
||||
bridge.authorize_case(self.root, self.cfg, 'novel_to_aj', probe['name'], 'gpu', 0)
|
||||
|
||||
def test_any_stop_marker_prevents_all_scientific_actions(self):
|
||||
for kind in ('representation_failure', 'execution_or_numerical_failure'):
|
||||
with self.subTest(kind=kind):
|
||||
write_json(self.root / 'audit/stopped-seed-validation.json', {'stop_kind': kind})
|
||||
for backend in ('gpu', 'oracle', 'component'):
|
||||
with self.assertRaisesRegex(ValueError, 'stop marker'):
|
||||
bridge.authorize_case(self.root, self.cfg, 'controls', self.cfg['control_cases'][0], backend, 0)
|
||||
|
||||
def test_standalone_action_failure_persists_stop_and_never_retries(self):
|
||||
action = mock.Mock(side_effect=RuntimeError('synthetic failure; no science executed'))
|
||||
with self.assertRaisesRegex(RuntimeError, 'synthetic failure'):
|
||||
bridge.guarded_action(self.root, 'gpu', 'controls', 'control-phys', 0, action)
|
||||
marker = self.root / 'audit/stopped-case-gpu-controls-control-phys-r0.json'
|
||||
report = bridge.read(marker)
|
||||
self.assertEqual(report['stop_kind'], 'execution_or_numerical_failure')
|
||||
self.assertFalse(report['numerical_instability_inferred_from_execution_error'])
|
||||
before = marker.read_bytes()
|
||||
with self.assertRaisesRegex(ValueError, 'stop marker'):
|
||||
bridge.guarded_action(self.root, 'gpu', 'controls', 'control-phys', 0, action)
|
||||
self.assertEqual(action.call_count, 1)
|
||||
self.assertEqual(marker.read_bytes(), before)
|
||||
|
||||
def test_successful_guarded_action_adds_no_stop(self):
|
||||
expected = {'pass_all': True}
|
||||
self.assertIs(bridge.guarded_action(self.root, 'gpu', 'controls', 'control-phys', 0, lambda: expected), expected)
|
||||
bridge.ensure_unstopped(self.root)
|
||||
|
||||
|
||||
class InvocationTests(unittest.TestCase):
|
||||
def test_original_gpu_function_receives_new_paths_unchanged(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
case, output, build = root / 'case', root / 'new-exp/gpu/run', root / 'new-exp/build'
|
||||
data = spec(root)
|
||||
data['budget_root'] = str(root / 'new-exp')
|
||||
write_json(case / 'spec.json', data)
|
||||
runtime = types.SimpleNamespace(run_gpu=mock.Mock(return_value={'pass_all': True}))
|
||||
with mock.patch.object(bridge, 'sealed', return_value=runtime), mock.patch.object(bridge, '_receipt'):
|
||||
result = bridge.run_gpu(case, output, build)
|
||||
self.assertTrue(result['pass_all'])
|
||||
runtime.run_gpu.assert_called_once_with(case, output, build)
|
||||
|
||||
def test_original_oracle_function_is_not_given_candidate_trace(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
case, output = root / 'case', root / 'new-exp/oracle/run'
|
||||
data = spec(root); data['budget_root'] = str(root / 'new-exp')
|
||||
write_json(case / 'spec.json', data)
|
||||
runtime = types.SimpleNamespace(run_case=mock.Mock(return_value={'pass_all': True}))
|
||||
with mock.patch.object(bridge, 'sealed', return_value=runtime), mock.patch.object(bridge, '_receipt'):
|
||||
bridge.run_oracle(case, output)
|
||||
runtime.run_case.assert_called_once_with(case, output)
|
||||
|
||||
|
||||
class ControlEvidenceTests(unittest.TestCase):
|
||||
def test_real_sealed_comparator_accepts_complete_synthetic_d18_and_rejects_signed_zero_tail(self):
|
||||
# Artifact construction only: these zero traces were not produced by any model.
|
||||
aj = bridge.sealed('compare_timing.py', AJ)
|
||||
timing = bridge.sealed('timing_queue.py', AJ)
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
cases, outputs = [root / name for name in ('old-case', 'new-case')], [root / name for name in ('old-out', 'new-out')]
|
||||
base = spec(root)
|
||||
(root / 'graph').mkdir()
|
||||
np.save(root / 'graph/weights.npy', np.zeros(1, np.float64), allow_pickle=False)
|
||||
write_json(root / 'coefficients.json', {'synthetic_only': True})
|
||||
states = dict(v=np.zeros(166700, np.float64), g=np.zeros(166700, np.float64),
|
||||
ref=np.zeros(166700, np.int32), counts=np.zeros(166700, np.int32),
|
||||
step=np.empty(0, np.int32), neuron_idx=np.empty(0, np.int32))
|
||||
generated = np.zeros((1000, 4064), np.float32)
|
||||
pending = np.zeros((18, 4064), np.float32)
|
||||
audit = timing.audit_sequence(generated, generated, pending, 18)
|
||||
audit.update(generated_rows=1000, applied_generated_rows=982, pending_shape=[18, 4064],
|
||||
pending_application_tick_start=1000, pending_application_tick_stop_exclusive=1018,
|
||||
pending_generated_tick_start=982, pending_generated_tick_stop_exclusive=1000,
|
||||
pending_raw_sha256=aj.raw_hash(pending), final_apl_state_float32=[0., 0.],
|
||||
final_apl_state_u32_bits=[0, 0], final_apl_state_nonzero=False,
|
||||
finite_generated_sequence_only=True, future_apl_decay_or_feedback_generated=False,
|
||||
closed_loop_branch_integrals_claimed_equal=False, biological_delay_measurement_claimed=False)
|
||||
hashes = [{**{name + '_sha256': aj.raw_hash(states[name]) for name in ('v', 'g', 'ref')}, 'tick': tick}
|
||||
for tick in range(1000)]
|
||||
queues = [aj._queue_digest(tick, np.empty(0, np.int64), np.empty(0, np.uint8)) for tick in range(1000)]
|
||||
for index, (case, output) in enumerate(zip(cases, outputs)):
|
||||
data = dict(base, budget_root=str(root / ('budget-' + str(index))), output_budget_bytes=10000 + index)
|
||||
write_json(case / 'spec.json', data)
|
||||
for name in ('requests.npz', 'pn-drive.npz'):
|
||||
(case / name).write_bytes(b'synthetic-identical-input')
|
||||
output.mkdir()
|
||||
np.savez_compressed(output / 'state.npz', **states)
|
||||
for name, value in (('drive', np.zeros((1000, 2), np.float32)), ('state', np.zeros((1000, 2), np.float32)),
|
||||
('inhibition', generated), ('applied', generated), ('pending-tail', pending)):
|
||||
np.save(output / ('apl-' + name + '.float32.npy'), value, allow_pickle=False)
|
||||
for name, rows in (('tick-state-hashes.jsonl', hashes), ('queue.jsonl', queues)):
|
||||
(output / name).write_text(''.join(json.dumps(row) + '\n' for row in rows), encoding='utf-8')
|
||||
(output / 'requests.csv').write_text('tick,neuron_idx,attempted,accepted,rejected\n', encoding='utf-8')
|
||||
component = {'synthetic_fixture': True}
|
||||
write_json(output / 'apl-component.json', component)
|
||||
write_json(output / 'apl-timing-audit.json', audit)
|
||||
inputs = {name: bridge.digest(case / name) for name in ('spec.json', 'requests.npz')}
|
||||
inputs.update({'base_weights.npy': bridge.digest(root / 'graph/weights.npy'),
|
||||
'coefficients.json': bridge.digest(root / 'coefficients.json')})
|
||||
summary = dict(case=base['name'], complete=True, pass_all=True, steps=1000, completed_ticks=1000,
|
||||
nodes=166700, edges=25582938, precision='float64', apl_precision='float32', state_units='SI volts',
|
||||
total_spikes=0, requested=0, accepted_requests=0, rejected_requests=0, arriving_edges=0,
|
||||
accepted_arriving_edges=0, counts_match_sparse_events=True, all_finite=True, record_history=False,
|
||||
apl_fast_edges_zeroed=5845, kc_targets_present=4064, effective_weights_raw_sha256='0' * 64,
|
||||
apl_schedule='same synthetic schedule', apl_si_conversion='same synthetic conversion',
|
||||
sealed_ae_backend_sha256='1' * 64, learning=False, apl_delay_steps=18, apl_timing=audit,
|
||||
source_provenance={'same_sealed_source': True}, pending_generated_count=18,
|
||||
pending_initial_zero_prefix=0, pending_tail_shape=[18, 4064], future_generated_decay_tail_included=False,
|
||||
apl_component=component, input_sha256=inputs, build={'path': str(root / ('build-' + str(index)))})
|
||||
write_json(output / 'summary.json', summary)
|
||||
result = bridge.control_identity(outputs[1], outputs[0], cases[1], cases[0], aj_model=AJ)
|
||||
self.assertTrue(result['pass_all'], result.get('error', result['failed_checks']))
|
||||
pending[0, 0] = np.float32(-0.)
|
||||
np.save(outputs[1] / 'apl-pending-tail.float32.npy', pending, allow_pickle=False)
|
||||
failed = bridge.control_identity(outputs[1], outputs[0], cases[1], cases[0], aj_model=AJ)
|
||||
self.assertFalse(failed['pass_all'])
|
||||
self.assertIn('apl.pending.bitwise_exact', failed['failed_checks'])
|
||||
self.assertIn('fresh_gpu.fixed_delay_pending.bitwise_exact', failed['failed_checks'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,298 @@
|
||||
"""Synthetic-only validation tests: no historical outputs or brain execution."""
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import validation as val
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
LEGACY = val.load_frozen(HERE.parent / "author-apl-lab-v2/calibration.py", val.AG_SHA256, "ak_test_ag")
|
||||
TIMING = val.load_frozen(HERE.parent / "apl-timing-lab-v1/calibration.py", val.AJ_SHA256, "ak_test_aj")
|
||||
|
||||
|
||||
def config():
|
||||
cfg = val.read(HERE / "config.json")
|
||||
cfg["validation"]["seeds"] = [1001, 1002, 1003, 1004, 1005] # Synthetic, not proposed scientific seeds.
|
||||
return cfg
|
||||
|
||||
|
||||
def panel():
|
||||
return [dict(odor_id=oid, odor_name=oid, split="calibration" if i < 6 else "heldout",
|
||||
mapped_response_sum=val.MEDIAN)
|
||||
for i, oid in enumerate(sorted(LEGACY.ORIGINAL_CAL_IDS) + ["synthetic-new-" + str(i) for i in range(6)])]
|
||||
|
||||
|
||||
def records(cfg):
|
||||
rows = []
|
||||
pp = panel()
|
||||
for ci, cohort in enumerate(val.COHORTS):
|
||||
for mode in val.MODES:
|
||||
for oi in range(6):
|
||||
for seed in cfg["validation"]["seeds"]:
|
||||
rows.append(dict(name=f"test-{mode}-{oi}-{seed}", cohort=cohort,
|
||||
source_panel_split=val.SPLITS[cohort], mode=mode, odor_id=pp[6 * ci + oi]["odor_id"],
|
||||
odor_index=oi, seed=seed, targets=1, requests=0))
|
||||
return rows
|
||||
|
||||
|
||||
def encoder(cfg, inventory):
|
||||
return dict(panel_count=12, cal_count=6, novel_to_aj_count=6, median_mapped_response_sum=val.MEDIAN,
|
||||
seeds=cfg["validation"]["seeds"], seeds_static_audit_sha256="a" * 64,
|
||||
cohorts_static_panel_rows={cohort: [dict(odor_id=oid, odor_index=i, source_panel_split=val.SPLITS[cohort])
|
||||
for i, oid in enumerate(inventory[cohort]["odor_ids"])] for cohort in val.COHORTS},
|
||||
requested_before_any_dynamics=True, historical_never_simulated_claim=False)
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, allow_nan=False)
|
||||
|
||||
|
||||
def make_raw_trial(root):
|
||||
"""Tiny neuron state with the real full-width APL trace schema."""
|
||||
output, case, bundle = root / "out", root / "case", root / "bundle"
|
||||
output.mkdir(); case.mkdir(); bundle.mkdir()
|
||||
steps, neurons = 20, 2
|
||||
cfg = dict(neurons=neurons, edges=0)
|
||||
for filename in ("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy"):
|
||||
np.save(bundle / filename, np.zeros(1, np.int32))
|
||||
spec = dict(name="synthetic", steps=steps, validation_cohort="novel_to_aj", source_panel_split="heldout",
|
||||
apl_delay_steps=18, mode="physiological", odor_id="synthetic", odor_name="synthetic", seed=1001,
|
||||
expected_external_events=1., rate_sum_hz=50., apl_bundle_dir=str(bundle))
|
||||
save(case / "spec.json", spec)
|
||||
np.savez(case / "requests.npz", tick=np.empty(0, np.int32), neuron_idx=np.empty(0, np.int32))
|
||||
state = {"step": np.empty(0, np.int32), "neuron_idx": np.empty(0, np.int32),
|
||||
"counts": np.zeros(neurons, np.int32), "v": np.zeros(neurons, np.float64),
|
||||
"g": np.zeros(neurons, np.float64), "ref": np.zeros(neurons, np.int32)}
|
||||
np.savez(output / "state.npz", **state)
|
||||
hashes = [dict(tick=t, **{key + "_sha256": LEGACY.raw_hash(state[key]) for key in ("v", "g", "ref")}) for t in range(steps)]
|
||||
queues = [dict(tick=t, count=0, accepted_count=0, ordered_edge_sha256="0" * 64,
|
||||
accepted_mask_sha256="0" * 64, joint_sha256="0" * 64) for t in range(steps)]
|
||||
for filename, rows in (("tick-state-hashes.jsonl", hashes), ("queue.jsonl", queues)):
|
||||
(output / filename).write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8")
|
||||
(output / "requests.csv").write_text("tick,neuron_idx,attempted,accepted,rejected\n", encoding="utf-8")
|
||||
for filename, width in LEGACY.APL_FILES.items():
|
||||
np.save(output / filename, np.zeros((steps, width), np.float32))
|
||||
np.save(output / "apl-applied.float32.npy", np.zeros((steps, 4064), np.float32))
|
||||
pending = np.zeros((18, 4064), np.float32)
|
||||
np.save(output / "apl-pending-tail.float32.npy", pending)
|
||||
audit = dict(pass_all=True, apl_delay_steps=18, steps=steps, pending_generated_count=18,
|
||||
pending_initial_zero_prefix=0, pending_application_tick_start=steps,
|
||||
pending_application_tick_stop_exclusive=steps + 18, pending_generated_tick_start=steps - 18,
|
||||
pending_generated_tick_stop_exclusive=steps, initial_padding_positive_zero=True,
|
||||
sample_bits_preserved=True, brain_tail_advanced=False, generated_trace="apl-inhibition.float32.npy",
|
||||
applied_trace="apl-applied.float32.npy", pending_trace="apl-pending-tail.float32.npy",
|
||||
pending_raw_sha256=LEGACY.raw_hash(pending), pending_queue_drained=False,
|
||||
finite_generated_sequence_only=True, future_apl_decay_or_feedback_generated=False,
|
||||
closed_loop_branch_integrals_claimed_equal=False, biological_delay_measurement_claimed=False,
|
||||
final_apl_state_float32=[0., 0.], final_apl_state_u32_bits=[0, 0], final_apl_state_nonzero=False)
|
||||
save(output / "apl-timing-audit.json", audit)
|
||||
component = dict(ok=True, n_neurons=neurons, n_kc=2, n_apl=2, dt_ms=.1,
|
||||
files_sha256={name: val.digest(bundle / name) for name in
|
||||
("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")})
|
||||
save(output / "apl-component.json", component)
|
||||
save(output / "summary.json", dict(complete=True, pass_all=True, all_finite=True, steps=steps,
|
||||
completed_ticks=steps, nodes=neurons, edges=0, precision="float64", apl_precision="float32",
|
||||
case=spec["name"], record_history=False, learning=False, requested=0, accepted_requests=0,
|
||||
rejected_requests=0, total_spikes=0, apl_component=component, apl_delay_steps=18, apl_timing=audit,
|
||||
input_sha256={name: val.digest(case / name) for name in ("spec.json", "requests.npz")}))
|
||||
return output, case, spec, cfg
|
||||
|
||||
|
||||
def fake_trial(row, cohort, silent=False):
|
||||
counts = np.zeros(1000, np.int32)
|
||||
if not silent:
|
||||
counts[row["odor_index"] * 50:row["odor_index"] * 50 + 50] = 1
|
||||
return dict(kc_counts=counts, expected_external_events=1., external_events=1,
|
||||
record=dict(case=row["name"], mode=row["mode"], seed=row["seed"], odor_id=row["odor_id"],
|
||||
requested=row["requests"], split=cohort, cohort=cohort, source_panel_split=val.SPLITS[cohort]),
|
||||
timing=dict(report=dict(pass_all=True)), apl={})
|
||||
|
||||
|
||||
class ValidationTests(unittest.TestCase):
|
||||
def test_source_pins_and_globals_are_unchanged(self):
|
||||
self.assertEqual(LEGACY.ORIGINAL_SEEDS, (51101, 51102, 51103, 51104, 51105))
|
||||
self.assertFalse(hasattr(LEGACY, "EXP"))
|
||||
self.assertFalse(hasattr(TIMING, "EXP"))
|
||||
with self.assertRaisesRegex(ValueError, "SHA"):
|
||||
val.load_frozen(HERE.parent / "author-apl-lab-v2/calibration.py", "0" * 64, "bad_pin")
|
||||
|
||||
def test_inventory_accepts_configured_seeds_and_keeps_static_halves(self):
|
||||
cfg = config(); inv = val.validate_inventory(records(cfg), cfg, panel(), LEGACY)
|
||||
self.assertEqual([len(inv[c]["records"]) for c in val.COHORTS], [60, 60])
|
||||
self.assertEqual(inv["novel_to_aj"]["odor_ids"], ["synthetic-new-" + str(i) for i in range(6)])
|
||||
val.validate_encoder(encoder(cfg, inv), inv, cfg)
|
||||
self.assertEqual(LEGACY.ORIGINAL_SEEDS[0], 51101)
|
||||
|
||||
def test_inventory_rejects_missing_duplicate_mislabelled_and_unaudited_cells(self):
|
||||
cfg = config()
|
||||
variants = []
|
||||
rr = records(cfg); variants.append(rr[:-1])
|
||||
rr = records(cfg); rr[-1] = copy.deepcopy(rr[-2]); variants.append(rr)
|
||||
for field, value in (("source_panel_split", "heldout"), ("odor_id", "wrong"),
|
||||
("seed", 51101), ("name", "../outside"), ("odor_index", True), ("targets", 0)):
|
||||
rr = records(cfg); rr[0][field] = value; variants.append(rr)
|
||||
for rr in variants:
|
||||
with self.subTest(first=rr[0]), self.assertRaises(ValueError):
|
||||
val.validate_inventory(rr, cfg, panel(), LEGACY)
|
||||
|
||||
def test_no_gate_relaxation_or_subset_median(self):
|
||||
for key, value in (("minimum_separable_odor_pairs", 1), ("sparse_min_fraction", .01),
|
||||
("repeat_exact_required", False)):
|
||||
cfg = config(); cfg["gates"][key] = value
|
||||
with self.subTest(key=key), self.assertRaises(ValueError):
|
||||
val.validate_inventory(records(cfg), cfg, panel(), LEGACY)
|
||||
pp = panel()
|
||||
for row in pp:
|
||||
row["mapped_response_sum"] = 1.
|
||||
with self.assertRaisesRegex(ValueError, "median"):
|
||||
val.validate_inventory(records(config()), config(), pp, LEGACY)
|
||||
|
||||
def test_encoder_rejects_history_claim_or_changed_panel_rows(self):
|
||||
cfg = config(); inv = val.validate_inventory(records(cfg), cfg, panel(), LEGACY)
|
||||
for key, value in (("historical_never_simulated_claim", True), ("requested_before_any_dynamics", False),
|
||||
("seeds_static_audit_sha256", ""), ("cohorts_static_panel_rows", {})):
|
||||
ee = encoder(cfg, inv); ee[key] = value
|
||||
with self.subTest(key=key), self.assertRaises(ValueError):
|
||||
val.validate_encoder(ee, inv, cfg)
|
||||
|
||||
def test_encoder_actual_prepare_panel_row_extension(self):
|
||||
cfg = config(); inv = val.validate_inventory(records(cfg), cfg, panel(), LEGACY)
|
||||
ee = encoder(cfg, inv)
|
||||
for ci, cohort in enumerate(val.COHORTS):
|
||||
for oi, row in enumerate(ee["cohorts_static_panel_rows"][cohort]):
|
||||
row["source_panel_row"] = ci * 6 + oi
|
||||
val.validate_encoder(ee, inv, cfg)
|
||||
ee["cohorts_static_panel_rows"]["novel_to_aj"][0]["source_panel_row"] = 0
|
||||
with self.assertRaisesRegex(ValueError, "panel row"):
|
||||
val.validate_encoder(ee, inv, cfg)
|
||||
|
||||
def test_fullgraph_case_matches_static_physics_and_exact_input_expectation(self):
|
||||
cfg = config(); row = records(cfg)[60]
|
||||
row.update(source_panel_row=6, expected_external_events=10., rates_sum=100.)
|
||||
spec = dict(name=row["name"], kind="fullgraph", validation_cohort=row["cohort"],
|
||||
source_panel_split=row["source_panel_split"], source_panel_row=6,
|
||||
**{key: row[key] for key in ("mode", "odor_id", "odor_index", "seed")},
|
||||
apl_delay_steps=18, steps=1000, dt_ms=.1, duration_ms=100., training=False, record_history=False,
|
||||
model={"resting_potential_mv": -52.}, graph_dir="unchanged-graph", apl_bundle_dir="unchanged-bundle",
|
||||
apl={"gain": .01}, coefficients_path="unchanged-coefficients", science_python="unchanged-entry",
|
||||
legacy_apl_worker="unchanged-worker", precision="float64", apl_precision="float32",
|
||||
target_count=1, rate_sum_hz=100., expected_external_events=10.)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
case = Path(tmp)
|
||||
save(case / "spec.json", spec)
|
||||
np.savez(case / "pn-drive.npz", targets=np.array([10], np.int32), rates_hz=np.array([100.], np.float32))
|
||||
np.savez(case / "requests.npz", tick=np.empty(0, np.int32), neuron_idx=np.empty(0, np.int32))
|
||||
self.assertEqual(val.validate_case(case, row, cfg, spec), spec)
|
||||
wrong = copy.deepcopy(spec); wrong["model"]["resting_potential_mv"] = -51.
|
||||
with self.assertRaisesRegex(ValueError, "physical field"):
|
||||
val.validate_case(case, row, cfg, wrong)
|
||||
wrong_row = dict(row, targets=2)
|
||||
with self.assertRaisesRegex(ValueError, "PN drive"):
|
||||
val.validate_case(case, wrong_row, cfg, spec)
|
||||
|
||||
def test_real_legacy_reader_record_is_relabelled_without_globals_or_numerical_changes(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output, case, spec, cfg = make_raw_trial(Path(tmp))
|
||||
trial = val.read_validation_trial(LEGACY, TIMING, output, case, spec, np.arange(2, dtype=np.int32), cfg)
|
||||
try:
|
||||
self.assertEqual(trial["record"]["split"], "novel_to_aj")
|
||||
self.assertEqual(trial["record"]["source_panel_split"], "heldout")
|
||||
self.assertFalse(trial["record"]["historical_never_simulated_claim"])
|
||||
self.assertTrue(trial["timing"]["report"]["pass_all"])
|
||||
self.assertEqual(LEGACY.ORIGINAL_SEEDS[0], 51101)
|
||||
finally:
|
||||
val.close_trial(trial)
|
||||
|
||||
def test_real_reader_still_rejects_missing_state_and_delay_zero(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output, case, spec, cfg = make_raw_trial(Path(tmp))
|
||||
bad = dict(spec, apl_delay_steps=0)
|
||||
with self.assertRaisesRegex(ValueError, "delay"):
|
||||
val.read_validation_trial(LEGACY, TIMING, output, case, bad, np.arange(2, dtype=np.int32), cfg)
|
||||
(output / "state.npz").unlink()
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
val.read_validation_trial(LEGACY, TIMING, output, case, spec, np.arange(2, dtype=np.int32), cfg)
|
||||
|
||||
def collect(self, silent=False, broken_repeat=False, missing=False):
|
||||
cfg = config(); inv = val.validate_inventory(records(cfg), cfg, panel(), LEGACY)
|
||||
cohort = "seed_validation"; rows = inv[cohort]["records"]
|
||||
byname = {row["name"]: row for row in rows}
|
||||
accessed = []
|
||||
def load(legacy, timing, output, case, spec, kc, config_arg):
|
||||
accessed.append(str(output))
|
||||
if missing and spec["name"] == rows[0]["name"]:
|
||||
raise FileNotFoundError("synthetic missing evidence")
|
||||
return fake_trial(byname[spec["name"]], cohort, silent)
|
||||
timing = types.SimpleNamespace(timing_repeat=lambda *args: dict(pass_all=not broken_repeat,
|
||||
checks=dict(apl_applied=not broken_repeat)))
|
||||
with patch.object(val, "validate_case", side_effect=lambda case, row, *args: row), \
|
||||
patch.object(val, "read_validation_trial", side_effect=load), \
|
||||
patch.object(val, "evidence_hashes", return_value={"synthetic": True}):
|
||||
result = val.collect_cohort(cohort, rows, inv[cohort]["odor_ids"], cfg, Path("synthetic"),
|
||||
np.arange(2, dtype=np.int32), {}, LEGACY, timing)
|
||||
return result, accessed
|
||||
|
||||
def test_full_60_plus_12_and_pair_specific_gate_without_other_cohort_read(self):
|
||||
result, accessed = self.collect()
|
||||
self.assertTrue(result["pass_all"])
|
||||
self.assertEqual((result["valid_base_trials"], result["checked_repeats"], result["checked_timing_trials"]), (60, 12, 72))
|
||||
self.assertEqual(len(accessed), 72)
|
||||
self.assertFalse(any("novel_to_aj" in path for path in accessed))
|
||||
for report in result["modes"].values():
|
||||
self.assertEqual((report["pair_count"], report["within_distance_count"], report["between_distance_count"]), (15, 60, 75))
|
||||
json.dumps(result, allow_nan=False)
|
||||
|
||||
def test_representation_failure_keeps_all_runs_and_is_not_execution_failure(self):
|
||||
result, accessed = self.collect(silent=True)
|
||||
self.assertTrue(result["execution_gate_pass"])
|
||||
self.assertFalse(result["representation_gate_pass"])
|
||||
self.assertFalse(result["pass_all"])
|
||||
self.assertEqual(len(accessed), 72)
|
||||
self.assertTrue(all(row["stage"] == "representation_gate" for row in result["failures"]))
|
||||
|
||||
def test_missing_evidence_and_repeat_failure_never_pass_execution(self):
|
||||
for options in (dict(missing=True), dict(broken_repeat=True)):
|
||||
with self.subTest(options=options):
|
||||
result, _ = self.collect(**options)
|
||||
self.assertFalse(result["execution_gate_pass"])
|
||||
self.assertFalse(result["pass_all"])
|
||||
self.assertTrue(result["failures"])
|
||||
|
||||
def test_none_recompute_is_deterministic_and_writes_nothing(self):
|
||||
cfg = config(); rr = records(cfg); inv = val.validate_inventory(rr, cfg, panel(), LEGACY)
|
||||
ee = encoder(cfg, inv)
|
||||
ctx = types.SimpleNamespace(ROOT=Path("synthetic-root"), MODEL=Path("synthetic-model"), EXP=Path("synthetic-exp"),
|
||||
AG_MODEL=Path("ag-model"), AJ_MODEL=Path("aj-model"), AG=Path("ag"), AJ=Path("aj"), CFG=cfg,
|
||||
check=lambda: {}, save=lambda *args: self.fail("Read-only summarize wrote output"))
|
||||
collected, _ = self.collect()
|
||||
def read_fixture(path):
|
||||
if path.name == "validation-cases.json": return rr
|
||||
if path.name == "encoder-audit.json": return ee
|
||||
if path.name == "spec.json": return dict(apl_delay_steps=18, training=False)
|
||||
raise AssertionError("Unexpected input read: " + str(path))
|
||||
def hash_fixture(path):
|
||||
return val.PANEL_SHA256 if path.name == "frozen-odor-panel.parquet" else "a" * 64
|
||||
with patch.dict("sys.modules", context=ctx), patch.object(val, "read", side_effect=read_fixture), \
|
||||
patch.object(val, "digest", side_effect=hash_fixture), \
|
||||
patch.object(val, "load_frozen", side_effect=[LEGACY, TIMING, LEGACY, TIMING]), \
|
||||
patch.object(pd, "read_parquet", return_value=pd.DataFrame(panel())), \
|
||||
patch.object(np, "load", return_value=np.arange(4064, dtype=np.int32)), \
|
||||
patch.object(val, "collect_cohort", side_effect=lambda *args: copy.deepcopy(collected)):
|
||||
first = val.summarize("seed_validation", None)
|
||||
second = val.summarize("seed_validation", None)
|
||||
self.assertEqual(first, second)
|
||||
self.assertTrue(first["pass_all"])
|
||||
json.dumps(first, allow_nan=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,344 @@
|
||||
"""Frozen-D18 validation cohorts; no simulation, fitting, or old heldout read.
|
||||
|
||||
Only the requested cohort's new trajectories are opened. The suite owns the
|
||||
seed-validation -> novel-to-AJ unlock. Legacy modules are source-hash pinned,
|
||||
loaded without writing bytecode, and never have their globals rebound.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
|
||||
AG_SHA256 = "edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c"
|
||||
AJ_SHA256 = "fb15ddeaa108305c8de697bbae36b23ab0414a7c58db51c66dce9feb4a048a0d"
|
||||
PANEL_SHA256 = "907f16a56e024a8e2112b5751e53b0e6037b329fa110098af3d31b119c10a1a0"
|
||||
COHORTS = ("seed_validation", "novel_to_aj")
|
||||
SPLITS = dict(zip(COHORTS, ("calibration", "heldout")))
|
||||
MODES = ("physiological", "matched_energy")
|
||||
MEDIAN = 2.556107997894287
|
||||
_DEFAULT_OUTPUT = object()
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def digest(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b""):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def load_frozen(path, expected, name):
|
||||
path = Path(path)
|
||||
source = path.read_bytes()
|
||||
require(hashlib.sha256(source).hexdigest() == expected, "Frozen source SHA mismatch: " + str(path))
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
sys.modules[name] = module
|
||||
exec(compile(source, str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def validate_design(cfg):
|
||||
design = cfg["validation"]
|
||||
seeds = design["seeds"]
|
||||
require(isinstance(seeds, list) and len(seeds) == 5 and len(set(seeds)) == 5 and
|
||||
all(type(seed) is int and 0 <= seed < 2**63 for seed in seeds), "Need five explicitly audited seed integers")
|
||||
require(design["cohorts"] == list(COHORTS) and design["modes"] == list(MODES) and
|
||||
design["odor_count"] == 6 and design["duration_ms"] == 100.0 and
|
||||
design["repeat_first_seed_each_odor"] is True, "Validation design changed")
|
||||
require(type(cfg["apl_delay_steps"]) is int and cfg["apl_delay_steps"] == 18 and
|
||||
cfg["neurons"] == 166700 and cfg["edges"] == 25582938 and cfg["training"] is False,
|
||||
"Frozen D18/fullgraph/nonlearning scope changed")
|
||||
gates = cfg["gates"]
|
||||
require(gates["sparse_min_fraction"] == .03 and gates["sparse_max_fraction"] == .09 and
|
||||
gates["max_fraction_strict"] == .5 and gates["min_fraction_strict"] == 0 and
|
||||
gates["minimum_separable_odor_pairs"] == 3 and all(gates[key] is True for key in
|
||||
("same_jaccard_gt_between", "between_euclidean_gt_within", "both_modes_required",
|
||||
"finite_required", "repeat_exact_required")), "Original complete representation/integrity gates changed")
|
||||
return seeds
|
||||
|
||||
|
||||
def validate_inventory(records, cfg, panel_rows, legacy):
|
||||
"""Static 120-row inventory, preserving original panel row order in each half."""
|
||||
seeds = validate_design(cfg)
|
||||
require(isinstance(records, list) and len(records) == 120, "Inventory must contain exactly 120 base cases")
|
||||
require(len(panel_rows) == 12 and [row["split"] for row in panel_rows] == ["calibration"] * 6 + ["heldout"] * 6,
|
||||
"Frozen static panel order/splits changed")
|
||||
all_ids = [row["odor_id"] for row in panel_rows]
|
||||
require(all(isinstance(oid, str) and oid for oid in all_ids) and len(set(all_ids)) == 12 and
|
||||
set(all_ids[:6]) == legacy.ORIGINAL_CAL_IDS, "Frozen panel identities invalid")
|
||||
require(float(np.median([row["mapped_response_sum"] for row in panel_rows])) == MEDIAN,
|
||||
"Matched-energy reference must remain the original full-12 median")
|
||||
result = {}
|
||||
names, keys = set(), set()
|
||||
for row in records:
|
||||
require(row["cohort"] in COHORTS and row["mode"] in MODES, "Unknown cohort/mode")
|
||||
require(type(row["odor_index"]) is int and 0 <= row["odor_index"] < 6 and
|
||||
type(row["seed"]) is int and row["seed"] in seeds, "Invalid odor index/seed")
|
||||
require(isinstance(row["name"], str) and re.fullmatch(r"[A-Za-z0-9_-]+", row["name"]), "Unsafe case name")
|
||||
namekey = (row["cohort"], row["name"])
|
||||
key = (row["cohort"], row["mode"], row["odor_index"], row["seed"])
|
||||
require(namekey not in names and key not in keys, "Duplicate case name or experimental cell")
|
||||
names.add(namekey); keys.add(key)
|
||||
offset = 0 if row["cohort"] == "seed_validation" else 6
|
||||
require(row["source_panel_split"] == SPLITS[row["cohort"]] and
|
||||
row["odor_id"] == all_ids[offset + row["odor_index"]], "Cohort identity/order/source split mismatch")
|
||||
if "source_panel_row" in row:
|
||||
require(type(row["source_panel_row"]) is int and row["source_panel_row"] == offset + row["odor_index"],
|
||||
"Inventory original panel row mismatch")
|
||||
require(type(row["targets"]) is int and 0 < row["targets"] <= cfg["neurons"] and
|
||||
type(row["requests"]) is int and row["requests"] >= 0, "Invalid inventory target/request counts")
|
||||
expected = {(cohort, mode, oi, seed) for cohort in COHORTS for mode in MODES for oi in range(6) for seed in seeds}
|
||||
require(keys == expected, "Inventory missing or replacing a predeclared cell")
|
||||
for index, cohort in enumerate(COHORTS):
|
||||
rows = [row for row in records if row["cohort"] == cohort]
|
||||
rows.sort(key=lambda row: (MODES.index(row["mode"]), row["odor_index"], seeds.index(row["seed"])))
|
||||
result[cohort] = {"records": rows, "odor_ids": all_ids[index * 6:(index + 1) * 6]}
|
||||
return result
|
||||
|
||||
|
||||
def validate_encoder(encoder, inventory, cfg):
|
||||
require(encoder["panel_count"] == 12 and encoder["cal_count"] == 6 and encoder["novel_to_aj_count"] == 6 and
|
||||
encoder["median_mapped_response_sum"] == MEDIAN and encoder["seeds"] == cfg["validation"]["seeds"] and
|
||||
encoder["requested_before_any_dynamics"] is True and encoder["historical_never_simulated_claim"] is False,
|
||||
"Frozen encoder/reference or validation scope changed")
|
||||
require(isinstance(encoder["seeds_static_audit_sha256"], str) and
|
||||
re.fullmatch(r"[0-9a-f]{64}", encoder["seeds_static_audit_sha256"]), "Missing seed audit SHA")
|
||||
expected = {cohort: [dict(odor_id=oid, odor_index=index, source_panel_split=SPLITS[cohort])
|
||||
for index, oid in enumerate(inventory[cohort]["odor_ids"])] for cohort in COHORTS}
|
||||
actual = encoder["cohorts_static_panel_rows"]
|
||||
require(set(actual) == set(COHORTS), "Encoder cohort set changed")
|
||||
for ci, cohort in enumerate(COHORTS):
|
||||
require(len(actual[cohort]) == 6, "Encoder cohort panel count changed")
|
||||
for index, (row, wanted) in enumerate(zip(actual[cohort], expected[cohort])):
|
||||
require(all(row.get(key) == value for key, value in wanted.items()),
|
||||
"Encoder cohort panel rows differ from frozen identities")
|
||||
if "source_panel_row" in row:
|
||||
require(type(row["source_panel_row"]) is int and row["source_panel_row"] == ci * 6 + index,
|
||||
"Encoder original panel row mismatch")
|
||||
|
||||
|
||||
def validate_case(case, row, cfg, frozen_spec):
|
||||
spec = read(case / "spec.json")
|
||||
require(spec["name"] == row["name"] and spec["kind"] == "fullgraph" and
|
||||
spec["validation_cohort"] == row["cohort"] and spec["source_panel_split"] == row["source_panel_split"] and
|
||||
all(spec[key] == row[key] for key in ("mode", "odor_id", "odor_index", "seed")), "Spec differs from validation inventory")
|
||||
require(type(spec["apl_delay_steps"]) is int and spec["apl_delay_steps"] == 18 and spec["steps"] == 1000 and
|
||||
spec["dt_ms"] == .1 and spec["duration_ms"] == 100.0 and spec["training"] is False and
|
||||
spec["record_history"] is False, "Fixed D18/window/nonlearning schema changed")
|
||||
if "source_panel_row" in row or "source_panel_row" in spec:
|
||||
expected_row = (0 if row["cohort"] == "seed_validation" else 6) + row["odor_index"]
|
||||
require(type(spec.get("source_panel_row")) is int and spec["source_panel_row"] == expected_row and
|
||||
row.get("source_panel_row", expected_row) == expected_row, "Spec original panel row mismatch")
|
||||
for key in ("model", "graph_dir", "apl_bundle_dir", "apl", "coefficients_path", "science_python",
|
||||
"legacy_apl_worker", "precision", "apl_precision", "dt_ms", "duration_ms", "steps"):
|
||||
require(spec[key] == frozen_spec[key], "Frozen physical field changed: " + key)
|
||||
with np.load(case / "pn-drive.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"targets", "rates_hz"}, "PN drive schema changed")
|
||||
targets, rates = source["targets"], source["rates_hz"]
|
||||
require(targets.dtype == np.int32 and targets.ndim == 1 and len(targets) == row["targets"] and
|
||||
len(np.unique(targets)) == len(targets) and np.all((targets >= 0) & (targets < cfg["neurons"])) and
|
||||
rates.dtype == np.float32 and rates.shape == targets.shape and np.isfinite(rates).all() and np.all(rates > 0),
|
||||
"Invalid PN drive identities/rates; no sorting or zero-channel insertion is allowed")
|
||||
expected_events = float(rates.sum()) * spec["steps"] * .1 / 1000.
|
||||
require(spec["target_count"] == row["targets"] and spec["rate_sum_hz"] == float(rates.sum()) and
|
||||
spec["expected_external_events"] == expected_events and np.isfinite(expected_events) and expected_events > 0,
|
||||
"PN rates/spec event expectation disagree")
|
||||
if "expected_external_events" in row:
|
||||
require(row["expected_external_events"] == expected_events, "Inventory expected events disagree")
|
||||
if "rates_sum" in row:
|
||||
require(row["rates_sum"] == float(rates.sum()), "Inventory rate sum disagrees")
|
||||
with np.load(case / "requests.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"tick", "neuron_idx"} and len(source["tick"]) == row["requests"] and
|
||||
np.isin(source["neuron_idx"], targets).all(), "Request count/targets differ from prepared input")
|
||||
return spec
|
||||
|
||||
|
||||
def read_validation_trial(legacy, timing, output, case, spec, kc_globals, cfg):
|
||||
"""Reuse all sealed checks; replace only the returned descriptive record."""
|
||||
cohort = spec["validation_cohort"]
|
||||
require(cohort in COHORTS and spec["source_panel_split"] == SPLITS[cohort] and
|
||||
type(spec["apl_delay_steps"]) is int and spec["apl_delay_steps"] == 18, "Wrong validation cohort or delay")
|
||||
trial = legacy.read_trial(output, case, spec, kc_globals, cfg["neurons"], cfg["edges"])
|
||||
try:
|
||||
trial["timing"] = timing.validate_timing(output, spec, trial)
|
||||
except Exception:
|
||||
close_trial(trial)
|
||||
raise
|
||||
trial["record"] = dict(trial["record"], split=cohort, cohort=cohort, validation_cohort=cohort,
|
||||
source_panel_split=spec["source_panel_split"], historical_never_simulated_claim=False,
|
||||
used_for_parameter_selection=False)
|
||||
if "source_panel_row" in spec:
|
||||
trial["record"]["source_panel_row"] = spec["source_panel_row"]
|
||||
return trial
|
||||
|
||||
|
||||
def close_trial(trial):
|
||||
if trial is None:
|
||||
return
|
||||
arrays = list(trial.get("apl", {}).values())
|
||||
arrays += [trial.get("timing", {}).get(key) for key in ("applied", "pending")]
|
||||
for value in arrays:
|
||||
mmap = getattr(value, "_mmap", None)
|
||||
if mmap is not None and not mmap.closed:
|
||||
mmap.close()
|
||||
|
||||
|
||||
def evidence_hashes(output, case, name, repeat):
|
||||
files = ("state.npz", "summary.json", "tick-state-hashes.jsonl", "queue.jsonl", "requests.csv", "apl-component.json",
|
||||
"apl-drive.float32.npy", "apl-state.float32.npy", "apl-inhibition.float32.npy",
|
||||
"apl-applied.float32.npy", "apl-pending-tail.float32.npy", "apl-timing-audit.json")
|
||||
return {"case": name, "repeat": repeat,
|
||||
"input_sha256": {key: digest(case / key) for key in ("spec.json", "requests.npz", "pn-drive.npz")},
|
||||
"output_sha256": {key: digest(output / key) for key in files}}
|
||||
|
||||
|
||||
def collect_cohort(cohort, records, odors, cfg, exp, kc_globals, frozen_spec, legacy, timing):
|
||||
"""Aggregate one block even when its representation gate fails."""
|
||||
seeds = cfg["validation"]["seeds"]
|
||||
failures, runs, rows, repeats, timings, proof, modes = [], {}, [], [], [], [], {}
|
||||
for row in records:
|
||||
name, first, spec = row["name"], None, None
|
||||
case = exp / "cases" / cohort / name
|
||||
try:
|
||||
spec = validate_case(case, row, cfg, frozen_spec)
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="case_input", case=name, type=type(error).__name__, message=str(error)))
|
||||
for repeat_index in ([0, 1] if row["seed"] == seeds[0] else [0]):
|
||||
trial = None
|
||||
try:
|
||||
require(spec is not None, "Case input validation unavailable")
|
||||
output = exp / "gpu" / cohort / (name + "-r" + str(repeat_index))
|
||||
trial = read_validation_trial(legacy, timing, output, case, spec, kc_globals, cfg)
|
||||
require(trial["record"]["requested"] == row["requests"], "Inventory/actual request count differs")
|
||||
entry = evidence_hashes(output, case, name, repeat_index)
|
||||
proof.append(entry)
|
||||
timings.append(dict(case=name, repeat=repeat_index, **trial["timing"]["report"]))
|
||||
if repeat_index == 0:
|
||||
first = trial
|
||||
runs[(row["mode"], row["odor_id"], row["seed"])] = {key: trial[key] for key in
|
||||
("kc_counts", "expected_external_events", "external_events")}
|
||||
rows.append(trial["record"])
|
||||
else:
|
||||
require(first is not None, "Baseline unavailable for exact repeat")
|
||||
report = dict(case=name, **timing.timing_repeat(first, trial, legacy))
|
||||
repeats.append(report)
|
||||
require(report["pass_all"] is True, "Exact repeat differs: " + ", ".join(
|
||||
key for key, value in report["checks"].items() if not value))
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="raw_trial" if repeat_index == 0 else "repeat_trial", case=name,
|
||||
repeat=repeat_index, type=type(error).__name__, message=str(error)))
|
||||
finally:
|
||||
if trial is not first:
|
||||
close_trial(trial)
|
||||
close_trial(first)
|
||||
for mode in MODES:
|
||||
expected = {(mode, odor, seed) for odor in odors for seed in seeds}
|
||||
if expected.issubset(runs):
|
||||
try:
|
||||
report = legacy.summarize_mode(mode, odors, seeds, runs, cfg["gates"])
|
||||
require(report["conditions"] == legacy.evaluate_gates(report, cfg["gates"]), "Original gate conjunction mismatch")
|
||||
require(report["within_distance_count"] == 60 and report["between_distance_count"] == 75 and
|
||||
report["pair_count"] == 15 and all(pair["within_distance_count"] == 20 and
|
||||
pair["between_distance_count"] == 5 for pair in report["pairs"]), "Pair-specific denominator changed")
|
||||
modes[mode] = report
|
||||
if not report["gate"]:
|
||||
failures.append(dict(stage="representation_gate", mode=mode,
|
||||
failed_conditions=[key for key, value in report["conditions"].items() if not value]))
|
||||
except Exception as error:
|
||||
failures.append(dict(stage="representation_calculation", mode=mode, type=type(error).__name__, message=str(error)))
|
||||
numerical = bool(len(runs) == 60 and len(timings) == 72 and not any(row["stage"] in
|
||||
("case_input", "raw_trial", "repeat_trial") for row in failures))
|
||||
repeated = bool(len(repeats) == 12 and all(row["pass_all"] is True for row in repeats))
|
||||
execution = bool(numerical and repeated and not any(row["stage"] == "representation_calculation" for row in failures))
|
||||
represented = bool(len(modes) == 2 and all(row["gate"] is True for row in modes.values()))
|
||||
return dict(cohort=cohort, source_panel_split=SPLITS[cohort], pass_all=bool(execution and represented),
|
||||
execution_gate_pass=execution, numerical_integrity_pass=numerical, exact_repeat_pass=repeated,
|
||||
representation_gate_pass=represented, original_representation_gate_pass=represented,
|
||||
expected_base_trials=60, valid_base_trials=len(runs), expected_repeats=12, checked_repeats=len(repeats),
|
||||
checked_timing_trials=len(timings), modes=modes, runs=rows, repeats=repeats, timing_checks=timings,
|
||||
failures=failures, input_output_provenance=proof)
|
||||
|
||||
|
||||
def summarize(cohort, out_json=_DEFAULT_OUTPUT):
|
||||
"""Default exclusive write; None recomputes deterministically without writes."""
|
||||
require(cohort in COHORTS, "Unknown validation cohort")
|
||||
import context as ctx
|
||||
target = ctx.EXP / "validation" / cohort / "summary.json" if out_json is _DEFAULT_OUTPUT else (
|
||||
None if out_json is None else Path(out_json))
|
||||
require(target is None or not target.exists(), "Refusing existing cohort summary")
|
||||
result = dict(cohort=cohort, source_panel_split=SPLITS[cohort], pass_all=False, execution_gate_pass=False,
|
||||
numerical_integrity_pass=False, exact_repeat_pass=False, representation_gate_pass=False,
|
||||
original_representation_gate_pass=False, expected_base_trials=60, valid_base_trials=0,
|
||||
expected_repeats=12, checked_repeats=0, checked_timing_trials=0,
|
||||
modes={}, runs=[], repeats=[], timing_checks=[], failures=[], input_output_provenance=[])
|
||||
proof, odors, encoder = {}, [], None
|
||||
try:
|
||||
ctx.check()
|
||||
legacy = load_frozen(ctx.AG_MODEL / "calibration.py", AG_SHA256, "phase4ak_frozen_ag_calibration")
|
||||
timing = load_frozen(ctx.AJ_MODEL / "calibration.py", AJ_SHA256, "phase4ak_frozen_aj_calibration")
|
||||
inventory_path = ctx.EXP / "protocol/validation-cases.json"
|
||||
encoder_path = ctx.EXP / "protocol/encoder-audit.json"
|
||||
panel_path = ctx.ROOT / "experiments/phase4l-olfactory-grounding/odor-encoder/frozen-odor-panel.parquet"
|
||||
require(digest(panel_path) == PANEL_SHA256, "Frozen static panel SHA changed")
|
||||
import pandas as pd
|
||||
panel = pd.read_parquet(panel_path).to_dict(orient="records")
|
||||
inventory = validate_inventory(read(inventory_path), ctx.CFG, panel, legacy)
|
||||
encoder = read(encoder_path)
|
||||
validate_encoder(encoder, inventory, ctx.CFG)
|
||||
seed_audit = ctx.MODEL / "seed-audit.json"
|
||||
require(digest(seed_audit) == encoder["seeds_static_audit_sha256"], "Seed-audit receipt/encoder mismatch")
|
||||
kc_path = ctx.AG / "apl-bundle/kc_globals.npy"
|
||||
kc = np.load(kc_path, allow_pickle=False)
|
||||
require(kc.dtype == np.int32 and kc.shape == (4064,) and np.all(np.diff(kc) > 0) and
|
||||
np.all((kc >= 0) & (kc < ctx.CFG["neurons"])), "Frozen KC identity invalid")
|
||||
base_path = ctx.AJ / "cases/delay18/cal-physiological-00-s51101/spec.json"
|
||||
base = read(base_path)
|
||||
require(base["apl_delay_steps"] == 18 and base["training"] is False, "Wrong frozen physical reference")
|
||||
proof = {"validation_inventory": digest(inventory_path), "encoder_audit": digest(encoder_path),
|
||||
"static_panel": digest(panel_path), "seed_audit": digest(seed_audit),
|
||||
"KC_identity": digest(kc_path), "frozen_D18_spec": digest(base_path),
|
||||
"AG_calibration_source": AG_SHA256, "AJ_calibration_source": AJ_SHA256}
|
||||
odors = inventory[cohort]["odor_ids"]
|
||||
result = collect_cohort(cohort, inventory[cohort]["records"], odors, ctx.CFG, ctx.EXP, kc, base, legacy, timing)
|
||||
except Exception as error:
|
||||
result["failures"].append(dict(stage="prerequisites", type=type(error).__name__, message=str(error)))
|
||||
result.update(phase="4AK", apl_delay_steps=18, seeds=ctx.CFG["validation"]["seeds"], duration_ms=100.0,
|
||||
odor_ids=odors, encoder_audit=encoder, prerequisite_input_sha256=proof,
|
||||
representation_failure_is_not_numerical_failure=True, old_module_globals_modified=False,
|
||||
old_heldout_dynamic_access=False, historical_never_simulated_claim=False,
|
||||
requested_cohort_only_dynamic_read=True, suite_owns_sequential_unlock=True,
|
||||
shared_apl_component_oracle_limit=True, independent_oracle_equivalence_claim=False,
|
||||
seed_namespace_claim="Declared static-audit scope only; not an unqualified all-history independence claim",
|
||||
biological_delay_measurement_claimed=False, candidate_generated=False, training=False,
|
||||
metric_source="SHA-pinned AG complete phase4l gate; SHA-pinned AJ timing/repeat checks")
|
||||
# Serializability/finiteness must be established even for read-only finalization.
|
||||
json.dumps(result, allow_nan=False, sort_keys=True)
|
||||
if target is not None:
|
||||
outputs = {target: result}
|
||||
outputs.update({target.parent / (mode + ".json"): report for mode, report in result["modes"].items()})
|
||||
require(all(not path.exists() for path in outputs), "Refusing existing cohort/mode output")
|
||||
for path, value in outputs.items():
|
||||
ctx.save(path, value)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
require(len(sys.argv) == 2, "Usage: validation.py seed_validation|novel_to_aj")
|
||||
answer = summarize(sys.argv[1])
|
||||
print(json.dumps({key: answer[key] for key in ("cohort", "pass_all", "execution_gate_pass", "representation_gate_pass")}))
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Unchanged deterministic-v2 reference rerun, exclusively into this new stage."""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import ROOT, EXP, CFG, check, save, bit_equal
|
||||
|
||||
|
||||
def main():
|
||||
check()
|
||||
assert not (EXP / 'regression/frozen-five.json').exists()
|
||||
from run_case import preflight
|
||||
preflight()
|
||||
runtime_path = ROOT / 'models/male-cns-lif/deterministic-runtime-v2'
|
||||
pc = yaml.safe_load((runtime_path / 'phase4p.yaml').read_text())
|
||||
sys.path.insert(0, str(ROOT / 'models/male-cns-lif'))
|
||||
sys.path.insert(0, str(runtime_path))
|
||||
import torch
|
||||
import phase4p
|
||||
assert torch.cuda.mem_get_info()[0] > 2 * 1024**3
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
started = time.perf_counter()
|
||||
runtime = phase4p.CanonicalRuntime(pc, runtime_path, EXP)
|
||||
rows = []
|
||||
for spec in pc['reference_trials']:
|
||||
preflight()
|
||||
reference_dir = ROOT / 'experiments/phase4p-deterministic-runtime/phase4c-v2/reference' / spec['name']
|
||||
events = pd.read_parquet(reference_dir / 'spike-events.parquet')
|
||||
reference = dict(step=events.step.to_numpy(), neuron_idx=events.neuron_idx.to_numpy(),
|
||||
counts=np.load(reference_dir / 'per-neuron-spike-counts.int32.npy'),
|
||||
v=np.load(reference_dir / 'final-voltage.float32.npy'), g=np.load(reference_dir / 'final-g.float32.npy'))
|
||||
result = runtime.run(np.flatnonzero(runtime.pops[spec['population']]), spec['rate_hz'], spec['seed'], spec['duration_ms'])
|
||||
row = dict(trial=spec['name'], exact={key: bool(bit_equal(value, result[key])) for key, value in reference.items()},
|
||||
dtype_shape={key: dict(reference_dtype=str(value.dtype), actual_dtype=str(result[key].dtype),
|
||||
reference_shape=list(value.shape), actual_shape=list(result[key].shape)) for key, value in reference.items()},
|
||||
wall_seconds=result['metrics']['wall_seconds'])
|
||||
rows.append(row)
|
||||
print(row, flush=True)
|
||||
passed = len(rows) == 5 and all(all(row['exact'].values()) for row in rows)
|
||||
save(EXP / 'regression/frozen-five.json', dict(pass_all=passed, trials=rows,
|
||||
wall_seconds=time.perf_counter() - started, peak_allocated_vram=int(torch.cuda.max_memory_allocated()),
|
||||
scope='Old frozen deterministic-v2 five references only; not the new D18 hypothesis'))
|
||||
assert passed
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,76 @@
|
||||
# Phase4AG: author LIF + frozen legacy graded APL
|
||||
|
||||
Authorized independent hypothesis branch. All old source, raw connectome, signs,
|
||||
W_syn, APL calibration, eligibility-v2 and 14,232-edge mask remain read-only.
|
||||
No learning, gain/threshold changes, new edges, top-K, heldout activity, game or RL.
|
||||
|
||||
Author code provenance is the already archived MIT-licensed
|
||||
https://github.com/philshiu/Drosophila_brain_model at
|
||||
91bdd1e7dcf193f3e7ca5a8933497fcef63b7960; original model.py SHA-256
|
||||
fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e.
|
||||
Associated paper: https://www.nature.com/articles/s41586-024-07763-9.
|
||||
This new MaleCNS/APL combination is not a strict reproduction of that model.
|
||||
|
||||
The declared combination is author float64 LIF plus the exact old float32 graded
|
||||
APL operator after synapses/resets at end. All 5,845 original APL fast out-edges
|
||||
are zeroed only in the effective in-memory model, as in the legacy overlay.
|
||||
Only KC receives graded replacement; known 1,212 non-KC edges remain unreplaced.
|
||||
This existing scope limitation is not silently repaired in a compatibility test.
|
||||
|
||||
Use the original full normalized I[2,166700]/O[2,4064] float32 bytes and original
|
||||
normalizers, even for induced real subgraphs. APL reads actual binary spikes,
|
||||
never requests, and keeps independent float32 state. Preserve unfused operations:
|
||||
state.mul_(float(f32(exp(-.1/20)))).add_(torch.mv(I, spikes_f32));
|
||||
h32=torch.matmul(state,O)*f32(.01). Promote h32 to float64, multiply by float64
|
||||
.001, then separately subtract from SI g. Do not alter TF32/default CuBLAS flags;
|
||||
record them. No added APL delay, drive dt/tau scaling, truncation or clipping.
|
||||
|
||||
APL writes KC g unconditionally after author reset, including refractory KC.
|
||||
Author g integration is frozen during refractory; therefore negative overlay g
|
||||
may accumulate and differs from old dynamics. This is an explicit hypothesis,
|
||||
not an assertion of original author or measured physiology. Guarded writes or
|
||||
reset-before/after changes would be different hypotheses, not fixes to this one.
|
||||
|
||||
Eight fixed metadata/connectivity-selected fixtures cover zero/reset, real PN/KC
|
||||
drive, current actual event feedback, duplicate natural/request event, 18-step
|
||||
arrival vs22-step refractory boundary, full fast APL mask, non-KC scope and mixed
|
||||
precision/high fan-in. No choice is made from runtime responses. Each GPU case
|
||||
has3 independent fresh runs. A Brian2 author oracle starts from its own state,
|
||||
computes its own actual spikes, and uses an isolated standard-I/O legacy-APL
|
||||
worker with independent state. No candidate inhibition trace is fed into Brian.
|
||||
All tick states, events, requests, queue order/guard, APL drive/state/h must agree
|
||||
exactly. The APL component is shared, so this is NOT a fully independent CPU
|
||||
oracle. Independently reconstruct old matrices using frozen initializer AST,
|
||||
replay actual events through original update AST, require exact component bytes;
|
||||
also quantify an independent float64 positive-sum arithmetic error bound. It is
|
||||
not a replacement for exact legacy equivalence. No arithmetic bound is adjusted.
|
||||
|
||||
Only after all fixture gates pass, run two predeclared full-graph probes: first
|
||||
old calibration odor, first original seed, both encoder modes. Compare each with
|
||||
the independently running Brian + isolated APL worker. Save state hashes and sparse
|
||||
events, not a time-by-all-neurons dense spike matrix. If a numerical gate fails,
|
||||
stop CAL; keep diagnostic evidence and do not widen tolerance.
|
||||
|
||||
Then run the complete original6 CAL odors ×5 original seeds ×2 original modes,
|
||||
100ms per run. Retain the ORIGINAL12-odor median for matched-energy encoding,
|
||||
original pn_drive target/RNG column order, original evaluation seeds51101..51105.
|
||||
Save Poisson requests before any trajectory. Only calibration dynamic cases are
|
||||
used; formal A/B and heldout remain unrun. Repeat first seed for every odor/mode.
|
||||
No per-odor pulse, amplitude, timing or threshold search. No learning.
|
||||
|
||||
Recompute the full original4L representation criteria on CAL only: mean KC active
|
||||
fraction .03..09, min>0, max<.5, same-odor Jaccard>different, between Euclidean>
|
||||
within, >=3 pairs whose mean between exceeds THAT PAIR's pooled within distances,
|
||||
both modes. Add finiteness and exact repeat requirements. CAL PASS does not imply
|
||||
heldout generalization, timing/credit specificity, memory or learning usability.
|
||||
If CAL fails stop promotion and report it, not a least-bad candidate.
|
||||
|
||||
Before/after verify SHA-256 of all original230239 files and AA--AF seals; rerun original five
|
||||
deterministic reference trials to new output. Archive code/env/source SHA,
|
||||
matrices/norms/masks/identities, requests and all outputs. No published candidate.
|
||||
|
||||
Technical references (queried2026-09-30): Brian2 2.5.1 refractory documentation
|
||||
https://brian2.readthedocs.io/en/2.5.1/user/refractoriness.html (actual generated code
|
||||
remains authoritative); PyTorch numerical accuracy notes
|
||||
https://docs.pytorch.org/docs/2.14/notes/numerical_accuracy.html (cross-backend
|
||||
floating operations are not assumed bitwise equivalent).
|
||||
@@ -0,0 +1,396 @@
|
||||
"""Complete original Phase 4L representation metrics on fixed CAL inputs only.
|
||||
|
||||
No simulation, fitting, heldout trajectory access, or candidate generation is
|
||||
performed here. The original 4L pair-specific within-odor baseline is retained;
|
||||
the weaker Phase 4P r4 aggregate gate is deliberately not used.
|
||||
"""
|
||||
import csv
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
STATE_DTYPES = {"step": np.int32, "neuron_idx": np.int32, "counts": np.int32,
|
||||
"v": np.float64, "g": np.float64, "ref": np.int32}
|
||||
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
|
||||
APL_FILES = {"apl-drive.float32.npy": 2, "apl-state.float32.npy": 2,
|
||||
"apl-inhibition.float32.npy": 4064}
|
||||
ORIGINAL_CAL_IDS = {
|
||||
"OMSUIQOIVADKIM-RXMQYKEDSA-N", "LABTWGUMFABVFG-ARJAWSKDSA-N",
|
||||
"QNVRIHYSUZMSGM-ZCFIWIBFSA-N", "VLSVVMPLPMNWBH-UHFFFAOYSA-N",
|
||||
"XHIUFYZDQBSEMF-UHFFFAOYSA-N", "OMSUIQOIVADKIM-YFKPBYRVSA-N"}
|
||||
ORIGINAL_MODES = ("physiological", "matched_energy")
|
||||
ORIGINAL_SEEDS = (51101, 51102, 51103, 51104, 51105)
|
||||
HEX_SHA = re.compile(r"[0-9a-f]{64}\Z")
|
||||
_DEFAULT_OUTPUT = object()
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def read_json(path):
|
||||
with Path(path).open(encoding="utf-8") as stream:
|
||||
return json.load(stream)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def arrays_bitwise_equal(first, second):
|
||||
"""Unlike array_equal, also reject different dtypes and signed-zero bits."""
|
||||
return bool(first.dtype == second.dtype and first.shape == second.shape and
|
||||
np.array_equal(np.ascontiguousarray(first).view(np.uint8),
|
||||
np.ascontiguousarray(second).view(np.uint8)))
|
||||
|
||||
|
||||
def pair_metrics(a, b):
|
||||
"""Exact definitions copied from phase4i.pair_metrics/safe_corr/cosine."""
|
||||
x, y = a.astype(np.float64), b.astype(np.float64)
|
||||
aa, bb = a > 0, b > 0
|
||||
union = int(np.count_nonzero(aa | bb))
|
||||
intersection = int(np.count_nonzero(aa & bb))
|
||||
denominator = float(np.linalg.norm(x) * np.linalg.norm(y))
|
||||
cosine = 1.0 if denominator == 0 and np.array_equal(x, y) else (
|
||||
0.0 if denominator == 0 else float(np.dot(x, y) / denominator))
|
||||
correlation = (1.0 if np.array_equal(x, y) else 0.0) if np.std(x) == 0 or np.std(y) == 0 else float(np.corrcoef(x, y)[0, 1])
|
||||
return {"active_intersection": intersection, "active_union": union,
|
||||
"active_jaccard": 1.0 if union == 0 else float(intersection / union),
|
||||
"cosine_similarity": cosine, "correlation": correlation,
|
||||
"euclidean_distance": float(np.linalg.norm(x - y))}
|
||||
|
||||
|
||||
def evaluate_gates(summary, gates):
|
||||
"""Each conjunct in original phase4l.py:239, kept visible for audit."""
|
||||
return {
|
||||
"mean_sparsity_in_closed_interval": bool(float(gates["sparse_min_fraction"]) <=
|
||||
summary["KC_active_fraction_mean"] <= float(gates["sparse_max_fraction"])),
|
||||
"maximum_strictly_below_half": bool(summary["KC_active_fraction_max"] < 0.5),
|
||||
"minimum_strictly_positive": bool(summary["KC_active_fraction_min"] > 0),
|
||||
"same_jaccard_gt_different": bool(summary["same_odor_jaccard_mean"] > summary["different_odor_jaccard_mean"]),
|
||||
"different_euclidean_gt_same": bool(summary["different_odor_euclidean_mean"] > summary["same_odor_euclidean_mean"]),
|
||||
"at_least_original_pair_count": bool(summary["pairwise_separation_pass_count"] >= int(gates["minimum_separable_odor_pairs"]))}
|
||||
|
||||
|
||||
def sample_cv(values):
|
||||
"""Original pandas Series.std uses ddof=1. Undefined diagnostic -> null."""
|
||||
values = np.asarray(values, dtype=np.float64)
|
||||
mean = float(values.mean())
|
||||
return None if mean == 0 else float(values.std(ddof=1) / mean)
|
||||
|
||||
|
||||
def summarize_mode(mode, odor_ids, seeds, runs, gates):
|
||||
within, within_j, between, between_j, between_cos, between_corr = [], [], [], [], [], []
|
||||
pairs = []
|
||||
for odor in odor_ids:
|
||||
arrays = [runs[(mode, odor, seed)]["kc_counts"] for seed in seeds]
|
||||
for first, second in itertools.combinations(arrays, 2):
|
||||
metric = pair_metrics(first, second)
|
||||
within.append(metric["euclidean_distance"])
|
||||
within_j.append(metric["active_jaccard"])
|
||||
for first_odor, second_odor in itertools.combinations(odor_ids, 2):
|
||||
distances = []
|
||||
for seed in seeds:
|
||||
metric = pair_metrics(runs[(mode, first_odor, seed)]["kc_counts"],
|
||||
runs[(mode, second_odor, seed)]["kc_counts"])
|
||||
distances.append(metric["euclidean_distance"])
|
||||
between.append(metric["euclidean_distance"])
|
||||
between_j.append(metric["active_jaccard"])
|
||||
between_cos.append(metric["cosine_similarity"])
|
||||
between_corr.append(metric["correlation"])
|
||||
pair_within = []
|
||||
for odor in (first_odor, second_odor):
|
||||
arrays = [runs[(mode, odor, seed)]["kc_counts"] for seed in seeds]
|
||||
pair_within.extend(pair_metrics(first, second)["euclidean_distance"]
|
||||
for first, second in itertools.combinations(arrays, 2))
|
||||
cross_mean, within_mean = float(np.mean(distances)), float(np.mean(pair_within))
|
||||
pairs.append({"odor_id_a": first_odor, "odor_id_b": second_odor,
|
||||
"between_same_seed_mean": cross_mean, "within_this_pair_mean": within_mean,
|
||||
"between_distance_count": len(distances), "within_distance_count": len(pair_within),
|
||||
"gate": bool(cross_mean > within_mean)})
|
||||
selected = [runs[(mode, odor, seed)] for odor in odor_ids for seed in seeds]
|
||||
fractions = [int(np.count_nonzero(run["kc_counts"])) / len(run["kc_counts"]) for run in selected]
|
||||
summary = {"KC_active_fraction_mean": float(np.mean(fractions)),
|
||||
"KC_active_fraction_min": float(np.min(fractions)), "KC_active_fraction_max": float(np.max(fractions)),
|
||||
"same_odor_jaccard_mean": float(np.mean(within_j)),
|
||||
"different_odor_jaccard_mean": float(np.mean(between_j)),
|
||||
"same_odor_euclidean_mean": float(np.mean(within)),
|
||||
"different_odor_euclidean_mean": float(np.mean(between)),
|
||||
"different_odor_cosine_mean": float(np.mean(between_cos)),
|
||||
"different_odor_correlation_mean": float(np.mean(between_corr)),
|
||||
"within_variance": float(np.var(within)), "between_distance_mean": float(np.mean(between)),
|
||||
"pairwise_separation_pass_count": sum(row["gate"] for row in pairs), "pair_count": len(pairs),
|
||||
"expected_event_count_cv": sample_cv([run["expected_external_events"] for run in selected]),
|
||||
"actual_event_count_cv": sample_cv([run["external_events"] for run in selected]),
|
||||
"within_distance_count": len(within), "between_distance_count": len(between),
|
||||
"pairwise_baseline": "Each pair's two odors: 10+10 within-seed distances; never pooled across all odors",
|
||||
"pairs": pairs}
|
||||
summary["conditions"] = evaluate_gates(summary, gates)
|
||||
summary["gate"] = bool(all(summary["conditions"].values()))
|
||||
return summary
|
||||
|
||||
|
||||
def validate_state(state, neurons, steps):
|
||||
require(set(state) == set(STATE_DTYPES), "state.npz must have exactly the six declared arrays")
|
||||
for name, dtype in STATE_DTYPES.items():
|
||||
value = state[name]
|
||||
require(value.dtype == dtype and value.ndim == 1, "Invalid state dtype/rank: " + name)
|
||||
require(bool(np.isfinite(value).all()), "Nonfinite state: " + name)
|
||||
if name not in ("step", "neuron_idx"):
|
||||
require(value.shape == (neurons,), "Invalid state length: " + name)
|
||||
ticks, nodes = state["step"], state["neuron_idx"]
|
||||
require(ticks.shape == nodes.shape and bool(np.all((ticks >= 0) & (ticks < steps))) and
|
||||
bool(np.all((nodes >= 0) & (nodes < neurons))), "Invalid sparse event indices")
|
||||
keys = ticks.astype(np.int64) * neurons + nodes
|
||||
require(bool(np.all(np.diff(keys) > 0)), "Sparse events not sorted unique by tick/global neuron")
|
||||
require(np.array_equal(np.bincount(nodes, minlength=neurons), state["counts"]), "Counts disagree with sparse events")
|
||||
require(bool(np.all((state["ref"] >= 0) & (state["ref"] <= 22))), "Final refractory outside frozen range")
|
||||
return keys
|
||||
|
||||
|
||||
def read_jsonl(path, steps, kind):
|
||||
with Path(path).open(encoding="utf-8") as source:
|
||||
rows = [json.loads(line) for line in source if line.strip()]
|
||||
require(len(rows) == steps and [row.get("tick") for row in rows] == list(range(steps)),
|
||||
"Missing/duplicate/reordered ticks in " + kind)
|
||||
for row in rows:
|
||||
names = ("v_sha256", "g_sha256", "ref_sha256") if kind == "state hashes" else (
|
||||
"ordered_edge_sha256", "accepted_mask_sha256", "joint_sha256")
|
||||
for name in names:
|
||||
require(isinstance(row.get(name), str) and HEX_SHA.fullmatch(row[name]), "Invalid SHA256 in " + kind)
|
||||
if kind == "queue":
|
||||
require(type(row.get("count")) is int and type(row.get("accepted_count")) is int and
|
||||
0 <= row["accepted_count"] <= row["count"], "Invalid queue counts")
|
||||
return rows
|
||||
|
||||
|
||||
def read_trial(output, case, spec, kc_globals, neurons, edges):
|
||||
summary = read_json(output / "summary.json")
|
||||
steps = spec["steps"]
|
||||
require(summary.get("complete") is True and summary.get("pass_all") is True and
|
||||
summary.get("all_finite") is True and summary.get("steps") == steps and
|
||||
summary.get("completed_ticks") == steps and summary.get("nodes") == neurons and
|
||||
summary.get("edges") == edges and summary.get("precision") == "float64" and
|
||||
summary.get("apl_precision") == "float32" and summary.get("case") == spec["name"] and
|
||||
summary.get("record_history") is False and summary.get("learning") is False,
|
||||
"Incomplete/failed/wrong-scope GPU summary")
|
||||
with np.load(output / "state.npz", allow_pickle=False) as source:
|
||||
state = {name: source[name].copy() for name in source.files}
|
||||
event_keys = validate_state(state, neurons, steps)
|
||||
hashes = read_jsonl(output / "tick-state-hashes.jsonl", steps, "state hashes")
|
||||
for name in ("v", "g", "ref"):
|
||||
require(hashes[-1][name + "_sha256"] == raw_hash(state[name]), "Final state does not match last tick hash: " + name)
|
||||
queues = read_jsonl(output / "queue.jsonl", steps, "queue")
|
||||
apl = {}
|
||||
for name, width in APL_FILES.items():
|
||||
value = np.load(output / name, mmap_mode="r", allow_pickle=False)
|
||||
require(value.dtype == np.float32 and value.shape == (steps, width) and
|
||||
bool(np.isfinite(value).all()) and bool(np.all(value >= 0)), "Invalid full APL trace: " + name)
|
||||
apl[name] = value
|
||||
with (output / "requests.csv").open(encoding="utf-8", newline="") as source:
|
||||
reader = csv.DictReader(source)
|
||||
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, "Request CSV schema mismatch")
|
||||
request_rows = [{name: int(row[name]) for name in REQUEST_FIELDS} for row in reader]
|
||||
with np.load(case / "requests.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"tick", "neuron_idx"}, "Input request schema mismatch")
|
||||
expected_ticks, expected_nodes = source["tick"], source["neuron_idx"]
|
||||
require(expected_ticks.dtype == np.int32 and expected_nodes.dtype == np.int32 and
|
||||
expected_ticks.ndim == 1 and expected_ticks.shape == expected_nodes.shape,
|
||||
"Input request dtype/shape mismatch")
|
||||
require(len(request_rows) == len(expected_ticks), "Requested event coverage mismatch")
|
||||
require([row["tick"] for row in request_rows] == expected_ticks.tolist() and
|
||||
[row["neuron_idx"] for row in request_rows] == expected_nodes.tolist(), "Requests differ from frozen input")
|
||||
require(all(row["attempted"] == 1 and row["accepted"] in (0, 1) and
|
||||
row["rejected"] == 1 - row["accepted"] for row in request_rows), "Invalid request acceptance bookkeeping")
|
||||
requested_keys = expected_ticks.astype(np.int64) * neurons + expected_nodes
|
||||
require(bool(np.all(np.diff(requested_keys) > 0)) and bool(np.all((expected_ticks >= 0) & (expected_ticks < steps))) and
|
||||
bool(np.all((expected_nodes >= 0) & (expected_nodes < neurons))), "Input requests not sorted unique/in range")
|
||||
actual_accepted = np.array([row["accepted"] for row in request_rows], dtype=np.uint8)
|
||||
require(np.array_equal(np.isin(requested_keys, event_keys, assume_unique=True), actual_accepted.astype(bool)),
|
||||
"Request accepted/rejected values disagree with actual spikes")
|
||||
accepted = int(actual_accepted.sum())
|
||||
require(summary.get("requested") == len(request_rows) and summary.get("accepted_requests") == accepted and
|
||||
summary.get("rejected_requests") == len(request_rows) - accepted and
|
||||
summary.get("total_spikes") == len(state["step"]), "Summary counts disagree with raw output")
|
||||
component = read_json(output / "apl-component.json")
|
||||
require(component.get("ok") is True and component.get("n_neurons") == neurons and
|
||||
component.get("n_kc") == len(kc_globals) and component.get("n_apl") == 2 and
|
||||
component.get("dt_ms") == .1 and summary.get("apl_component") == component,
|
||||
"APL component provenance mismatch")
|
||||
bundle = Path(spec["apl_bundle_dir"])
|
||||
require(component.get("files_sha256") == {name: file_hash(bundle / name) for name in
|
||||
("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")}, "APL bundle hashes changed")
|
||||
inputs = summary.get("input_sha256", {})
|
||||
require(inputs.get("spec.json") == file_hash(case / "spec.json") and
|
||||
inputs.get("requests.npz") == file_hash(case / "requests.npz"), "Case input provenance mismatch")
|
||||
kc_counts = state["counts"][kc_globals].copy()
|
||||
kc_event_mask = np.isin(state["neuron_idx"], kc_globals)
|
||||
record = {"case": spec["name"], "mode": spec["mode"], "odor_id": spec["odor_id"],
|
||||
"odor_name": spec["odor_name"], "seed": spec["seed"], "split": "calibration",
|
||||
"KC_active": int(np.count_nonzero(kc_counts)), "KC_active_fraction": float(np.count_nonzero(kc_counts) / len(kc_globals)),
|
||||
"KC_spikes": int(kc_counts.sum()), "KC_first_latency_ms":
|
||||
float(state["step"][kc_event_mask][0] * .1) if bool(np.any(kc_event_mask)) else None,
|
||||
"external_events": accepted, "expected_external_events": float(spec["expected_external_events"]),
|
||||
"total_PN_rate_hz": float(spec["rate_sum_hz"]), "finite": True,
|
||||
"total_spikes": len(state["step"]), "requested": len(request_rows)}
|
||||
require(np.isfinite(record["expected_external_events"]) and record["expected_external_events"] > 0,
|
||||
"CAL expected external events must be finite and positive")
|
||||
return {"state": state, "hashes": hashes, "queues": queues, "apl": apl,
|
||||
"requests": request_rows, "component": component, "kc_counts": kc_counts,
|
||||
"expected_external_events": record["expected_external_events"], "external_events": accepted,
|
||||
"record": record, "output": output}
|
||||
|
||||
|
||||
def exact_repeat(first, repeat):
|
||||
checks = {"state/" + name: arrays_bitwise_equal(first["state"][name], repeat["state"][name]) for name in STATE_DTYPES}
|
||||
checks.update({name: arrays_bitwise_equal(first["apl"][name], repeat["apl"][name]) for name in APL_FILES})
|
||||
checks.update(tick_state_hashes=first["hashes"] == repeat["hashes"],
|
||||
queues=first["queues"] == repeat["queues"], requests=first["requests"] == repeat["requests"],
|
||||
apl_component=first["component"] == repeat["component"])
|
||||
return {"pass_all": bool(all(checks.values())), "checks": checks,
|
||||
"scope": "All six state arrays, every tick all-neuron hashes, APL raw float32 traces, queues, requests and APL environment"}
|
||||
|
||||
|
||||
def validate_inventory(records, cfg):
|
||||
require(tuple(cfg["calibration"]["modes"]) == ORIGINAL_MODES and
|
||||
tuple(cfg["calibration"]["seeds"]) == ORIGINAL_SEEDS and
|
||||
cfg["calibration"]["duration_ms"] == 100.0 and cfg["calibration"]["odor_count"] == 6 and
|
||||
cfg["calibration"]["split"] == "calibration" and
|
||||
cfg["calibration"]["repeat_first_seed_each_odor"] is True, "Frozen CAL design changed")
|
||||
gates = cfg["gates"]
|
||||
require(gates["sparse_min_fraction"] == .03 and gates["sparse_max_fraction"] == .09 and
|
||||
gates["minimum_separable_odor_pairs"] == 3 and gates["max_fraction_strict"] == .5 and
|
||||
gates["min_fraction_strict"] == 0 and all(gates[key] is True for key in
|
||||
("same_jaccard_gt_between", "between_euclidean_gt_within", "both_modes_required", "finite_required", "repeat_exact_required")),
|
||||
"Original representation gates or additional integrity gates changed")
|
||||
expected = {(mode, index, seed) for mode in ORIGINAL_MODES for index in range(6) for seed in ORIGINAL_SEEDS}
|
||||
require(len(records) == 60 and {(row["mode"], row["odor_index"], row["seed"]) for row in records} == expected,
|
||||
"CAL inventory must cover exactly 2 modes x 6 odors x 5 seeds")
|
||||
odors = []
|
||||
for index in range(6):
|
||||
ids = {row["odor_id"] for row in records if row["odor_index"] == index}
|
||||
require(len(ids) == 1, "Odor identity changes across seeds/modes")
|
||||
odors.append(next(iter(ids)))
|
||||
require(set(odors) == ORIGINAL_CAL_IDS, "CAL identities differ from the original frozen six")
|
||||
for row in records:
|
||||
require(row["name"] == f"cal-{row['mode']}-{row['odor_index']:02d}-s{row['seed']}", "Unexpected CAL case name")
|
||||
return odors
|
||||
|
||||
|
||||
def summarize(out_json=_DEFAULT_OUTPUT):
|
||||
"""Recompute; default writes exclusively, out_json=None is strictly read-only."""
|
||||
from context import EXP, ROOT, CFG, check
|
||||
target = EXP / "calibration/summary.json" if out_json is _DEFAULT_OUTPUT else (
|
||||
None if out_json is None else Path(out_json))
|
||||
require(target is None or not target.exists(), "Refusing existing calibration summary")
|
||||
failures, runs, run_records, repeats, provenance = [], {}, [], [], []
|
||||
modes, odors = {}, []
|
||||
prerequisites_ok = False
|
||||
encoder = None
|
||||
records = []
|
||||
try:
|
||||
check()
|
||||
records = read_json(EXP / "protocol/calibration-cases.json")
|
||||
odors = validate_inventory(records, CFG)
|
||||
encoder = read_json(EXP / "protocol/encoder-audit.json")
|
||||
require(encoder["panel_count"] == 12 and encoder["cal_count"] == 6 and
|
||||
encoder["seeds"] == list(ORIGINAL_SEEDS) and encoder["requested_before_any_dynamics"] is True and
|
||||
encoder["formal_AB_run"] is False and encoder["heldout_run"] is False, "Invalid frozen encoder scope")
|
||||
# Only static original-panel metadata is read, never heldout trajectories.
|
||||
import pandas as pd
|
||||
original = pd.read_parquet(ROOT / "experiments/phase4l-olfactory-grounding/odor-encoder/frozen-odor-panel.parquet")
|
||||
require(len(original) == 12 and set(original[original.split == "calibration"].odor_id) == ORIGINAL_CAL_IDS,
|
||||
"Original static panel identity mismatch")
|
||||
require(float(np.median(original.mapped_response_sum)) == encoder["median_mapped_response_sum"],
|
||||
"Matched-energy reference is not the original frozen 12-panel median")
|
||||
kc_globals = np.load(EXP / "apl-bundle/kc_globals.npy", allow_pickle=False)
|
||||
require(kc_globals.dtype == np.int32 and kc_globals.shape == (4064,) and
|
||||
bool(np.all(np.diff(kc_globals) > 0)) and bool(np.all((kc_globals >= 0) & (kc_globals < CFG["neurons"]))),
|
||||
"Invalid frozen KC identities")
|
||||
prerequisites_ok = True
|
||||
except Exception as error:
|
||||
failures.append({"stage": "prerequisites", "type": type(error).__name__, "message": str(error)})
|
||||
if prerequisites_ok:
|
||||
for row in records:
|
||||
name = row["name"]
|
||||
first = None
|
||||
try:
|
||||
case = EXP / "cases" / name
|
||||
spec = read_json(case / "spec.json")
|
||||
require(spec["name"] == name and spec["kind"] == "calibration" and spec["steps"] == 1000 and
|
||||
spec["duration_ms"] == 100.0 and spec["dt_ms"] == .1 and spec["training"] is False and
|
||||
all(spec[key] == row[key] for key in ("mode", "odor_id", "odor_index", "seed")),
|
||||
"Case spec differs from fixed CAL inventory")
|
||||
first = read_trial(EXP / "gpu" / (name + "-r0"), case, spec, kc_globals, CFG["neurons"], CFG["edges"])
|
||||
require(first["record"]["requested"] == row["requests"], "Frozen inventory request count mismatch")
|
||||
runs[(row["mode"], row["odor_id"], row["seed"])] = {key: first[key] for key in
|
||||
("kc_counts", "expected_external_events", "external_events")}
|
||||
run_records.append(first["record"])
|
||||
provenance.append({"case": name, "repeat": 0, "state_npz_sha256": file_hash(first["output"] / "state.npz"),
|
||||
"summary_sha256": file_hash(first["output"] / "summary.json")})
|
||||
except Exception as error:
|
||||
failures.append({"stage": "raw_trial", "case": name, "repeat": 0,
|
||||
"type": type(error).__name__, "message": str(error)})
|
||||
if row["seed"] == ORIGINAL_SEEDS[0]:
|
||||
try:
|
||||
require(first is not None, "Baseline unavailable for exact repeat")
|
||||
repeat = read_trial(EXP / "gpu" / (name + "-r1"), case, spec, kc_globals, CFG["neurons"], CFG["edges"])
|
||||
repeat_report = {"case": name, **exact_repeat(first, repeat)}
|
||||
repeats.append(repeat_report)
|
||||
provenance.append({"case": name, "repeat": 1, "state_npz_sha256": file_hash(repeat["output"] / "state.npz"),
|
||||
"summary_sha256": file_hash(repeat["output"] / "summary.json")})
|
||||
require(repeat_report["pass_all"], "Exact repeat differs: " + ", ".join(
|
||||
key for key, value in repeat_report["checks"].items() if not value))
|
||||
except Exception as error:
|
||||
failures.append({"stage": "exact_repeat", "case": name, "repeat": 1,
|
||||
"type": type(error).__name__, "message": str(error)})
|
||||
if len(runs) == 60:
|
||||
for mode in ORIGINAL_MODES:
|
||||
try:
|
||||
modes[mode] = summarize_mode(mode, odors, ORIGINAL_SEEDS, runs, CFG["gates"])
|
||||
except Exception as error:
|
||||
failures.append({"stage": "representation", "mode": mode,
|
||||
"type": type(error).__name__, "message": str(error)})
|
||||
integrity_pass = bool(prerequisites_ok and len(runs) == 60 and not any(row["stage"] in
|
||||
("prerequisites", "raw_trial") for row in failures))
|
||||
repeat_pass = bool(len(repeats) == 12 and all(row["pass_all"] for row in repeats) and
|
||||
not any(row["stage"] == "exact_repeat" for row in failures))
|
||||
representation_pass = bool(len(modes) == 2 and all(row["gate"] for row in modes.values()))
|
||||
result = {"phase": "4AG", "scope": "Original fixed six CAL odors only; not heldout generalization or learning",
|
||||
"pass_all": bool(integrity_pass and repeat_pass and representation_pass),
|
||||
"numerical_integrity_pass": integrity_pass, "exact_repeat_pass": repeat_pass,
|
||||
"original_representation_gate_pass": representation_pass,
|
||||
"expected_base_trials": 60, "valid_base_trials": len(runs), "expected_repeats": 12,
|
||||
"checked_repeats": len(repeats), "seeds": list(ORIGINAL_SEEDS), "duration_ms": 100.0,
|
||||
"odor_ids": odors, "modes": modes, "runs": run_records, "repeats": repeats,
|
||||
"failures": failures, "input_output_provenance": provenance, "encoder_audit": encoder,
|
||||
"metric_source": "phase4l.representation (full original gate), phase4i.pair_metrics",
|
||||
"finite_gate_note": "Added integrity prerequisite; original 4L recorded finite but omitted it from its gate",
|
||||
"repeat_gate_note": "Additional exact repeat requirement; original metrics and numerical thresholds unchanged",
|
||||
"shared_apl_component_oracle_limit": True, "independent_oracle_equivalence_claim": False,
|
||||
"candidate_generated": False, "training": False, "heldout_dynamic_access": False}
|
||||
if target is not None:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with target.open("x", encoding="utf-8") as output:
|
||||
json.dump(result, output, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
output.write("\n")
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps({key: value for key, value in summarize().items() if key in
|
||||
("pass_all", "numerical_integrity_pass", "exact_repeat_pass", "original_representation_gate_pass")}), flush=True)
|
||||
@@ -0,0 +1,17 @@
|
||||
c14c2d3c35e2b404c516befa66370723fba075516540214ae46e4104ff87e813 PROTOCOL.md
|
||||
edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c calibration.py
|
||||
6e1616b086f667fa4e4b5087f500f8080281977ca9922e7b055acfddd2003bd5 compare.py
|
||||
2aa6b4d777ff698018e1a6eaf571215208b8c874a4c848a6d632e2bd778c90e3 config.json
|
||||
b8b1231d958a3ec935ec1d5a36e08cbd9bcd1127f4d4bb243a9051884a0e7a16 context.py
|
||||
b6e3c67e668a971b544df32d76435ab5e537061b9fc2e8d9dedb5d2a726f5782 finalize.py
|
||||
69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a gpu_apl.py
|
||||
21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291 legacy_apl_worker.py
|
||||
9b7f3e0b59f2117b94b41a6f542f37e6f7950138d324c905e8e0ac6cb7d17151 oracle_apl.py
|
||||
197ccc6aae70566c4db4d4de17fa032e853b9bd9a04262905d6c71e12a930b6f prepare.py
|
||||
afbe6d5a37c29bda66d38c470093584130d1852e67881c681662aec3c76b33c7 run_case.py
|
||||
84c681030b0032119b5392d0c3f3c136a9c6b0d133eb548c40791a10bbbb8b63 run_suite.py
|
||||
bab3136eecbc5bb09a985d715844ee845cb72b07930ce376bfb39be33bd1845a test_calibration.py
|
||||
1a233f47943e01bbd845ac1dacdb1eec3f88c270c65a5f75bce1cf679b5de097 test_protocol.py
|
||||
5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3 validate_components.py
|
||||
0d0e72b2f1348f6648124c8c34f75094f76fc5e1a281fbcca130a5fad2f35293 verify_frozen.py
|
||||
6aa01a937f1f3c6504ee92e847cf7b3a884e88aa4ef37a68b0b7d4e7deed4bf2 manifest.json
|
||||
@@ -0,0 +1,508 @@
|
||||
"""Fail-closed byte-exact audit of the shared-APL closed-loop fixtures.
|
||||
|
||||
Comparison is independent of either backend's stored PASS. No tolerance is
|
||||
used, timing fields are never compared, and reports contain no wall timestamp.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
N_GLOBAL = 166700
|
||||
STATE_KEYS = ('step', 'neuron_idx', 'counts', 'v', 'g', 'ref')
|
||||
REQUEST_FIELDS = ('tick', 'neuron_idx', 'attempted', 'accepted', 'rejected')
|
||||
HASH_FIELDS = {'tick', 'v_sha256', 'g_sha256', 'ref_sha256'}
|
||||
QUEUE_FIELDS = {'tick', 'count', 'accepted_count', 'ordered_edge_sha256',
|
||||
'accepted_mask_sha256', 'joint_sha256'}
|
||||
BUNDLE_FILES = ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')
|
||||
COEFFICIENTS = ('gd', 'c0', 'cg', 'cv', 'threshold', 'reset', 'seed_volt')
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(array, dtype=None):
|
||||
return hashlib.sha256(np.ascontiguousarray(array, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def save(path, report):
|
||||
if path is None:
|
||||
return
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as target:
|
||||
json.dump(report, target, indent=2, sort_keys=True, allow_nan=False)
|
||||
target.write('\n')
|
||||
|
||||
|
||||
def _read_json(path, used):
|
||||
used.add(Path(path))
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def _read_jsonl(path, used):
|
||||
used.add(Path(path))
|
||||
with Path(path).open(encoding='utf-8') as source:
|
||||
return [json.loads(line) for line in source if line.strip()]
|
||||
|
||||
|
||||
def _read_array(path, used):
|
||||
used.add(Path(path))
|
||||
return np.load(path, mmap_mode='r', allow_pickle=False)
|
||||
|
||||
|
||||
def _check(checks, name, condition):
|
||||
checks[name] = bool(condition)
|
||||
|
||||
|
||||
def _bit_mask(a, b):
|
||||
if a.dtype.kind == 'f':
|
||||
unsigned = np.dtype('uint'+str(a.dtype.itemsize*8))
|
||||
return np.ascontiguousarray(a).view(unsigned) != np.ascontiguousarray(b).view(unsigned)
|
||||
return a != b
|
||||
|
||||
|
||||
def _compare_array(name, a, b, checks, differences, dtype=None, shape=None):
|
||||
a, b = np.asarray(a), np.asarray(b)
|
||||
schema = a.shape == b.shape and a.dtype == b.dtype and \
|
||||
(dtype is None or a.dtype == np.dtype(dtype)) and (shape is None or a.shape == shape)
|
||||
_check(checks, name+'.schema', schema)
|
||||
if not schema:
|
||||
differences[name] = {'left_shape': list(a.shape), 'right_shape': list(b.shape),
|
||||
'left_dtype': str(a.dtype), 'right_dtype': str(b.dtype), 'expected_shape': None if shape is None else list(shape),
|
||||
'expected_dtype': None if dtype is None else str(np.dtype(dtype))}
|
||||
_check(checks, name+'.bitwise_exact', False)
|
||||
return False
|
||||
finite = bool(np.isfinite(a).all() and np.isfinite(b).all())
|
||||
_check(checks, name+'.finite', finite)
|
||||
mask = _bit_mask(a, b)
|
||||
exact = not bool(np.any(mask))
|
||||
_check(checks, name+'.bitwise_exact', exact)
|
||||
if not exact or not finite:
|
||||
flat = np.flatnonzero(mask.ravel())
|
||||
detail = {'mismatching_elements': int(len(flat)),
|
||||
'first_positions': [list(map(int, np.unravel_index(int(index), a.shape))) for index in flat[:5]],
|
||||
'left_nonfinite': int(np.count_nonzero(~np.isfinite(a))),
|
||||
'right_nonfinite': int(np.count_nonzero(~np.isfinite(b)))}
|
||||
if finite and a.size:
|
||||
error = np.abs(a.astype(np.float64)-b.astype(np.float64))
|
||||
detail.update(max_abs_error=float(error.max()), mean_abs_error=float(error.mean()))
|
||||
if a.dtype.kind == 'f' and len(flat):
|
||||
unsigned = np.dtype('uint'+str(a.dtype.itemsize*8))
|
||||
av = np.ascontiguousarray(a).view(unsigned).ravel()
|
||||
bv = np.ascontiguousarray(b).view(unsigned).ravel()
|
||||
detail['first_bit_patterns'] = [{'left': hex(int(av[index])), 'right': hex(int(bv[index]))} for index in flat[:5]]
|
||||
differences[name] = detail
|
||||
return exact and finite
|
||||
|
||||
|
||||
def _sha(value):
|
||||
return isinstance(value, str) and re.fullmatch('[0-9a-f]{64}', value) is not None
|
||||
|
||||
|
||||
def _read_requests(path, steps, used):
|
||||
used.add(Path(path))
|
||||
with Path(path).open(encoding='utf-8', newline='') as source:
|
||||
reader = csv.DictReader(source)
|
||||
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, 'Request CSV schema mismatch: '+str(path))
|
||||
rows = [{key: int(row[key]) for key in REQUEST_FIELDS} for row in reader]
|
||||
previous = (-1, -1)
|
||||
for row in rows:
|
||||
key = row['tick'], row['neuron_idx']
|
||||
require(previous < key and 0 <= key[0] < steps and 0 <= key[1] < N_GLOBAL,
|
||||
'Request rows must be sorted, unique, and in range: '+str(path))
|
||||
require(row['attempted'] == 1 and row['accepted'] in (0, 1) and row['rejected'] == 1-row['accepted'],
|
||||
'Request accounting is incomplete: '+str(path))
|
||||
previous = key
|
||||
return rows
|
||||
|
||||
|
||||
def _load_run(directory, role, n, edges, steps, record_history, name, used, checks):
|
||||
directory = Path(directory)
|
||||
summary = _read_json(directory/'summary.json', used)
|
||||
_check(checks, role+'.summary_complete', summary.get('complete') is True and summary.get('pass_all') is True and
|
||||
summary.get('steps') == steps and summary.get('completed_ticks') == steps and
|
||||
summary.get('nodes') == n and summary.get('edges') == edges and summary.get('precision') == 'float64')
|
||||
is_cpu = role == 'cpu'
|
||||
_check(checks, role+'.case_name', summary.get('name' if is_cpu else 'case') == name)
|
||||
_check(checks, role+'.history_mode', summary.get('history_recorded' if is_cpu else 'record_history') is record_history)
|
||||
_check(checks, role+'.apl_precision', summary.get('APL_precision' if is_cpu else 'apl_precision') == 'float32')
|
||||
_check(checks, role+'.no_error_marker', not (directory/'error.json').exists())
|
||||
used.add(directory/'state.npz')
|
||||
with np.load(directory/'state.npz', allow_pickle=False) as source:
|
||||
require(set(source.files) == set(STATE_KEYS), 'State NPZ keys changed: '+str(directory))
|
||||
state = {key: source[key].copy() for key in STATE_KEYS}
|
||||
for key, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32'), ('counts', 'int32')):
|
||||
require(state[key].shape == (n,) and state[key].dtype == np.dtype(dtype), 'Invalid final '+key+': '+str(directory))
|
||||
require(state['step'].dtype == state['neuron_idx'].dtype == np.int32 and state['step'].ndim == 1 and
|
||||
state['step'].shape == state['neuron_idx'].shape, 'Invalid sparse event schema: '+str(directory))
|
||||
require(bool(np.all((state['step'] >= 0) & (state['step'] < steps))) and
|
||||
bool(np.all((state['neuron_idx'] >= 0) & (state['neuron_idx'] < N_GLOBAL))), 'Sparse event range error')
|
||||
event_keys = state['step'].astype(np.int64)*N_GLOBAL+state['neuron_idx']
|
||||
_check(checks, role+'.events_sorted_unique', len(event_keys) < 2 or np.all(np.diff(event_keys) > 0))
|
||||
_check(checks, role+'.count_nonnegative_total', np.all(state['counts'] >= 0) and int(state['counts'].sum()) == len(event_keys))
|
||||
_check(checks, role+'.ref_range', np.all((state['ref'] >= 0) & (state['ref'] <= 22)))
|
||||
_check(checks, role+'.reported_events', summary.get('spikes' if is_cpu else 'total_spikes') == len(event_keys))
|
||||
hashes = _read_jsonl(directory/'tick-state-hashes.jsonl', used)
|
||||
require(len(hashes) == steps and [row.get('tick') for row in hashes] == list(range(steps)), 'Incomplete tick hash coverage')
|
||||
require(all(set(row) == HASH_FIELDS and all(_sha(row[key]) for key in HASH_FIELDS-{'tick'}) for row in hashes),
|
||||
'Invalid tick state hash schema')
|
||||
queues = _read_jsonl(directory/'queue.jsonl', used)
|
||||
require(len(queues) == steps and [row.get('tick') for row in queues] == list(range(steps)), 'Incomplete queue coverage')
|
||||
require(all(set(row) == QUEUE_FIELDS and type(row['count']) is int and type(row['accepted_count']) is int and
|
||||
0 <= row['accepted_count'] <= row['count'] <= edges and
|
||||
all(_sha(row[key]) for key in QUEUE_FIELDS-{'tick', 'count', 'accepted_count'}) for row in queues),
|
||||
'Invalid queue row schema/count/hash')
|
||||
for key in ('v', 'g', 'ref'):
|
||||
_check(checks, role+'.last_hash_'+key, hashes[-1][key+'_sha256'] == raw_hash(state[key], '<i4' if key == 'ref' else '<f8'))
|
||||
requests = _read_requests(directory/'requests.csv', steps, used)
|
||||
requested, accepted = len(requests), sum(row['accepted'] for row in requests)
|
||||
_check(checks, role+'.request_summary', summary.get('requests' if is_cpu else 'requested') == requested and
|
||||
summary.get('applicable_requests' if is_cpu else 'accepted_requests') == accepted and
|
||||
summary.get('ref_rejected_requests' if is_cpu else 'rejected_requests') == requested-accepted)
|
||||
apl = {field: _read_array(directory/('apl-'+field+'.float32.npy'), used) for field in ('drive', 'state', 'inhibition')}
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', 4064)):
|
||||
require(apl[field].dtype == np.float32 and apl[field].shape == (steps, width), 'Invalid APL '+field+' history schema')
|
||||
_check(checks, role+'.apl_positive_'+field, np.all(apl[field] >= 0))
|
||||
histories, pre = {}, {}
|
||||
for field, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32')):
|
||||
files = (directory/(field+'-history.'+dtype+'.npy'), directory/('preAPL-'+field+'.'+dtype+'.npy'))
|
||||
if record_history:
|
||||
histories[field], pre[field] = (_read_array(path, used) for path in files)
|
||||
require(all(array.dtype == np.dtype(dtype) and array.shape == (steps, n) for array in (histories[field], pre[field])),
|
||||
'Small pre/post history schema mismatch')
|
||||
_check(checks, role+'.history_hashes_'+field, all(row[field+'_sha256'] == raw_hash(histories[field][tick])
|
||||
for tick, row in enumerate(hashes)))
|
||||
_check(checks, role+'.history_final_'+field, not np.any(_bit_mask(histories[field][-1], state[field])))
|
||||
else:
|
||||
_check(checks, role+'.no_dense_'+field, not any(path.exists() for path in files))
|
||||
if is_cpu:
|
||||
component = _read_json(directory/'apl-worker-handshake.json', used)['handshake']
|
||||
else:
|
||||
component = _read_json(directory/'apl-component.json', used)
|
||||
_check(checks, role+'.component_summary_consistent', summary.get('apl_component') == component)
|
||||
return dict(directory=directory, summary=summary, state=state, hashes=hashes, queues=queues,
|
||||
requests=requests, apl=apl, histories=histories, pre=pre, component=component)
|
||||
|
||||
|
||||
def _pair_outputs(left, right, checks, differences, n, steps, record_history):
|
||||
for field in STATE_KEYS:
|
||||
shape = None if field in ('step', 'neuron_idx') else (n,)
|
||||
dtype = 'float64' if field in ('v', 'g') else 'int32'
|
||||
_compare_array('state.'+field, left['state'][field], right['state'][field], checks, differences, dtype, shape)
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', 4064)):
|
||||
_compare_array('apl.'+field, left['apl'][field], right['apl'][field], checks, differences, 'float32', (steps, width))
|
||||
for field in ('hashes', 'queues', 'requests', 'component'):
|
||||
same = left[field] == right[field]
|
||||
_check(checks, field+'.parsed_exact', same)
|
||||
if not same:
|
||||
if isinstance(left[field], list):
|
||||
first = next((i for i, (a, b) in enumerate(zip(left[field], right[field])) if a != b), None)
|
||||
differences[field] = {'left_count': len(left[field]), 'right_count': len(right[field]),
|
||||
'first_differing_row': first,
|
||||
'left_row': None if first is None else left[field][first],
|
||||
'right_row': None if first is None else right[field][first]}
|
||||
else:
|
||||
differences[field] = {'left': left[field], 'right': right[field]}
|
||||
if record_history:
|
||||
for stage in ('histories', 'pre'):
|
||||
for field, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32')):
|
||||
_compare_array(stage+'.'+field, left[stage][field], right[stage][field], checks, differences, dtype, (steps, n))
|
||||
|
||||
|
||||
def _queue_digest(tick, ids, accepted):
|
||||
ids = np.ascontiguousarray(ids, dtype='<i8').tobytes()
|
||||
mask = np.ascontiguousarray(accepted, dtype=np.uint8).tobytes()
|
||||
return {'tick': tick, 'count': len(mask), 'accepted_count': int(np.count_nonzero(accepted)),
|
||||
'ordered_edge_sha256': hashlib.sha256(ids).hexdigest(),
|
||||
'accepted_mask_sha256': hashlib.sha256(mask).hexdigest(),
|
||||
'joint_sha256': hashlib.sha256(b'phase4af-queue-v1\0'+struct.pack('<qq', tick, len(mask))+ids+mask).hexdigest()}
|
||||
|
||||
|
||||
def _independent_event_accounting(run, role, graph, requested, steps, checks, differences):
|
||||
nodes, ptr = graph['nodes'], graph['ptr']
|
||||
n = len(nodes)
|
||||
state = run['state']
|
||||
local = np.searchsorted(nodes, state['neuron_idx'])
|
||||
require(np.all(local < n) and np.array_equal(nodes[local], state['neuron_idx']), 'Actual spike outside graph identity')
|
||||
_check(checks, role+'.events_per_neuron_counts', np.array_equal(np.bincount(local, minlength=n), state['counts']))
|
||||
actual_request_pairs = [(row['tick'], row['neuron_idx']) for row in run['requests']]
|
||||
_check(checks, role+'.requests_match_frozen_input', actual_request_pairs == requested)
|
||||
require(actual_request_pairs == requested, 'Actual request population/time differs from frozen input')
|
||||
bounds = np.searchsorted(state['step'], np.arange(steps+1))
|
||||
request_ticks = np.asarray([row['tick'] for row in run['requests']], np.int32)
|
||||
rbounds = np.searchsorted(request_ticks, np.arange(steps+1))
|
||||
request_nodes = np.asarray([row['neuron_idx'] for row in run['requests']], np.int32)
|
||||
request_local = np.searchsorted(nodes, request_nodes)
|
||||
require(np.all(request_local < n) and np.array_equal(nodes[request_local], request_nodes), 'Request outside graph')
|
||||
last = np.full(n, -1_000_000_000, np.int64)
|
||||
degrees = np.diff(ptr)
|
||||
expected_counts = np.zeros(steps, np.int64)
|
||||
arrival_tick = state['step'].astype(np.int64)+18
|
||||
valid = arrival_tick < steps
|
||||
np.add.at(expected_counts, arrival_tick[valid], degrees[local[valid]])
|
||||
_check(checks, role+'.delayed_all_edges_count', np.array_equal(expected_counts, [row['count'] for row in run['queues']]))
|
||||
request_ok, delivered_ok, ref_ok, queue_ok, reset_ok = True, True, True, True, True
|
||||
first = {}
|
||||
for tick in range(steps):
|
||||
fired = local[bounds[tick]:bounds[tick+1]]
|
||||
lo, hi = rbounds[tick:tick+2]
|
||||
targets = request_local[lo:hi]
|
||||
accepted = (tick-last[targets]) >= 22
|
||||
for index, ok in zip(range(lo, hi), accepted):
|
||||
row = run['requests'][index]
|
||||
if row['accepted'] != int(ok) or row['rejected'] != int(not ok):
|
||||
request_ok = False
|
||||
first.setdefault('request_ref_mismatch', {'tick': tick, 'neuron_idx': row['neuron_idx']})
|
||||
if not np.isin(targets[accepted], fired).all() or np.isin(targets[~accepted], fired).any():
|
||||
delivered_ok = False
|
||||
first.setdefault('request_delivery_mismatch', tick)
|
||||
last[fired] = tick
|
||||
ref = np.maximum(22-(tick-last), 0).astype(np.int32)
|
||||
if raw_hash(ref, '<i4') != run['hashes'][tick]['ref_sha256']:
|
||||
ref_ok = False
|
||||
first.setdefault('ref_hash_mismatch', tick)
|
||||
if run['pre']:
|
||||
if len(fired) and not np.array_equal(np.ascontiguousarray(run['pre']['g'][tick, fired]).view(np.uint64), np.zeros(len(fired), np.uint64)):
|
||||
reset_ok = False
|
||||
first.setdefault('pre_apl_spike_reset_mismatch', tick)
|
||||
due = local[bounds[tick-18]:bounds[tick-17]] if tick >= 18 else np.empty(0, np.int64)
|
||||
chunks = [np.arange(ptr[source], ptr[source+1], dtype=np.int64) for source in due]
|
||||
indices = np.concatenate(chunks) if chunks else np.empty(0, np.int64)
|
||||
expected_queue = _queue_digest(tick, graph['edge_id'][indices], (ref[graph['dst'][indices]] == 0).astype(np.uint8))
|
||||
if expected_queue != run['queues'][tick]:
|
||||
queue_ok = False
|
||||
first.setdefault('queue_reconstruction_mismatch', tick)
|
||||
_check(checks, role+'.request_refractory_gate', request_ok)
|
||||
_check(checks, role+'.accepted_spikes_rejected_absent', delivered_ok)
|
||||
_check(checks, role+'.all_ref_hashes_from_actual_events', ref_ok)
|
||||
if run['pre']:
|
||||
_check(checks, role+'.small_queue_independent_reconstruction', queue_ok)
|
||||
_check(checks, role+'.spike_g_reset_before_apl', reset_ok)
|
||||
if first:
|
||||
differences[role+'.independent_accounting'] = first
|
||||
|
||||
|
||||
def _check_apl_application(run, role, nodes, kc, checks, differences):
|
||||
if not run['pre']:
|
||||
return
|
||||
common, local_kc, bundle_kc = np.intersect1d(nodes, kc, assume_unique=True, return_indices=True)
|
||||
_compare_array(role+'.apl_keeps_voltage', run['pre']['v'], run['histories']['v'], checks, differences, 'float64')
|
||||
_compare_array(role+'.apl_keeps_ref', run['pre']['ref'], run['histories']['ref'], checks, differences, 'int32')
|
||||
expected = np.array(run['pre']['g'], copy=True)
|
||||
delta = run['apl']['inhibition'][:, bundle_kc].astype(np.float64)*np.float64(.001)
|
||||
expected[:, local_kc] = expected[:, local_kc]-delta
|
||||
_compare_array(role+'.apl_unconditional_g_write', expected, run['histories']['g'], checks, differences, 'float64')
|
||||
|
||||
|
||||
def _finish(report, checks, differences, used, out_json):
|
||||
report['checks'] = {name: bool(value) for name, value in checks.items()}
|
||||
report['differences'] = differences
|
||||
report['pass_all'] = bool(checks) and all(checks.values()) and 'error' not in report
|
||||
report['failed_checks'] = sorted(name for name, value in checks.items() if not value)
|
||||
report['evidence_sha256'] = {str(path): file_hash(path) for path in sorted(used) if path.is_file()}
|
||||
save(out_json, report)
|
||||
return report
|
||||
|
||||
|
||||
def compare(case_dir, gpu_dir, cpu_dir, out_json=None):
|
||||
"""Strict independent closed-loop comparison; out_json=None is read-only."""
|
||||
case_dir, gpu_dir, cpu_dir = Path(case_dir), Path(gpu_dir), Path(cpu_dir)
|
||||
if out_json is not None:
|
||||
require(not Path(out_json).exists(), 'Refusing existing comparison report')
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'shared-APL-component closed-loop byte-exact comparison', 'pass_all': False,
|
||||
'case_dir': str(case_dir), 'gpu_dir': str(gpu_dir), 'cpu_dir': str(cpu_dir),
|
||||
'tolerance': None, 'signed_zero_included': True, 'timing_fields_compared': False,
|
||||
'independent_CPU_all_components_claim': False}
|
||||
try:
|
||||
spec = _read_json(case_dir/'spec.json', used)
|
||||
steps, history = spec['steps'], spec['record_history']
|
||||
require(type(steps) is int and steps > 0 and type(history) is bool and spec['dt_ms'] == .1,
|
||||
'Invalid fixed case clock/history schema')
|
||||
graph_dir, bundle_dir = Path(spec['graph_dir']), Path(spec['apl_bundle_dir'])
|
||||
graph = {field: _read_array(graph_dir/(field+'.npy'), used) for field in
|
||||
('nodes', 'edge_id', 'src', 'dst', 'ptr', 'signed_raw', 'weights')}
|
||||
nodes = graph['nodes']
|
||||
n, edges = len(nodes), len(graph['edge_id'])
|
||||
require(n > 0 and edges > 0 and nodes.dtype == np.int32 and nodes.ndim == 1 and np.all((nodes >= 0) & (nodes < N_GLOBAL)) and
|
||||
np.all(np.diff(nodes) > 0), 'Invalid canonical node identities')
|
||||
require(graph['ptr'].dtype == np.int64 and graph['ptr'].shape == (n+1,) and graph['ptr'][0] == 0 and
|
||||
graph['ptr'][-1] == edges and np.all(np.diff(graph['ptr']) >= 0), 'Invalid CSR')
|
||||
for key, dtype in (('edge_id', 'int64'), ('src', 'int32'), ('dst', 'int32'), ('signed_raw', 'int64'), ('weights', 'float64')):
|
||||
require(graph[key].dtype == np.dtype(dtype) and graph[key].shape == (edges,), 'Invalid graph '+key)
|
||||
require(np.array_equal(graph['src'], np.repeat(np.arange(n, dtype=np.int32), np.diff(graph['ptr']))) and
|
||||
np.all((graph['dst'] >= 0) & (graph['dst'] < n)), 'Invalid source/destination row mapping')
|
||||
require(history == (n < 512) and (n < 512 or n == N_GLOBAL), 'Invalid small/full history declaration')
|
||||
require(np.all(graph['edge_id'] >= 0) and np.all(np.diff(graph['edge_id']) > 0),
|
||||
'Original canonical edge IDs must be unique and increasing')
|
||||
if n == N_GLOBAL:
|
||||
require(edges == 25582938 and np.array_equal(nodes, np.arange(n, dtype=np.int32)) and
|
||||
np.array_equal(graph['edge_id'], np.arange(edges, dtype=np.int64)), 'Full canonical graph identity changed')
|
||||
bundle = {name: _read_array(bundle_dir/name, used) for name in BUNDLE_FILES}
|
||||
kc, apl_ids = bundle['kc_globals.npy'], bundle['apl_globals.npy']
|
||||
require(kc.dtype == apl_ids.dtype == np.int32 and kc.shape == (4064,) and apl_ids.shape == (2,), 'Invalid APL/KC identities')
|
||||
require(np.all((kc >= 0) & (kc < N_GLOBAL)) and np.all(np.diff(kc) > 0) and
|
||||
np.all((apl_ids >= 0) & (apl_ids < N_GLOBAL)) and np.all(np.diff(apl_ids) > 0) and
|
||||
not np.intersect1d(kc, apl_ids).size, 'APL/KC identities overlap or are invalid')
|
||||
for key, shape in (('input_matrix.npy', (2, N_GLOBAL)), ('output_matrix.npy', (2, 4064))):
|
||||
require(bundle[key].dtype == np.float32 and bundle[key].shape == shape and
|
||||
np.isfinite(bundle[key]).all() and np.all(bundle[key] >= 0), 'Invalid normalized APL matrix')
|
||||
gpu = _load_run(gpu_dir, 'gpu', n, edges, steps, history, spec['name'], used, checks)
|
||||
cpu = _load_run(cpu_dir, 'cpu', n, edges, steps, history, spec['name'], used, checks)
|
||||
_pair_outputs(gpu, cpu, checks, differences, n, steps, history)
|
||||
used.add(case_dir/'requests.npz')
|
||||
with np.load(case_dir/'requests.npz', allow_pickle=False) as source:
|
||||
require(set(source.files) == {'tick', 'neuron_idx'} and source['tick'].dtype == source['neuron_idx'].dtype == np.int32,
|
||||
'Fixed request NPZ schema changed')
|
||||
require(source['tick'].shape == source['neuron_idx'].shape and source['tick'].ndim == 1, 'Invalid sparse request shape')
|
||||
requested = list(zip(map(int, source['tick']), map(int, source['neuron_idx'])))
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
_independent_event_accounting(run, role, graph, requested, steps, checks, differences)
|
||||
_check_apl_application(run, role, nodes, kc, checks, differences)
|
||||
|
||||
# Independently derive the effective clone. Original graph weights stay read-only.
|
||||
cpu_weights = _read_array(cpu_dir/'weights.npy', used)
|
||||
require(cpu_weights.dtype == np.float64 and cpu_weights.shape == (edges,), 'CPU effective weight schema mismatch')
|
||||
expected_hash, base_hash, mask_hash = hashlib.sha256(), hashlib.sha256(), hashlib.sha256()
|
||||
weight_ok, mask_count = True, 0
|
||||
for start in range(0, edges, 1 << 20):
|
||||
stop = min(edges, start+(1 << 20))
|
||||
original = np.asarray(graph['weights'][start:stop])
|
||||
require(np.isfinite(original).all(), 'Nonfinite original weights')
|
||||
raw_expected = graph['signed_raw'][start:stop]*(.275*.001)
|
||||
require(not np.any(_bit_mask(original, raw_expected)), 'Original SI weights differ from unaltered signed contacts')
|
||||
mask = np.isin(nodes[graph['src'][start:stop]], apl_ids)
|
||||
expected = original.copy()
|
||||
expected[mask] = np.float64(0)
|
||||
mask_count += int(mask.sum())
|
||||
expected_hash.update(np.ascontiguousarray(expected, dtype='<f8').tobytes())
|
||||
base_hash.update(np.ascontiguousarray(original, dtype='<f8').tobytes())
|
||||
mask_hash.update(np.ascontiguousarray(graph['edge_id'][start:stop][mask], dtype='<i8').tobytes())
|
||||
weight_ok &= not np.any(_bit_mask(expected, cpu_weights[start:stop]))
|
||||
expected_sha = expected_hash.hexdigest()
|
||||
_check(checks, 'cpu.effective_weights_independent_bytes', weight_ok)
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
_check(checks, role+'.effective_weights_raw_sha', run['summary'].get('effective_weights_raw_sha256') == expected_sha)
|
||||
_check(checks, role+'.apl_fast_edge_count', run['summary'].get('apl_fast_edges_zeroed') == mask_count)
|
||||
_check(checks, role+'.kc_target_count', run['summary'].get('kc_targets_present') == len(np.intersect1d(nodes, kc)))
|
||||
mask_audit = _read_json(cpu_dir/'apl-mask-audit.json', used)
|
||||
_check(checks, 'cpu.mask_provenance', mask_audit['count'] == mask_count and
|
||||
mask_audit['ordered_original_edge_id_sha256'] == mask_hash.hexdigest() and
|
||||
mask_audit['base_weight_raw_bytes_sha256'] == base_hash.hexdigest() and
|
||||
mask_audit['effective_weights_raw_sha256'] == expected_sha and mask_audit['base_graph_files_modified'] is False)
|
||||
|
||||
case_hashes = {name: file_hash(case_dir/name) for name in ('spec.json', 'requests.npz')}
|
||||
if (case_dir/'arrays.npz').exists():
|
||||
case_hashes['arrays.npz'] = file_hash(case_dir/'arrays.npz')
|
||||
used.add(case_dir/'arrays.npz')
|
||||
baseline_coeff = _read_json(Path(spec['coefficients_path']), used)
|
||||
actual_coeff = _read_json(cpu_dir/'coefficients.json', used)
|
||||
_check(checks, 'cpu.actual_author_coefficients_exact', all(float(actual_coeff[key]).hex() == float(baseline_coeff[key]).hex()
|
||||
for key in COEFFICIENTS))
|
||||
audit = _read_json(cpu_dir/'input-audit.json', used)
|
||||
expected_graph_hashes = {name: file_hash(graph_dir/name) for name in
|
||||
tuple(field+'.npy' for field in graph)+('completeness.csv', 'connectivity.parquet')}
|
||||
bundle_hashes = {name: file_hash(bundle_dir/name) for name in BUNDLE_FILES}
|
||||
_check(checks, 'cpu.read_actual_case', audit['spec'] == spec and audit['case_hashes'] == case_hashes)
|
||||
_check(checks, 'cpu.graph_hashes', audit['graph_hashes'] == expected_graph_hashes)
|
||||
_check(checks, 'cpu.bundle_hashes', audit['bundle_hashes'] == bundle_hashes)
|
||||
_check(checks, 'cpu.no_candidate_trace_input', audit.get('candidate_trace_used_as_input') is False and
|
||||
cpu['summary'].get('candidate_trace_used_as_input') is False)
|
||||
_check(checks, 'cpu.worker_source_hash', audit['legacy_apl_worker_sha256'] == file_hash(spec['legacy_apl_worker']))
|
||||
_check(checks, 'cpu.coefficient_input_hash', audit['coefficients_sha256'] == file_hash(spec['coefficients_path']))
|
||||
gpu_hashes = dict(case_hashes, **{'base_weights.npy': file_hash(graph_dir/'weights.npy'),
|
||||
'coefficients.json': file_hash(spec['coefficients_path'])})
|
||||
_check(checks, 'gpu.input_hashes', gpu['summary']['input_sha256'] == gpu_hashes)
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
component = run['component']
|
||||
_check(checks, role+'.actual_component_bundle_bytes', component['files_sha256'] == bundle_hashes and
|
||||
component['array_sha256'] == {name: raw_hash(value) for name, value in bundle.items()})
|
||||
_check(checks, role+'.component_dimensions_clock', component.get('ok') is True and
|
||||
component['protocol'] == 'phase4ag-apl-v1' and component['n_neurons'] == N_GLOBAL and
|
||||
component['n_apl'] == 2 and component['n_kc'] == 4064 and component['dt_ms'] == .1)
|
||||
decay = np.float32(math.exp(-.1/20.0))
|
||||
gain = np.float32(.01)
|
||||
_check(checks, role+'.frozen_apl_constants', component['decay_f32_bits'] == f'{int(decay.view(np.uint32)):08x}' and
|
||||
component['gain_f32_bits'] == f'{int(gain.view(np.uint32)):08x}')
|
||||
env = component['environment']
|
||||
_check(checks, role+'.component_precision_environment', env['cuda_matmul_allow_tf32'] is False and
|
||||
env['float32_matmul_precision'] == 'highest' and env['precision_configuration_changed_by_component'] is False and
|
||||
'4090' in env['device_name'])
|
||||
environment = _read_json(cpu_dir/'environment.json', used)
|
||||
_check(checks, 'cpu.pinned_environment', environment['packages']['brian2'] == '2.5.1' and
|
||||
environment['packages']['numpy'] == '1.22.3' and environment['packages']['sympy'] == '1.12' and
|
||||
environment['Torch_imported_into_Brian'] is False and environment['author_precision'] == 'float64')
|
||||
queue_audit = _read_json(cpu_dir/'queue-audit.json', used)
|
||||
_check(checks, 'cpu.queue_observer_coverage', queue_audit['peek_calls'] == queue_audit['advance_calls'] == steps and
|
||||
queue_audit['same_peek_object_returned'] is True and queue_audit['zero_weight_and_refractory_rejected_edges_retained'] is True)
|
||||
_check(checks, 'cpu.sidecar_complete', cpu['summary']['APL_worker_exit_status'] == 0 and cpu['summary']['APL_worker_ticks'] == steps)
|
||||
report.update(case=spec['name'], steps=steps, nodes=n, edges=edges, record_history=history,
|
||||
total_spikes=len(gpu['state']['step']), active_neurons=int(np.count_nonzero(gpu['state']['counts'])),
|
||||
requested=len(requested), accepted_requests=sum(row['accepted'] for row in gpu['requests']),
|
||||
rejected_requests=sum(row['rejected'] for row in gpu['requests']),
|
||||
arriving_edges=sum(row['count'] for row in gpu['queues']),
|
||||
accepted_arriving_edges=sum(row['accepted_count'] for row in gpu['queues']),
|
||||
apl_fast_edges_zeroed=mask_count, effective_weights_raw_sha256=expected_sha,
|
||||
component_environment=gpu['component']['environment'],
|
||||
limits=['APL Torch primitives are shared; original AST component validation is a separate required gate.',
|
||||
'Small cases preserve and independently hash all pre/post states; full cases compare all-tick instrumented state hashes without saving dense whole-brain histories.',
|
||||
'For full cases intermediate finite checks are performed by the instrumented backends; this file independently inspects final states and all APL traces.',
|
||||
'This is exact agreement for the declared trajectory, not physiological validity or learning.'])
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return _finish(report, checks, differences, used, out_json)
|
||||
|
||||
|
||||
def compare_gpu_repeats(first, second, out_json=None):
|
||||
"""Repeat equality only; the first run's independent oracle gate is separate."""
|
||||
first, second = Path(first), Path(second)
|
||||
if out_json is not None:
|
||||
require(not Path(out_json).exists(), 'Refusing existing repeat report')
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'fresh-GPU-run exact repeat comparison', 'first': str(first), 'second': str(second),
|
||||
'pass_all': False, 'tolerance': None, 'signed_zero_included': True, 'timing_fields_compared': False,
|
||||
'scope': 'Scientific outputs, recording integrity and input hashes; independent case/graph audit belongs to compare()'}
|
||||
try:
|
||||
summary = _read_json(first/'summary.json', used)
|
||||
n, edges, steps, name, history = (summary[key] for key in ('nodes', 'edges', 'steps', 'case', 'record_history'))
|
||||
require(type(n) is int and type(edges) is int and type(steps) is int and n > 0 and edges > 0 and steps > 0 and
|
||||
type(history) is bool and history == (n < 512), 'Invalid repeat dimensions')
|
||||
left = _load_run(first, 'first_gpu', n, edges, steps, history, name, used, checks)
|
||||
right = _load_run(second, 'second_gpu', n, edges, steps, history, name, used, checks)
|
||||
_pair_outputs(left, right, checks, differences, n, steps, history)
|
||||
stable = ('case', 'complete', 'pass_all', 'steps', 'completed_ticks', 'nodes', 'edges', 'precision',
|
||||
'apl_precision', 'state_units', 'total_spikes', 'requested', 'accepted_requests', 'rejected_requests',
|
||||
'arriving_edges', 'accepted_arriving_edges', 'counts_match_sparse_events', 'all_finite', 'record_history',
|
||||
'apl_fast_edges_zeroed', 'kc_targets_present', 'effective_weights_raw_sha256', 'apl_schedule',
|
||||
'apl_si_conversion', 'build', 'sealed_ae_backend_sha256', 'learning', 'input_sha256')
|
||||
for key in stable:
|
||||
_check(checks, 'summary.'+key, key in left['summary'] and key in right['summary'] and
|
||||
left['summary'][key] == right['summary'][key])
|
||||
if not checks['summary.'+key]:
|
||||
differences['summary.'+key] = {'first': left['summary'].get(key), 'second': right['summary'].get(key)}
|
||||
report.update(case=name, steps=steps, nodes=n, edges=edges, record_history=history,
|
||||
total_spikes=len(left['state']['step']),
|
||||
explicit_exclusions=['wall_seconds', 'diagnostic_simulation_seconds', 'build_or_cache_seconds',
|
||||
'peak_allocated_vram_bytes', 'peak_reserved_vram_bytes'])
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return _finish(report, checks, differences, used, out_json)
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"phase": "4AG",
|
||||
"name": "author-apl-lab-v1",
|
||||
"human_authorization": "允许独立新模型分支测试动力学假设",
|
||||
"gpu_uuid": "GPU-79f3a85d-adce-7833-7003-6488140ca596",
|
||||
"neurons": 166700,
|
||||
"edges": 25582938,
|
||||
"raw_weight_sum": 124177617,
|
||||
"precision": "float64",
|
||||
"apl_precision": "float32",
|
||||
"apl": {"tau_ms": 20.0, "gain": 0.01, "normalization": 1.0},
|
||||
"hypothesis": "Author LIF plus unchanged legacy graded APL operator at end, after reset, unconditional KC g write. Mixed precision: h32 promoted to float64, then multiplied by float64 .001 SI, then subtracted. Not original author's APL model.",
|
||||
"fixtures": ["zero", "pn_pulse", "kc_ref_boundary", "two_sources", "arrival_collision", "natural_request", "apl_only", "high_fanin"],
|
||||
"fixture_repeats": 3,
|
||||
"calibration": {"split": "calibration", "odor_count": 6, "modes": ["physiological", "matched_energy"], "seeds": [51101,51102,51103,51104,51105], "duration_ms": 100.0, "median_scope": "original frozen 12-odor panel", "full_oracle_probes": ["cal-physiological-00-s51101", "cal-matched_energy-00-s51101"], "repeat_first_seed_each_odor": true},
|
||||
"gates": {"sparse_min_fraction": 0.03, "sparse_max_fraction": 0.09, "max_fraction_strict": 0.5, "min_fraction_strict": 0.0, "minimum_separable_odor_pairs": 3, "same_jaccard_gt_between": true, "between_euclidean_gt_within": true, "both_modes_required": true, "finite_required": true, "repeat_exact_required": true},
|
||||
"training": false,
|
||||
"heldout_dynamic_access": false,
|
||||
"candidate_generation": false,
|
||||
"output_budget_bytes": 16106127360,
|
||||
"scope": "Semantic compatibility and fixed old CAL only. Shared-APL-component closed-loop Brian oracle plus independent arithmetic audit; never claim all-component independent CPU equivalence or learning success. No parameter/input/site/window selection from results."
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT=Path('/data/digital-drosophila')
|
||||
MODEL=Path(__file__).resolve().parent
|
||||
EXP=ROOT/'experiments/phase4ag-author-apl'
|
||||
AF_MODEL=ROOT/'models/male-cns-lif/author-fullgraph-lab-v1'
|
||||
AF_EXP=ROOT/'experiments/phase4af-author-fullgraph'
|
||||
AD_EXP=ROOT/'experiments/phase4ad-author-semantics'
|
||||
CFG=json.loads((MODEL/'config.json').read_text(encoding='utf-8'))
|
||||
|
||||
def digest(path):
|
||||
h=hashlib.sha256()
|
||||
with Path(path).open('rb') as f:
|
||||
for b in iter(lambda:f.read(8<<20),b''):h.update(b)
|
||||
return h.hexdigest()
|
||||
|
||||
def save(path,value):
|
||||
path=Path(path);path.parent.mkdir(parents=True,exist_ok=True)
|
||||
with path.open('x',encoding='utf-8') as f:json.dump(value,f,indent=2,ensure_ascii=False,allow_nan=False);f.write('\n')
|
||||
|
||||
def sources():
|
||||
return {p.name:digest(p) for p in sorted(MODEL.iterdir()) if p.is_file() and p.name not in ('manifest.json','checksums.sha256')}
|
||||
|
||||
def integrity():
|
||||
spec=importlib.util.spec_from_file_location('frozen_af_context',AF_MODEL/'context.py')
|
||||
mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod)
|
||||
result=mod.integrity()
|
||||
import sys
|
||||
sys.path.insert(0,str(ROOT/'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import manifest_check
|
||||
for p in (AF_MODEL,AF_EXP):result[str(p.relative_to(ROOT))]=manifest_check(p,digest)
|
||||
assert all(r['pass'] for r in result.values())
|
||||
return result
|
||||
|
||||
def check():
|
||||
p=json.loads((EXP/'protocol/preregistered.json').read_text())
|
||||
assert p['sources']==sources(),'Source changed after preregistration'
|
||||
for row in p['inputs']:assert digest(ROOT/row['path'])==row['sha256'],row['path']
|
||||
for row in json.loads((EXP/'protocol/prepared-hashes.json').read_text()):
|
||||
assert digest(EXP/row['path'])==row['sha256'],row['path']
|
||||
return p
|
||||
|
||||
def budget():
|
||||
size=sum(p.stat().st_size for p in EXP.rglob('*') if p.is_file())
|
||||
assert size<CFG['output_budget_bytes'],'New output resource budget exceeded'
|
||||
return size
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Seal a completed gate or preserved failure; never claim learning."""
|
||||
import json
|
||||
from context import ROOT,MODEL,EXP,CFG,check,save,digest,integrity,budget
|
||||
|
||||
|
||||
def seal(root):
|
||||
assert not (root/'manifest.json').exists() and not (root/'checksums.sha256').exists()
|
||||
rows=[dict(path=str(p.relative_to(root)),bytes=p.stat().st_size,sha256=digest(p))
|
||||
for p in sorted(root.rglob('*')) if p.is_file() and '__pycache__' not in p.parts]
|
||||
save(root/'manifest.json',dict(root=str(root),files=rows))
|
||||
with (root/'checksums.sha256').open('x') as f:
|
||||
for row in rows:f.write(row['sha256']+' '+row['path']+'\n')
|
||||
f.write(digest(root/'manifest.json')+' manifest.json\n')
|
||||
|
||||
|
||||
def main():
|
||||
from compare import compare,compare_gpu_repeats
|
||||
check();budget();comparisons=[];repeats={};components={}
|
||||
for p in sorted((EXP/'audit').glob('compare-*.json')):
|
||||
name=p.stem[len('compare-'):]
|
||||
actual=compare(EXP/'cases'/name,EXP/'gpu'/(name+'-r0'),EXP/'oracle'/name,None)
|
||||
assert actual==json.loads(p.read_text()),'Changed raw closed-loop evidence: '+name
|
||||
comparisons.append(dict(case=name,pass_all=actual['pass_all']))
|
||||
for p in sorted((EXP/'audit').glob('repeat-*.json')):
|
||||
name,repeat=p.stem[len('repeat-'):].rsplit('-r',1)
|
||||
actual=compare_gpu_repeats(EXP/'gpu'/(name+'-r0'),EXP/'gpu'/f'{name}-r{repeat}',None)
|
||||
assert actual==json.loads(p.read_text()),'Changed raw repeat evidence: '+name
|
||||
repeats[f'{name}-r{repeat}']=actual['pass_all']
|
||||
for p in (EXP/'component-audit').glob('*.json'):
|
||||
result=json.loads(p.read_text())
|
||||
components[p.stem]=result['pass_all']
|
||||
for filename,sha in result.get('input_sha256',{}).items():
|
||||
assert digest(EXP/'gpu'/(p.stem+'-r0')/filename)==sha
|
||||
gates={}
|
||||
for name in ('fixtures','probes','calibration'):
|
||||
path=EXP/'audit'/(name+'-gate.json')
|
||||
gates[name]=json.loads(path.read_text()) if path.exists() else None
|
||||
calpath=EXP/'calibration/summary.json'
|
||||
cal=json.loads(calpath.read_text()) if calpath.exists() else None
|
||||
if cal is not None:
|
||||
from calibration import summarize
|
||||
assert summarize(out_json=None)==cal,'CAL raw evidence changed'
|
||||
frozen=json.loads((EXP/'regression/frozen-five.json').read_text())
|
||||
after=integrity();save(EXP/'regression/old-integrity-after.json',after)
|
||||
stops=[dict(path=str(p.relative_to(EXP)),report=json.loads(p.read_text())) for p in sorted((EXP/'audit').glob('stopped-*.json'))]
|
||||
expected_cases=CFG['fixtures']+CFG['calibration']['full_oracle_probes']
|
||||
expected_repeats=[f'{name}-r{r}' for name in CFG['fixtures'] for r in range(1,CFG['fixture_repeats'])]
|
||||
compared={x['case']:x['pass_all'] for x in comparisons}
|
||||
missing=dict(comparisons=sorted(set(expected_cases)-set(compared)),
|
||||
components=sorted(set(expected_cases)-set(components)),repeats=sorted(set(expected_repeats)-set(repeats)))
|
||||
evidence_ok=(set(compared)==set(components)==set(expected_cases) and set(repeats)==set(expected_repeats) and
|
||||
all(compared.values()) and all(components.values()) and all(repeats.values()))
|
||||
numerical=evidence_ok and all(gates[x] is not None and gates[x]['pass_all'] for x in ('fixtures','probes'))
|
||||
representation=bool(cal is not None and cal['pass_all'])
|
||||
if numerical:status='PASS-FIXED-CAL-REPRESENTATION' if representation else 'FAIL-FIXED-CAL-REPRESENTATION' if cal is not None else 'INCOMPLETE-CAL-EXECUTION'
|
||||
else:status='FAIL-NUMERICAL-OR-AUDIT-GATE' if any(not p for group in (compared,components,repeats) for p in group.values()) else 'INCOMPLETE-NUMERICAL-EXECUTION'
|
||||
oldpass=bool(frozen['pass_all'] and all(r['pass'] for r in after.values()))
|
||||
if not oldpass:status='FAIL-FROZEN-INTEGRITY-OR-REGRESSION'
|
||||
measurements=[]
|
||||
for p in sorted((EXP/'gpu').glob('*/summary.json')):
|
||||
s=json.loads(p.read_text())
|
||||
measurements.append({k:s[k] for k in ('case','total_spikes','accepted_requests','nodes','edges','diagnostic_simulation_seconds','peak_allocated_vram_bytes')})
|
||||
result=dict(status=status,numerical_gate_pass=bool(numerical),fixed_CAL_representation_pass=representation,
|
||||
gates=gates,closed_loop_comparisons=comparisons,component_results=components,repeat_results=repeats,
|
||||
missing_numerical_evidence=missing,calibration=cal,stopped=stops,
|
||||
old_frozen_integrity_and_five_reference_pass=oldpass,measurements=measurements,
|
||||
unchanged_APL_parameters=CFG['apl'],author_LIF_precision='float64',legacy_APL_precision='float32',
|
||||
explicit_hypothesis=CFG['hypothesis'],APL_nonKC_fast_edges_unreplaced=1212,
|
||||
shared_APL_component_oracle_limitation=True,whole_brain_universal_equivalence_claim=False,
|
||||
heldout_generalization_tested=False,formal_AB_tested=False,learning_achieved=False,candidate_generated=False,
|
||||
payload_bytes_before_manifest=budget(),
|
||||
next_gate='Independent seeds/novel-odor representation, then credit specificity; no learning promotion from CAL' if numerical and representation else
|
||||
'Audit first failure in this explicitly declared hypothesis; no gain search, no heldout tuning, no conditioning')
|
||||
save(EXP/'audit/final-status.json',result)
|
||||
lines=['# Phase 4AG: author LIF + unchanged legacy graded APL','',f'Status: `{status}`.','',
|
||||
f'Numerical closed-loop gate: {numerical}; original complete fixed six-CAL gate: {representation}.',
|
||||
'The independent Brian LIF and candidate use independent state but share the APL arithmetic component. Original frozen APL AST replay and a float64 error audit are separately recorded.',
|
||||
'Author LIF uses float64; APL retains float32, tau20ms/gain0.01/normalization1, full original matrices and order. No learning/parameter search.',
|
||||
'The explicitly hypothesized unconditional end-APL write includes refractory KC. Original fast APL mask leaves1,212 non-KC edges unreplaced; this scope limitation was not silently fixed.',
|
||||
f'Old checksums and five frozen reference trials: {oldpass}.',
|
||||
f'Successfully compared cases: {comparisons}.',
|
||||
'Detailed CAL, exact comparisons, component errors, event/request traces and execution exceptions are linked from audit/final-status.json.',
|
||||
'Wall/VRAM here include per-tick CPU synchronization, IPC and hashes. They are diagnostic costs, not runtime-performance benchmarks.',
|
||||
'No heldout dynamic outputs, formal A/B, learning, game, reward, decoder or new candidate.',
|
||||
'Next gate: '+result['next_gate'],'']
|
||||
with (EXP/'REPORT.md').open('x',encoding='utf-8') as f:f.write('\n'.join(lines))
|
||||
check();seal(MODEL);seal(EXP);print(status,flush=True)
|
||||
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,346 @@
|
||||
"""Diagnostic author-float64 LIF plus frozen legacy-float32 APL overlay.
|
||||
|
||||
The sealed Phase 4AE CUDA operators are reused without modification. APL is
|
||||
an explicitly mixed-precision engineering overlay, not an author-model claim.
|
||||
All CUDA/CPU synchronization here is diagnostic; no performance claim is made.
|
||||
"""
|
||||
from functools import lru_cache
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import time
|
||||
import traceback
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from legacy_apl_worker import LegacyAPL, N_NEURONS, file_sha256, array_sha256
|
||||
|
||||
|
||||
SOURCE_ROOT = Path(__file__).resolve().parent
|
||||
AE_MODEL = SOURCE_ROOT.parent / "author-runtime-lab-v1"
|
||||
AF_MODEL = SOURCE_ROOT.parent / "author-fullgraph-lab-v1"
|
||||
COEFFICIENT_NAMES = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def write_json(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as output:
|
||||
json.dump(value, output, indent=2, allow_nan=False)
|
||||
output.write("\n")
|
||||
|
||||
|
||||
def raw_hash(value, dtype):
|
||||
return hashlib.sha256(np.ascontiguousarray(value, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def load_source(path, name):
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(Path(path).read_bytes(), str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def sealed_backend():
|
||||
return load_source(AE_MODEL / "gpu_backend.py", "phase4ag_sealed_ae_backend")
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def queue_function():
|
||||
return load_source(AF_MODEL / "oracle_full.py", "phase4ag_sealed_af_queue").queue_summary
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def load_graph(graph_directory):
|
||||
root = Path(graph_directory)
|
||||
require(root.is_absolute(), "graph_dir must be absolute")
|
||||
graph = {}
|
||||
for name, dtype in (("nodes", np.int32), ("edge_id", np.int64), ("src", np.int32),
|
||||
("dst", np.int32), ("signed_raw", np.int64), ("ptr", np.int64),
|
||||
("weights", np.float64)):
|
||||
value = np.load(root / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
require(value.dtype == dtype and value.ndim == 1, "Graph dtype/rank mismatch: " + name)
|
||||
graph[name] = value
|
||||
nodes, edges = graph["nodes"], graph["edge_id"]
|
||||
n, e = len(nodes), len(edges)
|
||||
require(0 < n <= N_NEURONS and bool(np.all((nodes >= 0) & (nodes < N_NEURONS))) and
|
||||
bool(np.all(np.diff(nodes) > 0)), "Global node IDs must be sorted unique")
|
||||
require(n < 512 or (n == N_NEURONS and np.array_equal(nodes, np.arange(N_NEURONS, dtype=np.int32))),
|
||||
"Only small fixtures or the intact frozen full graph are permitted")
|
||||
require(n != N_NEURONS or e == 25582938, "Full graph edge count changed")
|
||||
for name in ("src", "dst", "signed_raw", "weights"):
|
||||
require(graph[name].shape == (e,), "Graph edge array length mismatch: " + name)
|
||||
ptr = graph["ptr"]
|
||||
require(ptr.shape == (n + 1,) and ptr[0] == 0 and ptr[-1] == e and
|
||||
bool(np.all(np.diff(ptr) >= 0)), "Invalid graph CSR")
|
||||
require(np.array_equal(graph["src"], np.repeat(np.arange(n, dtype=np.int32), np.diff(ptr))),
|
||||
"Source order does not match CSR")
|
||||
# Global edge IDs need not equal local row indices in a fixture.
|
||||
for start in range(0, e, 1 << 20):
|
||||
stop = min(start + (1 << 20), e)
|
||||
require(bool(np.all((graph["dst"][start:stop] >= 0) & (graph["dst"][start:stop] < n))),
|
||||
"Local destination out of range")
|
||||
require(bool(np.isfinite(graph["weights"][start:stop]).all()), "Nonfinite base SI weights")
|
||||
require(np.array_equal(np.sign(graph["weights"][start:stop]), np.sign(graph["signed_raw"][start:stop])),
|
||||
"Base weight sign/zero pattern differs from frozen raw contacts")
|
||||
lo = max(0, start - 1)
|
||||
require(bool(np.all(edges[start:stop] >= 0)) and bool(np.all(np.diff(edges[lo:stop]) > 0)),
|
||||
"Global edge IDs must retain canonical increasing source/edge order")
|
||||
return graph
|
||||
|
||||
|
||||
def load_inputs(case_dir):
|
||||
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
|
||||
for name in ("name", "steps", "dt_ms", "duration_ms", "model", "graph_dir", "apl",
|
||||
"apl_bundle_dir", "coefficients_path", "record_history"):
|
||||
require(name in spec, "Missing case field: " + name)
|
||||
steps = spec["steps"]
|
||||
require(type(steps) is int and 0 < steps <= 1000 and float(spec["dt_ms"]) == .1 and
|
||||
float(spec["duration_ms"]) == steps * .1, "Unexpected steps/dt/duration")
|
||||
require(type(spec["record_history"]) is bool, "record_history must be Boolean")
|
||||
require(spec["apl"] == {"tau_ms": 20, "gain": .01, "normalization": 1}, "Frozen APL parameters changed")
|
||||
for name in ("graph_dir", "apl_bundle_dir", "coefficients_path"):
|
||||
require(Path(spec[name]).is_absolute(), "Input path must be absolute: " + name)
|
||||
model = spec["model"]
|
||||
for name in ("resting_potential_mv", "threshold_mv", "reset_potential_mv", "synaptic_delay_ms",
|
||||
"refractory_ms", "w_syn_mv_per_anatomical_synapse"):
|
||||
require(name in model and math.isfinite(float(model[name])), "Invalid model field: " + name)
|
||||
require(float(model["synaptic_delay_ms"]) == 1.8 and float(model["refractory_ms"]) == 2.2 and
|
||||
float(model["w_syn_mv_per_anatomical_synapse"]) == .275, "Frozen delay/ref/W_syn changed")
|
||||
coeff = json.loads(Path(spec["coefficients_path"]).read_text(encoding="utf-8"))
|
||||
for name in COEFFICIENT_NAMES:
|
||||
require(name in coeff and math.isfinite(float(coeff[name])), "Invalid author coefficient: " + name)
|
||||
require(coeff["threshold"] == float(model["threshold_mv"]) * .001 and
|
||||
coeff["reset"] == float(model["reset_potential_mv"]) * .001, "Coefficient threshold/reset mismatch")
|
||||
graph = load_graph(str(Path(spec["graph_dir"]).resolve()))
|
||||
n = len(graph["nodes"])
|
||||
require(spec["record_history"] == (n < 512), "Fixtures need dense history; full graph must not duplicate dense histories")
|
||||
with np.load(case_dir / "requests.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"tick", "neuron_idx"}, "Request sparse schema changed")
|
||||
ticks, requested = source["tick"].copy(), source["neuron_idx"].copy()
|
||||
require(ticks.dtype == np.int32 and requested.dtype == np.int32 and ticks.ndim == 1 and
|
||||
ticks.shape == requested.shape, "Invalid sparse request dtype/shape")
|
||||
require(bool(np.all((ticks >= 0) & (ticks < steps))), "Request tick outside trial")
|
||||
if len(ticks) > 1:
|
||||
require(bool(np.all((ticks[1:] > ticks[:-1]) |
|
||||
((ticks[1:] == ticks[:-1]) & (requested[1:] > requested[:-1])))),
|
||||
"Requests must be sorted unique by tick/global neuron")
|
||||
local = np.searchsorted(graph["nodes"], requested)
|
||||
require(bool(np.all(local < n)) and np.array_equal(graph["nodes"][local], requested),
|
||||
"Requested neuron absent from graph")
|
||||
initial = {"initial_v_mv": np.full(n, float(model["resting_potential_mv"]), dtype=np.float64),
|
||||
"initial_g_mv": np.zeros(n, dtype=np.float64)}
|
||||
initial_path = case_dir / "arrays.npz"
|
||||
if initial_path.exists():
|
||||
with np.load(initial_path, allow_pickle=False) as source:
|
||||
require(set(source.files).issubset(initial), "Unexpected initial-state array")
|
||||
for name in source.files:
|
||||
value = source[name]
|
||||
require(value.dtype == np.float64 and value.shape == (n,) and bool(np.isfinite(value).all()),
|
||||
"Invalid initial state: " + name)
|
||||
initial[name] = value.copy()
|
||||
return spec, graph, coeff, ticks, requested, local, initial
|
||||
|
||||
|
||||
def new_array(path, dtype, shape):
|
||||
require(not Path(path).exists(), "Refusing existing output: " + str(path))
|
||||
return np.lib.format.open_memmap(path, mode="w+", dtype=dtype, shape=shape)
|
||||
|
||||
|
||||
def run_gpu(case_dir, out_dir, build_root):
|
||||
"""Run fresh candidate state; never consume oracle/candidate APL traces.
|
||||
|
||||
pass_all denotes internal recording/finite integrity only. Independent
|
||||
comparison and the original complete representation gate are separate.
|
||||
"""
|
||||
case_dir, out_dir = Path(case_dir), Path(out_dir)
|
||||
require(not out_dir.exists(), "Refusing existing output directory: " + str(out_dir))
|
||||
out_dir.mkdir(parents=True, exist_ok=False)
|
||||
start, tick, completed = time.perf_counter(), -1, 0
|
||||
maps = {}
|
||||
try:
|
||||
spec, graph, coeff, request_ticks, request_nodes, request_local, initial = load_inputs(case_dir)
|
||||
require(torch.cuda.is_available() and torch.cuda.device_count() == 1 and
|
||||
"4090" in torch.cuda.get_device_name(), "Only the declared single visible RTX4090 is permitted")
|
||||
ext, build = sealed_backend().build_extension(build_root)
|
||||
build_done = time.perf_counter()
|
||||
apl = LegacyAPL(spec["apl_bundle_dir"], spec["dt_ms"])
|
||||
write_json(out_dir / "apl-component.json", apl.metadata)
|
||||
device = apl.device
|
||||
torch.cuda.reset_peak_memory_stats(device)
|
||||
n, e, steps = len(graph["nodes"]), len(graph["edge_id"]), spec["steps"]
|
||||
|
||||
def upload(value, dtype):
|
||||
return torch.from_numpy(np.array(value, copy=True, order="C")).to(device=device, dtype=dtype).contiguous()
|
||||
|
||||
indptr, dst = upload(graph["ptr"], torch.int64), upload(graph["dst"], torch.int32)
|
||||
global_nodes_gpu = upload(graph["nodes"], torch.int64)
|
||||
base_weights = upload(graph["weights"], torch.float64)
|
||||
weights = base_weights.clone()
|
||||
del base_weights
|
||||
local_apl = np.flatnonzero(np.isin(graph["nodes"], apl.apl_globals)).astype(np.int64)
|
||||
apl_out_mask = np.isin(graph["src"], local_apl)
|
||||
if bool(np.any(apl_out_mask)):
|
||||
weights[upload(apl_out_mask, torch.bool)] = 0.0
|
||||
effective_hash = hashlib.sha256()
|
||||
for offset in range(0, e, 1 << 20):
|
||||
effective_hash.update(weights[offset:offset + (1 << 20)].cpu().numpy().astype("<f8", copy=False).tobytes())
|
||||
common_kc, local_kc, full_kc = np.intersect1d(graph["nodes"], apl.kc_globals, assume_unique=True, return_indices=True)
|
||||
kc_local_gpu, kc_full_gpu = upload(local_kc, torch.int64), upload(full_kc, torch.int64)
|
||||
v = upload(initial["initial_v_mv"] * .001, torch.float64)
|
||||
g = upload(initial["initial_g_mv"] * .001, torch.float64)
|
||||
ref = torch.zeros(n, device=device, dtype=torch.int32)
|
||||
counts = torch.zeros(n, device=device, dtype=torch.int32)
|
||||
requests = torch.zeros(n, device=device, dtype=torch.uint8)
|
||||
ring = torch.zeros((19, n), device=device, dtype=torch.uint8)
|
||||
for name, width in (("drive", 2), ("state", 2), ("inhibition", 4064)):
|
||||
maps["apl_" + name] = new_array(out_dir / ("apl-" + name + ".float32.npy"), np.float32, (steps, width))
|
||||
if spec["record_history"]:
|
||||
for name, dtype in (("v", np.float64), ("g", np.float64), ("ref", np.int32)):
|
||||
maps[name] = new_array(out_dir / (name + "-history." + np.dtype(dtype).name + ".npy"), dtype, (steps, n))
|
||||
maps["pre_" + name] = new_array(out_dir / ("preAPL-" + name + "." + np.dtype(dtype).name + ".npy"), dtype, (steps, n))
|
||||
event_steps, event_nodes = [], []
|
||||
accepted_requests_total, due_total, accepted_due_total = 0, 0, 0
|
||||
queue_summary = queue_function()
|
||||
simulation_start = time.perf_counter()
|
||||
with (out_dir / "queue.jsonl").open("x", encoding="utf-8", buffering=1) as queue_output, \
|
||||
(out_dir / "tick-state-hashes.jsonl").open("x", encoding="utf-8", buffering=1) as hash_output, \
|
||||
(out_dir / "requests.csv").open("x", encoding="utf-8", newline="", buffering=1) as request_output:
|
||||
writer = csv.DictWriter(request_output, fieldnames=REQUEST_FIELDS)
|
||||
writer.writeheader()
|
||||
for tick in range(steps):
|
||||
requests.zero_()
|
||||
begin, end = np.searchsorted(request_ticks, (tick, tick + 1))
|
||||
if end > begin:
|
||||
requested_local_gpu = upload(request_local[begin:end], torch.int64)
|
||||
requests[requested_local_gpu] = 1
|
||||
accepted = (ref[requested_local_gpu] <= 1).to(torch.uint8).cpu().numpy()
|
||||
accepted_requests_total += int(accepted.sum())
|
||||
writer.writerows({"tick": tick, "neuron_idx": int(node), "attempted": 1,
|
||||
"accepted": int(ok), "rejected": 1 - int(ok)}
|
||||
for node, ok in zip(request_nodes[begin:end], accepted))
|
||||
spikes = ring[tick % 19]
|
||||
ext.state_step(v, g, ref, requests, spikes, counts,
|
||||
*[float(coeff[name]) for name in COEFFICIENT_NAMES], 22)
|
||||
expanded_global = np.empty(0, dtype=np.int64)
|
||||
accepted_cpu = np.empty(0, dtype=np.uint8)
|
||||
if tick >= 18:
|
||||
due = ring[(tick - 18) % 19]
|
||||
sources = torch.nonzero(due, as_tuple=False).flatten().contiguous()
|
||||
if sources.numel():
|
||||
degrees = indptr[sources + 1] - indptr[sources]
|
||||
offsets = (torch.cumsum(degrees, dim=0) - degrees).contiguous()
|
||||
edge_count = int(degrees.sum().item())
|
||||
expanded = torch.empty(edge_count, device=device, dtype=torch.int64)
|
||||
ext.fill_edges(indptr, sources, offsets, expanded)
|
||||
if edge_count:
|
||||
destination = dst[expanded]
|
||||
# Actual active fanout order, BEFORE stable target grouping.
|
||||
expanded_global = graph["edge_id"][expanded.cpu().numpy()].copy()
|
||||
accepted_cpu = (ref[destination.long()] == 0).to(torch.uint8).cpu().numpy().copy()
|
||||
order = torch.argsort(destination, stable=True)
|
||||
ordered_edges, ordered_targets = expanded[order].contiguous(), destination[order]
|
||||
boundary = torch.ones(edge_count, device=device, dtype=torch.bool)
|
||||
boundary[1:] = ordered_targets[1:] != ordered_targets[:-1]
|
||||
starts = torch.nonzero(boundary, as_tuple=False).flatten().contiguous()
|
||||
targets = ordered_targets[starts].contiguous()
|
||||
ext.apply_arrivals(ordered_edges, starts, targets, weights, g, ref)
|
||||
queue = queue_summary(tick, expanded_global, accepted_cpu)
|
||||
queue_output.write(json.dumps(queue, allow_nan=False) + "\n")
|
||||
due_total += queue["count"]
|
||||
accepted_due_total += queue["accepted_count"]
|
||||
if spec["record_history"]:
|
||||
for name, value in (("v", v), ("g", g), ("ref", ref)):
|
||||
maps["pre_" + name][tick] = value.cpu().numpy()
|
||||
if n == N_NEURONS:
|
||||
full_spikes = spikes
|
||||
else:
|
||||
apl.spikes.zero_()
|
||||
apl.spikes[global_nodes_gpu] = spikes
|
||||
full_spikes = apl.spikes
|
||||
drive, apl_state, h = apl.advance(full_spikes)
|
||||
# Mixed precision contract: widen f32 h EXACTLY, THEN multiply
|
||||
# in f64, THEN subtract in f64. Do not guard refractory KCs.
|
||||
delta_si = h[kc_full_gpu].to(torch.float64) * 0.001
|
||||
g[kc_local_gpu] = g[kc_local_gpu] - delta_si
|
||||
apl_cpu = {"drive": drive.cpu().numpy().copy(), "state": apl_state.cpu().numpy().copy(),
|
||||
"inhibition": h.cpu().numpy().copy()}
|
||||
for name, value in apl_cpu.items():
|
||||
maps["apl_" + name][tick] = value
|
||||
actual = {"v": v.cpu().numpy().copy(), "g": g.cpu().numpy().copy(),
|
||||
"ref": ref.cpu().numpy().copy()}
|
||||
finite = all(bool(np.isfinite(value).all()) for value in list(actual.values()) + list(apl_cpu.values()))
|
||||
row = {"tick": tick, **{name + "_sha256": raw_hash(value, "<i4" if name == "ref" else "<f8")
|
||||
for name, value in actual.items()}}
|
||||
hash_output.write(json.dumps(row, allow_nan=False) + "\n")
|
||||
if spec["record_history"]:
|
||||
for name, value in actual.items():
|
||||
maps[name][tick] = value
|
||||
fired_local = torch.nonzero(spikes, as_tuple=False).flatten().cpu().numpy()
|
||||
if len(fired_local):
|
||||
event_steps.append(np.full(len(fired_local), tick, dtype=np.int32))
|
||||
event_nodes.append(graph["nodes"][fired_local].copy())
|
||||
completed += 1
|
||||
if not finite:
|
||||
with (out_dir / "first-failure-state.npz").open("xb") as target:
|
||||
np.savez_compressed(target, tick=np.array(tick, dtype=np.int32), **actual,
|
||||
**{"apl_" + name: value for name, value in apl_cpu.items()})
|
||||
require(False, "Nonfinite state; first failure saved")
|
||||
torch.cuda.synchronize(device)
|
||||
|
||||
def joined(values):
|
||||
return np.concatenate(values).astype(np.int32, copy=False) if values else np.empty(0, dtype=np.int32)
|
||||
|
||||
state = {"step": joined(event_steps), "neuron_idx": joined(event_nodes), "counts": counts.cpu().numpy(),
|
||||
"v": v.cpu().numpy(), "g": g.cpu().numpy(), "ref": ref.cpu().numpy()}
|
||||
with (out_dir / "state.npz").open("xb") as target:
|
||||
np.savez_compressed(target, **state)
|
||||
event_locals = np.searchsorted(graph["nodes"], state["neuron_idx"])
|
||||
counts_exact = np.array_equal(np.bincount(event_locals, minlength=n), state["counts"])
|
||||
require(counts_exact and completed == steps, "Sparse events/counts/completed ticks mismatch")
|
||||
for value in maps.values():
|
||||
value.flush()
|
||||
summary = {"case": spec["name"], "complete": True, "pass_all": True,
|
||||
"pass_all_scope": "internal finite/recording/event-count integrity only; independent equivalence not claimed",
|
||||
"steps": steps, "completed_ticks": completed, "nodes": n, "edges": e, "precision": "float64",
|
||||
"apl_precision": "float32", "state_units": "SI volts", "total_spikes": len(state["step"]),
|
||||
"requested": len(request_nodes), "accepted_requests": accepted_requests_total,
|
||||
"rejected_requests": len(request_nodes) - accepted_requests_total,
|
||||
"arriving_edges": due_total, "accepted_arriving_edges": accepted_due_total,
|
||||
"counts_match_sparse_events": bool(counts_exact), "all_finite": True,
|
||||
"record_history": spec["record_history"], "apl_fast_edges_zeroed": int(apl_out_mask.sum()),
|
||||
"kc_targets_present": len(common_kc), "effective_weights_raw_sha256": effective_hash.hexdigest(),
|
||||
"apl_component": apl.metadata, "apl_schedule": "end, after threshold/reset and guarded arrivals, unconditional all KC",
|
||||
"apl_si_conversion": "h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction",
|
||||
"build": build, "sealed_ae_backend_sha256": file_sha256(AE_MODEL / "gpu_backend.py"),
|
||||
"shared_apl_component_oracle_limitation": True, "oracle_validation_claim": False,
|
||||
"learning": False, "performance_runtime_claim": False, "cpu_sync_each_tick": True,
|
||||
"build_or_cache_seconds": build_done - start,
|
||||
"diagnostic_simulation_seconds": time.perf_counter() - simulation_start,
|
||||
"wall_seconds": time.perf_counter() - start,
|
||||
"peak_allocated_vram_bytes": int(torch.cuda.max_memory_allocated(device)),
|
||||
"peak_reserved_vram_bytes": int(torch.cuda.max_memory_reserved(device)),
|
||||
"input_sha256": {"spec.json": file_sha256(case_dir / "spec.json"),
|
||||
"requests.npz": file_sha256(case_dir / "requests.npz"),
|
||||
"base_weights.npy": file_sha256(Path(spec["graph_dir"]) / "weights.npy"),
|
||||
"coefficients.json": file_sha256(spec["coefficients_path"])}}
|
||||
if (case_dir / "arrays.npz").exists():
|
||||
summary["input_sha256"]["arrays.npz"] = file_sha256(case_dir / "arrays.npz")
|
||||
write_json(out_dir / "summary.json", summary)
|
||||
return summary
|
||||
except Exception as error:
|
||||
for value in maps.values():
|
||||
value.flush()
|
||||
write_json(out_dir / "error.json", {"complete": False, "pass_all": False,
|
||||
"tick_in_progress": tick, "completed_ticks": completed,
|
||||
"type": type(error).__name__, "message": str(error), "traceback": traceback.format_exc()})
|
||||
raise
|
||||
@@ -0,0 +1,175 @@
|
||||
"""Frozen eager Torch float32 APL component and binary oracle sidecar.
|
||||
|
||||
This is the Phase 4P operator sequence, not an author-neuron implementation.
|
||||
No precision/determinism flags are changed here. The full matrix dimensions
|
||||
are retained even when the caller simulates a small neuronal fixture.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
PROTOCOL = "phase4ag-apl-v1"
|
||||
N_NEURONS, N_APL, N_KC = 166700, 2, 4064
|
||||
BUNDLE_FILES = ("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")
|
||||
FRAME = struct.Struct("<qQ")
|
||||
PAYLOAD_BYTES = (N_APL + N_APL + N_KC) * 4
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def array_sha256(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def environment_metadata():
|
||||
device = torch.cuda.current_device()
|
||||
return {"torch": str(torch.__version__), "numpy": str(np.__version__),
|
||||
"cuda": torch.version.cuda, "cudnn": torch.backends.cudnn.version(),
|
||||
"device_name": torch.cuda.get_device_name(device), "device_index": device,
|
||||
"device_capability": list(torch.cuda.get_device_capability(device)),
|
||||
"deterministic_algorithms": torch.are_deterministic_algorithms_enabled(),
|
||||
"cudnn_deterministic": torch.backends.cudnn.deterministic,
|
||||
"cudnn_benchmark": torch.backends.cudnn.benchmark,
|
||||
"cuda_matmul_allow_tf32": torch.backends.cuda.matmul.allow_tf32,
|
||||
"cudnn_allow_tf32": torch.backends.cudnn.allow_tf32,
|
||||
"float32_matmul_precision": torch.get_float32_matmul_precision(),
|
||||
"CUBLAS_WORKSPACE_CONFIG": os.environ.get("CUBLAS_WORKSPACE_CONFIG"),
|
||||
"precision_configuration_changed_by_component": False}
|
||||
|
||||
|
||||
class LegacyAPL:
|
||||
"""Independent zero-initialized APL state with original full-width matrices."""
|
||||
|
||||
def __init__(self, bundle_dir, dt_ms):
|
||||
require(float(dt_ms) == 0.1, "Only frozen dt_ms=0.1 is allowed")
|
||||
require(torch.cuda.is_available(), "CUDA is required for the legacy Torch component")
|
||||
self.bundle_dir = Path(bundle_dir)
|
||||
expected = {"input_matrix.npy": (np.float32, (N_APL, N_NEURONS)),
|
||||
"output_matrix.npy": (np.float32, (N_APL, N_KC)),
|
||||
"kc_globals.npy": (np.int32, (N_KC,)),
|
||||
"apl_globals.npy": (np.int32, (N_APL,))}
|
||||
arrays = {}
|
||||
for name, (dtype, shape) in expected.items():
|
||||
value = np.load(self.bundle_dir / name, allow_pickle=False)
|
||||
require(value.dtype == dtype and value.shape == shape and value.flags.c_contiguous,
|
||||
"APL bundle dtype/shape/layout mismatch: " + name)
|
||||
require(bool(np.isfinite(value).all()), "Nonfinite APL bundle: " + name)
|
||||
arrays[name] = value
|
||||
for name in ("kc_globals.npy", "apl_globals.npy"):
|
||||
ids = arrays[name]
|
||||
require(bool(np.all((ids >= 0) & (ids < N_NEURONS))) and
|
||||
bool(np.all(np.diff(ids) > 0)), "APL/KC globals must be sorted unique: " + name)
|
||||
require(not np.intersect1d(arrays["kc_globals.npy"], arrays["apl_globals.npy"]).size,
|
||||
"APL and KC identities overlap")
|
||||
require(bool(np.all(arrays["input_matrix.npy"] >= 0)) and
|
||||
bool(np.all(arrays["output_matrix.npy"] >= 0)), "APL raw-contact matrices must be nonnegative")
|
||||
self.device = torch.device("cuda", torch.cuda.current_device())
|
||||
self.input_gpu = torch.from_numpy(arrays["input_matrix.npy"].copy()).to(self.device)
|
||||
self.output_gpu = torch.from_numpy(arrays["output_matrix.npy"].copy()).to(self.device)
|
||||
self.kc_globals = arrays["kc_globals.npy"].copy()
|
||||
self.apl_globals = arrays["apl_globals.npy"].copy()
|
||||
self.state = torch.zeros(N_APL, dtype=torch.float32, device=self.device)
|
||||
self.spikes = torch.zeros(N_NEURONS, dtype=torch.uint8, device=self.device)
|
||||
self.decay = np.float32(math.exp(-float(dt_ms) / 20.0))
|
||||
self.gain = np.float32(0.01)
|
||||
self.metadata = {"ok": True, "protocol": PROTOCOL, "n_neurons": N_NEURONS,
|
||||
"n_apl": N_APL, "n_kc": N_KC, "dt_ms": float(dt_ms),
|
||||
"files_sha256": {name: file_sha256(self.bundle_dir / name) for name in BUNDLE_FILES},
|
||||
"array_sha256": {name: array_sha256(value) for name, value in arrays.items()},
|
||||
"decay_f32_bits": f"{int(self.decay.view(np.uint32)):08x}",
|
||||
"gain_f32_bits": f"{int(self.gain.view(np.uint32)):08x}",
|
||||
"environment": environment_metadata(),
|
||||
"operator_sequence": "state.mul_(decay32); drive=mv(I32,spikes32); state.add_(drive); h=matmul(state,O32)*gain32",
|
||||
"h_units": "legacy mV", "initial_state": "float32 zeros"}
|
||||
|
||||
@torch.no_grad()
|
||||
def advance(self, spikes_u8):
|
||||
require(spikes_u8.device == self.device and spikes_u8.dtype == torch.uint8 and
|
||||
tuple(spikes_u8.shape) == (N_NEURONS,) and spikes_u8.is_contiguous(),
|
||||
"APL expects full-width contiguous CUDA uint8 spikes")
|
||||
# Preserve separate eager operations, including Python evaluation order
|
||||
# in the frozen state.mul_(...).add_(torch.mv(...)) expression.
|
||||
self.state.mul_(float(self.decay))
|
||||
drive = torch.mv(self.input_gpu, spikes_u8.to(torch.float32))
|
||||
self.state.add_(drive)
|
||||
h = torch.matmul(self.state, self.output_gpu) * self.gain
|
||||
return drive, self.state, h
|
||||
|
||||
def advance_global_fired(self, fired):
|
||||
require(fired.dtype == np.int32 and fired.ndim == 1, "Global spikes must be int32 vector")
|
||||
require(bool(np.all((fired >= 0) & (fired < N_NEURONS))) and
|
||||
bool(np.all(np.diff(fired) > 0)), "Global spikes must be sorted unique and in range")
|
||||
self.spikes.zero_()
|
||||
if fired.size:
|
||||
indices = torch.from_numpy(fired.copy()).to(device=self.device, dtype=torch.int64)
|
||||
self.spikes[indices] = 1
|
||||
return self.advance(self.spikes)
|
||||
|
||||
|
||||
def read_exact(stream, count, boundary_eof=False):
|
||||
result = bytearray()
|
||||
while len(result) < count:
|
||||
block = stream.read(count - len(result))
|
||||
if not block:
|
||||
if boundary_eof and not result:
|
||||
return None
|
||||
raise EOFError("Truncated APL request frame")
|
||||
result.extend(block)
|
||||
return bytes(result)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--bundle-dir", required=True)
|
||||
parser.add_argument("--dt-ms", required=True, type=float)
|
||||
args = parser.parse_args()
|
||||
# stdout is exclusively the handshake line and binary response frames.
|
||||
output, source = sys.stdout.buffer, sys.stdin.buffer
|
||||
component = LegacyAPL(args.bundle_dir, args.dt_ms)
|
||||
output.write((json.dumps(component.metadata, allow_nan=False) + "\n").encode("utf-8"))
|
||||
output.flush()
|
||||
expected_tick = 0
|
||||
while True:
|
||||
header = read_exact(source, FRAME.size, boundary_eof=True)
|
||||
if header is None:
|
||||
return
|
||||
tick, count = FRAME.unpack(header)
|
||||
require(tick == expected_tick and count <= N_NEURONS, "Invalid APL request tick/count")
|
||||
fired = np.frombuffer(read_exact(source, count * 4), dtype="<i4").copy()
|
||||
drive, state, h = component.advance_global_fired(fired)
|
||||
arrays = [value.detach().cpu().numpy().astype("<f4", copy=False) for value in (drive, state, h)]
|
||||
require(all(bool(np.isfinite(value).all()) for value in arrays), "Nonfinite APL output")
|
||||
output.write(FRAME.pack(tick, PAYLOAD_BYTES))
|
||||
for value in arrays:
|
||||
output.write(value.tobytes())
|
||||
output.flush()
|
||||
expected_tick += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"root": "/data/digital-drosophila/models/male-cns-lif/author-apl-lab-v1",
|
||||
"files": [
|
||||
{
|
||||
"path": "PROTOCOL.md",
|
||||
"bytes": 5033,
|
||||
"sha256": "c14c2d3c35e2b404c516befa66370723fba075516540214ae46e4104ff87e813"
|
||||
},
|
||||
{
|
||||
"path": "calibration.py",
|
||||
"bytes": 25414,
|
||||
"sha256": "edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c"
|
||||
},
|
||||
{
|
||||
"path": "compare.py",
|
||||
"bytes": 33632,
|
||||
"sha256": "6e1616b086f667fa4e4b5087f500f8080281977ca9922e7b055acfddd2003bd5"
|
||||
},
|
||||
{
|
||||
"path": "config.json",
|
||||
"bytes": 1845,
|
||||
"sha256": "2aa6b4d777ff698018e1a6eaf571215208b8c874a4c848a6d632e2bd778c90e3"
|
||||
},
|
||||
{
|
||||
"path": "context.py",
|
||||
"bytes": 2038,
|
||||
"sha256": "b8b1231d958a3ec935ec1d5a36e08cbd9bcd1127f4d4bb243a9051884a0e7a16"
|
||||
},
|
||||
{
|
||||
"path": "finalize.py",
|
||||
"bytes": 6747,
|
||||
"sha256": "b6e3c67e668a971b544df32d76435ab5e537061b9fc2e8d9dedb5d2a726f5782"
|
||||
},
|
||||
{
|
||||
"path": "gpu_apl.py",
|
||||
"bytes": 21206,
|
||||
"sha256": "69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a"
|
||||
},
|
||||
{
|
||||
"path": "legacy_apl_worker.py",
|
||||
"bytes": 8158,
|
||||
"sha256": "21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291"
|
||||
},
|
||||
{
|
||||
"path": "oracle_apl.py",
|
||||
"bytes": 46017,
|
||||
"sha256": "9b7f3e0b59f2117b94b41a6f542f37e6f7950138d324c905e8e0ac6cb7d17151"
|
||||
},
|
||||
{
|
||||
"path": "prepare.py",
|
||||
"bytes": 13633,
|
||||
"sha256": "197ccc6aae70566c4db4d4de17fa032e853b9bd9a04262905d6c71e12a930b6f"
|
||||
},
|
||||
{
|
||||
"path": "run_case.py",
|
||||
"bytes": 2116,
|
||||
"sha256": "afbe6d5a37c29bda66d38c470093584130d1852e67881c681662aec3c76b33c7"
|
||||
},
|
||||
{
|
||||
"path": "run_suite.py",
|
||||
"bytes": 3700,
|
||||
"sha256": "84c681030b0032119b5392d0c3f3c136a9c6b0d133eb548c40791a10bbbb8b63"
|
||||
},
|
||||
{
|
||||
"path": "test_calibration.py",
|
||||
"bytes": 8127,
|
||||
"sha256": "bab3136eecbc5bb09a985d715844ee845cb72b07930ce376bfb39be33bd1845a"
|
||||
},
|
||||
{
|
||||
"path": "test_protocol.py",
|
||||
"bytes": 11764,
|
||||
"sha256": "1a233f47943e01bbd845ac1dacdb1eec3f88c270c65a5f75bce1cf679b5de097"
|
||||
},
|
||||
{
|
||||
"path": "validate_components.py",
|
||||
"bytes": 24824,
|
||||
"sha256": "5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3"
|
||||
},
|
||||
{
|
||||
"path": "verify_frozen.py",
|
||||
"bytes": 2160,
|
||||
"sha256": "0d0e72b2f1348f6648124c8c34f75094f76fc5e1a281fbcca130a5fad2f35293"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
"""Phase 4AG shared-APL-component closed-loop Brian oracle.
|
||||
|
||||
Brian has no Torch dependency. A separate, pinned science-Python process
|
||||
executes the unchanged legacy float32 APL operations from Brian's own spikes.
|
||||
This is independent LIF/scheduling validation with a shared APL numerical
|
||||
component, NOT an all-components-independent CPU oracle.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import queue
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
BLOCK_BYTES = 8 << 20
|
||||
PROTOCOL = "phase4ag-apl-v1"
|
||||
AUTHOR_PATH = Path("/data/digital-drosophila/experiments/phase4ad-author-semantics/external-resources/model.py")
|
||||
AUTHOR_SHA256 = "fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e"
|
||||
AE_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-runtime-lab-v1/oracle_stress.py")
|
||||
AE_SHA256 = "32eb1c42383068ce52701d2139a376ce155ad7072cde019949f8ca2d55492409"
|
||||
AF_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-fullgraph-lab-v1/oracle_full.py")
|
||||
AF_SHA256 = "d72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961"
|
||||
EVENT_DTYPE = np.dtype([("step", "<i4"), ("neuron_idx", "<i4")])
|
||||
BUNDLE_FILES = ("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")
|
||||
_PROCESS_USED = False
|
||||
|
||||
|
||||
def _check(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def _save(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as handle:
|
||||
json.dump(value, handle, indent=2, sort_keys=True, allow_nan=False)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def _digest(path):
|
||||
result = hashlib.sha256()
|
||||
with Path(path).open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(BLOCK_BYTES), b""):
|
||||
result.update(block)
|
||||
return result.hexdigest()
|
||||
|
||||
|
||||
def _array_hash(array, dtype):
|
||||
return hashlib.sha256(np.ascontiguousarray(array, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def _load_checked(path, expected, name):
|
||||
data = Path(path).read_bytes()
|
||||
_check(hashlib.sha256(data).hexdigest() == expected, f"Pinned source hash mismatch: {path}")
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(data, str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def _new_memmap(path, dtype, shape):
|
||||
_check(not Path(path).exists(), f"Refusing to overwrite output: {path}")
|
||||
return np.lib.format.open_memmap(path, mode="w+", dtype=dtype, shape=shape)
|
||||
|
||||
|
||||
def _budget(root, limit, reserve=0):
|
||||
total = 0
|
||||
for directory, _, filenames in os.walk(root, followlinks=False):
|
||||
for filename in filenames:
|
||||
total += (Path(directory) / filename).stat(follow_symlinks=False).st_size
|
||||
_check(total + int(reserve) <= limit,
|
||||
f"Output budget exceeded: {total}+{reserve}>{limit}; inputs and duration were not adjusted")
|
||||
return total
|
||||
|
||||
|
||||
class _PipeReader:
|
||||
"""One bounded reader thread, with timeout/EOF checks for every frame."""
|
||||
|
||||
def __init__(self, pipe, timeout):
|
||||
self.pipe, self.timeout = pipe, float(timeout)
|
||||
self.chunks = queue.Queue(maxsize=8)
|
||||
self.buffer = bytearray()
|
||||
self.thread = threading.Thread(target=self._pump, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def _pump(self):
|
||||
try:
|
||||
while True:
|
||||
chunk = os.read(self.pipe.fileno(), 65536)
|
||||
self.chunks.put(chunk)
|
||||
if not chunk:
|
||||
return
|
||||
except BaseException as error:
|
||||
self.chunks.put(error)
|
||||
|
||||
def _fill(self, deadline):
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError("APL sidecar response timed out")
|
||||
try:
|
||||
chunk = self.chunks.get(timeout=remaining)
|
||||
except queue.Empty as error:
|
||||
raise TimeoutError("APL sidecar response timed out") from error
|
||||
if isinstance(chunk, BaseException):
|
||||
raise RuntimeError("APL sidecar stdout reader failed") from chunk
|
||||
if not chunk:
|
||||
raise EOFError("APL sidecar ended before a complete response")
|
||||
self.buffer.extend(chunk)
|
||||
|
||||
def read(self, size):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
while len(self.buffer) < size:
|
||||
self._fill(deadline)
|
||||
result = bytes(self.buffer[:size])
|
||||
del self.buffer[:size]
|
||||
return result
|
||||
|
||||
def line(self):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
while b"\n" not in self.buffer:
|
||||
_check(len(self.buffer) < 1 << 20, "APL sidecar handshake exceeds 1 MiB")
|
||||
self._fill(deadline)
|
||||
end = self.buffer.index(b"\n") + 1
|
||||
result = bytes(self.buffer[:end])
|
||||
del self.buffer[:end]
|
||||
return result
|
||||
|
||||
|
||||
class _APLWorker:
|
||||
def __init__(self, python, script, bundle_dir, stderr, timeout=180):
|
||||
self.command = [str(python), "-B", str(script), "--bundle-dir", str(bundle_dir), "--dt-ms", "0.1"]
|
||||
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=stderr, bufsize=0, shell=False)
|
||||
self.reader = _PipeReader(self.process.stdout, timeout)
|
||||
self.tick = 0
|
||||
try:
|
||||
self.hello = json.loads(self.reader.line().decode("utf-8"))
|
||||
_check(self.hello.get("ok") is True, "APL sidecar did not acknowledge initialization")
|
||||
_check(self.hello.get("protocol") == PROTOCOL, "Unexpected APL sidecar protocol")
|
||||
for key, value in (("n_neurons", 166700), ("n_apl", 2), ("n_kc", 4064)):
|
||||
_check(self.hello.get(key) == value, f"APL sidecar changed {key}")
|
||||
_check(self.hello.get("dt_ms") == .1, "APL sidecar changed dt")
|
||||
expected_decay = f"{int(np.float32(math.exp(-.1 / 20.)).view(np.uint32)):08x}"
|
||||
expected_gain = f"{int(np.float32(.01).view(np.uint32)):08x}"
|
||||
_check(self.hello.get("decay_f32_bits") == expected_decay and self.hello.get("gain_f32_bits") == expected_gain,
|
||||
"APL sidecar decay/gain differs from the frozen float32 scalars")
|
||||
_check(self.hello.get("files_sha256") == {name: _digest(bundle_dir / name) for name in BUNDLE_FILES},
|
||||
"APL sidecar bundle hashes differ from the independently read bundle")
|
||||
except BaseException:
|
||||
self.abort()
|
||||
raise
|
||||
|
||||
def step(self, tick, global_spikes):
|
||||
_check(tick == self.tick, "APL sidecar request ticks must be consecutive")
|
||||
nodes = np.asarray(global_spikes)
|
||||
_check(nodes.ndim == 1 and nodes.dtype == np.dtype("int32"), "APL input spikes must be int32 globals")
|
||||
_check(not len(nodes) or (nodes[0] >= 0 and nodes[-1] < 166700 and np.all(np.diff(nodes) > 0)),
|
||||
"APL input spikes must be sorted, unique, and in the frozen global range")
|
||||
packet = struct.pack("<qQ", int(tick), len(nodes)) + np.ascontiguousarray(nodes, dtype="<i4").tobytes()
|
||||
view = memoryview(packet)
|
||||
while view:
|
||||
written = self.process.stdin.write(view)
|
||||
if not written:
|
||||
raise BrokenPipeError("APL sidecar stdin failed")
|
||||
view = view[written:]
|
||||
self.process.stdin.flush()
|
||||
response_tick, byte_count = struct.unpack("<qQ", self.reader.read(16))
|
||||
_check(response_tick == tick and byte_count == (2 + 2 + 4064) * 4,
|
||||
"APL sidecar frame tick/length mismatch")
|
||||
values = np.frombuffer(self.reader.read(byte_count), dtype="<f4")
|
||||
_check(np.isfinite(values).all() and np.all(values >= 0), "APL sidecar returned invalid nonnegative f32 states")
|
||||
self.tick += 1
|
||||
return values[:2], values[2:4], values[4:]
|
||||
|
||||
def finish(self, steps):
|
||||
_check(self.tick == steps, "APL sidecar did not process every tick")
|
||||
self.process.stdin.close()
|
||||
status = self.process.wait(timeout=30)
|
||||
_check(status == 0, f"APL sidecar exited unsuccessfully: {status}")
|
||||
self.reader.thread.join(timeout=5)
|
||||
_check(not self.reader.thread.is_alive(), "APL stdout did not reach EOF")
|
||||
extra = bytes(self.reader.buffer)
|
||||
while not self.reader.chunks.empty():
|
||||
chunk = self.reader.chunks.get_nowait()
|
||||
_check(not isinstance(chunk, BaseException), "APL stdout reader failed after the last frame")
|
||||
extra += chunk
|
||||
_check(not extra, "APL sidecar emitted unrequested trailing stdout bytes")
|
||||
self.process.stdout.close()
|
||||
return status
|
||||
|
||||
def abort(self):
|
||||
if self.process.poll() is None:
|
||||
self.process.terminate()
|
||||
try:
|
||||
self.process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.process.kill()
|
||||
self.process.wait(timeout=5)
|
||||
for handle in (self.process.stdin, self.process.stdout):
|
||||
if handle is not None and not handle.closed:
|
||||
handle.close()
|
||||
|
||||
|
||||
def _graph_inputs(directory):
|
||||
arrays = {}
|
||||
definitions = {"nodes": "int32", "edge_id": "int64", "src": "int32",
|
||||
"dst": "int32", "signed_raw": "int64", "ptr": "int64", "weights": "float64"}
|
||||
for name, dtype in definitions.items():
|
||||
value = np.load(directory / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
_check(value.ndim == 1 and value.dtype == np.dtype(dtype), f"Invalid graph array {name}")
|
||||
arrays[name] = value
|
||||
nodes, edge_ids = arrays["nodes"], arrays["edge_id"]
|
||||
n, edges = len(nodes), len(edge_ids)
|
||||
_check(0 < n <= 166700 and edges > 0, "A case needs real global nodes and explicit nonempty connectivity")
|
||||
_check(nodes[0] >= 0 and nodes[-1] < 166700 and np.all(np.diff(nodes) > 0), "Invalid global node order")
|
||||
for name in ("src", "dst", "signed_raw", "weights"):
|
||||
_check(arrays[name].shape == (edges,), f"Graph array {name} has the wrong shape")
|
||||
pointer = arrays["ptr"]
|
||||
_check(pointer.shape == (n + 1,) and pointer[0] == 0 and pointer[-1] == edges and
|
||||
np.all(np.diff(pointer) >= 0), "Invalid graph CSR pointer")
|
||||
source_counts = np.zeros(n, np.int64)
|
||||
identity_edges = True
|
||||
previous_source, previous_edge = -1, -1
|
||||
width = BLOCK_BYTES // 8
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
source, target, ids = arrays["src"][begin:stop], arrays["dst"][begin:stop], edge_ids[begin:stop]
|
||||
_check(np.all((source >= 0) & (source < n)) and np.all((target >= 0) & (target < n)),
|
||||
"Graph edge endpoint is outside the local node array")
|
||||
_check(np.all(ids >= 0), "Graph edge IDs must be nonnegative")
|
||||
_check(source[0] > previous_source or (source[0] == previous_source and ids[0] > previous_edge),
|
||||
"Graph edge blocks are not in canonical source/edge order")
|
||||
_check(np.all((source[1:] > source[:-1]) |
|
||||
((source[1:] == source[:-1]) & (ids[1:] > ids[:-1]))),
|
||||
"Graph edges are not in canonical source/edge order")
|
||||
previous_source, previous_edge = int(source[-1]), int(ids[-1])
|
||||
identity_edges &= np.array_equal(ids, np.arange(begin, stop, dtype=np.int64))
|
||||
source_counts += np.bincount(source, minlength=n)
|
||||
_check(np.isfinite(arrays["weights"][begin:stop]).all(), "Nonfinite graph weights")
|
||||
_check(np.array_equal(source_counts, np.diff(pointer)), "CSR pointer and source rows disagree")
|
||||
if not identity_edges:
|
||||
_check(len(np.unique(edge_ids)) == edges, "Original edge IDs must be unique")
|
||||
for name in ("completeness.csv", "connectivity.parquet"):
|
||||
_check((directory / name).is_file(), f"Missing unchanged author input: {name}")
|
||||
if n == 166700:
|
||||
_check(edges == 25582938 and identity_edges and np.array_equal(nodes, np.arange(n, dtype=np.int32)),
|
||||
"A full probe must use the unchanged complete canonical graph")
|
||||
return arrays, identity_edges
|
||||
|
||||
|
||||
def _requests(case_dir, nodes, steps):
|
||||
with np.load(case_dir / "requests.npz", allow_pickle=False) as archive:
|
||||
_check(set(archive.files) == {"tick", "neuron_idx"}, "Unexpected sparse request schema")
|
||||
ticks, requested = archive["tick"].copy(), archive["neuron_idx"].copy()
|
||||
_check(ticks.dtype == requested.dtype == np.dtype("int32") and ticks.ndim == requested.ndim == 1 and
|
||||
ticks.shape == requested.shape, "Requests must be equal-length int32 vectors")
|
||||
_check(np.all((ticks >= 0) & (ticks < steps)), "Request time outside the case")
|
||||
_check(not len(ticks) or np.all((ticks[1:] > ticks[:-1]) |
|
||||
((ticks[1:] == ticks[:-1]) & (requested[1:] > requested[:-1]))),
|
||||
"Requests must be sorted and unique by tick/global node")
|
||||
local = np.searchsorted(nodes, requested)
|
||||
_check(np.all(local < len(nodes)) and np.array_equal(nodes[local], requested),
|
||||
"Request references a global neuron absent from this case")
|
||||
counts = np.bincount(ticks, minlength=steps).astype(np.int32)
|
||||
pointer = np.concatenate((np.zeros(1, np.int64), np.cumsum(counts, dtype=np.int64)))
|
||||
return requested, local.astype(np.int32), pointer, counts
|
||||
|
||||
|
||||
class _QueueProxy:
|
||||
def __init__(self, original, get_tick, available, arrays, identity_edges,
|
||||
due_counts, spike_ring, delay_steps, handle, records, summarize):
|
||||
self._original_queue, self._get_tick = original, get_tick
|
||||
self._available, self._arrays, self._identity_edges = available, arrays, identity_edges
|
||||
self._due_counts, self._spike_ring, self._delay_steps = due_counts, spike_ring, delay_steps
|
||||
self._handle, self._records, self._summarize = handle, records, summarize
|
||||
|
||||
def peek(self, *args, **kwargs):
|
||||
original = self._original_queue.peek(*args, **kwargs)
|
||||
local_edges = np.asarray(original)
|
||||
tick = self._get_tick()
|
||||
_check(tick == len(self._records["rows"]), "Queue peek ticks must be consecutive")
|
||||
_check(local_edges.ndim == 1 and np.all((local_edges >= 0) & (local_edges < len(self._arrays["edge_id"]))),
|
||||
"Original queue contains an invalid local edge index")
|
||||
_check(len(local_edges) == self._due_counts[tick % len(self._due_counts)],
|
||||
"Queue arrivals differ from the fixed-delay outgoing degree of actual spikes")
|
||||
fired_then = np.zeros(len(self._arrays["nodes"]), dtype=bool)
|
||||
if tick >= self._delay_steps:
|
||||
fired_then[self._spike_ring[(tick - self._delay_steps) % len(self._spike_ring)]] = True
|
||||
|
||||
def accepted(begin, stop):
|
||||
part = local_edges[begin:stop]
|
||||
_check(np.all(fired_then[self._arrays["src"][part]]), "Arrival lacks a presynaptic spike at author delay")
|
||||
return self._available[self._arrays["dst"][part]]
|
||||
|
||||
# Only the observed ID view changes; the queue is returned verbatim.
|
||||
edge_ids = local_edges if self._identity_edges else self._arrays["edge_id"][local_edges]
|
||||
summary = self._summarize(tick, edge_ids, accepted)
|
||||
self._handle.write(json.dumps(summary, sort_keys=True) + "\n")
|
||||
self._records["rows"].append(summary)
|
||||
self._records["peek_calls"] += 1
|
||||
return original
|
||||
|
||||
def advance(self, *args, **kwargs):
|
||||
self._records["advance_calls"] += 1
|
||||
return self._original_queue.advance(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._original_queue, name)
|
||||
|
||||
|
||||
def run_case(case_dir: Path, out_dir: Path) -> dict:
|
||||
"""Run one continuous case, with exclusive outputs and no candidate trace input.
|
||||
|
||||
Graph inputs are the AF array/author-table schema plus original weights.npy.
|
||||
requests.npz contains sparse tick/neuron_idx int32 vectors. Optional
|
||||
arrays.npz contains initial_v_mv/initial_g_mv float64 vectors only.
|
||||
Full probes write state hashes, sparse events and APL traces, not dense
|
||||
whole-brain histories. Each case requires a fresh process.
|
||||
"""
|
||||
global _PROCESS_USED
|
||||
_check(not _PROCESS_USED, "Every oracle case requires a fresh process")
|
||||
_PROCESS_USED = True
|
||||
case_dir, out_dir = Path(case_dir).resolve(strict=True), Path(out_dir).resolve()
|
||||
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
|
||||
graph_dir = Path(spec["graph_dir"]).resolve(strict=True)
|
||||
bundle_dir = Path(spec["apl_bundle_dir"]).resolve(strict=True)
|
||||
worker_path = Path(spec["legacy_apl_worker"]).resolve(strict=True)
|
||||
science_python = Path(spec["science_python"]).resolve(strict=True)
|
||||
author_path = Path(spec.get("author_path", AUTHOR_PATH)).resolve(strict=True)
|
||||
ae_path = Path(spec.get("ae_oracle_path", AE_PATH)).resolve(strict=True)
|
||||
af_path = Path(spec.get("af_oracle_path", AF_PATH)).resolve(strict=True)
|
||||
coefficients_path = Path(spec["coefficients_path"]).resolve(strict=True)
|
||||
steps = int(spec["steps"])
|
||||
_check(steps == spec["steps"] and 0 < steps <= np.iinfo(np.int32).max, "Invalid number of steps")
|
||||
_check(float(spec["dt_ms"]) == .1 and np.isclose(float(spec["duration_ms"]), steps * .1, rtol=0, atol=1e-12),
|
||||
"The frozen APL protocol requires dt_ms=0.1 and a matching duration")
|
||||
_check(spec.get("precision", "float64") == "float64", "Author state must be float64")
|
||||
_check(spec.get("training", False) is False and spec.get("poi_used", False) is False,
|
||||
"Training and original poi are outside this gate")
|
||||
_check(spec["apl"] == {"tau_ms": 20, "gain": .01, "normalization": 1}, "Frozen APL parameters changed")
|
||||
record_history = spec["record_history"]
|
||||
_check(isinstance(record_history, bool), "record_history must be a boolean")
|
||||
budget_root = Path(spec.get("budget_root", out_dir.parent)).resolve()
|
||||
budget_limit = int(spec.get("output_budget_bytes", 15 * 1024 ** 3))
|
||||
_check(budget_limit > 0 and out_dir.is_relative_to(budget_root), "Invalid output budget/root")
|
||||
out_dir.mkdir(parents=True, exist_ok=False)
|
||||
mapped_files = []
|
||||
saved_preferences, old_dt, worker = None, None, None
|
||||
last_complete_tick = -1
|
||||
try:
|
||||
arrays, identity_edges = _graph_inputs(graph_dir)
|
||||
nodes, n, edges = arrays["nodes"], len(arrays["nodes"]), len(arrays["edge_id"])
|
||||
_check(n < 166700 or not record_history, "Full probes must not save dense V/g/ref histories")
|
||||
bundle = {name: np.load(bundle_dir / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
for name in ("input_matrix", "output_matrix", "kc_globals", "apl_globals")}
|
||||
for name, shape in (("input_matrix", (2, 166700)), ("output_matrix", (2, 4064))):
|
||||
value = bundle[name]
|
||||
_check(value.dtype == np.dtype("float32") and value.shape == shape and value.flags.c_contiguous,
|
||||
f"Frozen APL {name} shape/dtype/layout changed")
|
||||
_check(np.isfinite(value).all() and np.all(value >= 0), f"Invalid positive raw-normalized APL {name}")
|
||||
for name, count in (("kc_globals", 4064), ("apl_globals", 2)):
|
||||
value = bundle[name]
|
||||
_check(value.dtype == np.dtype("int32") and value.shape == (count,) and value[0] >= 0 and
|
||||
value[-1] < 166700 and np.all(np.diff(value) > 0), f"Frozen APL identity {name} changed")
|
||||
_check(not np.intersect1d(bundle["kc_globals"], bundle["apl_globals"]).size, "APL and KC identities overlap")
|
||||
kc_positions = np.searchsorted(nodes, bundle["kc_globals"])
|
||||
present = kc_positions < n
|
||||
present[present] &= nodes[kc_positions[present]] == bundle["kc_globals"][present]
|
||||
kc_local, kc_in_bundle = kc_positions[present], np.flatnonzero(present)
|
||||
requested_globals, request_local, request_ptr, request_count = _requests(case_dir, nodes, steps)
|
||||
initial = None
|
||||
if (case_dir / "arrays.npz").exists():
|
||||
with np.load(case_dir / "arrays.npz", allow_pickle=False) as archive:
|
||||
_check(set(archive.files) == {"initial_v_mv", "initial_g_mv"}, "Unexpected initial-state schema")
|
||||
initial = {name: archive[name].copy() for name in archive.files}
|
||||
for name, value in initial.items():
|
||||
_check(value.dtype == np.dtype("float64") and value.shape == (n,) and np.isfinite(value).all(),
|
||||
f"Invalid diagnostic initial state: {name}")
|
||||
history_bytes = steps * n * 40 if record_history else 0
|
||||
_budget(budget_root, budget_limit, history_bytes + steps * (2 + 2 + 4064) * 4 + edges * 8 + (64 << 20))
|
||||
versions = {name: importlib.metadata.version(name) for name in
|
||||
("brian2", "numpy", "pandas", "pyarrow", "sympy", "scipy", "cython")}
|
||||
_save(out_dir / "environment.json", {"python": sys.version, "platform": platform.platform(),
|
||||
"packages": versions, "codegen": "numpy", "author_precision": "float64",
|
||||
"APL_precision": "float32 in separate science-Python process", "Torch_imported_into_Brian": False})
|
||||
for name, required in (("brian2", "2.5.1"), ("numpy", "1.22.3"), ("sympy", "1.12")):
|
||||
_check(versions[name] == required, f"{name} must remain pinned to {required}")
|
||||
import brian2 as b2
|
||||
import pandas as pd
|
||||
from brian2.core.base import BrianObject
|
||||
|
||||
ae = _load_checked(ae_path, AE_SHA256, "phase4ag_sealed_ae_helpers")
|
||||
af = _load_checked(af_path, AF_SHA256, "phase4ag_sealed_af_helpers")
|
||||
author = _load_checked(author_path, AUTHOR_SHA256, "phase4ag_unchanged_author")
|
||||
_check("torch" not in sys.modules, "Torch must not be imported into the pinned Brian process")
|
||||
comp = pd.read_csv(graph_dir / "completeness.csv", index_col=0)
|
||||
_check(np.array_equal(comp.index.to_numpy(), nodes) and
|
||||
np.array_equal(comp["local_index"].to_numpy(), np.arange(n)), "Author CSV neuron identity differs")
|
||||
del comp
|
||||
graph_hashes = {name: _digest(graph_dir / name) for name in
|
||||
tuple(key + ".npy" for key in arrays) + ("completeness.csv", "connectivity.parquet")}
|
||||
bundle_hashes = {name: _digest(bundle_dir / name) for name in BUNDLE_FILES}
|
||||
case_hashes = {name: _digest(case_dir / name) for name in ("spec.json", "requests.npz")}
|
||||
if initial is not None:
|
||||
case_hashes["arrays.npz"] = _digest(case_dir / "arrays.npz")
|
||||
worker_hash = _digest(worker_path)
|
||||
coefficient_hash = _digest(coefficients_path)
|
||||
baseline_coefficients = json.loads(coefficients_path.read_text(encoding="utf-8"))
|
||||
if "legacy_apl_worker_sha256" in spec:
|
||||
_check(worker_hash == spec["legacy_apl_worker_sha256"], "APL worker differs from preregistered source")
|
||||
_save(out_dir / "input-audit.json", {"spec": spec, "case_hashes": case_hashes,
|
||||
"graph_hashes": graph_hashes, "bundle_hashes": bundle_hashes,
|
||||
"author_sha256": AUTHOR_SHA256, "ae_sha256": AE_SHA256, "af_sha256": AF_SHA256,
|
||||
"legacy_apl_worker_sha256": worker_hash, "coefficients_sha256": coefficient_hash,
|
||||
"candidate_trace_used_as_input": False,
|
||||
"scope": "shared-APL-component closed-loop oracle, not all-components-independent CPU equivalence"})
|
||||
saved_preferences = {key: b2.prefs[key] for key in
|
||||
("codegen.target", "core.default_float_dtype", "legacy.refractory_timing")}
|
||||
old_dt = b2.defaultclock.dt
|
||||
b2.start_scope()
|
||||
b2.prefs["codegen.target"] = "numpy"
|
||||
b2.prefs["core.default_float_dtype"] = np.float64
|
||||
b2.prefs["legacy.refractory_timing"] = False
|
||||
b2.defaultclock.dt = .1 * b2.ms
|
||||
parameters = dict(author.default_params)
|
||||
_save(out_dir / "parameter-checks.json", ae._parameters_match(parameters, spec["model"], b2))
|
||||
neu, syn, spikes = author.create_model(graph_dir / "completeness.csv", graph_dir / "connectivity.parquet", parameters)
|
||||
_check(len(neu) == n and len(syn) == edges, "Author loaded an unexpected graph size")
|
||||
_check(all(np.dtype(neu.variables[key].dtype) == np.float64 for key in ("v", "g")) and
|
||||
np.dtype(syn.variables["w"].dtype) == np.float64, "Author states/weights are not float64")
|
||||
effective = _new_memmap(out_dir / "weights.npy", "<f8", (edges,))
|
||||
mapped_files.append(effective)
|
||||
width, w_unit, mask_count = BLOCK_BYTES // 8, float(parameters["w_syn"] / b2.volt), 0
|
||||
mask_hasher = hashlib.sha256()
|
||||
base_hasher = hashlib.sha256()
|
||||
effective_hasher = hashlib.sha256()
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
np.testing.assert_array_equal(np.asarray(syn.i[begin:stop]), arrays["src"][begin:stop])
|
||||
np.testing.assert_array_equal(np.asarray(syn.j[begin:stop]), arrays["dst"][begin:stop])
|
||||
original = np.asarray(syn.w_[begin:stop], dtype=np.float64).copy()
|
||||
raw_expected = arrays["signed_raw"][begin:stop] * w_unit
|
||||
_check(np.array_equal(original.view(np.uint64), raw_expected.view(np.uint64)) and
|
||||
np.array_equal(original.view(np.uint64), arrays["weights"][begin:stop].view(np.uint64)),
|
||||
"Actual original Brian weights differ from unchanged signed raw or AF base weights")
|
||||
base_hasher.update(np.ascontiguousarray(original, dtype="<f8").tobytes())
|
||||
mask = np.isin(nodes[arrays["src"][begin:stop]], bundle["apl_globals"])
|
||||
mask_count += int(mask.sum())
|
||||
mask_hasher.update(np.ascontiguousarray(arrays["edge_id"][begin:stop][mask], dtype="<i8").tobytes())
|
||||
original[mask] = np.float64(0.0)
|
||||
effective_hasher.update(np.ascontiguousarray(original, dtype="<f8").tobytes())
|
||||
effective[begin:stop] = original
|
||||
syn.w_[begin:stop] = original
|
||||
if n == 166700:
|
||||
_check(mask_count == 5845, "Full legacy APL fast-edge mask must contain exactly 5845 existing edges")
|
||||
effective.flush()
|
||||
_save(out_dir / "apl-mask-audit.json", {"count": mask_count, "ordered_original_edge_id_sha256": mask_hasher.hexdigest(),
|
||||
"base_weight_raw_bytes_sha256": base_hasher.hexdigest(), "effective_weights_file_sha256": _digest(out_dir / "weights.npy"),
|
||||
"effective_weights_raw_sha256": effective_hasher.hexdigest(),
|
||||
"rule": "zero +0 all existing fast edges whose source GLOBAL neuron is in apl_globals; no graph edge removal",
|
||||
"graded_targets": "only KC identities present in this LIF case", "KC_targets_in_case": len(kc_local),
|
||||
"base_graph_files_modified": False, "fullgraph_required_mask_count": 5845})
|
||||
if initial is not None:
|
||||
neu.v = initial["initial_v_mv"] * b2.mV
|
||||
neu.g = initial["initial_g_mv"] * b2.mV
|
||||
v_raw = neu.variables["v"].get_value()
|
||||
g_raw = neu.variables["g"].get_value()
|
||||
last_raw = neu.variables["lastspike"].get_value()
|
||||
available_raw = neu.variables["not_refractory"].get_value()
|
||||
histories = {}
|
||||
pre_apl_histories = {}
|
||||
if record_history:
|
||||
for name, dtype in (("v", "float64"), ("g", "float64"), ("ref", "int32")):
|
||||
histories[name] = _new_memmap(out_dir / f"{name}-history.{dtype}.npy", np.dtype(dtype), (steps, n))
|
||||
mapped_files.append(histories[name])
|
||||
pre_apl_histories[name] = _new_memmap(out_dir / f"preAPL-{name}.{dtype}.npy", np.dtype(dtype), (steps, n))
|
||||
mapped_files.append(pre_apl_histories[name])
|
||||
apl_history = {}
|
||||
for name, count in (("drive", 2), ("state", 2), ("inhibition", 4064)):
|
||||
apl_history[name] = _new_memmap(out_dir / f"apl-{name}.float32.npy", "<f4", (steps, count))
|
||||
mapped_files.append(apl_history[name])
|
||||
_save(out_dir / "output-schema.json", {"history_recorded": record_history, "history_shape": [steps, n],
|
||||
"state_hash_file": "tick-state-hashes.jsonl", "state_hash_encoding": "SHA256 of contiguous little-endian raw f64 V/g and i32 ref",
|
||||
"APL_dtype": "float32", "APL_h_units": "mV; gain already applied in float32",
|
||||
"APL_SI_conversion": "h.astype(float64) * float64(0.001), then separate float64 g subtraction",
|
||||
"APL_write_schedule": "end, order=0, after original reset, unconditional raw g write; recorder end/order=1",
|
||||
"APL_input": "Brian's own actual global spikes, never candidate spikes or candidate inhibition",
|
||||
"APL_shapes": {"drive": [steps, 2], "state": [steps, 2], "inhibition": [steps, 4064]},
|
||||
"request_columns": ["tick", "neuron_idx", "attempted", "accepted", "rejected"],
|
||||
"queue_protocol": "sealed Phase4AF queue_summary; original global edge IDs, including zero-weight/ref-rejected arrivals",
|
||||
"state_npz": ["step", "neuron_idx", "counts", "v", "g", "ref"],
|
||||
"complete_marker": "summary.json complete=true only after worker exit and all checks"})
|
||||
delay_steps = int(round(float(parameters["t_dly"] / b2.ms) / .1))
|
||||
ref_steps = int(round(float(parameters["t_rfc"] / b2.ms) / .1))
|
||||
_check(delay_steps == 18 and ref_steps == 22, "Author delay/refractory changed")
|
||||
queue_len = delay_steps + 1
|
||||
due_counts = np.zeros(queue_len, np.int64)
|
||||
spike_ring = [np.empty(0, np.int32) for _ in range(queue_len)]
|
||||
outdegree = np.diff(arrays["ptr"])
|
||||
request_seen = np.zeros(steps, np.uint8)
|
||||
apl_seen = np.zeros(steps, np.uint8)
|
||||
applicable_count = np.zeros(steps, np.int32)
|
||||
rejected_count = np.zeros(steps, np.int32)
|
||||
spike_count = np.zeros(steps, np.int32)
|
||||
total_counts = np.zeros(n, np.int32)
|
||||
records = {"rows": [], "peek_calls": 0, "advance_calls": 0, "installations": []}
|
||||
last_ticks = np.zeros(n, np.int64)
|
||||
final_ref = np.zeros(n, np.int32)
|
||||
permitted = np.empty(0, np.int32)
|
||||
total_events = 0
|
||||
|
||||
def tick_now():
|
||||
return int(round(float(neu.clock.t / b2.second) / .0001))
|
||||
|
||||
def current_ref(tick):
|
||||
seen = np.isfinite(last_raw) & (last_raw >= 0)
|
||||
last_ticks.fill(0)
|
||||
last_ticks[seen] = np.rint(last_raw[seen] / .0001).astype(np.int64)
|
||||
result = np.where(seen, np.maximum(ref_steps - (tick - last_ticks), 0), 0).astype(np.int32)
|
||||
_check(np.array_equal(result == 0, available_raw), "Ref history disagrees with author not_refractory")
|
||||
return result
|
||||
|
||||
with ExitStack() as stack:
|
||||
stderr = stack.enter_context((out_dir / "apl-worker.stderr.log").open("xb"))
|
||||
worker = _APLWorker(science_python, worker_path, bundle_dir, stderr, float(spec.get("worker_timeout_seconds", 180)))
|
||||
_save(out_dir / "apl-worker-handshake.json", {"command": worker.command, "handshake": worker.hello})
|
||||
queue_file = stack.enter_context((out_dir / "queue.jsonl").open("x", encoding="utf-8"))
|
||||
event_file = stack.enter_context((out_dir / "events.bin").open("xb"))
|
||||
hash_file = stack.enter_context((out_dir / "tick-state-hashes.jsonl").open("x", encoding="utf-8"))
|
||||
tick_file = stack.enter_context((out_dir / "ticks.jsonl").open("x", encoding="utf-8"))
|
||||
requests_file = stack.enter_context((out_dir / "requests.csv").open("x", encoding="utf-8", newline=""))
|
||||
request_writer = csv.DictWriter(requests_file, fieldnames=["tick", "neuron_idx", "attempted", "accepted", "rejected"])
|
||||
request_writer.writeheader()
|
||||
|
||||
def request_operation():
|
||||
nonlocal permitted
|
||||
tick = tick_now()
|
||||
_check(0 <= tick < steps and not request_seen[tick], "Invalid/duplicate common request tick")
|
||||
request_seen[tick] = 1
|
||||
begin, stop = request_ptr[tick:tick + 2]
|
||||
local = request_local[begin:stop]
|
||||
available = available_raw[local].copy()
|
||||
permitted = local[available]
|
||||
applicable_count[tick] = len(permitted)
|
||||
rejected_count[tick] = len(local) - len(permitted)
|
||||
for node, accepted in zip(requested_globals[begin:stop], available):
|
||||
request_writer.writerow({"tick": tick, "neuron_idx": int(node), "attempted": 1,
|
||||
"accepted": int(accepted), "rejected": int(not accepted)})
|
||||
if len(permitted):
|
||||
neu.v[permitted] = parameters["v_th"] + 1 * b2.mV
|
||||
|
||||
def apl_operation():
|
||||
tick = tick_now()
|
||||
_check(0 <= tick < steps and not apl_seen[tick] and request_seen[tick], "Invalid/duplicate APL tick")
|
||||
active = np.asarray(neu.spikes, dtype=np.int32)
|
||||
_check(not len(active) or np.all(np.diff(active) > 0), "Brian actual spikes are not canonical/unique")
|
||||
_check(np.all(g_raw[active] == 0.0), "APL ran before the original author g reset")
|
||||
if record_history:
|
||||
pre_apl_histories["v"][tick] = v_raw
|
||||
pre_apl_histories["g"][tick] = g_raw
|
||||
pre_apl_histories["ref"][tick] = current_ref(tick)
|
||||
drive, state, inhibition = worker.step(tick, np.asarray(nodes[active], dtype=np.int32))
|
||||
apl_history["drive"][tick] = drive
|
||||
apl_history["state"][tick] = state
|
||||
apl_history["inhibition"][tick] = inhibition
|
||||
# These are separate array operations. Do not move the unit
|
||||
# conversion into a fused subtract; do not guard by refractory.
|
||||
delta_si = inhibition[kc_in_bundle].astype(np.float64) * np.float64(.001)
|
||||
g_raw[kc_local] = g_raw[kc_local] - delta_si
|
||||
apl_seen[tick] = 1
|
||||
|
||||
def record_end():
|
||||
nonlocal last_complete_tick, total_events, final_ref
|
||||
tick = tick_now()
|
||||
_check(tick == last_complete_tick + 1 and tick < steps and apl_seen[tick], "End tick is incomplete/out of order")
|
||||
_check(len(records["rows"]) == tick + 1, "End state lacks queue observation")
|
||||
_check(np.isfinite(v_raw).all() and np.isfinite(g_raw).all(), "Nonfinite author/APL state")
|
||||
final_ref = current_ref(tick)
|
||||
if record_history:
|
||||
histories["v"][tick], histories["g"][tick], histories["ref"][tick] = v_raw, g_raw, final_ref
|
||||
active = np.asarray(neu.spikes, dtype=np.int32)
|
||||
_check(np.isin(permitted, active).all(), "An applicable request did not produce a real spike")
|
||||
spike_count[tick] = len(active)
|
||||
total_counts[active] += 1
|
||||
if len(active):
|
||||
events = np.empty(len(active), EVENT_DTYPE)
|
||||
events["step"], events["neuron_idx"] = tick, nodes[active]
|
||||
event_file.write(memoryview(events).cast("B"))
|
||||
total_events += len(active)
|
||||
spike_ring[tick % queue_len] = active.copy()
|
||||
due_counts[(tick + delay_steps) % queue_len] = int(outdegree[active].sum(dtype=np.int64))
|
||||
hash_file.write(json.dumps({"tick": tick, "v_sha256": _array_hash(v_raw, "<f8"),
|
||||
"g_sha256": _array_hash(g_raw, "<f8"), "ref_sha256": _array_hash(final_ref, "<i4")}, sort_keys=True) + "\n")
|
||||
tick_file.write(json.dumps({"tick": tick, "spikes": len(active), "requests": int(request_count[tick]),
|
||||
"applicable_requests": int(applicable_count[tick]), "ref_rejected_requests": int(rejected_count[tick]),
|
||||
"event_sha256": _array_hash(nodes[active], "<i4"),
|
||||
"due_arrivals": records["rows"][tick]["count"], "accepted_arrivals": records["rows"][tick]["accepted_count"],
|
||||
"APL_drive_sha256": _array_hash(apl_history["drive"][tick], "<f4"),
|
||||
"APL_state_sha256": _array_hash(apl_history["state"][tick], "<f4"),
|
||||
"APL_inhibition_sha256": _array_hash(apl_history["inhibition"][tick], "<f4")}, sort_keys=True) + "\n")
|
||||
_budget(budget_root, budget_limit, reserve=1 << 20)
|
||||
last_complete_tick = tick
|
||||
if (tick + 1) % 100 == 0 or tick + 1 == steps:
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
for handle in (queue_file, event_file, hash_file, tick_file, requests_file):
|
||||
handle.flush()
|
||||
print(json.dumps({"oracle": spec["name"], "completed_ticks": tick + 1,
|
||||
"spikes": total_events, "APL_worker_ticks": worker.tick}), flush=True)
|
||||
|
||||
class QueueInstaller(BrianObject):
|
||||
def __init__(self):
|
||||
super().__init__(clock=neu.clock, when="end", order=1_000_000, name="zz_apl_queue_installer")
|
||||
|
||||
def before_run(self, run_namespace):
|
||||
installed = []
|
||||
for code in syn.pre.code_objects:
|
||||
if "_queue" not in code.namespace or "_queue.peek()" not in str(code.code):
|
||||
continue
|
||||
original = code.namespace["_queue"]
|
||||
if isinstance(original, _QueueProxy):
|
||||
original = original._original_queue
|
||||
code.namespace["_queue"] = _QueueProxy(original, tick_now, available_raw, arrays, identity_edges,
|
||||
due_counts, spike_ring, delay_steps, queue_file, records, af.queue_summary)
|
||||
installed.append(code.name)
|
||||
_check(len(installed) == 1, "Expected one original anatomical queue consumer")
|
||||
records["installations"].append(installed[0])
|
||||
|
||||
objects = [neu, syn, spikes,
|
||||
b2.NetworkOperation(request_operation, clock=neu.clock, when="before_thresholds", order=0, name="oracle_common_requests"),
|
||||
b2.NetworkOperation(apl_operation, clock=neu.clock, when="end", order=0, name="oracle_legacy_apl_unconditional"),
|
||||
b2.NetworkOperation(record_end, clock=neu.clock, when="end", order=1, name="oracle_after_apl_record"), QueueInstaller()]
|
||||
network = b2.Network(*objects)
|
||||
_save(out_dir / "schedule.json", {"schedule": list(network.schedule), "summary": str(network.scheduling_summary())})
|
||||
network.run(0 * b2.ms, namespace={})
|
||||
_save(out_dir / "compiled-code.json", ae._code_records(network))
|
||||
compiled_coefficients = ae._coefficients(neu, parameters, b2, .0001)
|
||||
coefficient_keys = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
_check(all(float(compiled_coefficients[key]).hex() == float(baseline_coefficients[key]).hex()
|
||||
for key in coefficient_keys), "Compiled Brian coefficients differ from sealed AF")
|
||||
_check(last_complete_tick == -1 and worker.tick == 0 and not records["rows"] and not request_seen.any(),
|
||||
"Zero-duration compilation unexpectedly advanced state")
|
||||
network.run(steps * .1 * b2.ms, namespace={})
|
||||
_save(out_dir / "generated-code.json", ae._code_records(network))
|
||||
worker_status = worker.finish(steps)
|
||||
worker = None
|
||||
|
||||
_check(last_complete_tick == steps - 1 and np.all(request_seen == 1) and np.all(apl_seen == 1), "Incomplete trial")
|
||||
_check(records["peek_calls"] == records["advance_calls"] == steps, "Incomplete queue audit")
|
||||
_check(total_events == int(spikes.num_spikes) and np.array_equal(total_counts, np.asarray(spikes.count[:])),
|
||||
"Sparse events disagree with original SpikeMonitor")
|
||||
_check(np.array_equal(request_count, applicable_count + rejected_count), "Incomplete request accounting")
|
||||
_check(np.all(np.asarray(neu.rfc[:] / b2.ms) == float(parameters["t_rfc"] / b2.ms)), "Author refractory duration changed")
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
_check(np.array_equal(np.asarray(syn.w_[begin:stop]).view(np.uint64), effective[begin:stop].view(np.uint64)),
|
||||
"Effective synaptic weights changed during the trial")
|
||||
event_values = np.memmap(out_dir / "events.bin", dtype=EVENT_DTYPE, mode="r", shape=(total_events,)) if total_events else np.empty(0, EVENT_DTYPE)
|
||||
_check((out_dir / "events.bin").stat().st_size == total_events * 8, "Sparse event byte count mismatch")
|
||||
for begin in range(0, total_events, width):
|
||||
stop = min(total_events, begin + width)
|
||||
spike_local = np.asarray(spikes.i[begin:stop], dtype=np.int32)
|
||||
np.testing.assert_array_equal(event_values["neuron_idx"][begin:stop], nodes[spike_local])
|
||||
times = np.rint(np.asarray(spikes.t[begin:stop] / b2.second) / .0001).astype(np.int32)
|
||||
np.testing.assert_array_equal(event_values["step"][begin:stop], times)
|
||||
_budget(budget_root, budget_limit, total_events * 8 + n * 24 + (8 << 20))
|
||||
with (out_dir / "state.npz").open("xb") as handle:
|
||||
np.savez_compressed(handle, step=event_values["step"], neuron_idx=event_values["neuron_idx"],
|
||||
counts=total_counts, v=v_raw, g=g_raw, ref=final_ref)
|
||||
with (out_dir / "tick-summary.npz").open("xb") as handle:
|
||||
np.savez_compressed(handle, request_count=request_count, applicable_count=applicable_count,
|
||||
rejected_count=rejected_count, spike_count=spike_count,
|
||||
arrival_count=np.asarray([row["count"] for row in records["rows"]], np.int64),
|
||||
accepted_arrival_count=np.asarray([row["accepted_count"] for row in records["rows"]], np.int64))
|
||||
coefficients = ae._coefficients(neu, parameters, b2, .0001)
|
||||
keys = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
_check(all(float(coefficients[key]).hex() == float(baseline_coefficients[key]).hex() for key in keys),
|
||||
"Actual Brian coefficients differ from sealed AF coefficients")
|
||||
_save(out_dir / "coefficients.json", coefficients)
|
||||
_save(out_dir / "queue-audit.json", {"peek_calls": records["peek_calls"], "advance_calls": records["advance_calls"],
|
||||
"installations": records["installations"], "same_peek_object_returned": True,
|
||||
"original_edge_ids_not_sorted_or_filtered": True, "zero_weight_and_refractory_rejected_edges_retained": True,
|
||||
"post_threshold_pre_synaptic_guard_observed": True, "forwarding_contract": "sealed AE/AF design",
|
||||
"new_unwrapped_repeat_performed": False, "limit": "Current wrapper was not independently rerun without the queue proxy"})
|
||||
for path, expected in ((author_path, AUTHOR_SHA256), (ae_path, AE_SHA256), (af_path, AF_SHA256), (worker_path, worker_hash)):
|
||||
_check(_digest(path) == expected, f"Source changed during the run: {path}")
|
||||
_check(_digest(coefficients_path) == coefficient_hash, "Sealed coefficient reference changed during the run")
|
||||
for directory, hashes in ((graph_dir, graph_hashes), (bundle_dir, bundle_hashes), (case_dir, case_hashes)):
|
||||
for name, expected in hashes.items():
|
||||
_check(_digest(directory / name) == expected, f"Read-only input changed during the run: {name}")
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
_check("torch" not in sys.modules, "Pinned Brian process unexpectedly imported Torch")
|
||||
output_bytes = _budget(budget_root, budget_limit, 1 << 20)
|
||||
summary = {"name": spec["name"], "complete": True, "precision": "float64", "APL_precision": "float32",
|
||||
"backend": "shared-APL-component closed-loop Brian2 oracle", "steps": steps, "completed_ticks": last_complete_tick + 1,
|
||||
"nodes": n, "edges": edges, "spikes": total_events, "requests": int(request_count.sum()),
|
||||
"applicable_requests": int(applicable_count.sum()), "ref_rejected_requests": int(rejected_count.sum()),
|
||||
"APL_fast_edges_zeroed": mask_count, "APL_worker_exit_status": worker_status, "APL_worker_ticks": steps,
|
||||
"apl_fast_edges_zeroed": mask_count, "effective_weights_raw_sha256": effective_hasher.hexdigest(),
|
||||
"kc_targets_present": len(kc_local),
|
||||
"candidate_trace_used_as_input": False, "independent_CPU_all_components_claim": False,
|
||||
"history_recorded": record_history, "single_continuous_run": True, "no_training": True, "no_poi": True,
|
||||
"source_and_inputs_unchanged": True, "pass_all": True,
|
||||
"pass_all_scope": "Oracle construction, protocol and internal self-checks only; candidate comparison is a separate gate",
|
||||
"scope": "Only this fixed trajectory; legacy Torch APL is a shared numerical component, not an independent CPU APL implementation",
|
||||
"output_budget_bytes": budget_limit, "output_bytes_before_summary": output_bytes,
|
||||
"state_hashes_sha256": _digest(out_dir / "tick-state-hashes.jsonl"), "queue_sha256": _digest(out_dir / "queue.jsonl"),
|
||||
"events_sha256": _digest(out_dir / "events.bin"), "weights_sha256": _digest(out_dir / "weights.npy")}
|
||||
_save(out_dir / "summary.json", summary)
|
||||
return summary
|
||||
except Exception as error:
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
_save(out_dir / "error.json", {"complete": False, "last_complete_tick": last_complete_tick,
|
||||
"type": type(error).__name__, "message": str(error), "partial_files_preserved": True,
|
||||
"inputs_or_duration_adjusted": False})
|
||||
raise
|
||||
finally:
|
||||
if worker is not None:
|
||||
worker.abort()
|
||||
if saved_preferences is not None:
|
||||
for key, value in saved_preferences.items():
|
||||
b2.prefs[key] = value
|
||||
b2.defaultclock.dt = old_dt
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
"""Preregister exact legacy APL data and unmodified fixed CAL requests."""
|
||||
from datetime import datetime,timezone
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import ROOT,MODEL,EXP,AF_MODEL,AF_EXP,AD_EXP,CFG,save,digest,sources,integrity,budget
|
||||
|
||||
def arr(path,value):
|
||||
with Path(path).open('xb') as f:np.save(f,value,allow_pickle=False)
|
||||
|
||||
def graph(folder,nodes,full):
|
||||
folder.mkdir();nodes=np.asarray(nodes,np.int32);ptr,dst,src,raw,base=full
|
||||
if len(nodes)==CFG['neurons']:ids=np.arange(len(dst),dtype=np.int64)
|
||||
else:
|
||||
ids=np.concatenate([np.arange(ptr[s],ptr[s+1],dtype=np.int64) for s in nodes])
|
||||
ids=ids[np.isin(dst[ids],nodes)]
|
||||
lookup=np.full(CFG['neurons'],-1,np.int32);lookup[nodes]=np.arange(len(nodes),dtype=np.int32)
|
||||
ss,dd=lookup[src[ids]],lookup[dst[ids]];pp=np.r_[np.int64(0),np.bincount(ss,minlength=len(nodes)).cumsum(dtype=np.int64)]
|
||||
for name,value in dict(nodes=nodes,edge_id=ids,src=ss,dst=dd,ptr=pp,signed_raw=raw[ids],weights=base[ids]).items():arr(folder/(name+'.npy'),value)
|
||||
pd.DataFrame({'local_index':np.arange(len(nodes),dtype=np.int32)},index=pd.Index(nodes,name='global_neuron_idx')).to_csv(folder/'completeness.csv')
|
||||
pd.DataFrame({'Presynaptic_Index':ss,'Postsynaptic_Index':dd,'Excitatory x Connectivity':raw[ids],'edge_id':ids},copy=False).to_parquet(folder/'connectivity.parquet',index=False)
|
||||
save(folder/'audit.json',dict(nodes=len(nodes),edges=len(ids),canonical_original_edge_ids=True,anatomical_weights_not_modified=True,
|
||||
effective_APL_zeroing='Only in new runtime clone, never these original weight files'))
|
||||
return nodes
|
||||
|
||||
def case(name,kind,steps,nodes,graphdir,requests,initial=None,extra=None):
|
||||
folder=EXP/'cases'/name;folder.mkdir()
|
||||
model=json.loads((AD_EXP/'protocol/preregistered.json').read_text())['config']['model']
|
||||
spec=dict(name=name,kind=kind,steps=steps,dt_ms=.1,duration_ms=steps*.1,model=model,
|
||||
graph_dir=str(graphdir),apl_bundle_dir=str(EXP/'apl-bundle'),apl=CFG['apl'],record_history=kind=='fixture',
|
||||
coefficients_path=str(AF_EXP/'oracle/no_input/coefficients.json'),
|
||||
science_python=str(ROOT/'models/male-cns-lif/.venv/bin/python'),legacy_apl_worker=str(MODEL/'legacy_apl_worker.py'),
|
||||
budget_root=str(EXP),output_budget_bytes=CFG['output_budget_bytes'],precision='float64',apl_precision='float32',training=False)
|
||||
if extra:spec.update(extra)
|
||||
requests=np.asarray(requests,dtype=np.int32).reshape(-1,2)
|
||||
if len(requests):
|
||||
requests=requests[np.lexsort((requests[:,1],requests[:,0]))]
|
||||
assert len(np.unique(requests.astype(np.int64)[:,0]*CFG['neurons']+requests[:,1]))==len(requests)
|
||||
assert requests[:,0].min()>=0 and requests[:,0].max()<steps and np.isin(requests[:,1],nodes).all()
|
||||
with (folder/'requests.npz').open('xb') as f:np.savez_compressed(f,tick=requests[:,0],neuron_idx=requests[:,1])
|
||||
if kind=='fixture':
|
||||
v=np.full(len(nodes),model['resting_potential_mv'],np.float64);g=np.zeros(len(nodes),np.float64)
|
||||
if initial:
|
||||
for node,value in initial.get('v',{}).items():v[np.flatnonzero(nodes==node)[0]]=value
|
||||
for node,value in initial.get('g',{}).items():g[np.flatnonzero(nodes==node)[0]]=value
|
||||
with (folder/'arrays.npz').open('xb') as f:np.savez_compressed(f,initial_v_mv=v,initial_g_mv=g)
|
||||
save(folder/'spec.json',spec)
|
||||
|
||||
def main():
|
||||
assert not (EXP/'protocol/preregistered.json').exists()
|
||||
assert all(not (EXP/d).exists() or not any((EXP/d).iterdir()) for d in ('protocol','graphs','apl-bundle','cases','gpu','oracle'))
|
||||
assert shutil.disk_usage(EXP.parent).free>30*1024**3
|
||||
for d in ('protocol','apl-bundle','graphs','cases','gpu','oracle','component-audit','regression','calibration','audit','extension-build','logs'):(EXP/d).mkdir(parents=True,exist_ok=True)
|
||||
can=ROOT/'datasets/male-cns-v1.0/processed/neuron-only';legacy=ROOT/'models/male-cns-lif';lp=legacy/'olfactory-grounding-v1/phase4l.yaml'
|
||||
lock=ROOT/'experiments/phase4j-mb-physiology-repair/calibration/locked-parameters.json'
|
||||
files=[AF_MODEL/'manifest.json',AF_EXP/'manifest.json',AF_EXP/'oracle/no_input/coefficients.json',
|
||||
legacy/'deterministic-runtime-v2/phase4p_revalidate.py',lp,lock,can/'neuron-index/unified-metadata.parquet',can/'gpu/csr-weights.int64.npy',
|
||||
legacy/'author-runtime-lab-v1/author_ext.cpp',legacy/'author-runtime-lab-v1/author_ext.cu',legacy/'author-runtime-lab-v1/gpu_backend.py',
|
||||
legacy/'author-runtime-lab-v1/oracle_stress.py',AD_EXP/'external-resources/model.py',AF_MODEL/'oracle_full.py']
|
||||
lroot=ROOT/'experiments/phase4l-olfactory-grounding'
|
||||
files += [lroot/'odor-encoder'/x for x in ('frozen-odor-panel.parquet','mapped-glomerular-vectors.npz','selection.json')]
|
||||
files += [lroot/'receptor-mapping/olfactory-mapping.parquet']
|
||||
save(EXP/'protocol/preregistered.json',dict(timestamp_utc=datetime.now(timezone.utc).isoformat(),sources=sources(),config=CFG,
|
||||
inputs=[dict(path=str(p.relative_to(ROOT)),sha256=digest(p)) for p in files],before_any_trajectory=True,
|
||||
parent_commit=subprocess.check_output(['git','-C',str(ROOT),'rev-parse','HEAD'],text=True).strip()))
|
||||
save(EXP/'regression/old-integrity-before.json',integrity())
|
||||
locked=json.loads(lock.read_text());assert locked['parameters_locked'] and locked['calibration_only']
|
||||
assert {k:locked['selected'][k] for k in ('gain','normalization','tau_ms')}==CFG['apl']
|
||||
meta=pd.read_parquet(can/'neuron-index/unified-metadata.parquet').sort_values('neuron_idx').reset_index(drop=True)
|
||||
assert len(meta)==CFG['neurons'] and np.array_equal(meta.neuron_idx,np.arange(CFG['neurons']))
|
||||
cls=meta.annotation_class.fillna('').astype(str).to_numpy();typ=meta.annotation_type.fillna('').astype(str).to_numpy()
|
||||
kc=np.flatnonzero(cls=='Kenyon_Cell').astype(np.int32);pn=np.flatnonzero(cls=='ALPN').astype(np.int32);apl=np.flatnonzero(typ=='APL').astype(np.int32)
|
||||
assert len(kc)==4064 and len(apl)==2
|
||||
fg=AF_EXP/'graph';ptr=np.load(fg/'ptr.npy',mmap_mode='r');dst=np.load(fg/'dst.npy',mmap_mode='r');src=np.load(fg/'src.npy',mmap_mode='r')
|
||||
signed=np.load(fg/'signed_raw.npy',mmap_mode='r');base=np.load(AF_EXP/'oracle/no_input/weights.npy',mmap_mode='r');raw=np.load(can/'gpu/csr-weights.int64.npy',mmap_mode='r')
|
||||
assert len(dst)==CFG['edges'] and int(raw.sum())==CFG['raw_weight_sum']
|
||||
aplout=np.isin(src,apl);aplids=np.flatnonzero(aplout).astype(np.int64);assert len(aplids)==5845
|
||||
I=np.zeros((2,len(meta)),np.float32);O=np.zeros((2,len(kc)),np.float32)
|
||||
alook={int(v):i for i,v in enumerate(apl)};kl=np.full(len(meta),-1,np.int32);kl[kc]=np.arange(len(kc),dtype=np.int32)
|
||||
allowed=np.isin(src,np.r_[kc,pn])&np.isin(dst,apl)
|
||||
for eid in np.flatnonzero(allowed):I[alook[int(dst[eid])],int(src[eid])]+=float(raw[eid])
|
||||
for eid in np.flatnonzero(aplout&np.isin(dst,kc)):O[alook[int(src[eid])],kl[dst[eid]]]+=float(raw[eid])
|
||||
iscale=float(I[I>0].sum()/np.count_nonzero(np.any(I>0,axis=0)));oscale=float(O[O>0].mean());bundle=EXP/'apl-bundle'
|
||||
for name,value in dict(input_matrix=I/iscale,output_matrix=O/oscale,input_raw=I,output_raw=O,kc_globals=kc,pn_globals=pn,apl_globals=apl,apl_out_ids=aplids).items():arr(bundle/(name+'.npy'),value)
|
||||
save(bundle/'scales.json',dict(input_scale=iscale,output_scale=oscale))
|
||||
save(bundle/'provenance.json',dict(canonical_root=str(can),source=str(legacy/'deterministic-runtime-v2/phase4p_revalidate.py'),source_sha256=digest(legacy/'deterministic-runtime-v2/phase4p_revalidate.py'),
|
||||
parameters=CFG['apl'],lock_file=str(lock),lock_sha256=digest(lock),full_dimensions=[list(I.shape),list(O.shape)],
|
||||
APL=len(apl),KC=len(kc),ALPN=len(pn),APL_fast_edges=len(aplids),APL_nonKC_edges=int(np.count_nonzero(aplout&~np.isin(dst,kc))),
|
||||
APL_nonKC_contacts=int(raw[aplout&~np.isin(dst,kc)].sum()),normalization_recomputed_in_original_order=True,
|
||||
raw_files_modified=False))
|
||||
full=(ptr,dst,src,signed,base);fullnodes=graph(EXP/'graphs/full',np.arange(len(meta),dtype=np.int32),full)
|
||||
pscore=I.sum(axis=0);pnrank=pn[np.lexsort((pn,-pscore[pn]))];kscore=O.sum(axis=0);kcrank=kc[np.lexsort((kc,-kscore))]
|
||||
candidates=np.flatnonzero(np.isin(src,pn)&np.isin(dst,kc));best=int(candidates[np.argmax(raw[candidates])]);pairpn=int(src[best])
|
||||
pe=np.arange(ptr[pairpn],ptr[pairpn+1],dtype=np.int64);pe=pe[np.isin(dst[pe],kc)];pe=pe[np.lexsort((dst[pe],-raw[pe]))]
|
||||
collision_kc=np.unique(dst[pe[:3]]);assert len(collision_kc)==3
|
||||
nonkc=aplids[~np.isin(dst[aplids],kc)];nonkc_target=int(dst[nonkc[np.argmax(raw[nonkc])]])
|
||||
nodes=np.unique(np.r_[apl,pnrank[:16],kcrank[:16],pairpn,collision_kc,nonkc_target,[1197,1381,1511,82312,133666]]).astype(np.int32)
|
||||
fixturegraph=EXP/'graphs/fixtures';graph(fixturegraph,nodes,full)
|
||||
p=int(pnrank[0]);k=int(kcrank[0]);kc1,kc2,kc3=map(int,collision_kc)
|
||||
designs=[('zero',100,[],None),('pn_pulse',300,[(0,p)],None),
|
||||
('kc_ref_boundary',100,[(t,k) for t in (0,1,21,22,23,44)],None),
|
||||
('two_sources',300,[(t,n) for t in (0,22,44,66,88) for n in (p,k)],None),
|
||||
('arrival_collision',120,[(0,pairpn),(0,kc1),(18,kc2),(21,kc1)],None),
|
||||
('natural_request',100,[(0,k)],dict(v={k:-44.0})),
|
||||
('apl_only',100,[(0,int(a)) for a in apl],None),
|
||||
('high_fanin',400,[(t,int(n)) for t in (0,22,44) for n in nodes if pscore[n]>0],dict(g={k:.123}))]
|
||||
assert [v[0] for v in designs]==CFG['fixtures']
|
||||
for name,steps,requests,initial in designs:case(name,'fixture',steps,nodes,fixturegraph,requests,initial)
|
||||
meta.iloc[nodes][['neuron_idx','body_id','annotation_class','annotation_type','neurotransmitter']].to_csv(EXP/'protocol/fixture-identities.csv',index=False)
|
||||
save(EXP/'protocol/fixture-selection.json',dict(pn=p,kc=k,pair_PN=pairpn,arrival_KCs=collision_kc.tolist(),real_PN_KC_edge=best,
|
||||
nonKC_APL_target=nonkc_target,method='Static raw-count ranks and exact metadata; retain ALL existing induced edges and full APL matrices/norms',runtime_selection=False))
|
||||
os.environ['CUDA_VISIBLE_DEVICES']=CFG['gpu_uuid'];sys.path.insert(0,str(legacy));sys.path.insert(0,str(legacy/'deterministic-runtime-v2'))
|
||||
sys.path.insert(0,str(legacy/'terminal-scope-diagnostic-v1'));from diagnostic import queues
|
||||
queues()
|
||||
import torch
|
||||
from phase4p_revalidate import door_inputs,make_drive
|
||||
oc,panel,vectors,selection,mapping=door_inputs();cal=panel[panel.split=='calibration'].copy();assert len(panel)==12 and len(cal)==6
|
||||
assert oc['odor_encoder']['evaluation_seeds']==CFG['calibration']['seeds']
|
||||
assert float(oc['odor_encoder']['duration_ms'])==CFG['calibration']['duration_ms']
|
||||
cal.to_parquet(EXP/'protocol/calibration-panel.parquet',index=False);calrecords=[]
|
||||
for mode in CFG['calibration']['modes']:
|
||||
for oi,(_,odor) in enumerate(cal.iterrows()):
|
||||
targets,rates=make_drive(oc,panel,vectors,selection,mapping,odor.odor_id,mode)
|
||||
assert len(np.unique(targets))==len(targets),'Original encoder emits duplicate PN targets; audit instead of silently merging'
|
||||
rates=np.asarray(rates,np.float32);steps=round(CFG['calibration']['duration_ms']/.1)
|
||||
for seed in CFG['calibration']['seeds']:
|
||||
gen=torch.Generator(device='cuda');gen.manual_seed(seed)
|
||||
original_probability=rates*.1/1000.0
|
||||
explicit_probability=rates*np.float32(.1)/np.float32(1000.)
|
||||
assert original_probability.dtype==np.float32 and np.array_equal(original_probability.view(np.uint32),explicit_probability.view(np.uint32))
|
||||
probs=torch.as_tensor(original_probability,dtype=torch.float32,device='cuda')
|
||||
forced=(torch.rand((steps,len(targets)),generator=gen,device='cuda')<probs[None,:]).cpu().numpy()
|
||||
tt,cc=np.nonzero(forced);req=np.column_stack([tt,targets[cc]]).astype(np.int32)
|
||||
name=f'cal-{mode}-{oi:02d}-s{seed}'
|
||||
case(name,'calibration',steps,fullnodes,EXP/'graphs/full',req,extra=dict(odor_id=str(odor.odor_id),odor_name=str(odor.odor_name),
|
||||
mode=mode,seed=seed,odor_index=oi,expected_external_events=float(rates.sum())*steps*.1/1000.,target_count=len(targets),rate_sum_hz=float(rates.sum())))
|
||||
with (EXP/'cases'/name/'pn-drive.npz').open('xb') as f:np.savez_compressed(f,targets=targets,rates_hz=rates)
|
||||
calrecords.append(dict(name=name,mode=mode,odor_index=oi,odor_id=str(odor.odor_id),seed=seed,requests=len(req),rates_sum=float(rates.sum())))
|
||||
save(EXP/'protocol/calibration-cases.json',calrecords)
|
||||
save(EXP/'protocol/encoder-audit.json',dict(panel_count=12,cal_count=6,median_mapped_response_sum=float(np.median(panel.mapped_response_sum)),
|
||||
target_rng_column_order='Original pn_drive, no sorting before torch.rand',seeds=CFG['calibration']['seeds'],
|
||||
requested_before_any_dynamics=True,formal_AB_run=False,heldout_run=False,torch=torch.__version__,numpy=np.__version__,
|
||||
generation='Original rates*.1/1000 float32 GPU Bernoulli, independent per-case original seed'))
|
||||
save(EXP/'protocol/prepared-hashes.json',[dict(path=str(p.relative_to(EXP)),bytes=p.stat().st_size,sha256=digest(p))
|
||||
for d in ('graphs','apl-bundle','cases','protocol') for p in sorted((EXP/d).rglob('*')) if p.is_file() and p.name!='prepared-hashes.json'])
|
||||
budget();print('PREPARED8 fixtures and60 fixed CAL inputs; no trajectories; frozen integrity PASS',flush=True)
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,48 @@
|
||||
"""A fresh process per declared case; no concurrent scientific GPU jobs."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
from context import ROOT,MODEL,EXP,CFG,check,budget
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser()
|
||||
parser.add_argument('backend',choices=['gpu','oracle','component'])
|
||||
parser.add_argument('case')
|
||||
parser.add_argument('--repeat',type=int,default=0)
|
||||
args=parser.parse_args()
|
||||
assert '/' not in args.case and '..' not in args.case and 0<=args.repeat<3
|
||||
check();budget()
|
||||
os.environ['CUDA_VISIBLE_DEVICES']=CFG['gpu_uuid']
|
||||
os.environ['CUDA_HOME']='/usr/local/cuda'
|
||||
os.environ['PATH']=str(ROOT/'models/male-cns-lif/.venv/bin')+':/usr/local/cuda/bin:'+os.environ['PATH']
|
||||
os.environ['MAX_JOBS']='2';os.environ['TORCH_CUDA_ARCH_LIST']='8.9'
|
||||
# The pinned Brian environment intentionally has neither Torch nor PyYAML.
|
||||
# Keep this preflight standard-library-only; never install into that env.
|
||||
for port in (8188,8189,8190):
|
||||
with urllib.request.urlopen(f'http://192.168.10.51:{port}/queue',timeout=5) as response:
|
||||
queue=json.load(response)
|
||||
assert not queue['queue_running'] and not queue['queue_pending'],'ComfyUI busy: defer, never evict'
|
||||
case=EXP/'cases'/args.case
|
||||
assert case.is_dir()
|
||||
out=EXP/'gpu'/f'{args.case}-r{args.repeat}'
|
||||
if args.backend=='gpu':
|
||||
from gpu_apl import run_gpu
|
||||
result=run_gpu(case,out,EXP/'extension-build')
|
||||
elif args.backend=='oracle':
|
||||
assert args.repeat==0
|
||||
from oracle_apl import run_case
|
||||
result=run_case(case,EXP/'oracle'/args.case)
|
||||
else:
|
||||
assert args.repeat==0
|
||||
from validate_components import validate
|
||||
result=validate(out,EXP/'apl-bundle',EXP/'component-audit'/(args.case+'.json'))
|
||||
budget()
|
||||
print(json.dumps(dict(backend=args.backend,case=args.case,repeat=args.repeat,
|
||||
complete=result.get('complete'),pass_all=result.get('pass_all')),allow_nan=False),flush=True)
|
||||
if not result.get('pass_all',False):raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Ordered numerical gates before any full CAL; never tune from outcomes."""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import time
|
||||
import traceback
|
||||
from context import ROOT,MODEL,EXP,CFG,check,save,budget
|
||||
|
||||
SCIENCE=ROOT/'models/male-cns-lif/.venv/bin/python'
|
||||
ORACLE=ROOT/'experiments/phase4ad-author-semantics/environment/.venv/bin/python'
|
||||
|
||||
|
||||
def child(backend,name,repeat=0):
|
||||
log=EXP/'logs'/f'{backend}-{name}-r{repeat}.log'
|
||||
cmd=[str(ORACLE if backend=='oracle' else SCIENCE),'-B',str(MODEL/'run_case.py'),backend,name,'--repeat',str(repeat)]
|
||||
print('START',backend,name,repeat,flush=True);t0=time.perf_counter()
|
||||
with log.open('x',encoding='utf-8') as f:
|
||||
result=subprocess.run(cmd,stdout=f,stderr=subprocess.STDOUT,timeout=1800,check=False)
|
||||
print('FINISH',backend,name,repeat,'exit',result.returncode,'wall',round(time.perf_counter()-t0,2),flush=True)
|
||||
if result.returncode:raise RuntimeError(f'{backend} {name} failed; inspect {log}')
|
||||
|
||||
|
||||
def gate_case(name,repeats):
|
||||
from compare import compare,compare_gpu_repeats
|
||||
child('gpu',name)
|
||||
for r in range(1,repeats):child('gpu',name,r)
|
||||
child('oracle',name)
|
||||
result=compare(EXP/'cases'/name,EXP/'gpu'/(name+'-r0'),EXP/'oracle'/name,EXP/'audit'/('compare-'+name+'.json'))
|
||||
assert result['pass_all'],f'Closed-loop exact comparison failed: {name}; no later CAL permitted'
|
||||
for r in range(1,repeats):
|
||||
report=compare_gpu_repeats(EXP/'gpu'/(name+'-r0'),EXP/'gpu'/f'{name}-r{r}',EXP/'audit'/f'repeat-{name}-r{r}.json')
|
||||
assert report['pass_all'],f'Exact GPU repeat failed: {name}'
|
||||
child('component',name)
|
||||
return dict(case=name,pass_all=True,gpu_repeats=repeats)
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser();parser.add_argument('stage',choices=['fixtures','probes','calibration']);args=parser.parse_args()
|
||||
check();budget();receipt=EXP/'audit'/(args.stage+'-gate.json');assert not receipt.exists()
|
||||
try:
|
||||
if args.stage=='fixtures':
|
||||
rows=[gate_case(n,CFG['fixture_repeats']) for n in CFG['fixtures']]
|
||||
save(receipt,dict(pass_all=True,cases=rows,scope='Fixed real-edge induced fixtures only'))
|
||||
elif args.stage=='probes':
|
||||
assert json.loads((EXP/'audit/fixtures-gate.json').read_text())['pass_all']
|
||||
rows=[gate_case(n,1) for n in CFG['calibration']['full_oracle_probes']]
|
||||
save(receipt,dict(pass_all=True,cases=rows,scope='Only two original first-CAL fixed fullgraph trajectories'))
|
||||
else:
|
||||
assert json.loads((EXP/'audit/fixtures-gate.json').read_text())['pass_all']
|
||||
assert json.loads((EXP/'audit/probes-gate.json').read_text())['pass_all']
|
||||
records=json.loads((EXP/'protocol/calibration-cases.json').read_text());assert len(records)==60
|
||||
probes=CFG['calibration']['full_oracle_probes']
|
||||
for rec in records:
|
||||
if rec['name'] not in probes:child('gpu',rec['name'])
|
||||
if rec['seed']==CFG['calibration']['seeds'][0]:child('gpu',rec['name'],1)
|
||||
from calibration import summarize
|
||||
result=summarize()
|
||||
save(receipt,dict(pass_all=result['pass_all'],scope='Complete original six-CAL representation gate, not heldout or learning',summary='calibration/summary.json'))
|
||||
if not result['pass_all']:raise SystemExit(2)
|
||||
except Exception as e:
|
||||
save(EXP/'audit'/('stopped-'+args.stage+'.json'),dict(stage=args.stage,type=type(e).__name__,message=str(e),traceback=traceback.format_exc(),
|
||||
later_stages_not_permitted=True,partial_outputs_preserved=True,parameters_changed=False))
|
||||
raise
|
||||
budget()
|
||||
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Local, synthetic tests only: no CUDA, scientific trajectories, or heldout data."""
|
||||
import copy
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from calibration import (ORIGINAL_MODES, ORIGINAL_SEEDS, ORIGINAL_CAL_IDS,
|
||||
APL_FILES, STATE_DTYPES, arrays_bitwise_equal, evaluate_gates, exact_repeat, pair_metrics,
|
||||
sample_cv, summarize_mode, validate_inventory, validate_state)
|
||||
|
||||
|
||||
GATES = {"sparse_min_fraction": .03, "sparse_max_fraction": .09,
|
||||
"minimum_separable_odor_pairs": 3, "max_fraction_strict": .5, "min_fraction_strict": 0.,
|
||||
"same_jaccard_gt_between": True, "between_euclidean_gt_within": True,
|
||||
"both_modes_required": True, "finite_required": True, "repeat_exact_required": True}
|
||||
|
||||
|
||||
def synthetic_runs(silent=False):
|
||||
result = {}
|
||||
for mode in ORIGINAL_MODES:
|
||||
for odor in range(6):
|
||||
for seed in ORIGINAL_SEEDS:
|
||||
counts = np.zeros(1000, dtype=np.int32)
|
||||
if not silent:
|
||||
counts[odor * 50:(odor + 1) * 50] = 1
|
||||
result[(mode, str(odor), seed)] = {"kc_counts": counts,
|
||||
"expected_external_events": 10., "external_events": 0 if silent else 10}
|
||||
return result
|
||||
|
||||
|
||||
class CalibrationTests(unittest.TestCase):
|
||||
def test_complete_gate_and_original_pair_counts(self):
|
||||
summary = summarize_mode(ORIGINAL_MODES[0], list(map(str, range(6))), ORIGINAL_SEEDS, synthetic_runs(), GATES)
|
||||
self.assertTrue(summary["gate"])
|
||||
self.assertEqual(summary["within_distance_count"], 60)
|
||||
self.assertEqual(summary["between_distance_count"], 75)
|
||||
self.assertEqual(summary["pair_count"], 15)
|
||||
self.assertEqual(summary["pairwise_separation_pass_count"], 15)
|
||||
self.assertTrue(all(row["within_distance_count"] == 20 and row["between_distance_count"] == 5 for row in summary["pairs"]))
|
||||
|
||||
def test_silent_is_not_pass(self):
|
||||
summary = summarize_mode(ORIGINAL_MODES[0], list(map(str, range(6))), ORIGINAL_SEEDS, synthetic_runs(True), GATES)
|
||||
self.assertFalse(summary["gate"])
|
||||
self.assertFalse(summary["conditions"]["minimum_strictly_positive"])
|
||||
self.assertFalse(summary["conditions"]["same_jaccard_gt_different"])
|
||||
self.assertFalse(summary["conditions"]["different_euclidean_gt_same"])
|
||||
self.assertEqual(summary["pairwise_separation_pass_count"], 0)
|
||||
self.assertIsNone(summary["actual_event_count_cv"])
|
||||
|
||||
def test_every_original_conjunct_is_necessary(self):
|
||||
baseline = summarize_mode(ORIGINAL_MODES[0], list(map(str, range(6))), ORIGINAL_SEEDS, synthetic_runs(), GATES)
|
||||
changes = (("KC_active_fraction_mean", .029), ("KC_active_fraction_mean", .091),
|
||||
("KC_active_fraction_max", .5), ("KC_active_fraction_min", 0.),
|
||||
("same_odor_jaccard_mean", baseline["different_odor_jaccard_mean"]),
|
||||
("different_odor_euclidean_mean", baseline["same_odor_euclidean_mean"]),
|
||||
("pairwise_separation_pass_count", 2))
|
||||
for key, value in changes:
|
||||
with self.subTest(key=key, value=value):
|
||||
modified = dict(baseline, **{key: value})
|
||||
self.assertFalse(all(evaluate_gates(modified, GATES).values()))
|
||||
for value in (.03, .09):
|
||||
self.assertTrue(evaluate_gates(dict(baseline, KC_active_fraction_mean=value), GATES)["mean_sparsity_in_closed_interval"])
|
||||
|
||||
def test_pair_specific_not_global_baseline(self):
|
||||
runs = synthetic_runs()
|
||||
mode = ORIGINAL_MODES[0]
|
||||
for odor in range(6):
|
||||
for index, seed in enumerate(ORIGINAL_SEEDS):
|
||||
counts = np.array([index + odor * 100] if odor < 2 else [10000 * odor + index * 1000], dtype=np.int32)
|
||||
runs[(mode, str(odor), seed)]["kc_counts"] = counts
|
||||
summary = summarize_mode(mode, list(map(str, range(6))), ORIGINAL_SEEDS, runs, GATES)
|
||||
first_pair = summary["pairs"][0]
|
||||
self.assertEqual(first_pair["within_this_pair_mean"], 2.)
|
||||
self.assertEqual(first_pair["between_same_seed_mean"], 100.)
|
||||
self.assertTrue(first_pair["gate"])
|
||||
self.assertGreater(summary["same_odor_euclidean_mean"], 100.)
|
||||
|
||||
def test_original_metric_zero_and_count_semantics(self):
|
||||
zero = np.zeros(3, dtype=np.int32)
|
||||
self.assertEqual(pair_metrics(zero, zero)["active_jaccard"], 1.)
|
||||
self.assertEqual(pair_metrics(zero, zero)["correlation"], 1.)
|
||||
result = pair_metrics(np.array([2, 0, 0]), np.array([1, 0, 0]))
|
||||
self.assertEqual(result["euclidean_distance"], 1.)
|
||||
self.assertEqual(result["active_jaccard"], 1.)
|
||||
self.assertAlmostEqual(sample_cv([1., 2., 3.]), .5)
|
||||
|
||||
def test_repeat_comparison_is_bits_and_dtype(self):
|
||||
self.assertFalse(arrays_bitwise_equal(np.array([0.], np.float64), np.array([-0.], np.float64)))
|
||||
self.assertFalse(arrays_bitwise_equal(np.array([1], np.int32), np.array([1], np.int64)))
|
||||
self.assertTrue(arrays_bitwise_equal(np.array([1.], np.float32), np.array([1.], np.float32)))
|
||||
|
||||
def test_exact_repeat_covers_all_recorded_semantics(self):
|
||||
first = {"state": {name: np.zeros(1, dtype) for name, dtype in STATE_DTYPES.items()},
|
||||
"apl": {name: np.zeros((1, width), np.float32) for name, width in APL_FILES.items()},
|
||||
"hashes": [{"tick": 0}], "queues": [{"tick": 0}],
|
||||
"requests": [], "component": {"environment": {"tf32": False}}}
|
||||
self.assertTrue(exact_repeat(first, copy.deepcopy(first))["pass_all"])
|
||||
for category in ("state", "apl"):
|
||||
for name in first[category]:
|
||||
bad = copy.deepcopy(first)
|
||||
bad[category][name].flat[0] = 1
|
||||
with self.subTest(category=category, name=name):
|
||||
self.assertFalse(exact_repeat(first, bad)["pass_all"])
|
||||
for key in ("hashes", "queues", "requests", "component"):
|
||||
bad = copy.deepcopy(first)
|
||||
bad[key] = {} if isinstance(first[key], list) else []
|
||||
with self.subTest(key=key):
|
||||
self.assertFalse(exact_repeat(first, bad)["pass_all"])
|
||||
|
||||
def test_raw_state_rejects_nonfinite_duplicate_and_wrong_counts(self):
|
||||
state = {"step": np.array([0], np.int32), "neuron_idx": np.array([1], np.int32),
|
||||
"counts": np.array([0, 1], np.int32), "v": np.zeros(2, np.float64),
|
||||
"g": np.zeros(2, np.float64), "ref": np.zeros(2, np.int32)}
|
||||
validate_state(state, 2, 10)
|
||||
for mutation in ("nan", "counts", "duplicate", "wrong_dtype"):
|
||||
bad = copy.deepcopy(state)
|
||||
if mutation == "nan": bad["g"][0] = np.nan
|
||||
if mutation == "counts": bad["counts"][0] = 1
|
||||
if mutation == "duplicate":
|
||||
bad["step"] = np.array([0, 0], np.int32)
|
||||
bad["neuron_idx"] = np.array([1, 1], np.int32)
|
||||
bad["counts"][1] = 2
|
||||
if mutation == "wrong_dtype": bad["v"] = bad["v"].astype(np.float32)
|
||||
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
||||
validate_state(bad, 2, 10)
|
||||
|
||||
def test_inventory_rejects_missing_duplicate_or_heldout_identity(self):
|
||||
odors = sorted(ORIGINAL_CAL_IDS)
|
||||
records = [{"name": f"cal-{mode}-{index:02d}-s{seed}", "mode": mode, "odor_index": index,
|
||||
"odor_id": odors[index], "seed": seed} for mode in ORIGINAL_MODES
|
||||
for index in range(6) for seed in ORIGINAL_SEEDS]
|
||||
cfg = {"calibration": {"modes": list(ORIGINAL_MODES), "seeds": list(ORIGINAL_SEEDS),
|
||||
"duration_ms": 100., "odor_count": 6, "split": "calibration", "repeat_first_seed_each_odor": True}, "gates": GATES}
|
||||
self.assertEqual(validate_inventory(records, cfg), odors)
|
||||
for malformed in (records[:-1], records[:-1] + [records[0]],
|
||||
[dict(row, odor_id="heldout") if row["odor_index"] == 0 else row for row in records]):
|
||||
with self.assertRaises(ValueError):
|
||||
validate_inventory(malformed, cfg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,266 @@
|
||||
"""Local/remote pure protocol tests; no Brian, Torch, GPU or network execution.
|
||||
|
||||
Run with the already-provisioned Python and ``-B test_protocol.py``. The
|
||||
sidecar process is mocked with in-memory stdin and an anonymous stdout pipe.
|
||||
These tests validate transport and declared arithmetic/input contracts, not
|
||||
the scientific trajectory or independent correctness of the Torch component.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
import threading
|
||||
import types
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def load_no_cache(path, name):
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(path.read_bytes(), str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
ORACLE = load_no_cache(HERE / "oracle_apl.py", "phase4ag_protocol_oracle")
|
||||
|
||||
|
||||
def handshake(**updates):
|
||||
result = {"ok": True, "protocol": "phase4ag-apl-v1", "n_neurons": 166700,
|
||||
"n_apl": 2, "n_kc": 4064, "dt_ms": .1,
|
||||
"files_sha256": {name: "frozen-test-hash" for name in ORACLE.BUNDLE_FILES},
|
||||
"decay_f32_bits": f"{int(np.float32(math.exp(-.1 / 20.)).view(np.uint32)):08x}",
|
||||
"gain_f32_bits": f"{int(np.float32(.01).view(np.uint32)):08x}"}
|
||||
result.update(updates)
|
||||
return (json.dumps(result) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def frame(tick=0, values=None, byte_count=None):
|
||||
values = np.arange(4068, dtype="<f4") if values is None else np.asarray(values, dtype="<f4")
|
||||
count = values.nbytes if byte_count is None else byte_count
|
||||
return struct.pack("<qQ", tick, count) + values.tobytes()
|
||||
|
||||
|
||||
class MemoryInput(io.BytesIO):
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
self.closed_data = self.getvalue()
|
||||
super().close()
|
||||
|
||||
|
||||
class FakeProcess:
|
||||
"""The only use of OS descriptors is an anonymous read-only response pipe."""
|
||||
def __init__(self, response, status=0, chunk_size=97):
|
||||
read_fd, write_fd = os.pipe()
|
||||
self.stdin = MemoryInput()
|
||||
self.stdout = os.fdopen(read_fd, "rb", buffering=0)
|
||||
self.status = status
|
||||
self.terminated = False
|
||||
|
||||
def write_response():
|
||||
try:
|
||||
for begin in range(0, len(response), chunk_size):
|
||||
view = memoryview(response[begin:begin + chunk_size])
|
||||
while view:
|
||||
view = view[os.write(write_fd, view):]
|
||||
except (BrokenPipeError, OSError):
|
||||
# Invalid-response tests deliberately close the reader early.
|
||||
pass
|
||||
finally:
|
||||
os.close(write_fd)
|
||||
|
||||
self.thread = threading.Thread(target=write_response, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def poll(self):
|
||||
return self.status
|
||||
|
||||
def wait(self, timeout=None):
|
||||
return self.status
|
||||
|
||||
def terminate(self):
|
||||
self.terminated = True
|
||||
|
||||
def kill(self):
|
||||
self.terminated = True
|
||||
|
||||
def close(self):
|
||||
if not self.stdin.closed:
|
||||
self.stdin.close()
|
||||
if not self.stdout.closed:
|
||||
self.stdout.close()
|
||||
self.thread.join(timeout=2)
|
||||
|
||||
|
||||
class ProtocolTests(unittest.TestCase):
|
||||
def create_worker(self, response, status=0):
|
||||
process = FakeProcess(response, status=status)
|
||||
self.addCleanup(process.close)
|
||||
source_patch = mock.patch.object(ORACLE, "_digest", return_value="frozen-test-hash")
|
||||
process_patch = mock.patch.object(ORACLE.subprocess, "Popen", return_value=process)
|
||||
source_patch.start()
|
||||
process_mock = process_patch.start()
|
||||
self.addCleanup(source_patch.stop)
|
||||
self.addCleanup(process_patch.stop)
|
||||
worker = ORACLE._APLWorker(Path("science-python"), Path("legacy_apl_worker.py"), Path("bundle"), None, timeout=2)
|
||||
self.addCleanup(worker.abort)
|
||||
return worker, process, process_mock
|
||||
|
||||
def test_exact_handshake_frame_and_clean_eof(self):
|
||||
worker, process, process_mock = self.create_worker(handshake() + frame())
|
||||
globals_ = np.array([1, 40, 166699], dtype=np.int32)
|
||||
drive, state, inhibition = worker.step(0, globals_)
|
||||
values = np.arange(4068, dtype=np.float32)
|
||||
np.testing.assert_array_equal(drive, values[:2])
|
||||
np.testing.assert_array_equal(state, values[2:4])
|
||||
np.testing.assert_array_equal(inhibition, values[4:])
|
||||
request = process.stdin.getvalue()
|
||||
self.assertEqual(struct.unpack("<qQ", request[:16]), (0, 3))
|
||||
np.testing.assert_array_equal(np.frombuffer(request[16:], dtype="<i4"), globals_)
|
||||
self.assertEqual(worker.finish(1), 0)
|
||||
command = process_mock.call_args.args[0]
|
||||
self.assertEqual(command, ["science-python", "-B", "legacy_apl_worker.py", "--bundle-dir", "bundle", "--dt-ms", "0.1"])
|
||||
self.assertFalse(process_mock.call_args.kwargs["shell"])
|
||||
|
||||
def test_empty_spike_frame_still_advances_one_tick(self):
|
||||
worker, process, _ = self.create_worker(handshake() + frame(values=np.zeros(4068, np.float32)))
|
||||
arrays = worker.step(0, np.empty(0, np.int32))
|
||||
self.assertTrue(all(np.all(value == 0) for value in arrays))
|
||||
self.assertEqual(process.stdin.getvalue(), struct.pack("<qQ", 0, 0))
|
||||
self.assertEqual(worker.finish(1), 0)
|
||||
|
||||
def test_two_consecutive_ticks(self):
|
||||
worker, _, _ = self.create_worker(handshake() + frame(0) + frame(1))
|
||||
worker.step(0, np.array([4], np.int32))
|
||||
worker.step(1, np.array([8, 10], np.int32))
|
||||
self.assertEqual(worker.finish(2), 0)
|
||||
|
||||
def test_rejects_wrong_protocol_or_dimensions(self):
|
||||
for update in ({"protocol": "other"}, {"ok": False}, {"n_neurons": 42},
|
||||
{"n_kc": 3}, {"dt_ms": .2}, {"gain_f32_bits": "00000000"},
|
||||
{"files_sha256": {}}):
|
||||
with self.subTest(update=update):
|
||||
with self.assertRaises(ValueError):
|
||||
self.create_worker(handshake(**update))
|
||||
|
||||
def test_rejects_wrong_tick_or_payload_length(self):
|
||||
for response in (frame(tick=1), frame(byte_count=8)):
|
||||
with self.subTest(response_header=response[:16]):
|
||||
worker, _, _ = self.create_worker(handshake() + response)
|
||||
with self.assertRaises(ValueError):
|
||||
worker.step(0, np.empty(0, np.int32))
|
||||
|
||||
def test_rejects_truncated_response(self):
|
||||
worker, _, _ = self.create_worker(handshake() + frame()[:-1])
|
||||
with self.assertRaises(EOFError):
|
||||
worker.step(0, np.empty(0, np.int32))
|
||||
|
||||
def test_rejects_nonfinite_or_negative_apl_payload(self):
|
||||
for bad_value in (np.nan, np.inf, -1):
|
||||
with self.subTest(value=bad_value):
|
||||
values = np.zeros(4068, np.float32)
|
||||
values[-1] = bad_value
|
||||
worker, _, _ = self.create_worker(handshake() + frame(values=values))
|
||||
with self.assertRaises(ValueError):
|
||||
worker.step(0, np.empty(0, np.int32))
|
||||
|
||||
def test_rejects_invalid_spike_dtype_order_and_range(self):
|
||||
for values in (np.array([1], np.int64), np.array([2, 1], np.int32),
|
||||
np.array([1, 1], np.int32), np.array([-1], np.int32),
|
||||
np.array([166700], np.int32)):
|
||||
with self.subTest(values=values):
|
||||
worker, process, _ = self.create_worker(handshake() + frame())
|
||||
with self.assertRaises(ValueError):
|
||||
worker.step(0, values)
|
||||
self.assertEqual(process.stdin.getvalue(), b"")
|
||||
|
||||
def test_rejects_skipped_request_tick(self):
|
||||
worker, process, _ = self.create_worker(handshake() + frame())
|
||||
with self.assertRaises(ValueError):
|
||||
worker.step(1, np.empty(0, np.int32))
|
||||
self.assertEqual(process.stdin.getvalue(), b"")
|
||||
|
||||
def test_rejects_failed_exit_and_trailing_stdout(self):
|
||||
for suffix, status in ((b"", 7), (b"unexpected", 0)):
|
||||
with self.subTest(status=status, suffix=suffix):
|
||||
worker, _, _ = self.create_worker(handshake() + frame() + suffix, status=status)
|
||||
worker.step(0, np.empty(0, np.int32))
|
||||
with self.assertRaises(ValueError):
|
||||
worker.finish(1)
|
||||
|
||||
def test_pipe_reader_timeout(self):
|
||||
read_fd, write_fd = os.pipe()
|
||||
stream = os.fdopen(read_fd, "rb", buffering=0)
|
||||
reader = ORACLE._PipeReader(stream, .05)
|
||||
try:
|
||||
with self.assertRaises(TimeoutError):
|
||||
reader.read(1)
|
||||
finally:
|
||||
os.close(write_fd)
|
||||
reader.thread.join(timeout=2)
|
||||
stream.close()
|
||||
self.assertFalse(reader.thread.is_alive())
|
||||
|
||||
|
||||
class ArithmeticAndIdentityContractTests(unittest.TestCase):
|
||||
def test_si_conversion_must_follow_float64_promotion(self):
|
||||
h32 = np.array([.01, 1 / 7, 123.456], dtype=np.float32)
|
||||
correct = h32.astype(np.float64) * np.float64(.001)
|
||||
early_conversion = (h32 * np.float32(.001)).astype(np.float64)
|
||||
self.assertFalse(np.array_equal(correct.view(np.uint64), early_conversion.view(np.uint64)))
|
||||
for actual, original in zip(correct, h32):
|
||||
self.assertEqual(actual.hex(), (float(original) * float(.001)).hex())
|
||||
|
||||
def test_declared_overlay_does_not_guard_refractory_targets(self):
|
||||
before = np.array([0, -.002, .001], dtype=np.float64)
|
||||
refractory = np.array([22, 21, 0], dtype=np.int32)
|
||||
h32 = np.array([1, 2, 3], dtype=np.float32)
|
||||
delta = h32.astype(np.float64) * np.float64(.001)
|
||||
after = before - delta
|
||||
self.assertTrue(np.all(after != before))
|
||||
np.testing.assert_array_equal(refractory, [22, 21, 0])
|
||||
self.assertEqual(after[0], -.001)
|
||||
|
||||
def test_af_queue_hash_keeps_original_noncontiguous_ids_and_rejections(self):
|
||||
path = HERE.parent / "author-fullgraph-lab-v1" / "oracle_full.py"
|
||||
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), ORACLE.AF_SHA256)
|
||||
af = load_no_cache(path, "phase4ag_protocol_af")
|
||||
edges = np.array([81, 2, 500], dtype=np.int64)
|
||||
accepted = np.array([1, 0, 1], dtype=np.uint8)
|
||||
original_edges, original_mask = edges.copy(), accepted.copy()
|
||||
result = af.queue_summary(18, edges, accepted, chunk_bytes=8)
|
||||
packet = b"phase4af-queue-v1\0" + struct.pack("<qq", 18, 3) + edges.astype("<i8").tobytes() + accepted.tobytes()
|
||||
self.assertEqual(result["joint_sha256"], hashlib.sha256(packet).hexdigest())
|
||||
self.assertEqual((result["count"], result["accepted_count"]), (3, 2))
|
||||
self.assertEqual(result, af.queue_summary(18, edges, accepted, chunk_bytes=8192))
|
||||
np.testing.assert_array_equal(edges, original_edges)
|
||||
np.testing.assert_array_equal(accepted, original_mask)
|
||||
|
||||
def test_empty_queue_hash_includes_tick(self):
|
||||
af = load_no_cache(HERE.parent / "author-fullgraph-lab-v1" / "oracle_full.py", "phase4ag_empty_af")
|
||||
first = af.queue_summary(0, np.empty(0, np.int64), np.empty(0, np.uint8))
|
||||
second = af.queue_summary(1, np.empty(0, np.int64), np.empty(0, np.uint8))
|
||||
self.assertEqual(first["ordered_edge_sha256"], hashlib.sha256(b"").hexdigest())
|
||||
self.assertNotEqual(first["joint_sha256"], second["joint_sha256"])
|
||||
|
||||
def test_oracle_import_has_no_brian_or_torch_dependency(self):
|
||||
before = set(sys.modules)
|
||||
load_no_cache(HERE / "oracle_apl.py", "phase4ag_second_import")
|
||||
added = set(sys.modules) - before
|
||||
self.assertFalse(any(name == "brian2" or name.startswith("brian2.") or
|
||||
name == "torch" or name.startswith("torch.") for name in added))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -0,0 +1,431 @@
|
||||
"""Independent extraction/replay of the frozen legacy APL component.
|
||||
|
||||
Only the initializer prefix before ``self.base`` and two original APL update
|
||||
statements are executed. No old simulator is instantiated, no learning code is
|
||||
executed, and no frozen artifact is written. The Torch replay is a component
|
||||
oracle, not an independent CPU whole-brain closed-loop oracle.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import copy
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
import pyarrow.parquet as pq
|
||||
import torch
|
||||
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
CANONICAL = ROOT/'datasets/male-cns-v1.0/processed/neuron-only'
|
||||
SOURCE = ROOT/'models/male-cns-lif/deterministic-runtime-v2/phase4p_revalidate.py'
|
||||
SOURCE_SHA256 = 'c7e693a2da70acdce4f53af5c2853e52e8333acc93f88a46a5e8083eac639346'
|
||||
N, A, K = 166700, 2, 4064
|
||||
BUNDLE_ARRAYS = {
|
||||
'input_matrix': (np.float32, (A, N)), 'output_matrix': (np.float32, (A, K)),
|
||||
'input_raw': (np.float32, (A, N)), 'output_raw': (np.float32, (A, K)),
|
||||
'kc_globals': (np.int32, (K,)), 'apl_globals': (np.int32, (A,)),
|
||||
'pn_globals': (np.int32, None), 'apl_out_ids': (np.int64, (5845,)),
|
||||
}
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def digest(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def array_hash(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def bit_equal(a, b):
|
||||
a, b = np.asarray(a), np.asarray(b)
|
||||
return bool(a.dtype == b.dtype and a.shape == b.shape and
|
||||
np.array_equal(np.ascontiguousarray(a).view(np.uint8),
|
||||
np.ascontiguousarray(b).view(np.uint8)))
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as target:
|
||||
json.dump(value, target, indent=2, sort_keys=True, allow_nan=False)
|
||||
target.write('\n')
|
||||
|
||||
|
||||
class ReadOnlyNamespace:
|
||||
"""The extracted prefix can read rt, but cannot mutate or add rt fields."""
|
||||
def __init__(self, **values):
|
||||
object.__setattr__(self, '_values', types.MappingProxyType(values))
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._values[name]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError('Read-only canonical runtime view')
|
||||
|
||||
|
||||
def _code(nodes, filename):
|
||||
module = ast.Module(body=copy.deepcopy(nodes), type_ignores=[])
|
||||
return compile(ast.fix_missing_locations(module), str(filename), 'exec')
|
||||
|
||||
|
||||
def _self_attribute(target, field):
|
||||
return isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and \
|
||||
target.value.id == 'self' and target.attr == field
|
||||
|
||||
|
||||
def extract_statements():
|
||||
data = SOURCE.read_bytes()
|
||||
require(hashlib.sha256(data).hexdigest() == SOURCE_SHA256, 'Frozen legacy source SHA-256 mismatch')
|
||||
text = data.decode('utf-8')
|
||||
tree = ast.parse(text, filename=str(SOURCE))
|
||||
classes = [node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == 'DeterministicAPLSim']
|
||||
require(len(classes) == 1, 'Expected exactly one frozen DeterministicAPLSim')
|
||||
methods = {node.name: node for node in classes[0].body if isinstance(node, ast.FunctionDef)}
|
||||
init, run = methods['__init__'], methods['run']
|
||||
cuts = [i for i, node in enumerate(init.body) if isinstance(node, ast.Assign) and
|
||||
any(_self_attribute(target, 'base') for target in node.targets)]
|
||||
require(len(cuts) == 1 and cuts[0] > 0, 'Missing/ambiguous self.base cutoff')
|
||||
prefix = init.body[:cuts[0]]
|
||||
# Executing no import or file-writing call is allowed in the extracted prefix.
|
||||
require(not any(isinstance(node, (ast.Import, ast.ImportFrom, ast.With, ast.AsyncWith))
|
||||
for stmt in prefix for node in ast.walk(stmt)), 'Unexpected side-effect construct in initializer prefix')
|
||||
expressions = [node for node in ast.walk(run) if isinstance(node, ast.Expr) and
|
||||
isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Attribute) and
|
||||
node.value.func.attr == 'add_' and isinstance(node.value.func.value, ast.Call) and
|
||||
isinstance(node.value.func.value.func, ast.Attribute) and
|
||||
isinstance(node.value.func.value.func.value, ast.Name) and
|
||||
node.value.func.value.func.value.id == 'apl_state' and node.value.func.value.func.attr == 'mul_']
|
||||
inhibition = [node for node in ast.walk(run) if isinstance(node, ast.Assign) and
|
||||
any(isinstance(target, ast.Name) and target.id == 'inhibition' for target in node.targets)]
|
||||
decay = [node for node in ast.walk(run) if isinstance(node, ast.Assign) and
|
||||
any(isinstance(target, ast.Name) and target.id == 'apl_decay' for target in node.targets)]
|
||||
require(len(expressions) == len(inhibition) == len(decay) == 1, 'Ambiguous frozen APL operator extraction')
|
||||
require(expressions[0].lineno < inhibition[0].lineno, 'Unexpected frozen APL update order')
|
||||
rhs = inhibition[0].value
|
||||
require(isinstance(rhs, ast.BinOp) and isinstance(rhs.op, ast.Mult), 'Unexpected inhibition gain expression')
|
||||
gain_expression = ast.Expression(copy.deepcopy(rhs.right))
|
||||
provenance = {'source': str(SOURCE), 'sha256': SOURCE_SHA256,
|
||||
'initializer_line_start': prefix[0].lineno, 'initializer_line_end': prefix[-1].end_lineno,
|
||||
'excluded_self_base_line': init.body[cuts[0]].lineno,
|
||||
'decay_line': decay[0].lineno, 'state_update_line': expressions[0].lineno,
|
||||
'inhibition_line': inhibition[0].lineno,
|
||||
'initializer_source': '\n'.join(ast.get_source_segment(text, node) for node in prefix),
|
||||
'decay_source': ast.get_source_segment(text, decay[0]),
|
||||
'state_update_source': ast.get_source_segment(text, expressions[0]),
|
||||
'inhibition_source': ast.get_source_segment(text, inhibition[0]),
|
||||
'learning_initializer_suffix_executed': False, 'original_run_loop_executed': False}
|
||||
return (_code(prefix, SOURCE), _code(decay, SOURCE),
|
||||
_code([expressions[0], inhibition[0]], SOURCE),
|
||||
compile(ast.fix_missing_locations(gain_expression), str(SOURCE), 'eval'), provenance)
|
||||
|
||||
|
||||
def _canonical_files():
|
||||
return {name: CANONICAL/path for name, path in {
|
||||
'metadata': 'neuron-index/unified-metadata.parquet',
|
||||
'indptr': 'gpu/csr-indptr.int64.npy', 'indices': 'gpu/csr-indices.int32.npy',
|
||||
'raw': 'gpu/csr-weights.int64.npy'}.items()}
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def reconstruct(canonical_hashes, device_name):
|
||||
"""Cache only reconstructions tied to exact canonical bytes and source hash."""
|
||||
files = _canonical_files()
|
||||
require(dict(canonical_hashes) == {name: digest(path) for name, path in files.items()},
|
||||
'Canonical bytes changed before matrix reconstruction')
|
||||
arrays = {name: np.load(files[name], mmap_mode='r', allow_pickle=False)
|
||||
for name in ('indptr', 'indices', 'raw')}
|
||||
require(arrays['indptr'].dtype == np.int64 and arrays['indices'].dtype == np.int32 and
|
||||
arrays['raw'].dtype == np.int64, 'Canonical CSR dtypes changed')
|
||||
require(arrays['indptr'].shape == (N + 1,) and arrays['indptr'][-1] == 25582938 and
|
||||
arrays['indices'].shape == arrays['raw'].shape == (25582938,), 'Canonical graph shape changed')
|
||||
require(int(arrays['raw'].sum()) == 124177617 and bool(np.all(arrays['raw'] > 0)), 'Canonical raw weights changed')
|
||||
table = pq.read_table(files['metadata'])
|
||||
require(table.num_rows == N, 'Canonical metadata count changed')
|
||||
rt = ReadOnlyNamespace(n=N, dt=.1, device=torch.device(device_name), table=table,
|
||||
indptr_np=arrays['indptr'], indices_np=arrays['indices'], raw_np=arrays['raw'])
|
||||
view = types.SimpleNamespace()
|
||||
init_code, decay_code, update_code, gain_code, provenance = extract_statements()
|
||||
scope = {'self': view, 'rt': rt, 'np': np, 'torch': torch}
|
||||
exec(init_code, scope)
|
||||
require(not hasattr(view, 'base') and not hasattr(view, 'identity'), 'Initializer crossed the learning cutoff')
|
||||
expected = {'input_matrix': view.input_gpu.detach().cpu().numpy().copy(),
|
||||
'output_matrix': view.output_gpu.detach().cpu().numpy().copy(),
|
||||
'input_raw': scope['input_matrix'], 'output_raw': scope['output_matrix'],
|
||||
'kc_globals': view.kc, 'apl_globals': view.apl, 'pn_globals': view.pn,
|
||||
'apl_out_ids': view.apl_out_ids}
|
||||
for value in expected.values():
|
||||
value.flags.writeable = False
|
||||
return view, expected, provenance, (decay_code, update_code, gain_code)
|
||||
|
||||
|
||||
class TorchObservationProxy:
|
||||
"""Observe the result of the actual extracted torch.mv call unchanged."""
|
||||
def __init__(self):
|
||||
self.drive = None
|
||||
self.calls = 0
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(torch, name)
|
||||
|
||||
def mv(self, *args, **kwargs):
|
||||
value = torch.mv(*args, **kwargs)
|
||||
self.drive = value
|
||||
self.calls += 1
|
||||
return value
|
||||
|
||||
|
||||
def _up(value):
|
||||
return np.nextafter(np.asarray(value, dtype=np.float64), np.inf)
|
||||
|
||||
|
||||
def _add(a, b):
|
||||
return _up(np.asarray(a, dtype=np.float64) + np.asarray(b, dtype=np.float64))
|
||||
|
||||
|
||||
def _mul(a, b):
|
||||
return _up(np.asarray(a, dtype=np.float64) * np.asarray(b, dtype=np.float64))
|
||||
|
||||
|
||||
def _gamma(count, unit_roundoff):
|
||||
require(count * unit_roundoff < 1, 'Gamma bound is undefined')
|
||||
# Outward rounding on the division; the denominator is rounded downward.
|
||||
numerator = _mul(count, unit_roundoff)
|
||||
denominator = np.nextafter(1.0 - numerator, -np.inf)
|
||||
return _up(numerator / denominator)
|
||||
|
||||
|
||||
def _positive_dot_upper(state, matrix):
|
||||
result = np.zeros(matrix.shape[1], np.float64)
|
||||
for index in range(len(state)):
|
||||
result = _add(result, _mul(state[index], matrix[index]))
|
||||
return result
|
||||
|
||||
|
||||
def _normal_or_zero(value, tiny):
|
||||
value = np.asarray(value)
|
||||
return bool(np.all((value == 0) | (value >= tiny)))
|
||||
|
||||
|
||||
def _new_metric():
|
||||
return {'max_abs_error': 0.0, 'sum_abs_error': 0.0, 'elements': 0,
|
||||
'max_relative_error_nonzero_reference': 0.0, 'zero_reference_nonzero_actual': 0,
|
||||
'max_error_bound': 0.0, 'max_bound_utilization': 0.0, 'bound_violations': 0}
|
||||
|
||||
|
||||
def _accumulate(metric, actual, reference, bound):
|
||||
error = np.abs(np.asarray(actual, np.float64) - reference)
|
||||
error_upper = np.where(error == 0, 0.0, _up(error))
|
||||
nonzero = reference != 0
|
||||
ratio = np.divide(error, np.abs(reference), out=np.zeros_like(error), where=nonzero)
|
||||
usage = np.divide(error_upper, bound, out=np.zeros_like(error), where=bound > 0)
|
||||
metric['max_abs_error'] = max(metric['max_abs_error'], float(error.max(initial=0)))
|
||||
metric['sum_abs_error'] += float(error.sum(dtype=np.float64))
|
||||
metric['elements'] += error.size
|
||||
metric['max_relative_error_nonzero_reference'] = max(metric['max_relative_error_nonzero_reference'], float(ratio.max(initial=0)))
|
||||
metric['zero_reference_nonzero_actual'] += int(np.count_nonzero((~nonzero) & (error != 0)))
|
||||
metric['max_error_bound'] = max(metric['max_error_bound'], float(np.max(bound, initial=0)))
|
||||
metric['max_bound_utilization'] = max(metric['max_bound_utilization'], float(usage.max(initial=0)))
|
||||
metric['bound_violations'] += int(np.count_nonzero(error_upper > bound))
|
||||
|
||||
|
||||
def validate(gpu_case_dir, bundle_dir, out_json):
|
||||
"""Validate one completed fixture; write one exclusive pure-JSON audit."""
|
||||
gpu_case_dir, bundle_dir, out_json = Path(gpu_case_dir), Path(bundle_dir), Path(out_json)
|
||||
require(not out_json.exists(), 'Refusing to overwrite an existing component audit')
|
||||
report = {'pass_all': False, 'scope': 'Frozen APL component extraction and recorded-spike replay only',
|
||||
'independent_cpu_closed_loop_oracle': False, 'brain_or_learning_parameters_changed': False,
|
||||
'candidate_function_called': False, 'gpu_case_dir': str(gpu_case_dir), 'bundle_dir': str(bundle_dir)}
|
||||
try:
|
||||
require(digest(SOURCE) == SOURCE_SHA256, 'Frozen source changed before component validation')
|
||||
require(torch.cuda.is_available() and torch.cuda.device_count() == 1 and
|
||||
'4090' in torch.cuda.get_device_name(), 'Require the one declared RTX4090')
|
||||
environment = {'torch': str(torch.__version__), 'numpy': np.__version__,
|
||||
'gpu': torch.cuda.get_device_name(), 'cuda': torch.version.cuda,
|
||||
'cuda_matmul_allow_tf32': torch.backends.cuda.matmul.allow_tf32,
|
||||
'float32_matmul_precision': torch.get_float32_matmul_precision(),
|
||||
'deterministic_algorithms': torch.are_deterministic_algorithms_enabled(),
|
||||
'precision_flags_changed': False}
|
||||
report['environment'] = environment
|
||||
canonical_hashes = tuple(sorted((name, digest(path)) for name, path in _canonical_files().items()))
|
||||
view, reconstructed, provenance, code = reconstruct(canonical_hashes, 'cuda:0')
|
||||
bundle = {}
|
||||
matrix_checks = {}
|
||||
for name, (dtype, shape) in BUNDLE_ARRAYS.items():
|
||||
value = np.load(bundle_dir/(name+'.npy'), mmap_mode='r', allow_pickle=False)
|
||||
require(value.dtype == dtype and value.ndim == len(shape or (0,)) and
|
||||
(shape is None or value.shape == shape), 'Bundle schema mismatch: '+name)
|
||||
bundle[name] = value
|
||||
matrix_checks[name] = bit_equal(value, reconstructed[name])
|
||||
scales = json.loads((bundle_dir/'scales.json').read_text())
|
||||
for name in ('input_scale', 'output_scale'):
|
||||
matrix_checks[name] = struct.pack('<d', float(scales[name])) == struct.pack('<d', float(getattr(view, name)))
|
||||
report['matrix_checks'] = matrix_checks
|
||||
report['extracted_source'] = provenance
|
||||
report['canonical_sha256'] = dict(canonical_hashes)
|
||||
report['bundle_sha256'] = {name+'.npy': digest(bundle_dir/(name+'.npy')) for name in BUNDLE_ARRAYS}
|
||||
report['bundle_sha256']['scales.json'] = digest(bundle_dir/'scales.json')
|
||||
report['reconstructed_array_sha256'] = {name: array_hash(value) for name, value in reconstructed.items()}
|
||||
bundle_provenance = json.loads((bundle_dir/'provenance.json').read_text())
|
||||
require(Path(bundle_provenance['canonical_root']).resolve() == CANONICAL.resolve(),
|
||||
'Bundle provenance names another canonical dataset')
|
||||
report['bundle_sha256']['provenance.json'] = digest(bundle_dir/'provenance.json')
|
||||
require(all(matrix_checks.values()), 'Bundle differs from the actual frozen initializer prefix')
|
||||
|
||||
traces = {name: np.load(gpu_case_dir/('apl-'+name+'.float32.npy'), mmap_mode='r', allow_pickle=False)
|
||||
for name in ('drive', 'state', 'inhibition')}
|
||||
steps = len(traces['state'])
|
||||
require(steps > 0, 'Empty APL trajectory cannot pass')
|
||||
for name, width in (('drive', A), ('state', A), ('inhibition', K)):
|
||||
require(traces[name].dtype == np.float32 and traces[name].shape == (steps, width) and
|
||||
bool(np.isfinite(traces[name]).all()) and bool(np.all(traces[name] >= 0)), 'Invalid APL trace: '+name)
|
||||
summary = json.loads((gpu_case_dir/'summary.json').read_text())
|
||||
require(summary.get('complete') is True and summary.get('pass_all') is True and
|
||||
summary.get('steps') == steps and summary.get('completed_ticks') == steps,
|
||||
'GPU fixture did not complete its internal execution checks')
|
||||
with np.load(gpu_case_dir/'state.npz', allow_pickle=False) as archive:
|
||||
event_step, event_node = archive['step'].copy(), archive['neuron_idx'].copy()
|
||||
require(event_step.dtype == event_node.dtype == np.int32 and event_step.ndim == 1 and
|
||||
event_step.shape == event_node.shape, 'Invalid sparse actual spike event schema')
|
||||
require(bool(np.all((event_step >= 0) & (event_step < steps))) and
|
||||
bool(np.all((event_node >= 0) & (event_node < N))), 'Sparse event outside its clock/identity range')
|
||||
require(np.array_equal(np.lexsort((event_node, event_step)), np.arange(len(event_step))), 'Actual events not sorted')
|
||||
keys = event_step.astype(np.int64)*N + event_node
|
||||
require(len(keys) < 2 or bool(np.all(np.diff(keys) > 0)), 'Duplicate actual spikes')
|
||||
boundaries = np.searchsorted(event_step, np.arange(steps + 1))
|
||||
|
||||
proxy = TorchObservationProxy()
|
||||
# Reconstructed original matrices are already on the original GPU/dtype.
|
||||
replay_rt = types.SimpleNamespace(spike_u8=torch.zeros(N, dtype=torch.uint8, device=view.device))
|
||||
shadow = types.SimpleNamespace(input_gpu=view.input_gpu, output_gpu=view.output_gpu, rt=replay_rt, dt=.1)
|
||||
scope = {'self': shadow, 'np': np, 'math': math, 'torch': proxy,
|
||||
'apl_state': torch.zeros(A, dtype=torch.float32, device=view.device)}
|
||||
decay_code, update_code, gain_code = code
|
||||
exec(decay_code, scope)
|
||||
decay32 = np.float32(scope['apl_decay'])
|
||||
gain32 = np.float32(eval(gain_code, {'np': np}))
|
||||
require(0 < decay32 < 1 and gain32 > 0, 'Invalid extracted positive APL constants')
|
||||
exact = {name: True for name in traces}
|
||||
first_difference = None
|
||||
I64 = np.asarray(bundle['input_matrix'], np.float64)
|
||||
O64 = np.asarray(bundle['output_matrix'], np.float64)
|
||||
require(bool(np.all(I64 >= 0)) and bool(np.all(O64 >= 0)), 'Positive-sum theory requires nonnegative matrices')
|
||||
d, q = float(decay32), float(gain32)
|
||||
u32, u64 = 2.0**-24, 2.0**-53
|
||||
gd32, gd64 = _gamma(N-1, u32), _gamma(N-1, u64)
|
||||
g232, g264 = _gamma(2, u32), _gamma(2, u64)
|
||||
gh32, gh64 = _gamma(2*A+1, u32), _gamma(2*A+1, u64)
|
||||
state64 = np.zeros(A, np.float64)
|
||||
e32 = np.zeros(A, np.float64)
|
||||
e64 = np.zeros(A, np.float64)
|
||||
previous32 = np.zeros(A, np.float64)
|
||||
statistics = {name: _new_metric() for name in traces}
|
||||
normal_arithmetic = _normal_or_zero(I64, np.finfo(np.float32).tiny) and _normal_or_zero(O64, np.finfo(np.float32).tiny)
|
||||
max_active_terms = 0
|
||||
with torch.no_grad():
|
||||
for tick in range(steps):
|
||||
fired = event_node[boundaries[tick]:boundaries[tick+1]]
|
||||
replay_rt.spike_u8.zero_()
|
||||
if len(fired):
|
||||
replay_rt.spike_u8[torch.as_tensor(fired.astype(np.int64), device=view.device)] = 1
|
||||
before_calls = proxy.calls
|
||||
exec(update_code, scope)
|
||||
require(proxy.calls == before_calls + 1, 'Original update did not issue exactly one mv')
|
||||
actual = {'drive': proxy.drive.detach().cpu().numpy(),
|
||||
'state': scope['apl_state'].detach().cpu().numpy(),
|
||||
'inhibition': scope['inhibition'].detach().cpu().numpy()}
|
||||
for name in traces:
|
||||
same = bit_equal(actual[name], traces[name][tick])
|
||||
exact[name] &= same
|
||||
if not same and first_difference is None:
|
||||
first_difference = {'tick': tick, 'field': name,
|
||||
'max_abs_error': float(np.max(np.abs(actual[name].astype(np.float64)-traces[name][tick])))}
|
||||
# Float64 reference is measurement only and never feeds either brain.
|
||||
selected = I64[:, fired]
|
||||
drive64 = selected.sum(axis=1, dtype=np.float64)
|
||||
max_active_terms = max(max_active_terms, int(np.count_nonzero(selected, axis=1).max(initial=0)))
|
||||
Dupper = _up(drive64 / np.nextafter(1.0-gd64, -np.inf))
|
||||
drive_bound = _add(_mul(gd32, Dupper), _mul(gd64, Dupper))
|
||||
state32 = traces['state'][tick].astype(np.float64)
|
||||
normal_arithmetic &= _normal_or_zero(d*previous32, np.finfo(np.float32).tiny)
|
||||
normal_arithmetic &= _normal_or_zero(O64*state32[:, None], np.finfo(np.float32).tiny)
|
||||
e32 = _add(_mul(d, e32), _add(_mul(g232, _add(_mul(d, previous32), Dupper)),
|
||||
_mul(1.0+u32, _mul(gd32, Dupper))))
|
||||
e64 = _add(_mul(d, e64), _add(_mul(g264, _add(_mul(d, state64), Dupper)),
|
||||
_mul(1.0+u64, _mul(gd64, Dupper))))
|
||||
state64 = d*state64 + drive64
|
||||
inhibition64 = np.matmul(state64, O64)*q
|
||||
h32bound = _add(_mul(q, _positive_dot_upper(e32, O64)),
|
||||
_mul(gh32, _mul(q, _positive_dot_upper(state32, O64))))
|
||||
h64bound = _add(_mul(q, _positive_dot_upper(e64, O64)),
|
||||
_mul(gh64, _mul(q, _positive_dot_upper(state64, O64))))
|
||||
normal_arithmetic &= _normal_or_zero(q*np.matmul(state32, O64), np.finfo(np.float32).tiny)
|
||||
_accumulate(statistics['drive'], traces['drive'][tick], drive64, drive_bound)
|
||||
_accumulate(statistics['state'], traces['state'][tick], state64, _add(e32, e64))
|
||||
_accumulate(statistics['inhibition'], traces['inhibition'][tick], inhibition64, _add(h32bound, h64bound))
|
||||
previous32 = state32
|
||||
for metric in statistics.values():
|
||||
metric['mean_abs_error'] = metric.pop('sum_abs_error') / metric['elements']
|
||||
theory_applicable = bool(normal_arithmetic and not environment['cuda_matmul_allow_tf32'] and
|
||||
environment['float32_matmul_precision'] == 'highest')
|
||||
report.update(steps=steps, actual_spike_events=len(event_step),
|
||||
ast_replay_bitwise_exact=exact, first_ast_difference=first_difference,
|
||||
value_units={'drive': 'legacy normalized-contact spike drive',
|
||||
'state': 'legacy normalized-drive accumulator', 'inhibition': 'legacy mV'},
|
||||
decay_f32_bits=f'{int(decay32.view(np.uint32)):08x}', gain_f32_bits=f'{int(gain32.view(np.uint32)):08x}',
|
||||
float64_measurement=statistics,
|
||||
theoretical_bound={'applicable': theory_applicable, 'normal_or_zero_arithmetic': bool(normal_arithmetic),
|
||||
'gamma_definition': 'gamma_n=(n*u)/(1-n*u), upward-rounded; u32=2^-24,u64=2^-53',
|
||||
'drive_n': N-1, 'max_observed_nonzero_drive_terms': max_active_terms,
|
||||
'gamma_drive_f32': float(gd32), 'gamma_drive_f64': float(gd64),
|
||||
'state_recurrence': 'E_p[t] <= d*E_p[t-1] + gamma_2,p*(d*abs(s_p[t-1])+D_upper) + (1+u_p)*gamma_(N-1),p*D_upper',
|
||||
'output_bound': 'q*sum(B*E_p) + gamma_(2*A+1),p*q*sum(B*abs(s_p)); compare with E32+E64',
|
||||
'assumptions': 'nonnegative f32 matrices, spikes exactly 0/1, fixed stored f32 decay/gain, normal finite RN binary32/64 operations; no reduced-precision matmul',
|
||||
'bound_arithmetic': 'Each positive bound operation is rounded toward +infinity via nextafter; denominator toward -infinity',
|
||||
'tolerance_fitted_to_observed_error': False,
|
||||
'f64_values_used_to_drive_brain': False,
|
||||
'no_cpu_gpu_bitwise_expectation_for_f64_measurement': True},
|
||||
input_sha256={name: digest(gpu_case_dir/name) for name in
|
||||
('state.npz', 'summary.json', 'apl-drive.float32.npy', 'apl-state.float32.npy', 'apl-inhibition.float32.npy')})
|
||||
report['pass_all'] = bool(all(matrix_checks.values()) and all(exact.values()) and theory_applicable and
|
||||
all(value['bound_violations'] == 0 for value in statistics.values()))
|
||||
report['limitations'] = [
|
||||
'AST shadow and candidate use the same original Torch/CUDA primitives; this validates preserved component semantics, not an independent CPU whole-brain oracle.',
|
||||
'Float64 is a non-feedback mathematical measurement with explicit gamma error bounds, not replacement APL dynamics.',
|
||||
'Replay uses the recorded actual spike train; it does not establish equivalence for untested closed-loop inputs.',
|
||||
'The N-1 positive-sum bound is deliberately conservative; passing it does not prove physiological validity or learning.']
|
||||
save(out_json, report)
|
||||
return report
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
if not out_json.exists():
|
||||
save(out_json, report)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('gpu_case_dir', type=Path)
|
||||
parser.add_argument('bundle_dir', type=Path)
|
||||
parser.add_argument('out_json', type=Path)
|
||||
args = parser.parse_args()
|
||||
result = validate(args.gpu_case_dir, args.bundle_dir, args.out_json)
|
||||
print(json.dumps({'pass_all': result['pass_all'], 'steps': result['steps']}), flush=True)
|
||||
if not result['pass_all']:
|
||||
raise SystemExit(1)
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Run old reference unchanged, writing only new experiment output."""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import ROOT,EXP,CFG,save,check
|
||||
|
||||
|
||||
def main():
|
||||
check();assert not (EXP/'regression/frozen-five.json').exists()
|
||||
rtpath=ROOT/'models/male-cns-lif/deterministic-runtime-v2';pc=yaml.safe_load((rtpath/'phase4p.yaml').read_text())
|
||||
os.environ['CUDA_VISIBLE_DEVICES']=CFG['gpu_uuid'];os.environ['CUDA_HOME']='/usr/local/cuda'
|
||||
os.environ['PATH']=str(ROOT/'models/male-cns-lif/.venv/bin')+':/usr/local/cuda/bin:'+os.environ['PATH']
|
||||
os.environ['MAX_JOBS']='2';os.environ['TORCH_CUDA_ARCH_LIST']='8.9'
|
||||
sys.path.insert(0,str(ROOT/'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import queues
|
||||
queues();sys.path.insert(0,str(ROOT/'models/male-cns-lif'));sys.path.insert(0,str(rtpath))
|
||||
import torch,phase4p
|
||||
assert torch.cuda.mem_get_info()[0]>2*1024**3
|
||||
torch.cuda.reset_peak_memory_stats();t0=time.perf_counter();rt=phase4p.CanonicalRuntime(pc,rtpath,EXP);rows=[]
|
||||
for spec in pc['reference_trials']:
|
||||
queues();base=ROOT/'experiments/phase4p-deterministic-runtime/phase4c-v2/reference'/spec['name']
|
||||
ev=pd.read_parquet(base/'spike-events.parquet')
|
||||
ref=dict(step=ev.step.to_numpy(),neuron_idx=ev.neuron_idx.to_numpy(),counts=np.load(base/'per-neuron-spike-counts.int32.npy'),v=np.load(base/'final-voltage.float32.npy'),g=np.load(base/'final-g.float32.npy'))
|
||||
result=rt.run(np.flatnonzero(rt.pops[spec['population']]),spec['rate_hz'],spec['seed'],spec['duration_ms'])
|
||||
row=dict(trial=spec['name'],exact={k:bool(np.array_equal(v,result[k])) for k,v in ref.items()},wall_seconds=result['metrics']['wall_seconds'])
|
||||
rows.append(row);print(row,flush=True)
|
||||
save(EXP/'regression/frozen-five.json',dict(pass_all=all(all(r['exact'].values()) for r in rows),trials=rows,
|
||||
wall_seconds=time.perf_counter()-t0,peak_allocated_vram=int(torch.cuda.max_memory_allocated()),oracle='old frozen deterministic-v2 only'))
|
||||
assert all(all(r['exact'].values()) for r in rows)
|
||||
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,84 @@
|
||||
# Phase4AG: author LIF + frozen legacy graded APL
|
||||
|
||||
Execution revision r2: the first registered v1 attempt stopped before the first
|
||||
Brian tick because resolving the science-venv Python symlink launched system
|
||||
Python without NumPy. All v1 files and partial outputs are preserved separately.
|
||||
This revision retains the declared venv invocation path. It does not change any
|
||||
equation, arithmetic, mask, stimulus, gate, parameter or choice of trial.
|
||||
The complete original protocol is rerun from fresh state; no failed trial is
|
||||
overwritten or omitted, and the first version cannot be claimed numerical PASS.
|
||||
|
||||
Authorized independent hypothesis branch. All old source, raw connectome, signs,
|
||||
W_syn, APL calibration, eligibility-v2 and 14,232-edge mask remain read-only.
|
||||
No learning, gain/threshold changes, new edges, top-K, heldout activity, game or RL.
|
||||
|
||||
Author code provenance is the already archived MIT-licensed
|
||||
https://github.com/philshiu/Drosophila_brain_model at
|
||||
91bdd1e7dcf193f3e7ca5a8933497fcef63b7960; original model.py SHA-256
|
||||
fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e.
|
||||
Associated paper: https://www.nature.com/articles/s41586-024-07763-9.
|
||||
This new MaleCNS/APL combination is not a strict reproduction of that model.
|
||||
|
||||
The declared combination is author float64 LIF plus the exact old float32 graded
|
||||
APL operator after synapses/resets at end. All 5,845 original APL fast out-edges
|
||||
are zeroed only in the effective in-memory model, as in the legacy overlay.
|
||||
Only KC receives graded replacement; known 1,212 non-KC edges remain unreplaced.
|
||||
This existing scope limitation is not silently repaired in a compatibility test.
|
||||
|
||||
Use the original full normalized I[2,166700]/O[2,4064] float32 bytes and original
|
||||
normalizers, even for induced real subgraphs. APL reads actual binary spikes,
|
||||
never requests, and keeps independent float32 state. Preserve unfused operations:
|
||||
state.mul_(float(f32(exp(-.1/20)))).add_(torch.mv(I, spikes_f32));
|
||||
h32=torch.matmul(state,O)*f32(.01). Promote h32 to float64, multiply by float64
|
||||
.001, then separately subtract from SI g. Do not alter TF32/default CuBLAS flags;
|
||||
record them. No added APL delay, drive dt/tau scaling, truncation or clipping.
|
||||
|
||||
APL writes KC g unconditionally after author reset, including refractory KC.
|
||||
Author g integration is frozen during refractory; therefore negative overlay g
|
||||
may accumulate and differs from old dynamics. This is an explicit hypothesis,
|
||||
not an assertion of original author or measured physiology. Guarded writes or
|
||||
reset-before/after changes would be different hypotheses, not fixes to this one.
|
||||
|
||||
Eight fixed metadata/connectivity-selected fixtures cover zero/reset, real PN/KC
|
||||
drive, current actual event feedback, duplicate natural/request event, 18-step
|
||||
arrival vs22-step refractory boundary, full fast APL mask, non-KC scope and mixed
|
||||
precision/high fan-in. No choice is made from runtime responses. Each GPU case
|
||||
has3 independent fresh runs. A Brian2 author oracle starts from its own state,
|
||||
computes its own actual spikes, and uses an isolated standard-I/O legacy-APL
|
||||
worker with independent state. No candidate inhibition trace is fed into Brian.
|
||||
All tick states, events, requests, queue order/guard, APL drive/state/h must agree
|
||||
exactly. The APL component is shared, so this is NOT a fully independent CPU
|
||||
oracle. Independently reconstruct old matrices using frozen initializer AST,
|
||||
replay actual events through original update AST, require exact component bytes;
|
||||
also quantify an independent float64 positive-sum arithmetic error bound. It is
|
||||
not a replacement for exact legacy equivalence. No arithmetic bound is adjusted.
|
||||
|
||||
Only after all fixture gates pass, run two predeclared full-graph probes: first
|
||||
old calibration odor, first original seed, both encoder modes. Compare each with
|
||||
the independently running Brian + isolated APL worker. Save state hashes and sparse
|
||||
events, not a time-by-all-neurons dense spike matrix. If a numerical gate fails,
|
||||
stop CAL; keep diagnostic evidence and do not widen tolerance.
|
||||
|
||||
Then run the complete original6 CAL odors ×5 original seeds ×2 original modes,
|
||||
100ms per run. Retain the ORIGINAL12-odor median for matched-energy encoding,
|
||||
original pn_drive target/RNG column order, original evaluation seeds51101..51105.
|
||||
Save Poisson requests before any trajectory. Only calibration dynamic cases are
|
||||
used; formal A/B and heldout remain unrun. Repeat first seed for every odor/mode.
|
||||
No per-odor pulse, amplitude, timing or threshold search. No learning.
|
||||
|
||||
Recompute the full original4L representation criteria on CAL only: mean KC active
|
||||
fraction .03..09, min>0, max<.5, same-odor Jaccard>different, between Euclidean>
|
||||
within, >=3 pairs whose mean between exceeds THAT PAIR's pooled within distances,
|
||||
both modes. Add finiteness and exact repeat requirements. CAL PASS does not imply
|
||||
heldout generalization, timing/credit specificity, memory or learning usability.
|
||||
If CAL fails stop promotion and report it, not a least-bad candidate.
|
||||
|
||||
Before/after verify SHA-256 of all original230239 files and AA--AF seals; rerun original five
|
||||
deterministic reference trials to new output. Archive code/env/source SHA,
|
||||
matrices/norms/masks/identities, requests and all outputs. No published candidate.
|
||||
|
||||
Technical references (queried2026-09-30): Brian2 2.5.1 refractory documentation
|
||||
https://brian2.readthedocs.io/en/2.5.1/user/refractoriness.html (actual generated code
|
||||
remains authoritative); PyTorch numerical accuracy notes
|
||||
https://docs.pytorch.org/docs/2.14/notes/numerical_accuracy.html (cross-backend
|
||||
floating operations are not assumed bitwise equivalent).
|
||||
@@ -0,0 +1,396 @@
|
||||
"""Complete original Phase 4L representation metrics on fixed CAL inputs only.
|
||||
|
||||
No simulation, fitting, heldout trajectory access, or candidate generation is
|
||||
performed here. The original 4L pair-specific within-odor baseline is retained;
|
||||
the weaker Phase 4P r4 aggregate gate is deliberately not used.
|
||||
"""
|
||||
import csv
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
STATE_DTYPES = {"step": np.int32, "neuron_idx": np.int32, "counts": np.int32,
|
||||
"v": np.float64, "g": np.float64, "ref": np.int32}
|
||||
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
|
||||
APL_FILES = {"apl-drive.float32.npy": 2, "apl-state.float32.npy": 2,
|
||||
"apl-inhibition.float32.npy": 4064}
|
||||
ORIGINAL_CAL_IDS = {
|
||||
"OMSUIQOIVADKIM-RXMQYKEDSA-N", "LABTWGUMFABVFG-ARJAWSKDSA-N",
|
||||
"QNVRIHYSUZMSGM-ZCFIWIBFSA-N", "VLSVVMPLPMNWBH-UHFFFAOYSA-N",
|
||||
"XHIUFYZDQBSEMF-UHFFFAOYSA-N", "OMSUIQOIVADKIM-YFKPBYRVSA-N"}
|
||||
ORIGINAL_MODES = ("physiological", "matched_energy")
|
||||
ORIGINAL_SEEDS = (51101, 51102, 51103, 51104, 51105)
|
||||
HEX_SHA = re.compile(r"[0-9a-f]{64}\Z")
|
||||
_DEFAULT_OUTPUT = object()
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def read_json(path):
|
||||
with Path(path).open(encoding="utf-8") as stream:
|
||||
return json.load(stream)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def arrays_bitwise_equal(first, second):
|
||||
"""Unlike array_equal, also reject different dtypes and signed-zero bits."""
|
||||
return bool(first.dtype == second.dtype and first.shape == second.shape and
|
||||
np.array_equal(np.ascontiguousarray(first).view(np.uint8),
|
||||
np.ascontiguousarray(second).view(np.uint8)))
|
||||
|
||||
|
||||
def pair_metrics(a, b):
|
||||
"""Exact definitions copied from phase4i.pair_metrics/safe_corr/cosine."""
|
||||
x, y = a.astype(np.float64), b.astype(np.float64)
|
||||
aa, bb = a > 0, b > 0
|
||||
union = int(np.count_nonzero(aa | bb))
|
||||
intersection = int(np.count_nonzero(aa & bb))
|
||||
denominator = float(np.linalg.norm(x) * np.linalg.norm(y))
|
||||
cosine = 1.0 if denominator == 0 and np.array_equal(x, y) else (
|
||||
0.0 if denominator == 0 else float(np.dot(x, y) / denominator))
|
||||
correlation = (1.0 if np.array_equal(x, y) else 0.0) if np.std(x) == 0 or np.std(y) == 0 else float(np.corrcoef(x, y)[0, 1])
|
||||
return {"active_intersection": intersection, "active_union": union,
|
||||
"active_jaccard": 1.0 if union == 0 else float(intersection / union),
|
||||
"cosine_similarity": cosine, "correlation": correlation,
|
||||
"euclidean_distance": float(np.linalg.norm(x - y))}
|
||||
|
||||
|
||||
def evaluate_gates(summary, gates):
|
||||
"""Each conjunct in original phase4l.py:239, kept visible for audit."""
|
||||
return {
|
||||
"mean_sparsity_in_closed_interval": bool(float(gates["sparse_min_fraction"]) <=
|
||||
summary["KC_active_fraction_mean"] <= float(gates["sparse_max_fraction"])),
|
||||
"maximum_strictly_below_half": bool(summary["KC_active_fraction_max"] < 0.5),
|
||||
"minimum_strictly_positive": bool(summary["KC_active_fraction_min"] > 0),
|
||||
"same_jaccard_gt_different": bool(summary["same_odor_jaccard_mean"] > summary["different_odor_jaccard_mean"]),
|
||||
"different_euclidean_gt_same": bool(summary["different_odor_euclidean_mean"] > summary["same_odor_euclidean_mean"]),
|
||||
"at_least_original_pair_count": bool(summary["pairwise_separation_pass_count"] >= int(gates["minimum_separable_odor_pairs"]))}
|
||||
|
||||
|
||||
def sample_cv(values):
|
||||
"""Original pandas Series.std uses ddof=1. Undefined diagnostic -> null."""
|
||||
values = np.asarray(values, dtype=np.float64)
|
||||
mean = float(values.mean())
|
||||
return None if mean == 0 else float(values.std(ddof=1) / mean)
|
||||
|
||||
|
||||
def summarize_mode(mode, odor_ids, seeds, runs, gates):
|
||||
within, within_j, between, between_j, between_cos, between_corr = [], [], [], [], [], []
|
||||
pairs = []
|
||||
for odor in odor_ids:
|
||||
arrays = [runs[(mode, odor, seed)]["kc_counts"] for seed in seeds]
|
||||
for first, second in itertools.combinations(arrays, 2):
|
||||
metric = pair_metrics(first, second)
|
||||
within.append(metric["euclidean_distance"])
|
||||
within_j.append(metric["active_jaccard"])
|
||||
for first_odor, second_odor in itertools.combinations(odor_ids, 2):
|
||||
distances = []
|
||||
for seed in seeds:
|
||||
metric = pair_metrics(runs[(mode, first_odor, seed)]["kc_counts"],
|
||||
runs[(mode, second_odor, seed)]["kc_counts"])
|
||||
distances.append(metric["euclidean_distance"])
|
||||
between.append(metric["euclidean_distance"])
|
||||
between_j.append(metric["active_jaccard"])
|
||||
between_cos.append(metric["cosine_similarity"])
|
||||
between_corr.append(metric["correlation"])
|
||||
pair_within = []
|
||||
for odor in (first_odor, second_odor):
|
||||
arrays = [runs[(mode, odor, seed)]["kc_counts"] for seed in seeds]
|
||||
pair_within.extend(pair_metrics(first, second)["euclidean_distance"]
|
||||
for first, second in itertools.combinations(arrays, 2))
|
||||
cross_mean, within_mean = float(np.mean(distances)), float(np.mean(pair_within))
|
||||
pairs.append({"odor_id_a": first_odor, "odor_id_b": second_odor,
|
||||
"between_same_seed_mean": cross_mean, "within_this_pair_mean": within_mean,
|
||||
"between_distance_count": len(distances), "within_distance_count": len(pair_within),
|
||||
"gate": bool(cross_mean > within_mean)})
|
||||
selected = [runs[(mode, odor, seed)] for odor in odor_ids for seed in seeds]
|
||||
fractions = [int(np.count_nonzero(run["kc_counts"])) / len(run["kc_counts"]) for run in selected]
|
||||
summary = {"KC_active_fraction_mean": float(np.mean(fractions)),
|
||||
"KC_active_fraction_min": float(np.min(fractions)), "KC_active_fraction_max": float(np.max(fractions)),
|
||||
"same_odor_jaccard_mean": float(np.mean(within_j)),
|
||||
"different_odor_jaccard_mean": float(np.mean(between_j)),
|
||||
"same_odor_euclidean_mean": float(np.mean(within)),
|
||||
"different_odor_euclidean_mean": float(np.mean(between)),
|
||||
"different_odor_cosine_mean": float(np.mean(between_cos)),
|
||||
"different_odor_correlation_mean": float(np.mean(between_corr)),
|
||||
"within_variance": float(np.var(within)), "between_distance_mean": float(np.mean(between)),
|
||||
"pairwise_separation_pass_count": sum(row["gate"] for row in pairs), "pair_count": len(pairs),
|
||||
"expected_event_count_cv": sample_cv([run["expected_external_events"] for run in selected]),
|
||||
"actual_event_count_cv": sample_cv([run["external_events"] for run in selected]),
|
||||
"within_distance_count": len(within), "between_distance_count": len(between),
|
||||
"pairwise_baseline": "Each pair's two odors: 10+10 within-seed distances; never pooled across all odors",
|
||||
"pairs": pairs}
|
||||
summary["conditions"] = evaluate_gates(summary, gates)
|
||||
summary["gate"] = bool(all(summary["conditions"].values()))
|
||||
return summary
|
||||
|
||||
|
||||
def validate_state(state, neurons, steps):
|
||||
require(set(state) == set(STATE_DTYPES), "state.npz must have exactly the six declared arrays")
|
||||
for name, dtype in STATE_DTYPES.items():
|
||||
value = state[name]
|
||||
require(value.dtype == dtype and value.ndim == 1, "Invalid state dtype/rank: " + name)
|
||||
require(bool(np.isfinite(value).all()), "Nonfinite state: " + name)
|
||||
if name not in ("step", "neuron_idx"):
|
||||
require(value.shape == (neurons,), "Invalid state length: " + name)
|
||||
ticks, nodes = state["step"], state["neuron_idx"]
|
||||
require(ticks.shape == nodes.shape and bool(np.all((ticks >= 0) & (ticks < steps))) and
|
||||
bool(np.all((nodes >= 0) & (nodes < neurons))), "Invalid sparse event indices")
|
||||
keys = ticks.astype(np.int64) * neurons + nodes
|
||||
require(bool(np.all(np.diff(keys) > 0)), "Sparse events not sorted unique by tick/global neuron")
|
||||
require(np.array_equal(np.bincount(nodes, minlength=neurons), state["counts"]), "Counts disagree with sparse events")
|
||||
require(bool(np.all((state["ref"] >= 0) & (state["ref"] <= 22))), "Final refractory outside frozen range")
|
||||
return keys
|
||||
|
||||
|
||||
def read_jsonl(path, steps, kind):
|
||||
with Path(path).open(encoding="utf-8") as source:
|
||||
rows = [json.loads(line) for line in source if line.strip()]
|
||||
require(len(rows) == steps and [row.get("tick") for row in rows] == list(range(steps)),
|
||||
"Missing/duplicate/reordered ticks in " + kind)
|
||||
for row in rows:
|
||||
names = ("v_sha256", "g_sha256", "ref_sha256") if kind == "state hashes" else (
|
||||
"ordered_edge_sha256", "accepted_mask_sha256", "joint_sha256")
|
||||
for name in names:
|
||||
require(isinstance(row.get(name), str) and HEX_SHA.fullmatch(row[name]), "Invalid SHA256 in " + kind)
|
||||
if kind == "queue":
|
||||
require(type(row.get("count")) is int and type(row.get("accepted_count")) is int and
|
||||
0 <= row["accepted_count"] <= row["count"], "Invalid queue counts")
|
||||
return rows
|
||||
|
||||
|
||||
def read_trial(output, case, spec, kc_globals, neurons, edges):
|
||||
summary = read_json(output / "summary.json")
|
||||
steps = spec["steps"]
|
||||
require(summary.get("complete") is True and summary.get("pass_all") is True and
|
||||
summary.get("all_finite") is True and summary.get("steps") == steps and
|
||||
summary.get("completed_ticks") == steps and summary.get("nodes") == neurons and
|
||||
summary.get("edges") == edges and summary.get("precision") == "float64" and
|
||||
summary.get("apl_precision") == "float32" and summary.get("case") == spec["name"] and
|
||||
summary.get("record_history") is False and summary.get("learning") is False,
|
||||
"Incomplete/failed/wrong-scope GPU summary")
|
||||
with np.load(output / "state.npz", allow_pickle=False) as source:
|
||||
state = {name: source[name].copy() for name in source.files}
|
||||
event_keys = validate_state(state, neurons, steps)
|
||||
hashes = read_jsonl(output / "tick-state-hashes.jsonl", steps, "state hashes")
|
||||
for name in ("v", "g", "ref"):
|
||||
require(hashes[-1][name + "_sha256"] == raw_hash(state[name]), "Final state does not match last tick hash: " + name)
|
||||
queues = read_jsonl(output / "queue.jsonl", steps, "queue")
|
||||
apl = {}
|
||||
for name, width in APL_FILES.items():
|
||||
value = np.load(output / name, mmap_mode="r", allow_pickle=False)
|
||||
require(value.dtype == np.float32 and value.shape == (steps, width) and
|
||||
bool(np.isfinite(value).all()) and bool(np.all(value >= 0)), "Invalid full APL trace: " + name)
|
||||
apl[name] = value
|
||||
with (output / "requests.csv").open(encoding="utf-8", newline="") as source:
|
||||
reader = csv.DictReader(source)
|
||||
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, "Request CSV schema mismatch")
|
||||
request_rows = [{name: int(row[name]) for name in REQUEST_FIELDS} for row in reader]
|
||||
with np.load(case / "requests.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"tick", "neuron_idx"}, "Input request schema mismatch")
|
||||
expected_ticks, expected_nodes = source["tick"], source["neuron_idx"]
|
||||
require(expected_ticks.dtype == np.int32 and expected_nodes.dtype == np.int32 and
|
||||
expected_ticks.ndim == 1 and expected_ticks.shape == expected_nodes.shape,
|
||||
"Input request dtype/shape mismatch")
|
||||
require(len(request_rows) == len(expected_ticks), "Requested event coverage mismatch")
|
||||
require([row["tick"] for row in request_rows] == expected_ticks.tolist() and
|
||||
[row["neuron_idx"] for row in request_rows] == expected_nodes.tolist(), "Requests differ from frozen input")
|
||||
require(all(row["attempted"] == 1 and row["accepted"] in (0, 1) and
|
||||
row["rejected"] == 1 - row["accepted"] for row in request_rows), "Invalid request acceptance bookkeeping")
|
||||
requested_keys = expected_ticks.astype(np.int64) * neurons + expected_nodes
|
||||
require(bool(np.all(np.diff(requested_keys) > 0)) and bool(np.all((expected_ticks >= 0) & (expected_ticks < steps))) and
|
||||
bool(np.all((expected_nodes >= 0) & (expected_nodes < neurons))), "Input requests not sorted unique/in range")
|
||||
actual_accepted = np.array([row["accepted"] for row in request_rows], dtype=np.uint8)
|
||||
require(np.array_equal(np.isin(requested_keys, event_keys, assume_unique=True), actual_accepted.astype(bool)),
|
||||
"Request accepted/rejected values disagree with actual spikes")
|
||||
accepted = int(actual_accepted.sum())
|
||||
require(summary.get("requested") == len(request_rows) and summary.get("accepted_requests") == accepted and
|
||||
summary.get("rejected_requests") == len(request_rows) - accepted and
|
||||
summary.get("total_spikes") == len(state["step"]), "Summary counts disagree with raw output")
|
||||
component = read_json(output / "apl-component.json")
|
||||
require(component.get("ok") is True and component.get("n_neurons") == neurons and
|
||||
component.get("n_kc") == len(kc_globals) and component.get("n_apl") == 2 and
|
||||
component.get("dt_ms") == .1 and summary.get("apl_component") == component,
|
||||
"APL component provenance mismatch")
|
||||
bundle = Path(spec["apl_bundle_dir"])
|
||||
require(component.get("files_sha256") == {name: file_hash(bundle / name) for name in
|
||||
("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")}, "APL bundle hashes changed")
|
||||
inputs = summary.get("input_sha256", {})
|
||||
require(inputs.get("spec.json") == file_hash(case / "spec.json") and
|
||||
inputs.get("requests.npz") == file_hash(case / "requests.npz"), "Case input provenance mismatch")
|
||||
kc_counts = state["counts"][kc_globals].copy()
|
||||
kc_event_mask = np.isin(state["neuron_idx"], kc_globals)
|
||||
record = {"case": spec["name"], "mode": spec["mode"], "odor_id": spec["odor_id"],
|
||||
"odor_name": spec["odor_name"], "seed": spec["seed"], "split": "calibration",
|
||||
"KC_active": int(np.count_nonzero(kc_counts)), "KC_active_fraction": float(np.count_nonzero(kc_counts) / len(kc_globals)),
|
||||
"KC_spikes": int(kc_counts.sum()), "KC_first_latency_ms":
|
||||
float(state["step"][kc_event_mask][0] * .1) if bool(np.any(kc_event_mask)) else None,
|
||||
"external_events": accepted, "expected_external_events": float(spec["expected_external_events"]),
|
||||
"total_PN_rate_hz": float(spec["rate_sum_hz"]), "finite": True,
|
||||
"total_spikes": len(state["step"]), "requested": len(request_rows)}
|
||||
require(np.isfinite(record["expected_external_events"]) and record["expected_external_events"] > 0,
|
||||
"CAL expected external events must be finite and positive")
|
||||
return {"state": state, "hashes": hashes, "queues": queues, "apl": apl,
|
||||
"requests": request_rows, "component": component, "kc_counts": kc_counts,
|
||||
"expected_external_events": record["expected_external_events"], "external_events": accepted,
|
||||
"record": record, "output": output}
|
||||
|
||||
|
||||
def exact_repeat(first, repeat):
|
||||
checks = {"state/" + name: arrays_bitwise_equal(first["state"][name], repeat["state"][name]) for name in STATE_DTYPES}
|
||||
checks.update({name: arrays_bitwise_equal(first["apl"][name], repeat["apl"][name]) for name in APL_FILES})
|
||||
checks.update(tick_state_hashes=first["hashes"] == repeat["hashes"],
|
||||
queues=first["queues"] == repeat["queues"], requests=first["requests"] == repeat["requests"],
|
||||
apl_component=first["component"] == repeat["component"])
|
||||
return {"pass_all": bool(all(checks.values())), "checks": checks,
|
||||
"scope": "All six state arrays, every tick all-neuron hashes, APL raw float32 traces, queues, requests and APL environment"}
|
||||
|
||||
|
||||
def validate_inventory(records, cfg):
|
||||
require(tuple(cfg["calibration"]["modes"]) == ORIGINAL_MODES and
|
||||
tuple(cfg["calibration"]["seeds"]) == ORIGINAL_SEEDS and
|
||||
cfg["calibration"]["duration_ms"] == 100.0 and cfg["calibration"]["odor_count"] == 6 and
|
||||
cfg["calibration"]["split"] == "calibration" and
|
||||
cfg["calibration"]["repeat_first_seed_each_odor"] is True, "Frozen CAL design changed")
|
||||
gates = cfg["gates"]
|
||||
require(gates["sparse_min_fraction"] == .03 and gates["sparse_max_fraction"] == .09 and
|
||||
gates["minimum_separable_odor_pairs"] == 3 and gates["max_fraction_strict"] == .5 and
|
||||
gates["min_fraction_strict"] == 0 and all(gates[key] is True for key in
|
||||
("same_jaccard_gt_between", "between_euclidean_gt_within", "both_modes_required", "finite_required", "repeat_exact_required")),
|
||||
"Original representation gates or additional integrity gates changed")
|
||||
expected = {(mode, index, seed) for mode in ORIGINAL_MODES for index in range(6) for seed in ORIGINAL_SEEDS}
|
||||
require(len(records) == 60 and {(row["mode"], row["odor_index"], row["seed"]) for row in records} == expected,
|
||||
"CAL inventory must cover exactly 2 modes x 6 odors x 5 seeds")
|
||||
odors = []
|
||||
for index in range(6):
|
||||
ids = {row["odor_id"] for row in records if row["odor_index"] == index}
|
||||
require(len(ids) == 1, "Odor identity changes across seeds/modes")
|
||||
odors.append(next(iter(ids)))
|
||||
require(set(odors) == ORIGINAL_CAL_IDS, "CAL identities differ from the original frozen six")
|
||||
for row in records:
|
||||
require(row["name"] == f"cal-{row['mode']}-{row['odor_index']:02d}-s{row['seed']}", "Unexpected CAL case name")
|
||||
return odors
|
||||
|
||||
|
||||
def summarize(out_json=_DEFAULT_OUTPUT):
|
||||
"""Recompute; default writes exclusively, out_json=None is strictly read-only."""
|
||||
from context import EXP, ROOT, CFG, check
|
||||
target = EXP / "calibration/summary.json" if out_json is _DEFAULT_OUTPUT else (
|
||||
None if out_json is None else Path(out_json))
|
||||
require(target is None or not target.exists(), "Refusing existing calibration summary")
|
||||
failures, runs, run_records, repeats, provenance = [], {}, [], [], []
|
||||
modes, odors = {}, []
|
||||
prerequisites_ok = False
|
||||
encoder = None
|
||||
records = []
|
||||
try:
|
||||
check()
|
||||
records = read_json(EXP / "protocol/calibration-cases.json")
|
||||
odors = validate_inventory(records, CFG)
|
||||
encoder = read_json(EXP / "protocol/encoder-audit.json")
|
||||
require(encoder["panel_count"] == 12 and encoder["cal_count"] == 6 and
|
||||
encoder["seeds"] == list(ORIGINAL_SEEDS) and encoder["requested_before_any_dynamics"] is True and
|
||||
encoder["formal_AB_run"] is False and encoder["heldout_run"] is False, "Invalid frozen encoder scope")
|
||||
# Only static original-panel metadata is read, never heldout trajectories.
|
||||
import pandas as pd
|
||||
original = pd.read_parquet(ROOT / "experiments/phase4l-olfactory-grounding/odor-encoder/frozen-odor-panel.parquet")
|
||||
require(len(original) == 12 and set(original[original.split == "calibration"].odor_id) == ORIGINAL_CAL_IDS,
|
||||
"Original static panel identity mismatch")
|
||||
require(float(np.median(original.mapped_response_sum)) == encoder["median_mapped_response_sum"],
|
||||
"Matched-energy reference is not the original frozen 12-panel median")
|
||||
kc_globals = np.load(EXP / "apl-bundle/kc_globals.npy", allow_pickle=False)
|
||||
require(kc_globals.dtype == np.int32 and kc_globals.shape == (4064,) and
|
||||
bool(np.all(np.diff(kc_globals) > 0)) and bool(np.all((kc_globals >= 0) & (kc_globals < CFG["neurons"]))),
|
||||
"Invalid frozen KC identities")
|
||||
prerequisites_ok = True
|
||||
except Exception as error:
|
||||
failures.append({"stage": "prerequisites", "type": type(error).__name__, "message": str(error)})
|
||||
if prerequisites_ok:
|
||||
for row in records:
|
||||
name = row["name"]
|
||||
first = None
|
||||
try:
|
||||
case = EXP / "cases" / name
|
||||
spec = read_json(case / "spec.json")
|
||||
require(spec["name"] == name and spec["kind"] == "calibration" and spec["steps"] == 1000 and
|
||||
spec["duration_ms"] == 100.0 and spec["dt_ms"] == .1 and spec["training"] is False and
|
||||
all(spec[key] == row[key] for key in ("mode", "odor_id", "odor_index", "seed")),
|
||||
"Case spec differs from fixed CAL inventory")
|
||||
first = read_trial(EXP / "gpu" / (name + "-r0"), case, spec, kc_globals, CFG["neurons"], CFG["edges"])
|
||||
require(first["record"]["requested"] == row["requests"], "Frozen inventory request count mismatch")
|
||||
runs[(row["mode"], row["odor_id"], row["seed"])] = {key: first[key] for key in
|
||||
("kc_counts", "expected_external_events", "external_events")}
|
||||
run_records.append(first["record"])
|
||||
provenance.append({"case": name, "repeat": 0, "state_npz_sha256": file_hash(first["output"] / "state.npz"),
|
||||
"summary_sha256": file_hash(first["output"] / "summary.json")})
|
||||
except Exception as error:
|
||||
failures.append({"stage": "raw_trial", "case": name, "repeat": 0,
|
||||
"type": type(error).__name__, "message": str(error)})
|
||||
if row["seed"] == ORIGINAL_SEEDS[0]:
|
||||
try:
|
||||
require(first is not None, "Baseline unavailable for exact repeat")
|
||||
repeat = read_trial(EXP / "gpu" / (name + "-r1"), case, spec, kc_globals, CFG["neurons"], CFG["edges"])
|
||||
repeat_report = {"case": name, **exact_repeat(first, repeat)}
|
||||
repeats.append(repeat_report)
|
||||
provenance.append({"case": name, "repeat": 1, "state_npz_sha256": file_hash(repeat["output"] / "state.npz"),
|
||||
"summary_sha256": file_hash(repeat["output"] / "summary.json")})
|
||||
require(repeat_report["pass_all"], "Exact repeat differs: " + ", ".join(
|
||||
key for key, value in repeat_report["checks"].items() if not value))
|
||||
except Exception as error:
|
||||
failures.append({"stage": "exact_repeat", "case": name, "repeat": 1,
|
||||
"type": type(error).__name__, "message": str(error)})
|
||||
if len(runs) == 60:
|
||||
for mode in ORIGINAL_MODES:
|
||||
try:
|
||||
modes[mode] = summarize_mode(mode, odors, ORIGINAL_SEEDS, runs, CFG["gates"])
|
||||
except Exception as error:
|
||||
failures.append({"stage": "representation", "mode": mode,
|
||||
"type": type(error).__name__, "message": str(error)})
|
||||
integrity_pass = bool(prerequisites_ok and len(runs) == 60 and not any(row["stage"] in
|
||||
("prerequisites", "raw_trial") for row in failures))
|
||||
repeat_pass = bool(len(repeats) == 12 and all(row["pass_all"] for row in repeats) and
|
||||
not any(row["stage"] == "exact_repeat" for row in failures))
|
||||
representation_pass = bool(len(modes) == 2 and all(row["gate"] for row in modes.values()))
|
||||
result = {"phase": "4AG", "scope": "Original fixed six CAL odors only; not heldout generalization or learning",
|
||||
"pass_all": bool(integrity_pass and repeat_pass and representation_pass),
|
||||
"numerical_integrity_pass": integrity_pass, "exact_repeat_pass": repeat_pass,
|
||||
"original_representation_gate_pass": representation_pass,
|
||||
"expected_base_trials": 60, "valid_base_trials": len(runs), "expected_repeats": 12,
|
||||
"checked_repeats": len(repeats), "seeds": list(ORIGINAL_SEEDS), "duration_ms": 100.0,
|
||||
"odor_ids": odors, "modes": modes, "runs": run_records, "repeats": repeats,
|
||||
"failures": failures, "input_output_provenance": provenance, "encoder_audit": encoder,
|
||||
"metric_source": "phase4l.representation (full original gate), phase4i.pair_metrics",
|
||||
"finite_gate_note": "Added integrity prerequisite; original 4L recorded finite but omitted it from its gate",
|
||||
"repeat_gate_note": "Additional exact repeat requirement; original metrics and numerical thresholds unchanged",
|
||||
"shared_apl_component_oracle_limit": True, "independent_oracle_equivalence_claim": False,
|
||||
"candidate_generated": False, "training": False, "heldout_dynamic_access": False}
|
||||
if target is not None:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with target.open("x", encoding="utf-8") as output:
|
||||
json.dump(result, output, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
output.write("\n")
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps({key: value for key, value in summarize().items() if key in
|
||||
("pass_all", "numerical_integrity_pass", "exact_repeat_pass", "original_representation_gate_pass")}), flush=True)
|
||||
@@ -0,0 +1,18 @@
|
||||
49f53924f758317bfcbd2b53cab9b8b785121eb99f3234a8343c9b483c5701d8 PROTOCOL.md
|
||||
edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c calibration.py
|
||||
6e1616b086f667fa4e4b5087f500f8080281977ca9922e7b055acfddd2003bd5 compare.py
|
||||
a8bafb9deec49d918976f2836d977ef9f13c26a6fb7f70e015879e29cf986c9a config.json
|
||||
274eb13ccf38dbbc5c7f14d9d642130eb0f69072d91a090f208c30c27f87dd2b context.py
|
||||
b6e3c67e668a971b544df32d76435ab5e537061b9fc2e8d9dedb5d2a726f5782 finalize.py
|
||||
69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a gpu_apl.py
|
||||
21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291 legacy_apl_worker.py
|
||||
357a7d3debb5abc34a8714c0233b456547a154d9d5855d28661b4f01019a80ea oracle_apl.py
|
||||
e9328567753e144fc315491970f7e02af13725b93f35e4d4b97f7c4c48ce3338 prepare.py
|
||||
afbe6d5a37c29bda66d38c470093584130d1852e67881c681662aec3c76b33c7 run_case.py
|
||||
84c681030b0032119b5392d0c3f3c136a9c6b0d133eb548c40791a10bbbb8b63 run_suite.py
|
||||
bab3136eecbc5bb09a985d715844ee845cb72b07930ce376bfb39be33bd1845a test_calibration.py
|
||||
1a233f47943e01bbd845ac1dacdb1eec3f88c270c65a5f75bce1cf679b5de097 test_protocol.py
|
||||
0ed665f686e3499f8543e48f1712ccb4523946971cb7a0e096cf4f6d10d3bcba test_python_entry.py
|
||||
5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3 validate_components.py
|
||||
0d0e72b2f1348f6648124c8c34f75094f76fc5e1a281fbcca130a5fad2f35293 verify_frozen.py
|
||||
208c00961b9d895d14ac050bc23fbc18db49d0df1fcf5c20d4c3eaa8e15ff7ff manifest.json
|
||||
@@ -0,0 +1,508 @@
|
||||
"""Fail-closed byte-exact audit of the shared-APL closed-loop fixtures.
|
||||
|
||||
Comparison is independent of either backend's stored PASS. No tolerance is
|
||||
used, timing fields are never compared, and reports contain no wall timestamp.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
N_GLOBAL = 166700
|
||||
STATE_KEYS = ('step', 'neuron_idx', 'counts', 'v', 'g', 'ref')
|
||||
REQUEST_FIELDS = ('tick', 'neuron_idx', 'attempted', 'accepted', 'rejected')
|
||||
HASH_FIELDS = {'tick', 'v_sha256', 'g_sha256', 'ref_sha256'}
|
||||
QUEUE_FIELDS = {'tick', 'count', 'accepted_count', 'ordered_edge_sha256',
|
||||
'accepted_mask_sha256', 'joint_sha256'}
|
||||
BUNDLE_FILES = ('input_matrix.npy', 'output_matrix.npy', 'kc_globals.npy', 'apl_globals.npy')
|
||||
COEFFICIENTS = ('gd', 'c0', 'cg', 'cv', 'threshold', 'reset', 'seed_volt')
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as source:
|
||||
for block in iter(lambda: source.read(8 << 20), b''):
|
||||
h.update(block)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def raw_hash(array, dtype=None):
|
||||
return hashlib.sha256(np.ascontiguousarray(array, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def save(path, report):
|
||||
if path is None:
|
||||
return
|
||||
path = Path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('x', encoding='utf-8') as target:
|
||||
json.dump(report, target, indent=2, sort_keys=True, allow_nan=False)
|
||||
target.write('\n')
|
||||
|
||||
|
||||
def _read_json(path, used):
|
||||
used.add(Path(path))
|
||||
return json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def _read_jsonl(path, used):
|
||||
used.add(Path(path))
|
||||
with Path(path).open(encoding='utf-8') as source:
|
||||
return [json.loads(line) for line in source if line.strip()]
|
||||
|
||||
|
||||
def _read_array(path, used):
|
||||
used.add(Path(path))
|
||||
return np.load(path, mmap_mode='r', allow_pickle=False)
|
||||
|
||||
|
||||
def _check(checks, name, condition):
|
||||
checks[name] = bool(condition)
|
||||
|
||||
|
||||
def _bit_mask(a, b):
|
||||
if a.dtype.kind == 'f':
|
||||
unsigned = np.dtype('uint'+str(a.dtype.itemsize*8))
|
||||
return np.ascontiguousarray(a).view(unsigned) != np.ascontiguousarray(b).view(unsigned)
|
||||
return a != b
|
||||
|
||||
|
||||
def _compare_array(name, a, b, checks, differences, dtype=None, shape=None):
|
||||
a, b = np.asarray(a), np.asarray(b)
|
||||
schema = a.shape == b.shape and a.dtype == b.dtype and \
|
||||
(dtype is None or a.dtype == np.dtype(dtype)) and (shape is None or a.shape == shape)
|
||||
_check(checks, name+'.schema', schema)
|
||||
if not schema:
|
||||
differences[name] = {'left_shape': list(a.shape), 'right_shape': list(b.shape),
|
||||
'left_dtype': str(a.dtype), 'right_dtype': str(b.dtype), 'expected_shape': None if shape is None else list(shape),
|
||||
'expected_dtype': None if dtype is None else str(np.dtype(dtype))}
|
||||
_check(checks, name+'.bitwise_exact', False)
|
||||
return False
|
||||
finite = bool(np.isfinite(a).all() and np.isfinite(b).all())
|
||||
_check(checks, name+'.finite', finite)
|
||||
mask = _bit_mask(a, b)
|
||||
exact = not bool(np.any(mask))
|
||||
_check(checks, name+'.bitwise_exact', exact)
|
||||
if not exact or not finite:
|
||||
flat = np.flatnonzero(mask.ravel())
|
||||
detail = {'mismatching_elements': int(len(flat)),
|
||||
'first_positions': [list(map(int, np.unravel_index(int(index), a.shape))) for index in flat[:5]],
|
||||
'left_nonfinite': int(np.count_nonzero(~np.isfinite(a))),
|
||||
'right_nonfinite': int(np.count_nonzero(~np.isfinite(b)))}
|
||||
if finite and a.size:
|
||||
error = np.abs(a.astype(np.float64)-b.astype(np.float64))
|
||||
detail.update(max_abs_error=float(error.max()), mean_abs_error=float(error.mean()))
|
||||
if a.dtype.kind == 'f' and len(flat):
|
||||
unsigned = np.dtype('uint'+str(a.dtype.itemsize*8))
|
||||
av = np.ascontiguousarray(a).view(unsigned).ravel()
|
||||
bv = np.ascontiguousarray(b).view(unsigned).ravel()
|
||||
detail['first_bit_patterns'] = [{'left': hex(int(av[index])), 'right': hex(int(bv[index]))} for index in flat[:5]]
|
||||
differences[name] = detail
|
||||
return exact and finite
|
||||
|
||||
|
||||
def _sha(value):
|
||||
return isinstance(value, str) and re.fullmatch('[0-9a-f]{64}', value) is not None
|
||||
|
||||
|
||||
def _read_requests(path, steps, used):
|
||||
used.add(Path(path))
|
||||
with Path(path).open(encoding='utf-8', newline='') as source:
|
||||
reader = csv.DictReader(source)
|
||||
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, 'Request CSV schema mismatch: '+str(path))
|
||||
rows = [{key: int(row[key]) for key in REQUEST_FIELDS} for row in reader]
|
||||
previous = (-1, -1)
|
||||
for row in rows:
|
||||
key = row['tick'], row['neuron_idx']
|
||||
require(previous < key and 0 <= key[0] < steps and 0 <= key[1] < N_GLOBAL,
|
||||
'Request rows must be sorted, unique, and in range: '+str(path))
|
||||
require(row['attempted'] == 1 and row['accepted'] in (0, 1) and row['rejected'] == 1-row['accepted'],
|
||||
'Request accounting is incomplete: '+str(path))
|
||||
previous = key
|
||||
return rows
|
||||
|
||||
|
||||
def _load_run(directory, role, n, edges, steps, record_history, name, used, checks):
|
||||
directory = Path(directory)
|
||||
summary = _read_json(directory/'summary.json', used)
|
||||
_check(checks, role+'.summary_complete', summary.get('complete') is True and summary.get('pass_all') is True and
|
||||
summary.get('steps') == steps and summary.get('completed_ticks') == steps and
|
||||
summary.get('nodes') == n and summary.get('edges') == edges and summary.get('precision') == 'float64')
|
||||
is_cpu = role == 'cpu'
|
||||
_check(checks, role+'.case_name', summary.get('name' if is_cpu else 'case') == name)
|
||||
_check(checks, role+'.history_mode', summary.get('history_recorded' if is_cpu else 'record_history') is record_history)
|
||||
_check(checks, role+'.apl_precision', summary.get('APL_precision' if is_cpu else 'apl_precision') == 'float32')
|
||||
_check(checks, role+'.no_error_marker', not (directory/'error.json').exists())
|
||||
used.add(directory/'state.npz')
|
||||
with np.load(directory/'state.npz', allow_pickle=False) as source:
|
||||
require(set(source.files) == set(STATE_KEYS), 'State NPZ keys changed: '+str(directory))
|
||||
state = {key: source[key].copy() for key in STATE_KEYS}
|
||||
for key, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32'), ('counts', 'int32')):
|
||||
require(state[key].shape == (n,) and state[key].dtype == np.dtype(dtype), 'Invalid final '+key+': '+str(directory))
|
||||
require(state['step'].dtype == state['neuron_idx'].dtype == np.int32 and state['step'].ndim == 1 and
|
||||
state['step'].shape == state['neuron_idx'].shape, 'Invalid sparse event schema: '+str(directory))
|
||||
require(bool(np.all((state['step'] >= 0) & (state['step'] < steps))) and
|
||||
bool(np.all((state['neuron_idx'] >= 0) & (state['neuron_idx'] < N_GLOBAL))), 'Sparse event range error')
|
||||
event_keys = state['step'].astype(np.int64)*N_GLOBAL+state['neuron_idx']
|
||||
_check(checks, role+'.events_sorted_unique', len(event_keys) < 2 or np.all(np.diff(event_keys) > 0))
|
||||
_check(checks, role+'.count_nonnegative_total', np.all(state['counts'] >= 0) and int(state['counts'].sum()) == len(event_keys))
|
||||
_check(checks, role+'.ref_range', np.all((state['ref'] >= 0) & (state['ref'] <= 22)))
|
||||
_check(checks, role+'.reported_events', summary.get('spikes' if is_cpu else 'total_spikes') == len(event_keys))
|
||||
hashes = _read_jsonl(directory/'tick-state-hashes.jsonl', used)
|
||||
require(len(hashes) == steps and [row.get('tick') for row in hashes] == list(range(steps)), 'Incomplete tick hash coverage')
|
||||
require(all(set(row) == HASH_FIELDS and all(_sha(row[key]) for key in HASH_FIELDS-{'tick'}) for row in hashes),
|
||||
'Invalid tick state hash schema')
|
||||
queues = _read_jsonl(directory/'queue.jsonl', used)
|
||||
require(len(queues) == steps and [row.get('tick') for row in queues] == list(range(steps)), 'Incomplete queue coverage')
|
||||
require(all(set(row) == QUEUE_FIELDS and type(row['count']) is int and type(row['accepted_count']) is int and
|
||||
0 <= row['accepted_count'] <= row['count'] <= edges and
|
||||
all(_sha(row[key]) for key in QUEUE_FIELDS-{'tick', 'count', 'accepted_count'}) for row in queues),
|
||||
'Invalid queue row schema/count/hash')
|
||||
for key in ('v', 'g', 'ref'):
|
||||
_check(checks, role+'.last_hash_'+key, hashes[-1][key+'_sha256'] == raw_hash(state[key], '<i4' if key == 'ref' else '<f8'))
|
||||
requests = _read_requests(directory/'requests.csv', steps, used)
|
||||
requested, accepted = len(requests), sum(row['accepted'] for row in requests)
|
||||
_check(checks, role+'.request_summary', summary.get('requests' if is_cpu else 'requested') == requested and
|
||||
summary.get('applicable_requests' if is_cpu else 'accepted_requests') == accepted and
|
||||
summary.get('ref_rejected_requests' if is_cpu else 'rejected_requests') == requested-accepted)
|
||||
apl = {field: _read_array(directory/('apl-'+field+'.float32.npy'), used) for field in ('drive', 'state', 'inhibition')}
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', 4064)):
|
||||
require(apl[field].dtype == np.float32 and apl[field].shape == (steps, width), 'Invalid APL '+field+' history schema')
|
||||
_check(checks, role+'.apl_positive_'+field, np.all(apl[field] >= 0))
|
||||
histories, pre = {}, {}
|
||||
for field, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32')):
|
||||
files = (directory/(field+'-history.'+dtype+'.npy'), directory/('preAPL-'+field+'.'+dtype+'.npy'))
|
||||
if record_history:
|
||||
histories[field], pre[field] = (_read_array(path, used) for path in files)
|
||||
require(all(array.dtype == np.dtype(dtype) and array.shape == (steps, n) for array in (histories[field], pre[field])),
|
||||
'Small pre/post history schema mismatch')
|
||||
_check(checks, role+'.history_hashes_'+field, all(row[field+'_sha256'] == raw_hash(histories[field][tick])
|
||||
for tick, row in enumerate(hashes)))
|
||||
_check(checks, role+'.history_final_'+field, not np.any(_bit_mask(histories[field][-1], state[field])))
|
||||
else:
|
||||
_check(checks, role+'.no_dense_'+field, not any(path.exists() for path in files))
|
||||
if is_cpu:
|
||||
component = _read_json(directory/'apl-worker-handshake.json', used)['handshake']
|
||||
else:
|
||||
component = _read_json(directory/'apl-component.json', used)
|
||||
_check(checks, role+'.component_summary_consistent', summary.get('apl_component') == component)
|
||||
return dict(directory=directory, summary=summary, state=state, hashes=hashes, queues=queues,
|
||||
requests=requests, apl=apl, histories=histories, pre=pre, component=component)
|
||||
|
||||
|
||||
def _pair_outputs(left, right, checks, differences, n, steps, record_history):
|
||||
for field in STATE_KEYS:
|
||||
shape = None if field in ('step', 'neuron_idx') else (n,)
|
||||
dtype = 'float64' if field in ('v', 'g') else 'int32'
|
||||
_compare_array('state.'+field, left['state'][field], right['state'][field], checks, differences, dtype, shape)
|
||||
for field, width in (('drive', 2), ('state', 2), ('inhibition', 4064)):
|
||||
_compare_array('apl.'+field, left['apl'][field], right['apl'][field], checks, differences, 'float32', (steps, width))
|
||||
for field in ('hashes', 'queues', 'requests', 'component'):
|
||||
same = left[field] == right[field]
|
||||
_check(checks, field+'.parsed_exact', same)
|
||||
if not same:
|
||||
if isinstance(left[field], list):
|
||||
first = next((i for i, (a, b) in enumerate(zip(left[field], right[field])) if a != b), None)
|
||||
differences[field] = {'left_count': len(left[field]), 'right_count': len(right[field]),
|
||||
'first_differing_row': first,
|
||||
'left_row': None if first is None else left[field][first],
|
||||
'right_row': None if first is None else right[field][first]}
|
||||
else:
|
||||
differences[field] = {'left': left[field], 'right': right[field]}
|
||||
if record_history:
|
||||
for stage in ('histories', 'pre'):
|
||||
for field, dtype in (('v', 'float64'), ('g', 'float64'), ('ref', 'int32')):
|
||||
_compare_array(stage+'.'+field, left[stage][field], right[stage][field], checks, differences, dtype, (steps, n))
|
||||
|
||||
|
||||
def _queue_digest(tick, ids, accepted):
|
||||
ids = np.ascontiguousarray(ids, dtype='<i8').tobytes()
|
||||
mask = np.ascontiguousarray(accepted, dtype=np.uint8).tobytes()
|
||||
return {'tick': tick, 'count': len(mask), 'accepted_count': int(np.count_nonzero(accepted)),
|
||||
'ordered_edge_sha256': hashlib.sha256(ids).hexdigest(),
|
||||
'accepted_mask_sha256': hashlib.sha256(mask).hexdigest(),
|
||||
'joint_sha256': hashlib.sha256(b'phase4af-queue-v1\0'+struct.pack('<qq', tick, len(mask))+ids+mask).hexdigest()}
|
||||
|
||||
|
||||
def _independent_event_accounting(run, role, graph, requested, steps, checks, differences):
|
||||
nodes, ptr = graph['nodes'], graph['ptr']
|
||||
n = len(nodes)
|
||||
state = run['state']
|
||||
local = np.searchsorted(nodes, state['neuron_idx'])
|
||||
require(np.all(local < n) and np.array_equal(nodes[local], state['neuron_idx']), 'Actual spike outside graph identity')
|
||||
_check(checks, role+'.events_per_neuron_counts', np.array_equal(np.bincount(local, minlength=n), state['counts']))
|
||||
actual_request_pairs = [(row['tick'], row['neuron_idx']) for row in run['requests']]
|
||||
_check(checks, role+'.requests_match_frozen_input', actual_request_pairs == requested)
|
||||
require(actual_request_pairs == requested, 'Actual request population/time differs from frozen input')
|
||||
bounds = np.searchsorted(state['step'], np.arange(steps+1))
|
||||
request_ticks = np.asarray([row['tick'] for row in run['requests']], np.int32)
|
||||
rbounds = np.searchsorted(request_ticks, np.arange(steps+1))
|
||||
request_nodes = np.asarray([row['neuron_idx'] for row in run['requests']], np.int32)
|
||||
request_local = np.searchsorted(nodes, request_nodes)
|
||||
require(np.all(request_local < n) and np.array_equal(nodes[request_local], request_nodes), 'Request outside graph')
|
||||
last = np.full(n, -1_000_000_000, np.int64)
|
||||
degrees = np.diff(ptr)
|
||||
expected_counts = np.zeros(steps, np.int64)
|
||||
arrival_tick = state['step'].astype(np.int64)+18
|
||||
valid = arrival_tick < steps
|
||||
np.add.at(expected_counts, arrival_tick[valid], degrees[local[valid]])
|
||||
_check(checks, role+'.delayed_all_edges_count', np.array_equal(expected_counts, [row['count'] for row in run['queues']]))
|
||||
request_ok, delivered_ok, ref_ok, queue_ok, reset_ok = True, True, True, True, True
|
||||
first = {}
|
||||
for tick in range(steps):
|
||||
fired = local[bounds[tick]:bounds[tick+1]]
|
||||
lo, hi = rbounds[tick:tick+2]
|
||||
targets = request_local[lo:hi]
|
||||
accepted = (tick-last[targets]) >= 22
|
||||
for index, ok in zip(range(lo, hi), accepted):
|
||||
row = run['requests'][index]
|
||||
if row['accepted'] != int(ok) or row['rejected'] != int(not ok):
|
||||
request_ok = False
|
||||
first.setdefault('request_ref_mismatch', {'tick': tick, 'neuron_idx': row['neuron_idx']})
|
||||
if not np.isin(targets[accepted], fired).all() or np.isin(targets[~accepted], fired).any():
|
||||
delivered_ok = False
|
||||
first.setdefault('request_delivery_mismatch', tick)
|
||||
last[fired] = tick
|
||||
ref = np.maximum(22-(tick-last), 0).astype(np.int32)
|
||||
if raw_hash(ref, '<i4') != run['hashes'][tick]['ref_sha256']:
|
||||
ref_ok = False
|
||||
first.setdefault('ref_hash_mismatch', tick)
|
||||
if run['pre']:
|
||||
if len(fired) and not np.array_equal(np.ascontiguousarray(run['pre']['g'][tick, fired]).view(np.uint64), np.zeros(len(fired), np.uint64)):
|
||||
reset_ok = False
|
||||
first.setdefault('pre_apl_spike_reset_mismatch', tick)
|
||||
due = local[bounds[tick-18]:bounds[tick-17]] if tick >= 18 else np.empty(0, np.int64)
|
||||
chunks = [np.arange(ptr[source], ptr[source+1], dtype=np.int64) for source in due]
|
||||
indices = np.concatenate(chunks) if chunks else np.empty(0, np.int64)
|
||||
expected_queue = _queue_digest(tick, graph['edge_id'][indices], (ref[graph['dst'][indices]] == 0).astype(np.uint8))
|
||||
if expected_queue != run['queues'][tick]:
|
||||
queue_ok = False
|
||||
first.setdefault('queue_reconstruction_mismatch', tick)
|
||||
_check(checks, role+'.request_refractory_gate', request_ok)
|
||||
_check(checks, role+'.accepted_spikes_rejected_absent', delivered_ok)
|
||||
_check(checks, role+'.all_ref_hashes_from_actual_events', ref_ok)
|
||||
if run['pre']:
|
||||
_check(checks, role+'.small_queue_independent_reconstruction', queue_ok)
|
||||
_check(checks, role+'.spike_g_reset_before_apl', reset_ok)
|
||||
if first:
|
||||
differences[role+'.independent_accounting'] = first
|
||||
|
||||
|
||||
def _check_apl_application(run, role, nodes, kc, checks, differences):
|
||||
if not run['pre']:
|
||||
return
|
||||
common, local_kc, bundle_kc = np.intersect1d(nodes, kc, assume_unique=True, return_indices=True)
|
||||
_compare_array(role+'.apl_keeps_voltage', run['pre']['v'], run['histories']['v'], checks, differences, 'float64')
|
||||
_compare_array(role+'.apl_keeps_ref', run['pre']['ref'], run['histories']['ref'], checks, differences, 'int32')
|
||||
expected = np.array(run['pre']['g'], copy=True)
|
||||
delta = run['apl']['inhibition'][:, bundle_kc].astype(np.float64)*np.float64(.001)
|
||||
expected[:, local_kc] = expected[:, local_kc]-delta
|
||||
_compare_array(role+'.apl_unconditional_g_write', expected, run['histories']['g'], checks, differences, 'float64')
|
||||
|
||||
|
||||
def _finish(report, checks, differences, used, out_json):
|
||||
report['checks'] = {name: bool(value) for name, value in checks.items()}
|
||||
report['differences'] = differences
|
||||
report['pass_all'] = bool(checks) and all(checks.values()) and 'error' not in report
|
||||
report['failed_checks'] = sorted(name for name, value in checks.items() if not value)
|
||||
report['evidence_sha256'] = {str(path): file_hash(path) for path in sorted(used) if path.is_file()}
|
||||
save(out_json, report)
|
||||
return report
|
||||
|
||||
|
||||
def compare(case_dir, gpu_dir, cpu_dir, out_json=None):
|
||||
"""Strict independent closed-loop comparison; out_json=None is read-only."""
|
||||
case_dir, gpu_dir, cpu_dir = Path(case_dir), Path(gpu_dir), Path(cpu_dir)
|
||||
if out_json is not None:
|
||||
require(not Path(out_json).exists(), 'Refusing existing comparison report')
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'shared-APL-component closed-loop byte-exact comparison', 'pass_all': False,
|
||||
'case_dir': str(case_dir), 'gpu_dir': str(gpu_dir), 'cpu_dir': str(cpu_dir),
|
||||
'tolerance': None, 'signed_zero_included': True, 'timing_fields_compared': False,
|
||||
'independent_CPU_all_components_claim': False}
|
||||
try:
|
||||
spec = _read_json(case_dir/'spec.json', used)
|
||||
steps, history = spec['steps'], spec['record_history']
|
||||
require(type(steps) is int and steps > 0 and type(history) is bool and spec['dt_ms'] == .1,
|
||||
'Invalid fixed case clock/history schema')
|
||||
graph_dir, bundle_dir = Path(spec['graph_dir']), Path(spec['apl_bundle_dir'])
|
||||
graph = {field: _read_array(graph_dir/(field+'.npy'), used) for field in
|
||||
('nodes', 'edge_id', 'src', 'dst', 'ptr', 'signed_raw', 'weights')}
|
||||
nodes = graph['nodes']
|
||||
n, edges = len(nodes), len(graph['edge_id'])
|
||||
require(n > 0 and edges > 0 and nodes.dtype == np.int32 and nodes.ndim == 1 and np.all((nodes >= 0) & (nodes < N_GLOBAL)) and
|
||||
np.all(np.diff(nodes) > 0), 'Invalid canonical node identities')
|
||||
require(graph['ptr'].dtype == np.int64 and graph['ptr'].shape == (n+1,) and graph['ptr'][0] == 0 and
|
||||
graph['ptr'][-1] == edges and np.all(np.diff(graph['ptr']) >= 0), 'Invalid CSR')
|
||||
for key, dtype in (('edge_id', 'int64'), ('src', 'int32'), ('dst', 'int32'), ('signed_raw', 'int64'), ('weights', 'float64')):
|
||||
require(graph[key].dtype == np.dtype(dtype) and graph[key].shape == (edges,), 'Invalid graph '+key)
|
||||
require(np.array_equal(graph['src'], np.repeat(np.arange(n, dtype=np.int32), np.diff(graph['ptr']))) and
|
||||
np.all((graph['dst'] >= 0) & (graph['dst'] < n)), 'Invalid source/destination row mapping')
|
||||
require(history == (n < 512) and (n < 512 or n == N_GLOBAL), 'Invalid small/full history declaration')
|
||||
require(np.all(graph['edge_id'] >= 0) and np.all(np.diff(graph['edge_id']) > 0),
|
||||
'Original canonical edge IDs must be unique and increasing')
|
||||
if n == N_GLOBAL:
|
||||
require(edges == 25582938 and np.array_equal(nodes, np.arange(n, dtype=np.int32)) and
|
||||
np.array_equal(graph['edge_id'], np.arange(edges, dtype=np.int64)), 'Full canonical graph identity changed')
|
||||
bundle = {name: _read_array(bundle_dir/name, used) for name in BUNDLE_FILES}
|
||||
kc, apl_ids = bundle['kc_globals.npy'], bundle['apl_globals.npy']
|
||||
require(kc.dtype == apl_ids.dtype == np.int32 and kc.shape == (4064,) and apl_ids.shape == (2,), 'Invalid APL/KC identities')
|
||||
require(np.all((kc >= 0) & (kc < N_GLOBAL)) and np.all(np.diff(kc) > 0) and
|
||||
np.all((apl_ids >= 0) & (apl_ids < N_GLOBAL)) and np.all(np.diff(apl_ids) > 0) and
|
||||
not np.intersect1d(kc, apl_ids).size, 'APL/KC identities overlap or are invalid')
|
||||
for key, shape in (('input_matrix.npy', (2, N_GLOBAL)), ('output_matrix.npy', (2, 4064))):
|
||||
require(bundle[key].dtype == np.float32 and bundle[key].shape == shape and
|
||||
np.isfinite(bundle[key]).all() and np.all(bundle[key] >= 0), 'Invalid normalized APL matrix')
|
||||
gpu = _load_run(gpu_dir, 'gpu', n, edges, steps, history, spec['name'], used, checks)
|
||||
cpu = _load_run(cpu_dir, 'cpu', n, edges, steps, history, spec['name'], used, checks)
|
||||
_pair_outputs(gpu, cpu, checks, differences, n, steps, history)
|
||||
used.add(case_dir/'requests.npz')
|
||||
with np.load(case_dir/'requests.npz', allow_pickle=False) as source:
|
||||
require(set(source.files) == {'tick', 'neuron_idx'} and source['tick'].dtype == source['neuron_idx'].dtype == np.int32,
|
||||
'Fixed request NPZ schema changed')
|
||||
require(source['tick'].shape == source['neuron_idx'].shape and source['tick'].ndim == 1, 'Invalid sparse request shape')
|
||||
requested = list(zip(map(int, source['tick']), map(int, source['neuron_idx'])))
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
_independent_event_accounting(run, role, graph, requested, steps, checks, differences)
|
||||
_check_apl_application(run, role, nodes, kc, checks, differences)
|
||||
|
||||
# Independently derive the effective clone. Original graph weights stay read-only.
|
||||
cpu_weights = _read_array(cpu_dir/'weights.npy', used)
|
||||
require(cpu_weights.dtype == np.float64 and cpu_weights.shape == (edges,), 'CPU effective weight schema mismatch')
|
||||
expected_hash, base_hash, mask_hash = hashlib.sha256(), hashlib.sha256(), hashlib.sha256()
|
||||
weight_ok, mask_count = True, 0
|
||||
for start in range(0, edges, 1 << 20):
|
||||
stop = min(edges, start+(1 << 20))
|
||||
original = np.asarray(graph['weights'][start:stop])
|
||||
require(np.isfinite(original).all(), 'Nonfinite original weights')
|
||||
raw_expected = graph['signed_raw'][start:stop]*(.275*.001)
|
||||
require(not np.any(_bit_mask(original, raw_expected)), 'Original SI weights differ from unaltered signed contacts')
|
||||
mask = np.isin(nodes[graph['src'][start:stop]], apl_ids)
|
||||
expected = original.copy()
|
||||
expected[mask] = np.float64(0)
|
||||
mask_count += int(mask.sum())
|
||||
expected_hash.update(np.ascontiguousarray(expected, dtype='<f8').tobytes())
|
||||
base_hash.update(np.ascontiguousarray(original, dtype='<f8').tobytes())
|
||||
mask_hash.update(np.ascontiguousarray(graph['edge_id'][start:stop][mask], dtype='<i8').tobytes())
|
||||
weight_ok &= not np.any(_bit_mask(expected, cpu_weights[start:stop]))
|
||||
expected_sha = expected_hash.hexdigest()
|
||||
_check(checks, 'cpu.effective_weights_independent_bytes', weight_ok)
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
_check(checks, role+'.effective_weights_raw_sha', run['summary'].get('effective_weights_raw_sha256') == expected_sha)
|
||||
_check(checks, role+'.apl_fast_edge_count', run['summary'].get('apl_fast_edges_zeroed') == mask_count)
|
||||
_check(checks, role+'.kc_target_count', run['summary'].get('kc_targets_present') == len(np.intersect1d(nodes, kc)))
|
||||
mask_audit = _read_json(cpu_dir/'apl-mask-audit.json', used)
|
||||
_check(checks, 'cpu.mask_provenance', mask_audit['count'] == mask_count and
|
||||
mask_audit['ordered_original_edge_id_sha256'] == mask_hash.hexdigest() and
|
||||
mask_audit['base_weight_raw_bytes_sha256'] == base_hash.hexdigest() and
|
||||
mask_audit['effective_weights_raw_sha256'] == expected_sha and mask_audit['base_graph_files_modified'] is False)
|
||||
|
||||
case_hashes = {name: file_hash(case_dir/name) for name in ('spec.json', 'requests.npz')}
|
||||
if (case_dir/'arrays.npz').exists():
|
||||
case_hashes['arrays.npz'] = file_hash(case_dir/'arrays.npz')
|
||||
used.add(case_dir/'arrays.npz')
|
||||
baseline_coeff = _read_json(Path(spec['coefficients_path']), used)
|
||||
actual_coeff = _read_json(cpu_dir/'coefficients.json', used)
|
||||
_check(checks, 'cpu.actual_author_coefficients_exact', all(float(actual_coeff[key]).hex() == float(baseline_coeff[key]).hex()
|
||||
for key in COEFFICIENTS))
|
||||
audit = _read_json(cpu_dir/'input-audit.json', used)
|
||||
expected_graph_hashes = {name: file_hash(graph_dir/name) for name in
|
||||
tuple(field+'.npy' for field in graph)+('completeness.csv', 'connectivity.parquet')}
|
||||
bundle_hashes = {name: file_hash(bundle_dir/name) for name in BUNDLE_FILES}
|
||||
_check(checks, 'cpu.read_actual_case', audit['spec'] == spec and audit['case_hashes'] == case_hashes)
|
||||
_check(checks, 'cpu.graph_hashes', audit['graph_hashes'] == expected_graph_hashes)
|
||||
_check(checks, 'cpu.bundle_hashes', audit['bundle_hashes'] == bundle_hashes)
|
||||
_check(checks, 'cpu.no_candidate_trace_input', audit.get('candidate_trace_used_as_input') is False and
|
||||
cpu['summary'].get('candidate_trace_used_as_input') is False)
|
||||
_check(checks, 'cpu.worker_source_hash', audit['legacy_apl_worker_sha256'] == file_hash(spec['legacy_apl_worker']))
|
||||
_check(checks, 'cpu.coefficient_input_hash', audit['coefficients_sha256'] == file_hash(spec['coefficients_path']))
|
||||
gpu_hashes = dict(case_hashes, **{'base_weights.npy': file_hash(graph_dir/'weights.npy'),
|
||||
'coefficients.json': file_hash(spec['coefficients_path'])})
|
||||
_check(checks, 'gpu.input_hashes', gpu['summary']['input_sha256'] == gpu_hashes)
|
||||
for role, run in (('gpu', gpu), ('cpu', cpu)):
|
||||
component = run['component']
|
||||
_check(checks, role+'.actual_component_bundle_bytes', component['files_sha256'] == bundle_hashes and
|
||||
component['array_sha256'] == {name: raw_hash(value) for name, value in bundle.items()})
|
||||
_check(checks, role+'.component_dimensions_clock', component.get('ok') is True and
|
||||
component['protocol'] == 'phase4ag-apl-v1' and component['n_neurons'] == N_GLOBAL and
|
||||
component['n_apl'] == 2 and component['n_kc'] == 4064 and component['dt_ms'] == .1)
|
||||
decay = np.float32(math.exp(-.1/20.0))
|
||||
gain = np.float32(.01)
|
||||
_check(checks, role+'.frozen_apl_constants', component['decay_f32_bits'] == f'{int(decay.view(np.uint32)):08x}' and
|
||||
component['gain_f32_bits'] == f'{int(gain.view(np.uint32)):08x}')
|
||||
env = component['environment']
|
||||
_check(checks, role+'.component_precision_environment', env['cuda_matmul_allow_tf32'] is False and
|
||||
env['float32_matmul_precision'] == 'highest' and env['precision_configuration_changed_by_component'] is False and
|
||||
'4090' in env['device_name'])
|
||||
environment = _read_json(cpu_dir/'environment.json', used)
|
||||
_check(checks, 'cpu.pinned_environment', environment['packages']['brian2'] == '2.5.1' and
|
||||
environment['packages']['numpy'] == '1.22.3' and environment['packages']['sympy'] == '1.12' and
|
||||
environment['Torch_imported_into_Brian'] is False and environment['author_precision'] == 'float64')
|
||||
queue_audit = _read_json(cpu_dir/'queue-audit.json', used)
|
||||
_check(checks, 'cpu.queue_observer_coverage', queue_audit['peek_calls'] == queue_audit['advance_calls'] == steps and
|
||||
queue_audit['same_peek_object_returned'] is True and queue_audit['zero_weight_and_refractory_rejected_edges_retained'] is True)
|
||||
_check(checks, 'cpu.sidecar_complete', cpu['summary']['APL_worker_exit_status'] == 0 and cpu['summary']['APL_worker_ticks'] == steps)
|
||||
report.update(case=spec['name'], steps=steps, nodes=n, edges=edges, record_history=history,
|
||||
total_spikes=len(gpu['state']['step']), active_neurons=int(np.count_nonzero(gpu['state']['counts'])),
|
||||
requested=len(requested), accepted_requests=sum(row['accepted'] for row in gpu['requests']),
|
||||
rejected_requests=sum(row['rejected'] for row in gpu['requests']),
|
||||
arriving_edges=sum(row['count'] for row in gpu['queues']),
|
||||
accepted_arriving_edges=sum(row['accepted_count'] for row in gpu['queues']),
|
||||
apl_fast_edges_zeroed=mask_count, effective_weights_raw_sha256=expected_sha,
|
||||
component_environment=gpu['component']['environment'],
|
||||
limits=['APL Torch primitives are shared; original AST component validation is a separate required gate.',
|
||||
'Small cases preserve and independently hash all pre/post states; full cases compare all-tick instrumented state hashes without saving dense whole-brain histories.',
|
||||
'For full cases intermediate finite checks are performed by the instrumented backends; this file independently inspects final states and all APL traces.',
|
||||
'This is exact agreement for the declared trajectory, not physiological validity or learning.'])
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return _finish(report, checks, differences, used, out_json)
|
||||
|
||||
|
||||
def compare_gpu_repeats(first, second, out_json=None):
|
||||
"""Repeat equality only; the first run's independent oracle gate is separate."""
|
||||
first, second = Path(first), Path(second)
|
||||
if out_json is not None:
|
||||
require(not Path(out_json).exists(), 'Refusing existing repeat report')
|
||||
checks, differences, used = {}, {}, set()
|
||||
report = {'kind': 'fresh-GPU-run exact repeat comparison', 'first': str(first), 'second': str(second),
|
||||
'pass_all': False, 'tolerance': None, 'signed_zero_included': True, 'timing_fields_compared': False,
|
||||
'scope': 'Scientific outputs, recording integrity and input hashes; independent case/graph audit belongs to compare()'}
|
||||
try:
|
||||
summary = _read_json(first/'summary.json', used)
|
||||
n, edges, steps, name, history = (summary[key] for key in ('nodes', 'edges', 'steps', 'case', 'record_history'))
|
||||
require(type(n) is int and type(edges) is int and type(steps) is int and n > 0 and edges > 0 and steps > 0 and
|
||||
type(history) is bool and history == (n < 512), 'Invalid repeat dimensions')
|
||||
left = _load_run(first, 'first_gpu', n, edges, steps, history, name, used, checks)
|
||||
right = _load_run(second, 'second_gpu', n, edges, steps, history, name, used, checks)
|
||||
_pair_outputs(left, right, checks, differences, n, steps, history)
|
||||
stable = ('case', 'complete', 'pass_all', 'steps', 'completed_ticks', 'nodes', 'edges', 'precision',
|
||||
'apl_precision', 'state_units', 'total_spikes', 'requested', 'accepted_requests', 'rejected_requests',
|
||||
'arriving_edges', 'accepted_arriving_edges', 'counts_match_sparse_events', 'all_finite', 'record_history',
|
||||
'apl_fast_edges_zeroed', 'kc_targets_present', 'effective_weights_raw_sha256', 'apl_schedule',
|
||||
'apl_si_conversion', 'build', 'sealed_ae_backend_sha256', 'learning', 'input_sha256')
|
||||
for key in stable:
|
||||
_check(checks, 'summary.'+key, key in left['summary'] and key in right['summary'] and
|
||||
left['summary'][key] == right['summary'][key])
|
||||
if not checks['summary.'+key]:
|
||||
differences['summary.'+key] = {'first': left['summary'].get(key), 'second': right['summary'].get(key)}
|
||||
report.update(case=name, steps=steps, nodes=n, edges=edges, record_history=history,
|
||||
total_spikes=len(left['state']['step']),
|
||||
explicit_exclusions=['wall_seconds', 'diagnostic_simulation_seconds', 'build_or_cache_seconds',
|
||||
'peak_allocated_vram_bytes', 'peak_reserved_vram_bytes'])
|
||||
except Exception as error:
|
||||
report['error'] = {'type': type(error).__name__, 'message': str(error)}
|
||||
return _finish(report, checks, differences, used, out_json)
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"phase": "4AG-r2",
|
||||
"name": "author-apl-lab-v2",
|
||||
"execution_revision": "Preserve declared venv Python entry rather than resolving its symlink to system Python. No dynamics or inputs changed.",
|
||||
"human_authorization": "允许独立新模型分支测试动力学假设",
|
||||
"gpu_uuid": "GPU-79f3a85d-adce-7833-7003-6488140ca596",
|
||||
"neurons": 166700,
|
||||
"edges": 25582938,
|
||||
"raw_weight_sum": 124177617,
|
||||
"precision": "float64",
|
||||
"apl_precision": "float32",
|
||||
"apl": {"tau_ms": 20.0, "gain": 0.01, "normalization": 1.0},
|
||||
"hypothesis": "Author LIF plus unchanged legacy graded APL operator at end, after reset, unconditional KC g write. Mixed precision: h32 promoted to float64, then multiplied by float64 .001 SI, then subtracted. Not original author's APL model.",
|
||||
"fixtures": ["zero", "pn_pulse", "kc_ref_boundary", "two_sources", "arrival_collision", "natural_request", "apl_only", "high_fanin"],
|
||||
"fixture_repeats": 3,
|
||||
"calibration": {"split": "calibration", "odor_count": 6, "modes": ["physiological", "matched_energy"], "seeds": [51101,51102,51103,51104,51105], "duration_ms": 100.0, "median_scope": "original frozen 12-odor panel", "full_oracle_probes": ["cal-physiological-00-s51101", "cal-matched_energy-00-s51101"], "repeat_first_seed_each_odor": true},
|
||||
"gates": {"sparse_min_fraction": 0.03, "sparse_max_fraction": 0.09, "max_fraction_strict": 0.5, "min_fraction_strict": 0.0, "minimum_separable_odor_pairs": 3, "same_jaccard_gt_between": true, "between_euclidean_gt_within": true, "both_modes_required": true, "finite_required": true, "repeat_exact_required": true},
|
||||
"training": false,
|
||||
"heldout_dynamic_access": false,
|
||||
"candidate_generation": false,
|
||||
"output_budget_bytes": 16106127360,
|
||||
"scope": "Semantic compatibility and fixed old CAL only. Shared-APL-component closed-loop Brian oracle plus independent arithmetic audit; never claim all-component independent CPU equivalence or learning success. No parameter/input/site/window selection from results."
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT=Path('/data/digital-drosophila')
|
||||
MODEL=Path(__file__).resolve().parent
|
||||
EXP=ROOT/'experiments/phase4ag-author-apl-r2'
|
||||
AF_MODEL=ROOT/'models/male-cns-lif/author-fullgraph-lab-v1'
|
||||
AF_EXP=ROOT/'experiments/phase4af-author-fullgraph'
|
||||
AD_EXP=ROOT/'experiments/phase4ad-author-semantics'
|
||||
CFG=json.loads((MODEL/'config.json').read_text(encoding='utf-8'))
|
||||
|
||||
def digest(path):
|
||||
h=hashlib.sha256()
|
||||
with Path(path).open('rb') as f:
|
||||
for b in iter(lambda:f.read(8<<20),b''):h.update(b)
|
||||
return h.hexdigest()
|
||||
|
||||
def save(path,value):
|
||||
path=Path(path);path.parent.mkdir(parents=True,exist_ok=True)
|
||||
with path.open('x',encoding='utf-8') as f:json.dump(value,f,indent=2,ensure_ascii=False,allow_nan=False);f.write('\n')
|
||||
|
||||
def sources():
|
||||
return {p.name:digest(p) for p in sorted(MODEL.iterdir()) if p.is_file() and p.name not in ('manifest.json','checksums.sha256')}
|
||||
|
||||
def integrity():
|
||||
spec=importlib.util.spec_from_file_location('frozen_af_context',AF_MODEL/'context.py')
|
||||
mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod)
|
||||
result=mod.integrity()
|
||||
import sys
|
||||
sys.path.insert(0,str(ROOT/'models/male-cns-lif/terminal-scope-diagnostic-v1'))
|
||||
from diagnostic import manifest_check
|
||||
for p in (AF_MODEL,AF_EXP,ROOT/'models/male-cns-lif/author-apl-lab-v1',ROOT/'experiments/phase4ag-author-apl'):
|
||||
result[str(p.relative_to(ROOT))]=manifest_check(p,digest)
|
||||
assert all(r['pass'] for r in result.values())
|
||||
return result
|
||||
|
||||
def check():
|
||||
p=json.loads((EXP/'protocol/preregistered.json').read_text())
|
||||
assert p['sources']==sources(),'Source changed after preregistration'
|
||||
for row in p['inputs']:assert digest(ROOT/row['path'])==row['sha256'],row['path']
|
||||
for row in json.loads((EXP/'protocol/prepared-hashes.json').read_text()):
|
||||
assert digest(EXP/row['path'])==row['sha256'],row['path']
|
||||
return p
|
||||
|
||||
def budget():
|
||||
size=sum(p.stat().st_size for p in EXP.rglob('*') if p.is_file())
|
||||
assert size<CFG['output_budget_bytes'],'New output resource budget exceeded'
|
||||
return size
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Seal a completed gate or preserved failure; never claim learning."""
|
||||
import json
|
||||
from context import ROOT,MODEL,EXP,CFG,check,save,digest,integrity,budget
|
||||
|
||||
|
||||
def seal(root):
|
||||
assert not (root/'manifest.json').exists() and not (root/'checksums.sha256').exists()
|
||||
rows=[dict(path=str(p.relative_to(root)),bytes=p.stat().st_size,sha256=digest(p))
|
||||
for p in sorted(root.rglob('*')) if p.is_file() and '__pycache__' not in p.parts]
|
||||
save(root/'manifest.json',dict(root=str(root),files=rows))
|
||||
with (root/'checksums.sha256').open('x') as f:
|
||||
for row in rows:f.write(row['sha256']+' '+row['path']+'\n')
|
||||
f.write(digest(root/'manifest.json')+' manifest.json\n')
|
||||
|
||||
|
||||
def main():
|
||||
from compare import compare,compare_gpu_repeats
|
||||
check();budget();comparisons=[];repeats={};components={}
|
||||
for p in sorted((EXP/'audit').glob('compare-*.json')):
|
||||
name=p.stem[len('compare-'):]
|
||||
actual=compare(EXP/'cases'/name,EXP/'gpu'/(name+'-r0'),EXP/'oracle'/name,None)
|
||||
assert actual==json.loads(p.read_text()),'Changed raw closed-loop evidence: '+name
|
||||
comparisons.append(dict(case=name,pass_all=actual['pass_all']))
|
||||
for p in sorted((EXP/'audit').glob('repeat-*.json')):
|
||||
name,repeat=p.stem[len('repeat-'):].rsplit('-r',1)
|
||||
actual=compare_gpu_repeats(EXP/'gpu'/(name+'-r0'),EXP/'gpu'/f'{name}-r{repeat}',None)
|
||||
assert actual==json.loads(p.read_text()),'Changed raw repeat evidence: '+name
|
||||
repeats[f'{name}-r{repeat}']=actual['pass_all']
|
||||
for p in (EXP/'component-audit').glob('*.json'):
|
||||
result=json.loads(p.read_text())
|
||||
components[p.stem]=result['pass_all']
|
||||
for filename,sha in result.get('input_sha256',{}).items():
|
||||
assert digest(EXP/'gpu'/(p.stem+'-r0')/filename)==sha
|
||||
gates={}
|
||||
for name in ('fixtures','probes','calibration'):
|
||||
path=EXP/'audit'/(name+'-gate.json')
|
||||
gates[name]=json.loads(path.read_text()) if path.exists() else None
|
||||
calpath=EXP/'calibration/summary.json'
|
||||
cal=json.loads(calpath.read_text()) if calpath.exists() else None
|
||||
if cal is not None:
|
||||
from calibration import summarize
|
||||
assert summarize(out_json=None)==cal,'CAL raw evidence changed'
|
||||
frozen=json.loads((EXP/'regression/frozen-five.json').read_text())
|
||||
after=integrity();save(EXP/'regression/old-integrity-after.json',after)
|
||||
stops=[dict(path=str(p.relative_to(EXP)),report=json.loads(p.read_text())) for p in sorted((EXP/'audit').glob('stopped-*.json'))]
|
||||
expected_cases=CFG['fixtures']+CFG['calibration']['full_oracle_probes']
|
||||
expected_repeats=[f'{name}-r{r}' for name in CFG['fixtures'] for r in range(1,CFG['fixture_repeats'])]
|
||||
compared={x['case']:x['pass_all'] for x in comparisons}
|
||||
missing=dict(comparisons=sorted(set(expected_cases)-set(compared)),
|
||||
components=sorted(set(expected_cases)-set(components)),repeats=sorted(set(expected_repeats)-set(repeats)))
|
||||
evidence_ok=(set(compared)==set(components)==set(expected_cases) and set(repeats)==set(expected_repeats) and
|
||||
all(compared.values()) and all(components.values()) and all(repeats.values()))
|
||||
numerical=evidence_ok and all(gates[x] is not None and gates[x]['pass_all'] for x in ('fixtures','probes'))
|
||||
representation=bool(cal is not None and cal['pass_all'])
|
||||
if numerical:status='PASS-FIXED-CAL-REPRESENTATION' if representation else 'FAIL-FIXED-CAL-REPRESENTATION' if cal is not None else 'INCOMPLETE-CAL-EXECUTION'
|
||||
else:status='FAIL-NUMERICAL-OR-AUDIT-GATE' if any(not p for group in (compared,components,repeats) for p in group.values()) else 'INCOMPLETE-NUMERICAL-EXECUTION'
|
||||
oldpass=bool(frozen['pass_all'] and all(r['pass'] for r in after.values()))
|
||||
if not oldpass:status='FAIL-FROZEN-INTEGRITY-OR-REGRESSION'
|
||||
measurements=[]
|
||||
for p in sorted((EXP/'gpu').glob('*/summary.json')):
|
||||
s=json.loads(p.read_text())
|
||||
measurements.append({k:s[k] for k in ('case','total_spikes','accepted_requests','nodes','edges','diagnostic_simulation_seconds','peak_allocated_vram_bytes')})
|
||||
result=dict(status=status,numerical_gate_pass=bool(numerical),fixed_CAL_representation_pass=representation,
|
||||
gates=gates,closed_loop_comparisons=comparisons,component_results=components,repeat_results=repeats,
|
||||
missing_numerical_evidence=missing,calibration=cal,stopped=stops,
|
||||
old_frozen_integrity_and_five_reference_pass=oldpass,measurements=measurements,
|
||||
unchanged_APL_parameters=CFG['apl'],author_LIF_precision='float64',legacy_APL_precision='float32',
|
||||
explicit_hypothesis=CFG['hypothesis'],APL_nonKC_fast_edges_unreplaced=1212,
|
||||
shared_APL_component_oracle_limitation=True,whole_brain_universal_equivalence_claim=False,
|
||||
heldout_generalization_tested=False,formal_AB_tested=False,learning_achieved=False,candidate_generated=False,
|
||||
payload_bytes_before_manifest=budget(),
|
||||
next_gate='Independent seeds/novel-odor representation, then credit specificity; no learning promotion from CAL' if numerical and representation else
|
||||
'Audit first failure in this explicitly declared hypothesis; no gain search, no heldout tuning, no conditioning')
|
||||
save(EXP/'audit/final-status.json',result)
|
||||
lines=['# Phase 4AG: author LIF + unchanged legacy graded APL','',f'Status: `{status}`.','',
|
||||
f'Numerical closed-loop gate: {numerical}; original complete fixed six-CAL gate: {representation}.',
|
||||
'The independent Brian LIF and candidate use independent state but share the APL arithmetic component. Original frozen APL AST replay and a float64 error audit are separately recorded.',
|
||||
'Author LIF uses float64; APL retains float32, tau20ms/gain0.01/normalization1, full original matrices and order. No learning/parameter search.',
|
||||
'The explicitly hypothesized unconditional end-APL write includes refractory KC. Original fast APL mask leaves1,212 non-KC edges unreplaced; this scope limitation was not silently fixed.',
|
||||
f'Old checksums and five frozen reference trials: {oldpass}.',
|
||||
f'Successfully compared cases: {comparisons}.',
|
||||
'Detailed CAL, exact comparisons, component errors, event/request traces and execution exceptions are linked from audit/final-status.json.',
|
||||
'Wall/VRAM here include per-tick CPU synchronization, IPC and hashes. They are diagnostic costs, not runtime-performance benchmarks.',
|
||||
'No heldout dynamic outputs, formal A/B, learning, game, reward, decoder or new candidate.',
|
||||
'Next gate: '+result['next_gate'],'']
|
||||
with (EXP/'REPORT.md').open('x',encoding='utf-8') as f:f.write('\n'.join(lines))
|
||||
check();seal(MODEL);seal(EXP);print(status,flush=True)
|
||||
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,346 @@
|
||||
"""Diagnostic author-float64 LIF plus frozen legacy-float32 APL overlay.
|
||||
|
||||
The sealed Phase 4AE CUDA operators are reused without modification. APL is
|
||||
an explicitly mixed-precision engineering overlay, not an author-model claim.
|
||||
All CUDA/CPU synchronization here is diagnostic; no performance claim is made.
|
||||
"""
|
||||
from functools import lru_cache
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import time
|
||||
import traceback
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from legacy_apl_worker import LegacyAPL, N_NEURONS, file_sha256, array_sha256
|
||||
|
||||
|
||||
SOURCE_ROOT = Path(__file__).resolve().parent
|
||||
AE_MODEL = SOURCE_ROOT.parent / "author-runtime-lab-v1"
|
||||
AF_MODEL = SOURCE_ROOT.parent / "author-fullgraph-lab-v1"
|
||||
COEFFICIENT_NAMES = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def write_json(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as output:
|
||||
json.dump(value, output, indent=2, allow_nan=False)
|
||||
output.write("\n")
|
||||
|
||||
|
||||
def raw_hash(value, dtype):
|
||||
return hashlib.sha256(np.ascontiguousarray(value, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def load_source(path, name):
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(Path(path).read_bytes(), str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def sealed_backend():
|
||||
return load_source(AE_MODEL / "gpu_backend.py", "phase4ag_sealed_ae_backend")
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def queue_function():
|
||||
return load_source(AF_MODEL / "oracle_full.py", "phase4ag_sealed_af_queue").queue_summary
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def load_graph(graph_directory):
|
||||
root = Path(graph_directory)
|
||||
require(root.is_absolute(), "graph_dir must be absolute")
|
||||
graph = {}
|
||||
for name, dtype in (("nodes", np.int32), ("edge_id", np.int64), ("src", np.int32),
|
||||
("dst", np.int32), ("signed_raw", np.int64), ("ptr", np.int64),
|
||||
("weights", np.float64)):
|
||||
value = np.load(root / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
require(value.dtype == dtype and value.ndim == 1, "Graph dtype/rank mismatch: " + name)
|
||||
graph[name] = value
|
||||
nodes, edges = graph["nodes"], graph["edge_id"]
|
||||
n, e = len(nodes), len(edges)
|
||||
require(0 < n <= N_NEURONS and bool(np.all((nodes >= 0) & (nodes < N_NEURONS))) and
|
||||
bool(np.all(np.diff(nodes) > 0)), "Global node IDs must be sorted unique")
|
||||
require(n < 512 or (n == N_NEURONS and np.array_equal(nodes, np.arange(N_NEURONS, dtype=np.int32))),
|
||||
"Only small fixtures or the intact frozen full graph are permitted")
|
||||
require(n != N_NEURONS or e == 25582938, "Full graph edge count changed")
|
||||
for name in ("src", "dst", "signed_raw", "weights"):
|
||||
require(graph[name].shape == (e,), "Graph edge array length mismatch: " + name)
|
||||
ptr = graph["ptr"]
|
||||
require(ptr.shape == (n + 1,) and ptr[0] == 0 and ptr[-1] == e and
|
||||
bool(np.all(np.diff(ptr) >= 0)), "Invalid graph CSR")
|
||||
require(np.array_equal(graph["src"], np.repeat(np.arange(n, dtype=np.int32), np.diff(ptr))),
|
||||
"Source order does not match CSR")
|
||||
# Global edge IDs need not equal local row indices in a fixture.
|
||||
for start in range(0, e, 1 << 20):
|
||||
stop = min(start + (1 << 20), e)
|
||||
require(bool(np.all((graph["dst"][start:stop] >= 0) & (graph["dst"][start:stop] < n))),
|
||||
"Local destination out of range")
|
||||
require(bool(np.isfinite(graph["weights"][start:stop]).all()), "Nonfinite base SI weights")
|
||||
require(np.array_equal(np.sign(graph["weights"][start:stop]), np.sign(graph["signed_raw"][start:stop])),
|
||||
"Base weight sign/zero pattern differs from frozen raw contacts")
|
||||
lo = max(0, start - 1)
|
||||
require(bool(np.all(edges[start:stop] >= 0)) and bool(np.all(np.diff(edges[lo:stop]) > 0)),
|
||||
"Global edge IDs must retain canonical increasing source/edge order")
|
||||
return graph
|
||||
|
||||
|
||||
def load_inputs(case_dir):
|
||||
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
|
||||
for name in ("name", "steps", "dt_ms", "duration_ms", "model", "graph_dir", "apl",
|
||||
"apl_bundle_dir", "coefficients_path", "record_history"):
|
||||
require(name in spec, "Missing case field: " + name)
|
||||
steps = spec["steps"]
|
||||
require(type(steps) is int and 0 < steps <= 1000 and float(spec["dt_ms"]) == .1 and
|
||||
float(spec["duration_ms"]) == steps * .1, "Unexpected steps/dt/duration")
|
||||
require(type(spec["record_history"]) is bool, "record_history must be Boolean")
|
||||
require(spec["apl"] == {"tau_ms": 20, "gain": .01, "normalization": 1}, "Frozen APL parameters changed")
|
||||
for name in ("graph_dir", "apl_bundle_dir", "coefficients_path"):
|
||||
require(Path(spec[name]).is_absolute(), "Input path must be absolute: " + name)
|
||||
model = spec["model"]
|
||||
for name in ("resting_potential_mv", "threshold_mv", "reset_potential_mv", "synaptic_delay_ms",
|
||||
"refractory_ms", "w_syn_mv_per_anatomical_synapse"):
|
||||
require(name in model and math.isfinite(float(model[name])), "Invalid model field: " + name)
|
||||
require(float(model["synaptic_delay_ms"]) == 1.8 and float(model["refractory_ms"]) == 2.2 and
|
||||
float(model["w_syn_mv_per_anatomical_synapse"]) == .275, "Frozen delay/ref/W_syn changed")
|
||||
coeff = json.loads(Path(spec["coefficients_path"]).read_text(encoding="utf-8"))
|
||||
for name in COEFFICIENT_NAMES:
|
||||
require(name in coeff and math.isfinite(float(coeff[name])), "Invalid author coefficient: " + name)
|
||||
require(coeff["threshold"] == float(model["threshold_mv"]) * .001 and
|
||||
coeff["reset"] == float(model["reset_potential_mv"]) * .001, "Coefficient threshold/reset mismatch")
|
||||
graph = load_graph(str(Path(spec["graph_dir"]).resolve()))
|
||||
n = len(graph["nodes"])
|
||||
require(spec["record_history"] == (n < 512), "Fixtures need dense history; full graph must not duplicate dense histories")
|
||||
with np.load(case_dir / "requests.npz", allow_pickle=False) as source:
|
||||
require(set(source.files) == {"tick", "neuron_idx"}, "Request sparse schema changed")
|
||||
ticks, requested = source["tick"].copy(), source["neuron_idx"].copy()
|
||||
require(ticks.dtype == np.int32 and requested.dtype == np.int32 and ticks.ndim == 1 and
|
||||
ticks.shape == requested.shape, "Invalid sparse request dtype/shape")
|
||||
require(bool(np.all((ticks >= 0) & (ticks < steps))), "Request tick outside trial")
|
||||
if len(ticks) > 1:
|
||||
require(bool(np.all((ticks[1:] > ticks[:-1]) |
|
||||
((ticks[1:] == ticks[:-1]) & (requested[1:] > requested[:-1])))),
|
||||
"Requests must be sorted unique by tick/global neuron")
|
||||
local = np.searchsorted(graph["nodes"], requested)
|
||||
require(bool(np.all(local < n)) and np.array_equal(graph["nodes"][local], requested),
|
||||
"Requested neuron absent from graph")
|
||||
initial = {"initial_v_mv": np.full(n, float(model["resting_potential_mv"]), dtype=np.float64),
|
||||
"initial_g_mv": np.zeros(n, dtype=np.float64)}
|
||||
initial_path = case_dir / "arrays.npz"
|
||||
if initial_path.exists():
|
||||
with np.load(initial_path, allow_pickle=False) as source:
|
||||
require(set(source.files).issubset(initial), "Unexpected initial-state array")
|
||||
for name in source.files:
|
||||
value = source[name]
|
||||
require(value.dtype == np.float64 and value.shape == (n,) and bool(np.isfinite(value).all()),
|
||||
"Invalid initial state: " + name)
|
||||
initial[name] = value.copy()
|
||||
return spec, graph, coeff, ticks, requested, local, initial
|
||||
|
||||
|
||||
def new_array(path, dtype, shape):
|
||||
require(not Path(path).exists(), "Refusing existing output: " + str(path))
|
||||
return np.lib.format.open_memmap(path, mode="w+", dtype=dtype, shape=shape)
|
||||
|
||||
|
||||
def run_gpu(case_dir, out_dir, build_root):
|
||||
"""Run fresh candidate state; never consume oracle/candidate APL traces.
|
||||
|
||||
pass_all denotes internal recording/finite integrity only. Independent
|
||||
comparison and the original complete representation gate are separate.
|
||||
"""
|
||||
case_dir, out_dir = Path(case_dir), Path(out_dir)
|
||||
require(not out_dir.exists(), "Refusing existing output directory: " + str(out_dir))
|
||||
out_dir.mkdir(parents=True, exist_ok=False)
|
||||
start, tick, completed = time.perf_counter(), -1, 0
|
||||
maps = {}
|
||||
try:
|
||||
spec, graph, coeff, request_ticks, request_nodes, request_local, initial = load_inputs(case_dir)
|
||||
require(torch.cuda.is_available() and torch.cuda.device_count() == 1 and
|
||||
"4090" in torch.cuda.get_device_name(), "Only the declared single visible RTX4090 is permitted")
|
||||
ext, build = sealed_backend().build_extension(build_root)
|
||||
build_done = time.perf_counter()
|
||||
apl = LegacyAPL(spec["apl_bundle_dir"], spec["dt_ms"])
|
||||
write_json(out_dir / "apl-component.json", apl.metadata)
|
||||
device = apl.device
|
||||
torch.cuda.reset_peak_memory_stats(device)
|
||||
n, e, steps = len(graph["nodes"]), len(graph["edge_id"]), spec["steps"]
|
||||
|
||||
def upload(value, dtype):
|
||||
return torch.from_numpy(np.array(value, copy=True, order="C")).to(device=device, dtype=dtype).contiguous()
|
||||
|
||||
indptr, dst = upload(graph["ptr"], torch.int64), upload(graph["dst"], torch.int32)
|
||||
global_nodes_gpu = upload(graph["nodes"], torch.int64)
|
||||
base_weights = upload(graph["weights"], torch.float64)
|
||||
weights = base_weights.clone()
|
||||
del base_weights
|
||||
local_apl = np.flatnonzero(np.isin(graph["nodes"], apl.apl_globals)).astype(np.int64)
|
||||
apl_out_mask = np.isin(graph["src"], local_apl)
|
||||
if bool(np.any(apl_out_mask)):
|
||||
weights[upload(apl_out_mask, torch.bool)] = 0.0
|
||||
effective_hash = hashlib.sha256()
|
||||
for offset in range(0, e, 1 << 20):
|
||||
effective_hash.update(weights[offset:offset + (1 << 20)].cpu().numpy().astype("<f8", copy=False).tobytes())
|
||||
common_kc, local_kc, full_kc = np.intersect1d(graph["nodes"], apl.kc_globals, assume_unique=True, return_indices=True)
|
||||
kc_local_gpu, kc_full_gpu = upload(local_kc, torch.int64), upload(full_kc, torch.int64)
|
||||
v = upload(initial["initial_v_mv"] * .001, torch.float64)
|
||||
g = upload(initial["initial_g_mv"] * .001, torch.float64)
|
||||
ref = torch.zeros(n, device=device, dtype=torch.int32)
|
||||
counts = torch.zeros(n, device=device, dtype=torch.int32)
|
||||
requests = torch.zeros(n, device=device, dtype=torch.uint8)
|
||||
ring = torch.zeros((19, n), device=device, dtype=torch.uint8)
|
||||
for name, width in (("drive", 2), ("state", 2), ("inhibition", 4064)):
|
||||
maps["apl_" + name] = new_array(out_dir / ("apl-" + name + ".float32.npy"), np.float32, (steps, width))
|
||||
if spec["record_history"]:
|
||||
for name, dtype in (("v", np.float64), ("g", np.float64), ("ref", np.int32)):
|
||||
maps[name] = new_array(out_dir / (name + "-history." + np.dtype(dtype).name + ".npy"), dtype, (steps, n))
|
||||
maps["pre_" + name] = new_array(out_dir / ("preAPL-" + name + "." + np.dtype(dtype).name + ".npy"), dtype, (steps, n))
|
||||
event_steps, event_nodes = [], []
|
||||
accepted_requests_total, due_total, accepted_due_total = 0, 0, 0
|
||||
queue_summary = queue_function()
|
||||
simulation_start = time.perf_counter()
|
||||
with (out_dir / "queue.jsonl").open("x", encoding="utf-8", buffering=1) as queue_output, \
|
||||
(out_dir / "tick-state-hashes.jsonl").open("x", encoding="utf-8", buffering=1) as hash_output, \
|
||||
(out_dir / "requests.csv").open("x", encoding="utf-8", newline="", buffering=1) as request_output:
|
||||
writer = csv.DictWriter(request_output, fieldnames=REQUEST_FIELDS)
|
||||
writer.writeheader()
|
||||
for tick in range(steps):
|
||||
requests.zero_()
|
||||
begin, end = np.searchsorted(request_ticks, (tick, tick + 1))
|
||||
if end > begin:
|
||||
requested_local_gpu = upload(request_local[begin:end], torch.int64)
|
||||
requests[requested_local_gpu] = 1
|
||||
accepted = (ref[requested_local_gpu] <= 1).to(torch.uint8).cpu().numpy()
|
||||
accepted_requests_total += int(accepted.sum())
|
||||
writer.writerows({"tick": tick, "neuron_idx": int(node), "attempted": 1,
|
||||
"accepted": int(ok), "rejected": 1 - int(ok)}
|
||||
for node, ok in zip(request_nodes[begin:end], accepted))
|
||||
spikes = ring[tick % 19]
|
||||
ext.state_step(v, g, ref, requests, spikes, counts,
|
||||
*[float(coeff[name]) for name in COEFFICIENT_NAMES], 22)
|
||||
expanded_global = np.empty(0, dtype=np.int64)
|
||||
accepted_cpu = np.empty(0, dtype=np.uint8)
|
||||
if tick >= 18:
|
||||
due = ring[(tick - 18) % 19]
|
||||
sources = torch.nonzero(due, as_tuple=False).flatten().contiguous()
|
||||
if sources.numel():
|
||||
degrees = indptr[sources + 1] - indptr[sources]
|
||||
offsets = (torch.cumsum(degrees, dim=0) - degrees).contiguous()
|
||||
edge_count = int(degrees.sum().item())
|
||||
expanded = torch.empty(edge_count, device=device, dtype=torch.int64)
|
||||
ext.fill_edges(indptr, sources, offsets, expanded)
|
||||
if edge_count:
|
||||
destination = dst[expanded]
|
||||
# Actual active fanout order, BEFORE stable target grouping.
|
||||
expanded_global = graph["edge_id"][expanded.cpu().numpy()].copy()
|
||||
accepted_cpu = (ref[destination.long()] == 0).to(torch.uint8).cpu().numpy().copy()
|
||||
order = torch.argsort(destination, stable=True)
|
||||
ordered_edges, ordered_targets = expanded[order].contiguous(), destination[order]
|
||||
boundary = torch.ones(edge_count, device=device, dtype=torch.bool)
|
||||
boundary[1:] = ordered_targets[1:] != ordered_targets[:-1]
|
||||
starts = torch.nonzero(boundary, as_tuple=False).flatten().contiguous()
|
||||
targets = ordered_targets[starts].contiguous()
|
||||
ext.apply_arrivals(ordered_edges, starts, targets, weights, g, ref)
|
||||
queue = queue_summary(tick, expanded_global, accepted_cpu)
|
||||
queue_output.write(json.dumps(queue, allow_nan=False) + "\n")
|
||||
due_total += queue["count"]
|
||||
accepted_due_total += queue["accepted_count"]
|
||||
if spec["record_history"]:
|
||||
for name, value in (("v", v), ("g", g), ("ref", ref)):
|
||||
maps["pre_" + name][tick] = value.cpu().numpy()
|
||||
if n == N_NEURONS:
|
||||
full_spikes = spikes
|
||||
else:
|
||||
apl.spikes.zero_()
|
||||
apl.spikes[global_nodes_gpu] = spikes
|
||||
full_spikes = apl.spikes
|
||||
drive, apl_state, h = apl.advance(full_spikes)
|
||||
# Mixed precision contract: widen f32 h EXACTLY, THEN multiply
|
||||
# in f64, THEN subtract in f64. Do not guard refractory KCs.
|
||||
delta_si = h[kc_full_gpu].to(torch.float64) * 0.001
|
||||
g[kc_local_gpu] = g[kc_local_gpu] - delta_si
|
||||
apl_cpu = {"drive": drive.cpu().numpy().copy(), "state": apl_state.cpu().numpy().copy(),
|
||||
"inhibition": h.cpu().numpy().copy()}
|
||||
for name, value in apl_cpu.items():
|
||||
maps["apl_" + name][tick] = value
|
||||
actual = {"v": v.cpu().numpy().copy(), "g": g.cpu().numpy().copy(),
|
||||
"ref": ref.cpu().numpy().copy()}
|
||||
finite = all(bool(np.isfinite(value).all()) for value in list(actual.values()) + list(apl_cpu.values()))
|
||||
row = {"tick": tick, **{name + "_sha256": raw_hash(value, "<i4" if name == "ref" else "<f8")
|
||||
for name, value in actual.items()}}
|
||||
hash_output.write(json.dumps(row, allow_nan=False) + "\n")
|
||||
if spec["record_history"]:
|
||||
for name, value in actual.items():
|
||||
maps[name][tick] = value
|
||||
fired_local = torch.nonzero(spikes, as_tuple=False).flatten().cpu().numpy()
|
||||
if len(fired_local):
|
||||
event_steps.append(np.full(len(fired_local), tick, dtype=np.int32))
|
||||
event_nodes.append(graph["nodes"][fired_local].copy())
|
||||
completed += 1
|
||||
if not finite:
|
||||
with (out_dir / "first-failure-state.npz").open("xb") as target:
|
||||
np.savez_compressed(target, tick=np.array(tick, dtype=np.int32), **actual,
|
||||
**{"apl_" + name: value for name, value in apl_cpu.items()})
|
||||
require(False, "Nonfinite state; first failure saved")
|
||||
torch.cuda.synchronize(device)
|
||||
|
||||
def joined(values):
|
||||
return np.concatenate(values).astype(np.int32, copy=False) if values else np.empty(0, dtype=np.int32)
|
||||
|
||||
state = {"step": joined(event_steps), "neuron_idx": joined(event_nodes), "counts": counts.cpu().numpy(),
|
||||
"v": v.cpu().numpy(), "g": g.cpu().numpy(), "ref": ref.cpu().numpy()}
|
||||
with (out_dir / "state.npz").open("xb") as target:
|
||||
np.savez_compressed(target, **state)
|
||||
event_locals = np.searchsorted(graph["nodes"], state["neuron_idx"])
|
||||
counts_exact = np.array_equal(np.bincount(event_locals, minlength=n), state["counts"])
|
||||
require(counts_exact and completed == steps, "Sparse events/counts/completed ticks mismatch")
|
||||
for value in maps.values():
|
||||
value.flush()
|
||||
summary = {"case": spec["name"], "complete": True, "pass_all": True,
|
||||
"pass_all_scope": "internal finite/recording/event-count integrity only; independent equivalence not claimed",
|
||||
"steps": steps, "completed_ticks": completed, "nodes": n, "edges": e, "precision": "float64",
|
||||
"apl_precision": "float32", "state_units": "SI volts", "total_spikes": len(state["step"]),
|
||||
"requested": len(request_nodes), "accepted_requests": accepted_requests_total,
|
||||
"rejected_requests": len(request_nodes) - accepted_requests_total,
|
||||
"arriving_edges": due_total, "accepted_arriving_edges": accepted_due_total,
|
||||
"counts_match_sparse_events": bool(counts_exact), "all_finite": True,
|
||||
"record_history": spec["record_history"], "apl_fast_edges_zeroed": int(apl_out_mask.sum()),
|
||||
"kc_targets_present": len(common_kc), "effective_weights_raw_sha256": effective_hash.hexdigest(),
|
||||
"apl_component": apl.metadata, "apl_schedule": "end, after threshold/reset and guarded arrivals, unconditional all KC",
|
||||
"apl_si_conversion": "h32 -> float64 -> multiply float64 0.001 -> separate float64 subtraction",
|
||||
"build": build, "sealed_ae_backend_sha256": file_sha256(AE_MODEL / "gpu_backend.py"),
|
||||
"shared_apl_component_oracle_limitation": True, "oracle_validation_claim": False,
|
||||
"learning": False, "performance_runtime_claim": False, "cpu_sync_each_tick": True,
|
||||
"build_or_cache_seconds": build_done - start,
|
||||
"diagnostic_simulation_seconds": time.perf_counter() - simulation_start,
|
||||
"wall_seconds": time.perf_counter() - start,
|
||||
"peak_allocated_vram_bytes": int(torch.cuda.max_memory_allocated(device)),
|
||||
"peak_reserved_vram_bytes": int(torch.cuda.max_memory_reserved(device)),
|
||||
"input_sha256": {"spec.json": file_sha256(case_dir / "spec.json"),
|
||||
"requests.npz": file_sha256(case_dir / "requests.npz"),
|
||||
"base_weights.npy": file_sha256(Path(spec["graph_dir"]) / "weights.npy"),
|
||||
"coefficients.json": file_sha256(spec["coefficients_path"])}}
|
||||
if (case_dir / "arrays.npz").exists():
|
||||
summary["input_sha256"]["arrays.npz"] = file_sha256(case_dir / "arrays.npz")
|
||||
write_json(out_dir / "summary.json", summary)
|
||||
return summary
|
||||
except Exception as error:
|
||||
for value in maps.values():
|
||||
value.flush()
|
||||
write_json(out_dir / "error.json", {"complete": False, "pass_all": False,
|
||||
"tick_in_progress": tick, "completed_ticks": completed,
|
||||
"type": type(error).__name__, "message": str(error), "traceback": traceback.format_exc()})
|
||||
raise
|
||||
@@ -0,0 +1,175 @@
|
||||
"""Frozen eager Torch float32 APL component and binary oracle sidecar.
|
||||
|
||||
This is the Phase 4P operator sequence, not an author-neuron implementation.
|
||||
No precision/determinism flags are changed here. The full matrix dimensions
|
||||
are retained even when the caller simulates a small neuronal fixture.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
PROTOCOL = "phase4ag-apl-v1"
|
||||
N_NEURONS, N_APL, N_KC = 166700, 2, 4064
|
||||
BUNDLE_FILES = ("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")
|
||||
FRAME = struct.Struct("<qQ")
|
||||
PAYLOAD_BYTES = (N_APL + N_APL + N_KC) * 4
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(8 << 20), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def array_sha256(value):
|
||||
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def environment_metadata():
|
||||
device = torch.cuda.current_device()
|
||||
return {"torch": str(torch.__version__), "numpy": str(np.__version__),
|
||||
"cuda": torch.version.cuda, "cudnn": torch.backends.cudnn.version(),
|
||||
"device_name": torch.cuda.get_device_name(device), "device_index": device,
|
||||
"device_capability": list(torch.cuda.get_device_capability(device)),
|
||||
"deterministic_algorithms": torch.are_deterministic_algorithms_enabled(),
|
||||
"cudnn_deterministic": torch.backends.cudnn.deterministic,
|
||||
"cudnn_benchmark": torch.backends.cudnn.benchmark,
|
||||
"cuda_matmul_allow_tf32": torch.backends.cuda.matmul.allow_tf32,
|
||||
"cudnn_allow_tf32": torch.backends.cudnn.allow_tf32,
|
||||
"float32_matmul_precision": torch.get_float32_matmul_precision(),
|
||||
"CUBLAS_WORKSPACE_CONFIG": os.environ.get("CUBLAS_WORKSPACE_CONFIG"),
|
||||
"precision_configuration_changed_by_component": False}
|
||||
|
||||
|
||||
class LegacyAPL:
|
||||
"""Independent zero-initialized APL state with original full-width matrices."""
|
||||
|
||||
def __init__(self, bundle_dir, dt_ms):
|
||||
require(float(dt_ms) == 0.1, "Only frozen dt_ms=0.1 is allowed")
|
||||
require(torch.cuda.is_available(), "CUDA is required for the legacy Torch component")
|
||||
self.bundle_dir = Path(bundle_dir)
|
||||
expected = {"input_matrix.npy": (np.float32, (N_APL, N_NEURONS)),
|
||||
"output_matrix.npy": (np.float32, (N_APL, N_KC)),
|
||||
"kc_globals.npy": (np.int32, (N_KC,)),
|
||||
"apl_globals.npy": (np.int32, (N_APL,))}
|
||||
arrays = {}
|
||||
for name, (dtype, shape) in expected.items():
|
||||
value = np.load(self.bundle_dir / name, allow_pickle=False)
|
||||
require(value.dtype == dtype and value.shape == shape and value.flags.c_contiguous,
|
||||
"APL bundle dtype/shape/layout mismatch: " + name)
|
||||
require(bool(np.isfinite(value).all()), "Nonfinite APL bundle: " + name)
|
||||
arrays[name] = value
|
||||
for name in ("kc_globals.npy", "apl_globals.npy"):
|
||||
ids = arrays[name]
|
||||
require(bool(np.all((ids >= 0) & (ids < N_NEURONS))) and
|
||||
bool(np.all(np.diff(ids) > 0)), "APL/KC globals must be sorted unique: " + name)
|
||||
require(not np.intersect1d(arrays["kc_globals.npy"], arrays["apl_globals.npy"]).size,
|
||||
"APL and KC identities overlap")
|
||||
require(bool(np.all(arrays["input_matrix.npy"] >= 0)) and
|
||||
bool(np.all(arrays["output_matrix.npy"] >= 0)), "APL raw-contact matrices must be nonnegative")
|
||||
self.device = torch.device("cuda", torch.cuda.current_device())
|
||||
self.input_gpu = torch.from_numpy(arrays["input_matrix.npy"].copy()).to(self.device)
|
||||
self.output_gpu = torch.from_numpy(arrays["output_matrix.npy"].copy()).to(self.device)
|
||||
self.kc_globals = arrays["kc_globals.npy"].copy()
|
||||
self.apl_globals = arrays["apl_globals.npy"].copy()
|
||||
self.state = torch.zeros(N_APL, dtype=torch.float32, device=self.device)
|
||||
self.spikes = torch.zeros(N_NEURONS, dtype=torch.uint8, device=self.device)
|
||||
self.decay = np.float32(math.exp(-float(dt_ms) / 20.0))
|
||||
self.gain = np.float32(0.01)
|
||||
self.metadata = {"ok": True, "protocol": PROTOCOL, "n_neurons": N_NEURONS,
|
||||
"n_apl": N_APL, "n_kc": N_KC, "dt_ms": float(dt_ms),
|
||||
"files_sha256": {name: file_sha256(self.bundle_dir / name) for name in BUNDLE_FILES},
|
||||
"array_sha256": {name: array_sha256(value) for name, value in arrays.items()},
|
||||
"decay_f32_bits": f"{int(self.decay.view(np.uint32)):08x}",
|
||||
"gain_f32_bits": f"{int(self.gain.view(np.uint32)):08x}",
|
||||
"environment": environment_metadata(),
|
||||
"operator_sequence": "state.mul_(decay32); drive=mv(I32,spikes32); state.add_(drive); h=matmul(state,O32)*gain32",
|
||||
"h_units": "legacy mV", "initial_state": "float32 zeros"}
|
||||
|
||||
@torch.no_grad()
|
||||
def advance(self, spikes_u8):
|
||||
require(spikes_u8.device == self.device and spikes_u8.dtype == torch.uint8 and
|
||||
tuple(spikes_u8.shape) == (N_NEURONS,) and spikes_u8.is_contiguous(),
|
||||
"APL expects full-width contiguous CUDA uint8 spikes")
|
||||
# Preserve separate eager operations, including Python evaluation order
|
||||
# in the frozen state.mul_(...).add_(torch.mv(...)) expression.
|
||||
self.state.mul_(float(self.decay))
|
||||
drive = torch.mv(self.input_gpu, spikes_u8.to(torch.float32))
|
||||
self.state.add_(drive)
|
||||
h = torch.matmul(self.state, self.output_gpu) * self.gain
|
||||
return drive, self.state, h
|
||||
|
||||
def advance_global_fired(self, fired):
|
||||
require(fired.dtype == np.int32 and fired.ndim == 1, "Global spikes must be int32 vector")
|
||||
require(bool(np.all((fired >= 0) & (fired < N_NEURONS))) and
|
||||
bool(np.all(np.diff(fired) > 0)), "Global spikes must be sorted unique and in range")
|
||||
self.spikes.zero_()
|
||||
if fired.size:
|
||||
indices = torch.from_numpy(fired.copy()).to(device=self.device, dtype=torch.int64)
|
||||
self.spikes[indices] = 1
|
||||
return self.advance(self.spikes)
|
||||
|
||||
|
||||
def read_exact(stream, count, boundary_eof=False):
|
||||
result = bytearray()
|
||||
while len(result) < count:
|
||||
block = stream.read(count - len(result))
|
||||
if not block:
|
||||
if boundary_eof and not result:
|
||||
return None
|
||||
raise EOFError("Truncated APL request frame")
|
||||
result.extend(block)
|
||||
return bytes(result)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--bundle-dir", required=True)
|
||||
parser.add_argument("--dt-ms", required=True, type=float)
|
||||
args = parser.parse_args()
|
||||
# stdout is exclusively the handshake line and binary response frames.
|
||||
output, source = sys.stdout.buffer, sys.stdin.buffer
|
||||
component = LegacyAPL(args.bundle_dir, args.dt_ms)
|
||||
output.write((json.dumps(component.metadata, allow_nan=False) + "\n").encode("utf-8"))
|
||||
output.flush()
|
||||
expected_tick = 0
|
||||
while True:
|
||||
header = read_exact(source, FRAME.size, boundary_eof=True)
|
||||
if header is None:
|
||||
return
|
||||
tick, count = FRAME.unpack(header)
|
||||
require(tick == expected_tick and count <= N_NEURONS, "Invalid APL request tick/count")
|
||||
fired = np.frombuffer(read_exact(source, count * 4), dtype="<i4").copy()
|
||||
drive, state, h = component.advance_global_fired(fired)
|
||||
arrays = [value.detach().cpu().numpy().astype("<f4", copy=False) for value in (drive, state, h)]
|
||||
require(all(bool(np.isfinite(value).all()) for value in arrays), "Nonfinite APL output")
|
||||
output.write(FRAME.pack(tick, PAYLOAD_BYTES))
|
||||
for value in arrays:
|
||||
output.write(value.tobytes())
|
||||
output.flush()
|
||||
expected_tick += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"root": "/data/digital-drosophila/models/male-cns-lif/author-apl-lab-v2",
|
||||
"files": [
|
||||
{
|
||||
"path": "PROTOCOL.md",
|
||||
"bytes": 5583,
|
||||
"sha256": "49f53924f758317bfcbd2b53cab9b8b785121eb99f3234a8343c9b483c5701d8"
|
||||
},
|
||||
{
|
||||
"path": "calibration.py",
|
||||
"bytes": 25414,
|
||||
"sha256": "edb9cb2d890471fd07afd88c959ef3540b0cd40af490fac3f26396b5e7f5bf9c"
|
||||
},
|
||||
{
|
||||
"path": "compare.py",
|
||||
"bytes": 33632,
|
||||
"sha256": "6e1616b086f667fa4e4b5087f500f8080281977ca9922e7b055acfddd2003bd5"
|
||||
},
|
||||
{
|
||||
"path": "config.json",
|
||||
"bytes": 1994,
|
||||
"sha256": "a8bafb9deec49d918976f2836d977ef9f13c26a6fb7f70e015879e29cf986c9a"
|
||||
},
|
||||
{
|
||||
"path": "context.py",
|
||||
"bytes": 2134,
|
||||
"sha256": "274eb13ccf38dbbc5c7f14d9d642130eb0f69072d91a090f208c30c27f87dd2b"
|
||||
},
|
||||
{
|
||||
"path": "finalize.py",
|
||||
"bytes": 6747,
|
||||
"sha256": "b6e3c67e668a971b544df32d76435ab5e537061b9fc2e8d9dedb5d2a726f5782"
|
||||
},
|
||||
{
|
||||
"path": "gpu_apl.py",
|
||||
"bytes": 21206,
|
||||
"sha256": "69f5d893f47dafff4ef7c3186c3df0b9014666b32c8f4e943b57016bec92b18a"
|
||||
},
|
||||
{
|
||||
"path": "legacy_apl_worker.py",
|
||||
"bytes": 8158,
|
||||
"sha256": "21ceec65fe42581dc55872010e0025d660e80358905f1cf4df1dadf341a3b291"
|
||||
},
|
||||
{
|
||||
"path": "oracle_apl.py",
|
||||
"bytes": 46236,
|
||||
"sha256": "357a7d3debb5abc34a8714c0233b456547a154d9d5855d28661b4f01019a80ea"
|
||||
},
|
||||
{
|
||||
"path": "prepare.py",
|
||||
"bytes": 15297,
|
||||
"sha256": "e9328567753e144fc315491970f7e02af13725b93f35e4d4b97f7c4c48ce3338"
|
||||
},
|
||||
{
|
||||
"path": "run_case.py",
|
||||
"bytes": 2116,
|
||||
"sha256": "afbe6d5a37c29bda66d38c470093584130d1852e67881c681662aec3c76b33c7"
|
||||
},
|
||||
{
|
||||
"path": "run_suite.py",
|
||||
"bytes": 3700,
|
||||
"sha256": "84c681030b0032119b5392d0c3f3c136a9c6b0d133eb548c40791a10bbbb8b63"
|
||||
},
|
||||
{
|
||||
"path": "test_calibration.py",
|
||||
"bytes": 8127,
|
||||
"sha256": "bab3136eecbc5bb09a985d715844ee845cb72b07930ce376bfb39be33bd1845a"
|
||||
},
|
||||
{
|
||||
"path": "test_protocol.py",
|
||||
"bytes": 11764,
|
||||
"sha256": "1a233f47943e01bbd845ac1dacdb1eec3f88c270c65a5f75bce1cf679b5de097"
|
||||
},
|
||||
{
|
||||
"path": "test_python_entry.py",
|
||||
"bytes": 1760,
|
||||
"sha256": "0ed665f686e3499f8543e48f1712ccb4523946971cb7a0e096cf4f6d10d3bcba"
|
||||
},
|
||||
{
|
||||
"path": "validate_components.py",
|
||||
"bytes": 24824,
|
||||
"sha256": "5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3"
|
||||
},
|
||||
{
|
||||
"path": "verify_frozen.py",
|
||||
"bytes": 2160,
|
||||
"sha256": "0d0e72b2f1348f6648124c8c34f75094f76fc5e1a281fbcca130a5fad2f35293"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
"""Phase 4AG shared-APL-component closed-loop Brian oracle.
|
||||
|
||||
Brian has no Torch dependency. A separate, pinned science-Python process
|
||||
executes the unchanged legacy float32 APL operations from Brian's own spikes.
|
||||
This is independent LIF/scheduling validation with a shared APL numerical
|
||||
component, NOT an all-components-independent CPU oracle.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import queue
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
BLOCK_BYTES = 8 << 20
|
||||
PROTOCOL = "phase4ag-apl-v1"
|
||||
AUTHOR_PATH = Path("/data/digital-drosophila/experiments/phase4ad-author-semantics/external-resources/model.py")
|
||||
AUTHOR_SHA256 = "fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e"
|
||||
AE_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-runtime-lab-v1/oracle_stress.py")
|
||||
AE_SHA256 = "32eb1c42383068ce52701d2139a376ce155ad7072cde019949f8ca2d55492409"
|
||||
AF_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-fullgraph-lab-v1/oracle_full.py")
|
||||
AF_SHA256 = "d72003d98f0f1624b12126eff50bd361c43d888f4a64d90ba9247a58976f1961"
|
||||
EVENT_DTYPE = np.dtype([("step", "<i4"), ("neuron_idx", "<i4")])
|
||||
BUNDLE_FILES = ("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")
|
||||
_PROCESS_USED = False
|
||||
|
||||
|
||||
def _check(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def _save(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as handle:
|
||||
json.dump(value, handle, indent=2, sort_keys=True, allow_nan=False)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def _digest(path):
|
||||
result = hashlib.sha256()
|
||||
with Path(path).open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(BLOCK_BYTES), b""):
|
||||
result.update(block)
|
||||
return result.hexdigest()
|
||||
|
||||
|
||||
def _array_hash(array, dtype):
|
||||
return hashlib.sha256(np.ascontiguousarray(array, dtype=dtype).tobytes()).hexdigest()
|
||||
|
||||
|
||||
def _load_checked(path, expected, name):
|
||||
data = Path(path).read_bytes()
|
||||
_check(hashlib.sha256(data).hexdigest() == expected, f"Pinned source hash mismatch: {path}")
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(data, str(path), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def _python_entry(path):
|
||||
"""A venv is selected by its invocation path, not the resolved ELF target."""
|
||||
entry = Path(path).absolute()
|
||||
_check(entry.is_file(), f"Missing declared Python entry: {entry}")
|
||||
return entry
|
||||
|
||||
|
||||
def _new_memmap(path, dtype, shape):
|
||||
_check(not Path(path).exists(), f"Refusing to overwrite output: {path}")
|
||||
return np.lib.format.open_memmap(path, mode="w+", dtype=dtype, shape=shape)
|
||||
|
||||
|
||||
def _budget(root, limit, reserve=0):
|
||||
total = 0
|
||||
for directory, _, filenames in os.walk(root, followlinks=False):
|
||||
for filename in filenames:
|
||||
total += (Path(directory) / filename).stat(follow_symlinks=False).st_size
|
||||
_check(total + int(reserve) <= limit,
|
||||
f"Output budget exceeded: {total}+{reserve}>{limit}; inputs and duration were not adjusted")
|
||||
return total
|
||||
|
||||
|
||||
class _PipeReader:
|
||||
"""One bounded reader thread, with timeout/EOF checks for every frame."""
|
||||
|
||||
def __init__(self, pipe, timeout):
|
||||
self.pipe, self.timeout = pipe, float(timeout)
|
||||
self.chunks = queue.Queue(maxsize=8)
|
||||
self.buffer = bytearray()
|
||||
self.thread = threading.Thread(target=self._pump, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def _pump(self):
|
||||
try:
|
||||
while True:
|
||||
chunk = os.read(self.pipe.fileno(), 65536)
|
||||
self.chunks.put(chunk)
|
||||
if not chunk:
|
||||
return
|
||||
except BaseException as error:
|
||||
self.chunks.put(error)
|
||||
|
||||
def _fill(self, deadline):
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError("APL sidecar response timed out")
|
||||
try:
|
||||
chunk = self.chunks.get(timeout=remaining)
|
||||
except queue.Empty as error:
|
||||
raise TimeoutError("APL sidecar response timed out") from error
|
||||
if isinstance(chunk, BaseException):
|
||||
raise RuntimeError("APL sidecar stdout reader failed") from chunk
|
||||
if not chunk:
|
||||
raise EOFError("APL sidecar ended before a complete response")
|
||||
self.buffer.extend(chunk)
|
||||
|
||||
def read(self, size):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
while len(self.buffer) < size:
|
||||
self._fill(deadline)
|
||||
result = bytes(self.buffer[:size])
|
||||
del self.buffer[:size]
|
||||
return result
|
||||
|
||||
def line(self):
|
||||
deadline = time.monotonic() + self.timeout
|
||||
while b"\n" not in self.buffer:
|
||||
_check(len(self.buffer) < 1 << 20, "APL sidecar handshake exceeds 1 MiB")
|
||||
self._fill(deadline)
|
||||
end = self.buffer.index(b"\n") + 1
|
||||
result = bytes(self.buffer[:end])
|
||||
del self.buffer[:end]
|
||||
return result
|
||||
|
||||
|
||||
class _APLWorker:
|
||||
def __init__(self, python, script, bundle_dir, stderr, timeout=180):
|
||||
self.command = [str(python), "-B", str(script), "--bundle-dir", str(bundle_dir), "--dt-ms", "0.1"]
|
||||
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=stderr, bufsize=0, shell=False)
|
||||
self.reader = _PipeReader(self.process.stdout, timeout)
|
||||
self.tick = 0
|
||||
try:
|
||||
self.hello = json.loads(self.reader.line().decode("utf-8"))
|
||||
_check(self.hello.get("ok") is True, "APL sidecar did not acknowledge initialization")
|
||||
_check(self.hello.get("protocol") == PROTOCOL, "Unexpected APL sidecar protocol")
|
||||
for key, value in (("n_neurons", 166700), ("n_apl", 2), ("n_kc", 4064)):
|
||||
_check(self.hello.get(key) == value, f"APL sidecar changed {key}")
|
||||
_check(self.hello.get("dt_ms") == .1, "APL sidecar changed dt")
|
||||
expected_decay = f"{int(np.float32(math.exp(-.1 / 20.)).view(np.uint32)):08x}"
|
||||
expected_gain = f"{int(np.float32(.01).view(np.uint32)):08x}"
|
||||
_check(self.hello.get("decay_f32_bits") == expected_decay and self.hello.get("gain_f32_bits") == expected_gain,
|
||||
"APL sidecar decay/gain differs from the frozen float32 scalars")
|
||||
_check(self.hello.get("files_sha256") == {name: _digest(bundle_dir / name) for name in BUNDLE_FILES},
|
||||
"APL sidecar bundle hashes differ from the independently read bundle")
|
||||
except BaseException:
|
||||
self.abort()
|
||||
raise
|
||||
|
||||
def step(self, tick, global_spikes):
|
||||
_check(tick == self.tick, "APL sidecar request ticks must be consecutive")
|
||||
nodes = np.asarray(global_spikes)
|
||||
_check(nodes.ndim == 1 and nodes.dtype == np.dtype("int32"), "APL input spikes must be int32 globals")
|
||||
_check(not len(nodes) or (nodes[0] >= 0 and nodes[-1] < 166700 and np.all(np.diff(nodes) > 0)),
|
||||
"APL input spikes must be sorted, unique, and in the frozen global range")
|
||||
packet = struct.pack("<qQ", int(tick), len(nodes)) + np.ascontiguousarray(nodes, dtype="<i4").tobytes()
|
||||
view = memoryview(packet)
|
||||
while view:
|
||||
written = self.process.stdin.write(view)
|
||||
if not written:
|
||||
raise BrokenPipeError("APL sidecar stdin failed")
|
||||
view = view[written:]
|
||||
self.process.stdin.flush()
|
||||
response_tick, byte_count = struct.unpack("<qQ", self.reader.read(16))
|
||||
_check(response_tick == tick and byte_count == (2 + 2 + 4064) * 4,
|
||||
"APL sidecar frame tick/length mismatch")
|
||||
values = np.frombuffer(self.reader.read(byte_count), dtype="<f4")
|
||||
_check(np.isfinite(values).all() and np.all(values >= 0), "APL sidecar returned invalid nonnegative f32 states")
|
||||
self.tick += 1
|
||||
return values[:2], values[2:4], values[4:]
|
||||
|
||||
def finish(self, steps):
|
||||
_check(self.tick == steps, "APL sidecar did not process every tick")
|
||||
self.process.stdin.close()
|
||||
status = self.process.wait(timeout=30)
|
||||
_check(status == 0, f"APL sidecar exited unsuccessfully: {status}")
|
||||
self.reader.thread.join(timeout=5)
|
||||
_check(not self.reader.thread.is_alive(), "APL stdout did not reach EOF")
|
||||
extra = bytes(self.reader.buffer)
|
||||
while not self.reader.chunks.empty():
|
||||
chunk = self.reader.chunks.get_nowait()
|
||||
_check(not isinstance(chunk, BaseException), "APL stdout reader failed after the last frame")
|
||||
extra += chunk
|
||||
_check(not extra, "APL sidecar emitted unrequested trailing stdout bytes")
|
||||
self.process.stdout.close()
|
||||
return status
|
||||
|
||||
def abort(self):
|
||||
if self.process.poll() is None:
|
||||
self.process.terminate()
|
||||
try:
|
||||
self.process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.process.kill()
|
||||
self.process.wait(timeout=5)
|
||||
for handle in (self.process.stdin, self.process.stdout):
|
||||
if handle is not None and not handle.closed:
|
||||
handle.close()
|
||||
|
||||
|
||||
def _graph_inputs(directory):
|
||||
arrays = {}
|
||||
definitions = {"nodes": "int32", "edge_id": "int64", "src": "int32",
|
||||
"dst": "int32", "signed_raw": "int64", "ptr": "int64", "weights": "float64"}
|
||||
for name, dtype in definitions.items():
|
||||
value = np.load(directory / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
_check(value.ndim == 1 and value.dtype == np.dtype(dtype), f"Invalid graph array {name}")
|
||||
arrays[name] = value
|
||||
nodes, edge_ids = arrays["nodes"], arrays["edge_id"]
|
||||
n, edges = len(nodes), len(edge_ids)
|
||||
_check(0 < n <= 166700 and edges > 0, "A case needs real global nodes and explicit nonempty connectivity")
|
||||
_check(nodes[0] >= 0 and nodes[-1] < 166700 and np.all(np.diff(nodes) > 0), "Invalid global node order")
|
||||
for name in ("src", "dst", "signed_raw", "weights"):
|
||||
_check(arrays[name].shape == (edges,), f"Graph array {name} has the wrong shape")
|
||||
pointer = arrays["ptr"]
|
||||
_check(pointer.shape == (n + 1,) and pointer[0] == 0 and pointer[-1] == edges and
|
||||
np.all(np.diff(pointer) >= 0), "Invalid graph CSR pointer")
|
||||
source_counts = np.zeros(n, np.int64)
|
||||
identity_edges = True
|
||||
previous_source, previous_edge = -1, -1
|
||||
width = BLOCK_BYTES // 8
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
source, target, ids = arrays["src"][begin:stop], arrays["dst"][begin:stop], edge_ids[begin:stop]
|
||||
_check(np.all((source >= 0) & (source < n)) and np.all((target >= 0) & (target < n)),
|
||||
"Graph edge endpoint is outside the local node array")
|
||||
_check(np.all(ids >= 0), "Graph edge IDs must be nonnegative")
|
||||
_check(source[0] > previous_source or (source[0] == previous_source and ids[0] > previous_edge),
|
||||
"Graph edge blocks are not in canonical source/edge order")
|
||||
_check(np.all((source[1:] > source[:-1]) |
|
||||
((source[1:] == source[:-1]) & (ids[1:] > ids[:-1]))),
|
||||
"Graph edges are not in canonical source/edge order")
|
||||
previous_source, previous_edge = int(source[-1]), int(ids[-1])
|
||||
identity_edges &= np.array_equal(ids, np.arange(begin, stop, dtype=np.int64))
|
||||
source_counts += np.bincount(source, minlength=n)
|
||||
_check(np.isfinite(arrays["weights"][begin:stop]).all(), "Nonfinite graph weights")
|
||||
_check(np.array_equal(source_counts, np.diff(pointer)), "CSR pointer and source rows disagree")
|
||||
if not identity_edges:
|
||||
_check(len(np.unique(edge_ids)) == edges, "Original edge IDs must be unique")
|
||||
for name in ("completeness.csv", "connectivity.parquet"):
|
||||
_check((directory / name).is_file(), f"Missing unchanged author input: {name}")
|
||||
if n == 166700:
|
||||
_check(edges == 25582938 and identity_edges and np.array_equal(nodes, np.arange(n, dtype=np.int32)),
|
||||
"A full probe must use the unchanged complete canonical graph")
|
||||
return arrays, identity_edges
|
||||
|
||||
|
||||
def _requests(case_dir, nodes, steps):
|
||||
with np.load(case_dir / "requests.npz", allow_pickle=False) as archive:
|
||||
_check(set(archive.files) == {"tick", "neuron_idx"}, "Unexpected sparse request schema")
|
||||
ticks, requested = archive["tick"].copy(), archive["neuron_idx"].copy()
|
||||
_check(ticks.dtype == requested.dtype == np.dtype("int32") and ticks.ndim == requested.ndim == 1 and
|
||||
ticks.shape == requested.shape, "Requests must be equal-length int32 vectors")
|
||||
_check(np.all((ticks >= 0) & (ticks < steps)), "Request time outside the case")
|
||||
_check(not len(ticks) or np.all((ticks[1:] > ticks[:-1]) |
|
||||
((ticks[1:] == ticks[:-1]) & (requested[1:] > requested[:-1]))),
|
||||
"Requests must be sorted and unique by tick/global node")
|
||||
local = np.searchsorted(nodes, requested)
|
||||
_check(np.all(local < len(nodes)) and np.array_equal(nodes[local], requested),
|
||||
"Request references a global neuron absent from this case")
|
||||
counts = np.bincount(ticks, minlength=steps).astype(np.int32)
|
||||
pointer = np.concatenate((np.zeros(1, np.int64), np.cumsum(counts, dtype=np.int64)))
|
||||
return requested, local.astype(np.int32), pointer, counts
|
||||
|
||||
|
||||
class _QueueProxy:
|
||||
def __init__(self, original, get_tick, available, arrays, identity_edges,
|
||||
due_counts, spike_ring, delay_steps, handle, records, summarize):
|
||||
self._original_queue, self._get_tick = original, get_tick
|
||||
self._available, self._arrays, self._identity_edges = available, arrays, identity_edges
|
||||
self._due_counts, self._spike_ring, self._delay_steps = due_counts, spike_ring, delay_steps
|
||||
self._handle, self._records, self._summarize = handle, records, summarize
|
||||
|
||||
def peek(self, *args, **kwargs):
|
||||
original = self._original_queue.peek(*args, **kwargs)
|
||||
local_edges = np.asarray(original)
|
||||
tick = self._get_tick()
|
||||
_check(tick == len(self._records["rows"]), "Queue peek ticks must be consecutive")
|
||||
_check(local_edges.ndim == 1 and np.all((local_edges >= 0) & (local_edges < len(self._arrays["edge_id"]))),
|
||||
"Original queue contains an invalid local edge index")
|
||||
_check(len(local_edges) == self._due_counts[tick % len(self._due_counts)],
|
||||
"Queue arrivals differ from the fixed-delay outgoing degree of actual spikes")
|
||||
fired_then = np.zeros(len(self._arrays["nodes"]), dtype=bool)
|
||||
if tick >= self._delay_steps:
|
||||
fired_then[self._spike_ring[(tick - self._delay_steps) % len(self._spike_ring)]] = True
|
||||
|
||||
def accepted(begin, stop):
|
||||
part = local_edges[begin:stop]
|
||||
_check(np.all(fired_then[self._arrays["src"][part]]), "Arrival lacks a presynaptic spike at author delay")
|
||||
return self._available[self._arrays["dst"][part]]
|
||||
|
||||
# Only the observed ID view changes; the queue is returned verbatim.
|
||||
edge_ids = local_edges if self._identity_edges else self._arrays["edge_id"][local_edges]
|
||||
summary = self._summarize(tick, edge_ids, accepted)
|
||||
self._handle.write(json.dumps(summary, sort_keys=True) + "\n")
|
||||
self._records["rows"].append(summary)
|
||||
self._records["peek_calls"] += 1
|
||||
return original
|
||||
|
||||
def advance(self, *args, **kwargs):
|
||||
self._records["advance_calls"] += 1
|
||||
return self._original_queue.advance(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._original_queue, name)
|
||||
|
||||
|
||||
def run_case(case_dir: Path, out_dir: Path) -> dict:
|
||||
"""Run one continuous case, with exclusive outputs and no candidate trace input.
|
||||
|
||||
Graph inputs are the AF array/author-table schema plus original weights.npy.
|
||||
requests.npz contains sparse tick/neuron_idx int32 vectors. Optional
|
||||
arrays.npz contains initial_v_mv/initial_g_mv float64 vectors only.
|
||||
Full probes write state hashes, sparse events and APL traces, not dense
|
||||
whole-brain histories. Each case requires a fresh process.
|
||||
"""
|
||||
global _PROCESS_USED
|
||||
_check(not _PROCESS_USED, "Every oracle case requires a fresh process")
|
||||
_PROCESS_USED = True
|
||||
case_dir, out_dir = Path(case_dir).resolve(strict=True), Path(out_dir).resolve()
|
||||
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
|
||||
graph_dir = Path(spec["graph_dir"]).resolve(strict=True)
|
||||
bundle_dir = Path(spec["apl_bundle_dir"]).resolve(strict=True)
|
||||
worker_path = Path(spec["legacy_apl_worker"]).resolve(strict=True)
|
||||
science_python = _python_entry(spec["science_python"])
|
||||
author_path = Path(spec.get("author_path", AUTHOR_PATH)).resolve(strict=True)
|
||||
ae_path = Path(spec.get("ae_oracle_path", AE_PATH)).resolve(strict=True)
|
||||
af_path = Path(spec.get("af_oracle_path", AF_PATH)).resolve(strict=True)
|
||||
coefficients_path = Path(spec["coefficients_path"]).resolve(strict=True)
|
||||
steps = int(spec["steps"])
|
||||
_check(steps == spec["steps"] and 0 < steps <= np.iinfo(np.int32).max, "Invalid number of steps")
|
||||
_check(float(spec["dt_ms"]) == .1 and np.isclose(float(spec["duration_ms"]), steps * .1, rtol=0, atol=1e-12),
|
||||
"The frozen APL protocol requires dt_ms=0.1 and a matching duration")
|
||||
_check(spec.get("precision", "float64") == "float64", "Author state must be float64")
|
||||
_check(spec.get("training", False) is False and spec.get("poi_used", False) is False,
|
||||
"Training and original poi are outside this gate")
|
||||
_check(spec["apl"] == {"tau_ms": 20, "gain": .01, "normalization": 1}, "Frozen APL parameters changed")
|
||||
record_history = spec["record_history"]
|
||||
_check(isinstance(record_history, bool), "record_history must be a boolean")
|
||||
budget_root = Path(spec.get("budget_root", out_dir.parent)).resolve()
|
||||
budget_limit = int(spec.get("output_budget_bytes", 15 * 1024 ** 3))
|
||||
_check(budget_limit > 0 and out_dir.is_relative_to(budget_root), "Invalid output budget/root")
|
||||
out_dir.mkdir(parents=True, exist_ok=False)
|
||||
mapped_files = []
|
||||
saved_preferences, old_dt, worker = None, None, None
|
||||
last_complete_tick = -1
|
||||
try:
|
||||
arrays, identity_edges = _graph_inputs(graph_dir)
|
||||
nodes, n, edges = arrays["nodes"], len(arrays["nodes"]), len(arrays["edge_id"])
|
||||
_check(n < 166700 or not record_history, "Full probes must not save dense V/g/ref histories")
|
||||
bundle = {name: np.load(bundle_dir / (name + ".npy"), mmap_mode="r", allow_pickle=False)
|
||||
for name in ("input_matrix", "output_matrix", "kc_globals", "apl_globals")}
|
||||
for name, shape in (("input_matrix", (2, 166700)), ("output_matrix", (2, 4064))):
|
||||
value = bundle[name]
|
||||
_check(value.dtype == np.dtype("float32") and value.shape == shape and value.flags.c_contiguous,
|
||||
f"Frozen APL {name} shape/dtype/layout changed")
|
||||
_check(np.isfinite(value).all() and np.all(value >= 0), f"Invalid positive raw-normalized APL {name}")
|
||||
for name, count in (("kc_globals", 4064), ("apl_globals", 2)):
|
||||
value = bundle[name]
|
||||
_check(value.dtype == np.dtype("int32") and value.shape == (count,) and value[0] >= 0 and
|
||||
value[-1] < 166700 and np.all(np.diff(value) > 0), f"Frozen APL identity {name} changed")
|
||||
_check(not np.intersect1d(bundle["kc_globals"], bundle["apl_globals"]).size, "APL and KC identities overlap")
|
||||
kc_positions = np.searchsorted(nodes, bundle["kc_globals"])
|
||||
present = kc_positions < n
|
||||
present[present] &= nodes[kc_positions[present]] == bundle["kc_globals"][present]
|
||||
kc_local, kc_in_bundle = kc_positions[present], np.flatnonzero(present)
|
||||
requested_globals, request_local, request_ptr, request_count = _requests(case_dir, nodes, steps)
|
||||
initial = None
|
||||
if (case_dir / "arrays.npz").exists():
|
||||
with np.load(case_dir / "arrays.npz", allow_pickle=False) as archive:
|
||||
_check(set(archive.files) == {"initial_v_mv", "initial_g_mv"}, "Unexpected initial-state schema")
|
||||
initial = {name: archive[name].copy() for name in archive.files}
|
||||
for name, value in initial.items():
|
||||
_check(value.dtype == np.dtype("float64") and value.shape == (n,) and np.isfinite(value).all(),
|
||||
f"Invalid diagnostic initial state: {name}")
|
||||
history_bytes = steps * n * 40 if record_history else 0
|
||||
_budget(budget_root, budget_limit, history_bytes + steps * (2 + 2 + 4064) * 4 + edges * 8 + (64 << 20))
|
||||
versions = {name: importlib.metadata.version(name) for name in
|
||||
("brian2", "numpy", "pandas", "pyarrow", "sympy", "scipy", "cython")}
|
||||
_save(out_dir / "environment.json", {"python": sys.version, "platform": platform.platform(),
|
||||
"packages": versions, "codegen": "numpy", "author_precision": "float64",
|
||||
"APL_precision": "float32 in separate science-Python process", "Torch_imported_into_Brian": False})
|
||||
for name, required in (("brian2", "2.5.1"), ("numpy", "1.22.3"), ("sympy", "1.12")):
|
||||
_check(versions[name] == required, f"{name} must remain pinned to {required}")
|
||||
import brian2 as b2
|
||||
import pandas as pd
|
||||
from brian2.core.base import BrianObject
|
||||
|
||||
ae = _load_checked(ae_path, AE_SHA256, "phase4ag_sealed_ae_helpers")
|
||||
af = _load_checked(af_path, AF_SHA256, "phase4ag_sealed_af_helpers")
|
||||
author = _load_checked(author_path, AUTHOR_SHA256, "phase4ag_unchanged_author")
|
||||
_check("torch" not in sys.modules, "Torch must not be imported into the pinned Brian process")
|
||||
comp = pd.read_csv(graph_dir / "completeness.csv", index_col=0)
|
||||
_check(np.array_equal(comp.index.to_numpy(), nodes) and
|
||||
np.array_equal(comp["local_index"].to_numpy(), np.arange(n)), "Author CSV neuron identity differs")
|
||||
del comp
|
||||
graph_hashes = {name: _digest(graph_dir / name) for name in
|
||||
tuple(key + ".npy" for key in arrays) + ("completeness.csv", "connectivity.parquet")}
|
||||
bundle_hashes = {name: _digest(bundle_dir / name) for name in BUNDLE_FILES}
|
||||
case_hashes = {name: _digest(case_dir / name) for name in ("spec.json", "requests.npz")}
|
||||
if initial is not None:
|
||||
case_hashes["arrays.npz"] = _digest(case_dir / "arrays.npz")
|
||||
worker_hash = _digest(worker_path)
|
||||
coefficient_hash = _digest(coefficients_path)
|
||||
baseline_coefficients = json.loads(coefficients_path.read_text(encoding="utf-8"))
|
||||
if "legacy_apl_worker_sha256" in spec:
|
||||
_check(worker_hash == spec["legacy_apl_worker_sha256"], "APL worker differs from preregistered source")
|
||||
_save(out_dir / "input-audit.json", {"spec": spec, "case_hashes": case_hashes,
|
||||
"graph_hashes": graph_hashes, "bundle_hashes": bundle_hashes,
|
||||
"author_sha256": AUTHOR_SHA256, "ae_sha256": AE_SHA256, "af_sha256": AF_SHA256,
|
||||
"legacy_apl_worker_sha256": worker_hash, "coefficients_sha256": coefficient_hash,
|
||||
"candidate_trace_used_as_input": False,
|
||||
"scope": "shared-APL-component closed-loop oracle, not all-components-independent CPU equivalence"})
|
||||
saved_preferences = {key: b2.prefs[key] for key in
|
||||
("codegen.target", "core.default_float_dtype", "legacy.refractory_timing")}
|
||||
old_dt = b2.defaultclock.dt
|
||||
b2.start_scope()
|
||||
b2.prefs["codegen.target"] = "numpy"
|
||||
b2.prefs["core.default_float_dtype"] = np.float64
|
||||
b2.prefs["legacy.refractory_timing"] = False
|
||||
b2.defaultclock.dt = .1 * b2.ms
|
||||
parameters = dict(author.default_params)
|
||||
_save(out_dir / "parameter-checks.json", ae._parameters_match(parameters, spec["model"], b2))
|
||||
neu, syn, spikes = author.create_model(graph_dir / "completeness.csv", graph_dir / "connectivity.parquet", parameters)
|
||||
_check(len(neu) == n and len(syn) == edges, "Author loaded an unexpected graph size")
|
||||
_check(all(np.dtype(neu.variables[key].dtype) == np.float64 for key in ("v", "g")) and
|
||||
np.dtype(syn.variables["w"].dtype) == np.float64, "Author states/weights are not float64")
|
||||
effective = _new_memmap(out_dir / "weights.npy", "<f8", (edges,))
|
||||
mapped_files.append(effective)
|
||||
width, w_unit, mask_count = BLOCK_BYTES // 8, float(parameters["w_syn"] / b2.volt), 0
|
||||
mask_hasher = hashlib.sha256()
|
||||
base_hasher = hashlib.sha256()
|
||||
effective_hasher = hashlib.sha256()
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
np.testing.assert_array_equal(np.asarray(syn.i[begin:stop]), arrays["src"][begin:stop])
|
||||
np.testing.assert_array_equal(np.asarray(syn.j[begin:stop]), arrays["dst"][begin:stop])
|
||||
original = np.asarray(syn.w_[begin:stop], dtype=np.float64).copy()
|
||||
raw_expected = arrays["signed_raw"][begin:stop] * w_unit
|
||||
_check(np.array_equal(original.view(np.uint64), raw_expected.view(np.uint64)) and
|
||||
np.array_equal(original.view(np.uint64), arrays["weights"][begin:stop].view(np.uint64)),
|
||||
"Actual original Brian weights differ from unchanged signed raw or AF base weights")
|
||||
base_hasher.update(np.ascontiguousarray(original, dtype="<f8").tobytes())
|
||||
mask = np.isin(nodes[arrays["src"][begin:stop]], bundle["apl_globals"])
|
||||
mask_count += int(mask.sum())
|
||||
mask_hasher.update(np.ascontiguousarray(arrays["edge_id"][begin:stop][mask], dtype="<i8").tobytes())
|
||||
original[mask] = np.float64(0.0)
|
||||
effective_hasher.update(np.ascontiguousarray(original, dtype="<f8").tobytes())
|
||||
effective[begin:stop] = original
|
||||
syn.w_[begin:stop] = original
|
||||
if n == 166700:
|
||||
_check(mask_count == 5845, "Full legacy APL fast-edge mask must contain exactly 5845 existing edges")
|
||||
effective.flush()
|
||||
_save(out_dir / "apl-mask-audit.json", {"count": mask_count, "ordered_original_edge_id_sha256": mask_hasher.hexdigest(),
|
||||
"base_weight_raw_bytes_sha256": base_hasher.hexdigest(), "effective_weights_file_sha256": _digest(out_dir / "weights.npy"),
|
||||
"effective_weights_raw_sha256": effective_hasher.hexdigest(),
|
||||
"rule": "zero +0 all existing fast edges whose source GLOBAL neuron is in apl_globals; no graph edge removal",
|
||||
"graded_targets": "only KC identities present in this LIF case", "KC_targets_in_case": len(kc_local),
|
||||
"base_graph_files_modified": False, "fullgraph_required_mask_count": 5845})
|
||||
if initial is not None:
|
||||
neu.v = initial["initial_v_mv"] * b2.mV
|
||||
neu.g = initial["initial_g_mv"] * b2.mV
|
||||
v_raw = neu.variables["v"].get_value()
|
||||
g_raw = neu.variables["g"].get_value()
|
||||
last_raw = neu.variables["lastspike"].get_value()
|
||||
available_raw = neu.variables["not_refractory"].get_value()
|
||||
histories = {}
|
||||
pre_apl_histories = {}
|
||||
if record_history:
|
||||
for name, dtype in (("v", "float64"), ("g", "float64"), ("ref", "int32")):
|
||||
histories[name] = _new_memmap(out_dir / f"{name}-history.{dtype}.npy", np.dtype(dtype), (steps, n))
|
||||
mapped_files.append(histories[name])
|
||||
pre_apl_histories[name] = _new_memmap(out_dir / f"preAPL-{name}.{dtype}.npy", np.dtype(dtype), (steps, n))
|
||||
mapped_files.append(pre_apl_histories[name])
|
||||
apl_history = {}
|
||||
for name, count in (("drive", 2), ("state", 2), ("inhibition", 4064)):
|
||||
apl_history[name] = _new_memmap(out_dir / f"apl-{name}.float32.npy", "<f4", (steps, count))
|
||||
mapped_files.append(apl_history[name])
|
||||
_save(out_dir / "output-schema.json", {"history_recorded": record_history, "history_shape": [steps, n],
|
||||
"state_hash_file": "tick-state-hashes.jsonl", "state_hash_encoding": "SHA256 of contiguous little-endian raw f64 V/g and i32 ref",
|
||||
"APL_dtype": "float32", "APL_h_units": "mV; gain already applied in float32",
|
||||
"APL_SI_conversion": "h.astype(float64) * float64(0.001), then separate float64 g subtraction",
|
||||
"APL_write_schedule": "end, order=0, after original reset, unconditional raw g write; recorder end/order=1",
|
||||
"APL_input": "Brian's own actual global spikes, never candidate spikes or candidate inhibition",
|
||||
"APL_shapes": {"drive": [steps, 2], "state": [steps, 2], "inhibition": [steps, 4064]},
|
||||
"request_columns": ["tick", "neuron_idx", "attempted", "accepted", "rejected"],
|
||||
"queue_protocol": "sealed Phase4AF queue_summary; original global edge IDs, including zero-weight/ref-rejected arrivals",
|
||||
"state_npz": ["step", "neuron_idx", "counts", "v", "g", "ref"],
|
||||
"complete_marker": "summary.json complete=true only after worker exit and all checks"})
|
||||
delay_steps = int(round(float(parameters["t_dly"] / b2.ms) / .1))
|
||||
ref_steps = int(round(float(parameters["t_rfc"] / b2.ms) / .1))
|
||||
_check(delay_steps == 18 and ref_steps == 22, "Author delay/refractory changed")
|
||||
queue_len = delay_steps + 1
|
||||
due_counts = np.zeros(queue_len, np.int64)
|
||||
spike_ring = [np.empty(0, np.int32) for _ in range(queue_len)]
|
||||
outdegree = np.diff(arrays["ptr"])
|
||||
request_seen = np.zeros(steps, np.uint8)
|
||||
apl_seen = np.zeros(steps, np.uint8)
|
||||
applicable_count = np.zeros(steps, np.int32)
|
||||
rejected_count = np.zeros(steps, np.int32)
|
||||
spike_count = np.zeros(steps, np.int32)
|
||||
total_counts = np.zeros(n, np.int32)
|
||||
records = {"rows": [], "peek_calls": 0, "advance_calls": 0, "installations": []}
|
||||
last_ticks = np.zeros(n, np.int64)
|
||||
final_ref = np.zeros(n, np.int32)
|
||||
permitted = np.empty(0, np.int32)
|
||||
total_events = 0
|
||||
|
||||
def tick_now():
|
||||
return int(round(float(neu.clock.t / b2.second) / .0001))
|
||||
|
||||
def current_ref(tick):
|
||||
seen = np.isfinite(last_raw) & (last_raw >= 0)
|
||||
last_ticks.fill(0)
|
||||
last_ticks[seen] = np.rint(last_raw[seen] / .0001).astype(np.int64)
|
||||
result = np.where(seen, np.maximum(ref_steps - (tick - last_ticks), 0), 0).astype(np.int32)
|
||||
_check(np.array_equal(result == 0, available_raw), "Ref history disagrees with author not_refractory")
|
||||
return result
|
||||
|
||||
with ExitStack() as stack:
|
||||
stderr = stack.enter_context((out_dir / "apl-worker.stderr.log").open("xb"))
|
||||
worker = _APLWorker(science_python, worker_path, bundle_dir, stderr, float(spec.get("worker_timeout_seconds", 180)))
|
||||
_save(out_dir / "apl-worker-handshake.json", {"command": worker.command, "handshake": worker.hello})
|
||||
queue_file = stack.enter_context((out_dir / "queue.jsonl").open("x", encoding="utf-8"))
|
||||
event_file = stack.enter_context((out_dir / "events.bin").open("xb"))
|
||||
hash_file = stack.enter_context((out_dir / "tick-state-hashes.jsonl").open("x", encoding="utf-8"))
|
||||
tick_file = stack.enter_context((out_dir / "ticks.jsonl").open("x", encoding="utf-8"))
|
||||
requests_file = stack.enter_context((out_dir / "requests.csv").open("x", encoding="utf-8", newline=""))
|
||||
request_writer = csv.DictWriter(requests_file, fieldnames=["tick", "neuron_idx", "attempted", "accepted", "rejected"])
|
||||
request_writer.writeheader()
|
||||
|
||||
def request_operation():
|
||||
nonlocal permitted
|
||||
tick = tick_now()
|
||||
_check(0 <= tick < steps and not request_seen[tick], "Invalid/duplicate common request tick")
|
||||
request_seen[tick] = 1
|
||||
begin, stop = request_ptr[tick:tick + 2]
|
||||
local = request_local[begin:stop]
|
||||
available = available_raw[local].copy()
|
||||
permitted = local[available]
|
||||
applicable_count[tick] = len(permitted)
|
||||
rejected_count[tick] = len(local) - len(permitted)
|
||||
for node, accepted in zip(requested_globals[begin:stop], available):
|
||||
request_writer.writerow({"tick": tick, "neuron_idx": int(node), "attempted": 1,
|
||||
"accepted": int(accepted), "rejected": int(not accepted)})
|
||||
if len(permitted):
|
||||
neu.v[permitted] = parameters["v_th"] + 1 * b2.mV
|
||||
|
||||
def apl_operation():
|
||||
tick = tick_now()
|
||||
_check(0 <= tick < steps and not apl_seen[tick] and request_seen[tick], "Invalid/duplicate APL tick")
|
||||
active = np.asarray(neu.spikes, dtype=np.int32)
|
||||
_check(not len(active) or np.all(np.diff(active) > 0), "Brian actual spikes are not canonical/unique")
|
||||
_check(np.all(g_raw[active] == 0.0), "APL ran before the original author g reset")
|
||||
if record_history:
|
||||
pre_apl_histories["v"][tick] = v_raw
|
||||
pre_apl_histories["g"][tick] = g_raw
|
||||
pre_apl_histories["ref"][tick] = current_ref(tick)
|
||||
drive, state, inhibition = worker.step(tick, np.asarray(nodes[active], dtype=np.int32))
|
||||
apl_history["drive"][tick] = drive
|
||||
apl_history["state"][tick] = state
|
||||
apl_history["inhibition"][tick] = inhibition
|
||||
# These are separate array operations. Do not move the unit
|
||||
# conversion into a fused subtract; do not guard by refractory.
|
||||
delta_si = inhibition[kc_in_bundle].astype(np.float64) * np.float64(.001)
|
||||
g_raw[kc_local] = g_raw[kc_local] - delta_si
|
||||
apl_seen[tick] = 1
|
||||
|
||||
def record_end():
|
||||
nonlocal last_complete_tick, total_events, final_ref
|
||||
tick = tick_now()
|
||||
_check(tick == last_complete_tick + 1 and tick < steps and apl_seen[tick], "End tick is incomplete/out of order")
|
||||
_check(len(records["rows"]) == tick + 1, "End state lacks queue observation")
|
||||
_check(np.isfinite(v_raw).all() and np.isfinite(g_raw).all(), "Nonfinite author/APL state")
|
||||
final_ref = current_ref(tick)
|
||||
if record_history:
|
||||
histories["v"][tick], histories["g"][tick], histories["ref"][tick] = v_raw, g_raw, final_ref
|
||||
active = np.asarray(neu.spikes, dtype=np.int32)
|
||||
_check(np.isin(permitted, active).all(), "An applicable request did not produce a real spike")
|
||||
spike_count[tick] = len(active)
|
||||
total_counts[active] += 1
|
||||
if len(active):
|
||||
events = np.empty(len(active), EVENT_DTYPE)
|
||||
events["step"], events["neuron_idx"] = tick, nodes[active]
|
||||
event_file.write(memoryview(events).cast("B"))
|
||||
total_events += len(active)
|
||||
spike_ring[tick % queue_len] = active.copy()
|
||||
due_counts[(tick + delay_steps) % queue_len] = int(outdegree[active].sum(dtype=np.int64))
|
||||
hash_file.write(json.dumps({"tick": tick, "v_sha256": _array_hash(v_raw, "<f8"),
|
||||
"g_sha256": _array_hash(g_raw, "<f8"), "ref_sha256": _array_hash(final_ref, "<i4")}, sort_keys=True) + "\n")
|
||||
tick_file.write(json.dumps({"tick": tick, "spikes": len(active), "requests": int(request_count[tick]),
|
||||
"applicable_requests": int(applicable_count[tick]), "ref_rejected_requests": int(rejected_count[tick]),
|
||||
"event_sha256": _array_hash(nodes[active], "<i4"),
|
||||
"due_arrivals": records["rows"][tick]["count"], "accepted_arrivals": records["rows"][tick]["accepted_count"],
|
||||
"APL_drive_sha256": _array_hash(apl_history["drive"][tick], "<f4"),
|
||||
"APL_state_sha256": _array_hash(apl_history["state"][tick], "<f4"),
|
||||
"APL_inhibition_sha256": _array_hash(apl_history["inhibition"][tick], "<f4")}, sort_keys=True) + "\n")
|
||||
_budget(budget_root, budget_limit, reserve=1 << 20)
|
||||
last_complete_tick = tick
|
||||
if (tick + 1) % 100 == 0 or tick + 1 == steps:
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
for handle in (queue_file, event_file, hash_file, tick_file, requests_file):
|
||||
handle.flush()
|
||||
print(json.dumps({"oracle": spec["name"], "completed_ticks": tick + 1,
|
||||
"spikes": total_events, "APL_worker_ticks": worker.tick}), flush=True)
|
||||
|
||||
class QueueInstaller(BrianObject):
|
||||
def __init__(self):
|
||||
super().__init__(clock=neu.clock, when="end", order=1_000_000, name="zz_apl_queue_installer")
|
||||
|
||||
def before_run(self, run_namespace):
|
||||
installed = []
|
||||
for code in syn.pre.code_objects:
|
||||
if "_queue" not in code.namespace or "_queue.peek()" not in str(code.code):
|
||||
continue
|
||||
original = code.namespace["_queue"]
|
||||
if isinstance(original, _QueueProxy):
|
||||
original = original._original_queue
|
||||
code.namespace["_queue"] = _QueueProxy(original, tick_now, available_raw, arrays, identity_edges,
|
||||
due_counts, spike_ring, delay_steps, queue_file, records, af.queue_summary)
|
||||
installed.append(code.name)
|
||||
_check(len(installed) == 1, "Expected one original anatomical queue consumer")
|
||||
records["installations"].append(installed[0])
|
||||
|
||||
objects = [neu, syn, spikes,
|
||||
b2.NetworkOperation(request_operation, clock=neu.clock, when="before_thresholds", order=0, name="oracle_common_requests"),
|
||||
b2.NetworkOperation(apl_operation, clock=neu.clock, when="end", order=0, name="oracle_legacy_apl_unconditional"),
|
||||
b2.NetworkOperation(record_end, clock=neu.clock, when="end", order=1, name="oracle_after_apl_record"), QueueInstaller()]
|
||||
network = b2.Network(*objects)
|
||||
_save(out_dir / "schedule.json", {"schedule": list(network.schedule), "summary": str(network.scheduling_summary())})
|
||||
network.run(0 * b2.ms, namespace={})
|
||||
_save(out_dir / "compiled-code.json", ae._code_records(network))
|
||||
compiled_coefficients = ae._coefficients(neu, parameters, b2, .0001)
|
||||
coefficient_keys = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
_check(all(float(compiled_coefficients[key]).hex() == float(baseline_coefficients[key]).hex()
|
||||
for key in coefficient_keys), "Compiled Brian coefficients differ from sealed AF")
|
||||
_check(last_complete_tick == -1 and worker.tick == 0 and not records["rows"] and not request_seen.any(),
|
||||
"Zero-duration compilation unexpectedly advanced state")
|
||||
network.run(steps * .1 * b2.ms, namespace={})
|
||||
_save(out_dir / "generated-code.json", ae._code_records(network))
|
||||
worker_status = worker.finish(steps)
|
||||
worker = None
|
||||
|
||||
_check(last_complete_tick == steps - 1 and np.all(request_seen == 1) and np.all(apl_seen == 1), "Incomplete trial")
|
||||
_check(records["peek_calls"] == records["advance_calls"] == steps, "Incomplete queue audit")
|
||||
_check(total_events == int(spikes.num_spikes) and np.array_equal(total_counts, np.asarray(spikes.count[:])),
|
||||
"Sparse events disagree with original SpikeMonitor")
|
||||
_check(np.array_equal(request_count, applicable_count + rejected_count), "Incomplete request accounting")
|
||||
_check(np.all(np.asarray(neu.rfc[:] / b2.ms) == float(parameters["t_rfc"] / b2.ms)), "Author refractory duration changed")
|
||||
for begin in range(0, edges, width):
|
||||
stop = min(edges, begin + width)
|
||||
_check(np.array_equal(np.asarray(syn.w_[begin:stop]).view(np.uint64), effective[begin:stop].view(np.uint64)),
|
||||
"Effective synaptic weights changed during the trial")
|
||||
event_values = np.memmap(out_dir / "events.bin", dtype=EVENT_DTYPE, mode="r", shape=(total_events,)) if total_events else np.empty(0, EVENT_DTYPE)
|
||||
_check((out_dir / "events.bin").stat().st_size == total_events * 8, "Sparse event byte count mismatch")
|
||||
for begin in range(0, total_events, width):
|
||||
stop = min(total_events, begin + width)
|
||||
spike_local = np.asarray(spikes.i[begin:stop], dtype=np.int32)
|
||||
np.testing.assert_array_equal(event_values["neuron_idx"][begin:stop], nodes[spike_local])
|
||||
times = np.rint(np.asarray(spikes.t[begin:stop] / b2.second) / .0001).astype(np.int32)
|
||||
np.testing.assert_array_equal(event_values["step"][begin:stop], times)
|
||||
_budget(budget_root, budget_limit, total_events * 8 + n * 24 + (8 << 20))
|
||||
with (out_dir / "state.npz").open("xb") as handle:
|
||||
np.savez_compressed(handle, step=event_values["step"], neuron_idx=event_values["neuron_idx"],
|
||||
counts=total_counts, v=v_raw, g=g_raw, ref=final_ref)
|
||||
with (out_dir / "tick-summary.npz").open("xb") as handle:
|
||||
np.savez_compressed(handle, request_count=request_count, applicable_count=applicable_count,
|
||||
rejected_count=rejected_count, spike_count=spike_count,
|
||||
arrival_count=np.asarray([row["count"] for row in records["rows"]], np.int64),
|
||||
accepted_arrival_count=np.asarray([row["accepted_count"] for row in records["rows"]], np.int64))
|
||||
coefficients = ae._coefficients(neu, parameters, b2, .0001)
|
||||
keys = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
|
||||
_check(all(float(coefficients[key]).hex() == float(baseline_coefficients[key]).hex() for key in keys),
|
||||
"Actual Brian coefficients differ from sealed AF coefficients")
|
||||
_save(out_dir / "coefficients.json", coefficients)
|
||||
_save(out_dir / "queue-audit.json", {"peek_calls": records["peek_calls"], "advance_calls": records["advance_calls"],
|
||||
"installations": records["installations"], "same_peek_object_returned": True,
|
||||
"original_edge_ids_not_sorted_or_filtered": True, "zero_weight_and_refractory_rejected_edges_retained": True,
|
||||
"post_threshold_pre_synaptic_guard_observed": True, "forwarding_contract": "sealed AE/AF design",
|
||||
"new_unwrapped_repeat_performed": False, "limit": "Current wrapper was not independently rerun without the queue proxy"})
|
||||
for path, expected in ((author_path, AUTHOR_SHA256), (ae_path, AE_SHA256), (af_path, AF_SHA256), (worker_path, worker_hash)):
|
||||
_check(_digest(path) == expected, f"Source changed during the run: {path}")
|
||||
_check(_digest(coefficients_path) == coefficient_hash, "Sealed coefficient reference changed during the run")
|
||||
for directory, hashes in ((graph_dir, graph_hashes), (bundle_dir, bundle_hashes), (case_dir, case_hashes)):
|
||||
for name, expected in hashes.items():
|
||||
_check(_digest(directory / name) == expected, f"Read-only input changed during the run: {name}")
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
_check("torch" not in sys.modules, "Pinned Brian process unexpectedly imported Torch")
|
||||
output_bytes = _budget(budget_root, budget_limit, 1 << 20)
|
||||
summary = {"name": spec["name"], "complete": True, "precision": "float64", "APL_precision": "float32",
|
||||
"backend": "shared-APL-component closed-loop Brian2 oracle", "steps": steps, "completed_ticks": last_complete_tick + 1,
|
||||
"nodes": n, "edges": edges, "spikes": total_events, "requests": int(request_count.sum()),
|
||||
"applicable_requests": int(applicable_count.sum()), "ref_rejected_requests": int(rejected_count.sum()),
|
||||
"APL_fast_edges_zeroed": mask_count, "APL_worker_exit_status": worker_status, "APL_worker_ticks": steps,
|
||||
"apl_fast_edges_zeroed": mask_count, "effective_weights_raw_sha256": effective_hasher.hexdigest(),
|
||||
"kc_targets_present": len(kc_local),
|
||||
"candidate_trace_used_as_input": False, "independent_CPU_all_components_claim": False,
|
||||
"history_recorded": record_history, "single_continuous_run": True, "no_training": True, "no_poi": True,
|
||||
"source_and_inputs_unchanged": True, "pass_all": True,
|
||||
"pass_all_scope": "Oracle construction, protocol and internal self-checks only; candidate comparison is a separate gate",
|
||||
"scope": "Only this fixed trajectory; legacy Torch APL is a shared numerical component, not an independent CPU APL implementation",
|
||||
"output_budget_bytes": budget_limit, "output_bytes_before_summary": output_bytes,
|
||||
"state_hashes_sha256": _digest(out_dir / "tick-state-hashes.jsonl"), "queue_sha256": _digest(out_dir / "queue.jsonl"),
|
||||
"events_sha256": _digest(out_dir / "events.bin"), "weights_sha256": _digest(out_dir / "weights.npy")}
|
||||
_save(out_dir / "summary.json", summary)
|
||||
return summary
|
||||
except Exception as error:
|
||||
for mapped in mapped_files:
|
||||
mapped.flush()
|
||||
_save(out_dir / "error.json", {"complete": False, "last_complete_tick": last_complete_tick,
|
||||
"type": type(error).__name__, "message": str(error), "partial_files_preserved": True,
|
||||
"inputs_or_duration_adjusted": False})
|
||||
raise
|
||||
finally:
|
||||
if worker is not None:
|
||||
worker.abort()
|
||||
if saved_preferences is not None:
|
||||
for key, value in saved_preferences.items():
|
||||
b2.prefs[key] = value
|
||||
b2.defaultclock.dt = old_dt
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Preregister exact legacy APL data and unmodified fixed CAL requests."""
|
||||
from datetime import datetime,timezone
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from context import ROOT,MODEL,EXP,AF_MODEL,AF_EXP,AD_EXP,CFG,save,digest,sources,integrity,budget
|
||||
|
||||
def arr(path,value):
|
||||
with Path(path).open('xb') as f:np.save(f,value,allow_pickle=False)
|
||||
|
||||
def graph(folder,nodes,full):
|
||||
folder.mkdir();nodes=np.asarray(nodes,np.int32);ptr,dst,src,raw,base=full
|
||||
if len(nodes)==CFG['neurons']:ids=np.arange(len(dst),dtype=np.int64)
|
||||
else:
|
||||
ids=np.concatenate([np.arange(ptr[s],ptr[s+1],dtype=np.int64) for s in nodes])
|
||||
ids=ids[np.isin(dst[ids],nodes)]
|
||||
lookup=np.full(CFG['neurons'],-1,np.int32);lookup[nodes]=np.arange(len(nodes),dtype=np.int32)
|
||||
ss,dd=lookup[src[ids]],lookup[dst[ids]];pp=np.r_[np.int64(0),np.bincount(ss,minlength=len(nodes)).cumsum(dtype=np.int64)]
|
||||
for name,value in dict(nodes=nodes,edge_id=ids,src=ss,dst=dd,ptr=pp,signed_raw=raw[ids],weights=base[ids]).items():arr(folder/(name+'.npy'),value)
|
||||
pd.DataFrame({'local_index':np.arange(len(nodes),dtype=np.int32)},index=pd.Index(nodes,name='global_neuron_idx')).to_csv(folder/'completeness.csv')
|
||||
pd.DataFrame({'Presynaptic_Index':ss,'Postsynaptic_Index':dd,'Excitatory x Connectivity':raw[ids],'edge_id':ids},copy=False).to_parquet(folder/'connectivity.parquet',index=False)
|
||||
save(folder/'audit.json',dict(nodes=len(nodes),edges=len(ids),canonical_original_edge_ids=True,anatomical_weights_not_modified=True,
|
||||
effective_APL_zeroing='Only in new runtime clone, never these original weight files'))
|
||||
return nodes
|
||||
|
||||
def case(name,kind,steps,nodes,graphdir,requests,initial=None,extra=None):
|
||||
folder=EXP/'cases'/name;folder.mkdir()
|
||||
model=json.loads((AD_EXP/'protocol/preregistered.json').read_text())['config']['model']
|
||||
spec=dict(name=name,kind=kind,steps=steps,dt_ms=.1,duration_ms=steps*.1,model=model,
|
||||
graph_dir=str(graphdir),apl_bundle_dir=str(EXP/'apl-bundle'),apl=CFG['apl'],record_history=kind=='fixture',
|
||||
coefficients_path=str(AF_EXP/'oracle/no_input/coefficients.json'),
|
||||
science_python=str(ROOT/'models/male-cns-lif/.venv/bin/python'),legacy_apl_worker=str(MODEL/'legacy_apl_worker.py'),
|
||||
budget_root=str(EXP),output_budget_bytes=CFG['output_budget_bytes'],precision='float64',apl_precision='float32',training=False)
|
||||
if extra:spec.update(extra)
|
||||
requests=np.asarray(requests,dtype=np.int32).reshape(-1,2)
|
||||
if len(requests):
|
||||
requests=requests[np.lexsort((requests[:,1],requests[:,0]))]
|
||||
assert len(np.unique(requests.astype(np.int64)[:,0]*CFG['neurons']+requests[:,1]))==len(requests)
|
||||
assert requests[:,0].min()>=0 and requests[:,0].max()<steps and np.isin(requests[:,1],nodes).all()
|
||||
with (folder/'requests.npz').open('xb') as f:np.savez_compressed(f,tick=requests[:,0],neuron_idx=requests[:,1])
|
||||
if kind=='fixture':
|
||||
v=np.full(len(nodes),model['resting_potential_mv'],np.float64);g=np.zeros(len(nodes),np.float64)
|
||||
if initial:
|
||||
for node,value in initial.get('v',{}).items():v[np.flatnonzero(nodes==node)[0]]=value
|
||||
for node,value in initial.get('g',{}).items():g[np.flatnonzero(nodes==node)[0]]=value
|
||||
with (folder/'arrays.npz').open('xb') as f:np.savez_compressed(f,initial_v_mv=v,initial_g_mv=g)
|
||||
save(folder/'spec.json',spec)
|
||||
|
||||
def main():
|
||||
assert not (EXP/'protocol/preregistered.json').exists()
|
||||
assert all(not (EXP/d).exists() or not any((EXP/d).iterdir()) for d in ('protocol','graphs','apl-bundle','cases','gpu','oracle'))
|
||||
assert shutil.disk_usage(EXP.parent).free>30*1024**3
|
||||
for d in ('protocol','apl-bundle','graphs','cases','gpu','oracle','component-audit','regression','calibration','audit','extension-build','logs'):(EXP/d).mkdir(parents=True,exist_ok=True)
|
||||
can=ROOT/'datasets/male-cns-v1.0/processed/neuron-only';legacy=ROOT/'models/male-cns-lif';lp=legacy/'olfactory-grounding-v1/phase4l.yaml'
|
||||
lock=ROOT/'experiments/phase4j-mb-physiology-repair/calibration/locked-parameters.json'
|
||||
files=[AF_MODEL/'manifest.json',AF_EXP/'manifest.json',AF_EXP/'oracle/no_input/coefficients.json',
|
||||
legacy/'deterministic-runtime-v2/phase4p_revalidate.py',lp,lock,can/'neuron-index/unified-metadata.parquet',can/'gpu/csr-weights.int64.npy',
|
||||
legacy/'author-runtime-lab-v1/author_ext.cpp',legacy/'author-runtime-lab-v1/author_ext.cu',legacy/'author-runtime-lab-v1/gpu_backend.py',
|
||||
legacy/'author-runtime-lab-v1/oracle_stress.py',AD_EXP/'external-resources/model.py',AF_MODEL/'oracle_full.py']
|
||||
lroot=ROOT/'experiments/phase4l-olfactory-grounding'
|
||||
files += [lroot/'odor-encoder'/x for x in ('frozen-odor-panel.parquet','mapped-glomerular-vectors.npz','selection.json')]
|
||||
files += [lroot/'receptor-mapping/olfactory-mapping.parquet']
|
||||
prior=ROOT/'experiments/phase4ag-author-apl'
|
||||
files += [prior/'manifest.json',ROOT/'models/male-cns-lif/author-apl-lab-v1/manifest.json']
|
||||
save(EXP/'protocol/preregistered.json',dict(timestamp_utc=datetime.now(timezone.utc).isoformat(),sources=sources(),config=CFG,
|
||||
inputs=[dict(path=str(p.relative_to(ROOT)),sha256=digest(p)) for p in files],before_any_trajectory=True,
|
||||
parent_commit=subprocess.check_output(['git','-C',str(ROOT),'rev-parse','HEAD'],text=True).strip()))
|
||||
save(EXP/'regression/old-integrity-before.json',integrity())
|
||||
science_entry=ROOT/'models/male-cns-lif/.venv/bin/python'
|
||||
command=[str(science_entry),'-B','-c',
|
||||
'import sys,json,importlib.util; print(json.dumps(dict(executable=sys.executable,prefix=sys.prefix,base_prefix=sys.base_prefix,numpy_visible=importlib.util.find_spec("numpy") is not None,torch_visible=importlib.util.find_spec("torch") is not None)))']
|
||||
environment=json.loads(subprocess.check_output(command,text=True))
|
||||
assert environment['executable']==str(science_entry) and environment['prefix']==str(science_entry.parent.parent)
|
||||
assert environment['prefix']!=environment['base_prefix'] and environment['numpy_visible'] and environment['torch_visible']
|
||||
save(EXP/'protocol/execution-preflight.json',dict(command=command,observed=environment,
|
||||
before_any_trajectory=True,torch_imported=False,CUDA_initialized=False))
|
||||
locked=json.loads(lock.read_text());assert locked['parameters_locked'] and locked['calibration_only']
|
||||
assert {k:locked['selected'][k] for k in ('gain','normalization','tau_ms')}==CFG['apl']
|
||||
meta=pd.read_parquet(can/'neuron-index/unified-metadata.parquet').sort_values('neuron_idx').reset_index(drop=True)
|
||||
assert len(meta)==CFG['neurons'] and np.array_equal(meta.neuron_idx,np.arange(CFG['neurons']))
|
||||
cls=meta.annotation_class.fillna('').astype(str).to_numpy();typ=meta.annotation_type.fillna('').astype(str).to_numpy()
|
||||
kc=np.flatnonzero(cls=='Kenyon_Cell').astype(np.int32);pn=np.flatnonzero(cls=='ALPN').astype(np.int32);apl=np.flatnonzero(typ=='APL').astype(np.int32)
|
||||
assert len(kc)==4064 and len(apl)==2
|
||||
fg=AF_EXP/'graph';ptr=np.load(fg/'ptr.npy',mmap_mode='r');dst=np.load(fg/'dst.npy',mmap_mode='r');src=np.load(fg/'src.npy',mmap_mode='r')
|
||||
signed=np.load(fg/'signed_raw.npy',mmap_mode='r');base=np.load(AF_EXP/'oracle/no_input/weights.npy',mmap_mode='r');raw=np.load(can/'gpu/csr-weights.int64.npy',mmap_mode='r')
|
||||
assert len(dst)==CFG['edges'] and int(raw.sum())==CFG['raw_weight_sum']
|
||||
aplout=np.isin(src,apl);aplids=np.flatnonzero(aplout).astype(np.int64);assert len(aplids)==5845
|
||||
I=np.zeros((2,len(meta)),np.float32);O=np.zeros((2,len(kc)),np.float32)
|
||||
alook={int(v):i for i,v in enumerate(apl)};kl=np.full(len(meta),-1,np.int32);kl[kc]=np.arange(len(kc),dtype=np.int32)
|
||||
allowed=np.isin(src,np.r_[kc,pn])&np.isin(dst,apl)
|
||||
for eid in np.flatnonzero(allowed):I[alook[int(dst[eid])],int(src[eid])]+=float(raw[eid])
|
||||
for eid in np.flatnonzero(aplout&np.isin(dst,kc)):O[alook[int(src[eid])],kl[dst[eid]]]+=float(raw[eid])
|
||||
iscale=float(I[I>0].sum()/np.count_nonzero(np.any(I>0,axis=0)));oscale=float(O[O>0].mean());bundle=EXP/'apl-bundle'
|
||||
for name,value in dict(input_matrix=I/iscale,output_matrix=O/oscale,input_raw=I,output_raw=O,kc_globals=kc,pn_globals=pn,apl_globals=apl,apl_out_ids=aplids).items():arr(bundle/(name+'.npy'),value)
|
||||
save(bundle/'scales.json',dict(input_scale=iscale,output_scale=oscale))
|
||||
save(bundle/'provenance.json',dict(canonical_root=str(can),source=str(legacy/'deterministic-runtime-v2/phase4p_revalidate.py'),source_sha256=digest(legacy/'deterministic-runtime-v2/phase4p_revalidate.py'),
|
||||
parameters=CFG['apl'],lock_file=str(lock),lock_sha256=digest(lock),full_dimensions=[list(I.shape),list(O.shape)],
|
||||
APL=len(apl),KC=len(kc),ALPN=len(pn),APL_fast_edges=len(aplids),APL_nonKC_edges=int(np.count_nonzero(aplout&~np.isin(dst,kc))),
|
||||
APL_nonKC_contacts=int(raw[aplout&~np.isin(dst,kc)].sum()),normalization_recomputed_in_original_order=True,
|
||||
raw_files_modified=False))
|
||||
full=(ptr,dst,src,signed,base);fullnodes=graph(EXP/'graphs/full',np.arange(len(meta),dtype=np.int32),full)
|
||||
pscore=I.sum(axis=0);pnrank=pn[np.lexsort((pn,-pscore[pn]))];kscore=O.sum(axis=0);kcrank=kc[np.lexsort((kc,-kscore))]
|
||||
candidates=np.flatnonzero(np.isin(src,pn)&np.isin(dst,kc));best=int(candidates[np.argmax(raw[candidates])]);pairpn=int(src[best])
|
||||
pe=np.arange(ptr[pairpn],ptr[pairpn+1],dtype=np.int64);pe=pe[np.isin(dst[pe],kc)];pe=pe[np.lexsort((dst[pe],-raw[pe]))]
|
||||
collision_kc=np.unique(dst[pe[:3]]);assert len(collision_kc)==3
|
||||
nonkc=aplids[~np.isin(dst[aplids],kc)];nonkc_target=int(dst[nonkc[np.argmax(raw[nonkc])]])
|
||||
nodes=np.unique(np.r_[apl,pnrank[:16],kcrank[:16],pairpn,collision_kc,nonkc_target,[1197,1381,1511,82312,133666]]).astype(np.int32)
|
||||
fixturegraph=EXP/'graphs/fixtures';graph(fixturegraph,nodes,full)
|
||||
p=int(pnrank[0]);k=int(kcrank[0]);kc1,kc2,kc3=map(int,collision_kc)
|
||||
designs=[('zero',100,[],None),('pn_pulse',300,[(0,p)],None),
|
||||
('kc_ref_boundary',100,[(t,k) for t in (0,1,21,22,23,44)],None),
|
||||
('two_sources',300,[(t,n) for t in (0,22,44,66,88) for n in (p,k)],None),
|
||||
('arrival_collision',120,[(0,pairpn),(0,kc1),(18,kc2),(21,kc1)],None),
|
||||
('natural_request',100,[(0,k)],dict(v={k:-44.0})),
|
||||
('apl_only',100,[(0,int(a)) for a in apl],None),
|
||||
('high_fanin',400,[(t,int(n)) for t in (0,22,44) for n in nodes if pscore[n]>0],dict(g={k:.123}))]
|
||||
assert [v[0] for v in designs]==CFG['fixtures']
|
||||
for name,steps,requests,initial in designs:case(name,'fixture',steps,nodes,fixturegraph,requests,initial)
|
||||
meta.iloc[nodes][['neuron_idx','body_id','annotation_class','annotation_type','neurotransmitter']].to_csv(EXP/'protocol/fixture-identities.csv',index=False)
|
||||
save(EXP/'protocol/fixture-selection.json',dict(pn=p,kc=k,pair_PN=pairpn,arrival_KCs=collision_kc.tolist(),real_PN_KC_edge=best,
|
||||
nonKC_APL_target=nonkc_target,method='Static raw-count ranks and exact metadata; retain ALL existing induced edges and full APL matrices/norms',runtime_selection=False))
|
||||
os.environ['CUDA_VISIBLE_DEVICES']=CFG['gpu_uuid'];sys.path.insert(0,str(legacy));sys.path.insert(0,str(legacy/'deterministic-runtime-v2'))
|
||||
sys.path.insert(0,str(legacy/'terminal-scope-diagnostic-v1'));from diagnostic import queues
|
||||
queues()
|
||||
import torch
|
||||
from phase4p_revalidate import door_inputs,make_drive
|
||||
oc,panel,vectors,selection,mapping=door_inputs();cal=panel[panel.split=='calibration'].copy();assert len(panel)==12 and len(cal)==6
|
||||
assert oc['odor_encoder']['evaluation_seeds']==CFG['calibration']['seeds']
|
||||
assert float(oc['odor_encoder']['duration_ms'])==CFG['calibration']['duration_ms']
|
||||
cal.to_parquet(EXP/'protocol/calibration-panel.parquet',index=False);calrecords=[]
|
||||
for mode in CFG['calibration']['modes']:
|
||||
for oi,(_,odor) in enumerate(cal.iterrows()):
|
||||
targets,rates=make_drive(oc,panel,vectors,selection,mapping,odor.odor_id,mode)
|
||||
assert len(np.unique(targets))==len(targets),'Original encoder emits duplicate PN targets; audit instead of silently merging'
|
||||
rates=np.asarray(rates,np.float32);steps=round(CFG['calibration']['duration_ms']/.1)
|
||||
for seed in CFG['calibration']['seeds']:
|
||||
gen=torch.Generator(device='cuda');gen.manual_seed(seed)
|
||||
original_probability=rates*.1/1000.0
|
||||
explicit_probability=rates*np.float32(.1)/np.float32(1000.)
|
||||
assert original_probability.dtype==np.float32 and np.array_equal(original_probability.view(np.uint32),explicit_probability.view(np.uint32))
|
||||
probs=torch.as_tensor(original_probability,dtype=torch.float32,device='cuda')
|
||||
forced=(torch.rand((steps,len(targets)),generator=gen,device='cuda')<probs[None,:]).cpu().numpy()
|
||||
tt,cc=np.nonzero(forced);req=np.column_stack([tt,targets[cc]]).astype(np.int32)
|
||||
name=f'cal-{mode}-{oi:02d}-s{seed}'
|
||||
case(name,'calibration',steps,fullnodes,EXP/'graphs/full',req,extra=dict(odor_id=str(odor.odor_id),odor_name=str(odor.odor_name),
|
||||
mode=mode,seed=seed,odor_index=oi,expected_external_events=float(rates.sum())*steps*.1/1000.,target_count=len(targets),rate_sum_hz=float(rates.sum())))
|
||||
with (EXP/'cases'/name/'pn-drive.npz').open('xb') as f:np.savez_compressed(f,targets=targets,rates_hz=rates)
|
||||
calrecords.append(dict(name=name,mode=mode,odor_index=oi,odor_id=str(odor.odor_id),seed=seed,requests=len(req),rates_sum=float(rates.sum())))
|
||||
save(EXP/'protocol/calibration-cases.json',calrecords)
|
||||
save(EXP/'protocol/encoder-audit.json',dict(panel_count=12,cal_count=6,median_mapped_response_sum=float(np.median(panel.mapped_response_sum)),
|
||||
target_rng_column_order='Original pn_drive, no sorting before torch.rand',seeds=CFG['calibration']['seeds'],
|
||||
requested_before_any_dynamics=True,formal_AB_run=False,heldout_run=False,torch=torch.__version__,numpy=np.__version__,
|
||||
generation='Original rates*.1/1000 float32 GPU Bernoulli, independent per-case original seed'))
|
||||
revision=[]
|
||||
for folder in sorted((EXP/'cases').iterdir()):
|
||||
with np.load(folder/'requests.npz',allow_pickle=False) as new,np.load(prior/'cases'/folder.name/'requests.npz',allow_pickle=False) as old:
|
||||
assert new.files==old.files and all(new[key].dtype==old[key].dtype and np.array_equal(new[key],old[key]) for key in old.files)
|
||||
revision.append(dict(case=folder.name,sparse_request_arrays_exact=True))
|
||||
save(EXP/'protocol/revision-input-equivalence.json',dict(prior_experiment=str(prior),cases=revision,
|
||||
input_arrays_unchanged=True,reason='Execution-only venv invocation fix; no trajectory outcome used for input choice'))
|
||||
save(EXP/'protocol/prepared-hashes.json',[dict(path=str(p.relative_to(EXP)),bytes=p.stat().st_size,sha256=digest(p))
|
||||
for d in ('graphs','apl-bundle','cases','protocol') for p in sorted((EXP/d).rglob('*')) if p.is_file() and p.name!='prepared-hashes.json'])
|
||||
budget();print('PREPARED8 fixtures and60 fixed CAL inputs; no trajectories; frozen integrity PASS',flush=True)
|
||||
|
||||
if __name__=='__main__':main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user