graphwise 1.5.0 → 1.5.2
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/__test__/fixtures/graphs/index.d.ts +1 -0
- package/dist/__test__/fixtures/graphs/index.d.ts.map +1 -1
- package/dist/__test__/fixtures/graphs/linear-chain.d.ts +18 -0
- package/dist/__test__/fixtures/graphs/linear-chain.d.ts.map +1 -0
- package/dist/__test__/fixtures/helpers.d.ts +20 -1
- package/dist/__test__/fixtures/helpers.d.ts.map +1 -1
- package/dist/expansion/comparison.integration.test.d.ts +14 -0
- package/dist/expansion/comparison.integration.test.d.ts.map +1 -0
- package/dist/expansion/edge.d.ts.map +1 -1
- package/dist/expansion/flux.d.ts.map +1 -1
- package/dist/expansion/fuse.d.ts.map +1 -1
- package/dist/expansion/lace.d.ts.map +1 -1
- package/dist/expansion/maze.d.ts.map +1 -1
- package/dist/expansion/priority-helpers.d.ts +43 -0
- package/dist/expansion/priority-helpers.d.ts.map +1 -0
- package/dist/expansion/sage.d.ts.map +1 -1
- package/dist/expansion/sift.d.ts.map +1 -1
- package/dist/expansion/warp.d.ts.map +1 -1
- package/dist/index/index.cjs +186 -307
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +187 -309
- package/dist/index/index.js.map +1 -1
- package/dist/ranking/baselines/betweenness.d.ts.map +1 -1
- package/dist/ranking/baselines/communicability.d.ts.map +1 -1
- package/dist/ranking/baselines/degree-sum.d.ts.map +1 -1
- package/dist/ranking/baselines/hitting-time.d.ts.map +1 -1
- package/dist/ranking/baselines/jaccard-arithmetic.d.ts.map +1 -1
- package/dist/ranking/baselines/katz.d.ts.map +1 -1
- package/dist/ranking/baselines/pagerank.d.ts.map +1 -1
- package/dist/ranking/baselines/random-ranking.d.ts.map +1 -1
- package/dist/ranking/baselines/resistance-distance.d.ts.map +1 -1
- package/dist/ranking/baselines/shortest.d.ts.map +1 -1
- package/dist/ranking/baselines/utils.d.ts +20 -0
- package/dist/ranking/baselines/utils.d.ts.map +1 -0
- package/dist/ranking/baselines/widest-path.d.ts.map +1 -1
- package/dist/ranking/mi/adaptive.d.ts.map +1 -1
- package/dist/ranking/mi/comparison.integration.test.d.ts +18 -0
- package/dist/ranking/mi/comparison.integration.test.d.ts.map +1 -0
- package/dist/ranking/mi/etch.d.ts.map +1 -1
- package/dist/ranking/mi/jaccard.d.ts.map +1 -1
- package/dist/ranking/mi/notch.d.ts.map +1 -1
- package/dist/ranking/mi/scale.d.ts.map +1 -1
- package/dist/ranking/mi/skew.d.ts.map +1 -1
- package/dist/ranking/mi/span.d.ts.map +1 -1
- package/dist/utils/index.cjs +22 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +22 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/neighbours.d.ts +23 -0
- package/dist/utils/neighbours.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { AdjacencyMapGraph } from "../graph/index.js";
|
|
|
2
2
|
import { bfs, bfsWithPath, dfs, dfsWithPath } from "../traversal/index.js";
|
|
3
3
|
import { PriorityQueue } from "../structures/index.js";
|
|
4
4
|
import { n as miniBatchKMeans, r as normaliseFeatures, t as _computeMean } from "../kmeans-87ExSUNZ.js";
|
|
5
|
-
import { approximateClusteringCoefficient, batchClusteringCoefficients, countEdgesOfType, countNodesOfType, entropyFromCounts, localClusteringCoefficient, localTypeEntropy, neighbourIntersection, neighbourOverlap, neighbourSet, normalisedEntropy, shannonEntropy } from "../utils/index.js";
|
|
5
|
+
import { approximateClusteringCoefficient, batchClusteringCoefficients, computeJaccard, countEdgesOfType, countNodesOfType, entropyFromCounts, localClusteringCoefficient, localTypeEntropy, neighbourIntersection, neighbourOverlap, neighbourSet, normalisedEntropy, shannonEntropy } from "../utils/index.js";
|
|
6
6
|
import { grasp, stratified } from "../seeds/index.js";
|
|
7
7
|
import { GPUContext, GPUNotAvailableError, assertWebGPUAvailable, createGPUContext, createResultBuffer, csrToGPUBuffers, detectWebGPU, getGPUContext, graphToCSR, isWebGPUAvailable, readBufferToCPU } from "../gpu/index.js";
|
|
8
8
|
//#region src/expansion/base.ts
|
|
@@ -254,46 +254,12 @@ function domeHighDegree(graph, seeds, config) {
|
|
|
254
254
|
});
|
|
255
255
|
}
|
|
256
256
|
//#endregion
|
|
257
|
-
//#region src/expansion/edge.ts
|
|
258
|
-
var EPSILON$1 = 1e-10;
|
|
259
|
-
/**
|
|
260
|
-
* Priority function using local type entropy.
|
|
261
|
-
* Lower values = higher priority (expanded first).
|
|
262
|
-
*/
|
|
263
|
-
function edgePriority(nodeId, context) {
|
|
264
|
-
const graph = context.graph;
|
|
265
|
-
const neighbours = graph.neighbours(nodeId);
|
|
266
|
-
const neighbourTypes = [];
|
|
267
|
-
for (const neighbour of neighbours) {
|
|
268
|
-
const node = graph.getNode(neighbour);
|
|
269
|
-
neighbourTypes.push(node?.type ?? "default");
|
|
270
|
-
}
|
|
271
|
-
return 1 / (localTypeEntropy(neighbourTypes) + EPSILON$1) * Math.log(context.degree + 1);
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Run EDGE expansion (Entropy-Driven Graph Expansion).
|
|
275
|
-
*
|
|
276
|
-
* Discovers paths by prioritising nodes with diverse neighbour types,
|
|
277
|
-
* deferring nodes with homogeneous neighbourhoods.
|
|
278
|
-
*
|
|
279
|
-
* @param graph - Source graph
|
|
280
|
-
* @param seeds - Seed nodes for expansion
|
|
281
|
-
* @param config - Expansion configuration
|
|
282
|
-
* @returns Expansion result with discovered paths
|
|
283
|
-
*/
|
|
284
|
-
function edge(graph, seeds, config) {
|
|
285
|
-
return base(graph, seeds, {
|
|
286
|
-
...config,
|
|
287
|
-
priority: edgePriority
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
//#endregion
|
|
291
257
|
//#region src/expansion/hae.ts
|
|
292
258
|
var EPSILON = 1e-10;
|
|
293
259
|
/**
|
|
294
260
|
* Default type mapper - uses node.type property.
|
|
295
261
|
*/
|
|
296
|
-
function defaultTypeMapper(node) {
|
|
262
|
+
function defaultTypeMapper$1(node) {
|
|
297
263
|
return node.type ?? "default";
|
|
298
264
|
}
|
|
299
265
|
/**
|
|
@@ -323,13 +289,34 @@ function createHAEPriority(typeMapper) {
|
|
|
323
289
|
* @returns Expansion result with discovered paths
|
|
324
290
|
*/
|
|
325
291
|
function hae(graph, seeds, config) {
|
|
326
|
-
const typeMapper = config?.typeMapper ?? defaultTypeMapper;
|
|
292
|
+
const typeMapper = config?.typeMapper ?? defaultTypeMapper$1;
|
|
327
293
|
return base(graph, seeds, {
|
|
328
294
|
...config,
|
|
329
295
|
priority: createHAEPriority(typeMapper)
|
|
330
296
|
});
|
|
331
297
|
}
|
|
332
298
|
//#endregion
|
|
299
|
+
//#region src/expansion/edge.ts
|
|
300
|
+
/** Default type mapper: reads `node.type`, falling back to "default". */
|
|
301
|
+
var defaultTypeMapper = (n) => typeof n.type === "string" ? n.type : "default";
|
|
302
|
+
/**
|
|
303
|
+
* Run EDGE expansion (Entropy-Driven Graph Expansion).
|
|
304
|
+
*
|
|
305
|
+
* Discovers paths by prioritising nodes with diverse neighbour types,
|
|
306
|
+
* deferring nodes with homogeneous neighbourhoods.
|
|
307
|
+
*
|
|
308
|
+
* @param graph - Source graph
|
|
309
|
+
* @param seeds - Seed nodes for expansion
|
|
310
|
+
* @param config - Expansion configuration
|
|
311
|
+
* @returns Expansion result with discovered paths
|
|
312
|
+
*/
|
|
313
|
+
function edge(graph, seeds, config) {
|
|
314
|
+
return hae(graph, seeds, {
|
|
315
|
+
...config,
|
|
316
|
+
typeMapper: defaultTypeMapper
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
333
320
|
//#region src/expansion/pipe.ts
|
|
334
321
|
/**
|
|
335
322
|
* Priority function using path potential.
|
|
@@ -365,6 +352,69 @@ function pipe(graph, seeds, config) {
|
|
|
365
352
|
});
|
|
366
353
|
}
|
|
367
354
|
//#endregion
|
|
355
|
+
//#region src/expansion/priority-helpers.ts
|
|
356
|
+
/**
|
|
357
|
+
* Compute the average mutual information between a node and all visited
|
|
358
|
+
* nodes in the same frontier.
|
|
359
|
+
*
|
|
360
|
+
* Returns a value in [0, 1] — higher means the node is more similar
|
|
361
|
+
* (on average) to already-visited same-frontier nodes.
|
|
362
|
+
*
|
|
363
|
+
* @param graph - Source graph
|
|
364
|
+
* @param nodeId - Node being prioritised
|
|
365
|
+
* @param context - Current priority context
|
|
366
|
+
* @param mi - MI function to use for pairwise scoring
|
|
367
|
+
* @returns Average MI score, or 0 if no same-frontier visited nodes exist
|
|
368
|
+
*/
|
|
369
|
+
function avgFrontierMI(graph, nodeId, context, mi) {
|
|
370
|
+
const { frontierIndex, visitedByFrontier } = context;
|
|
371
|
+
let total = 0;
|
|
372
|
+
let count = 0;
|
|
373
|
+
for (const [visitedId, idx] of visitedByFrontier) if (idx === frontierIndex && visitedId !== nodeId) {
|
|
374
|
+
total += mi(graph, visitedId, nodeId);
|
|
375
|
+
count++;
|
|
376
|
+
}
|
|
377
|
+
return count > 0 ? total / count : 0;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Count the number of a node's neighbours that have been visited by
|
|
381
|
+
* frontiers other than the node's own frontier.
|
|
382
|
+
*
|
|
383
|
+
* A higher count indicates this node is likely to bridge two frontiers,
|
|
384
|
+
* making it a strong candidate for path completion.
|
|
385
|
+
*
|
|
386
|
+
* @param graph - Source graph
|
|
387
|
+
* @param nodeId - Node being evaluated
|
|
388
|
+
* @param context - Current priority context
|
|
389
|
+
* @returns Number of neighbours visited by other frontiers
|
|
390
|
+
*/
|
|
391
|
+
function countCrossFrontierNeighbours(graph, nodeId, context) {
|
|
392
|
+
const { frontierIndex, visitedByFrontier } = context;
|
|
393
|
+
const nodeNeighbours = new Set(graph.neighbours(nodeId));
|
|
394
|
+
let count = 0;
|
|
395
|
+
for (const [visitedId, idx] of visitedByFrontier) if (idx !== frontierIndex && nodeNeighbours.has(visitedId)) count++;
|
|
396
|
+
return count;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Incrementally update salience counts for paths discovered since the
|
|
400
|
+
* last update.
|
|
401
|
+
*
|
|
402
|
+
* Iterates only over paths from `fromIndex` onwards, avoiding redundant
|
|
403
|
+
* re-processing of already-counted paths.
|
|
404
|
+
*
|
|
405
|
+
* @param salienceCounts - Mutable map of node ID to salience count (mutated in place)
|
|
406
|
+
* @param paths - Full list of discovered paths
|
|
407
|
+
* @param fromIndex - Index to start counting from (exclusive of earlier paths)
|
|
408
|
+
* @returns The new `fromIndex` value (i.e. `paths.length` after update)
|
|
409
|
+
*/
|
|
410
|
+
function updateSalienceCounts(salienceCounts, paths, fromIndex) {
|
|
411
|
+
for (let i = fromIndex; i < paths.length; i++) {
|
|
412
|
+
const path = paths[i];
|
|
413
|
+
if (path !== void 0) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
414
|
+
}
|
|
415
|
+
return paths.length;
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
368
418
|
//#region src/expansion/sage.ts
|
|
369
419
|
/**
|
|
370
420
|
* Run SAGE expansion algorithm.
|
|
@@ -388,13 +438,7 @@ function sage(graph, seeds, config) {
|
|
|
388
438
|
function sagePriority(nodeId, context) {
|
|
389
439
|
const pathCount = context.discoveredPaths.length;
|
|
390
440
|
if (pathCount > 0 && !inPhase2) inPhase2 = true;
|
|
391
|
-
if (pathCount > lastPathCount)
|
|
392
|
-
for (let i = lastPathCount; i < pathCount; i++) {
|
|
393
|
-
const path = context.discoveredPaths[i];
|
|
394
|
-
if (path !== void 0) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
395
|
-
}
|
|
396
|
-
lastPathCount = pathCount;
|
|
397
|
-
}
|
|
441
|
+
if (pathCount > lastPathCount) lastPathCount = updateSalienceCounts(salienceCounts, context.discoveredPaths, lastPathCount);
|
|
398
442
|
if (!inPhase2) return Math.log(context.degree + 1);
|
|
399
443
|
return -((salienceCounts.get(nodeId) ?? 0) * 1e3 - context.degree);
|
|
400
444
|
}
|
|
@@ -416,10 +460,9 @@ function sage(graph, seeds, config) {
|
|
|
416
460
|
*/
|
|
417
461
|
function jaccard(graph, source, target, config) {
|
|
418
462
|
const { epsilon = 1e-10 } = config ?? {};
|
|
419
|
-
const {
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
return Math.max(epsilon, score);
|
|
463
|
+
const { jaccard: jaccardScore, sourceNeighbours, targetNeighbours } = computeJaccard(graph, source, target);
|
|
464
|
+
if (sourceNeighbours.size === 0 && targetNeighbours.size === 0) return 0;
|
|
465
|
+
return Math.max(epsilon, jaccardScore);
|
|
423
466
|
}
|
|
424
467
|
//#endregion
|
|
425
468
|
//#region src/expansion/reach.ts
|
|
@@ -505,15 +548,9 @@ function maze(graph, seeds, config) {
|
|
|
505
548
|
const pathCount = context.discoveredPaths.length;
|
|
506
549
|
if (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {
|
|
507
550
|
inPhase2 = true;
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
if (inPhase2 && pathCount > lastPathCount) {
|
|
511
|
-
for (let i = lastPathCount; i < pathCount; i++) {
|
|
512
|
-
const path = context.discoveredPaths[i];
|
|
513
|
-
if (path !== void 0) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
514
|
-
}
|
|
515
|
-
lastPathCount = pathCount;
|
|
551
|
+
updateSalienceCounts(salienceCounts, context.discoveredPaths, 0);
|
|
516
552
|
}
|
|
553
|
+
if (inPhase2 && pathCount > lastPathCount) lastPathCount = updateSalienceCounts(salienceCounts, context.discoveredPaths, lastPathCount);
|
|
517
554
|
const nodeNeighbours = graph.neighbours(nodeId);
|
|
518
555
|
let pathPotential = 0;
|
|
519
556
|
for (const neighbour of nodeNeighbours) {
|
|
@@ -565,22 +602,11 @@ function tide(graph, seeds, config) {
|
|
|
565
602
|
/**
|
|
566
603
|
* LACE priority function.
|
|
567
604
|
*
|
|
568
|
-
* Priority = 1 -
|
|
569
|
-
* Higher MI = lower priority value = explored first
|
|
605
|
+
* Priority = 1 - avgMI(node, same-frontier visited nodes)
|
|
606
|
+
* Higher average MI = lower priority value = explored first
|
|
570
607
|
*/
|
|
571
608
|
function lacePriority(nodeId, context, mi) {
|
|
572
|
-
|
|
573
|
-
const frontierIndex = context.frontierIndex;
|
|
574
|
-
let maxMi = 0;
|
|
575
|
-
let totalMi = 0;
|
|
576
|
-
let count = 0;
|
|
577
|
-
for (const [visitedId, idx] of context.visitedByFrontier) if (idx === frontierIndex && visitedId !== nodeId) {
|
|
578
|
-
const edgeMi = mi(graph, visitedId, nodeId);
|
|
579
|
-
totalMi += edgeMi;
|
|
580
|
-
count++;
|
|
581
|
-
if (edgeMi > maxMi) maxMi = edgeMi;
|
|
582
|
-
}
|
|
583
|
-
return 1 - (count > 0 ? totalMi / count : 0);
|
|
609
|
+
return 1 - avgFrontierMI(context.graph, nodeId, context, mi);
|
|
584
610
|
}
|
|
585
611
|
/**
|
|
586
612
|
* Run LACE expansion algorithm.
|
|
@@ -604,18 +630,15 @@ function lace(graph, seeds, config) {
|
|
|
604
630
|
//#endregion
|
|
605
631
|
//#region src/expansion/warp.ts
|
|
606
632
|
/**
|
|
607
|
-
*
|
|
633
|
+
* WARP priority function.
|
|
608
634
|
*
|
|
609
635
|
* Priority = 1 / (1 + bridge_score)
|
|
610
|
-
* Bridge score =
|
|
611
|
-
*
|
|
636
|
+
* Bridge score = cross-frontier neighbour count plus bonus for nodes
|
|
637
|
+
* already on discovered paths.
|
|
638
|
+
* Higher bridge score = more likely to complete paths = explored first.
|
|
612
639
|
*/
|
|
613
640
|
function warpPriority(nodeId, context) {
|
|
614
|
-
|
|
615
|
-
const currentFrontier = context.frontierIndex;
|
|
616
|
-
const nodeNeighbours = new Set(graph.neighbours(nodeId));
|
|
617
|
-
let bridgeScore = 0;
|
|
618
|
-
for (const [visitedId, frontierIdx] of context.visitedByFrontier) if (frontierIdx !== currentFrontier && nodeNeighbours.has(visitedId)) bridgeScore++;
|
|
641
|
+
let bridgeScore = countCrossFrontierNeighbours(context.graph, nodeId, context);
|
|
619
642
|
for (const path of context.discoveredPaths) if (path.nodes.includes(nodeId)) bridgeScore += 2;
|
|
620
643
|
return 1 / (1 + bridgeScore);
|
|
621
644
|
}
|
|
@@ -639,24 +662,15 @@ function warp(graph, seeds, config) {
|
|
|
639
662
|
//#endregion
|
|
640
663
|
//#region src/expansion/fuse.ts
|
|
641
664
|
/**
|
|
642
|
-
*
|
|
665
|
+
* FUSE priority function.
|
|
643
666
|
*
|
|
644
|
-
* Combines degree with salience:
|
|
645
|
-
* Priority = (1 - w) * degree + w * (1 -
|
|
646
|
-
* Lower values = higher priority
|
|
667
|
+
* Combines degree with average frontier MI as a salience proxy:
|
|
668
|
+
* Priority = (1 - w) * degree + w * (1 - avgMI)
|
|
669
|
+
* Lower values = higher priority; high salience lowers priority
|
|
647
670
|
*/
|
|
648
671
|
function fusePriority(nodeId, context, mi, salienceWeight) {
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
const frontierIndex = context.frontierIndex;
|
|
652
|
-
let totalSalience = 0;
|
|
653
|
-
let count = 0;
|
|
654
|
-
for (const [visitedId, idx] of context.visitedByFrontier) if (idx === frontierIndex && visitedId !== nodeId) {
|
|
655
|
-
totalSalience += mi(graph, visitedId, nodeId);
|
|
656
|
-
count++;
|
|
657
|
-
}
|
|
658
|
-
const avgSalience = count > 0 ? totalSalience / count : 0;
|
|
659
|
-
return (1 - salienceWeight) * degree + salienceWeight * (1 - avgSalience);
|
|
672
|
+
const avgSalience = avgFrontierMI(context.graph, nodeId, context, mi);
|
|
673
|
+
return (1 - salienceWeight) * context.degree + salienceWeight * (1 - avgSalience);
|
|
660
674
|
}
|
|
661
675
|
/**
|
|
662
676
|
* Run FUSE expansion algorithm.
|
|
@@ -680,20 +694,13 @@ function fuse(graph, seeds, config) {
|
|
|
680
694
|
//#endregion
|
|
681
695
|
//#region src/expansion/sift.ts
|
|
682
696
|
/**
|
|
683
|
-
* REACH priority function
|
|
697
|
+
* REACH (SIFT) priority function.
|
|
684
698
|
*
|
|
685
|
-
*
|
|
699
|
+
* Prioritises nodes with average frontier MI above the threshold;
|
|
700
|
+
* falls back to degree-based ordering for those below it.
|
|
686
701
|
*/
|
|
687
702
|
function siftPriority(nodeId, context, mi, miThreshold) {
|
|
688
|
-
const
|
|
689
|
-
const frontierIndex = context.frontierIndex;
|
|
690
|
-
let totalMi = 0;
|
|
691
|
-
let count = 0;
|
|
692
|
-
for (const [visitedId, idx] of context.visitedByFrontier) if (idx === frontierIndex && visitedId !== nodeId) {
|
|
693
|
-
totalMi += mi(graph, visitedId, nodeId);
|
|
694
|
-
count++;
|
|
695
|
-
}
|
|
696
|
-
const avgMi = count > 0 ? totalMi / count : 0;
|
|
703
|
+
const avgMi = avgFrontierMI(context.graph, nodeId, context, mi);
|
|
697
704
|
if (avgMi >= miThreshold) return 1 - avgMi;
|
|
698
705
|
else return context.degree + 100;
|
|
699
706
|
}
|
|
@@ -735,16 +742,6 @@ function localDensity(graph, nodeId) {
|
|
|
735
742
|
return edges / maxEdges;
|
|
736
743
|
}
|
|
737
744
|
/**
|
|
738
|
-
* Compute bridge score (how many other frontiers visit neighbours).
|
|
739
|
-
*/
|
|
740
|
-
function bridgeScore(nodeId, context) {
|
|
741
|
-
const currentFrontier = context.frontierIndex;
|
|
742
|
-
const nodeNeighbours = new Set(context.graph.neighbours(nodeId));
|
|
743
|
-
let score = 0;
|
|
744
|
-
for (const [visitedId, idx] of context.visitedByFrontier) if (idx !== currentFrontier && nodeNeighbours.has(visitedId)) score++;
|
|
745
|
-
return score;
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
745
|
* MAZE adaptive priority function.
|
|
749
746
|
*
|
|
750
747
|
* Switches strategies based on local conditions:
|
|
@@ -756,7 +753,7 @@ function fluxPriority(nodeId, context, densityThreshold, bridgeThreshold) {
|
|
|
756
753
|
const graph = context.graph;
|
|
757
754
|
const degree = context.degree;
|
|
758
755
|
const density = localDensity(graph, nodeId);
|
|
759
|
-
const bridge =
|
|
756
|
+
const bridge = countCrossFrontierNeighbours(graph, nodeId, context);
|
|
760
757
|
const numFrontiers = new Set(context.visitedByFrontier.values()).size;
|
|
761
758
|
if ((numFrontiers > 0 ? bridge / numFrontiers : 0) >= bridgeThreshold) return 1 / (1 + bridge);
|
|
762
759
|
else if (density >= densityThreshold) return 1 / (degree + 1);
|
|
@@ -1405,14 +1402,13 @@ function hubPromoted(graph, source, target, config) {
|
|
|
1405
1402
|
*/
|
|
1406
1403
|
function scale(graph, source, target, config) {
|
|
1407
1404
|
const { epsilon = 1e-10 } = config ?? {};
|
|
1408
|
-
const {
|
|
1409
|
-
const jaccard = union > 0 ? intersection / union : 0;
|
|
1405
|
+
const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
|
|
1410
1406
|
const n = graph.nodeCount;
|
|
1411
1407
|
const m = graph.edgeCount;
|
|
1412
1408
|
const possibleEdges = n * (n - 1);
|
|
1413
1409
|
const density = possibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;
|
|
1414
1410
|
if (density === 0) return epsilon;
|
|
1415
|
-
const score =
|
|
1411
|
+
const score = jaccardScore / density;
|
|
1416
1412
|
return Math.max(epsilon, score);
|
|
1417
1413
|
}
|
|
1418
1414
|
//#endregion
|
|
@@ -1422,14 +1418,13 @@ function scale(graph, source, target, config) {
|
|
|
1422
1418
|
*/
|
|
1423
1419
|
function skew(graph, source, target, config) {
|
|
1424
1420
|
const { epsilon = 1e-10 } = config ?? {};
|
|
1425
|
-
const {
|
|
1426
|
-
const jaccard = union > 0 ? intersection / union : 0;
|
|
1421
|
+
const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
|
|
1427
1422
|
const N = graph.nodeCount;
|
|
1428
1423
|
const sourceDegree = graph.degree(source);
|
|
1429
1424
|
const targetDegree = graph.degree(target);
|
|
1430
1425
|
const sourceIdf = Math.log(N / (sourceDegree + 1));
|
|
1431
1426
|
const targetIdf = Math.log(N / (targetDegree + 1));
|
|
1432
|
-
const score =
|
|
1427
|
+
const score = jaccardScore * sourceIdf * targetIdf;
|
|
1433
1428
|
return Math.max(epsilon, score);
|
|
1434
1429
|
}
|
|
1435
1430
|
//#endregion
|
|
@@ -1439,11 +1434,10 @@ function skew(graph, source, target, config) {
|
|
|
1439
1434
|
*/
|
|
1440
1435
|
function span(graph, source, target, config) {
|
|
1441
1436
|
const { epsilon = 1e-10 } = config ?? {};
|
|
1442
|
-
const {
|
|
1443
|
-
const jaccard = union > 0 ? intersection / union : 0;
|
|
1437
|
+
const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
|
|
1444
1438
|
const sourceCc = localClusteringCoefficient(graph, source);
|
|
1445
1439
|
const targetCc = localClusteringCoefficient(graph, target);
|
|
1446
|
-
const score =
|
|
1440
|
+
const score = jaccardScore * (1 - Math.max(sourceCc, targetCc));
|
|
1447
1441
|
return Math.max(epsilon, score);
|
|
1448
1442
|
}
|
|
1449
1443
|
//#endregion
|
|
@@ -1453,13 +1447,12 @@ function span(graph, source, target, config) {
|
|
|
1453
1447
|
*/
|
|
1454
1448
|
function etch(graph, source, target, config) {
|
|
1455
1449
|
const { epsilon = 1e-10 } = config ?? {};
|
|
1456
|
-
const {
|
|
1457
|
-
const jaccard = union > 0 ? intersection / union : 0;
|
|
1450
|
+
const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
|
|
1458
1451
|
const edge = graph.getEdge(source, target);
|
|
1459
|
-
if (edge?.type === void 0) return Math.max(epsilon,
|
|
1452
|
+
if (edge?.type === void 0) return Math.max(epsilon, jaccardScore);
|
|
1460
1453
|
const edgeTypeCount = countEdgesOfType(graph, edge.type);
|
|
1461
|
-
if (edgeTypeCount === 0) return Math.max(epsilon,
|
|
1462
|
-
const score =
|
|
1454
|
+
if (edgeTypeCount === 0) return Math.max(epsilon, jaccardScore);
|
|
1455
|
+
const score = jaccardScore * Math.log(graph.edgeCount / edgeTypeCount);
|
|
1463
1456
|
return Math.max(epsilon, score);
|
|
1464
1457
|
}
|
|
1465
1458
|
//#endregion
|
|
@@ -1469,17 +1462,16 @@ function etch(graph, source, target, config) {
|
|
|
1469
1462
|
*/
|
|
1470
1463
|
function notch(graph, source, target, config) {
|
|
1471
1464
|
const { epsilon = 1e-10 } = config ?? {};
|
|
1472
|
-
const {
|
|
1473
|
-
const jaccard = union > 0 ? intersection / union : 0;
|
|
1465
|
+
const { jaccard: jaccardScore } = computeJaccard(graph, source, target);
|
|
1474
1466
|
const sourceNode = graph.getNode(source);
|
|
1475
1467
|
const targetNode = graph.getNode(target);
|
|
1476
|
-
if (sourceNode?.type === void 0 || targetNode?.type === void 0) return Math.max(epsilon,
|
|
1468
|
+
if (sourceNode?.type === void 0 || targetNode?.type === void 0) return Math.max(epsilon, jaccardScore);
|
|
1477
1469
|
const sourceTypeCount = countNodesOfType(graph, sourceNode.type);
|
|
1478
1470
|
const targetTypeCount = countNodesOfType(graph, targetNode.type);
|
|
1479
|
-
if (sourceTypeCount === 0 || targetTypeCount === 0) return Math.max(epsilon,
|
|
1471
|
+
if (sourceTypeCount === 0 || targetTypeCount === 0) return Math.max(epsilon, jaccardScore);
|
|
1480
1472
|
const sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);
|
|
1481
1473
|
const targetRarity = Math.log(graph.nodeCount / targetTypeCount);
|
|
1482
|
-
const score =
|
|
1474
|
+
const score = jaccardScore * sourceRarity * targetRarity;
|
|
1483
1475
|
return Math.max(epsilon, score);
|
|
1484
1476
|
}
|
|
1485
1477
|
//#endregion
|
|
@@ -1498,13 +1490,12 @@ function notch(graph, source, target, config) {
|
|
|
1498
1490
|
*/
|
|
1499
1491
|
function adaptive(graph, source, target, config) {
|
|
1500
1492
|
const { epsilon = 1e-10, structuralWeight = .4, degreeWeight = .3, overlapWeight = .3 } = config ?? {};
|
|
1501
|
-
const
|
|
1493
|
+
const { jaccard: jaccardScore, sourceNeighbours, targetNeighbours } = computeJaccard(graph, source, target);
|
|
1494
|
+
const structural = sourceNeighbours.size === 0 && targetNeighbours.size === 0 ? 0 : Math.max(epsilon, jaccardScore);
|
|
1502
1495
|
const degreeComponent = adamicAdar(graph, source, target, {
|
|
1503
1496
|
epsilon,
|
|
1504
1497
|
normalise: true
|
|
1505
1498
|
});
|
|
1506
|
-
const sourceNeighbours = neighbourSet(graph, source, target);
|
|
1507
|
-
const targetNeighbours = neighbourSet(graph, target, source);
|
|
1508
1499
|
let overlap;
|
|
1509
1500
|
if (sourceNeighbours.size > 0 && targetNeighbours.size > 0) {
|
|
1510
1501
|
const { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);
|
|
@@ -1516,6 +1507,42 @@ function adaptive(graph, source, target, config) {
|
|
|
1516
1507
|
return Math.max(epsilon, Math.min(1, score));
|
|
1517
1508
|
}
|
|
1518
1509
|
//#endregion
|
|
1510
|
+
//#region src/ranking/baselines/utils.ts
|
|
1511
|
+
/**
|
|
1512
|
+
* Normalise a set of scored paths and return them sorted highest-first.
|
|
1513
|
+
*
|
|
1514
|
+
* All scores are normalised relative to the maximum observed score.
|
|
1515
|
+
* When `includeScores` is false, raw (un-normalised) scores are preserved.
|
|
1516
|
+
* Handles degenerate cases: empty input and all-zero scores.
|
|
1517
|
+
*
|
|
1518
|
+
* @param paths - Original paths in input order
|
|
1519
|
+
* @param scored - Paths paired with their computed scores
|
|
1520
|
+
* @param method - Method name to embed in the result
|
|
1521
|
+
* @param includeScores - When true, normalise scores to [0, 1]; when false, keep raw scores
|
|
1522
|
+
* @returns BaselineResult with ranked paths
|
|
1523
|
+
*/
|
|
1524
|
+
function normaliseAndRank(paths, scored, method, includeScores) {
|
|
1525
|
+
if (scored.length === 0) return {
|
|
1526
|
+
paths: [],
|
|
1527
|
+
method
|
|
1528
|
+
};
|
|
1529
|
+
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1530
|
+
if (maxScore === 0) return {
|
|
1531
|
+
paths: paths.map((path) => ({
|
|
1532
|
+
...path,
|
|
1533
|
+
score: 0
|
|
1534
|
+
})),
|
|
1535
|
+
method
|
|
1536
|
+
};
|
|
1537
|
+
return {
|
|
1538
|
+
paths: scored.map(({ path, score }) => ({
|
|
1539
|
+
...path,
|
|
1540
|
+
score: includeScores ? score / maxScore : score
|
|
1541
|
+
})).sort((a, b) => b.score - a.score),
|
|
1542
|
+
method
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
//#endregion
|
|
1519
1546
|
//#region src/ranking/baselines/shortest.ts
|
|
1520
1547
|
/**
|
|
1521
1548
|
* Rank paths by length (shortest first).
|
|
@@ -1533,18 +1560,10 @@ function shortest(_graph, paths, config) {
|
|
|
1533
1560
|
paths: [],
|
|
1534
1561
|
method: "shortest"
|
|
1535
1562
|
};
|
|
1536
|
-
|
|
1563
|
+
return normaliseAndRank(paths, paths.map((path) => ({
|
|
1537
1564
|
path,
|
|
1538
1565
|
score: 1 / path.nodes.length
|
|
1539
|
-
}));
|
|
1540
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1541
|
-
return {
|
|
1542
|
-
paths: scored.map(({ path, score }) => ({
|
|
1543
|
-
...path,
|
|
1544
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
1545
|
-
})).sort((a, b) => b.score - a.score),
|
|
1546
|
-
method: "shortest"
|
|
1547
|
-
};
|
|
1566
|
+
})), "shortest", includeScores);
|
|
1548
1567
|
}
|
|
1549
1568
|
//#endregion
|
|
1550
1569
|
//#region src/ranking/baselines/degree-sum.ts
|
|
@@ -1562,29 +1581,14 @@ function degreeSum(graph, paths, config) {
|
|
|
1562
1581
|
paths: [],
|
|
1563
1582
|
method: "degree-sum"
|
|
1564
1583
|
};
|
|
1565
|
-
|
|
1584
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
1566
1585
|
let degreeSum = 0;
|
|
1567
1586
|
for (const nodeId of path.nodes) degreeSum += graph.degree(nodeId);
|
|
1568
1587
|
return {
|
|
1569
1588
|
path,
|
|
1570
1589
|
score: degreeSum
|
|
1571
1590
|
};
|
|
1572
|
-
});
|
|
1573
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1574
|
-
if (maxScore === 0) return {
|
|
1575
|
-
paths: paths.map((path) => ({
|
|
1576
|
-
...path,
|
|
1577
|
-
score: 0
|
|
1578
|
-
})),
|
|
1579
|
-
method: "degree-sum"
|
|
1580
|
-
};
|
|
1581
|
-
return {
|
|
1582
|
-
paths: scored.map(({ path, score }) => ({
|
|
1583
|
-
...path,
|
|
1584
|
-
score: includeScores ? score / maxScore : score
|
|
1585
|
-
})).sort((a, b) => b.score - a.score),
|
|
1586
|
-
method: "degree-sum"
|
|
1587
|
-
};
|
|
1591
|
+
}), "degree-sum", includeScores);
|
|
1588
1592
|
}
|
|
1589
1593
|
//#endregion
|
|
1590
1594
|
//#region src/ranking/baselines/widest-path.ts
|
|
@@ -1602,7 +1606,7 @@ function widestPath(graph, paths, config) {
|
|
|
1602
1606
|
paths: [],
|
|
1603
1607
|
method: "widest-path"
|
|
1604
1608
|
};
|
|
1605
|
-
|
|
1609
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
1606
1610
|
if (path.nodes.length < 2) return {
|
|
1607
1611
|
path,
|
|
1608
1612
|
score: 1
|
|
@@ -1619,22 +1623,7 @@ function widestPath(graph, paths, config) {
|
|
|
1619
1623
|
path,
|
|
1620
1624
|
score: minSimilarity === Number.POSITIVE_INFINITY ? 1 : minSimilarity
|
|
1621
1625
|
};
|
|
1622
|
-
});
|
|
1623
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1624
|
-
if (maxScore === 0) return {
|
|
1625
|
-
paths: paths.map((path) => ({
|
|
1626
|
-
...path,
|
|
1627
|
-
score: 0
|
|
1628
|
-
})),
|
|
1629
|
-
method: "widest-path"
|
|
1630
|
-
};
|
|
1631
|
-
return {
|
|
1632
|
-
paths: scored.map(({ path, score }) => ({
|
|
1633
|
-
...path,
|
|
1634
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
1635
|
-
})).sort((a, b) => b.score - a.score),
|
|
1636
|
-
method: "widest-path"
|
|
1637
|
-
};
|
|
1626
|
+
}), "widest-path", includeScores);
|
|
1638
1627
|
}
|
|
1639
1628
|
//#endregion
|
|
1640
1629
|
//#region src/ranking/baselines/jaccard-arithmetic.ts
|
|
@@ -1652,7 +1641,7 @@ function jaccardArithmetic(graph, paths, config) {
|
|
|
1652
1641
|
paths: [],
|
|
1653
1642
|
method: "jaccard-arithmetic"
|
|
1654
1643
|
};
|
|
1655
|
-
|
|
1644
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
1656
1645
|
if (path.nodes.length < 2) return {
|
|
1657
1646
|
path,
|
|
1658
1647
|
score: 1
|
|
@@ -1671,22 +1660,7 @@ function jaccardArithmetic(graph, paths, config) {
|
|
|
1671
1660
|
path,
|
|
1672
1661
|
score: edgeCount > 0 ? similaritySum / edgeCount : 1
|
|
1673
1662
|
};
|
|
1674
|
-
});
|
|
1675
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1676
|
-
if (maxScore === 0) return {
|
|
1677
|
-
paths: paths.map((path) => ({
|
|
1678
|
-
...path,
|
|
1679
|
-
score: 0
|
|
1680
|
-
})),
|
|
1681
|
-
method: "jaccard-arithmetic"
|
|
1682
|
-
};
|
|
1683
|
-
return {
|
|
1684
|
-
paths: scored.map(({ path, score }) => ({
|
|
1685
|
-
...path,
|
|
1686
|
-
score: includeScores ? score / maxScore : score
|
|
1687
|
-
})).sort((a, b) => b.score - a.score),
|
|
1688
|
-
method: "jaccard-arithmetic"
|
|
1689
|
-
};
|
|
1663
|
+
}), "jaccard-arithmetic", includeScores);
|
|
1690
1664
|
}
|
|
1691
1665
|
//#endregion
|
|
1692
1666
|
//#region src/ranking/baselines/pagerank.ts
|
|
@@ -1747,29 +1721,14 @@ function pagerank(graph, paths, config) {
|
|
|
1747
1721
|
method: "pagerank"
|
|
1748
1722
|
};
|
|
1749
1723
|
const ranks = computePageRank(graph);
|
|
1750
|
-
|
|
1724
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
1751
1725
|
let prSum = 0;
|
|
1752
1726
|
for (const nodeId of path.nodes) prSum += ranks.get(nodeId) ?? 0;
|
|
1753
1727
|
return {
|
|
1754
1728
|
path,
|
|
1755
1729
|
score: prSum
|
|
1756
1730
|
};
|
|
1757
|
-
});
|
|
1758
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1759
|
-
if (maxScore === 0) return {
|
|
1760
|
-
paths: paths.map((path) => ({
|
|
1761
|
-
...path,
|
|
1762
|
-
score: 0
|
|
1763
|
-
})),
|
|
1764
|
-
method: "pagerank"
|
|
1765
|
-
};
|
|
1766
|
-
return {
|
|
1767
|
-
paths: scored.map(({ path, score }) => ({
|
|
1768
|
-
...path,
|
|
1769
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
1770
|
-
})).sort((a, b) => b.score - a.score),
|
|
1771
|
-
method: "pagerank"
|
|
1772
|
-
};
|
|
1731
|
+
}), "pagerank", includeScores);
|
|
1773
1732
|
}
|
|
1774
1733
|
//#endregion
|
|
1775
1734
|
//#region src/ranking/baselines/betweenness.ts
|
|
@@ -1854,29 +1813,14 @@ function betweenness(graph, paths, config) {
|
|
|
1854
1813
|
method: "betweenness"
|
|
1855
1814
|
};
|
|
1856
1815
|
const bcMap = computeBetweenness(graph);
|
|
1857
|
-
|
|
1816
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
1858
1817
|
let bcSum = 0;
|
|
1859
1818
|
for (const nodeId of path.nodes) bcSum += bcMap.get(nodeId) ?? 0;
|
|
1860
1819
|
return {
|
|
1861
1820
|
path,
|
|
1862
1821
|
score: bcSum
|
|
1863
1822
|
};
|
|
1864
|
-
});
|
|
1865
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1866
|
-
if (maxScore === 0) return {
|
|
1867
|
-
paths: paths.map((path) => ({
|
|
1868
|
-
...path,
|
|
1869
|
-
score: 0
|
|
1870
|
-
})),
|
|
1871
|
-
method: "betweenness"
|
|
1872
|
-
};
|
|
1873
|
-
return {
|
|
1874
|
-
paths: scored.map(({ path, score }) => ({
|
|
1875
|
-
...path,
|
|
1876
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
1877
|
-
})).sort((a, b) => b.score - a.score),
|
|
1878
|
-
method: "betweenness"
|
|
1879
|
-
};
|
|
1823
|
+
}), "betweenness", includeScores);
|
|
1880
1824
|
}
|
|
1881
1825
|
//#endregion
|
|
1882
1826
|
//#region src/ranking/baselines/katz.ts
|
|
@@ -1939,7 +1883,7 @@ function katz(graph, paths, config) {
|
|
|
1939
1883
|
paths: [],
|
|
1940
1884
|
method: "katz"
|
|
1941
1885
|
};
|
|
1942
|
-
|
|
1886
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
1943
1887
|
const source = path.nodes[0];
|
|
1944
1888
|
const target = path.nodes[path.nodes.length - 1];
|
|
1945
1889
|
if (source === void 0 || target === void 0) return {
|
|
@@ -1950,22 +1894,7 @@ function katz(graph, paths, config) {
|
|
|
1950
1894
|
path,
|
|
1951
1895
|
score: computeKatz(graph, source, target)
|
|
1952
1896
|
};
|
|
1953
|
-
});
|
|
1954
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
1955
|
-
if (maxScore === 0) return {
|
|
1956
|
-
paths: paths.map((path) => ({
|
|
1957
|
-
...path,
|
|
1958
|
-
score: 0
|
|
1959
|
-
})),
|
|
1960
|
-
method: "katz"
|
|
1961
|
-
};
|
|
1962
|
-
return {
|
|
1963
|
-
paths: scored.map(({ path, score }) => ({
|
|
1964
|
-
...path,
|
|
1965
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
1966
|
-
})).sort((a, b) => b.score - a.score),
|
|
1967
|
-
method: "katz"
|
|
1968
|
-
};
|
|
1897
|
+
}), "katz", includeScores);
|
|
1969
1898
|
}
|
|
1970
1899
|
//#endregion
|
|
1971
1900
|
//#region src/ranking/baselines/communicability.ts
|
|
@@ -2027,7 +1956,7 @@ function communicability(graph, paths, config) {
|
|
|
2027
1956
|
paths: [],
|
|
2028
1957
|
method: "communicability"
|
|
2029
1958
|
};
|
|
2030
|
-
|
|
1959
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
2031
1960
|
const source = path.nodes[0];
|
|
2032
1961
|
const target = path.nodes[path.nodes.length - 1];
|
|
2033
1962
|
if (source === void 0 || target === void 0) return {
|
|
@@ -2038,22 +1967,7 @@ function communicability(graph, paths, config) {
|
|
|
2038
1967
|
path,
|
|
2039
1968
|
score: computeCommunicability(graph, source, target)
|
|
2040
1969
|
};
|
|
2041
|
-
});
|
|
2042
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
2043
|
-
if (maxScore === 0) return {
|
|
2044
|
-
paths: paths.map((path) => ({
|
|
2045
|
-
...path,
|
|
2046
|
-
score: 0
|
|
2047
|
-
})),
|
|
2048
|
-
method: "communicability"
|
|
2049
|
-
};
|
|
2050
|
-
return {
|
|
2051
|
-
paths: scored.map(({ path, score }) => ({
|
|
2052
|
-
...path,
|
|
2053
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
2054
|
-
})).sort((a, b) => b.score - a.score),
|
|
2055
|
-
method: "communicability"
|
|
2056
|
-
};
|
|
1970
|
+
}), "communicability", includeScores);
|
|
2057
1971
|
}
|
|
2058
1972
|
//#endregion
|
|
2059
1973
|
//#region src/ranking/baselines/resistance-distance.ts
|
|
@@ -2170,7 +2084,7 @@ function resistanceDistance(graph, paths, config) {
|
|
|
2170
2084
|
};
|
|
2171
2085
|
const nodeCount = Array.from(graph.nodeIds()).length;
|
|
2172
2086
|
if (nodeCount > 5e3) throw new Error(`Cannot rank paths: graph too large (${String(nodeCount)} nodes). Resistance distance requires O(n^3) computation; maximum 5000 nodes.`);
|
|
2173
|
-
|
|
2087
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
2174
2088
|
const source = path.nodes[0];
|
|
2175
2089
|
const target = path.nodes[path.nodes.length - 1];
|
|
2176
2090
|
if (source === void 0 || target === void 0) return {
|
|
@@ -2181,22 +2095,7 @@ function resistanceDistance(graph, paths, config) {
|
|
|
2181
2095
|
path,
|
|
2182
2096
|
score: 1 / computeResistance(graph, source, target)
|
|
2183
2097
|
};
|
|
2184
|
-
});
|
|
2185
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
2186
|
-
if (maxScore === 0) return {
|
|
2187
|
-
paths: paths.map((path) => ({
|
|
2188
|
-
...path,
|
|
2189
|
-
score: 0
|
|
2190
|
-
})),
|
|
2191
|
-
method: "resistance-distance"
|
|
2192
|
-
};
|
|
2193
|
-
return {
|
|
2194
|
-
paths: scored.map(({ path, score }) => ({
|
|
2195
|
-
...path,
|
|
2196
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
2197
|
-
})).sort((a, b) => b.score - a.score),
|
|
2198
|
-
method: "resistance-distance"
|
|
2199
|
-
};
|
|
2098
|
+
}), "resistance-distance", includeScores);
|
|
2200
2099
|
}
|
|
2201
2100
|
//#endregion
|
|
2202
2101
|
//#region src/ranking/baselines/random-ranking.ts
|
|
@@ -2230,27 +2129,12 @@ function randomRanking(_graph, paths, config) {
|
|
|
2230
2129
|
paths: [],
|
|
2231
2130
|
method: "random"
|
|
2232
2131
|
};
|
|
2233
|
-
|
|
2132
|
+
return normaliseAndRank(paths, paths.map((path) => {
|
|
2234
2133
|
return {
|
|
2235
2134
|
path,
|
|
2236
2135
|
score: seededRandom(path.nodes.join(","), seed)
|
|
2237
2136
|
};
|
|
2238
|
-
});
|
|
2239
|
-
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
2240
|
-
if (maxScore === 0) return {
|
|
2241
|
-
paths: paths.map((path) => ({
|
|
2242
|
-
...path,
|
|
2243
|
-
score: 0
|
|
2244
|
-
})),
|
|
2245
|
-
method: "random"
|
|
2246
|
-
};
|
|
2247
|
-
return {
|
|
2248
|
-
paths: scored.map(({ path, score }) => ({
|
|
2249
|
-
...path,
|
|
2250
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
2251
|
-
})).sort((a, b) => b.score - a.score),
|
|
2252
|
-
method: "random"
|
|
2253
|
-
};
|
|
2137
|
+
}), "random", includeScores);
|
|
2254
2138
|
}
|
|
2255
2139
|
//#endregion
|
|
2256
2140
|
//#region src/ranking/baselines/hitting-time.ts
|
|
@@ -2489,20 +2373,14 @@ function hittingTime(graph, paths, config) {
|
|
|
2489
2373
|
};
|
|
2490
2374
|
});
|
|
2491
2375
|
const maxScore = Math.max(...scored.map((s) => s.score));
|
|
2492
|
-
if (
|
|
2376
|
+
if (!Number.isFinite(maxScore)) return {
|
|
2493
2377
|
paths: paths.map((path) => ({
|
|
2494
2378
|
...path,
|
|
2495
2379
|
score: 0
|
|
2496
2380
|
})),
|
|
2497
2381
|
method: "hitting-time"
|
|
2498
2382
|
};
|
|
2499
|
-
return
|
|
2500
|
-
paths: scored.map(({ path, score }) => ({
|
|
2501
|
-
...path,
|
|
2502
|
-
score: includeScores ? score / maxScore : score / maxScore
|
|
2503
|
-
})).sort((a, b) => b.score - a.score),
|
|
2504
|
-
method: "hitting-time"
|
|
2505
|
-
};
|
|
2383
|
+
return normaliseAndRank(paths, scored, "hitting-time", includeScores);
|
|
2506
2384
|
}
|
|
2507
2385
|
//#endregion
|
|
2508
2386
|
//#region src/extraction/ego-network.ts
|
|
@@ -3122,6 +3000,6 @@ function filterSubgraph(graph, options) {
|
|
|
3122
3000
|
return result;
|
|
3123
3001
|
}
|
|
3124
3002
|
//#endregion
|
|
3125
|
-
export { AdjacencyMapGraph, GPUContext, GPUNotAvailableError, PriorityQueue, _computeMean, adamicAdar, adaptive, approximateClusteringCoefficient, assertWebGPUAvailable, base, batchClusteringCoefficients, betweenness, bfs, bfsWithPath, communicability, computeTrussNumbers, cosine, countEdgesOfType, countNodesOfType, createGPUContext, createResultBuffer, csrToGPUBuffers, degreeSum, detectWebGPU, dfs, dfsPriority, dfsPriorityFn, dfsWithPath, dome, domeHighDegree, edge, entropyFromCounts, enumerateMotifs, enumerateMotifsWithInstances, etch, extractEgoNetwork, extractInducedSubgraph, extractKCore, extractKTruss, filterSubgraph, flux, frontierBalanced, fuse, getGPUContext, getMotifName, graphToCSR, grasp, hae, hittingTime, hubPromoted, isWebGPUAvailable, jaccard, jaccardArithmetic, kHop, katz, lace, localClusteringCoefficient, localTypeEntropy, maze, miniBatchKMeans, neighbourIntersection, neighbourOverlap, neighbourSet, normaliseFeatures, normaliseFeatures as zScoreNormalise, normalisedEntropy, notch, overlapCoefficient, pagerank, parse, pipe, randomPriority, randomRanking, randomWalk, reach, readBufferToCPU, resistanceDistance, resourceAllocation, sage, scale, shannonEntropy, shortest, sift, skew, sorensen, span, standardBfs, stratified, tide, warp, widestPath };
|
|
3003
|
+
export { AdjacencyMapGraph, GPUContext, GPUNotAvailableError, PriorityQueue, _computeMean, adamicAdar, adaptive, approximateClusteringCoefficient, assertWebGPUAvailable, base, batchClusteringCoefficients, betweenness, bfs, bfsWithPath, communicability, computeJaccard, computeTrussNumbers, cosine, countEdgesOfType, countNodesOfType, createGPUContext, createResultBuffer, csrToGPUBuffers, degreeSum, detectWebGPU, dfs, dfsPriority, dfsPriorityFn, dfsWithPath, dome, domeHighDegree, edge, entropyFromCounts, enumerateMotifs, enumerateMotifsWithInstances, etch, extractEgoNetwork, extractInducedSubgraph, extractKCore, extractKTruss, filterSubgraph, flux, frontierBalanced, fuse, getGPUContext, getMotifName, graphToCSR, grasp, hae, hittingTime, hubPromoted, isWebGPUAvailable, jaccard, jaccardArithmetic, kHop, katz, lace, localClusteringCoefficient, localTypeEntropy, maze, miniBatchKMeans, neighbourIntersection, neighbourOverlap, neighbourSet, normaliseFeatures, normaliseFeatures as zScoreNormalise, normalisedEntropy, notch, overlapCoefficient, pagerank, parse, pipe, randomPriority, randomRanking, randomWalk, reach, readBufferToCPU, resistanceDistance, resourceAllocation, sage, scale, shannonEntropy, shortest, sift, skew, sorensen, span, standardBfs, stratified, tide, warp, widestPath };
|
|
3126
3004
|
|
|
3127
3005
|
//# sourceMappingURL=index.js.map
|