@optave/codegraph 3.5.0 → 3.7.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 +47 -21
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +119 -127
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/db/connection.d.ts +12 -2
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +81 -53
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +38 -32
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +51 -66
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +62 -70
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/diff-impact.d.ts +9 -7
- package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
- package/dist/domain/analysis/exports.d.ts.map +1 -1
- package/dist/domain/analysis/exports.js +29 -33
- package/dist/domain/analysis/exports.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +15 -17
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +35 -65
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +91 -6
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +20 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
- package/dist/domain/analysis/query-helpers.js +27 -0
- package/dist/domain/analysis/query-helpers.js.map +1 -0
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +15 -9
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +3 -2
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +69 -3
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +7 -51
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +7 -5
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +2 -2
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +124 -105
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/resolve.d.ts +0 -4
- package/dist/domain/graph/resolve.d.ts.map +1 -1
- package/dist/domain/graph/resolve.js +32 -48
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +12 -12
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +206 -101
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
- package/dist/domain/search/search/cli-formatter.js +88 -83
- package/dist/domain/search/search/cli-formatter.js.map +1 -1
- package/dist/extractors/bash.d.ts +6 -0
- package/dist/extractors/bash.d.ts.map +1 -0
- package/dist/extractors/bash.js +91 -0
- package/dist/extractors/bash.js.map +1 -0
- package/dist/extractors/c.d.ts +6 -0
- package/dist/extractors/c.d.ts.map +1 -0
- package/dist/extractors/c.js +204 -0
- package/dist/extractors/c.js.map +1 -0
- package/dist/extractors/cpp.d.ts +6 -0
- package/dist/extractors/cpp.d.ts.map +1 -0
- package/dist/extractors/cpp.js +283 -0
- package/dist/extractors/cpp.js.map +1 -0
- package/dist/extractors/csharp.d.ts.map +1 -1
- package/dist/extractors/csharp.js +42 -54
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/dart.d.ts +6 -0
- package/dist/extractors/dart.d.ts.map +1 -0
- package/dist/extractors/dart.js +277 -0
- package/dist/extractors/dart.js.map +1 -0
- package/dist/extractors/elixir.d.ts +9 -0
- package/dist/extractors/elixir.d.ts.map +1 -0
- package/dist/extractors/elixir.js +223 -0
- package/dist/extractors/elixir.js.map +1 -0
- package/dist/extractors/go.d.ts.map +1 -1
- package/dist/extractors/go.js +126 -130
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/haskell.d.ts +8 -0
- package/dist/extractors/haskell.d.ts.map +1 -0
- package/dist/extractors/haskell.js +217 -0
- package/dist/extractors/haskell.js.map +1 -0
- package/dist/extractors/hcl.js +6 -6
- package/dist/extractors/hcl.js.map +1 -1
- package/dist/extractors/helpers.d.ts +32 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +74 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/index.d.ts +12 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +12 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +32 -47
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +306 -292
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.d.ts +6 -0
- package/dist/extractors/kotlin.d.ts.map +1 -0
- package/dist/extractors/kotlin.js +275 -0
- package/dist/extractors/kotlin.js.map +1 -0
- package/dist/extractors/lua.d.ts +6 -0
- package/dist/extractors/lua.d.ts.map +1 -0
- package/dist/extractors/lua.js +162 -0
- package/dist/extractors/lua.js.map +1 -0
- package/dist/extractors/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +236 -0
- package/dist/extractors/ocaml.js.map +1 -0
- package/dist/extractors/php.d.ts.map +1 -1
- package/dist/extractors/php.js +39 -44
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.d.ts.map +1 -1
- package/dist/extractors/python.js +75 -93
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/ruby.js +6 -13
- package/dist/extractors/ruby.js.map +1 -1
- package/dist/extractors/rust.d.ts.map +1 -1
- package/dist/extractors/rust.js +58 -83
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/scala.d.ts +6 -0
- package/dist/extractors/scala.d.ts.map +1 -0
- package/dist/extractors/scala.js +269 -0
- package/dist/extractors/scala.js.map +1 -0
- package/dist/extractors/swift.d.ts +6 -0
- package/dist/extractors/swift.d.ts.map +1 -0
- package/dist/extractors/swift.js +275 -0
- package/dist/extractors/swift.js.map +1 -0
- package/dist/extractors/zig.d.ts +9 -0
- package/dist/extractors/zig.d.ts.map +1 -0
- package/dist/extractors/zig.js +276 -0
- package/dist/extractors/zig.js.map +1 -0
- package/dist/features/ast.d.ts +2 -0
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +9 -24
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +17 -21
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +47 -3
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/cfg.d.ts +7 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +72 -61
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +79 -62
- package/dist/features/check.js.map +1 -1
- package/dist/features/complexity-query.d.ts.map +1 -1
- package/dist/features/complexity-query.js +142 -137
- package/dist/features/complexity-query.js.map +1 -1
- package/dist/features/complexity.d.ts +7 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +62 -1
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts +7 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +356 -188
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +117 -104
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +25 -4
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +29 -4
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +35 -15
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.js +88 -73
- package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
- package/dist/graph/algorithms/leiden/index.js +43 -28
- package/dist/graph/algorithms/leiden/index.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +90 -104
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +89 -106
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts +2 -0
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +20 -8
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts +0 -8
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +73 -62
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +0 -8
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +12 -14
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +45 -36
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/audit.d.ts.map +1 -1
- package/dist/presentation/audit.js +61 -57
- package/dist/presentation/audit.js.map +1 -1
- package/dist/presentation/branch-compare.d.ts.map +1 -1
- package/dist/presentation/branch-compare.js +56 -38
- package/dist/presentation/branch-compare.js.map +1 -1
- package/dist/presentation/check.d.ts.map +1 -1
- package/dist/presentation/check.js +30 -32
- package/dist/presentation/check.js.map +1 -1
- package/dist/presentation/colors.d.ts.map +1 -1
- package/dist/presentation/colors.js +2 -0
- package/dist/presentation/colors.js.map +1 -1
- package/dist/presentation/complexity.d.ts.map +1 -1
- package/dist/presentation/complexity.js +25 -19
- package/dist/presentation/complexity.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +15 -15
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +29 -19
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/types.d.ts +182 -7
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +19 -7
- package/src/ast-analysis/engine.ts +147 -138
- package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
- package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
- package/src/db/connection.ts +90 -59
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +36 -32
- package/src/domain/analysis/context.ts +73 -75
- package/src/domain/analysis/dependencies.ts +78 -68
- package/src/domain/analysis/exports.ts +45 -34
- package/src/domain/analysis/fn-impact.ts +67 -64
- package/src/domain/analysis/module-map.ts +103 -8
- package/src/domain/analysis/query-helpers.ts +35 -0
- package/src/domain/graph/builder/helpers.ts +12 -6
- package/src/domain/graph/builder/incremental.ts +3 -2
- package/src/domain/graph/builder/pipeline.ts +71 -3
- package/src/domain/graph/builder/stages/build-edges.ts +10 -75
- package/src/domain/graph/builder/stages/build-structure.ts +9 -7
- package/src/domain/graph/builder/stages/collect-files.ts +2 -2
- package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
- package/src/domain/graph/builder/stages/finalize.ts +159 -125
- package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
- package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
- package/src/domain/graph/resolve.ts +34 -46
- package/src/domain/graph/watcher.ts +12 -14
- package/src/domain/parser.ts +222 -97
- package/src/domain/search/search/cli-formatter.ts +121 -94
- package/src/extractors/bash.ts +97 -0
- package/src/extractors/c.ts +212 -0
- package/src/extractors/cpp.ts +298 -0
- package/src/extractors/csharp.ts +53 -56
- package/src/extractors/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/go.ts +152 -134
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/hcl.ts +6 -6
- package/src/extractors/helpers.ts +93 -1
- package/src/extractors/index.ts +12 -0
- package/src/extractors/java.ts +43 -48
- package/src/extractors/javascript.ts +328 -281
- package/src/extractors/kotlin.ts +293 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/ocaml.ts +259 -0
- package/src/extractors/php.ts +46 -40
- package/src/extractors/python.ts +81 -104
- package/src/extractors/ruby.ts +6 -13
- package/src/extractors/rust.ts +65 -85
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/ast.ts +10 -25
- package/src/features/audit.ts +24 -20
- package/src/features/branch-compare.ts +51 -4
- package/src/features/cfg.ts +113 -65
- package/src/features/check.ts +90 -74
- package/src/features/complexity-query.ts +181 -163
- package/src/features/complexity.ts +64 -1
- package/src/features/dataflow.ts +462 -217
- package/src/features/graph-enrichment.ts +161 -117
- package/src/features/sequence.ts +27 -4
- package/src/features/structure-query.ts +43 -4
- package/src/features/structure.ts +50 -22
- package/src/graph/algorithms/leiden/adapter.ts +126 -71
- package/src/graph/algorithms/leiden/index.ts +67 -28
- package/src/graph/algorithms/leiden/optimiser.ts +114 -105
- package/src/graph/algorithms/leiden/partition.ts +131 -98
- package/src/graph/model.ts +19 -7
- package/src/infrastructure/config.ts +60 -58
- package/src/infrastructure/registry.ts +17 -14
- package/src/mcp/server.ts +46 -37
- package/src/presentation/audit.ts +72 -67
- package/src/presentation/branch-compare.ts +54 -50
- package/src/presentation/check.ts +34 -34
- package/src/presentation/colors.ts +2 -0
- package/src/presentation/complexity.ts +39 -33
- package/src/presentation/queries-cli/exports.ts +17 -17
- package/src/presentation/queries-cli/impact.ts +30 -22
- package/src/types.ts +195 -7
|
@@ -50,6 +50,111 @@ function taAdd(a: Float64Array, i: number, v: number): void {
|
|
|
50
50
|
a[i] = taGet(a, i) + v;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Populate edge arrays for a directed graph. Each edge is stored once in
|
|
55
|
+
* outEdges[from] and inEdges[to]. Self-loops are tracked in both the selfLoop
|
|
56
|
+
* array and the adjacency lists (partition.ts accounts for this).
|
|
57
|
+
*/
|
|
58
|
+
function populateDirectedEdges(
|
|
59
|
+
graph: CodeGraph,
|
|
60
|
+
idToIndex: Map<string, number>,
|
|
61
|
+
linkWeight: (attrs: EdgeAttrs) => number,
|
|
62
|
+
selfLoop: Float64Array,
|
|
63
|
+
outEdges: EdgeEntry[][],
|
|
64
|
+
inEdges: InEdgeEntry[][],
|
|
65
|
+
strengthOut: Float64Array,
|
|
66
|
+
strengthIn: Float64Array,
|
|
67
|
+
): void {
|
|
68
|
+
for (const [src, tgt, attrs] of graph.edges()) {
|
|
69
|
+
const from = idToIndex.get(src);
|
|
70
|
+
const to = idToIndex.get(tgt);
|
|
71
|
+
if (from == null || to == null) continue;
|
|
72
|
+
const w: number = +linkWeight(attrs) || 0;
|
|
73
|
+
if (from === to) {
|
|
74
|
+
taAdd(selfLoop, from, w);
|
|
75
|
+
// Self-loop is intentionally kept in outEdges/inEdges as well.
|
|
76
|
+
// partition.ts's moveNodeToCommunity (directed path) accounts for this
|
|
77
|
+
// by subtracting selfLoopWeight once from outToOld+inFromOld to avoid
|
|
78
|
+
// triple-counting (see partition.ts moveNodeToCommunity directed block).
|
|
79
|
+
}
|
|
80
|
+
(outEdges[from] as EdgeEntry[]).push({ to, w });
|
|
81
|
+
(inEdges[to] as InEdgeEntry[]).push({ from, w });
|
|
82
|
+
taAdd(strengthOut, from, w);
|
|
83
|
+
taAdd(strengthIn, to, w);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Populate edge arrays for an undirected graph. Reciprocal pairs are
|
|
89
|
+
* symmetrized and averaged to produce a single weight per undirected edge.
|
|
90
|
+
* Self-loops use single-w convention (matching modularity.ts formulas).
|
|
91
|
+
*/
|
|
92
|
+
function populateUndirectedEdges(
|
|
93
|
+
graph: CodeGraph,
|
|
94
|
+
idToIndex: Map<string, number>,
|
|
95
|
+
linkWeight: (attrs: EdgeAttrs) => number,
|
|
96
|
+
n: number,
|
|
97
|
+
selfLoop: Float64Array,
|
|
98
|
+
outEdges: EdgeEntry[][],
|
|
99
|
+
inEdges: InEdgeEntry[][],
|
|
100
|
+
strengthOut: Float64Array,
|
|
101
|
+
strengthIn: Float64Array,
|
|
102
|
+
): void {
|
|
103
|
+
const pairAgg = new Map<string, { sum: number; seenAB: number; seenBA: number }>();
|
|
104
|
+
|
|
105
|
+
for (const [src, tgt, attrs] of graph.edges()) {
|
|
106
|
+
const a = idToIndex.get(src);
|
|
107
|
+
const b = idToIndex.get(tgt);
|
|
108
|
+
if (a == null || b == null) continue;
|
|
109
|
+
const w: number = +linkWeight(attrs) || 0;
|
|
110
|
+
if (a === b) {
|
|
111
|
+
taAdd(selfLoop, a, w);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const i = a < b ? a : b;
|
|
115
|
+
const j = a < b ? b : a;
|
|
116
|
+
const key = `${i}:${j}`;
|
|
117
|
+
let rec = pairAgg.get(key);
|
|
118
|
+
if (!rec) {
|
|
119
|
+
rec = { sum: 0, seenAB: 0, seenBA: 0 };
|
|
120
|
+
pairAgg.set(key, rec);
|
|
121
|
+
}
|
|
122
|
+
rec.sum += w;
|
|
123
|
+
if (a === i) rec.seenAB = 1;
|
|
124
|
+
else rec.seenBA = 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const [key, rec] of pairAgg.entries()) {
|
|
128
|
+
const parts = key.split(':');
|
|
129
|
+
const i = +(parts[0] as string);
|
|
130
|
+
const j = +(parts[1] as string);
|
|
131
|
+
const dirCount: number = (rec.seenAB ? 1 : 0) + (rec.seenBA ? 1 : 0);
|
|
132
|
+
const w: number = dirCount > 0 ? rec.sum / dirCount : 0;
|
|
133
|
+
if (w === 0) continue;
|
|
134
|
+
(outEdges[i] as EdgeEntry[]).push({ to: j, w });
|
|
135
|
+
(outEdges[j] as EdgeEntry[]).push({ to: i, w });
|
|
136
|
+
(inEdges[i] as InEdgeEntry[]).push({ from: j, w });
|
|
137
|
+
(inEdges[j] as InEdgeEntry[]).push({ from: i, w });
|
|
138
|
+
taAdd(strengthOut, i, w);
|
|
139
|
+
taAdd(strengthOut, j, w);
|
|
140
|
+
taAdd(strengthIn, i, w);
|
|
141
|
+
taAdd(strengthIn, j, w);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Add self-loops into adjacency and strengths.
|
|
145
|
+
// Note: uses single-w convention (not standard 2w) — the modularity formulas in
|
|
146
|
+
// modularity.ts are written to match this convention, keeping the system self-consistent.
|
|
147
|
+
for (let v = 0; v < n; v++) {
|
|
148
|
+
const w: number = taGet(selfLoop, v);
|
|
149
|
+
if (w !== 0) {
|
|
150
|
+
(outEdges[v] as EdgeEntry[]).push({ to: v, w });
|
|
151
|
+
(inEdges[v] as InEdgeEntry[]).push({ from: v, w });
|
|
152
|
+
taAdd(strengthOut, v, w);
|
|
153
|
+
taAdd(strengthIn, v, w);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
53
158
|
export function makeGraphAdapter(graph: CodeGraph, opts: GraphAdapterOptions = {}): GraphAdapter {
|
|
54
159
|
const linkWeight: (attrs: EdgeAttrs) => number =
|
|
55
160
|
opts.linkWeight || ((attrs) => (attrs && typeof attrs.weight === 'number' ? attrs.weight : 1));
|
|
@@ -92,78 +197,28 @@ export function makeGraphAdapter(graph: CodeGraph, opts: GraphAdapterOptions = {
|
|
|
92
197
|
|
|
93
198
|
// Populate from graph
|
|
94
199
|
if (directed) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// triple-counting (see partition.ts moveNodeToCommunity directed block).
|
|
106
|
-
}
|
|
107
|
-
(outEdges[from] as EdgeEntry[]).push({ to, w });
|
|
108
|
-
(inEdges[to] as InEdgeEntry[]).push({ from, w });
|
|
109
|
-
taAdd(strengthOut, from, w);
|
|
110
|
-
taAdd(strengthIn, to, w);
|
|
111
|
-
}
|
|
200
|
+
populateDirectedEdges(
|
|
201
|
+
graph,
|
|
202
|
+
idToIndex,
|
|
203
|
+
linkWeight,
|
|
204
|
+
selfLoop,
|
|
205
|
+
outEdges,
|
|
206
|
+
inEdges,
|
|
207
|
+
strengthOut,
|
|
208
|
+
strengthIn,
|
|
209
|
+
);
|
|
112
210
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
const i = a < b ? a : b;
|
|
126
|
-
const j = a < b ? b : a;
|
|
127
|
-
const key = `${i}:${j}`;
|
|
128
|
-
let rec = pairAgg.get(key);
|
|
129
|
-
if (!rec) {
|
|
130
|
-
rec = { sum: 0, seenAB: 0, seenBA: 0 };
|
|
131
|
-
pairAgg.set(key, rec);
|
|
132
|
-
}
|
|
133
|
-
rec.sum += w;
|
|
134
|
-
if (a === i) rec.seenAB = 1;
|
|
135
|
-
else rec.seenBA = 1;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
for (const [key, rec] of pairAgg.entries()) {
|
|
139
|
-
const parts = key.split(':');
|
|
140
|
-
const i = +(parts[0] as string);
|
|
141
|
-
const j = +(parts[1] as string);
|
|
142
|
-
const dirCount: number = (rec.seenAB ? 1 : 0) + (rec.seenBA ? 1 : 0);
|
|
143
|
-
const w: number = dirCount > 0 ? rec.sum / dirCount : 0;
|
|
144
|
-
if (w === 0) continue;
|
|
145
|
-
(outEdges[i] as EdgeEntry[]).push({ to: j, w });
|
|
146
|
-
(outEdges[j] as EdgeEntry[]).push({ to: i, w });
|
|
147
|
-
(inEdges[i] as InEdgeEntry[]).push({ from: j, w });
|
|
148
|
-
(inEdges[j] as InEdgeEntry[]).push({ from: i, w });
|
|
149
|
-
taAdd(strengthOut, i, w);
|
|
150
|
-
taAdd(strengthOut, j, w);
|
|
151
|
-
taAdd(strengthIn, i, w);
|
|
152
|
-
taAdd(strengthIn, j, w);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Add self-loops into adjacency and strengths.
|
|
156
|
-
// Note: uses single-w convention (not standard 2w) — the modularity formulas in
|
|
157
|
-
// modularity.ts are written to match this convention, keeping the system self-consistent.
|
|
158
|
-
for (let v = 0; v < n; v++) {
|
|
159
|
-
const w: number = taGet(selfLoop, v);
|
|
160
|
-
if (w !== 0) {
|
|
161
|
-
(outEdges[v] as EdgeEntry[]).push({ to: v, w });
|
|
162
|
-
(inEdges[v] as InEdgeEntry[]).push({ from: v, w });
|
|
163
|
-
taAdd(strengthOut, v, w);
|
|
164
|
-
taAdd(strengthIn, v, w);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
211
|
+
populateUndirectedEdges(
|
|
212
|
+
graph,
|
|
213
|
+
idToIndex,
|
|
214
|
+
linkWeight,
|
|
215
|
+
n,
|
|
216
|
+
selfLoop,
|
|
217
|
+
outEdges,
|
|
218
|
+
inEdges,
|
|
219
|
+
strengthOut,
|
|
220
|
+
strengthIn,
|
|
221
|
+
);
|
|
167
222
|
}
|
|
168
223
|
|
|
169
224
|
// Node sizes
|
|
@@ -119,34 +119,17 @@ interface OriginalPartition {
|
|
|
119
119
|
getInEdgeWeightFromCommunity(c: number): number;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const totalStr = new Float64Array(cc);
|
|
134
|
-
const totalOutStr = new Float64Array(cc);
|
|
135
|
-
const totalInStr = new Float64Array(cc);
|
|
136
|
-
const totalSize = new Float64Array(cc);
|
|
137
|
-
|
|
138
|
-
for (let i = 0; i < n; i++) {
|
|
139
|
-
const c: number = iget(communityMap, i);
|
|
140
|
-
totalSize[c] = fget(totalSize, c) + fget(g.size, i);
|
|
141
|
-
if (g.directed) {
|
|
142
|
-
totalOutStr[c] = fget(totalOutStr, c) + fget(g.strengthOut, i);
|
|
143
|
-
totalInStr[c] = fget(totalInStr, c) + fget(g.strengthIn, i);
|
|
144
|
-
} else {
|
|
145
|
-
totalStr[c] = fget(totalStr, c) + fget(g.strengthOut, i);
|
|
146
|
-
}
|
|
147
|
-
if (fget(g.selfLoop, i)) internalWeight[c] = fget(internalWeight, c) + fget(g.selfLoop, i);
|
|
148
|
-
}
|
|
149
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Accumulate intra-community edge weights for quality evaluation.
|
|
124
|
+
* For directed graphs, counts all intra-community non-self edges.
|
|
125
|
+
* For undirected, counts each edge once (j > i) to avoid double-counting.
|
|
126
|
+
*/
|
|
127
|
+
function accumulateInternalEdgeWeights(
|
|
128
|
+
g: GraphAdapter,
|
|
129
|
+
communityMap: Int32Array,
|
|
130
|
+
n: number,
|
|
131
|
+
internalWeight: Float64Array,
|
|
132
|
+
): void {
|
|
150
133
|
if (g.directed) {
|
|
151
134
|
for (let i = 0; i < n; i++) {
|
|
152
135
|
const ci: number = iget(communityMap, i);
|
|
@@ -168,6 +151,62 @@ function buildOriginalPartition(g: GraphAdapter, communityMap: Int32Array): Orig
|
|
|
168
151
|
}
|
|
169
152
|
}
|
|
170
153
|
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Accumulate per-community node-level aggregates (size, strength) from
|
|
158
|
+
* the graph adapter and community mapping.
|
|
159
|
+
*/
|
|
160
|
+
function accumulateNodeAggregates(
|
|
161
|
+
g: GraphAdapter,
|
|
162
|
+
communityMap: Int32Array,
|
|
163
|
+
n: number,
|
|
164
|
+
totalSize: Float64Array,
|
|
165
|
+
totalStr: Float64Array,
|
|
166
|
+
totalOutStr: Float64Array,
|
|
167
|
+
totalInStr: Float64Array,
|
|
168
|
+
internalWeight: Float64Array,
|
|
169
|
+
): void {
|
|
170
|
+
for (let i = 0; i < n; i++) {
|
|
171
|
+
const c: number = iget(communityMap, i);
|
|
172
|
+
totalSize[c] = fget(totalSize, c) + fget(g.size, i);
|
|
173
|
+
if (g.directed) {
|
|
174
|
+
totalOutStr[c] = fget(totalOutStr, c) + fget(g.strengthOut, i);
|
|
175
|
+
totalInStr[c] = fget(totalInStr, c) + fget(g.strengthIn, i);
|
|
176
|
+
} else {
|
|
177
|
+
totalStr[c] = fget(totalStr, c) + fget(g.strengthOut, i);
|
|
178
|
+
}
|
|
179
|
+
if (fget(g.selfLoop, i)) internalWeight[c] = fget(internalWeight, c) + fget(g.selfLoop, i);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function buildOriginalPartition(g: GraphAdapter, communityMap: Int32Array): OriginalPartition {
|
|
184
|
+
const n: number = g.n;
|
|
185
|
+
let maxC: number = 0;
|
|
186
|
+
for (let i = 0; i < n; i++) {
|
|
187
|
+
const ci = iget(communityMap, i);
|
|
188
|
+
if (ci > maxC) maxC = ci;
|
|
189
|
+
}
|
|
190
|
+
const cc: number = maxC + 1;
|
|
191
|
+
|
|
192
|
+
const nodeCommunity = communityMap;
|
|
193
|
+
const internalWeight = new Float64Array(cc);
|
|
194
|
+
const totalStr = new Float64Array(cc);
|
|
195
|
+
const totalOutStr = new Float64Array(cc);
|
|
196
|
+
const totalInStr = new Float64Array(cc);
|
|
197
|
+
const totalSize = new Float64Array(cc);
|
|
198
|
+
|
|
199
|
+
accumulateNodeAggregates(
|
|
200
|
+
g,
|
|
201
|
+
communityMap,
|
|
202
|
+
n,
|
|
203
|
+
totalSize,
|
|
204
|
+
totalStr,
|
|
205
|
+
totalOutStr,
|
|
206
|
+
totalInStr,
|
|
207
|
+
internalWeight,
|
|
208
|
+
);
|
|
209
|
+
accumulateInternalEdgeWeights(g, communityMap, n, internalWeight);
|
|
171
210
|
|
|
172
211
|
return {
|
|
173
212
|
communityCount: cc,
|
|
@@ -129,83 +129,15 @@ export function runLouvainUndirectedModularity(
|
|
|
129
129
|
const nodeIndex: number = order[idx]!;
|
|
130
130
|
if (level === 0 && fixedNodeMask && fixedNodeMask[nodeIndex]) continue;
|
|
131
131
|
const candidateCount: number = partition.accumulateNeighborCommunityEdgeWeights(nodeIndex);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
maxCommunitySize
|
|
142
|
-
)
|
|
143
|
-
continue;
|
|
144
|
-
const gain: number = computeQualityGain(partition, nodeIndex, communityId, options);
|
|
145
|
-
if (gain > bestGain) {
|
|
146
|
-
bestGain = gain;
|
|
147
|
-
bestCommunityId = communityId;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
} else if (strategyCode === CandidateStrategy.RandomAny) {
|
|
151
|
-
const tries: number = Math.min(10, Math.max(1, partition.communityCount));
|
|
152
|
-
for (let trialIndex = 0; trialIndex < tries; trialIndex++) {
|
|
153
|
-
const communityId: number = (random() * partition.communityCount) | 0;
|
|
154
|
-
if (communityId === partition.nodeCommunity[nodeIndex]!) continue;
|
|
155
|
-
if (
|
|
156
|
-
maxCommunitySize < Infinity &&
|
|
157
|
-
partition.getCommunityTotalSize(communityId) + graphAdapter.size[nodeIndex]! >
|
|
158
|
-
maxCommunitySize
|
|
159
|
-
)
|
|
160
|
-
continue;
|
|
161
|
-
const gain: number = computeQualityGain(partition, nodeIndex, communityId, options);
|
|
162
|
-
if (gain > bestGain) {
|
|
163
|
-
bestGain = gain;
|
|
164
|
-
bestCommunityId = communityId;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} else if (strategyCode === CandidateStrategy.RandomNeighbor) {
|
|
168
|
-
const tries: number = Math.min(10, Math.max(1, candidateCount));
|
|
169
|
-
for (let trialIndex = 0; trialIndex < tries; trialIndex++) {
|
|
170
|
-
const communityId: number = partition.getCandidateCommunityAt(
|
|
171
|
-
(random() * candidateCount) | 0,
|
|
172
|
-
);
|
|
173
|
-
if (communityId === partition.nodeCommunity[nodeIndex]!) continue;
|
|
174
|
-
if (
|
|
175
|
-
maxCommunitySize < Infinity &&
|
|
176
|
-
partition.getCommunityTotalSize(communityId) + graphAdapter.size[nodeIndex]! >
|
|
177
|
-
maxCommunitySize
|
|
178
|
-
)
|
|
179
|
-
continue;
|
|
180
|
-
const gain: number = computeQualityGain(partition, nodeIndex, communityId, options);
|
|
181
|
-
if (gain > bestGain) {
|
|
182
|
-
bestGain = gain;
|
|
183
|
-
bestCommunityId = communityId;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
for (let trialIndex = 0; trialIndex < candidateCount; trialIndex++) {
|
|
188
|
-
const communityId: number = partition.getCandidateCommunityAt(trialIndex);
|
|
189
|
-
if (maxCommunitySize < Infinity) {
|
|
190
|
-
const nextSize: number =
|
|
191
|
-
partition.getCommunityTotalSize(communityId) + graphAdapter.size[nodeIndex]!;
|
|
192
|
-
if (nextSize > maxCommunitySize) continue;
|
|
193
|
-
}
|
|
194
|
-
const gain: number = computeQualityGain(partition, nodeIndex, communityId, options);
|
|
195
|
-
if (gain > bestGain) {
|
|
196
|
-
bestGain = gain;
|
|
197
|
-
bestCommunityId = communityId;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (options.allowNewCommunity) {
|
|
202
|
-
const newCommunityId: number = partition.communityCount;
|
|
203
|
-
const gain: number = computeQualityGain(partition, nodeIndex, newCommunityId, options);
|
|
204
|
-
if (gain > bestGain) {
|
|
205
|
-
bestGain = gain;
|
|
206
|
-
bestCommunityId = newCommunityId;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
132
|
+
const { bestCommunityId, bestGain } = findBestCommunityMove(
|
|
133
|
+
partition,
|
|
134
|
+
graphAdapter,
|
|
135
|
+
nodeIndex,
|
|
136
|
+
candidateCount,
|
|
137
|
+
strategyCode,
|
|
138
|
+
options,
|
|
139
|
+
random,
|
|
140
|
+
);
|
|
209
141
|
if (bestCommunityId !== partition.nodeCommunity[nodeIndex]! && bestGain > GAIN_EPSILON) {
|
|
210
142
|
partition.moveNodeToCommunity(nodeIndex, bestCommunityId);
|
|
211
143
|
improved = true;
|
|
@@ -267,6 +199,109 @@ export function runLouvainUndirectedModularity(
|
|
|
267
199
|
};
|
|
268
200
|
}
|
|
269
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Evaluate all candidate communities for a node and return the best move.
|
|
204
|
+
* Encapsulates the four candidate-selection strategies (All, RandomAny,
|
|
205
|
+
* RandomNeighbor, Neighbors) and the optional new-community probe.
|
|
206
|
+
*/
|
|
207
|
+
function findBestCommunityMove(
|
|
208
|
+
partition: Partition,
|
|
209
|
+
graphAdapter: GraphAdapter,
|
|
210
|
+
nodeIndex: number,
|
|
211
|
+
candidateCount: number,
|
|
212
|
+
strategyCode: CandidateStrategyCode,
|
|
213
|
+
options: NormalizedOptions,
|
|
214
|
+
random: () => number,
|
|
215
|
+
): { bestCommunityId: number; bestGain: number } {
|
|
216
|
+
let bestCommunityId: number = partition.nodeCommunity[nodeIndex]!;
|
|
217
|
+
let bestGain: number = 0;
|
|
218
|
+
const maxCommunitySize: number = options.maxCommunitySize;
|
|
219
|
+
|
|
220
|
+
const evaluateCandidate = (communityId: number): void => {
|
|
221
|
+
if (communityId === partition.nodeCommunity[nodeIndex]!) return;
|
|
222
|
+
if (
|
|
223
|
+
maxCommunitySize < Infinity &&
|
|
224
|
+
partition.getCommunityTotalSize(communityId) + graphAdapter.size[nodeIndex]! >
|
|
225
|
+
maxCommunitySize
|
|
226
|
+
)
|
|
227
|
+
return;
|
|
228
|
+
const gain: number = computeQualityGain(partition, nodeIndex, communityId, options);
|
|
229
|
+
if (gain > bestGain) {
|
|
230
|
+
bestGain = gain;
|
|
231
|
+
bestCommunityId = communityId;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
if (strategyCode === CandidateStrategy.All) {
|
|
236
|
+
for (let communityId = 0; communityId < partition.communityCount; communityId++) {
|
|
237
|
+
evaluateCandidate(communityId);
|
|
238
|
+
}
|
|
239
|
+
} else if (strategyCode === CandidateStrategy.RandomAny) {
|
|
240
|
+
const tries: number = Math.min(10, Math.max(1, partition.communityCount));
|
|
241
|
+
for (let trialIndex = 0; trialIndex < tries; trialIndex++) {
|
|
242
|
+
evaluateCandidate((random() * partition.communityCount) | 0);
|
|
243
|
+
}
|
|
244
|
+
} else if (strategyCode === CandidateStrategy.RandomNeighbor) {
|
|
245
|
+
const tries: number = Math.min(10, Math.max(1, candidateCount));
|
|
246
|
+
for (let trialIndex = 0; trialIndex < tries; trialIndex++) {
|
|
247
|
+
evaluateCandidate(partition.getCandidateCommunityAt((random() * candidateCount) | 0));
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
for (let trialIndex = 0; trialIndex < candidateCount; trialIndex++) {
|
|
251
|
+
evaluateCandidate(partition.getCandidateCommunityAt(trialIndex));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (options.allowNewCommunity) {
|
|
256
|
+
const newCommunityId: number = partition.communityCount;
|
|
257
|
+
const gain: number = computeQualityGain(partition, nodeIndex, newCommunityId, options);
|
|
258
|
+
if (gain > bestGain) {
|
|
259
|
+
bestGain = gain;
|
|
260
|
+
bestCommunityId = newCommunityId;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { bestCommunityId, bestGain };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Run a BFS on the subgraph induced by `inCommunity` starting from `start`.
|
|
269
|
+
* Returns the list of visited nodes. Works on both directed (weak connectivity
|
|
270
|
+
* via both outEdges and inEdges) and undirected graphs.
|
|
271
|
+
*/
|
|
272
|
+
function bfsComponent(
|
|
273
|
+
g: GraphAdapter,
|
|
274
|
+
start: number,
|
|
275
|
+
inCommunity: Uint8Array,
|
|
276
|
+
visited: Uint8Array,
|
|
277
|
+
): number[] {
|
|
278
|
+
const queue: number[] = [start];
|
|
279
|
+
visited[start] = 1;
|
|
280
|
+
let head: number = 0;
|
|
281
|
+
while (head < queue.length) {
|
|
282
|
+
const v: number = queue[head++]!;
|
|
283
|
+
const out: EdgeEntry[] = g.outEdges[v]!;
|
|
284
|
+
for (let k = 0; k < out.length; k++) {
|
|
285
|
+
const w: number = out[k]!.to;
|
|
286
|
+
if (inCommunity[w] && !visited[w]) {
|
|
287
|
+
visited[w] = 1;
|
|
288
|
+
queue.push(w);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (g.directed) {
|
|
292
|
+
const inc: InEdgeEntry[] = g.inEdges[v]!;
|
|
293
|
+
for (let k = 0; k < inc.length; k++) {
|
|
294
|
+
const w: number = inc[k]!.from;
|
|
295
|
+
if (inCommunity[w] && !visited[w]) {
|
|
296
|
+
visited[w] = 1;
|
|
297
|
+
queue.push(w);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return queue;
|
|
303
|
+
}
|
|
304
|
+
|
|
270
305
|
// Build a coarse graph where each community becomes a single node.
|
|
271
306
|
// Self-loops (g.selfLoop[]) don't need separate handling here because they
|
|
272
307
|
// are already present in g.outEdges (directed path keeps them in both arrays).
|
|
@@ -450,38 +485,12 @@ function splitDisconnectedCommunities(g: GraphAdapter, partition: Partition): vo
|
|
|
450
485
|
if (visited[start]) continue;
|
|
451
486
|
componentCount++;
|
|
452
487
|
|
|
453
|
-
|
|
454
|
-
// For directed graphs, traverse both outEdges and inEdges to check
|
|
455
|
-
// weak connectivity (reachability ignoring edge direction).
|
|
456
|
-
const queue: number[] = [start];
|
|
457
|
-
visited[start] = 1;
|
|
458
|
-
let head: number = 0;
|
|
459
|
-
while (head < queue.length) {
|
|
460
|
-
const v: number = queue[head++]!;
|
|
461
|
-
const out: EdgeEntry[] = g.outEdges[v]!;
|
|
462
|
-
for (let k = 0; k < out.length; k++) {
|
|
463
|
-
const w: number = out[k]!.to;
|
|
464
|
-
if (inCommunity[w] && !visited[w]) {
|
|
465
|
-
visited[w] = 1;
|
|
466
|
-
queue.push(w);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
if (g.directed) {
|
|
470
|
-
const inc: InEdgeEntry[] = g.inEdges[v]!;
|
|
471
|
-
for (let k = 0; k < inc.length; k++) {
|
|
472
|
-
const w: number = inc[k]!.from;
|
|
473
|
-
if (inCommunity[w] && !visited[w]) {
|
|
474
|
-
visited[w] = 1;
|
|
475
|
-
queue.push(w);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
488
|
+
const component: number[] = bfsComponent(g, start, inCommunity, visited);
|
|
480
489
|
|
|
481
490
|
if (componentCount > 1) {
|
|
482
491
|
// Secondary component — assign new community ID directly.
|
|
483
492
|
const newC: number = nextC++;
|
|
484
|
-
for (let q = 0; q <
|
|
493
|
+
for (let q = 0; q < component.length; q++) nc[component[q]!] = newC;
|
|
485
494
|
didSplit = true;
|
|
486
495
|
}
|
|
487
496
|
}
|