Exports and formats - a field guide
What GridSim writes, which verb writes it, and which tools read it. This is the practical map, not the schema reference - field-by-field definitions live in the technical manual (../technical/10-file-and-wire-formats.md). All JSON is camelCase with enums by name; all numbers are invariant-culture.
Run directories and run-metadata.json
Every report-producing verb writes into an output directory (default under results/)
and drops a run-metadata.json sidecar so the run is self-describing: who ran it, on
what code, on what machine. Written by RunInfo.WriteJson; read by you, months later,
when two results disagree.
{
"timestampUtc": "2026-07-01T10:18:36.0000000Z",
"backend": "cpu",
"gitCommit": "c41ef951",
"buildConfig": "Release",
"machineName": "curious-bohr",
"logicalCores": 28,
"run": "bench-matrix cases=153 engines=3 runs=10 parallel=28"
}
The same information heads every Markdown report and every CSV (as # comment lines),
and the git commit is stamped into every CSV row's commit column. The WPF app's
LOAD RUN picker auto-discovers run directories under results/.
gridsim-solve-export/1 - the solve-math export
The full record of one power-flow solve: the network as solved, the per-iteration math trace, and the converged state.
- Written by:
--enableExport[=path]on any case run;crossval(solve-export.json);transient(exports/slice-*.json);gda-replay(exports/state-*.json);estimate --export-solve(estimated-solve-export.json); GDA'sgridsim_export.to_solve_exporton the lake side. - Read by:
tools/crossval/crossval.pyandresidual_check.py(validation);Core/IO/SolveImport.cs(pre-solved replay in the WPF/web apps); the GDA bridge, whose topology and query payloads reuse this document'smodelblock.
{
"schema": "gridsim-solve-export/1",
"meta": { "case": "case9", "backend": "cpu", "gitCommit": "...", "timestampUtc": "..." },
"model": { "baseMva": 100, "buses": [ { "id": 1, "type": "Slack", "baseKv": 16.5, "pd": 0, "vm": 1.04 } ],
"generators": [ ... ], "branches": [ { "from": 1, "to": 4, "r": 0, "x": 0.0576, "tap": 1.0 } ] },
"solve": { "solver": "dense-newton", "converged": true, "iterations": 4,
"maxMismatch": 1.8E-14, "ybus": [ ... ], "passes": [ { "iterations": [ ... ] } ] },
"result": { "totalLossMw": 4.641, "buses": [ { "id": 1, "vm": 1.04, "vaDeg": 0.0, "pInjMw": 71.6 } ],
"lineFlows": [ ... ] }
}
The ybus block is empty for BFS solves (it forms no Y-bus); the Jacobian is captured
only for small dense solves unless you pass --export-jacobian.
gridsim-estimate-export/1 - the estimate export (+ trace block)
The entire input contract of the estimation sidecars: model, the original measurement set, the estimated state, the residual table, observability and removals.
- Written by:
estimate(estimate-export.jsonin the run directory). - Read by:
tools/crossval/crossval_estimation.py,se_residual_check.py; the contract is pinned in CI byEstimateExportContractTests.
{
"schema": "gridsim-estimate-export/1",
"meta": { ... }, "model": { ...same shape as solve-export... },
"measurements": { "measurementSchema": "gridsim-measurements/1", "count": 1098, "items": [ ... ] },
"estimate": { "status": "Converged", "iterations": 5, "objectiveJ": 96.3,
"chiSquareThreshold": 931.2, "chiSquarePassed": true,
"buses": [ { "id": 1, "vm": 1.0399, "vaDeg": 0.0 } ] },
"residuals": [ { "measurement": { "kind": "PInjMw", "bus": 4, ... },
"predicted": -47.71, "residual": -1.2E-01, "normalizedResidual": 0.43, "critical": false } ],
"observability": { "observable": true, "rank": 235, "stateSize": 235 },
"removed": [ { "measurement": { ... }, "normalizedResidual": 21.4, "pass": 1 } ],
"trace": [ { "pass": 1, "iteration": 1, "maxStateUpdate": 4.2E-02, "stepScale": 1.0 } ]
}
The trace block appears only when the run passed --trace: the per-iteration
convergence path (monotone maxStateUpdate on a healthy run), re-checked by
se_residual_check.py. Readers ignore unknown fields, so trace is additive.
residuals.csv
The estimate's residual table flattened for spreadsheets - one row per measurement the
final estimate used. Written by estimate next to the export; read by humans hunting
outliers and by GDA's sigma-calibration tooling.
kind,location,value,sigma,predicted,residual,normalized_residual,critical
VmPu,bus 1,1.0601,0.004,1.06008,2.1E-05,0.005,False
PInjMw,bus 4,-47.83,1.0,-47.71,-1.2E-01,0.43,False
gridsim-measurements/1 - the estimator's input
A versioned, self-describing measurement set over one case.
- Written by:
estimate --synth(persists the sampled set asmeasurements.jsonso the sidecars consume the identical input); GDA'sNetworkModel/build_estimator_measurements.pyfor real GB telemetry. - Read by:
estimate --measurements <m.json>. AtakenUtcstamp is checked against the 169 h historical horizon on load.
{
"schema": "gridsim-measurements/1",
"case": "case14", "baseMva": 100.0, "takenUtc": "2026-05-01T08:30:00Z",
"measurements": [
{ "kind": "VmPu", "bus": 1, "value": 1.0601, "sigma": 0.004 },
{ "kind": "PFlowMw", "from": 1, "to": 2, "end": "From", "value": 156.9, "sigma": 1.6 }
]
}
Kinds: VmPu, PInjMw, QInjMvar, PFlowMw, QFlowMvar (and the PMU angle kind -
see the technical manual). Parallel circuits disambiguate with circuit (a name, or
"#k" for the k-th branch).
gridsim-event-catalog/1 - recorded events
The catalogue of recorded grid events the evidence verbs replay.
- Written by:
replay-event --dump-catalog <path>(the built-ins, so you can edit and source them); hand-authored files underdata/events/. - Read by:
replay-event,scenario,fleet,evidencevia--catalog. EverywhenUtcis horizon-checked on load.
{
"schema": "gridsim-event-catalog/1",
"events": [ {
"id": "gb-2019-08-09", "whenUtc": "2019-08-09T15:52:00Z",
"preEventInertiaMwS": 200000, "preEventDemandMw": 30000,
"losses": [ { "atSeconds": 1.0, "deltaPMw": -737, "provenance": "CuratedPlaceholder" } ],
"outcome": { "initialRocofHzPerS": -0.17, "nadirHz": 48.8, "lfddMwShed": 1000,
"nesoTraceCsvPath": null }
} ]
}
Placeholder figures carry CuratedPlaceholder provenance and [VERIFY] notes until
sourced; nesoTraceCsvPath (or the --trace flag) attaches the 1-second NESO trace
that enables the per-sample RMSE check.
gridsim-evidence/1 - the dossier
- Written by:
evidence(evidence.json, wrapped as{ schema, report }). - Read by: reviewers; the summary is mirrored to
summary.mdin the run directory.
{
"schema": "gridsim-evidence/1",
"report": {
"eventId": "gb-2019-08-09", "baselineVerdict": "Reproduced", "counterfactualUnlocked": true,
"columns": [ { "label": "recorded", "rocofHzPerS": -0.170, "nadirHz": 48.80,
"lfddAtRisk": true, "provenance": "CuratedPlaceholder" } ],
"riskCurve": { "points": [ ... ] }, "fleet": { ... },
"avoidedCost": { "lfddMwShedAvoided": 1000, "vollPerMwh": 6000, "avoidedCostGbp": 3000000 }
}
}
The gating is structural: when the baseline is not Reproduced, the counterfactual
column, risk curve, fleet and avoided-cost sections are simply absent - the file cannot
carry an ungated claim.
gridsim-frequency-export/1 - the swing export
One frequency-dynamics simulation with its full recurrence, so RoCoF and nadir are hand-checkable from the numbers in the file.
- Written by:
replay-event(baseline-frequency.json) andscenario(counterfactual-frequency.json, the device at the lowest swept inertia, worst corner). - Read by:
tools/crossval/crossval_freq.py(re-integrates the identical physics with scipy RK45; agreement < 2e-4 Hz).
{
"schema": "gridsim-frequency-export/1",
"inertia": { "systemInertiaMwS": 200000, "deviceRotationalInertiaMwS": 0, "effectiveInertiaMwS": 200000 },
"fastResponse": { "fastResponseMw": 0, "timeConstantS": 0.5, "deadbandHz": 0.015 },
"events": [ { "atSeconds": 1.0, "deltaPMw": -737 } ],
"recurrence": [ "rocof = f0/(2E) * pImbalance ; f += rocof*dt", ... ],
"headline": { "initialRocofHzPerS": -0.170, "nadirHz": 48.80, "settleHz": 49.98 },
"points": [ { "t": 0.0, "hz": 50.0, "rocofHzPerS": 0.0 }, ... ]
}
Intrinsic inertia and fast response are kept in separate labelled blocks - FFR is a power injection, never stored energy, and the export says so.
Time-series CSVs
transient -> timeseries.csv - one row per logged slice; the sidecar telemetry
that pre-solved replay plays back in the GUI:
tick,t_seconds,freq_hz,rocof_hz_s,gen_mw,demand_mw,iters,max_mismatch,vmin_pu,vmax_pu,solve_ms,converged
1,0.0010,50.00000,0.00000,315.00,315.00,4,1.8E-14,0.9958,1.0400,2.1,True
gda-replay -> state-estimate.csv - one row per reconstructed timestamp:
index,time_utc,load_factor,converged,iterations,max_mismatch,total_loss_mw,vmin_pu,vmax_pu
0,2024-01-01T00:00:00Z,0.6910,True,4,3.1E-11,3.212,0.99310,1.04000
Other verbs write analogous CSVs next to their Markdown reports (matrix.csv and
risk-curve.csv for scenario, fleet.csv for fleet, bench's
benchmark-results.csv, ...); each begins with the #-commented RunInfo header and
carries the commit column where rows may be aggregated across runs.
See also
- ../technical/10-file-and-wire-formats.md - field-by-field schemas.
- 08-replaying-recorded-events.md - the event catalogue in use.
- 10-state-estimation.md - producing measurement sets and estimate exports.
- 12-replaying-estimated-states.md - how the bridge streams these shapes.
- 14-validation-tools.md - the tools that consume the exports.