@c4a/graph-analytics 0.4.12-beta.14

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 (2) hide show
  1. package/index.js +811 -0
  2. package/package.json +7 -0
package/index.js ADDED
@@ -0,0 +1,811 @@
1
+ // src/leiden/network.ts
2
+ var fill = (len, val = 0) => {
3
+ const arr = new Array(len);
4
+ for (let i = 0;i < len; i++)
5
+ arr[i] = val;
6
+ return arr;
7
+ };
8
+ var pairKey = (a, b) => a < b ? a * 1048576 + b : b * 1048576 + a;
9
+ var buildNetwork = (adjacency) => {
10
+ const nodeIds = [];
11
+ const indexById = new Map;
12
+ const ensureNode = (id) => {
13
+ let idx = indexById.get(id);
14
+ if (idx !== undefined)
15
+ return idx;
16
+ idx = nodeIds.length;
17
+ nodeIds.push(id);
18
+ indexById.set(id, idx);
19
+ return idx;
20
+ };
21
+ for (const [source, edges] of adjacency.entries()) {
22
+ ensureNode(source);
23
+ for (const edge of edges)
24
+ ensureNode(edge.target);
25
+ }
26
+ const nNodes = nodeIds.length;
27
+ if (nNodes === 0) {
28
+ return {
29
+ network: {
30
+ nNodes: 0,
31
+ nEdges: 0,
32
+ nodeWeights: [],
33
+ firstNeighborIndices: [0],
34
+ neighbors: [],
35
+ edgeWeights: [],
36
+ totalEdgeWeightSelfLinks: 0
37
+ },
38
+ nodeIds
39
+ };
40
+ }
41
+ const edgeMap = new Map;
42
+ for (const [source, edges] of adjacency.entries()) {
43
+ const si = indexById.get(source);
44
+ for (const edge of edges) {
45
+ const ti = indexById.get(edge.target);
46
+ const key = pairKey(si, ti);
47
+ edgeMap.set(key, Math.max(edgeMap.get(key) ?? 0, edge.weight));
48
+ }
49
+ }
50
+ const adjLists = Array.from({ length: nNodes }, () => []);
51
+ let totalEdgeWeightSelfLinks = 0;
52
+ for (const [key, weight] of edgeMap.entries()) {
53
+ const a = Math.floor(key / 1048576);
54
+ const b = key % 1048576;
55
+ if (a === b) {
56
+ adjLists[a].push({ target: a, weight });
57
+ totalEdgeWeightSelfLinks += weight;
58
+ } else {
59
+ adjLists[a].push({ target: b, weight });
60
+ adjLists[b].push({ target: a, weight });
61
+ }
62
+ }
63
+ for (let i = 0;i < nNodes; i++) {
64
+ adjLists[i].sort((x, y) => x.target - y.target);
65
+ }
66
+ let nEdges = 0;
67
+ for (let i = 0;i < nNodes; i++)
68
+ nEdges += adjLists[i].length;
69
+ const firstNeighborIndices = fill(nNodes + 1);
70
+ const neighbors = fill(nEdges);
71
+ const edgeWeightsArr = fill(nEdges);
72
+ const nodeWeights = fill(nNodes);
73
+ let offset = 0;
74
+ for (let i = 0;i < nNodes; i++) {
75
+ firstNeighborIndices[i] = offset;
76
+ for (const e of adjLists[i]) {
77
+ neighbors[offset] = e.target;
78
+ edgeWeightsArr[offset] = e.weight;
79
+ offset++;
80
+ }
81
+ nodeWeights[i] = 1;
82
+ }
83
+ firstNeighborIndices[nNodes] = offset;
84
+ return {
85
+ network: {
86
+ nNodes,
87
+ nEdges,
88
+ nodeWeights,
89
+ firstNeighborIndices,
90
+ neighbors,
91
+ edgeWeights: edgeWeightsArr,
92
+ totalEdgeWeightSelfLinks
93
+ },
94
+ nodeIds
95
+ };
96
+ };
97
+ var getNodesPerCluster = (clusters, nClusters) => {
98
+ const result = Array.from({ length: nClusters }, () => []);
99
+ for (let i = 0;i < clusters.length; i++) {
100
+ result[clusters[i]].push(i);
101
+ }
102
+ return result;
103
+ };
104
+ var createSubnetworks = (network, clusters, nClusters) => {
105
+ const nodesPerCluster = getNodesPerCluster(clusters, nClusters);
106
+ const subnetworks = new Array(nClusters);
107
+ const subnetworkNodeMap = fill(network.nNodes, -1);
108
+ const tempNeighbors = fill(network.nEdges);
109
+ const tempEdgeWeights = fill(network.nEdges);
110
+ for (let c = 0;c < nClusters; c++) {
111
+ const nodes = nodesPerCluster[c];
112
+ const nSubNodes = nodes.length;
113
+ if (nSubNodes === 1) {
114
+ subnetworks[c] = {
115
+ nNodes: 1,
116
+ nEdges: 0,
117
+ nodeWeights: [network.nodeWeights[nodes[0]]],
118
+ firstNeighborIndices: [0, 0],
119
+ neighbors: [],
120
+ edgeWeights: [],
121
+ totalEdgeWeightSelfLinks: 0
122
+ };
123
+ continue;
124
+ }
125
+ for (let i = 0;i < nSubNodes; i++) {
126
+ subnetworkNodeMap[nodes[i]] = i;
127
+ }
128
+ const subNodeWeights = fill(nSubNodes);
129
+ const subFirstNeighbor = fill(nSubNodes + 1);
130
+ let subNEdges = 0;
131
+ for (let i = 0;i < nSubNodes; i++) {
132
+ const j = nodes[i];
133
+ subNodeWeights[i] = network.nodeWeights[j];
134
+ const start = network.firstNeighborIndices[j];
135
+ const end = network.firstNeighborIndices[j + 1];
136
+ for (let k = start;k < end; k++) {
137
+ const neighbor = network.neighbors[k];
138
+ if (clusters[neighbor] === c) {
139
+ tempNeighbors[subNEdges] = subnetworkNodeMap[neighbor];
140
+ tempEdgeWeights[subNEdges] = network.edgeWeights[k];
141
+ subNEdges++;
142
+ }
143
+ }
144
+ subFirstNeighbor[i + 1] = subNEdges;
145
+ }
146
+ subnetworks[c] = {
147
+ nNodes: nSubNodes,
148
+ nEdges: subNEdges,
149
+ nodeWeights: subNodeWeights,
150
+ firstNeighborIndices: subFirstNeighbor,
151
+ neighbors: tempNeighbors.slice(0, subNEdges),
152
+ edgeWeights: tempEdgeWeights.slice(0, subNEdges),
153
+ totalEdgeWeightSelfLinks: 0
154
+ };
155
+ for (let i = 0;i < nSubNodes; i++) {
156
+ subnetworkNodeMap[nodes[i]] = -1;
157
+ }
158
+ }
159
+ return { subnetworks, nodesPerCluster };
160
+ };
161
+ var createReducedNetwork = (network, clusters, nClusters) => {
162
+ const nodesPerCluster = getNodesPerCluster(clusters, nClusters);
163
+ let reducedNEdges = 0;
164
+ const reducedNodeWeights = fill(nClusters);
165
+ const reducedFirstNeighbor = fill(nClusters + 1);
166
+ let totalEdgeWeightSelfLinks = network.totalEdgeWeightSelfLinks;
167
+ const tempNeighbors = fill(network.nEdges);
168
+ const tempEdgeWeights = fill(network.nEdges);
169
+ const scatterWeights = fill(nClusters);
170
+ const scatterNeighbors = fill(nClusters);
171
+ for (let i = 0;i < nClusters; i++) {
172
+ const nodes = nodesPerCluster[i];
173
+ let j = 0;
174
+ for (let k = 0;k < nodes.length; k++) {
175
+ const node = nodes[k];
176
+ reducedNodeWeights[i] += network.nodeWeights[node];
177
+ const start = network.firstNeighborIndices[node];
178
+ const end = network.firstNeighborIndices[node + 1];
179
+ for (let m = start;m < end; m++) {
180
+ const neighborCluster = clusters[network.neighbors[m]];
181
+ if (neighborCluster !== i) {
182
+ if (scatterWeights[neighborCluster] === 0) {
183
+ scatterNeighbors[j] = neighborCluster;
184
+ j++;
185
+ }
186
+ scatterWeights[neighborCluster] += network.edgeWeights[m];
187
+ } else {
188
+ totalEdgeWeightSelfLinks += network.edgeWeights[m];
189
+ }
190
+ }
191
+ }
192
+ for (let k = 0;k < j; k++) {
193
+ const nc = scatterNeighbors[k];
194
+ tempNeighbors[reducedNEdges + k] = nc;
195
+ tempEdgeWeights[reducedNEdges + k] = scatterWeights[nc];
196
+ scatterWeights[nc] = 0;
197
+ }
198
+ reducedNEdges += j;
199
+ reducedFirstNeighbor[i + 1] = reducedNEdges;
200
+ }
201
+ return {
202
+ nNodes: nClusters,
203
+ nEdges: reducedNEdges,
204
+ nodeWeights: reducedNodeWeights,
205
+ firstNeighborIndices: reducedFirstNeighbor,
206
+ neighbors: tempNeighbors.slice(0, reducedNEdges),
207
+ edgeWeights: tempEdgeWeights.slice(0, reducedNEdges),
208
+ totalEdgeWeightSelfLinks
209
+ };
210
+ };
211
+ var getTotalNodeWeight = (network) => {
212
+ let sum = 0;
213
+ for (let i = 0;i < network.nNodes; i++)
214
+ sum += network.nodeWeights[i];
215
+ return sum;
216
+ };
217
+
218
+ // src/leiden/random.ts
219
+ var createRandom = (seed) => {
220
+ let state = (seed ?? 2654435769) >>> 0;
221
+ if (state === 0)
222
+ state = 1;
223
+ const nextDouble = () => {
224
+ state ^= state << 13;
225
+ state ^= state >>> 17;
226
+ state ^= state << 5;
227
+ return (state >>> 0) / 4294967295;
228
+ };
229
+ return {
230
+ nextDouble,
231
+ nextInt: (bound) => Math.floor(nextDouble() * bound)
232
+ };
233
+ };
234
+ var generateRandomPermutation = (n, random) => {
235
+ const perm = new Array(n);
236
+ for (let i = 0;i < n; i++) {
237
+ perm[i] = i;
238
+ }
239
+ for (let i = 0;i < n; i++) {
240
+ const j = random.nextInt(n);
241
+ const tmp = perm[i];
242
+ perm[i] = perm[j];
243
+ perm[j] = tmp;
244
+ }
245
+ return perm;
246
+ };
247
+
248
+ // src/leiden/fastLocalMoving.ts
249
+ var fill2 = (len, val = 0) => {
250
+ const arr = new Array(len);
251
+ for (let i = 0;i < len; i++)
252
+ arr[i] = val;
253
+ return arr;
254
+ };
255
+ var removeEmptyClusters = (clustering) => {
256
+ const map = new Map;
257
+ for (let i = 0;i < clustering.clusters.length; i++) {
258
+ const c = clustering.clusters[i];
259
+ if (!map.has(c))
260
+ map.set(c, map.size);
261
+ clustering.clusters[i] = map.get(c);
262
+ }
263
+ clustering.nClusters = map.size;
264
+ };
265
+ var fastLocalMoving = (network, clustering, resolution, random) => {
266
+ if (network.nNodes === 1)
267
+ return false;
268
+ let update = false;
269
+ const n = network.nNodes;
270
+ const clusterWeights = fill2(n);
271
+ const nNodesPerCluster = fill2(n);
272
+ for (let i2 = 0;i2 < n; i2++) {
273
+ clusterWeights[clustering.clusters[i2]] += network.nodeWeights[i2];
274
+ nNodesPerCluster[clustering.clusters[i2]]++;
275
+ }
276
+ let nUnusedClusters = 0;
277
+ const unusedClusters = fill2(n);
278
+ for (let i2 = n - 1;i2 >= 0; i2--) {
279
+ if (nNodesPerCluster[i2] === 0) {
280
+ unusedClusters[nUnusedClusters] = i2;
281
+ nUnusedClusters++;
282
+ }
283
+ }
284
+ const nodeOrder = generateRandomPermutation(n, random);
285
+ const edgeWeightPerCluster = fill2(n);
286
+ const neighboringClusters = fill2(n);
287
+ const stableNodes = fill2(n);
288
+ let nUnstableNodes = n;
289
+ let i = 0;
290
+ do {
291
+ const j = nodeOrder[i];
292
+ const currentCluster = clustering.clusters[j];
293
+ clusterWeights[currentCluster] -= network.nodeWeights[j];
294
+ nNodesPerCluster[currentCluster]--;
295
+ if (nNodesPerCluster[currentCluster] === 0) {
296
+ unusedClusters[nUnusedClusters] = currentCluster;
297
+ nUnusedClusters++;
298
+ }
299
+ neighboringClusters[0] = unusedClusters[nUnusedClusters - 1];
300
+ let nNeighboringClusters = 1;
301
+ const jStart = network.firstNeighborIndices[j];
302
+ const jEnd = network.firstNeighborIndices[j + 1];
303
+ for (let k = jStart;k < jEnd; k++) {
304
+ const l = clustering.clusters[network.neighbors[k]];
305
+ if (edgeWeightPerCluster[l] === 0) {
306
+ neighboringClusters[nNeighboringClusters] = l;
307
+ nNeighboringClusters++;
308
+ }
309
+ edgeWeightPerCluster[l] += network.edgeWeights[k];
310
+ }
311
+ let bestCluster = currentCluster;
312
+ let maxIncrement = edgeWeightPerCluster[currentCluster] - network.nodeWeights[j] * clusterWeights[currentCluster] * resolution;
313
+ for (let k = 0;k < nNeighboringClusters; k++) {
314
+ const l = neighboringClusters[k];
315
+ const increment = edgeWeightPerCluster[l] - network.nodeWeights[j] * clusterWeights[l] * resolution;
316
+ if (increment > maxIncrement) {
317
+ bestCluster = l;
318
+ maxIncrement = increment;
319
+ }
320
+ edgeWeightPerCluster[l] = 0;
321
+ }
322
+ clusterWeights[bestCluster] += network.nodeWeights[j];
323
+ nNodesPerCluster[bestCluster]++;
324
+ if (bestCluster === unusedClusters[nUnusedClusters - 1]) {
325
+ nUnusedClusters--;
326
+ }
327
+ stableNodes[j] = 1;
328
+ nUnstableNodes--;
329
+ if (bestCluster !== currentCluster) {
330
+ clustering.clusters[j] = bestCluster;
331
+ if (bestCluster >= clustering.nClusters) {
332
+ clustering.nClusters = bestCluster + 1;
333
+ }
334
+ for (let k = jStart;k < jEnd; k++) {
335
+ const neighbor = network.neighbors[k];
336
+ if (stableNodes[neighbor] === 1 && clustering.clusters[neighbor] !== bestCluster) {
337
+ stableNodes[neighbor] = 0;
338
+ nUnstableNodes++;
339
+ const queueIdx = i + nUnstableNodes < n ? i + nUnstableNodes : i + nUnstableNodes - n;
340
+ nodeOrder[queueIdx] = neighbor;
341
+ }
342
+ }
343
+ update = true;
344
+ }
345
+ i = i < n - 1 ? i + 1 : 0;
346
+ } while (nUnstableNodes > 0);
347
+ if (update) {
348
+ removeEmptyClusters(clustering);
349
+ }
350
+ return update;
351
+ };
352
+
353
+ // src/leiden/fastMath.ts
354
+ var fastExp = (exponent) => {
355
+ if (exponent < -256)
356
+ return 0;
357
+ exponent = 1 + exponent / 256;
358
+ exponent *= exponent;
359
+ exponent *= exponent;
360
+ exponent *= exponent;
361
+ exponent *= exponent;
362
+ exponent *= exponent;
363
+ exponent *= exponent;
364
+ exponent *= exponent;
365
+ exponent *= exponent;
366
+ return exponent;
367
+ };
368
+
369
+ // src/leiden/localMerging.ts
370
+ var fill3 = (len, val = 0) => {
371
+ const arr = new Array(len);
372
+ for (let i = 0;i < len; i++)
373
+ arr[i] = val;
374
+ return arr;
375
+ };
376
+ var removeEmptyClusters2 = (clustering) => {
377
+ const map = new Map;
378
+ for (let i = 0;i < clustering.clusters.length; i++) {
379
+ const c = clustering.clusters[i];
380
+ if (!map.has(c))
381
+ map.set(c, map.size);
382
+ clustering.clusters[i] = map.get(c);
383
+ }
384
+ clustering.nClusters = map.size;
385
+ };
386
+ var localMerging = (network, resolution, randomness, random) => {
387
+ const n = network.nNodes;
388
+ const clusters = fill3(n);
389
+ for (let i = 0;i < n; i++)
390
+ clusters[i] = i;
391
+ const clustering = { clusters, nClusters: n };
392
+ if (n === 1)
393
+ return clustering;
394
+ let updated = false;
395
+ const totalNodeWeight = getTotalNodeWeight(network);
396
+ const clusterWeights = fill3(n);
397
+ for (let i = 0;i < n; i++)
398
+ clusterWeights[i] = network.nodeWeights[i];
399
+ const nonSingletonClusters = fill3(n);
400
+ const externalEdgeWeightPerCluster = fill3(n);
401
+ for (let i = 0;i < n; i++) {
402
+ let sum = 0;
403
+ const start = network.firstNeighborIndices[i];
404
+ const end = network.firstNeighborIndices[i + 1];
405
+ for (let k = start;k < end; k++)
406
+ sum += network.edgeWeights[k];
407
+ externalEdgeWeightPerCluster[i] = sum;
408
+ }
409
+ const nodeOrder = generateRandomPermutation(n, random);
410
+ const edgeWeightPerCluster = fill3(n);
411
+ const neighboringClusters = fill3(n);
412
+ const cumTransformed = fill3(n);
413
+ for (let idx = 0;idx < n; idx++) {
414
+ const j = nodeOrder[idx];
415
+ if (nonSingletonClusters[j] === 1)
416
+ continue;
417
+ if (externalEdgeWeightPerCluster[j] < clusterWeights[j] * (totalNodeWeight - clusterWeights[j]) * resolution) {
418
+ continue;
419
+ }
420
+ clusterWeights[j] = 0;
421
+ externalEdgeWeightPerCluster[j] = 0;
422
+ neighboringClusters[0] = j;
423
+ let nNeighboringClusters = 1;
424
+ const jStart = network.firstNeighborIndices[j];
425
+ const jEnd = network.firstNeighborIndices[j + 1];
426
+ for (let k = jStart;k < jEnd; k++) {
427
+ const l = clustering.clusters[network.neighbors[k]];
428
+ if (edgeWeightPerCluster[l] === 0) {
429
+ neighboringClusters[nNeighboringClusters] = l;
430
+ nNeighboringClusters++;
431
+ }
432
+ edgeWeightPerCluster[l] += network.edgeWeights[k];
433
+ }
434
+ let bestCluster = j;
435
+ let maxIncrement = 0;
436
+ let totalTransformed = 0;
437
+ for (let k = 0;k < nNeighboringClusters; k++) {
438
+ const l = neighboringClusters[k];
439
+ if (externalEdgeWeightPerCluster[l] >= clusterWeights[l] * (totalNodeWeight - clusterWeights[l]) * resolution) {
440
+ const increment = edgeWeightPerCluster[l] - network.nodeWeights[j] * clusterWeights[l] * resolution;
441
+ if (increment > maxIncrement) {
442
+ bestCluster = l;
443
+ maxIncrement = increment;
444
+ }
445
+ if (increment >= 0) {
446
+ totalTransformed += fastExp(increment / randomness);
447
+ }
448
+ }
449
+ cumTransformed[k] = totalTransformed;
450
+ edgeWeightPerCluster[l] = 0;
451
+ }
452
+ let chosenCluster;
453
+ if (totalTransformed < Number.POSITIVE_INFINITY) {
454
+ const r = totalTransformed * random.nextDouble();
455
+ let lo = -1;
456
+ let hi = nNeighboringClusters + 1;
457
+ while (lo < hi - 1) {
458
+ const mid = lo + hi >>> 1;
459
+ if (cumTransformed[mid] >= r) {
460
+ hi = mid;
461
+ } else {
462
+ lo = mid;
463
+ }
464
+ }
465
+ chosenCluster = neighboringClusters[hi];
466
+ } else {
467
+ chosenCluster = bestCluster;
468
+ }
469
+ clusterWeights[chosenCluster] += network.nodeWeights[j];
470
+ for (let k = jStart;k < jEnd; k++) {
471
+ if (clustering.clusters[network.neighbors[k]] === chosenCluster) {
472
+ externalEdgeWeightPerCluster[chosenCluster] -= network.edgeWeights[k];
473
+ } else {
474
+ externalEdgeWeightPerCluster[chosenCluster] += network.edgeWeights[k];
475
+ }
476
+ }
477
+ if (chosenCluster !== j) {
478
+ clustering.clusters[j] = chosenCluster;
479
+ nonSingletonClusters[chosenCluster] = 1;
480
+ updated = true;
481
+ }
482
+ }
483
+ if (updated) {
484
+ removeEmptyClusters2(clustering);
485
+ }
486
+ return clustering;
487
+ };
488
+
489
+ // src/leiden/leidenAlgorithm.ts
490
+ var fill4 = (len, val = 0) => {
491
+ const arr = new Array(len);
492
+ for (let i = 0;i < len; i++)
493
+ arr[i] = val;
494
+ return arr;
495
+ };
496
+ var improveClusteringOneIteration = (network, clustering, config) => {
497
+ const { resolution, randomness, random } = config;
498
+ let update = fastLocalMoving(network, clustering, resolution, random);
499
+ if (clustering.nClusters >= network.nNodes)
500
+ return update;
501
+ const { subnetworks, nodesPerCluster } = createSubnetworks(network, clustering.clusters, clustering.nClusters);
502
+ const refinement = {
503
+ clusters: fill4(network.nNodes),
504
+ nClusters: 0
505
+ };
506
+ for (let i = 0;i < subnetworks.length; i++) {
507
+ const subClustering = localMerging(subnetworks[i], resolution, randomness, random);
508
+ const nodes = nodesPerCluster[i];
509
+ for (let j = 0;j < subnetworks[i].nNodes; j++) {
510
+ refinement.clusters[nodes[j]] = refinement.nClusters + subClustering.clusters[j];
511
+ }
512
+ refinement.nClusters += subClustering.nClusters;
513
+ }
514
+ let reducedNetwork;
515
+ let reducedClustering;
516
+ if (refinement.nClusters < network.nNodes) {
517
+ reducedNetwork = createReducedNetwork(network, refinement.clusters, refinement.nClusters);
518
+ const reducedClusters = fill4(refinement.nClusters);
519
+ for (let i = 0;i < network.nNodes; i++) {
520
+ reducedClusters[refinement.clusters[i]] = clustering.clusters[i];
521
+ }
522
+ reducedClustering = {
523
+ clusters: reducedClusters,
524
+ nClusters: clustering.nClusters
525
+ };
526
+ clustering.clusters = refinement.clusters;
527
+ clustering.nClusters = refinement.nClusters;
528
+ } else {
529
+ reducedNetwork = createReducedNetwork(network, clustering.clusters, clustering.nClusters);
530
+ const reducedClusters = fill4(reducedNetwork.nNodes);
531
+ for (let i = 0;i < reducedNetwork.nNodes; i++)
532
+ reducedClusters[i] = i;
533
+ reducedClustering = {
534
+ clusters: reducedClusters,
535
+ nClusters: reducedNetwork.nNodes
536
+ };
537
+ }
538
+ const update2 = improveClusteringOneIteration(reducedNetwork, reducedClustering, config);
539
+ update = update || update2;
540
+ for (let i = 0;i < network.nNodes; i++) {
541
+ clustering.clusters[i] = reducedClustering.clusters[clustering.clusters[i]];
542
+ }
543
+ clustering.nClusters = reducedClustering.nClusters;
544
+ return update;
545
+ };
546
+ var runLeiden = (network, config) => {
547
+ const n = network.nNodes;
548
+ const clusters = fill4(n);
549
+ for (let i = 0;i < n; i++)
550
+ clusters[i] = i;
551
+ const clustering = { clusters, nClusters: n };
552
+ if (n <= 1)
553
+ return clustering;
554
+ if (config.nIterations > 0) {
555
+ for (let i = 0;i < config.nIterations; i++) {
556
+ improveClusteringOneIteration(network, clustering, config);
557
+ }
558
+ } else {
559
+ while (improveClusteringOneIteration(network, clustering, config)) {}
560
+ }
561
+ return clustering;
562
+ };
563
+
564
+ // src/leiden/index.ts
565
+ var leiden = (graph, options = {}) => {
566
+ const { network, nodeIds } = buildNetwork(graph);
567
+ if (network.nNodes === 0)
568
+ return [];
569
+ const clustering = runLeiden(network, {
570
+ resolution: options.resolution ?? 1,
571
+ randomness: options.randomness ?? 0.01,
572
+ nIterations: options.nIterations ?? 0,
573
+ random: createRandom(options.randomSeed)
574
+ });
575
+ const results = [];
576
+ for (let i = 0;i < network.nNodes; i++) {
577
+ results.push({
578
+ entityId: nodeIds[i],
579
+ communityId: String(clustering.clusters[i]),
580
+ level: 0
581
+ });
582
+ }
583
+ return results;
584
+ };
585
+ // src/pagerank.ts
586
+ var buildIndex = (graph) => {
587
+ const nodeIds = [];
588
+ const indexById = new Map;
589
+ const ensure = (id) => {
590
+ const existing = indexById.get(id);
591
+ if (existing !== undefined) {
592
+ return existing;
593
+ }
594
+ const index = nodeIds.length;
595
+ nodeIds.push(id);
596
+ indexById.set(id, index);
597
+ return index;
598
+ };
599
+ for (const [source, edges] of graph.entries()) {
600
+ ensure(source);
601
+ for (const edge of edges) {
602
+ ensure(edge.target);
603
+ }
604
+ }
605
+ return { nodeIds, indexById };
606
+ };
607
+ var normalizeEdges = (graph, indexById) => {
608
+ const edges = Array.from({ length: indexById.size }, () => []);
609
+ for (const [source, outgoing] of graph.entries()) {
610
+ const sourceIndex = indexById.get(source);
611
+ if (sourceIndex === undefined) {
612
+ continue;
613
+ }
614
+ for (const edge of outgoing) {
615
+ const targetIndex = indexById.get(edge.target);
616
+ if (targetIndex === undefined) {
617
+ continue;
618
+ }
619
+ edges[sourceIndex].push({ target: targetIndex, weight: edge.weight });
620
+ }
621
+ }
622
+ return edges;
623
+ };
624
+ var pagerank = (graph, options = {}) => {
625
+ const damping = options.damping ?? 0.85;
626
+ const maxIterations = options.maxIterations ?? 100;
627
+ const epsilon = options.epsilon ?? 0.000001;
628
+ const { nodeIds, indexById } = buildIndex(graph);
629
+ const count = nodeIds.length;
630
+ if (count === 0) {
631
+ return [];
632
+ }
633
+ const edges = normalizeEdges(graph, indexById);
634
+ const outWeight = new Float64Array(count);
635
+ for (let i = 0;i < count; i += 1) {
636
+ let sum = 0;
637
+ for (const edge of edges[i]) {
638
+ sum += edge.weight;
639
+ }
640
+ outWeight[i] = sum;
641
+ }
642
+ let ranks = new Float64Array(count);
643
+ let next = new Float64Array(count);
644
+ const initRank = 1 / count;
645
+ for (let i = 0;i < count; i += 1) {
646
+ ranks[i] = initRank;
647
+ }
648
+ for (let iteration = 0;iteration < maxIterations; iteration += 1) {
649
+ let danglingMass = 0;
650
+ for (let i = 0;i < count; i += 1) {
651
+ if (outWeight[i] === 0) {
652
+ danglingMass += ranks[i];
653
+ }
654
+ }
655
+ const base = (1 - damping) / count;
656
+ const danglingShare = damping * danglingMass / count;
657
+ for (let i = 0;i < count; i += 1) {
658
+ next[i] = base + danglingShare;
659
+ }
660
+ for (let i = 0;i < count; i += 1) {
661
+ const weight = outWeight[i];
662
+ if (weight === 0) {
663
+ continue;
664
+ }
665
+ for (const edge of edges[i]) {
666
+ next[edge.target] = (next[edge.target] ?? 0) + damping * (ranks[i] * edge.weight) / weight;
667
+ }
668
+ }
669
+ let delta = 0;
670
+ for (let i = 0;i < count; i += 1) {
671
+ delta += Math.abs(next[i] - ranks[i]);
672
+ }
673
+ const temp = ranks;
674
+ ranks = next;
675
+ next = temp;
676
+ if (delta < epsilon) {
677
+ break;
678
+ }
679
+ }
680
+ const results = [];
681
+ for (let i = 0;i < count; i += 1) {
682
+ results.push({ entityId: nodeIds[i], rank: ranks[i] });
683
+ }
684
+ return results;
685
+ };
686
+ // src/ppr.ts
687
+ var buildIndex2 = (graph) => {
688
+ const nodeIds = [];
689
+ const indexById = new Map;
690
+ const ensure = (id) => {
691
+ const existing = indexById.get(id);
692
+ if (existing !== undefined) {
693
+ return existing;
694
+ }
695
+ const index = nodeIds.length;
696
+ nodeIds.push(id);
697
+ indexById.set(id, index);
698
+ return index;
699
+ };
700
+ for (const [source, edges] of graph.entries()) {
701
+ ensure(source);
702
+ for (const edge of edges) {
703
+ ensure(edge.target);
704
+ }
705
+ }
706
+ return { nodeIds, indexById };
707
+ };
708
+ var normalizeEdges2 = (graph, indexById) => {
709
+ const edges = Array.from({ length: indexById.size }, () => []);
710
+ for (const [source, outgoing] of graph.entries()) {
711
+ const sourceIndex = indexById.get(source);
712
+ if (sourceIndex === undefined) {
713
+ continue;
714
+ }
715
+ for (const edge of outgoing) {
716
+ const targetIndex = indexById.get(edge.target);
717
+ if (targetIndex === undefined) {
718
+ continue;
719
+ }
720
+ edges[sourceIndex].push({ target: targetIndex, weight: edge.weight });
721
+ }
722
+ }
723
+ return edges;
724
+ };
725
+ var personalizedPageRank = (graph, seedIds, options = {}) => {
726
+ const alpha = options.alpha ?? 0.85;
727
+ const maxIterations = options.maxIterations ?? 100;
728
+ const epsilon = options.epsilon ?? 0.000001;
729
+ const { nodeIds, indexById } = buildIndex2(graph);
730
+ const count = nodeIds.length;
731
+ if (count === 0) {
732
+ return [];
733
+ }
734
+ const edges = normalizeEdges2(graph, indexById);
735
+ const outWeight = new Float64Array(count);
736
+ for (let i = 0;i < count; i += 1) {
737
+ let sum = 0;
738
+ for (const edge of edges[i]) {
739
+ sum += edge.weight;
740
+ }
741
+ outWeight[i] = sum;
742
+ }
743
+ const teleport = new Float64Array(count);
744
+ const seedSet = new Set(seedIds);
745
+ let seedCount = 0;
746
+ for (let i = 0;i < count; i += 1) {
747
+ if (seedSet.has(nodeIds[i])) {
748
+ seedCount += 1;
749
+ }
750
+ }
751
+ if (seedCount === 0) {
752
+ seedCount = count;
753
+ for (let i = 0;i < count; i += 1) {
754
+ teleport[i] = 1 / count;
755
+ }
756
+ } else {
757
+ for (let i = 0;i < count; i += 1) {
758
+ teleport[i] = seedSet.has(nodeIds[i]) ? 1 / seedCount : 0;
759
+ }
760
+ }
761
+ let ranks = new Float64Array(count);
762
+ let next = new Float64Array(count);
763
+ for (let i = 0;i < count; i += 1) {
764
+ ranks[i] = teleport[i];
765
+ }
766
+ for (let iteration = 0;iteration < maxIterations; iteration += 1) {
767
+ for (let i = 0;i < count; i += 1) {
768
+ next[i] = (1 - alpha) * teleport[i];
769
+ }
770
+ let danglingMass = 0;
771
+ for (let i = 0;i < count; i += 1) {
772
+ if (outWeight[i] === 0) {
773
+ danglingMass += ranks[i];
774
+ }
775
+ }
776
+ for (let i = 0;i < count; i += 1) {
777
+ if (outWeight[i] === 0) {
778
+ continue;
779
+ }
780
+ for (const edge of edges[i]) {
781
+ next[edge.target] = (next[edge.target] ?? 0) + alpha * (ranks[i] * edge.weight) / outWeight[i];
782
+ }
783
+ }
784
+ if (danglingMass > 0) {
785
+ const danglingShare = alpha * danglingMass;
786
+ for (let i = 0;i < count; i += 1) {
787
+ next[i] = (next[i] ?? 0) + danglingShare * teleport[i];
788
+ }
789
+ }
790
+ let delta = 0;
791
+ for (let i = 0;i < count; i += 1) {
792
+ delta += Math.abs(next[i] - ranks[i]);
793
+ }
794
+ const temp = ranks;
795
+ ranks = next;
796
+ next = temp;
797
+ if (delta < epsilon) {
798
+ break;
799
+ }
800
+ }
801
+ const results = [];
802
+ for (let i = 0;i < count; i += 1) {
803
+ results.push({ entityId: nodeIds[i], rank: ranks[i] });
804
+ }
805
+ return results;
806
+ };
807
+ export {
808
+ personalizedPageRank,
809
+ pagerank,
810
+ leiden
811
+ };
package/package.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@c4a/graph-analytics",
3
+ "version": "0.4.12-beta.14",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "types": "./index.d.ts"
7
+ }