dsh-codegraph-visualizer 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +391 -226
  2. package/assets/icon-180.png +0 -0
  3. package/assets/icon.svg +30 -0
  4. package/cordis.patch.yml +22 -17
  5. package/dist/CodeGraphAdapter-BNIK0xHU.js +422 -0
  6. package/dist/CodeGraphAdapter-BNIK0xHU.js.map +1 -0
  7. package/dist/CodeGraphAdapter-CXj1hbdm.js +10 -0
  8. package/dist/FileSystemAdapter-CgasSGbB.js +524 -0
  9. package/dist/FileSystemAdapter-CgasSGbB.js.map +1 -0
  10. package/dist/LensAdapter-C9Saf_i_.js +89 -0
  11. package/dist/LensAdapter-C9Saf_i_.js.map +1 -0
  12. package/dist/Logger-DUIJSeNu.js +80 -0
  13. package/dist/Logger-DUIJSeNu.js.map +1 -0
  14. package/dist/TreeSitterAdapter-C-EWy8br.js +631 -0
  15. package/dist/TreeSitterAdapter-C-EWy8br.js.map +1 -0
  16. package/dist/TreeSitterAdapter-C6FcO2_s.js +8 -0
  17. package/dist/client/index.css +8963 -1724
  18. package/dist/client/index.css.map +1 -0
  19. package/dist/client/index.d.ts +634 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.esm.js +57197 -20713
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/{index.js → worker/layoutWorker.js} +7657 -8787
  24. package/dist/client.js +90426 -0
  25. package/dist/codeGraphDb-B-vpZmpm.js +9 -0
  26. package/dist/codeGraphDb-T6K3dK5D.js +357 -0
  27. package/dist/codeGraphDb-T6K3dK5D.js.map +1 -0
  28. package/dist/codeGraphRows-BhZX34uB.js +6 -0
  29. package/dist/codeGraphRows-C7hqu35E.js +246 -0
  30. package/dist/codeGraphRows-C7hqu35E.js.map +1 -0
  31. package/dist/constants-CQq93l3e.js +399 -0
  32. package/dist/constants-CQq93l3e.js.map +1 -0
  33. package/dist/graphFingerprint-CeARo7jG.js +4 -0
  34. package/dist/graphFingerprint-PuxORZUF.js +277 -0
  35. package/dist/graphFingerprint-PuxORZUF.js.map +1 -0
  36. package/dist/graphMetrics-C0F30730.js +555 -0
  37. package/dist/graphMetrics-C0F30730.js.map +1 -0
  38. package/dist/index.d.ts +572 -9
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +14819 -2009
  41. package/dist/index.js.map +1 -1
  42. package/dist/inheritanceEnhancer-Bi3UoYJS.js +291 -0
  43. package/dist/inheritanceEnhancer-Bi3UoYJS.js.map +1 -0
  44. package/dist/layerUtils-DDm-EsaD.js +72 -0
  45. package/dist/layerUtils-DDm-EsaD.js.map +1 -0
  46. package/dist/nativeIndex-CABbgERW.js +347 -0
  47. package/dist/nativeIndex-CABbgERW.js.map +1 -0
  48. package/dist/nativeIndex-esaDOsq8.js +5 -0
  49. package/dist/treeSitterExtractors-B1ybKNrC.js +394 -0
  50. package/dist/treeSitterExtractors-B1ybKNrC.js.map +1 -0
  51. package/dist/types-BUDxI_00.js +23 -0
  52. package/dist/types-BUDxI_00.js.map +1 -0
  53. package/locale/en.json +6 -0
  54. package/locale/zh-CN.json +6 -0
  55. package/locale/zh.json +6 -0
  56. package/package.json +100 -20
  57. package/dist/client-cjs/index.cjs +0 -44283
  58. package/dist/client-cjs/index.css +0 -2404
@@ -0,0 +1,555 @@
1
+ //#region src/xuanji/analysis/brandes.ts
2
+ /**
3
+ * BFS 从 start 出发,产出距离 / 最短路径数 σ / 前驱 / SPLIT 出栈顺序。
4
+ * 四个量缺一不可——只有距离无法做依赖累加,只有 σ 无法做比例分摊。
5
+ */
6
+ function bfsShortestPaths(adj, start) {
7
+ const dist = new Map();
8
+ const sigma = new Map();
9
+ const pred = new Map();
10
+ const stack = [];
11
+ dist.set(start, 0);
12
+ sigma.set(start, 1);
13
+ const queue = [start];
14
+ while (queue.length > 0) {
15
+ const v = queue.shift();
16
+ stack.push(v);
17
+ const neighbors = adj.all.get(v);
18
+ if (!neighbors) continue;
19
+ for (const w of neighbors) {
20
+ if (!dist.has(w)) {
21
+ dist.set(w, (dist.get(v) ?? 0) + 1);
22
+ sigma.set(w, 0);
23
+ pred.set(w, []);
24
+ queue.push(w);
25
+ }
26
+ if (dist.get(w) === (dist.get(v) ?? 0) + 1) {
27
+ sigma.set(w, (sigma.get(w) ?? 0) + (sigma.get(v) ?? 0));
28
+ pred.get(w).push(v);
29
+ }
30
+ }
31
+ }
32
+ return {
33
+ dist,
34
+ sigma,
35
+ pred,
36
+ stack
37
+ };
38
+ }
39
+ /**
40
+ * 介数中心性——真实的无向 Brandes 算法。
41
+ *
42
+ * 在无向投影上做 BFS + SPLIT 依赖累加,归一化 `2/((n-1)(n-2))`。
43
+ * 大图采样时用等距步长(无偏),并如实返回 `approximate`。
44
+ *
45
+ * ## sampleRate 必须有预算,不能默认全量(cycle-141 第四轮)
46
+ *
47
+ * 本函数自拆出以来 `sampleRate` 默认 1.0,而**唯一生产调用点**(`graphMetrics.ts`
48
+ * 的 `computeBetweennessCentrality(adj)`)从不传它——8393 节点的真实仓库上
49
+ * 8393 源 × 全图 BFS 让 `manage/metrics` 路由实测 **65.4s**(真机 3085 沙箱
50
+ * 2026-11-05)。`distance.ts` 的 `computeDistanceStats` 有 `maxSample=200`
51
+ * 上界而这里没有,同一文件族两种纪律。采样基础设施(等距步长 / approximate
52
+ * 披露)早已齐备,缺的只是调用点把预算接上。
53
+ *
54
+ * 归一化与放大估计已按「采样时乘 n/sampleSize」实现,样本数变化不影响正确性——
55
+ * 只影响精度(上界估计,如实标注 approximate)。maxSample 的量级与
56
+ * `computeDistanceStats.maxSample` 一致:两条指标共享同一批 BFS 成本特征,
57
+ * 「近似披露」在 UI 上口径统一。
58
+ */
59
+ function computeBetweennessCentrality(adj, sampleRate = 1, maxSample = 200) {
60
+ const allNodes = [...adj.all.keys()];
61
+ const n = allNodes.length;
62
+ const centrality = new Map();
63
+ for (const id of allNodes) centrality.set(id, 0);
64
+ if (n < 3) return {
65
+ centrality,
66
+ sampleSize: 0,
67
+ approximate: false
68
+ };
69
+ const byRate = Math.max(1, Math.floor(n * sampleRate));
70
+ const sampleSize = maxSample === null ? Math.min(n, byRate) : Math.min(n, byRate, maxSample);
71
+ const indices = [];
72
+ if (sampleSize >= n) for (let i = 0; i < n; i++) indices.push(i);
73
+ else {
74
+ const stride = n / sampleSize;
75
+ for (let i = 0; i < sampleSize; i++) indices.push(Math.floor(i * stride));
76
+ }
77
+ for (const startIdx of indices) {
78
+ const start = allNodes[startIdx];
79
+ if (!start) continue;
80
+ const { sigma, pred, stack } = bfsShortestPaths(adj, start);
81
+ const delta = new Map();
82
+ for (const id of allNodes) delta.set(id, 0);
83
+ while (stack.length > 0) {
84
+ const w = stack.pop();
85
+ for (const v of pred.get(w) ?? []) {
86
+ const sigmaW = sigma.get(w) ?? 0;
87
+ if (sigmaW > 0) delta.set(v, (delta.get(v) ?? 0) + (sigma.get(v) ?? 0) / sigmaW * (1 + (delta.get(w) ?? 0)));
88
+ }
89
+ if (w !== start) centrality.set(w, (centrality.get(w) ?? 0) + (delta.get(w) ?? 0));
90
+ }
91
+ }
92
+ const denom = (n - 1) * (n - 2);
93
+ const scale = sampleSize > 0 ? n / sampleSize : 1;
94
+ const factor = scale / denom;
95
+ for (const [id, val] of centrality) centrality.set(id, val * factor);
96
+ return {
97
+ centrality,
98
+ sampleSize,
99
+ approximate: sampleSize < n
100
+ };
101
+ }
102
+
103
+ //#endregion
104
+ //#region src/xuanji/analysis/connectivity.ts
105
+ /**
106
+ * 弱连通分量数(WCC)——在无向投影上做 BFS 计数。
107
+ * 复杂度 O(V + E)。
108
+ */
109
+ function countWeaklyConnectedComponents(adj) {
110
+ const visited = new Set();
111
+ let count = 0;
112
+ for (const start of adj.all.keys()) {
113
+ if (visited.has(start)) continue;
114
+ count++;
115
+ const queue = [start];
116
+ visited.add(start);
117
+ while (queue.length > 0) {
118
+ const v = queue.shift();
119
+ for (const w of adj.all.get(v) ?? []) if (!visited.has(w)) {
120
+ visited.add(w);
121
+ queue.push(w);
122
+ }
123
+ }
124
+ }
125
+ return count;
126
+ }
127
+ /**
128
+ * 强连通分量数(SCC)——Kosaraju 算法(两趟 DFS)。
129
+ * 复杂度 O(V + E)。
130
+ */
131
+ function countStronglyConnectedComponents(adj) {
132
+ const nodes = [...adj.all.keys()];
133
+ const visited = new Set();
134
+ const order = [];
135
+ const dfs1 = (v) => {
136
+ visited.add(v);
137
+ for (const w of adj.out.get(v) ?? []) if (!visited.has(w)) dfs1(w);
138
+ order.push(v);
139
+ };
140
+ for (const v of nodes) if (!visited.has(v)) dfs1(v);
141
+ const visited2 = new Set();
142
+ let count = 0;
143
+ const dfs2 = (v) => {
144
+ visited2.add(v);
145
+ for (const w of adj.in.get(v) ?? []) if (!visited2.has(w)) dfs2(w);
146
+ };
147
+ for (let i = order.length - 1; i >= 0; i--) {
148
+ const v = order[i];
149
+ if (!visited2.has(v)) {
150
+ count++;
151
+ dfs2(v);
152
+ }
153
+ }
154
+ return count;
155
+ }
156
+ /**
157
+ * 关节点(articulation point)计数——Tarjan 算法,在无向投影上。
158
+ *
159
+ * 关节点是「去掉后增加连通分量数」的节点——代码图里的「咽喉」。
160
+ * 复杂度 O(V + E)。
161
+ */
162
+ function countArticulationPoints(adj) {
163
+ const nodes = [...adj.all.keys()];
164
+ const visited = new Set();
165
+ const disc = new Map();
166
+ const low = new Map();
167
+ const parent = new Map();
168
+ let time = 0;
169
+ let count = 0;
170
+ const dfs = (v) => {
171
+ visited.add(v);
172
+ disc.set(v, time);
173
+ low.set(v, time);
174
+ time++;
175
+ let children = 0;
176
+ for (const w of adj.all.get(v) ?? []) if (!visited.has(w)) {
177
+ children++;
178
+ parent.set(w, v);
179
+ dfs(w);
180
+ low.set(v, Math.min(low.get(v), low.get(w)));
181
+ if (parent.get(v) === null && children > 1) count++;
182
+ if (parent.get(v) !== null && (low.get(w) ?? 0) >= (disc.get(v) ?? 0)) count++;
183
+ } else if (w !== parent.get(v)) low.set(v, Math.min(low.get(v), disc.get(w)));
184
+ };
185
+ for (const v of nodes) if (!visited.has(v)) {
186
+ parent.set(v, null);
187
+ dfs(v);
188
+ }
189
+ return count;
190
+ }
191
+
192
+ //#endregion
193
+ //#region src/xuanji/analysis/distance.ts
194
+ /**
195
+ * 图的直径与平均最短路径——BFS 全源(大图时采样源节点)。
196
+ *
197
+ * 断开的图返回 `null`(直径在非连通图上无定义)。大图时只从部分源做 BFS,
198
+ * 直径是**上界估计**——如实标注,不假装精确。
199
+ */
200
+ function computeDistanceStats(adj, maxSample = 200) {
201
+ const nodes = [...adj.all.keys()];
202
+ const n = nodes.length;
203
+ if (n === 0) return {
204
+ diameter: null,
205
+ avgShortestPath: null,
206
+ sampled: false
207
+ };
208
+ const sources = n <= maxSample ? nodes : nodes.filter((_, i) => i % Math.ceil(n / maxSample) === 0).slice(0, maxSample);
209
+ let maxDist = 0;
210
+ let totalDist = 0;
211
+ let pairs = 0;
212
+ let disconnected = false;
213
+ for (const s of sources) {
214
+ const dist = bfsShortestPaths(adj, s).dist;
215
+ for (const t of nodes) {
216
+ if (t === s) continue;
217
+ if (!dist.has(t)) {
218
+ disconnected = true;
219
+ continue;
220
+ }
221
+ const d = dist.get(t);
222
+ if (d > maxDist) maxDist = d;
223
+ totalDist += d;
224
+ pairs++;
225
+ }
226
+ }
227
+ if (disconnected || pairs === 0) return {
228
+ diameter: null,
229
+ avgShortestPath: null,
230
+ sampled: sources.length < n
231
+ };
232
+ return {
233
+ diameter: maxDist,
234
+ avgShortestPath: totalDist / pairs,
235
+ sampled: sources.length < n
236
+ };
237
+ }
238
+
239
+ //#endregion
240
+ //#region src/xuanji/analysis/community.ts
241
+ /**
242
+ * k-core:最大的 k,使得存在一个子图其中每个节点度数 ≥ k。
243
+ *
244
+ * 反复移除最小度节点并更新度数,记录过程中的最大「最小度」。
245
+ * 复杂度 O(V + E)(用优先队列可优化,此处用线性扫描,代码图谱规模可接受)。
246
+ */
247
+ function computeKCore(adj) {
248
+ const deg = new Map();
249
+ const alive = new Set();
250
+ for (const [id, neighbors] of adj.all) {
251
+ deg.set(id, neighbors.size);
252
+ alive.add(id);
253
+ }
254
+ let k = 0;
255
+ while (alive.size > 0) {
256
+ let minDeg = Infinity;
257
+ for (const d of deg.values()) if (d < minDeg) minDeg = d;
258
+ if (minDeg > k) k = minDeg;
259
+ let removed = false;
260
+ for (const id of alive) if ((deg.get(id) ?? 0) <= k) {
261
+ alive.delete(id);
262
+ for (const nb of adj.all.get(id) ?? []) if (alive.has(nb)) deg.set(nb, (deg.get(nb) ?? 0) - 1);
263
+ removed = true;
264
+ }
265
+ if (!removed) break;
266
+ }
267
+ return k;
268
+ }
269
+ /**
270
+ * 模块度(modularity)——单层贪心 Louvain 近似。
271
+ *
272
+ * 从每个节点自成社区开始,迭代地把节点移到能最大化模块度增益的邻居社区,
273
+ * 最后计算最终划分的模块度 Q ∈ [-0.5, 1]。Q 越接近 1 社区结构越明显,
274
+ * 无结构的图 Q ≈ 0。这是**近似**——不是完整 Louvain(多层 + 分辨率参数),
275
+ * 但对代码图的模块度估计足够,且复杂度 O(iterations × V × avgDeg)。
276
+ */
277
+ function computeModularity(adj) {
278
+ const nodes = [...adj.all.keys()];
279
+ const n = nodes.length;
280
+ if (n === 0) return 0;
281
+ let m = 0;
282
+ for (const [, neighbors] of adj.all) m += neighbors.size;
283
+ m /= 2;
284
+ if (m === 0) return 0;
285
+ const k = new Map();
286
+ for (const [id, neighbors] of adj.all) k.set(id, neighbors.size);
287
+ const community = new Map();
288
+ nodes.forEach((id, i) => community.set(id, i));
289
+ for (let round = 0; round < 10; round++) {
290
+ let improved = false;
291
+ for (const v of nodes) {
292
+ const cv = community.get(v);
293
+ const toComm = new Map();
294
+ for (const w of adj.all.get(v) ?? []) {
295
+ const cw = community.get(w);
296
+ toComm.set(cw, (toComm.get(cw) ?? 0) + 1);
297
+ }
298
+ let bestComm = -1;
299
+ let bestCount = 0;
300
+ for (const [c, count] of toComm) if (c !== cv && count > bestCount) {
301
+ bestComm = c;
302
+ bestCount = count;
303
+ }
304
+ if (bestComm >= 0) {
305
+ community.set(v, bestComm);
306
+ improved = true;
307
+ }
308
+ }
309
+ if (!improved) break;
310
+ }
311
+ let q = 0;
312
+ for (const v of nodes) {
313
+ const cv = community.get(v);
314
+ for (const w of adj.all.get(v) ?? []) if (community.get(w) === cv) q += 1 - (k.get(v) ?? 0) * (k.get(w) ?? 0) / (2 * m);
315
+ }
316
+ return q / (2 * m);
317
+ }
318
+
319
+ //#endregion
320
+ //#region src/xuanji/analysis/graphMetrics.ts
321
+ /**
322
+ * BFS 采样源的统一预算(cycle-141 第四轮)。
323
+ *
324
+ * 真机量化(8393 节点仓库):介数全量(8393 源 × 全图 BFS)实测 65.4s,
325
+ * `manage/metrics` 路由从「即时」退化为「一分钟白屏」。200 源与
326
+ * `computeDistanceStats.maxSample` 同量级——两条近似指标共享同一口径,
327
+ * 采样是上界估计且响应里如实披露(`betweennessApproximate` / `sampled`)。
328
+ *
329
+ * 常量放在 xuanji 库内而不是宿主 `shared/constants.ts`:库边界契约
330
+ * (libraryBoundary)要求 xuanji 零宿主依赖,预算是算法自身的成本特征,
331
+ * 属于库的语义而非宿主配置。
332
+ */
333
+ const BFS_SAMPLE_SOURCES = 200;
334
+ /** 构建邻接表——一次遍历即可。 */
335
+ function buildAdjacencyList(nodes, edges) {
336
+ const out = new Map();
337
+ const inn = new Map();
338
+ const all = new Map();
339
+ for (const n of nodes) {
340
+ out.set(n.id, new Set());
341
+ inn.set(n.id, new Set());
342
+ all.set(n.id, new Set());
343
+ }
344
+ for (const e of edges) {
345
+ if (!out.has(e.source) || !inn.has(e.target)) continue;
346
+ out.get(e.source).add(e.target);
347
+ inn.get(e.target).add(e.source);
348
+ all.get(e.source).add(e.target);
349
+ all.get(e.target).add(e.source);
350
+ }
351
+ return {
352
+ out,
353
+ in: inn,
354
+ all
355
+ };
356
+ }
357
+ /**
358
+ * 图密度:实际边数 / 可能边数。完全图的密度为 1,空图为 0。
359
+ *
360
+ * ## 有向语义(cycle-141 P0 修复)
361
+ *
362
+ * 代码图谱的边是**有向**的(call / import / extend …),但旧实现用无向可能边数
363
+ * `n(n-1)/2` 作分母——同一张图按有向算是无向的两倍,且没有任何声明说明用的是
364
+ * 哪种口径。两种口径相差 2 倍,用户无法判断「密度 0.3」意味着什么。
365
+ *
366
+ * 修法:改用**有向**可能边数 `n(n-1)`。这是代码图的正确口径;若将来需要无向
367
+ * 密度,应显式新增一个函数而不是悄悄改分母。
368
+ */
369
+ function computeDensity(nodeCount, edgeCount) {
370
+ if (nodeCount < 2) return 0;
371
+ const possible = nodeCount * (nodeCount - 1);
372
+ return Math.min(1, edgeCount / possible);
373
+ }
374
+ /**
375
+ * 局部聚类系数:节点邻居间实际边数 / 可能边数。
376
+ * 邻居数 < 2 时返回 0(无法形成三角形)。
377
+ */
378
+ function computeClusteringCoefficient(adj, nodeId) {
379
+ const neighbors = adj.all.get(nodeId);
380
+ if (!neighbors || neighbors.size < 2) return 0;
381
+ const neighborList = [...neighbors];
382
+ let actualEdges = 0;
383
+ for (let i = 0; i < neighborList.length; i++) for (let j = i + 1; j < neighborList.length; j++) if (adj.all.get(neighborList[i]).has(neighborList[j])) actualEdges++;
384
+ const possible = neighborList.length * (neighborList.length - 1) / 2;
385
+ return possible > 0 ? actualEdges / possible : 0;
386
+ }
387
+ /** 全局平均聚类系数。 */
388
+ function computeAverageClustering(adj) {
389
+ if (adj.all.size === 0) return 0;
390
+ let sum = 0;
391
+ for (const id of adj.all.keys()) sum += computeClusteringCoefficient(adj, id);
392
+ return sum / adj.all.size;
393
+ }
394
+ /**
395
+ * PageRank:基于随机游走的节点重要性排名。
396
+ * 迭代收敛,阻尼系数 0.85(标准值)。复杂度 O(iterations × E)。
397
+ *
398
+ * ## 悬挂节点质量守恒(cycle-141 P0 修复)
399
+ *
400
+ * 旧实现对出度为 0 的节点直接 `continue`,其 `damping × rank` 质量**每轮丢失**,
401
+ * 于是总质量逐轮衰减、排名整体失真——收敛判据以 n 缩放,恰好掩盖了不归一。
402
+ *
403
+ * 标准做法:悬挂节点不流出质量,而是把 `damping × rank` **平均重分配给所有节点**。
404
+ * 这样每轮总质量恒为 1((1-damping) 的 teleport + damping×1 的重分配),
405
+ * 排名与「图是否连通」解耦。修复后 `pageRankMass ≈ 1` 是可断言的守恒见证。
406
+ */
407
+ function computePageRank(adj, damping = .85, iterations = 20, epsilon = 1e-6) {
408
+ const nodeIds = [...adj.all.keys()];
409
+ const n = nodeIds.length;
410
+ if (n === 0) return new Map();
411
+ const rank = new Map();
412
+ for (const id of nodeIds) rank.set(id, 1 / n);
413
+ for (let iter = 0; iter < iterations; iter++) {
414
+ const next = new Map();
415
+ let diff = 0;
416
+ for (const id of nodeIds) next.set(id, (1 - damping) / n);
417
+ let danglingMass = 0;
418
+ for (const id of nodeIds) {
419
+ const outNeighbors = adj.out.get(id);
420
+ if (!outNeighbors || outNeighbors.size === 0) {
421
+ danglingMass += damping * (rank.get(id) ?? 0);
422
+ continue;
423
+ }
424
+ const share = damping * (rank.get(id) ?? 0) / outNeighbors.size;
425
+ for (const neighbor of outNeighbors) next.set(neighbor, (next.get(neighbor) ?? 0) + share);
426
+ }
427
+ if (danglingMass > 0) {
428
+ const danglingShare = danglingMass / n;
429
+ for (const id of nodeIds) next.set(id, (next.get(id) ?? 0) + danglingShare);
430
+ }
431
+ for (const id of nodeIds) {
432
+ diff += Math.abs((next.get(id) ?? 0) - (rank.get(id) ?? 0));
433
+ rank.set(id, next.get(id) ?? 0);
434
+ }
435
+ if (diff < epsilon * n) break;
436
+ }
437
+ return rank;
438
+ }
439
+ /** 度数分布:返回每个节点的出度和入度。 */
440
+ function computeDegreeDistribution(adj) {
441
+ const outDegrees = new Map();
442
+ const inDegrees = new Map();
443
+ for (const [id, neighbors] of adj.out) outDegrees.set(id, neighbors.size);
444
+ for (const [id, neighbors] of adj.in) inDegrees.set(id, neighbors.size);
445
+ return {
446
+ outDegrees,
447
+ inDegrees
448
+ };
449
+ }
450
+ /**
451
+ * 度数分布的幂律检验(简化版)。
452
+ * 返回 { alpha, isPowerLaw, maxDegree, minDegree }。
453
+ * alpha 越接近 1 越接近幂律分布(代码图谱通常 alpha ≈ 2-3)。
454
+ */
455
+ function analyzeDegreeDistribution(outDegrees) {
456
+ const degrees = [...outDegrees.values()].filter((d) => d > 0);
457
+ if (degrees.length < 10) return {
458
+ alpha: 0,
459
+ isPowerLaw: false,
460
+ maxDegree: 0,
461
+ minDegree: 0,
462
+ meanDegree: 0
463
+ };
464
+ const maxDeg = degrees.reduce((a, b) => b > a ? b : a, 0);
465
+ const minDeg = degrees.reduce((a, b) => b < a ? b : a, degrees[0]);
466
+ const meanDeg = degrees.reduce((a, b) => a + b, 0) / degrees.length;
467
+ const sorted = [...degrees].sort((a, b) => b - a);
468
+ const logDeg = [];
469
+ const logRank = [];
470
+ for (let i = 0; i < sorted.length; i++) if (sorted[i] > 0) {
471
+ logDeg.push(Math.log(sorted[i]));
472
+ logRank.push(Math.log(i + 1));
473
+ }
474
+ const n = logDeg.length;
475
+ if (n < 5) return {
476
+ alpha: 0,
477
+ isPowerLaw: false,
478
+ maxDegree: maxDeg,
479
+ minDegree: minDeg,
480
+ meanDegree: meanDeg
481
+ };
482
+ const meanX = logRank.reduce((a, b) => a + b, 0) / n;
483
+ const meanY = logDeg.reduce((a, b) => a + b, 0) / n;
484
+ let num = 0, den = 0;
485
+ for (let i = 0; i < n; i++) {
486
+ num += (logRank[i] - meanX) * (logDeg[i] - meanY);
487
+ den += (logRank[i] - meanX) ** 2;
488
+ }
489
+ const slope = den > 0 ? num / den : 0;
490
+ const alpha = -slope;
491
+ return {
492
+ alpha,
493
+ isPowerLaw: alpha > 1 && alpha < 4,
494
+ maxDegree: maxDeg,
495
+ minDegree: minDeg,
496
+ meanDegree: meanDeg
497
+ };
498
+ }
499
+ /** 计算完整的图指标。 */
500
+ function computeGraphMetrics(nodes, edges) {
501
+ const adj = buildAdjacencyList(nodes, edges);
502
+ const nodeCount = nodes.length;
503
+ const edgeCount = edges.length;
504
+ const density = computeDensity(nodeCount, edgeCount);
505
+ const avgClustering = computeAverageClustering(adj);
506
+ const betweennessResult = computeBetweennessCentrality(adj, 1, BFS_SAMPLE_SOURCES);
507
+ const betweenness = betweennessResult.centrality;
508
+ const pageRank = computePageRank(adj);
509
+ const { outDegrees } = computeDegreeDistribution(adj);
510
+ const degreeStats = analyzeDegreeDistribution(outDegrees);
511
+ const topBetweenness = [...betweenness.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([id, value]) => ({
512
+ id,
513
+ value
514
+ }));
515
+ const topPageRank = [...pageRank.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([id, value]) => ({
516
+ id,
517
+ value
518
+ }));
519
+ const components = {
520
+ weaklyConnected: countWeaklyConnectedComponents(adj),
521
+ stronglyConnected: countStronglyConnectedComponents(adj)
522
+ };
523
+ const articulationPoints = countArticulationPoints(adj);
524
+ const distStats = computeDistanceStats(adj);
525
+ const modularity = computeModularity(adj);
526
+ const cyclomaticNumber = edgeCount - nodeCount + components.weaklyConnected;
527
+ const kCore = computeKCore(adj);
528
+ let pageRankMass = 0;
529
+ for (const v of pageRank.values()) pageRankMass += v;
530
+ return {
531
+ nodeCount,
532
+ edgeCount,
533
+ density,
534
+ avgClustering,
535
+ betweenness,
536
+ pageRank,
537
+ degreeDistribution: degreeStats,
538
+ topBetweenness,
539
+ topPageRank,
540
+ betweennessSampleSize: betweennessResult.sampleSize,
541
+ betweennessApproximate: betweennessResult.approximate,
542
+ pageRankMass,
543
+ components,
544
+ articulationPoints,
545
+ diameter: distStats.diameter,
546
+ avgShortestPath: distStats.avgShortestPath,
547
+ modularity,
548
+ cyclomaticNumber,
549
+ kCore
550
+ };
551
+ }
552
+
553
+ //#endregion
554
+ export { computeGraphMetrics };
555
+ //# sourceMappingURL=graphMetrics-C0F30730.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphMetrics-C0F30730.js","names":["adj: AdjacencyList","start: string","stack: string[]","queue: string[]","maxSample: number | null","indices: number[]","adj: AdjacencyList","queue: string[]","order: string[]","v: string","adj: AdjacencyList","adj: AdjacencyList","nodes: MetricNode[]","edges: MetricEdge[]","nodeCount: number","edgeCount: number","adj: AdjacencyList","nodeId: string","outDegrees: Map<string, number>","logDeg: number[]","logRank: number[]"],"sources":["../src/xuanji/analysis/brandes.ts","../src/xuanji/analysis/connectivity.ts","../src/xuanji/analysis/distance.ts","../src/xuanji/analysis/community.ts","../src/xuanji/analysis/graphMetrics.ts"],"sourcesContent":["/**\n * 介数中心性——真实无向 Brandes 算法(cycle-141)。\n *\n * ## 旧实现为什么是错的\n *\n * 旧代码 BFS 沿有向出边走、回溯用无向邻接、只累加距离 ≤ 2 的路径、用固定\n * 0.5/0.25 系数——既不是无向 Brandes 也不是有向 Brandes,Top 榜数学上无意义。\n *\n * ## 新实现的口径\n *\n * - 在**无向投影**(`adj.all`)上做 Brandes:BFS 产出距离 / 最短路径数 σ /\n * 前驱 / 出栈顺序,再用 SPLIT 逆序做依赖累加 δ(s,v) = Σ (σ(s,v)/σ(s,w))·(1+δ(s,w))。\n * - 归一化:无向图每个无序对被算两次,故 `BC_norm(v) = BC_raw(v) / ((n-1)(n-2))`。\n * - 采样:大图时等距步长取源节点(无模偏差),`sampleSize` 不会超过节点数;\n * 采样时按 `n / sampleSize` 放大估计,并如实返回 `approximate: true`。\n *\n * 复杂度 O(sampleSize · (V + E))。从 graphMetrics.ts 拆出(cycle-141 复杂度治理)。\n */\nimport type { AdjacencyList } from './types.ts';\n\n/** BFS 最短路径计数——Brandes 算法的第一步。 */\nexport interface BfsResult {\n dist: Map<string, number>;\n sigma: Map<string, number>;\n pred: Map<string, string[]>;\n stack: string[];\n}\n\n/**\n * BFS 从 start 出发,产出距离 / 最短路径数 σ / 前驱 / SPLIT 出栈顺序。\n * 四个量缺一不可——只有距离无法做依赖累加,只有 σ 无法做比例分摊。\n */\nexport function bfsShortestPaths(adj: AdjacencyList, start: string): BfsResult {\n const dist = new Map<string, number>();\n const sigma = new Map<string, number>();\n const pred = new Map<string, string[]>();\n const stack: string[] = [];\n dist.set(start, 0);\n sigma.set(start, 1);\n const queue: string[] = [start];\n\n while (queue.length > 0) {\n const v = queue.shift()!;\n stack.push(v);\n const neighbors = adj.all.get(v);\n if (!neighbors) continue;\n for (const w of neighbors) {\n // 首次到达:设置距离、初始化 σ 与前驱。\n if (!dist.has(w)) {\n dist.set(w, (dist.get(v) ?? 0) + 1);\n sigma.set(w, 0);\n pred.set(w, []);\n queue.push(w);\n }\n // 等距前驱:累加最短路径数并记录前驱。\n if (dist.get(w) === (dist.get(v) ?? 0) + 1) {\n sigma.set(w, (sigma.get(w) ?? 0) + (sigma.get(v) ?? 0));\n pred.get(w)!.push(v);\n }\n }\n }\n return { dist, sigma, pred, stack };\n}\n\n/** 介数中心性的返回值——附带采样信息以便调用方如实披露。 */\nexport interface BetweennessResult {\n centrality: Map<string, number>;\n /** 实际做 BFS 的源节点数(< 节点总数时表示近似)。 */\n sampleSize: number;\n /** true = 采样近似,结果不代表全图精确值。 */\n approximate: boolean;\n}\n\n/**\n * 介数中心性——真实的无向 Brandes 算法。\n *\n * 在无向投影上做 BFS + SPLIT 依赖累加,归一化 `2/((n-1)(n-2))`。\n * 大图采样时用等距步长(无偏),并如实返回 `approximate`。\n *\n * ## sampleRate 必须有预算,不能默认全量(cycle-141 第四轮)\n *\n * 本函数自拆出以来 `sampleRate` 默认 1.0,而**唯一生产调用点**(`graphMetrics.ts`\n * 的 `computeBetweennessCentrality(adj)`)从不传它——8393 节点的真实仓库上\n * 8393 源 × 全图 BFS 让 `manage/metrics` 路由实测 **65.4s**(真机 3085 沙箱\n * 2026-11-05)。`distance.ts` 的 `computeDistanceStats` 有 `maxSample=200`\n * 上界而这里没有,同一文件族两种纪律。采样基础设施(等距步长 / approximate\n * 披露)早已齐备,缺的只是调用点把预算接上。\n *\n * 归一化与放大估计已按「采样时乘 n/sampleSize」实现,样本数变化不影响正确性——\n * 只影响精度(上界估计,如实标注 approximate)。maxSample 的量级与\n * `computeDistanceStats.maxSample` 一致:两条指标共享同一批 BFS 成本特征,\n * 「近似披露」在 UI 上口径统一。\n */\nexport function computeBetweennessCentrality(\n adj: AdjacencyList,\n sampleRate = 1.0,\n maxSample: number | null = 200,\n): BetweennessResult {\n const allNodes = [...adj.all.keys()];\n const n = allNodes.length;\n const centrality = new Map<string, number>();\n for (const id of allNodes) centrality.set(id, 0);\n\n if (n < 3) {\n // n < 3 时不存在「经过某节点的最短路径」,介数恒为 0。\n return { centrality, sampleSize: 0, approximate: false };\n }\n\n // 无偏确定性采样:等距步长取 index,避免旧实现的模 233280 偏差。\n // maxSample(显式 null = 不设上界,保持旧全量行为的逃生口)与 sampleRate\n // 取交集:先按比例、再封顶——两个旋钮各自语义不变。\n const byRate = Math.max(1, Math.floor(n * sampleRate));\n const sampleSize = maxSample === null ? Math.min(n, byRate) : Math.min(n, byRate, maxSample);\n const indices: number[] = [];\n if (sampleSize >= n) {\n for (let i = 0; i < n; i++) indices.push(i);\n } else {\n const stride = n / sampleSize;\n for (let i = 0; i < sampleSize; i++) indices.push(Math.floor(i * stride));\n }\n\n for (const startIdx of indices) {\n const start = allNodes[startIdx];\n if (!start) continue;\n const { sigma, pred, stack } = bfsShortestPaths(adj, start);\n\n // SPLIT:按 BFS 逆序累加依赖。\n const delta = new Map<string, number>();\n for (const id of allNodes) delta.set(id, 0);\n\n while (stack.length > 0) {\n const w = stack.pop()!;\n for (const v of pred.get(w) ?? []) {\n const sigmaW = sigma.get(w) ?? 0;\n if (sigmaW > 0) {\n delta.set(v, (delta.get(v) ?? 0) + ((sigma.get(v) ?? 0) / sigmaW) * (1 + (delta.get(w) ?? 0)));\n }\n }\n if (w !== start) {\n centrality.set(w, (centrality.get(w) ?? 0) + (delta.get(w) ?? 0));\n }\n }\n }\n\n // 归一化:BC_norm(v) = BC_raw(v) / ((n-1)(n-2));采样时乘以 n/sampleSize 放大估计。\n const denom = (n - 1) * (n - 2);\n const scale = sampleSize > 0 ? n / sampleSize : 1;\n const factor = scale / denom;\n for (const [id, val] of centrality) {\n centrality.set(id, val * factor);\n }\n\n return { centrality, sampleSize, approximate: sampleSize < n };\n}\n","/**\n * 图连通性——WCC / SCC / 关节点(cycle-141 拆分)。\n *\n * 从 graphMetrics.ts 拆出(复杂度治理:单文件 ≤~250 行)。\n * 三个算法都是线性时间,代码图谱规模下可接受。\n */\nimport type { AdjacencyList } from './types.ts';\n\n/**\n * 弱连通分量数(WCC)——在无向投影上做 BFS 计数。\n * 复杂度 O(V + E)。\n */\nexport function countWeaklyConnectedComponents(adj: AdjacencyList): number {\n const visited = new Set<string>();\n let count = 0;\n for (const start of adj.all.keys()) {\n if (visited.has(start)) continue;\n count++;\n const queue: string[] = [start];\n visited.add(start);\n while (queue.length > 0) {\n const v = queue.shift()!;\n for (const w of adj.all.get(v) ?? []) {\n if (!visited.has(w)) { visited.add(w); queue.push(w); }\n }\n }\n }\n return count;\n}\n\n/**\n * 强连通分量数(SCC)——Kosaraju 算法(两趟 DFS)。\n * 复杂度 O(V + E)。\n */\nexport function countStronglyConnectedComponents(adj: AdjacencyList): number {\n const nodes = [...adj.all.keys()];\n const visited = new Set<string>();\n const order: string[] = [];\n const dfs1 = (v: string): void => {\n visited.add(v);\n for (const w of adj.out.get(v) ?? []) {\n if (!visited.has(w)) dfs1(w);\n }\n order.push(v);\n };\n for (const v of nodes) if (!visited.has(v)) dfs1(v);\n\n const visited2 = new Set<string>();\n let count = 0;\n const dfs2 = (v: string): void => {\n visited2.add(v);\n for (const w of adj.in.get(v) ?? []) {\n if (!visited2.has(w)) dfs2(w);\n }\n };\n for (let i = order.length - 1; i >= 0; i--) {\n const v = order[i]!;\n if (!visited2.has(v)) { count++; dfs2(v); }\n }\n return count;\n}\n\n/**\n * 关节点(articulation point)计数——Tarjan 算法,在无向投影上。\n *\n * 关节点是「去掉后增加连通分量数」的节点——代码图里的「咽喉」。\n * 复杂度 O(V + E)。\n */\nexport function countArticulationPoints(adj: AdjacencyList): number {\n const nodes = [...adj.all.keys()];\n const visited = new Set<string>();\n const disc = new Map<string, number>();\n const low = new Map<string, number>();\n const parent = new Map<string, string | null>();\n let time = 0;\n let count = 0;\n\n const dfs = (v: string): void => {\n visited.add(v);\n disc.set(v, time);\n low.set(v, time);\n time++;\n let children = 0;\n for (const w of adj.all.get(v) ?? []) {\n if (!visited.has(w)) {\n children++;\n parent.set(w, v);\n dfs(w);\n low.set(v, Math.min(low.get(v)!, low.get(w)!));\n if (parent.get(v) === null && children > 1) count++;\n if (parent.get(v) !== null && (low.get(w) ?? 0) >= (disc.get(v) ?? 0)) count++;\n } else if (w !== parent.get(v)) {\n low.set(v, Math.min(low.get(v)!, disc.get(w)!));\n }\n }\n };\n\n for (const v of nodes) {\n if (!visited.has(v)) { parent.set(v, null); dfs(v); }\n }\n return count;\n}\n","/**\n * 图距离统计——直径 / 平均最短路径(cycle-141 拆分)。\n *\n * 从 graphMetrics.ts 拆出(复杂度治理)。全源 BFS,大图时采样源节点。\n * 断开的图返回 `null`(直径在非连通图上无定义)。\n */\nimport type { AdjacencyList } from './types.ts';\nimport { bfsShortestPaths } from './brandes.ts';\n\nexport interface DistanceStats {\n diameter: number | null;\n avgShortestPath: number | null;\n /** true = 大图采样源,直径是上界估计而非精确值。 */\n sampled: boolean;\n}\n\n/**\n * 图的直径与平均最短路径——BFS 全源(大图时采样源节点)。\n *\n * 断开的图返回 `null`(直径在非连通图上无定义)。大图时只从部分源做 BFS,\n * 直径是**上界估计**——如实标注,不假装精确。\n */\nexport function computeDistanceStats(\n adj: AdjacencyList,\n maxSample = 200,\n): DistanceStats {\n const nodes = [...adj.all.keys()];\n const n = nodes.length;\n if (n === 0) return { diameter: null, avgShortestPath: null, sampled: false };\n\n const sources = n <= maxSample\n ? nodes\n : nodes.filter((_, i) => i % Math.ceil(n / maxSample) === 0).slice(0, maxSample);\n\n let maxDist = 0;\n let totalDist = 0;\n let pairs = 0;\n let disconnected = false;\n\n for (const s of sources) {\n const dist = bfsShortestPaths(adj, s).dist;\n for (const t of nodes) {\n if (t === s) continue;\n if (!dist.has(t)) { disconnected = true; continue; }\n const d = dist.get(t)!;\n if (d > maxDist) maxDist = d;\n totalDist += d;\n pairs++;\n }\n }\n\n if (disconnected || pairs === 0) return { diameter: null, avgShortestPath: null, sampled: sources.length < n };\n return { diameter: maxDist, avgShortestPath: totalDist / pairs, sampled: sources.length < n };\n}\n","/**\n * 图社区结构——模块度 / k-core(cycle-141 拆分)。\n *\n * 从 graphMetrics.ts 拆出(复杂度治理)。\n * - 模块度:单层贪心 Louvain 近似,Q ∈ [-0.5, 1]。\n * - k-core:反复移除最小度节点,记录过程中的最大「最小度」。\n */\nimport type { AdjacencyList } from './types.ts';\n\n/**\n * k-core:最大的 k,使得存在一个子图其中每个节点度数 ≥ k。\n *\n * 反复移除最小度节点并更新度数,记录过程中的最大「最小度」。\n * 复杂度 O(V + E)(用优先队列可优化,此处用线性扫描,代码图谱规模可接受)。\n */\nexport function computeKCore(adj: AdjacencyList): number {\n const deg = new Map<string, number>();\n const alive = new Set<string>();\n for (const [id, neighbors] of adj.all) { deg.set(id, neighbors.size); alive.add(id); }\n\n let k = 0;\n while (alive.size > 0) {\n let minDeg = Infinity;\n for (const d of deg.values()) if (d < minDeg) minDeg = d;\n if (minDeg > k) k = minDeg;\n\n let removed = false;\n for (const id of alive) {\n if ((deg.get(id) ?? 0) <= k) {\n alive.delete(id);\n for (const nb of adj.all.get(id) ?? []) {\n if (alive.has(nb)) deg.set(nb, (deg.get(nb) ?? 0) - 1);\n }\n removed = true;\n }\n }\n if (!removed) break;\n }\n return k;\n}\n\n/**\n * 模块度(modularity)——单层贪心 Louvain 近似。\n *\n * 从每个节点自成社区开始,迭代地把节点移到能最大化模块度增益的邻居社区,\n * 最后计算最终划分的模块度 Q ∈ [-0.5, 1]。Q 越接近 1 社区结构越明显,\n * 无结构的图 Q ≈ 0。这是**近似**——不是完整 Louvain(多层 + 分辨率参数),\n * 但对代码图的模块度估计足够,且复杂度 O(iterations × V × avgDeg)。\n */\nexport function computeModularity(adj: AdjacencyList): number {\n const nodes = [...adj.all.keys()];\n const n = nodes.length;\n if (n === 0) return 0;\n\n // 无向边总数(每条边在 adj.all 里被两端各记一次)。\n let m = 0;\n for (const [, neighbors] of adj.all) m += neighbors.size;\n m /= 2;\n if (m === 0) return 0;\n\n const k = new Map<string, number>();\n for (const [id, neighbors] of adj.all) k.set(id, neighbors.size);\n\n // 初始划分:每个节点独立社区。\n const community = new Map<string, number>();\n nodes.forEach((id, i) => community.set(id, i));\n\n // 贪心合并:最多 10 轮,每轮把每个节点移到邻居中边数最多的社区。\n for (let round = 0; round < 10; round++) {\n let improved = false;\n for (const v of nodes) {\n const cv = community.get(v)!;\n const toComm = new Map<number, number>();\n for (const w of adj.all.get(v) ?? []) {\n const cw = community.get(w)!;\n toComm.set(cw, (toComm.get(cw) ?? 0) + 1);\n }\n let bestComm = -1;\n let bestCount = 0;\n for (const [c, count] of toComm) {\n if (c !== cv && count > bestCount) { bestComm = c; bestCount = count; }\n }\n if (bestComm >= 0) { community.set(v, bestComm); improved = true; }\n }\n if (!improved) break;\n }\n\n // Q = (1/2m) Σ_ij [A_ij - k_i·k_j/(2m)] δ(c_i, c_j)\n let q = 0;\n for (const v of nodes) {\n const cv = community.get(v)!;\n for (const w of adj.all.get(v) ?? []) {\n if (community.get(w) === cv) {\n q += 1 - ((k.get(v) ?? 0) * (k.get(w) ?? 0)) / (2 * m);\n }\n }\n }\n return q / (2 * m);\n}\n","/**\n * 图指标计算(cycle-141 原生代码图谱业务增强;cycle-141 第二轮复杂度治理拆分)。\n *\n * ## 模块化拆分(cycle-141 第二轮)\n *\n * 原文件因新增 8 项图论指标后复杂度 score 132 > upperBand 115,成为全仓 #1\n * 复杂度热点。按「一域一文件、单文件 ≤~250 行」的纪律拆分为:\n *\n * | 模块 | 内容 |\n * |---|---|\n * | `types.ts` | MetricNode / MetricEdge / AdjacencyList(跨模块共享类型) |\n * | `brandes.ts` | 介数中心性(真实无向 Brandes + SPLIT 依赖累加) |\n * | `connectivity.ts` | WCC / SCC(Kosaraju) / 关节点(Tarjan) |\n * | `distance.ts` | 直径 / 平均最短路径(全源 BFS,大图采样) |\n * | `community.ts` | 模块度(单层贪心 Louvain)/ k-core |\n * | `graphMetrics.ts`(本文件) | 密度 / 聚类系数 / PageRank / 度数分布 + 编排 |\n *\n * 本文件**重新导出**全部子模块的公开 API,保持调用方(路由、测试、文档)零改动。\n *\n * ## 数学模型\n *\n * - **密度** (density):实际边数 / 可能边数(**有向**口径,代码图边是 call/import)。\n * - **聚类系数** (clustering coefficient):节点邻居间实际边数 / 可能边数。\n * - **介数中心性**:真实无向 Brandes(见 brandes.ts)。\n * - **PageRank**:随机游走排名,悬挂节点质量平均重分配(质量守恒)。\n * - **度数分布**:幂律检验(代码图谱通常呈长尾分布)。\n * - **连通性 / 关节点 / 直径 / 模块度 / 圈复杂度 / k-core**:见各子模块。\n *\n * ## 数据结构与算法\n *\n * - **邻接表** (AdjacencyList):代码图谱的稀疏性决定了邻接表优于邻接矩阵。\n * - **BFS / Kosaraju / Tarjan**:线性时间图算法。\n * - **贪心模块度优化**:单层 Louvain 近似。\n *\n * 注:**不实现**加权最短路径(Dijkstra 变体)——代码图当前无边权,\n * 预留声明不提供任何功能,故不写进本文件(避免空头承诺)。\n *\n * 库边界自包含约束:所有 import 必须留在 `src/xuanji` 内。\n *\n * @module xuanji/analysis/graphMetrics\n */\n\nimport type { AdjacencyList, MetricNode, MetricEdge } from './types.ts';\nimport { computeBetweennessCentrality } from './brandes.ts';\nimport {\n countWeaklyConnectedComponents,\n countStronglyConnectedComponents,\n countArticulationPoints,\n} from './connectivity.ts';\nimport { computeDistanceStats } from './distance.ts';\nimport { computeModularity, computeKCore } from './community.ts';\n\n/**\n * BFS 采样源的统一预算(cycle-141 第四轮)。\n *\n * 真机量化(8393 节点仓库):介数全量(8393 源 × 全图 BFS)实测 65.4s,\n * `manage/metrics` 路由从「即时」退化为「一分钟白屏」。200 源与\n * `computeDistanceStats.maxSample` 同量级——两条近似指标共享同一口径,\n * 采样是上界估计且响应里如实披露(`betweennessApproximate` / `sampled`)。\n *\n * 常量放在 xuanji 库内而不是宿主 `shared/constants.ts`:库边界契约\n * (libraryBoundary)要求 xuanji 零宿主依赖,预算是算法自身的成本特征,\n * 属于库的语义而非宿主配置。\n */\nconst BFS_SAMPLE_SOURCES = 200;\n\n// ── 重新导出:保持调用方零改动 ──────────────────────────────────────────\nexport type { MetricNode, MetricEdge, AdjacencyList } from './types.ts';\nexport {\n computeBetweennessCentrality,\n bfsShortestPaths,\n type BetweennessResult,\n type BfsResult,\n} from './brandes.ts';\nexport {\n countWeaklyConnectedComponents,\n countStronglyConnectedComponents,\n countArticulationPoints,\n} from './connectivity.ts';\nexport { computeDistanceStats, type DistanceStats } from './distance.ts';\nexport { computeModularity, computeKCore } from './community.ts';\n\n/** 构建邻接表——一次遍历即可。 */\nexport function buildAdjacencyList(nodes: MetricNode[], edges: MetricEdge[]): AdjacencyList {\n const out = new Map<string, Set<string>>();\n const inn = new Map<string, Set<string>>();\n const all = new Map<string, Set<string>>();\n for (const n of nodes) {\n out.set(n.id, new Set());\n inn.set(n.id, new Set());\n all.set(n.id, new Set());\n }\n for (const e of edges) {\n if (!out.has(e.source) || !inn.has(e.target)) continue;\n out.get(e.source)!.add(e.target);\n inn.get(e.target)!.add(e.source);\n all.get(e.source)!.add(e.target);\n all.get(e.target)!.add(e.source);\n }\n return { out, in: inn, all };\n}\n\n/**\n * 图密度:实际边数 / 可能边数。完全图的密度为 1,空图为 0。\n *\n * ## 有向语义(cycle-141 P0 修复)\n *\n * 代码图谱的边是**有向**的(call / import / extend …),但旧实现用无向可能边数\n * `n(n-1)/2` 作分母——同一张图按有向算是无向的两倍,且没有任何声明说明用的是\n * 哪种口径。两种口径相差 2 倍,用户无法判断「密度 0.3」意味着什么。\n *\n * 修法:改用**有向**可能边数 `n(n-1)`。这是代码图的正确口径;若将来需要无向\n * 密度,应显式新增一个函数而不是悄悄改分母。\n */\nexport function computeDensity(nodeCount: number, edgeCount: number): number {\n if (nodeCount < 2) return 0;\n const possible = nodeCount * (nodeCount - 1);\n return Math.min(1, edgeCount / possible);\n}\n\n/**\n * 局部聚类系数:节点邻居间实际边数 / 可能边数。\n * 邻居数 < 2 时返回 0(无法形成三角形)。\n */\nexport function computeClusteringCoefficient(adj: AdjacencyList, nodeId: string): number {\n const neighbors = adj.all.get(nodeId);\n if (!neighbors || neighbors.size < 2) return 0;\n const neighborList = [...neighbors];\n let actualEdges = 0;\n for (let i = 0; i < neighborList.length; i++) {\n for (let j = i + 1; j < neighborList.length; j++) {\n if (adj.all.get(neighborList[i]!)!.has(neighborList[j]!)) actualEdges++;\n }\n }\n const possible = neighborList.length * (neighborList.length - 1) / 2;\n return possible > 0 ? actualEdges / possible : 0;\n}\n\n/** 全局平均聚类系数。 */\nexport function computeAverageClustering(adj: AdjacencyList): number {\n if (adj.all.size === 0) return 0;\n let sum = 0;\n for (const id of adj.all.keys()) {\n sum += computeClusteringCoefficient(adj, id);\n }\n return sum / adj.all.size;\n}\n\n/**\n * PageRank:基于随机游走的节点重要性排名。\n * 迭代收敛,阻尼系数 0.85(标准值)。复杂度 O(iterations × E)。\n *\n * ## 悬挂节点质量守恒(cycle-141 P0 修复)\n *\n * 旧实现对出度为 0 的节点直接 `continue`,其 `damping × rank` 质量**每轮丢失**,\n * 于是总质量逐轮衰减、排名整体失真——收敛判据以 n 缩放,恰好掩盖了不归一。\n *\n * 标准做法:悬挂节点不流出质量,而是把 `damping × rank` **平均重分配给所有节点**。\n * 这样每轮总质量恒为 1((1-damping) 的 teleport + damping×1 的重分配),\n * 排名与「图是否连通」解耦。修复后 `pageRankMass ≈ 1` 是可断言的守恒见证。\n */\nexport function computePageRank(\n adj: AdjacencyList,\n damping = 0.85,\n iterations = 20,\n epsilon = 1e-6,\n): Map<string, number> {\n const nodeIds = [...adj.all.keys()];\n const n = nodeIds.length;\n if (n === 0) return new Map();\n\n const rank = new Map<string, number>();\n for (const id of nodeIds) rank.set(id, 1 / n);\n\n for (let iter = 0; iter < iterations; iter++) {\n const next = new Map<string, number>();\n let diff = 0;\n for (const id of nodeIds) next.set(id, (1 - damping) / n);\n\n // 收集悬挂节点(出度 0)的质量,稍后平均重分配。\n let danglingMass = 0;\n for (const id of nodeIds) {\n const outNeighbors = adj.out.get(id);\n if (!outNeighbors || outNeighbors.size === 0) {\n // 悬挂节点:质量不流出,收集后平均重分配(cycle-141 质量守恒修复)。\n danglingMass += damping * (rank.get(id) ?? 0);\n continue;\n }\n const share = damping * (rank.get(id)! ?? 0) / outNeighbors.size;\n for (const neighbor of outNeighbors) {\n next.set(neighbor, (next.get(neighbor) ?? 0) + share);\n }\n }\n\n // 悬挂质量平均重分配给所有节点——每轮总质量恒为 1。\n if (danglingMass > 0) {\n const danglingShare = danglingMass / n;\n for (const id of nodeIds) next.set(id, (next.get(id) ?? 0) + danglingShare);\n }\n\n for (const id of nodeIds) {\n diff += Math.abs((next.get(id) ?? 0) - (rank.get(id) ?? 0));\n rank.set(id, next.get(id) ?? 0);\n }\n if (diff < epsilon * n) break;\n }\n\n return rank;\n}\n\n/** 度数分布:返回每个节点的出度和入度。 */\nexport function computeDegreeDistribution(adj: AdjacencyList): {\n outDegrees: Map<string, number>;\n inDegrees: Map<string, number>;\n} {\n const outDegrees = new Map<string, number>();\n const inDegrees = new Map<string, number>();\n for (const [id, neighbors] of adj.out) outDegrees.set(id, neighbors.size);\n for (const [id, neighbors] of adj.in) inDegrees.set(id, neighbors.size);\n return { outDegrees, inDegrees };\n}\n\n/**\n * 度数分布的幂律检验(简化版)。\n * 返回 { alpha, isPowerLaw, maxDegree, minDegree }。\n * alpha 越接近 1 越接近幂律分布(代码图谱通常 alpha ≈ 2-3)。\n */\nexport function analyzeDegreeDistribution(outDegrees: Map<string, number>): {\n alpha: number;\n isPowerLaw: boolean;\n maxDegree: number;\n minDegree: number;\n meanDegree: number;\n} {\n const degrees = [...outDegrees.values()].filter((d) => d > 0);\n if (degrees.length < 10) {\n return { alpha: 0, isPowerLaw: false, maxDegree: 0, minDegree: 0, meanDegree: 0 };\n }\n // 用 reduce 而非 Math.max(...degrees):后者在超大数组上有栈风险。\n const maxDeg = degrees.reduce((a, b) => (b > a ? b : a), 0);\n const minDeg = degrees.reduce((a, b) => (b < a ? b : a), degrees[0]!);\n const meanDeg = degrees.reduce((a, b) => a + b, 0) / degrees.length;\n\n // 简化幂律拟合:对 log(degree) vs log(rank) 做线性回归\n const sorted = [...degrees].sort((a, b) => b - a);\n const logDeg: number[] = [];\n const logRank: number[] = [];\n for (let i = 0; i < sorted.length; i++) {\n if (sorted[i]! > 0) {\n logDeg.push(Math.log(sorted[i]!));\n logRank.push(Math.log(i + 1));\n }\n }\n const n = logDeg.length;\n if (n < 5) return { alpha: 0, isPowerLaw: false, maxDegree: maxDeg, minDegree: minDeg, meanDegree: meanDeg };\n\n const meanX = logRank.reduce((a, b) => a + b, 0) / n;\n const meanY = logDeg.reduce((a, b) => a + b, 0) / n;\n let num = 0, den = 0;\n for (let i = 0; i < n; i++) {\n num += (logRank[i]! - meanX) * (logDeg[i]! - meanY);\n den += (logRank[i]! - meanX) ** 2;\n }\n const slope = den > 0 ? num / den : 0;\n const alpha = -slope;\n return { alpha, isPowerLaw: alpha > 1 && alpha < 4, maxDegree: maxDeg, minDegree: minDeg, meanDegree: meanDeg };\n}\n\n/** 图指标汇总。 */\nexport interface GraphMetrics {\n nodeCount: number;\n edgeCount: number;\n density: number;\n avgClustering: number;\n betweenness: Map<string, number>;\n pageRank: Map<string, number>;\n degreeDistribution: {\n alpha: number;\n isPowerLaw: boolean;\n maxDegree: number;\n minDegree: number;\n meanDegree: number;\n };\n topBetweenness: Array<{ id: string; value: number }>;\n topPageRank: Array<{ id: string; value: number }>;\n /** cycle-141 附加:介数采样的源节点数(< 节点总数 = 近似)。 */\n betweennessSampleSize: number;\n betweennessApproximate: boolean;\n /** PageRank 总质量——应 ≈ 1,是悬挂质量守恒的见证。 */\n pageRankMass: number;\n /** 弱/强连通分量数。 */\n components: { weaklyConnected: number; stronglyConnected: number };\n /** 关节点(咽喉)数。 */\n articulationPoints: number;\n /** 直径(断开图 null)。 */\n diameter: number | null;\n /** 平均最短路径(断开图 null)。 */\n avgShortestPath: number | null;\n /** 模块度 Q ∈ [-0.5, 1](单层贪心 Louvain 近似)。 */\n modularity: number;\n /** 圈复杂度 = E − N + P(P = 弱连通分量数)。 */\n cyclomaticNumber: number;\n /** k-core 的最大 k。 */\n kCore: number;\n}\n\n/** 计算完整的图指标。 */\nexport function computeGraphMetrics(nodes: MetricNode[], edges: MetricEdge[]): GraphMetrics {\n const adj = buildAdjacencyList(nodes, edges);\n const nodeCount = nodes.length;\n const edgeCount = edges.length;\n\n const density = computeDensity(nodeCount, edgeCount);\n const avgClustering = computeAverageClustering(adj);\n // 采样预算(cycle-141 第四轮):maxSample: null 表示「调用方明确不封顶」,\n // 这里必须封顶——真机 8393 节点全量 Brandes 实测 65.4s。近似结果经由\n // betweennessSampleSize/betweennessApproximate 如实披露,预算语义见 BFS_SAMPLE_SOURCES。\n const betweennessResult = computeBetweennessCentrality(adj, 1.0, BFS_SAMPLE_SOURCES);\n const betweenness = betweennessResult.centrality;\n const pageRank = computePageRank(adj);\n const { outDegrees } = computeDegreeDistribution(adj);\n const degreeStats = analyzeDegreeDistribution(outDegrees);\n\n const topBetweenness = [...betweenness.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([id, value]) => ({ id, value }));\n\n const topPageRank = [...pageRank.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([id, value]) => ({ id, value }));\n\n // cycle-141 附加图论指标(全部纯函数、库内自包含)。\n const components = {\n weaklyConnected: countWeaklyConnectedComponents(adj),\n stronglyConnected: countStronglyConnectedComponents(adj),\n };\n const articulationPoints = countArticulationPoints(adj);\n const distStats = computeDistanceStats(adj);\n const modularity = computeModularity(adj);\n const cyclomaticNumber = edgeCount - nodeCount + components.weaklyConnected;\n const kCore = computeKCore(adj);\n\n // PageRank 总质量——悬挂质量守恒的见证(应 ≈ 1)。\n let pageRankMass = 0;\n for (const v of pageRank.values()) pageRankMass += v;\n\n return {\n nodeCount,\n edgeCount,\n density,\n avgClustering,\n betweenness,\n pageRank,\n degreeDistribution: degreeStats,\n topBetweenness,\n topPageRank,\n betweennessSampleSize: betweennessResult.sampleSize,\n betweennessApproximate: betweennessResult.approximate,\n pageRankMass,\n components,\n articulationPoints,\n diameter: distStats.diameter,\n avgShortestPath: distStats.avgShortestPath,\n modularity,\n cyclomaticNumber,\n kCore,\n };\n}\n"],"mappings":"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"}