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.
- package/dist/adjacency-map-BtKzcuJq.js +229 -0
- package/dist/adjacency-map-BtKzcuJq.js.map +1 -0
- package/dist/adjacency-map-JqBnMNkF.cjs +234 -0
- package/dist/adjacency-map-JqBnMNkF.cjs.map +1 -0
- package/dist/async/index.cjs +15 -242
- package/dist/async/index.js +2 -229
- package/dist/expansion/index.cjs +43 -0
- package/dist/expansion/index.js +2 -0
- package/dist/expansion-ClDhlMK8.js +1704 -0
- package/dist/expansion-ClDhlMK8.js.map +1 -0
- package/dist/expansion-DaTroIyv.cjs +1949 -0
- package/dist/expansion-DaTroIyv.cjs.map +1 -0
- package/dist/extraction/index.cjs +630 -0
- package/dist/extraction/index.cjs.map +1 -0
- package/dist/extraction/index.js +621 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/gpu/csr.d.ts +29 -30
- package/dist/gpu/csr.d.ts.map +1 -1
- package/dist/gpu/dispatch.d.ts +31 -0
- package/dist/gpu/dispatch.d.ts.map +1 -0
- package/dist/gpu/dispatch.unit.test.d.ts +5 -0
- package/dist/gpu/dispatch.unit.test.d.ts.map +1 -0
- package/dist/gpu/index.cjs +15 -410
- package/dist/gpu/index.d.ts +3 -1
- package/dist/gpu/index.d.ts.map +1 -1
- package/dist/gpu/index.js +2 -400
- package/dist/gpu/kernels/bfs/kernel.d.ts +59 -0
- package/dist/gpu/kernels/bfs/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/bfs/logic.d.ts +47 -0
- package/dist/gpu/kernels/bfs/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/bfs/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/bfs/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/degree-histogram/kernel.d.ts +32 -0
- package/dist/gpu/kernels/degree-histogram/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/degree-histogram/logic.d.ts +45 -0
- package/dist/gpu/kernels/degree-histogram/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/jaccard/kernel.d.ts +40 -0
- package/dist/gpu/kernels/jaccard/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/jaccard/logic.d.ts +43 -0
- package/dist/gpu/kernels/jaccard/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/pagerank/kernel.d.ts +44 -0
- package/dist/gpu/kernels/pagerank/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/pagerank/logic.d.ts +50 -0
- package/dist/gpu/kernels/pagerank/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/spmv/kernel.d.ts +43 -0
- package/dist/gpu/kernels/spmv/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/spmv/logic.d.ts +31 -0
- package/dist/gpu/kernels/spmv/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/spmv/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/spmv/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/operations.d.ts +76 -0
- package/dist/gpu/operations.d.ts.map +1 -0
- package/dist/gpu/operations.unit.test.d.ts +5 -0
- package/dist/gpu/operations.unit.test.d.ts.map +1 -0
- package/dist/gpu/root.d.ts +53 -0
- package/dist/gpu/root.d.ts.map +1 -0
- package/dist/gpu/root.unit.test.d.ts +2 -0
- package/dist/gpu/root.unit.test.d.ts.map +1 -0
- package/dist/gpu/types.d.ts +3 -8
- package/dist/gpu/types.d.ts.map +1 -1
- package/dist/gpu-CHiCN0wa.js +16945 -0
- package/dist/gpu-CHiCN0wa.js.map +1 -0
- package/dist/gpu-Y6owRVMi.cjs +17028 -0
- package/dist/gpu-Y6owRVMi.cjs.map +1 -0
- package/dist/graph/index.cjs +2 -229
- package/dist/graph/index.js +1 -228
- package/dist/index/index.cjs +141 -4040
- package/dist/index/index.js +15 -3917
- package/dist/jaccard-3rCdilwm.js +39 -0
- package/dist/jaccard-3rCdilwm.js.map +1 -0
- package/dist/jaccard-Bys9_dGW.cjs +50 -0
- package/dist/jaccard-Bys9_dGW.cjs.map +1 -0
- package/dist/{kmeans-BIgSyGKu.cjs → kmeans-B8x9D1kt.cjs} +1 -1
- package/dist/{kmeans-BIgSyGKu.cjs.map → kmeans-B8x9D1kt.cjs.map} +1 -1
- package/dist/{kmeans-87ExSUNZ.js → kmeans-DKkL9rAN.js} +1 -1
- package/dist/{kmeans-87ExSUNZ.js.map → kmeans-DKkL9rAN.js.map} +1 -1
- package/dist/ops-djAsQQSh.cjs +277 -0
- package/dist/ops-djAsQQSh.cjs.map +1 -0
- package/dist/ops-upIi6JIi.js +212 -0
- package/dist/ops-upIi6JIi.js.map +1 -0
- package/dist/priority-queue-BIiD1L0k.cjs +148 -0
- package/dist/priority-queue-BIiD1L0k.cjs.map +1 -0
- package/dist/priority-queue-CFDd5cBg.js +143 -0
- package/dist/priority-queue-CFDd5cBg.js.map +1 -0
- package/dist/ranking/index.cjs +43 -0
- package/dist/ranking/index.js +4 -0
- package/dist/ranking/mi/index.cjs +581 -0
- package/dist/ranking/mi/index.cjs.map +1 -0
- package/dist/ranking/mi/index.js +555 -0
- package/dist/ranking/mi/index.js.map +1 -0
- package/dist/ranking-3ez5m67U.js +1016 -0
- package/dist/ranking-3ez5m67U.js.map +1 -0
- package/dist/ranking-DVvajgUZ.cjs +1093 -0
- package/dist/ranking-DVvajgUZ.cjs.map +1 -0
- package/dist/seeds/index.cjs +1 -1
- package/dist/seeds/index.js +1 -1
- package/dist/structures/index.cjs +2 -143
- package/dist/structures/index.js +1 -142
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils-BodeE2Mo.js +22 -0
- package/dist/utils-BodeE2Mo.js.map +1 -0
- package/dist/utils-CDtCcsyF.cjs +33 -0
- package/dist/utils-CDtCcsyF.cjs.map +1 -0
- package/package.json +3 -1
- package/dist/async/index.cjs.map +0 -1
- package/dist/async/index.js.map +0 -1
- package/dist/gpu/context.d.ts +0 -118
- package/dist/gpu/context.d.ts.map +0 -1
- package/dist/gpu/context.unit.test.d.ts +0 -2
- package/dist/gpu/context.unit.test.d.ts.map +0 -1
- package/dist/gpu/index.cjs.map +0 -1
- package/dist/gpu/index.js.map +0 -1
- package/dist/graph/index.cjs.map +0 -1
- package/dist/graph/index.js.map +0 -1
- package/dist/index/index.cjs.map +0 -1
- package/dist/index/index.js.map +0 -1
- package/dist/structures/index.cjs.map +0 -1
- 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
|