@graphty/algorithms 1.0.1 → 1.1.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 +827 -60
- package/dist/algorithms.d.ts +2 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +2 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +22 -7
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +7 -7
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +1 -1
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.js +113 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
- package/dist/src/algorithms/centrality/hits.d.ts +35 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/hits.js +146 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +6 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +3 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +30 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/katz.js +85 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -0
- package/dist/src/algorithms/centrality/pagerank.js +3 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.js +297 -0
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
- package/dist/src/algorithms/community/index.d.ts +12 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -0
- package/dist/src/algorithms/community/index.js +12 -0
- package/dist/src/algorithms/community/index.js.map +1 -0
- package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/community/label-propagation.js +307 -0
- package/dist/src/algorithms/community/label-propagation.js.map +1 -0
- package/dist/src/algorithms/community/leiden.d.ts +33 -0
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
- package/dist/src/algorithms/community/leiden.js +434 -0
- package/dist/src/algorithms/community/leiden.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +19 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain.js +219 -0
- package/dist/src/algorithms/community/louvain.js.map +1 -0
- package/dist/src/algorithms/components/connected.js +7 -7
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +7 -0
- package/dist/src/algorithms/index.d.ts.map +1 -1
- package/dist/src/algorithms/index.js +14 -0
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
- package/dist/src/algorithms/matching/bipartite.js +127 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -0
- package/dist/src/algorithms/matching/index.d.ts +8 -0
- package/dist/src/algorithms/matching/index.d.ts.map +1 -0
- package/dist/src/algorithms/matching/index.js +6 -0
- package/dist/src/algorithms/matching/index.js.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.js +291 -0
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
- package/dist/src/algorithms/mst/index.d.ts +4 -0
- package/dist/src/algorithms/mst/index.d.ts.map +1 -0
- package/dist/src/algorithms/mst/index.js +3 -0
- package/dist/src/algorithms/mst/index.js.map +1 -0
- package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
- package/dist/src/algorithms/mst/kruskal.js +43 -0
- package/dist/src/algorithms/mst/kruskal.js.map +1 -0
- package/dist/src/algorithms/mst/prim.d.ts +5 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
- package/dist/src/algorithms/mst/prim.js +63 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
- package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js +1 -0
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +2 -2
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +11 -11
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +48 -0
- package/dist/src/clustering/hierarchical.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical.js +439 -0
- package/dist/src/clustering/hierarchical.js.map +1 -0
- package/dist/src/clustering/index.d.ts +7 -0
- package/dist/src/clustering/index.d.ts.map +1 -0
- package/dist/src/clustering/index.js +5 -0
- package/dist/src/clustering/index.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +63 -0
- package/dist/src/clustering/k-core.d.ts.map +1 -0
- package/dist/src/clustering/k-core.js +377 -0
- package/dist/src/clustering/k-core.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +36 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -0
- package/dist/src/clustering/mcl.js +343 -0
- package/dist/src/clustering/mcl.js.map +1 -0
- package/dist/src/clustering/spectral.d.ts +28 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -0
- package/dist/src/clustering/spectral.js +447 -0
- package/dist/src/clustering/spectral.js.map +1 -0
- package/dist/src/core/graph.d.ts +9 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +31 -11
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/priority-queue.js +0 -2
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.js +2 -5
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +53 -0
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
- package/dist/src/flow/ford-fulkerson.js +355 -0
- package/dist/src/flow/ford-fulkerson.js.map +1 -0
- package/dist/src/flow/index.d.ts +3 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +3 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/flow/min-cut.d.ts +49 -0
- package/dist/src/flow/min-cut.d.ts.map +1 -0
- package/dist/src/flow/min-cut.js +383 -0
- package/dist/src/flow/min-cut.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
- package/dist/src/link-prediction/adamic-adar.js +256 -0
- package/dist/src/link-prediction/adamic-adar.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors.js +155 -0
- package/dist/src/link-prediction/common-neighbors.js.map +1 -0
- package/dist/src/link-prediction/index.d.ts +7 -0
- package/dist/src/link-prediction/index.d.ts.map +1 -0
- package/dist/src/link-prediction/index.js +6 -0
- package/dist/src/link-prediction/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +50 -0
- package/dist/src/pathfinding/astar.d.ts.map +1 -0
- package/dist/src/pathfinding/astar.js +180 -0
- package/dist/src/pathfinding/astar.js.map +1 -0
- package/dist/src/pathfinding/index.d.ts +3 -0
- package/dist/src/pathfinding/index.d.ts.map +1 -0
- package/dist/src/pathfinding/index.js +3 -0
- package/dist/src/pathfinding/index.js.map +1 -0
- package/dist/src/pathfinding/utils.d.ts +18 -0
- package/dist/src/pathfinding/utils.d.ts.map +1 -0
- package/dist/src/pathfinding/utils.js +68 -0
- package/dist/src/pathfinding/utils.js.map +1 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/graphNode.d.ts +8 -0
- package/dist/src/utils/graphNode.d.ts.map +1 -0
- package/dist/src/utils/graphNode.js +2 -0
- package/dist/src/utils/graphNode.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +21 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -0
- package/dist/src/utils/priorityQueue.js +101 -0
- package/dist/src/utils/priorityQueue.js.map +1 -0
- package/package.json +3 -1
- package/src/algorithms/centrality/betweenness.ts +25 -7
- package/src/algorithms/centrality/closeness.ts +7 -7
- package/src/algorithms/centrality/degree.ts +1 -1
- package/src/algorithms/centrality/eigenvector.ts +162 -0
- package/src/algorithms/centrality/hits.ts +210 -0
- package/src/algorithms/centrality/index.ts +6 -0
- package/src/algorithms/centrality/katz.ts +139 -0
- package/src/algorithms/centrality/pagerank.ts +3 -3
- package/src/algorithms/community/girvan-newman.ts +372 -0
- package/src/algorithms/community/index.ts +13 -0
- package/src/algorithms/community/label-propagation.ts +392 -0
- package/src/algorithms/community/leiden.ts +572 -0
- package/src/algorithms/community/louvain.ts +298 -0
- package/src/algorithms/components/connected.ts +7 -7
- package/src/algorithms/index.ts +21 -0
- package/src/algorithms/matching/bipartite.ts +179 -0
- package/src/algorithms/matching/index.ts +8 -0
- package/src/algorithms/matching/isomorphism.ts +392 -0
- package/src/algorithms/mst/index.ts +4 -0
- package/src/algorithms/mst/kruskal.ts +61 -0
- package/src/algorithms/mst/prim.ts +80 -0
- package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
- package/src/algorithms/shortest-path/dijkstra.ts +5 -5
- package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
- package/src/algorithms/shortest-path/index.ts +2 -0
- package/src/algorithms/traversal/bfs.ts +2 -2
- package/src/algorithms/traversal/dfs.ts +11 -11
- package/src/clustering/hierarchical.ts +555 -0
- package/src/clustering/index.ts +6 -0
- package/src/clustering/k-core.ts +460 -0
- package/src/clustering/mcl.ts +454 -0
- package/src/clustering/spectral.ts +545 -0
- package/src/core/graph.ts +34 -6
- package/src/data-structures/union-find.ts +2 -2
- package/src/flow/ford-fulkerson.ts +459 -0
- package/src/flow/index.ts +2 -0
- package/src/flow/min-cut.ts +482 -0
- package/src/index.ts +2 -0
- package/src/link-prediction/adamic-adar.ts +353 -0
- package/src/link-prediction/common-neighbors.ts +252 -0
- package/src/link-prediction/index.ts +20 -0
- package/src/pathfinding/astar.ts +229 -0
- package/src/pathfinding/index.ts +2 -0
- package/src/pathfinding/utils.ts +87 -0
- package/src/types/index.ts +11 -0
- package/src/utils/graphNode.ts +7 -0
- package/src/utils/priorityQueue.ts +120 -0
- package/dist/test/browser/basic.test.d.ts +0 -2
- package/dist/test/browser/basic.test.d.ts.map +0 -1
- package/dist/test/browser/basic.test.js +0 -70
- package/dist/test/browser/basic.test.js.map +0 -1
- package/dist/test/unit/bellman-ford.test.d.ts +0 -2
- package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
- package/dist/test/unit/bellman-ford.test.js +0 -231
- package/dist/test/unit/bellman-ford.test.js.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
- package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.js +0 -311
- package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
- package/dist/test/unit/bfs.test.d.ts +0 -2
- package/dist/test/unit/bfs.test.d.ts.map +0 -1
- package/dist/test/unit/bfs.test.js +0 -277
- package/dist/test/unit/bfs.test.js.map +0 -1
- package/dist/test/unit/closeness.test.d.ts +0 -2
- package/dist/test/unit/closeness.test.d.ts.map +0 -1
- package/dist/test/unit/closeness.test.js +0 -266
- package/dist/test/unit/closeness.test.js.map +0 -1
- package/dist/test/unit/connected-components.test.d.ts +0 -2
- package/dist/test/unit/connected-components.test.d.ts.map +0 -1
- package/dist/test/unit/connected-components.test.js +0 -355
- package/dist/test/unit/connected-components.test.js.map +0 -1
- package/dist/test/unit/degree-centrality.test.d.ts +0 -2
- package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/degree-centrality.test.js +0 -260
- package/dist/test/unit/degree-centrality.test.js.map +0 -1
- package/dist/test/unit/dfs.test.d.ts +0 -2
- package/dist/test/unit/dfs.test.d.ts.map +0 -1
- package/dist/test/unit/dfs.test.js +0 -350
- package/dist/test/unit/dfs.test.js.map +0 -1
- package/dist/test/unit/dijkstra.test.d.ts +0 -2
- package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
- package/dist/test/unit/dijkstra.test.js +0 -311
- package/dist/test/unit/dijkstra.test.js.map +0 -1
- package/dist/test/unit/graph.test.d.ts +0 -2
- package/dist/test/unit/graph.test.d.ts.map +0 -1
- package/dist/test/unit/graph.test.js +0 -175
- package/dist/test/unit/graph.test.js.map +0 -1
- package/dist/test/unit/pagerank.test.d.ts +0 -2
- package/dist/test/unit/pagerank.test.d.ts.map +0 -1
- package/dist/test/unit/pagerank.test.js +0 -281
- package/dist/test/unit/pagerank.test.js.map +0 -1
- package/dist/test/unit/priority-queue.test.d.ts +0 -2
- package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
- package/dist/test/unit/priority-queue.test.js +0 -189
- package/dist/test/unit/priority-queue.test.js.map +0 -1
- package/dist/test/unit/union-find.test.d.ts +0 -2
- package/dist/test/unit/union-find.test.d.ts.map +0 -1
- package/dist/test/unit/union-find.test.js +0 -245
- package/dist/test/unit/union-find.test.js.map +0 -1
|
@@ -21,7 +21,7 @@ export function degreeCentrality(
|
|
|
21
21
|
// Calculate normalization factor
|
|
22
22
|
const maxPossibleDegree = graph.isDirected ? nodeCount - 1 : nodeCount - 1;
|
|
23
23
|
|
|
24
|
-
for (const node of graph.nodes()) {
|
|
24
|
+
for (const node of Array.from(graph.nodes())) {
|
|
25
25
|
let degree: number;
|
|
26
26
|
|
|
27
27
|
if (graph.isDirected && options.mode) {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {CentralityOptions, CentralityResult} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Eigenvector centrality implementation
|
|
6
|
+
*
|
|
7
|
+
* Measures the influence of a node based on the influence of its neighbors.
|
|
8
|
+
* A node has high eigenvector centrality if it is connected to nodes
|
|
9
|
+
* that themselves have high eigenvector centrality.
|
|
10
|
+
*
|
|
11
|
+
* Time complexity: O(V + E) per iteration
|
|
12
|
+
* Space complexity: O(V)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface EigenvectorCentralityOptions extends CentralityOptions {
|
|
16
|
+
maxIterations?: number; // Maximum iterations (default: 100)
|
|
17
|
+
tolerance?: number; // Convergence tolerance (default: 1e-6)
|
|
18
|
+
startVector?: Map<string, number>; // Initial vector (optional)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculate eigenvector centrality for all nodes in the graph
|
|
23
|
+
* Uses the power iteration method to find the dominant eigenvector
|
|
24
|
+
*/
|
|
25
|
+
export function eigenvectorCentrality(
|
|
26
|
+
graph: Graph,
|
|
27
|
+
options: EigenvectorCentralityOptions = {},
|
|
28
|
+
): CentralityResult {
|
|
29
|
+
const {
|
|
30
|
+
maxIterations = 100,
|
|
31
|
+
tolerance = 1e-6,
|
|
32
|
+
normalized = true,
|
|
33
|
+
startVector,
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
const centrality: CentralityResult = {};
|
|
37
|
+
const nodes = Array.from(graph.nodes());
|
|
38
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
39
|
+
|
|
40
|
+
if (nodeIds.length === 0) {
|
|
41
|
+
return centrality;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Initialize the eigenvector
|
|
45
|
+
let currentVector = new Map<string, number>();
|
|
46
|
+
let previousVector = new Map<string, number>();
|
|
47
|
+
|
|
48
|
+
if (startVector) {
|
|
49
|
+
// Use provided start vector
|
|
50
|
+
for (const nodeId of nodeIds) {
|
|
51
|
+
const key = nodeId.toString();
|
|
52
|
+
currentVector.set(key, startVector.get(key) ?? 1.0 / Math.sqrt(nodeIds.length));
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Initialize with uniform distribution
|
|
56
|
+
const initialValue = 1.0 / Math.sqrt(nodeIds.length);
|
|
57
|
+
for (const nodeId of nodeIds) {
|
|
58
|
+
currentVector.set(nodeId.toString(), initialValue);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Power iteration
|
|
63
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
64
|
+
previousVector = new Map(currentVector);
|
|
65
|
+
currentVector = new Map();
|
|
66
|
+
|
|
67
|
+
// Update each node's centrality based on neighbors
|
|
68
|
+
for (const nodeId of nodeIds) {
|
|
69
|
+
let sum = 0;
|
|
70
|
+
const neighbors = Array.from(graph.neighbors(nodeId));
|
|
71
|
+
|
|
72
|
+
for (const neighbor of neighbors) {
|
|
73
|
+
const neighborKey = neighbor.toString();
|
|
74
|
+
const prevValue = previousVector.get(neighborKey);
|
|
75
|
+
sum += prevValue ?? 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
currentVector.set(nodeId.toString(), sum);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Normalize the vector
|
|
82
|
+
let norm = 0;
|
|
83
|
+
for (const value of Array.from(currentVector.values())) {
|
|
84
|
+
norm += value * value;
|
|
85
|
+
}
|
|
86
|
+
norm = Math.sqrt(norm);
|
|
87
|
+
|
|
88
|
+
if (norm === 0) {
|
|
89
|
+
// Graph has no edges or is disconnected
|
|
90
|
+
for (const nodeId of nodeIds) {
|
|
91
|
+
centrality[nodeId.toString()] = 0;
|
|
92
|
+
}
|
|
93
|
+
return centrality;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Normalize the vector
|
|
97
|
+
for (const [nodeId, value] of Array.from(currentVector)) {
|
|
98
|
+
currentVector.set(nodeId, value / norm);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for convergence
|
|
102
|
+
let maxDiff = 0;
|
|
103
|
+
for (const [nodeId, value] of Array.from(currentVector)) {
|
|
104
|
+
const prevValue = previousVector.get(nodeId) ?? 0;
|
|
105
|
+
const diff = Math.abs(value - prevValue);
|
|
106
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (maxDiff < tolerance) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Prepare results
|
|
115
|
+
for (const [nodeId, value] of Array.from(currentVector)) {
|
|
116
|
+
centrality[nodeId] = value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Additional normalization if requested (normalize to [0,1] range)
|
|
120
|
+
if (normalized) {
|
|
121
|
+
let maxValue = 0;
|
|
122
|
+
let minValue = Number.POSITIVE_INFINITY;
|
|
123
|
+
|
|
124
|
+
for (const value of Object.values(centrality)) {
|
|
125
|
+
maxValue = Math.max(maxValue, value);
|
|
126
|
+
minValue = Math.min(minValue, value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const range = maxValue - minValue;
|
|
130
|
+
if (range > 0) {
|
|
131
|
+
for (const nodeId of Object.keys(centrality)) {
|
|
132
|
+
const centralityValue = centrality[nodeId];
|
|
133
|
+
if (centralityValue !== undefined) {
|
|
134
|
+
centrality[nodeId] = (centralityValue - minValue) / range;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
// All values are the same, set to 1
|
|
139
|
+
for (const nodeId of Object.keys(centrality)) {
|
|
140
|
+
centrality[nodeId] = maxValue > 0 ? 1 : 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return centrality;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Calculate eigenvector centrality for a specific node
|
|
150
|
+
*/
|
|
151
|
+
export function nodeEigenvectorCentrality(
|
|
152
|
+
graph: Graph,
|
|
153
|
+
nodeId: string | number,
|
|
154
|
+
options: EigenvectorCentralityOptions = {},
|
|
155
|
+
): number {
|
|
156
|
+
if (!graph.hasNode(nodeId)) {
|
|
157
|
+
throw new Error(`Node ${String(nodeId)} not found in graph`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const centrality = eigenvectorCentrality(graph, options);
|
|
161
|
+
return centrality[nodeId.toString()] ?? 0;
|
|
162
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {CentralityOptions} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HITS (Hyperlink-Induced Topic Search) algorithm implementation
|
|
6
|
+
*
|
|
7
|
+
* Identifies hub and authority scores for nodes in a graph.
|
|
8
|
+
* - Authorities: nodes with valuable information (pointed to by hubs)
|
|
9
|
+
* - Hubs: nodes that point to many authorities
|
|
10
|
+
*
|
|
11
|
+
* Originally designed for web page ranking but applicable to any directed network.
|
|
12
|
+
*
|
|
13
|
+
* Time complexity: O(V + E) per iteration
|
|
14
|
+
* Space complexity: O(V)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface HITSResult {
|
|
18
|
+
hubs: Record<string, number>;
|
|
19
|
+
authorities: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface HITSOptions extends CentralityOptions {
|
|
23
|
+
maxIterations?: number; // Maximum iterations (default: 100)
|
|
24
|
+
tolerance?: number; // Convergence tolerance (default: 1e-6)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calculate HITS hub and authority scores for all nodes in the graph
|
|
29
|
+
* Uses iterative method with normalization
|
|
30
|
+
*/
|
|
31
|
+
export function hits(
|
|
32
|
+
graph: Graph,
|
|
33
|
+
options: HITSOptions = {},
|
|
34
|
+
): HITSResult {
|
|
35
|
+
const {
|
|
36
|
+
maxIterations = 100,
|
|
37
|
+
tolerance = 1e-6,
|
|
38
|
+
normalized = true,
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
const hubs: Record<string, number> = {};
|
|
42
|
+
const authorities: Record<string, number> = {};
|
|
43
|
+
|
|
44
|
+
const nodes = Array.from(graph.nodes());
|
|
45
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
46
|
+
|
|
47
|
+
if (nodeIds.length === 0) {
|
|
48
|
+
return {hubs, authorities};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initialize scores
|
|
52
|
+
let currentHubs = new Map<string, number>();
|
|
53
|
+
let currentAuthorities = new Map<string, number>();
|
|
54
|
+
let previousHubs = new Map<string, number>();
|
|
55
|
+
let previousAuthorities = new Map<string, number>();
|
|
56
|
+
|
|
57
|
+
// Start with uniform distribution
|
|
58
|
+
const initialValue = 1.0 / Math.sqrt(nodeIds.length);
|
|
59
|
+
for (const nodeId of nodeIds) {
|
|
60
|
+
const key = nodeId.toString();
|
|
61
|
+
currentHubs.set(key, initialValue);
|
|
62
|
+
currentAuthorities.set(key, initialValue);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Iterative computation
|
|
66
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
67
|
+
previousHubs = new Map(currentHubs);
|
|
68
|
+
previousAuthorities = new Map(currentAuthorities);
|
|
69
|
+
|
|
70
|
+
// Update authority scores
|
|
71
|
+
// Authority score = sum of hub scores of nodes pointing to it
|
|
72
|
+
const newAuthorities = new Map<string, number>();
|
|
73
|
+
for (const nodeId of nodeIds) {
|
|
74
|
+
let authorityScore = 0;
|
|
75
|
+
const inNeighbors = Array.from(graph.inNeighbors(nodeId));
|
|
76
|
+
|
|
77
|
+
for (const inNeighbor of inNeighbors) {
|
|
78
|
+
const neighborKey = inNeighbor.toString();
|
|
79
|
+
authorityScore += previousHubs.get(neighborKey) ?? 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
newAuthorities.set(nodeId.toString(), authorityScore);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update hub scores
|
|
86
|
+
// Hub score = sum of authority scores of nodes it points to
|
|
87
|
+
const newHubs = new Map<string, number>();
|
|
88
|
+
for (const nodeId of nodeIds) {
|
|
89
|
+
let hubScore = 0;
|
|
90
|
+
const outNeighbors = Array.from(graph.outNeighbors(nodeId));
|
|
91
|
+
|
|
92
|
+
for (const outNeighbor of outNeighbors) {
|
|
93
|
+
const neighborKey = outNeighbor.toString();
|
|
94
|
+
hubScore += previousAuthorities.get(neighborKey) ?? 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
newHubs.set(nodeId.toString(), hubScore);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Normalize authority scores
|
|
101
|
+
let authNorm = 0;
|
|
102
|
+
for (const value of Array.from(newAuthorities.values())) {
|
|
103
|
+
authNorm += value * value;
|
|
104
|
+
}
|
|
105
|
+
authNorm = Math.sqrt(authNorm);
|
|
106
|
+
|
|
107
|
+
if (authNorm > 0) {
|
|
108
|
+
for (const [nodeId, value] of Array.from(newAuthorities)) {
|
|
109
|
+
newAuthorities.set(nodeId, value / authNorm);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Normalize hub scores
|
|
114
|
+
let hubNorm = 0;
|
|
115
|
+
for (const value of Array.from(newHubs.values())) {
|
|
116
|
+
hubNorm += value * value;
|
|
117
|
+
}
|
|
118
|
+
hubNorm = Math.sqrt(hubNorm);
|
|
119
|
+
|
|
120
|
+
if (hubNorm > 0) {
|
|
121
|
+
for (const [nodeId, value] of Array.from(newHubs)) {
|
|
122
|
+
newHubs.set(nodeId, value / hubNorm);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
currentAuthorities = newAuthorities;
|
|
127
|
+
currentHubs = newHubs;
|
|
128
|
+
|
|
129
|
+
// Check for convergence
|
|
130
|
+
let maxDiff = 0;
|
|
131
|
+
for (const [nodeId, value] of currentHubs) {
|
|
132
|
+
const prevValue = previousHubs.get(nodeId) ?? 0;
|
|
133
|
+
const diff = Math.abs(value - prevValue);
|
|
134
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
135
|
+
}
|
|
136
|
+
for (const [nodeId, value] of currentAuthorities) {
|
|
137
|
+
const prevValue = previousAuthorities.get(nodeId) ?? 0;
|
|
138
|
+
const diff = Math.abs(value - prevValue);
|
|
139
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (maxDiff < tolerance) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Prepare results
|
|
148
|
+
for (const [nodeId, value] of currentHubs) {
|
|
149
|
+
hubs[nodeId] = value;
|
|
150
|
+
}
|
|
151
|
+
for (const [nodeId, value] of currentAuthorities) {
|
|
152
|
+
authorities[nodeId] = value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The algorithm already normalizes to unit L2 norm during iterations
|
|
156
|
+
// Additional max normalization is only applied if explicitly requested
|
|
157
|
+
if (!normalized) {
|
|
158
|
+
// If normalization is explicitly disabled, normalize to [0, 1] range
|
|
159
|
+
let maxHub = 0;
|
|
160
|
+
let maxAuth = 0;
|
|
161
|
+
|
|
162
|
+
for (const value of Object.values(hubs)) {
|
|
163
|
+
maxHub = Math.max(maxHub, value);
|
|
164
|
+
}
|
|
165
|
+
for (const value of Object.values(authorities)) {
|
|
166
|
+
maxAuth = Math.max(maxAuth, value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (maxHub > 0) {
|
|
170
|
+
for (const nodeId of Object.keys(hubs)) {
|
|
171
|
+
const hubValue = hubs[nodeId];
|
|
172
|
+
if (hubValue !== undefined) {
|
|
173
|
+
hubs[nodeId] = hubValue / maxHub;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (maxAuth > 0) {
|
|
179
|
+
for (const nodeId of Object.keys(authorities)) {
|
|
180
|
+
const authValue = authorities[nodeId];
|
|
181
|
+
if (authValue !== undefined) {
|
|
182
|
+
authorities[nodeId] = authValue / maxAuth;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {hubs, authorities};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Calculate HITS scores for a specific node
|
|
193
|
+
*/
|
|
194
|
+
export function nodeHITS(
|
|
195
|
+
graph: Graph,
|
|
196
|
+
nodeId: string | number,
|
|
197
|
+
options: HITSOptions = {},
|
|
198
|
+
): {hub: number, authority: number} {
|
|
199
|
+
if (!graph.hasNode(nodeId)) {
|
|
200
|
+
throw new Error(`Node ${String(nodeId)} not found in graph`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = hits(graph, options);
|
|
204
|
+
const key = nodeId.toString();
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
hub: result.hubs[key] ?? 0,
|
|
208
|
+
authority: result.authorities[key] ?? 0,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -7,5 +7,11 @@ export {betweennessCentrality, edgeBetweennessCentrality, nodeBetweennessCentral
|
|
|
7
7
|
export type {ClosenessCentralityOptions} from "./closeness.js";
|
|
8
8
|
export {closenessCentrality, nodeClosenessCentrality, nodeWeightedClosenessCentrality, weightedClosenessCentrality} from "./closeness.js";
|
|
9
9
|
export {degreeCentrality, nodeDegreeCentrality} from "./degree.js";
|
|
10
|
+
export type {EigenvectorCentralityOptions} from "./eigenvector.js";
|
|
11
|
+
export {eigenvectorCentrality, nodeEigenvectorCentrality} from "./eigenvector.js";
|
|
12
|
+
export type {HITSOptions, HITSResult} from "./hits.js";
|
|
13
|
+
export {hits, nodeHITS} from "./hits.js";
|
|
14
|
+
export type {KatzCentralityOptions} from "./katz.js";
|
|
15
|
+
export {katzCentrality, nodeKatzCentrality} from "./katz.js";
|
|
10
16
|
export type {PageRankOptions, PageRankResult} from "./pagerank.js";
|
|
11
17
|
export {pageRank, pageRankCentrality, personalizedPageRank, topPageRankNodes} from "./pagerank.js";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {CentralityOptions, CentralityResult} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Katz centrality implementation
|
|
6
|
+
*
|
|
7
|
+
* A generalization of eigenvector centrality that gives each node a base amount
|
|
8
|
+
* of influence regardless of its position in the network. Uses an attenuation
|
|
9
|
+
* factor (alpha) to control how much a node's centrality depends on its neighbors.
|
|
10
|
+
*
|
|
11
|
+
* Katz centrality = alpha * sum(neighbors' centrality) + beta
|
|
12
|
+
*
|
|
13
|
+
* Time complexity: O(V + E) per iteration
|
|
14
|
+
* Space complexity: O(V)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface KatzCentralityOptions extends CentralityOptions {
|
|
18
|
+
alpha?: number; // Attenuation factor (default: 0.1)
|
|
19
|
+
beta?: number; // Base centrality weight (default: 1.0)
|
|
20
|
+
maxIterations?: number; // Maximum iterations (default: 100)
|
|
21
|
+
tolerance?: number; // Convergence tolerance (default: 1e-6)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Calculate Katz centrality for all nodes in the graph
|
|
26
|
+
* Uses iterative method to compute centrality scores
|
|
27
|
+
*/
|
|
28
|
+
export function katzCentrality(
|
|
29
|
+
graph: Graph,
|
|
30
|
+
options: KatzCentralityOptions = {},
|
|
31
|
+
): CentralityResult {
|
|
32
|
+
const {
|
|
33
|
+
alpha = 0.1,
|
|
34
|
+
beta = 1.0,
|
|
35
|
+
maxIterations = 100,
|
|
36
|
+
tolerance = 1e-6,
|
|
37
|
+
normalized = true,
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
const centrality: CentralityResult = {};
|
|
41
|
+
const nodes = Array.from(graph.nodes());
|
|
42
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
43
|
+
|
|
44
|
+
if (nodeIds.length === 0) {
|
|
45
|
+
return centrality;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if alpha is valid (should be less than 1/lambda_max)
|
|
49
|
+
// For simplicity, we'll trust the user's choice or use conservative default
|
|
50
|
+
|
|
51
|
+
// Initialize centrality scores
|
|
52
|
+
let currentScores = new Map<string, number>();
|
|
53
|
+
let previousScores = new Map<string, number>();
|
|
54
|
+
|
|
55
|
+
// Start with beta for all nodes
|
|
56
|
+
for (const nodeId of nodeIds) {
|
|
57
|
+
currentScores.set(nodeId.toString(), beta);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Iterative computation
|
|
61
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
62
|
+
previousScores = new Map(currentScores);
|
|
63
|
+
currentScores = new Map();
|
|
64
|
+
|
|
65
|
+
// Update each node's centrality
|
|
66
|
+
for (const nodeId of nodeIds) {
|
|
67
|
+
let sum = 0;
|
|
68
|
+
|
|
69
|
+
// For directed graphs, use in-neighbors
|
|
70
|
+
// For undirected graphs, use all neighbors
|
|
71
|
+
const neighbors = graph.isDirected ?
|
|
72
|
+
Array.from(graph.inNeighbors(nodeId)) :
|
|
73
|
+
graph.neighbors(nodeId);
|
|
74
|
+
|
|
75
|
+
for (const neighbor of neighbors) {
|
|
76
|
+
const neighborKey = neighbor.toString();
|
|
77
|
+
sum += previousScores.get(neighborKey) ?? 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
currentScores.set(nodeId.toString(), (alpha * sum) + beta);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for convergence
|
|
84
|
+
let maxDiff = 0;
|
|
85
|
+
for (const [nodeId, value] of Array.from(currentScores)) {
|
|
86
|
+
const prevValue = previousScores.get(nodeId) ?? 0;
|
|
87
|
+
const diff = Math.abs(value - prevValue);
|
|
88
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (maxDiff < tolerance) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Prepare results
|
|
97
|
+
for (const [nodeId, value] of Array.from(currentScores)) {
|
|
98
|
+
centrality[nodeId] = value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Normalize if requested
|
|
102
|
+
if (normalized) {
|
|
103
|
+
let maxValue = 0;
|
|
104
|
+
let minValue = Number.POSITIVE_INFINITY;
|
|
105
|
+
|
|
106
|
+
for (const value of Object.values(centrality)) {
|
|
107
|
+
maxValue = Math.max(maxValue, value);
|
|
108
|
+
minValue = Math.min(minValue, value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const range = maxValue - minValue;
|
|
112
|
+
if (range > 0) {
|
|
113
|
+
for (const nodeId of Object.keys(centrality)) {
|
|
114
|
+
const centralityValue = centrality[nodeId];
|
|
115
|
+
if (centralityValue !== undefined) {
|
|
116
|
+
centrality[nodeId] = (centralityValue - minValue) / range;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return centrality;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Calculate Katz centrality for a specific node
|
|
127
|
+
*/
|
|
128
|
+
export function nodeKatzCentrality(
|
|
129
|
+
graph: Graph,
|
|
130
|
+
nodeId: string | number,
|
|
131
|
+
options: KatzCentralityOptions = {},
|
|
132
|
+
): number {
|
|
133
|
+
if (!graph.hasNode(nodeId)) {
|
|
134
|
+
throw new Error(`Node ${String(nodeId)} not found in graph`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const centrality = katzCentrality(graph, options);
|
|
138
|
+
return centrality[nodeId.toString()] ?? 0;
|
|
139
|
+
}
|
|
@@ -111,7 +111,7 @@ export function pageRank(
|
|
|
111
111
|
let outDegree = 0;
|
|
112
112
|
let totalOutWeight = 0;
|
|
113
113
|
|
|
114
|
-
for (const neighbor of graph.neighbors(nodeId)) {
|
|
114
|
+
for (const neighbor of Array.from(graph.neighbors(nodeId))) {
|
|
115
115
|
outDegree++;
|
|
116
116
|
if (weight) {
|
|
117
117
|
const edge = graph.getEdge(nodeId, neighbor);
|
|
@@ -178,7 +178,7 @@ export function pageRank(
|
|
|
178
178
|
const nodeOutWeight = outWeights.get(nodeId) ?? 0;
|
|
179
179
|
|
|
180
180
|
if (nodeOutWeight > 0) {
|
|
181
|
-
for (const neighbor of graph.neighbors(nodeId)) {
|
|
181
|
+
for (const neighbor of Array.from(graph.neighbors(nodeId))) {
|
|
182
182
|
let edgeWeight = 1;
|
|
183
183
|
if (weight) {
|
|
184
184
|
const edge = graph.getEdge(nodeId, neighbor);
|
|
@@ -295,7 +295,7 @@ function normalizeRanks(ranks: Map<NodeId, number>): void {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
if (sum > 0) {
|
|
298
|
-
for (const [nodeId, rank] of ranks) {
|
|
298
|
+
for (const [nodeId, rank] of Array.from(ranks)) {
|
|
299
299
|
ranks.set(nodeId, rank / sum);
|
|
300
300
|
}
|
|
301
301
|
}
|