feat: refactor based on spectral spectral_analysis
Asking the agent to refactor to reduce spectral complexity, it worked. Trivial change, but this did reduce code complexity.
This commit is contained in:
parent
89be164c19
commit
6897d71e4b
13 changed files with 1433 additions and 83 deletions
14
README.md
14
README.md
|
|
@ -263,3 +263,17 @@ node test.mjs # run WASM tests
|
||||||
| `getrandom` | yes | System RNG for actor addresses |
|
| `getrandom` | yes | System RNG for actor addresses |
|
||||||
| `no_random` | no | Deterministic counter (for WASM / reproducible tests) |
|
| `no_random` | no | Deterministic counter (for WASM / reproducible tests) |
|
||||||
| `python` | no | PyO3 bindings, builds cdylib wheel |
|
| `python` | no | PyO3 bindings, builds cdylib wheel |
|
||||||
|
## Connectome analysis
|
||||||
|
|
||||||
|
Spectral analysis of the internal dependency graph, producing a Connectome Complexity Index (CCI) and visual dashboards.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Generate the dependency DAG
|
||||||
|
cargo run --manifest-path tools/depgraph/Cargo.toml -- --src-dir src/ --output deps
|
||||||
|
|
||||||
|
# Run spectral analysis (outputs to docs/connectome/)
|
||||||
|
source .venv/bin/activate
|
||||||
|
python tools/spectral/spectral_analysis.py deps.dot
|
||||||
|
```
|
||||||
|
|
||||||
|
This produces a text report, an interactive HTML dashboard, and a static PNG dashboard in `docs/connectome/`. See [docs/connectome.md](docs/connectome.md) for details on the metrics and interpretation.
|
||||||
|
|
|
||||||
76
docs/connectome.md
Normal file
76
docs/connectome.md
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Connectome Analysis
|
||||||
|
|
||||||
|
The connectome analysis applies spectral graph theory to the codebase's internal dependency DAG, producing quantitative coupling metrics and visual dashboards.
|
||||||
|
|
||||||
|
## What it measures
|
||||||
|
|
||||||
|
The tool parses `deps.dot` (a GraphViz DOT file describing struct/trait dependencies between modules) and computes:
|
||||||
|
|
||||||
|
- **Laplacian eigenvalue spectrum** -- encodes the graph's overall connectivity structure
|
||||||
|
- **Fiedler vector** -- the optimal spectral bisection of the dependency graph, revealing natural module clusters
|
||||||
|
- **Module coupling matrix** -- directed edge counts between every pair of modules
|
||||||
|
- **Connectome Complexity Index (CCI)** -- a single 0-1 score combining five sub-metrics:
|
||||||
|
|
||||||
|
| Sub-metric | Weight | What it captures |
|
||||||
|
|---|---|---|
|
||||||
|
| Algebraic connectivity (lambda_2/n) | 25% | How tightly connected the graph is |
|
||||||
|
| Spectral entropy (H/log2(k)) | 25% | How uniformly distributed coupling is across eigenvalues |
|
||||||
|
| Edge density (\|E\|/n(n-1)) | 15% | Raw ratio of edges to possible edges |
|
||||||
|
| Cross-module coupling ratio | 20% | Fraction of edges that cross module boundaries |
|
||||||
|
| Spectral radius (rho/(n-1)) | 15% | Maximum hub concentration |
|
||||||
|
|
||||||
|
### Interpreting CCI
|
||||||
|
|
||||||
|
| CCI range | Label | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| < 0.30 | LOW | Well-decomposed architecture |
|
||||||
|
| 0.30 - 0.60 | MODERATE | Typical well-structured codebase |
|
||||||
|
| > 0.60 | HIGH | Consider reviewing module boundaries |
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
From the project root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Default: outputs to docs/connectome/
|
||||||
|
python tools/spectral/spectral_analysis.py deps.dot
|
||||||
|
|
||||||
|
# Custom output directory
|
||||||
|
python tools/spectral/spectral_analysis.py deps.dot -o path/to/output
|
||||||
|
|
||||||
|
# Also emit JSON metrics
|
||||||
|
python tools/spectral/spectral_analysis.py deps.dot --json
|
||||||
|
|
||||||
|
# Text report only (skip matplotlib PNG)
|
||||||
|
python tools/spectral/spectral_analysis.py deps.dot --no-plots
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
The script requires numpy, scipy, and matplotlib (for the PNG dashboard). These are available in the project's `.venv`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
source .venv/bin/activate
|
||||||
|
python tools/spectral/spectral_analysis.py deps.dot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output files
|
||||||
|
|
||||||
|
All output goes to `docs/connectome/` by default:
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|---|---|
|
||||||
|
| `connectome_report.txt` | Full text report with eigenvalues, Fiedler bisection, coupling matrix, and CCI breakdown |
|
||||||
|
| `connectome_dashboard.html` | Interactive HTML dashboard with zoomable DAG, eigenvalue plot, Fiedler bar chart, and coupling heatmap |
|
||||||
|
| `connectome_dashboard.png` | Static PNG snapshot of the spectral dashboard (dark theme, 16x12 @ 150 DPI) |
|
||||||
|
| `connectome_metrics.json` | Machine-readable metrics (only with `--json` flag) |
|
||||||
|
|
||||||
|
## Regenerating deps.dot
|
||||||
|
|
||||||
|
The DOT file is the input to the spectral analysis. To regenerate it from source:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --manifest-path tools/depgraph/Cargo.toml -- --src-dir src/ --output deps
|
||||||
|
```
|
||||||
|
|
||||||
|
Then re-run the spectral analysis to update the connectome report.
|
||||||
738
docs/connectome/connectome_dashboard.html
Normal file
738
docs/connectome/connectome_dashboard.html
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/connectome/connectome_dashboard.png
Normal file
BIN
docs/connectome/connectome_dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 260 KiB |
280
docs/connectome/connectome_metrics.json
Normal file
280
docs/connectome/connectome_metrics.json
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
{
|
||||||
|
"graph": {
|
||||||
|
"n_nodes": 36,
|
||||||
|
"n_edges": 78,
|
||||||
|
"n_modules": 8,
|
||||||
|
"connected_components": 2,
|
||||||
|
"modules": [
|
||||||
|
"error",
|
||||||
|
"config",
|
||||||
|
"channel",
|
||||||
|
"actor",
|
||||||
|
"address_map",
|
||||||
|
"runtime",
|
||||||
|
"worker",
|
||||||
|
"python"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"spectral": {
|
||||||
|
"eigenvalues": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.18637427422819514,
|
||||||
|
0.4813940269111958,
|
||||||
|
0.6123548189907484,
|
||||||
|
0.7985629750697533,
|
||||||
|
0.8319091149970231,
|
||||||
|
1.004600219615323,
|
||||||
|
1.2394224070963267,
|
||||||
|
1.3689639255261323,
|
||||||
|
1.4526860286383532,
|
||||||
|
1.626080007307936,
|
||||||
|
2.321279039207482,
|
||||||
|
2.3935870074779477,
|
||||||
|
2.909249108581605,
|
||||||
|
3.1569529438124246,
|
||||||
|
3.219980753498557,
|
||||||
|
3.3901681819448264,
|
||||||
|
3.4799333923457128,
|
||||||
|
3.605153966968332,
|
||||||
|
3.8847634489335645,
|
||||||
|
4.186333826694949,
|
||||||
|
4.707024553452379,
|
||||||
|
5.173220891347629,
|
||||||
|
5.586454240023603,
|
||||||
|
5.795938099946378,
|
||||||
|
5.8549806331718415,
|
||||||
|
6.1828765255391644,
|
||||||
|
6.461944112192484,
|
||||||
|
6.898584006266002,
|
||||||
|
7.3807011063714905,
|
||||||
|
7.896480708195232,
|
||||||
|
9.160238969430825,
|
||||||
|
11.171010263156152,
|
||||||
|
14.04747425561517,
|
||||||
|
15.533322167445291
|
||||||
|
],
|
||||||
|
"fiedler_value": 0.0,
|
||||||
|
"fiedler_vector": [
|
||||||
|
0.0,
|
||||||
|
1.6667674979754847e-17,
|
||||||
|
-4.4166826078552935e-16,
|
||||||
|
-5.256955919501151e-16,
|
||||||
|
-1.6422080940489055e-18,
|
||||||
|
-7.037238109196825e-17,
|
||||||
|
8.390622125197347e-16,
|
||||||
|
2.3690827037115515e-17,
|
||||||
|
1.4176669953736474e-16,
|
||||||
|
1.4226827878099615e-16,
|
||||||
|
3.1675939003075104e-17,
|
||||||
|
2.7236604915425953e-18,
|
||||||
|
-1.744993274089968e-16,
|
||||||
|
2.918795638720409e-17,
|
||||||
|
-2.1047785816801073e-16,
|
||||||
|
1.6100142369066343e-16,
|
||||||
|
-1.1048855416219909e-16,
|
||||||
|
2.623380592723269e-16,
|
||||||
|
-6.257340472605819e-17,
|
||||||
|
-2.7901019807352287e-17,
|
||||||
|
7.954130131218555e-17,
|
||||||
|
-2.8145783605573126e-16,
|
||||||
|
5.097927800469914e-17,
|
||||||
|
1.0000000000000002,
|
||||||
|
-7.635525673846673e-17,
|
||||||
|
4.0203070989124624e-17,
|
||||||
|
5.607482503879278e-17,
|
||||||
|
1.4848475991077948e-17,
|
||||||
|
-8.451175680174382e-17,
|
||||||
|
-1.3333327282927672e-16,
|
||||||
|
2.6566833162138994e-16,
|
||||||
|
1.0987812721413363e-16,
|
||||||
|
4.959951093541129e-16,
|
||||||
|
-1.2067067461630528e-16,
|
||||||
|
-2.172546179303562e-16,
|
||||||
|
-2.3212297109883297e-16
|
||||||
|
],
|
||||||
|
"node_names": [
|
||||||
|
"Error",
|
||||||
|
"BackoffPolicy",
|
||||||
|
"RuntimeConfig",
|
||||||
|
"HybridChannel",
|
||||||
|
"Receiver",
|
||||||
|
"Sender",
|
||||||
|
"Actor",
|
||||||
|
"ActorAddress",
|
||||||
|
"ActorInterface",
|
||||||
|
"AnyActor",
|
||||||
|
"ContextInner",
|
||||||
|
"Ctx",
|
||||||
|
"Message",
|
||||||
|
"AddressMap",
|
||||||
|
"Placement",
|
||||||
|
"WorkerId",
|
||||||
|
"Envelope",
|
||||||
|
"Inbox",
|
||||||
|
"InboxRegistry",
|
||||||
|
"Runtime",
|
||||||
|
"RuntimeHandle",
|
||||||
|
"SenderT",
|
||||||
|
"ActorPool",
|
||||||
|
"Mailbox",
|
||||||
|
"TickContext",
|
||||||
|
"Worker",
|
||||||
|
"WorkerContext",
|
||||||
|
"Effect",
|
||||||
|
"PyActor",
|
||||||
|
"PyActorAddress",
|
||||||
|
"PyCtx",
|
||||||
|
"PyInbox",
|
||||||
|
"PyMsg",
|
||||||
|
"PyRuntime",
|
||||||
|
"PyRuntimeConfig",
|
||||||
|
"PyRuntimeHandle"
|
||||||
|
],
|
||||||
|
"node_modules": [
|
||||||
|
"error",
|
||||||
|
"config",
|
||||||
|
"config",
|
||||||
|
"channel",
|
||||||
|
"channel",
|
||||||
|
"channel",
|
||||||
|
"actor",
|
||||||
|
"actor",
|
||||||
|
"actor",
|
||||||
|
"actor",
|
||||||
|
"actor",
|
||||||
|
"actor",
|
||||||
|
"actor",
|
||||||
|
"address_map",
|
||||||
|
"address_map",
|
||||||
|
"address_map",
|
||||||
|
"runtime",
|
||||||
|
"runtime",
|
||||||
|
"runtime",
|
||||||
|
"runtime",
|
||||||
|
"runtime",
|
||||||
|
"runtime",
|
||||||
|
"worker",
|
||||||
|
"worker",
|
||||||
|
"worker",
|
||||||
|
"worker",
|
||||||
|
"worker",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python",
|
||||||
|
"python"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"module_coupling": {
|
||||||
|
"module_names": [
|
||||||
|
"error",
|
||||||
|
"config",
|
||||||
|
"channel",
|
||||||
|
"actor",
|
||||||
|
"address_map",
|
||||||
|
"runtime",
|
||||||
|
"worker",
|
||||||
|
"python"
|
||||||
|
],
|
||||||
|
"coupling_matrix": [
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
3.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
7.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2.0,
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
6.0,
|
||||||
|
2.0,
|
||||||
|
6.0,
|
||||||
|
1.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
9.0,
|
||||||
|
4.0,
|
||||||
|
3.0,
|
||||||
|
3.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
5.0,
|
||||||
|
0.0,
|
||||||
|
3.0,
|
||||||
|
0.0,
|
||||||
|
10.0
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"cross_module_edges": 46,
|
||||||
|
"total_edges": 78
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"algebraic_connectivity": 0.0,
|
||||||
|
"normalized_algebraic_connectivity": 0.0,
|
||||||
|
"spectral_entropy": 4.641128070102523,
|
||||||
|
"normalized_spectral_entropy": 0.9122677088609219,
|
||||||
|
"edge_density": 0.06190476190476191,
|
||||||
|
"cross_module_ratio": 0.5897435897435898,
|
||||||
|
"spectral_radius": 6.676215667817795,
|
||||||
|
"normalized_spectral_radius": 0.19074901908050843,
|
||||||
|
"cci": 0.383913712311739
|
||||||
|
}
|
||||||
|
}
|
||||||
125
docs/connectome/connectome_report.txt
Normal file
125
docs/connectome/connectome_report.txt
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
========================================================================
|
||||||
|
SPECTRAL ANALYSIS REPORT — Dependency DAG
|
||||||
|
========================================================================
|
||||||
|
|
||||||
|
GRAPH SUMMARY
|
||||||
|
----------------------------------------
|
||||||
|
Nodes: 36
|
||||||
|
Directed edges: 78
|
||||||
|
Modules: 8
|
||||||
|
Connected components: 2
|
||||||
|
Modules: error, config, channel, actor, address_map, runtime, worker, python
|
||||||
|
|
||||||
|
LAPLACIAN EIGENVALUE SPECTRUM
|
||||||
|
----------------------------------------
|
||||||
|
lambda_ 0 = 0.0000
|
||||||
|
lambda_ 1 = 0.0000 <-- Fiedler value (lambda_2)
|
||||||
|
lambda_ 2 = 0.1864
|
||||||
|
lambda_ 3 = 0.4814
|
||||||
|
lambda_ 4 = 0.6124
|
||||||
|
lambda_ 5 = 0.7986
|
||||||
|
lambda_ 6 = 0.8319
|
||||||
|
lambda_ 7 = 1.0046
|
||||||
|
lambda_ 8 = 1.2394
|
||||||
|
lambda_ 9 = 1.3690
|
||||||
|
lambda_10 = 1.4527
|
||||||
|
lambda_11 = 1.6261
|
||||||
|
lambda_12 = 2.3213
|
||||||
|
lambda_13 = 2.3936
|
||||||
|
lambda_14 = 2.9092
|
||||||
|
lambda_15 = 3.1570
|
||||||
|
lambda_16 = 3.2200
|
||||||
|
lambda_17 = 3.3902
|
||||||
|
lambda_18 = 3.4799
|
||||||
|
lambda_19 = 3.6052
|
||||||
|
lambda_20 = 3.8848
|
||||||
|
lambda_21 = 4.1863
|
||||||
|
lambda_22 = 4.7070
|
||||||
|
lambda_23 = 5.1732
|
||||||
|
lambda_24 = 5.5865
|
||||||
|
lambda_25 = 5.7959
|
||||||
|
lambda_26 = 5.8550
|
||||||
|
lambda_27 = 6.1829
|
||||||
|
lambda_28 = 6.4619
|
||||||
|
lambda_29 = 6.8986
|
||||||
|
lambda_30 = 7.3807
|
||||||
|
lambda_31 = 7.8965
|
||||||
|
lambda_32 = 9.1602
|
||||||
|
lambda_33 = 11.1710
|
||||||
|
lambda_34 = 14.0475
|
||||||
|
lambda_35 = 15.5333
|
||||||
|
|
||||||
|
Spectral gap (lambda_max - lambda_2): 15.5333
|
||||||
|
Fiedler value (algebraic connectivity): 0.0000
|
||||||
|
|
||||||
|
FIEDLER VECTOR — SPECTRAL BISECTION
|
||||||
|
----------------------------------------
|
||||||
|
Partition A (Fiedler < 0):
|
||||||
|
HybridChannel [channel ] f = -0.0000
|
||||||
|
RuntimeConfig [config ] f = -0.0000
|
||||||
|
SenderT [runtime ] f = -0.0000
|
||||||
|
PyRuntimeHandle [python ] f = -0.0000
|
||||||
|
PyRuntimeConfig [python ] f = -0.0000
|
||||||
|
Placement [address_map ] f = -0.0000
|
||||||
|
Message [actor ] f = -0.0000
|
||||||
|
PyActorAddress [python ] f = -0.0000
|
||||||
|
PyRuntime [python ] f = -0.0000
|
||||||
|
Envelope [runtime ] f = -0.0000
|
||||||
|
PyActor [python ] f = -0.0000
|
||||||
|
TickContext [worker ] f = -0.0000
|
||||||
|
Sender [channel ] f = -0.0000
|
||||||
|
InboxRegistry [runtime ] f = -0.0000
|
||||||
|
Runtime [runtime ] f = -0.0000
|
||||||
|
Receiver [channel ] f = -0.0000
|
||||||
|
────────────────────────────────────
|
||||||
|
Partition B (Fiedler >= 0):
|
||||||
|
Error [error ] f = +0.0000
|
||||||
|
Ctx [actor ] f = +0.0000
|
||||||
|
Effect [python ] f = +0.0000
|
||||||
|
BackoffPolicy [config ] f = +0.0000
|
||||||
|
ActorAddress [actor ] f = +0.0000
|
||||||
|
AddressMap [address_map ] f = +0.0000
|
||||||
|
ContextInner [actor ] f = +0.0000
|
||||||
|
Worker [worker ] f = +0.0000
|
||||||
|
ActorPool [worker ] f = +0.0000
|
||||||
|
WorkerContext [worker ] f = +0.0000
|
||||||
|
RuntimeHandle [runtime ] f = +0.0000
|
||||||
|
PyInbox [python ] f = +0.0000
|
||||||
|
ActorInterface [actor ] f = +0.0000
|
||||||
|
AnyActor [actor ] f = +0.0000
|
||||||
|
WorkerId [address_map ] f = +0.0000
|
||||||
|
Inbox [runtime ] f = +0.0000
|
||||||
|
PyCtx [python ] f = +0.0000
|
||||||
|
PyMsg [python ] f = +0.0000
|
||||||
|
Actor [actor ] f = +0.0000
|
||||||
|
Mailbox [worker ] f = +1.0000
|
||||||
|
|
||||||
|
MODULE COUPLING MATRIX (directed edge counts)
|
||||||
|
----------------------------------------
|
||||||
|
error config channel actoraddress_map runtime worker python
|
||||||
|
error 0 0 0 0 0 0 0 0
|
||||||
|
config 0 1 0 0 0 0 0 0
|
||||||
|
channel 0 0 3 0 0 1 0 0
|
||||||
|
actor 2 0 0 7 0 0 0 0
|
||||||
|
address_map 0 0 0 1 2 0 0 0
|
||||||
|
runtime 2 1 2 6 2 6 1 0
|
||||||
|
worker 1 1 2 9 4 3 3 0
|
||||||
|
python 0 0 0 5 0 3 0 10
|
||||||
|
|
||||||
|
Cross-module edges: 46 / 78 (59.0%)
|
||||||
|
|
||||||
|
CONNECTOME COMPLEXITY INDEX (CCI)
|
||||||
|
----------------------------------------
|
||||||
|
Sub-metric Raw Normalized Weight Contrib
|
||||||
|
──────────────────────────────────────── ────────── ────────── ──────── ────────
|
||||||
|
Algebraic connectivity (lambda_2/n) 0.0000 0.0000 0.25 0.0000
|
||||||
|
Spectral entropy (H/log2(k)) 4.6411 0.9123 0.25 0.2281
|
||||||
|
Edge density (|E|/n(n-1)) 0.0619 0.0619 0.15 0.0093
|
||||||
|
Cross-module coupling ratio 0.5897 0.5897 0.20 0.1179
|
||||||
|
Spectral radius (rho/(n-1)) 6.6762 0.1907 0.15 0.0286
|
||||||
|
──────────────────────────────────────── ────────── ────────── ──────── ────────
|
||||||
|
CCI (weighted sum) 1.00 0.3839
|
||||||
|
|
||||||
|
Interpretation: MODERATE complexity — typical well-structured codebase
|
||||||
|
|
||||||
|
========================================================================
|
||||||
125
spectral_report.txt
Normal file
125
spectral_report.txt
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
========================================================================
|
||||||
|
SPECTRAL ANALYSIS REPORT — Dependency DAG
|
||||||
|
========================================================================
|
||||||
|
|
||||||
|
GRAPH SUMMARY
|
||||||
|
----------------------------------------
|
||||||
|
Nodes: 36
|
||||||
|
Directed edges: 78
|
||||||
|
Modules: 8
|
||||||
|
Connected components: 2
|
||||||
|
Modules: error, config, channel, actor, address_map, runtime, worker, python
|
||||||
|
|
||||||
|
LAPLACIAN EIGENVALUE SPECTRUM
|
||||||
|
----------------------------------------
|
||||||
|
lambda_ 0 = 0.0000
|
||||||
|
lambda_ 1 = 0.0000 <-- Fiedler value (lambda_2)
|
||||||
|
lambda_ 2 = 0.1864
|
||||||
|
lambda_ 3 = 0.4814
|
||||||
|
lambda_ 4 = 0.6124
|
||||||
|
lambda_ 5 = 0.7986
|
||||||
|
lambda_ 6 = 0.8319
|
||||||
|
lambda_ 7 = 1.0046
|
||||||
|
lambda_ 8 = 1.2394
|
||||||
|
lambda_ 9 = 1.3690
|
||||||
|
lambda_10 = 1.4527
|
||||||
|
lambda_11 = 1.6261
|
||||||
|
lambda_12 = 2.3213
|
||||||
|
lambda_13 = 2.3936
|
||||||
|
lambda_14 = 2.9092
|
||||||
|
lambda_15 = 3.1570
|
||||||
|
lambda_16 = 3.2200
|
||||||
|
lambda_17 = 3.3902
|
||||||
|
lambda_18 = 3.4799
|
||||||
|
lambda_19 = 3.6052
|
||||||
|
lambda_20 = 3.8848
|
||||||
|
lambda_21 = 4.1863
|
||||||
|
lambda_22 = 4.7070
|
||||||
|
lambda_23 = 5.1732
|
||||||
|
lambda_24 = 5.5865
|
||||||
|
lambda_25 = 5.7959
|
||||||
|
lambda_26 = 5.8550
|
||||||
|
lambda_27 = 6.1829
|
||||||
|
lambda_28 = 6.4619
|
||||||
|
lambda_29 = 6.8986
|
||||||
|
lambda_30 = 7.3807
|
||||||
|
lambda_31 = 7.8965
|
||||||
|
lambda_32 = 9.1602
|
||||||
|
lambda_33 = 11.1710
|
||||||
|
lambda_34 = 14.0475
|
||||||
|
lambda_35 = 15.5333
|
||||||
|
|
||||||
|
Spectral gap (lambda_max - lambda_2): 15.5333
|
||||||
|
Fiedler value (algebraic connectivity): 0.0000
|
||||||
|
|
||||||
|
FIEDLER VECTOR — SPECTRAL BISECTION
|
||||||
|
----------------------------------------
|
||||||
|
Partition A (Fiedler < 0):
|
||||||
|
HybridChannel [channel ] f = -0.0000
|
||||||
|
RuntimeConfig [config ] f = -0.0000
|
||||||
|
SenderT [runtime ] f = -0.0000
|
||||||
|
PyRuntimeHandle [python ] f = -0.0000
|
||||||
|
PyRuntimeConfig [python ] f = -0.0000
|
||||||
|
Placement [address_map ] f = -0.0000
|
||||||
|
Message [actor ] f = -0.0000
|
||||||
|
PyActorAddress [python ] f = -0.0000
|
||||||
|
PyRuntime [python ] f = -0.0000
|
||||||
|
Envelope [runtime ] f = -0.0000
|
||||||
|
PyActor [python ] f = -0.0000
|
||||||
|
TickContext [worker ] f = -0.0000
|
||||||
|
Sender [channel ] f = -0.0000
|
||||||
|
InboxRegistry [runtime ] f = -0.0000
|
||||||
|
Runtime [runtime ] f = -0.0000
|
||||||
|
Receiver [channel ] f = -0.0000
|
||||||
|
────────────────────────────────────
|
||||||
|
Partition B (Fiedler >= 0):
|
||||||
|
Error [error ] f = +0.0000
|
||||||
|
Ctx [actor ] f = +0.0000
|
||||||
|
Effect [python ] f = +0.0000
|
||||||
|
BackoffPolicy [config ] f = +0.0000
|
||||||
|
ActorAddress [actor ] f = +0.0000
|
||||||
|
AddressMap [address_map ] f = +0.0000
|
||||||
|
ContextInner [actor ] f = +0.0000
|
||||||
|
Worker [worker ] f = +0.0000
|
||||||
|
ActorPool [worker ] f = +0.0000
|
||||||
|
WorkerContext [worker ] f = +0.0000
|
||||||
|
RuntimeHandle [runtime ] f = +0.0000
|
||||||
|
PyInbox [python ] f = +0.0000
|
||||||
|
ActorInterface [actor ] f = +0.0000
|
||||||
|
AnyActor [actor ] f = +0.0000
|
||||||
|
WorkerId [address_map ] f = +0.0000
|
||||||
|
Inbox [runtime ] f = +0.0000
|
||||||
|
PyCtx [python ] f = +0.0000
|
||||||
|
PyMsg [python ] f = +0.0000
|
||||||
|
Actor [actor ] f = +0.0000
|
||||||
|
Mailbox [worker ] f = +1.0000
|
||||||
|
|
||||||
|
MODULE COUPLING MATRIX (directed edge counts)
|
||||||
|
----------------------------------------
|
||||||
|
error config channel actoraddress_map runtime worker python
|
||||||
|
error 0 0 0 0 0 0 0 0
|
||||||
|
config 0 1 0 0 0 0 0 0
|
||||||
|
channel 0 0 3 0 0 1 0 0
|
||||||
|
actor 2 0 0 7 0 0 0 0
|
||||||
|
address_map 0 0 0 1 2 0 0 0
|
||||||
|
runtime 2 1 2 6 2 6 1 0
|
||||||
|
worker 1 1 2 9 4 3 3 0
|
||||||
|
python 0 0 0 5 0 3 0 10
|
||||||
|
|
||||||
|
Cross-module edges: 46 / 78 (59.0%)
|
||||||
|
|
||||||
|
CONNECTOME COMPLEXITY INDEX (CCI)
|
||||||
|
----------------------------------------
|
||||||
|
Sub-metric Raw Normalized Weight Contrib
|
||||||
|
──────────────────────────────────────── ────────── ────────── ──────── ────────
|
||||||
|
Algebraic connectivity (lambda_2/n) 0.0000 0.0000 0.25 0.0000
|
||||||
|
Spectral entropy (H/log2(k)) 4.6411 0.9123 0.25 0.2281
|
||||||
|
Edge density (|E|/n(n-1)) 0.0619 0.0619 0.15 0.0093
|
||||||
|
Cross-module coupling ratio 0.5897 0.5897 0.20 0.1179
|
||||||
|
Spectral radius (rho/(n-1)) 6.6762 0.1907 0.15 0.0286
|
||||||
|
──────────────────────────────────────── ────────── ────────── ──────── ────────
|
||||||
|
CCI (weighted sum) 1.00 0.3839
|
||||||
|
|
||||||
|
Interpretation: MODERATE complexity — typical well-structured codebase
|
||||||
|
|
||||||
|
========================================================================
|
||||||
47
src/actor.rs
47
src/actor.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use crate::runtime::Ctx;
|
use crate::Error;
|
||||||
|
|
||||||
/// The primary trait defining data that can be passed to and from actor processes
|
/// The primary trait defining data that can be passed to and from actor processes
|
||||||
pub trait Message: 'static + Sized + Clone + Send + Sync {}
|
pub trait Message: 'static + Sized + Clone + Send + Sync {}
|
||||||
|
|
@ -49,3 +49,48 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Object-safe inner trait for sending type-erased messages.
|
||||||
|
pub(crate) trait ContextInner {
|
||||||
|
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error>;
|
||||||
|
fn spawn_any(&self, addr: ActorAddress, actor: Box<dyn AnyActor>) -> Result<(), Error>;
|
||||||
|
fn mailbox_waterlevel(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actor syscall interface — passed to `ActorInterface::handle()`.
|
||||||
|
///
|
||||||
|
/// Wraps a `&dyn ContextInner` to solve the object-safety problem while
|
||||||
|
/// providing a typed public API.
|
||||||
|
pub struct Ctx<'a> {
|
||||||
|
inner: &'a dyn ContextInner,
|
||||||
|
self_addr: ActorAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Ctx<'a> {
|
||||||
|
pub(crate) fn new(inner: &'a dyn ContextInner, self_addr: ActorAddress) -> Self {
|
||||||
|
Self { inner, self_addr }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
pub(crate) fn raw_inner(&self) -> &dyn ContextInner {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the address of the actor currently being ticked.
|
||||||
|
pub fn self_addr(&self) -> ActorAddress {
|
||||||
|
self.self_addr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a typed message to an actor address.
|
||||||
|
pub fn send<M: Message>(&self, addr: ActorAddress, msg: M) -> Result<(), Error> {
|
||||||
|
self.inner.send_any(addr, Box::new(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a new actor, returning its address.
|
||||||
|
pub fn spawn<A: ActorInterface>(&self, actor: A) -> Result<ActorAddress, Error> {
|
||||||
|
let addr = ActorAddress::new_random();
|
||||||
|
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(actor));
|
||||||
|
self.inner.spawn_any(addr, boxed)?;
|
||||||
|
Ok(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ use std::cell::RefCell;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyModule;
|
use pyo3::types::PyModule;
|
||||||
|
|
||||||
use crate::actor::{Actor, ActorAddress, ActorInterface, AnyActor};
|
use crate::actor::{Actor, ActorAddress, ActorInterface, AnyActor, Ctx};
|
||||||
use crate::config::{BackoffPolicy, RuntimeConfig};
|
use crate::config::{BackoffPolicy, RuntimeConfig};
|
||||||
use crate::runtime::{Ctx, Inbox, Runtime, RuntimeHandle};
|
use crate::runtime::{Inbox, Runtime, RuntimeHandle};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
// ─── PyMsg newtype ───────────────────────────────────────────────────────────
|
// ─── PyMsg newtype ───────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -65,43 +65,8 @@ impl RuntimeHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actor syscall interface — passed to `ActorInterface::handle()`.
|
// Re-export Ctx and ContextInner for backwards compatibility
|
||||||
///
|
pub use crate::actor::{ContextInner, Ctx};
|
||||||
/// Wraps a `&dyn ContextInner` to solve the object-safety problem while
|
|
||||||
/// providing a typed public API.
|
|
||||||
pub struct Ctx<'a> {
|
|
||||||
inner: &'a dyn ContextInner,
|
|
||||||
self_addr: ActorAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Ctx<'a> {
|
|
||||||
pub(crate) fn new(inner: &'a dyn ContextInner, self_addr: ActorAddress) -> Self {
|
|
||||||
Self { inner, self_addr }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "python")]
|
|
||||||
pub(crate) fn raw_inner(&self) -> &dyn ContextInner {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the address of the actor currently being ticked.
|
|
||||||
pub fn self_addr(&self) -> ActorAddress {
|
|
||||||
self.self_addr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a typed message to an actor address.
|
|
||||||
pub fn send<M: Message>(&self, addr: ActorAddress, msg: M) -> Result<(), Error> {
|
|
||||||
self.inner.send_any(addr, Box::new(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn a new actor, returning its address.
|
|
||||||
pub fn spawn<A: ActorInterface>(&self, actor: A) -> Result<ActorAddress, Error> {
|
|
||||||
let addr = ActorAddress::new_random();
|
|
||||||
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(actor));
|
|
||||||
self.inner.spawn_any(addr, boxed)?;
|
|
||||||
Ok(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type-erased sender for external inboxes.
|
/// Type-erased sender for external inboxes.
|
||||||
pub(crate) trait SenderT: Send + Sync {
|
pub(crate) trait SenderT: Send + Sync {
|
||||||
|
|
@ -287,7 +252,7 @@ impl Runtime {
|
||||||
inbox_registry: &rt_clone.inbox_registry,
|
inbox_registry: &rt_clone.inbox_registry,
|
||||||
config: &rt_clone.config,
|
config: &rt_clone.config,
|
||||||
};
|
};
|
||||||
worker.run(&tc, &rt_clone.is_running, &rt_clone.config.backoff_policy);
|
worker.run(&tc, &rt_clone.is_running);
|
||||||
});
|
});
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
@ -400,14 +365,6 @@ impl InboxRegistry {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Object-safe inner trait for sending type-erased messages.
|
|
||||||
pub(crate) trait ContextInner {
|
|
||||||
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error>;
|
|
||||||
fn spawn_any(&self, addr: ActorAddress, actor: Box<dyn AnyActor>) -> Result<(), Error>;
|
|
||||||
fn mailbox_waterlevel(&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl ContextInner for Runtime {
|
impl ContextInner for Runtime {
|
||||||
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error> {
|
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error> {
|
||||||
match self.address_map.lookup(&addr) {
|
match self.address_map.lookup(&addr) {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crate::actor::{ActorAddress, AnyActor, Message};
|
use crate::actor::{ActorAddress, AnyActor, ContextInner, Ctx, Message};
|
||||||
use crate::address_map::{AddressMap, Placement, WorkerId};
|
use crate::address_map::{AddressMap, Placement, WorkerId};
|
||||||
use crate::channel::{Receiver, Sender};
|
use crate::channel::{Receiver, Sender};
|
||||||
use crate::config::{BackoffPolicy, RuntimeConfig};
|
use crate::config::RuntimeConfig;
|
||||||
use crate::runtime::{ContextInner, Ctx, Envelope, InboxRegistry};
|
use crate::runtime::{Envelope, InboxRegistry};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// Per-worker stats published via atomics. Readable from any thread.
|
/// Per-worker stats published via atomics. Readable from any thread.
|
||||||
|
|
@ -90,12 +90,7 @@ impl Worker {
|
||||||
{
|
{
|
||||||
let worker_ctx = WorkerContext {
|
let worker_ctx = WorkerContext {
|
||||||
worker_id: self.id,
|
worker_id: self.id,
|
||||||
address_map: tc.address_map,
|
tc,
|
||||||
transfer_txs: tc.transfer_txs,
|
|
||||||
spawn_txs: tc.spawn_txs,
|
|
||||||
placement: tc.placement,
|
|
||||||
inbox_registry: tc.inbox_registry,
|
|
||||||
config: tc.config,
|
|
||||||
pending_local: &pending_local,
|
pending_local: &pending_local,
|
||||||
};
|
};
|
||||||
processed = self.pool.tick_all(&worker_ctx);
|
processed = self.pool.tick_all(&worker_ctx);
|
||||||
|
|
@ -121,7 +116,8 @@ impl Worker {
|
||||||
did_work
|
did_work
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run(&mut self, tc: &TickContext, is_running: &AtomicBool, backoff: &BackoffPolicy) {
|
pub(crate) fn run(&mut self, tc: &TickContext, is_running: &AtomicBool) {
|
||||||
|
let backoff = &tc.config.backoff_policy;
|
||||||
let mut idle_count: u32 = 0;
|
let mut idle_count: u32 = 0;
|
||||||
while is_running.load(Ordering::Acquire) {
|
while is_running.load(Ordering::Acquire) {
|
||||||
let did_work = self.tick_once(tc);
|
let did_work = self.tick_once(tc);
|
||||||
|
|
@ -151,18 +147,13 @@ impl Worker {
|
||||||
/// Cross-worker sends go through the transfer queue.
|
/// Cross-worker sends go through the transfer queue.
|
||||||
struct WorkerContext<'a> {
|
struct WorkerContext<'a> {
|
||||||
worker_id: WorkerId,
|
worker_id: WorkerId,
|
||||||
address_map: &'a AddressMap,
|
tc: &'a TickContext<'a>,
|
||||||
transfer_txs: &'a [Sender<Envelope>],
|
|
||||||
spawn_txs: &'a [Sender<(ActorAddress, Box<dyn AnyActor>)>],
|
|
||||||
placement: &'a Placement,
|
|
||||||
inbox_registry: &'a InboxRegistry,
|
|
||||||
config: &'a RuntimeConfig,
|
|
||||||
pending_local: &'a RefCell<Vec<(ActorAddress, Box<dyn Any + Send>)>>,
|
pending_local: &'a RefCell<Vec<(ActorAddress, Box<dyn Any + Send>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextInner for WorkerContext<'_> {
|
impl ContextInner for WorkerContext<'_> {
|
||||||
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error> {
|
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error> {
|
||||||
match self.address_map.lookup(&addr) {
|
match self.tc.address_map.lookup(&addr) {
|
||||||
Some(wid) if wid == self.worker_id => {
|
Some(wid) if wid == self.worker_id => {
|
||||||
// Same worker: buffer for local delivery (after current tick round)
|
// Same worker: buffer for local delivery (after current tick round)
|
||||||
self.pending_local.borrow_mut().push((addr, msg));
|
self.pending_local.borrow_mut().push((addr, msg));
|
||||||
|
|
@ -171,26 +162,26 @@ impl ContextInner for WorkerContext<'_> {
|
||||||
Some(wid) => {
|
Some(wid) => {
|
||||||
// Cross worker: envelope through transfer queue
|
// Cross worker: envelope through transfer queue
|
||||||
let envelope = Envelope::new(addr, msg);
|
let envelope = Envelope::new(addr, msg);
|
||||||
let _ = self.transfer_txs[wid.as_usize()].try_send(envelope);
|
let _ = self.tc.transfer_txs[wid.as_usize()].try_send(envelope);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Try inbox registry (external inboxes)
|
// Try inbox registry (external inboxes)
|
||||||
self.inbox_registry.try_deliver(addr, msg)
|
self.tc.inbox_registry.try_deliver(addr, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_any(&self, addr: ActorAddress, actor: Box<dyn AnyActor>) -> Result<(), Error> {
|
fn spawn_any(&self, addr: ActorAddress, actor: Box<dyn AnyActor>) -> Result<(), Error> {
|
||||||
let worker_id = self.placement.next_worker();
|
let worker_id = self.tc.placement.next_worker();
|
||||||
self.address_map.insert(addr, worker_id);
|
self.tc.address_map.insert(addr, worker_id);
|
||||||
self.spawn_txs[worker_id.as_usize()]
|
self.tc.spawn_txs[worker_id.as_usize()]
|
||||||
.try_send((addr, actor))
|
.try_send((addr, actor))
|
||||||
.map_err(|_| Error::from("Spawn queue full"))
|
.map_err(|_| Error::from("Spawn queue full"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mailbox_waterlevel(&self) -> usize {
|
fn mailbox_waterlevel(&self) -> usize {
|
||||||
self.config.mailbox_waterlevel
|
self.tc.config.mailbox_waterlevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crate::actor::{ActorAddress, AnyActor};
|
use crate::actor::{ActorAddress, AnyActor, Ctx};
|
||||||
use crate::address_map::{AddressMap, Placement, WorkerId};
|
use crate::address_map::{AddressMap, Placement, WorkerId};
|
||||||
use crate::channel::Receiver;
|
use crate::channel::Receiver;
|
||||||
use crate::config::{BackoffPolicy, RuntimeConfig};
|
use crate::config::RuntimeConfig;
|
||||||
use crate::runtime::{Ctx, Envelope, InboxRegistry};
|
use crate::runtime::{Envelope, InboxRegistry};
|
||||||
|
|
||||||
use super::{TickContext, Worker, WorkerStats};
|
use super::{TickContext, Worker, WorkerStats};
|
||||||
|
|
||||||
|
|
@ -399,7 +399,6 @@ fn run_loop_stops_on_shutdown() {
|
||||||
let mut worker = Worker::new(WorkerId(0), transfer_rx, spawn_rx, stats);
|
let mut worker = Worker::new(WorkerId(0), transfer_rx, spawn_rx, stats);
|
||||||
|
|
||||||
let is_running = AtomicBool::new(false);
|
let is_running = AtomicBool::new(false);
|
||||||
let backoff = BackoffPolicy::default();
|
|
||||||
let address_map = AddressMap::new();
|
let address_map = AddressMap::new();
|
||||||
let placement = Placement::new(1);
|
let placement = Placement::new(1);
|
||||||
let inbox_registry = InboxRegistry::new();
|
let inbox_registry = InboxRegistry::new();
|
||||||
|
|
@ -415,6 +414,6 @@ fn run_loop_stops_on_shutdown() {
|
||||||
};
|
};
|
||||||
|
|
||||||
thread::scope(|s| {
|
thread::scope(|s| {
|
||||||
s.spawn(|| worker.run(&tc, &is_running, &backoff));
|
s.spawn(|| worker.run(&tc, &is_running));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1412,8 +1412,8 @@ def main() -> None:
|
||||||
description="Spectral analysis of dependency DAGs"
|
description="Spectral analysis of dependency DAGs"
|
||||||
)
|
)
|
||||||
parser.add_argument("dot_file", help="Path to DOT file (from depgraph)")
|
parser.add_argument("dot_file", help="Path to DOT file (from depgraph)")
|
||||||
parser.add_argument("-o", "--output-dir", default=".",
|
parser.add_argument("-o", "--output-dir", default="docs/connectome",
|
||||||
help="Output directory (default: current directory)")
|
help="Output directory (default: docs/connectome)")
|
||||||
parser.add_argument("--no-plots", action="store_true",
|
parser.add_argument("--no-plots", action="store_true",
|
||||||
help="Text report only (no matplotlib dependency)")
|
help="Text report only (no matplotlib dependency)")
|
||||||
parser.add_argument("--json", action="store_true",
|
parser.add_argument("--json", action="store_true",
|
||||||
|
|
@ -1435,25 +1435,25 @@ def main() -> None:
|
||||||
# Generate report
|
# Generate report
|
||||||
report = generate_report(result)
|
report = generate_report(result)
|
||||||
print(report)
|
print(report)
|
||||||
report_path = os.path.join(args.output_dir, "spectral_report.txt")
|
report_path = os.path.join(args.output_dir, "connectome_report.txt")
|
||||||
with open(report_path, "w") as f:
|
with open(report_path, "w") as f:
|
||||||
f.write(report)
|
f.write(report)
|
||||||
print(f"\nReport saved to {report_path}")
|
print(f"\nReport saved to {report_path}")
|
||||||
|
|
||||||
# Generate interactive HTML dashboard
|
# Generate interactive HTML dashboard
|
||||||
html_path = os.path.join(args.output_dir, "spectral_dashboard.html")
|
html_path = os.path.join(args.output_dir, "connectome_dashboard.html")
|
||||||
generate_dashboard_html(result, html_path, dot_source=dot_text)
|
generate_dashboard_html(result, html_path, dot_source=dot_text)
|
||||||
print(f"Interactive dashboard saved to {html_path}")
|
print(f"Interactive dashboard saved to {html_path}")
|
||||||
|
|
||||||
# Generate static PNG dashboard
|
# Generate static PNG dashboard
|
||||||
if not args.no_plots:
|
if not args.no_plots:
|
||||||
dashboard_path = os.path.join(args.output_dir, "spectral_dashboard.png")
|
dashboard_path = os.path.join(args.output_dir, "connectome_dashboard.png")
|
||||||
generate_dashboard(result, dashboard_path)
|
generate_dashboard(result, dashboard_path)
|
||||||
print(f"Static dashboard saved to {dashboard_path}")
|
print(f"Static dashboard saved to {dashboard_path}")
|
||||||
|
|
||||||
# Generate JSON
|
# Generate JSON
|
||||||
if args.json:
|
if args.json:
|
||||||
json_path = os.path.join(args.output_dir, "spectral_metrics.json")
|
json_path = os.path.join(args.output_dir, "connectome_metrics.json")
|
||||||
with open(json_path, "w") as f:
|
with open(json_path, "w") as f:
|
||||||
json.dump(metrics_to_dict(result), f, indent=2)
|
json.dump(metrics_to_dict(result), f, indent=2)
|
||||||
print(f"JSON saved to {json_path}")
|
print(f"JSON saved to {json_path}")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue