@graphty/graphty-element 2.3.0 → 2.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.
- package/dist/ai.js +115 -222
- package/dist/catalog.js +56 -55
- package/dist/chunks/{AiManager-Bd_r1Hei.js → AiManager-BBmGJbH4.js} +793 -654
- package/dist/chunks/{DataSource-bt0DhBjG.js → DataSource-OeN3NeyD.js} +1 -1
- package/dist/chunks/{GraphSession-iNyKm7Ds.js → GraphSession-Bef1AYw9.js} +2136 -2104
- package/dist/chunks/{GraphStyle-D0PXnZKu.js → GraphStyle-Cwr55SAE.js} +5 -2
- package/dist/chunks/{VoiceInputAdapter-DszYl6Ha.js → VoiceInputAdapter-Dr9Gcmds.js} +1 -1
- package/dist/chunks/{XRPivotCameraController-XtxbbZ-D.js → XRPivotCameraController-BbfgZWpS.js} +1 -1
- package/dist/chunks/algorithms-CpX56sUB.js +3482 -0
- package/dist/chunks/{capability-check-BqIEXcun.js → capability-check-Blhb2aBB.js} +1 -1
- package/dist/chunks/{detect-DM29BEaB.js → detect-Cqwshr9a.js} +1 -1
- package/dist/chunks/{format-detection-BEdmtvsy.js → format-detection-BXGO1lSn.js} +1 -1
- package/dist/chunks/{index-CD0_RJv-.js → index-C0mIoumR.js} +2258 -2129
- package/dist/chunks/optionsFromZod-17lkrAJs.js +2565 -0
- package/dist/chunks/paletteRegistry-x7WOEKZY.js +1153 -0
- package/dist/chunks/scales-BRwl51k8.js +3047 -0
- package/dist/custom-elements.json +1 -1
- package/dist/extend.js +42 -42
- package/dist/graphty-catalog.json +1 -1
- package/dist/graphty.bundle.js +25814 -25456
- package/dist/graphty.js +29 -29
- package/dist/schema.js +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +28 -29
- package/dist/src/Graph.d.ts +33 -3
- package/dist/src/camera/builtins.d.ts +14 -1
- package/dist/src/camera/types.d.ts +7 -0
- package/dist/src/cameras/CameraManager.d.ts +13 -0
- package/dist/src/cameras/OrbitCameraController.d.ts +9 -0
- package/dist/src/catalog/types.d.ts +10 -0
- package/dist/src/config/GraphStyle.d.ts +5 -1
- package/dist/src/config/StyleTemplate.d.ts +2 -2
- package/dist/src/graphty-element.d.ts +11 -6
- package/dist/src/managers/StylePainter.d.ts +9 -0
- package/dist/src/managers/UpdateManager.d.ts +5 -0
- package/dist/src/session/results/index.d.ts +1 -1
- package/dist/src/session/results/statistics.d.ts +8 -1
- package/dist/src/session/results/types.d.ts +33 -0
- package/dist/src/session/selection/targets.d.ts +4 -1
- package/dist/src/session/styles/predicate.d.ts +26 -1
- package/dist/src/session/styles/repaint.d.ts +11 -0
- package/dist/src/session/styles/selector.d.ts +13 -1
- package/dist/src/session/styles/sources.d.ts +9 -0
- package/package.json +1 -1
- package/dist/chunks/Algorithm-RQ629NLb.js +0 -494
- package/dist/chunks/cameras-ii4vngYY.js +0 -435
- package/dist/chunks/paletteRegistry-DnWHQsAD.js +0 -3166
- package/dist/chunks/scales-DyuwlJKI.js +0 -6087
|
@@ -0,0 +1,3482 @@
|
|
|
1
|
+
import { c as it } from "./common-DWNKjpH_.js";
|
|
2
|
+
import { N as st, E as Je } from "./NodeStyle-DKj7HjMJ.js";
|
|
3
|
+
import { F as ot, k as rt, a as z, h as N, P as Qe, f as M, A as S, Y as ct, G as lt, H as dt, n as $, L as qe, s as We, e as _, m as oe, i as je, o as Ue } from "./optionsFromZod-17lkrAJs.js";
|
|
4
|
+
import { z as c } from "zod/v4";
|
|
5
|
+
import { bellmanFord as ht, betweennessCentrality as ut, breadthFirstSearch as pt, bipartitePartition as mt, maximumBipartiteMatching as gt, closenessCentrality as ft, depthFirstSearch as yt, eigenvectorCentrality as bt, ConvergenceError as wt, floydWarshall as vt, girvanNewman as Nt, hits as kt, katzCentrality as St, labelPropagation as Ot, leiden as xt, louvain as It, Graph as _e, fordFulkerson as Mt, kargerMinCut as Ct, stoerWagner as zt, minSTCut as Tt, pageRank as Et, primMST as Dt, stronglyConnectedComponents as Pt } from "@graphty/algorithms";
|
|
6
|
+
import { G as et } from "./GraphtyError-BwcnblTH.js";
|
|
7
|
+
import { INVALID_INDEX as R } from "@graphty/graph-format";
|
|
8
|
+
var Oa = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
|
|
9
|
+
function xa(s) {
|
|
10
|
+
return s && s.__esModule && Object.prototype.hasOwnProperty.call(s, "default") ? s.default : s;
|
|
11
|
+
}
|
|
12
|
+
const Rt = /^#?(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;
|
|
13
|
+
function Le(s) {
|
|
14
|
+
return Number.isFinite(s) ? Math.max(0, Math.min(255, Math.round(s))) : 0;
|
|
15
|
+
}
|
|
16
|
+
function ne(s) {
|
|
17
|
+
return s.toString(16).padStart(2, "0");
|
|
18
|
+
}
|
|
19
|
+
function Ft(s) {
|
|
20
|
+
if (!Rt.test(s))
|
|
21
|
+
return null;
|
|
22
|
+
const e = s.startsWith("#") ? s.slice(1) : s, t = e.length <= 4, n = (i) => {
|
|
23
|
+
const o = t ? e[i].repeat(2) : e.slice(i * 2, i * 2 + 2);
|
|
24
|
+
return parseInt(o, 16);
|
|
25
|
+
}, a = e.length === 4 || e.length === 8;
|
|
26
|
+
return {
|
|
27
|
+
r: n(0),
|
|
28
|
+
g: n(1),
|
|
29
|
+
b: n(2),
|
|
30
|
+
a: a ? n(3) / 255 : 1
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function Ia(s) {
|
|
34
|
+
return typeof s != "object" || s === null || !("hex" in s) ? null : s;
|
|
35
|
+
}
|
|
36
|
+
function $t(s) {
|
|
37
|
+
if (s == null)
|
|
38
|
+
return null;
|
|
39
|
+
if (typeof s == "object")
|
|
40
|
+
return Ae(s);
|
|
41
|
+
const e = Ft(s);
|
|
42
|
+
if (e !== null)
|
|
43
|
+
return Ae(e);
|
|
44
|
+
try {
|
|
45
|
+
const t = it(s);
|
|
46
|
+
return t === void 0 ? null : $t(t);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function Ae(s) {
|
|
52
|
+
const e = Le(s.r), t = Le(s.g), n = Le(s.b), a = Number.isFinite(s.a) ? Math.max(0, Math.min(1, s.a)) : 1, i = `#${ne(e)}${ne(t)}${ne(n)}`;
|
|
53
|
+
return { r: e, g: t, b: n, a, hex: a === 1 ? i : `${i}${ne(Math.round(a * 255))}` };
|
|
54
|
+
}
|
|
55
|
+
const Gt = st.options, Ht = Je.shape.line.unwrap().shape.type.unwrap().options, Be = Je.shape.arrowHead.unwrap().shape.type.unwrap().unwrap().options, Wt = "An outline has a colour and no width. Babylon draws it with a highlight LAYER, and the stroke's blur size belongs to that layer rather than to a mesh, so every outline on screen is drawn at one width whatever a style asks for -- which is why the node style declares no outline width to ask with. The outline's COLOUR is per style; a width per style would need one highlight layer per width, at a full-screen pass each.", ie = 'A caption hangs from the cap at that end of the edge, so an end drawn with no arrow carries none: a tail caption needs `edge.arrowTail` set to something other than "none". The words are what switch a caption on, so a layer that writes only the caption\'s appearance and no words draws nothing, exactly as `node.labelStyle` draws nothing without `node.label`.', Ye = "A tooltip is drawn on hover and only on hover, so a graph at rest shows none: a layer that writes one changes nothing until a reader points at the node. The words are what switch it on, so a layer that writes only the tooltip's appearance and no words draws nothing, exactly as `node.labelStyle` draws nothing without `node.label`. Only a NODE has a tooltip; `edge.tooltip` was withdrawn in 2.0 because an edge cannot be hovered.", te = {
|
|
56
|
+
"node.color": {
|
|
57
|
+
channel: "node.color",
|
|
58
|
+
target: "node",
|
|
59
|
+
plainName: "Node Colour",
|
|
60
|
+
accepts: "color",
|
|
61
|
+
stylePath: "texture.color",
|
|
62
|
+
renderable: !0
|
|
63
|
+
},
|
|
64
|
+
"node.size": {
|
|
65
|
+
channel: "node.size",
|
|
66
|
+
target: "node",
|
|
67
|
+
plainName: "Node Size",
|
|
68
|
+
accepts: "number",
|
|
69
|
+
min: 0,
|
|
70
|
+
stylePath: "shape.size",
|
|
71
|
+
renderable: !0
|
|
72
|
+
},
|
|
73
|
+
"node.shape": {
|
|
74
|
+
channel: "node.shape",
|
|
75
|
+
target: "node",
|
|
76
|
+
plainName: "Node Shape",
|
|
77
|
+
accepts: "enum",
|
|
78
|
+
values: Gt,
|
|
79
|
+
stylePath: "shape.type",
|
|
80
|
+
renderable: !0
|
|
81
|
+
},
|
|
82
|
+
"node.label": {
|
|
83
|
+
channel: "node.label",
|
|
84
|
+
target: "node",
|
|
85
|
+
plainName: "Node Label",
|
|
86
|
+
accepts: "text",
|
|
87
|
+
stylePath: "label.text",
|
|
88
|
+
renderable: !0
|
|
89
|
+
},
|
|
90
|
+
"node.labelStyle": {
|
|
91
|
+
channel: "node.labelStyle",
|
|
92
|
+
target: "node",
|
|
93
|
+
plainName: "Node Label Style",
|
|
94
|
+
accepts: "labelStyle",
|
|
95
|
+
stylePath: "label",
|
|
96
|
+
renderable: !0
|
|
97
|
+
},
|
|
98
|
+
"node.tooltip": {
|
|
99
|
+
channel: "node.tooltip",
|
|
100
|
+
target: "node",
|
|
101
|
+
plainName: "Node Tooltip",
|
|
102
|
+
accepts: "text",
|
|
103
|
+
stylePath: "tooltip.text",
|
|
104
|
+
renderable: !0,
|
|
105
|
+
caveat: Ye
|
|
106
|
+
},
|
|
107
|
+
"node.tooltipStyle": {
|
|
108
|
+
channel: "node.tooltipStyle",
|
|
109
|
+
target: "node",
|
|
110
|
+
plainName: "Node Tooltip Style",
|
|
111
|
+
accepts: "labelStyle",
|
|
112
|
+
stylePath: "tooltip",
|
|
113
|
+
renderable: !0,
|
|
114
|
+
caveat: Ye
|
|
115
|
+
},
|
|
116
|
+
"node.opacity": {
|
|
117
|
+
channel: "node.opacity",
|
|
118
|
+
target: "node",
|
|
119
|
+
plainName: "Node Opacity",
|
|
120
|
+
accepts: "number",
|
|
121
|
+
min: 0,
|
|
122
|
+
max: 1,
|
|
123
|
+
stylePath: "texture.color.opacity",
|
|
124
|
+
renderable: !0
|
|
125
|
+
},
|
|
126
|
+
"node.outline": {
|
|
127
|
+
channel: "node.outline",
|
|
128
|
+
target: "node",
|
|
129
|
+
plainName: "Node Outline",
|
|
130
|
+
accepts: "color",
|
|
131
|
+
stylePath: "effect.outline.color",
|
|
132
|
+
renderable: !0,
|
|
133
|
+
caveat: Wt
|
|
134
|
+
},
|
|
135
|
+
"node.glow": {
|
|
136
|
+
channel: "node.glow",
|
|
137
|
+
target: "node",
|
|
138
|
+
plainName: "Node Glow",
|
|
139
|
+
accepts: "color",
|
|
140
|
+
stylePath: "effect.glow.color",
|
|
141
|
+
renderable: !0
|
|
142
|
+
},
|
|
143
|
+
"node.glowStrength": {
|
|
144
|
+
channel: "node.glowStrength",
|
|
145
|
+
target: "node",
|
|
146
|
+
plainName: "Node Glow Strength",
|
|
147
|
+
accepts: "number",
|
|
148
|
+
min: 0,
|
|
149
|
+
stylePath: "effect.glow.strength",
|
|
150
|
+
renderable: !0
|
|
151
|
+
},
|
|
152
|
+
"node.wireframe": {
|
|
153
|
+
channel: "node.wireframe",
|
|
154
|
+
target: "node",
|
|
155
|
+
plainName: "Node Wireframe",
|
|
156
|
+
accepts: "boolean",
|
|
157
|
+
stylePath: "effect.wireframe",
|
|
158
|
+
renderable: !0
|
|
159
|
+
},
|
|
160
|
+
"node.flat": {
|
|
161
|
+
channel: "node.flat",
|
|
162
|
+
target: "node",
|
|
163
|
+
plainName: "Node Flat Shading",
|
|
164
|
+
accepts: "boolean",
|
|
165
|
+
stylePath: "effect.flatShaded",
|
|
166
|
+
renderable: !0
|
|
167
|
+
},
|
|
168
|
+
"node.marker": {
|
|
169
|
+
channel: "node.marker",
|
|
170
|
+
target: "node",
|
|
171
|
+
plainName: "Node Marker",
|
|
172
|
+
accepts: "nothing",
|
|
173
|
+
stylePath: "texture.icon",
|
|
174
|
+
renderable: !1,
|
|
175
|
+
caveat: "The element draws no marker. texture.icon is in the node schema and no renderer reads it, and the badge vocabulary belongs to a label rather than to a node."
|
|
176
|
+
},
|
|
177
|
+
"edge.color": {
|
|
178
|
+
channel: "edge.color",
|
|
179
|
+
target: "edge",
|
|
180
|
+
plainName: "Edge Colour",
|
|
181
|
+
accepts: "color",
|
|
182
|
+
stylePath: "line.color",
|
|
183
|
+
renderable: !0
|
|
184
|
+
},
|
|
185
|
+
"edge.width": {
|
|
186
|
+
channel: "edge.width",
|
|
187
|
+
target: "edge",
|
|
188
|
+
plainName: "Edge Width",
|
|
189
|
+
accepts: "number",
|
|
190
|
+
min: 0,
|
|
191
|
+
stylePath: "line.width",
|
|
192
|
+
renderable: !0
|
|
193
|
+
},
|
|
194
|
+
"edge.opacity": {
|
|
195
|
+
channel: "edge.opacity",
|
|
196
|
+
target: "edge",
|
|
197
|
+
plainName: "Edge Opacity",
|
|
198
|
+
accepts: "number",
|
|
199
|
+
min: 0,
|
|
200
|
+
max: 1,
|
|
201
|
+
stylePath: "line.opacity",
|
|
202
|
+
renderable: !0
|
|
203
|
+
},
|
|
204
|
+
"edge.style": {
|
|
205
|
+
channel: "edge.style",
|
|
206
|
+
target: "edge",
|
|
207
|
+
plainName: "Edge Line Pattern",
|
|
208
|
+
accepts: "enum",
|
|
209
|
+
values: Ht,
|
|
210
|
+
stylePath: "line.type",
|
|
211
|
+
renderable: !0
|
|
212
|
+
},
|
|
213
|
+
"edge.patternCount": {
|
|
214
|
+
channel: "edge.patternCount",
|
|
215
|
+
target: "edge",
|
|
216
|
+
plainName: "Edge Pattern Count",
|
|
217
|
+
accepts: "number",
|
|
218
|
+
min: 2,
|
|
219
|
+
stylePath: "line.patternCount",
|
|
220
|
+
renderable: !0,
|
|
221
|
+
caveat: "A cap on how many dots or dashes a patterned edge draws, and it applies to a patterned line only: a solid line has no elements to count, and zigzag and sinewave always tile the whole edge, so they ignore it. Left unset, the spacing rule decides, so a long edge or a thin one draws more of them."
|
|
222
|
+
},
|
|
223
|
+
"edge.curvature": {
|
|
224
|
+
channel: "edge.curvature",
|
|
225
|
+
target: "edge",
|
|
226
|
+
plainName: "Edge Curve",
|
|
227
|
+
accepts: "boolean",
|
|
228
|
+
stylePath: "line.bezier",
|
|
229
|
+
renderable: !0,
|
|
230
|
+
caveat: "A switch, not an amount: the renderer offsets its control points by a fixed fraction of the edge's length, so how far an edge bows cannot be set."
|
|
231
|
+
},
|
|
232
|
+
"edge.arrowHead": {
|
|
233
|
+
channel: "edge.arrowHead",
|
|
234
|
+
target: "edge",
|
|
235
|
+
plainName: "Arrow Head",
|
|
236
|
+
accepts: "enum",
|
|
237
|
+
values: Be,
|
|
238
|
+
stylePath: "arrowHead.type",
|
|
239
|
+
renderable: !0
|
|
240
|
+
},
|
|
241
|
+
"edge.arrowHeadSize": {
|
|
242
|
+
channel: "edge.arrowHeadSize",
|
|
243
|
+
target: "edge",
|
|
244
|
+
plainName: "Arrow Head Size",
|
|
245
|
+
accepts: "number",
|
|
246
|
+
min: 0,
|
|
247
|
+
stylePath: "arrowHead.size",
|
|
248
|
+
renderable: !0
|
|
249
|
+
},
|
|
250
|
+
"edge.arrowHeadColor": {
|
|
251
|
+
channel: "edge.arrowHeadColor",
|
|
252
|
+
target: "edge",
|
|
253
|
+
plainName: "Arrow Head Colour",
|
|
254
|
+
accepts: "color",
|
|
255
|
+
stylePath: "arrowHead.color",
|
|
256
|
+
renderable: !0
|
|
257
|
+
},
|
|
258
|
+
"edge.arrowHeadOpacity": {
|
|
259
|
+
channel: "edge.arrowHeadOpacity",
|
|
260
|
+
target: "edge",
|
|
261
|
+
plainName: "Arrow Head Opacity",
|
|
262
|
+
accepts: "number",
|
|
263
|
+
min: 0,
|
|
264
|
+
max: 1,
|
|
265
|
+
stylePath: "arrowHead.opacity",
|
|
266
|
+
renderable: !0
|
|
267
|
+
},
|
|
268
|
+
"edge.arrowHeadText": {
|
|
269
|
+
channel: "edge.arrowHeadText",
|
|
270
|
+
target: "edge",
|
|
271
|
+
plainName: "Arrow Head Caption",
|
|
272
|
+
accepts: "text",
|
|
273
|
+
stylePath: "arrowHead.text.text",
|
|
274
|
+
renderable: !0,
|
|
275
|
+
caveat: ie
|
|
276
|
+
},
|
|
277
|
+
"edge.arrowHeadTextStyle": {
|
|
278
|
+
channel: "edge.arrowHeadTextStyle",
|
|
279
|
+
target: "edge",
|
|
280
|
+
plainName: "Arrow Head Caption Style",
|
|
281
|
+
accepts: "labelStyle",
|
|
282
|
+
stylePath: "arrowHead.text",
|
|
283
|
+
renderable: !0,
|
|
284
|
+
caveat: ie
|
|
285
|
+
},
|
|
286
|
+
"edge.arrowTail": {
|
|
287
|
+
channel: "edge.arrowTail",
|
|
288
|
+
target: "edge",
|
|
289
|
+
plainName: "Arrow Tail",
|
|
290
|
+
accepts: "enum",
|
|
291
|
+
values: Be,
|
|
292
|
+
stylePath: "arrowTail.type",
|
|
293
|
+
renderable: !0
|
|
294
|
+
},
|
|
295
|
+
"edge.arrowTailSize": {
|
|
296
|
+
channel: "edge.arrowTailSize",
|
|
297
|
+
target: "edge",
|
|
298
|
+
plainName: "Arrow Tail Size",
|
|
299
|
+
accepts: "number",
|
|
300
|
+
min: 0,
|
|
301
|
+
stylePath: "arrowTail.size",
|
|
302
|
+
renderable: !0
|
|
303
|
+
},
|
|
304
|
+
"edge.arrowTailColor": {
|
|
305
|
+
channel: "edge.arrowTailColor",
|
|
306
|
+
target: "edge",
|
|
307
|
+
plainName: "Arrow Tail Colour",
|
|
308
|
+
accepts: "color",
|
|
309
|
+
stylePath: "arrowTail.color",
|
|
310
|
+
renderable: !0
|
|
311
|
+
},
|
|
312
|
+
"edge.arrowTailOpacity": {
|
|
313
|
+
channel: "edge.arrowTailOpacity",
|
|
314
|
+
target: "edge",
|
|
315
|
+
plainName: "Arrow Tail Opacity",
|
|
316
|
+
accepts: "number",
|
|
317
|
+
min: 0,
|
|
318
|
+
max: 1,
|
|
319
|
+
stylePath: "arrowTail.opacity",
|
|
320
|
+
renderable: !0
|
|
321
|
+
},
|
|
322
|
+
"edge.arrowTailText": {
|
|
323
|
+
channel: "edge.arrowTailText",
|
|
324
|
+
target: "edge",
|
|
325
|
+
plainName: "Arrow Tail Caption",
|
|
326
|
+
accepts: "text",
|
|
327
|
+
stylePath: "arrowTail.text.text",
|
|
328
|
+
renderable: !0,
|
|
329
|
+
caveat: ie
|
|
330
|
+
},
|
|
331
|
+
"edge.arrowTailTextStyle": {
|
|
332
|
+
channel: "edge.arrowTailTextStyle",
|
|
333
|
+
target: "edge",
|
|
334
|
+
plainName: "Arrow Tail Caption Style",
|
|
335
|
+
accepts: "labelStyle",
|
|
336
|
+
stylePath: "arrowTail.text",
|
|
337
|
+
renderable: !0,
|
|
338
|
+
caveat: ie
|
|
339
|
+
},
|
|
340
|
+
"edge.animationSpeed": {
|
|
341
|
+
channel: "edge.animationSpeed",
|
|
342
|
+
target: "edge",
|
|
343
|
+
plainName: "Edge Animation Speed",
|
|
344
|
+
accepts: "number",
|
|
345
|
+
min: 0,
|
|
346
|
+
stylePath: "line.animationSpeed",
|
|
347
|
+
renderable: !0
|
|
348
|
+
},
|
|
349
|
+
"edge.label": {
|
|
350
|
+
channel: "edge.label",
|
|
351
|
+
target: "edge",
|
|
352
|
+
plainName: "Edge Label",
|
|
353
|
+
accepts: "text",
|
|
354
|
+
stylePath: "label.text",
|
|
355
|
+
renderable: !0
|
|
356
|
+
},
|
|
357
|
+
"edge.labelStyle": {
|
|
358
|
+
channel: "edge.labelStyle",
|
|
359
|
+
target: "edge",
|
|
360
|
+
plainName: "Edge Label Style",
|
|
361
|
+
accepts: "labelStyle",
|
|
362
|
+
stylePath: "label",
|
|
363
|
+
renderable: !0
|
|
364
|
+
}
|
|
365
|
+
}, tt = Object.keys(te);
|
|
366
|
+
tt.filter(
|
|
367
|
+
(s) => te[s].accepts === "color"
|
|
368
|
+
);
|
|
369
|
+
function Kt(s) {
|
|
370
|
+
return typeof s == "string" && Object.hasOwn(te, s);
|
|
371
|
+
}
|
|
372
|
+
function Ma(s) {
|
|
373
|
+
return Kt(s) ? te[s] : void 0;
|
|
374
|
+
}
|
|
375
|
+
function Ca(s) {
|
|
376
|
+
return tt.map((e) => te[e]).filter(
|
|
377
|
+
(e) => e.target === s
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
const at = Object.freeze([
|
|
381
|
+
{
|
|
382
|
+
id: "fitToGraph",
|
|
383
|
+
plainName: "Fit to graph",
|
|
384
|
+
description: "Frames every element, from an angle in 3D and straight on in 2D.",
|
|
385
|
+
modes: ["2d", "3d"],
|
|
386
|
+
options: []
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: "topView",
|
|
390
|
+
plainName: "From above",
|
|
391
|
+
description: "Looks straight down at the graph. The natural view for a flat arrangement.",
|
|
392
|
+
modes: ["2d", "3d"],
|
|
393
|
+
options: []
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "sideView",
|
|
397
|
+
plainName: "From the side",
|
|
398
|
+
description: "Looks along the x axis, which shows how deep an arrangement is.",
|
|
399
|
+
modes: ["3d"],
|
|
400
|
+
options: []
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
id: "frontView",
|
|
404
|
+
plainName: "From the front",
|
|
405
|
+
description: "Looks along the z axis, straight at the face of the arrangement.",
|
|
406
|
+
modes: ["3d"],
|
|
407
|
+
options: []
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
id: "isometric",
|
|
411
|
+
plainName: "Isometric",
|
|
412
|
+
description: "The classic three-quarter view: 45 degrees around and about 35 degrees up.",
|
|
413
|
+
modes: ["3d"],
|
|
414
|
+
options: []
|
|
415
|
+
}
|
|
416
|
+
]);
|
|
417
|
+
function za(s) {
|
|
418
|
+
return at.find((e) => e.id === s) ?? ot(s)?.descriptor;
|
|
419
|
+
}
|
|
420
|
+
function Ta(s) {
|
|
421
|
+
return [...at, ...rt()].filter(
|
|
422
|
+
(e) => e.modes.includes(s)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
function Ea(s) {
|
|
426
|
+
return s;
|
|
427
|
+
}
|
|
428
|
+
function Lt(s) {
|
|
429
|
+
const e = {};
|
|
430
|
+
for (const [t, n] of Object.entries(s))
|
|
431
|
+
e[t] = n.schema;
|
|
432
|
+
return c.object(e);
|
|
433
|
+
}
|
|
434
|
+
function Ut(s, e) {
|
|
435
|
+
return Lt(s).parse(e);
|
|
436
|
+
}
|
|
437
|
+
function T(s, e) {
|
|
438
|
+
return `${String(s)}:${String(e)}`;
|
|
439
|
+
}
|
|
440
|
+
function re(s, e, t, n) {
|
|
441
|
+
const a = String(t);
|
|
442
|
+
if (!n.some((i) => String(i) === a))
|
|
443
|
+
throw new et({
|
|
444
|
+
code: "E_OPTION_RANGE",
|
|
445
|
+
source: "run",
|
|
446
|
+
message: `${s}: the ${e} option names node "${a}", which is not in the graph.`,
|
|
447
|
+
details: { algorithm: s, option: e, value: t }
|
|
448
|
+
});
|
|
449
|
+
return a;
|
|
450
|
+
}
|
|
451
|
+
const jt = {
|
|
452
|
+
source: {
|
|
453
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
454
|
+
meta: {
|
|
455
|
+
label: "Source Node",
|
|
456
|
+
description: "Starting node for shortest path (uses first node if not set)"
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
target: {
|
|
460
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
461
|
+
meta: {
|
|
462
|
+
label: "Target Node",
|
|
463
|
+
description: "Destination node for shortest path (uses last node if not set)"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}, W = class W extends z {
|
|
467
|
+
constructor() {
|
|
468
|
+
super(...arguments), this.legacyOptions = null;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Configure the algorithm with source and optional target nodes
|
|
472
|
+
* @param options - Configuration options
|
|
473
|
+
* @param options.source - The source node ID
|
|
474
|
+
* @param options.target - The optional target node ID
|
|
475
|
+
* @returns This algorithm instance for chaining
|
|
476
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
477
|
+
*/
|
|
478
|
+
configure(e) {
|
|
479
|
+
return this.legacyOptions = e, this;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Find the cheapest route from the source to the target, negative weights included.
|
|
483
|
+
*
|
|
484
|
+
* Publishes the same path fields Dijkstra does, because the engine is a parameter of the
|
|
485
|
+
* question and not a different question. What is different is what the method can tell you:
|
|
486
|
+
* it detects a loop that costs less every time round, and it says so on the graph half of
|
|
487
|
+
* the result, where a distance computed in the presence of one cannot be trusted.
|
|
488
|
+
* @param context - What the element gave the run.
|
|
489
|
+
* @returns The route, or null when there are no nodes to search.
|
|
490
|
+
*/
|
|
491
|
+
async compute(e) {
|
|
492
|
+
const t = this.graph.getDataManager(), n = Array.from(t.nodes.keys());
|
|
493
|
+
if (n.length === 0)
|
|
494
|
+
return null;
|
|
495
|
+
const a = this.legacyOptions?.source ?? this._schemaOptions.source ?? n[0], i = this.legacyOptions?.target ?? this._schemaOptions.target ?? n[n.length - 1], o = this.algorithmGraph("undirected");
|
|
496
|
+
e.report({ phase: "Relaxing edges", total: null });
|
|
497
|
+
const r = ht(o, a), d = this.reconstructPath(r.predecessors, a, i), h = /* @__PURE__ */ new Map();
|
|
498
|
+
d.forEach((f, O) => h.set(f, O));
|
|
499
|
+
for (const f of r.distances.values())
|
|
500
|
+
;
|
|
501
|
+
const m = [];
|
|
502
|
+
await N(e, "Marking the route", n, (f) => {
|
|
503
|
+
const O = h.get(f), y = r.distances.get(f) ?? 1 / 0;
|
|
504
|
+
m.push({ id: f, values: { onPath: O !== void 0, order: O, distance: y } });
|
|
505
|
+
});
|
|
506
|
+
const l = this.getPathEdges(d), u = [];
|
|
507
|
+
await N(e, "Marking the route", Array.from(t.edges.values()), (f) => {
|
|
508
|
+
const O = T(f.srcId, f.dstId), y = T(f.dstId, f.srcId);
|
|
509
|
+
u.push({ id: f.id, values: { onPath: l.has(O) || l.has(y) } });
|
|
510
|
+
});
|
|
511
|
+
const p = [`Route from ${String(a)} to ${String(i)}.`];
|
|
512
|
+
return r.hasNegativeCycle && p.push("A loop that costs less every time round was found, so no distance past it is meaningful."), {
|
|
513
|
+
shape: "path",
|
|
514
|
+
fields: [
|
|
515
|
+
...Qe,
|
|
516
|
+
{ name: "distance", kind: "node", type: "number" },
|
|
517
|
+
{ name: "hasNegativeCycle", kind: "graph", type: "boolean" }
|
|
518
|
+
],
|
|
519
|
+
nodes: m,
|
|
520
|
+
edges: u,
|
|
521
|
+
graph: {
|
|
522
|
+
length: d.length,
|
|
523
|
+
cost: d.length > 0 ? r.distances.get(i) ?? 0 : 0,
|
|
524
|
+
hops: Math.max(d.length - 1, 0),
|
|
525
|
+
hasNegativeCycle: r.hasNegativeCycle
|
|
526
|
+
},
|
|
527
|
+
caveats: M({
|
|
528
|
+
method: "bellman-ford",
|
|
529
|
+
direction: "undirected",
|
|
530
|
+
weight: { attribute: "weight", meaning: "distance" },
|
|
531
|
+
notes: p
|
|
532
|
+
})
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Reconstruct the shortest path from predecessors
|
|
537
|
+
* @param predecessors - Map of node to its predecessor in the shortest path
|
|
538
|
+
* @param source - The source node
|
|
539
|
+
* @param target - The target node
|
|
540
|
+
* @returns Array of node IDs representing the shortest path
|
|
541
|
+
*/
|
|
542
|
+
reconstructPath(e, t, n) {
|
|
543
|
+
const a = [];
|
|
544
|
+
let i = n;
|
|
545
|
+
for (; i !== null && (a.unshift(i), i !== t); )
|
|
546
|
+
i = e.get(i) ?? null;
|
|
547
|
+
return a.length === 0 || a[0] !== t ? [] : a;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Get set of edge keys that are part of the path
|
|
551
|
+
* @param path - Array of node IDs representing the path
|
|
552
|
+
* @returns Set of edge keys in "srcId:dstId" format
|
|
553
|
+
*/
|
|
554
|
+
getPathEdges(e) {
|
|
555
|
+
const t = /* @__PURE__ */ new Set();
|
|
556
|
+
for (let n = 0; n < e.length - 1; n++)
|
|
557
|
+
t.add(T(e[n], e[n + 1]));
|
|
558
|
+
return t;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
W.namespace = "graphty", W.type = "bellman-ford", W.zodOptionsSchema = jt, W.optionsSchema = {
|
|
562
|
+
source: {
|
|
563
|
+
type: "nodeId",
|
|
564
|
+
default: null,
|
|
565
|
+
label: "Source Node",
|
|
566
|
+
description: "Starting node for shortest path (uses first node if not set)",
|
|
567
|
+
required: !1
|
|
568
|
+
},
|
|
569
|
+
target: {
|
|
570
|
+
type: "nodeId",
|
|
571
|
+
default: null,
|
|
572
|
+
label: "Target Node",
|
|
573
|
+
description: "Destination node for shortest path (uses last node if not set)",
|
|
574
|
+
required: !1
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
let ce = W;
|
|
578
|
+
S.register(ce);
|
|
579
|
+
const Vt = 2048;
|
|
580
|
+
function qt(s) {
|
|
581
|
+
return {
|
|
582
|
+
runId: s,
|
|
583
|
+
signal: new AbortController().signal,
|
|
584
|
+
report: () => {
|
|
585
|
+
},
|
|
586
|
+
yieldNow: () => new Promise((e) => {
|
|
587
|
+
setTimeout(e, 0);
|
|
588
|
+
})
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
async function F(s, e, t, n) {
|
|
592
|
+
const a = s.length;
|
|
593
|
+
let i = performance.now();
|
|
594
|
+
for (let o = 0; o < a; o++)
|
|
595
|
+
e.signal.throwIfAborted(), n(s[o], o), (o + 1) % Vt === 0 && o + 1 < a && (e.report({ phase: t, completed: o + 1, total: a }), performance.now() - i >= ct && (await e.yieldNow(), i = performance.now()));
|
|
596
|
+
e.report({ phase: t, completed: a, total: a });
|
|
597
|
+
}
|
|
598
|
+
class G extends S {
|
|
599
|
+
/** What the last run published, kept so a caller that has the algorithm can read the result. */
|
|
600
|
+
#e;
|
|
601
|
+
/**
|
|
602
|
+
* What the last run published.
|
|
603
|
+
* @returns The result, or undefined when nothing has run or the graph was empty.
|
|
604
|
+
*/
|
|
605
|
+
get result() {
|
|
606
|
+
return this.#e;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Run the algorithm.
|
|
610
|
+
*
|
|
611
|
+
* This is the 1.10 entry point and it still returns nothing, because `AlgorithmManager` and
|
|
612
|
+
* every caller behind `element.runAlgorithm` still expect that. It publishes a result all the
|
|
613
|
+
* same: read {@link MetricAlgorithm.result}, or call {@link MetricAlgorithm.measureRun} with a
|
|
614
|
+
* run context to get one back.
|
|
615
|
+
* @returns A promise that settles once the result has been published.
|
|
616
|
+
*/
|
|
617
|
+
async run() {
|
|
618
|
+
await this.measureRun(qt(this.type));
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Measure the graph on behalf of a run, and hand the result back.
|
|
622
|
+
*
|
|
623
|
+
* The run machinery's entry point. A metric's own context is its run context plus the run id,
|
|
624
|
+
* so this is the whole of the translation.
|
|
625
|
+
* @param context - A signal, a progress channel and a yield.
|
|
626
|
+
* @param runId - The id the result is published under.
|
|
627
|
+
* @returns The result, or undefined when there was nothing to measure.
|
|
628
|
+
* @throws Whatever the context's signal throws once the run has been cancelled.
|
|
629
|
+
*/
|
|
630
|
+
publishResult(e, t) {
|
|
631
|
+
return this.measureRun({ runId: t, ...e });
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Measure the graph, publish the result, and project the 1.10 view from it.
|
|
635
|
+
* @param context - Where progress goes, where cancellation arrives, and which run id to
|
|
636
|
+
* publish under.
|
|
637
|
+
* @returns The result, or undefined when there was nothing to measure.
|
|
638
|
+
* @throws Whatever the context's signal throws once the run has been cancelled, which is a
|
|
639
|
+
* `DOMException` named `AbortError`.
|
|
640
|
+
*/
|
|
641
|
+
async measureRun(e) {
|
|
642
|
+
const t = this.graph.getDataManager(), n = Array.from(t.nodes.keys());
|
|
643
|
+
if (n.length === 0)
|
|
644
|
+
return;
|
|
645
|
+
const a = Date.now(), i = await this.measure(e, n), o = lt(this.graph), r = dt({
|
|
646
|
+
...o === void 0 ? {} : { labelOf: o },
|
|
647
|
+
runId: e.runId,
|
|
648
|
+
shape: "node-metric",
|
|
649
|
+
fields: this.resultFields(),
|
|
650
|
+
measured: { nodes: n.length, edges: t.edges.size },
|
|
651
|
+
// The only graph-level field a metric publishes itself. Everything else the shape
|
|
652
|
+
// promises -- the range, the average, how many elements were measured, how many sit at
|
|
653
|
+
// the bottom -- is computed from the column the measurement produced.
|
|
654
|
+
graph: { normalization: i.normalization },
|
|
655
|
+
nodes: i.nodes,
|
|
656
|
+
caveats: i.caveats,
|
|
657
|
+
durationMs: Date.now() - a
|
|
658
|
+
});
|
|
659
|
+
return this.#e = r, r;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const At = $({
|
|
663
|
+
plainName: "Bridging",
|
|
664
|
+
technicalName: "betweenness"
|
|
665
|
+
}), Ee = class Ee extends G {
|
|
666
|
+
/**
|
|
667
|
+
* The fields a betweenness result publishes.
|
|
668
|
+
* @returns The uniform node-metric fields.
|
|
669
|
+
*/
|
|
670
|
+
resultFields() {
|
|
671
|
+
return At;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Score every node by the shortest paths that run through it.
|
|
675
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
676
|
+
* @param nodeIds - The nodes to measure.
|
|
677
|
+
* @returns One raw path count per node, unscaled.
|
|
678
|
+
*/
|
|
679
|
+
async measure(e, t) {
|
|
680
|
+
const n = this.algorithmGraph("undirected");
|
|
681
|
+
e.report({
|
|
682
|
+
phase: "tracing shortest paths",
|
|
683
|
+
completed: 0,
|
|
684
|
+
total: t.length,
|
|
685
|
+
message: "Brandes runs a breadth-first search from every node."
|
|
686
|
+
});
|
|
687
|
+
const a = ut(n);
|
|
688
|
+
e.signal.throwIfAborted();
|
|
689
|
+
const i = [];
|
|
690
|
+
return await F(t, e, "reading scores", (o) => {
|
|
691
|
+
const r = a[String(o)];
|
|
692
|
+
i.push({ id: o, values: r === void 0 ? {} : { value: r } });
|
|
693
|
+
}), {
|
|
694
|
+
nodes: i,
|
|
695
|
+
// Raw counts. `options.normalized` is not set, so nothing rescales them.
|
|
696
|
+
normalization: "none",
|
|
697
|
+
caveats: {
|
|
698
|
+
exact: !0,
|
|
699
|
+
direction: "undirected",
|
|
700
|
+
weight: null,
|
|
701
|
+
precision: "f64",
|
|
702
|
+
method: "brandes",
|
|
703
|
+
notes: [
|
|
704
|
+
"Every shortest path is counted exactly, over the graph read as undirected.",
|
|
705
|
+
"The raw counts are halved, because an undirected shortest path is reached from both ends.",
|
|
706
|
+
"Path lengths count edges; edge weights are not read."
|
|
707
|
+
]
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
Ee.namespace = "graphty", Ee.type = "betweenness";
|
|
713
|
+
let le = Ee;
|
|
714
|
+
S.register(le);
|
|
715
|
+
const Bt = {
|
|
716
|
+
source: {
|
|
717
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
718
|
+
meta: {
|
|
719
|
+
label: "Source Node",
|
|
720
|
+
description: "Starting node for BFS traversal (uses first node if not set)"
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
targetNode: {
|
|
724
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
725
|
+
meta: {
|
|
726
|
+
label: "Target Node",
|
|
727
|
+
description: "Target node for early termination (optional - searches all nodes if not set)",
|
|
728
|
+
advanced: !0
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}, K = class K extends z {
|
|
732
|
+
constructor() {
|
|
733
|
+
super(...arguments), this.legacyOptions = null;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Configure the algorithm with source node
|
|
737
|
+
* @param options - Configuration options
|
|
738
|
+
* @param options.source - The source node to start BFS from
|
|
739
|
+
* @returns This algorithm instance for chaining
|
|
740
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
741
|
+
*/
|
|
742
|
+
configure(e) {
|
|
743
|
+
return this.legacyOptions = e, this;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Walk outwards from one node a level at a time.
|
|
747
|
+
*
|
|
748
|
+
* A breadth-first walk sorts the graph into layers, so the result is shaped as a layered
|
|
749
|
+
* grouping: every reached node carries the level it sits on and the position it was reached
|
|
750
|
+
* in, and the element derives how many nodes share each level and how many levels there are.
|
|
751
|
+
* A node the walk never reached carries nothing at all -- it is not on level 0, and saying so
|
|
752
|
+
* would put every unreachable node in the same layer as the source.
|
|
753
|
+
* @param context - What the element gave the run.
|
|
754
|
+
* @returns The layered result, or null when there is nothing to walk.
|
|
755
|
+
*/
|
|
756
|
+
async compute(e) {
|
|
757
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
758
|
+
if (t.length === 0)
|
|
759
|
+
return null;
|
|
760
|
+
const n = this._schemaOptions.targetNode ?? void 0, a = this.graph.getDataManager().getSnapshot();
|
|
761
|
+
if (a.nodeCount === 0)
|
|
762
|
+
return null;
|
|
763
|
+
const i = this.legacyOptions?.source ?? this._schemaOptions.source ?? a.ids.idOf(0);
|
|
764
|
+
if (n !== void 0)
|
|
765
|
+
return this.legacyWalk(e, t, i, n);
|
|
766
|
+
const { snapshot: o, run: r } = this.accelerated("breadthFirstSearch", "undirected"), d = this.nodeIndex(o, "source", i);
|
|
767
|
+
e.report({ phase: "Walking outwards", total: null });
|
|
768
|
+
const { value: h, precision: m } = await r((p, f) => p.breadthFirstSearch(f, d)), l = /* @__PURE__ */ new Map();
|
|
769
|
+
for (let p = 0; p < h.visitedCount; p++)
|
|
770
|
+
l.set(h.order[p], p);
|
|
771
|
+
const u = [];
|
|
772
|
+
return await N(e, "Recording levels", t, (p) => {
|
|
773
|
+
const f = o.ids.indexOf(p), O = f === R ? R : h.depth[f];
|
|
774
|
+
O !== R && u.push({ id: p, values: { level: O, order: l.get(f) } });
|
|
775
|
+
}), {
|
|
776
|
+
shape: "layered-grouping",
|
|
777
|
+
fields: [...qe, { name: "order", kind: "node", type: "integer" }],
|
|
778
|
+
nodes: u,
|
|
779
|
+
caveats: M({
|
|
780
|
+
method: "bfs",
|
|
781
|
+
direction: "undirected",
|
|
782
|
+
weight: null,
|
|
783
|
+
precision: m,
|
|
784
|
+
notes: [`Walked outwards from ${String(i)}, which is level 0.`]
|
|
785
|
+
})
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Walk with an early stop at a target, on the CPU reference implementation.
|
|
790
|
+
* @param context - What the element gave the run.
|
|
791
|
+
* @param nodeIds - The nodes to publish for.
|
|
792
|
+
* @param source - Where the walk starts.
|
|
793
|
+
* @param targetNode - Where it stops.
|
|
794
|
+
* @returns The layered result, or null when the source is not in the graph.
|
|
795
|
+
*/
|
|
796
|
+
async legacyWalk(e, t, n, a) {
|
|
797
|
+
const i = this.algorithmGraph("undirected");
|
|
798
|
+
if (!i.hasNode(n))
|
|
799
|
+
return null;
|
|
800
|
+
const o = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map();
|
|
801
|
+
let d = !1;
|
|
802
|
+
e.report({ phase: "Walking outwards", total: null }), pt(i, n, {
|
|
803
|
+
targetNode: a,
|
|
804
|
+
visitCallback: (l, u) => {
|
|
805
|
+
o.set(l, u), r.set(l, r.size), l === a && (d = !0);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
const h = [];
|
|
809
|
+
return await N(e, "Recording levels", t, (l) => {
|
|
810
|
+
const u = o.get(l);
|
|
811
|
+
u !== void 0 && h.push({ id: l, values: { level: u, order: r.get(l) } });
|
|
812
|
+
}), {
|
|
813
|
+
shape: "layered-grouping",
|
|
814
|
+
fields: [
|
|
815
|
+
...qe,
|
|
816
|
+
{ name: "order", kind: "node", type: "integer" },
|
|
817
|
+
{ name: "targetFound", kind: "graph", type: "boolean" }
|
|
818
|
+
],
|
|
819
|
+
nodes: h,
|
|
820
|
+
graph: { targetFound: d },
|
|
821
|
+
caveats: M({
|
|
822
|
+
method: "bfs",
|
|
823
|
+
direction: "undirected",
|
|
824
|
+
weight: null,
|
|
825
|
+
notes: [
|
|
826
|
+
`Walked outwards from ${String(n)}, which is level 0.`,
|
|
827
|
+
`Computed on the CPU reference implementation: the walk stops early at ${String(a)}, and no accelerated implementation has an early stop.`
|
|
828
|
+
]
|
|
829
|
+
})
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
K.namespace = "graphty", K.type = "bfs", K.zodOptionsSchema = Bt, K.optionsSchema = {
|
|
834
|
+
source: {
|
|
835
|
+
type: "nodeId",
|
|
836
|
+
default: null,
|
|
837
|
+
label: "Source Node",
|
|
838
|
+
description: "Starting node for BFS traversal (uses first node if not set)",
|
|
839
|
+
required: !1
|
|
840
|
+
},
|
|
841
|
+
targetNode: {
|
|
842
|
+
type: "nodeId",
|
|
843
|
+
default: null,
|
|
844
|
+
label: "Target Node",
|
|
845
|
+
description: "Target node for early termination (optional - searches all nodes if not set)",
|
|
846
|
+
required: !1,
|
|
847
|
+
advanced: !0
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
let de = K;
|
|
851
|
+
S.register(de);
|
|
852
|
+
const De = class De extends z {
|
|
853
|
+
/**
|
|
854
|
+
* Pair up the two sides of a two-sided graph so that as many nodes as possible get a partner.
|
|
855
|
+
*
|
|
856
|
+
* A matching is a set of edges, so the result is shaped as one: every edge says whether it is
|
|
857
|
+
* in the pairing and the element counts how many are. The one number the answer is read for
|
|
858
|
+
* is whether the graph has two sides at all -- a graph that does not cannot be paired up, and
|
|
859
|
+
* the run says so rather than returning an empty pairing that looks like a bad result.
|
|
860
|
+
* @param context - What the element gave the run.
|
|
861
|
+
* @returns The edge set, or null when there are no edges to pair.
|
|
862
|
+
*/
|
|
863
|
+
async compute(e) {
|
|
864
|
+
const t = Array.from(this.graph.getDataManager().edges.values());
|
|
865
|
+
if (t.length === 0)
|
|
866
|
+
return null;
|
|
867
|
+
const n = this.algorithmGraph("undirected"), a = [
|
|
868
|
+
...We("edge", { name: "bipartite", type: "boolean" }),
|
|
869
|
+
{ name: "side", kind: "node", type: "string" },
|
|
870
|
+
{ name: "matched", kind: "node", type: "boolean" }
|
|
871
|
+
];
|
|
872
|
+
e.report({ phase: "Checking for two sides", total: null });
|
|
873
|
+
const i = mt(n);
|
|
874
|
+
if (i === null) {
|
|
875
|
+
const l = [];
|
|
876
|
+
return await N(e, "Marking the pairing", t, (u) => {
|
|
877
|
+
l.push({ id: u.id, values: { in: !1 } });
|
|
878
|
+
}), {
|
|
879
|
+
shape: "edge-set",
|
|
880
|
+
fields: a,
|
|
881
|
+
edges: l,
|
|
882
|
+
graph: { bipartite: !1 },
|
|
883
|
+
caveats: M({
|
|
884
|
+
method: "bipartite-matching",
|
|
885
|
+
direction: "undirected",
|
|
886
|
+
weight: null,
|
|
887
|
+
notes: ["The graph does not have two sides, so nothing could be paired up."]
|
|
888
|
+
})
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
e.report({ phase: "Pairing nodes", total: null });
|
|
892
|
+
const o = gt(n, {
|
|
893
|
+
leftNodes: i.left,
|
|
894
|
+
rightNodes: i.right
|
|
895
|
+
}), r = /* @__PURE__ */ new Set();
|
|
896
|
+
for (const [l, u] of o.matching)
|
|
897
|
+
r.add(T(l, u)), r.add(T(u, l));
|
|
898
|
+
const d = [];
|
|
899
|
+
await N(e, "Marking the pairing", t, (l) => {
|
|
900
|
+
d.push({ id: l.id, values: { in: r.has(T(l.srcId, l.dstId)) } });
|
|
901
|
+
});
|
|
902
|
+
const h = new Set(o.matching.values()), m = [];
|
|
903
|
+
return await N(e, "Marking sides", Array.from(i.left), (l) => {
|
|
904
|
+
m.push({ id: l, values: { side: "left", matched: o.matching.has(l) } });
|
|
905
|
+
}), await N(e, "Marking sides", Array.from(i.right), (l) => {
|
|
906
|
+
m.push({ id: l, values: { side: "right", matched: h.has(l) } });
|
|
907
|
+
}), {
|
|
908
|
+
shape: "edge-set",
|
|
909
|
+
fields: a,
|
|
910
|
+
nodes: m,
|
|
911
|
+
edges: d,
|
|
912
|
+
graph: { bipartite: !0 },
|
|
913
|
+
caveats: M({
|
|
914
|
+
method: "bipartite-matching",
|
|
915
|
+
direction: "undirected",
|
|
916
|
+
weight: null,
|
|
917
|
+
notes: [`${String(o.size)} of the two sides' nodes found a partner.`]
|
|
918
|
+
})
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
De.namespace = "graphty", De.type = "bipartite-matching";
|
|
923
|
+
let he = De;
|
|
924
|
+
S.register(he);
|
|
925
|
+
const Yt = $({
|
|
926
|
+
plainName: "Reach",
|
|
927
|
+
technicalName: "closeness"
|
|
928
|
+
}), Pe = class Pe extends G {
|
|
929
|
+
/**
|
|
930
|
+
* The fields a closeness result publishes.
|
|
931
|
+
* @returns The uniform node-metric fields.
|
|
932
|
+
*/
|
|
933
|
+
resultFields() {
|
|
934
|
+
return Yt;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Score every node by how short its paths to the rest of the graph are.
|
|
938
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
939
|
+
* @param nodeIds - The nodes to measure.
|
|
940
|
+
* @returns One score per node, unscaled.
|
|
941
|
+
*/
|
|
942
|
+
async measure(e, t) {
|
|
943
|
+
const n = this.algorithmGraph("undirected");
|
|
944
|
+
e.report({
|
|
945
|
+
phase: "measuring distances",
|
|
946
|
+
completed: 0,
|
|
947
|
+
total: t.length,
|
|
948
|
+
message: "A breadth-first search runs from every node."
|
|
949
|
+
});
|
|
950
|
+
const a = ft(n);
|
|
951
|
+
e.signal.throwIfAborted();
|
|
952
|
+
const i = [];
|
|
953
|
+
return await F(t, e, "reading scores", (o) => {
|
|
954
|
+
const r = a[String(o)];
|
|
955
|
+
i.push({ id: o, values: r === void 0 ? {} : { value: r } });
|
|
956
|
+
}), {
|
|
957
|
+
nodes: i,
|
|
958
|
+
// The algorithm's own figure, published as it was computed.
|
|
959
|
+
normalization: "none",
|
|
960
|
+
caveats: {
|
|
961
|
+
exact: !0,
|
|
962
|
+
direction: "undirected",
|
|
963
|
+
weight: null,
|
|
964
|
+
precision: "f64",
|
|
965
|
+
method: "closeness-bfs",
|
|
966
|
+
notes: [
|
|
967
|
+
"Distances are exact, measured over the graph read as undirected.",
|
|
968
|
+
"Distance counts edges; edge weights are not read.",
|
|
969
|
+
"A score is the reciprocal of the total distance to the nodes this one can reach, with no correction for how many that is, so a node in a small component scores as though it reached the whole graph."
|
|
970
|
+
]
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
Pe.namespace = "graphty", Pe.type = "closeness";
|
|
976
|
+
let ue = Pe;
|
|
977
|
+
S.register(ue);
|
|
978
|
+
const Re = class Re extends z {
|
|
979
|
+
/**
|
|
980
|
+
* Find the separate pieces of the graph.
|
|
981
|
+
*
|
|
982
|
+
* A piece is a group in the community shape's sense -- two nodes are in the same one when a
|
|
983
|
+
* path joins them -- so this publishes the same `group` per node that every grouping method
|
|
984
|
+
* does, and no modularity, which is not defined for a partition nothing optimised.
|
|
985
|
+
* @param context - What the element gave the run.
|
|
986
|
+
* @returns The community result, or null when there are no nodes to group.
|
|
987
|
+
*/
|
|
988
|
+
async compute(e) {
|
|
989
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
990
|
+
if (t.length === 0)
|
|
991
|
+
return null;
|
|
992
|
+
const { snapshot: n, run: a } = this.accelerated("connectedComponents", "undirected"), { ids: i } = n;
|
|
993
|
+
e.report({ phase: "Finding pieces", total: null });
|
|
994
|
+
const { value: o, precision: r } = await a((h, m) => h.connectedComponents(m)), d = [];
|
|
995
|
+
return await N(e, "Grouping nodes", t, (h) => {
|
|
996
|
+
const m = i.indexOf(h);
|
|
997
|
+
d.push({ id: h, values: { group: o.labels[m] ?? 0 } });
|
|
998
|
+
}), {
|
|
999
|
+
shape: "community",
|
|
1000
|
+
fields: _(!1),
|
|
1001
|
+
nodes: d,
|
|
1002
|
+
caveats: M({
|
|
1003
|
+
method: "connected-components",
|
|
1004
|
+
direction: "undirected",
|
|
1005
|
+
weight: null,
|
|
1006
|
+
precision: r,
|
|
1007
|
+
notes: ["Strength: weak. An edge joins its two nodes whichever way it was declared."]
|
|
1008
|
+
})
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
Re.namespace = "graphty", Re.type = "connected-components";
|
|
1013
|
+
let pe = Re;
|
|
1014
|
+
S.register(pe);
|
|
1015
|
+
const Zt = [
|
|
1016
|
+
...$({ plainName: "Connections", technicalName: "degree", type: "integer", unit: "links" }),
|
|
1017
|
+
oe({
|
|
1018
|
+
name: "inDegree",
|
|
1019
|
+
plainName: "Incoming connections",
|
|
1020
|
+
technicalName: "inDegree",
|
|
1021
|
+
kind: "node",
|
|
1022
|
+
type: "integer"
|
|
1023
|
+
}),
|
|
1024
|
+
oe({
|
|
1025
|
+
name: "outDegree",
|
|
1026
|
+
plainName: "Outgoing connections",
|
|
1027
|
+
technicalName: "outDegree",
|
|
1028
|
+
kind: "node",
|
|
1029
|
+
type: "integer"
|
|
1030
|
+
})
|
|
1031
|
+
], Fe = class Fe extends G {
|
|
1032
|
+
/**
|
|
1033
|
+
* The fields a degree result publishes.
|
|
1034
|
+
* @returns The uniform node-metric fields, plus `inDegree` and `outDegree`.
|
|
1035
|
+
*/
|
|
1036
|
+
resultFields() {
|
|
1037
|
+
return Zt;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Count every node's edges.
|
|
1041
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
1042
|
+
* @param nodeIds - The nodes to measure.
|
|
1043
|
+
* @returns One count per node, unscaled.
|
|
1044
|
+
*/
|
|
1045
|
+
async measure(e, t) {
|
|
1046
|
+
const n = this.algorithmGraph("directed"), a = [];
|
|
1047
|
+
return await F(t, e, "counting connections", (i) => {
|
|
1048
|
+
const o = n.inDegree(i), r = n.outDegree(i);
|
|
1049
|
+
a.push({ id: i, values: { value: o + r, inDegree: o, outDegree: r } });
|
|
1050
|
+
}), {
|
|
1051
|
+
nodes: a,
|
|
1052
|
+
// A count of edges, published as it was counted.
|
|
1053
|
+
normalization: "none",
|
|
1054
|
+
caveats: {
|
|
1055
|
+
exact: !0,
|
|
1056
|
+
direction: "directed",
|
|
1057
|
+
weight: null,
|
|
1058
|
+
precision: "f64",
|
|
1059
|
+
method: "degree",
|
|
1060
|
+
notes: ["Counted over the graph as the records declared it, so a node's total is its incoming edges plus its outgoing ones."]
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
Fe.namespace = "graphty", Fe.type = "degree";
|
|
1066
|
+
let me = Fe;
|
|
1067
|
+
S.register(me);
|
|
1068
|
+
const Xt = {
|
|
1069
|
+
source: {
|
|
1070
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
1071
|
+
meta: {
|
|
1072
|
+
label: "Source Node",
|
|
1073
|
+
description: "Starting node for DFS traversal (uses first node if not set)"
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
targetNode: {
|
|
1077
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
1078
|
+
meta: {
|
|
1079
|
+
label: "Target Node",
|
|
1080
|
+
description: "Target node for early termination (optional - searches all nodes if not set)",
|
|
1081
|
+
advanced: !0
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
recursive: {
|
|
1085
|
+
schema: c.boolean().default(!1),
|
|
1086
|
+
meta: {
|
|
1087
|
+
label: "Recursive",
|
|
1088
|
+
description: "Use recursive implementation instead of iterative (may cause stack overflow on large graphs)",
|
|
1089
|
+
advanced: !0
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
preOrder: {
|
|
1093
|
+
schema: c.boolean().default(!0),
|
|
1094
|
+
meta: {
|
|
1095
|
+
label: "Pre-Order",
|
|
1096
|
+
description: "Visit nodes before their children (pre-order) vs after (post-order)",
|
|
1097
|
+
advanced: !0
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}, L = class L extends z {
|
|
1101
|
+
constructor() {
|
|
1102
|
+
super(...arguments), this.legacyOptions = null;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Configure the algorithm with source node
|
|
1106
|
+
* @param options - Configuration options
|
|
1107
|
+
* @param options.source - The source node to start DFS from
|
|
1108
|
+
* @returns This algorithm instance for chaining
|
|
1109
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
1110
|
+
*/
|
|
1111
|
+
configure(e) {
|
|
1112
|
+
return this.legacyOptions = e, this;
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Walk as deep as possible from one node before backtracking.
|
|
1116
|
+
*
|
|
1117
|
+
* What a depth-first walk produces is one number per node -- the position it was reached in
|
|
1118
|
+
* -- so the result is a node metric, and the ranking and range of that column come from the
|
|
1119
|
+
* element rather than from here. A node the walk never reached carries no position, and says
|
|
1120
|
+
* so with `visited`, because "not reached" and "reached first" are not the same answer.
|
|
1121
|
+
* @param context - What the element gave the run.
|
|
1122
|
+
* @returns The exploration order, or null when there is nothing to walk.
|
|
1123
|
+
*/
|
|
1124
|
+
async compute(e) {
|
|
1125
|
+
const t = this.graph.getDataManager(), n = Array.from(t.nodes.keys());
|
|
1126
|
+
if (n.length === 0)
|
|
1127
|
+
return null;
|
|
1128
|
+
const a = this.legacyOptions?.source ?? this._schemaOptions.source ?? n[0], { targetNode: i, recursive: o, preOrder: r } = this._schemaOptions;
|
|
1129
|
+
if (!t.nodes.has(a))
|
|
1130
|
+
return null;
|
|
1131
|
+
const d = this.algorithmGraph("undirected");
|
|
1132
|
+
e.report({ phase: "Walking deep", total: null });
|
|
1133
|
+
const h = yt(d, a, {
|
|
1134
|
+
targetNode: i ?? void 0,
|
|
1135
|
+
recursive: o,
|
|
1136
|
+
preOrder: r
|
|
1137
|
+
}), m = /* @__PURE__ */ new Map();
|
|
1138
|
+
h.order.forEach((u, p) => m.set(u, p));
|
|
1139
|
+
const l = [];
|
|
1140
|
+
return await N(e, "Recording the order", n, (u) => {
|
|
1141
|
+
const p = m.get(u);
|
|
1142
|
+
l.push({ id: u, values: { value: p, visited: h.visited.has(u) } });
|
|
1143
|
+
}), {
|
|
1144
|
+
shape: "node-metric",
|
|
1145
|
+
fields: [...je("node", "integer"), { name: "visited", kind: "node", type: "boolean" }],
|
|
1146
|
+
nodes: l,
|
|
1147
|
+
caveats: M({
|
|
1148
|
+
method: "dfs",
|
|
1149
|
+
direction: "undirected",
|
|
1150
|
+
weight: null,
|
|
1151
|
+
notes: [`Walked from ${String(a)}, ${r ? "recording each node as it was reached" : "recording each node as it was left"}.`]
|
|
1152
|
+
})
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
L.namespace = "graphty", L.type = "dfs", L.zodOptionsSchema = Xt, L.optionsSchema = {
|
|
1157
|
+
source: {
|
|
1158
|
+
type: "nodeId",
|
|
1159
|
+
default: null,
|
|
1160
|
+
label: "Source Node",
|
|
1161
|
+
description: "Starting node for DFS traversal (uses first node if not set)",
|
|
1162
|
+
required: !1
|
|
1163
|
+
},
|
|
1164
|
+
targetNode: {
|
|
1165
|
+
type: "nodeId",
|
|
1166
|
+
default: null,
|
|
1167
|
+
label: "Target Node",
|
|
1168
|
+
description: "Target node for early termination (optional - searches all nodes if not set)",
|
|
1169
|
+
required: !1,
|
|
1170
|
+
advanced: !0
|
|
1171
|
+
},
|
|
1172
|
+
recursive: {
|
|
1173
|
+
type: "boolean",
|
|
1174
|
+
default: !1,
|
|
1175
|
+
label: "Recursive",
|
|
1176
|
+
description: "Use recursive implementation instead of iterative (may cause stack overflow on large graphs)",
|
|
1177
|
+
advanced: !0
|
|
1178
|
+
},
|
|
1179
|
+
preOrder: {
|
|
1180
|
+
type: "boolean",
|
|
1181
|
+
default: !0,
|
|
1182
|
+
label: "Pre-Order",
|
|
1183
|
+
description: "Visit nodes before their children (pre-order) vs after (post-order)",
|
|
1184
|
+
advanced: !0
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
let ge = L;
|
|
1188
|
+
S.register(ge);
|
|
1189
|
+
const Jt = {
|
|
1190
|
+
source: {
|
|
1191
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
1192
|
+
meta: {
|
|
1193
|
+
label: "Source Node",
|
|
1194
|
+
description: "Starting node for shortest path (uses first node if not set)"
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
target: {
|
|
1198
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
1199
|
+
meta: {
|
|
1200
|
+
label: "Target Node",
|
|
1201
|
+
description: "Destination node for shortest path (uses last node if not set)"
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
bidirectional: {
|
|
1205
|
+
schema: c.boolean().default(!0),
|
|
1206
|
+
meta: {
|
|
1207
|
+
label: "Bidirectional Search",
|
|
1208
|
+
description: "Accepted and ignored: the shortest-path search relaxes outwards from the source in one direction, and the route it finds is the same one either way",
|
|
1209
|
+
advanced: !0
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}, U = class U extends z {
|
|
1213
|
+
constructor() {
|
|
1214
|
+
super(...arguments), this.legacyOptions = null;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Configure the algorithm with source and optional target nodes
|
|
1218
|
+
* @param options - Configuration options
|
|
1219
|
+
* @param options.source - The source node for shortest path computation
|
|
1220
|
+
* @param options.target - The target node (optional)
|
|
1221
|
+
* @returns This algorithm instance for chaining
|
|
1222
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
1223
|
+
*/
|
|
1224
|
+
configure(e) {
|
|
1225
|
+
return this.legacyOptions = e, this;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Find the cheapest route from the source to the target.
|
|
1229
|
+
*
|
|
1230
|
+
* Publishes the path shape's uniform fields: whether each node and edge is on the route,
|
|
1231
|
+
* where each node sits along it counting the source as 0, and the route's size and cost. It
|
|
1232
|
+
* also publishes how far every reachable node is from the source, which is what makes one
|
|
1233
|
+
* run answer both "show me the way there" and "how far is everything".
|
|
1234
|
+
* @param context - What the element gave the run.
|
|
1235
|
+
* @returns The route, or null when there are no nodes to search.
|
|
1236
|
+
*/
|
|
1237
|
+
async compute(e) {
|
|
1238
|
+
const t = this.graph.getDataManager(), n = Array.from(t.nodes.keys());
|
|
1239
|
+
if (n.length === 0)
|
|
1240
|
+
return null;
|
|
1241
|
+
const { snapshot: a, edgeRemap: i, run: o } = this.accelerated("sssp", "undirected");
|
|
1242
|
+
if (a.nodeCount === 0)
|
|
1243
|
+
return null;
|
|
1244
|
+
const { ids: r } = a, d = this.legacyOptions?.source ?? this._schemaOptions.source ?? r.idOf(0), h = this.legacyOptions?.target ?? this._schemaOptions.target ?? r.idOf(a.nodeCount - 1), m = this.nodeIndex(a, "source", d), l = this.nodeIndex(a, "target", h);
|
|
1245
|
+
e.report({ phase: "Searching for the route", total: null });
|
|
1246
|
+
const { value: u, precision: p } = await o((w, v) => w.sssp(v, m)), f = u.pathTo(l), O = new Set(u.pathEdges(l)), y = /* @__PURE__ */ new Map();
|
|
1247
|
+
f.forEach((w, v) => y.set(w, v));
|
|
1248
|
+
const k = [];
|
|
1249
|
+
await N(e, "Marking the route", n, (w) => {
|
|
1250
|
+
const v = r.indexOf(w), b = v === R ? void 0 : y.get(v);
|
|
1251
|
+
k.push({
|
|
1252
|
+
id: w,
|
|
1253
|
+
values: {
|
|
1254
|
+
onPath: b !== void 0,
|
|
1255
|
+
order: b,
|
|
1256
|
+
distance: v === R ? 1 / 0 : u.dist[v]
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
});
|
|
1260
|
+
const I = [];
|
|
1261
|
+
return await N(e, "Marking the route", Array.from(t.edges.values()), (w) => {
|
|
1262
|
+
const v = i === null ? w.index : i[w.index] ?? R;
|
|
1263
|
+
I.push({ id: w.id, values: { onPath: O.has(v) } });
|
|
1264
|
+
}), {
|
|
1265
|
+
shape: "path",
|
|
1266
|
+
fields: [...Qe, { name: "distance", kind: "node", type: "number" }],
|
|
1267
|
+
nodes: k,
|
|
1268
|
+
edges: I,
|
|
1269
|
+
graph: {
|
|
1270
|
+
length: f.length,
|
|
1271
|
+
cost: f.length === 0 ? 0 : u.dist[l],
|
|
1272
|
+
hops: Math.max(f.length - 1, 0)
|
|
1273
|
+
},
|
|
1274
|
+
caveats: M({
|
|
1275
|
+
method: "dijkstra",
|
|
1276
|
+
direction: "undirected",
|
|
1277
|
+
weight: { attribute: "weight", meaning: "distance" },
|
|
1278
|
+
precision: p,
|
|
1279
|
+
notes: f.length === 0 ? [`No route runs from ${String(d)} to ${String(h)}.`] : [`Route from ${String(d)} to ${String(h)}.`]
|
|
1280
|
+
})
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
U.namespace = "graphty", U.type = "dijkstra", U.zodOptionsSchema = Jt, U.optionsSchema = {
|
|
1285
|
+
source: {
|
|
1286
|
+
type: "nodeId",
|
|
1287
|
+
default: null,
|
|
1288
|
+
label: "Source Node",
|
|
1289
|
+
description: "Starting node for shortest path (uses first node if not set)",
|
|
1290
|
+
required: !1
|
|
1291
|
+
},
|
|
1292
|
+
target: {
|
|
1293
|
+
type: "nodeId",
|
|
1294
|
+
default: null,
|
|
1295
|
+
label: "Target Node",
|
|
1296
|
+
description: "Destination node for shortest path (uses last node if not set)",
|
|
1297
|
+
required: !1
|
|
1298
|
+
},
|
|
1299
|
+
bidirectional: {
|
|
1300
|
+
type: "boolean",
|
|
1301
|
+
default: !0,
|
|
1302
|
+
label: "Bidirectional Search",
|
|
1303
|
+
description: "Accepted and ignored: the shortest-path search relaxes outwards from the source in one direction, and the route it finds is the same one either way",
|
|
1304
|
+
advanced: !0
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
let fe = U;
|
|
1308
|
+
S.register(fe);
|
|
1309
|
+
const Qt = {
|
|
1310
|
+
maxIterations: {
|
|
1311
|
+
schema: c.number().int().min(1).max(1e4).default(1e3),
|
|
1312
|
+
meta: {
|
|
1313
|
+
label: "Max Iterations",
|
|
1314
|
+
description: "Maximum power iterations",
|
|
1315
|
+
advanced: !0
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
tolerance: {
|
|
1319
|
+
schema: c.number().min(1e-10).max(0.01).default(1e-6),
|
|
1320
|
+
meta: {
|
|
1321
|
+
label: "Tolerance",
|
|
1322
|
+
description: "Convergence threshold",
|
|
1323
|
+
advanced: !0
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
normalized: {
|
|
1327
|
+
schema: c.boolean().default(!0),
|
|
1328
|
+
meta: {
|
|
1329
|
+
label: "Normalized",
|
|
1330
|
+
description: "Whether to normalize the final scores",
|
|
1331
|
+
advanced: !0
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
mode: {
|
|
1335
|
+
schema: c.enum(["in", "out", "total"]).default("total"),
|
|
1336
|
+
meta: {
|
|
1337
|
+
label: "Direction Mode",
|
|
1338
|
+
description: "Direction mode for directed graphs",
|
|
1339
|
+
advanced: !0
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
endpoints: {
|
|
1343
|
+
schema: c.boolean().default(!1),
|
|
1344
|
+
meta: {
|
|
1345
|
+
label: "Include Endpoints",
|
|
1346
|
+
description: "Whether to include endpoints in path calculations",
|
|
1347
|
+
advanced: !0
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}, _t = $({
|
|
1351
|
+
plainName: "Influence by association",
|
|
1352
|
+
technicalName: "eigenvector score"
|
|
1353
|
+
}), P = class P extends G {
|
|
1354
|
+
/**
|
|
1355
|
+
* The fields an eigenvector result publishes.
|
|
1356
|
+
* @returns The uniform node-metric fields.
|
|
1357
|
+
*/
|
|
1358
|
+
resultFields() {
|
|
1359
|
+
return _t;
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Score every node by the influence of its neighbours.
|
|
1363
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
1364
|
+
* @param nodeIds - The nodes to measure.
|
|
1365
|
+
* @returns One score per node, scaled as the options asked for.
|
|
1366
|
+
*/
|
|
1367
|
+
async measure(e, t) {
|
|
1368
|
+
const { maxIterations: n, tolerance: a, normalized: i, mode: o, endpoints: r } = this.schemaOptions, d = this._schemaOptions.startVector ?? void 0, h = this.algorithmGraph("undirected");
|
|
1369
|
+
e.report({
|
|
1370
|
+
phase: "iterating",
|
|
1371
|
+
completed: 0,
|
|
1372
|
+
total: t.length,
|
|
1373
|
+
message: `Power iteration, up to ${String(n)} passes.`
|
|
1374
|
+
});
|
|
1375
|
+
let m;
|
|
1376
|
+
try {
|
|
1377
|
+
m = bt(h, {
|
|
1378
|
+
normalized: i,
|
|
1379
|
+
maxIterations: n,
|
|
1380
|
+
tolerance: a,
|
|
1381
|
+
mode: o,
|
|
1382
|
+
endpoints: r,
|
|
1383
|
+
startVector: d
|
|
1384
|
+
});
|
|
1385
|
+
} catch (u) {
|
|
1386
|
+
if (!(u instanceof wt))
|
|
1387
|
+
throw u;
|
|
1388
|
+
const p = `${P.namespace}:${P.type}`;
|
|
1389
|
+
throw new et({
|
|
1390
|
+
code: "E_NOT_CONVERGED",
|
|
1391
|
+
message: `Eigenvector centrality did not converge in ${String(n)} iterations (tolerance ${String(a)}). Raise the maxIterations param (up to 10000), or loosen tolerance, and run it again.`,
|
|
1392
|
+
source: "run",
|
|
1393
|
+
target: { kind: "run", id: e.runId },
|
|
1394
|
+
details: { algorithm: p, maxIterations: n, tolerance: a },
|
|
1395
|
+
cause: u
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
e.signal.throwIfAborted();
|
|
1399
|
+
const l = [];
|
|
1400
|
+
return await F(t, e, "reading scores", (u) => {
|
|
1401
|
+
const p = m[String(u)];
|
|
1402
|
+
l.push({ id: u, values: p === void 0 ? {} : { value: p } });
|
|
1403
|
+
}), {
|
|
1404
|
+
nodes: l,
|
|
1405
|
+
// With `normalized`, the algorithm rescales its eigenvector so the lowest score is 0
|
|
1406
|
+
// and the highest is 1. Without it the raw unit-length eigenvector is published.
|
|
1407
|
+
normalization: i ? "min-max" : "none",
|
|
1408
|
+
caveats: {
|
|
1409
|
+
exact: !0,
|
|
1410
|
+
direction: "undirected",
|
|
1411
|
+
weight: null,
|
|
1412
|
+
precision: "f64",
|
|
1413
|
+
method: "power-iteration",
|
|
1414
|
+
// Measured, not assumed: the algorithm throws when it hits its iteration cap, and
|
|
1415
|
+
// that becomes E_NOT_CONVERGED above, so a result exists only when it converged.
|
|
1416
|
+
converged: !0,
|
|
1417
|
+
notes: [
|
|
1418
|
+
`Power iteration reached a tolerance of ${String(a)} within ${String(n)} passes.`,
|
|
1419
|
+
"Edge weights are not read."
|
|
1420
|
+
]
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
P.namespace = "graphty", P.type = "eigenvector", P.zodOptionsSchema = Qt, P.optionsSchema = {
|
|
1426
|
+
maxIterations: {
|
|
1427
|
+
type: "integer",
|
|
1428
|
+
default: 1e3,
|
|
1429
|
+
label: "Max Iterations",
|
|
1430
|
+
description: "Maximum power iterations",
|
|
1431
|
+
min: 1,
|
|
1432
|
+
max: 1e4,
|
|
1433
|
+
advanced: !0
|
|
1434
|
+
},
|
|
1435
|
+
tolerance: {
|
|
1436
|
+
type: "number",
|
|
1437
|
+
default: 1e-6,
|
|
1438
|
+
label: "Tolerance",
|
|
1439
|
+
description: "Convergence threshold",
|
|
1440
|
+
min: 1e-10,
|
|
1441
|
+
max: 0.01,
|
|
1442
|
+
advanced: !0
|
|
1443
|
+
},
|
|
1444
|
+
normalized: {
|
|
1445
|
+
type: "boolean",
|
|
1446
|
+
default: !0,
|
|
1447
|
+
label: "Normalized",
|
|
1448
|
+
description: "Whether to normalize the final scores",
|
|
1449
|
+
advanced: !0
|
|
1450
|
+
},
|
|
1451
|
+
mode: {
|
|
1452
|
+
type: "select",
|
|
1453
|
+
default: "total",
|
|
1454
|
+
label: "Direction Mode",
|
|
1455
|
+
description: "Direction mode for directed graphs",
|
|
1456
|
+
options: [
|
|
1457
|
+
{ value: "total", label: "Total (both directions)" },
|
|
1458
|
+
{ value: "in", label: "In-degree (incoming edges)" },
|
|
1459
|
+
{ value: "out", label: "Out-degree (outgoing edges)" }
|
|
1460
|
+
],
|
|
1461
|
+
advanced: !0
|
|
1462
|
+
},
|
|
1463
|
+
endpoints: {
|
|
1464
|
+
type: "boolean",
|
|
1465
|
+
default: !1,
|
|
1466
|
+
label: "Include Endpoints",
|
|
1467
|
+
description: "Whether to include endpoints in path calculations",
|
|
1468
|
+
advanced: !0
|
|
1469
|
+
}
|
|
1470
|
+
// Note: startVector is a Map type - programmatic only, not in schema
|
|
1471
|
+
};
|
|
1472
|
+
let ye = P;
|
|
1473
|
+
S.register(ye);
|
|
1474
|
+
const $e = class $e extends z {
|
|
1475
|
+
/**
|
|
1476
|
+
* Measure the distance between every pair of nodes, and give each node the distance to the
|
|
1477
|
+
* node furthest from it.
|
|
1478
|
+
*
|
|
1479
|
+
* Asked for every pair, the shortest-path question stops being one route: there is no route
|
|
1480
|
+
* here to put an `onPath` on. What it produces instead is a measurement of every node -- its
|
|
1481
|
+
* eccentricity, the distance to the furthest node it can reach -- so the result is shaped as
|
|
1482
|
+
* a node metric, and the widest and narrowest of those distances, the graph's diameter and
|
|
1483
|
+
* radius, ride along as facts about the whole graph.
|
|
1484
|
+
*
|
|
1485
|
+
* THE SHAPE IS WHAT MAKES IT PAINTABLE. A shape of "fact" declares no per-element field, so
|
|
1486
|
+
* the element would have had nothing to colour by and a consumer asking to encode
|
|
1487
|
+
* eccentricity on node colour would have been refused -- with no way to do it by hand
|
|
1488
|
+
* either, which is the case that has to be fixed here rather than worked around outside.
|
|
1489
|
+
* @param context - What the element gave the run.
|
|
1490
|
+
* @returns The all-pairs measurement, or null when there are no nodes to measure.
|
|
1491
|
+
*/
|
|
1492
|
+
async compute(e) {
|
|
1493
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
1494
|
+
if (t.length === 0)
|
|
1495
|
+
return null;
|
|
1496
|
+
const n = this.algorithmGraph("undirected");
|
|
1497
|
+
e.report({ phase: "Measuring every pair", total: null });
|
|
1498
|
+
const a = vt(n), i = /* @__PURE__ */ new Map();
|
|
1499
|
+
await N(e, "Measuring reach", t, (h) => {
|
|
1500
|
+
const m = a.distances.get(h);
|
|
1501
|
+
if (m === void 0) {
|
|
1502
|
+
i.set(h, 1 / 0);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
let l = 0;
|
|
1506
|
+
for (const u of m.values())
|
|
1507
|
+
isFinite(u) && u > l && (l = u);
|
|
1508
|
+
i.set(h, l);
|
|
1509
|
+
});
|
|
1510
|
+
let o = 0, r = 1 / 0;
|
|
1511
|
+
for (const h of i.values())
|
|
1512
|
+
isFinite(h) && (o = Math.max(o, h), r = Math.min(r, h));
|
|
1513
|
+
r === 1 / 0 && (r = 0);
|
|
1514
|
+
const d = [];
|
|
1515
|
+
return await N(e, "Publishing distances", t, (h) => {
|
|
1516
|
+
const m = i.get(h) ?? 1 / 0;
|
|
1517
|
+
isFinite(m) && d.push({ id: h, values: { value: m } });
|
|
1518
|
+
}), {
|
|
1519
|
+
shape: "node-metric",
|
|
1520
|
+
fields: [
|
|
1521
|
+
...je("node"),
|
|
1522
|
+
{ name: "diameter", kind: "graph", type: "number" },
|
|
1523
|
+
{ name: "radius", kind: "graph", type: "number" },
|
|
1524
|
+
{ name: "hasNegativeCycle", kind: "graph", type: "boolean" }
|
|
1525
|
+
],
|
|
1526
|
+
nodes: d,
|
|
1527
|
+
graph: { diameter: o, radius: r, hasNegativeCycle: a.hasNegativeCycle },
|
|
1528
|
+
caveats: M({
|
|
1529
|
+
method: "floyd-warshall",
|
|
1530
|
+
direction: "undirected",
|
|
1531
|
+
weight: { attribute: "weight", meaning: "distance" },
|
|
1532
|
+
notes: [
|
|
1533
|
+
"Every pair was measured. Each node's value is its eccentricity: the distance to the furthest node it can reach."
|
|
1534
|
+
]
|
|
1535
|
+
})
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
$e.namespace = "graphty", $e.type = "floyd-warshall";
|
|
1540
|
+
let be = $e;
|
|
1541
|
+
S.register(be);
|
|
1542
|
+
const ea = {
|
|
1543
|
+
maxCommunities: {
|
|
1544
|
+
schema: c.number().int().min(0).max(100).default(0),
|
|
1545
|
+
meta: {
|
|
1546
|
+
label: "Max Communities",
|
|
1547
|
+
description: "Stop when this many communities reached (0 = find optimal)"
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
minCommunitySize: {
|
|
1551
|
+
schema: c.number().int().min(1).max(100).default(1),
|
|
1552
|
+
meta: {
|
|
1553
|
+
label: "Min Community Size",
|
|
1554
|
+
description: "Minimum nodes per community",
|
|
1555
|
+
advanced: !0
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
maxIterations: {
|
|
1559
|
+
schema: c.number().int().min(1).max(1e4).default(1e3),
|
|
1560
|
+
meta: {
|
|
1561
|
+
label: "Max Iterations",
|
|
1562
|
+
description: "Maximum edge removal iterations before stopping",
|
|
1563
|
+
advanced: !0
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}, j = class j extends z {
|
|
1567
|
+
/**
|
|
1568
|
+
* Group the nodes into communities by cutting the edges the most routes run through.
|
|
1569
|
+
*
|
|
1570
|
+
* The method produces a dendrogram -- one partition per cut -- and the run publishes the cut
|
|
1571
|
+
* that scored the highest modularity, with that score. A graph with no edges falls apart at
|
|
1572
|
+
* the first step, and every node is then its own community.
|
|
1573
|
+
* @param context - What the element gave the run.
|
|
1574
|
+
* @returns The community result, or null when there are no nodes to group.
|
|
1575
|
+
*/
|
|
1576
|
+
async compute(e) {
|
|
1577
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
1578
|
+
if (t.length === 0)
|
|
1579
|
+
return null;
|
|
1580
|
+
const { maxCommunities: n, minCommunitySize: a, maxIterations: i } = this.schemaOptions, o = this.algorithmGraph("undirected");
|
|
1581
|
+
e.report({ phase: "Cutting bridges", total: null });
|
|
1582
|
+
const r = Nt(o, {
|
|
1583
|
+
maxCommunities: n > 0 ? n : void 0,
|
|
1584
|
+
minCommunitySize: a,
|
|
1585
|
+
maxIterations: i
|
|
1586
|
+
}), d = n > 0 ? r.filter((p) => p.communities.length <= n) : r, m = (d.length > 0 ? d : r.slice(0, 1)).reduce(
|
|
1587
|
+
(p, f) => p === void 0 || f.modularity > p.modularity ? f : p,
|
|
1588
|
+
void 0
|
|
1589
|
+
), l = /* @__PURE__ */ new Map();
|
|
1590
|
+
if (m !== void 0)
|
|
1591
|
+
for (let p = 0; p < m.communities.length; p++)
|
|
1592
|
+
for (const f of m.communities[p])
|
|
1593
|
+
l.set(f, p);
|
|
1594
|
+
else
|
|
1595
|
+
t.forEach((p, f) => l.set(p, f));
|
|
1596
|
+
const u = [];
|
|
1597
|
+
return await N(e, "Grouping nodes", t, (p) => {
|
|
1598
|
+
u.push({ id: p, values: { group: l.get(p) ?? 0 } });
|
|
1599
|
+
}), {
|
|
1600
|
+
shape: "community",
|
|
1601
|
+
fields: _(!0),
|
|
1602
|
+
nodes: u,
|
|
1603
|
+
graph: { modularity: m?.modularity ?? 0 },
|
|
1604
|
+
caveats: M({
|
|
1605
|
+
method: "girvan-newman",
|
|
1606
|
+
direction: "undirected",
|
|
1607
|
+
weight: { attribute: "weight", meaning: "strength" },
|
|
1608
|
+
notes: m === void 0 ? ["No edge could be cut, so every node is its own community."] : [`Kept the best of ${String(r.length)} successive cuts.`]
|
|
1609
|
+
})
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
j.namespace = "graphty", j.type = "girvan-newman", j.zodOptionsSchema = ea, j.optionsSchema = {
|
|
1614
|
+
maxCommunities: {
|
|
1615
|
+
type: "integer",
|
|
1616
|
+
default: 0,
|
|
1617
|
+
label: "Max Communities",
|
|
1618
|
+
description: "Stop when this many communities reached (0 = find optimal)",
|
|
1619
|
+
min: 0,
|
|
1620
|
+
max: 100
|
|
1621
|
+
},
|
|
1622
|
+
minCommunitySize: {
|
|
1623
|
+
type: "integer",
|
|
1624
|
+
default: 1,
|
|
1625
|
+
label: "Min Community Size",
|
|
1626
|
+
description: "Minimum nodes per community",
|
|
1627
|
+
min: 1,
|
|
1628
|
+
max: 100,
|
|
1629
|
+
advanced: !0
|
|
1630
|
+
},
|
|
1631
|
+
maxIterations: {
|
|
1632
|
+
type: "integer",
|
|
1633
|
+
default: 1e3,
|
|
1634
|
+
label: "Max Iterations",
|
|
1635
|
+
description: "Maximum edge removal iterations before stopping",
|
|
1636
|
+
min: 1,
|
|
1637
|
+
max: 1e4,
|
|
1638
|
+
advanced: !0
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
let we = j;
|
|
1642
|
+
S.register(we);
|
|
1643
|
+
const ta = {
|
|
1644
|
+
maxIterations: {
|
|
1645
|
+
schema: c.number().int().min(1).max(1e3).default(100),
|
|
1646
|
+
meta: {
|
|
1647
|
+
label: "Max Iterations",
|
|
1648
|
+
description: "Maximum iterations for hub/authority computation",
|
|
1649
|
+
advanced: !0
|
|
1650
|
+
}
|
|
1651
|
+
},
|
|
1652
|
+
tolerance: {
|
|
1653
|
+
schema: c.number().min(1e-10).max(0.01).default(1e-6),
|
|
1654
|
+
meta: {
|
|
1655
|
+
label: "Tolerance",
|
|
1656
|
+
description: "Convergence threshold",
|
|
1657
|
+
advanced: !0
|
|
1658
|
+
}
|
|
1659
|
+
},
|
|
1660
|
+
normalized: {
|
|
1661
|
+
schema: c.boolean().default(!0),
|
|
1662
|
+
meta: {
|
|
1663
|
+
label: "Normalized",
|
|
1664
|
+
description: "Whether to normalize the final hub/authority scores",
|
|
1665
|
+
advanced: !0
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
mode: {
|
|
1669
|
+
schema: c.enum(["in", "out", "total"]).default("total"),
|
|
1670
|
+
meta: {
|
|
1671
|
+
label: "Direction Mode",
|
|
1672
|
+
description: "Direction mode for directed graphs",
|
|
1673
|
+
advanced: !0
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
endpoints: {
|
|
1677
|
+
schema: c.boolean().default(!1),
|
|
1678
|
+
meta: {
|
|
1679
|
+
label: "Include Endpoints",
|
|
1680
|
+
description: "Whether to include endpoints in path calculations",
|
|
1681
|
+
advanced: !0
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}, aa = [
|
|
1685
|
+
...$({ plainName: "Hub and authority", technicalName: "combined HITS score" }),
|
|
1686
|
+
oe({ name: "hub", plainName: "Hub", technicalName: "hub score", kind: "node", type: "number" }),
|
|
1687
|
+
oe({
|
|
1688
|
+
name: "authority",
|
|
1689
|
+
plainName: "Authority",
|
|
1690
|
+
technicalName: "authority score",
|
|
1691
|
+
kind: "node",
|
|
1692
|
+
type: "number"
|
|
1693
|
+
})
|
|
1694
|
+
], V = class V extends G {
|
|
1695
|
+
/**
|
|
1696
|
+
* The fields a HITS result publishes.
|
|
1697
|
+
* @returns The uniform node-metric fields, plus `hub` and `authority`.
|
|
1698
|
+
*/
|
|
1699
|
+
resultFields() {
|
|
1700
|
+
return aa;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Score every node as a hub and as an authority.
|
|
1704
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
1705
|
+
* @param nodeIds - The nodes to measure.
|
|
1706
|
+
* @returns The combined score per node, with the two halves beside it.
|
|
1707
|
+
*/
|
|
1708
|
+
async measure(e, t) {
|
|
1709
|
+
const { maxIterations: n, tolerance: a, normalized: i, mode: o, endpoints: r } = this.schemaOptions, d = this.algorithmGraph("directed");
|
|
1710
|
+
e.report({
|
|
1711
|
+
phase: "iterating",
|
|
1712
|
+
completed: 0,
|
|
1713
|
+
total: t.length,
|
|
1714
|
+
message: `Hub and authority scores refine each other, up to ${String(n)} passes.`
|
|
1715
|
+
});
|
|
1716
|
+
const h = kt(d, {
|
|
1717
|
+
maxIterations: n,
|
|
1718
|
+
tolerance: a,
|
|
1719
|
+
normalized: i,
|
|
1720
|
+
mode: o,
|
|
1721
|
+
endpoints: r
|
|
1722
|
+
});
|
|
1723
|
+
e.signal.throwIfAborted();
|
|
1724
|
+
const m = [];
|
|
1725
|
+
return await F(t, e, "reading scores", (l) => {
|
|
1726
|
+
const u = h.hubs[String(l)], p = h.authorities[String(l)];
|
|
1727
|
+
if (u === void 0 || p === void 0) {
|
|
1728
|
+
m.push({ id: l, values: {} });
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
m.push({ id: l, values: { value: (u + p) / 2, hub: u, authority: p } });
|
|
1732
|
+
}), {
|
|
1733
|
+
nodes: m,
|
|
1734
|
+
// The combined score is the average of two vectors the algorithm scaled, which leaves
|
|
1735
|
+
// it scaled by neither of the two rules this field names. The notes say what was done.
|
|
1736
|
+
normalization: "none",
|
|
1737
|
+
caveats: {
|
|
1738
|
+
exact: !0,
|
|
1739
|
+
direction: "directed",
|
|
1740
|
+
weight: null,
|
|
1741
|
+
precision: "f64",
|
|
1742
|
+
method: "hits",
|
|
1743
|
+
// `converged` and `iterations` are deliberately absent: the implementation stops
|
|
1744
|
+
// either at its tolerance or at its iteration cap and reports neither.
|
|
1745
|
+
notes: [
|
|
1746
|
+
"The published value is the average of this node's hub score and its authority score.",
|
|
1747
|
+
i ? "The hub and authority vectors each have unit length, which is how the iteration leaves them." : "The hub and authority vectors were each divided by their own highest score.",
|
|
1748
|
+
`Iteration stops at a tolerance of ${String(a)} or after ${String(n)} passes, whichever comes first.`,
|
|
1749
|
+
"Whether it reached the tolerance is not reported by the implementation, so this run cannot say whether it converged.",
|
|
1750
|
+
"Edge weights are not read."
|
|
1751
|
+
]
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
V.namespace = "graphty", V.type = "hits", V.zodOptionsSchema = ta, V.optionsSchema = {
|
|
1757
|
+
maxIterations: {
|
|
1758
|
+
type: "integer",
|
|
1759
|
+
default: 100,
|
|
1760
|
+
label: "Max Iterations",
|
|
1761
|
+
description: "Maximum iterations for hub/authority computation",
|
|
1762
|
+
min: 1,
|
|
1763
|
+
max: 1e3,
|
|
1764
|
+
advanced: !0
|
|
1765
|
+
},
|
|
1766
|
+
tolerance: {
|
|
1767
|
+
type: "number",
|
|
1768
|
+
default: 1e-6,
|
|
1769
|
+
label: "Tolerance",
|
|
1770
|
+
description: "Convergence threshold",
|
|
1771
|
+
min: 1e-10,
|
|
1772
|
+
max: 0.01,
|
|
1773
|
+
advanced: !0
|
|
1774
|
+
},
|
|
1775
|
+
normalized: {
|
|
1776
|
+
type: "boolean",
|
|
1777
|
+
default: !0,
|
|
1778
|
+
label: "Normalized",
|
|
1779
|
+
description: "Whether to normalize the final hub/authority scores",
|
|
1780
|
+
advanced: !0
|
|
1781
|
+
},
|
|
1782
|
+
mode: {
|
|
1783
|
+
type: "select",
|
|
1784
|
+
default: "total",
|
|
1785
|
+
label: "Direction Mode",
|
|
1786
|
+
description: "Direction mode for directed graphs",
|
|
1787
|
+
options: [
|
|
1788
|
+
{ value: "total", label: "Total (both directions)" },
|
|
1789
|
+
{ value: "in", label: "In-degree (incoming edges)" },
|
|
1790
|
+
{ value: "out", label: "Out-degree (outgoing edges)" }
|
|
1791
|
+
],
|
|
1792
|
+
advanced: !0
|
|
1793
|
+
},
|
|
1794
|
+
endpoints: {
|
|
1795
|
+
type: "boolean",
|
|
1796
|
+
default: !1,
|
|
1797
|
+
label: "Include Endpoints",
|
|
1798
|
+
description: "Whether to include endpoints in path calculations",
|
|
1799
|
+
advanced: !0
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
let ve = V;
|
|
1803
|
+
S.register(ve);
|
|
1804
|
+
const na = {
|
|
1805
|
+
alpha: {
|
|
1806
|
+
schema: c.number().min(0.01).max(0.5).default(0.1),
|
|
1807
|
+
meta: {
|
|
1808
|
+
label: "Alpha (Attenuation)",
|
|
1809
|
+
description: "Attenuation factor - must be less than 1/λmax",
|
|
1810
|
+
step: 0.01
|
|
1811
|
+
}
|
|
1812
|
+
},
|
|
1813
|
+
beta: {
|
|
1814
|
+
schema: c.number().min(0).max(10).default(1),
|
|
1815
|
+
meta: {
|
|
1816
|
+
label: "Beta (Base Weight)",
|
|
1817
|
+
description: "Base centrality added to each node",
|
|
1818
|
+
step: 0.1,
|
|
1819
|
+
advanced: !0
|
|
1820
|
+
}
|
|
1821
|
+
},
|
|
1822
|
+
maxIterations: {
|
|
1823
|
+
schema: c.number().int().min(1).max(1e3).default(100),
|
|
1824
|
+
meta: {
|
|
1825
|
+
label: "Max Iterations",
|
|
1826
|
+
description: "Maximum iterations",
|
|
1827
|
+
advanced: !0
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
tolerance: {
|
|
1831
|
+
schema: c.number().min(1e-10).max(0.01).default(1e-6),
|
|
1832
|
+
meta: {
|
|
1833
|
+
label: "Tolerance",
|
|
1834
|
+
description: "Convergence threshold",
|
|
1835
|
+
advanced: !0
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
normalized: {
|
|
1839
|
+
schema: c.boolean().default(!0),
|
|
1840
|
+
meta: {
|
|
1841
|
+
label: "Normalized",
|
|
1842
|
+
description: "Whether to normalize the final scores",
|
|
1843
|
+
advanced: !0
|
|
1844
|
+
}
|
|
1845
|
+
},
|
|
1846
|
+
mode: {
|
|
1847
|
+
schema: c.enum(["in", "out", "total"]).default("total"),
|
|
1848
|
+
meta: {
|
|
1849
|
+
label: "Direction Mode",
|
|
1850
|
+
description: "Direction mode for directed graphs",
|
|
1851
|
+
advanced: !0
|
|
1852
|
+
}
|
|
1853
|
+
},
|
|
1854
|
+
endpoints: {
|
|
1855
|
+
schema: c.boolean().default(!1),
|
|
1856
|
+
meta: {
|
|
1857
|
+
label: "Include Endpoints",
|
|
1858
|
+
description: "Whether to include endpoints in path calculations",
|
|
1859
|
+
advanced: !0
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}, ia = $({
|
|
1863
|
+
plainName: "Influence at a distance",
|
|
1864
|
+
technicalName: "Katz score"
|
|
1865
|
+
}), q = class q extends G {
|
|
1866
|
+
/**
|
|
1867
|
+
* The fields a Katz result publishes.
|
|
1868
|
+
* @returns The uniform node-metric fields.
|
|
1869
|
+
*/
|
|
1870
|
+
resultFields() {
|
|
1871
|
+
return ia;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Score every node by the paths that reach it.
|
|
1875
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
1876
|
+
* @param nodeIds - The nodes to measure.
|
|
1877
|
+
* @returns One score per node, scaled as the options asked for.
|
|
1878
|
+
*/
|
|
1879
|
+
async measure(e, t) {
|
|
1880
|
+
const { alpha: n, beta: a, maxIterations: i, tolerance: o, normalized: r, mode: d, endpoints: h } = this.schemaOptions, m = this.algorithmGraph("undirected");
|
|
1881
|
+
e.report({
|
|
1882
|
+
phase: "iterating",
|
|
1883
|
+
completed: 0,
|
|
1884
|
+
total: t.length,
|
|
1885
|
+
message: `Attenuated path sums, up to ${String(i)} passes.`
|
|
1886
|
+
});
|
|
1887
|
+
const l = St(m, {
|
|
1888
|
+
normalized: r,
|
|
1889
|
+
alpha: n,
|
|
1890
|
+
beta: a,
|
|
1891
|
+
maxIterations: i,
|
|
1892
|
+
tolerance: o,
|
|
1893
|
+
mode: d,
|
|
1894
|
+
endpoints: h
|
|
1895
|
+
});
|
|
1896
|
+
e.signal.throwIfAborted();
|
|
1897
|
+
const u = [];
|
|
1898
|
+
return await F(t, e, "reading scores", (p) => {
|
|
1899
|
+
const f = l[String(p)];
|
|
1900
|
+
u.push({ id: p, values: f === void 0 ? {} : { value: f } });
|
|
1901
|
+
}), {
|
|
1902
|
+
nodes: u,
|
|
1903
|
+
// With `normalized`, the algorithm rescales so the lowest score is 0 and the highest
|
|
1904
|
+
// is 1. Without it the raw attenuated sums are published.
|
|
1905
|
+
normalization: r ? "min-max" : "none",
|
|
1906
|
+
caveats: {
|
|
1907
|
+
exact: !0,
|
|
1908
|
+
direction: "undirected",
|
|
1909
|
+
weight: null,
|
|
1910
|
+
precision: "f64",
|
|
1911
|
+
method: "katz-iteration",
|
|
1912
|
+
// `converged` and `iterations` are deliberately absent: the implementation stops
|
|
1913
|
+
// either at its tolerance or at its iteration cap and reports neither.
|
|
1914
|
+
notes: [
|
|
1915
|
+
`Every node starts with a base influence of ${String(a)}, and a path of length k contributes ${String(n)} to the power k.`,
|
|
1916
|
+
`Iteration stops at a tolerance of ${String(o)} or after ${String(i)} passes, whichever comes first.`,
|
|
1917
|
+
"Whether it reached the tolerance is not reported by the implementation, so this run cannot say whether it converged.",
|
|
1918
|
+
"Edge weights are not read."
|
|
1919
|
+
]
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
q.namespace = "graphty", q.type = "katz", q.zodOptionsSchema = na, q.optionsSchema = {
|
|
1925
|
+
alpha: {
|
|
1926
|
+
type: "number",
|
|
1927
|
+
default: 0.1,
|
|
1928
|
+
label: "Alpha (Attenuation)",
|
|
1929
|
+
description: "Attenuation factor - must be less than 1/λmax",
|
|
1930
|
+
min: 0.01,
|
|
1931
|
+
max: 0.5,
|
|
1932
|
+
step: 0.01
|
|
1933
|
+
},
|
|
1934
|
+
beta: {
|
|
1935
|
+
type: "number",
|
|
1936
|
+
default: 1,
|
|
1937
|
+
label: "Beta (Base Weight)",
|
|
1938
|
+
description: "Base centrality added to each node",
|
|
1939
|
+
min: 0,
|
|
1940
|
+
max: 10,
|
|
1941
|
+
step: 0.1,
|
|
1942
|
+
advanced: !0
|
|
1943
|
+
},
|
|
1944
|
+
maxIterations: {
|
|
1945
|
+
type: "integer",
|
|
1946
|
+
default: 100,
|
|
1947
|
+
label: "Max Iterations",
|
|
1948
|
+
description: "Maximum iterations",
|
|
1949
|
+
min: 1,
|
|
1950
|
+
max: 1e3,
|
|
1951
|
+
advanced: !0
|
|
1952
|
+
},
|
|
1953
|
+
tolerance: {
|
|
1954
|
+
type: "number",
|
|
1955
|
+
default: 1e-6,
|
|
1956
|
+
label: "Tolerance",
|
|
1957
|
+
description: "Convergence threshold",
|
|
1958
|
+
min: 1e-10,
|
|
1959
|
+
max: 0.01,
|
|
1960
|
+
advanced: !0
|
|
1961
|
+
},
|
|
1962
|
+
normalized: {
|
|
1963
|
+
type: "boolean",
|
|
1964
|
+
default: !0,
|
|
1965
|
+
label: "Normalized",
|
|
1966
|
+
description: "Whether to normalize the final scores",
|
|
1967
|
+
advanced: !0
|
|
1968
|
+
},
|
|
1969
|
+
mode: {
|
|
1970
|
+
type: "select",
|
|
1971
|
+
default: "total",
|
|
1972
|
+
label: "Direction Mode",
|
|
1973
|
+
description: "Direction mode for directed graphs",
|
|
1974
|
+
options: [
|
|
1975
|
+
{ value: "total", label: "Total (both directions)" },
|
|
1976
|
+
{ value: "in", label: "In-degree (incoming edges)" },
|
|
1977
|
+
{ value: "out", label: "Out-degree (outgoing edges)" }
|
|
1978
|
+
],
|
|
1979
|
+
advanced: !0
|
|
1980
|
+
},
|
|
1981
|
+
endpoints: {
|
|
1982
|
+
type: "boolean",
|
|
1983
|
+
default: !1,
|
|
1984
|
+
label: "Include Endpoints",
|
|
1985
|
+
description: "Whether to include endpoints in path calculations",
|
|
1986
|
+
advanced: !0
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
let Ne = q;
|
|
1990
|
+
S.register(Ne);
|
|
1991
|
+
const Ge = class Ge extends z {
|
|
1992
|
+
/**
|
|
1993
|
+
* Find the cheapest set of edges that still joins every node.
|
|
1994
|
+
*
|
|
1995
|
+
* A spanning tree is a set of edges, so the result is shaped as one: every edge says whether
|
|
1996
|
+
* it is in the network, the element counts how many are, and the run publishes the one number
|
|
1997
|
+
* the answer is actually read for -- what the network costs in total.
|
|
1998
|
+
* @param context - What the element gave the run.
|
|
1999
|
+
* @returns The edge set, or null when there are no edges to choose from.
|
|
2000
|
+
*/
|
|
2001
|
+
async compute(e) {
|
|
2002
|
+
const t = Array.from(this.graph.getDataManager().edges.values());
|
|
2003
|
+
if (t.length === 0)
|
|
2004
|
+
return null;
|
|
2005
|
+
const { edgeRemap: n, run: a } = this.accelerated("minimumSpanningTree", "undirected");
|
|
2006
|
+
e.report({ phase: "Choosing edges", total: null });
|
|
2007
|
+
const { value: i, precision: o } = await a((h, m) => h.minimumSpanningTree(m)), r = new Set(i.edges), d = [];
|
|
2008
|
+
return await N(e, "Marking the network", t, (h) => {
|
|
2009
|
+
const m = n === null ? h.index : n[h.index] ?? R;
|
|
2010
|
+
d.push({ id: h.id, values: { in: r.has(m) } });
|
|
2011
|
+
}), {
|
|
2012
|
+
shape: "edge-set",
|
|
2013
|
+
fields: We("edge", { name: "totalWeight", type: "number" }),
|
|
2014
|
+
edges: d,
|
|
2015
|
+
graph: { totalWeight: i.totalWeight },
|
|
2016
|
+
caveats: M({
|
|
2017
|
+
method: "kruskal",
|
|
2018
|
+
direction: "undirected",
|
|
2019
|
+
weight: { attribute: "weight", meaning: "distance" },
|
|
2020
|
+
precision: o,
|
|
2021
|
+
notes: [`The tree joins the graph with ${String(i.edges.length)} edges.`]
|
|
2022
|
+
})
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
Ge.namespace = "graphty", Ge.type = "kruskal";
|
|
2027
|
+
let ke = Ge;
|
|
2028
|
+
S.register(ke);
|
|
2029
|
+
const sa = {
|
|
2030
|
+
maxIterations: {
|
|
2031
|
+
schema: c.number().int().min(1).max(500).default(100),
|
|
2032
|
+
meta: {
|
|
2033
|
+
label: "Max Iterations",
|
|
2034
|
+
description: "Maximum label propagation rounds"
|
|
2035
|
+
}
|
|
2036
|
+
},
|
|
2037
|
+
randomSeed: {
|
|
2038
|
+
schema: c.number().int().min(0).max(2147483647).default(42),
|
|
2039
|
+
meta: {
|
|
2040
|
+
label: "Random Seed",
|
|
2041
|
+
description: "Seed for reproducible tie-breaking",
|
|
2042
|
+
advanced: !0
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}, A = class A extends z {
|
|
2046
|
+
/**
|
|
2047
|
+
* Group the nodes into communities by letting labels spread between neighbours.
|
|
2048
|
+
*
|
|
2049
|
+
* Publishes a group per node and nothing the shape does not derive. Label propagation does
|
|
2050
|
+
* not score its own partition, so it reports no modularity: the community shape makes that
|
|
2051
|
+
* field optional for exactly this reason, and a number invented to fill it would be read as
|
|
2052
|
+
* a measurement.
|
|
2053
|
+
* @param context - What the element gave the run.
|
|
2054
|
+
* @returns The community result, or null when there are no nodes to group.
|
|
2055
|
+
*/
|
|
2056
|
+
async compute(e) {
|
|
2057
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
2058
|
+
if (t.length === 0)
|
|
2059
|
+
return null;
|
|
2060
|
+
const { maxIterations: n, randomSeed: a } = this.schemaOptions, i = this.algorithmGraph("undirected");
|
|
2061
|
+
e.report({ phase: "Spreading labels", total: null });
|
|
2062
|
+
const o = Ot(i, {
|
|
2063
|
+
maxIterations: n,
|
|
2064
|
+
randomSeed: a
|
|
2065
|
+
}), r = [];
|
|
2066
|
+
return await N(e, "Grouping nodes", t, (d) => {
|
|
2067
|
+
r.push({ id: d, values: { group: o.communities.get(String(d)) ?? 0 } });
|
|
2068
|
+
}), {
|
|
2069
|
+
shape: "community",
|
|
2070
|
+
fields: _(!1),
|
|
2071
|
+
nodes: r,
|
|
2072
|
+
caveats: M({
|
|
2073
|
+
method: "label-propagation",
|
|
2074
|
+
direction: "undirected",
|
|
2075
|
+
weight: { attribute: "weight", meaning: "strength" },
|
|
2076
|
+
converged: o.converged,
|
|
2077
|
+
iterations: o.iterations,
|
|
2078
|
+
seed: a,
|
|
2079
|
+
notes: ["Label propagation does not score its own partition, so it reports no modularity."]
|
|
2080
|
+
})
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
A.namespace = "graphty", A.type = "label-propagation", A.zodOptionsSchema = sa, A.optionsSchema = {
|
|
2085
|
+
maxIterations: {
|
|
2086
|
+
type: "integer",
|
|
2087
|
+
default: 100,
|
|
2088
|
+
label: "Max Iterations",
|
|
2089
|
+
description: "Maximum label propagation rounds",
|
|
2090
|
+
min: 1,
|
|
2091
|
+
max: 500
|
|
2092
|
+
},
|
|
2093
|
+
randomSeed: {
|
|
2094
|
+
type: "integer",
|
|
2095
|
+
default: 42,
|
|
2096
|
+
label: "Random Seed",
|
|
2097
|
+
description: "Seed for reproducible tie-breaking",
|
|
2098
|
+
min: 0,
|
|
2099
|
+
max: 2147483647,
|
|
2100
|
+
advanced: !0
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
let Se = A;
|
|
2104
|
+
S.register(Se);
|
|
2105
|
+
const oa = {
|
|
2106
|
+
resolution: {
|
|
2107
|
+
schema: c.number().min(0.1).max(5).default(1),
|
|
2108
|
+
meta: {
|
|
2109
|
+
label: "Resolution",
|
|
2110
|
+
description: "Controls community granularity",
|
|
2111
|
+
step: 0.1
|
|
2112
|
+
}
|
|
2113
|
+
},
|
|
2114
|
+
randomSeed: {
|
|
2115
|
+
schema: c.number().int().min(0).max(2147483647).default(42),
|
|
2116
|
+
meta: {
|
|
2117
|
+
label: "Random Seed",
|
|
2118
|
+
description: "Seed for reproducible results",
|
|
2119
|
+
advanced: !0
|
|
2120
|
+
}
|
|
2121
|
+
},
|
|
2122
|
+
maxIterations: {
|
|
2123
|
+
schema: c.number().int().min(1).max(500).default(100),
|
|
2124
|
+
meta: {
|
|
2125
|
+
label: "Max Iterations",
|
|
2126
|
+
description: "Maximum refinement iterations",
|
|
2127
|
+
advanced: !0
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
threshold: {
|
|
2131
|
+
schema: c.number().min(1e-10).max(0.01).default(1e-6),
|
|
2132
|
+
meta: {
|
|
2133
|
+
label: "Threshold",
|
|
2134
|
+
description: "Convergence threshold",
|
|
2135
|
+
advanced: !0
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}, B = class B extends z {
|
|
2139
|
+
/**
|
|
2140
|
+
* Group the nodes into communities, refining each group before it is kept.
|
|
2141
|
+
*
|
|
2142
|
+
* Publishes the community shape's uniform fields: a group per node, and the modularity the
|
|
2143
|
+
* method reported. How many passes it took qualifies those numbers rather than being one of
|
|
2144
|
+
* them, so it travels in the caveats.
|
|
2145
|
+
* @param context - What the element gave the run.
|
|
2146
|
+
* @returns The community result, or null when there are no nodes to group.
|
|
2147
|
+
*/
|
|
2148
|
+
async compute(e) {
|
|
2149
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
2150
|
+
if (t.length === 0)
|
|
2151
|
+
return null;
|
|
2152
|
+
const { resolution: n, randomSeed: a, maxIterations: i, threshold: o } = this.schemaOptions, r = this.algorithmGraph("undirected");
|
|
2153
|
+
e.report({ phase: "Refining communities", total: null });
|
|
2154
|
+
const d = xt(r, {
|
|
2155
|
+
resolution: n,
|
|
2156
|
+
randomSeed: a,
|
|
2157
|
+
maxIterations: i,
|
|
2158
|
+
threshold: o
|
|
2159
|
+
}), h = [];
|
|
2160
|
+
return await N(e, "Grouping nodes", t, (m) => {
|
|
2161
|
+
h.push({ id: m, values: { group: d.communities.get(String(m)) ?? 0 } });
|
|
2162
|
+
}), {
|
|
2163
|
+
shape: "community",
|
|
2164
|
+
fields: _(!0),
|
|
2165
|
+
nodes: h,
|
|
2166
|
+
graph: { modularity: d.modularity },
|
|
2167
|
+
caveats: M({
|
|
2168
|
+
method: "leiden",
|
|
2169
|
+
direction: "undirected",
|
|
2170
|
+
weight: { attribute: "weight", meaning: "strength" },
|
|
2171
|
+
iterations: d.iterations,
|
|
2172
|
+
notes: [`Resolution ${String(n)}.`]
|
|
2173
|
+
})
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
B.namespace = "graphty", B.type = "leiden", B.zodOptionsSchema = oa, B.optionsSchema = {
|
|
2178
|
+
resolution: {
|
|
2179
|
+
type: "number",
|
|
2180
|
+
default: 1,
|
|
2181
|
+
label: "Resolution",
|
|
2182
|
+
description: "Controls community granularity",
|
|
2183
|
+
min: 0.1,
|
|
2184
|
+
max: 5,
|
|
2185
|
+
step: 0.1
|
|
2186
|
+
},
|
|
2187
|
+
randomSeed: {
|
|
2188
|
+
type: "integer",
|
|
2189
|
+
default: 42,
|
|
2190
|
+
label: "Random Seed",
|
|
2191
|
+
description: "Seed for reproducible results",
|
|
2192
|
+
min: 0,
|
|
2193
|
+
max: 2147483647,
|
|
2194
|
+
advanced: !0
|
|
2195
|
+
},
|
|
2196
|
+
maxIterations: {
|
|
2197
|
+
type: "integer",
|
|
2198
|
+
default: 100,
|
|
2199
|
+
label: "Max Iterations",
|
|
2200
|
+
description: "Maximum refinement iterations",
|
|
2201
|
+
min: 1,
|
|
2202
|
+
max: 500,
|
|
2203
|
+
advanced: !0
|
|
2204
|
+
},
|
|
2205
|
+
threshold: {
|
|
2206
|
+
type: "number",
|
|
2207
|
+
default: 1e-6,
|
|
2208
|
+
label: "Threshold",
|
|
2209
|
+
description: "Convergence threshold",
|
|
2210
|
+
min: 1e-10,
|
|
2211
|
+
max: 0.01,
|
|
2212
|
+
advanced: !0
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
let Oe = B;
|
|
2216
|
+
S.register(Oe);
|
|
2217
|
+
const ra = {
|
|
2218
|
+
resolution: {
|
|
2219
|
+
schema: c.number().min(0.1).max(5).default(1),
|
|
2220
|
+
meta: {
|
|
2221
|
+
label: "Resolution",
|
|
2222
|
+
description: "Higher = more communities, lower = fewer larger communities",
|
|
2223
|
+
step: 0.1
|
|
2224
|
+
}
|
|
2225
|
+
},
|
|
2226
|
+
maxIterations: {
|
|
2227
|
+
schema: c.number().int().min(1).max(500).default(100),
|
|
2228
|
+
meta: {
|
|
2229
|
+
label: "Max Iterations",
|
|
2230
|
+
description: "Maximum optimization iterations",
|
|
2231
|
+
advanced: !0
|
|
2232
|
+
}
|
|
2233
|
+
},
|
|
2234
|
+
tolerance: {
|
|
2235
|
+
schema: c.number().min(1e-10).max(0.01).default(1e-6),
|
|
2236
|
+
meta: {
|
|
2237
|
+
label: "Tolerance",
|
|
2238
|
+
description: "Minimum modularity improvement to continue",
|
|
2239
|
+
advanced: !0
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
useOptimized: {
|
|
2243
|
+
schema: c.boolean().default(!0),
|
|
2244
|
+
meta: {
|
|
2245
|
+
label: "Use Optimized",
|
|
2246
|
+
description: "Use optimized implementation for better performance on large graphs",
|
|
2247
|
+
advanced: !0
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
}, Y = class Y extends z {
|
|
2251
|
+
/**
|
|
2252
|
+
* Group the nodes into communities by optimising modularity.
|
|
2253
|
+
*
|
|
2254
|
+
* Publishes the community shape's uniform fields: a group per node, and the modularity the
|
|
2255
|
+
* method reported for its own partition. The group sizes, the group count and the size table
|
|
2256
|
+
* are derived from the groups when the result is built, not counted again here.
|
|
2257
|
+
* @param context - What the element gave the run.
|
|
2258
|
+
* @returns The community result, or null when there are no nodes to group.
|
|
2259
|
+
*/
|
|
2260
|
+
async compute(e) {
|
|
2261
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
2262
|
+
if (t.length === 0)
|
|
2263
|
+
return null;
|
|
2264
|
+
const { resolution: n, maxIterations: a, tolerance: i, useOptimized: o } = this.schemaOptions, r = this.algorithmGraph("undirected");
|
|
2265
|
+
e.report({ phase: "Optimizing modularity", total: null });
|
|
2266
|
+
const d = It(r, {
|
|
2267
|
+
resolution: n,
|
|
2268
|
+
maxIterations: a,
|
|
2269
|
+
tolerance: i,
|
|
2270
|
+
useOptimized: o
|
|
2271
|
+
}), h = /* @__PURE__ */ new Map();
|
|
2272
|
+
for (let l = 0; l < d.communities.length; l++)
|
|
2273
|
+
for (const u of d.communities[l])
|
|
2274
|
+
h.set(u, l);
|
|
2275
|
+
const m = [];
|
|
2276
|
+
return await N(e, "Grouping nodes", t, (l) => {
|
|
2277
|
+
m.push({ id: l, values: { group: h.get(l) ?? 0 } });
|
|
2278
|
+
}), {
|
|
2279
|
+
shape: "community",
|
|
2280
|
+
fields: _(!0),
|
|
2281
|
+
nodes: m,
|
|
2282
|
+
graph: { modularity: d.modularity },
|
|
2283
|
+
caveats: M({
|
|
2284
|
+
method: "louvain",
|
|
2285
|
+
direction: "undirected",
|
|
2286
|
+
weight: { attribute: "weight", meaning: "strength" },
|
|
2287
|
+
notes: [`Resolution ${String(n)}.`]
|
|
2288
|
+
})
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
Y.namespace = "graphty", Y.type = "louvain", Y.zodOptionsSchema = ra, Y.optionsSchema = {
|
|
2293
|
+
resolution: {
|
|
2294
|
+
type: "number",
|
|
2295
|
+
default: 1,
|
|
2296
|
+
label: "Resolution",
|
|
2297
|
+
description: "Higher = more communities, lower = fewer larger communities",
|
|
2298
|
+
min: 0.1,
|
|
2299
|
+
max: 5,
|
|
2300
|
+
step: 0.1
|
|
2301
|
+
},
|
|
2302
|
+
maxIterations: {
|
|
2303
|
+
type: "integer",
|
|
2304
|
+
default: 100,
|
|
2305
|
+
label: "Max Iterations",
|
|
2306
|
+
description: "Maximum optimization iterations",
|
|
2307
|
+
min: 1,
|
|
2308
|
+
max: 500,
|
|
2309
|
+
advanced: !0
|
|
2310
|
+
},
|
|
2311
|
+
tolerance: {
|
|
2312
|
+
type: "number",
|
|
2313
|
+
default: 1e-6,
|
|
2314
|
+
label: "Tolerance",
|
|
2315
|
+
description: "Minimum modularity improvement to continue",
|
|
2316
|
+
min: 1e-10,
|
|
2317
|
+
max: 0.01,
|
|
2318
|
+
advanced: !0
|
|
2319
|
+
},
|
|
2320
|
+
useOptimized: {
|
|
2321
|
+
type: "boolean",
|
|
2322
|
+
default: !0,
|
|
2323
|
+
label: "Use Optimized",
|
|
2324
|
+
description: "Use optimized implementation for better performance on large graphs",
|
|
2325
|
+
advanced: !0
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2328
|
+
let xe = Y;
|
|
2329
|
+
S.register(xe);
|
|
2330
|
+
const ca = {
|
|
2331
|
+
source: {
|
|
2332
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
2333
|
+
meta: {
|
|
2334
|
+
label: "Source Node",
|
|
2335
|
+
description: "Source node for flow network (uses first node if not set)"
|
|
2336
|
+
}
|
|
2337
|
+
},
|
|
2338
|
+
sink: {
|
|
2339
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
2340
|
+
meta: {
|
|
2341
|
+
label: "Sink Node",
|
|
2342
|
+
description: "Sink node for flow network (uses last node if not set)"
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
}, Z = class Z extends z {
|
|
2346
|
+
constructor() {
|
|
2347
|
+
super(...arguments), this.legacyOptions = null;
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Configure the algorithm with source and sink nodes
|
|
2351
|
+
* @param options - Configuration options
|
|
2352
|
+
* @param options.source - The source node for the flow network
|
|
2353
|
+
* @param options.sink - The sink node for the flow network
|
|
2354
|
+
* @returns This algorithm instance for chaining
|
|
2355
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
2356
|
+
*/
|
|
2357
|
+
configure(e) {
|
|
2358
|
+
return this.legacyOptions = e, this;
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Find the most that can flow from the source to the sink.
|
|
2362
|
+
*
|
|
2363
|
+
* The answer is a number per edge -- how much runs through it -- so the result is shaped as
|
|
2364
|
+
* an edge metric, and the ranking, the range and the rest come from that column. Two further
|
|
2365
|
+
* numbers travel with each edge because they cannot be recovered from the flow alone: the
|
|
2366
|
+
* capacity it was measured against, and the share of that capacity it used.
|
|
2367
|
+
* @param context - What the element gave the run.
|
|
2368
|
+
* @returns The flow per edge, or null when there is no network to measure.
|
|
2369
|
+
*/
|
|
2370
|
+
async compute(e) {
|
|
2371
|
+
const t = this.graph.getDataManager(), n = Array.from(t.edges.values()), a = Array.from(t.nodes.keys());
|
|
2372
|
+
if (n.length === 0 || a.length === 0)
|
|
2373
|
+
return null;
|
|
2374
|
+
const i = this.legacyOptions?.source ?? this._schemaOptions.source, o = this.legacyOptions?.sink ?? this._schemaOptions.sink, r = i === null ? String(a[0]) : re("max-flow", "source", i, a), d = o === null ? String(a[a.length - 1]) : re("max-flow", "sink", o, a), h = new _e({ directed: !0 });
|
|
2375
|
+
for (const y of a)
|
|
2376
|
+
h.addNode(String(y));
|
|
2377
|
+
const m = /* @__PURE__ */ new Map();
|
|
2378
|
+
for (const y of n) {
|
|
2379
|
+
const k = String(y.srcId), I = String(y.dstId), w = y.data, v = y, b = w?.capacity ?? w?.value ?? v.value ?? 1, C = typeof b == "number" ? b : 1;
|
|
2380
|
+
h.addEdge(k, I, C), m.set(T(k, I), C);
|
|
2381
|
+
}
|
|
2382
|
+
e.report({ phase: "Pushing flow", total: null });
|
|
2383
|
+
const l = Mt(h, r, d), u = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map();
|
|
2384
|
+
for (const [y, k] of l.flowGraph)
|
|
2385
|
+
for (const [I, w] of k)
|
|
2386
|
+
p.set(y, (p.get(y) ?? 0) + w), u.set(I, (u.get(I) ?? 0) + w);
|
|
2387
|
+
const f = [];
|
|
2388
|
+
await N(e, "Measuring edges", n, (y) => {
|
|
2389
|
+
const k = String(y.srcId), I = String(y.dstId), w = l.flowGraph.get(k)?.get(I) ?? 0, v = m.get(T(k, I)) ?? 1, b = v > 0 ? Math.abs(w) / v : 0;
|
|
2390
|
+
f.push({ id: y.id, values: { value: w, capacity: v, utilization: b } });
|
|
2391
|
+
});
|
|
2392
|
+
const O = [];
|
|
2393
|
+
return await N(e, "Measuring nodes", a, (y) => {
|
|
2394
|
+
const k = String(y), I = u.get(k) ?? 0, w = p.get(k) ?? 0, v = { netFlow: I - w };
|
|
2395
|
+
k === r ? v.role = "source" : k === d && (v.role = "sink"), O.push({ id: y, values: v });
|
|
2396
|
+
}), {
|
|
2397
|
+
shape: "edge-metric",
|
|
2398
|
+
fields: [
|
|
2399
|
+
...je("edge"),
|
|
2400
|
+
{ name: "capacity", kind: "edge", type: "number" },
|
|
2401
|
+
{ name: "utilization", kind: "edge", type: "number" },
|
|
2402
|
+
{ name: "netFlow", kind: "node", type: "number" },
|
|
2403
|
+
{ name: "role", kind: "node", type: "string" },
|
|
2404
|
+
{ name: "maxFlow", kind: "graph", type: "number" }
|
|
2405
|
+
],
|
|
2406
|
+
nodes: O,
|
|
2407
|
+
edges: f,
|
|
2408
|
+
graph: { maxFlow: l.maxFlow },
|
|
2409
|
+
caveats: M({
|
|
2410
|
+
method: "ford-fulkerson",
|
|
2411
|
+
direction: "directed",
|
|
2412
|
+
weight: { attribute: "capacity", meaning: "strength" },
|
|
2413
|
+
notes: [
|
|
2414
|
+
`Flow from ${r} to ${d}.`,
|
|
2415
|
+
...i === null || o === null ? [
|
|
2416
|
+
"The source or sink was chosen automatically (the first and last node); set the source and sink options to measure between the nodes you mean."
|
|
2417
|
+
] : [],
|
|
2418
|
+
...l.maxFlow === 0 ? [`There is no directed path from ${r} to ${d}, so no flow can run.`] : []
|
|
2419
|
+
]
|
|
2420
|
+
})
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
Z.namespace = "graphty", Z.type = "max-flow", Z.zodOptionsSchema = ca, Z.optionsSchema = {
|
|
2425
|
+
source: {
|
|
2426
|
+
type: "nodeId",
|
|
2427
|
+
default: null,
|
|
2428
|
+
label: "Source Node",
|
|
2429
|
+
description: "Source node for flow network (uses first node if not set)",
|
|
2430
|
+
required: !1
|
|
2431
|
+
},
|
|
2432
|
+
sink: {
|
|
2433
|
+
type: "nodeId",
|
|
2434
|
+
default: null,
|
|
2435
|
+
label: "Sink Node",
|
|
2436
|
+
description: "Sink node for flow network (uses last node if not set)",
|
|
2437
|
+
required: !1
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
let Ie = Z;
|
|
2441
|
+
S.register(Ie);
|
|
2442
|
+
const la = {
|
|
2443
|
+
source: {
|
|
2444
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
2445
|
+
meta: {
|
|
2446
|
+
label: "Source Node",
|
|
2447
|
+
description: "Source node for s-t cut (uses global min cut if not set)"
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
sink: {
|
|
2451
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
2452
|
+
meta: {
|
|
2453
|
+
label: "Sink Node",
|
|
2454
|
+
description: "Sink node for s-t cut (uses global min cut if not set)"
|
|
2455
|
+
}
|
|
2456
|
+
},
|
|
2457
|
+
useGlobalMinCut: {
|
|
2458
|
+
schema: c.boolean().default(!1),
|
|
2459
|
+
meta: {
|
|
2460
|
+
label: "Use Global Min Cut",
|
|
2461
|
+
description: "Use global minimum cut algorithm instead of s-t cut"
|
|
2462
|
+
}
|
|
2463
|
+
},
|
|
2464
|
+
useKarger: {
|
|
2465
|
+
schema: c.boolean().default(!1),
|
|
2466
|
+
meta: {
|
|
2467
|
+
label: "Use Karger Algorithm",
|
|
2468
|
+
description: "Use Karger's randomized algorithm instead of Stoer-Wagner for global min cut",
|
|
2469
|
+
advanced: !0
|
|
2470
|
+
}
|
|
2471
|
+
},
|
|
2472
|
+
kargerIterations: {
|
|
2473
|
+
schema: c.number().int().min(1).max(1e4).default(100),
|
|
2474
|
+
meta: {
|
|
2475
|
+
label: "Karger Iterations",
|
|
2476
|
+
description: "Number of iterations for Karger's algorithm (higher = better accuracy but slower)",
|
|
2477
|
+
advanced: !0
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}, X = class X extends z {
|
|
2481
|
+
constructor() {
|
|
2482
|
+
super(...arguments), this.legacyOptions = null;
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Configure the algorithm with source, sink, and useGlobalMinCut options
|
|
2486
|
+
* @param options - Configuration options
|
|
2487
|
+
* @param options.source - The source node for s-t cut (optional)
|
|
2488
|
+
* @param options.sink - The sink node for s-t cut (optional)
|
|
2489
|
+
* @param options.useGlobalMinCut - Whether to use global min cut (optional)
|
|
2490
|
+
* @returns This algorithm instance for chaining
|
|
2491
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
2492
|
+
*/
|
|
2493
|
+
configure(e) {
|
|
2494
|
+
return this.legacyOptions = e, this;
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Find the cheapest set of edges to remove to split the graph in two.
|
|
2498
|
+
*
|
|
2499
|
+
* A cut is a set of edges, so the result is shaped as one: every edge says whether it is in
|
|
2500
|
+
* the cut, the element counts how many are, and the run publishes what the cut costs, which
|
|
2501
|
+
* is the number the answer is read for. Every node also carries which side of the split it
|
|
2502
|
+
* ended up on, because a cut is only legible beside the two pieces it makes.
|
|
2503
|
+
*
|
|
2504
|
+
* Three methods can answer, and they answer different questions: Stoer-Wagner and Karger find
|
|
2505
|
+
* the cheapest cut anywhere in the graph, while the max-flow route finds the cheapest cut
|
|
2506
|
+
* between two named nodes. Karger is randomised and is the only one that may be wrong, so the
|
|
2507
|
+
* caveats say which ran and whether it was exact.
|
|
2508
|
+
* @param context - What the element gave the run.
|
|
2509
|
+
* @returns The edge set, or null when there is nothing to cut.
|
|
2510
|
+
*/
|
|
2511
|
+
async compute(e) {
|
|
2512
|
+
const t = this.graph.getDataManager(), n = Array.from(t.edges.values()), a = Array.from(t.nodes.keys());
|
|
2513
|
+
if (n.length === 0 || a.length === 0)
|
|
2514
|
+
return null;
|
|
2515
|
+
const i = /* @__PURE__ */ new Map(), o = new _e({ directed: !1 });
|
|
2516
|
+
for (const b of a)
|
|
2517
|
+
i.set(String(b), /* @__PURE__ */ new Map()), o.addNode(String(b));
|
|
2518
|
+
for (const b of n) {
|
|
2519
|
+
const C = String(b.srcId), D = String(b.dstId), H = b.data, ae = b, Ve = H?.value ?? ae.value ?? 1, Ke = typeof Ve == "number" ? Ve : 1;
|
|
2520
|
+
i.get(C)?.set(D, Ke), i.get(D)?.set(C, Ke), o.addEdge(C, D, Ke);
|
|
2521
|
+
}
|
|
2522
|
+
const r = this.legacyOptions?.useGlobalMinCut ?? this._schemaOptions.useGlobalMinCut, d = this.legacyOptions?.source ?? this._schemaOptions.source, h = this.legacyOptions?.sink ?? this._schemaOptions.sink, { useKarger: m, kargerIterations: l } = this._schemaOptions;
|
|
2523
|
+
let u, p, f, O, y, k;
|
|
2524
|
+
if (e.report({ phase: "Finding the cut", total: null }), r || d === null && h === null)
|
|
2525
|
+
m ? (y = "karger", { partition1: u, partition2: p, cutEdges: f, cutValue: O } = Ct(i, l)) : (y = "stoer-wagner", { partition1: u, partition2: p, cutEdges: f, cutValue: O } = zt(i));
|
|
2526
|
+
else {
|
|
2527
|
+
y = "min-st-cut";
|
|
2528
|
+
const b = d === null ? String(a[0]) : re("min-cut", "source", d, a), C = h === null ? String(a[a.length - 1]) : re("min-cut", "sink", h, a);
|
|
2529
|
+
(d === null || h === null) && (k = `Only one end was set, so the other was chosen automatically (cut between ${b} and ${C}); set both source and sink to cut between the nodes you mean.`), { partition1: u, partition2: p, cutEdges: f, cutValue: O } = Tt(o, b, C);
|
|
2530
|
+
}
|
|
2531
|
+
const I = /* @__PURE__ */ new Map();
|
|
2532
|
+
for (const b of f)
|
|
2533
|
+
I.set(T(b.from, b.to), b.weight), I.set(T(b.to, b.from), b.weight);
|
|
2534
|
+
const w = [];
|
|
2535
|
+
await N(e, "Marking the cut", n, (b) => {
|
|
2536
|
+
const C = I.get(T(b.srcId, b.dstId));
|
|
2537
|
+
w.push({ id: b.id, values: { in: C !== void 0 } });
|
|
2538
|
+
});
|
|
2539
|
+
const v = [];
|
|
2540
|
+
return await N(e, "Marking the sides", a, (b) => {
|
|
2541
|
+
const C = u.has(String(b));
|
|
2542
|
+
v.push({ id: b, values: { side: C ? "1" : "2" } });
|
|
2543
|
+
}), {
|
|
2544
|
+
shape: "edge-set",
|
|
2545
|
+
fields: [
|
|
2546
|
+
...We("edge", { name: "cutValue", type: "number" }),
|
|
2547
|
+
{ name: "side", kind: "node", type: "string" }
|
|
2548
|
+
],
|
|
2549
|
+
nodes: v,
|
|
2550
|
+
edges: w,
|
|
2551
|
+
graph: { cutValue: O },
|
|
2552
|
+
caveats: M({
|
|
2553
|
+
method: y,
|
|
2554
|
+
direction: "undirected",
|
|
2555
|
+
weight: { attribute: "weight", meaning: "strength" },
|
|
2556
|
+
exact: y !== "karger",
|
|
2557
|
+
iterations: y === "karger" ? l : void 0,
|
|
2558
|
+
notes: [
|
|
2559
|
+
y === "karger" ? "Karger's method is randomised: it finds the cheapest cut with high probability, not certainty." : `The cut separates ${String(u.size)} nodes from ${String(p.size)}.`,
|
|
2560
|
+
...k === void 0 ? [] : [k]
|
|
2561
|
+
]
|
|
2562
|
+
})
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
X.namespace = "graphty", X.type = "min-cut", X.zodOptionsSchema = la, X.optionsSchema = {
|
|
2567
|
+
source: {
|
|
2568
|
+
type: "nodeId",
|
|
2569
|
+
default: null,
|
|
2570
|
+
label: "Source Node",
|
|
2571
|
+
description: "Source node for s-t cut (uses global min cut if not set)",
|
|
2572
|
+
required: !1
|
|
2573
|
+
},
|
|
2574
|
+
sink: {
|
|
2575
|
+
type: "nodeId",
|
|
2576
|
+
default: null,
|
|
2577
|
+
label: "Sink Node",
|
|
2578
|
+
description: "Sink node for s-t cut (uses global min cut if not set)",
|
|
2579
|
+
required: !1
|
|
2580
|
+
},
|
|
2581
|
+
useGlobalMinCut: {
|
|
2582
|
+
type: "boolean",
|
|
2583
|
+
default: !1,
|
|
2584
|
+
label: "Use Global Min Cut",
|
|
2585
|
+
description: "Use global minimum cut algorithm instead of s-t cut",
|
|
2586
|
+
required: !1
|
|
2587
|
+
},
|
|
2588
|
+
useKarger: {
|
|
2589
|
+
type: "boolean",
|
|
2590
|
+
default: !1,
|
|
2591
|
+
label: "Use Karger Algorithm",
|
|
2592
|
+
description: "Use Karger's randomized algorithm instead of Stoer-Wagner for global min cut",
|
|
2593
|
+
advanced: !0
|
|
2594
|
+
},
|
|
2595
|
+
kargerIterations: {
|
|
2596
|
+
type: "integer",
|
|
2597
|
+
default: 100,
|
|
2598
|
+
label: "Karger Iterations",
|
|
2599
|
+
description: "Number of iterations for Karger's algorithm (higher = better accuracy but slower)",
|
|
2600
|
+
min: 1,
|
|
2601
|
+
max: 1e4,
|
|
2602
|
+
advanced: !0
|
|
2603
|
+
}
|
|
2604
|
+
};
|
|
2605
|
+
let Me = X;
|
|
2606
|
+
S.register(Me);
|
|
2607
|
+
const Ze = {
|
|
2608
|
+
dampingFactor: {
|
|
2609
|
+
schema: c.number().min(0).max(1).default(0.85),
|
|
2610
|
+
meta: {
|
|
2611
|
+
label: "Damping Factor",
|
|
2612
|
+
description: "Probability of following a link (0.85 is standard for web graphs)",
|
|
2613
|
+
step: 0.05
|
|
2614
|
+
}
|
|
2615
|
+
},
|
|
2616
|
+
maxIterations: {
|
|
2617
|
+
schema: c.number().int().min(1).max(1e3).default(100),
|
|
2618
|
+
meta: {
|
|
2619
|
+
label: "Max Iterations",
|
|
2620
|
+
description: "Maximum power iterations before stopping",
|
|
2621
|
+
advanced: !0
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
tolerance: {
|
|
2625
|
+
schema: c.number().min(1e-10).max(0.1).default(1e-6),
|
|
2626
|
+
meta: {
|
|
2627
|
+
label: "Tolerance",
|
|
2628
|
+
description: "Convergence threshold for early stopping",
|
|
2629
|
+
advanced: !0
|
|
2630
|
+
}
|
|
2631
|
+
},
|
|
2632
|
+
weight: {
|
|
2633
|
+
schema: c.string().nullable().default(null),
|
|
2634
|
+
meta: {
|
|
2635
|
+
label: "Weight Attribute",
|
|
2636
|
+
description: "Edge attribute name for weighted PageRank (empty = unweighted)",
|
|
2637
|
+
advanced: !0
|
|
2638
|
+
}
|
|
2639
|
+
},
|
|
2640
|
+
useDelta: {
|
|
2641
|
+
schema: c.boolean().default(!0),
|
|
2642
|
+
meta: {
|
|
2643
|
+
label: "Use Delta Optimization",
|
|
2644
|
+
description: "Use delta-based optimization for faster convergence on large graphs",
|
|
2645
|
+
advanced: !0
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
}, da = $({
|
|
2649
|
+
plainName: "Influence",
|
|
2650
|
+
technicalName: "PageRank score"
|
|
2651
|
+
}), ha = 100;
|
|
2652
|
+
function ua(s) {
|
|
2653
|
+
return s.hasPersonalization ? "a personalization vector was given, and only the reference implementation takes one" : s.hasInitialRanks ? "initial ranks were given, and only the reference implementation takes them" : s.directed ? null : "the graph is undirected, and rank flows along out-edges";
|
|
2654
|
+
}
|
|
2655
|
+
const J = class J extends G {
|
|
2656
|
+
/**
|
|
2657
|
+
* Creates a new PageRank algorithm instance
|
|
2658
|
+
* @param g - The graph to run the algorithm on
|
|
2659
|
+
* @param options - Optional configuration options
|
|
2660
|
+
*/
|
|
2661
|
+
constructor(e, t) {
|
|
2662
|
+
super(e, t), this.zodOptions = Ut(Ze, t ?? {}), this.programmaticOptions = {
|
|
2663
|
+
initialRanks: t?.initialRanks ?? null,
|
|
2664
|
+
personalization: t?.personalization ?? null
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* The fields a PageRank result publishes.
|
|
2669
|
+
* @returns The uniform node-metric fields.
|
|
2670
|
+
*/
|
|
2671
|
+
resultFields() {
|
|
2672
|
+
return da;
|
|
2673
|
+
}
|
|
2674
|
+
/**
|
|
2675
|
+
* Rank every node by the influence flowing into it.
|
|
2676
|
+
* @param context - Where progress goes and where cancellation arrives.
|
|
2677
|
+
* @param nodeIds - The nodes to measure.
|
|
2678
|
+
* @returns One raw rank per node, unscaled.
|
|
2679
|
+
*/
|
|
2680
|
+
async measure(e, t) {
|
|
2681
|
+
const { dampingFactor: n, maxIterations: a, tolerance: i, weight: o, useDelta: r } = this.zodOptions, d = this.programmaticOptions.initialRanks ?? void 0, h = this.programmaticOptions.personalization ?? void 0, m = ua({
|
|
2682
|
+
directed: this.graph.getDataManager().getSnapshot().directed,
|
|
2683
|
+
hasInitialRanks: d !== void 0,
|
|
2684
|
+
hasPersonalization: h !== void 0
|
|
2685
|
+
});
|
|
2686
|
+
if (m === null) {
|
|
2687
|
+
const { snapshot: y, run: k } = this.accelerated("pageRank", "directed");
|
|
2688
|
+
e.report({
|
|
2689
|
+
phase: "iterating",
|
|
2690
|
+
completed: 0,
|
|
2691
|
+
total: t.length,
|
|
2692
|
+
message: `Power iteration, up to ${String(a)} passes.`
|
|
2693
|
+
});
|
|
2694
|
+
const { value: I, precision: w } = await k(
|
|
2695
|
+
(D, H) => D.pageRank(H, {
|
|
2696
|
+
dampingFactor: n,
|
|
2697
|
+
maxIterations: a,
|
|
2698
|
+
tolerance: i,
|
|
2699
|
+
// ONE weight column, so naming an attribute is the same request as asking for
|
|
2700
|
+
// a weighted run: the snapshot carries the weight the element resolved.
|
|
2701
|
+
weighted: o !== null
|
|
2702
|
+
})
|
|
2703
|
+
);
|
|
2704
|
+
e.signal.throwIfAborted();
|
|
2705
|
+
const { ids: v } = y, b = [];
|
|
2706
|
+
await F(t, e, "reading ranks", (D) => {
|
|
2707
|
+
const H = v.indexOf(D), ae = H === R ? void 0 : I.scores[H];
|
|
2708
|
+
b.push({ id: D, values: ae === void 0 ? {} : { value: ae } });
|
|
2709
|
+
});
|
|
2710
|
+
const C = [
|
|
2711
|
+
`A reader follows a link with probability ${String(n)} and jumps to a random node otherwise.`,
|
|
2712
|
+
"The ranks sum to 1 across the graph."
|
|
2713
|
+
];
|
|
2714
|
+
return o === null && C.push("Edge weights are not read."), r && C.push(
|
|
2715
|
+
"Power iteration ran: the delta optimisation is the CPU reference implementation's own and has no index-based counterpart, so it was not taken."
|
|
2716
|
+
), {
|
|
2717
|
+
nodes: b,
|
|
2718
|
+
normalization: "none",
|
|
2719
|
+
caveats: {
|
|
2720
|
+
exact: !0,
|
|
2721
|
+
direction: "directed",
|
|
2722
|
+
weight: o === null ? null : { attribute: o, meaning: "strength" },
|
|
2723
|
+
precision: w,
|
|
2724
|
+
method: "power-iteration",
|
|
2725
|
+
converged: I.converged,
|
|
2726
|
+
iterations: I.iterations,
|
|
2727
|
+
notes: C
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
const l = this.algorithmGraph("directed"), u = r && l.nodeCount > ha;
|
|
2732
|
+
e.report({
|
|
2733
|
+
phase: "iterating",
|
|
2734
|
+
completed: 0,
|
|
2735
|
+
total: t.length,
|
|
2736
|
+
message: `Power iteration, up to ${String(a)} passes.`
|
|
2737
|
+
});
|
|
2738
|
+
const p = Et(l, {
|
|
2739
|
+
dampingFactor: n,
|
|
2740
|
+
maxIterations: a,
|
|
2741
|
+
tolerance: i,
|
|
2742
|
+
weight: o ?? void 0,
|
|
2743
|
+
useDelta: r,
|
|
2744
|
+
initialRanks: d,
|
|
2745
|
+
personalization: h
|
|
2746
|
+
});
|
|
2747
|
+
e.signal.throwIfAborted();
|
|
2748
|
+
const f = [];
|
|
2749
|
+
await F(t, e, "reading ranks", (y) => {
|
|
2750
|
+
const k = p.ranks[String(y)];
|
|
2751
|
+
f.push({ id: y, values: k === void 0 ? {} : { value: k } });
|
|
2752
|
+
});
|
|
2753
|
+
const O = [
|
|
2754
|
+
`A reader follows a link with probability ${String(n)} and jumps to a random node otherwise.`,
|
|
2755
|
+
"The ranks sum to 1 across the graph.",
|
|
2756
|
+
`Computed on the CPU reference implementation: ${m}.`
|
|
2757
|
+
];
|
|
2758
|
+
return u && O.push(
|
|
2759
|
+
"The delta method ran, and it reports neither how many passes it took nor whether it converged, so this run cannot say."
|
|
2760
|
+
), o === null && O.push("Edge weights are not read."), {
|
|
2761
|
+
nodes: f,
|
|
2762
|
+
// Raw ranks, published as they were computed.
|
|
2763
|
+
normalization: "none",
|
|
2764
|
+
caveats: {
|
|
2765
|
+
exact: !0,
|
|
2766
|
+
direction: "directed",
|
|
2767
|
+
weight: o === null ? null : { attribute: o, meaning: "strength" },
|
|
2768
|
+
precision: "f64",
|
|
2769
|
+
method: u ? "delta-pagerank" : "power-iteration",
|
|
2770
|
+
// Present only when the method that ran measured them. Absent is the honest
|
|
2771
|
+
// answer where it did not; a hard-coded `true` was the defect this replaces.
|
|
2772
|
+
...u ? {} : { converged: p.converged, iterations: p.iterations },
|
|
2773
|
+
notes: O
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
J.namespace = "graphty", J.type = "pagerank", J.zodOptionsSchema = Ze, J.optionsSchema = {
|
|
2779
|
+
dampingFactor: {
|
|
2780
|
+
type: "number",
|
|
2781
|
+
default: 0.85,
|
|
2782
|
+
label: "Damping Factor",
|
|
2783
|
+
description: "Probability of following a link (0.85 is standard for web graphs)",
|
|
2784
|
+
min: 0,
|
|
2785
|
+
max: 1,
|
|
2786
|
+
step: 0.05
|
|
2787
|
+
},
|
|
2788
|
+
maxIterations: {
|
|
2789
|
+
type: "integer",
|
|
2790
|
+
default: 100,
|
|
2791
|
+
label: "Max Iterations",
|
|
2792
|
+
description: "Maximum power iterations before stopping",
|
|
2793
|
+
min: 1,
|
|
2794
|
+
max: 1e3,
|
|
2795
|
+
advanced: !0
|
|
2796
|
+
},
|
|
2797
|
+
tolerance: {
|
|
2798
|
+
type: "number",
|
|
2799
|
+
default: 1e-6,
|
|
2800
|
+
label: "Tolerance",
|
|
2801
|
+
description: "Convergence threshold for early stopping",
|
|
2802
|
+
min: 1e-10,
|
|
2803
|
+
max: 0.1,
|
|
2804
|
+
advanced: !0
|
|
2805
|
+
},
|
|
2806
|
+
weight: {
|
|
2807
|
+
type: "string",
|
|
2808
|
+
default: null,
|
|
2809
|
+
label: "Weight Attribute",
|
|
2810
|
+
description: "Edge attribute name for weighted PageRank (empty = unweighted)",
|
|
2811
|
+
advanced: !0
|
|
2812
|
+
},
|
|
2813
|
+
useDelta: {
|
|
2814
|
+
type: "boolean",
|
|
2815
|
+
default: !0,
|
|
2816
|
+
label: "Use Delta Optimization",
|
|
2817
|
+
description: "Use delta-based optimization for faster convergence on large graphs",
|
|
2818
|
+
advanced: !0
|
|
2819
|
+
}
|
|
2820
|
+
// Note: initialRanks and personalization are Map types - programmatic only, not in schema
|
|
2821
|
+
};
|
|
2822
|
+
let Ce = J;
|
|
2823
|
+
S.register(Ce);
|
|
2824
|
+
const pa = {
|
|
2825
|
+
startNode: {
|
|
2826
|
+
schema: c.union([c.string(), c.number()]).nullable().default(null),
|
|
2827
|
+
meta: {
|
|
2828
|
+
label: "Start Node",
|
|
2829
|
+
description: "Starting node for MST growth (uses first node if not set)"
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}, Q = class Q extends z {
|
|
2833
|
+
constructor() {
|
|
2834
|
+
super(...arguments), this.legacyOptions = null;
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Configure the algorithm with an optional start node
|
|
2838
|
+
* @param options - Configuration options
|
|
2839
|
+
* @param options.startNode - The optional start node ID
|
|
2840
|
+
* @returns This algorithm instance for chaining
|
|
2841
|
+
* @deprecated Use constructor options instead. This method is kept for backward compatibility.
|
|
2842
|
+
*/
|
|
2843
|
+
configure(e) {
|
|
2844
|
+
return this.legacyOptions = { startNode: e.startNode ?? null }, this;
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Find the cheapest set of edges that still joins every node.
|
|
2848
|
+
*
|
|
2849
|
+
* A spanning tree is a set of edges, so the result is shaped as one: every edge says whether
|
|
2850
|
+
* it is in the network, the element counts how many are, and the run publishes the one number
|
|
2851
|
+
* the answer is actually read for -- what the network costs in total.
|
|
2852
|
+
* @param context - What the element gave the run.
|
|
2853
|
+
* @returns The edge set, or null when there are no edges to choose from.
|
|
2854
|
+
*/
|
|
2855
|
+
async compute(e) {
|
|
2856
|
+
const t = Array.from(this.graph.getDataManager().edges.values());
|
|
2857
|
+
if (t.length === 0)
|
|
2858
|
+
return null;
|
|
2859
|
+
const n = this.legacyOptions?.startNode ?? this._schemaOptions.startNode ?? void 0, a = this.algorithmGraph("undirected");
|
|
2860
|
+
e.report({ phase: "Choosing edges", total: null });
|
|
2861
|
+
const i = Dt(a, n), o = /* @__PURE__ */ new Set();
|
|
2862
|
+
for (const d of i.edges)
|
|
2863
|
+
o.add(T(d.source, d.target)), o.add(T(d.target, d.source));
|
|
2864
|
+
const r = [];
|
|
2865
|
+
return await N(e, "Marking the network", t, (d) => {
|
|
2866
|
+
r.push({ id: d.id, values: { in: o.has(T(d.srcId, d.dstId)) } });
|
|
2867
|
+
}), {
|
|
2868
|
+
shape: "edge-set",
|
|
2869
|
+
fields: We("edge", { name: "totalWeight", type: "number" }),
|
|
2870
|
+
edges: r,
|
|
2871
|
+
graph: { totalWeight: i.totalWeight },
|
|
2872
|
+
caveats: M({
|
|
2873
|
+
method: "prim",
|
|
2874
|
+
direction: "undirected",
|
|
2875
|
+
weight: { attribute: "weight", meaning: "distance" },
|
|
2876
|
+
notes: [`The tree joins the graph with ${String(i.edges.length)} edges.`]
|
|
2877
|
+
})
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
Q.namespace = "graphty", Q.type = "prim", Q.zodOptionsSchema = pa, Q.optionsSchema = {
|
|
2882
|
+
startNode: {
|
|
2883
|
+
type: "nodeId",
|
|
2884
|
+
default: null,
|
|
2885
|
+
label: "Start Node",
|
|
2886
|
+
description: "Starting node for MST growth (uses first node if not set)",
|
|
2887
|
+
required: !1
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
let ze = Q;
|
|
2891
|
+
S.register(ze);
|
|
2892
|
+
const He = class He extends z {
|
|
2893
|
+
/**
|
|
2894
|
+
* Find the pieces of the graph that a directed path can cross both ways.
|
|
2895
|
+
*
|
|
2896
|
+
* The same community shape as the weak reading, run at the other strength: two nodes share a
|
|
2897
|
+
* group only when a directed path runs each way between them. The caveats say which strength
|
|
2898
|
+
* ran, because on a directed graph the two answers differ and the numbers do not say so.
|
|
2899
|
+
* @param context - What the element gave the run.
|
|
2900
|
+
* @returns The community result, or null when there are no nodes to group.
|
|
2901
|
+
*/
|
|
2902
|
+
async compute(e) {
|
|
2903
|
+
const t = Array.from(this.graph.getDataManager().nodes.keys());
|
|
2904
|
+
if (t.length === 0)
|
|
2905
|
+
return null;
|
|
2906
|
+
const n = this.algorithmGraph("directed");
|
|
2907
|
+
e.report({ phase: "Finding pieces", total: null });
|
|
2908
|
+
const a = Pt(n), i = /* @__PURE__ */ new Map();
|
|
2909
|
+
for (let r = 0; r < a.length; r++)
|
|
2910
|
+
for (const d of a[r])
|
|
2911
|
+
i.set(d, r);
|
|
2912
|
+
const o = [];
|
|
2913
|
+
return await N(e, "Grouping nodes", t, (r) => {
|
|
2914
|
+
o.push({ id: r, values: { group: i.get(r) ?? 0 } });
|
|
2915
|
+
}), {
|
|
2916
|
+
shape: "community",
|
|
2917
|
+
fields: _(!1),
|
|
2918
|
+
nodes: o,
|
|
2919
|
+
caveats: M({
|
|
2920
|
+
method: "strongly-connected-components",
|
|
2921
|
+
direction: "directed",
|
|
2922
|
+
weight: null,
|
|
2923
|
+
notes: ["Strength: strong. Two nodes share a piece only when a directed path runs each way."]
|
|
2924
|
+
})
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
He.namespace = "graphty", He.type = "scc";
|
|
2929
|
+
let Te = He;
|
|
2930
|
+
S.register(Te);
|
|
2931
|
+
const ma = "results.$";
|
|
2932
|
+
function g(s) {
|
|
2933
|
+
return { ...s, path: `${ma}.${s.name}` };
|
|
2934
|
+
}
|
|
2935
|
+
function E(s, e) {
|
|
2936
|
+
return [
|
|
2937
|
+
g({
|
|
2938
|
+
name: "value",
|
|
2939
|
+
plainName: e.plainName,
|
|
2940
|
+
technicalName: e.technicalName,
|
|
2941
|
+
kind: s,
|
|
2942
|
+
type: e.type ?? "number",
|
|
2943
|
+
...e.unit === void 0 ? {} : { unit: e.unit }
|
|
2944
|
+
}),
|
|
2945
|
+
g({ name: "rank", plainName: "Rank", technicalName: "rank", kind: s, type: "integer" }),
|
|
2946
|
+
g({ name: "percentile", plainName: "Percentile", technicalName: "percentile", kind: s, type: "number" }),
|
|
2947
|
+
g({ name: "min", plainName: "Lowest", technicalName: "min", kind: "graph", type: "number" }),
|
|
2948
|
+
g({ name: "max", plainName: "Highest", technicalName: "max", kind: "graph", type: "number" }),
|
|
2949
|
+
g({ name: "median", plainName: "Middle", technicalName: "median", kind: "graph", type: "number" }),
|
|
2950
|
+
g({ name: "mean", plainName: "Average", technicalName: "mean", kind: "graph", type: "number" }),
|
|
2951
|
+
g({ name: "measured", plainName: "Measured", technicalName: "measured", kind: "graph", type: "integer" }),
|
|
2952
|
+
g({
|
|
2953
|
+
name: "normalization",
|
|
2954
|
+
plainName: "Normalization",
|
|
2955
|
+
technicalName: "normalization",
|
|
2956
|
+
kind: "graph",
|
|
2957
|
+
type: "string"
|
|
2958
|
+
}),
|
|
2959
|
+
g({
|
|
2960
|
+
name: "tiedAtMin",
|
|
2961
|
+
plainName: "Tied at the lowest value",
|
|
2962
|
+
technicalName: "tiedAtMin",
|
|
2963
|
+
kind: "graph",
|
|
2964
|
+
type: "integer"
|
|
2965
|
+
})
|
|
2966
|
+
];
|
|
2967
|
+
}
|
|
2968
|
+
function ee(s, e) {
|
|
2969
|
+
const t = [
|
|
2970
|
+
g({ name: "group", plainName: s, technicalName: "group", kind: "node", type: "integer" }),
|
|
2971
|
+
g({
|
|
2972
|
+
name: "groupSize",
|
|
2973
|
+
plainName: `${s} size`,
|
|
2974
|
+
technicalName: "groupSize",
|
|
2975
|
+
kind: "node",
|
|
2976
|
+
type: "integer"
|
|
2977
|
+
}),
|
|
2978
|
+
g({
|
|
2979
|
+
name: "groupCount",
|
|
2980
|
+
plainName: `${s} count`,
|
|
2981
|
+
technicalName: "groupCount",
|
|
2982
|
+
kind: "graph",
|
|
2983
|
+
type: "integer"
|
|
2984
|
+
}),
|
|
2985
|
+
g({
|
|
2986
|
+
name: "sizes",
|
|
2987
|
+
plainName: `${s} sizes`,
|
|
2988
|
+
technicalName: "sizes",
|
|
2989
|
+
kind: "graph",
|
|
2990
|
+
type: "table"
|
|
2991
|
+
})
|
|
2992
|
+
];
|
|
2993
|
+
return e && t.push(
|
|
2994
|
+
g({
|
|
2995
|
+
name: "modularity",
|
|
2996
|
+
plainName: "Community strength",
|
|
2997
|
+
technicalName: "modularity",
|
|
2998
|
+
kind: "graph",
|
|
2999
|
+
type: "number"
|
|
3000
|
+
})
|
|
3001
|
+
), t;
|
|
3002
|
+
}
|
|
3003
|
+
function se(s, e, t) {
|
|
3004
|
+
return [
|
|
3005
|
+
g({ name: "in", plainName: e, technicalName: "in", kind: s, type: "boolean" }),
|
|
3006
|
+
g({ name: "count", plainName: "Count", technicalName: "count", kind: "graph", type: "integer" }),
|
|
3007
|
+
g({ ...t, kind: "graph" })
|
|
3008
|
+
];
|
|
3009
|
+
}
|
|
3010
|
+
function x(s) {
|
|
3011
|
+
return Ue(s.getZodOptionsSchema());
|
|
3012
|
+
}
|
|
3013
|
+
function Xe(...s) {
|
|
3014
|
+
const e = [], t = /* @__PURE__ */ new Set();
|
|
3015
|
+
for (const n of s)
|
|
3016
|
+
for (const a of n)
|
|
3017
|
+
t.has(a.name) || (t.add(a.name), e.push(a));
|
|
3018
|
+
return e;
|
|
3019
|
+
}
|
|
3020
|
+
const ga = {
|
|
3021
|
+
method: {
|
|
3022
|
+
schema: c.enum(["dijkstra", "bellman-ford"]).nullable().default(null),
|
|
3023
|
+
meta: {
|
|
3024
|
+
label: "Method",
|
|
3025
|
+
description: "Which engine computes the paths. Left unset, the element uses Dijkstra when every weight is zero or above and Bellman-Ford when any weight is negative.",
|
|
3026
|
+
advanced: !0,
|
|
3027
|
+
group: "engine"
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}, fa = {
|
|
3031
|
+
strength: {
|
|
3032
|
+
schema: c.enum(["weak", "strong"]).default("weak"),
|
|
3033
|
+
meta: {
|
|
3034
|
+
label: "Strength",
|
|
3035
|
+
description: "Weak follows an edge in either direction. Strong needs a directed path each way, and only differs from weak on a directed graph."
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
}, nt = [
|
|
3039
|
+
{
|
|
3040
|
+
key: "degree",
|
|
3041
|
+
plainName: "Connections",
|
|
3042
|
+
technicalName: "Degree centrality",
|
|
3043
|
+
description: "Counts how many edges each node has, incoming, outgoing and in total.",
|
|
3044
|
+
category: "centrality",
|
|
3045
|
+
shape: "node-metric",
|
|
3046
|
+
fields: [
|
|
3047
|
+
...E("node", { plainName: "Connections", technicalName: "degree", type: "integer", unit: "links" }),
|
|
3048
|
+
g({
|
|
3049
|
+
name: "inDegree",
|
|
3050
|
+
plainName: "Incoming connections",
|
|
3051
|
+
technicalName: "inDegree",
|
|
3052
|
+
kind: "node",
|
|
3053
|
+
type: "integer"
|
|
3054
|
+
}),
|
|
3055
|
+
g({
|
|
3056
|
+
name: "outDegree",
|
|
3057
|
+
plainName: "Outgoing connections",
|
|
3058
|
+
technicalName: "outDegree",
|
|
3059
|
+
kind: "node",
|
|
3060
|
+
type: "integer"
|
|
3061
|
+
})
|
|
3062
|
+
],
|
|
3063
|
+
options: x(me),
|
|
3064
|
+
costClass: "instant",
|
|
3065
|
+
complexity: "O(n + m)",
|
|
3066
|
+
legacyKeys: [{ key: "degree" }]
|
|
3067
|
+
},
|
|
3068
|
+
{
|
|
3069
|
+
key: "betweenness",
|
|
3070
|
+
plainName: "Bridges",
|
|
3071
|
+
technicalName: "Betweenness centrality",
|
|
3072
|
+
description: "Scores a node by how many of the shortest paths between other nodes run through it, which is what finds the bridges.",
|
|
3073
|
+
category: "centrality",
|
|
3074
|
+
shape: "node-metric",
|
|
3075
|
+
fields: E("node", { plainName: "Bridging", technicalName: "betweenness" }),
|
|
3076
|
+
options: x(le),
|
|
3077
|
+
costClass: "heavy",
|
|
3078
|
+
complexity: "O(n * m)",
|
|
3079
|
+
legacyKeys: [{ key: "betweenness" }]
|
|
3080
|
+
},
|
|
3081
|
+
{
|
|
3082
|
+
key: "closeness",
|
|
3083
|
+
plainName: "Reach",
|
|
3084
|
+
technicalName: "Closeness centrality",
|
|
3085
|
+
description: "Scores a node by how short its paths to the rest of the graph are, which is what finds the nodes that reach everything quickly.",
|
|
3086
|
+
category: "centrality",
|
|
3087
|
+
shape: "node-metric",
|
|
3088
|
+
fields: E("node", { plainName: "Reach", technicalName: "closeness" }),
|
|
3089
|
+
options: x(ue),
|
|
3090
|
+
costClass: "heavy",
|
|
3091
|
+
complexity: "O(n * m)",
|
|
3092
|
+
legacyKeys: [{ key: "closeness" }]
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
key: "pagerank",
|
|
3096
|
+
plainName: "Influence",
|
|
3097
|
+
technicalName: "PageRank",
|
|
3098
|
+
description: "Ranks a node by the influence that flows into it from the nodes that point at it.",
|
|
3099
|
+
category: "centrality",
|
|
3100
|
+
shape: "node-metric",
|
|
3101
|
+
fields: E("node", { plainName: "Influence", technicalName: "PageRank score" }),
|
|
3102
|
+
options: x(Ce),
|
|
3103
|
+
costClass: "iterative",
|
|
3104
|
+
complexity: "O(k(n + m))",
|
|
3105
|
+
legacyKeys: [{ key: "pagerank" }]
|
|
3106
|
+
},
|
|
3107
|
+
{
|
|
3108
|
+
key: "eigenvector",
|
|
3109
|
+
plainName: "Influence by association",
|
|
3110
|
+
technicalName: "Eigenvector centrality",
|
|
3111
|
+
description: "Scores a node by how influential its neighbours are, so a few important connections count for more than many unimportant ones.",
|
|
3112
|
+
category: "centrality",
|
|
3113
|
+
shape: "node-metric",
|
|
3114
|
+
fields: E("node", { plainName: "Influence by association", technicalName: "eigenvector score" }),
|
|
3115
|
+
options: x(ye),
|
|
3116
|
+
costClass: "iterative",
|
|
3117
|
+
complexity: "O(k(n + m))",
|
|
3118
|
+
legacyKeys: [{ key: "eigenvector" }]
|
|
3119
|
+
},
|
|
3120
|
+
{
|
|
3121
|
+
key: "katz",
|
|
3122
|
+
plainName: "Influence at a distance",
|
|
3123
|
+
technicalName: "Katz centrality",
|
|
3124
|
+
description: "Scores a node by every path that reaches it, with a longer path counting for less.",
|
|
3125
|
+
category: "centrality",
|
|
3126
|
+
shape: "node-metric",
|
|
3127
|
+
fields: E("node", { plainName: "Influence at a distance", technicalName: "Katz score" }),
|
|
3128
|
+
options: x(Ne),
|
|
3129
|
+
costClass: "iterative",
|
|
3130
|
+
complexity: "O(k(n + m))",
|
|
3131
|
+
legacyKeys: [{ key: "katz" }]
|
|
3132
|
+
},
|
|
3133
|
+
{
|
|
3134
|
+
key: "hits",
|
|
3135
|
+
plainName: "Hubs and authorities",
|
|
3136
|
+
technicalName: "HITS",
|
|
3137
|
+
description: "Scores every node twice: as a hub that points at good sources, and as an authority that good hubs point at.",
|
|
3138
|
+
category: "centrality",
|
|
3139
|
+
shape: "node-metric",
|
|
3140
|
+
fields: [
|
|
3141
|
+
...E("node", { plainName: "Hub and authority", technicalName: "combined HITS score" }),
|
|
3142
|
+
g({ name: "hub", plainName: "Hub", technicalName: "hub score", kind: "node", type: "number" }),
|
|
3143
|
+
g({
|
|
3144
|
+
name: "authority",
|
|
3145
|
+
plainName: "Authority",
|
|
3146
|
+
technicalName: "authority score",
|
|
3147
|
+
kind: "node",
|
|
3148
|
+
type: "number"
|
|
3149
|
+
})
|
|
3150
|
+
],
|
|
3151
|
+
options: x(ve),
|
|
3152
|
+
costClass: "iterative",
|
|
3153
|
+
complexity: "O(k(n + m))",
|
|
3154
|
+
legacyKeys: [{ key: "hits" }]
|
|
3155
|
+
},
|
|
3156
|
+
{
|
|
3157
|
+
key: "louvain",
|
|
3158
|
+
plainName: "Communities",
|
|
3159
|
+
technicalName: "Louvain",
|
|
3160
|
+
description: "Groups nodes into communities by repeatedly merging the groups whose merger tightens the partition the most.",
|
|
3161
|
+
category: "community",
|
|
3162
|
+
shape: "community",
|
|
3163
|
+
fields: ee("Community", !0),
|
|
3164
|
+
options: x(xe),
|
|
3165
|
+
costClass: "iterative",
|
|
3166
|
+
complexity: "O(k(n + m))",
|
|
3167
|
+
legacyKeys: [{ key: "louvain" }]
|
|
3168
|
+
},
|
|
3169
|
+
{
|
|
3170
|
+
key: "leiden",
|
|
3171
|
+
plainName: "Communities, refined",
|
|
3172
|
+
technicalName: "Leiden",
|
|
3173
|
+
description: "Groups nodes into communities the way Louvain does, with a refinement pass that rules out groups held together by a single node.",
|
|
3174
|
+
category: "community",
|
|
3175
|
+
shape: "community",
|
|
3176
|
+
fields: ee("Community", !0),
|
|
3177
|
+
options: x(Oe),
|
|
3178
|
+
costClass: "iterative",
|
|
3179
|
+
complexity: "O(k(n + m))",
|
|
3180
|
+
legacyKeys: [{ key: "leiden" }]
|
|
3181
|
+
},
|
|
3182
|
+
{
|
|
3183
|
+
key: "label-propagation",
|
|
3184
|
+
plainName: "Communities, fast",
|
|
3185
|
+
technicalName: "Label propagation",
|
|
3186
|
+
description: "Groups nodes into communities by letting every node repeatedly take whichever label most of its neighbours carry.",
|
|
3187
|
+
category: "community",
|
|
3188
|
+
shape: "community",
|
|
3189
|
+
fields: ee("Community", !1),
|
|
3190
|
+
options: x(Se),
|
|
3191
|
+
costClass: "iterative",
|
|
3192
|
+
complexity: "O(k(n + m))",
|
|
3193
|
+
legacyKeys: [{ key: "label-propagation" }]
|
|
3194
|
+
},
|
|
3195
|
+
{
|
|
3196
|
+
key: "girvan-newman",
|
|
3197
|
+
plainName: "Communities by cutting bridges",
|
|
3198
|
+
technicalName: "Girvan-Newman",
|
|
3199
|
+
description: "Groups nodes into communities by repeatedly cutting the edge that the most shortest paths run through, until the graph falls apart.",
|
|
3200
|
+
category: "community",
|
|
3201
|
+
shape: "community",
|
|
3202
|
+
fields: ee("Community", !0),
|
|
3203
|
+
options: x(we),
|
|
3204
|
+
costClass: "heavy",
|
|
3205
|
+
complexity: "O(n * m^2)",
|
|
3206
|
+
legacyKeys: [{ key: "girvan-newman" }]
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
key: "components",
|
|
3210
|
+
plainName: "Separate pieces",
|
|
3211
|
+
technicalName: "Connected components",
|
|
3212
|
+
description: "Finds the separate pieces of the graph: two nodes belong to the same piece when a path joins them.",
|
|
3213
|
+
category: "structure",
|
|
3214
|
+
shape: "community",
|
|
3215
|
+
fields: ee("Piece", !1),
|
|
3216
|
+
options: Xe(
|
|
3217
|
+
x(pe),
|
|
3218
|
+
x(Te),
|
|
3219
|
+
Ue(fa)
|
|
3220
|
+
),
|
|
3221
|
+
costClass: "instant",
|
|
3222
|
+
complexity: "O(n + m)",
|
|
3223
|
+
legacyKeys: [{ key: "connected-components" }, { key: "scc", params: { strength: "strong" } }]
|
|
3224
|
+
},
|
|
3225
|
+
{
|
|
3226
|
+
key: "shortest-path",
|
|
3227
|
+
plainName: "Shortest route",
|
|
3228
|
+
technicalName: "Shortest path",
|
|
3229
|
+
description: "Finds the cheapest route from one node to another, and how far every reachable node is from the source.",
|
|
3230
|
+
category: "path",
|
|
3231
|
+
shape: "path",
|
|
3232
|
+
fields: [
|
|
3233
|
+
g({ name: "onPath", plainName: "On the route", technicalName: "onPath", kind: "node", type: "boolean" }),
|
|
3234
|
+
g({ name: "order", plainName: "Position on the route", technicalName: "order", kind: "node", type: "integer" }),
|
|
3235
|
+
g({ name: "distance", plainName: "Distance from the source", technicalName: "distance", kind: "node", type: "number" }),
|
|
3236
|
+
g({ name: "onPath", plainName: "On the route", technicalName: "onPath", kind: "edge", type: "boolean" }),
|
|
3237
|
+
g({ name: "length", plainName: "Nodes on the route", technicalName: "length", kind: "graph", type: "integer" }),
|
|
3238
|
+
g({ name: "cost", plainName: "Total cost", technicalName: "cost", kind: "graph", type: "number" }),
|
|
3239
|
+
g({ name: "hops", plainName: "Edges on the route", technicalName: "hops", kind: "graph", type: "integer" }),
|
|
3240
|
+
g({
|
|
3241
|
+
name: "hasNegativeCycle",
|
|
3242
|
+
plainName: "Has a loop that costs less every time round",
|
|
3243
|
+
technicalName: "hasNegativeCycle",
|
|
3244
|
+
kind: "graph",
|
|
3245
|
+
type: "boolean"
|
|
3246
|
+
})
|
|
3247
|
+
],
|
|
3248
|
+
options: Xe(
|
|
3249
|
+
x(fe),
|
|
3250
|
+
x(ce),
|
|
3251
|
+
Ue(ga)
|
|
3252
|
+
),
|
|
3253
|
+
costClass: "instant",
|
|
3254
|
+
complexity: "O((n + m) log n) with dijkstra, O(n * m) with bellman-ford",
|
|
3255
|
+
legacyKeys: [
|
|
3256
|
+
{ key: "dijkstra", params: { method: "dijkstra" } },
|
|
3257
|
+
{ key: "bellman-ford", params: { method: "bellman-ford" } }
|
|
3258
|
+
]
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
/* ONE KEY, ONE RESULT SHAPE. The all-pairs sweep used to be a PARAMETER on shortest-path
|
|
3262
|
+
-- `allPairs: true`, engine floyd-warshall -- which made one catalogue key claim two
|
|
3263
|
+
different result shapes: a route for the single-source engines, and a measurement of
|
|
3264
|
+
every node for this one. A run takes its shape from its descriptor once, at creation,
|
|
3265
|
+
so the all-pairs run reported "path", found no `onPath` to paint from, and suggested
|
|
3266
|
+
nothing; `styles.encode` refused a hand-written encoding for the same reason. Its own
|
|
3267
|
+
key is what makes the shape true, and every consumer that reads the catalogue up front
|
|
3268
|
+
right about what a run of it will publish. */
|
|
3269
|
+
key: "all-pairs-distance",
|
|
3270
|
+
plainName: "How far from everything else",
|
|
3271
|
+
technicalName: "All-pairs shortest paths",
|
|
3272
|
+
description: "Measures the distance between every pair of nodes, and gives each node the distance to the furthest node it can reach.",
|
|
3273
|
+
category: "path",
|
|
3274
|
+
shape: "node-metric",
|
|
3275
|
+
fields: [
|
|
3276
|
+
...E("node", { plainName: "Distance to the furthest node", technicalName: "eccentricity" }),
|
|
3277
|
+
g({ name: "diameter", plainName: "Widest distance", technicalName: "diameter", kind: "graph", type: "number" }),
|
|
3278
|
+
g({ name: "radius", plainName: "Narrowest distance", technicalName: "radius", kind: "graph", type: "number" }),
|
|
3279
|
+
g({
|
|
3280
|
+
name: "hasNegativeCycle",
|
|
3281
|
+
plainName: "Has a loop that costs less every time round",
|
|
3282
|
+
technicalName: "hasNegativeCycle",
|
|
3283
|
+
kind: "graph",
|
|
3284
|
+
type: "boolean"
|
|
3285
|
+
})
|
|
3286
|
+
],
|
|
3287
|
+
options: x(be),
|
|
3288
|
+
costClass: "cubic",
|
|
3289
|
+
complexity: "O(n^3)",
|
|
3290
|
+
legacyKeys: [{ key: "floyd-warshall" }]
|
|
3291
|
+
},
|
|
3292
|
+
{
|
|
3293
|
+
key: "bfs",
|
|
3294
|
+
plainName: "Steps away",
|
|
3295
|
+
technicalName: "Breadth-first search",
|
|
3296
|
+
description: "Walks outwards from one node a level at a time, recording how many steps away each node is.",
|
|
3297
|
+
category: "path",
|
|
3298
|
+
shape: "layered-grouping",
|
|
3299
|
+
fields: [
|
|
3300
|
+
g({ name: "level", plainName: "Steps away", technicalName: "level", kind: "node", type: "integer" }),
|
|
3301
|
+
g({ name: "levelSize", plainName: "Nodes this many steps away", technicalName: "levelSize", kind: "node", type: "integer" }),
|
|
3302
|
+
g({ name: "order", plainName: "Visit order", technicalName: "order", kind: "node", type: "integer" }),
|
|
3303
|
+
g({ name: "levelCount", plainName: "Levels", technicalName: "levelCount", kind: "graph", type: "integer" }),
|
|
3304
|
+
g({ name: "sizes", plainName: "Level sizes", technicalName: "sizes", kind: "graph", type: "table" }),
|
|
3305
|
+
g({ name: "targetFound", plainName: "Target reached", technicalName: "targetFound", kind: "graph", type: "boolean" })
|
|
3306
|
+
],
|
|
3307
|
+
options: x(de),
|
|
3308
|
+
costClass: "instant",
|
|
3309
|
+
complexity: "O(n + m)",
|
|
3310
|
+
legacyKeys: [{ key: "bfs" }]
|
|
3311
|
+
},
|
|
3312
|
+
{
|
|
3313
|
+
key: "dfs",
|
|
3314
|
+
plainName: "Exploration order",
|
|
3315
|
+
technicalName: "Depth-first search",
|
|
3316
|
+
description: "Walks as deep as it can from one node before backtracking, recording the order in which it reached each node.",
|
|
3317
|
+
category: "path",
|
|
3318
|
+
shape: "node-metric",
|
|
3319
|
+
fields: [
|
|
3320
|
+
...E("node", { plainName: "Exploration order", technicalName: "discovery time", type: "integer" }),
|
|
3321
|
+
g({ name: "visited", plainName: "Reached", technicalName: "visited", kind: "node", type: "boolean" })
|
|
3322
|
+
],
|
|
3323
|
+
options: x(ge),
|
|
3324
|
+
costClass: "instant",
|
|
3325
|
+
complexity: "O(n + m)",
|
|
3326
|
+
legacyKeys: [{ key: "dfs" }]
|
|
3327
|
+
},
|
|
3328
|
+
{
|
|
3329
|
+
key: "kruskal",
|
|
3330
|
+
plainName: "Cheapest connecting network",
|
|
3331
|
+
technicalName: "Kruskal minimum spanning tree",
|
|
3332
|
+
description: "Finds the cheapest set of edges that still joins every node, by taking the cheapest edge that does not close a loop.",
|
|
3333
|
+
category: "structure",
|
|
3334
|
+
shape: "edge-set",
|
|
3335
|
+
fields: se("edge", "In the network", {
|
|
3336
|
+
name: "totalWeight",
|
|
3337
|
+
plainName: "Total cost",
|
|
3338
|
+
technicalName: "totalWeight",
|
|
3339
|
+
type: "number"
|
|
3340
|
+
}),
|
|
3341
|
+
options: x(ke),
|
|
3342
|
+
costClass: "instant",
|
|
3343
|
+
complexity: "O(m log m)",
|
|
3344
|
+
requires: { directed: !1 },
|
|
3345
|
+
legacyKeys: [{ key: "kruskal" }]
|
|
3346
|
+
},
|
|
3347
|
+
{
|
|
3348
|
+
key: "prim",
|
|
3349
|
+
plainName: "Cheapest network from one node",
|
|
3350
|
+
technicalName: "Prim minimum spanning tree",
|
|
3351
|
+
description: "Finds the cheapest set of edges that still joins every node, by growing outwards from one starting node.",
|
|
3352
|
+
category: "structure",
|
|
3353
|
+
shape: "edge-set",
|
|
3354
|
+
fields: se("edge", "In the network", {
|
|
3355
|
+
name: "totalWeight",
|
|
3356
|
+
plainName: "Total cost",
|
|
3357
|
+
technicalName: "totalWeight",
|
|
3358
|
+
type: "number"
|
|
3359
|
+
}),
|
|
3360
|
+
options: x(ze),
|
|
3361
|
+
costClass: "instant",
|
|
3362
|
+
complexity: "O(m log n)",
|
|
3363
|
+
requires: { directed: !1 },
|
|
3364
|
+
legacyKeys: [{ key: "prim" }]
|
|
3365
|
+
},
|
|
3366
|
+
{
|
|
3367
|
+
key: "bipartite-matching",
|
|
3368
|
+
plainName: "Best pairing",
|
|
3369
|
+
technicalName: "Maximum bipartite matching",
|
|
3370
|
+
description: "Pairs up the two sides of a two-sided graph so that as many nodes as possible end up with a partner.",
|
|
3371
|
+
category: "structure",
|
|
3372
|
+
shape: "edge-set",
|
|
3373
|
+
fields: [
|
|
3374
|
+
...se("edge", "In the pairing", {
|
|
3375
|
+
name: "bipartite",
|
|
3376
|
+
plainName: "Has two sides",
|
|
3377
|
+
technicalName: "bipartite",
|
|
3378
|
+
type: "boolean"
|
|
3379
|
+
}),
|
|
3380
|
+
g({ name: "side", plainName: "Side", technicalName: "side", kind: "node", type: "string" }),
|
|
3381
|
+
g({ name: "matched", plainName: "Paired", technicalName: "matched", kind: "node", type: "boolean" })
|
|
3382
|
+
],
|
|
3383
|
+
options: x(he),
|
|
3384
|
+
costClass: "heavy",
|
|
3385
|
+
complexity: "O(n * m)",
|
|
3386
|
+
requires: { directed: !1 },
|
|
3387
|
+
legacyKeys: [{ key: "bipartite-matching" }]
|
|
3388
|
+
},
|
|
3389
|
+
{
|
|
3390
|
+
key: "max-flow",
|
|
3391
|
+
plainName: "Most that can flow",
|
|
3392
|
+
technicalName: "Maximum flow",
|
|
3393
|
+
description: "Finds the most that can flow from a source to a sink when every edge has a capacity.",
|
|
3394
|
+
category: "flow",
|
|
3395
|
+
shape: "edge-metric",
|
|
3396
|
+
fields: [
|
|
3397
|
+
...E("edge", { plainName: "Flow", technicalName: "flow" }),
|
|
3398
|
+
g({ name: "capacity", plainName: "Capacity", technicalName: "capacity", kind: "edge", type: "number" }),
|
|
3399
|
+
g({ name: "utilization", plainName: "Share of capacity used", technicalName: "utilization", kind: "edge", type: "number" }),
|
|
3400
|
+
g({ name: "netFlow", plainName: "Net flow", technicalName: "netFlow", kind: "node", type: "number" }),
|
|
3401
|
+
g({ name: "role", plainName: "Source or sink", technicalName: "role", kind: "node", type: "string" }),
|
|
3402
|
+
g({ name: "maxFlow", plainName: "Most that can flow", technicalName: "maxFlow", kind: "graph", type: "number" })
|
|
3403
|
+
],
|
|
3404
|
+
options: x(Ie),
|
|
3405
|
+
costClass: "heavy",
|
|
3406
|
+
complexity: "O(n * m^2)",
|
|
3407
|
+
legacyKeys: [{ key: "max-flow" }]
|
|
3408
|
+
},
|
|
3409
|
+
{
|
|
3410
|
+
key: "min-cut",
|
|
3411
|
+
plainName: "Weakest link",
|
|
3412
|
+
technicalName: "Minimum cut",
|
|
3413
|
+
description: "Finds the cheapest set of edges to remove to split the graph into two pieces.",
|
|
3414
|
+
category: "flow",
|
|
3415
|
+
shape: "edge-set",
|
|
3416
|
+
fields: [
|
|
3417
|
+
...se("edge", "In the cut", {
|
|
3418
|
+
name: "cutValue",
|
|
3419
|
+
plainName: "Cost of the cut",
|
|
3420
|
+
technicalName: "cutValue",
|
|
3421
|
+
type: "number"
|
|
3422
|
+
}),
|
|
3423
|
+
g({ name: "side", plainName: "Side", technicalName: "side", kind: "node", type: "string" })
|
|
3424
|
+
],
|
|
3425
|
+
options: x(Me),
|
|
3426
|
+
costClass: "heavy",
|
|
3427
|
+
complexity: "O(n * m^2) between two nodes, O(n * m + n^2 log n) across the whole graph, O(k * n^2) with Karger",
|
|
3428
|
+
legacyKeys: [{ key: "min-cut" }]
|
|
3429
|
+
}
|
|
3430
|
+
];
|
|
3431
|
+
function Da(s) {
|
|
3432
|
+
return nt.find((e) => e.key === s);
|
|
3433
|
+
}
|
|
3434
|
+
function Pa(s) {
|
|
3435
|
+
for (const e of nt)
|
|
3436
|
+
for (const t of e.legacyKeys)
|
|
3437
|
+
if (t.key === s)
|
|
3438
|
+
return { descriptor: e, params: t.params ?? {} };
|
|
3439
|
+
}
|
|
3440
|
+
export {
|
|
3441
|
+
Me as A,
|
|
3442
|
+
nt as B,
|
|
3443
|
+
at as C,
|
|
3444
|
+
me as D,
|
|
3445
|
+
ye as E,
|
|
3446
|
+
be as F,
|
|
3447
|
+
we as G,
|
|
3448
|
+
ve as H,
|
|
3449
|
+
Lt as I,
|
|
3450
|
+
Ne as K,
|
|
3451
|
+
xe as L,
|
|
3452
|
+
Ie as M,
|
|
3453
|
+
Wt as N,
|
|
3454
|
+
Ce as P,
|
|
3455
|
+
Te as S,
|
|
3456
|
+
tt as a,
|
|
3457
|
+
te as b,
|
|
3458
|
+
Da as c,
|
|
3459
|
+
Pa as d,
|
|
3460
|
+
za as e,
|
|
3461
|
+
Ta as f,
|
|
3462
|
+
Ma as g,
|
|
3463
|
+
Ca as h,
|
|
3464
|
+
Oa as i,
|
|
3465
|
+
xa as j,
|
|
3466
|
+
Ea as k,
|
|
3467
|
+
Kt as l,
|
|
3468
|
+
Ia as m,
|
|
3469
|
+
fe as n,
|
|
3470
|
+
le as o,
|
|
3471
|
+
ue as p,
|
|
3472
|
+
Oe as q,
|
|
3473
|
+
Se as r,
|
|
3474
|
+
ce as s,
|
|
3475
|
+
$t as t,
|
|
3476
|
+
de as u,
|
|
3477
|
+
ge as v,
|
|
3478
|
+
pe as w,
|
|
3479
|
+
ke as x,
|
|
3480
|
+
ze as y,
|
|
3481
|
+
he as z
|
|
3482
|
+
};
|