User manual — chapter list

Troubleshooting

Symptom -> cause -> fix, grouped by where it bites. Exit-code meanings are in 19-cli-reference.md.

Build failures

Symptom Cause Fix
dotnet build fails with MSB3027/MSB3021 copy errors The app is running (especially under the VS debugger) and its output DLLs are locked. Stop the debugger, or taskkill //F //IM GridSim.Wpf.exe, then rebuild. To compile-check without disturbing a running instance: dotnet build ... -p:BaseOutputPath=obj/verifybin/ (delete obj/verifybin afterwards).
Build fails on a warning (error CA... / error CS...) that looks cosmetic Directory.Build.props sets TreatWarningsAsErrors with the .NET analyzers in Recommended mode - the repo builds at 0 warnings / 0 errors by policy. Fix the warning; do not suppress it inline. If a CA rule is genuinely noise for numerical code, the tuning point is the repo-root .editorconfig, not a pragma.

Power flow does not converge

Not every DID NOT CONVERGE is a bug. Three input classes are expected not to converge (the canonical list; details and evidence in docs/benchmarking.md sec. 5):

Symptom Cause Fix
A pglib_opf_case* shows **NO** at 100 iterations, while its non-OPF twin (e.g. case2848rte) solves in 2 PGLib-OPF files are optimisation solutions, not valid flat-start power flows. Expected. Bench the non-OPF twin, or accept the NO as an input property.
A small radial feeder (case33bw, case69, case85, case141, ...) diverges under Newton-Raphson Radial distribution networks need the backward/forward-sweep solver, not the transmission NR. Run with --bfs (auto-selected when the model looks radial). Known limitation: BFS is exact only at unity tap.
LOAD FAIL: The input string '135/sqrt(3)' was not in a correct format (case533mt_*) The MATPOWER parser does not evaluate arithmetic expressions in data matrices. Parser gap, not a solver failure. Pre-evaluate the expression in the .m file if you need the case.
Dense engine reports OOM on a large case The dense path is memory-bound above ~600 buses (9,241 buses OOMs an 8 GB box). Use the sparse engine - it solves the same case. Cap the heap (DOTNET_GCHeapHardLimit) so sweeps fail catchably.
A case that used to converge now does not Genuine regression. Run gridsim crossval <case> and tools/crossval/residual_check.py on the export before touching solver code - they tell you whether the last good solution was real.

State estimation

Symptom Cause Fix
observability: UNOBSERVABLE, exit 3 Measurement coverage is too thin to pin every state - the console lists the unmeasured island buses and free state directions. Add measurements where it says: a Vm or injection measurement on each listed bus, or a flow on a branch into the unobserved region. With --synth, raise --coverage-vm/--coverage-inj/--coverage-flow. On real GB sets, the pseudo-measurement builders exist precisely to close these gaps.
consistency: J = ... vs chi2(95%) = ... -> FAIL The measurements are mutually inconsistent at their declared sigmas: a gross error survived, sigmas are optimistic, or the model does not match the era of the telemetry. Look at what bad-data removal fingered: the removed block of estimate-export.json (or removed_json in the GDA estimated-state lake rows) and the worst rows of residuals.csv. Recurrent offenders at reactive-compensated sites (the QInjMvar outlier league) point at model fidelity, not telemetry. If you enabled SPINE_ETYS=1, that era mismatch alone regresses chi-square - turn it off (16-configuration.md).
estimate on real data diverges or crawls from flat start Real operating points are far from flat. Pass --warm-start (or --prior solve); this is standard EMS practice and the documented requirement for real data.
Every removal makes things worse in a weak area Removal in a weakly-redundant region lets the state drift from the prior. Use --backfill-removals - each removed measurement is replaced by a loose prior-evaluated pseudo.

Bridge (WPF/web <-> GDA)

Symptom Cause Fix
Status pill: CONNECT FAILED * ... No bridge server listening at the configured endpoint, or the endpoint is wrong. Start the GDA bridge (Applications/GridSim-Bridge/bridge_server.py) and check BridgeEndpoint in %LOCALAPPDATA%\GridSim\settings.json (WPF) or GridSim:BridgeEndpoint (web) - default ws://127.0.0.1:8770/ws/bridge.
Status pill: HORIZON REJECTION * stream stopped You asked for (or the stream reached) a timestamp newer than the no-forward-inference cutoff - the bridge replays settled history only (cutoff ~ now - 7 d 1 h). Pick an earlier start: the --back launch arg or the start picker in the WPF app; an earlier date in the web session. This is an invariant, not a fault.
Info message: No presolved/estimated frames for '<case>' in <window>; streaming the live solve instead The stored lake window you asked for has not been built; the bridge falls back to on-demand solving (you lose per-bus frame-by-frame replay, not the view). Build the window: NetworkModel/presolve_lake.py for presolved, NetworkModel/build_estimated_state.py for estimated.
SRC: estimated shows nothing for a recent day The estimated-state lake is built day by day; that day has not been estimated yet. Run the daily task (build_gsp_measurements -> build_estimated_state for the date) - see 11-estimating-gb-history.md - then reconnect.
ESTIMATED * ... * chi-square FAIL on the status pill Not a bridge fault: the stored estimate for that period failed consistency and the pill is honestly saying so. Treat as the chi-square FAIL row above - inspect that period's removed_json/residuals in the lake.
BRIDGE FAULT * disconnected mid-stream The receive loop faulted (server restarted, network drop). Reconnect; check the bridge server console for the underlying error.

GUI

Symptom Cause Fix
Map draws every bus at one point (bottom-left, the Cornwall corner) The streamed topology carries no x/y layout coordinates - an old bridge server predating the layout fields in the topology block. Update/restart the GDA bridge server so its topology export populates bus x/y; local JSON cases need X/Y on their buses.
Loaded a run directory but tabs show computed, not recorded, telemetry The run has no sidecar timeseries.csv, so replay computes what it can. Re-run transient with logging on (default) or accept computed values.

See also