- 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
+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`
+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
+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 # ── 路径汇聚度 κ ──
+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)