Electricity, hydrogen and heat dispatch (Pyomo/HiGHS) on a real fetched year of ERA5 wind, with independent verification on every run and an interactive Streamlit GUI.
Hydrogen · Heat · Offshore wind · Pyomo dispatch
PyNEXUS Multi-Vector Dispatch Model
A Pyomo/HiGHS model coupling electricity, hydrogen and heat — offshore wind, PEM electrolysis, hydrogen storage/pipeline, grid exchange and electrolyser waste-heat recovery — run on a real fetched year of ERA5 wind, with an interactive Streamlit GUI. The model demonstrates how I couple distinct energy vectors, verify balance equations and confront infeasible demand rather than adjusting inputs until a preferred scenario solves.
What I built
- Offshore wind, PEM electrolyser, hydrogen pipeline, hydrogen storage, grid import/export, and electrolyser waste-heat recovery (with heat storage and backup boiler) — storage/grid/heat are opt-in and off by default, regression-tested against a locked reference objective.
- A real ERA5 ingestion path (Copernicus CDS API): monthly-chunked fetch, provenance records, fail-loud gap checking. Used it to fetch a genuine year of 2023 wind for the site and validate the model's capacity factor (39.7%) against a peer-reviewed source.
- Weighted-least-squares data reconciliation with a chi-square gross-error test on the hydrogen mass balance — the same method used in my ThermoTwin repo, applied here to a different balance.
- A Streamlit GUI wrapping the exact same optimizer/verification code the CLI uses: run a dispatch live, browse committed runs, inspect the real capacity factor, and an interactive reconciliation demo.
- An independent verification pass on every run (mass/energy balances, storage recursion, cost/objective reconstruction) — a solver reporting "optimal" is not treated as sufficient on its own.
Method
Multi-vector dispatch model
- Core MILP: electrolyser power
p[t](binary online status), minimising cost or emissions subject to wind availability, min/max load, ramp limits, pipeline capacity, and hourly or cumulative hydrogen demand. Solved with Pyomo + HiGHS. - Phase A (opt-in): hydrogen storage with a terminal condition (so the optimiser can't drain it for free on the last hour) and grid import/export against a single energy balance — both off by default and regression-tested to leave the base model's locked reference objective unchanged.
- Phase B (opt-in): electrolyser waste heat (same linearised efficiency the hydrogen output already assumes, so the energy balance stays internally consistent), heat storage, and a backup boiler. A real swept finding: at the current demand-shortfall penalty, heat value has zero measurable effect on electrolyser dispatch — the penalty dominates it by orders of magnitude, confirmed by deliberately weakening the penalty and watching the coupling switch on.
- Phase C (real data): ERA5 wind fetched via the Copernicus CDS API, hub-height shear-corrected, and run through the model for the full 2023 year. The wind-only and grid-only-cumulative annual variants are genuinely infeasible against real demand — not a bug, a real capacity-planning finding, reported rather than hidden. The recorded successful run (grid + storage, soft hourly demand) still leaves ~16% of demand unmet.
- Verification: every run is independently re-checked from the exported CSV — wind/energy balances, storage recursion, cost identity, objective reconstruction — separate code from the optimizer itself, so a correct-looking solve can't silently hide an inconsistency.
Outputs
What the model produces
- run_manifest.json — config/input/dispatch hashes, environment, solver status and gap, model size, objective value
- dispatch.csv — per-hour power, hydrogen, storage, grid and heat flows for the solved run
- verification.json — the independent check results (mass balance, cost identity, objective reconstruction, pass/fail)
- ERA5 provenance records — one per monthly fetch plus an annual summary: source, grid cell, checksum, calendar-completeness check
- A Streamlit GUI (screenshot above) for running, verifying, and browsing all of the above without writing Python
Limitations
What the model does not include
- The MILP's hydrogen output uses a single linearised electrolyser efficiency (fixed at the 80%-load point), not the full nonlinear load-dependent curve — a deliberate tractability choice, stated explicitly rather than left implicit.
- ENTSO-E day-ahead prices are implemented and unit-tested (same fetch/provenance discipline as ERA5) but not yet run live — needs a separate API credential I haven't requested yet. Dispatch runs pair real wind with a synthetic price series until then.
- The data reconciliation module deliberately does not try to name which sensor is at fault when it flags an inconsistency: with one balance equation across four sensors, that ranking is mathematically determined by the configured variances alone, independent of which sensor is actually biased — proved and tested, not assumed.
- It's a screening model: the turbine power curve is a cubic approximation, loss factors are literature-sourced rather than site-measured, and the demand/capacity figures are illustrative, not a project-specific engineering design.
Relevance
Why this matters
This project extends the energy-systems portfolio into genuinely multi-vector infrastructure — electricity, hydrogen and heat coupled in one optimisation, not three separate models bolted together. It carries the same discipline as the rest of the portfolio into a harder setting: real fetched data (not just synthetic examples), independent verification on every run, and honest reporting of infeasible/incomplete results (the wind-only annual case, the 16% unmet demand) rather than only showing the runs that came out clean.
Software verification
Hosted CI and parallel execution
Nine OS/Python cells and the Linux MPI equivalence job passed. The implementation distributes independent synthetic demand/price sensitivities across ranks and reproduces standalone results, including infeasible overload cases. This is scenario-level parallelism, not a parallel PDE solver.
The feature has been merged into the default branch. These software checks do not establish independent scientific validation.