graphwise 1.8.0 → 1.9.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 (125) hide show
  1. package/dist/adjacency-map-BtKzcuJq.js +229 -0
  2. package/dist/adjacency-map-BtKzcuJq.js.map +1 -0
  3. package/dist/adjacency-map-JqBnMNkF.cjs +234 -0
  4. package/dist/adjacency-map-JqBnMNkF.cjs.map +1 -0
  5. package/dist/async/index.cjs +15 -242
  6. package/dist/async/index.js +2 -229
  7. package/dist/expansion/index.cjs +43 -0
  8. package/dist/expansion/index.js +2 -0
  9. package/dist/expansion-ClDhlMK8.js +1704 -0
  10. package/dist/expansion-ClDhlMK8.js.map +1 -0
  11. package/dist/expansion-DaTroIyv.cjs +1949 -0
  12. package/dist/expansion-DaTroIyv.cjs.map +1 -0
  13. package/dist/extraction/index.cjs +630 -0
  14. package/dist/extraction/index.cjs.map +1 -0
  15. package/dist/extraction/index.js +621 -0
  16. package/dist/extraction/index.js.map +1 -0
  17. package/dist/gpu/csr.d.ts +29 -30
  18. package/dist/gpu/csr.d.ts.map +1 -1
  19. package/dist/gpu/dispatch.d.ts +31 -0
  20. package/dist/gpu/dispatch.d.ts.map +1 -0
  21. package/dist/gpu/dispatch.unit.test.d.ts +5 -0
  22. package/dist/gpu/dispatch.unit.test.d.ts.map +1 -0
  23. package/dist/gpu/index.cjs +15 -410
  24. package/dist/gpu/index.d.ts +3 -1
  25. package/dist/gpu/index.d.ts.map +1 -1
  26. package/dist/gpu/index.js +2 -400
  27. package/dist/gpu/kernels/bfs/kernel.d.ts +59 -0
  28. package/dist/gpu/kernels/bfs/kernel.d.ts.map +1 -0
  29. package/dist/gpu/kernels/bfs/logic.d.ts +47 -0
  30. package/dist/gpu/kernels/bfs/logic.d.ts.map +1 -0
  31. package/dist/gpu/kernels/bfs/logic.unit.test.d.ts +2 -0
  32. package/dist/gpu/kernels/bfs/logic.unit.test.d.ts.map +1 -0
  33. package/dist/gpu/kernels/degree-histogram/kernel.d.ts +32 -0
  34. package/dist/gpu/kernels/degree-histogram/kernel.d.ts.map +1 -0
  35. package/dist/gpu/kernels/degree-histogram/logic.d.ts +45 -0
  36. package/dist/gpu/kernels/degree-histogram/logic.d.ts.map +1 -0
  37. package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts +2 -0
  38. package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts.map +1 -0
  39. package/dist/gpu/kernels/jaccard/kernel.d.ts +40 -0
  40. package/dist/gpu/kernels/jaccard/kernel.d.ts.map +1 -0
  41. package/dist/gpu/kernels/jaccard/logic.d.ts +43 -0
  42. package/dist/gpu/kernels/jaccard/logic.d.ts.map +1 -0
  43. package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts +2 -0
  44. package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts.map +1 -0
  45. package/dist/gpu/kernels/pagerank/kernel.d.ts +44 -0
  46. package/dist/gpu/kernels/pagerank/kernel.d.ts.map +1 -0
  47. package/dist/gpu/kernels/pagerank/logic.d.ts +50 -0
  48. package/dist/gpu/kernels/pagerank/logic.d.ts.map +1 -0
  49. package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts +2 -0
  50. package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts.map +1 -0
  51. package/dist/gpu/kernels/spmv/kernel.d.ts +43 -0
  52. package/dist/gpu/kernels/spmv/kernel.d.ts.map +1 -0
  53. package/dist/gpu/kernels/spmv/logic.d.ts +31 -0
  54. package/dist/gpu/kernels/spmv/logic.d.ts.map +1 -0
  55. package/dist/gpu/kernels/spmv/logic.unit.test.d.ts +2 -0
  56. package/dist/gpu/kernels/spmv/logic.unit.test.d.ts.map +1 -0
  57. package/dist/gpu/operations.d.ts +76 -0
  58. package/dist/gpu/operations.d.ts.map +1 -0
  59. package/dist/gpu/operations.unit.test.d.ts +5 -0
  60. package/dist/gpu/operations.unit.test.d.ts.map +1 -0
  61. package/dist/gpu/root.d.ts +53 -0
  62. package/dist/gpu/root.d.ts.map +1 -0
  63. package/dist/gpu/root.unit.test.d.ts +2 -0
  64. package/dist/gpu/root.unit.test.d.ts.map +1 -0
  65. package/dist/gpu/types.d.ts +3 -8
  66. package/dist/gpu/types.d.ts.map +1 -1
  67. package/dist/gpu-CHiCN0wa.js +16945 -0
  68. package/dist/gpu-CHiCN0wa.js.map +1 -0
  69. package/dist/gpu-Y6owRVMi.cjs +17028 -0
  70. package/dist/gpu-Y6owRVMi.cjs.map +1 -0
  71. package/dist/graph/index.cjs +2 -229
  72. package/dist/graph/index.js +1 -228
  73. package/dist/index/index.cjs +141 -4040
  74. package/dist/index/index.js +15 -3917
  75. package/dist/jaccard-3rCdilwm.js +39 -0
  76. package/dist/jaccard-3rCdilwm.js.map +1 -0
  77. package/dist/jaccard-Bys9_dGW.cjs +50 -0
  78. package/dist/jaccard-Bys9_dGW.cjs.map +1 -0
  79. package/dist/{kmeans-BIgSyGKu.cjs → kmeans-B8x9D1kt.cjs} +1 -1
  80. package/dist/{kmeans-BIgSyGKu.cjs.map → kmeans-B8x9D1kt.cjs.map} +1 -1
  81. package/dist/{kmeans-87ExSUNZ.js → kmeans-DKkL9rAN.js} +1 -1
  82. package/dist/{kmeans-87ExSUNZ.js.map → kmeans-DKkL9rAN.js.map} +1 -1
  83. package/dist/ops-djAsQQSh.cjs +277 -0
  84. package/dist/ops-djAsQQSh.cjs.map +1 -0
  85. package/dist/ops-upIi6JIi.js +212 -0
  86. package/dist/ops-upIi6JIi.js.map +1 -0
  87. package/dist/priority-queue-BIiD1L0k.cjs +148 -0
  88. package/dist/priority-queue-BIiD1L0k.cjs.map +1 -0
  89. package/dist/priority-queue-CFDd5cBg.js +143 -0
  90. package/dist/priority-queue-CFDd5cBg.js.map +1 -0
  91. package/dist/ranking/index.cjs +43 -0
  92. package/dist/ranking/index.js +4 -0
  93. package/dist/ranking/mi/index.cjs +581 -0
  94. package/dist/ranking/mi/index.cjs.map +1 -0
  95. package/dist/ranking/mi/index.js +555 -0
  96. package/dist/ranking/mi/index.js.map +1 -0
  97. package/dist/ranking-3ez5m67U.js +1016 -0
  98. package/dist/ranking-3ez5m67U.js.map +1 -0
  99. package/dist/ranking-DVvajgUZ.cjs +1093 -0
  100. package/dist/ranking-DVvajgUZ.cjs.map +1 -0
  101. package/dist/seeds/index.cjs +1 -1
  102. package/dist/seeds/index.js +1 -1
  103. package/dist/structures/index.cjs +2 -143
  104. package/dist/structures/index.js +1 -142
  105. package/dist/utils/index.cjs +1 -1
  106. package/dist/utils/index.js +1 -1
  107. package/dist/utils-BodeE2Mo.js +22 -0
  108. package/dist/utils-BodeE2Mo.js.map +1 -0
  109. package/dist/utils-CDtCcsyF.cjs +33 -0
  110. package/dist/utils-CDtCcsyF.cjs.map +1 -0
  111. package/package.json +3 -1
  112. package/dist/async/index.cjs.map +0 -1
  113. package/dist/async/index.js.map +0 -1
  114. package/dist/gpu/context.d.ts +0 -118
  115. package/dist/gpu/context.d.ts.map +0 -1
  116. package/dist/gpu/context.unit.test.d.ts +0 -2
  117. package/dist/gpu/context.unit.test.d.ts.map +0 -1
  118. package/dist/gpu/index.cjs.map +0 -1
  119. package/dist/gpu/index.js.map +0 -1
  120. package/dist/graph/index.cjs.map +0 -1
  121. package/dist/graph/index.js.map +0 -1
  122. package/dist/index/index.cjs.map +0 -1
  123. package/dist/index/index.js.map +0 -1
  124. package/dist/structures/index.cjs.map +0 -1
  125. package/dist/structures/index.js.map +0 -1
@@ -0,0 +1,555 @@
1
+ import { t as collectAsyncIterable } from "../../utils-BodeE2Mo.js";
2
+ import { computeJaccard, countEdgesOfType, countNodesOfType, localClusteringCoefficient, neighbourIntersection, neighbourOverlap, neighbourSet } from "../../utils/index.js";
3
+ import { n as jaccardAsync, t as jaccard } from "../../jaccard-3rCdilwm.js";
4
+ //#region src/ranking/mi/adamic-adar.ts
5
+ /**
6
+ * Compute Adamic-Adar index between neighbourhoods of two nodes.
7
+ *
8
+ * @param graph - Source graph
9
+ * @param source - Source node ID
10
+ * @param target - Target node ID
11
+ * @param config - Optional configuration
12
+ * @returns Adamic-Adar index (normalised to [0, 1] if configured)
13
+ */
14
+ function adamicAdar(graph, source, target, config) {
15
+ const { epsilon = 1e-10, normalise = true } = config ?? {};
16
+ const commonNeighbours = neighbourIntersection(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
17
+ let score = 0;
18
+ for (const neighbour of commonNeighbours) {
19
+ const degree = graph.degree(neighbour);
20
+ score += 1 / Math.log(degree + 1);
21
+ }
22
+ if (normalise && commonNeighbours.size > 0) {
23
+ const maxScore = commonNeighbours.size / Math.log(2);
24
+ score = score / maxScore;
25
+ }
26
+ return Math.max(epsilon, score);
27
+ }
28
+ /**
29
+ * Async variant of Adamic-Adar index for use with async graph data sources.
30
+ *
31
+ * Fetches both neighbourhoods concurrently, then fetches degree for each common
32
+ * neighbour to compute the inverse-log-degree weighted sum.
33
+ */
34
+ async function adamicAdarAsync(graph, source, target, config) {
35
+ const { epsilon = 1e-10, normalise = true } = config ?? {};
36
+ const [sourceArr, targetArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
37
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
38
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
39
+ const commonNeighbours = [];
40
+ for (const n of srcSet) if (tgtSet.has(n)) commonNeighbours.push(n);
41
+ if (commonNeighbours.length === 0) return epsilon;
42
+ const degrees = await Promise.all(commonNeighbours.map((n) => graph.degree(n)));
43
+ let score = 0;
44
+ for (const degree of degrees) score += 1 / Math.log(degree + 1);
45
+ if (normalise) {
46
+ const maxScore = commonNeighbours.length / Math.log(2);
47
+ score = score / maxScore;
48
+ }
49
+ return Math.max(epsilon, score);
50
+ }
51
+ //#endregion
52
+ //#region src/ranking/mi/cosine.ts
53
+ /**
54
+ * Compute cosine similarity between neighbourhoods of two nodes.
55
+ *
56
+ * @param graph - Source graph
57
+ * @param source - Source node ID
58
+ * @param target - Target node ID
59
+ * @param config - Optional configuration
60
+ * @returns Cosine similarity in [0, 1]
61
+ */
62
+ function cosine(graph, source, target, config) {
63
+ const { epsilon = 1e-10 } = config ?? {};
64
+ const sourceNeighbours = neighbourSet(graph, source, target);
65
+ const targetNeighbours = neighbourSet(graph, target, source);
66
+ const { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);
67
+ const denominator = Math.sqrt(sourceNeighbours.size) * Math.sqrt(targetNeighbours.size);
68
+ if (denominator === 0) return 0;
69
+ const score = intersection / denominator;
70
+ return Math.max(epsilon, score);
71
+ }
72
+ /**
73
+ * Async variant of cosine similarity for use with async graph data sources.
74
+ *
75
+ * Fetches both neighbourhoods concurrently, then applies the same formula.
76
+ */
77
+ async function cosineAsync(graph, source, target, config) {
78
+ const { epsilon = 1e-10 } = config ?? {};
79
+ const [sourceArr, targetArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
80
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
81
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
82
+ let intersection = 0;
83
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
84
+ const denominator = Math.sqrt(srcSet.size) * Math.sqrt(tgtSet.size);
85
+ if (denominator === 0) return 0;
86
+ const score = intersection / denominator;
87
+ return Math.max(epsilon, score);
88
+ }
89
+ //#endregion
90
+ //#region src/ranking/mi/sorensen.ts
91
+ /**
92
+ * Compute Sorensen-Dice similarity between neighbourhoods of two nodes.
93
+ *
94
+ * @param graph - Source graph
95
+ * @param source - Source node ID
96
+ * @param target - Target node ID
97
+ * @param config - Optional configuration
98
+ * @returns Sorensen-Dice coefficient in [0, 1]
99
+ */
100
+ function sorensen(graph, source, target, config) {
101
+ const { epsilon = 1e-10 } = config ?? {};
102
+ const sourceNeighbours = neighbourSet(graph, source, target);
103
+ const targetNeighbours = neighbourSet(graph, target, source);
104
+ const { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);
105
+ const denominator = sourceNeighbours.size + targetNeighbours.size;
106
+ if (denominator === 0) return 0;
107
+ const score = 2 * intersection / denominator;
108
+ return Math.max(epsilon, score);
109
+ }
110
+ /**
111
+ * Async variant of Sorensen-Dice coefficient for use with async graph data sources.
112
+ *
113
+ * Fetches both neighbourhoods concurrently, then applies the same formula.
114
+ */
115
+ async function sorensenAsync(graph, source, target, config) {
116
+ const { epsilon = 1e-10 } = config ?? {};
117
+ const [sourceArr, targetArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
118
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
119
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
120
+ let intersection = 0;
121
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
122
+ const denominator = srcSet.size + tgtSet.size;
123
+ if (denominator === 0) return 0;
124
+ const score = 2 * intersection / denominator;
125
+ return Math.max(epsilon, score);
126
+ }
127
+ //#endregion
128
+ //#region src/ranking/mi/resource-allocation.ts
129
+ /**
130
+ * Compute Resource Allocation index between neighbourhoods of two nodes.
131
+ *
132
+ * @param graph - Source graph
133
+ * @param source - Source node ID
134
+ * @param target - Target node ID
135
+ * @param config - Optional configuration
136
+ * @returns Resource Allocation index (normalised to [0, 1] if configured)
137
+ */
138
+ function resourceAllocation(graph, source, target, config) {
139
+ const { epsilon = 1e-10, normalise = true } = config ?? {};
140
+ const commonNeighbours = neighbourIntersection(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
141
+ let score = 0;
142
+ for (const neighbour of commonNeighbours) {
143
+ const degree = graph.degree(neighbour);
144
+ if (degree > 0) score += 1 / degree;
145
+ }
146
+ if (normalise && commonNeighbours.size > 0) {
147
+ const maxScore = commonNeighbours.size;
148
+ score = score / maxScore;
149
+ }
150
+ return Math.max(epsilon, score);
151
+ }
152
+ /**
153
+ * Async variant of Resource Allocation index for use with async graph data sources.
154
+ *
155
+ * Fetches both neighbourhoods concurrently, then fetches degree for each common
156
+ * neighbour to compute the inverse-degree weighted sum.
157
+ */
158
+ async function resourceAllocationAsync(graph, source, target, config) {
159
+ const { epsilon = 1e-10, normalise = true } = config ?? {};
160
+ const [sourceArr, targetArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
161
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
162
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
163
+ const commonNeighbours = [];
164
+ for (const n of srcSet) if (tgtSet.has(n)) commonNeighbours.push(n);
165
+ if (commonNeighbours.length === 0) return epsilon;
166
+ const degrees = await Promise.all(commonNeighbours.map((n) => graph.degree(n)));
167
+ let score = 0;
168
+ for (const degree of degrees) if (degree > 0) score += 1 / degree;
169
+ if (normalise) {
170
+ const maxScore = commonNeighbours.length;
171
+ score = score / maxScore;
172
+ }
173
+ return Math.max(epsilon, score);
174
+ }
175
+ //#endregion
176
+ //#region src/ranking/mi/overlap-coefficient.ts
177
+ /**
178
+ * Compute Overlap Coefficient between neighbourhoods of two nodes.
179
+ *
180
+ * @param graph - Source graph
181
+ * @param source - Source node ID
182
+ * @param target - Target node ID
183
+ * @param config - Optional configuration
184
+ * @returns Overlap Coefficient in [0, 1]
185
+ */
186
+ function overlapCoefficient(graph, source, target, config) {
187
+ const { epsilon = 1e-10 } = config ?? {};
188
+ const sourceNeighbours = neighbourSet(graph, source, target);
189
+ const targetNeighbours = neighbourSet(graph, target, source);
190
+ const { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);
191
+ const denominator = Math.min(sourceNeighbours.size, targetNeighbours.size);
192
+ if (denominator === 0) return 0;
193
+ const score = intersection / denominator;
194
+ return Math.max(epsilon, score);
195
+ }
196
+ /**
197
+ * Async variant of Overlap Coefficient for use with async graph data sources.
198
+ *
199
+ * Fetches both neighbourhoods concurrently, then applies the same formula.
200
+ */
201
+ async function overlapCoefficientAsync(graph, source, target, config) {
202
+ const { epsilon = 1e-10 } = config ?? {};
203
+ const [sourceArr, targetArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
204
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
205
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
206
+ let intersection = 0;
207
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
208
+ const denominator = Math.min(srcSet.size, tgtSet.size);
209
+ if (denominator === 0) return 0;
210
+ const score = intersection / denominator;
211
+ return Math.max(epsilon, score);
212
+ }
213
+ //#endregion
214
+ //#region src/ranking/mi/hub-promoted.ts
215
+ /**
216
+ * Compute Hub Promoted index between neighbourhoods of two nodes.
217
+ *
218
+ * @param graph - Source graph
219
+ * @param source - Source node ID
220
+ * @param target - Target node ID
221
+ * @param config - Optional configuration
222
+ * @returns Hub Promoted index in [0, 1]
223
+ */
224
+ function hubPromoted(graph, source, target, config) {
225
+ const { epsilon = 1e-10 } = config ?? {};
226
+ const { intersection } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
227
+ const sourceDegree = graph.degree(source);
228
+ const targetDegree = graph.degree(target);
229
+ const denominator = Math.min(sourceDegree, targetDegree);
230
+ if (denominator === 0) return 0;
231
+ const score = intersection / denominator;
232
+ return Math.max(epsilon, score);
233
+ }
234
+ /**
235
+ * Async variant of Hub Promoted index for use with async graph data sources.
236
+ *
237
+ * Fetches both neighbourhoods and degrees concurrently, then applies the same formula.
238
+ */
239
+ async function hubPromotedAsync(graph, source, target, config) {
240
+ const { epsilon = 1e-10 } = config ?? {};
241
+ const [sourceArr, targetArr, sourceDegree, targetDegree] = await Promise.all([
242
+ collectAsyncIterable(graph.neighbours(source)),
243
+ collectAsyncIterable(graph.neighbours(target)),
244
+ graph.degree(source),
245
+ graph.degree(target)
246
+ ]);
247
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
248
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
249
+ let intersection = 0;
250
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
251
+ const denominator = Math.min(sourceDegree, targetDegree);
252
+ if (denominator === 0) return 0;
253
+ const score = intersection / denominator;
254
+ return Math.max(epsilon, score);
255
+ }
256
+ //#endregion
257
+ //#region src/ranking/mi/scale.ts
258
+ /**
259
+ * Compute SCALE MI between two nodes.
260
+ */
261
+ function scale(graph, source, target, config) {
262
+ const { epsilon = 1e-10 } = config ?? {};
263
+ const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
264
+ const n = graph.nodeCount;
265
+ const m = graph.edgeCount;
266
+ const possibleEdges = n * (n - 1);
267
+ const density = possibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;
268
+ if (density === 0) return epsilon;
269
+ const score = jaccardScore / density;
270
+ return Math.max(epsilon, score);
271
+ }
272
+ /**
273
+ * Async variant of SCALE MI for use with async graph data sources.
274
+ *
275
+ * Fetches both neighbourhoods, node count, and edge count concurrently.
276
+ */
277
+ async function scaleAsync(graph, source, target, config) {
278
+ const { epsilon = 1e-10 } = config ?? {};
279
+ const [sourceArr, targetArr, n, m] = await Promise.all([
280
+ collectAsyncIterable(graph.neighbours(source)),
281
+ collectAsyncIterable(graph.neighbours(target)),
282
+ graph.nodeCount,
283
+ graph.edgeCount
284
+ ]);
285
+ const srcSet = new Set(sourceArr.filter((node) => node !== target));
286
+ const tgtSet = new Set(targetArr.filter((node) => node !== source));
287
+ let intersection = 0;
288
+ for (const node of srcSet) if (tgtSet.has(node)) intersection++;
289
+ const union = srcSet.size + tgtSet.size - intersection;
290
+ const jaccardScore = union > 0 ? intersection / union : 0;
291
+ const possibleEdges = n * (n - 1);
292
+ const density = possibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;
293
+ if (density === 0) return epsilon;
294
+ const score = jaccardScore / density;
295
+ return Math.max(epsilon, score);
296
+ }
297
+ //#endregion
298
+ //#region src/ranking/mi/skew.ts
299
+ /**
300
+ * Compute SKEW MI between two nodes.
301
+ */
302
+ function skew(graph, source, target, config) {
303
+ const { epsilon = 1e-10 } = config ?? {};
304
+ const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
305
+ const N = graph.nodeCount;
306
+ const sourceDegree = graph.degree(source);
307
+ const targetDegree = graph.degree(target);
308
+ const sourceIdf = Math.log(N / (sourceDegree + 1));
309
+ const targetIdf = Math.log(N / (targetDegree + 1));
310
+ const score = jaccardScore * sourceIdf * targetIdf;
311
+ return Math.max(epsilon, score);
312
+ }
313
+ /**
314
+ * Async variant of SKEW MI for use with async graph data sources.
315
+ *
316
+ * Fetches both neighbourhoods, degrees, and node count concurrently.
317
+ */
318
+ async function skewAsync(graph, source, target, config) {
319
+ const { epsilon = 1e-10 } = config ?? {};
320
+ const [sourceArr, targetArr, N, sourceDegree, targetDegree] = await Promise.all([
321
+ collectAsyncIterable(graph.neighbours(source)),
322
+ collectAsyncIterable(graph.neighbours(target)),
323
+ graph.nodeCount,
324
+ graph.degree(source),
325
+ graph.degree(target)
326
+ ]);
327
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
328
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
329
+ let intersection = 0;
330
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
331
+ const union = srcSet.size + tgtSet.size - intersection;
332
+ const jaccardScore = union > 0 ? intersection / union : 0;
333
+ const sourceIdf = Math.log(N / (sourceDegree + 1));
334
+ const targetIdf = Math.log(N / (targetDegree + 1));
335
+ const score = jaccardScore * sourceIdf * targetIdf;
336
+ return Math.max(epsilon, score);
337
+ }
338
+ //#endregion
339
+ //#region src/ranking/mi/span.ts
340
+ /**
341
+ * Compute SPAN MI between two nodes.
342
+ */
343
+ function span(graph, source, target, config) {
344
+ const { epsilon = 1e-10 } = config ?? {};
345
+ const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
346
+ const sourceCc = localClusteringCoefficient(graph, source);
347
+ const targetCc = localClusteringCoefficient(graph, target);
348
+ const score = jaccardScore * (1 - Math.max(sourceCc, targetCc));
349
+ return Math.max(epsilon, score);
350
+ }
351
+ /**
352
+ * Async variant of SPAN MI for use with async graph data sources.
353
+ *
354
+ * Fetches both neighbourhoods concurrently, then computes the clustering
355
+ * coefficient for each endpoint from the collected neighbour arrays.
356
+ */
357
+ async function spanAsync(graph, source, target, config) {
358
+ const { epsilon = 1e-10 } = config ?? {};
359
+ const [sourceArr, targetArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
360
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
361
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
362
+ let intersection = 0;
363
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
364
+ const union = srcSet.size + tgtSet.size - intersection;
365
+ const jaccardScore = union > 0 ? intersection / union : 0;
366
+ const computeClusteringCoefficient = async (nodeId, neighbourArr) => {
367
+ const degree = neighbourArr.length;
368
+ if (degree < 2) return 0;
369
+ const pairs = [];
370
+ for (let i = 0; i < neighbourArr.length; i++) for (let j = i + 1; j < neighbourArr.length; j++) {
371
+ const u = neighbourArr[i];
372
+ const v = neighbourArr[j];
373
+ if (u !== void 0 && v !== void 0) pairs.push([u, v]);
374
+ }
375
+ const edgeResults = await Promise.all(pairs.flatMap(([u, v]) => [graph.getEdge(u, v), graph.getEdge(v, u)]));
376
+ let triangleCount = 0;
377
+ for (let i = 0; i < pairs.length; i++) if (edgeResults[2 * i] !== void 0 || edgeResults[2 * i + 1] !== void 0) triangleCount++;
378
+ const possibleTriangles = degree * (degree - 1) / 2;
379
+ return triangleCount / possibleTriangles;
380
+ };
381
+ const [sourceCc, targetCc] = await Promise.all([computeClusteringCoefficient(source, sourceArr), computeClusteringCoefficient(target, targetArr)]);
382
+ const score = jaccardScore * (1 - Math.max(sourceCc, targetCc));
383
+ return Math.max(epsilon, score);
384
+ }
385
+ //#endregion
386
+ //#region src/ranking/mi/etch.ts
387
+ /**
388
+ * Compute ETCH MI between two nodes.
389
+ */
390
+ function etch(graph, source, target, config) {
391
+ const { epsilon = 1e-10 } = config ?? {};
392
+ const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
393
+ const edge = graph.getEdge(source, target);
394
+ if (edge?.type === void 0) return Math.max(epsilon, jaccardScore);
395
+ const edgeTypeCount = countEdgesOfType(graph, edge.type);
396
+ if (edgeTypeCount === 0) return Math.max(epsilon, jaccardScore);
397
+ const score = jaccardScore * Math.log(graph.edgeCount / edgeTypeCount);
398
+ return Math.max(epsilon, score);
399
+ }
400
+ /**
401
+ * Async variant of ETCH MI for use with async graph data sources.
402
+ *
403
+ * Fetches both neighbourhoods and edge data concurrently, then counts
404
+ * edges of the same type by iterating the async edge stream.
405
+ */
406
+ async function etchAsync(graph, source, target, config) {
407
+ const { epsilon = 1e-10 } = config ?? {};
408
+ const [sourceArr, targetArr, edge] = await Promise.all([
409
+ collectAsyncIterable(graph.neighbours(source)),
410
+ collectAsyncIterable(graph.neighbours(target)),
411
+ graph.getEdge(source, target)
412
+ ]);
413
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
414
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
415
+ let intersection = 0;
416
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
417
+ const union = srcSet.size + tgtSet.size - intersection;
418
+ const jaccardScore = union > 0 ? intersection / union : 0;
419
+ if (edge?.type === void 0) return Math.max(epsilon, jaccardScore);
420
+ const edgeType = edge.type;
421
+ let edgeTypeCount = 0;
422
+ let totalEdges = 0;
423
+ for await (const e of graph.edges()) {
424
+ totalEdges++;
425
+ if (e.type === edgeType) edgeTypeCount++;
426
+ }
427
+ if (edgeTypeCount === 0) return Math.max(epsilon, jaccardScore);
428
+ const score = jaccardScore * Math.log(totalEdges / edgeTypeCount);
429
+ return Math.max(epsilon, score);
430
+ }
431
+ //#endregion
432
+ //#region src/ranking/mi/notch.ts
433
+ /**
434
+ * Compute NOTCH MI between two nodes.
435
+ */
436
+ function notch(graph, source, target, config) {
437
+ const { epsilon = 1e-10 } = config ?? {};
438
+ const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
439
+ const sourceNode = graph.getNode(source);
440
+ const targetNode = graph.getNode(target);
441
+ if (sourceNode?.type === void 0 || targetNode?.type === void 0) return Math.max(epsilon, jaccardScore);
442
+ const sourceTypeCount = countNodesOfType(graph, sourceNode.type);
443
+ const targetTypeCount = countNodesOfType(graph, targetNode.type);
444
+ if (sourceTypeCount === 0 || targetTypeCount === 0) return Math.max(epsilon, jaccardScore);
445
+ const sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);
446
+ const targetRarity = Math.log(graph.nodeCount / targetTypeCount);
447
+ const score = jaccardScore * sourceRarity * targetRarity;
448
+ return Math.max(epsilon, score);
449
+ }
450
+ /**
451
+ * Async variant of NOTCH MI for use with async graph data sources.
452
+ *
453
+ * Fetches both neighbourhoods and node data concurrently, then counts
454
+ * nodes of each type by iterating the async node stream.
455
+ */
456
+ async function notchAsync(graph, source, target, config) {
457
+ const { epsilon = 1e-10 } = config ?? {};
458
+ const [sourceArr, targetArr, sourceNode, targetNode] = await Promise.all([
459
+ collectAsyncIterable(graph.neighbours(source)),
460
+ collectAsyncIterable(graph.neighbours(target)),
461
+ graph.getNode(source),
462
+ graph.getNode(target)
463
+ ]);
464
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
465
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
466
+ let intersection = 0;
467
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
468
+ const union = srcSet.size + tgtSet.size - intersection;
469
+ const jaccardScore = union > 0 ? intersection / union : 0;
470
+ if (sourceNode?.type === void 0 || targetNode?.type === void 0) return Math.max(epsilon, jaccardScore);
471
+ const sourceType = sourceNode.type;
472
+ const targetType = targetNode.type;
473
+ let totalNodes = 0;
474
+ let sourceTypeCount = 0;
475
+ let targetTypeCount = 0;
476
+ for await (const nodeId of graph.nodeIds()) {
477
+ totalNodes++;
478
+ const node = await graph.getNode(nodeId);
479
+ if (node?.type === sourceType) sourceTypeCount++;
480
+ if (node?.type === targetType) targetTypeCount++;
481
+ }
482
+ if (sourceTypeCount === 0 || targetTypeCount === 0) return Math.max(epsilon, jaccardScore);
483
+ const sourceRarity = Math.log(totalNodes / sourceTypeCount);
484
+ const targetRarity = Math.log(totalNodes / targetTypeCount);
485
+ const score = jaccardScore * sourceRarity * targetRarity;
486
+ return Math.max(epsilon, score);
487
+ }
488
+ //#endregion
489
+ //#region src/ranking/mi/adaptive.ts
490
+ /**
491
+ * Compute unified adaptive MI between two connected nodes.
492
+ *
493
+ * Combines structural, degree, and overlap signals with
494
+ * configurable weighting.
495
+ *
496
+ * @param graph - Source graph
497
+ * @param source - Source node ID
498
+ * @param target - Target node ID
499
+ * @param config - Optional configuration with component weights
500
+ * @returns Adaptive MI score in [0, 1]
501
+ */
502
+ function adaptive(graph, source, target, config) {
503
+ const { epsilon = 1e-10, structuralWeight = .4, degreeWeight = .3, overlapWeight = .3 } = config ?? {};
504
+ const { jaccard: jaccardScore, sourceNeighbours, targetNeighbours } = computeJaccard(graph, source, target);
505
+ const structural = sourceNeighbours.size === 0 && targetNeighbours.size === 0 ? 0 : Math.max(epsilon, jaccardScore);
506
+ const degreeComponent = adamicAdar(graph, source, target, {
507
+ epsilon,
508
+ normalise: true
509
+ });
510
+ let overlap;
511
+ if (sourceNeighbours.size > 0 && targetNeighbours.size > 0) {
512
+ const { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);
513
+ const minDegree = Math.min(sourceNeighbours.size, targetNeighbours.size);
514
+ overlap = minDegree > 0 ? intersection / minDegree : epsilon;
515
+ } else overlap = epsilon;
516
+ const totalWeight = structuralWeight + degreeWeight + overlapWeight;
517
+ const score = (structuralWeight * structural + degreeWeight * degreeComponent + overlapWeight * overlap) / totalWeight;
518
+ return Math.max(epsilon, Math.min(1, score));
519
+ }
520
+ /**
521
+ * Async variant of Adaptive MI for use with async graph data sources.
522
+ *
523
+ * Fetches both neighbourhoods concurrently, then delegates degree-weighted
524
+ * component to the async Adamic-Adar variant.
525
+ */
526
+ async function adaptiveAsync(graph, source, target, config) {
527
+ const { epsilon = 1e-10, structuralWeight = .4, degreeWeight = .3, overlapWeight = .3 } = config ?? {};
528
+ const [sourceArr, targetArr, degreeComponent] = await Promise.all([
529
+ collectAsyncIterable(graph.neighbours(source)),
530
+ collectAsyncIterable(graph.neighbours(target)),
531
+ adamicAdarAsync(graph, source, target, {
532
+ epsilon,
533
+ normalise: true
534
+ })
535
+ ]);
536
+ const srcSet = new Set(sourceArr.filter((n) => n !== target));
537
+ const tgtSet = new Set(targetArr.filter((n) => n !== source));
538
+ let intersection = 0;
539
+ for (const n of srcSet) if (tgtSet.has(n)) intersection++;
540
+ const union = srcSet.size + tgtSet.size - intersection;
541
+ const jaccardScore = union > 0 ? intersection / union : 0;
542
+ const structural = srcSet.size === 0 && tgtSet.size === 0 ? 0 : Math.max(epsilon, jaccardScore);
543
+ let overlap;
544
+ if (srcSet.size > 0 && tgtSet.size > 0) {
545
+ const minDegree = Math.min(srcSet.size, tgtSet.size);
546
+ overlap = minDegree > 0 ? intersection / minDegree : epsilon;
547
+ } else overlap = epsilon;
548
+ const totalWeight = structuralWeight + degreeWeight + overlapWeight;
549
+ const score = (structuralWeight * structural + degreeWeight * degreeComponent + overlapWeight * overlap) / totalWeight;
550
+ return Math.max(epsilon, Math.min(1, score));
551
+ }
552
+ //#endregion
553
+ export { adamicAdar, adamicAdarAsync, adaptive, adaptiveAsync, cosine, cosineAsync, etch, etchAsync, hubPromoted, hubPromotedAsync, jaccard, jaccardAsync, notch, notchAsync, overlapCoefficient, overlapCoefficientAsync, resourceAllocation, resourceAllocationAsync, scale, scaleAsync, skew, skewAsync, sorensen, sorensenAsync, span, spanAsync };
554
+
555
+ //# sourceMappingURL=index.js.map