@statelyai/graph 1.0.0 → 2.1.0
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/README.md +121 -44
- package/dist/{adjacency-list-VsUaH9SJ.mjs → adjacency-list-DQ32Mmhx.mjs} +3 -1
- package/dist/algorithms-D1cgly0g.d.mts +452 -0
- package/dist/algorithms-DBpH74hR.mjs +3309 -0
- package/dist/algorithms.d.mts +2 -2
- package/dist/algorithms.mjs +2 -2
- package/dist/config-Dt5u1gSf.mjs +793 -0
- package/dist/{converter-udLITX36.mjs → converter-DB6Rg6Vd.mjs} +2 -2
- package/dist/format-support.mjs +38 -11
- package/dist/formats/adjacency-list/index.d.mts +1 -1
- package/dist/formats/adjacency-list/index.mjs +1 -1
- package/dist/formats/converter/index.d.mts +1 -1
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +4 -4
- package/dist/formats/cytoscape/index.mjs +10 -4
- package/dist/formats/d2/index.d.mts +1 -1
- package/dist/formats/d2/index.mjs +26 -12
- package/dist/formats/d3/index.d.mts +4 -4
- package/dist/formats/d3/index.mjs +10 -4
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/dot/index.mjs +22 -6
- package/dist/formats/edge-list/index.d.mts +1 -1
- package/dist/formats/edge-list/index.mjs +1 -1
- package/dist/formats/elk/index.d.mts +1 -1
- package/dist/formats/elk/index.mjs +63 -24
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +43 -16
- package/dist/formats/gml/index.d.mts +4 -4
- package/dist/formats/gml/index.mjs +28 -15
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +96 -23
- package/dist/formats/jgf/index.d.mts +4 -4
- package/dist/formats/jgf/index.mjs +12 -5
- package/dist/formats/mermaid/index.d.mts +1 -1
- package/dist/formats/mermaid/index.mjs +49 -12
- package/dist/formats/tgf/index.d.mts +4 -4
- package/dist/formats/tgf/index.mjs +4 -4
- package/dist/formats/xyflow/index.d.mts +12 -6
- package/dist/formats/xyflow/index.mjs +42 -10
- package/dist/{index-D9Kj6Fe3.d.mts → index-BlbSWUvH.d.mts} +1 -1
- package/dist/{index-CHoriXZD.d.mts → index-CNvqxPLJ.d.mts} +157 -30
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +290 -307
- package/dist/layout/cytoscape.d.mts +66 -0
- package/dist/layout/cytoscape.mjs +114 -0
- package/dist/layout/d3-force.d.mts +52 -0
- package/dist/layout/d3-force.mjs +127 -0
- package/dist/layout/d3-hierarchy.d.mts +39 -0
- package/dist/layout/d3-hierarchy.mjs +135 -0
- package/dist/layout/dagre.d.mts +32 -0
- package/dist/layout/dagre.mjs +99 -0
- package/dist/layout/elk.d.mts +47 -0
- package/dist/layout/elk.mjs +73 -0
- package/dist/layout/forceatlas2.d.mts +48 -0
- package/dist/layout/forceatlas2.mjs +100 -0
- package/dist/layout/graphviz.d.mts +50 -0
- package/dist/layout/graphviz.mjs +179 -0
- package/dist/layout/index.d.mts +185 -0
- package/dist/layout/index.mjs +181 -0
- package/dist/layout/webcola.d.mts +40 -0
- package/dist/layout/webcola.mjs +104 -0
- package/dist/{queries-BlkA1HAN.d.mts → queries-B6quF529.d.mts} +43 -12
- package/dist/queries-BMM0XAv_.mjs +986 -0
- package/dist/queries.d.mts +1 -1
- package/dist/queries.mjs +1 -768
- package/dist/schemas.d.mts +19 -1
- package/dist/schemas.mjs +32 -84
- package/dist/{types-3-FS9NV2.d.mts → types-BAEQTwK_.d.mts} +99 -7
- package/dist/validate-BsfSOv0S.mjs +190 -0
- package/package.json +59 -7
- package/schemas/edge.schema.json +27 -0
- package/schemas/graph.schema.json +27 -0
- package/dist/algorithms-Ba7o7niK.mjs +0 -2394
- package/dist/algorithms-fTqmvhzP.d.mts +0 -178
- package/dist/indexing-DR8M1vBy.mjs +0 -137
- /package/dist/{edge-list-DP4otyPU.mjs → edge-list-CA9UTvn2.mjs} +0 -0
- /package/dist/{mode-D8OnHFBk.mjs → mode-gu_mhKKs.mjs} +0 -0
|
@@ -1,2394 +0,0 @@
|
|
|
1
|
-
import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-DR8M1vBy.mjs";
|
|
2
|
-
import { t as getEdgeMode } from "./mode-D8OnHFBk.mjs";
|
|
3
|
-
|
|
4
|
-
//#region src/graph.ts
|
|
5
|
-
/**
|
|
6
|
-
* Create a resolved graph port from a config. Fills in defaults.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const port = createGraphPort({ name: 'output', direction: 'out' });
|
|
11
|
-
* // { name: 'output', direction: 'out', data: null }
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
function createGraphPort(config) {
|
|
15
|
-
if (!config.name) throw new Error("Port name must be a non-empty string");
|
|
16
|
-
const port = {
|
|
17
|
-
name: config.name,
|
|
18
|
-
direction: config.direction ?? "inout",
|
|
19
|
-
data: config.data ?? null
|
|
20
|
-
};
|
|
21
|
-
if (config.label !== void 0) port.label = config.label;
|
|
22
|
-
if (config.x !== void 0) port.x = config.x;
|
|
23
|
-
if (config.y !== void 0) port.y = config.y;
|
|
24
|
-
if (config.width !== void 0) port.width = config.width;
|
|
25
|
-
if (config.height !== void 0) port.height = config.height;
|
|
26
|
-
if (config.style !== void 0) port.style = config.style;
|
|
27
|
-
return port;
|
|
28
|
-
}
|
|
29
|
-
function validatePortNames(ports) {
|
|
30
|
-
const seen = /* @__PURE__ */ new Set();
|
|
31
|
-
for (const port of ports) {
|
|
32
|
-
if (seen.has(port.name)) throw new Error(`Duplicate port name "${port.name}" on node`);
|
|
33
|
-
seen.add(port.name);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Create a resolved graph node from a config. Fills in defaults.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```ts
|
|
41
|
-
* const node = createGraphNode({ id: 'a', data: { label: 'hi' } });
|
|
42
|
-
* // { type: 'node', id: 'a', label: '', data: { label: 'hi' } }
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
function createGraphNode(config) {
|
|
46
|
-
if (!config.id) throw new Error("Node id must be a non-empty string");
|
|
47
|
-
if (config.parentId === "") throw new Error("Node parentId must be a non-empty string");
|
|
48
|
-
const node = {
|
|
49
|
-
type: "node",
|
|
50
|
-
id: config.id,
|
|
51
|
-
...config.parentId !== void 0 && { parentId: config.parentId ?? null },
|
|
52
|
-
...config.initialNodeId !== void 0 && { initialNodeId: config.initialNodeId ?? null },
|
|
53
|
-
label: config.label ?? null,
|
|
54
|
-
data: config.data ?? null
|
|
55
|
-
};
|
|
56
|
-
if (config.ports !== void 0 && config.ports.length > 0) {
|
|
57
|
-
validatePortNames(config.ports);
|
|
58
|
-
node.ports = config.ports.map(createGraphPort);
|
|
59
|
-
}
|
|
60
|
-
if (config.x !== void 0) node.x = config.x;
|
|
61
|
-
if (config.y !== void 0) node.y = config.y;
|
|
62
|
-
if (config.width !== void 0) node.width = config.width;
|
|
63
|
-
if (config.height !== void 0) node.height = config.height;
|
|
64
|
-
if (config.shape !== void 0) node.shape = config.shape;
|
|
65
|
-
if (config.color !== void 0) node.color = config.color;
|
|
66
|
-
if (config.style !== void 0) node.style = config.style;
|
|
67
|
-
return node;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Create a resolved graph edge from a config. Fills in defaults.
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```ts
|
|
74
|
-
* const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
75
|
-
* // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: null }
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
function createGraphEdge(config) {
|
|
79
|
-
if (!config.id) throw new Error("Edge id must be a non-empty string");
|
|
80
|
-
if (!config.sourceId) throw new Error("Edge sourceId must be a non-empty string");
|
|
81
|
-
if (!config.targetId) throw new Error("Edge targetId must be a non-empty string");
|
|
82
|
-
const edge = {
|
|
83
|
-
type: "edge",
|
|
84
|
-
id: config.id,
|
|
85
|
-
sourceId: config.sourceId,
|
|
86
|
-
targetId: config.targetId,
|
|
87
|
-
label: config.label ?? null,
|
|
88
|
-
data: config.data ?? null
|
|
89
|
-
};
|
|
90
|
-
if (config.sourcePort !== void 0) edge.sourcePort = config.sourcePort;
|
|
91
|
-
if (config.targetPort !== void 0) edge.targetPort = config.targetPort;
|
|
92
|
-
if (config.mode !== void 0) edge.mode = config.mode;
|
|
93
|
-
if (config.weight !== void 0) edge.weight = config.weight;
|
|
94
|
-
if (config.x !== void 0) edge.x = config.x;
|
|
95
|
-
if (config.y !== void 0) edge.y = config.y;
|
|
96
|
-
if (config.width !== void 0) edge.width = config.width;
|
|
97
|
-
if (config.height !== void 0) edge.height = config.height;
|
|
98
|
-
if (config.color !== void 0) edge.color = config.color;
|
|
99
|
-
if (config.style !== void 0) edge.style = config.style;
|
|
100
|
-
return edge;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Create a graph from a config. Resolves defaults for all fields.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```ts
|
|
107
|
-
* const graph = createGraph({
|
|
108
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
109
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
110
|
-
* });
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
function createGraph(config) {
|
|
114
|
-
const graph = {
|
|
115
|
-
id: config?.id ?? "",
|
|
116
|
-
mode: config?.mode ?? "directed",
|
|
117
|
-
initialNodeId: config?.initialNodeId ?? null,
|
|
118
|
-
nodes: (config?.nodes ?? []).map(createGraphNode),
|
|
119
|
-
edges: (config?.edges ?? []).map(createGraphEdge),
|
|
120
|
-
data: config?.data ?? null
|
|
121
|
-
};
|
|
122
|
-
if (config?.direction !== void 0) graph.direction = config.direction;
|
|
123
|
-
if (config?.style !== void 0) graph.style = config.style;
|
|
124
|
-
return graph;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Create a visual graph with required position/size on all nodes and edges.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```ts
|
|
131
|
-
* const graph = createVisualGraph({
|
|
132
|
-
* nodes: [{ id: 'a', x: 0, y: 0, width: 100, height: 50 }],
|
|
133
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', x: 0, y: 0, width: 0, height: 0 }],
|
|
134
|
-
* });
|
|
135
|
-
* // graph.nodes[0].x === 0
|
|
136
|
-
* ```
|
|
137
|
-
*/
|
|
138
|
-
function createVisualGraph(config) {
|
|
139
|
-
const base = createGraph(config);
|
|
140
|
-
return {
|
|
141
|
-
...base,
|
|
142
|
-
direction: config?.direction ?? "down",
|
|
143
|
-
nodes: base.nodes.map((n) => {
|
|
144
|
-
const { ports, ...rest } = n;
|
|
145
|
-
return {
|
|
146
|
-
...rest,
|
|
147
|
-
x: n.x ?? 0,
|
|
148
|
-
y: n.y ?? 0,
|
|
149
|
-
width: n.width ?? 0,
|
|
150
|
-
height: n.height ?? 0,
|
|
151
|
-
...n.shape !== void 0 && { shape: n.shape },
|
|
152
|
-
...ports !== void 0 && { ports: ports.map((p) => ({
|
|
153
|
-
...p,
|
|
154
|
-
x: p.x ?? 0,
|
|
155
|
-
y: p.y ?? 0,
|
|
156
|
-
width: p.width ?? 0,
|
|
157
|
-
height: p.height ?? 0
|
|
158
|
-
})) }
|
|
159
|
-
};
|
|
160
|
-
}),
|
|
161
|
-
edges: base.edges.map((e) => ({
|
|
162
|
-
...e,
|
|
163
|
-
x: e.x ?? 0,
|
|
164
|
-
y: e.y ?? 0,
|
|
165
|
-
width: e.width ?? 0,
|
|
166
|
-
height: e.height ?? 0
|
|
167
|
-
}))
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Create a graph by BFS exploration of a transition function.
|
|
172
|
-
* Each unique state becomes a node; each (state, event) -> nextState becomes an edge.
|
|
173
|
-
*
|
|
174
|
-
* - Node IDs are determined by `serializeState` (default: `JSON.stringify`).
|
|
175
|
-
* - Edge IDs use the format `sourceId|serializedEvent|targetId` for uniqueness
|
|
176
|
-
* and debuggability. Edge labels are just the serialized event string.
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* ```ts
|
|
180
|
-
* const graph = createGraphFromTransition(
|
|
181
|
-
* (state, event) => {
|
|
182
|
-
* if (state === 'green' && event === 'TIMER') return 'yellow';
|
|
183
|
-
* if (state === 'yellow' && event === 'TIMER') return 'red';
|
|
184
|
-
* if (state === 'red' && event === 'TIMER') return 'green';
|
|
185
|
-
* return state;
|
|
186
|
-
* },
|
|
187
|
-
* {
|
|
188
|
-
* initialState: 'green',
|
|
189
|
-
* events: ['TIMER'],
|
|
190
|
-
* serializeState: (s) => s,
|
|
191
|
-
* serializeEvent: (e) => e,
|
|
192
|
-
* },
|
|
193
|
-
* );
|
|
194
|
-
* // graph.nodes.length === 3
|
|
195
|
-
* ```
|
|
196
|
-
*/
|
|
197
|
-
function createGraphFromTransition(transition, options) {
|
|
198
|
-
const serializeState = options.serializeState ?? JSON.stringify;
|
|
199
|
-
const serializeEvent = options.serializeEvent ?? JSON.stringify;
|
|
200
|
-
const limit = options.limit ?? Infinity;
|
|
201
|
-
const getEvents = typeof options.events === "function" ? options.events : () => options.events;
|
|
202
|
-
const nodes = [];
|
|
203
|
-
const edges = [];
|
|
204
|
-
const visited = /* @__PURE__ */ new Set();
|
|
205
|
-
const edgeSet = /* @__PURE__ */ new Set();
|
|
206
|
-
const queue = [options.initialState];
|
|
207
|
-
const initialStateId = serializeState(options.initialState);
|
|
208
|
-
visited.add(initialStateId);
|
|
209
|
-
nodes.push({
|
|
210
|
-
id: initialStateId,
|
|
211
|
-
label: initialStateId,
|
|
212
|
-
data: options.initialState
|
|
213
|
-
});
|
|
214
|
-
let iterations = 0;
|
|
215
|
-
while (queue.length > 0) {
|
|
216
|
-
const state = queue.shift();
|
|
217
|
-
const stateId = serializeState(state);
|
|
218
|
-
if (++iterations > limit) throw new Error("Traversal limit exceeded");
|
|
219
|
-
if (options.stopWhen?.(state)) continue;
|
|
220
|
-
const events = getEvents(state);
|
|
221
|
-
for (const event of events) {
|
|
222
|
-
const nextState = transition(state, event);
|
|
223
|
-
const nextStateId = serializeState(nextState);
|
|
224
|
-
const eventStr = serializeEvent(event);
|
|
225
|
-
if (!visited.has(nextStateId)) {
|
|
226
|
-
visited.add(nextStateId);
|
|
227
|
-
nodes.push({
|
|
228
|
-
id: nextStateId,
|
|
229
|
-
label: nextStateId,
|
|
230
|
-
data: nextState
|
|
231
|
-
});
|
|
232
|
-
queue.push(nextState);
|
|
233
|
-
}
|
|
234
|
-
const edgeKey = `${stateId}|${eventStr}|${nextStateId}`;
|
|
235
|
-
if (!edgeSet.has(edgeKey)) {
|
|
236
|
-
edgeSet.add(edgeKey);
|
|
237
|
-
edges.push({
|
|
238
|
-
id: edgeKey,
|
|
239
|
-
sourceId: stateId,
|
|
240
|
-
targetId: nextStateId,
|
|
241
|
-
label: eventStr,
|
|
242
|
-
data: event
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return createGraph({
|
|
248
|
-
id: options.id ?? "",
|
|
249
|
-
mode: "directed",
|
|
250
|
-
initialNodeId: initialStateId,
|
|
251
|
-
nodes,
|
|
252
|
-
edges
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Get a node by id, or `undefined` if not found.
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* ```ts
|
|
260
|
-
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
261
|
-
* const node = getNode(graph, 'a'); // GraphNode
|
|
262
|
-
* const missing = getNode(graph, 'z'); // undefined
|
|
263
|
-
* ```
|
|
264
|
-
*/
|
|
265
|
-
function getNode(graph, id) {
|
|
266
|
-
const arrayIdx = getIndex(graph).nodeById.get(id);
|
|
267
|
-
return arrayIdx !== void 0 ? graph.nodes[arrayIdx] : void 0;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Get an edge by id, or `undefined` if not found.
|
|
271
|
-
*
|
|
272
|
-
* @example
|
|
273
|
-
* ```ts
|
|
274
|
-
* const graph = createGraph({
|
|
275
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
276
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
277
|
-
* });
|
|
278
|
-
* const edge = getEdge(graph, 'e1'); // GraphEdge
|
|
279
|
-
* const missing = getEdge(graph, 'z'); // undefined
|
|
280
|
-
* ```
|
|
281
|
-
*/
|
|
282
|
-
function getEdge(graph, id) {
|
|
283
|
-
const arrayIdx = getIndex(graph).edgeById.get(id);
|
|
284
|
-
return arrayIdx !== void 0 ? graph.edges[arrayIdx] : void 0;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Check if a node exists in the graph.
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* ```ts
|
|
291
|
-
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
292
|
-
* hasNode(graph, 'a'); // true
|
|
293
|
-
* hasNode(graph, 'z'); // false
|
|
294
|
-
* ```
|
|
295
|
-
*/
|
|
296
|
-
function hasNode(graph, id) {
|
|
297
|
-
return getIndex(graph).nodeById.has(id);
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Check if an edge exists in the graph.
|
|
301
|
-
*
|
|
302
|
-
* @example
|
|
303
|
-
* ```ts
|
|
304
|
-
* const graph = createGraph({
|
|
305
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
306
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
307
|
-
* });
|
|
308
|
-
* hasEdge(graph, 'e1'); // true
|
|
309
|
-
* hasEdge(graph, 'z'); // false
|
|
310
|
-
* ```
|
|
311
|
-
*/
|
|
312
|
-
function hasEdge(graph, id) {
|
|
313
|
-
return getIndex(graph).edgeById.has(id);
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* **Mutable.** Add a node to the graph. Mutates `graph.nodes` in place.
|
|
317
|
-
* @returns The resolved node that was added.
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
* ```ts
|
|
321
|
-
* const graph = createGraph();
|
|
322
|
-
* const node = addNode(graph, { id: 'a', label: 'Node A' });
|
|
323
|
-
* // graph.nodes.length === 1
|
|
324
|
-
* ```
|
|
325
|
-
*/
|
|
326
|
-
function addNode(graph, config) {
|
|
327
|
-
const node = createGraphNode(config);
|
|
328
|
-
const idx = getIndex(graph);
|
|
329
|
-
if (idx.nodeById.has(config.id)) throw new Error(`Node "${config.id}" already exists`);
|
|
330
|
-
if (config.parentId && !idx.nodeById.has(config.parentId)) throw new Error(`Parent node "${config.parentId}" does not exist`);
|
|
331
|
-
indexAddNode(idx, node, graph.nodes.push(node) - 1);
|
|
332
|
-
return node;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* **Mutable.** Add an edge to the graph. Mutates `graph.edges` in place.
|
|
336
|
-
* @returns The resolved edge that was added.
|
|
337
|
-
*
|
|
338
|
-
* @example
|
|
339
|
-
* ```ts
|
|
340
|
-
* const graph = createGraph({ nodes: [{ id: 'a' }, { id: 'b' }] });
|
|
341
|
-
* const edge = addEdge(graph, { id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
342
|
-
* // graph.edges.length === 1
|
|
343
|
-
* ```
|
|
344
|
-
*/
|
|
345
|
-
function addEdge(graph, config) {
|
|
346
|
-
const edge = createGraphEdge(config);
|
|
347
|
-
const idx = getIndex(graph);
|
|
348
|
-
if (idx.edgeById.has(config.id)) throw new Error(`Edge "${config.id}" already exists`);
|
|
349
|
-
if (!idx.nodeById.has(config.sourceId)) throw new Error(`Source node "${config.sourceId}" does not exist`);
|
|
350
|
-
if (!idx.nodeById.has(config.targetId)) throw new Error(`Target node "${config.targetId}" does not exist`);
|
|
351
|
-
if (config.sourcePort !== void 0) {
|
|
352
|
-
if (!graph.nodes[idx.nodeById.get(config.sourceId)].ports?.some((p) => p.name === config.sourcePort)) throw new Error(`Port "${config.sourcePort}" does not exist on source node "${config.sourceId}"`);
|
|
353
|
-
}
|
|
354
|
-
if (config.targetPort !== void 0) {
|
|
355
|
-
if (!graph.nodes[idx.nodeById.get(config.targetId)].ports?.some((p) => p.name === config.targetPort)) throw new Error(`Port "${config.targetPort}" does not exist on target node "${config.targetId}"`);
|
|
356
|
-
}
|
|
357
|
-
indexAddEdge(idx, edge, graph.edges.push(edge) - 1);
|
|
358
|
-
return edge;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* **Mutable.** Delete a node and its connected edges. Mutates `graph.nodes`
|
|
362
|
-
* and `graph.edges` in place.
|
|
363
|
-
*
|
|
364
|
-
* By default, children are deleted recursively.
|
|
365
|
-
* With `{ reparent: true }`, children are re-parented to the deleted node's parent.
|
|
366
|
-
*
|
|
367
|
-
* @example
|
|
368
|
-
* ```ts
|
|
369
|
-
* const graph = createGraph({
|
|
370
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
371
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
372
|
-
* });
|
|
373
|
-
* deleteNode(graph, 'a');
|
|
374
|
-
* // graph.nodes.length === 1, edge e1 also removed
|
|
375
|
-
* ```
|
|
376
|
-
*/
|
|
377
|
-
function deleteNode(graph, id, opts) {
|
|
378
|
-
const node = getNode(graph, id);
|
|
379
|
-
if (!node) throw new Error(`Node "${id}" does not exist`);
|
|
380
|
-
if (opts?.reparent) {
|
|
381
|
-
for (const n of graph.nodes) if (n.parentId === id) n.parentId = node.parentId;
|
|
382
|
-
graph.nodes = graph.nodes.filter((n) => n.id !== id);
|
|
383
|
-
graph.edges = graph.edges.filter((e) => e.sourceId !== id && e.targetId !== id);
|
|
384
|
-
} else {
|
|
385
|
-
const toDelete = collectDescendants(graph, id);
|
|
386
|
-
graph.nodes = graph.nodes.filter((n) => !toDelete.has(n.id));
|
|
387
|
-
graph.edges = graph.edges.filter((e) => !toDelete.has(e.sourceId) && !toDelete.has(e.targetId));
|
|
388
|
-
}
|
|
389
|
-
invalidateIndex(graph);
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* **Mutable.** Delete an edge. Mutates `graph.edges` in place.
|
|
393
|
-
*
|
|
394
|
-
* @example
|
|
395
|
-
* ```ts
|
|
396
|
-
* const graph = createGraph({
|
|
397
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
398
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
399
|
-
* });
|
|
400
|
-
* deleteEdge(graph, 'e1');
|
|
401
|
-
* // graph.edges.length === 0
|
|
402
|
-
* ```
|
|
403
|
-
*/
|
|
404
|
-
function deleteEdge(graph, id) {
|
|
405
|
-
if (!hasEdge(graph, id)) throw new Error(`Edge "${id}" does not exist`);
|
|
406
|
-
graph.edges = graph.edges.filter((e) => e.id !== id);
|
|
407
|
-
invalidateIndex(graph);
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* **Mutable.** Update a node in place.
|
|
411
|
-
* @returns The updated node.
|
|
412
|
-
*
|
|
413
|
-
* @example
|
|
414
|
-
* ```ts
|
|
415
|
-
* const graph = createGraph({ nodes: [{ id: 'a', label: 'old' }] });
|
|
416
|
-
* const updated = updateNode(graph, 'a', { label: 'new' });
|
|
417
|
-
* // updated.label === 'new'
|
|
418
|
-
* ```
|
|
419
|
-
*/
|
|
420
|
-
function updateNode(graph, id, update) {
|
|
421
|
-
const idx = getIndex(graph);
|
|
422
|
-
const arrayIdx = idx.nodeById.get(id);
|
|
423
|
-
if (arrayIdx === void 0) throw new Error(`Node "${id}" does not exist`);
|
|
424
|
-
if (update.parentId !== void 0 && update.parentId !== null) {
|
|
425
|
-
if (!idx.nodeById.has(update.parentId)) throw new Error(`Parent node "${update.parentId}" does not exist`);
|
|
426
|
-
}
|
|
427
|
-
if (update.ports !== void 0 && update.ports.length > 0) validatePortNames(update.ports);
|
|
428
|
-
const node = graph.nodes[arrayIdx];
|
|
429
|
-
const oldParentId = node.parentId;
|
|
430
|
-
const updated = {
|
|
431
|
-
...node,
|
|
432
|
-
...update.parentId !== void 0 && { parentId: update.parentId ?? null },
|
|
433
|
-
...update.initialNodeId !== void 0 && { initialNodeId: update.initialNodeId ?? null },
|
|
434
|
-
...update.label !== void 0 && { label: update.label },
|
|
435
|
-
...update.data !== void 0 && { data: update.data },
|
|
436
|
-
...update.ports !== void 0 && { ports: update.ports.map(createGraphPort) }
|
|
437
|
-
};
|
|
438
|
-
graph.nodes[arrayIdx] = updated;
|
|
439
|
-
if (update.parentId !== void 0 && updated.parentId !== oldParentId) indexReparentNode(idx, id, oldParentId, updated.parentId);
|
|
440
|
-
return updated;
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* **Mutable.** Update an edge in place.
|
|
444
|
-
* @returns The updated edge.
|
|
445
|
-
*
|
|
446
|
-
* @example
|
|
447
|
-
* ```ts
|
|
448
|
-
* const graph = createGraph({
|
|
449
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
450
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b', label: 'old' }],
|
|
451
|
-
* });
|
|
452
|
-
* const updated = updateEdge(graph, 'e1', { label: 'new' });
|
|
453
|
-
* // updated.label === 'new'
|
|
454
|
-
* ```
|
|
455
|
-
*/
|
|
456
|
-
function updateEdge(graph, id, update) {
|
|
457
|
-
const idx = getIndex(graph);
|
|
458
|
-
const arrayIdx = idx.edgeById.get(id);
|
|
459
|
-
if (arrayIdx === void 0) throw new Error(`Edge "${id}" does not exist`);
|
|
460
|
-
if (update.sourceId !== void 0 && !idx.nodeById.has(update.sourceId)) throw new Error(`Source node "${update.sourceId}" does not exist`);
|
|
461
|
-
if (update.targetId !== void 0 && !idx.nodeById.has(update.targetId)) throw new Error(`Target node "${update.targetId}" does not exist`);
|
|
462
|
-
const edge = graph.edges[arrayIdx];
|
|
463
|
-
const oldSourceId = edge.sourceId;
|
|
464
|
-
const oldTargetId = edge.targetId;
|
|
465
|
-
const effectiveSourceId = update.sourceId ?? edge.sourceId;
|
|
466
|
-
const effectiveTargetId = update.targetId ?? edge.targetId;
|
|
467
|
-
if (update.sourcePort !== void 0) {
|
|
468
|
-
if (!graph.nodes[idx.nodeById.get(effectiveSourceId)].ports?.some((p) => p.name === update.sourcePort)) throw new Error(`Port "${update.sourcePort}" does not exist on source node "${effectiveSourceId}"`);
|
|
469
|
-
}
|
|
470
|
-
if (update.targetPort !== void 0) {
|
|
471
|
-
if (!graph.nodes[idx.nodeById.get(effectiveTargetId)].ports?.some((p) => p.name === update.targetPort)) throw new Error(`Port "${update.targetPort}" does not exist on target node "${effectiveTargetId}"`);
|
|
472
|
-
}
|
|
473
|
-
const updated = {
|
|
474
|
-
...edge,
|
|
475
|
-
...update.sourceId !== void 0 && { sourceId: update.sourceId },
|
|
476
|
-
...update.targetId !== void 0 && { targetId: update.targetId },
|
|
477
|
-
...update.label !== void 0 && { label: update.label },
|
|
478
|
-
...update.data !== void 0 && { data: update.data },
|
|
479
|
-
...update.sourcePort !== void 0 && { sourcePort: update.sourcePort },
|
|
480
|
-
...update.targetPort !== void 0 && { targetPort: update.targetPort }
|
|
481
|
-
};
|
|
482
|
-
graph.edges[arrayIdx] = updated;
|
|
483
|
-
if (updated.sourceId !== oldSourceId || updated.targetId !== oldTargetId) indexUpdateEdgeEndpoints(idx, id, oldSourceId, oldTargetId, updated.sourceId, updated.targetId);
|
|
484
|
-
return updated;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* **Mutable.** Add multiple nodes and edges to the graph.
|
|
488
|
-
* Nodes are added first, then edges (so edges can reference new nodes).
|
|
489
|
-
*
|
|
490
|
-
* @example
|
|
491
|
-
* ```ts
|
|
492
|
-
* const graph = createGraph();
|
|
493
|
-
* addEntities(graph, {
|
|
494
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
495
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
496
|
-
* });
|
|
497
|
-
* // graph.nodes.length === 2, graph.edges.length === 1
|
|
498
|
-
* ```
|
|
499
|
-
*/
|
|
500
|
-
function addEntities(graph, entities) {
|
|
501
|
-
for (const nodeConfig of entities.nodes ?? []) addNode(graph, nodeConfig);
|
|
502
|
-
for (const edgeConfig of entities.edges ?? []) addEdge(graph, edgeConfig);
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* **Mutable.** Delete entities by id(s). Automatically detects whether each id
|
|
506
|
-
* is a node or edge. Node deletions cascade to children and connected edges.
|
|
507
|
-
*
|
|
508
|
-
* @example
|
|
509
|
-
* ```ts
|
|
510
|
-
* const graph = createGraph({
|
|
511
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
512
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
513
|
-
* });
|
|
514
|
-
* deleteEntities(graph, ['a', 'e1']);
|
|
515
|
-
* // graph.nodes.length === 1, graph.edges.length === 0
|
|
516
|
-
* ```
|
|
517
|
-
*/
|
|
518
|
-
function deleteEntities(graph, ids, opts) {
|
|
519
|
-
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
520
|
-
for (const id of idArray) if (hasNode(graph, id)) deleteNode(graph, id, opts);
|
|
521
|
-
else if (hasEdge(graph, id)) deleteEdge(graph, id);
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* **Mutable.** Update multiple nodes and edges in place.
|
|
525
|
-
* Each entry must include an `id` to identify which entity to update.
|
|
526
|
-
*
|
|
527
|
-
* @example
|
|
528
|
-
* ```ts
|
|
529
|
-
* const graph = createGraph({
|
|
530
|
-
* nodes: [{ id: 'a', label: 'old' }],
|
|
531
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', label: 'old' }],
|
|
532
|
-
* });
|
|
533
|
-
* updateEntities(graph, {
|
|
534
|
-
* nodes: [{ id: 'a', label: 'new' }],
|
|
535
|
-
* edges: [{ id: 'e1', label: 'new' }],
|
|
536
|
-
* });
|
|
537
|
-
* ```
|
|
538
|
-
*/
|
|
539
|
-
function updateEntities(graph, updates) {
|
|
540
|
-
for (const nodeUpdate of updates.nodes ?? []) {
|
|
541
|
-
const { id, ...patch } = nodeUpdate;
|
|
542
|
-
updateNode(graph, id, patch);
|
|
543
|
-
}
|
|
544
|
-
for (const edgeUpdate of updates.edges ?? []) {
|
|
545
|
-
const { id, ...patch } = edgeUpdate;
|
|
546
|
-
updateEdge(graph, id, patch);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* OOP wrapper around a plain `Graph` object.
|
|
551
|
-
* Delegates to the standalone mutable functions.
|
|
552
|
-
*
|
|
553
|
-
* @example
|
|
554
|
-
* ```ts
|
|
555
|
-
* const instance = new GraphInstance({
|
|
556
|
-
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
557
|
-
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
558
|
-
* });
|
|
559
|
-
* instance.addNode({ id: 'c' });
|
|
560
|
-
* instance.hasNode('c'); // true
|
|
561
|
-
* instance.toJSON(); // plain Graph object
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
|
-
var GraphInstance = class GraphInstance {
|
|
565
|
-
graph;
|
|
566
|
-
constructor(config) {
|
|
567
|
-
this.graph = createGraph(config);
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Wrap an existing plain graph object.
|
|
571
|
-
*
|
|
572
|
-
* @example
|
|
573
|
-
* ```ts
|
|
574
|
-
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
575
|
-
* const instance = GraphInstance.from(graph);
|
|
576
|
-
* instance.hasNode('a'); // true
|
|
577
|
-
* ```
|
|
578
|
-
*/
|
|
579
|
-
static from(graph) {
|
|
580
|
-
const instance = Object.create(GraphInstance.prototype);
|
|
581
|
-
instance.graph = graph;
|
|
582
|
-
return instance;
|
|
583
|
-
}
|
|
584
|
-
get id() {
|
|
585
|
-
return this.graph.id;
|
|
586
|
-
}
|
|
587
|
-
/** Default directedness for all edges. */
|
|
588
|
-
get mode() {
|
|
589
|
-
return this.graph.mode;
|
|
590
|
-
}
|
|
591
|
-
get nodes() {
|
|
592
|
-
return this.graph.nodes;
|
|
593
|
-
}
|
|
594
|
-
get edges() {
|
|
595
|
-
return this.graph.edges;
|
|
596
|
-
}
|
|
597
|
-
get data() {
|
|
598
|
-
return this.graph.data;
|
|
599
|
-
}
|
|
600
|
-
getNode(id) {
|
|
601
|
-
return getNode(this.graph, id);
|
|
602
|
-
}
|
|
603
|
-
getEdge(id) {
|
|
604
|
-
return getEdge(this.graph, id);
|
|
605
|
-
}
|
|
606
|
-
hasNode(id) {
|
|
607
|
-
return hasNode(this.graph, id);
|
|
608
|
-
}
|
|
609
|
-
hasEdge(id) {
|
|
610
|
-
return hasEdge(this.graph, id);
|
|
611
|
-
}
|
|
612
|
-
addNode(config) {
|
|
613
|
-
return addNode(this.graph, config);
|
|
614
|
-
}
|
|
615
|
-
addEdge(config) {
|
|
616
|
-
return addEdge(this.graph, config);
|
|
617
|
-
}
|
|
618
|
-
deleteNode(id, opts) {
|
|
619
|
-
return deleteNode(this.graph, id, opts);
|
|
620
|
-
}
|
|
621
|
-
deleteEdge(id) {
|
|
622
|
-
return deleteEdge(this.graph, id);
|
|
623
|
-
}
|
|
624
|
-
updateNode(id, update) {
|
|
625
|
-
return updateNode(this.graph, id, update);
|
|
626
|
-
}
|
|
627
|
-
updateEdge(id, update) {
|
|
628
|
-
return updateEdge(this.graph, id, update);
|
|
629
|
-
}
|
|
630
|
-
addEntities(entities) {
|
|
631
|
-
return addEntities(this.graph, entities);
|
|
632
|
-
}
|
|
633
|
-
deleteEntities(ids, opts) {
|
|
634
|
-
return deleteEntities(this.graph, ids, opts);
|
|
635
|
-
}
|
|
636
|
-
updateEntities(updates) {
|
|
637
|
-
return updateEntities(this.graph, updates);
|
|
638
|
-
}
|
|
639
|
-
toJSON() {
|
|
640
|
-
return this.graph;
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
function collectDescendants(graph, id) {
|
|
644
|
-
const idx = getIndex(graph);
|
|
645
|
-
const toDelete = /* @__PURE__ */ new Set();
|
|
646
|
-
const walk = (nodeId) => {
|
|
647
|
-
toDelete.add(nodeId);
|
|
648
|
-
const childIds = idx.childNodes.get(nodeId) ?? [];
|
|
649
|
-
for (const childId of childIds) if (!toDelete.has(childId)) walk(childId);
|
|
650
|
-
};
|
|
651
|
-
walk(id);
|
|
652
|
-
return toDelete;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
//#endregion
|
|
656
|
-
//#region src/algorithms/shared.ts
|
|
657
|
-
var MinPriorityQueue = class {
|
|
658
|
-
items = [];
|
|
659
|
-
constructor(compare) {
|
|
660
|
-
this.compare = compare;
|
|
661
|
-
}
|
|
662
|
-
get size() {
|
|
663
|
-
return this.items.length;
|
|
664
|
-
}
|
|
665
|
-
push(item) {
|
|
666
|
-
this.items.push(item);
|
|
667
|
-
this.bubbleUp(this.items.length - 1);
|
|
668
|
-
}
|
|
669
|
-
pop() {
|
|
670
|
-
if (this.items.length === 0) return void 0;
|
|
671
|
-
const first = this.items[0];
|
|
672
|
-
const last = this.items.pop();
|
|
673
|
-
if (this.items.length > 0) {
|
|
674
|
-
this.items[0] = last;
|
|
675
|
-
this.bubbleDown(0);
|
|
676
|
-
}
|
|
677
|
-
return first;
|
|
678
|
-
}
|
|
679
|
-
bubbleUp(index) {
|
|
680
|
-
let current = index;
|
|
681
|
-
while (current > 0) {
|
|
682
|
-
const parent = Math.floor((current - 1) / 2);
|
|
683
|
-
if (this.compare(this.items[current], this.items[parent]) >= 0) break;
|
|
684
|
-
[this.items[current], this.items[parent]] = [this.items[parent], this.items[current]];
|
|
685
|
-
current = parent;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
bubbleDown(index) {
|
|
689
|
-
let current = index;
|
|
690
|
-
while (true) {
|
|
691
|
-
const left = current * 2 + 1;
|
|
692
|
-
const right = left + 1;
|
|
693
|
-
let smallest = current;
|
|
694
|
-
if (left < this.items.length && this.compare(this.items[left], this.items[smallest]) < 0) smallest = left;
|
|
695
|
-
if (right < this.items.length && this.compare(this.items[right], this.items[smallest]) < 0) smallest = right;
|
|
696
|
-
if (smallest === current) break;
|
|
697
|
-
[this.items[current], this.items[smallest]] = [this.items[smallest], this.items[current]];
|
|
698
|
-
current = smallest;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
};
|
|
702
|
-
function getNeighborIds$1(graph, nodeId) {
|
|
703
|
-
const idx = getIndex(graph);
|
|
704
|
-
const ids = [];
|
|
705
|
-
for (const eid of idx.outEdges.get(nodeId) ?? []) {
|
|
706
|
-
const ai = idx.edgeById.get(eid);
|
|
707
|
-
if (ai !== void 0) ids.push(graph.edges[ai].targetId);
|
|
708
|
-
}
|
|
709
|
-
for (const eid of idx.inEdges.get(nodeId) ?? []) {
|
|
710
|
-
const ai = idx.edgeById.get(eid);
|
|
711
|
-
if (ai === void 0) continue;
|
|
712
|
-
const edge = graph.edges[ai];
|
|
713
|
-
if (getEdgeMode(graph, edge) !== "directed") ids.push(edge.sourceId);
|
|
714
|
-
}
|
|
715
|
-
return ids;
|
|
716
|
-
}
|
|
717
|
-
function getSuccessorIds(graph, nodeId) {
|
|
718
|
-
const idx = getIndex(graph);
|
|
719
|
-
return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)].targetId);
|
|
720
|
-
}
|
|
721
|
-
function resolveFrom(graph, opts) {
|
|
722
|
-
if (opts?.from) return opts.from;
|
|
723
|
-
if (graph.initialNodeId) return graph.initialNodeId;
|
|
724
|
-
const inDeg = /* @__PURE__ */ new Map();
|
|
725
|
-
for (const node of graph.nodes) inDeg.set(node.id, 0);
|
|
726
|
-
for (const edge of graph.edges) inDeg.set(edge.targetId, (inDeg.get(edge.targetId) ?? 0) + 1);
|
|
727
|
-
const roots = [...inDeg.entries()].filter(([, degree]) => degree === 0).map(([id]) => id);
|
|
728
|
-
if (roots.length === 1) return roots[0];
|
|
729
|
-
throw new Error("Cannot determine start node — provide opts.from or set graph.initialNodeId");
|
|
730
|
-
}
|
|
731
|
-
function getNeighborEdges(graph, nodeId) {
|
|
732
|
-
const idx = getIndex(graph);
|
|
733
|
-
const result = [];
|
|
734
|
-
for (const eid of idx.outEdges.get(nodeId) ?? []) {
|
|
735
|
-
const ai = idx.edgeById.get(eid);
|
|
736
|
-
if (ai !== void 0) {
|
|
737
|
-
const edge = graph.edges[ai];
|
|
738
|
-
result.push({
|
|
739
|
-
neighborId: edge.targetId,
|
|
740
|
-
edge
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
for (const eid of idx.inEdges.get(nodeId) ?? []) {
|
|
745
|
-
const ai = idx.edgeById.get(eid);
|
|
746
|
-
if (ai !== void 0) {
|
|
747
|
-
const edge = graph.edges[ai];
|
|
748
|
-
if (getEdgeMode(graph, edge) !== "directed") result.push({
|
|
749
|
-
neighborId: edge.sourceId,
|
|
750
|
-
edge
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return result;
|
|
755
|
-
}
|
|
756
|
-
function getNeighborEdgesAll(graph, nodeId) {
|
|
757
|
-
const idx = getIndex(graph);
|
|
758
|
-
const result = [];
|
|
759
|
-
for (const eid of idx.outEdges.get(nodeId) ?? []) {
|
|
760
|
-
const ai = idx.edgeById.get(eid);
|
|
761
|
-
if (ai !== void 0) {
|
|
762
|
-
const edge = graph.edges[ai];
|
|
763
|
-
result.push({
|
|
764
|
-
neighborId: edge.targetId,
|
|
765
|
-
edge
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
for (const eid of idx.inEdges.get(nodeId) ?? []) {
|
|
770
|
-
const ai = idx.edgeById.get(eid);
|
|
771
|
-
if (ai !== void 0) {
|
|
772
|
-
const edge = graph.edges[ai];
|
|
773
|
-
result.push({
|
|
774
|
-
neighborId: edge.sourceId,
|
|
775
|
-
edge
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
return result;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
//#endregion
|
|
783
|
-
//#region src/algorithms/paths.ts
|
|
784
|
-
function computeShortestDistances(graph, sourceId, getWeight, algorithm) {
|
|
785
|
-
if (algorithm === "bellman-ford") return bellmanFord(graph, sourceId, getWeight);
|
|
786
|
-
const dist = /* @__PURE__ */ new Map();
|
|
787
|
-
const prev = /* @__PURE__ */ new Map();
|
|
788
|
-
dist.set(sourceId, 0);
|
|
789
|
-
prev.set(sourceId, []);
|
|
790
|
-
if (!getWeight && !graph.edges.some((edge) => edge.weight !== void 0)) {
|
|
791
|
-
const queue = [sourceId];
|
|
792
|
-
while (queue.length > 0) {
|
|
793
|
-
const id = queue.shift();
|
|
794
|
-
const distance = dist.get(id);
|
|
795
|
-
for (const { neighborId, edge } of getNeighborEdges(graph, id)) {
|
|
796
|
-
const nextDistance = distance + 1;
|
|
797
|
-
const existing = dist.get(neighborId);
|
|
798
|
-
if (existing === void 0) {
|
|
799
|
-
dist.set(neighborId, nextDistance);
|
|
800
|
-
prev.set(neighborId, [{
|
|
801
|
-
from: id,
|
|
802
|
-
edge
|
|
803
|
-
}]);
|
|
804
|
-
queue.push(neighborId);
|
|
805
|
-
} else if (existing === nextDistance) prev.get(neighborId).push({
|
|
806
|
-
from: id,
|
|
807
|
-
edge
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
} else {
|
|
812
|
-
const effectiveWeight = getWeight ?? ((edge) => edge.weight ?? 1);
|
|
813
|
-
const visited = /* @__PURE__ */ new Set();
|
|
814
|
-
const pq = new MinPriorityQueue((a, b) => a.dist - b.dist);
|
|
815
|
-
pq.push({
|
|
816
|
-
id: sourceId,
|
|
817
|
-
dist: 0
|
|
818
|
-
});
|
|
819
|
-
while (pq.size > 0) {
|
|
820
|
-
const { id, dist: distance } = pq.pop();
|
|
821
|
-
if (visited.has(id) || distance !== dist.get(id)) continue;
|
|
822
|
-
visited.add(id);
|
|
823
|
-
for (const { neighborId, edge } of getNeighborEdges(graph, id)) {
|
|
824
|
-
const nextDistance = distance + effectiveWeight(edge);
|
|
825
|
-
const existing = dist.get(neighborId);
|
|
826
|
-
if (existing === void 0 || nextDistance < existing) {
|
|
827
|
-
dist.set(neighborId, nextDistance);
|
|
828
|
-
prev.set(neighborId, [{
|
|
829
|
-
from: id,
|
|
830
|
-
edge
|
|
831
|
-
}]);
|
|
832
|
-
pq.push({
|
|
833
|
-
id: neighborId,
|
|
834
|
-
dist: nextDistance
|
|
835
|
-
});
|
|
836
|
-
} else if (existing === nextDistance) prev.get(neighborId).push({
|
|
837
|
-
from: id,
|
|
838
|
-
edge
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
return {
|
|
844
|
-
dist,
|
|
845
|
-
prev
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
function bellmanFord(graph, sourceId, getWeight) {
|
|
849
|
-
const dist = /* @__PURE__ */ new Map();
|
|
850
|
-
const prev = /* @__PURE__ */ new Map();
|
|
851
|
-
const effectiveWeight = getWeight ?? ((edge) => edge.weight ?? 1);
|
|
852
|
-
for (const node of graph.nodes) {
|
|
853
|
-
dist.set(node.id, Infinity);
|
|
854
|
-
prev.set(node.id, []);
|
|
855
|
-
}
|
|
856
|
-
dist.set(sourceId, 0);
|
|
857
|
-
const directedEdges = [];
|
|
858
|
-
for (const edge of graph.edges) {
|
|
859
|
-
directedEdges.push({
|
|
860
|
-
fromId: edge.sourceId,
|
|
861
|
-
toId: edge.targetId,
|
|
862
|
-
edge
|
|
863
|
-
});
|
|
864
|
-
if (getEdgeMode(graph, edge) !== "directed") directedEdges.push({
|
|
865
|
-
fromId: edge.targetId,
|
|
866
|
-
toId: edge.sourceId,
|
|
867
|
-
edge
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
for (let i = 0; i < graph.nodes.length - 1; i++) {
|
|
871
|
-
let changed = false;
|
|
872
|
-
for (const { fromId, toId, edge } of directedEdges) {
|
|
873
|
-
const distance = dist.get(fromId);
|
|
874
|
-
if (distance === Infinity) continue;
|
|
875
|
-
const nextDistance = distance + effectiveWeight(edge);
|
|
876
|
-
const existing = dist.get(toId);
|
|
877
|
-
if (nextDistance < existing) {
|
|
878
|
-
dist.set(toId, nextDistance);
|
|
879
|
-
prev.set(toId, [{
|
|
880
|
-
from: fromId,
|
|
881
|
-
edge
|
|
882
|
-
}]);
|
|
883
|
-
changed = true;
|
|
884
|
-
} else if (nextDistance === existing && existing !== Infinity) {
|
|
885
|
-
const predecessors = prev.get(toId);
|
|
886
|
-
if (!predecessors.some((entry) => entry.from === fromId && entry.edge === edge)) predecessors.push({
|
|
887
|
-
from: fromId,
|
|
888
|
-
edge
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (!changed) break;
|
|
893
|
-
}
|
|
894
|
-
for (const { fromId, toId, edge } of directedEdges) {
|
|
895
|
-
const distance = dist.get(fromId);
|
|
896
|
-
if (distance === Infinity) continue;
|
|
897
|
-
if (distance + effectiveWeight(edge) < dist.get(toId)) throw new Error("Graph contains a negative-weight cycle reachable from the source node");
|
|
898
|
-
}
|
|
899
|
-
for (const [id, distance] of dist) if (distance === Infinity) {
|
|
900
|
-
dist.delete(id);
|
|
901
|
-
prev.delete(id);
|
|
902
|
-
}
|
|
903
|
-
return {
|
|
904
|
-
dist,
|
|
905
|
-
prev
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
function* reconstructPaths(graph, prev, sourceNode, targetId) {
|
|
909
|
-
if (targetId === sourceNode.id) {
|
|
910
|
-
yield {
|
|
911
|
-
source: sourceNode,
|
|
912
|
-
steps: []
|
|
913
|
-
};
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
const predecessors = prev.get(targetId);
|
|
917
|
-
if (!predecessors || predecessors.length === 0) return;
|
|
918
|
-
const targetNi = getIndex(graph).nodeById.get(targetId);
|
|
919
|
-
const targetNode = targetNi !== void 0 ? graph.nodes[targetNi] : graph.nodes.find((node) => node.id === targetId);
|
|
920
|
-
for (const { from, edge } of predecessors) for (const prefix of reconstructPaths(graph, prev, sourceNode, from)) yield {
|
|
921
|
-
source: sourceNode,
|
|
922
|
-
steps: [...prefix.steps, {
|
|
923
|
-
edge,
|
|
924
|
-
node: targetNode
|
|
925
|
-
}]
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
function* genShortestPaths(graph, opts) {
|
|
929
|
-
const idx = getIndex(graph);
|
|
930
|
-
const sourceId = resolveFrom(graph, opts);
|
|
931
|
-
const { dist, prev } = computeShortestDistances(graph, sourceId, opts?.getWeight, opts?.algorithm);
|
|
932
|
-
const targets = opts?.to ? [opts.to].filter((id) => dist.has(id)) : [...dist.keys()].filter((id) => id !== sourceId);
|
|
933
|
-
const sourceNi = idx.nodeById.get(sourceId);
|
|
934
|
-
const sourceNode = sourceNi !== void 0 ? graph.nodes[sourceNi] : graph.nodes.find((node) => node.id === sourceId);
|
|
935
|
-
for (const targetId of targets) yield* reconstructPaths(graph, prev, sourceNode, targetId);
|
|
936
|
-
}
|
|
937
|
-
function getShortestPaths(graph, opts) {
|
|
938
|
-
return [...genShortestPaths(graph, opts)];
|
|
939
|
-
}
|
|
940
|
-
function getShortestPath(graph, opts) {
|
|
941
|
-
for (const path of genShortestPaths(graph, opts)) return path;
|
|
942
|
-
}
|
|
943
|
-
function getSimplePaths(graph, opts) {
|
|
944
|
-
return [...genSimplePaths(graph, opts)];
|
|
945
|
-
}
|
|
946
|
-
function* genSimplePaths(graph, opts) {
|
|
947
|
-
const idx = getIndex(graph);
|
|
948
|
-
const sourceId = resolveFrom(graph, opts);
|
|
949
|
-
const sourceNi = idx.nodeById.get(sourceId);
|
|
950
|
-
const sourceNode = sourceNi !== void 0 ? graph.nodes[sourceNi] : graph.nodes.find((node) => node.id === sourceId);
|
|
951
|
-
const targetId = opts?.to;
|
|
952
|
-
const visited = /* @__PURE__ */ new Set();
|
|
953
|
-
const currentSteps = [];
|
|
954
|
-
function* dfsCollect(nodeId) {
|
|
955
|
-
visited.add(nodeId);
|
|
956
|
-
if (targetId !== void 0) {
|
|
957
|
-
if (nodeId === targetId) {
|
|
958
|
-
yield {
|
|
959
|
-
source: sourceNode,
|
|
960
|
-
steps: [...currentSteps]
|
|
961
|
-
};
|
|
962
|
-
visited.delete(nodeId);
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
} else if (currentSteps.length > 0) yield {
|
|
966
|
-
source: sourceNode,
|
|
967
|
-
steps: [...currentSteps]
|
|
968
|
-
};
|
|
969
|
-
for (const { neighborId, edge } of getNeighborEdges(graph, nodeId)) if (!visited.has(neighborId)) {
|
|
970
|
-
const neighborNi = idx.nodeById.get(neighborId);
|
|
971
|
-
const neighborNode = neighborNi !== void 0 ? graph.nodes[neighborNi] : graph.nodes.find((node) => node.id === neighborId);
|
|
972
|
-
currentSteps.push({
|
|
973
|
-
edge,
|
|
974
|
-
node: neighborNode
|
|
975
|
-
});
|
|
976
|
-
yield* dfsCollect(neighborId);
|
|
977
|
-
currentSteps.pop();
|
|
978
|
-
}
|
|
979
|
-
visited.delete(nodeId);
|
|
980
|
-
}
|
|
981
|
-
yield* dfsCollect(sourceId);
|
|
982
|
-
}
|
|
983
|
-
function getSimplePath(graph, opts) {
|
|
984
|
-
for (const path of genSimplePaths(graph, opts)) return path;
|
|
985
|
-
}
|
|
986
|
-
function getStronglyConnectedComponents(graph) {
|
|
987
|
-
const idx = getIndex(graph);
|
|
988
|
-
let indexCounter = 0;
|
|
989
|
-
const nodeIndex = /* @__PURE__ */ new Map();
|
|
990
|
-
const lowlink = /* @__PURE__ */ new Map();
|
|
991
|
-
const onStack = /* @__PURE__ */ new Set();
|
|
992
|
-
const stack = [];
|
|
993
|
-
const result = [];
|
|
994
|
-
function strongconnect(id) {
|
|
995
|
-
nodeIndex.set(id, indexCounter);
|
|
996
|
-
lowlink.set(id, indexCounter);
|
|
997
|
-
indexCounter++;
|
|
998
|
-
stack.push(id);
|
|
999
|
-
onStack.add(id);
|
|
1000
|
-
for (const eid of idx.outEdges.get(id) ?? []) {
|
|
1001
|
-
const ai = idx.edgeById.get(eid);
|
|
1002
|
-
if (ai === void 0) continue;
|
|
1003
|
-
const neighborId = graph.edges[ai].targetId;
|
|
1004
|
-
if (!nodeIndex.has(neighborId)) {
|
|
1005
|
-
strongconnect(neighborId);
|
|
1006
|
-
lowlink.set(id, Math.min(lowlink.get(id), lowlink.get(neighborId)));
|
|
1007
|
-
} else if (onStack.has(neighborId)) lowlink.set(id, Math.min(lowlink.get(id), nodeIndex.get(neighborId)));
|
|
1008
|
-
}
|
|
1009
|
-
if (lowlink.get(id) === nodeIndex.get(id)) {
|
|
1010
|
-
const component = [];
|
|
1011
|
-
let neighborId;
|
|
1012
|
-
do {
|
|
1013
|
-
neighborId = stack.pop();
|
|
1014
|
-
onStack.delete(neighborId);
|
|
1015
|
-
const ni = idx.nodeById.get(neighborId);
|
|
1016
|
-
if (ni !== void 0) component.push(graph.nodes[ni]);
|
|
1017
|
-
} while (neighborId !== id);
|
|
1018
|
-
result.push(component);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
for (const node of graph.nodes) if (!nodeIndex.has(node.id)) strongconnect(node.id);
|
|
1022
|
-
return result;
|
|
1023
|
-
}
|
|
1024
|
-
function getCycles(graph) {
|
|
1025
|
-
return [...genCycles(graph)];
|
|
1026
|
-
}
|
|
1027
|
-
function* genCycles(graph) {
|
|
1028
|
-
if (graph.mode !== "directed") yield* genCyclesUndirected(graph);
|
|
1029
|
-
else yield* genCyclesDirected(graph);
|
|
1030
|
-
}
|
|
1031
|
-
function* genCyclesDirected(graph) {
|
|
1032
|
-
const idx = getIndex(graph);
|
|
1033
|
-
const sortedIds = graph.nodes.map((node) => node.id).sort();
|
|
1034
|
-
for (let startIndex = 0; startIndex < sortedIds.length; startIndex++) {
|
|
1035
|
-
const startId = sortedIds[startIndex];
|
|
1036
|
-
const allowed = new Set(sortedIds.slice(startIndex));
|
|
1037
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1038
|
-
const steps = [];
|
|
1039
|
-
const startNi = idx.nodeById.get(startId);
|
|
1040
|
-
const startNode = graph.nodes[startNi];
|
|
1041
|
-
const found = [];
|
|
1042
|
-
function dfsFind(currentId) {
|
|
1043
|
-
visited.add(currentId);
|
|
1044
|
-
for (const eid of idx.outEdges.get(currentId) ?? []) {
|
|
1045
|
-
const ai = idx.edgeById.get(eid);
|
|
1046
|
-
if (ai === void 0) continue;
|
|
1047
|
-
const edge = graph.edges[ai];
|
|
1048
|
-
const neighborId = edge.targetId;
|
|
1049
|
-
if (neighborId === startId && (steps.length > 0 || currentId === startId)) found.push({
|
|
1050
|
-
source: startNode,
|
|
1051
|
-
steps: [...steps, {
|
|
1052
|
-
edge,
|
|
1053
|
-
node: startNode
|
|
1054
|
-
}]
|
|
1055
|
-
});
|
|
1056
|
-
else if (allowed.has(neighborId) && !visited.has(neighborId)) {
|
|
1057
|
-
const ni = idx.nodeById.get(neighborId);
|
|
1058
|
-
steps.push({
|
|
1059
|
-
edge,
|
|
1060
|
-
node: graph.nodes[ni]
|
|
1061
|
-
});
|
|
1062
|
-
dfsFind(neighborId);
|
|
1063
|
-
steps.pop();
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
visited.delete(currentId);
|
|
1067
|
-
}
|
|
1068
|
-
dfsFind(startId);
|
|
1069
|
-
yield* found;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
function* genCyclesUndirected(graph) {
|
|
1073
|
-
const idx = getIndex(graph);
|
|
1074
|
-
const sortedIds = graph.nodes.map((node) => node.id).sort();
|
|
1075
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1076
|
-
for (let startIndex = 0; startIndex < sortedIds.length; startIndex++) {
|
|
1077
|
-
const startId = sortedIds[startIndex];
|
|
1078
|
-
const allowed = new Set(sortedIds.slice(startIndex));
|
|
1079
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1080
|
-
const steps = [];
|
|
1081
|
-
const startNi = idx.nodeById.get(startId);
|
|
1082
|
-
const startNode = graph.nodes[startNi];
|
|
1083
|
-
const found = [];
|
|
1084
|
-
function dfsFind(currentId, parentId) {
|
|
1085
|
-
visited.add(currentId);
|
|
1086
|
-
for (const { neighborId, edge } of getNeighborEdgesAll(graph, currentId)) {
|
|
1087
|
-
if (neighborId === parentId) {
|
|
1088
|
-
parentId = null;
|
|
1089
|
-
continue;
|
|
1090
|
-
}
|
|
1091
|
-
if (neighborId === startId && steps.length >= 2) {
|
|
1092
|
-
const innerIds = steps.map((step) => step.node.id).sort().join(",");
|
|
1093
|
-
if (!seen.has(innerIds)) {
|
|
1094
|
-
seen.add(innerIds);
|
|
1095
|
-
found.push({
|
|
1096
|
-
source: startNode,
|
|
1097
|
-
steps: [...steps, {
|
|
1098
|
-
edge,
|
|
1099
|
-
node: startNode
|
|
1100
|
-
}]
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
} else if (allowed.has(neighborId) && !visited.has(neighborId)) {
|
|
1104
|
-
const ni = idx.nodeById.get(neighborId);
|
|
1105
|
-
steps.push({
|
|
1106
|
-
edge,
|
|
1107
|
-
node: graph.nodes[ni]
|
|
1108
|
-
});
|
|
1109
|
-
dfsFind(neighborId, currentId);
|
|
1110
|
-
steps.pop();
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
visited.delete(currentId);
|
|
1114
|
-
}
|
|
1115
|
-
dfsFind(startId, null);
|
|
1116
|
-
yield* found;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
function getAllPairsShortestPaths(graph, opts) {
|
|
1120
|
-
const algorithm = opts?.algorithm ?? "dijkstra";
|
|
1121
|
-
if (algorithm === "floyd-warshall") return floydWarshallAllPaths(graph, opts?.getWeight);
|
|
1122
|
-
if (algorithm === "bellman-ford") return bellmanFordAllPaths(graph, opts?.getWeight);
|
|
1123
|
-
return dijkstraAllPaths(graph, opts?.getWeight);
|
|
1124
|
-
}
|
|
1125
|
-
function bellmanFordAllPaths(graph, getWeight) {
|
|
1126
|
-
const results = [];
|
|
1127
|
-
for (const node of graph.nodes) results.push(...getShortestPaths(graph, {
|
|
1128
|
-
from: node.id,
|
|
1129
|
-
getWeight,
|
|
1130
|
-
algorithm: "bellman-ford"
|
|
1131
|
-
}));
|
|
1132
|
-
return results;
|
|
1133
|
-
}
|
|
1134
|
-
function dijkstraAllPaths(graph, getWeight) {
|
|
1135
|
-
const results = [];
|
|
1136
|
-
for (const node of graph.nodes) results.push(...getShortestPaths(graph, {
|
|
1137
|
-
from: node.id,
|
|
1138
|
-
getWeight
|
|
1139
|
-
}));
|
|
1140
|
-
return results;
|
|
1141
|
-
}
|
|
1142
|
-
function floydWarshallAllPaths(graph, getWeight) {
|
|
1143
|
-
const idx = getIndex(graph);
|
|
1144
|
-
const weight = getWeight ?? ((edge) => edge.weight ?? 1);
|
|
1145
|
-
const nodeIds = graph.nodes.map((node) => node.id);
|
|
1146
|
-
const nodeCount = nodeIds.length;
|
|
1147
|
-
const indexOf = /* @__PURE__ */ new Map();
|
|
1148
|
-
for (let i = 0; i < nodeCount; i++) indexOf.set(nodeIds[i], i);
|
|
1149
|
-
const INF = Infinity;
|
|
1150
|
-
const dist = Array.from({ length: nodeCount }, () => Array(nodeCount).fill(INF));
|
|
1151
|
-
const prev = Array.from({ length: nodeCount }, () => Array.from({ length: nodeCount }, () => []));
|
|
1152
|
-
for (let i = 0; i < nodeCount; i++) dist[i][i] = 0;
|
|
1153
|
-
for (const edge of graph.edges) {
|
|
1154
|
-
const source = indexOf.get(edge.sourceId);
|
|
1155
|
-
const target = indexOf.get(edge.targetId);
|
|
1156
|
-
const edgeWeight = weight(edge);
|
|
1157
|
-
if (edgeWeight < dist[source][target]) {
|
|
1158
|
-
dist[source][target] = edgeWeight;
|
|
1159
|
-
prev[source][target] = [{
|
|
1160
|
-
from: source,
|
|
1161
|
-
edge
|
|
1162
|
-
}];
|
|
1163
|
-
} else if (edgeWeight === dist[source][target] && edgeWeight < INF) prev[source][target].push({
|
|
1164
|
-
from: source,
|
|
1165
|
-
edge
|
|
1166
|
-
});
|
|
1167
|
-
if (getEdgeMode(graph, edge) !== "directed") {
|
|
1168
|
-
if (edgeWeight < dist[target][source]) {
|
|
1169
|
-
dist[target][source] = edgeWeight;
|
|
1170
|
-
prev[target][source] = [{
|
|
1171
|
-
from: target,
|
|
1172
|
-
edge
|
|
1173
|
-
}];
|
|
1174
|
-
} else if (edgeWeight === dist[target][source] && edgeWeight < INF) prev[target][source].push({
|
|
1175
|
-
from: target,
|
|
1176
|
-
edge
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
for (let k = 0; k < nodeCount; k++) for (let i = 0; i < nodeCount; i++) for (let j = 0; j < nodeCount; j++) {
|
|
1181
|
-
if (dist[i][k] === INF || dist[k][j] === INF) continue;
|
|
1182
|
-
const nextDistance = dist[i][k] + dist[k][j];
|
|
1183
|
-
if (nextDistance < dist[i][j]) {
|
|
1184
|
-
dist[i][j] = nextDistance;
|
|
1185
|
-
prev[i][j] = prev[k][j].map((entry) => ({ ...entry }));
|
|
1186
|
-
} else if (nextDistance === dist[i][j] && nextDistance < INF) {
|
|
1187
|
-
for (const entry of prev[k][j]) if (!prev[i][j].some((existing) => existing.edge.id === entry.edge.id)) prev[i][j].push({ ...entry });
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
const results = [];
|
|
1191
|
-
for (let i = 0; i < nodeCount; i++) {
|
|
1192
|
-
const sourceNi = idx.nodeById.get(nodeIds[i]);
|
|
1193
|
-
if (sourceNi === void 0) continue;
|
|
1194
|
-
const sourceNode = graph.nodes[sourceNi];
|
|
1195
|
-
for (let j = 0; j < nodeCount; j++) {
|
|
1196
|
-
if (i === j || dist[i][j] === INF) continue;
|
|
1197
|
-
results.push(...fwReconstruct(graph, prev, nodeIds, sourceNode, i, j));
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
return results;
|
|
1201
|
-
}
|
|
1202
|
-
function fwReconstruct(graph, prev, nodeIds, sourceNode, sourceIdx, targetIdx) {
|
|
1203
|
-
if (sourceIdx === targetIdx) return [{
|
|
1204
|
-
source: sourceNode,
|
|
1205
|
-
steps: []
|
|
1206
|
-
}];
|
|
1207
|
-
const predecessors = prev[sourceIdx][targetIdx];
|
|
1208
|
-
if (predecessors.length === 0) return [];
|
|
1209
|
-
const targetNi = getIndex(graph).nodeById.get(nodeIds[targetIdx]);
|
|
1210
|
-
if (targetNi === void 0) return [];
|
|
1211
|
-
const targetNode = graph.nodes[targetNi];
|
|
1212
|
-
const results = [];
|
|
1213
|
-
for (const { from, edge } of predecessors) {
|
|
1214
|
-
const prefixPaths = fwReconstruct(graph, prev, nodeIds, sourceNode, sourceIdx, from);
|
|
1215
|
-
for (const prefix of prefixPaths) results.push({
|
|
1216
|
-
source: sourceNode,
|
|
1217
|
-
steps: [...prefix.steps, {
|
|
1218
|
-
edge,
|
|
1219
|
-
node: targetNode
|
|
1220
|
-
}]
|
|
1221
|
-
});
|
|
1222
|
-
}
|
|
1223
|
-
return results;
|
|
1224
|
-
}
|
|
1225
|
-
function getAStarPath(graph, opts) {
|
|
1226
|
-
const idx = getIndex(graph);
|
|
1227
|
-
const { from: sourceId, to: targetId, heuristic } = opts;
|
|
1228
|
-
const getWeight = opts.getWeight ?? ((edge) => edge.weight ?? 1);
|
|
1229
|
-
const sourceNi = idx.nodeById.get(sourceId);
|
|
1230
|
-
if (sourceNi === void 0) return void 0;
|
|
1231
|
-
if (!idx.nodeById.has(targetId)) return void 0;
|
|
1232
|
-
if (sourceId === targetId) return {
|
|
1233
|
-
source: graph.nodes[sourceNi],
|
|
1234
|
-
steps: []
|
|
1235
|
-
};
|
|
1236
|
-
const gScore = /* @__PURE__ */ new Map();
|
|
1237
|
-
const cameFrom = /* @__PURE__ */ new Map();
|
|
1238
|
-
const closedSet = /* @__PURE__ */ new Set();
|
|
1239
|
-
const openSet = new MinPriorityQueue((a, b) => a.f - b.f);
|
|
1240
|
-
gScore.set(sourceId, 0);
|
|
1241
|
-
openSet.push({
|
|
1242
|
-
id: sourceId,
|
|
1243
|
-
f: heuristic(sourceId)
|
|
1244
|
-
});
|
|
1245
|
-
while (openSet.size > 0) {
|
|
1246
|
-
const { id: currentId } = openSet.pop();
|
|
1247
|
-
if (closedSet.has(currentId)) continue;
|
|
1248
|
-
if (currentId === targetId) {
|
|
1249
|
-
const steps = [];
|
|
1250
|
-
let current = targetId;
|
|
1251
|
-
while (current !== sourceId) {
|
|
1252
|
-
const previous = cameFrom.get(current);
|
|
1253
|
-
const ni = idx.nodeById.get(current);
|
|
1254
|
-
steps.unshift({
|
|
1255
|
-
edge: previous.edge,
|
|
1256
|
-
node: graph.nodes[ni]
|
|
1257
|
-
});
|
|
1258
|
-
current = previous.from;
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
source: graph.nodes[sourceNi],
|
|
1262
|
-
steps
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
closedSet.add(currentId);
|
|
1266
|
-
for (const { neighborId, edge } of getNeighborEdges(graph, currentId)) {
|
|
1267
|
-
if (closedSet.has(neighborId)) continue;
|
|
1268
|
-
const tentativeScore = (gScore.get(currentId) ?? Infinity) + getWeight(edge);
|
|
1269
|
-
if (tentativeScore < (gScore.get(neighborId) ?? Infinity)) {
|
|
1270
|
-
cameFrom.set(neighborId, {
|
|
1271
|
-
from: currentId,
|
|
1272
|
-
edge
|
|
1273
|
-
});
|
|
1274
|
-
gScore.set(neighborId, tentativeScore);
|
|
1275
|
-
openSet.push({
|
|
1276
|
-
id: neighborId,
|
|
1277
|
-
f: tentativeScore + heuristic(neighborId)
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
function joinPaths(headPath, tailPath) {
|
|
1284
|
-
const headEnd = headPath.steps.length > 0 ? headPath.steps[headPath.steps.length - 1].node : headPath.source;
|
|
1285
|
-
if (headEnd.id !== tailPath.source.id) throw new Error(`Paths cannot be joined: head path ends at "${headEnd.id}" but tail path starts at "${tailPath.source.id}"`);
|
|
1286
|
-
return {
|
|
1287
|
-
source: headPath.source,
|
|
1288
|
-
steps: [...headPath.steps, ...tailPath.steps]
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
//#endregion
|
|
1293
|
-
//#region src/algorithms/traversal.ts
|
|
1294
|
-
function* bfs(graph, startId) {
|
|
1295
|
-
const idx = getIndex(graph);
|
|
1296
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1297
|
-
const queue = [startId];
|
|
1298
|
-
visited.add(startId);
|
|
1299
|
-
while (queue.length > 0) {
|
|
1300
|
-
const id = queue.shift();
|
|
1301
|
-
const ni = idx.nodeById.get(id);
|
|
1302
|
-
if (ni === void 0) continue;
|
|
1303
|
-
yield graph.nodes[ni];
|
|
1304
|
-
for (const neighborId of getNeighborIds$1(graph, id)) if (!visited.has(neighborId)) {
|
|
1305
|
-
visited.add(neighborId);
|
|
1306
|
-
queue.push(neighborId);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
function* dfs(graph, startId) {
|
|
1311
|
-
const idx = getIndex(graph);
|
|
1312
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1313
|
-
const stack = [startId];
|
|
1314
|
-
while (stack.length > 0) {
|
|
1315
|
-
const id = stack.pop();
|
|
1316
|
-
if (visited.has(id)) continue;
|
|
1317
|
-
visited.add(id);
|
|
1318
|
-
const ni = idx.nodeById.get(id);
|
|
1319
|
-
if (ni === void 0) continue;
|
|
1320
|
-
yield graph.nodes[ni];
|
|
1321
|
-
for (const neighborId of getNeighborIds$1(graph, id)) if (!visited.has(neighborId)) stack.push(neighborId);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
function isAcyclic(graph) {
|
|
1325
|
-
if (graph.mode !== "directed") return isAcyclicUndirected(graph);
|
|
1326
|
-
const WHITE = 0;
|
|
1327
|
-
const GRAY = 1;
|
|
1328
|
-
const BLACK = 2;
|
|
1329
|
-
const color = /* @__PURE__ */ new Map();
|
|
1330
|
-
for (const node of graph.nodes) color.set(node.id, WHITE);
|
|
1331
|
-
const hasCycle = (id) => {
|
|
1332
|
-
color.set(id, GRAY);
|
|
1333
|
-
for (const neighborId of getSuccessorIds(graph, id)) {
|
|
1334
|
-
const current = color.get(neighborId);
|
|
1335
|
-
if (current === GRAY) return true;
|
|
1336
|
-
if (current === WHITE && hasCycle(neighborId)) return true;
|
|
1337
|
-
}
|
|
1338
|
-
color.set(id, BLACK);
|
|
1339
|
-
return false;
|
|
1340
|
-
};
|
|
1341
|
-
for (const node of graph.nodes) if (color.get(node.id) === WHITE && hasCycle(node.id)) return false;
|
|
1342
|
-
return true;
|
|
1343
|
-
}
|
|
1344
|
-
function isAcyclicUndirected(graph) {
|
|
1345
|
-
const idx = getIndex(graph);
|
|
1346
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1347
|
-
const hasCycle = (id, parentId) => {
|
|
1348
|
-
visited.add(id);
|
|
1349
|
-
for (const eid of idx.outEdges.get(id) ?? []) {
|
|
1350
|
-
const ai = idx.edgeById.get(eid);
|
|
1351
|
-
if (ai === void 0) continue;
|
|
1352
|
-
const neighborId = graph.edges[ai].targetId;
|
|
1353
|
-
if (!visited.has(neighborId)) {
|
|
1354
|
-
if (hasCycle(neighborId, id)) return true;
|
|
1355
|
-
} else if (neighborId !== parentId) return true;
|
|
1356
|
-
}
|
|
1357
|
-
for (const eid of idx.inEdges.get(id) ?? []) {
|
|
1358
|
-
const ai = idx.edgeById.get(eid);
|
|
1359
|
-
if (ai === void 0) continue;
|
|
1360
|
-
const neighborId = graph.edges[ai].sourceId;
|
|
1361
|
-
if (!visited.has(neighborId)) {
|
|
1362
|
-
if (hasCycle(neighborId, id)) return true;
|
|
1363
|
-
} else if (neighborId !== parentId) return true;
|
|
1364
|
-
}
|
|
1365
|
-
return false;
|
|
1366
|
-
};
|
|
1367
|
-
for (const node of graph.nodes) if (!visited.has(node.id) && hasCycle(node.id, null)) return false;
|
|
1368
|
-
return true;
|
|
1369
|
-
}
|
|
1370
|
-
function getConnectedComponents(graph) {
|
|
1371
|
-
const idx = getIndex(graph);
|
|
1372
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1373
|
-
const components = [];
|
|
1374
|
-
for (const node of graph.nodes) {
|
|
1375
|
-
if (visited.has(node.id)) continue;
|
|
1376
|
-
const component = [];
|
|
1377
|
-
const queue = [node.id];
|
|
1378
|
-
visited.add(node.id);
|
|
1379
|
-
while (queue.length > 0) {
|
|
1380
|
-
const id = queue.shift();
|
|
1381
|
-
const ni = idx.nodeById.get(id);
|
|
1382
|
-
if (ni !== void 0) component.push(graph.nodes[ni]);
|
|
1383
|
-
for (const eid of idx.outEdges.get(id) ?? []) {
|
|
1384
|
-
const ai = idx.edgeById.get(eid);
|
|
1385
|
-
if (ai === void 0) continue;
|
|
1386
|
-
const neighborId = graph.edges[ai].targetId;
|
|
1387
|
-
if (!visited.has(neighborId)) {
|
|
1388
|
-
visited.add(neighborId);
|
|
1389
|
-
queue.push(neighborId);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
for (const eid of idx.inEdges.get(id) ?? []) {
|
|
1393
|
-
const ai = idx.edgeById.get(eid);
|
|
1394
|
-
if (ai === void 0) continue;
|
|
1395
|
-
const neighborId = graph.edges[ai].sourceId;
|
|
1396
|
-
if (!visited.has(neighborId)) {
|
|
1397
|
-
visited.add(neighborId);
|
|
1398
|
-
queue.push(neighborId);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
components.push(component);
|
|
1403
|
-
}
|
|
1404
|
-
return components;
|
|
1405
|
-
}
|
|
1406
|
-
function getTopologicalSort(graph) {
|
|
1407
|
-
const idx = getIndex(graph);
|
|
1408
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1409
|
-
for (const node of graph.nodes) inDegree.set(node.id, 0);
|
|
1410
|
-
for (const edge of graph.edges) inDegree.set(edge.targetId, (inDegree.get(edge.targetId) ?? 0) + 1);
|
|
1411
|
-
const queue = [];
|
|
1412
|
-
for (const [id, degree] of inDegree) if (degree === 0) queue.push(id);
|
|
1413
|
-
const result = [];
|
|
1414
|
-
while (queue.length > 0) {
|
|
1415
|
-
const id = queue.shift();
|
|
1416
|
-
const ni = idx.nodeById.get(id);
|
|
1417
|
-
if (ni !== void 0) result.push(graph.nodes[ni]);
|
|
1418
|
-
for (const eid of idx.outEdges.get(id) ?? []) {
|
|
1419
|
-
const ai = idx.edgeById.get(eid);
|
|
1420
|
-
if (ai === void 0) continue;
|
|
1421
|
-
const targetId = graph.edges[ai].targetId;
|
|
1422
|
-
const nextDegree = (inDegree.get(targetId) ?? 1) - 1;
|
|
1423
|
-
inDegree.set(targetId, nextDegree);
|
|
1424
|
-
if (nextDegree === 0) queue.push(targetId);
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
if (result.length !== graph.nodes.length) return null;
|
|
1428
|
-
return result;
|
|
1429
|
-
}
|
|
1430
|
-
function hasPath(graph, sourceId, targetId) {
|
|
1431
|
-
return getShortestPaths(graph, {
|
|
1432
|
-
from: sourceId,
|
|
1433
|
-
to: targetId
|
|
1434
|
-
}).length > 0;
|
|
1435
|
-
}
|
|
1436
|
-
function isConnected(graph) {
|
|
1437
|
-
if (graph.nodes.length === 0) return true;
|
|
1438
|
-
return getConnectedComponents(graph).length <= 1;
|
|
1439
|
-
}
|
|
1440
|
-
function isTree(graph) {
|
|
1441
|
-
return isConnected(graph) && isAcyclic(graph);
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
//#endregion
|
|
1445
|
-
//#region src/algorithms/ordering.ts
|
|
1446
|
-
function getPreorder(graph, opts) {
|
|
1447
|
-
const idx = getIndex(graph);
|
|
1448
|
-
const startId = resolveFrom(graph, opts);
|
|
1449
|
-
const startNi = idx.nodeById.get(startId);
|
|
1450
|
-
if (startNi === void 0) return [];
|
|
1451
|
-
const visited = new Set([startId]);
|
|
1452
|
-
const result = [graph.nodes[startNi]];
|
|
1453
|
-
const stack = [startId];
|
|
1454
|
-
while (stack.length > 0) {
|
|
1455
|
-
const top = stack[stack.length - 1];
|
|
1456
|
-
const next = getNeighborIds$1(graph, top).find((id) => !visited.has(id));
|
|
1457
|
-
if (next === void 0) {
|
|
1458
|
-
stack.pop();
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
|
-
visited.add(next);
|
|
1462
|
-
stack.push(next);
|
|
1463
|
-
const ni = idx.nodeById.get(next);
|
|
1464
|
-
if (ni !== void 0) result.push(graph.nodes[ni]);
|
|
1465
|
-
}
|
|
1466
|
-
return result;
|
|
1467
|
-
}
|
|
1468
|
-
function getPostorder(graph, opts) {
|
|
1469
|
-
const idx = getIndex(graph);
|
|
1470
|
-
const startId = resolveFrom(graph, opts);
|
|
1471
|
-
if (idx.nodeById.get(startId) === void 0) return [];
|
|
1472
|
-
const visited = new Set([startId]);
|
|
1473
|
-
const result = [];
|
|
1474
|
-
const stack = [startId];
|
|
1475
|
-
while (stack.length > 0) {
|
|
1476
|
-
const top = stack[stack.length - 1];
|
|
1477
|
-
const next = getNeighborIds$1(graph, top).find((id) => !visited.has(id));
|
|
1478
|
-
if (next === void 0) {
|
|
1479
|
-
stack.pop();
|
|
1480
|
-
const ni = idx.nodeById.get(top);
|
|
1481
|
-
if (ni !== void 0) result.push(graph.nodes[ni]);
|
|
1482
|
-
continue;
|
|
1483
|
-
}
|
|
1484
|
-
visited.add(next);
|
|
1485
|
-
stack.push(next);
|
|
1486
|
-
}
|
|
1487
|
-
return result;
|
|
1488
|
-
}
|
|
1489
|
-
function getPreorders(graph, opts) {
|
|
1490
|
-
return [...genPreorders(graph, opts)];
|
|
1491
|
-
}
|
|
1492
|
-
function getPostorders(graph, opts) {
|
|
1493
|
-
return [...genPostorders(graph, opts)];
|
|
1494
|
-
}
|
|
1495
|
-
function* genPreorders(graph, opts) {
|
|
1496
|
-
const idx = getIndex(graph);
|
|
1497
|
-
const startId = resolveFrom(graph, opts);
|
|
1498
|
-
const startNi = idx.nodeById.get(startId);
|
|
1499
|
-
const startNode = startNi !== void 0 ? graph.nodes[startNi] : void 0;
|
|
1500
|
-
if (!startNode) return;
|
|
1501
|
-
const queue = [{
|
|
1502
|
-
visited: new Set([startId]),
|
|
1503
|
-
preorder: [startNode],
|
|
1504
|
-
dfsStack: [startId]
|
|
1505
|
-
}];
|
|
1506
|
-
while (queue.length > 0) {
|
|
1507
|
-
const frame = queue.pop();
|
|
1508
|
-
const { visited, dfsStack } = frame;
|
|
1509
|
-
let { preorder } = frame;
|
|
1510
|
-
let branched = false;
|
|
1511
|
-
while (dfsStack.length > 0) {
|
|
1512
|
-
const top = dfsStack[dfsStack.length - 1];
|
|
1513
|
-
const unvisited = getNeighborIds$1(graph, top).filter((id) => !visited.has(id));
|
|
1514
|
-
if (unvisited.length === 0) {
|
|
1515
|
-
dfsStack.pop();
|
|
1516
|
-
continue;
|
|
1517
|
-
}
|
|
1518
|
-
for (const nextId of unvisited) {
|
|
1519
|
-
const ni = idx.nodeById.get(nextId);
|
|
1520
|
-
if (ni === void 0) continue;
|
|
1521
|
-
const newVisited = new Set(visited);
|
|
1522
|
-
newVisited.add(nextId);
|
|
1523
|
-
queue.push({
|
|
1524
|
-
visited: newVisited,
|
|
1525
|
-
preorder: [...preorder, graph.nodes[ni]],
|
|
1526
|
-
dfsStack: [...dfsStack, nextId]
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
branched = true;
|
|
1530
|
-
break;
|
|
1531
|
-
}
|
|
1532
|
-
if (!branched) yield preorder;
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
function* genPostorders(graph, opts) {
|
|
1536
|
-
const idx = getIndex(graph);
|
|
1537
|
-
const startId = resolveFrom(graph, opts);
|
|
1538
|
-
if (idx.nodeById.get(startId) === void 0) return;
|
|
1539
|
-
const queue = [{
|
|
1540
|
-
visited: new Set([startId]),
|
|
1541
|
-
postorder: [],
|
|
1542
|
-
dfsStack: [startId]
|
|
1543
|
-
}];
|
|
1544
|
-
while (queue.length > 0) {
|
|
1545
|
-
const frame = queue.pop();
|
|
1546
|
-
const { visited, dfsStack } = frame;
|
|
1547
|
-
let { postorder } = frame;
|
|
1548
|
-
let branched = false;
|
|
1549
|
-
while (dfsStack.length > 0) {
|
|
1550
|
-
const top = dfsStack[dfsStack.length - 1];
|
|
1551
|
-
const unvisited = getNeighborIds$1(graph, top).filter((id) => !visited.has(id));
|
|
1552
|
-
if (unvisited.length === 0) {
|
|
1553
|
-
dfsStack.pop();
|
|
1554
|
-
const ni = idx.nodeById.get(top);
|
|
1555
|
-
if (ni !== void 0) postorder = [...postorder, graph.nodes[ni]];
|
|
1556
|
-
continue;
|
|
1557
|
-
}
|
|
1558
|
-
for (const nextId of unvisited) {
|
|
1559
|
-
if (idx.nodeById.get(nextId) === void 0) continue;
|
|
1560
|
-
const newVisited = new Set(visited);
|
|
1561
|
-
newVisited.add(nextId);
|
|
1562
|
-
queue.push({
|
|
1563
|
-
visited: newVisited,
|
|
1564
|
-
postorder: [...postorder],
|
|
1565
|
-
dfsStack: [...dfsStack, nextId]
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
|
-
branched = true;
|
|
1569
|
-
break;
|
|
1570
|
-
}
|
|
1571
|
-
if (!branched) yield postorder;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
//#endregion
|
|
1576
|
-
//#region src/algorithms/spanning-tree.ts
|
|
1577
|
-
function getMinimumSpanningTree(graph, opts) {
|
|
1578
|
-
const algorithm = opts?.algorithm ?? "prim";
|
|
1579
|
-
const getWeight = opts?.getWeight ?? ((edge) => edge.weight ?? 1);
|
|
1580
|
-
const mstEdges = algorithm === "kruskal" ? kruskalMST(graph, getWeight) : primMST(graph, getWeight);
|
|
1581
|
-
return createGraph({
|
|
1582
|
-
id: graph.id,
|
|
1583
|
-
mode: graph.mode,
|
|
1584
|
-
initialNodeId: graph.initialNodeId ?? void 0,
|
|
1585
|
-
nodes: graph.nodes.map((node) => ({
|
|
1586
|
-
id: node.id,
|
|
1587
|
-
parentId: node.parentId ?? void 0,
|
|
1588
|
-
initialNodeId: node.initialNodeId ?? void 0,
|
|
1589
|
-
label: node.label,
|
|
1590
|
-
data: node.data
|
|
1591
|
-
})),
|
|
1592
|
-
edges: mstEdges.map((edge) => ({
|
|
1593
|
-
id: edge.id,
|
|
1594
|
-
sourceId: edge.sourceId,
|
|
1595
|
-
targetId: edge.targetId,
|
|
1596
|
-
label: edge.label,
|
|
1597
|
-
data: edge.data,
|
|
1598
|
-
...edge.weight !== void 0 && { weight: edge.weight }
|
|
1599
|
-
}))
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
function primMST(graph, getWeight) {
|
|
1603
|
-
if (graph.nodes.length === 0) return [];
|
|
1604
|
-
const idx = getIndex(graph);
|
|
1605
|
-
const inMST = /* @__PURE__ */ new Set();
|
|
1606
|
-
const mstEdges = [];
|
|
1607
|
-
const candidates = new MinPriorityQueue((a, b) => a.weight - b.weight);
|
|
1608
|
-
function addEdgesOf(nodeId) {
|
|
1609
|
-
for (const eid of idx.outEdges.get(nodeId) ?? []) {
|
|
1610
|
-
const ai = idx.edgeById.get(eid);
|
|
1611
|
-
if (ai === void 0) continue;
|
|
1612
|
-
const edge = graph.edges[ai];
|
|
1613
|
-
if (!inMST.has(edge.targetId)) candidates.push({
|
|
1614
|
-
weight: getWeight(edge),
|
|
1615
|
-
edge
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
if (graph.mode !== "directed") for (const eid of idx.inEdges.get(nodeId) ?? []) {
|
|
1619
|
-
const ai = idx.edgeById.get(eid);
|
|
1620
|
-
if (ai === void 0) continue;
|
|
1621
|
-
const edge = graph.edges[ai];
|
|
1622
|
-
if (!inMST.has(edge.sourceId)) candidates.push({
|
|
1623
|
-
weight: getWeight(edge),
|
|
1624
|
-
edge
|
|
1625
|
-
});
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
const startId = graph.nodes[0].id;
|
|
1629
|
-
inMST.add(startId);
|
|
1630
|
-
addEdgesOf(startId);
|
|
1631
|
-
while (candidates.size > 0 && inMST.size < graph.nodes.length) {
|
|
1632
|
-
const { edge } = candidates.pop();
|
|
1633
|
-
const targetId = graph.mode !== "directed" && inMST.has(edge.targetId) ? edge.sourceId : edge.targetId;
|
|
1634
|
-
if (inMST.has(targetId)) continue;
|
|
1635
|
-
inMST.add(targetId);
|
|
1636
|
-
mstEdges.push(edge);
|
|
1637
|
-
addEdgesOf(targetId);
|
|
1638
|
-
}
|
|
1639
|
-
return mstEdges;
|
|
1640
|
-
}
|
|
1641
|
-
function kruskalMST(graph, getWeight) {
|
|
1642
|
-
const sorted = [...graph.edges].sort((a, b) => getWeight(a) - getWeight(b));
|
|
1643
|
-
const parent = /* @__PURE__ */ new Map();
|
|
1644
|
-
const rank = /* @__PURE__ */ new Map();
|
|
1645
|
-
for (const node of graph.nodes) {
|
|
1646
|
-
parent.set(node.id, node.id);
|
|
1647
|
-
rank.set(node.id, 0);
|
|
1648
|
-
}
|
|
1649
|
-
function find(id) {
|
|
1650
|
-
if (parent.get(id) !== id) parent.set(id, find(parent.get(id)));
|
|
1651
|
-
return parent.get(id);
|
|
1652
|
-
}
|
|
1653
|
-
function union(a, b) {
|
|
1654
|
-
const rootA = find(a);
|
|
1655
|
-
const rootB = find(b);
|
|
1656
|
-
if (rootA === rootB) return false;
|
|
1657
|
-
if (rank.get(rootA) < rank.get(rootB)) parent.set(rootA, rootB);
|
|
1658
|
-
else if (rank.get(rootA) > rank.get(rootB)) parent.set(rootB, rootA);
|
|
1659
|
-
else {
|
|
1660
|
-
parent.set(rootB, rootA);
|
|
1661
|
-
rank.set(rootA, rank.get(rootA) + 1);
|
|
1662
|
-
}
|
|
1663
|
-
return true;
|
|
1664
|
-
}
|
|
1665
|
-
const mstEdges = [];
|
|
1666
|
-
for (const edge of sorted) if (union(edge.sourceId, edge.targetId)) mstEdges.push(edge);
|
|
1667
|
-
return mstEdges;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
//#endregion
|
|
1671
|
-
//#region src/algorithms/centrality.ts
|
|
1672
|
-
function getNodeIds(graph) {
|
|
1673
|
-
return graph.nodes.map((node) => node.id);
|
|
1674
|
-
}
|
|
1675
|
-
function getNeighborIds(graph, nodeId) {
|
|
1676
|
-
const idx = getIndex(graph);
|
|
1677
|
-
const neighbors = [];
|
|
1678
|
-
for (const edgeId of idx.outEdges.get(nodeId) ?? []) {
|
|
1679
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
1680
|
-
if (edgeIndex !== void 0) neighbors.push(graph.edges[edgeIndex].targetId);
|
|
1681
|
-
}
|
|
1682
|
-
if (graph.mode !== "directed") for (const edgeId of idx.inEdges.get(nodeId) ?? []) {
|
|
1683
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
1684
|
-
if (edgeIndex !== void 0) neighbors.push(graph.edges[edgeIndex].sourceId);
|
|
1685
|
-
}
|
|
1686
|
-
return neighbors;
|
|
1687
|
-
}
|
|
1688
|
-
function getIncomingIds(graph, nodeId) {
|
|
1689
|
-
const idx = getIndex(graph);
|
|
1690
|
-
const incoming = [];
|
|
1691
|
-
for (const edgeId of idx.inEdges.get(nodeId) ?? []) {
|
|
1692
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
1693
|
-
if (edgeIndex !== void 0) incoming.push(graph.edges[edgeIndex].sourceId);
|
|
1694
|
-
}
|
|
1695
|
-
if (graph.mode !== "directed") for (const edgeId of idx.outEdges.get(nodeId) ?? []) {
|
|
1696
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
1697
|
-
if (edgeIndex !== void 0) incoming.push(graph.edges[edgeIndex].targetId);
|
|
1698
|
-
}
|
|
1699
|
-
return incoming;
|
|
1700
|
-
}
|
|
1701
|
-
function createEmptyScoreMap(graph) {
|
|
1702
|
-
return Object.fromEntries(graph.nodes.map((node) => [node.id, 0]));
|
|
1703
|
-
}
|
|
1704
|
-
function normalizeVector(scores) {
|
|
1705
|
-
const magnitude = Math.sqrt(Object.values(scores).reduce((sum, value) => sum + value * value, 0));
|
|
1706
|
-
if (magnitude === 0) return scores;
|
|
1707
|
-
for (const key of Object.keys(scores)) scores[key] /= magnitude;
|
|
1708
|
-
return scores;
|
|
1709
|
-
}
|
|
1710
|
-
function maxDiff(previous, next) {
|
|
1711
|
-
let diff = 0;
|
|
1712
|
-
for (const key of Object.keys(next)) diff = Math.max(diff, Math.abs((previous[key] ?? 0) - next[key]));
|
|
1713
|
-
return diff;
|
|
1714
|
-
}
|
|
1715
|
-
function getReachableDistances(graph, startId) {
|
|
1716
|
-
const distances = /* @__PURE__ */ new Map();
|
|
1717
|
-
const queue = [startId];
|
|
1718
|
-
distances.set(startId, 0);
|
|
1719
|
-
while (queue.length > 0) {
|
|
1720
|
-
const currentId = queue.shift();
|
|
1721
|
-
const currentDistance = distances.get(currentId);
|
|
1722
|
-
for (const neighborId of getNeighborIds(graph, currentId)) {
|
|
1723
|
-
if (distances.has(neighborId)) continue;
|
|
1724
|
-
distances.set(neighborId, currentDistance + 1);
|
|
1725
|
-
queue.push(neighborId);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
return distances;
|
|
1729
|
-
}
|
|
1730
|
-
/**
|
|
1731
|
-
* Returns degree centrality scores for all nodes.
|
|
1732
|
-
*
|
|
1733
|
-
* Degree centrality is the node degree normalized by `n - 1`.
|
|
1734
|
-
*
|
|
1735
|
-
* @example
|
|
1736
|
-
* ```ts
|
|
1737
|
-
* const scores = getDegreeCentrality(graph);
|
|
1738
|
-
* console.log(scores.a); // 0.5
|
|
1739
|
-
* ```
|
|
1740
|
-
*/
|
|
1741
|
-
function getDegreeCentrality(graph) {
|
|
1742
|
-
const scale = graph.nodes.length > 1 ? 1 / (graph.nodes.length - 1) : 0;
|
|
1743
|
-
const idx = getIndex(graph);
|
|
1744
|
-
const scores = createEmptyScoreMap(graph);
|
|
1745
|
-
for (const node of graph.nodes) {
|
|
1746
|
-
const outDegree = idx.outEdges.get(node.id)?.length ?? 0;
|
|
1747
|
-
const inDegree = idx.inEdges.get(node.id)?.length ?? 0;
|
|
1748
|
-
const degree = graph.mode !== "directed" ? new Set([...idx.outEdges.get(node.id) ?? [], ...idx.inEdges.get(node.id) ?? []]).size : outDegree + inDegree;
|
|
1749
|
-
scores[node.id] = degree * scale;
|
|
1750
|
-
}
|
|
1751
|
-
return scores;
|
|
1752
|
-
}
|
|
1753
|
-
/**
|
|
1754
|
-
* Returns in-degree centrality scores for all nodes.
|
|
1755
|
-
*
|
|
1756
|
-
* In-degree centrality is the incoming degree normalized by `n - 1`.
|
|
1757
|
-
*/
|
|
1758
|
-
function getInDegreeCentrality(graph) {
|
|
1759
|
-
const scale = graph.nodes.length > 1 ? 1 / (graph.nodes.length - 1) : 0;
|
|
1760
|
-
const idx = getIndex(graph);
|
|
1761
|
-
const scores = createEmptyScoreMap(graph);
|
|
1762
|
-
for (const node of graph.nodes) scores[node.id] = (idx.inEdges.get(node.id)?.length ?? 0) * scale;
|
|
1763
|
-
return scores;
|
|
1764
|
-
}
|
|
1765
|
-
/**
|
|
1766
|
-
* Returns out-degree centrality scores for all nodes.
|
|
1767
|
-
*
|
|
1768
|
-
* Out-degree centrality is the outgoing degree normalized by `n - 1`.
|
|
1769
|
-
*/
|
|
1770
|
-
function getOutDegreeCentrality(graph) {
|
|
1771
|
-
const scale = graph.nodes.length > 1 ? 1 / (graph.nodes.length - 1) : 0;
|
|
1772
|
-
const idx = getIndex(graph);
|
|
1773
|
-
const scores = createEmptyScoreMap(graph);
|
|
1774
|
-
for (const node of graph.nodes) scores[node.id] = (idx.outEdges.get(node.id)?.length ?? 0) * scale;
|
|
1775
|
-
return scores;
|
|
1776
|
-
}
|
|
1777
|
-
/**
|
|
1778
|
-
* Returns closeness centrality scores for all nodes.
|
|
1779
|
-
*
|
|
1780
|
-
* Distances are computed over unweighted shortest paths using the graph's
|
|
1781
|
-
* existing directed or undirected edge semantics.
|
|
1782
|
-
*/
|
|
1783
|
-
function getClosenessCentrality(graph) {
|
|
1784
|
-
const scores = createEmptyScoreMap(graph);
|
|
1785
|
-
const order = graph.nodes.length;
|
|
1786
|
-
for (const node of graph.nodes) {
|
|
1787
|
-
const distances = getReachableDistances(graph, node.id);
|
|
1788
|
-
distances.delete(node.id);
|
|
1789
|
-
if (distances.size === 0) continue;
|
|
1790
|
-
const totalDistance = [...distances.values()].reduce((sum, distance) => sum + distance, 0);
|
|
1791
|
-
if (totalDistance === 0) continue;
|
|
1792
|
-
const reachable = distances.size;
|
|
1793
|
-
const closeness = reachable / totalDistance;
|
|
1794
|
-
scores[node.id] = order > 1 ? closeness * (reachable / (order - 1)) : closeness;
|
|
1795
|
-
}
|
|
1796
|
-
return scores;
|
|
1797
|
-
}
|
|
1798
|
-
/**
|
|
1799
|
-
* Returns betweenness centrality scores for all nodes.
|
|
1800
|
-
*
|
|
1801
|
-
* Uses Brandes' algorithm over unweighted shortest paths and returns
|
|
1802
|
-
* normalized scores.
|
|
1803
|
-
*/
|
|
1804
|
-
function getBetweennessCentrality(graph) {
|
|
1805
|
-
const scores = createEmptyScoreMap(graph);
|
|
1806
|
-
for (const source of graph.nodes) {
|
|
1807
|
-
const stack = [];
|
|
1808
|
-
const predecessors = /* @__PURE__ */ new Map();
|
|
1809
|
-
const sigma = /* @__PURE__ */ new Map();
|
|
1810
|
-
const distance = /* @__PURE__ */ new Map();
|
|
1811
|
-
const queue = [source.id];
|
|
1812
|
-
for (const node of graph.nodes) {
|
|
1813
|
-
predecessors.set(node.id, []);
|
|
1814
|
-
sigma.set(node.id, 0);
|
|
1815
|
-
distance.set(node.id, -1);
|
|
1816
|
-
}
|
|
1817
|
-
sigma.set(source.id, 1);
|
|
1818
|
-
distance.set(source.id, 0);
|
|
1819
|
-
while (queue.length > 0) {
|
|
1820
|
-
const currentId = queue.shift();
|
|
1821
|
-
stack.push(currentId);
|
|
1822
|
-
for (const neighborId of getNeighborIds(graph, currentId)) {
|
|
1823
|
-
if (distance.get(neighborId) === -1) {
|
|
1824
|
-
queue.push(neighborId);
|
|
1825
|
-
distance.set(neighborId, distance.get(currentId) + 1);
|
|
1826
|
-
}
|
|
1827
|
-
if (distance.get(neighborId) === distance.get(currentId) + 1) {
|
|
1828
|
-
sigma.set(neighborId, sigma.get(neighborId) + sigma.get(currentId));
|
|
1829
|
-
predecessors.get(neighborId).push(currentId);
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
const delta = /* @__PURE__ */ new Map();
|
|
1834
|
-
for (const node of graph.nodes) delta.set(node.id, 0);
|
|
1835
|
-
while (stack.length > 0) {
|
|
1836
|
-
const nodeId = stack.pop();
|
|
1837
|
-
const sigmaNode = sigma.get(nodeId);
|
|
1838
|
-
if (sigmaNode === 0) continue;
|
|
1839
|
-
for (const predecessorId of predecessors.get(nodeId)) {
|
|
1840
|
-
const contribution = sigma.get(predecessorId) / sigmaNode * (1 + delta.get(nodeId));
|
|
1841
|
-
delta.set(predecessorId, delta.get(predecessorId) + contribution);
|
|
1842
|
-
}
|
|
1843
|
-
if (nodeId !== source.id) scores[nodeId] += delta.get(nodeId);
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
const order = graph.nodes.length;
|
|
1847
|
-
if (order <= 2) return scores;
|
|
1848
|
-
const scale = graph.mode !== "directed" ? 1 / ((order - 1) * (order - 2) / 2) : 1 / ((order - 1) * (order - 2));
|
|
1849
|
-
for (const nodeId of Object.keys(scores)) {
|
|
1850
|
-
if (graph.mode !== "directed") scores[nodeId] /= 2;
|
|
1851
|
-
scores[nodeId] *= scale;
|
|
1852
|
-
}
|
|
1853
|
-
return scores;
|
|
1854
|
-
}
|
|
1855
|
-
/**
|
|
1856
|
-
* Returns PageRank scores for all nodes.
|
|
1857
|
-
*
|
|
1858
|
-
* Uses power iteration with damping factor `alpha`.
|
|
1859
|
-
*/
|
|
1860
|
-
function getPageRank(graph, options) {
|
|
1861
|
-
const nodeIds = getNodeIds(graph);
|
|
1862
|
-
if (nodeIds.length === 0) return {};
|
|
1863
|
-
const alpha = options?.alpha ?? .85;
|
|
1864
|
-
const maxIterations = options?.maxIterations ?? 100;
|
|
1865
|
-
const tolerance = options?.tolerance ?? 1e-6;
|
|
1866
|
-
let scores = Object.fromEntries(nodeIds.map((nodeId) => [nodeId, 1 / nodeIds.length]));
|
|
1867
|
-
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1868
|
-
const nextScores = Object.fromEntries(nodeIds.map((nodeId) => [nodeId, (1 - alpha) / nodeIds.length]));
|
|
1869
|
-
let danglingMass = 0;
|
|
1870
|
-
for (const nodeId of nodeIds) {
|
|
1871
|
-
const neighbors = getNeighborIds(graph, nodeId);
|
|
1872
|
-
if (neighbors.length === 0) {
|
|
1873
|
-
danglingMass += scores[nodeId];
|
|
1874
|
-
continue;
|
|
1875
|
-
}
|
|
1876
|
-
const share = scores[nodeId] / neighbors.length;
|
|
1877
|
-
for (const neighborId of neighbors) nextScores[neighborId] += alpha * share;
|
|
1878
|
-
}
|
|
1879
|
-
if (danglingMass > 0) {
|
|
1880
|
-
const share = alpha * danglingMass / nodeIds.length;
|
|
1881
|
-
for (const nodeId of nodeIds) nextScores[nodeId] += share;
|
|
1882
|
-
}
|
|
1883
|
-
if (maxDiff(scores, nextScores) <= tolerance) {
|
|
1884
|
-
scores = nextScores;
|
|
1885
|
-
break;
|
|
1886
|
-
}
|
|
1887
|
-
scores = nextScores;
|
|
1888
|
-
}
|
|
1889
|
-
const total = Object.values(scores).reduce((sum, value) => sum + value, 0);
|
|
1890
|
-
if (total !== 0) for (const nodeId of nodeIds) scores[nodeId] /= total;
|
|
1891
|
-
return scores;
|
|
1892
|
-
}
|
|
1893
|
-
/**
|
|
1894
|
-
* Returns HITS hub and authority scores for all nodes.
|
|
1895
|
-
*
|
|
1896
|
-
* Uses power iteration and L2 normalization per iteration.
|
|
1897
|
-
*/
|
|
1898
|
-
function getHITS(graph, options) {
|
|
1899
|
-
const nodeIds = getNodeIds(graph);
|
|
1900
|
-
if (nodeIds.length === 0) return {
|
|
1901
|
-
hubs: {},
|
|
1902
|
-
authorities: {}
|
|
1903
|
-
};
|
|
1904
|
-
const maxIterations = options?.maxIterations ?? 100;
|
|
1905
|
-
const tolerance = options?.tolerance ?? 1e-6;
|
|
1906
|
-
let hubs = Object.fromEntries(nodeIds.map((nodeId) => [nodeId, 1]));
|
|
1907
|
-
let authorities = createEmptyScoreMap(graph);
|
|
1908
|
-
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1909
|
-
const nextAuthorities = createEmptyScoreMap(graph);
|
|
1910
|
-
for (const nodeId of nodeIds) for (const predecessorId of getIncomingIds(graph, nodeId)) nextAuthorities[nodeId] += hubs[predecessorId];
|
|
1911
|
-
normalizeVector(nextAuthorities);
|
|
1912
|
-
const nextHubs = createEmptyScoreMap(graph);
|
|
1913
|
-
for (const nodeId of nodeIds) for (const neighborId of getNeighborIds(graph, nodeId)) nextHubs[nodeId] += nextAuthorities[neighborId];
|
|
1914
|
-
normalizeVector(nextHubs);
|
|
1915
|
-
const hubDiff = maxDiff(hubs, nextHubs);
|
|
1916
|
-
const authorityDiff = maxDiff(authorities, nextAuthorities);
|
|
1917
|
-
hubs = nextHubs;
|
|
1918
|
-
authorities = nextAuthorities;
|
|
1919
|
-
if (Math.max(hubDiff, authorityDiff) <= tolerance) break;
|
|
1920
|
-
}
|
|
1921
|
-
return {
|
|
1922
|
-
hubs,
|
|
1923
|
-
authorities
|
|
1924
|
-
};
|
|
1925
|
-
}
|
|
1926
|
-
/**
|
|
1927
|
-
* Returns eigenvector centrality scores for all nodes.
|
|
1928
|
-
*
|
|
1929
|
-
* Uses power iteration over incoming neighbors for directed graphs and
|
|
1930
|
-
* undirected adjacency for undirected graphs.
|
|
1931
|
-
*/
|
|
1932
|
-
function getEigenvectorCentrality(graph, options) {
|
|
1933
|
-
const nodeIds = getNodeIds(graph);
|
|
1934
|
-
if (nodeIds.length === 0) return {};
|
|
1935
|
-
const maxIterations = options?.maxIterations ?? 100;
|
|
1936
|
-
const tolerance = options?.tolerance ?? 1e-6;
|
|
1937
|
-
let scores = Object.fromEntries(nodeIds.map((nodeId) => [nodeId, 1]));
|
|
1938
|
-
normalizeVector(scores);
|
|
1939
|
-
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1940
|
-
const nextScores = createEmptyScoreMap(graph);
|
|
1941
|
-
for (const nodeId of nodeIds) for (const predecessorId of getIncomingIds(graph, nodeId)) nextScores[nodeId] += scores[predecessorId];
|
|
1942
|
-
normalizeVector(nextScores);
|
|
1943
|
-
const diff = maxDiff(scores, nextScores);
|
|
1944
|
-
scores = nextScores;
|
|
1945
|
-
if (diff <= tolerance) break;
|
|
1946
|
-
}
|
|
1947
|
-
return scores;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
//#endregion
|
|
1951
|
-
//#region src/algorithms/community.ts
|
|
1952
|
-
function getUndirectedNeighbors$1(graph, nodeId) {
|
|
1953
|
-
const idx = getIndex(graph);
|
|
1954
|
-
const neighbors = [];
|
|
1955
|
-
for (const edgeId of idx.outEdges.get(nodeId) ?? []) {
|
|
1956
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
1957
|
-
if (edgeIndex !== void 0) neighbors.push({
|
|
1958
|
-
nodeId: graph.edges[edgeIndex].targetId,
|
|
1959
|
-
edgeId
|
|
1960
|
-
});
|
|
1961
|
-
}
|
|
1962
|
-
for (const edgeId of idx.inEdges.get(nodeId) ?? []) {
|
|
1963
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
1964
|
-
if (edgeIndex !== void 0) neighbors.push({
|
|
1965
|
-
nodeId: graph.edges[edgeIndex].sourceId,
|
|
1966
|
-
edgeId
|
|
1967
|
-
});
|
|
1968
|
-
}
|
|
1969
|
-
return neighbors;
|
|
1970
|
-
}
|
|
1971
|
-
function getUndirectedConnectedComponents(graph) {
|
|
1972
|
-
const idx = getIndex(graph);
|
|
1973
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1974
|
-
const communities = [];
|
|
1975
|
-
for (const node of graph.nodes) {
|
|
1976
|
-
if (visited.has(node.id)) continue;
|
|
1977
|
-
const community = [];
|
|
1978
|
-
const queue = [node.id];
|
|
1979
|
-
visited.add(node.id);
|
|
1980
|
-
while (queue.length > 0) {
|
|
1981
|
-
const currentId = queue.shift();
|
|
1982
|
-
const nodeIndex = idx.nodeById.get(currentId);
|
|
1983
|
-
if (nodeIndex !== void 0) community.push(graph.nodes[nodeIndex]);
|
|
1984
|
-
for (const neighbor of getUndirectedNeighbors$1(graph, currentId)) {
|
|
1985
|
-
if (visited.has(neighbor.nodeId)) continue;
|
|
1986
|
-
visited.add(neighbor.nodeId);
|
|
1987
|
-
queue.push(neighbor.nodeId);
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
communities.push(community.sort((a, b) => a.id.localeCompare(b.id)));
|
|
1991
|
-
}
|
|
1992
|
-
return communities.sort((a, b) => a[0].id.localeCompare(b[0].id));
|
|
1993
|
-
}
|
|
1994
|
-
function getNodeMap(graph) {
|
|
1995
|
-
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1996
|
-
}
|
|
1997
|
-
function normalizeCommunities(graph, labels) {
|
|
1998
|
-
const nodeMap = getNodeMap(graph);
|
|
1999
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
2000
|
-
for (const [nodeId, label] of Object.entries(labels)) {
|
|
2001
|
-
if (!grouped.has(label)) grouped.set(label, []);
|
|
2002
|
-
const node = nodeMap.get(nodeId);
|
|
2003
|
-
if (node) grouped.get(label).push(node);
|
|
2004
|
-
}
|
|
2005
|
-
return [...grouped.values()].map((community) => community.sort((a, b) => a.id.localeCompare(b.id))).sort((a, b) => a[0].id.localeCompare(b[0].id));
|
|
2006
|
-
}
|
|
2007
|
-
function getEdgeBetweenness(graph) {
|
|
2008
|
-
const scores = Object.fromEntries(graph.edges.map((edge) => [edge.id, 0]));
|
|
2009
|
-
for (const source of graph.nodes) {
|
|
2010
|
-
const stack = [];
|
|
2011
|
-
const predecessors = /* @__PURE__ */ new Map();
|
|
2012
|
-
const sigma = /* @__PURE__ */ new Map();
|
|
2013
|
-
const distance = /* @__PURE__ */ new Map();
|
|
2014
|
-
const queue = [source.id];
|
|
2015
|
-
for (const node of graph.nodes) {
|
|
2016
|
-
predecessors.set(node.id, []);
|
|
2017
|
-
sigma.set(node.id, 0);
|
|
2018
|
-
distance.set(node.id, -1);
|
|
2019
|
-
}
|
|
2020
|
-
sigma.set(source.id, 1);
|
|
2021
|
-
distance.set(source.id, 0);
|
|
2022
|
-
while (queue.length > 0) {
|
|
2023
|
-
const currentId = queue.shift();
|
|
2024
|
-
stack.push(currentId);
|
|
2025
|
-
for (const neighbor of getUndirectedNeighbors$1(graph, currentId)) {
|
|
2026
|
-
if (distance.get(neighbor.nodeId) === -1) {
|
|
2027
|
-
queue.push(neighbor.nodeId);
|
|
2028
|
-
distance.set(neighbor.nodeId, distance.get(currentId) + 1);
|
|
2029
|
-
}
|
|
2030
|
-
if (distance.get(neighbor.nodeId) === distance.get(currentId) + 1) {
|
|
2031
|
-
sigma.set(neighbor.nodeId, sigma.get(neighbor.nodeId) + sigma.get(currentId));
|
|
2032
|
-
predecessors.get(neighbor.nodeId).push({
|
|
2033
|
-
nodeId: currentId,
|
|
2034
|
-
edgeId: neighbor.edgeId
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
const delta = /* @__PURE__ */ new Map();
|
|
2040
|
-
for (const node of graph.nodes) delta.set(node.id, 0);
|
|
2041
|
-
while (stack.length > 0) {
|
|
2042
|
-
const nodeId = stack.pop();
|
|
2043
|
-
const sigmaNode = sigma.get(nodeId);
|
|
2044
|
-
if (sigmaNode === 0) continue;
|
|
2045
|
-
for (const predecessor of predecessors.get(nodeId)) {
|
|
2046
|
-
const contribution = sigma.get(predecessor.nodeId) / sigmaNode * (1 + delta.get(nodeId));
|
|
2047
|
-
scores[predecessor.edgeId] += contribution;
|
|
2048
|
-
delta.set(predecessor.nodeId, delta.get(predecessor.nodeId) + contribution);
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
for (const edgeId of Object.keys(scores)) scores[edgeId] /= 2;
|
|
2053
|
-
return scores;
|
|
2054
|
-
}
|
|
2055
|
-
function cloneWithEdges(graph, edges) {
|
|
2056
|
-
return {
|
|
2057
|
-
...graph,
|
|
2058
|
-
nodes: [...graph.nodes],
|
|
2059
|
-
edges
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
function toCommunityIds(communities) {
|
|
2063
|
-
return communities.map((community) => new Set(community.map((node) => node.id)));
|
|
2064
|
-
}
|
|
2065
|
-
/**
|
|
2066
|
-
* Returns label-propagation communities for the graph.
|
|
2067
|
-
*
|
|
2068
|
-
* The implementation is deterministic: ties are broken by lexicographic label
|
|
2069
|
-
* order so test results remain stable.
|
|
2070
|
-
*/
|
|
2071
|
-
function getLabelPropagationCommunities(graph, options) {
|
|
2072
|
-
if (graph.nodes.length === 0) return [];
|
|
2073
|
-
const maxIterations = options?.maxIterations ?? 50;
|
|
2074
|
-
let labels = Object.fromEntries(graph.nodes.map((node) => [node.id, node.id]));
|
|
2075
|
-
const nodeIds = graph.nodes.map((node) => node.id).sort();
|
|
2076
|
-
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
2077
|
-
const nextLabels = { ...labels };
|
|
2078
|
-
let changed = false;
|
|
2079
|
-
for (const nodeId of nodeIds) {
|
|
2080
|
-
const counts = /* @__PURE__ */ new Map();
|
|
2081
|
-
for (const neighbor of getUndirectedNeighbors$1(graph, nodeId)) {
|
|
2082
|
-
const label = labels[neighbor.nodeId];
|
|
2083
|
-
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
2084
|
-
}
|
|
2085
|
-
if (counts.size === 0) continue;
|
|
2086
|
-
const bestLabel = [...counts.entries()].sort((a, b) => {
|
|
2087
|
-
if (b[1] !== a[1]) return b[1] - a[1];
|
|
2088
|
-
return a[0].localeCompare(b[0]);
|
|
2089
|
-
})[0][0];
|
|
2090
|
-
if (bestLabel !== labels[nodeId]) {
|
|
2091
|
-
nextLabels[nodeId] = bestLabel;
|
|
2092
|
-
changed = true;
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
labels = nextLabels;
|
|
2096
|
-
if (!changed) break;
|
|
2097
|
-
}
|
|
2098
|
-
return normalizeCommunities(graph, labels);
|
|
2099
|
-
}
|
|
2100
|
-
/**
|
|
2101
|
-
* Lazily yields Girvan-Newman community splits as edge betweenness removes
|
|
2102
|
-
* bridge-like edges from the graph.
|
|
2103
|
-
*/
|
|
2104
|
-
function* genGirvanNewmanCommunities(graph, options) {
|
|
2105
|
-
if (graph.nodes.length === 0 || graph.edges.length === 0) return;
|
|
2106
|
-
const maxLevels = options?.maxLevels ?? Number.POSITIVE_INFINITY;
|
|
2107
|
-
let yielded = 0;
|
|
2108
|
-
let edges = [...graph.edges];
|
|
2109
|
-
let previousCount = getUndirectedConnectedComponents(graph).length;
|
|
2110
|
-
while (edges.length > 0 && yielded < maxLevels) {
|
|
2111
|
-
const betweenness = getEdgeBetweenness(cloneWithEdges(graph, edges));
|
|
2112
|
-
const maxScore = Math.max(...Object.values(betweenness));
|
|
2113
|
-
edges = edges.filter((edge) => betweenness[edge.id] < maxScore - 1e-12);
|
|
2114
|
-
const components = getUndirectedConnectedComponents(cloneWithEdges(graph, edges));
|
|
2115
|
-
if (components.length > previousCount) {
|
|
2116
|
-
yield components;
|
|
2117
|
-
yielded++;
|
|
2118
|
-
previousCount = components.length;
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
/**
|
|
2123
|
-
* Returns the requested Girvan-Newman split level eagerly.
|
|
2124
|
-
*
|
|
2125
|
-
* `level: 1` returns the first split yielded by `genGirvanNewmanCommunities`.
|
|
2126
|
-
*/
|
|
2127
|
-
function getGirvanNewmanCommunities(graph, options) {
|
|
2128
|
-
if (graph.nodes.length === 0) return [];
|
|
2129
|
-
const targetLevel = options?.level ?? 1;
|
|
2130
|
-
if (targetLevel <= 0) return getUndirectedConnectedComponents(graph);
|
|
2131
|
-
let last = getUndirectedConnectedComponents(graph);
|
|
2132
|
-
let level = 0;
|
|
2133
|
-
for (const partition of genGirvanNewmanCommunities(graph, { maxLevels: targetLevel })) {
|
|
2134
|
-
last = partition;
|
|
2135
|
-
level++;
|
|
2136
|
-
if (level >= targetLevel) break;
|
|
2137
|
-
}
|
|
2138
|
-
return last;
|
|
2139
|
-
}
|
|
2140
|
-
/**
|
|
2141
|
-
* Returns the modularity score for a partition of communities.
|
|
2142
|
-
*
|
|
2143
|
-
* Community algorithms in this module treat the graph as undirected.
|
|
2144
|
-
*/
|
|
2145
|
-
function getModularity(graph, communities) {
|
|
2146
|
-
if (graph.edges.length === 0 || communities.length === 0) return 0;
|
|
2147
|
-
const nodeIds = graph.nodes.map((node) => node.id);
|
|
2148
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
2149
|
-
const degree = Object.fromEntries(nodeIds.map((nodeId) => [nodeId, 0]));
|
|
2150
|
-
for (const nodeId of nodeIds) adjacency.set(nodeId, /* @__PURE__ */ new Map());
|
|
2151
|
-
for (const edge of graph.edges) {
|
|
2152
|
-
adjacency.get(edge.sourceId).set(edge.targetId, (adjacency.get(edge.sourceId).get(edge.targetId) ?? 0) + 1);
|
|
2153
|
-
adjacency.get(edge.targetId).set(edge.sourceId, (adjacency.get(edge.targetId).get(edge.sourceId) ?? 0) + 1);
|
|
2154
|
-
degree[edge.sourceId]++;
|
|
2155
|
-
degree[edge.targetId]++;
|
|
2156
|
-
}
|
|
2157
|
-
const m2 = graph.edges.length * 2;
|
|
2158
|
-
let modularity = 0;
|
|
2159
|
-
for (const community of toCommunityIds(communities)) {
|
|
2160
|
-
const ids = [...community];
|
|
2161
|
-
for (const i of ids) for (const j of ids) {
|
|
2162
|
-
const aij = adjacency.get(i).get(j) ?? 0;
|
|
2163
|
-
modularity += aij - degree[i] * degree[j] / m2;
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
return modularity / m2;
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* Returns communities found by greedily merging partitions that improve
|
|
2170
|
-
* modularity the most at each step.
|
|
2171
|
-
*/
|
|
2172
|
-
function getGreedyModularityCommunities(graph) {
|
|
2173
|
-
if (graph.nodes.length === 0) return [];
|
|
2174
|
-
let communities = graph.nodes.map((node) => [node]);
|
|
2175
|
-
let currentScore = getModularity(graph, communities);
|
|
2176
|
-
while (communities.length > 1) {
|
|
2177
|
-
let bestScore = currentScore;
|
|
2178
|
-
let bestMerge;
|
|
2179
|
-
for (let i = 0; i < communities.length; i++) for (let j = i + 1; j < communities.length; j++) {
|
|
2180
|
-
const merged = communities.filter((_, index) => index !== i && index !== j);
|
|
2181
|
-
merged.push([...communities[i], ...communities[j]].sort((a, b) => a.id.localeCompare(b.id)));
|
|
2182
|
-
const score = getModularity(graph, merged);
|
|
2183
|
-
if (score > bestScore + 1e-12) {
|
|
2184
|
-
bestScore = score;
|
|
2185
|
-
bestMerge = merged;
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
if (!bestMerge) break;
|
|
2189
|
-
communities = bestMerge.sort((a, b) => a[0].id.localeCompare(b[0].id));
|
|
2190
|
-
currentScore = bestScore;
|
|
2191
|
-
}
|
|
2192
|
-
return communities;
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
//#endregion
|
|
2196
|
-
//#region src/algorithms/connectivity.ts
|
|
2197
|
-
function getUndirectedNeighbors(graph, nodeId) {
|
|
2198
|
-
const idx = getIndex(graph);
|
|
2199
|
-
const neighbors = [];
|
|
2200
|
-
for (const edgeId of idx.outEdges.get(nodeId) ?? []) {
|
|
2201
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
2202
|
-
if (edgeIndex !== void 0) neighbors.push({
|
|
2203
|
-
nodeId: graph.edges[edgeIndex].targetId,
|
|
2204
|
-
edgeId
|
|
2205
|
-
});
|
|
2206
|
-
}
|
|
2207
|
-
for (const edgeId of idx.inEdges.get(nodeId) ?? []) {
|
|
2208
|
-
const edgeIndex = idx.edgeById.get(edgeId);
|
|
2209
|
-
if (edgeIndex !== void 0) neighbors.push({
|
|
2210
|
-
nodeId: graph.edges[edgeIndex].sourceId,
|
|
2211
|
-
edgeId
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
return neighbors;
|
|
2215
|
-
}
|
|
2216
|
-
function popComponentUntil(state, stopEdgeId) {
|
|
2217
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
2218
|
-
while (state.edgeStack.length > 0) {
|
|
2219
|
-
const edgeId = state.edgeStack.pop();
|
|
2220
|
-
const edge = state.edgeById.get(edgeId);
|
|
2221
|
-
if (edge) {
|
|
2222
|
-
nodeIds.add(edge.sourceId);
|
|
2223
|
-
nodeIds.add(edge.targetId);
|
|
2224
|
-
}
|
|
2225
|
-
if (edgeId === stopEdgeId) break;
|
|
2226
|
-
}
|
|
2227
|
-
if (nodeIds.size > 0) state.components.push(nodeIds);
|
|
2228
|
-
}
|
|
2229
|
-
function finalizeRemainingComponent(state) {
|
|
2230
|
-
if (state.edgeStack.length === 0) return;
|
|
2231
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
2232
|
-
while (state.edgeStack.length > 0) {
|
|
2233
|
-
const edge = state.edgeById.get(state.edgeStack.pop());
|
|
2234
|
-
if (edge) {
|
|
2235
|
-
nodeIds.add(edge.sourceId);
|
|
2236
|
-
nodeIds.add(edge.targetId);
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
if (nodeIds.size > 0) state.components.push(nodeIds);
|
|
2240
|
-
}
|
|
2241
|
-
function traverseConnectivity(graph, nodeId, parentEdgeId, state) {
|
|
2242
|
-
state.time += 1;
|
|
2243
|
-
state.disc.set(nodeId, state.time);
|
|
2244
|
-
state.low.set(nodeId, state.time);
|
|
2245
|
-
let childCount = 0;
|
|
2246
|
-
for (const neighbor of getUndirectedNeighbors(graph, nodeId)) {
|
|
2247
|
-
if (neighbor.edgeId === parentEdgeId) continue;
|
|
2248
|
-
if (!state.disc.has(neighbor.nodeId)) {
|
|
2249
|
-
childCount += 1;
|
|
2250
|
-
state.edgeStack.push(neighbor.edgeId);
|
|
2251
|
-
traverseConnectivity(graph, neighbor.nodeId, neighbor.edgeId, state);
|
|
2252
|
-
state.low.set(nodeId, Math.min(state.low.get(nodeId), state.low.get(neighbor.nodeId)));
|
|
2253
|
-
if (state.low.get(neighbor.nodeId) > state.disc.get(nodeId)) state.bridges.add(neighbor.edgeId);
|
|
2254
|
-
if (parentEdgeId !== null && state.low.get(neighbor.nodeId) >= state.disc.get(nodeId)) {
|
|
2255
|
-
state.articulationPoints.add(nodeId);
|
|
2256
|
-
popComponentUntil(state, neighbor.edgeId);
|
|
2257
|
-
}
|
|
2258
|
-
} else if (state.disc.get(neighbor.nodeId) < state.disc.get(nodeId)) {
|
|
2259
|
-
state.edgeStack.push(neighbor.edgeId);
|
|
2260
|
-
state.low.set(nodeId, Math.min(state.low.get(nodeId), state.disc.get(neighbor.nodeId)));
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
if (parentEdgeId === null && childCount > 1) state.articulationPoints.add(nodeId);
|
|
2264
|
-
}
|
|
2265
|
-
function analyzeConnectivity(graph) {
|
|
2266
|
-
const state = {
|
|
2267
|
-
time: 0,
|
|
2268
|
-
disc: /* @__PURE__ */ new Map(),
|
|
2269
|
-
low: /* @__PURE__ */ new Map(),
|
|
2270
|
-
edgeStack: [],
|
|
2271
|
-
bridges: /* @__PURE__ */ new Set(),
|
|
2272
|
-
articulationPoints: /* @__PURE__ */ new Set(),
|
|
2273
|
-
components: [],
|
|
2274
|
-
nodeById: new Map(graph.nodes.map((node) => [node.id, node])),
|
|
2275
|
-
edgeById: new Map(graph.edges.map((edge) => [edge.id, edge]))
|
|
2276
|
-
};
|
|
2277
|
-
for (const node of graph.nodes) {
|
|
2278
|
-
if (state.disc.has(node.id)) continue;
|
|
2279
|
-
traverseConnectivity(graph, node.id, null, state);
|
|
2280
|
-
finalizeRemainingComponent(state);
|
|
2281
|
-
}
|
|
2282
|
-
return state;
|
|
2283
|
-
}
|
|
2284
|
-
/**
|
|
2285
|
-
* Returns bridge edges whose removal disconnects the graph.
|
|
2286
|
-
*
|
|
2287
|
-
* Connectivity algorithms in this module treat the graph as undirected.
|
|
2288
|
-
*/
|
|
2289
|
-
function getBridges(graph) {
|
|
2290
|
-
if (graph.edges.length === 0) return [];
|
|
2291
|
-
const state = analyzeConnectivity(graph);
|
|
2292
|
-
return [...state.bridges].map((edgeId) => state.edgeById.get(edgeId)).sort((a, b) => a.id.localeCompare(b.id));
|
|
2293
|
-
}
|
|
2294
|
-
/**
|
|
2295
|
-
* Returns articulation points (cut vertices) for the graph.
|
|
2296
|
-
*
|
|
2297
|
-
* Connectivity algorithms in this module treat the graph as undirected.
|
|
2298
|
-
*/
|
|
2299
|
-
function getArticulationPoints(graph) {
|
|
2300
|
-
if (graph.nodes.length === 0) return [];
|
|
2301
|
-
const state = analyzeConnectivity(graph);
|
|
2302
|
-
return [...state.articulationPoints].map((nodeId) => state.nodeById.get(nodeId)).sort((a, b) => a.id.localeCompare(b.id));
|
|
2303
|
-
}
|
|
2304
|
-
/**
|
|
2305
|
-
* Returns biconnected components as arrays of nodes.
|
|
2306
|
-
*
|
|
2307
|
-
* Articulation points may appear in multiple returned components.
|
|
2308
|
-
*/
|
|
2309
|
-
function getBiconnectedComponents(graph) {
|
|
2310
|
-
if (graph.edges.length === 0) return [];
|
|
2311
|
-
const state = analyzeConnectivity(graph);
|
|
2312
|
-
return state.components.map((component) => [...component].map((nodeId) => state.nodeById.get(nodeId)).sort((a, b) => a.id.localeCompare(b.id))).sort((a, b) => a[0].id.localeCompare(b[0].id));
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
//#endregion
|
|
2316
|
-
//#region src/algorithms/isomorphism.ts
|
|
2317
|
-
function getDegreeSignature(graph, nodeId) {
|
|
2318
|
-
const idx = getIndex(graph);
|
|
2319
|
-
const outDegree = idx.outEdges.get(nodeId)?.length ?? 0;
|
|
2320
|
-
const inDegree = idx.inEdges.get(nodeId)?.length ?? 0;
|
|
2321
|
-
if (graph.mode !== "directed") return `u:${new Set([...idx.outEdges.get(nodeId) ?? [], ...idx.inEdges.get(nodeId) ?? []]).size}`;
|
|
2322
|
-
return `d:${inDegree}:${outDegree}`;
|
|
2323
|
-
}
|
|
2324
|
-
function getEdgesBetween(graph, sourceId, targetId) {
|
|
2325
|
-
if (graph.mode !== "directed") return graph.edges.filter((edge) => edge.sourceId === sourceId && edge.targetId === targetId || edge.sourceId === targetId && edge.targetId === sourceId);
|
|
2326
|
-
return graph.edges.filter((edge) => edge.sourceId === sourceId && edge.targetId === targetId);
|
|
2327
|
-
}
|
|
2328
|
-
function edgesAreCompatible(edgesA, edgesB, edgeMatch) {
|
|
2329
|
-
if (edgesA.length !== edgesB.length) return false;
|
|
2330
|
-
if (!edgeMatch || edgesA.length === 0) return true;
|
|
2331
|
-
const remaining = [...edgesB];
|
|
2332
|
-
for (const edgeA of edgesA) {
|
|
2333
|
-
const matchIndex = remaining.findIndex((edgeB) => edgeMatch(edgeA, edgeB));
|
|
2334
|
-
if (matchIndex === -1) return false;
|
|
2335
|
-
remaining.splice(matchIndex, 1);
|
|
2336
|
-
}
|
|
2337
|
-
return true;
|
|
2338
|
-
}
|
|
2339
|
-
/**
|
|
2340
|
-
* Returns whether two graphs are structurally isomorphic.
|
|
2341
|
-
*
|
|
2342
|
-
* Optional `nodeMatch` and `edgeMatch` predicates can refine the match using
|
|
2343
|
-
* node and edge payloads.
|
|
2344
|
-
*/
|
|
2345
|
-
function isIsomorphic(graphA, graphB, options) {
|
|
2346
|
-
if (graphA.mode !== graphB.mode) return false;
|
|
2347
|
-
if (graphA.nodes.length !== graphB.nodes.length) return false;
|
|
2348
|
-
if (graphA.edges.length !== graphB.edges.length) return false;
|
|
2349
|
-
const nodeMatch = options?.nodeMatch;
|
|
2350
|
-
const edgeMatch = options?.edgeMatch;
|
|
2351
|
-
const nodesA = [...graphA.nodes].sort((a, b) => {
|
|
2352
|
-
const sigDiff = getDegreeSignature(graphA, b.id).localeCompare(getDegreeSignature(graphA, a.id));
|
|
2353
|
-
if (sigDiff !== 0) return sigDiff;
|
|
2354
|
-
return a.id.localeCompare(b.id);
|
|
2355
|
-
});
|
|
2356
|
-
const nodesB = [...graphB.nodes];
|
|
2357
|
-
const signaturesA = nodesA.map((node) => getDegreeSignature(graphA, node.id)).sort();
|
|
2358
|
-
const signaturesB = nodesB.map((node) => getDegreeSignature(graphB, node.id)).sort();
|
|
2359
|
-
if (signaturesA.join("|") !== signaturesB.join("|")) return false;
|
|
2360
|
-
const mapping = /* @__PURE__ */ new Map();
|
|
2361
|
-
const usedB = /* @__PURE__ */ new Set();
|
|
2362
|
-
const backtrack = (index) => {
|
|
2363
|
-
if (index >= nodesA.length) return true;
|
|
2364
|
-
const nodeA = nodesA[index];
|
|
2365
|
-
const signatureA = getDegreeSignature(graphA, nodeA.id);
|
|
2366
|
-
for (const nodeB of nodesB) {
|
|
2367
|
-
if (usedB.has(nodeB.id)) continue;
|
|
2368
|
-
if (getDegreeSignature(graphB, nodeB.id) !== signatureA) continue;
|
|
2369
|
-
if (nodeMatch && !nodeMatch(nodeA, nodeB)) continue;
|
|
2370
|
-
let compatible = true;
|
|
2371
|
-
for (const [mappedAId, mappedBId] of mapping.entries()) {
|
|
2372
|
-
if (!edgesAreCompatible(getEdgesBetween(graphA, nodeA.id, mappedAId), getEdgesBetween(graphB, nodeB.id, mappedBId), edgeMatch)) {
|
|
2373
|
-
compatible = false;
|
|
2374
|
-
break;
|
|
2375
|
-
}
|
|
2376
|
-
if (!edgesAreCompatible(getEdgesBetween(graphA, mappedAId, nodeA.id), getEdgesBetween(graphB, mappedBId, nodeB.id), edgeMatch)) {
|
|
2377
|
-
compatible = false;
|
|
2378
|
-
break;
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
if (!compatible) continue;
|
|
2382
|
-
mapping.set(nodeA.id, nodeB.id);
|
|
2383
|
-
usedB.add(nodeB.id);
|
|
2384
|
-
if (backtrack(index + 1)) return true;
|
|
2385
|
-
mapping.delete(nodeA.id);
|
|
2386
|
-
usedB.delete(nodeB.id);
|
|
2387
|
-
}
|
|
2388
|
-
return false;
|
|
2389
|
-
};
|
|
2390
|
-
return backtrack(0);
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
//#endregion
|
|
2394
|
-
export { createGraphPort as $, isAcyclic as A, getShortestPaths as B, getPreorder as C, getConnectedComponents as D, dfs as E, genSimplePaths as F, GraphInstance as G, getSimplePaths as H, getAStarPath as I, addNode as J, addEdge as K, getAllPairsShortestPaths as L, isTree as M, genCycles as N, getTopologicalSort as O, genShortestPaths as P, createGraphNode as Q, getCycles as R, getPostorders as S, bfs as T, getStronglyConnectedComponents as U, getSimplePath as V, joinPaths as W, createGraphEdge as X, createGraph as Y, createGraphFromTransition as Z, getPageRank as _, genGirvanNewmanCommunities as a, getNode as at, genPreorders as b, getLabelPropagationCommunities as c, updateEdge as ct, getClosenessCentrality as d, createVisualGraph as et, getDegreeCentrality as f, getOutDegreeCentrality as g, getInDegreeCentrality as h, getBridges as i, getEdge as it, isConnected as j, hasPath as k, getModularity as l, updateEntities as lt, getHITS as m, getArticulationPoints as n, deleteEntities as nt, getGirvanNewmanCommunities as o, hasEdge as ot, getEigenvectorCentrality as p, addEntities as q, getBiconnectedComponents as r, deleteNode as rt, getGreedyModularityCommunities as s, hasNode as st, isIsomorphic as t, deleteEdge as tt, getBetweennessCentrality as u, updateNode as ut, getMinimumSpanningTree as v, getPreorders as w, getPostorder as x, genPostorders as y, getShortestPath as z };
|