@graphty/algorithms 1.0.1 → 1.2.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 +1259 -61
- package/dist/algorithms.d.ts +2 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +2 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +22 -7
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +7 -7
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +1 -1
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.js +113 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
- package/dist/src/algorithms/centrality/hits.d.ts +35 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/hits.js +146 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +6 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +3 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +30 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/katz.js +85 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -0
- package/dist/src/algorithms/centrality/pagerank.js +3 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.js +297 -0
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
- package/dist/src/algorithms/community/index.d.ts +12 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -0
- package/dist/src/algorithms/community/index.js +12 -0
- package/dist/src/algorithms/community/index.js.map +1 -0
- package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/community/label-propagation.js +307 -0
- package/dist/src/algorithms/community/label-propagation.js.map +1 -0
- package/dist/src/algorithms/community/leiden.d.ts +33 -0
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
- package/dist/src/algorithms/community/leiden.js +434 -0
- package/dist/src/algorithms/community/leiden.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +19 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain.js +219 -0
- package/dist/src/algorithms/community/louvain.js.map +1 -0
- package/dist/src/algorithms/components/connected.js +7 -7
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +7 -0
- package/dist/src/algorithms/index.d.ts.map +1 -1
- package/dist/src/algorithms/index.js +14 -0
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
- package/dist/src/algorithms/matching/bipartite.js +127 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -0
- package/dist/src/algorithms/matching/index.d.ts +8 -0
- package/dist/src/algorithms/matching/index.d.ts.map +1 -0
- package/dist/src/algorithms/matching/index.js +6 -0
- package/dist/src/algorithms/matching/index.js.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.js +291 -0
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
- package/dist/src/algorithms/mst/index.d.ts +4 -0
- package/dist/src/algorithms/mst/index.d.ts.map +1 -0
- package/dist/src/algorithms/mst/index.js +3 -0
- package/dist/src/algorithms/mst/index.js.map +1 -0
- package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
- package/dist/src/algorithms/mst/kruskal.js +43 -0
- package/dist/src/algorithms/mst/kruskal.js.map +1 -0
- package/dist/src/algorithms/mst/prim.d.ts +5 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
- package/dist/src/algorithms/mst/prim.js +63 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
- package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js +1 -0
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +2 -2
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +11 -11
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +48 -0
- package/dist/src/clustering/hierarchical.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical.js +439 -0
- package/dist/src/clustering/hierarchical.js.map +1 -0
- package/dist/src/clustering/index.d.ts +7 -0
- package/dist/src/clustering/index.d.ts.map +1 -0
- package/dist/src/clustering/index.js +5 -0
- package/dist/src/clustering/index.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +63 -0
- package/dist/src/clustering/k-core.d.ts.map +1 -0
- package/dist/src/clustering/k-core.js +377 -0
- package/dist/src/clustering/k-core.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +36 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -0
- package/dist/src/clustering/mcl.js +343 -0
- package/dist/src/clustering/mcl.js.map +1 -0
- package/dist/src/clustering/spectral.d.ts +28 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -0
- package/dist/src/clustering/spectral.js +551 -0
- package/dist/src/clustering/spectral.js.map +1 -0
- package/dist/src/core/graph.d.ts +9 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +31 -11
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/priority-queue.js +0 -2
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.js +2 -5
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +53 -0
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
- package/dist/src/flow/ford-fulkerson.js +355 -0
- package/dist/src/flow/ford-fulkerson.js.map +1 -0
- package/dist/src/flow/index.d.ts +3 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +3 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/flow/min-cut.d.ts +49 -0
- package/dist/src/flow/min-cut.d.ts.map +1 -0
- package/dist/src/flow/min-cut.js +383 -0
- package/dist/src/flow/min-cut.js.map +1 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
- package/dist/src/link-prediction/adamic-adar.js +256 -0
- package/dist/src/link-prediction/adamic-adar.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors.js +155 -0
- package/dist/src/link-prediction/common-neighbors.js.map +1 -0
- package/dist/src/link-prediction/index.d.ts +7 -0
- package/dist/src/link-prediction/index.d.ts.map +1 -0
- package/dist/src/link-prediction/index.js +6 -0
- package/dist/src/link-prediction/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +50 -0
- package/dist/src/pathfinding/astar.d.ts.map +1 -0
- package/dist/src/pathfinding/astar.js +180 -0
- package/dist/src/pathfinding/astar.js.map +1 -0
- package/dist/src/pathfinding/index.d.ts +3 -0
- package/dist/src/pathfinding/index.d.ts.map +1 -0
- package/dist/src/pathfinding/index.js +3 -0
- package/dist/src/pathfinding/index.js.map +1 -0
- package/dist/src/pathfinding/utils.d.ts +18 -0
- package/dist/src/pathfinding/utils.d.ts.map +1 -0
- package/dist/src/pathfinding/utils.js +68 -0
- package/dist/src/pathfinding/utils.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +83 -0
- package/dist/src/research/grsbm.d.ts.map +1 -0
- package/dist/src/research/grsbm.js +411 -0
- package/dist/src/research/grsbm.js.map +1 -0
- package/dist/src/research/index.d.ts +12 -0
- package/dist/src/research/index.d.ts.map +1 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/index.js.map +1 -0
- package/dist/src/research/sync.d.ts +49 -0
- package/dist/src/research/sync.d.ts.map +1 -0
- package/dist/src/research/sync.js +342 -0
- package/dist/src/research/sync.js.map +1 -0
- package/dist/src/research/terahac.d.ts +63 -0
- package/dist/src/research/terahac.d.ts.map +1 -0
- package/dist/src/research/terahac.js +375 -0
- package/dist/src/research/terahac.js.map +1 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/graphNode.d.ts +8 -0
- package/dist/src/utils/graphNode.d.ts.map +1 -0
- package/dist/src/utils/graphNode.js +2 -0
- package/dist/src/utils/graphNode.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +21 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -0
- package/dist/src/utils/priorityQueue.js +101 -0
- package/dist/src/utils/priorityQueue.js.map +1 -0
- package/package.json +13 -2
- package/src/algorithms/centrality/betweenness.ts +25 -7
- package/src/algorithms/centrality/closeness.ts +7 -7
- package/src/algorithms/centrality/degree.ts +1 -1
- package/src/algorithms/centrality/eigenvector.ts +162 -0
- package/src/algorithms/centrality/hits.ts +210 -0
- package/src/algorithms/centrality/index.ts +6 -0
- package/src/algorithms/centrality/katz.ts +139 -0
- package/src/algorithms/centrality/pagerank.ts +3 -3
- package/src/algorithms/community/girvan-newman.ts +372 -0
- package/src/algorithms/community/index.ts +13 -0
- package/src/algorithms/community/label-propagation.ts +392 -0
- package/src/algorithms/community/leiden.ts +572 -0
- package/src/algorithms/community/louvain.ts +298 -0
- package/src/algorithms/components/connected.ts +7 -7
- package/src/algorithms/index.ts +21 -0
- package/src/algorithms/matching/bipartite.ts +179 -0
- package/src/algorithms/matching/index.ts +8 -0
- package/src/algorithms/matching/isomorphism.ts +392 -0
- package/src/algorithms/mst/index.ts +4 -0
- package/src/algorithms/mst/kruskal.ts +61 -0
- package/src/algorithms/mst/prim.ts +80 -0
- package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
- package/src/algorithms/shortest-path/dijkstra.ts +5 -5
- package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
- package/src/algorithms/shortest-path/index.ts +2 -0
- package/src/algorithms/traversal/bfs.ts +2 -2
- package/src/algorithms/traversal/dfs.ts +11 -11
- package/src/clustering/hierarchical.ts +555 -0
- package/src/clustering/index.ts +6 -0
- package/src/clustering/k-core.ts +460 -0
- package/src/clustering/mcl.ts +454 -0
- package/src/clustering/spectral.ts +672 -0
- package/src/core/graph.ts +34 -6
- package/src/data-structures/union-find.ts +2 -2
- package/src/flow/ford-fulkerson.ts +459 -0
- package/src/flow/index.ts +2 -0
- package/src/flow/min-cut.ts +482 -0
- package/src/index.ts +5 -0
- package/src/link-prediction/adamic-adar.ts +353 -0
- package/src/link-prediction/common-neighbors.ts +252 -0
- package/src/link-prediction/index.ts +20 -0
- package/src/pathfinding/astar.ts +229 -0
- package/src/pathfinding/index.ts +2 -0
- package/src/pathfinding/utils.ts +87 -0
- package/src/research/grsbm.ts +589 -0
- package/src/research/index.ts +17 -0
- package/src/research/sync.ts +469 -0
- package/src/research/terahac.ts +506 -0
- package/src/types/index.ts +11 -0
- package/src/utils/graphNode.ts +7 -0
- package/src/utils/priorityQueue.ts +120 -0
- package/dist/test/browser/basic.test.d.ts +0 -2
- package/dist/test/browser/basic.test.d.ts.map +0 -1
- package/dist/test/browser/basic.test.js +0 -70
- package/dist/test/browser/basic.test.js.map +0 -1
- package/dist/test/unit/bellman-ford.test.d.ts +0 -2
- package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
- package/dist/test/unit/bellman-ford.test.js +0 -231
- package/dist/test/unit/bellman-ford.test.js.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
- package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.js +0 -311
- package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
- package/dist/test/unit/bfs.test.d.ts +0 -2
- package/dist/test/unit/bfs.test.d.ts.map +0 -1
- package/dist/test/unit/bfs.test.js +0 -277
- package/dist/test/unit/bfs.test.js.map +0 -1
- package/dist/test/unit/closeness.test.d.ts +0 -2
- package/dist/test/unit/closeness.test.d.ts.map +0 -1
- package/dist/test/unit/closeness.test.js +0 -266
- package/dist/test/unit/closeness.test.js.map +0 -1
- package/dist/test/unit/connected-components.test.d.ts +0 -2
- package/dist/test/unit/connected-components.test.d.ts.map +0 -1
- package/dist/test/unit/connected-components.test.js +0 -355
- package/dist/test/unit/connected-components.test.js.map +0 -1
- package/dist/test/unit/degree-centrality.test.d.ts +0 -2
- package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/degree-centrality.test.js +0 -260
- package/dist/test/unit/degree-centrality.test.js.map +0 -1
- package/dist/test/unit/dfs.test.d.ts +0 -2
- package/dist/test/unit/dfs.test.d.ts.map +0 -1
- package/dist/test/unit/dfs.test.js +0 -350
- package/dist/test/unit/dfs.test.js.map +0 -1
- package/dist/test/unit/dijkstra.test.d.ts +0 -2
- package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
- package/dist/test/unit/dijkstra.test.js +0 -311
- package/dist/test/unit/dijkstra.test.js.map +0 -1
- package/dist/test/unit/graph.test.d.ts +0 -2
- package/dist/test/unit/graph.test.d.ts.map +0 -1
- package/dist/test/unit/graph.test.js +0 -175
- package/dist/test/unit/graph.test.js.map +0 -1
- package/dist/test/unit/pagerank.test.d.ts +0 -2
- package/dist/test/unit/pagerank.test.d.ts.map +0 -1
- package/dist/test/unit/pagerank.test.js +0 -281
- package/dist/test/unit/pagerank.test.js.map +0 -1
- package/dist/test/unit/priority-queue.test.d.ts +0 -2
- package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
- package/dist/test/unit/priority-queue.test.js +0 -189
- package/dist/test/unit/priority-queue.test.js.map +0 -1
- package/dist/test/unit/union-find.test.d.ts +0 -2
- package/dist/test/unit/union-find.test.d.ts.map +0 -1
- package/dist/test/unit/union-find.test.js +0 -245
- package/dist/test/unit/union-find.test.js.map +0 -1
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {NodeId} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Graph Isomorphism (VF2) algorithm implementation
|
|
6
|
+
*
|
|
7
|
+
* Determines if two graphs are isomorphic (structurally identical).
|
|
8
|
+
* Two graphs are isomorphic if there exists a bijection between their vertices
|
|
9
|
+
* that preserves adjacency.
|
|
10
|
+
*
|
|
11
|
+
* Based on the VF2 algorithm by Cordella et al. (2004)
|
|
12
|
+
*
|
|
13
|
+
* Time complexity: O(n! × n) worst case, but typically much faster
|
|
14
|
+
* Space complexity: O(n)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface IsomorphismResult {
|
|
18
|
+
isIsomorphic: boolean;
|
|
19
|
+
mapping?: Map<NodeId, NodeId>; // Maps nodes from graph1 to graph2
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IsomorphismOptions {
|
|
23
|
+
nodeMatch?: (node1: NodeId, node2: NodeId, g1: Graph, g2: Graph) => boolean;
|
|
24
|
+
edgeMatch?: (edge1: [NodeId, NodeId], edge2: [NodeId, NodeId], g1: Graph, g2: Graph) => boolean;
|
|
25
|
+
findAllMappings?: boolean; // Find all possible isomorphisms
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface VF2State {
|
|
29
|
+
core1: Map<NodeId, NodeId>; // Mapping from G1 to G2
|
|
30
|
+
core2: Map<NodeId, NodeId>; // Mapping from G2 to G1
|
|
31
|
+
in1: Map<NodeId, number>; // Terminal set in G1
|
|
32
|
+
in2: Map<NodeId, number>; // Terminal set in G2
|
|
33
|
+
out1: Map<NodeId, number>; // Terminal set out G1
|
|
34
|
+
out2: Map<NodeId, number>; // Terminal set out G2
|
|
35
|
+
depth: number; // Current depth of search
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if two graphs are isomorphic using VF2 algorithm
|
|
40
|
+
*/
|
|
41
|
+
export function isGraphIsomorphic(
|
|
42
|
+
graph1: Graph,
|
|
43
|
+
graph2: Graph,
|
|
44
|
+
options: IsomorphismOptions = {},
|
|
45
|
+
): IsomorphismResult {
|
|
46
|
+
// Quick checks
|
|
47
|
+
if (graph1.nodeCount !== graph2.nodeCount || graph1.totalEdgeCount !== graph2.totalEdgeCount) {
|
|
48
|
+
return {isIsomorphic: false};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (graph1.isDirected !== graph2.isDirected) {
|
|
52
|
+
return {isIsomorphic: false};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const nodes1 = Array.from(graph1.nodes()).map((n) => n.id);
|
|
56
|
+
const nodes2 = Array.from(graph2.nodes()).map((n) => n.id);
|
|
57
|
+
|
|
58
|
+
// Check degree sequences
|
|
59
|
+
const degrees1 = nodes1.map((n) => graph1.degree(n)).sort((a, b) => a - b);
|
|
60
|
+
const degrees2 = nodes2.map((n) => graph2.degree(n)).sort((a, b) => a - b);
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < degrees1.length; i++) {
|
|
63
|
+
if (degrees1[i] !== degrees2[i]) {
|
|
64
|
+
return {isIsomorphic: false};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Initialize VF2 state
|
|
69
|
+
const state: VF2State = {
|
|
70
|
+
core1: new Map(),
|
|
71
|
+
core2: new Map(),
|
|
72
|
+
in1: new Map(),
|
|
73
|
+
in2: new Map(),
|
|
74
|
+
out1: new Map(),
|
|
75
|
+
out2: new Map(),
|
|
76
|
+
depth: 0,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Find isomorphism
|
|
80
|
+
const mappings: Map<NodeId, NodeId>[] = [];
|
|
81
|
+
const found = vf2Recurse(graph1, graph2, state, nodes1, nodes2, options, mappings);
|
|
82
|
+
|
|
83
|
+
if (found && mappings.length > 0) {
|
|
84
|
+
return {
|
|
85
|
+
isIsomorphic: true,
|
|
86
|
+
mapping: mappings[0] ?? new Map<NodeId, NodeId>(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {isIsomorphic: false};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* VF2 recursive search
|
|
95
|
+
*/
|
|
96
|
+
function vf2Recurse(
|
|
97
|
+
g1: Graph,
|
|
98
|
+
g2: Graph,
|
|
99
|
+
state: VF2State,
|
|
100
|
+
nodes1: NodeId[],
|
|
101
|
+
nodes2: NodeId[],
|
|
102
|
+
options: IsomorphismOptions,
|
|
103
|
+
mappings: Map<NodeId, NodeId>[],
|
|
104
|
+
): boolean {
|
|
105
|
+
// Check if we've found a complete mapping
|
|
106
|
+
if (state.core1.size === nodes1.length) {
|
|
107
|
+
mappings.push(new Map(state.core1));
|
|
108
|
+
return !options.findAllMappings; // Continue if we want all mappings
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Get candidate pairs
|
|
112
|
+
const candidates = getCandidatePairs(g1, g2, state, nodes1, nodes2);
|
|
113
|
+
|
|
114
|
+
for (const [node1, node2] of candidates) {
|
|
115
|
+
if (isFeasible(g1, g2, state, node1, node2, options)) {
|
|
116
|
+
// Add pair to mapping
|
|
117
|
+
const newState = addPair(g1, g2, state, node1, node2);
|
|
118
|
+
|
|
119
|
+
if (vf2Recurse(g1, g2, newState, nodes1, nodes2, options, mappings)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get candidate pairs for the next mapping
|
|
130
|
+
*/
|
|
131
|
+
function getCandidatePairs(
|
|
132
|
+
g1: Graph,
|
|
133
|
+
g2: Graph,
|
|
134
|
+
state: VF2State,
|
|
135
|
+
nodes1: NodeId[],
|
|
136
|
+
nodes2: NodeId[],
|
|
137
|
+
): [NodeId, NodeId][] {
|
|
138
|
+
const pairs: [NodeId, NodeId][] = [];
|
|
139
|
+
|
|
140
|
+
// Try to pick from terminal sets first
|
|
141
|
+
let node1: NodeId | null = null;
|
|
142
|
+
|
|
143
|
+
// Pick from out1
|
|
144
|
+
for (const [n] of state.out1) {
|
|
145
|
+
if (!state.core1.has(n)) {
|
|
146
|
+
node1 = n;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If not found, pick from in1
|
|
152
|
+
if (!node1) {
|
|
153
|
+
for (const [n] of state.in1) {
|
|
154
|
+
if (!state.core1.has(n)) {
|
|
155
|
+
node1 = n;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If still not found, pick any unmapped node
|
|
162
|
+
if (!node1) {
|
|
163
|
+
for (const n of nodes1) {
|
|
164
|
+
if (!state.core1.has(n)) {
|
|
165
|
+
node1 = n;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!node1) {
|
|
172
|
+
return pairs;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Find compatible nodes in G2
|
|
176
|
+
for (const node2 of nodes2) {
|
|
177
|
+
if (!state.core2.has(node2)) {
|
|
178
|
+
// Basic compatibility check
|
|
179
|
+
if (g1.degree(node1) === g2.degree(node2)) {
|
|
180
|
+
pairs.push([node1, node2]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return pairs;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if a pair is feasible
|
|
190
|
+
*/
|
|
191
|
+
function isFeasible(
|
|
192
|
+
g1: Graph,
|
|
193
|
+
g2: Graph,
|
|
194
|
+
state: VF2State,
|
|
195
|
+
node1: NodeId,
|
|
196
|
+
node2: NodeId,
|
|
197
|
+
options: IsomorphismOptions,
|
|
198
|
+
): boolean {
|
|
199
|
+
// Node match predicate
|
|
200
|
+
if (options.nodeMatch && !options.nodeMatch(node1, node2, g1, g2)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check syntactic feasibility
|
|
205
|
+
const neighbors1 = new Set(g1.neighbors(node1));
|
|
206
|
+
const neighbors2 = new Set(g2.neighbors(node2));
|
|
207
|
+
|
|
208
|
+
// Check that mapped neighbors correspond
|
|
209
|
+
for (const [n1, n2] of state.core1) {
|
|
210
|
+
if (neighbors1.has(n1)) {
|
|
211
|
+
if (!neighbors2.has(n2)) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Edge match predicate
|
|
216
|
+
if (options.edgeMatch && !options.edgeMatch([node1, n1], [node2, n2], g1, g2)) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
} else if (neighbors2.has(n2)) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check terminal set sizes
|
|
225
|
+
let new1In = 0; let new1Out = 0; let term1In = 0; let term1Out = 0;
|
|
226
|
+
let new2In = 0; let new2Out = 0; let term2In = 0; let term2Out = 0;
|
|
227
|
+
|
|
228
|
+
for (const neighbor of neighbors1) {
|
|
229
|
+
if (state.core1.has(neighbor)) {
|
|
230
|
+
// Already mapped
|
|
231
|
+
} else if (state.in1.has(neighbor)) {
|
|
232
|
+
term1In++;
|
|
233
|
+
} else if (state.out1.has(neighbor)) {
|
|
234
|
+
term1Out++;
|
|
235
|
+
} else {
|
|
236
|
+
if (g1.isDirected) {
|
|
237
|
+
if (g1.hasEdge(neighbor, node1)) {
|
|
238
|
+
new1In++;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (g1.hasEdge(node1, neighbor)) {
|
|
242
|
+
new1Out++;
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
new1In++;
|
|
246
|
+
new1Out++;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const neighbor of neighbors2) {
|
|
252
|
+
if (state.core2.has(neighbor)) {
|
|
253
|
+
// Already mapped
|
|
254
|
+
} else if (state.in2.has(neighbor)) {
|
|
255
|
+
term2In++;
|
|
256
|
+
} else if (state.out2.has(neighbor)) {
|
|
257
|
+
term2Out++;
|
|
258
|
+
} else {
|
|
259
|
+
if (g2.isDirected) {
|
|
260
|
+
if (g2.hasEdge(neighbor, node2)) {
|
|
261
|
+
new2In++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (g2.hasEdge(node2, neighbor)) {
|
|
265
|
+
new2Out++;
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
new2In++;
|
|
269
|
+
new2Out++;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return term1In === term2In && term1Out === term2Out &&
|
|
275
|
+
new1In === new2In && new1Out === new2Out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Add a pair to the mapping and update terminal sets
|
|
280
|
+
*/
|
|
281
|
+
function addPair(
|
|
282
|
+
g1: Graph,
|
|
283
|
+
g2: Graph,
|
|
284
|
+
state: VF2State,
|
|
285
|
+
node1: NodeId,
|
|
286
|
+
node2: NodeId,
|
|
287
|
+
): VF2State {
|
|
288
|
+
const newState: VF2State = {
|
|
289
|
+
core1: new Map(state.core1),
|
|
290
|
+
core2: new Map(state.core2),
|
|
291
|
+
in1: new Map(state.in1),
|
|
292
|
+
in2: new Map(state.in2),
|
|
293
|
+
out1: new Map(state.out1),
|
|
294
|
+
out2: new Map(state.out2),
|
|
295
|
+
depth: state.depth + 1,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
newState.core1.set(node1, node2);
|
|
299
|
+
newState.core2.set(node2, node1);
|
|
300
|
+
|
|
301
|
+
// Update terminal sets
|
|
302
|
+
const neighbors1 = g1.neighbors(node1);
|
|
303
|
+
for (const neighbor of neighbors1) {
|
|
304
|
+
if (!newState.core1.has(neighbor) && !newState.in1.has(neighbor) && !newState.out1.has(neighbor)) {
|
|
305
|
+
if (g1.isDirected) {
|
|
306
|
+
if (g1.hasEdge(neighbor, node1)) {
|
|
307
|
+
newState.in1.set(neighbor, newState.depth);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (g1.hasEdge(node1, neighbor)) {
|
|
311
|
+
newState.out1.set(neighbor, newState.depth);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
newState.in1.set(neighbor, newState.depth);
|
|
315
|
+
newState.out1.set(neighbor, newState.depth);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const neighbors2 = g2.neighbors(node2);
|
|
321
|
+
for (const neighbor of neighbors2) {
|
|
322
|
+
if (!newState.core2.has(neighbor) && !newState.in2.has(neighbor) && !newState.out2.has(neighbor)) {
|
|
323
|
+
if (g2.isDirected) {
|
|
324
|
+
if (g2.hasEdge(neighbor, node2)) {
|
|
325
|
+
newState.in2.set(neighbor, newState.depth);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (g2.hasEdge(node2, neighbor)) {
|
|
329
|
+
newState.out2.set(neighbor, newState.depth);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
newState.in2.set(neighbor, newState.depth);
|
|
333
|
+
newState.out2.set(neighbor, newState.depth);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return newState;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Find all isomorphisms between two graphs
|
|
343
|
+
*/
|
|
344
|
+
export function findAllIsomorphisms(
|
|
345
|
+
graph1: Graph,
|
|
346
|
+
graph2: Graph,
|
|
347
|
+
options: IsomorphismOptions = {},
|
|
348
|
+
): Map<NodeId, NodeId>[] {
|
|
349
|
+
// Quick checks
|
|
350
|
+
if (graph1.nodeCount !== graph2.nodeCount || graph1.totalEdgeCount !== graph2.totalEdgeCount) {
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (graph1.isDirected !== graph2.isDirected) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const nodes1 = Array.from(graph1.nodes()).map((n) => n.id);
|
|
359
|
+
const nodes2 = Array.from(graph2.nodes()).map((n) => n.id);
|
|
360
|
+
|
|
361
|
+
// Check degree sequences
|
|
362
|
+
const degrees1 = nodes1.map((n) => graph1.degree(n)).sort((a, b) => a - b);
|
|
363
|
+
const degrees2 = nodes2.map((n) => graph2.degree(n)).sort((a, b) => a - b);
|
|
364
|
+
|
|
365
|
+
for (let i = 0; i < degrees1.length; i++) {
|
|
366
|
+
if (degrees1[i] !== degrees2[i]) {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Handle empty graphs
|
|
372
|
+
if (nodes1.length === 0) {
|
|
373
|
+
return [new Map<NodeId, NodeId>()];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Initialize VF2 state
|
|
377
|
+
const state: VF2State = {
|
|
378
|
+
core1: new Map(),
|
|
379
|
+
core2: new Map(),
|
|
380
|
+
in1: new Map(),
|
|
381
|
+
in2: new Map(),
|
|
382
|
+
out1: new Map(),
|
|
383
|
+
out2: new Map(),
|
|
384
|
+
depth: 0,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Find all isomorphisms
|
|
388
|
+
const mappings: Map<NodeId, NodeId>[] = [];
|
|
389
|
+
vf2Recurse(graph1, graph2, state, nodes1, nodes2, {... options, findAllMappings: true}, mappings);
|
|
390
|
+
|
|
391
|
+
return mappings;
|
|
392
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {Graph} from "../../core/graph.js";
|
|
2
|
+
import {UnionFind} from "../../data-structures/union-find.js";
|
|
3
|
+
import type {Edge} from "../../types/index.js";
|
|
4
|
+
|
|
5
|
+
export interface MSTResult {
|
|
6
|
+
edges: Edge[];
|
|
7
|
+
totalWeight: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function kruskalMST(graph: Graph): MSTResult {
|
|
11
|
+
if (graph.isDirected) {
|
|
12
|
+
throw new Error("Kruskal's algorithm requires an undirected graph");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const edges: Edge[] = [];
|
|
16
|
+
const visitedEdges = new Set<string>();
|
|
17
|
+
|
|
18
|
+
for (const edge of Array.from(graph.edges())) {
|
|
19
|
+
const edgeKey = edge.source < edge.target ?
|
|
20
|
+
`${String(edge.source)}-${String(edge.target)}` :
|
|
21
|
+
`${String(edge.target)}-${String(edge.source)}`;
|
|
22
|
+
|
|
23
|
+
if (!visitedEdges.has(edgeKey)) {
|
|
24
|
+
visitedEdges.add(edgeKey);
|
|
25
|
+
edges.push(edge);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
edges.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0));
|
|
30
|
+
|
|
31
|
+
const nodes = Array.from(graph.nodes()).map((n) => n.id);
|
|
32
|
+
const uf = new UnionFind(nodes);
|
|
33
|
+
|
|
34
|
+
const mstEdges: Edge[] = [];
|
|
35
|
+
let totalWeight = 0;
|
|
36
|
+
|
|
37
|
+
for (const edge of edges) {
|
|
38
|
+
if (!uf.connected(edge.source, edge.target)) {
|
|
39
|
+
uf.union(edge.source, edge.target);
|
|
40
|
+
mstEdges.push(edge);
|
|
41
|
+
totalWeight += edge.weight ?? 0;
|
|
42
|
+
|
|
43
|
+
if (mstEdges.length === graph.nodeCount - 1) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (mstEdges.length !== graph.nodeCount - 1) {
|
|
50
|
+
throw new Error("Graph is not connected");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
edges: mstEdges,
|
|
55
|
+
totalWeight,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function minimumSpanningTree(graph: Graph): MSTResult {
|
|
60
|
+
return kruskalMST(graph);
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {Graph} from "../../core/graph.js";
|
|
2
|
+
import {PriorityQueue} from "../../data-structures/priority-queue.js";
|
|
3
|
+
import type {Edge, NodeId} from "../../types/index.js";
|
|
4
|
+
import type {MSTResult} from "./kruskal.js";
|
|
5
|
+
|
|
6
|
+
export function primMST(graph: Graph, startNode?: NodeId): MSTResult {
|
|
7
|
+
if (graph.isDirected) {
|
|
8
|
+
throw new Error("Prim's algorithm requires an undirected graph");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const nodes = Array.from(graph.nodes());
|
|
12
|
+
if (nodes.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
edges: [],
|
|
15
|
+
totalWeight: 0,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const start = startNode ?? nodes[0]?.id;
|
|
20
|
+
if (!start) {
|
|
21
|
+
return {
|
|
22
|
+
edges: [],
|
|
23
|
+
totalWeight: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!graph.hasNode(start)) {
|
|
28
|
+
throw new Error(`Start node ${String(start)} not found in graph`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const visited = new Set<NodeId>();
|
|
32
|
+
const mstEdges: Edge[] = [];
|
|
33
|
+
let totalWeight = 0;
|
|
34
|
+
|
|
35
|
+
const pq = new PriorityQueue<Edge>();
|
|
36
|
+
|
|
37
|
+
visited.add(start);
|
|
38
|
+
|
|
39
|
+
for (const neighbor of Array.from(graph.neighbors(start))) {
|
|
40
|
+
const edge = graph.getEdge(start, neighbor);
|
|
41
|
+
if (edge) {
|
|
42
|
+
pq.enqueue(edge, edge.weight ?? 0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
while (!pq.isEmpty() && mstEdges.length < graph.nodeCount - 1) {
|
|
47
|
+
const edge = pq.dequeue();
|
|
48
|
+
if (!edge) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const unvisitedNode = visited.has(edge.source) ? edge.target : edge.source;
|
|
53
|
+
|
|
54
|
+
if (visited.has(unvisitedNode)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
visited.add(unvisitedNode);
|
|
59
|
+
mstEdges.push(edge);
|
|
60
|
+
totalWeight += edge.weight ?? 0;
|
|
61
|
+
|
|
62
|
+
for (const neighbor of Array.from(graph.neighbors(unvisitedNode))) {
|
|
63
|
+
if (!visited.has(neighbor)) {
|
|
64
|
+
const neighborEdge = graph.getEdge(unvisitedNode, neighbor);
|
|
65
|
+
if (neighborEdge) {
|
|
66
|
+
pq.enqueue(neighborEdge, neighborEdge.weight ?? 0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (mstEdges.length !== graph.nodeCount - 1) {
|
|
73
|
+
throw new Error("Graph is not connected");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
edges: mstEdges,
|
|
78
|
+
totalWeight,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -67,7 +67,7 @@ export function bellmanFord(
|
|
|
67
67
|
for (let i = 0; i < nodes.length - 1; i++) {
|
|
68
68
|
let updated = false;
|
|
69
69
|
|
|
70
|
-
for (const edge of graph.edges()) {
|
|
70
|
+
for (const edge of Array.from(graph.edges())) {
|
|
71
71
|
const u = edge.source;
|
|
72
72
|
const v = edge.target;
|
|
73
73
|
const weight = edge.weight ?? 1;
|
|
@@ -187,7 +187,7 @@ export function hasNegativeCycle(graph: Graph): boolean {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
// Mark all reachable nodes as checked
|
|
190
|
-
for (const [nodeId, distance] of result.distances) {
|
|
190
|
+
for (const [nodeId, distance] of Array.from(result.distances)) {
|
|
191
191
|
if (distance !== Infinity) {
|
|
192
192
|
checked.add(nodeId);
|
|
193
193
|
}
|
|
@@ -27,7 +27,7 @@ export function dijkstra(
|
|
|
27
27
|
const pq = new PriorityQueue<NodeId>();
|
|
28
28
|
|
|
29
29
|
// Initialize distances
|
|
30
|
-
for (const node of graph.nodes()) {
|
|
30
|
+
for (const node of Array.from(graph.nodes())) {
|
|
31
31
|
const distance = node.id === source ? 0 : Infinity;
|
|
32
32
|
distances.set(node.id, distance);
|
|
33
33
|
previous.set(node.id, null);
|
|
@@ -63,7 +63,7 @@ export function dijkstra(
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Check all neighbors
|
|
66
|
-
for (const neighbor of graph.neighbors(currentNode)) {
|
|
66
|
+
for (const neighbor of Array.from(graph.neighbors(currentNode))) {
|
|
67
67
|
if (visited.has(neighbor)) {
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
@@ -158,7 +158,7 @@ export function singleSourceShortestPath(
|
|
|
158
158
|
const pq = new PriorityQueue<NodeId>();
|
|
159
159
|
|
|
160
160
|
// Initialize distances
|
|
161
|
-
for (const node of graph.nodes()) {
|
|
161
|
+
for (const node of Array.from(graph.nodes())) {
|
|
162
162
|
const distance = node.id === source ? 0 : Infinity;
|
|
163
163
|
distances.set(node.id, distance);
|
|
164
164
|
pq.enqueue(node.id, distance);
|
|
@@ -188,7 +188,7 @@ export function singleSourceShortestPath(
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// Check all neighbors
|
|
191
|
-
for (const neighbor of graph.neighbors(currentNode)) {
|
|
191
|
+
for (const neighbor of Array.from(graph.neighbors(currentNode))) {
|
|
192
192
|
if (visited.has(neighbor)) {
|
|
193
193
|
continue;
|
|
194
194
|
}
|
|
@@ -232,7 +232,7 @@ export function singleSourceShortestPath(
|
|
|
232
232
|
export function allPairsShortestPath(graph: Graph): Map<NodeId, Map<NodeId, number>> {
|
|
233
233
|
const results = new Map<NodeId, Map<NodeId, number>>();
|
|
234
234
|
|
|
235
|
-
for (const node of graph.nodes()) {
|
|
235
|
+
for (const node of Array.from(graph.nodes())) {
|
|
236
236
|
const distances = singleSourceShortestPath(graph, node.id);
|
|
237
237
|
results.set(node.id, distances);
|
|
238
238
|
}
|