@graphty/algorithms 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -50
- package/dist/algorithms.js +15260 -10390
- package/dist/algorithms.js.map +1 -1
- package/dist/algorithms.standalone.js +26675 -21542
- package/dist/algorithms.standalone.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +18 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +40 -1
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +20 -0
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +93 -8
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +9 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +18 -0
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +29 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +9 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +23 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +9 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +31 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +13 -13
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +47 -10
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts +27 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js +335 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts +5 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +34 -308
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +9 -6
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +24 -110
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +28 -77
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +59 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +46 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +27 -0
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +15 -5
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +97 -107
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +2 -12
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +47 -308
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +5 -25
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +0 -3
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +0 -4
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +33 -154
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical-legacy.d.ts +56 -0
- package/dist/src/clustering/hierarchical-legacy.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical-legacy.js +441 -0
- package/dist/src/clustering/hierarchical-legacy.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +7 -39
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +56 -417
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/k-core-legacy.d.ts +30 -0
- package/dist/src/clustering/k-core-legacy.d.ts.map +1 -0
- package/dist/src/clustering/k-core-legacy.js +191 -0
- package/dist/src/clustering/k-core-legacy.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +8 -7
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +33 -174
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl-legacy.d.ts +42 -0
- package/dist/src/clustering/mcl-legacy.d.ts.map +1 -0
- package/dist/src/clustering/mcl-legacy.js +386 -0
- package/dist/src/clustering/mcl-legacy.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +9 -32
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +48 -374
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/index.d.ts +29 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexed/accelerator.d.ts +141 -11
- package/dist/src/indexed/accelerator.d.ts.map +1 -1
- package/dist/src/indexed/accelerator.js +319 -5
- package/dist/src/indexed/accelerator.js.map +1 -1
- package/dist/src/indexed/all-pairs.d.ts +59 -0
- package/dist/src/indexed/all-pairs.d.ts.map +1 -0
- package/dist/src/indexed/all-pairs.js +274 -0
- package/dist/src/indexed/all-pairs.js.map +1 -0
- package/dist/src/indexed/bellman-ford.d.ts +25 -0
- package/dist/src/indexed/bellman-ford.d.ts.map +1 -0
- package/dist/src/indexed/bellman-ford.js +59 -0
- package/dist/src/indexed/bellman-ford.js.map +1 -0
- package/dist/src/indexed/betweenness.d.ts +85 -0
- package/dist/src/indexed/betweenness.d.ts.map +1 -0
- package/dist/src/indexed/betweenness.js +188 -0
- package/dist/src/indexed/betweenness.js.map +1 -0
- package/dist/src/indexed/bfs.d.ts +69 -2
- package/dist/src/indexed/bfs.d.ts.map +1 -1
- package/dist/src/indexed/bfs.js +148 -1
- package/dist/src/indexed/bfs.js.map +1 -1
- package/dist/src/indexed/bipartite.d.ts +37 -0
- package/dist/src/indexed/bipartite.d.ts.map +1 -0
- package/dist/src/indexed/bipartite.js +54 -0
- package/dist/src/indexed/bipartite.js.map +1 -0
- package/dist/src/indexed/closeness.d.ts +49 -0
- package/dist/src/indexed/closeness.d.ts.map +1 -0
- package/dist/src/indexed/closeness.js +127 -0
- package/dist/src/indexed/closeness.js.map +1 -0
- package/dist/src/indexed/common-neighbors.d.ts +14 -1
- package/dist/src/indexed/common-neighbors.d.ts.map +1 -1
- package/dist/src/indexed/common-neighbors.js +24 -14
- package/dist/src/indexed/common-neighbors.js.map +1 -1
- package/dist/src/indexed/degree.d.ts +19 -0
- package/dist/src/indexed/degree.d.ts.map +1 -0
- package/dist/src/indexed/degree.js +45 -0
- package/dist/src/indexed/degree.js.map +1 -0
- package/dist/src/indexed/delta-pagerank.d.ts +145 -0
- package/dist/src/indexed/delta-pagerank.d.ts.map +1 -0
- package/dist/src/indexed/delta-pagerank.js +428 -0
- package/dist/src/indexed/delta-pagerank.js.map +1 -0
- package/dist/src/indexed/dfs.d.ts +55 -0
- package/dist/src/indexed/dfs.d.ts.map +1 -0
- package/dist/src/indexed/dfs.js +146 -0
- package/dist/src/indexed/dfs.js.map +1 -0
- package/dist/src/indexed/eigenvector.d.ts +48 -0
- package/dist/src/indexed/eigenvector.d.ts.map +1 -0
- package/dist/src/indexed/eigenvector.js +162 -0
- package/dist/src/indexed/eigenvector.js.map +1 -0
- package/dist/src/indexed/facade.d.ts +109 -0
- package/dist/src/indexed/facade.d.ts.map +1 -0
- package/dist/src/indexed/facade.js +204 -0
- package/dist/src/indexed/facade.js.map +1 -0
- package/dist/src/indexed/flow.d.ts +120 -0
- package/dist/src/indexed/flow.d.ts.map +1 -0
- package/dist/src/indexed/flow.js +377 -0
- package/dist/src/indexed/flow.js.map +1 -0
- package/dist/src/indexed/girvan-newman.d.ts +42 -0
- package/dist/src/indexed/girvan-newman.d.ts.map +1 -0
- package/dist/src/indexed/girvan-newman.js +90 -0
- package/dist/src/indexed/girvan-newman.js.map +1 -0
- package/dist/src/indexed/grsbm.d.ts +85 -0
- package/dist/src/indexed/grsbm.d.ts.map +1 -0
- package/dist/src/indexed/grsbm.js +287 -0
- package/dist/src/indexed/grsbm.js.map +1 -0
- package/dist/src/indexed/hierarchical.d.ts +69 -0
- package/dist/src/indexed/hierarchical.d.ts.map +1 -0
- package/dist/src/indexed/hierarchical.js +245 -0
- package/dist/src/indexed/hierarchical.js.map +1 -0
- package/dist/src/indexed/index.d.ts +29 -3
- package/dist/src/indexed/index.d.ts.map +1 -1
- package/dist/src/indexed/index.js +29 -3
- package/dist/src/indexed/index.js.map +1 -1
- package/dist/src/indexed/isomorphism.d.ts +42 -0
- package/dist/src/indexed/isomorphism.d.ts.map +1 -0
- package/dist/src/indexed/isomorphism.js +269 -0
- package/dist/src/indexed/isomorphism.js.map +1 -0
- package/dist/src/indexed/label-propagation.d.ts +134 -0
- package/dist/src/indexed/label-propagation.d.ts.map +1 -0
- package/dist/src/indexed/label-propagation.js +486 -0
- package/dist/src/indexed/label-propagation.js.map +1 -0
- package/dist/src/indexed/leiden.d.ts +52 -0
- package/dist/src/indexed/leiden.d.ts.map +1 -0
- package/dist/src/indexed/leiden.js +384 -0
- package/dist/src/indexed/leiden.js.map +1 -0
- package/dist/src/indexed/link-prediction.d.ts +147 -0
- package/dist/src/indexed/link-prediction.d.ts.map +1 -0
- package/dist/src/indexed/link-prediction.js +317 -0
- package/dist/src/indexed/link-prediction.js.map +1 -0
- package/dist/src/indexed/markov.d.ts +54 -0
- package/dist/src/indexed/markov.d.ts.map +1 -0
- package/dist/src/indexed/markov.js +284 -0
- package/dist/src/indexed/markov.js.map +1 -0
- package/dist/src/indexed/matching.d.ts +53 -0
- package/dist/src/indexed/matching.d.ts.map +1 -0
- package/dist/src/indexed/matching.js +139 -0
- package/dist/src/indexed/matching.js.map +1 -0
- package/dist/src/indexed/min-cut.d.ts +48 -0
- package/dist/src/indexed/min-cut.d.ts.map +1 -0
- package/dist/src/indexed/min-cut.js +175 -0
- package/dist/src/indexed/min-cut.js.map +1 -0
- package/dist/src/indexed/modularity.d.ts +34 -0
- package/dist/src/indexed/modularity.d.ts.map +1 -0
- package/dist/src/indexed/modularity.js +74 -0
- package/dist/src/indexed/modularity.js.map +1 -0
- package/dist/src/indexed/mst.d.ts +29 -1
- package/dist/src/indexed/mst.d.ts.map +1 -1
- package/dist/src/indexed/mst.js +62 -0
- package/dist/src/indexed/mst.js.map +1 -1
- package/dist/src/indexed/pagerank.d.ts +29 -6
- package/dist/src/indexed/pagerank.d.ts.map +1 -1
- package/dist/src/indexed/pagerank.js +107 -11
- package/dist/src/indexed/pagerank.js.map +1 -1
- package/dist/src/indexed/point-to-point.d.ts +53 -0
- package/dist/src/indexed/point-to-point.d.ts.map +1 -0
- package/dist/src/indexed/point-to-point.js +173 -0
- package/dist/src/indexed/point-to-point.js.map +1 -0
- package/dist/src/indexed/scc.d.ts +53 -0
- package/dist/src/indexed/scc.d.ts.map +1 -0
- package/dist/src/indexed/scc.js +110 -0
- package/dist/src/indexed/scc.js.map +1 -0
- package/dist/src/indexed/spectral.d.ts +61 -0
- package/dist/src/indexed/spectral.d.ts.map +1 -0
- package/dist/src/indexed/spectral.js +476 -0
- package/dist/src/indexed/spectral.js.map +1 -0
- package/dist/src/indexed/structures/bit-set.d.ts +45 -0
- package/dist/src/indexed/structures/bit-set.d.ts.map +1 -0
- package/dist/src/indexed/structures/bit-set.js +57 -0
- package/dist/src/indexed/structures/bit-set.js.map +1 -0
- package/dist/src/indexed/structures/max-heap.d.ts +52 -0
- package/dist/src/indexed/structures/max-heap.d.ts.map +1 -0
- package/dist/src/indexed/structures/max-heap.js +66 -0
- package/dist/src/indexed/structures/max-heap.js.map +1 -0
- package/dist/src/indexed/structures/min-heap.d.ts +30 -1
- package/dist/src/indexed/structures/min-heap.d.ts.map +1 -1
- package/dist/src/indexed/structures/min-heap.js +46 -6
- package/dist/src/indexed/structures/min-heap.js.map +1 -1
- package/dist/src/indexed/structures/ring-queue.d.ts +41 -0
- package/dist/src/indexed/structures/ring-queue.d.ts.map +1 -0
- package/dist/src/indexed/structures/ring-queue.js +64 -0
- package/dist/src/indexed/structures/ring-queue.js.map +1 -0
- package/dist/src/indexed/sync.d.ts +50 -0
- package/dist/src/indexed/sync.d.ts.map +1 -0
- package/dist/src/indexed/sync.js +175 -0
- package/dist/src/indexed/sync.js.map +1 -0
- package/dist/src/indexed/terahac.d.ts +58 -0
- package/dist/src/indexed/terahac.d.ts.map +1 -0
- package/dist/src/indexed/terahac.js +244 -0
- package/dist/src/indexed/terahac.js.map +1 -0
- package/dist/src/indexed/to-snapshot.d.ts +45 -2
- package/dist/src/indexed/to-snapshot.d.ts.map +1 -1
- package/dist/src/indexed/to-snapshot.js +140 -6
- package/dist/src/indexed/to-snapshot.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts +71 -0
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js +172 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +11 -19
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +56 -123
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +6 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +6 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/research/grsbm-legacy.d.ts +82 -0
- package/dist/src/research/grsbm-legacy.d.ts.map +1 -0
- package/dist/src/research/grsbm-legacy.js +416 -0
- package/dist/src/research/grsbm-legacy.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +9 -66
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +67 -391
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/sync-legacy.d.ts +48 -0
- package/dist/src/research/sync-legacy.d.ts.map +1 -0
- package/dist/src/research/sync-legacy.js +337 -0
- package/dist/src/research/sync-legacy.js.map +1 -0
- package/dist/src/research/sync.d.ts +10 -32
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +28 -316
- package/dist/src/research/sync.js.map +1 -1
- package/package.json +4 -3
- package/src/algorithms/centrality/betweenness.ts +51 -1
- package/src/algorithms/centrality/closeness.ts +112 -12
- package/src/algorithms/centrality/degree.ts +19 -0
- package/src/algorithms/centrality/eigenvector.ts +34 -0
- package/src/algorithms/centrality/hits.ts +24 -0
- package/src/algorithms/centrality/katz.ts +32 -0
- package/src/algorithms/centrality/pagerank.ts +53 -13
- package/src/algorithms/community/girvan-newman-legacy.ts +414 -0
- package/src/algorithms/community/girvan-newman.ts +36 -386
- package/src/algorithms/community/label-propagation.ts +24 -133
- package/src/algorithms/components/connected.ts +28 -95
- package/src/algorithms/mst/kruskal.ts +65 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +49 -0
- package/src/algorithms/shortest-path/dijkstra.ts +29 -0
- package/src/algorithms/shortest-path/floyd-warshall.ts +106 -136
- package/src/algorithms/traversal/bfs-unified.ts +52 -366
- package/src/algorithms/traversal/bfs-variants.ts +5 -28
- package/src/algorithms/traversal/bfs.ts +0 -4
- package/src/algorithms/traversal/dfs.ts +39 -182
- package/src/clustering/hierarchical-legacy.ts +551 -0
- package/src/clustering/hierarchical.ts +64 -520
- package/src/clustering/k-core-legacy.ts +229 -0
- package/src/clustering/k-core.ts +38 -209
- package/src/clustering/mcl-legacy.ts +498 -0
- package/src/clustering/mcl.ts +46 -479
- package/src/index.ts +90 -3
- package/src/indexed/accelerator.ts +502 -16
- package/src/indexed/all-pairs.ts +342 -0
- package/src/indexed/bellman-ford.ts +72 -0
- package/src/indexed/betweenness.ts +258 -0
- package/src/indexed/bfs.ts +189 -3
- package/src/indexed/bipartite.ts +77 -0
- package/src/indexed/closeness.ts +160 -0
- package/src/indexed/common-neighbors.ts +31 -14
- package/src/indexed/degree.ts +55 -0
- package/src/indexed/delta-pagerank.ts +513 -0
- package/src/indexed/dfs.ts +189 -0
- package/src/indexed/eigenvector.ts +202 -0
- package/src/indexed/facade.ts +229 -0
- package/src/indexed/flow.ts +500 -0
- package/src/indexed/girvan-newman.ts +120 -0
- package/src/indexed/grsbm.ts +390 -0
- package/src/indexed/hierarchical.ts +295 -0
- package/src/indexed/index.ts +94 -3
- package/src/indexed/isomorphism.ts +305 -0
- package/src/indexed/label-propagation.ts +582 -0
- package/src/indexed/leiden.ts +436 -0
- package/src/indexed/link-prediction.ts +399 -0
- package/src/indexed/markov.ts +324 -0
- package/src/indexed/matching.ts +192 -0
- package/src/indexed/min-cut.ts +201 -0
- package/src/indexed/modularity.ts +81 -0
- package/src/indexed/mst.ts +79 -1
- package/src/indexed/pagerank.ts +128 -15
- package/src/indexed/point-to-point.ts +220 -0
- package/src/indexed/scc.ts +132 -0
- package/src/indexed/spectral.ts +550 -0
- package/src/indexed/structures/bit-set.ts +65 -0
- package/src/indexed/structures/max-heap.ts +74 -0
- package/src/indexed/structures/min-heap.ts +51 -6
- package/src/indexed/structures/ring-queue.ts +70 -0
- package/src/indexed/sync.ts +229 -0
- package/src/indexed/terahac.ts +290 -0
- package/src/indexed/to-snapshot.ts +156 -6
- package/src/link-prediction/common-neighbors-legacy.ts +252 -0
- package/src/link-prediction/common-neighbors.ts +65 -168
- package/src/optimized/direction-optimized-bfs.ts +6 -1
- package/src/research/grsbm-legacy.ts +586 -0
- package/src/research/grsbm.ts +76 -555
- package/src/research/sync-legacy.ts +456 -0
- package/src/research/sync.ts +30 -431
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { GraphBuilder, type GraphSnapshot } from "@graphty/graph-format";
|
|
9
|
+
import { GraphBuilder, GraphFormatError, type GraphSnapshot, type U32 } from "@graphty/graph-format";
|
|
10
10
|
|
|
11
11
|
import type { Graph } from "../core/graph.js";
|
|
12
12
|
|
|
@@ -24,6 +24,27 @@ interface CacheEntry {
|
|
|
24
24
|
readonly mutationCount: number;
|
|
25
25
|
readonly checksum: boolean;
|
|
26
26
|
readonly snapshot: GraphSnapshot;
|
|
27
|
+
/** Every edge's weight in `graph.edges()` order, as frozen. */
|
|
28
|
+
readonly weights: readonly (number | undefined)[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether every edge still carries the weight it was frozen with. `mutationCount` counts topology
|
|
33
|
+
* changes only, and `graph.getEdge(u, v)` hands out the live edge, so a weight can change in place
|
|
34
|
+
* without moving the counter.
|
|
35
|
+
* @param graph - The legacy graph
|
|
36
|
+
* @param weights - The weights recorded at freeze
|
|
37
|
+
* @returns True when no weight changed
|
|
38
|
+
*/
|
|
39
|
+
function sameWeights(graph: Graph, weights: readonly (number | undefined)[]): boolean {
|
|
40
|
+
let i = 0;
|
|
41
|
+
for (const edge of graph.edges()) {
|
|
42
|
+
// Object.is, so a NaN weight (kept in the topology cache) matches itself.
|
|
43
|
+
if (!Object.is(edge.weight, weights[i++])) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return i === weights.length;
|
|
27
48
|
}
|
|
28
49
|
|
|
29
50
|
/**
|
|
@@ -34,7 +55,8 @@ interface CacheEntry {
|
|
|
34
55
|
const SNAPSHOT_CACHE = new WeakMap<Graph, CacheEntry>();
|
|
35
56
|
|
|
36
57
|
/**
|
|
37
|
-
* Freeze a legacy `Graph` into a `GraphSnapshot`, memoised on the graph's `mutationCount
|
|
58
|
+
* Freeze a legacy `Graph` into a `GraphSnapshot`, memoised on the graph's `mutationCount` and its
|
|
59
|
+
* edge weights (a weight set in place does not move the counter, so a hit re-reads the weights).
|
|
38
60
|
*
|
|
39
61
|
* The builder is created with `weightDtype: "f64"` so a legacy graph's double weights survive
|
|
40
62
|
* exactly: at freeze, graph-format keeps the original values in an f64 edge column with role
|
|
@@ -53,11 +75,33 @@ const SNAPSHOT_CACHE = new WeakMap<Graph, CacheEntry>();
|
|
|
53
75
|
*/
|
|
54
76
|
export function toSnapshot(graph: Graph, options: ToSnapshotOptions = {}): GraphSnapshot {
|
|
55
77
|
const checksum = options.checksum === true;
|
|
56
|
-
|
|
78
|
+
return cachedFreeze(SNAPSHOT_CACHE, graph, checksum, true);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Freeze `graph` through `cache`, reusing the entry while the graph is unchanged.
|
|
83
|
+
* @param cache - The cache to read and replace the entry in
|
|
84
|
+
* @param graph - The legacy graph
|
|
85
|
+
* @param checksum - Whether to record checksums
|
|
86
|
+
* @param weighted - Whether to keep the edge weights
|
|
87
|
+
* @returns The snapshot
|
|
88
|
+
*/
|
|
89
|
+
function cachedFreeze(
|
|
90
|
+
cache: WeakMap<Graph, CacheEntry>,
|
|
91
|
+
graph: Graph,
|
|
92
|
+
checksum: boolean,
|
|
93
|
+
weighted: boolean,
|
|
94
|
+
): GraphSnapshot {
|
|
95
|
+
const cached = cache.get(graph);
|
|
57
96
|
// A checksummed snapshot answers a plain request; a plain one cannot answer a checksummed
|
|
58
97
|
// request -- validate({ checksum: true }) throws E_INVALID_SNAPSHOT ("no-checksum") when none
|
|
59
98
|
// were recorded (graph-format/src/types/snapshot.ts:401-405).
|
|
60
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
cached !== undefined &&
|
|
101
|
+
cached.mutationCount === graph.mutationCount &&
|
|
102
|
+
(cached.checksum || !checksum) &&
|
|
103
|
+
sameWeights(graph, cached.weights)
|
|
104
|
+
) {
|
|
61
105
|
return cached.snapshot;
|
|
62
106
|
}
|
|
63
107
|
const builder = new GraphBuilder({
|
|
@@ -69,10 +113,116 @@ export function toSnapshot(graph: Graph, options: ToSnapshotOptions = {}): Graph
|
|
|
69
113
|
for (const node of graph.nodes()) {
|
|
70
114
|
builder.addNode(node.id);
|
|
71
115
|
}
|
|
116
|
+
const weights: (number | undefined)[] = [];
|
|
72
117
|
for (const edge of graph.edges()) {
|
|
73
|
-
builder.addEdge(edge.source, edge.target, edge.weight);
|
|
118
|
+
builder.addEdge(edge.source, edge.target, weighted ? edge.weight : undefined);
|
|
119
|
+
weights.push(edge.weight);
|
|
74
120
|
}
|
|
75
121
|
const snapshot = builder.freeze({ label: "algorithms.toSnapshot", checksum });
|
|
76
|
-
|
|
122
|
+
cache.set(graph, { mutationCount: graph.mutationCount, checksum, snapshot, weights });
|
|
77
123
|
return snapshot;
|
|
78
124
|
}
|
|
125
|
+
|
|
126
|
+
/** Snapshots frozen without weights, for graphs holding a NaN weight; see {@link toTopologySnapshot}. */
|
|
127
|
+
const TOPOLOGY_CACHE = new WeakMap<Graph, CacheEntry>();
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* {@link toSnapshot}, or null when an edge weight is NaN: a legacy `Graph` accepts one, a snapshot
|
|
131
|
+
* does not. A facade whose legacy answer depends on how NaN compares keeps its legacy code for such
|
|
132
|
+
* a graph.
|
|
133
|
+
* @param graph - The legacy graph
|
|
134
|
+
* @param options - Conversion options
|
|
135
|
+
* @returns The snapshot, or null
|
|
136
|
+
*/
|
|
137
|
+
export function toSnapshotOrNull(graph: Graph, options: ToSnapshotOptions = {}): GraphSnapshot | null {
|
|
138
|
+
try {
|
|
139
|
+
return toSnapshot(graph, options);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof GraphFormatError && error.code === "E_INVALID_WEIGHT") {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A snapshot for a port that reads no weights (a traversal, the components, a clustering or link
|
|
150
|
+
* prediction that counts neighbours): {@link toSnapshot}, or,
|
|
151
|
+
* when a NaN weight stops that, the same nodes and edges frozen without weights, so a graph with a
|
|
152
|
+
* NaN weight traverses as it always did.
|
|
153
|
+
* @param graph - The legacy graph
|
|
154
|
+
* @returns The snapshot; its weights are not to be read
|
|
155
|
+
*/
|
|
156
|
+
export function toTopologySnapshot(graph: Graph): GraphSnapshot {
|
|
157
|
+
return toSnapshotOrNull(graph) ?? cachedFreeze(TOPOLOGY_CACHE, graph, false, false);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The order a legacy `Graph` hands out each node's neighbours in, as the `arcOrder` option of the
|
|
162
|
+
* order-sensitive traversals (`depthFirstSearch`, `topologicalSort`, `stronglyConnectedComponents`,
|
|
163
|
+
* `condensation`, `breadthFirstSearch`). A snapshot row is sorted by neighbour index, while the
|
|
164
|
+
* legacy adjacency Map is in insertion order, so a traversal that must visit nodes in the legacy
|
|
165
|
+
* order -- a facade keeping its published result -- passes this.
|
|
166
|
+
* @param graph - The legacy graph
|
|
167
|
+
* @param s - A snapshot with the graph's nodes in the same order and its neighbour pairs, such as
|
|
168
|
+
* `toSnapshot(graph)`; parallel arcs of one pair are kept together in row order
|
|
169
|
+
* @returns For every row, its arcs in legacy neighbour order
|
|
170
|
+
*/
|
|
171
|
+
export function legacyArcOrder(graph: Graph, s: GraphSnapshot): U32 {
|
|
172
|
+
const order = new Uint32Array(s.arcCount);
|
|
173
|
+
let next = 0;
|
|
174
|
+
for (const node of graph.nodes()) {
|
|
175
|
+
const u = s.ids.requireIndex(node.id);
|
|
176
|
+
if (next !== s.rowPtr[u]) {
|
|
177
|
+
throw new Error("legacyArcOrder: the snapshot does not hold this graph's nodes and neighbours");
|
|
178
|
+
}
|
|
179
|
+
for (const neighbor of graph.neighbors(node.id)) {
|
|
180
|
+
// Every arc to this neighbour, in row order: the legacy graph holds one edge per pair,
|
|
181
|
+
// but a multigraph snapshot of it may hold several.
|
|
182
|
+
const [lo, hi] = s.arcsBetween(u, s.ids.requireIndex(neighbor));
|
|
183
|
+
for (let a = lo; a < hi; a++) {
|
|
184
|
+
order[next++] = a;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (next !== s.rowPtr[u + 1]) {
|
|
188
|
+
throw new Error("legacyArcOrder: the snapshot does not hold this graph's nodes and neighbours");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return order;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const LEGACY_ONLY_CACHE = new WeakMap<Graph, { readonly mutationCount: number; readonly value: boolean }>();
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Whether a facade must run its legacy code on this graph because a snapshot cannot stand in for it:
|
|
198
|
+
* - an edge weight is NaN, which the graph-format builder rejects, so `toSnapshot` would throw where
|
|
199
|
+
* the legacy code returns a result (the unweighted functions never even read the weight);
|
|
200
|
+
* - two node ids share a spelling (the number 1 and the string "1"), so a result keyed by
|
|
201
|
+
* `String(id)` holds one entry for both, and which value it holds depends on how the legacy code
|
|
202
|
+
* wrote it (betweenness adds the two, others keep the last).
|
|
203
|
+
* Memoised on the graph's `mutationCount`, like {@link toSnapshot}.
|
|
204
|
+
* @param graph - The legacy graph
|
|
205
|
+
* @returns True when the facade must call its legacy implementation
|
|
206
|
+
*/
|
|
207
|
+
export function needsLegacyCode(graph: Graph): boolean {
|
|
208
|
+
const cached = LEGACY_ONLY_CACHE.get(graph);
|
|
209
|
+
if (cached?.mutationCount === graph.mutationCount) {
|
|
210
|
+
return cached.value;
|
|
211
|
+
}
|
|
212
|
+
let value = false;
|
|
213
|
+
for (const edge of graph.edges()) {
|
|
214
|
+
if (Number.isNaN(edge.weight)) {
|
|
215
|
+
value = true;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!value) {
|
|
220
|
+
const spellings = new Set<string>();
|
|
221
|
+
for (const node of graph.nodes()) {
|
|
222
|
+
spellings.add(String(node.id));
|
|
223
|
+
}
|
|
224
|
+
value = spellings.size !== graph.nodeCount;
|
|
225
|
+
}
|
|
226
|
+
LEGACY_ONLY_CACHE.set(graph, { mutationCount: graph.mutationCount, value });
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
|
+
import { getCommonNeighbors, getIntermediateNodes } from "../utils/graph-utilities.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Common Neighbors link prediction implementation
|
|
7
|
+
*
|
|
8
|
+
* Predicts the likelihood of a link between two nodes based on the number
|
|
9
|
+
* of common neighbors they share. The intuition is that nodes with many
|
|
10
|
+
* common neighbors are more likely to be connected.
|
|
11
|
+
*
|
|
12
|
+
* Time complexity: O(k²) where k is the average degree
|
|
13
|
+
* Space complexity: O(k)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface LinkPredictionScore {
|
|
17
|
+
source: NodeId;
|
|
18
|
+
target: NodeId;
|
|
19
|
+
score: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LinkPredictionOptions {
|
|
23
|
+
directed?: boolean; // Consider direction (default: false)
|
|
24
|
+
includeExisting?: boolean; // Include existing edges (default: false)
|
|
25
|
+
topK?: number; // Return only top K predictions
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculate common neighbors score for a pair of nodes
|
|
30
|
+
* @param graph - The input graph
|
|
31
|
+
* @param source - The source node ID
|
|
32
|
+
* @param target - The target node ID
|
|
33
|
+
* @param options - Link prediction options
|
|
34
|
+
* @returns The number of common neighbors
|
|
35
|
+
*/
|
|
36
|
+
export function commonNeighborsScore(
|
|
37
|
+
graph: Graph,
|
|
38
|
+
source: NodeId,
|
|
39
|
+
target: NodeId,
|
|
40
|
+
options: LinkPredictionOptions = {},
|
|
41
|
+
): number {
|
|
42
|
+
if (!graph.hasNode(source) || !graph.hasNode(target)) {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { directed = false } = options;
|
|
47
|
+
|
|
48
|
+
// Use utility function to get common neighbors
|
|
49
|
+
// For directed graphs, we want intermediate nodes that form paths source->X->target
|
|
50
|
+
const commonNeighborsSet = directed
|
|
51
|
+
? getIntermediateNodes(graph, source, target)
|
|
52
|
+
: getCommonNeighbors(graph, source, target, false);
|
|
53
|
+
|
|
54
|
+
return commonNeighborsSet.size;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Calculate common neighbors scores for all possible node pairs
|
|
59
|
+
* @param graph - The input graph
|
|
60
|
+
* @param options - Link prediction options
|
|
61
|
+
* @returns Array of link prediction scores sorted by score descending
|
|
62
|
+
*/
|
|
63
|
+
export function commonNeighborsPrediction(graph: Graph, options: LinkPredictionOptions = {}): LinkPredictionScore[] {
|
|
64
|
+
const { directed = false, includeExisting = false, topK } = options;
|
|
65
|
+
|
|
66
|
+
const scores: LinkPredictionScore[] = [];
|
|
67
|
+
const nodes = Array.from(graph.nodes()).map((n) => n.id);
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
70
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
71
|
+
const source = nodes[i];
|
|
72
|
+
const target = nodes[j];
|
|
73
|
+
|
|
74
|
+
if (source === undefined || target === undefined) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Skip existing edges unless requested
|
|
79
|
+
if (!includeExisting && graph.hasEdge(source, target)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const score = commonNeighborsScore(graph, source, target, { directed });
|
|
84
|
+
|
|
85
|
+
if (score > 0) {
|
|
86
|
+
scores.push({ source, target, score });
|
|
87
|
+
|
|
88
|
+
// For undirected graphs, also add the reverse pair
|
|
89
|
+
if (!directed && source !== target) {
|
|
90
|
+
scores.push({ source: target, target: source, score });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Sort by score in descending order
|
|
97
|
+
scores.sort((a, b) => b.score - a.score);
|
|
98
|
+
|
|
99
|
+
// Return top K if specified
|
|
100
|
+
if (topK && topK > 0) {
|
|
101
|
+
return scores.slice(0, topK);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return scores;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculate common neighbors scores for specific node pairs
|
|
109
|
+
* @param graph - The input graph
|
|
110
|
+
* @param pairs - Array of node ID pairs to calculate scores for
|
|
111
|
+
* @param options - Link prediction options
|
|
112
|
+
* @returns Array of link prediction scores for the specified pairs
|
|
113
|
+
*/
|
|
114
|
+
export function commonNeighborsForPairs(
|
|
115
|
+
graph: Graph,
|
|
116
|
+
pairs: [NodeId, NodeId][],
|
|
117
|
+
options: LinkPredictionOptions = {},
|
|
118
|
+
): LinkPredictionScore[] {
|
|
119
|
+
return pairs.map(([source, target]) => ({
|
|
120
|
+
source,
|
|
121
|
+
target,
|
|
122
|
+
score: commonNeighborsScore(graph, source, target, options),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get top candidates for link prediction for a specific node
|
|
128
|
+
* @param graph - The input graph
|
|
129
|
+
* @param node - The node ID to get candidates for
|
|
130
|
+
* @param options - Link prediction options with optional candidate list
|
|
131
|
+
* @returns Array of top link prediction candidates sorted by score descending
|
|
132
|
+
*/
|
|
133
|
+
export function getTopCandidatesForNode(
|
|
134
|
+
graph: Graph,
|
|
135
|
+
node: NodeId,
|
|
136
|
+
options: LinkPredictionOptions & { candidates?: NodeId[] } = {},
|
|
137
|
+
): LinkPredictionScore[] {
|
|
138
|
+
if (!graph.hasNode(node)) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { directed = false, includeExisting = false, topK = 10, candidates } = options;
|
|
143
|
+
|
|
144
|
+
const scores: LinkPredictionScore[] = [];
|
|
145
|
+
const targetNodes = candidates ?? Array.from(graph.nodes()).map((n) => n.id);
|
|
146
|
+
|
|
147
|
+
for (const target of targetNodes) {
|
|
148
|
+
if (target === node) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Skip existing edges unless requested
|
|
153
|
+
if (!includeExisting && graph.hasEdge(node, target)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const score = commonNeighborsScore(graph, node, target, { directed });
|
|
158
|
+
|
|
159
|
+
if (score > 0) {
|
|
160
|
+
scores.push({ source: node, target, score });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Sort by score in descending order
|
|
165
|
+
scores.sort((a, b) => b.score - a.score);
|
|
166
|
+
|
|
167
|
+
return scores.slice(0, topK);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Calculate precision and recall for link prediction evaluation
|
|
172
|
+
* @param trainingGraph - The training graph without test edges
|
|
173
|
+
* @param testEdges - Array of node pairs that are actual edges
|
|
174
|
+
* @param nonEdges - Array of node pairs that are not edges
|
|
175
|
+
* @param options - Link prediction options
|
|
176
|
+
* @returns Evaluation metrics including precision, recall, F1 score, and AUC
|
|
177
|
+
*/
|
|
178
|
+
export function evaluateCommonNeighbors(
|
|
179
|
+
trainingGraph: Graph,
|
|
180
|
+
testEdges: [NodeId, NodeId][],
|
|
181
|
+
nonEdges: [NodeId, NodeId][],
|
|
182
|
+
options: LinkPredictionOptions = {},
|
|
183
|
+
): {
|
|
184
|
+
precision: number;
|
|
185
|
+
recall: number;
|
|
186
|
+
f1Score: number;
|
|
187
|
+
auc: number;
|
|
188
|
+
} {
|
|
189
|
+
// Get scores for test edges and non-edges
|
|
190
|
+
const testScores = commonNeighborsForPairs(trainingGraph, testEdges, options);
|
|
191
|
+
const nonEdgeScores = commonNeighborsForPairs(trainingGraph, nonEdges, options);
|
|
192
|
+
|
|
193
|
+
// Combine and sort all scores
|
|
194
|
+
const allScores = [
|
|
195
|
+
...testScores.map((s) => ({ ...s, isActualEdge: true })),
|
|
196
|
+
...nonEdgeScores.map((s) => ({ ...s, isActualEdge: false })),
|
|
197
|
+
].sort((a, b) => b.score - a.score);
|
|
198
|
+
|
|
199
|
+
// Calculate precision and recall at different thresholds
|
|
200
|
+
let truePositives = 0;
|
|
201
|
+
let falsePositives = 0;
|
|
202
|
+
let bestF1 = 0;
|
|
203
|
+
let bestPrecision = 0;
|
|
204
|
+
let bestRecall = 0;
|
|
205
|
+
|
|
206
|
+
const totalPositives = testEdges.length;
|
|
207
|
+
|
|
208
|
+
for (const scoreItem of allScores) {
|
|
209
|
+
if (scoreItem.isActualEdge) {
|
|
210
|
+
truePositives++;
|
|
211
|
+
} else {
|
|
212
|
+
falsePositives++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const precision = truePositives / (truePositives + falsePositives);
|
|
216
|
+
const recall = truePositives / totalPositives;
|
|
217
|
+
const f1 = precision + recall > 0 ? (2 * (precision * recall)) / (precision + recall) : 0;
|
|
218
|
+
|
|
219
|
+
if (f1 > bestF1) {
|
|
220
|
+
bestF1 = f1;
|
|
221
|
+
bestPrecision = precision;
|
|
222
|
+
bestRecall = recall;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Calculate AUC (Area Under Curve)
|
|
227
|
+
let auc = 0;
|
|
228
|
+
let tpCount = 0;
|
|
229
|
+
let fpCount = 0;
|
|
230
|
+
|
|
231
|
+
for (const item of allScores) {
|
|
232
|
+
if (item.isActualEdge) {
|
|
233
|
+
tpCount++;
|
|
234
|
+
} else {
|
|
235
|
+
auc += tpCount;
|
|
236
|
+
fpCount++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (tpCount > 0 && fpCount > 0) {
|
|
241
|
+
auc = auc / (tpCount * fpCount);
|
|
242
|
+
} else {
|
|
243
|
+
auc = 0.5; // Random performance
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
precision: bestPrecision,
|
|
248
|
+
recall: bestRecall,
|
|
249
|
+
f1Score: bestF1,
|
|
250
|
+
auc,
|
|
251
|
+
};
|
|
252
|
+
}
|