@graphty/algorithms 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +820 -235
  2. package/dist/algorithms.d.ts +42 -2
  3. package/dist/algorithms.js +11099 -2
  4. package/dist/algorithms.js.map +1 -1
  5. package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +124 -170
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  11. package/dist/src/algorithms/centrality/closeness.js +56 -149
  12. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  13. package/dist/src/algorithms/centrality/degree.js +3 -3
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  15. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
  16. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
  18. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
  19. package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
  20. package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
  22. package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
  23. package/dist/src/algorithms/centrality/index.d.ts +2 -0
  24. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  25. package/dist/src/algorithms/centrality/index.js +1 -0
  26. package/dist/src/algorithms/centrality/index.js.map +1 -1
  27. package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
  28. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  29. package/dist/src/algorithms/centrality/pagerank.js +45 -0
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  32. package/dist/src/algorithms/community/girvan-newman.js +47 -54
  33. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  34. package/dist/src/algorithms/community/index.d.ts +2 -0
  35. package/dist/src/algorithms/community/index.d.ts.map +1 -1
  36. package/dist/src/algorithms/community/index.js.map +1 -1
  37. package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
  38. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  39. package/dist/src/algorithms/community/label-propagation.js +50 -35
  40. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  41. package/dist/src/algorithms/community/leiden.d.ts +3 -2
  42. package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
  43. package/dist/src/algorithms/community/leiden.js +34 -39
  44. package/dist/src/algorithms/community/leiden.js.map +1 -1
  45. package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
  46. package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
  47. package/dist/src/algorithms/community/louvain-optimized.js +303 -0
  48. package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
  49. package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
  50. package/dist/src/algorithms/community/louvain.js +17 -76
  51. package/dist/src/algorithms/community/louvain.js.map +1 -1
  52. package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
  53. package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
  54. package/dist/src/algorithms/community/modularity-utils.js +112 -0
  55. package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
  56. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.js +8 -40
  58. package/dist/src/algorithms/matching/bipartite.js.map +1 -1
  59. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
  61. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  62. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
  63. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
  64. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
  65. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
  66. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
  67. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  68. package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
  69. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  70. package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
  71. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
  72. package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
  73. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
  74. package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
  75. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
  76. package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
  77. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
  78. package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
  79. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  80. package/dist/src/algorithms/traversal/bfs.js +5 -182
  81. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  82. package/dist/src/benchmark-all-algorithms.d.ts +23 -0
  83. package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
  84. package/dist/src/benchmark-all-algorithms.js +462 -0
  85. package/dist/src/benchmark-all-algorithms.js.map +1 -0
  86. package/dist/src/clustering/hierarchical.d.ts +16 -15
  87. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  88. package/dist/src/clustering/hierarchical.js +209 -223
  89. package/dist/src/clustering/hierarchical.js.map +1 -1
  90. package/dist/src/clustering/index.d.ts +4 -2
  91. package/dist/src/clustering/index.d.ts.map +1 -1
  92. package/dist/src/clustering/index.js +2 -2
  93. package/dist/src/clustering/index.js.map +1 -1
  94. package/dist/src/clustering/k-core.d.ts +16 -15
  95. package/dist/src/clustering/k-core.d.ts.map +1 -1
  96. package/dist/src/clustering/k-core.js +57 -21
  97. package/dist/src/clustering/k-core.js.map +1 -1
  98. package/dist/src/clustering/spectral.d.ts +8 -0
  99. package/dist/src/clustering/spectral.d.ts.map +1 -1
  100. package/dist/src/clustering/spectral.js +158 -35
  101. package/dist/src/clustering/spectral.js.map +1 -1
  102. package/dist/src/core/graph.js +4 -4
  103. package/dist/src/core/graph.js.map +1 -1
  104. package/dist/src/flow/ford-fulkerson.d.ts +17 -11
  105. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
  106. package/dist/src/flow/ford-fulkerson.js +141 -217
  107. package/dist/src/flow/ford-fulkerson.js.map +1 -1
  108. package/dist/src/flow/min-cut.d.ts +6 -5
  109. package/dist/src/flow/min-cut.d.ts.map +1 -1
  110. package/dist/src/flow/min-cut.js +17 -8
  111. package/dist/src/flow/min-cut.js.map +1 -1
  112. package/dist/src/index.d.ts +2 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +6 -0
  115. package/dist/src/index.js.map +1 -1
  116. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
  117. package/dist/src/link-prediction/adamic-adar.js +17 -26
  118. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  119. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  120. package/dist/src/link-prediction/common-neighbors.js +7 -11
  121. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  122. package/dist/src/optimized/bit-packed.d.ts +143 -0
  123. package/dist/src/optimized/bit-packed.d.ts.map +1 -0
  124. package/dist/src/optimized/bit-packed.js +292 -0
  125. package/dist/src/optimized/bit-packed.js.map +1 -0
  126. package/dist/src/optimized/csr-graph.d.ts +86 -0
  127. package/dist/src/optimized/csr-graph.d.ts.map +1 -0
  128. package/dist/src/optimized/csr-graph.js +341 -0
  129. package/dist/src/optimized/csr-graph.js.map +1 -0
  130. package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
  131. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
  132. package/dist/src/optimized/direction-optimized-bfs.js +223 -0
  133. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
  134. package/dist/src/optimized/graph-adapter.d.ts +62 -0
  135. package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
  136. package/dist/src/optimized/graph-adapter.js +146 -0
  137. package/dist/src/optimized/graph-adapter.js.map +1 -0
  138. package/dist/src/optimized/index.d.ts +14 -0
  139. package/dist/src/optimized/index.d.ts.map +1 -0
  140. package/dist/src/optimized/index.js +28 -0
  141. package/dist/src/optimized/index.js.map +1 -0
  142. package/dist/src/research/grsbm.d.ts +83 -0
  143. package/dist/src/research/grsbm.d.ts.map +1 -0
  144. package/dist/src/research/grsbm.js +403 -0
  145. package/dist/src/research/grsbm.js.map +1 -0
  146. package/dist/src/research/index.d.ts +12 -0
  147. package/dist/src/research/index.d.ts.map +1 -0
  148. package/dist/src/research/index.js +14 -0
  149. package/dist/src/research/index.js.map +1 -0
  150. package/dist/src/research/sync.d.ts +49 -0
  151. package/dist/src/research/sync.d.ts.map +1 -0
  152. package/dist/src/research/sync.js +316 -0
  153. package/dist/src/research/sync.js.map +1 -0
  154. package/dist/src/research/terahac.d.ts +65 -0
  155. package/dist/src/research/terahac.d.ts.map +1 -0
  156. package/dist/src/research/terahac.js +379 -0
  157. package/dist/src/research/terahac.js.map +1 -0
  158. package/dist/src/types/index.d.ts +8 -0
  159. package/dist/src/types/index.d.ts.map +1 -1
  160. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  161. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  162. package/dist/src/utils/algorithm-utilities.js +2 -0
  163. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  164. package/dist/src/utils/graph-converters.d.ts +79 -0
  165. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  166. package/dist/src/utils/graph-converters.js +196 -0
  167. package/dist/src/utils/graph-converters.js.map +1 -0
  168. package/dist/src/utils/graph-utilities.d.ts +62 -0
  169. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  170. package/dist/src/utils/graph-utilities.js +148 -0
  171. package/dist/src/utils/graph-utilities.js.map +1 -0
  172. package/dist/src/utils/index.d.ts +5 -0
  173. package/dist/src/utils/index.d.ts.map +1 -0
  174. package/dist/src/utils/index.js +5 -0
  175. package/dist/src/utils/index.js.map +1 -0
  176. package/dist/src/utils/math-utilities.d.ts +52 -0
  177. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  178. package/dist/src/utils/math-utilities.js +123 -0
  179. package/dist/src/utils/math-utilities.js.map +1 -0
  180. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  181. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  182. package/dist/src/utils/matrix-utilities.js +2 -0
  183. package/dist/src/utils/matrix-utilities.js.map +1 -0
  184. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  185. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  186. package/dist/src/utils/optimization-helpers.js +47 -0
  187. package/dist/src/utils/optimization-helpers.js.map +1 -0
  188. package/package.json +28 -4
  189. package/src/algorithms/centrality/betweenness.ts +167 -199
  190. package/src/algorithms/centrality/closeness.ts +73 -191
  191. package/src/algorithms/centrality/degree.ts +3 -3
  192. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  193. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  194. package/src/algorithms/centrality/index.ts +2 -0
  195. package/src/algorithms/centrality/pagerank.ts +66 -0
  196. package/src/algorithms/community/girvan-newman.ts +52 -59
  197. package/src/algorithms/community/index.ts +2 -0
  198. package/src/algorithms/community/label-propagation.ts +64 -35
  199. package/src/algorithms/community/leiden.ts +40 -41
  200. package/src/algorithms/community/louvain-optimized.ts +445 -0
  201. package/src/algorithms/community/louvain.ts +24 -104
  202. package/src/algorithms/community/modularity-utils.ts +146 -0
  203. package/src/algorithms/matching/bipartite.ts +8 -44
  204. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  205. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  206. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  207. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  208. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  209. package/src/algorithms/traversal/bfs.ts +10 -228
  210. package/src/benchmark-all-algorithms.ts +548 -0
  211. package/src/clustering/hierarchical.ts +241 -251
  212. package/src/clustering/index.ts +4 -2
  213. package/src/clustering/k-core.ts +87 -39
  214. package/src/clustering/spectral.ts +185 -34
  215. package/src/core/graph.ts +4 -4
  216. package/src/flow/ford-fulkerson.ts +186 -254
  217. package/src/flow/min-cut.ts +21 -11
  218. package/src/index.ts +9 -0
  219. package/src/link-prediction/adamic-adar.ts +17 -30
  220. package/src/link-prediction/common-neighbors.ts +7 -16
  221. package/src/optimized/bit-packed.ts +340 -0
  222. package/src/optimized/csr-graph.ts +455 -0
  223. package/src/optimized/direction-optimized-bfs.ts +291 -0
  224. package/src/optimized/graph-adapter.ts +200 -0
  225. package/src/optimized/index.ts +36 -0
  226. package/src/research/grsbm.ts +580 -0
  227. package/src/research/index.ts +17 -0
  228. package/src/research/sync.ts +440 -0
  229. package/src/research/terahac.ts +514 -0
  230. package/src/types/index.ts +8 -0
  231. package/src/utils/algorithm-utilities.ts +2 -0
  232. package/src/utils/graph-converters.ts +241 -0
  233. package/src/utils/graph-utilities.ts +199 -0
  234. package/src/utils/index.ts +4 -0
  235. package/src/utils/math-utilities.ts +140 -0
  236. package/src/utils/matrix-utilities.ts +2 -0
  237. package/src/utils/optimization-helpers.ts +81 -0
@@ -0,0 +1,580 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+ import {SeededRandom} from "../utils/math-utilities.js";
4
+
5
+ /**
6
+ * Configuration options for the GRSBM (Greedy Recursive Spectral Bisection) algorithm
7
+ */
8
+ export interface GRSBMConfig {
9
+ /** Maximum depth of recursive bisection */
10
+ maxDepth?: number;
11
+ /** Minimum cluster size to continue splitting */
12
+ minClusterSize?: number;
13
+ /** Number of eigenvectors to use for spectral embedding */
14
+ numEigenvectors?: number;
15
+ /** Convergence tolerance for eigenvalue computation */
16
+ tolerance?: number;
17
+ /** Maximum number of power iterations for eigenvalue computation */
18
+ maxIterations?: number;
19
+ /** Random seed for reproducibility */
20
+ seed?: number;
21
+ }
22
+
23
+ /**
24
+ * Represents a cluster in the hierarchical structure
25
+ */
26
+ export interface GRSBMCluster {
27
+ /** Unique identifier for this cluster */
28
+ id: string;
29
+ /** Node IDs in this cluster */
30
+ members: Set<NodeId>;
31
+ /** Left child cluster (if split) */
32
+ left?: GRSBMCluster;
33
+ /** Right child cluster (if split) */
34
+ right?: GRSBMCluster;
35
+ /** Modularity score of this cluster */
36
+ modularity: number;
37
+ /** Depth in the hierarchy */
38
+ depth: number;
39
+ /** Spectral embedding quality score */
40
+ spectralScore: number;
41
+ }
42
+
43
+ /**
44
+ * Result of the GRSBM clustering algorithm
45
+ */
46
+ export interface GRSBMResult {
47
+ /** Root of the cluster hierarchy */
48
+ root: GRSBMCluster;
49
+ /** Flat clustering at leaf level */
50
+ clusters: Map<NodeId, number>;
51
+ /** Number of final clusters */
52
+ numClusters: number;
53
+ /** Modularity scores for each level */
54
+ modularityScores: number[];
55
+ /** Explanation of the clustering structure */
56
+ explanation: ClusterExplanation[];
57
+ }
58
+
59
+ /**
60
+ * Explanation for a cluster split decision
61
+ */
62
+ export interface ClusterExplanation {
63
+ /** Cluster that was split */
64
+ clusterId: string;
65
+ /** Reason for the split */
66
+ reason: string;
67
+ /** Modularity improvement from the split */
68
+ modularityImprovement: number;
69
+ /** Key nodes that influenced the split */
70
+ keyNodes: NodeId[];
71
+ /** Spectral embedding values */
72
+ spectralValues: number[];
73
+ }
74
+
75
+ /**
76
+ * GRSBM - Greedy Recursive Spectral Bisection with Modularity
77
+ *
78
+ * This algorithm performs hierarchical community detection using spectral
79
+ * bisection guided by modularity optimization. It provides explainable
80
+ * community structure by tracking the reasoning behind each split.
81
+ *
82
+ * Based on: "Explainable Community Detection via Hierarchical Spectral Clustering" (2024)
83
+ *
84
+ * @param graph - Input graph to cluster
85
+ * @param config - Configuration options
86
+ * @returns Hierarchical clustering result with explanations
87
+ */
88
+ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
89
+ const {
90
+ maxDepth = 10,
91
+ minClusterSize = 2, // Reduced default to allow more splits
92
+ numEigenvectors = 2,
93
+ tolerance = 1e-6,
94
+ maxIterations = 100,
95
+ seed = 42,
96
+ } = config;
97
+
98
+ // Set random seed for reproducibility
99
+ const rng = SeededRandom.createGenerator(seed);
100
+ const originalRandom = Math.random;
101
+ Math.random = rng;
102
+
103
+ const nodes = Array.from(graph.nodes());
104
+ const nodeCount = nodes.length;
105
+
106
+ if (nodeCount === 0) {
107
+ throw new Error("Cannot cluster empty graph");
108
+ }
109
+
110
+ // Initialize root cluster with all nodes
111
+ const rootMembers = new Set(nodes.map((node) => node.id));
112
+ const initialModularity = calculateModularity(graph, new Map([... rootMembers].map((id) => [id, 0])));
113
+
114
+ const root: GRSBMCluster = {
115
+ id: "root",
116
+ members: rootMembers,
117
+ modularity: initialModularity,
118
+ depth: 0,
119
+ spectralScore: 0,
120
+ };
121
+
122
+ const explanations: ClusterExplanation[] = [];
123
+ const modularityScores: number[] = [initialModularity];
124
+
125
+ // Perform recursive bisection
126
+ const clusterQueue: GRSBMCluster[] = [root];
127
+ let nextClusterId = 1;
128
+
129
+ while (clusterQueue.length > 0) {
130
+ const currentCluster = clusterQueue.shift();
131
+ if (!currentCluster) {
132
+ continue;
133
+ }
134
+
135
+ // Check stopping criteria
136
+ if (currentCluster.depth >= maxDepth ||
137
+ currentCluster.members.size < minClusterSize * 2) {
138
+ continue;
139
+ }
140
+
141
+ // Attempt spectral bisection
142
+ const bisectionResult = spectralBisection(
143
+ graph,
144
+ currentCluster,
145
+ numEigenvectors,
146
+ tolerance,
147
+ maxIterations,
148
+ );
149
+
150
+ if (!bisectionResult) {
151
+ continue; // Could not split this cluster
152
+ }
153
+
154
+ const {leftMembers, rightMembers, spectralScore, keyNodes, spectralValues, reason} = bisectionResult;
155
+
156
+ // Create child clusters
157
+ const leftCluster: GRSBMCluster = {
158
+ id: `cluster_${String(nextClusterId++)}`,
159
+ members: leftMembers,
160
+ modularity: 0, // Will be calculated below
161
+ depth: currentCluster.depth + 1,
162
+ spectralScore,
163
+ };
164
+
165
+ const rightCluster: GRSBMCluster = {
166
+ id: `cluster_${String(nextClusterId++)}`,
167
+ members: rightMembers,
168
+ modularity: 0, // Will be calculated below
169
+ depth: currentCluster.depth + 1,
170
+ spectralScore,
171
+ };
172
+
173
+ // Calculate modularity improvement
174
+ const newAssignment = new Map<NodeId, number>();
175
+ for (const nodeId of leftMembers) {
176
+ newAssignment.set(nodeId, 0);
177
+ }
178
+ for (const nodeId of rightMembers) {
179
+ newAssignment.set(nodeId, 1);
180
+ }
181
+
182
+ const newModularity = calculateModularity(graph, newAssignment);
183
+ const modularityImprovement = newModularity - currentCluster.modularity;
184
+
185
+ // Only split if modularity improves or is neutral (allow small splits)
186
+ if (modularityImprovement >= -0.01) { // Small tolerance for neutral splits
187
+ leftCluster.modularity = newModularity;
188
+ rightCluster.modularity = newModularity;
189
+
190
+ currentCluster.left = leftCluster;
191
+ currentCluster.right = rightCluster;
192
+
193
+ // Add explanation
194
+ explanations.push({
195
+ clusterId: currentCluster.id,
196
+ reason,
197
+ modularityImprovement,
198
+ keyNodes,
199
+ spectralValues,
200
+ });
201
+
202
+ modularityScores.push(newModularity);
203
+
204
+ // Add children to queue for further processing
205
+ clusterQueue.push(leftCluster, rightCluster);
206
+ }
207
+ }
208
+
209
+ // Extract flat clustering from leaf nodes
210
+ const clusters = new Map<NodeId, number>();
211
+ let clusterId = 0;
212
+
213
+ function assignClusterIds(cluster: GRSBMCluster): void {
214
+ if (!cluster.left && !cluster.right) {
215
+ // Leaf node
216
+ for (const nodeId of cluster.members) {
217
+ clusters.set(nodeId, clusterId);
218
+ }
219
+ clusterId++;
220
+ } else {
221
+ // Internal node - traverse children
222
+ if (cluster.left) {
223
+ assignClusterIds(cluster.left);
224
+ }
225
+
226
+ if (cluster.right) {
227
+ assignClusterIds(cluster.right);
228
+ }
229
+ }
230
+ }
231
+
232
+ assignClusterIds(root);
233
+
234
+ // Restore original random function
235
+ Math.random = originalRandom;
236
+
237
+ return {
238
+ root,
239
+ clusters,
240
+ numClusters: clusterId,
241
+ modularityScores,
242
+ explanation: explanations,
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Perform spectral bisection on a cluster
248
+ */
249
+ function spectralBisection(
250
+ graph: Graph,
251
+ cluster: GRSBMCluster,
252
+ numEigenvectors: number,
253
+ tolerance: number,
254
+ maxIterations: number,
255
+ ): {
256
+ leftMembers: Set<NodeId>;
257
+ rightMembers: Set<NodeId>;
258
+ spectralScore: number;
259
+ keyNodes: NodeId[];
260
+ spectralValues: number[];
261
+ reason: string;
262
+ } | null {
263
+ const members = Array.from(cluster.members);
264
+ const n = members.length;
265
+
266
+ if (n < 4) {
267
+ return null; // Too small to split meaningfully
268
+ }
269
+
270
+ // Create subgraph Laplacian matrix
271
+ const laplacian = createLaplacianMatrix(graph, members);
272
+
273
+ // Compute the second smallest eigenvector (Fiedler vector)
274
+ const eigenvector = computeFiedlerVector(laplacian, tolerance, maxIterations);
275
+
276
+ if (!eigenvector) {
277
+ return null; // Could not compute eigenvector
278
+ }
279
+
280
+ // Find optimal split point
281
+ const sortedIndices = eigenvector
282
+ .map((value, index) => ({value, index}))
283
+ .sort((a, b) => a.value - b.value);
284
+
285
+ let bestSplitIndex = Math.floor(n / 2);
286
+ let bestModularity = -Infinity;
287
+
288
+ // Try different split points
289
+ for (let splitIndex = Math.floor(n * 0.2); splitIndex <= Math.floor(n * 0.8); splitIndex++) {
290
+ const leftIndices = sortedIndices.slice(0, splitIndex).map((item) => item.index);
291
+ const rightIndices = sortedIndices.slice(splitIndex).map((item) => item.index);
292
+
293
+ const assignment = new Map<NodeId, number>();
294
+ for (const idx of leftIndices) {
295
+ const nodeId = members[idx];
296
+ if (nodeId !== undefined) {
297
+ assignment.set(nodeId, 0);
298
+ }
299
+ }
300
+ for (const idx of rightIndices) {
301
+ const nodeId = members[idx];
302
+ if (nodeId !== undefined) {
303
+ assignment.set(nodeId, 1);
304
+ }
305
+ }
306
+
307
+ const modularity = calculateModularity(graph, assignment);
308
+
309
+ if (modularity > bestModularity) {
310
+ bestModularity = modularity;
311
+ bestSplitIndex = splitIndex;
312
+ }
313
+ }
314
+
315
+ // Create final split
316
+ const leftIndices = sortedIndices.slice(0, bestSplitIndex).map((item) => item.index);
317
+ const rightIndices = sortedIndices.slice(bestSplitIndex).map((item) => item.index);
318
+
319
+ const leftMembers = new Set<NodeId>();
320
+ const rightMembers = new Set<NodeId>();
321
+
322
+ for (const idx of leftIndices) {
323
+ const nodeId = members[idx];
324
+ if (nodeId !== undefined) {
325
+ leftMembers.add(nodeId);
326
+ }
327
+ }
328
+
329
+ for (const idx of rightIndices) {
330
+ const nodeId = members[idx];
331
+ if (nodeId !== undefined) {
332
+ rightMembers.add(nodeId);
333
+ }
334
+ }
335
+
336
+ // Identify key nodes (those with extreme spectral values)
337
+ const extremeThreshold = 0.1;
338
+ const sortedValues = [... eigenvector].sort((a, b) => Math.abs(b) - Math.abs(a));
339
+ const threshold = sortedValues[Math.floor(sortedValues.length * extremeThreshold)] ?? 0;
340
+
341
+ const keyNodes: NodeId[] = [];
342
+ for (let i = 0; i < eigenvector.length; i++) {
343
+ const eigenValue = eigenvector[i];
344
+ if (eigenValue !== undefined && Math.abs(eigenValue) >= Math.abs(threshold)) {
345
+ const nodeId = members[i];
346
+ if (nodeId !== undefined) {
347
+ keyNodes.push(nodeId);
348
+ }
349
+ }
350
+ }
351
+
352
+ // Calculate spectral score (based on eigenvalue gap)
353
+ const firstValue = sortedValues[0] ?? 0;
354
+ const lastValue = sortedValues[sortedValues.length - 1] ?? 0;
355
+ const spectralScore = Math.abs(firstValue - lastValue);
356
+
357
+ // Generate explanation
358
+ const reason = `Spectral bisection based on Fiedler vector with modularity ${bestModularity.toFixed(3)}. Split creates clusters of sizes ${String(leftMembers.size)} and ${String(rightMembers.size)}.`;
359
+
360
+ return {
361
+ leftMembers,
362
+ rightMembers,
363
+ spectralScore,
364
+ keyNodes: keyNodes.slice(0, 5), // Top 5 key nodes
365
+ spectralValues: eigenvector,
366
+ reason,
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Create Laplacian matrix for a subgraph
372
+ */
373
+ function createLaplacianMatrix(graph: Graph, nodes: NodeId[]): number[][] {
374
+ const n = nodes.length;
375
+ const nodeIndex = new Map<NodeId, number>();
376
+
377
+ // Create node index mapping
378
+ for (let i = 0; i < n; i++) {
379
+ const node = nodes[i];
380
+ if (node !== undefined) {
381
+ nodeIndex.set(node, i);
382
+ }
383
+ }
384
+
385
+ // Initialize Laplacian matrix
386
+ const laplacian = Array.from({length: n}, () => new Array<number>(n).fill(0));
387
+
388
+ // Fill adjacency part and calculate degrees
389
+ const degrees = new Array<number>(n).fill(0);
390
+
391
+ for (let i = 0; i < n; i++) {
392
+ const nodeId = nodes[i];
393
+ if (nodeId === undefined) {
394
+ continue;
395
+ }
396
+
397
+ for (const neighbor of graph.neighbors(nodeId)) {
398
+ const neighborIdx = nodeIndex.get(neighbor);
399
+ if (neighborIdx !== undefined && i < laplacian.length && i < degrees.length) {
400
+ const row = laplacian[i];
401
+ if (row) {
402
+ row[neighborIdx] = -1;
403
+ degrees[i] = (degrees[i] ?? 0) + 1;
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ // Set diagonal (degree) values
410
+ for (let i = 0; i < n; i++) {
411
+ const row = laplacian[i];
412
+ const degree = degrees[i];
413
+ if (row && degree !== undefined) {
414
+ row[i] = degree;
415
+ }
416
+ }
417
+
418
+ return laplacian;
419
+ }
420
+
421
+ /**
422
+ * Compute the Fiedler vector (second smallest eigenvector) using power iteration
423
+ */
424
+ function computeFiedlerVector(laplacian: number[][], tolerance: number, maxIterations: number): number[] | null {
425
+ const n = laplacian.length;
426
+ if (n < 2) {
427
+ return null;
428
+ }
429
+
430
+ // Initialize random vector orthogonal to the all-ones vector
431
+ let vector = new Array<number>(n).fill(0);
432
+ for (let i = 0; i < n; i++) {
433
+ vector[i] = Math.random() - 0.5;
434
+ }
435
+
436
+ // Make orthogonal to all-ones vector
437
+ const mean = vector.reduce((sum: number, val: number) => sum + val, 0) / n;
438
+ for (let i = 0; i < n; i++) {
439
+ const val = vector[i];
440
+ if (val !== undefined) {
441
+ vector[i] = val - mean;
442
+ }
443
+ }
444
+
445
+ // Normalize
446
+ const norm = Math.sqrt(vector.reduce((sum: number, val: number) => sum + (val * val), 0));
447
+ if (norm > 0) {
448
+ for (let i = 0; i < n; i++) {
449
+ const val = vector[i];
450
+ if (val !== undefined) {
451
+ vector[i] = val / norm;
452
+ }
453
+ }
454
+ }
455
+
456
+ // Power iteration with deflation for second smallest eigenvalue
457
+ for (let iter = 0; iter < maxIterations; iter++) {
458
+ const newVector = new Array<number>(n).fill(0);
459
+
460
+ // Matrix-vector multiplication: newVector = L * vector
461
+ for (let i = 0; i < n; i++) {
462
+ let sum = 0;
463
+ const row = laplacian[i];
464
+ if (row) {
465
+ for (let j = 0; j < n; j++) {
466
+ const matrixVal = row[j];
467
+ const vectorVal = vector[j];
468
+ if (matrixVal !== undefined && vectorVal !== undefined) {
469
+ sum += matrixVal * vectorVal;
470
+ }
471
+ }
472
+ }
473
+
474
+ newVector[i] = sum;
475
+ }
476
+
477
+ // For smallest eigenvalue, we actually want inverse iteration
478
+ // This is simplified - in practice would solve (L - shift*I)x = vector
479
+ // For now, use power iteration on -L (approximately)
480
+ for (let i = 0; i < n; i++) {
481
+ const val = newVector[i];
482
+ if (val !== undefined) {
483
+ newVector[i] = -val;
484
+ }
485
+ }
486
+
487
+ // Orthogonalize against all-ones vector
488
+ const newMean = newVector.reduce((sum, val) => sum + val, 0) / n;
489
+ for (let i = 0; i < n; i++) {
490
+ const val = newVector[i];
491
+ if (val !== undefined) {
492
+ newVector[i] = val - newMean;
493
+ }
494
+ }
495
+
496
+ // Normalize
497
+ const newNorm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
498
+ if (newNorm < tolerance) {
499
+ break; // Converged to zero vector
500
+ }
501
+
502
+ for (let i = 0; i < n; i++) {
503
+ const val = newVector[i];
504
+ if (val !== undefined) {
505
+ newVector[i] = val / newNorm;
506
+ }
507
+ }
508
+
509
+ // Check convergence
510
+ let diff = 0;
511
+ for (let i = 0; i < n; i++) {
512
+ const newVal = newVector[i];
513
+ const oldVal = vector[i];
514
+ if (newVal !== undefined && oldVal !== undefined) {
515
+ diff += Math.abs(newVal - oldVal);
516
+ }
517
+ }
518
+
519
+ vector = newVector;
520
+
521
+ if (diff < tolerance) {
522
+ break;
523
+ }
524
+ }
525
+
526
+ return vector;
527
+ }
528
+
529
+ /**
530
+ * Calculate modularity of a graph partitioning
531
+ */
532
+ function calculateModularity(graph: Graph, assignment: Map<NodeId, number>): number {
533
+ const totalEdges = graph.uniqueEdgeCount;
534
+ if (totalEdges === 0) {
535
+ return 0;
536
+ }
537
+
538
+ let modularity = 0;
539
+ const communities = new Map<number, Set<NodeId>>();
540
+
541
+ // Group nodes by community
542
+ for (const [nodeId, communityId] of assignment) {
543
+ if (!communities.has(communityId)) {
544
+ communities.set(communityId, new Set());
545
+ }
546
+
547
+ const community = communities.get(communityId);
548
+ if (community) {
549
+ community.add(nodeId);
550
+ }
551
+ }
552
+
553
+ // Calculate modularity for each community
554
+ for (const [, members] of communities) {
555
+ let internalEdges = 0;
556
+ let totalDegree = 0;
557
+
558
+ for (const nodeId of members) {
559
+ const nodeDegree = graph.degree(nodeId);
560
+ totalDegree += nodeDegree;
561
+
562
+ // Count internal edges
563
+ for (const neighbor of graph.neighbors(nodeId)) {
564
+ if (members.has(neighbor)) {
565
+ internalEdges++;
566
+ }
567
+ }
568
+ }
569
+
570
+ // Each edge is counted twice in undirected graphs
571
+ internalEdges /= 2;
572
+
573
+ // Modularity contribution for this community
574
+ const expectedEdges = (totalDegree * totalDegree) / (4 * totalEdges);
575
+ modularity += (internalEdges - expectedEdges) / totalEdges;
576
+ }
577
+
578
+ return modularity;
579
+ }
580
+
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Priority 4 Research Algorithms (2023-2025)
3
+ *
4
+ * This module contains cutting-edge graph algorithms based on recent research
5
+ * from 2023-2025, focusing on deep learning integration, scalable algorithms,
6
+ * and explainable community detection.
7
+ */
8
+
9
+ // Synergistic Deep Graph Clustering
10
+ export {syncClustering, type SynCConfig, type SynCResult} from "./sync.js";
11
+
12
+ // TeraHAC - Hierarchical Agglomerative Clustering
13
+ export {teraHAC, type TeraHACConfig, type TeraHACResult} from "./terahac.js";
14
+ export {type ClusterNode as TeraHACClusterNode} from "./terahac.js";
15
+
16
+ // GRSBM - Greedy Recursive Spectral Bisection with Modularity
17
+ export {type ClusterExplanation, grsbm, type GRSBMCluster, type GRSBMConfig, type GRSBMResult} from "./grsbm.js";