@graphty/algorithms 1.2.0 → 1.4.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/README.md +375 -276
- package/dist/algorithms.d.ts +42 -2
- package/dist/algorithms.js +11402 -2
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +14 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +144 -169
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +35 -1
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +70 -149
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +7 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +12 -9
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +57 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +304 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +106 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js +320 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -3
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +11 -4
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +10 -3
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +11 -4
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/index.d.ts +3 -1
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +2 -1
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +10 -3
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +13 -8
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +31 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +61 -1
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +2 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +67 -57
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/index.d.ts +2 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -1
- package/dist/src/algorithms/community/index.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +11 -4
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +60 -38
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +3 -3
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +70 -44
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +149 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain-optimized.js +338 -0
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +2 -3
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +35 -81
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +55 -0
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
- package/dist/src/algorithms/community/modularity-utils.js +108 -0
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
- package/dist/src/algorithms/components/connected.d.ts +23 -0
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +29 -3
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +10 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +18 -40
- package/dist/src/algorithms/matching/bipartite.js.map +1 -1
- package/dist/src/algorithms/matching/index.d.ts.map +1 -1
- package/dist/src/algorithms/matching/index.js.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.d.ts +8 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.js +36 -2
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -1
- package/dist/src/algorithms/mst/index.d.ts.map +1 -1
- package/dist/src/algorithms/mst/index.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts +14 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +17 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/mst/prim.d.ts +9 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -1
- package/dist/src/algorithms/mst/prim.js +9 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +11 -12
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +44 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +195 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +20 -2
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +30 -14
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +42 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-unified.js +370 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +77 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.js +402 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +5 -182
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts +10 -0
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +48 -0
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/algorithms/traversal/index.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/index.js.map +1 -1
- package/dist/src/benchmark-all-algorithms.d.ts +25 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
- package/dist/src/benchmark-all-algorithms.js +491 -0
- package/dist/src/benchmark-all-algorithms.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +23 -15
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +226 -224
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts +4 -2
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js +2 -2
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +17 -19
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +68 -30
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl.d.ts +6 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +34 -3
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +11 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +99 -47
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.d.ts +45 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +49 -5
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/index.d.ts.map +1 -1
- package/dist/src/data-structures/index.js.map +1 -1
- package/dist/src/data-structures/priority-queue.d.ts +18 -1
- package/dist/src/data-structures/priority-queue.d.ts.map +1 -1
- package/dist/src/data-structures/priority-queue.js +26 -5
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.d.ts +21 -0
- package/dist/src/data-structures/union-find.d.ts.map +1 -1
- package/dist/src/data-structures/union-find.js +21 -0
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +20 -12
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +170 -217
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +6 -8
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +34 -12
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +26 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +47 -30
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts +21 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +31 -14
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/link-prediction/index.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +193 -0
- package/dist/src/optimized/bit-packed.d.ts.map +1 -0
- package/dist/src/optimized/bit-packed.js +342 -0
- package/dist/src/optimized/bit-packed.js.map +1 -0
- package/dist/src/optimized/csr-graph.d.ts +149 -0
- package/dist/src/optimized/csr-graph.d.ts.map +1 -0
- package/dist/src/optimized/csr-graph.js +404 -0
- package/dist/src/optimized/csr-graph.js.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts +95 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.js +242 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
- package/dist/src/optimized/graph-adapter.d.ts +111 -0
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
- package/dist/src/optimized/graph-adapter.js +194 -0
- package/dist/src/optimized/graph-adapter.js.map +1 -0
- package/dist/src/optimized/index.d.ts +14 -0
- package/dist/src/optimized/index.d.ts.map +1 -0
- package/dist/src/optimized/index.js +30 -0
- package/dist/src/optimized/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +25 -9
- package/dist/src/pathfinding/astar.d.ts.map +1 -1
- package/dist/src/pathfinding/astar.js +25 -10
- package/dist/src/pathfinding/astar.js.map +1 -1
- package/dist/src/pathfinding/utils.d.ts +15 -6
- package/dist/src/pathfinding/utils.d.ts.map +1 -1
- package/dist/src/pathfinding/utils.js +19 -7
- package/dist/src/pathfinding/utils.js.map +1 -1
- package/dist/src/research/grsbm.d.ts +0 -1
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +28 -23
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/index.d.ts.map +1 -1
- package/dist/src/research/index.js.map +1 -1
- package/dist/src/research/sync.d.ts +0 -1
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +31 -36
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +2 -1
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +39 -7
- package/dist/src/research/terahac.js.map +1 -1
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/algorithm-utilities.d.ts +2 -0
- package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
- package/dist/src/utils/algorithm-utilities.js +2 -0
- package/dist/src/utils/algorithm-utilities.js.map +1 -0
- package/dist/src/utils/graph-converters.d.ts +110 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -0
- package/dist/src/utils/graph-converters.js +227 -0
- package/dist/src/utils/graph-converters.js.map +1 -0
- package/dist/src/utils/graph-utilities.d.ts +62 -0
- package/dist/src/utils/graph-utilities.d.ts.map +1 -0
- package/dist/src/utils/graph-utilities.js +148 -0
- package/dist/src/utils/graph-utilities.js.map +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +5 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/math-utilities.d.ts +63 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -0
- package/dist/src/utils/math-utilities.js +132 -0
- package/dist/src/utils/math-utilities.js.map +1 -0
- package/dist/src/utils/matrix-utilities.d.ts +2 -0
- package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
- package/dist/src/utils/matrix-utilities.js +2 -0
- package/dist/src/utils/matrix-utilities.js.map +1 -0
- package/dist/src/utils/optimization-helpers.d.ts +42 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
- package/dist/src/utils/optimization-helpers.js +59 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +24 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -1
- package/dist/src/utils/priorityQueue.js +34 -4
- package/dist/src/utils/priorityQueue.js.map +1 -1
- package/package.json +58 -39
- package/src/algorithms/centrality/betweenness.ts +185 -200
- package/src/algorithms/centrality/closeness.ts +93 -194
- package/src/algorithms/centrality/degree.ts +18 -22
- package/src/algorithms/centrality/delta-pagerank-simple.ts +374 -0
- package/src/algorithms/centrality/delta-pagerank.ts +458 -0
- package/src/algorithms/centrality/eigenvector.ts +14 -15
- package/src/algorithms/centrality/hits.ts +17 -17
- package/src/algorithms/centrality/index.ts +20 -13
- package/src/algorithms/centrality/katz.ts +17 -25
- package/src/algorithms/centrality/pagerank.ts +91 -16
- package/src/algorithms/community/girvan-newman.ts +90 -86
- package/src/algorithms/community/index.ts +6 -5
- package/src/algorithms/community/label-propagation.ts +69 -48
- package/src/algorithms/community/leiden.ts +88 -65
- package/src/algorithms/community/louvain-optimized.ts +474 -0
- package/src/algorithms/community/louvain.ts +52 -122
- package/src/algorithms/community/modularity-utils.ts +134 -0
- package/src/algorithms/components/connected.ts +40 -17
- package/src/algorithms/matching/bipartite.ts +24 -53
- package/src/algorithms/matching/index.ts +4 -4
- package/src/algorithms/matching/isomorphism.ts +53 -23
- package/src/algorithms/mst/index.ts +3 -4
- package/src/algorithms/mst/kruskal.ts +21 -6
- package/src/algorithms/mst/prim.ts +13 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +16 -28
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
- package/src/algorithms/shortest-path/dijkstra.ts +38 -30
- package/src/algorithms/shortest-path/floyd-warshall.ts +21 -4
- package/src/algorithms/shortest-path/index.ts +5 -5
- package/src/algorithms/traversal/bfs-unified.ts +437 -0
- package/src/algorithms/traversal/bfs-variants.ts +520 -0
- package/src/algorithms/traversal/bfs.ts +5 -229
- package/src/algorithms/traversal/dfs.ts +61 -42
- package/src/algorithms/traversal/index.ts +3 -3
- package/src/benchmark-all-algorithms.ts +580 -0
- package/src/clustering/hierarchical.ts +268 -272
- package/src/clustering/index.ts +8 -6
- package/src/clustering/k-core.ts +93 -61
- package/src/clustering/mcl.ts +53 -23
- package/src/clustering/spectral.ts +130 -72
- package/src/core/graph.ts +58 -18
- package/src/data-structures/index.ts +2 -2
- package/src/data-structures/priority-queue.ts +36 -11
- package/src/data-structures/union-find.ts +22 -1
- package/src/flow/ford-fulkerson.ts +221 -262
- package/src/flow/min-cut.ts +59 -51
- package/src/index.ts +9 -2
- package/src/link-prediction/adamic-adar.ts +75 -74
- package/src/link-prediction/common-neighbors.ts +49 -49
- package/src/link-prediction/index.ts +1 -1
- package/src/optimized/bit-packed.ts +390 -0
- package/src/optimized/csr-graph.ts +518 -0
- package/src/optimized/direction-optimized-bfs.ts +310 -0
- package/src/optimized/graph-adapter.ts +248 -0
- package/src/optimized/index.ts +34 -0
- package/src/pathfinding/astar.ts +40 -25
- package/src/pathfinding/utils.ts +20 -13
- package/src/research/grsbm.ts +36 -39
- package/src/research/index.ts +4 -4
- package/src/research/sync.ts +41 -54
- package/src/research/terahac.ts +63 -21
- package/src/types/index.ts +8 -0
- package/src/utils/algorithm-utilities.ts +2 -0
- package/src/utils/graph-converters.ts +269 -0
- package/src/utils/graph-utilities.ts +173 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/math-utilities.ts +149 -0
- package/src/utils/matrix-utilities.ts +2 -0
- package/src/utils/optimization-helpers.ts +87 -0
- package/src/utils/priorityQueue.ts +40 -6
package/src/research/terahac.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {Graph} from "../core/graph.js";
|
|
2
|
-
import type {NodeId} from "../types/index.js";
|
|
1
|
+
import type { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Configuration options for the TeraHAC (Hierarchical Agglomerative Clustering) algorithm
|
|
@@ -15,6 +15,8 @@ export interface TeraHACConfig {
|
|
|
15
15
|
maxNodes?: number;
|
|
16
16
|
/** Use graph structure for distance calculation */
|
|
17
17
|
useGraphDistance?: boolean;
|
|
18
|
+
/** Custom warning handler (default: console.warn) */
|
|
19
|
+
onWarning?: (message: string) => void;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -58,7 +60,6 @@ export interface TeraHACResult {
|
|
|
58
60
|
*
|
|
59
61
|
* Based on: "Scaling Hierarchical Agglomerative Clustering to Trillion-Edge Graphs"
|
|
60
62
|
* Google Research 2024
|
|
61
|
-
*
|
|
62
63
|
* @param graph - Input graph to cluster
|
|
63
64
|
* @param config - Configuration options
|
|
64
65
|
* @returns Hierarchical clustering result
|
|
@@ -70,8 +71,14 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
70
71
|
distanceThreshold,
|
|
71
72
|
maxNodes = 10000,
|
|
72
73
|
useGraphDistance = true,
|
|
74
|
+
onWarning = console.warn,
|
|
73
75
|
} = config;
|
|
74
76
|
|
|
77
|
+
// Input validation
|
|
78
|
+
if (numClusters !== undefined && (numClusters < 1 || !Number.isInteger(numClusters))) {
|
|
79
|
+
throw new Error("numClusters must be a positive integer");
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
const nodes = Array.from(graph.nodes());
|
|
76
83
|
const nodeCount = nodes.length;
|
|
77
84
|
|
|
@@ -80,7 +87,9 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
if (nodeCount > maxNodes) {
|
|
83
|
-
|
|
90
|
+
onWarning(
|
|
91
|
+
`Graph has ${String(nodeCount)} nodes, which exceeds maxNodes (${String(maxNodes)}). Performance may be degraded.`,
|
|
92
|
+
);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
// Initialize each node as its own cluster
|
|
@@ -112,9 +121,9 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
112
121
|
let dendrogram: ClusterNode | undefined;
|
|
113
122
|
|
|
114
123
|
// Perform agglomerative clustering
|
|
115
|
-
while (clusters.size > 1) {
|
|
124
|
+
while (clusters.size > 1 && mergeCandidates.length > 0) {
|
|
116
125
|
// Find closest pair of clusters
|
|
117
|
-
const {cluster1Id, cluster2Id, distance} = findClosestPair(mergeCandidates);
|
|
126
|
+
const { cluster1Id, cluster2Id, distance } = findClosestPair(mergeCandidates);
|
|
118
127
|
|
|
119
128
|
// Check stopping criteria
|
|
120
129
|
if (numClusters && clusters.size <= numClusters) {
|
|
@@ -134,7 +143,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
134
143
|
|
|
135
144
|
// Create new merged cluster
|
|
136
145
|
const newClusterId = (nextClusterId++).toString();
|
|
137
|
-
const mergedMembers = new Set([...
|
|
146
|
+
const mergedMembers = new Set([...cluster1.members, ...cluster2.members]);
|
|
138
147
|
|
|
139
148
|
const newCluster: ClusterNode = {
|
|
140
149
|
id: newClusterId,
|
|
@@ -174,7 +183,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
174
183
|
|
|
175
184
|
const newRoot: ClusterNode = {
|
|
176
185
|
id: (nextClusterId++).toString(),
|
|
177
|
-
members: new Set([...
|
|
186
|
+
members: new Set([...root.members, ...currentCluster.members]),
|
|
178
187
|
left: root,
|
|
179
188
|
right: currentCluster,
|
|
180
189
|
distance: Infinity,
|
|
@@ -208,10 +217,14 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
208
217
|
|
|
209
218
|
/**
|
|
210
219
|
* Calculate distance matrix between all pairs of nodes
|
|
220
|
+
* @param graph - The input graph
|
|
221
|
+
* @param nodes - Array of nodes to calculate distances between
|
|
222
|
+
* @param useGraphDistance - Whether to use graph-based shortest path distances
|
|
223
|
+
* @returns 2D distance matrix
|
|
211
224
|
*/
|
|
212
|
-
function calculateDistanceMatrix(graph: Graph, nodes: {id: NodeId}[], useGraphDistance: boolean): number[][] {
|
|
225
|
+
function calculateDistanceMatrix(graph: Graph, nodes: { id: NodeId }[], useGraphDistance: boolean): number[][] {
|
|
213
226
|
const n = nodes.length;
|
|
214
|
-
const matrix: number[][] = Array.from({length: n}, () => new Array(n).fill(Infinity) as number[]);
|
|
227
|
+
const matrix: number[][] = Array.from({ length: n }, () => new Array(n).fill(Infinity) as number[]);
|
|
215
228
|
|
|
216
229
|
if (useGraphDistance) {
|
|
217
230
|
// Use graph-based distances (shortest path)
|
|
@@ -278,6 +291,9 @@ function calculateDistanceMatrix(graph: Graph, nodes: {id: NodeId}[], useGraphDi
|
|
|
278
291
|
|
|
279
292
|
/**
|
|
280
293
|
* BFS-based shortest path calculation from a source node
|
|
294
|
+
* @param graph - The input graph
|
|
295
|
+
* @param source - The source node ID
|
|
296
|
+
* @returns Map of node IDs to their distances from the source
|
|
281
297
|
*/
|
|
282
298
|
function bfsShortestPaths(graph: Graph, source: NodeId): Map<NodeId, number> {
|
|
283
299
|
const distances = new Map<NodeId, number>();
|
|
@@ -309,12 +325,15 @@ function bfsShortestPaths(graph: Graph, source: NodeId): Map<NodeId, number> {
|
|
|
309
325
|
|
|
310
326
|
/**
|
|
311
327
|
* Initialize merge candidates priority queue
|
|
328
|
+
* @param clusters - Map of cluster IDs to cluster nodes
|
|
329
|
+
* @param distanceMatrix - 2D distance matrix between nodes
|
|
330
|
+
* @returns Array of merge candidates sorted by distance
|
|
312
331
|
*/
|
|
313
332
|
function initializeMergeCandidates(
|
|
314
333
|
clusters: Map<string, ClusterNode>,
|
|
315
334
|
distanceMatrix: number[][],
|
|
316
|
-
): {cluster1Id: string
|
|
317
|
-
const candidates: {cluster1Id: string
|
|
335
|
+
): { cluster1Id: string; cluster2Id: string; distance: number }[] {
|
|
336
|
+
const candidates: { cluster1Id: string; cluster2Id: string; distance: number }[] = [];
|
|
318
337
|
const clusterIds = Array.from(clusters.keys());
|
|
319
338
|
|
|
320
339
|
for (let i = 0; i < clusterIds.length; i++) {
|
|
@@ -347,10 +366,14 @@ function initializeMergeCandidates(
|
|
|
347
366
|
|
|
348
367
|
/**
|
|
349
368
|
* Find the closest pair of clusters
|
|
369
|
+
* @param mergeCandidates - Array of merge candidates
|
|
370
|
+
* @returns The closest cluster pair with their distance
|
|
350
371
|
*/
|
|
351
|
-
function findClosestPair(
|
|
352
|
-
|
|
353
|
-
|
|
372
|
+
function findClosestPair(mergeCandidates: { cluster1Id: string; cluster2Id: string; distance: number }[]): {
|
|
373
|
+
cluster1Id: string;
|
|
374
|
+
cluster2Id: string;
|
|
375
|
+
distance: number;
|
|
376
|
+
} {
|
|
354
377
|
// Return the first (closest) valid candidate
|
|
355
378
|
const candidate = mergeCandidates.shift();
|
|
356
379
|
if (!candidate) {
|
|
@@ -362,9 +385,16 @@ function findClosestPair(
|
|
|
362
385
|
|
|
363
386
|
/**
|
|
364
387
|
* Update merge candidates after a merge operation
|
|
388
|
+
* @param mergeCandidates - Array of merge candidates to update
|
|
389
|
+
* @param oldCluster1Id - ID of first merged cluster
|
|
390
|
+
* @param oldCluster2Id - ID of second merged cluster
|
|
391
|
+
* @param newClusterId - ID of the newly created cluster
|
|
392
|
+
* @param clusters - Map of cluster IDs to cluster nodes
|
|
393
|
+
* @param distanceMatrix - 2D distance matrix between nodes
|
|
394
|
+
* @param linkage - Linkage criterion to use
|
|
365
395
|
*/
|
|
366
396
|
function updateMergeCandidates(
|
|
367
|
-
mergeCandidates: {cluster1Id: string
|
|
397
|
+
mergeCandidates: { cluster1Id: string; cluster2Id: string; distance: number }[],
|
|
368
398
|
oldCluster1Id: string,
|
|
369
399
|
oldCluster2Id: string,
|
|
370
400
|
newClusterId: string,
|
|
@@ -379,8 +409,12 @@ function updateMergeCandidates(
|
|
|
379
409
|
continue;
|
|
380
410
|
}
|
|
381
411
|
|
|
382
|
-
if (
|
|
383
|
-
candidate.
|
|
412
|
+
if (
|
|
413
|
+
candidate.cluster1Id === oldCluster1Id ||
|
|
414
|
+
candidate.cluster1Id === oldCluster2Id ||
|
|
415
|
+
candidate.cluster2Id === oldCluster1Id ||
|
|
416
|
+
candidate.cluster2Id === oldCluster2Id
|
|
417
|
+
) {
|
|
384
418
|
mergeCandidates.splice(i, 1);
|
|
385
419
|
}
|
|
386
420
|
}
|
|
@@ -408,6 +442,11 @@ function updateMergeCandidates(
|
|
|
408
442
|
|
|
409
443
|
/**
|
|
410
444
|
* Calculate distance between two clusters based on linkage criterion
|
|
445
|
+
* @param cluster1 - First cluster
|
|
446
|
+
* @param cluster2 - Second cluster
|
|
447
|
+
* @param distanceMatrix - 2D distance matrix between nodes
|
|
448
|
+
* @param linkage - Linkage criterion (single, complete, average, ward)
|
|
449
|
+
* @returns The distance between the two clusters
|
|
411
450
|
*/
|
|
412
451
|
function calculateClusterDistance(
|
|
413
452
|
cluster1: ClusterNode,
|
|
@@ -443,14 +482,14 @@ function calculateClusterDistance(
|
|
|
443
482
|
// Apply linkage criterion
|
|
444
483
|
switch (linkage) {
|
|
445
484
|
case "single":
|
|
446
|
-
return Math.min(...
|
|
485
|
+
return Math.min(...distances);
|
|
447
486
|
case "complete":
|
|
448
|
-
return Math.max(...
|
|
487
|
+
return Math.max(...distances);
|
|
449
488
|
case "average":
|
|
450
489
|
return distances.reduce((sum, d) => sum + d, 0) / distances.length;
|
|
451
490
|
case "ward":
|
|
452
491
|
// Simplified Ward linkage (would need cluster centroids for full implementation)
|
|
453
|
-
return distances.reduce((sum, d) => sum +
|
|
492
|
+
return distances.reduce((sum, d) => sum + d * d, 0) / distances.length;
|
|
454
493
|
default:
|
|
455
494
|
return distances.reduce((sum, d) => sum + d, 0) / distances.length;
|
|
456
495
|
}
|
|
@@ -458,6 +497,9 @@ function calculateClusterDistance(
|
|
|
458
497
|
|
|
459
498
|
/**
|
|
460
499
|
* Extract flat clustering from dendrogram
|
|
500
|
+
* @param dendrogram - The root of the dendrogram tree
|
|
501
|
+
* @param numClusters - The target number of clusters
|
|
502
|
+
* @returns Map of node IDs to cluster assignments
|
|
461
503
|
*/
|
|
462
504
|
function extractFlatClustering(dendrogram: ClusterNode, numClusters: number): Map<NodeId, number> {
|
|
463
505
|
const clusters = new Map<NodeId, number>();
|
package/src/types/index.ts
CHANGED
|
@@ -72,6 +72,12 @@ export interface TraversalOptions {
|
|
|
72
72
|
|
|
73
73
|
export interface DijkstraOptions {
|
|
74
74
|
target?: NodeId;
|
|
75
|
+
/**
|
|
76
|
+
* Use bidirectional search optimization for point-to-point queries.
|
|
77
|
+
* Defaults to true for graphs with >10 nodes, false for smaller graphs.
|
|
78
|
+
* Set explicitly to override automatic heuristic.
|
|
79
|
+
*/
|
|
80
|
+
bidirectional?: boolean;
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
export interface BellmanFordResult {
|
|
@@ -98,9 +104,11 @@ export interface LouvainOptions {
|
|
|
98
104
|
resolution?: number; // Resolution parameter (default: 1.0)
|
|
99
105
|
maxIterations?: number; // Maximum iterations (default: 100)
|
|
100
106
|
tolerance?: number; // Improvement tolerance (default: 1e-6)
|
|
107
|
+
useOptimized?: boolean; // Use optimized implementation for large graphs (default: true for graphs > 50 nodes)
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
export interface GirvanNewmanOptions {
|
|
104
111
|
maxCommunities?: number; // Stop when this many communities reached
|
|
105
112
|
minCommunitySize?: number; // Minimum size for valid community
|
|
113
|
+
maxIterations?: number; // Maximum iterations to prevent infinite loops
|
|
106
114
|
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Convert Graph class to Map representation
|
|
6
|
+
* Used by community detection and flow algorithms
|
|
7
|
+
* @param graph - The Graph instance to convert
|
|
8
|
+
* @returns A nested Map where outer keys are source nodes and inner maps contain target nodes with edge weights
|
|
9
|
+
*/
|
|
10
|
+
export function graphToMap(graph: Graph): Map<string, Map<string, number>> {
|
|
11
|
+
const map = new Map<string, Map<string, number>>();
|
|
12
|
+
|
|
13
|
+
// Initialize all nodes
|
|
14
|
+
for (const node of graph.nodes()) {
|
|
15
|
+
map.set(String(node.id), new Map());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Add edges with weights
|
|
19
|
+
for (const edge of graph.edges()) {
|
|
20
|
+
const source = String(edge.source);
|
|
21
|
+
const target = String(edge.target);
|
|
22
|
+
const weight = edge.weight ?? 1;
|
|
23
|
+
|
|
24
|
+
const sourceMap = map.get(source);
|
|
25
|
+
if (sourceMap) {
|
|
26
|
+
sourceMap.set(target, weight);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// For undirected graphs, add reverse edge
|
|
30
|
+
if (!graph.isDirected) {
|
|
31
|
+
const targetMap = map.get(target);
|
|
32
|
+
if (targetMap) {
|
|
33
|
+
targetMap.set(source, weight);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return map;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Convert Map representation to Graph class
|
|
43
|
+
* @param map - The nested Map representation of the graph
|
|
44
|
+
* @param isDirected - Whether the resulting graph should be directed (defaults to true)
|
|
45
|
+
* @returns A new Graph instance with nodes and edges from the map
|
|
46
|
+
*/
|
|
47
|
+
export function mapToGraph(map: Map<string, Map<string, number>>, isDirected = true): Graph {
|
|
48
|
+
const graph = new Graph({ directed: isDirected });
|
|
49
|
+
|
|
50
|
+
// Add all nodes first
|
|
51
|
+
for (const node of map.keys()) {
|
|
52
|
+
graph.addNode(node);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add edges
|
|
56
|
+
const processedEdges = new Set<string>();
|
|
57
|
+
|
|
58
|
+
for (const [source, neighbors] of map) {
|
|
59
|
+
for (const [target, weight] of neighbors) {
|
|
60
|
+
const edgeKey = isDirected ? `${source}->${target}` : [source, target].sort().join("-");
|
|
61
|
+
|
|
62
|
+
// Skip if we've already processed this edge (for undirected graphs)
|
|
63
|
+
if (!isDirected && processedEdges.has(edgeKey)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
graph.addEdge(source, target, weight);
|
|
68
|
+
processedEdges.add(edgeKey);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return graph;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Adapter for using Graph algorithms with Map representation
|
|
77
|
+
* Implements a subset of Graph interface methods needed by algorithms
|
|
78
|
+
*/
|
|
79
|
+
export class GraphAdapter {
|
|
80
|
+
private map: Map<string, Map<string, number>>;
|
|
81
|
+
public readonly isDirected: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates a new GraphAdapter instance wrapping a Map representation.
|
|
85
|
+
* @param map - The nested Map representation of the graph
|
|
86
|
+
* @param isDirected - Whether the graph should be treated as directed (defaults to true)
|
|
87
|
+
*/
|
|
88
|
+
constructor(map: Map<string, Map<string, number>>, isDirected = true) {
|
|
89
|
+
this.map = map;
|
|
90
|
+
this.isDirected = isDirected;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get all nodes
|
|
95
|
+
* @returns Array of node objects with their ids
|
|
96
|
+
*/
|
|
97
|
+
nodes(): { id: NodeId }[] {
|
|
98
|
+
return Array.from(this.map.keys()).map((id) => ({ id }));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get node count
|
|
103
|
+
* @returns The total number of nodes in the graph
|
|
104
|
+
*/
|
|
105
|
+
get nodeCount(): number {
|
|
106
|
+
return this.map.size;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if node exists
|
|
111
|
+
* @param nodeId - The id of the node to check
|
|
112
|
+
* @returns True if the node exists, false otherwise
|
|
113
|
+
*/
|
|
114
|
+
hasNode(nodeId: NodeId): boolean {
|
|
115
|
+
return this.map.has(String(nodeId));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get neighbors of a node
|
|
120
|
+
* @param nodeId - The id of the node to get neighbors for
|
|
121
|
+
* @returns Array of neighbor node ids
|
|
122
|
+
*/
|
|
123
|
+
neighbors(nodeId: NodeId): NodeId[] {
|
|
124
|
+
const neighbors = this.map.get(String(nodeId));
|
|
125
|
+
if (!neighbors) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return Array.from(neighbors.keys());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get edge between two nodes
|
|
134
|
+
* @param source - The source node id
|
|
135
|
+
* @param target - The target node id
|
|
136
|
+
* @returns The edge object with source, target and weight, or null if no edge exists
|
|
137
|
+
*/
|
|
138
|
+
getEdge(source: NodeId, target: NodeId): { source: NodeId; target: NodeId; weight?: number } | null {
|
|
139
|
+
const sourceNeighbors = this.map.get(String(source));
|
|
140
|
+
if (!sourceNeighbors) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const weight = sourceNeighbors.get(String(target));
|
|
145
|
+
if (weight === undefined) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { source, target, weight };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if edge exists
|
|
154
|
+
* @param source - The source node id
|
|
155
|
+
* @param target - The target node id
|
|
156
|
+
* @returns True if an edge exists between source and target, false otherwise
|
|
157
|
+
*/
|
|
158
|
+
hasEdge(source: NodeId, target: NodeId): boolean {
|
|
159
|
+
const sourceNeighbors = this.map.get(String(source));
|
|
160
|
+
if (!sourceNeighbors) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return sourceNeighbors.has(String(target));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get all edges
|
|
169
|
+
* @returns Array of edge objects with source, target and optional weight
|
|
170
|
+
*/
|
|
171
|
+
edges(): { source: NodeId; target: NodeId; weight?: number }[] {
|
|
172
|
+
const edges: { source: NodeId; target: NodeId; weight?: number }[] = [];
|
|
173
|
+
const processedEdges = new Set<string>();
|
|
174
|
+
|
|
175
|
+
for (const [source, neighbors] of this.map) {
|
|
176
|
+
for (const [target, weight] of neighbors) {
|
|
177
|
+
const edgeKey = this.isDirected ? `${source}->${target}` : [source, target].sort().join("-");
|
|
178
|
+
|
|
179
|
+
// Skip if we've already processed this edge (for undirected graphs)
|
|
180
|
+
if (!this.isDirected && processedEdges.has(edgeKey)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
edges.push({ source, target, weight });
|
|
185
|
+
processedEdges.add(edgeKey);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return edges;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get edge count
|
|
194
|
+
* @returns The total number of edges in the graph
|
|
195
|
+
*/
|
|
196
|
+
get edgeCount(): number {
|
|
197
|
+
if (this.isDirected) {
|
|
198
|
+
let count = 0;
|
|
199
|
+
for (const neighbors of this.map.values()) {
|
|
200
|
+
count += neighbors.size;
|
|
201
|
+
}
|
|
202
|
+
return count;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// For undirected graphs, count each edge once
|
|
206
|
+
let count = 0;
|
|
207
|
+
const counted = new Set<string>();
|
|
208
|
+
|
|
209
|
+
for (const [source, neighbors] of this.map) {
|
|
210
|
+
for (const target of neighbors.keys()) {
|
|
211
|
+
const edgeKey = [source, target].sort().join("-");
|
|
212
|
+
if (!counted.has(edgeKey)) {
|
|
213
|
+
counted.add(edgeKey);
|
|
214
|
+
count++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return count;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get node degree
|
|
223
|
+
* @param nodeId - The id of the node to get degree for
|
|
224
|
+
* @returns The number of edges connected to the node
|
|
225
|
+
*/
|
|
226
|
+
degree(nodeId: NodeId): number {
|
|
227
|
+
const neighbors = this.map.get(String(nodeId));
|
|
228
|
+
return neighbors ? neighbors.size : 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get in-degree (for directed graphs)
|
|
233
|
+
* @param nodeId - The id of the node to get in-degree for
|
|
234
|
+
* @returns The number of incoming edges to the node
|
|
235
|
+
*/
|
|
236
|
+
inDegree(nodeId: NodeId): number {
|
|
237
|
+
if (!this.isDirected) {
|
|
238
|
+
return this.degree(nodeId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let count = 0;
|
|
242
|
+
const targetId = String(nodeId);
|
|
243
|
+
|
|
244
|
+
for (const neighbors of this.map.values()) {
|
|
245
|
+
if (neighbors.has(targetId)) {
|
|
246
|
+
count++;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return count;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get out-degree (for directed graphs)
|
|
255
|
+
* @param nodeId - The id of the node to get out-degree for
|
|
256
|
+
* @returns The number of outgoing edges from the node
|
|
257
|
+
*/
|
|
258
|
+
outDegree(nodeId: NodeId): number {
|
|
259
|
+
return this.degree(nodeId);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get underlying map representation
|
|
264
|
+
* @returns The internal Map representation of the graph
|
|
265
|
+
*/
|
|
266
|
+
getMap(): Map<string, Map<string, number>> {
|
|
267
|
+
return this.map;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reconstructs a path from source to target using a predecessor map
|
|
6
|
+
* @param target - The target node
|
|
7
|
+
* @param predecessor - Map of node to its predecessor in the path
|
|
8
|
+
* @returns Array of nodes from source to target, or empty array if no path exists
|
|
9
|
+
*/
|
|
10
|
+
export function reconstructPath<T>(target: T, predecessor: Map<T, T | null>): T[] {
|
|
11
|
+
const path: T[] = [];
|
|
12
|
+
let current: T | null = target;
|
|
13
|
+
|
|
14
|
+
// Build path backwards from target to source
|
|
15
|
+
while (current !== null) {
|
|
16
|
+
path.unshift(current);
|
|
17
|
+
const pred = predecessor.get(current);
|
|
18
|
+
if (pred === undefined) {
|
|
19
|
+
// No path exists
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
current = pred;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Find common neighbors between two nodes
|
|
31
|
+
* @param graph - The graph
|
|
32
|
+
* @param source - First node
|
|
33
|
+
* @param target - Second node
|
|
34
|
+
* @param directed - Whether to consider edge direction
|
|
35
|
+
* @returns Set of common neighbor node IDs
|
|
36
|
+
*/
|
|
37
|
+
export function getCommonNeighbors(graph: Graph, source: NodeId, target: NodeId, directed = false): Set<NodeId> {
|
|
38
|
+
const sourceNeighbors = new Set(directed ? graph.outNeighbors(source) : graph.neighbors(source));
|
|
39
|
+
const targetNeighbors = new Set(directed ? graph.outNeighbors(target) : graph.neighbors(target));
|
|
40
|
+
|
|
41
|
+
const common = new Set<NodeId>();
|
|
42
|
+
for (const neighbor of sourceNeighbors) {
|
|
43
|
+
if (targetNeighbors.has(neighbor)) {
|
|
44
|
+
common.add(neighbor);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return common;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Find common neighbors for link prediction in directed graphs
|
|
53
|
+
* This finds nodes that form a path from source to target (source->X->target)
|
|
54
|
+
* @param graph - The graph
|
|
55
|
+
* @param source - First node
|
|
56
|
+
* @param target - Second node
|
|
57
|
+
* @returns Set of intermediate node IDs
|
|
58
|
+
*/
|
|
59
|
+
export function getIntermediateNodes(graph: Graph, source: NodeId, target: NodeId): Set<NodeId> {
|
|
60
|
+
const sourceOutNeighbors = new Set(graph.outNeighbors(source));
|
|
61
|
+
const targetInNeighbors = new Set(graph.inNeighbors(target));
|
|
62
|
+
|
|
63
|
+
const common = new Set<NodeId>();
|
|
64
|
+
for (const neighbor of sourceOutNeighbors) {
|
|
65
|
+
if (targetInNeighbors.has(neighbor)) {
|
|
66
|
+
common.add(neighbor);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return common;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate a consistent edge key for undirected graphs
|
|
75
|
+
* @param source - Source node
|
|
76
|
+
* @param target - Target node
|
|
77
|
+
* @param isDirected - Whether the graph is directed
|
|
78
|
+
* @returns A consistent edge identifier
|
|
79
|
+
*/
|
|
80
|
+
export function getEdgeKey(source: NodeId, target: NodeId, isDirected: boolean): string {
|
|
81
|
+
if (isDirected) {
|
|
82
|
+
return `${String(source)}->${String(target)}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For undirected graphs, sort to ensure consistency
|
|
86
|
+
const [first, second] = source < target ? [source, target] : [target, source];
|
|
87
|
+
return `${String(first)}-${String(second)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Calculate total edge weight in a graph
|
|
92
|
+
* @param graph - The graph
|
|
93
|
+
* @returns Total weight of all edges
|
|
94
|
+
*/
|
|
95
|
+
export function getTotalEdgeWeight(graph: Graph): number {
|
|
96
|
+
let totalWeight = 0;
|
|
97
|
+
for (const edge of graph.edges()) {
|
|
98
|
+
totalWeight += edge.weight ?? 1;
|
|
99
|
+
}
|
|
100
|
+
return graph.isDirected ? totalWeight : totalWeight / 2;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get node degree with optional mode for directed graphs
|
|
105
|
+
* @param graph - The graph
|
|
106
|
+
* @param nodeId - The node ID
|
|
107
|
+
* @param mode - Degree mode for directed graphs
|
|
108
|
+
* @returns Node degree
|
|
109
|
+
*/
|
|
110
|
+
export function getNodeDegree(graph: Graph, nodeId: NodeId, mode: "in" | "out" | "total" = "total"): number {
|
|
111
|
+
if (!graph.isDirected || mode === "total") {
|
|
112
|
+
return graph.degree(nodeId);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (mode === "in") {
|
|
116
|
+
return graph.inDegree(nodeId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return graph.outDegree(nodeId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convert directed graph to undirected
|
|
124
|
+
* @param graph - The directed graph
|
|
125
|
+
* @returns A new undirected graph
|
|
126
|
+
*/
|
|
127
|
+
export function makeUndirected(graph: Graph): Graph {
|
|
128
|
+
if (!graph.isDirected) {
|
|
129
|
+
return graph;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const undirected = new Graph({ directed: false });
|
|
133
|
+
|
|
134
|
+
// Add all nodes
|
|
135
|
+
for (const node of graph.nodes()) {
|
|
136
|
+
undirected.addNode(node.id, node.data);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Add edges (will automatically be made bidirectional)
|
|
140
|
+
for (const edge of graph.edges()) {
|
|
141
|
+
// For undirected, we only add each edge once
|
|
142
|
+
if (!undirected.hasEdge(edge.source, edge.target)) {
|
|
143
|
+
undirected.addEdge(edge.source, edge.target, edge.weight, edge.data);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return undirected;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Renumber communities consecutively starting from 0
|
|
152
|
+
* @param communities - Map of node to community ID
|
|
153
|
+
* @returns Map with renumbered community IDs
|
|
154
|
+
*/
|
|
155
|
+
export function renumberCommunities<T>(communities: Map<T, number>): Map<T, number> {
|
|
156
|
+
const uniqueCommunities = new Set(communities.values());
|
|
157
|
+
const remapping = new Map<number, number>();
|
|
158
|
+
let newId = 0;
|
|
159
|
+
|
|
160
|
+
for (const community of uniqueCommunities) {
|
|
161
|
+
remapping.set(community, newId++);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const renumbered = new Map<T, number>();
|
|
165
|
+
for (const [node, community] of communities) {
|
|
166
|
+
const newCommunity = remapping.get(community);
|
|
167
|
+
if (newCommunity !== undefined) {
|
|
168
|
+
renumbered.set(node, newCommunity);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return renumbered;
|
|
173
|
+
}
|