@graphty/algorithms 1.1.0 → 1.3.1
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 +820 -235
- package/dist/algorithms.d.ts +42 -2
- package/dist/algorithms.js +11099 -2
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +124 -170
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +56 -149
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +3 -3
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +2 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +1 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +45 -0
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +47 -54
- 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 +13 -3
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +50 -35
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +3 -2
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +34 -39
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain-optimized.js +303 -0
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +17 -76
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
- package/dist/src/algorithms/community/modularity-utils.js +112 -0
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +8 -40
- package/dist/src/algorithms/matching/bipartite.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 +1 -12
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.js +368 -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/benchmark-all-algorithms.d.ts +23 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
- package/dist/src/benchmark-all-algorithms.js +462 -0
- package/dist/src/benchmark-all-algorithms.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +16 -15
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +209 -223
- 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 +16 -15
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +57 -21
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +8 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +158 -35
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.js +4 -4
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +17 -11
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +141 -217
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +6 -5
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +17 -8
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +17 -26
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +7 -11
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +143 -0
- package/dist/src/optimized/bit-packed.d.ts.map +1 -0
- package/dist/src/optimized/bit-packed.js +292 -0
- package/dist/src/optimized/bit-packed.js.map +1 -0
- package/dist/src/optimized/csr-graph.d.ts +86 -0
- package/dist/src/optimized/csr-graph.d.ts.map +1 -0
- package/dist/src/optimized/csr-graph.js +341 -0
- package/dist/src/optimized/csr-graph.js.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.js +223 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
- package/dist/src/optimized/graph-adapter.d.ts +62 -0
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
- package/dist/src/optimized/graph-adapter.js +146 -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 +28 -0
- package/dist/src/optimized/index.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +83 -0
- package/dist/src/research/grsbm.d.ts.map +1 -0
- package/dist/src/research/grsbm.js +403 -0
- package/dist/src/research/grsbm.js.map +1 -0
- package/dist/src/research/index.d.ts +12 -0
- package/dist/src/research/index.d.ts.map +1 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/index.js.map +1 -0
- package/dist/src/research/sync.d.ts +49 -0
- package/dist/src/research/sync.d.ts.map +1 -0
- package/dist/src/research/sync.js +316 -0
- package/dist/src/research/sync.js.map +1 -0
- package/dist/src/research/terahac.d.ts +65 -0
- package/dist/src/research/terahac.d.ts.map +1 -0
- package/dist/src/research/terahac.js +379 -0
- package/dist/src/research/terahac.js.map +1 -0
- 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 +79 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -0
- package/dist/src/utils/graph-converters.js +196 -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 +52 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -0
- package/dist/src/utils/math-utilities.js +123 -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 +33 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
- package/dist/src/utils/optimization-helpers.js +47 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -0
- package/package.json +28 -4
- package/src/algorithms/centrality/betweenness.ts +167 -199
- package/src/algorithms/centrality/closeness.ts +73 -191
- package/src/algorithms/centrality/degree.ts +3 -3
- package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
- package/src/algorithms/centrality/delta-pagerank.ts +435 -0
- package/src/algorithms/centrality/index.ts +2 -0
- package/src/algorithms/centrality/pagerank.ts +66 -0
- package/src/algorithms/community/girvan-newman.ts +52 -59
- package/src/algorithms/community/index.ts +2 -0
- package/src/algorithms/community/label-propagation.ts +64 -35
- package/src/algorithms/community/leiden.ts +40 -41
- package/src/algorithms/community/louvain-optimized.ts +445 -0
- package/src/algorithms/community/louvain.ts +24 -104
- package/src/algorithms/community/modularity-utils.ts +146 -0
- package/src/algorithms/matching/bipartite.ts +8 -44
- package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
- package/src/algorithms/shortest-path/dijkstra.ts +17 -15
- package/src/algorithms/traversal/bfs-unified.ts +433 -0
- package/src/algorithms/traversal/bfs-variants.ts +496 -0
- package/src/algorithms/traversal/bfs.ts +10 -228
- package/src/benchmark-all-algorithms.ts +548 -0
- package/src/clustering/hierarchical.ts +241 -251
- package/src/clustering/index.ts +4 -2
- package/src/clustering/k-core.ts +87 -39
- package/src/clustering/spectral.ts +185 -34
- package/src/core/graph.ts +4 -4
- package/src/flow/ford-fulkerson.ts +186 -254
- package/src/flow/min-cut.ts +21 -11
- package/src/index.ts +9 -0
- package/src/link-prediction/adamic-adar.ts +17 -30
- package/src/link-prediction/common-neighbors.ts +7 -16
- package/src/optimized/bit-packed.ts +340 -0
- package/src/optimized/csr-graph.ts +455 -0
- package/src/optimized/direction-optimized-bfs.ts +291 -0
- package/src/optimized/graph-adapter.ts +200 -0
- package/src/optimized/index.ts +36 -0
- package/src/research/grsbm.ts +580 -0
- package/src/research/index.ts +17 -0
- package/src/research/sync.ts +440 -0
- package/src/research/terahac.ts +514 -0
- package/src/types/index.ts +8 -0
- package/src/utils/algorithm-utilities.ts +2 -0
- package/src/utils/graph-converters.ts +241 -0
- package/src/utils/graph-utilities.ts +199 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/math-utilities.ts +140 -0
- package/src/utils/matrix-utilities.ts +2 -0
- package/src/utils/optimization-helpers.ts +81 -0
package/src/clustering/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export type {ClusterNode, HierarchicalClusteringResult, LinkageMethod} from "./hierarchical.js";
|
|
2
|
+
export {hierarchicalClustering} from "./hierarchical.js";
|
|
3
|
+
export type {KCoreResult} from "./k-core.js";
|
|
4
|
+
export {getKCore, kCoreDecomposition} from "./k-core.js";
|
|
3
5
|
export type {MCLOptions, MCLResult} from "./mcl.js";
|
|
4
6
|
export {calculateMCLModularity, markovClustering} from "./mcl.js";
|
|
5
7
|
export type {SpectralClusteringOptions, SpectralClusteringResult} from "./spectral.js";
|
package/src/clustering/k-core.ts
CHANGED
|
@@ -6,6 +6,41 @@
|
|
|
6
6
|
* understanding graph structure.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type {Graph} from "../core/graph.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert Graph to adjacency set representation
|
|
13
|
+
*/
|
|
14
|
+
function graphToAdjacencySet(graph: Graph): Map<string, Set<string>> {
|
|
15
|
+
const adjacency = new Map<string, Set<string>>();
|
|
16
|
+
|
|
17
|
+
// Initialize all nodes
|
|
18
|
+
for (const node of graph.nodes()) {
|
|
19
|
+
adjacency.set(String(node.id), new Set());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Add edges
|
|
23
|
+
for (const edge of graph.edges()) {
|
|
24
|
+
const source = String(edge.source);
|
|
25
|
+
const target = String(edge.target);
|
|
26
|
+
|
|
27
|
+
const sourceSet = adjacency.get(source);
|
|
28
|
+
if (sourceSet) {
|
|
29
|
+
sourceSet.add(target);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// For undirected graphs, add reverse edge
|
|
33
|
+
if (!graph.isDirected) {
|
|
34
|
+
const targetSet = adjacency.get(target);
|
|
35
|
+
if (targetSet) {
|
|
36
|
+
targetSet.add(source);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return adjacency;
|
|
42
|
+
}
|
|
43
|
+
|
|
9
44
|
export interface KCoreResult<T> {
|
|
10
45
|
cores: Map<number, Set<T>>;
|
|
11
46
|
coreness: Map<T, number>;
|
|
@@ -13,16 +48,9 @@ export interface KCoreResult<T> {
|
|
|
13
48
|
}
|
|
14
49
|
|
|
15
50
|
/**
|
|
16
|
-
* K-Core decomposition algorithm
|
|
17
|
-
* Finds all k-cores in the graph and assigns coreness values to nodes
|
|
18
|
-
*
|
|
19
|
-
* @param graph - Undirected graph as adjacency list
|
|
20
|
-
* @returns K-core decomposition results
|
|
21
|
-
*
|
|
22
|
-
* Time Complexity: O(V + E)
|
|
23
|
-
* Space Complexity: O(V)
|
|
51
|
+
* Internal implementation of K-Core decomposition algorithm
|
|
24
52
|
*/
|
|
25
|
-
|
|
53
|
+
function kCoreDecompositionImpl<T>(
|
|
26
54
|
graph: Map<T, Set<T>>,
|
|
27
55
|
): KCoreResult<T> {
|
|
28
56
|
if (graph.size === 0) {
|
|
@@ -191,12 +219,12 @@ export function kCoreDecomposition<T>(
|
|
|
191
219
|
* @param k - Core number
|
|
192
220
|
* @returns Set of nodes in k-core or higher
|
|
193
221
|
*/
|
|
194
|
-
export function getKCore
|
|
195
|
-
graph:
|
|
222
|
+
export function getKCore(
|
|
223
|
+
graph: Graph,
|
|
196
224
|
k: number,
|
|
197
|
-
): Set<
|
|
225
|
+
): Set<string> {
|
|
198
226
|
const {coreness} = kCoreDecomposition(graph);
|
|
199
|
-
const kCore = new Set<
|
|
227
|
+
const kCore = new Set<string>();
|
|
200
228
|
|
|
201
229
|
for (const [node, core] of coreness) {
|
|
202
230
|
if (core >= k) {
|
|
@@ -215,17 +243,18 @@ export function getKCore<T>(
|
|
|
215
243
|
* @param k - Core number
|
|
216
244
|
* @returns K-core subgraph
|
|
217
245
|
*/
|
|
218
|
-
export function getKCoreSubgraph
|
|
219
|
-
graph:
|
|
246
|
+
export function getKCoreSubgraph(
|
|
247
|
+
graph: Graph,
|
|
220
248
|
k: number,
|
|
221
|
-
): Map<
|
|
249
|
+
): Map<string, Set<string>> {
|
|
222
250
|
const kCoreNodes = getKCore(graph, k);
|
|
223
|
-
const
|
|
251
|
+
const adjacencySet = graphToAdjacencySet(graph);
|
|
252
|
+
const subgraph = new Map<string, Set<string>>();
|
|
224
253
|
|
|
225
254
|
for (const node of kCoreNodes) {
|
|
226
|
-
const neighbors =
|
|
255
|
+
const neighbors = adjacencySet.get(node);
|
|
227
256
|
if (neighbors) {
|
|
228
|
-
const coreNeighbors = new Set<
|
|
257
|
+
const coreNeighbors = new Set<string>();
|
|
229
258
|
for (const neighbor of neighbors) {
|
|
230
259
|
if (kCoreNodes.has(neighbor)) {
|
|
231
260
|
coreNeighbors.add(neighbor);
|
|
@@ -245,24 +274,25 @@ export function getKCoreSubgraph<T>(
|
|
|
245
274
|
* @param graph - Undirected graph
|
|
246
275
|
* @returns Array of nodes ordered by degeneracy
|
|
247
276
|
*/
|
|
248
|
-
export function degeneracyOrdering
|
|
249
|
-
graph:
|
|
250
|
-
):
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
277
|
+
export function degeneracyOrdering(
|
|
278
|
+
graph: Graph,
|
|
279
|
+
): string[] {
|
|
280
|
+
const adjacencySet = graphToAdjacencySet(graph);
|
|
281
|
+
const degree = new Map<string, number>();
|
|
282
|
+
const remaining = new Map<string, Set<string>>();
|
|
283
|
+
const ordering: string[] = [];
|
|
254
284
|
|
|
255
285
|
// Initialize
|
|
256
|
-
for (const [node, neighbors] of
|
|
286
|
+
for (const [node, neighbors] of adjacencySet) {
|
|
257
287
|
degree.set(node, neighbors.size);
|
|
258
288
|
remaining.set(node, new Set(neighbors));
|
|
259
289
|
}
|
|
260
290
|
|
|
261
291
|
// Build ordering
|
|
262
|
-
while (ordering.length <
|
|
292
|
+
while (ordering.length < adjacencySet.size) {
|
|
263
293
|
// Find minimum degree node
|
|
264
294
|
let minDegree = Infinity;
|
|
265
|
-
let minNode:
|
|
295
|
+
let minNode: string | undefined;
|
|
266
296
|
|
|
267
297
|
for (const [node, deg] of degree) {
|
|
268
298
|
if (!ordering.includes(node) && deg < minDegree) {
|
|
@@ -302,20 +332,22 @@ export function degeneracyOrdering<T>(
|
|
|
302
332
|
* @param k - Truss number (k >= 2)
|
|
303
333
|
* @returns Edges in k-truss
|
|
304
334
|
*/
|
|
305
|
-
export function kTruss
|
|
306
|
-
graph:
|
|
335
|
+
export function kTruss(
|
|
336
|
+
graph: Graph,
|
|
307
337
|
k: number,
|
|
308
338
|
): Set<string> {
|
|
309
339
|
if (k < 2) {
|
|
310
340
|
throw new Error("k must be at least 2 for k-truss");
|
|
311
341
|
}
|
|
312
342
|
|
|
343
|
+
const adjacencySet = graphToAdjacencySet(graph);
|
|
344
|
+
|
|
313
345
|
// Count triangles for each edge
|
|
314
346
|
const edgeTriangles = new Map<string, number>();
|
|
315
347
|
const edges = new Set<string>();
|
|
316
348
|
|
|
317
349
|
// Initialize edges
|
|
318
|
-
for (const [u, neighbors] of
|
|
350
|
+
for (const [u, neighbors] of adjacencySet) {
|
|
319
351
|
for (const v of neighbors) {
|
|
320
352
|
if (u < v) { // Avoid duplicates
|
|
321
353
|
const edge = `${String(u)},${String(v)}`;
|
|
@@ -326,10 +358,10 @@ export function kTruss<T>(
|
|
|
326
358
|
}
|
|
327
359
|
|
|
328
360
|
// Count triangles
|
|
329
|
-
for (const [u, uNeighbors] of
|
|
361
|
+
for (const [u, uNeighbors] of adjacencySet) {
|
|
330
362
|
for (const v of uNeighbors) {
|
|
331
363
|
if (u < v) {
|
|
332
|
-
const vNeighbors =
|
|
364
|
+
const vNeighbors = adjacencySet.get(v);
|
|
333
365
|
if (vNeighbors) {
|
|
334
366
|
// Find common neighbors (triangles)
|
|
335
367
|
for (const w of uNeighbors) {
|
|
@@ -390,16 +422,14 @@ export function kTruss<T>(
|
|
|
390
422
|
}
|
|
391
423
|
|
|
392
424
|
// Update triangle counts for affected edges
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
const uNeighbors = graph.get(uNode);
|
|
396
|
-
const vNeighbors = graph.get(vNode);
|
|
425
|
+
const uNeighbors = adjacencySet.get(u);
|
|
426
|
+
const vNeighbors = adjacencySet.get(v);
|
|
397
427
|
|
|
398
428
|
if (uNeighbors && vNeighbors) {
|
|
399
429
|
for (const w of uNeighbors) {
|
|
400
430
|
if (vNeighbors.has(w)) {
|
|
401
|
-
const edge1 =
|
|
402
|
-
const edge2 =
|
|
431
|
+
const edge1 = u < w ? `${String(u)},${String(w)}` : `${String(w)},${String(u)}`;
|
|
432
|
+
const edge2 = v < w ? `${String(v)},${String(w)}` : `${String(w)},${String(v)}`;
|
|
403
433
|
|
|
404
434
|
if (kTrussEdges.has(edge1)) {
|
|
405
435
|
const edge1Count = edgeTriangles.get(edge1);
|
|
@@ -458,3 +488,21 @@ export function toUndirected<T>(
|
|
|
458
488
|
return undirected;
|
|
459
489
|
}
|
|
460
490
|
|
|
491
|
+
/**
|
|
492
|
+
* K-Core decomposition algorithm
|
|
493
|
+
* Finds all k-cores in the graph and assigns coreness values to nodes
|
|
494
|
+
*
|
|
495
|
+
* @param graph - Undirected graph - accepts Graph class or Map<T, Set<T>>
|
|
496
|
+
* @returns K-core decomposition results
|
|
497
|
+
*
|
|
498
|
+
* Time Complexity: O(V + E)
|
|
499
|
+
* Space Complexity: O(V)
|
|
500
|
+
*/
|
|
501
|
+
export function kCoreDecomposition(
|
|
502
|
+
graph: Graph,
|
|
503
|
+
): KCoreResult<string> {
|
|
504
|
+
// Convert Graph to adjacency set representation
|
|
505
|
+
const adjacencySet = graphToAdjacencySet(graph);
|
|
506
|
+
return kCoreDecompositionImpl(adjacencySet);
|
|
507
|
+
}
|
|
508
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {Graph} from "../core/graph.js";
|
|
2
2
|
import type {NodeId} from "../types/index.js";
|
|
3
|
+
import {euclideanDistance} from "../utils/math-utilities.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Spectral Clustering implementation
|
|
@@ -27,6 +28,14 @@ export interface SpectralClusteringResult {
|
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Perform spectral clustering on a graph
|
|
31
|
+
*
|
|
32
|
+
* @warning This implementation uses simplified power iteration for eigenvector
|
|
33
|
+
* computation with approximate eigenvalues. For production use cases requiring
|
|
34
|
+
* precise clustering, consider using a proper linear algebra library like ml-matrix.
|
|
35
|
+
*
|
|
36
|
+
* The approximate eigenvalues (0.1, 0.2 for second and third eigenvectors) work
|
|
37
|
+
* well for most graph structures but may produce suboptimal results for graphs
|
|
38
|
+
* with unusual spectral properties.
|
|
30
39
|
*/
|
|
31
40
|
export function spectralClustering(
|
|
32
41
|
graph: Graph,
|
|
@@ -36,8 +45,14 @@ export function spectralClustering(
|
|
|
36
45
|
k,
|
|
37
46
|
laplacianType = "normalized",
|
|
38
47
|
maxIterations = 100,
|
|
48
|
+
tolerance = 1e-4,
|
|
39
49
|
} = options;
|
|
40
50
|
|
|
51
|
+
// Input validation
|
|
52
|
+
if (k < 1 || !Number.isInteger(k)) {
|
|
53
|
+
throw new Error("k must be a positive integer");
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
const nodes = Array.from(graph.nodes());
|
|
42
57
|
const nodeIds = nodes.map((node) => node.id);
|
|
43
58
|
const n = nodeIds.length;
|
|
@@ -59,13 +74,27 @@ export function spectralClustering(
|
|
|
59
74
|
// Find k smallest eigenvectors
|
|
60
75
|
const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
|
|
61
76
|
|
|
62
|
-
//
|
|
77
|
+
// Perform k-means clustering on the eigenvectors
|
|
78
|
+
// For spectral clustering, we need to transpose the eigenvector matrix
|
|
79
|
+
// Each row should be a data point (node) with features from the eigenvectors
|
|
80
|
+
const dataPoints: number[][] = [];
|
|
81
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
82
|
+
const point: number[] = [];
|
|
83
|
+
for (let j = 0; j < k; j++) {
|
|
84
|
+
const eigenvector = eigenResult.eigenvectors[j];
|
|
85
|
+
if (eigenvector) {
|
|
86
|
+
point.push(eigenvector[i] ?? 0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
dataPoints.push(point);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Normalize the data points row-wise (for normalized spectral clustering)
|
|
63
93
|
if (laplacianType === "normalized") {
|
|
64
|
-
normalizeRows(
|
|
94
|
+
normalizeRows(dataPoints);
|
|
65
95
|
}
|
|
66
96
|
|
|
67
|
-
|
|
68
|
-
const kmeans = kMeansClustering(eigenResult.eigenvectors, k, maxIterations);
|
|
97
|
+
const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance);
|
|
69
98
|
|
|
70
99
|
// Build communities
|
|
71
100
|
const communities: NodeId[][] = Array.from({length: k}, () => []);
|
|
@@ -265,32 +294,34 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
|
265
294
|
return {eigenvalues: [], eigenvectors: []};
|
|
266
295
|
}
|
|
267
296
|
|
|
268
|
-
// For small matrices
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
for (let i = 0; i < Math.min(k, n); i++) {
|
|
275
|
-
const vector = Array(n).fill(0) as number[];
|
|
276
|
-
vector[i] = 1;
|
|
277
|
-
eigenvectors.push(vector);
|
|
278
|
-
eigenvalues.push(i);
|
|
279
|
-
}
|
|
297
|
+
// For very small matrices, use the full power iteration approach
|
|
298
|
+
// Remove the simplified approach that was causing issues
|
|
299
|
+
if (k >= n) {
|
|
300
|
+
// If k >= n, we still need proper eigenvectors, not identity
|
|
301
|
+
// Fall through to the power iteration below
|
|
302
|
+
}
|
|
280
303
|
|
|
281
|
-
|
|
304
|
+
// For spectral clustering, we need proper eigenvectors
|
|
305
|
+
// Special handling for small k values which are common in clustering
|
|
306
|
+
if (k <= 3 && n > k) {
|
|
307
|
+
return computeSmallestEigenvectorsSimple(matrix, k, n);
|
|
282
308
|
}
|
|
283
309
|
|
|
284
|
-
// For larger
|
|
285
|
-
// This is still simplified but better than random
|
|
310
|
+
// For larger k, use power iteration
|
|
286
311
|
const eigenvectors: number[][] = [];
|
|
287
312
|
const eigenvalues: number[] = [];
|
|
288
|
-
const maxIterations =
|
|
313
|
+
const maxIterations = 100;
|
|
289
314
|
|
|
290
315
|
for (let eigIdx = 0; eigIdx < k; eigIdx++) {
|
|
291
316
|
// Initialize random vector
|
|
292
317
|
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
293
318
|
|
|
319
|
+
// Normalize initial vector
|
|
320
|
+
const initNorm = Math.sqrt(vector.reduce((sum, val) => sum + (val * val), 0));
|
|
321
|
+
if (initNorm > 0) {
|
|
322
|
+
vector = vector.map((val) => val / initNorm);
|
|
323
|
+
}
|
|
324
|
+
|
|
294
325
|
// Orthogonalize against previous eigenvectors
|
|
295
326
|
for (let j = 0; j < eigIdx; j++) {
|
|
296
327
|
const ejVector = eigenvectors[j];
|
|
@@ -400,6 +431,7 @@ function kMeansClustering(
|
|
|
400
431
|
data: number[][],
|
|
401
432
|
k: number,
|
|
402
433
|
maxIterations: number,
|
|
434
|
+
tolerance = 1e-4,
|
|
403
435
|
): {assignments: number[], centroids: number[][]} {
|
|
404
436
|
const n = data.length;
|
|
405
437
|
const d = data[0]?.length ?? 0;
|
|
@@ -464,21 +496,24 @@ function kMeansClustering(
|
|
|
464
496
|
assignments[i] = bestCluster;
|
|
465
497
|
}
|
|
466
498
|
|
|
467
|
-
// Check for convergence
|
|
468
|
-
let
|
|
499
|
+
// Check for convergence based on assignment changes
|
|
500
|
+
let assignmentsChanged = false;
|
|
469
501
|
for (let i = 0; i < n; i++) {
|
|
470
502
|
if (assignments[i] !== oldAssignments[i]) {
|
|
471
|
-
|
|
503
|
+
assignmentsChanged = true;
|
|
472
504
|
break;
|
|
473
505
|
}
|
|
474
506
|
}
|
|
475
507
|
|
|
476
|
-
if (!
|
|
508
|
+
if (!assignmentsChanged) {
|
|
477
509
|
break;
|
|
478
510
|
}
|
|
479
511
|
|
|
480
512
|
oldAssignments = [... assignments];
|
|
481
513
|
|
|
514
|
+
// Store old centroids for tolerance-based convergence check
|
|
515
|
+
const oldCentroids = centroids.map((c) => [... c]);
|
|
516
|
+
|
|
482
517
|
// Update centroids
|
|
483
518
|
const counts = Array(k).fill(0) as number[];
|
|
484
519
|
const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
|
|
@@ -518,28 +553,144 @@ function kMeansClustering(
|
|
|
518
553
|
for (let j = 0; j < d; j++) {
|
|
519
554
|
const sumVal = sumsRow[j];
|
|
520
555
|
if (sumVal !== undefined) {
|
|
521
|
-
const
|
|
522
|
-
if (
|
|
523
|
-
centroidsRow[j] = sumVal /
|
|
556
|
+
const countValInner = counts[i];
|
|
557
|
+
if (countValInner !== undefined) {
|
|
558
|
+
centroidsRow[j] = sumVal / countValInner;
|
|
524
559
|
}
|
|
525
560
|
}
|
|
526
561
|
}
|
|
527
562
|
}
|
|
528
563
|
}
|
|
529
564
|
}
|
|
565
|
+
|
|
566
|
+
// Check for tolerance-based convergence (centroid movement)
|
|
567
|
+
let maxCentroidShift = 0;
|
|
568
|
+
for (let i = 0; i < k; i++) {
|
|
569
|
+
const oldCentroid = oldCentroids[i];
|
|
570
|
+
const newCentroid = centroids[i];
|
|
571
|
+
if (oldCentroid && newCentroid) {
|
|
572
|
+
const shift = euclideanDistance(oldCentroid, newCentroid);
|
|
573
|
+
if (shift > maxCentroidShift) {
|
|
574
|
+
maxCentroidShift = shift;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (maxCentroidShift < tolerance) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
530
582
|
}
|
|
531
583
|
|
|
532
584
|
return {assignments, centroids};
|
|
533
585
|
}
|
|
534
586
|
|
|
535
587
|
/**
|
|
536
|
-
*
|
|
588
|
+
* Compute smallest eigenvectors for small k (optimized for k=2, k=3)
|
|
537
589
|
*/
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
590
|
+
function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: number): {
|
|
591
|
+
eigenvalues: number[];
|
|
592
|
+
eigenvectors: number[][];
|
|
593
|
+
} {
|
|
594
|
+
const eigenvectors: number[][] = [];
|
|
595
|
+
const eigenvalues: number[] = [];
|
|
596
|
+
|
|
597
|
+
// First eigenvector is constant (corresponds to eigenvalue 0 for connected graph)
|
|
598
|
+
const firstVector = Array(n).fill(1 / Math.sqrt(n)) as number[];
|
|
599
|
+
eigenvectors.push(firstVector);
|
|
600
|
+
eigenvalues.push(0);
|
|
601
|
+
|
|
602
|
+
// For k >= 2, compute the Fiedler vector (second smallest eigenvector)
|
|
603
|
+
if (k >= 2) {
|
|
604
|
+
// Use power iteration on I - L/lambda_max to find second smallest
|
|
605
|
+
const maxEig = 2; // For normalized Laplacian, max eigenvalue <= 2
|
|
606
|
+
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
607
|
+
|
|
608
|
+
// Make orthogonal to first eigenvector
|
|
609
|
+
const dot1 = vector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
|
|
610
|
+
vector = vector.map((val) => val - (dot1 / Math.sqrt(n)));
|
|
611
|
+
|
|
612
|
+
// Power iteration on shifted matrix
|
|
613
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
614
|
+
// Compute (I - L/maxEig) * v
|
|
615
|
+
const newVector = Array(n).fill(0) as number[];
|
|
616
|
+
|
|
617
|
+
// Identity part
|
|
618
|
+
for (let i = 0; i < n; i++) {
|
|
619
|
+
newVector[i] = vector[i] ?? 0;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Subtract L * v / maxEig
|
|
623
|
+
for (let i = 0; i < n; i++) {
|
|
624
|
+
for (let j = 0; j < n; j++) {
|
|
625
|
+
const matrixVal = matrix[i]?.[j] ?? 0;
|
|
626
|
+
const vecVal = vector[j] ?? 0;
|
|
627
|
+
newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / maxEig);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Orthogonalize against first eigenvector
|
|
632
|
+
const dot = newVector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
|
|
633
|
+
for (let i = 0; i < n; i++) {
|
|
634
|
+
newVector[i] = (newVector[i] ?? 0) - (dot / Math.sqrt(n));
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Normalize
|
|
638
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
|
|
639
|
+
if (norm > 1e-10) {
|
|
640
|
+
vector = newVector.map((val) => val / norm);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
eigenvectors.push(vector);
|
|
645
|
+
eigenvalues.push(0.1); // Approximate
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// For k = 3, add another eigenvector
|
|
649
|
+
if (k >= 3) {
|
|
650
|
+
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
651
|
+
|
|
652
|
+
// Orthogonalize against previous eigenvectors
|
|
653
|
+
for (const prev of eigenvectors) {
|
|
654
|
+
const dot = vector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
|
|
655
|
+
vector = vector.map((val, idx) => val - (dot * (prev[idx] ?? 0)));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Similar power iteration
|
|
659
|
+
for (let iter = 0; iter < 50; iter++) {
|
|
660
|
+
const newVector = Array(n).fill(0) as number[];
|
|
661
|
+
|
|
662
|
+
// Identity part
|
|
663
|
+
for (let i = 0; i < n; i++) {
|
|
664
|
+
newVector[i] = vector[i] ?? 0;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Subtract L * v / 2
|
|
668
|
+
for (let i = 0; i < n; i++) {
|
|
669
|
+
for (let j = 0; j < n; j++) {
|
|
670
|
+
const matrixVal = matrix[i]?.[j] ?? 0;
|
|
671
|
+
const vecVal = vector[j] ?? 0;
|
|
672
|
+
newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / 2);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Orthogonalize
|
|
677
|
+
for (const prev of eigenvectors) {
|
|
678
|
+
const dot = newVector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
|
|
679
|
+
for (let i = 0; i < n; i++) {
|
|
680
|
+
newVector[i] = (newVector[i] ?? 0) - (dot * (prev[i] ?? 0));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Normalize
|
|
685
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
|
|
686
|
+
if (norm > 1e-10) {
|
|
687
|
+
vector = newVector.map((val) => val / norm);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
eigenvectors.push(vector);
|
|
692
|
+
eigenvalues.push(0.2); // Approximate
|
|
543
693
|
}
|
|
544
|
-
|
|
694
|
+
|
|
695
|
+
return {eigenvalues: eigenvalues.slice(0, k), eigenvectors: eigenvectors.slice(0, k)};
|
|
545
696
|
}
|
package/src/core/graph.ts
CHANGED
|
@@ -222,7 +222,7 @@ export class Graph {
|
|
|
222
222
|
* Get all edges in the graph
|
|
223
223
|
*/
|
|
224
224
|
*edges(): IterableIterator<Edge> {
|
|
225
|
-
for (const [source, edges] of
|
|
225
|
+
for (const [source, edges] of this.adjacencyList) {
|
|
226
226
|
for (const edge of edges.values()) {
|
|
227
227
|
// For undirected graphs, only yield each edge once
|
|
228
228
|
if (!this.config.directed && source > edge.target) {
|
|
@@ -300,12 +300,12 @@ export class Graph {
|
|
|
300
300
|
const cloned = new Graph(this.config);
|
|
301
301
|
|
|
302
302
|
// Copy nodes
|
|
303
|
-
for (const node of
|
|
303
|
+
for (const node of this.nodeMap.values()) {
|
|
304
304
|
cloned.addNode(node.id, node.data ? {... node.data} : undefined);
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// Copy edges
|
|
308
|
-
for (const edge of
|
|
308
|
+
for (const edge of this.edges()) {
|
|
309
309
|
cloned.addEdge(
|
|
310
310
|
edge.source,
|
|
311
311
|
edge.target,
|
|
@@ -346,7 +346,7 @@ export class Graph {
|
|
|
346
346
|
// For undirected graphs, we need to count each edge only once
|
|
347
347
|
let count = 0;
|
|
348
348
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
349
|
-
for (const _edge of
|
|
349
|
+
for (const _edge of this.edges()) {
|
|
350
350
|
count++;
|
|
351
351
|
}
|
|
352
352
|
return count;
|