Repository navigation
Expand file tree
/
Copy pathmultidomain_experiment.py
More file actions
460 lines (413 loc) · 21.4 KB
/
Copy pathmultidomain_experiment.py
File metadata and controls
460 lines (413 loc) · 21.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
#!/usr/bin/env python3
"""
PHM横向扩展实验 — 5领域真实数据实测
====================================
同一套HESC物理引擎(d_0=12), 喂5个完全不同领域的真实网络:
1. 社交网络 — Zachary空手道俱乐部(34节点, 1977年真实分裂事件)
2. 电力网络 — IEEE 118母线标准测试系统(美国中西部电网, MATPOWER官方数据)
3. 航空网络 — OpenFlights全球航线(67663条真实航线, ~3400机场)
4. 生物网络 — STRING v12.0人类PPI(全基因组, combined_score≥970)
5. 脑网络 — ABIDE 100人真实fMRI(AAL 116脑区, 组级功能连接)
每个领域: PHM全指标计算 + 领域已知事实对照(ground truth)
输出: results/multidomain_results.json
数据来源(全部真实公开数据):
- karate: networkx内置(Zachary 1977)
- IEEE118: MATPOWER官方GitHub case118.m
- OpenFlights: openflights.org routes.dat + airports.dat
- STRING: string-db.org v12.0 9606(人类)
- ABIDE: PCP预处理CPAC/filt_noglobal, AAL atlas
"""
import os
import sys
import json
import time
import gzip
import math
import numpy as np
import networkx as nx
_HERE = os.path.dirname(os.path.abspath(__file__))
_ROOT = os.path.dirname(_HERE)
sys.path.insert(0, os.path.join(_ROOT, 'L1_engine'))
sys.path.insert(0, os.path.join(_ROOT, 'L3_deepseek'))
# PHM核心引擎为闭源IP(见CLOSED_SOURCE_LIST.md)。本仓库不含引擎本体,
# 但结果数据(data/multidomain/*_result.json)全部提交, 可用replay模式复现汇总表:
# python3 src/multidomain_experiment.py replay
try:
from phm_pipeline import PHMPipeline
_ENGINE_AVAILABLE = True
except ImportError:
_ENGINE_AVAILABLE = False
DATA = os.path.join(_HERE, '..', 'data', 'multidomain')
# 外部大数据源(不在交付包内): 环境变量按需配置; 缺失时对应领域报明确错误
STRING_LINKS = os.environ.get('PHM_STRING_LINKS', '')
STRING_ALIAS = os.environ.get('PHM_STRING_ALIAS', '')
ABIDE_DIR = os.environ.get('PHM_ABIDE_DIR', '')
OUT = os.path.join(_HERE, 'results')
os.makedirs(OUT, exist_ok=True)
RESULTS = {}
def run_domain(name, graph, data_source, ground_truth_fn=None, d_0=12.0):
"""跑一个领域: PHM全指标 + 领域对照"""
print(f"\n{'='*60}")
print(f"[{name}] {graph.number_of_nodes()}节点/{graph.number_of_edges()}边")
t0 = time.time()
if not _ENGINE_AVAILABLE:
raise RuntimeError('需要PHM闭源核心引擎(见CLOSED_SOURCE_LIST.md); '
'结果数据见data/multidomain/, 可用replay模式查看: '
'python3 src/multidomain_experiment.py replay')
pipe = PHMPipeline()
phm = pipe.compute_hesc(graph=graph, d_0=d_0, name=name)
elapsed = round(time.time() - t0, 2)
entry = {
'domain': name,
'data_source': data_source,
'network_size': f'{graph.number_of_nodes()}节点/{graph.number_of_edges()}边',
'phm': phm,
'elapsed_s': elapsed,
}
if ground_truth_fn:
entry['ground_truth_check'] = ground_truth_fn(graph, phm)
RESULTS[name] = entry
print(f" d_eff={phm['d_eff']} η={phm['eta']} phase={phm['phase']}")
print(f" 隐蔽风险={phm['hidden_risks_count']} 临界移除比例={phm['critical_removal_fraction']}")
print(f" 耗时{elapsed}s")
return entry
# ═══════════════════════════════════════════════════════
# 1. 社交网络 — Zachary空手道俱乐部
# ═══════════════════════════════════════════════════════
def run_social():
G0 = nx.karate_club_graph()
G = nx.Graph()
for u, v in G0.edges():
# 无权结构网络: 统一置信度0.9(存在连边=高置信连接; d_eff为纯拓扑量不受影响)
G.add_edge(f'member_{u}', f'member_{v}', weight=0.9, confidence=0.9)
def gt(graph, phm):
"""对照: 1977年俱乐部真实分裂, node 0(Mr. Hi教练)和node 33(Officer管理员)
是两派领袖。PHM关键节点应突出他们。"""
degs = dict(graph.degree)
top_deg = sorted(degs.items(), key=lambda x: -x[1])[:5]
leaders = ['member_0', 'member_33']
hit = [n for n, _ in top_deg if n in leaders]
# 分裂后两派(ground truth from Zachary 1977, networkx club属性)
faction = {f'member_{n}': G0.nodes[n]['club'] for n in G0.nodes()}
return {
'known_fact': '1977年真实分裂事件: Mr.Hi(教练,node 0)与Officer(node 33)两派领袖',
'phm_top5_degree': [{'node': n, 'degree': d} for n, d in top_deg],
'leaders_in_top5': hit,
'leader_degrees': {'member_0': degs['member_0'], 'member_33': degs['member_33']},
'faction_split': {'Mr.Hi派': sum(1 for v in faction.values() if v == 'Mr. Hi'),
'Officer派': sum(1 for v in faction.values() if v == 'Officer')},
'verdict': 'PASS' if len(hit) >= 2 else 'CHECK',
}
return run_domain('社交网络(空手道俱乐部)', G,
'networkx karate_club_graph — Zachary 1977真实分裂事件', gt)
# ═══════════════════════════════════════════════════════
# 2. 电力网络 — IEEE 118母线
# ═══════════════════════════════════════════════════════
def run_power():
"""解析MATPOWER case118.m: bus段(母线负荷Pd) + branch段(拓扑)"""
with open(os.path.join(DATA, 'case118.m'), encoding='utf-8') as f:
text = f.read()
def extract_section(name):
i = text.find(f'mpc.{name} = [')
j = text.find('];', i)
rows = []
for line in text[i:j].splitlines()[1:]:
line = line.split('%')[0].strip().rstrip(';').strip()
if not line:
continue
rows.append([float(x) for x in line.split()])
return rows
buses = extract_section('bus')
branches = extract_section('branch')
load = {int(b[0]): b[2] for b in buses} # bus_i, Pd(MW)
G = nx.Graph()
for br in branches:
f_bus, t_bus = int(br[0]), int(br[1])
# 无权拓扑: 统一置信度0.9(线路存在=高置信连接)
G.add_edge(f'bus_{f_bus}', f'bus_{t_bus}', weight=0.9, confidence=0.9)
def gt(graph, phm):
"""对照: 高负荷母线是电网关键节点(工程常识)。
PHM top节点 vs 全网负荷Top10母线 重叠度"""
top_load = sorted(load.items(), key=lambda x: -x[1])[:10]
top_load_set = {f'bus_{b}' for b, _ in top_load}
phm_top = [t['node'] for t in phm['top_critical_nodes'][:10]]
overlap = [n for n in phm_top if n in top_load_set]
return {
'known_fact': 'IEEE 118母线=美国中西部AEP电网快照; 高负荷/高连接母线是系统性关键节点',
'phm_top10': phm_top,
'top10_load_buses_MW': [{'bus': b, 'Pd_MW': round(p, 1)} for b, p in top_load],
'overlap_count': len(overlap),
'overlap_nodes': overlap,
'total_load_MW': round(sum(load.values()), 1),
'verdict': 'PASS' if len(overlap) >= 4 else 'CHECK',
}
return run_domain('电力网络(IEEE 118母线)', G,
'MATPOWER官方 case118.m — 美国中西部电网标准测试系统', gt)
# ═══════════════════════════════════════════════════════
# 3. 航空网络 — OpenFlights全球航线
# ═══════════════════════════════════════════════════════
def build_aviation_graph():
"""routes.dat: 航空公司,IATA代码,源机场ID,目的机场ID(重复行=多家运营=航线强度)
airports.dat: ID,名称,城市,国家,IATA..."""
airports = {}
with open(os.path.join(DATA, 'airports.dat'), encoding='utf-8', errors='replace') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) > 6:
aid, name, city, country, iata = parts[0], parts[1], parts[2], parts[3], parts[4]
airports[aid] = {'name': name.strip('"'), 'city': city.strip('"'),
'country': country.strip('"'), 'iata': iata.strip('"')}
from collections import Counter
edge_count = Counter()
with open(os.path.join(DATA, 'routes.dat'), encoding='utf-8') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 5:
s, t = parts[3], parts[5]
if s in airports and t in airports and s != t:
edge_count[(s, t)] += 1 # 同一机场对多家运营=航线强度
G = nx.Graph()
max_c = max(edge_count.values())
for (s, t), c in edge_count.items():
w = 0.5 + 0.5 * (c / max_c) # 航线强度→置信度[0.5,1.0]
G.add_edge(airports[s]['iata'], airports[t]['iata'], weight=w, confidence=w,
route_count=c)
# 取最大连通分量(全球航空网)
G = G.subgraph(max(nx.connected_components(G), key=len)).copy()
return G
def run_aviation():
G = build_aviation_graph()
def gt(graph, phm):
"""对照: PHM top关键机场 vs 数据集实际连接数top机场。
全球航空枢纽(2014年前后): ATL/ORD/PEK/LHR/CDG/DFW等是公认事实"""
degs = dict(graph.degree)
top_actual = sorted(degs.items(), key=lambda x: -x[1])[:10]
phm_top = [t['node'] for t in phm['top_critical_nodes'][:10]]
overlap = set(phm_top) & {a for a, _ in top_actual}
return {
'known_fact': 'OpenFlights 2014年前后全球航线快照; ATL/ORD/PEK/LHR等是全球公认枢纽',
'phm_top10': phm_top,
'actual_top10_by_connections': [{'airport': a, 'destinations': d} for a, d in top_actual],
'overlap_count': len(overlap),
'verdict': 'PASS' if len(overlap) >= 6 else 'CHECK',
}
return run_domain('航空网络(OpenFlights全球航线)', G,
'openflights.org routes.dat — 67663条真实航线', gt)
# ═══════════════════════════════════════════════════════
# 4. 生物网络 — STRING人类PPI
# ═══════════════════════════════════════════════════════
def run_biological():
"""STRING v12.0人类蛋白质相互作用, combined_score≥970, 最大连通分量"""
links_path = STRING_LINKS or os.path.join(DATA, '9606.protein.links.v12.0.txt.gz')
if not os.path.exists(links_path):
raise FileNotFoundError('STRING数据未配置: 设PHM_STRING_LINKS环境变量或放到 '
'experiments/data/9606.protein.links.v12.0.txt.gz')
G = nx.Graph()
with gzip.open(links_path, 'rt') as f:
next(f)
for line in f:
p1, p2, s = line.split()
if int(s) >= 970:
w = int(s) / 1000.0
G.add_edge(p1, p2, weight=w, confidence=w)
G = G.subgraph(max(nx.connected_components(G), key=len)).copy()
# ENSP→基因名映射(只要度Top200的, 省内存)
top200 = sorted(dict(G.degree).items(), key=lambda x: -x[1])[:200]
want = {p for p, _ in top200}
ensp2gene = {}
alias_path = STRING_ALIAS or os.path.join(DATA, '9606.protein.aliases.v12.0.txt.gz')
with gzip.open(alias_path, 'rt') as f:
next(f)
for line in f:
parts = line.strip().split('\t')
if len(parts) >= 3 and parts[0] in want and parts[2] == 'Ensembl_HGNC_symbol':
ensp2gene[parts[0]] = parts[1]
def gt(graph, phm):
"""对照(2026-08-20实测修正):
① STRING v12.0全网络度排名: GAPDH(18714) > ACTB(15546) > TP53(14886)
[本地全量计算] — 'TP53第一'是旧版本说法, 已过时
② score≥970高置信核心: 核糖体蛋白(RPS家族)连接数最高 [本地实测]
③ 核糖体蛋白基因是DepMap common essential核心(如RPL3)
[DepMap文档/Medium, 2026-08-20 web-search核实]
PHM在≥970核心的top节点应为核糖体/翻译机器基因"""
phm_top = [t['node'] for t in phm['top_critical_nodes'][:10]]
top_named = [{'rank': i + 1, 'gene': ensp2gene.get(p, p), 'degree': graph.degree(p)}
for i, p in enumerate(phm_top)]
ribosomal = [t['gene'] for t in top_named
if t['gene'].startswith(('RPS', 'RPL', 'FAU', 'RPN'))]
return {
'known_fact': ('STRING v12.0全网络度Top3=GAPDH/ACTB/TP53[本地实测]; '
'≥970高置信核心由核糖体蛋白主导[本地实测]; '
'核糖体蛋白是DepMap common essential[web-search核实]'),
'phm_top10_genes': top_named,
'ribosomal_in_top10': ribosomal,
'note': '核糖体蛋白=翻译机器核心, 细胞最基础必需基因家族; '
'全网络(含低置信边)的hub=GAPDH/ACTB/TP53等经典管家/肿瘤基因',
'verdict': 'PASS' if len(ribosomal) >= 8 else 'CHECK',
}
return run_domain('生物网络(STRING人类PPI)', G,
'string-db.org v12.0 — 人类全基因组PPI(score≥970)', gt)
# ═══════════════════════════════════════════════════════
# 5. 脑网络 — ABIDE 100人真实fMRI
# ═══════════════════════════════════════════════════════
def run_brain():
"""ABIDE 100人(ASD+TD), AAL 116脑区时间序列 → 组级功能连接网络"""
abide_dir = ABIDE_DIR
if not abide_dir or not os.path.isdir(abide_dir):
raise FileNotFoundError('ABIDE数据未配置: 设PHM_ABIDE_DIR环境变量'
'(nilearn ABIDE_pcp/cpac/filt_noglobal)')
files = sorted(f for f in os.listdir(abide_dir) if f.endswith('_rois_aal.1D'))
print(f" 加载{len(files)}名被试时间序列...")
corr_sum = np.zeros((116, 116))
n_used = 0
for fn in files:
ts = np.loadtxt(os.path.join(abide_dir, fn))
if ts.ndim != 2 or ts.shape[1] != 116 or ts.shape[0] < 50:
continue
c = np.corrcoef(ts.T)
corr_sum += c
n_used += 1
corr_mean = corr_sum / n_used
# 阈值0.15(TOOLS.md: 100人组级最优阈值), |r|为置信度
G = nx.Graph()
for i in range(116):
for j in range(i + 1, 116):
r = corr_mean[i, j]
if abs(r) >= 0.15:
w = min(abs(r), 1.0)
G.add_edge(f'AAL_{i+1:03d}', f'AAL_{j+1:03d}', weight=w, confidence=w)
# 旧组级结果对照(2026-07历史运行)
old = {}
old_bet_regions = []
try:
with open(os.path.join(DATA, 'abide_100_group_validation.json')) as f:
d = json.load(f)
old = {'asd_d_eff_old': d.get('asd_d_eff'), 'td_d_eff_old': d.get('td_d_eff'),
'asd_eta_old': d.get('asd_eta'), 'td_eta_old': d.get('td_eta')}
except Exception:
pass
try:
with open(os.path.join(DATA, 'real_abide_validation.json')) as f:
d2 = json.load(f)
old_bet_regions = [t['region'] for t in d2.get('top_betweenness', [])]
except Exception:
pass
def gt(graph, phm):
phm_top = [t['node'] for t in phm['top_critical_nodes'][:10]]
# 历史运行(quantum_spacetime_agent)的Top介数脑区 — 从JSON直接读取
overlap = [r for r in phm_top if r in old_bet_regions]
return {
'known_fact': 'AAL图谱116脑区; 脑功能网络hub集中于默认模式网络等核心区域(脑科学共识)',
'n_subjects': n_used,
'threshold': '|r|>=0.15, 组级平均相关',
'phm_top10_regions': phm_top,
'overlap_with_old_betweenness_hubs': {
'overlap_regions': overlap,
'overlap_count': len(overlap),
'note': '历史运行(hsec_agent, Top介数)与本次(Top度数)识别的hub脑区重叠数',
},
'historical_reference': {
**old,
'note': ('历史d_eff=3.4-3.5为旧定义(路径长度法, 2026-07-19桥接公式之前); '
'本次d_eff=4×S_local(现行桥接公式). 两者不可直接比较, '
'但hub脑区识别结果一致(见overlap)'),
},
'verdict': 'PASS' if len(overlap) >= 3 else 'RECORDED',
}
return run_domain('脑网络(ABIDE 100人fMRI)', G,
f'ABIDE PCP(cpac/filt_noglobal) {n_used}名被试, AAL 116脑区', gt)
# ═══════════════════════════════════════════════════════
# 主流程
# ═══════════════════════════════════════════════════════
def replay():
"""复现模式: 读取已提交的实验结果JSON, 打印汇总表(不需要闭源引擎)"""
keys = {'social': '社交', 'power': '电力', 'aviation': '航空', 'bio': '生物', 'brain': '脑'}
rows = []
for k, label in keys.items():
p = os.path.join(DATA, f'{k}_result.json')
if not os.path.exists(p):
continue
v = json.load(open(p, encoding='utf-8'))
v = next(iter(v.values())) # 分域JSON外层是域名键
phm, gt = v['phm'], v.get('ground_truth_check', {})
rows.append((v['domain'], phm['d_eff'], phm['eta'], phm['phase'],
v['elapsed_s'], gt.get('verdict', '-')))
print(f"{'领域':<26} {'d_eff':>7} {'η':>7} {'相':>15} {'耗时':>7} 对照")
print('-' * 70)
for r in rows:
print(f"{r[0]:<26} {r[1]:>7} {r[2]:>7} {r[3]:>15} {r[4]:>6}s {r[5]}")
print('-' * 70)
print("数据: data/multidomain/*_result.json | LLM闭环: llm_aviation_report.json")
print("实测日期: 2026-08-20 | 引擎: PHM闭源核心(方法学见本脚本)")
if __name__ == '__main__':
t_all = time.time()
which = sys.argv[1] if len(sys.argv) > 1 else 'all'
if which == 'replay':
replay()
sys.exit(0)
runners = {
'social': run_social,
'power': run_power,
'aviation': run_aviation,
'bio': run_biological,
'brain': run_brain,
}
for key, fn in runners.items():
if which in ('all', key):
try:
fn()
except Exception as e:
import traceback
traceback.print_exc()
RESULTS[key] = {'domain': key, 'error': str(e)}
# 分域独立保存(避免504丢结果)
if which != 'all':
dom_file = os.path.join(OUT, f'{which}_result.json')
with open(dom_file, 'w', encoding='utf-8') as f:
json.dump(RESULTS, f, ensure_ascii=False, indent=2, default=str)
print(f"\n→ 已保存 {dom_file}")
# 保存
out_path = os.path.join(OUT, 'multidomain_results.json')
if which == 'all':
with open(out_path, 'w', encoding='utf-8') as f:
json.dump(RESULTS, f, ensure_ascii=False, indent=2)
print(f"\n{'='*60}")
print(f"全部5领域完成, 总耗时{round(time.time()-t_all,1)}s → {out_path}")
# 汇总表
print(f"\n{'领域':<20} {'d_eff':>7} {'η':>7} {'相':>15} {'耗时':>6} 对照")
for k, v in RESULTS.items():
if 'error' in v:
print(f"{k:<20} ERROR: {v['error'][:40]}")
continue
p = v['phm']
vd = v.get('ground_truth_check', {}).get('verdict', '-')
print(f"{v['domain']:<20} {p['d_eff']:>7} {p['eta']:>7} {p['phase']:>15} "
f"{v['elapsed_s']:>5}s {vd}")
elif which == 'merge':
pass # merge分支在下方单独处理
else:
print(json.dumps(RESULTS, ensure_ascii=False, indent=2, default=str))
# merge模式: 合并所有分域结果为总JSON(独立于上面, 防分域运行时误合并)
def _merge():
merged = {}
for key in ('social', 'power', 'aviation', 'bio', 'brain'):
p = os.path.join(OUT, f'{key}_result.json')
if os.path.exists(p):
with open(p, encoding='utf-8') as f:
merged.update(json.load(f))
out = os.path.join(OUT, 'multidomain_results.json')
with open(out, 'w', encoding='utf-8') as f:
json.dump(merged, f, ensure_ascii=False, indent=2)
print(f"合并{len(merged)}个领域 → {out}")
print(f"\n{'领域':<24} {'d_eff':>7} {'η':>7} {'相':>15} {'耗时':>6} 对照")
for k, v in merged.items():
if 'error' in v:
print(f"{k:<24} ERROR: {v['error'][:40]}")
continue
p = v['phm']
vd = v.get('ground_truth_check', {}).get('verdict', '-')
print(f"{v['domain']:<24} {p['d_eff']:>7} {p['eta']:>7} {p['phase']:>15} "
f"{v['elapsed_s']:>5}s {vd}")
return merged
if __name__ == '__main__' and len(sys.argv) > 1 and sys.argv[1] == 'merge':
_merge()