Spaces:
Sleeping
Sleeping
| # Co-Study4Grid — Performance benchmarks | |
| Consolidated micro-benchmarks that exercise the critical path of a | |
| Load Study on the reference **PyPSA-EUR France 400 kV** grid | |
| (~25 MB SVG, 6 835 VLs, 85 304 switches, 14 880 loads+generators, | |
| 55 104 operational-limit entries). | |
| These scripts drive the same code paths as the web UI but without the | |
| HTTP stack, so they can be re-run after a patch to catch regressions | |
| before pushing. | |
| ## Prerequisites | |
| ```bash | |
| # Same venv as the backend (`expert_backend`) — needs: | |
| # pypowsybl, expert_op4grid_recommender, pandas, numpy | |
| export PATH="$HOME/.asdf/shims:$PATH" | |
| python -c "import pypowsybl, expert_op4grid_recommender" | |
| ``` | |
| Override the reference grid / action file via env vars: | |
| ```bash | |
| export BENCH_NETWORK_PATH=/path/to/grid_dir # contains grid.xiidm + grid_layout.json | |
| export BENCH_ACTION_FILE=/path/to/reduced_actions.json | |
| export BENCH_CONTINGENCY=DISCO_NAME # only for bench_n1_diagram.py | |
| ``` | |
| ## Scripts | |
| | Script | What it measures | Where the patch history lives | | |
| |---|---|---| | |
| | `bench_load_study.py` | Full `/api/config` + 4 parallel XHRs round-trip (reset + load_network + update_config + 4 response helpers). Cumulative target of every patch on the branch. | `docs/performance/history/loading-parallel.md` | | |
| | `bench_topology_cache.py` | Per-helper + full `NetworkTopologyCache(net)` init. Validates upstream vectorisation series (0.2.0.post3 → post8). | `docs/performance/history/vectorize-topology-cache.md`, `docs/performance/history/topology-cache-iter2.md` | | |
| | `bench_voltage_level_queries.py` | `/api/voltage-levels`, `/api/nominal-voltages`, `get_monitored_elements`, `_get_switches_with_topology` narrow-attr wins. | `docs/performance/history/narrow-voltage-level-queries.md` | | |
| | `bench_n1_diagram.py` | Full `get_n1_diagram(contingency)` cold + warm, per-sub-step breakdown. Validates the 3 N-1 fast-path patches. | `docs/performance/history/n1-diagram-fast-path.md` | | |
| | `bench_nad_n_state.py` | `get_network_diagram()` cold + warm on the N-state. Captures NAD / SVG / Meta sub-timings from the `[RECO]` log lines. | `docs/performance/nad-profile-bare-env.md` | | |
| | `bench_nad_toggles.py` | Matrix of `NadParameters` toggle combinations — quantifies per-flag impact on NAD gen + SVG size, surfaces the cost of `injections_added=True`. | `docs/performance/nad-profile-bare-env.md` | | |
| | `bench_analyze_suggest.py` | **Full "Analyze & Suggest" for a Game Mode study** — drives `/api/config` → `step1` → `step2` (streaming NDJSON) through the FastAPI `TestClient` and prints the UI's execution-time breakdown (step1 / overflow / prediction / **assessment** / enrichment / **Other**), with "Other" decomposed into discovery-overhead / result `sanitize_for_json` / transport. `--serial` forces serial reassessment; `--compare` runs parallel-vs-serial. This is the case the 30 s → 75 s regression was reported on. | `docs/performance/history/analyze-suggest-2vcpu.md` | | |
| | `bench_load_flow_modes.py` | **Per-LF knob sweep** — times a single `run_ac` on the N-1 state under each load-flow parameter variant (tap-changer control mode, shunt / reactive / phase-shifter toggles), reporting time / Newton iters / constrained-line current. Isolates the ~6x reassessment cost to `transformer_voltage_control_on` and shows `AFTER_GENERATOR_VOLTAGE_CONTROL` recovers it. `--contingency` / `--overload` / `--reps`. | `docs/performance/history/reassessment-fast-mode-tap-control.md` | | |
| | `run_all.py` | Drives every benchmark above sequentially. | — | | |
| ### `bench_analyze_suggest.py` | |
| ```bash | |
| # The reported "this case, first scenario": Pyrenees LANNEL61PRAGN on the | |
| # medium/European grid (its network.xiidm ships as a Git-LFS zip — run | |
| # `git lfs pull` first, or use --tier high for the uncompressed French grid). | |
| python benchmarks/bench_analyze_suggest.py # medium tier, first study | |
| python benchmarks/bench_analyze_suggest.py --tier high # French grid, first study | |
| python benchmarks/bench_analyze_suggest.py --compare # parallel vs serial, same case | |
| ``` | |
| Two levers this benchmark validates: | |
| - **Per-action reassessment goes serial on a CPU-limited host.** The tail line | |
| reports `reassessment: serial|parallel — N worker(s) / M effective core(s)`. | |
| On a 2-vCPU host the recommender's container-aware detection picks serial; | |
| even on a 4-core dev box `--compare` shows parallel is no faster than serial | |
| (each worker clones a full network), so over-subscribing 2 vCPUs with ~10 | |
| workers was the 47 s assessment in the regression. | |
| - **The step-2 result payload no longer ships full-grid per-branch arrays.** | |
| Each combined-action pair used to carry `p_or_combined` / `p_ex_combined` | |
| (one float per line × ~100 pairs ≈ **29 MB** on the European grid); the | |
| frontend reads neither, so they are emptied at the API boundary. Watch | |
| `payload=… KiB` and the `result sanitize_for_json` sub-line drop | |
| (29 269 KiB / 2.57 s → 267 KiB / 0.01 s). | |
| ## Reference measurements | |
| On a developer box with pypowsybl 1.14.0 + Python 3.12 + the full | |
| PyPSA-EUR France 400 kV grid, current branch tip: | |
| ### `bench_load_study.py` | |
| | Segment | Measured | | |
| |---|---| | |
| | `reset()` | ~0 ms | | |
| | `load_network` | ~2 200 ms | | |
| | `update_config` | ~5 900 ms | | |
| | 4 response XHRs | ~330 ms | | |
| | **Total** | **~8.5 s** | | |
| This maps to ~8.8 s end-to-end wall-clock on Chrome DevTools traces — | |
| see v18 row of `docs/performance/history/loading-parallel.md` (-63 % vs v6 baseline). | |
| ### `bench_voltage_level_queries.py` | |
| | Endpoint | Before | After | | |
| |---|---|---| | |
| | `/api/voltage-levels` | 7.5 ms | 4.5 ms | | |
| | `/api/nominal-voltages` | **144 ms** | **5.7 ms** (~25×) | | |
| | `get_monitored_elements` | 265 ms | 175 ms | | |
| | `_get_switches_with_topology` | 174 ms | 141 ms | | |
| ### `bench_n1_diagram.py` (contingency `ARGIAL71CANTE`) | |
| | Call | Before | After | | |
| |---|---|---| | |
| | COLD (first view) | 18 125 ms | **4 159 ms** (-77 %) | | |
| | WARM (repeat view) | 11 906 ms | **3 200 ms** (-73 %) | | |
| ## When to run them | |
| - **Before pushing a perf patch** on the backend or on | |
| `expert_op4grid_recommender`: run the benchmark closest to the | |
| change and confirm no regression on the rest via `run_all.py`. | |
| - **When a DevTools trace suggests slowdown**: map the hot span to | |
| one of the four scripts to isolate it from web-layer variance. | |
| - **When upstream bumps** `expert_op4grid_recommender`: re-run | |
| `bench_topology_cache.py` + `bench_voltage_level_queries.py` to | |
| catch behavioural changes in pypowsybl / numpy / pandas upgrades. | |
| ## Notes | |
| - These scripts import `expert_backend.services.*` directly, so they | |
| need to run inside the Co-Study4Grid venv. | |
| - Each benchmark is idempotent — running one does not alter global | |
| state in a way that would affect the next (each call to | |
| `setup_service` resets the recommender). | |
| - The scripts intentionally use real data paths rather than mocks: | |
| the goal is to measure the full pypowsybl + JNI + pandas stack. | |
| Unit tests in `expert_backend/tests/` cover the mock path. | |