CIE-Unified


commit
6f7caa7
parent
4c794d4
author
im_wower
date
2026-04-01 09:54:52 +0800 CST
integration: 方案C落板——文件级merge方案(核B壳A)
7 files changed,  +286, -0
Raw patch view.
  1diff --git a/MERGE_PLAN.md b/MERGE_PLAN.md
  2new file mode 100644
  3index 0000000000000000000000000000000000000000..a253bcb83d624884fea90609f3feb0da711e1811
  4--- /dev/null
  5+++ b/MERGE_PLAN.md
  6@@ -0,0 +1,198 @@
  7+# CIE-Unified 整合方案
  8+
  9+> 日期: 2026-03-31
 10+> 决策依据: Branch A 6/8, Branch B 8/8 (同一 cie-datasets formal dataset)
 11+> 策略: 方案 C — 核取 B,壳取 A
 12+
 13+---
 14+
 15+## 0. 冻结点
 16+
 17+| 分支 | Commit | 状态 |
 18+|------|--------|------|
 19+| `branch-a/task04-validation-reporting` | `419ae8d` | 冻结,不再改 |
 20+| `branch-b` | `c734288` | 冻结,不再改 |
 21+| `review/branch-b-formal-dataset-strict-rerun` | `c110916` | 参考(B 的 strict rerun 验证) |
 22+| `review/branch-a-formal-dataset-strict-rerun` | `273f416` | 参考(A 的 strict rerun 验证) |
 23+
 24+---
 25+
 26+## 1. 文件级合并计划
 27+
 28+### 核 — 取 Branch B
 29+
 30+这些文件直接从 `branch-b @ c734288` 复制,构成 runtime 内核:
 31+
 32+| 文件 | 来源 | 关键机制 |
 33+|------|------|---------|
 34+| `cie/graph.py` | **Branch B** | 非对称图(fwd_weight ≠ bwd_weight)、`laplacian_at()`、`asymmetry_at()`、`circulation()`、`convergence()` |
 35+| `cie/dynamics.py` | **Branch B** | L_G φ 扩散 + soft clamp、自适应衰减 α(x)=α₀·(1-c)^β·(1/κ)、动态锚点阈值(10th percentile)、三级归巢、置信度自然衰减 |
 36+| `cie/state.py` | **Branch B 为基底,吸收 A 的 Profile** | (φ,μ,J) 三元组、AttentionPool、Dirichlet K=3 置信度。**需要吸收 A 的 `SedimentationProfile` dataclass(见下)** |
 37+| `cie/runtime.py` | **Branch B 为基底,吸收 A 的信号队列** | CIERuntime 六接口、action_release u=o·c·ε、feedback_loop。**需要吸收 A 的 `PendingSignal` 机制(见下)** |
 38+
 39+### 壳 — 取 Branch A
 40+
 41+这些工程设施从 `branch-a @ 419ae8d` 移植到整合分支:
 42+
 43+| 设施 | 来源 | 说明 |
 44+|------|------|------|
 45+| `PendingSignal` 信号队列 | **Branch A** `cie/state.py` + `cie/runtime.py` | 将输入/反馈/回灌统一为信号对象排队,下一步 step 时消费。替换 Branch B 的直接 `_feedback_loop()` 调用 |
 46+| `SedimentationProfile` | **Branch A** `cie/state.py` | 每节点生命周期对象(stage, activation_hits, stable_steps, dormant_steps, resonance, candidate_score, merged_into)。替换 Branch B 的 `experience_hits` 计数器 |
 47+| `cie/validation.py` | **Branch A** `cie/validation.py` | 476 行标准化验证框架,输出 JSON + Markdown 报告。需适配 Branch B 的 snapshot 字段 |
 48+| 报告模板 | **Branch A** `reports/` 目录结构 | JSON schema + Markdown 自动生成 |
 49+
 50+### 不要的
 51+
 52+| 项目 | 原分支 | 原因 |
 53+|------|--------|------|
 54+| Branch A 的对称图 `graph.py` | A | 被 B 的非对称图替代 |
 55+| Branch A 的 `confidence_proxy` | A | 被 B 的 Dirichlet 替代 |
 56+| Branch A 的 age-based 衰减 | A | 被 B 的自适应衰减替代 |
 57+| Branch B 的手写 `STAGE_REPORT.md` | B | 被 A 的 validation.py 自动报告替代 |
 58+| 两边各自的魔法数字 | A+B | 统一为参数类 `CIEConfig` |
 59+| Branch B 的 `HANDOFF.md` | B | 开发阶段文档,不进主干 |
 60+
 61+---
 62+
 63+## 2. 接口对齐
 64+
 65+Branch A 和 B 的六接口签名已基本一致(SPEC §5),需要统一的是内部实现路径:
 66+
 67+```
 68+ingest(input, context, anchors)
 69+  → 创建 PendingSignal(来自 A 的模式)
 70+  → 排入 state.pending_signals
 71+
 72+step(n)
 73+  → 消费 pending_signals → _apply_signal()(A 的队列消费)
 74+  → _propagate_activation()(B 的 L_G 扩散 + 激活传播)
 75+  → _apply_homing()(B 的三级归巢)
 76+  → _apply_decay()(B 的自适应衰减)
 77+  → _refresh_sedimentation()(A 的 SedimentationProfile 检测 + B 的阈值)
 78+  → _refresh_observability()
 79+
 80+emit()
 81+  → action_release u=o·c·ε(B 的公式)
 82+  → 创建 feedback PendingSignal(A 的模式)
 83+
 84+commit_feedback(feedback)
 85+  → 创建 feedback PendingSignal with polarity(A 的模式)
 86+  → 包含 weaken_confidence(B 的 Dirichlet 衰减)
 87+
 88+snapshot_state()
 89+  → 所有 SPEC §6 字段(A+B 已对齐)
 90+
 91+reset_session()
 92+  → 清激活/注意力,保留 φ/ability_cores/anchors(B 的逻辑)
 93+```
 94+
 95+---
 96+
 97+## 3. 合并顺序
 98+
 99+**原则:先壳接核,不同时大改理论和工程外壳。**
100+
101+### Phase 1: 基底搭建(Day 1)
102+
103+1. 从 `branch-b @ c734288` 复制 `cie/graph.py`、`cie/dynamics.py`、`cie/state.py`、`cie/runtime.py`
104+2. 跑 Branch B 的 42 项测试确认基底不坏
105+3. 提交:`integration: 基底——Branch B runtime 核心`
106+
107+### Phase 2: 信号队列移植(Day 1-2)
108+
109+1. 从 Branch A 移植 `PendingSignal` dataclass 到 `cie/state.py`
110+2. 改写 `runtime.py` 的 `ingest()`、`emit()`、`commit_feedback()` 使用信号队列
111+3. 改写 `_feedback_loop()` 为 `_apply_signal()` 的队列消费
112+4. 跑测试确认回灌/反馈功能不退化
113+5. 提交:`integration: 信号队列——PendingSignal 替换直接调用`
114+
115+### Phase 3: 沉积 Profile 移植(Day 2)
116+
117+1. 从 Branch A 移植 `SedimentationProfile` dataclass 到 `cie/state.py`
118+2. 改写 `dynamics.py` 的 `sediment()` 使用 Profile(保留 B 的滑动窗口 + 动态阈值)
119+3. 在 snapshot_state 中输出 Profile 信息
120+4. 跑测试确认沉积路径功能不退化
121+5. 提交:`integration: 沉积 Profile——SedimentationProfile 替换 experience_hits`
122+
123+### Phase 4: 参数统一(Day 2)
124+
125+1. 创建 `cie/config.py`,把两边散落的魔法数字收集为 `CIEConfig` dataclass
126+2. `CIERuntime.__init__` 接收 `config: CIEConfig`
127+3. 提交:`integration: 参数统一——CIEConfig 替换魔法数字`
128+
129+### Phase 5: 验证框架移植(Day 3)
130+
131+1. 从 Branch A 移植 `cie/validation.py`
132+2. 适配 Branch B 的 snapshot 字段(已基本对齐)
133+3. 添加 Branch B 特有的检查项:非对称比、环流、Dirichlet 分化
134+4. 跑 validation,生成 JSON + Markdown 报告
135+5. 提交:`integration: 验证框架——validation.py 适配整合 runtime`
136+
137+### Phase 6: 正式数据集全量验证(Day 3)
138+
139+1. 用 `/Users/george/code/cie-datasets` 的 hydrated JSONL 跑完整 8 场景验证
140+2. 生成正式报告
141+3. 与 Branch A 6/8 和 Branch B 8/8 做三方对比
142+4. 提交:`integration: 正式验证——cie-datasets 8 场景`
143+
144+---
145+
146+## 4. 验收标准
147+
148+整合完成后必须满足:
149+
150+| # | 标准 | 来源 |
151+|---|------|------|
152+| 1 | SPEC §9 全部 11 项 | LOCKED_IMPLEMENTATION_SPEC |
153+| 2 | Branch B 的 42 项测试全通 | branch-b tests |
154+| 3 | Branch A 的 18 项测试逻辑等价全通 | branch-a tests(适配后) |
155+| 4 | cie-datasets 8 场景全 PASS | formal_validation |
156+| 5 | phi 不超 ±10.1(soft clamp 保留) | 数值稳定性 |
157+| 6 | 非对称比能涌现词结构 | word_emergence 验证 |
158+| 7 | validation.py 自动生成 JSON + MD 报告 | 工程要求 |
159+| 8 | 无魔法数字,全部在 CIEConfig 中 | 可维护性 |
160+
161+---
162+
163+## 5. 风险
164+
165+| 风险 | 影响 | 缓解 |
166+|------|------|------|
167+| PendingSignal 移植引入延迟回灌 bug | 回灌检测失败 | Phase 2 立即跑 smoke test 02 |
168+| SedimentationProfile 与 B 的衰减/阈值不兼容 | 沉积路径退化 | Phase 3 跑 dynamics test 03-05 |
169+| A 的 validation.py 假设 A 的 snapshot 格式 | 字段不匹配 | Phase 5 逐字段适配 |
170+| 合并后数值行为微变 | 原有测试值不完全匹配 | 允许 ±5% 浮点容差 |
171+
172+---
173+
174+## 6. 时间估计
175+
176+| Phase | 预计耗时 |
177+|-------|---------|
178+| Phase 1 基底 | 30 分钟 |
179+| Phase 2 信号队列 | 2 小时 |
180+| Phase 3 沉积 Profile | 1.5 小时 |
181+| Phase 4 参数统一 | 1 小时 |
182+| Phase 5 验证框架 | 1.5 小时 |
183+| Phase 6 全量验证 | 1 小时 |
184+| **总计** | **~7.5 小时** |
185+
186+---
187+
188+## 7. 决策记录
189+
190+**方案 C 落板依据:**
191+
192+| 事实 | 数据 |
193+|------|------|
194+| Branch B formal dataset | **8/8 PASS**(独立复现) |
195+| Branch A formal dataset | **6/8 PASS**(初中数学 phi=12.07 超限,跨学科 phi=20.18 超限) |
196+| Branch A raw 五本课本 | 8/10(更早轮次) |
197+| Branch B 理论贴合 | 三核量 (o,c,ω) 全实现 |
198+| Branch A 工程壳 | PendingSignal + SedimentationProfile + validation.py |
199+
200+**核取 B 的理由**:非对称图/L_G/Dirichlet/circulation 是 README 理论主线的忠实实现,丢掉等于放弃 CIE 核心;Branch A 在稳定性上也不如 B(两个场景 phi 超限)。
201+
202+**壳取 A 的理由**:信号队列模式比直接调用更规整;SedimentationProfile 比计数器更精细;validation.py 自动报告是工程必需品。
203+
204+**三方共识**:George(决策者)、ChatGPT(Branch A 开发者/审查者)、Claude(Branch B 开发者/审查者)均同意方案 C。
205diff --git a/cie/__pycache__/__init__.cpython-310.pyc b/cie/__pycache__/__init__.cpython-310.pyc
206new file mode 100644
207index 0000000000000000000000000000000000000000..88cf0a484e070af7f8ed7ecf56c1214090c10242
208Binary files /dev/null and b/cie/__pycache__/__init__.cpython-310.pyc differ
209diff --git a/cie/__pycache__/dynamics.cpython-310.pyc b/cie/__pycache__/dynamics.cpython-310.pyc
210new file mode 100644
211index 0000000000000000000000000000000000000000..35fdb5c1644169e05af271bef9677e07ed9d40a4
212Binary files /dev/null and b/cie/__pycache__/dynamics.cpython-310.pyc differ
213diff --git a/cie/__pycache__/graph.cpython-310.pyc b/cie/__pycache__/graph.cpython-310.pyc
214new file mode 100644
215index 0000000000000000000000000000000000000000..1015b58ab49777a452f9704b801e330e2524a8ff
216Binary files /dev/null and b/cie/__pycache__/graph.cpython-310.pyc differ
217diff --git a/cie/__pycache__/runtime.cpython-310.pyc b/cie/__pycache__/runtime.cpython-310.pyc
218new file mode 100644
219index 0000000000000000000000000000000000000000..a12da98775e997d65b8a6ce80e2af8110b2be479
220Binary files /dev/null and b/cie/__pycache__/runtime.cpython-310.pyc differ
221diff --git a/cie/__pycache__/state.cpython-310.pyc b/cie/__pycache__/state.cpython-310.pyc
222new file mode 100644
223index 0000000000000000000000000000000000000000..762f4b591fd98d399aa547b13532c982cdc23178
224Binary files /dev/null and b/cie/__pycache__/state.cpython-310.pyc differ
225diff --git a/tests/formal_validation.py b/tests/formal_validation.py
226new file mode 100644
227index 0000000000000000000000000000000000000000..e8f434156c3896b78ddcaced53bfc25b4d3095cb
228--- /dev/null
229+++ b/tests/formal_validation.py
230@@ -0,0 +1,88 @@
231+import sys, os, json, math, time
232+sys.path.insert(0, "/Users/george/code/CIE-Unified")
233+from cie import CIERuntime
234+
235+DS = "/Users/george/code/cie-datasets/china_text_book_md/v2026-03-28"
236+
237+def load_recs(stage, subject, n=80):
238+    path = os.path.join(DS, "splits", "by_stage_subject", stage, f"{subject}.jsonl")
239+    recs = []
240+    if not os.path.exists(path): return recs
241+    with open(path) as f:
242+        for line in f:
243+            rec = json.loads(line)
244+            if not rec.get("is_content"): continue
245+            t = rec.get("text","")
246+            if len(t) >= 4: recs.append(t)
247+            if len(recs) >= n: break
248+    return recs
249+
250+combos = [("小学","语文"),("小学","数学"),("初中","语文"),("初中","数学"),("高中","语文")]
251+tests = []
252+
253+# Pipeline + Stability per combo
254+for stage, subj in combos:
255+    label = stage + subj
256+    recs = load_recs(stage, subj, 80)
257+    if not recs:
258+        tests.append({"name": label, "status": "FAIL", "detail": "no data"})
259+        continue
260+    rt = CIERuntime(seed=42)
261+    t0 = time.time()
262+    for r in recs:
263+        rt.ingest(r[:60])
264+        rt.step(n=1)
265+    elapsed = time.time() - t0
266+    ot = rt.emit()
267+    if ot["activated"]:
268+        rt.commit_feedback({"correct": [ot["activated"][0]["node"]], "reward": 1.0})
269+    snap = rt.snapshot_state()
270+    ok = snap["phi_summary"]["count"] > 20 and abs(snap["phi_summary"]["max"]) <= 10.1
271+    ok &= snap["attention"]["used"] <= snap["attention"]["total"] + 0.01
272+    ok &= all(math.isfinite(v) for v in rt.state.phi.values())
273+    
274+    # Emergence: top words
275+    g = rt.graph
276+    cn_bg = []
277+    for se in g.fwd_edges.values():
278+        for dst, edge in se.items():
279+            if "\u4e00" <= edge.src <= "\u9fff" and "\u4e00" <= dst <= "\u9fff":
280+                bwd = g.get_bwd_weight(edge.src, dst)
281+                ratio = edge.weight / bwd if bwd > 0.01 else edge.weight * 100
282+                cn_bg.append((edge.src+dst, round(ratio,1)))
283+    cn_bg.sort(key=lambda x: -x[1])
284+    
285+    d = f"n={snap['phi_summary']['count']}, e={snap['graph']['edge_count']}, phi=[{snap['phi_summary']['min']:.3f},{snap['phi_summary']['max']:.3f}], mode={ot['mode']}, t={elapsed:.1f}s, words={cn_bg[:5]}"
286+    tests.append({"name": label, "status": "PASS" if ok else "FAIL", "detail": d})
287+
288+# Cross-stage
289+rt2 = CIERuntime(seed=42)
290+for stage in ["小学","初中","高中"]:
291+    for r in load_recs(stage, "语文", 30):
292+        rt2.ingest(r[:50])
293+        rt2.step(n=1)
294+s2 = rt2.snapshot_state()
295+ok2 = abs(s2["phi_summary"]["max"]) <= 10.1 and s2["phi_summary"]["count"] > 30
296+tests.append({"name": "cross_stage", "status": "PASS" if ok2 else "FAIL",
297+    "detail": f"n={s2['phi_summary']['count']}, phi={s2['phi_summary']['max']:.3f}"})
298+
299+# Cross-subject
300+rt3 = CIERuntime(seed=42)
301+for subj in ["语文","数学","科学"]:
302+    for r in load_recs("小学", subj, 30):
303+        rt3.ingest(r[:50], anchors=[subj])
304+        rt3.step(n=1)
305+s3 = rt3.snapshot_state()
306+ok3 = abs(s3["phi_summary"]["max"]) <= 10.1
307+tests.append({"name": "cross_subject", "status": "PASS" if ok3 else "FAIL",
308+    "detail": f"n={s3['phi_summary']['count']}, phi={s3['phi_summary']['max']:.3f}, cores={len(rt3.state.ability_cores)}"})
309+
310+# Summary
311+passed = sum(1 for t in tests if t["status"]=="PASS")
312+failed = sum(1 for t in tests if t["status"]=="FAIL")
313+for t in tests:
314+    print(f"[{t['status']}] {t['name']}: {t['detail']}")
315+print(f"\n总计: {passed}/{len(tests)} PASS")
316+
317+with open("/tmp/formal_val_results.json", "w") as f:
318+    json.dump({"tests": tests, "summary": {"passed": passed, "failed": failed, "total": len(tests), "dataset": DS}}, f, ensure_ascii=False, indent=2, default=str)