graphwise 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,8 @@ 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
6
  import { grasp, stratified } from "../seeds/index.js";
6
- import { approximateClusteringCoefficient, batchClusteringCoefficients, entropyFromCounts, localClusteringCoefficient, localTypeEntropy, normalisedEntropy, shannonEntropy } from "../utils/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
9
9
  /**
@@ -298,15 +298,9 @@ function edge(graph, seeds, config) {
298
298
  */
299
299
  function jaccard(graph, source, target, config) {
300
300
  const { epsilon = 1e-10 } = config ?? {};
301
- const sourceNeighbours = new Set(graph.neighbours(source));
302
- const targetNeighbours = new Set(graph.neighbours(target));
303
- sourceNeighbours.delete(target);
304
- targetNeighbours.delete(source);
305
- let intersectionSize = 0;
306
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) intersectionSize++;
307
- const unionSize = sourceNeighbours.size + targetNeighbours.size - intersectionSize;
308
- if (unionSize === 0) return 0;
309
- const score = intersectionSize / unionSize;
301
+ const { intersection, union } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
302
+ if (union === 0) return 0;
303
+ const score = intersection / union;
310
304
  return Math.max(epsilon, score);
311
305
  }
312
306
  //#endregion
@@ -686,19 +680,14 @@ function computePathSalience(graph, path, mi, epsilon) {
686
680
  */
687
681
  function adamicAdar(graph, source, target, config) {
688
682
  const { epsilon = 1e-10, normalise = true } = config ?? {};
689
- const sourceNeighbours = new Set(graph.neighbours(source));
690
- const targetNeighbours = new Set(graph.neighbours(target));
691
- sourceNeighbours.delete(target);
692
- targetNeighbours.delete(source);
683
+ const commonNeighbours = neighbourIntersection(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
693
684
  let score = 0;
694
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) {
685
+ for (const neighbour of commonNeighbours) {
695
686
  const degree = graph.degree(neighbour);
696
- if (degree > 1) score += 1 / Math.log(degree);
687
+ score += 1 / Math.log(degree + 1);
697
688
  }
698
- if (normalise) {
699
- const commonCount = sourceNeighbours.size < targetNeighbours.size ? sourceNeighbours.size : targetNeighbours.size;
700
- if (commonCount === 0) return 0;
701
- const maxScore = commonCount / Math.log(2);
689
+ if (normalise && commonNeighbours.size > 0) {
690
+ const maxScore = commonNeighbours.size / Math.log(2);
702
691
  score = score / maxScore;
703
692
  }
704
693
  return Math.max(epsilon, score);
@@ -710,22 +699,15 @@ function adamicAdar(graph, source, target, config) {
710
699
  */
711
700
  function scale(graph, source, target, config) {
712
701
  const { epsilon = 1e-10 } = config ?? {};
713
- const sourceNeighbours = new Set(graph.neighbours(source));
714
- const targetNeighbours = new Set(graph.neighbours(target));
715
- sourceNeighbours.delete(target);
716
- targetNeighbours.delete(source);
717
- const sourceDegree = sourceNeighbours.size;
718
- const targetDegree = targetNeighbours.size;
719
- let intersectionSize = 0;
720
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) intersectionSize++;
721
- const unionSize = sourceDegree + targetDegree - intersectionSize;
722
- const jaccard = unionSize > 0 ? intersectionSize / unionSize : 0;
723
- const minDegree = Math.min(sourceDegree, targetDegree);
724
- const maxDegree = Math.max(sourceDegree, targetDegree);
725
- const degreeRatio = maxDegree > 0 ? minDegree / maxDegree : 0;
726
- if (jaccard + degreeRatio === 0) return epsilon;
727
- const score = 2 * jaccard * degreeRatio / (jaccard + degreeRatio);
728
- return Math.max(epsilon, Math.min(1, score));
702
+ const { intersection, union } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
703
+ const jaccard = union > 0 ? intersection / union : 0;
704
+ const n = graph.nodeCount;
705
+ const m = graph.edgeCount;
706
+ const densityNormaliser = graph.directed ? n * (n - 1) : 2 * n * (n - 1);
707
+ const density = densityNormaliser > 0 ? m / densityNormaliser : 0;
708
+ if (density === 0) return epsilon;
709
+ const score = jaccard / density;
710
+ return Math.max(epsilon, score);
729
711
  }
730
712
  //#endregion
731
713
  //#region src/ranking/mi/skew.ts
@@ -734,23 +716,15 @@ function scale(graph, source, target, config) {
734
716
  */
735
717
  function skew(graph, source, target, config) {
736
718
  const { epsilon = 1e-10 } = config ?? {};
737
- const sourceNeighbours = new Set(graph.neighbours(source));
738
- const targetNeighbours = new Set(graph.neighbours(target));
739
- sourceNeighbours.delete(target);
740
- targetNeighbours.delete(source);
741
- let weightedIntersection = 0;
742
- let commonCount = 0;
743
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) {
744
- commonCount++;
745
- const degree = graph.degree(neighbour);
746
- if (degree > 1) weightedIntersection += 1 / Math.log(degree);
747
- }
748
- if (commonCount === 0) return epsilon;
749
- const sourceDegree = sourceNeighbours.size;
750
- const targetDegree = targetNeighbours.size;
751
- const maxScore = Math.min(sourceDegree, targetDegree) / Math.log(2);
752
- const score = weightedIntersection / maxScore;
753
- return Math.max(epsilon, Math.min(1, score));
719
+ const { intersection, union } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
720
+ const jaccard = union > 0 ? intersection / union : 0;
721
+ const N = graph.nodeCount;
722
+ const sourceDegree = graph.degree(source);
723
+ const targetDegree = graph.degree(target);
724
+ const sourceIdf = Math.log(N / (sourceDegree + 1));
725
+ const targetIdf = Math.log(N / (targetDegree + 1));
726
+ const score = jaccard * sourceIdf * targetIdf;
727
+ return Math.max(epsilon, score);
754
728
  }
755
729
  //#endregion
756
730
  //#region src/ranking/mi/span.ts
@@ -759,21 +733,12 @@ function skew(graph, source, target, config) {
759
733
  */
760
734
  function span(graph, source, target, config) {
761
735
  const { epsilon = 1e-10 } = config ?? {};
762
- const sourceNeighbours = new Set(graph.neighbours(source));
763
- const targetNeighbours = new Set(graph.neighbours(target));
764
- sourceNeighbours.delete(target);
765
- targetNeighbours.delete(source);
766
- const sourceDegree = sourceNeighbours.size;
767
- const targetDegree = targetNeighbours.size;
768
- let intersectionSize = 0;
769
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) intersectionSize++;
770
- const unionSize = sourceDegree + targetDegree - intersectionSize;
771
- const jaccard = unionSize > 0 ? intersectionSize / unionSize : 0;
772
- const maxDegree = Math.max(sourceDegree, targetDegree);
773
- const degreeDiff = Math.abs(sourceDegree - targetDegree);
774
- const degreeSimilarity = maxDegree > 0 ? 1 - degreeDiff / maxDegree : 1;
775
- const score = Math.sqrt(jaccard * degreeSimilarity);
776
- return Math.max(epsilon, Math.min(1, score));
736
+ const { intersection, union } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
737
+ const jaccard = union > 0 ? intersection / union : 0;
738
+ const sourceCc = localClusteringCoefficient(graph, source);
739
+ const targetCc = localClusteringCoefficient(graph, target);
740
+ const score = jaccard * (1 - Math.max(sourceCc, targetCc));
741
+ return Math.max(epsilon, score);
777
742
  }
778
743
  //#endregion
779
744
  //#region src/ranking/mi/etch.ts
@@ -782,30 +747,14 @@ function span(graph, source, target, config) {
782
747
  */
783
748
  function etch(graph, source, target, config) {
784
749
  const { epsilon = 1e-10 } = config ?? {};
785
- const sourceNeighbours = new Set(graph.neighbours(source));
786
- const targetNeighbours = new Set(graph.neighbours(target));
787
- sourceNeighbours.delete(target);
788
- targetNeighbours.delete(source);
789
- const commonNeighbours = [];
790
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) commonNeighbours.push(neighbour);
791
- if (commonNeighbours.length < 2) return epsilon;
792
- let jointEdges = 0;
793
- for (let i = 0; i < commonNeighbours.length; i++) for (let j = i + 1; j < commonNeighbours.length; j++) {
794
- const ni = commonNeighbours[i];
795
- const nj = commonNeighbours[j];
796
- if (ni !== void 0 && nj !== void 0 && graph.getEdge(ni, nj) !== void 0) jointEdges++;
797
- }
798
- const maxJointEdges = commonNeighbours.length * (commonNeighbours.length - 1) / 2;
799
- const jointDensity = maxJointEdges > 0 ? jointEdges / maxJointEdges : 0;
800
- let commonEdges = 0;
801
- for (const cn of commonNeighbours) {
802
- if (graph.getEdge(source, cn) !== void 0) commonEdges++;
803
- if (graph.getEdge(target, cn) !== void 0) commonEdges++;
804
- }
805
- const maxCommonEdges = commonNeighbours.length * 2;
806
- const commonDensity = maxCommonEdges > 0 ? commonEdges / maxCommonEdges : 0;
807
- const score = jointDensity * .7 + commonDensity * .3;
808
- return Math.max(epsilon, Math.min(1, score));
750
+ const { intersection, union } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
751
+ const jaccard = union > 0 ? intersection / union : 0;
752
+ const edge = graph.getEdge(source, target);
753
+ if (edge?.type === void 0) return Math.max(epsilon, jaccard);
754
+ const edgeTypeCount = countEdgesOfType(graph, edge.type);
755
+ if (edgeTypeCount === 0) return Math.max(epsilon, jaccard);
756
+ const score = jaccard * Math.log(graph.edgeCount / edgeTypeCount);
757
+ return Math.max(epsilon, score);
809
758
  }
810
759
  //#endregion
811
760
  //#region src/ranking/mi/notch.ts
@@ -814,20 +763,18 @@ function etch(graph, source, target, config) {
814
763
  */
815
764
  function notch(graph, source, target, config) {
816
765
  const { epsilon = 1e-10 } = config ?? {};
817
- const sourceNeighbours = new Set(graph.neighbours(source));
818
- const targetNeighbours = new Set(graph.neighbours(target));
819
- sourceNeighbours.delete(target);
820
- targetNeighbours.delete(source);
821
- const sourceDegree = sourceNeighbours.size;
822
- const targetDegree = targetNeighbours.size;
823
- let intersectionSize = 0;
824
- for (const neighbour of sourceNeighbours) if (targetNeighbours.has(neighbour)) intersectionSize++;
825
- const minDegree = Math.min(sourceDegree, targetDegree);
826
- const overlap = minDegree > 0 ? intersectionSize / minDegree : 0;
827
- const maxDegree = Math.max(sourceDegree, targetDegree);
828
- const correlation = maxDegree > 0 ? 1 - Math.abs(sourceDegree - targetDegree) / maxDegree : 1;
829
- const score = overlap * .6 + correlation * .4;
830
- return Math.max(epsilon, Math.min(1, score));
766
+ const { intersection, union } = neighbourOverlap(neighbourSet(graph, source, target), neighbourSet(graph, target, source));
767
+ const jaccard = union > 0 ? intersection / union : 0;
768
+ const sourceNode = graph.getNode(source);
769
+ const targetNode = graph.getNode(target);
770
+ if (sourceNode?.type === void 0 || targetNode?.type === void 0) return Math.max(epsilon, jaccard);
771
+ const sourceTypeCount = countNodesOfType(graph, sourceNode.type);
772
+ const targetTypeCount = countNodesOfType(graph, targetNode.type);
773
+ if (sourceTypeCount === 0 || targetTypeCount === 0) return Math.max(epsilon, jaccard);
774
+ const sourceRarity = Math.log(graph.nodeCount / sourceTypeCount);
775
+ const targetRarity = Math.log(graph.nodeCount / targetTypeCount);
776
+ const score = jaccard * sourceRarity * targetRarity;
777
+ return Math.max(epsilon, score);
831
778
  }
832
779
  //#endregion
833
780
  //#region src/ranking/mi/adaptive.ts
@@ -835,7 +782,7 @@ function notch(graph, source, target, config) {
835
782
  * Compute unified adaptive MI between two connected nodes.
836
783
  *
837
784
  * Combines structural, degree, and overlap signals with
838
- * adaptive weighting based on graph density.
785
+ * configurable weighting.
839
786
  *
840
787
  * @param graph - Source graph
841
788
  * @param source - Source node ID
@@ -850,17 +797,13 @@ function adaptive(graph, source, target, config) {
850
797
  epsilon,
851
798
  normalise: true
852
799
  });
853
- const sourceNeighbours = new Set(graph.neighbours(source));
854
- const targetNeighbours = new Set(graph.neighbours(target));
855
- sourceNeighbours.delete(target);
856
- targetNeighbours.delete(source);
857
- const sourceDegree = sourceNeighbours.size;
858
- const targetDegree = targetNeighbours.size;
800
+ const sourceNeighbours = neighbourSet(graph, source, target);
801
+ const targetNeighbours = neighbourSet(graph, target, source);
859
802
  let overlap;
860
- if (sourceDegree > 0 && targetDegree > 0) {
861
- let commonCount = 0;
862
- for (const n of sourceNeighbours) if (targetNeighbours.has(n)) commonCount++;
863
- overlap = commonCount / Math.min(sourceDegree, targetDegree);
803
+ if (sourceNeighbours.size > 0 && targetNeighbours.size > 0) {
804
+ const { intersection } = neighbourOverlap(sourceNeighbours, targetNeighbours);
805
+ const minDegree = Math.min(sourceNeighbours.size, targetNeighbours.size);
806
+ overlap = minDegree > 0 ? intersection / minDegree : epsilon;
864
807
  } else overlap = epsilon;
865
808
  const totalWeight = structuralWeight + degreeWeight + overlapWeight;
866
809
  const score = (structuralWeight * structural + degreeWeight * degreeComponent + overlapWeight * overlap) / totalWeight;
@@ -2221,6 +2164,6 @@ function filterSubgraph(graph, options) {
2221
2164
  return result;
2222
2165
  }
2223
2166
  //#endregion
2224
- export { AdjacencyMapGraph, GPUContext, GPUNotAvailableError, PriorityQueue, _computeMean, adamicAdar, adaptive, approximateClusteringCoefficient, assertWebGPUAvailable, base, batchClusteringCoefficients, betweenness, bfs, bfsWithPath, communicability, computeTrussNumbers, createGPUContext, createResultBuffer, csrToGPUBuffers, degreeSum, detectWebGPU, dfs, dfsWithPath, dome, domeHighDegree, edge, entropyFromCounts, enumerateMotifs, enumerateMotifsWithInstances, etch, extractEgoNetwork, extractInducedSubgraph, extractKCore, extractKTruss, filterSubgraph, frontierBalanced, getGPUContext, getMotifName, graphToCSR, grasp, hae, isWebGPUAvailable, jaccard, jaccardArithmetic, katz, localClusteringCoefficient, localTypeEntropy, maze, miniBatchKMeans, normaliseFeatures, normaliseFeatures as zScoreNormalise, normalisedEntropy, notch, pagerank, parse, pipe, randomPriority, randomRanking, reach, readBufferToCPU, resistanceDistance, sage, scale, shannonEntropy, shortest, skew, span, standardBfs, stratified, widestPath };
2167
+ export { AdjacencyMapGraph, GPUContext, GPUNotAvailableError, PriorityQueue, _computeMean, adamicAdar, adaptive, approximateClusteringCoefficient, assertWebGPUAvailable, base, batchClusteringCoefficients, betweenness, bfs, bfsWithPath, communicability, computeTrussNumbers, countEdgesOfType, countNodesOfType, createGPUContext, createResultBuffer, csrToGPUBuffers, degreeSum, detectWebGPU, dfs, dfsWithPath, dome, domeHighDegree, edge, entropyFromCounts, enumerateMotifs, enumerateMotifsWithInstances, etch, extractEgoNetwork, extractInducedSubgraph, extractKCore, extractKTruss, filterSubgraph, frontierBalanced, getGPUContext, getMotifName, graphToCSR, grasp, hae, isWebGPUAvailable, jaccard, jaccardArithmetic, katz, localClusteringCoefficient, localTypeEntropy, maze, miniBatchKMeans, neighbourIntersection, neighbourOverlap, neighbourSet, normaliseFeatures, normaliseFeatures as zScoreNormalise, normalisedEntropy, notch, pagerank, parse, pipe, randomPriority, randomRanking, reach, readBufferToCPU, resistanceDistance, sage, scale, shannonEntropy, shortest, skew, span, standardBfs, stratified, widestPath };
2225
2168
 
2226
2169
  //# sourceMappingURL=index.js.map