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