@graphty/algorithms 1.0.1 → 1.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.
Files changed (273) hide show
  1. package/README.md +827 -60
  2. package/dist/algorithms.d.ts +2 -0
  3. package/dist/algorithms.d.ts.map +1 -0
  4. package/dist/algorithms.js +2 -0
  5. package/dist/algorithms.js.map +1 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +22 -7
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.js +7 -7
  10. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  11. package/dist/src/algorithms/centrality/degree.js +1 -1
  12. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  13. package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
  14. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
  15. package/dist/src/algorithms/centrality/eigenvector.js +113 -0
  16. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
  17. package/dist/src/algorithms/centrality/hits.d.ts +35 -0
  18. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
  19. package/dist/src/algorithms/centrality/hits.js +146 -0
  20. package/dist/src/algorithms/centrality/hits.js.map +1 -0
  21. package/dist/src/algorithms/centrality/index.d.ts +6 -0
  22. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  23. package/dist/src/algorithms/centrality/index.js +3 -0
  24. package/dist/src/algorithms/centrality/index.js.map +1 -1
  25. package/dist/src/algorithms/centrality/katz.d.ts +30 -0
  26. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
  27. package/dist/src/algorithms/centrality/katz.js +85 -0
  28. package/dist/src/algorithms/centrality/katz.js.map +1 -0
  29. package/dist/src/algorithms/centrality/pagerank.js +3 -3
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
  32. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
  33. package/dist/src/algorithms/community/girvan-newman.js +297 -0
  34. package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
  35. package/dist/src/algorithms/community/index.d.ts +12 -0
  36. package/dist/src/algorithms/community/index.d.ts.map +1 -0
  37. package/dist/src/algorithms/community/index.js +12 -0
  38. package/dist/src/algorithms/community/index.js.map +1 -0
  39. package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
  40. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
  41. package/dist/src/algorithms/community/label-propagation.js +307 -0
  42. package/dist/src/algorithms/community/label-propagation.js.map +1 -0
  43. package/dist/src/algorithms/community/leiden.d.ts +33 -0
  44. package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
  45. package/dist/src/algorithms/community/leiden.js +434 -0
  46. package/dist/src/algorithms/community/leiden.js.map +1 -0
  47. package/dist/src/algorithms/community/louvain.d.ts +19 -0
  48. package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
  49. package/dist/src/algorithms/community/louvain.js +219 -0
  50. package/dist/src/algorithms/community/louvain.js.map +1 -0
  51. package/dist/src/algorithms/components/connected.js +7 -7
  52. package/dist/src/algorithms/components/connected.js.map +1 -1
  53. package/dist/src/algorithms/index.d.ts +7 -0
  54. package/dist/src/algorithms/index.d.ts.map +1 -1
  55. package/dist/src/algorithms/index.js +14 -0
  56. package/dist/src/algorithms/index.js.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
  58. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
  59. package/dist/src/algorithms/matching/bipartite.js +127 -0
  60. package/dist/src/algorithms/matching/bipartite.js.map +1 -0
  61. package/dist/src/algorithms/matching/index.d.ts +8 -0
  62. package/dist/src/algorithms/matching/index.d.ts.map +1 -0
  63. package/dist/src/algorithms/matching/index.js +6 -0
  64. package/dist/src/algorithms/matching/index.js.map +1 -0
  65. package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
  66. package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
  67. package/dist/src/algorithms/matching/isomorphism.js +291 -0
  68. package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
  69. package/dist/src/algorithms/mst/index.d.ts +4 -0
  70. package/dist/src/algorithms/mst/index.d.ts.map +1 -0
  71. package/dist/src/algorithms/mst/index.js +3 -0
  72. package/dist/src/algorithms/mst/index.js.map +1 -0
  73. package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
  74. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
  75. package/dist/src/algorithms/mst/kruskal.js +43 -0
  76. package/dist/src/algorithms/mst/kruskal.js.map +1 -0
  77. package/dist/src/algorithms/mst/prim.d.ts +5 -0
  78. package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
  79. package/dist/src/algorithms/mst/prim.js +63 -0
  80. package/dist/src/algorithms/mst/prim.js.map +1 -0
  81. package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
  82. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  83. package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
  84. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  85. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
  86. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
  87. package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
  88. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
  89. package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
  90. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
  91. package/dist/src/algorithms/shortest-path/index.js +1 -0
  92. package/dist/src/algorithms/shortest-path/index.js.map +1 -1
  93. package/dist/src/algorithms/traversal/bfs.js +2 -2
  94. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  95. package/dist/src/algorithms/traversal/dfs.js +11 -11
  96. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  97. package/dist/src/clustering/hierarchical.d.ts +48 -0
  98. package/dist/src/clustering/hierarchical.d.ts.map +1 -0
  99. package/dist/src/clustering/hierarchical.js +439 -0
  100. package/dist/src/clustering/hierarchical.js.map +1 -0
  101. package/dist/src/clustering/index.d.ts +7 -0
  102. package/dist/src/clustering/index.d.ts.map +1 -0
  103. package/dist/src/clustering/index.js +5 -0
  104. package/dist/src/clustering/index.js.map +1 -0
  105. package/dist/src/clustering/k-core.d.ts +63 -0
  106. package/dist/src/clustering/k-core.d.ts.map +1 -0
  107. package/dist/src/clustering/k-core.js +377 -0
  108. package/dist/src/clustering/k-core.js.map +1 -0
  109. package/dist/src/clustering/mcl.d.ts +36 -0
  110. package/dist/src/clustering/mcl.d.ts.map +1 -0
  111. package/dist/src/clustering/mcl.js +343 -0
  112. package/dist/src/clustering/mcl.js.map +1 -0
  113. package/dist/src/clustering/spectral.d.ts +28 -0
  114. package/dist/src/clustering/spectral.d.ts.map +1 -0
  115. package/dist/src/clustering/spectral.js +447 -0
  116. package/dist/src/clustering/spectral.js.map +1 -0
  117. package/dist/src/core/graph.d.ts +9 -0
  118. package/dist/src/core/graph.d.ts.map +1 -1
  119. package/dist/src/core/graph.js +31 -11
  120. package/dist/src/core/graph.js.map +1 -1
  121. package/dist/src/data-structures/priority-queue.js +0 -2
  122. package/dist/src/data-structures/priority-queue.js.map +1 -1
  123. package/dist/src/data-structures/union-find.js +2 -5
  124. package/dist/src/data-structures/union-find.js.map +1 -1
  125. package/dist/src/flow/ford-fulkerson.d.ts +53 -0
  126. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
  127. package/dist/src/flow/ford-fulkerson.js +355 -0
  128. package/dist/src/flow/ford-fulkerson.js.map +1 -0
  129. package/dist/src/flow/index.d.ts +3 -0
  130. package/dist/src/flow/index.d.ts.map +1 -0
  131. package/dist/src/flow/index.js +3 -0
  132. package/dist/src/flow/index.js.map +1 -0
  133. package/dist/src/flow/min-cut.d.ts +49 -0
  134. package/dist/src/flow/min-cut.d.ts.map +1 -0
  135. package/dist/src/flow/min-cut.js +383 -0
  136. package/dist/src/flow/min-cut.js.map +1 -0
  137. package/dist/src/index.d.ts +1 -1
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
  141. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
  142. package/dist/src/link-prediction/adamic-adar.js +256 -0
  143. package/dist/src/link-prediction/adamic-adar.js.map +1 -0
  144. package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
  145. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
  146. package/dist/src/link-prediction/common-neighbors.js +155 -0
  147. package/dist/src/link-prediction/common-neighbors.js.map +1 -0
  148. package/dist/src/link-prediction/index.d.ts +7 -0
  149. package/dist/src/link-prediction/index.d.ts.map +1 -0
  150. package/dist/src/link-prediction/index.js +6 -0
  151. package/dist/src/link-prediction/index.js.map +1 -0
  152. package/dist/src/pathfinding/astar.d.ts +50 -0
  153. package/dist/src/pathfinding/astar.d.ts.map +1 -0
  154. package/dist/src/pathfinding/astar.js +180 -0
  155. package/dist/src/pathfinding/astar.js.map +1 -0
  156. package/dist/src/pathfinding/index.d.ts +3 -0
  157. package/dist/src/pathfinding/index.d.ts.map +1 -0
  158. package/dist/src/pathfinding/index.js +3 -0
  159. package/dist/src/pathfinding/index.js.map +1 -0
  160. package/dist/src/pathfinding/utils.d.ts +18 -0
  161. package/dist/src/pathfinding/utils.d.ts.map +1 -0
  162. package/dist/src/pathfinding/utils.js +68 -0
  163. package/dist/src/pathfinding/utils.js.map +1 -0
  164. package/dist/src/types/index.d.ts +9 -0
  165. package/dist/src/types/index.d.ts.map +1 -1
  166. package/dist/src/utils/graphNode.d.ts +8 -0
  167. package/dist/src/utils/graphNode.d.ts.map +1 -0
  168. package/dist/src/utils/graphNode.js +2 -0
  169. package/dist/src/utils/graphNode.js.map +1 -0
  170. package/dist/src/utils/priorityQueue.d.ts +21 -0
  171. package/dist/src/utils/priorityQueue.d.ts.map +1 -0
  172. package/dist/src/utils/priorityQueue.js +101 -0
  173. package/dist/src/utils/priorityQueue.js.map +1 -0
  174. package/package.json +3 -1
  175. package/src/algorithms/centrality/betweenness.ts +25 -7
  176. package/src/algorithms/centrality/closeness.ts +7 -7
  177. package/src/algorithms/centrality/degree.ts +1 -1
  178. package/src/algorithms/centrality/eigenvector.ts +162 -0
  179. package/src/algorithms/centrality/hits.ts +210 -0
  180. package/src/algorithms/centrality/index.ts +6 -0
  181. package/src/algorithms/centrality/katz.ts +139 -0
  182. package/src/algorithms/centrality/pagerank.ts +3 -3
  183. package/src/algorithms/community/girvan-newman.ts +372 -0
  184. package/src/algorithms/community/index.ts +13 -0
  185. package/src/algorithms/community/label-propagation.ts +392 -0
  186. package/src/algorithms/community/leiden.ts +572 -0
  187. package/src/algorithms/community/louvain.ts +298 -0
  188. package/src/algorithms/components/connected.ts +7 -7
  189. package/src/algorithms/index.ts +21 -0
  190. package/src/algorithms/matching/bipartite.ts +179 -0
  191. package/src/algorithms/matching/index.ts +8 -0
  192. package/src/algorithms/matching/isomorphism.ts +392 -0
  193. package/src/algorithms/mst/index.ts +4 -0
  194. package/src/algorithms/mst/kruskal.ts +61 -0
  195. package/src/algorithms/mst/prim.ts +80 -0
  196. package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
  197. package/src/algorithms/shortest-path/dijkstra.ts +5 -5
  198. package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
  199. package/src/algorithms/shortest-path/index.ts +2 -0
  200. package/src/algorithms/traversal/bfs.ts +2 -2
  201. package/src/algorithms/traversal/dfs.ts +11 -11
  202. package/src/clustering/hierarchical.ts +555 -0
  203. package/src/clustering/index.ts +6 -0
  204. package/src/clustering/k-core.ts +460 -0
  205. package/src/clustering/mcl.ts +454 -0
  206. package/src/clustering/spectral.ts +545 -0
  207. package/src/core/graph.ts +34 -6
  208. package/src/data-structures/union-find.ts +2 -2
  209. package/src/flow/ford-fulkerson.ts +459 -0
  210. package/src/flow/index.ts +2 -0
  211. package/src/flow/min-cut.ts +482 -0
  212. package/src/index.ts +2 -0
  213. package/src/link-prediction/adamic-adar.ts +353 -0
  214. package/src/link-prediction/common-neighbors.ts +252 -0
  215. package/src/link-prediction/index.ts +20 -0
  216. package/src/pathfinding/astar.ts +229 -0
  217. package/src/pathfinding/index.ts +2 -0
  218. package/src/pathfinding/utils.ts +87 -0
  219. package/src/types/index.ts +11 -0
  220. package/src/utils/graphNode.ts +7 -0
  221. package/src/utils/priorityQueue.ts +120 -0
  222. package/dist/test/browser/basic.test.d.ts +0 -2
  223. package/dist/test/browser/basic.test.d.ts.map +0 -1
  224. package/dist/test/browser/basic.test.js +0 -70
  225. package/dist/test/browser/basic.test.js.map +0 -1
  226. package/dist/test/unit/bellman-ford.test.d.ts +0 -2
  227. package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
  228. package/dist/test/unit/bellman-ford.test.js +0 -231
  229. package/dist/test/unit/bellman-ford.test.js.map +0 -1
  230. package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
  231. package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
  232. package/dist/test/unit/betweenness-centrality.test.js +0 -311
  233. package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
  234. package/dist/test/unit/bfs.test.d.ts +0 -2
  235. package/dist/test/unit/bfs.test.d.ts.map +0 -1
  236. package/dist/test/unit/bfs.test.js +0 -277
  237. package/dist/test/unit/bfs.test.js.map +0 -1
  238. package/dist/test/unit/closeness.test.d.ts +0 -2
  239. package/dist/test/unit/closeness.test.d.ts.map +0 -1
  240. package/dist/test/unit/closeness.test.js +0 -266
  241. package/dist/test/unit/closeness.test.js.map +0 -1
  242. package/dist/test/unit/connected-components.test.d.ts +0 -2
  243. package/dist/test/unit/connected-components.test.d.ts.map +0 -1
  244. package/dist/test/unit/connected-components.test.js +0 -355
  245. package/dist/test/unit/connected-components.test.js.map +0 -1
  246. package/dist/test/unit/degree-centrality.test.d.ts +0 -2
  247. package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
  248. package/dist/test/unit/degree-centrality.test.js +0 -260
  249. package/dist/test/unit/degree-centrality.test.js.map +0 -1
  250. package/dist/test/unit/dfs.test.d.ts +0 -2
  251. package/dist/test/unit/dfs.test.d.ts.map +0 -1
  252. package/dist/test/unit/dfs.test.js +0 -350
  253. package/dist/test/unit/dfs.test.js.map +0 -1
  254. package/dist/test/unit/dijkstra.test.d.ts +0 -2
  255. package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
  256. package/dist/test/unit/dijkstra.test.js +0 -311
  257. package/dist/test/unit/dijkstra.test.js.map +0 -1
  258. package/dist/test/unit/graph.test.d.ts +0 -2
  259. package/dist/test/unit/graph.test.d.ts.map +0 -1
  260. package/dist/test/unit/graph.test.js +0 -175
  261. package/dist/test/unit/graph.test.js.map +0 -1
  262. package/dist/test/unit/pagerank.test.d.ts +0 -2
  263. package/dist/test/unit/pagerank.test.d.ts.map +0 -1
  264. package/dist/test/unit/pagerank.test.js +0 -281
  265. package/dist/test/unit/pagerank.test.js.map +0 -1
  266. package/dist/test/unit/priority-queue.test.d.ts +0 -2
  267. package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
  268. package/dist/test/unit/priority-queue.test.js +0 -189
  269. package/dist/test/unit/priority-queue.test.js.map +0 -1
  270. package/dist/test/unit/union-find.test.d.ts +0 -2
  271. package/dist/test/unit/union-find.test.d.ts.map +0 -1
  272. package/dist/test/unit/union-find.test.js +0 -245
  273. 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,4 @@
1
+ export type {MSTResult} from "./kruskal.js";
2
+ export {kruskalMST, minimumSpanningTree} from "./kruskal.js";
3
+ export {primMST} from "./prim.js";
4
+
@@ -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
  }