@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,353 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+ import type {LinkPredictionOptions, LinkPredictionScore} from "./common-neighbors.js";
4
+
5
+ /**
6
+ * Adamic-Adar Index link prediction implementation
7
+ *
8
+ * Predicts the likelihood of a link between two nodes based on their common
9
+ * neighbors, weighted by the inverse logarithm of the neighbors' degrees.
10
+ * This gives higher weight to rare common neighbors.
11
+ *
12
+ * Formula: AA(x,y) = Σ (1 / log(|Γ(z)|)) for all z ∈ Γ(x) ∩ Γ(y)
13
+ * where Γ(z) is the set of neighbors of node z
14
+ *
15
+ * Time complexity: O(k²) where k is the average degree
16
+ * Space complexity: O(k)
17
+ */
18
+
19
+ /**
20
+ * Calculate Adamic-Adar index for a pair of nodes
21
+ */
22
+ export function adamicAdarScore(
23
+ graph: Graph,
24
+ source: NodeId,
25
+ target: NodeId,
26
+ options: LinkPredictionOptions = {},
27
+ ): number {
28
+ if (!graph.hasNode(source) || !graph.hasNode(target)) {
29
+ return 0;
30
+ }
31
+
32
+ const {directed = false} = options;
33
+
34
+ // Get neighbors
35
+ const sourceNeighbors = new Set(
36
+ directed ? Array.from(graph.outNeighbors(source)) : Array.from(graph.neighbors(source)),
37
+ );
38
+ const targetNeighbors = new Set(
39
+ directed ? Array.from(graph.inNeighbors(target)) : Array.from(graph.neighbors(target)),
40
+ );
41
+
42
+ // Calculate Adamic-Adar score
43
+ let score = 0;
44
+ for (const neighbor of sourceNeighbors) {
45
+ if (targetNeighbors.has(neighbor)) {
46
+ const degree = directed ?
47
+ graph.outDegree(neighbor) : // Use out-degree for directed graphs
48
+ graph.degree(neighbor); // Use total degree for undirected graphs
49
+
50
+ if (degree > 1) {
51
+ score += 1 / Math.log(degree);
52
+ } else if (degree === 1) {
53
+ // For degree 1, we can't use log(1) = 0, so use a small constant
54
+ score += 1; // or some other reasonable value
55
+ }
56
+ }
57
+ }
58
+
59
+ return score;
60
+ }
61
+
62
+ /**
63
+ * Calculate Adamic-Adar scores for all possible node pairs
64
+ */
65
+ export function adamicAdarPrediction(
66
+ graph: Graph,
67
+ options: LinkPredictionOptions = {},
68
+ ): LinkPredictionScore[] {
69
+ const {
70
+ directed = false,
71
+ includeExisting = false,
72
+ topK,
73
+ } = options;
74
+
75
+ const scores: LinkPredictionScore[] = [];
76
+ const nodes = Array.from(graph.nodes()).map((n) => n.id);
77
+
78
+ for (let i = 0; i < nodes.length; i++) {
79
+ for (let j = i + 1; j < nodes.length; j++) {
80
+ const source = nodes[i];
81
+ const target = nodes[j];
82
+
83
+ if (!source || !target) {
84
+ continue;
85
+ }
86
+
87
+ // Skip existing edges unless requested
88
+ if (!includeExisting && graph.hasEdge(source, target)) {
89
+ continue;
90
+ }
91
+
92
+ const score = adamicAdarScore(graph, source, target, {directed});
93
+
94
+ if (score > 0) {
95
+ scores.push({source, target, score});
96
+
97
+ // For undirected graphs, also add the reverse pair
98
+ if (!directed && source !== target) {
99
+ scores.push({source: target, target: source, score});
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ // Sort by score in descending order
106
+ scores.sort((a, b) => b.score - a.score);
107
+
108
+ // Return top K if specified
109
+ if (topK && topK > 0) {
110
+ return scores.slice(0, topK);
111
+ }
112
+
113
+ return scores;
114
+ }
115
+
116
+ /**
117
+ * Calculate Adamic-Adar scores for specific node pairs
118
+ */
119
+ export function adamicAdarForPairs(
120
+ graph: Graph,
121
+ pairs: [NodeId, NodeId][],
122
+ options: LinkPredictionOptions = {},
123
+ ): LinkPredictionScore[] {
124
+ return pairs.map(([source, target]) => ({
125
+ source,
126
+ target,
127
+ score: adamicAdarScore(graph, source, target, options),
128
+ }));
129
+ }
130
+
131
+ /**
132
+ * Get top Adamic-Adar candidates for link prediction for a specific node
133
+ */
134
+ export function getTopAdamicAdarCandidatesForNode(
135
+ graph: Graph,
136
+ node: NodeId,
137
+ options: LinkPredictionOptions & {candidates?: NodeId[]} = {},
138
+ ): LinkPredictionScore[] {
139
+ if (!graph.hasNode(node)) {
140
+ return [];
141
+ }
142
+
143
+ const {
144
+ directed = false,
145
+ includeExisting = false,
146
+ topK = 10,
147
+ candidates,
148
+ } = options;
149
+
150
+ const scores: LinkPredictionScore[] = [];
151
+ const targetNodes = candidates ?? Array.from(graph.nodes()).map((n) => n.id);
152
+
153
+ for (const target of targetNodes) {
154
+ if (target === node) {
155
+ continue;
156
+ }
157
+
158
+ // Skip existing edges unless requested
159
+ if (!includeExisting && graph.hasEdge(node, target)) {
160
+ continue;
161
+ }
162
+
163
+ const score = adamicAdarScore(graph, node, target, {directed});
164
+
165
+ if (score > 0) {
166
+ scores.push({source: node, target, score});
167
+ }
168
+ }
169
+
170
+ // Sort by score in descending order
171
+ scores.sort((a, b) => b.score - a.score);
172
+
173
+ return scores.slice(0, topK);
174
+ }
175
+
176
+ /**
177
+ * Calculate precision and recall for Adamic-Adar link prediction evaluation
178
+ */
179
+ export function evaluateAdamicAdar(
180
+ trainingGraph: Graph,
181
+ testEdges: [NodeId, NodeId][],
182
+ nonEdges: [NodeId, NodeId][],
183
+ options: LinkPredictionOptions = {},
184
+ ): {
185
+ precision: number;
186
+ recall: number;
187
+ f1Score: number;
188
+ auc: number;
189
+ } {
190
+ // Get scores for test edges and non-edges
191
+ const testScores = adamicAdarForPairs(trainingGraph, testEdges, options);
192
+ const nonEdgeScores = adamicAdarForPairs(trainingGraph, nonEdges, options);
193
+
194
+ // Combine and sort all scores
195
+ const allScores = [
196
+ ... testScores.map((s) => ({... s, isActualEdge: true})),
197
+ ... nonEdgeScores.map((s) => ({... s, isActualEdge: false})),
198
+ ].sort((a, b) => b.score - a.score);
199
+
200
+ // Calculate precision and recall at different thresholds
201
+ let truePositives = 0;
202
+ let falsePositives = 0;
203
+ let bestF1 = 0;
204
+ let bestPrecision = 0;
205
+ let bestRecall = 0;
206
+
207
+ const totalPositives = testEdges.length;
208
+
209
+ for (const scoreItem of allScores) {
210
+ if (scoreItem.isActualEdge) {
211
+ truePositives++;
212
+ } else {
213
+ falsePositives++;
214
+ }
215
+
216
+ const precision = truePositives / (truePositives + falsePositives);
217
+ const recall = truePositives / totalPositives;
218
+ const f1 = precision + recall > 0 ? 2 * (precision * recall) / (precision + recall) : 0;
219
+
220
+ if (f1 > bestF1) {
221
+ bestF1 = f1;
222
+ bestPrecision = precision;
223
+ bestRecall = recall;
224
+ }
225
+ }
226
+
227
+ // Calculate AUC (Area Under Curve)
228
+ let auc = 0;
229
+ let tpCount = 0;
230
+ let fpCount = 0;
231
+
232
+ for (const item of allScores) {
233
+ if (item.isActualEdge) {
234
+ tpCount++;
235
+ } else {
236
+ auc += tpCount;
237
+ fpCount++;
238
+ }
239
+ }
240
+
241
+ if (tpCount > 0 && fpCount > 0) {
242
+ auc = auc / (tpCount * fpCount);
243
+ } else {
244
+ auc = 0.5; // Random performance
245
+ }
246
+
247
+ return {
248
+ precision: bestPrecision,
249
+ recall: bestRecall,
250
+ f1Score: bestF1,
251
+ auc,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Compare Adamic-Adar with Common Neighbors for the same dataset
257
+ */
258
+ export function compareAdamicAdarWithCommonNeighbors(
259
+ graph: Graph,
260
+ testEdges: [NodeId, NodeId][],
261
+ nonEdges: [NodeId, NodeId][],
262
+ options: LinkPredictionOptions = {},
263
+ ): {
264
+ adamicAdar: ReturnType<typeof evaluateAdamicAdar>;
265
+ commonNeighbors: {
266
+ precision: number;
267
+ recall: number;
268
+ f1Score: number;
269
+ auc: number;
270
+ };
271
+ } {
272
+ // Import common neighbors evaluation function
273
+ // Since we're using ES modules, we can't use require. Instead, we'll implement a simple version here
274
+ const commonNeighborsPairs = (pairs: [NodeId, NodeId][]): LinkPredictionScore[] =>
275
+ pairs.map(([source, target]) => ({
276
+ source,
277
+ target,
278
+ score: (() => {
279
+ const sourceNeighbors = new Set(Array.from(graph.neighbors(source)));
280
+ const targetNeighbors = new Set(Array.from(graph.neighbors(target)));
281
+ let count = 0;
282
+ for (const n of sourceNeighbors) {
283
+ if (targetNeighbors.has(n)) {
284
+ count++;
285
+ }
286
+ }
287
+ return count;
288
+ })(),
289
+ }));
290
+
291
+ const testScores = commonNeighborsPairs(testEdges);
292
+ const nonEdgeScores = commonNeighborsPairs(nonEdges);
293
+
294
+ const allScores = [
295
+ ... testScores.map((s) => ({... s, isActualEdge: true})),
296
+ ... nonEdgeScores.map((s) => ({... s, isActualEdge: false})),
297
+ ].sort((a, b) => b.score - a.score);
298
+
299
+ let truePositives = 0;
300
+ let falsePositives = 0;
301
+ let bestF1 = 0;
302
+ let bestPrecision = 0;
303
+ let bestRecall = 0;
304
+ const totalPositives = testEdges.length;
305
+
306
+ for (const scoreItem of allScores) {
307
+ if (scoreItem.isActualEdge) {
308
+ truePositives++;
309
+ } else {
310
+ falsePositives++;
311
+ }
312
+
313
+ const precision = truePositives / (truePositives + falsePositives);
314
+ const recall = truePositives / totalPositives;
315
+ const f1 = precision + recall > 0 ? 2 * (precision * recall) / (precision + recall) : 0;
316
+ if (f1 > bestF1) {
317
+ bestF1 = f1;
318
+ bestPrecision = precision;
319
+ bestRecall = recall;
320
+ }
321
+ }
322
+
323
+ let auc = 0;
324
+ let tpCount = 0;
325
+ let fpCount = 0;
326
+ for (const item of allScores) {
327
+ if (item.isActualEdge) {
328
+ tpCount++;
329
+ } else {
330
+ auc += tpCount;
331
+ fpCount++;
332
+ }
333
+ }
334
+ if (tpCount > 0 && fpCount > 0) {
335
+ auc = auc / (tpCount * fpCount);
336
+ } else {
337
+ auc = 0.5;
338
+ }
339
+
340
+ const commonNeighborsResults = {
341
+ precision: bestPrecision,
342
+ recall: bestRecall,
343
+ f1Score: bestF1,
344
+ auc,
345
+ };
346
+
347
+ const adamicAdarResults = evaluateAdamicAdar(graph, testEdges, nonEdges, options);
348
+
349
+ return {
350
+ adamicAdar: adamicAdarResults,
351
+ commonNeighbors: commonNeighborsResults,
352
+ };
353
+ }
@@ -0,0 +1,252 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+
4
+ /**
5
+ * Common Neighbors link prediction implementation
6
+ *
7
+ * Predicts the likelihood of a link between two nodes based on the number
8
+ * of common neighbors they share. The intuition is that nodes with many
9
+ * common neighbors are more likely to be connected.
10
+ *
11
+ * Time complexity: O(k²) where k is the average degree
12
+ * Space complexity: O(k)
13
+ */
14
+
15
+ export interface LinkPredictionScore {
16
+ source: NodeId;
17
+ target: NodeId;
18
+ score: number;
19
+ }
20
+
21
+ export interface LinkPredictionOptions {
22
+ directed?: boolean; // Consider direction (default: false)
23
+ includeExisting?: boolean; // Include existing edges (default: false)
24
+ topK?: number; // Return only top K predictions
25
+ }
26
+
27
+ /**
28
+ * Calculate common neighbors score for a pair of nodes
29
+ */
30
+ export function commonNeighborsScore(
31
+ graph: Graph,
32
+ source: NodeId,
33
+ target: NodeId,
34
+ options: LinkPredictionOptions = {},
35
+ ): number {
36
+ if (!graph.hasNode(source) || !graph.hasNode(target)) {
37
+ return 0;
38
+ }
39
+
40
+ const {directed = false} = options;
41
+
42
+ // Get neighbors
43
+ const sourceNeighbors = new Set(
44
+ directed ? Array.from(graph.outNeighbors(source)) : Array.from(graph.neighbors(source)),
45
+ );
46
+ const targetNeighbors = new Set(
47
+ directed ? Array.from(graph.inNeighbors(target)) : Array.from(graph.neighbors(target)),
48
+ );
49
+
50
+ // Count common neighbors
51
+ let commonCount = 0;
52
+ for (const neighbor of sourceNeighbors) {
53
+ if (targetNeighbors.has(neighbor)) {
54
+ commonCount++;
55
+ }
56
+ }
57
+
58
+ return commonCount;
59
+ }
60
+
61
+ /**
62
+ * Calculate common neighbors scores for all possible node pairs
63
+ */
64
+ export function commonNeighborsPrediction(
65
+ graph: Graph,
66
+ options: LinkPredictionOptions = {},
67
+ ): LinkPredictionScore[] {
68
+ const {
69
+ directed = false,
70
+ includeExisting = false,
71
+ topK,
72
+ } = options;
73
+
74
+ const scores: LinkPredictionScore[] = [];
75
+ const nodes = Array.from(graph.nodes()).map((n) => n.id);
76
+
77
+ for (let i = 0; i < nodes.length; i++) {
78
+ for (let j = i + 1; j < nodes.length; j++) {
79
+ const source = nodes[i];
80
+ const target = nodes[j];
81
+
82
+ if (!source || !target) {
83
+ continue;
84
+ }
85
+
86
+ // Skip existing edges unless requested
87
+ if (!includeExisting && graph.hasEdge(source, target)) {
88
+ continue;
89
+ }
90
+
91
+ const score = commonNeighborsScore(graph, source, target, {directed});
92
+
93
+ if (score > 0) {
94
+ scores.push({source, target, score});
95
+
96
+ // For undirected graphs, also add the reverse pair
97
+ if (!directed && source !== target) {
98
+ scores.push({source: target, target: source, score});
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ // Sort by score in descending order
105
+ scores.sort((a, b) => b.score - a.score);
106
+
107
+ // Return top K if specified
108
+ if (topK && topK > 0) {
109
+ return scores.slice(0, topK);
110
+ }
111
+
112
+ return scores;
113
+ }
114
+
115
+ /**
116
+ * Calculate common neighbors scores for specific node pairs
117
+ */
118
+ export function commonNeighborsForPairs(
119
+ graph: Graph,
120
+ pairs: [NodeId, NodeId][],
121
+ options: LinkPredictionOptions = {},
122
+ ): LinkPredictionScore[] {
123
+ return pairs.map(([source, target]) => ({
124
+ source,
125
+ target,
126
+ score: commonNeighborsScore(graph, source, target, options),
127
+ }));
128
+ }
129
+
130
+ /**
131
+ * Get top candidates for link prediction for a specific node
132
+ */
133
+ export function getTopCandidatesForNode(
134
+ graph: Graph,
135
+ node: NodeId,
136
+ options: LinkPredictionOptions & {candidates?: NodeId[]} = {},
137
+ ): LinkPredictionScore[] {
138
+ if (!graph.hasNode(node)) {
139
+ return [];
140
+ }
141
+
142
+ const {
143
+ directed = false,
144
+ includeExisting = false,
145
+ topK = 10,
146
+ candidates,
147
+ } = options;
148
+
149
+ const scores: LinkPredictionScore[] = [];
150
+ const targetNodes = candidates ?? Array.from(graph.nodes()).map((n) => n.id);
151
+
152
+ for (const target of targetNodes) {
153
+ if (target === node) {
154
+ continue;
155
+ }
156
+
157
+ // Skip existing edges unless requested
158
+ if (!includeExisting && graph.hasEdge(node, target)) {
159
+ continue;
160
+ }
161
+
162
+ const score = commonNeighborsScore(graph, node, target, {directed});
163
+
164
+ if (score > 0) {
165
+ scores.push({source: node, target, score});
166
+ }
167
+ }
168
+
169
+ // Sort by score in descending order
170
+ scores.sort((a, b) => b.score - a.score);
171
+
172
+ return scores.slice(0, topK);
173
+ }
174
+
175
+ /**
176
+ * Calculate precision and recall for link prediction evaluation
177
+ */
178
+ export function evaluateCommonNeighbors(
179
+ trainingGraph: Graph,
180
+ testEdges: [NodeId, NodeId][],
181
+ nonEdges: [NodeId, NodeId][],
182
+ options: LinkPredictionOptions = {},
183
+ ): {
184
+ precision: number;
185
+ recall: number;
186
+ f1Score: number;
187
+ auc: number;
188
+ } {
189
+ // Get scores for test edges and non-edges
190
+ const testScores = commonNeighborsForPairs(trainingGraph, testEdges, options);
191
+ const nonEdgeScores = commonNeighborsForPairs(trainingGraph, nonEdges, options);
192
+
193
+ // Combine and sort all scores
194
+ const allScores = [
195
+ ... testScores.map((s) => ({... s, isActualEdge: true})),
196
+ ... nonEdgeScores.map((s) => ({... s, isActualEdge: false})),
197
+ ].sort((a, b) => b.score - a.score);
198
+
199
+ // Calculate precision and recall at different thresholds
200
+ let truePositives = 0;
201
+ let falsePositives = 0;
202
+ let bestF1 = 0;
203
+ let bestPrecision = 0;
204
+ let bestRecall = 0;
205
+
206
+ const totalPositives = testEdges.length;
207
+
208
+ for (const scoreItem of allScores) {
209
+ if (scoreItem.isActualEdge) {
210
+ truePositives++;
211
+ } else {
212
+ falsePositives++;
213
+ }
214
+
215
+ const precision = truePositives / (truePositives + falsePositives);
216
+ const recall = truePositives / totalPositives;
217
+ const f1 = precision + recall > 0 ? 2 * (precision * recall) / (precision + recall) : 0;
218
+
219
+ if (f1 > bestF1) {
220
+ bestF1 = f1;
221
+ bestPrecision = precision;
222
+ bestRecall = recall;
223
+ }
224
+ }
225
+
226
+ // Calculate AUC (Area Under Curve)
227
+ let auc = 0;
228
+ let tpCount = 0;
229
+ let fpCount = 0;
230
+
231
+ for (const item of allScores) {
232
+ if (item.isActualEdge) {
233
+ tpCount++;
234
+ } else {
235
+ auc += tpCount;
236
+ fpCount++;
237
+ }
238
+ }
239
+
240
+ if (tpCount > 0 && fpCount > 0) {
241
+ auc = auc / (tpCount * fpCount);
242
+ } else {
243
+ auc = 0.5; // Random performance
244
+ }
245
+
246
+ return {
247
+ precision: bestPrecision,
248
+ recall: bestRecall,
249
+ f1Score: bestF1,
250
+ auc,
251
+ };
252
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Link prediction algorithms
3
+ */
4
+
5
+ export {
6
+ adamicAdarForPairs,
7
+ adamicAdarPrediction,
8
+ adamicAdarScore,
9
+ compareAdamicAdarWithCommonNeighbors,
10
+ evaluateAdamicAdar,
11
+ getTopAdamicAdarCandidatesForNode,
12
+ } from "./adamic-adar.js";
13
+ export type {LinkPredictionOptions, LinkPredictionScore} from "./common-neighbors.js";
14
+ export {
15
+ commonNeighborsForPairs,
16
+ commonNeighborsPrediction,
17
+ commonNeighborsScore,
18
+ evaluateCommonNeighbors,
19
+ getTopCandidatesForNode,
20
+ } from "./common-neighbors.js";