CIE-Unified

git clone 

commit
dc722fa
parent
70cfdf3
author
codex@macbookpro
date
2026-04-01 11:47:57 +0800 CST
feat: phase2.4 kernel math cleanup before phase3
4 files changed,  +138, -7
M MERGE_PLAN.md
+20, -1
 1@@ -8,7 +8,10 @@
 2 > `origin/integration/merge-plan @ fabbb69` 已完成 Phase 2.2(Kernel Sanity Gate);
 3 > `integration/phase2.3-sanity-cleanup` 已完成 Phase 2.3(validation/context/attention sanity cleanup),
 4 > 并通过 quick gate、Phase 2.3 gate、broader regression gate、formal validation smoke。
 5-> **Phase 3 尚未开始**;只有在 Phase 2.3 gates 全通过后,才允许进入 Phase 3。
 6+> `feat/phase2.4-kernel-math-cleanup` 已完成 Phase 2.4(weighted-degree normalization + net-flow circulation),
 7+> 并通过 quick gate、Phase 2.4 gate、broader regression gate、formal validation smoke。
 8+> **Phase 3 尚未开始**;只有在 Phase 2.4 gates 全通过后,才允许进入 Phase 3,
 9+> 且仍保持 **dual-write first, replacement later**。
10 
11 ---
12 
13@@ -158,6 +161,22 @@ reset_session()
14    `python3 tests/formal_validation.py`
15 6. Phase 2.3 完成后,Phase 3 才允许开始;当前 **Phase 3 仍未开始**
16 
17+### Phase 2.4: Kernel Math Cleanup(已完成)
18+
19+1. 将 `diffuse_phi()` 的归一化从未加权邻居数改为加权度,避免高权重稠密区被错误压扁
20+2. 将 `circulation()` 从单向路径权重求和改为逐边净流 `Σ(W(u,v) - W(v,u))`
21+3. 新增 `tests/test_phase24_kernel_math.py`,覆盖:
22+   weighted-degree normalization、
23+   symmetric cycle net-flow≈0、
24+   asymmetric cycle directionality、
25+   Phase 2.2 kernel primitives regression
26+4. 通过 gate:
27+   `python3 -m pytest tests/test_smoke.py tests/test_dynamics.py tests/test_exactly_once.py tests/test_kernel_sanity.py tests/test_phase23_validation_sanity.py -q`
28+   `python3 -m pytest tests/test_phase24_kernel_math.py -q`
29+   `python3 -m pytest tests/test_smoke.py tests/test_dynamics.py tests/test_exactly_once.py tests/test_kernel_sanity.py tests/test_phase23_validation_sanity.py tests/test_comprehensive.py tests/test_phase24_kernel_math.py -q`
30+   `python3 tests/formal_validation.py`
31+5. 只有在 Phase 2.4 gates 全通过后,Phase 3 才允许开始;当前 **Phase 3 仍未开始**
32+
33 ### Phase 3: 沉积 Profile 并行观测(未开始,Day 2)
34 
35 1. 从 Branch A 移植 `SedimentationProfile` dataclass 到 `cie/state.py`
M cie/dynamics.py
+3, -4
 1@@ -44,16 +44,15 @@ class Dynamics:
 2         L_G 是图拉普拉斯。没有维度,没有向量。
 3         非对称项是旋度来源。
 4         阻尼项防止 φ 无界增长——半杯水原则。
 5-        Laplacian 按度归一化,防止高权重边放大信号。
 6+        Laplacian 按加权度归一化,防止高权重边放大信号。
 7         """
 8         phi = self.state.phi
 9         new_phi = {}
10         for node_id in self.graph.nodes:
11             lap = self.graph.laplacian_at(node_id, phi)
12             asym = self.graph.asymmetry_at(node_id, phi)
13-            # 按节点度归一化,防止高权重累积放大
14-            degree = len(self.graph.neighbors_all(node_id))
15-            norm = max(degree, 1)
16+            # 按节点加权度归一化,避免高权重稠密区被错误压扁
17+            norm = max(self.graph.weighted_degree(node_id), 1e-10)
18             phi_v = phi.get(node_id, 0.0)
19             new_phi[node_id] = (
20                 phi_v
M cie/graph.py
+30, -2
 1@@ -148,6 +148,32 @@ class Graph:
 2         bwd = set(self.bwd_edges.get(node_id, {}).keys())
 3         return fwd | bwd
 4 
 5+    def directed_weight(self, src: str, dst: str) -> float:
 6+        """
 7+        返回有向权重 W(src, dst)。
 8+
 9+        优先读取显式正向边;若该方向只作为另一条边的反向权重存在,
10+        则从 bwd_edges 中读取对应的反向表示。
11+        """
12+        if src in self.fwd_edges and dst in self.fwd_edges[src]:
13+            return self.fwd_edges[src][dst].weight
14+        if src in self.bwd_edges and dst in self.bwd_edges[src]:
15+            return self.bwd_edges[src][dst].weight
16+        return 0.0
17+
18+    def weighted_degree(self, node_id: str) -> float:
19+        """
20+        返回节点在当前扩散算子下的加权度。
21+
22+        与 laplacian_at() 一致,只统计该节点实际参与求和的相邻边权重。
23+        """
24+        total = 0.0
25+        for edge in self.fwd_edges.get(node_id, {}).values():
26+            total += abs(edge.weight)
27+        for edge in self.bwd_edges.get(node_id, {}).values():
28+            total += abs(edge.weight)
29+        return total
30+
31     # ── 图拉普拉斯 ──
32 
33     def laplacian_at(self, node_id: str, phi: dict[str, float]) -> float:
34@@ -192,7 +218,7 @@ class Graph:
35 
36     def circulation(self, path: list[str]) -> float:
37         """
38-        计算闭合路径的环流 Σ J(u,v)。
39+        计算闭合路径的净环流 Σ (W_fwd(u,v) - W_bwd(v,u))。
40         非零环流 = 旋度 ≠ 0 = 技能/极限环。
41         path 应为闭合的:[a, b, c, a]
42         """
43@@ -200,7 +226,9 @@ class Graph:
44             return 0.0
45         total = 0.0
46         for i in range(len(path) - 1):
47-            total += self.get_edge_weight(path[i], path[i + 1])
48+            src = path[i]
49+            dst = path[i + 1]
50+            total += self.directed_weight(src, dst) - self.directed_weight(dst, src)
51         return total
52 
53     # ── 路径汇聚度 κ ──
A tests/test_phase24_kernel_math.py
+85, -0
 1@@ -0,0 +1,85 @@
 2+"""
 3+Phase 2.4: Kernel Math Cleanup Tests
 4+
 5+1. diffuse_phi 使用加权度归一化
 6+2. 对称闭环的净环流为零
 7+3. 非对称闭环的净环流保留方向性
 8+4. Phase 2.2 的基础 kernel 行为不回退
 9+"""
10+
11+import math
12+import os
13+import sys
14+
15+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16+
17+from cie.dynamics import Dynamics
18+from cie.graph import Graph
19+from cie.state import CIEState
20+
21+
22+def _build_state(graph: Graph, phi_values: dict[str, float]) -> CIEState:
23+    state = CIEState()
24+    for node_id, phi_val in phi_values.items():
25+        graph.add_node(node_id)
26+        state.init_node(node_id, phi_val=phi_val)
27+    return state
28+
29+
30+def test_diffuse_phi_uses_weighted_degree_normalization():
31+    """高权重节点应按加权度而不是邻居数归一化"""
32+    graph = Graph()
33+    graph.add_edge("hub", "heavy", weight=9.0, bwd_weight=9.0)
34+    graph.add_edge("hub", "light", weight=1.0, bwd_weight=1.0)
35+
36+    state = _build_state(graph, {"hub": 0.0, "heavy": 1.0, "light": 1.0})
37+    dynamics = Dynamics(graph, state)
38+    dynamics.diffusion_rate = 0.1
39+    dynamics.asym_lambda = 0.0
40+    dynamics.phi_damping = 0.0
41+
42+    dynamics.diffuse_phi()
43+
44+    expected = 0.1  # 0 + 0.1 * ((9 + 1) / (9 + 1))
45+    assert math.isclose(graph.weighted_degree("hub"), 10.0, rel_tol=0.0, abs_tol=1e-12)
46+    assert math.isclose(state.phi["hub"], expected, rel_tol=0.0, abs_tol=1e-12)
47+    assert not math.isclose(state.phi["hub"], 0.5, rel_tol=0.0, abs_tol=1e-12)
48+
49+
50+def test_circulation_is_zero_on_symmetric_cycle():
51+    """对称权重图上的闭环不应产生假正环流"""
52+    graph = Graph()
53+    graph.add_edge("a", "b", weight=1.0, bwd_weight=1.0)
54+    graph.add_edge("b", "c", weight=2.0, bwd_weight=2.0)
55+    graph.add_edge("c", "a", weight=3.0, bwd_weight=3.0)
56+
57+    circ = graph.circulation(["a", "b", "c", "a"])
58+    assert abs(circ) < 1e-12
59+
60+
61+def test_circulation_uses_net_flow_direction():
62+    """非对称闭环的环流应等于逐边净流之和"""
63+    graph = Graph()
64+    graph.add_edge("a", "b", weight=2.0, bwd_weight=0.5)
65+    graph.add_edge("b", "c", weight=1.5, bwd_weight=0.25)
66+    graph.add_edge("c", "a", weight=1.0, bwd_weight=0.75)
67+
68+    circ = graph.circulation(["a", "b", "c", "a"])
69+    expected = (2.0 - 0.5) + (1.5 - 0.25) + (1.0 - 0.75)
70+
71+    assert math.isclose(circ, expected, rel_tol=0.0, abs_tol=1e-12)
72+    assert circ > 0
73+
74+
75+def test_phase24_cleanup_preserves_kernel_sanity_primitives():
76+    """Phase 2.4 修补后,Phase 2.2 的基础 kernel 接线仍然正确"""
77+    graph = Graph()
78+    graph.add_edge("x", "y", weight=3.0, bwd_weight=1.0)
79+
80+    asym = graph.asymmetry_at("x", {"x": 1.0, "y": 1.0})
81+    exported = graph.to_dict()
82+    xy_edge = next(edge for edge in exported["edges"] if edge["src"] == "x" and edge["dst"] == "y")
83+
84+    assert math.isclose(asym, 2.0, rel_tol=0.0, abs_tol=1e-12)
85+    assert math.isclose(xy_edge["weight"], 3.0, rel_tol=0.0, abs_tol=1e-12)
86+    assert math.isclose(xy_edge["bwd_weight"], 1.0, rel_tol=0.0, abs_tol=1e-12)