@graphty/algorithms 1.2.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 +490 -336
- 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 +40 -21
- 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 +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -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.map +1 -1
- package/dist/src/research/grsbm.js +6 -14
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +3 -29
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +2 -0
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +7 -3
- 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 +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 +18 -3
- 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 +44 -20
- 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 +6 -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 +7 -16
- package/src/research/sync.ts +3 -32
- package/src/research/terahac.ts +10 -2
- 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;
|
|
@@ -79,7 +94,7 @@ export function spectralClustering(
|
|
|
79
94
|
normalizeRows(dataPoints);
|
|
80
95
|
}
|
|
81
96
|
|
|
82
|
-
const kmeans = kMeansClustering(dataPoints, k, maxIterations);
|
|
97
|
+
const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance);
|
|
83
98
|
|
|
84
99
|
// Build communities
|
|
85
100
|
const communities: NodeId[][] = Array.from({length: k}, () => []);
|
|
@@ -416,6 +431,7 @@ function kMeansClustering(
|
|
|
416
431
|
data: number[][],
|
|
417
432
|
k: number,
|
|
418
433
|
maxIterations: number,
|
|
434
|
+
tolerance = 1e-4,
|
|
419
435
|
): {assignments: number[], centroids: number[][]} {
|
|
420
436
|
const n = data.length;
|
|
421
437
|
const d = data[0]?.length ?? 0;
|
|
@@ -480,21 +496,24 @@ function kMeansClustering(
|
|
|
480
496
|
assignments[i] = bestCluster;
|
|
481
497
|
}
|
|
482
498
|
|
|
483
|
-
// Check for convergence
|
|
484
|
-
let
|
|
499
|
+
// Check for convergence based on assignment changes
|
|
500
|
+
let assignmentsChanged = false;
|
|
485
501
|
for (let i = 0; i < n; i++) {
|
|
486
502
|
if (assignments[i] !== oldAssignments[i]) {
|
|
487
|
-
|
|
503
|
+
assignmentsChanged = true;
|
|
488
504
|
break;
|
|
489
505
|
}
|
|
490
506
|
}
|
|
491
507
|
|
|
492
|
-
if (!
|
|
508
|
+
if (!assignmentsChanged) {
|
|
493
509
|
break;
|
|
494
510
|
}
|
|
495
511
|
|
|
496
512
|
oldAssignments = [... assignments];
|
|
497
513
|
|
|
514
|
+
// Store old centroids for tolerance-based convergence check
|
|
515
|
+
const oldCentroids = centroids.map((c) => [... c]);
|
|
516
|
+
|
|
498
517
|
// Update centroids
|
|
499
518
|
const counts = Array(k).fill(0) as number[];
|
|
500
519
|
const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
|
|
@@ -534,30 +553,35 @@ function kMeansClustering(
|
|
|
534
553
|
for (let j = 0; j < d; j++) {
|
|
535
554
|
const sumVal = sumsRow[j];
|
|
536
555
|
if (sumVal !== undefined) {
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
539
|
-
centroidsRow[j] = sumVal /
|
|
556
|
+
const countValInner = counts[i];
|
|
557
|
+
if (countValInner !== undefined) {
|
|
558
|
+
centroidsRow[j] = sumVal / countValInner;
|
|
540
559
|
}
|
|
541
560
|
}
|
|
542
561
|
}
|
|
543
562
|
}
|
|
544
563
|
}
|
|
545
564
|
}
|
|
546
|
-
}
|
|
547
565
|
|
|
548
|
-
|
|
549
|
-
|
|
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
|
+
}
|
|
550
578
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
function euclideanDistance(a: number[], b: number[]): number {
|
|
555
|
-
let sum = 0;
|
|
556
|
-
for (let i = 0; i < a.length; i++) {
|
|
557
|
-
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
558
|
-
sum += diff * diff;
|
|
579
|
+
if (maxCentroidShift < tolerance) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
559
582
|
}
|
|
560
|
-
|
|
583
|
+
|
|
584
|
+
return {assignments, centroids};
|
|
561
585
|
}
|
|
562
586
|
|
|
563
587
|
/**
|
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;
|