@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
@@ -21,7 +21,7 @@ export function degreeCentrality(
21
21
  // Calculate normalization factor
22
22
  const maxPossibleDegree = graph.isDirected ? nodeCount - 1 : nodeCount - 1;
23
23
 
24
- for (const node of graph.nodes()) {
24
+ for (const node of Array.from(graph.nodes())) {
25
25
  let degree: number;
26
26
 
27
27
  if (graph.isDirected && options.mode) {
@@ -0,0 +1,162 @@
1
+ import type {Graph} from "../../core/graph.js";
2
+ import type {CentralityOptions, CentralityResult} from "../../types/index.js";
3
+
4
+ /**
5
+ * Eigenvector centrality implementation
6
+ *
7
+ * Measures the influence of a node based on the influence of its neighbors.
8
+ * A node has high eigenvector centrality if it is connected to nodes
9
+ * that themselves have high eigenvector centrality.
10
+ *
11
+ * Time complexity: O(V + E) per iteration
12
+ * Space complexity: O(V)
13
+ */
14
+
15
+ export interface EigenvectorCentralityOptions extends CentralityOptions {
16
+ maxIterations?: number; // Maximum iterations (default: 100)
17
+ tolerance?: number; // Convergence tolerance (default: 1e-6)
18
+ startVector?: Map<string, number>; // Initial vector (optional)
19
+ }
20
+
21
+ /**
22
+ * Calculate eigenvector centrality for all nodes in the graph
23
+ * Uses the power iteration method to find the dominant eigenvector
24
+ */
25
+ export function eigenvectorCentrality(
26
+ graph: Graph,
27
+ options: EigenvectorCentralityOptions = {},
28
+ ): CentralityResult {
29
+ const {
30
+ maxIterations = 100,
31
+ tolerance = 1e-6,
32
+ normalized = true,
33
+ startVector,
34
+ } = options;
35
+
36
+ const centrality: CentralityResult = {};
37
+ const nodes = Array.from(graph.nodes());
38
+ const nodeIds = nodes.map((node) => node.id);
39
+
40
+ if (nodeIds.length === 0) {
41
+ return centrality;
42
+ }
43
+
44
+ // Initialize the eigenvector
45
+ let currentVector = new Map<string, number>();
46
+ let previousVector = new Map<string, number>();
47
+
48
+ if (startVector) {
49
+ // Use provided start vector
50
+ for (const nodeId of nodeIds) {
51
+ const key = nodeId.toString();
52
+ currentVector.set(key, startVector.get(key) ?? 1.0 / Math.sqrt(nodeIds.length));
53
+ }
54
+ } else {
55
+ // Initialize with uniform distribution
56
+ const initialValue = 1.0 / Math.sqrt(nodeIds.length);
57
+ for (const nodeId of nodeIds) {
58
+ currentVector.set(nodeId.toString(), initialValue);
59
+ }
60
+ }
61
+
62
+ // Power iteration
63
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
64
+ previousVector = new Map(currentVector);
65
+ currentVector = new Map();
66
+
67
+ // Update each node's centrality based on neighbors
68
+ for (const nodeId of nodeIds) {
69
+ let sum = 0;
70
+ const neighbors = Array.from(graph.neighbors(nodeId));
71
+
72
+ for (const neighbor of neighbors) {
73
+ const neighborKey = neighbor.toString();
74
+ const prevValue = previousVector.get(neighborKey);
75
+ sum += prevValue ?? 0;
76
+ }
77
+
78
+ currentVector.set(nodeId.toString(), sum);
79
+ }
80
+
81
+ // Normalize the vector
82
+ let norm = 0;
83
+ for (const value of Array.from(currentVector.values())) {
84
+ norm += value * value;
85
+ }
86
+ norm = Math.sqrt(norm);
87
+
88
+ if (norm === 0) {
89
+ // Graph has no edges or is disconnected
90
+ for (const nodeId of nodeIds) {
91
+ centrality[nodeId.toString()] = 0;
92
+ }
93
+ return centrality;
94
+ }
95
+
96
+ // Normalize the vector
97
+ for (const [nodeId, value] of Array.from(currentVector)) {
98
+ currentVector.set(nodeId, value / norm);
99
+ }
100
+
101
+ // Check for convergence
102
+ let maxDiff = 0;
103
+ for (const [nodeId, value] of Array.from(currentVector)) {
104
+ const prevValue = previousVector.get(nodeId) ?? 0;
105
+ const diff = Math.abs(value - prevValue);
106
+ maxDiff = Math.max(maxDiff, diff);
107
+ }
108
+
109
+ if (maxDiff < tolerance) {
110
+ break;
111
+ }
112
+ }
113
+
114
+ // Prepare results
115
+ for (const [nodeId, value] of Array.from(currentVector)) {
116
+ centrality[nodeId] = value;
117
+ }
118
+
119
+ // Additional normalization if requested (normalize to [0,1] range)
120
+ if (normalized) {
121
+ let maxValue = 0;
122
+ let minValue = Number.POSITIVE_INFINITY;
123
+
124
+ for (const value of Object.values(centrality)) {
125
+ maxValue = Math.max(maxValue, value);
126
+ minValue = Math.min(minValue, value);
127
+ }
128
+
129
+ const range = maxValue - minValue;
130
+ if (range > 0) {
131
+ for (const nodeId of Object.keys(centrality)) {
132
+ const centralityValue = centrality[nodeId];
133
+ if (centralityValue !== undefined) {
134
+ centrality[nodeId] = (centralityValue - minValue) / range;
135
+ }
136
+ }
137
+ } else {
138
+ // All values are the same, set to 1
139
+ for (const nodeId of Object.keys(centrality)) {
140
+ centrality[nodeId] = maxValue > 0 ? 1 : 0;
141
+ }
142
+ }
143
+ }
144
+
145
+ return centrality;
146
+ }
147
+
148
+ /**
149
+ * Calculate eigenvector centrality for a specific node
150
+ */
151
+ export function nodeEigenvectorCentrality(
152
+ graph: Graph,
153
+ nodeId: string | number,
154
+ options: EigenvectorCentralityOptions = {},
155
+ ): number {
156
+ if (!graph.hasNode(nodeId)) {
157
+ throw new Error(`Node ${String(nodeId)} not found in graph`);
158
+ }
159
+
160
+ const centrality = eigenvectorCentrality(graph, options);
161
+ return centrality[nodeId.toString()] ?? 0;
162
+ }
@@ -0,0 +1,210 @@
1
+ import type {Graph} from "../../core/graph.js";
2
+ import type {CentralityOptions} from "../../types/index.js";
3
+
4
+ /**
5
+ * HITS (Hyperlink-Induced Topic Search) algorithm implementation
6
+ *
7
+ * Identifies hub and authority scores for nodes in a graph.
8
+ * - Authorities: nodes with valuable information (pointed to by hubs)
9
+ * - Hubs: nodes that point to many authorities
10
+ *
11
+ * Originally designed for web page ranking but applicable to any directed network.
12
+ *
13
+ * Time complexity: O(V + E) per iteration
14
+ * Space complexity: O(V)
15
+ */
16
+
17
+ export interface HITSResult {
18
+ hubs: Record<string, number>;
19
+ authorities: Record<string, number>;
20
+ }
21
+
22
+ export interface HITSOptions extends CentralityOptions {
23
+ maxIterations?: number; // Maximum iterations (default: 100)
24
+ tolerance?: number; // Convergence tolerance (default: 1e-6)
25
+ }
26
+
27
+ /**
28
+ * Calculate HITS hub and authority scores for all nodes in the graph
29
+ * Uses iterative method with normalization
30
+ */
31
+ export function hits(
32
+ graph: Graph,
33
+ options: HITSOptions = {},
34
+ ): HITSResult {
35
+ const {
36
+ maxIterations = 100,
37
+ tolerance = 1e-6,
38
+ normalized = true,
39
+ } = options;
40
+
41
+ const hubs: Record<string, number> = {};
42
+ const authorities: Record<string, number> = {};
43
+
44
+ const nodes = Array.from(graph.nodes());
45
+ const nodeIds = nodes.map((node) => node.id);
46
+
47
+ if (nodeIds.length === 0) {
48
+ return {hubs, authorities};
49
+ }
50
+
51
+ // Initialize scores
52
+ let currentHubs = new Map<string, number>();
53
+ let currentAuthorities = new Map<string, number>();
54
+ let previousHubs = new Map<string, number>();
55
+ let previousAuthorities = new Map<string, number>();
56
+
57
+ // Start with uniform distribution
58
+ const initialValue = 1.0 / Math.sqrt(nodeIds.length);
59
+ for (const nodeId of nodeIds) {
60
+ const key = nodeId.toString();
61
+ currentHubs.set(key, initialValue);
62
+ currentAuthorities.set(key, initialValue);
63
+ }
64
+
65
+ // Iterative computation
66
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
67
+ previousHubs = new Map(currentHubs);
68
+ previousAuthorities = new Map(currentAuthorities);
69
+
70
+ // Update authority scores
71
+ // Authority score = sum of hub scores of nodes pointing to it
72
+ const newAuthorities = new Map<string, number>();
73
+ for (const nodeId of nodeIds) {
74
+ let authorityScore = 0;
75
+ const inNeighbors = Array.from(graph.inNeighbors(nodeId));
76
+
77
+ for (const inNeighbor of inNeighbors) {
78
+ const neighborKey = inNeighbor.toString();
79
+ authorityScore += previousHubs.get(neighborKey) ?? 0;
80
+ }
81
+
82
+ newAuthorities.set(nodeId.toString(), authorityScore);
83
+ }
84
+
85
+ // Update hub scores
86
+ // Hub score = sum of authority scores of nodes it points to
87
+ const newHubs = new Map<string, number>();
88
+ for (const nodeId of nodeIds) {
89
+ let hubScore = 0;
90
+ const outNeighbors = Array.from(graph.outNeighbors(nodeId));
91
+
92
+ for (const outNeighbor of outNeighbors) {
93
+ const neighborKey = outNeighbor.toString();
94
+ hubScore += previousAuthorities.get(neighborKey) ?? 0;
95
+ }
96
+
97
+ newHubs.set(nodeId.toString(), hubScore);
98
+ }
99
+
100
+ // Normalize authority scores
101
+ let authNorm = 0;
102
+ for (const value of Array.from(newAuthorities.values())) {
103
+ authNorm += value * value;
104
+ }
105
+ authNorm = Math.sqrt(authNorm);
106
+
107
+ if (authNorm > 0) {
108
+ for (const [nodeId, value] of Array.from(newAuthorities)) {
109
+ newAuthorities.set(nodeId, value / authNorm);
110
+ }
111
+ }
112
+
113
+ // Normalize hub scores
114
+ let hubNorm = 0;
115
+ for (const value of Array.from(newHubs.values())) {
116
+ hubNorm += value * value;
117
+ }
118
+ hubNorm = Math.sqrt(hubNorm);
119
+
120
+ if (hubNorm > 0) {
121
+ for (const [nodeId, value] of Array.from(newHubs)) {
122
+ newHubs.set(nodeId, value / hubNorm);
123
+ }
124
+ }
125
+
126
+ currentAuthorities = newAuthorities;
127
+ currentHubs = newHubs;
128
+
129
+ // Check for convergence
130
+ let maxDiff = 0;
131
+ for (const [nodeId, value] of currentHubs) {
132
+ const prevValue = previousHubs.get(nodeId) ?? 0;
133
+ const diff = Math.abs(value - prevValue);
134
+ maxDiff = Math.max(maxDiff, diff);
135
+ }
136
+ for (const [nodeId, value] of currentAuthorities) {
137
+ const prevValue = previousAuthorities.get(nodeId) ?? 0;
138
+ const diff = Math.abs(value - prevValue);
139
+ maxDiff = Math.max(maxDiff, diff);
140
+ }
141
+
142
+ if (maxDiff < tolerance) {
143
+ break;
144
+ }
145
+ }
146
+
147
+ // Prepare results
148
+ for (const [nodeId, value] of currentHubs) {
149
+ hubs[nodeId] = value;
150
+ }
151
+ for (const [nodeId, value] of currentAuthorities) {
152
+ authorities[nodeId] = value;
153
+ }
154
+
155
+ // The algorithm already normalizes to unit L2 norm during iterations
156
+ // Additional max normalization is only applied if explicitly requested
157
+ if (!normalized) {
158
+ // If normalization is explicitly disabled, normalize to [0, 1] range
159
+ let maxHub = 0;
160
+ let maxAuth = 0;
161
+
162
+ for (const value of Object.values(hubs)) {
163
+ maxHub = Math.max(maxHub, value);
164
+ }
165
+ for (const value of Object.values(authorities)) {
166
+ maxAuth = Math.max(maxAuth, value);
167
+ }
168
+
169
+ if (maxHub > 0) {
170
+ for (const nodeId of Object.keys(hubs)) {
171
+ const hubValue = hubs[nodeId];
172
+ if (hubValue !== undefined) {
173
+ hubs[nodeId] = hubValue / maxHub;
174
+ }
175
+ }
176
+ }
177
+
178
+ if (maxAuth > 0) {
179
+ for (const nodeId of Object.keys(authorities)) {
180
+ const authValue = authorities[nodeId];
181
+ if (authValue !== undefined) {
182
+ authorities[nodeId] = authValue / maxAuth;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ return {hubs, authorities};
189
+ }
190
+
191
+ /**
192
+ * Calculate HITS scores for a specific node
193
+ */
194
+ export function nodeHITS(
195
+ graph: Graph,
196
+ nodeId: string | number,
197
+ options: HITSOptions = {},
198
+ ): {hub: number, authority: number} {
199
+ if (!graph.hasNode(nodeId)) {
200
+ throw new Error(`Node ${String(nodeId)} not found in graph`);
201
+ }
202
+
203
+ const result = hits(graph, options);
204
+ const key = nodeId.toString();
205
+
206
+ return {
207
+ hub: result.hubs[key] ?? 0,
208
+ authority: result.authorities[key] ?? 0,
209
+ };
210
+ }
@@ -7,5 +7,11 @@ export {betweennessCentrality, edgeBetweennessCentrality, nodeBetweennessCentral
7
7
  export type {ClosenessCentralityOptions} from "./closeness.js";
8
8
  export {closenessCentrality, nodeClosenessCentrality, nodeWeightedClosenessCentrality, weightedClosenessCentrality} from "./closeness.js";
9
9
  export {degreeCentrality, nodeDegreeCentrality} from "./degree.js";
10
+ export type {EigenvectorCentralityOptions} from "./eigenvector.js";
11
+ export {eigenvectorCentrality, nodeEigenvectorCentrality} from "./eigenvector.js";
12
+ export type {HITSOptions, HITSResult} from "./hits.js";
13
+ export {hits, nodeHITS} from "./hits.js";
14
+ export type {KatzCentralityOptions} from "./katz.js";
15
+ export {katzCentrality, nodeKatzCentrality} from "./katz.js";
10
16
  export type {PageRankOptions, PageRankResult} from "./pagerank.js";
11
17
  export {pageRank, pageRankCentrality, personalizedPageRank, topPageRankNodes} from "./pagerank.js";
@@ -0,0 +1,139 @@
1
+ import type {Graph} from "../../core/graph.js";
2
+ import type {CentralityOptions, CentralityResult} from "../../types/index.js";
3
+
4
+ /**
5
+ * Katz centrality implementation
6
+ *
7
+ * A generalization of eigenvector centrality that gives each node a base amount
8
+ * of influence regardless of its position in the network. Uses an attenuation
9
+ * factor (alpha) to control how much a node's centrality depends on its neighbors.
10
+ *
11
+ * Katz centrality = alpha * sum(neighbors' centrality) + beta
12
+ *
13
+ * Time complexity: O(V + E) per iteration
14
+ * Space complexity: O(V)
15
+ */
16
+
17
+ export interface KatzCentralityOptions extends CentralityOptions {
18
+ alpha?: number; // Attenuation factor (default: 0.1)
19
+ beta?: number; // Base centrality weight (default: 1.0)
20
+ maxIterations?: number; // Maximum iterations (default: 100)
21
+ tolerance?: number; // Convergence tolerance (default: 1e-6)
22
+ }
23
+
24
+ /**
25
+ * Calculate Katz centrality for all nodes in the graph
26
+ * Uses iterative method to compute centrality scores
27
+ */
28
+ export function katzCentrality(
29
+ graph: Graph,
30
+ options: KatzCentralityOptions = {},
31
+ ): CentralityResult {
32
+ const {
33
+ alpha = 0.1,
34
+ beta = 1.0,
35
+ maxIterations = 100,
36
+ tolerance = 1e-6,
37
+ normalized = true,
38
+ } = options;
39
+
40
+ const centrality: CentralityResult = {};
41
+ const nodes = Array.from(graph.nodes());
42
+ const nodeIds = nodes.map((node) => node.id);
43
+
44
+ if (nodeIds.length === 0) {
45
+ return centrality;
46
+ }
47
+
48
+ // Check if alpha is valid (should be less than 1/lambda_max)
49
+ // For simplicity, we'll trust the user's choice or use conservative default
50
+
51
+ // Initialize centrality scores
52
+ let currentScores = new Map<string, number>();
53
+ let previousScores = new Map<string, number>();
54
+
55
+ // Start with beta for all nodes
56
+ for (const nodeId of nodeIds) {
57
+ currentScores.set(nodeId.toString(), beta);
58
+ }
59
+
60
+ // Iterative computation
61
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
62
+ previousScores = new Map(currentScores);
63
+ currentScores = new Map();
64
+
65
+ // Update each node's centrality
66
+ for (const nodeId of nodeIds) {
67
+ let sum = 0;
68
+
69
+ // For directed graphs, use in-neighbors
70
+ // For undirected graphs, use all neighbors
71
+ const neighbors = graph.isDirected ?
72
+ Array.from(graph.inNeighbors(nodeId)) :
73
+ graph.neighbors(nodeId);
74
+
75
+ for (const neighbor of neighbors) {
76
+ const neighborKey = neighbor.toString();
77
+ sum += previousScores.get(neighborKey) ?? 0;
78
+ }
79
+
80
+ currentScores.set(nodeId.toString(), (alpha * sum) + beta);
81
+ }
82
+
83
+ // Check for convergence
84
+ let maxDiff = 0;
85
+ for (const [nodeId, value] of Array.from(currentScores)) {
86
+ const prevValue = previousScores.get(nodeId) ?? 0;
87
+ const diff = Math.abs(value - prevValue);
88
+ maxDiff = Math.max(maxDiff, diff);
89
+ }
90
+
91
+ if (maxDiff < tolerance) {
92
+ break;
93
+ }
94
+ }
95
+
96
+ // Prepare results
97
+ for (const [nodeId, value] of Array.from(currentScores)) {
98
+ centrality[nodeId] = value;
99
+ }
100
+
101
+ // Normalize if requested
102
+ if (normalized) {
103
+ let maxValue = 0;
104
+ let minValue = Number.POSITIVE_INFINITY;
105
+
106
+ for (const value of Object.values(centrality)) {
107
+ maxValue = Math.max(maxValue, value);
108
+ minValue = Math.min(minValue, value);
109
+ }
110
+
111
+ const range = maxValue - minValue;
112
+ if (range > 0) {
113
+ for (const nodeId of Object.keys(centrality)) {
114
+ const centralityValue = centrality[nodeId];
115
+ if (centralityValue !== undefined) {
116
+ centrality[nodeId] = (centralityValue - minValue) / range;
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ return centrality;
123
+ }
124
+
125
+ /**
126
+ * Calculate Katz centrality for a specific node
127
+ */
128
+ export function nodeKatzCentrality(
129
+ graph: Graph,
130
+ nodeId: string | number,
131
+ options: KatzCentralityOptions = {},
132
+ ): number {
133
+ if (!graph.hasNode(nodeId)) {
134
+ throw new Error(`Node ${String(nodeId)} not found in graph`);
135
+ }
136
+
137
+ const centrality = katzCentrality(graph, options);
138
+ return centrality[nodeId.toString()] ?? 0;
139
+ }
@@ -111,7 +111,7 @@ export function pageRank(
111
111
  let outDegree = 0;
112
112
  let totalOutWeight = 0;
113
113
 
114
- for (const neighbor of graph.neighbors(nodeId)) {
114
+ for (const neighbor of Array.from(graph.neighbors(nodeId))) {
115
115
  outDegree++;
116
116
  if (weight) {
117
117
  const edge = graph.getEdge(nodeId, neighbor);
@@ -178,7 +178,7 @@ export function pageRank(
178
178
  const nodeOutWeight = outWeights.get(nodeId) ?? 0;
179
179
 
180
180
  if (nodeOutWeight > 0) {
181
- for (const neighbor of graph.neighbors(nodeId)) {
181
+ for (const neighbor of Array.from(graph.neighbors(nodeId))) {
182
182
  let edgeWeight = 1;
183
183
  if (weight) {
184
184
  const edge = graph.getEdge(nodeId, neighbor);
@@ -295,7 +295,7 @@ function normalizeRanks(ranks: Map<NodeId, number>): void {
295
295
  }
296
296
 
297
297
  if (sum > 0) {
298
- for (const [nodeId, rank] of ranks) {
298
+ for (const [nodeId, rank] of Array.from(ranks)) {
299
299
  ranks.set(nodeId, rank / sum);
300
300
  }
301
301
  }