Overview

One page, six solved modules, no network calls.
Energy Transition Model · v1.0.0

A whole energy-system model in one offline page

Every number below was produced by solving a PyPSA/HiGHS linear program in this repository — capacity expansion, multi-duration storage, CO₂ cap-and-trade duality, ramp/reserve adequacy, meshed DC-OPF and a mean-CVaR stochastic program. Move a control and the page re-reads the solved data; nothing is typed by hand and nothing is fetched from the internet.

System LCOE · 7% WACC—reference case, VRE CapEx ×1.00
VRE share of energy—solar + wind dispatch
Net-zero carbon price—shadow price at a 100% cap
Surrogate accuracy—16 unseen nodes

Cost surface: discount rate × VRE CapEx

Exact LP solves on a 5×5 grid. Click a cell to load that case into the dispatch view.

Module map

Each card is a solved model; select one to explore.

Cross-module invariants

Residuals recomputed from the solved primal/dual pairs at build time; tolerance is the numerical acceptance band used by the test suite.

ModuleIdentityResidualToleranceStatus
01 · Modules 1–2 · capacity expansion & nodal duality

Dispatch, cost stack and the price of capital

A representative day is solved to least cost with extendable solar, wind and gas. The KKT dual of the power balance is the nodal price; its load-weighted mean equals the system LCOE to machine precision. Drag the financial levers to watch the portfolio re-optimise.

Hourly dispatch —

Controls

7.0%
Grid nodes at 1%, 4%, 7%, 11% and 15%. Between nodes the surface is bilinearly interpolated.
×1.00
Scales annualised solar and wind CapEx only; gas costs are untouched.
LCOE—
VRE share—
Peak LMP—

Cost stack across the surface

Annualised capital versus fuel and variable O&M at the current case.

Hourly prices

Nodal shadow price λt; scarcity hours price gas at its marginal cost plus capacity scarcity.

02 · Module 2 · chronological multi-duration storage

Batteries, hydrogen and the length of a dark lull

A 168-hour week is solved with a 4-hour Li-ion battery (95% one-way efficiency, cyclic SoC) and a decoupled power-to-hydrogen chain (68% electrolyser, 50% fuel cell, cavern store). The crossover between power-dense and energy-dense storage is a function of how long the wind and sun disappear.

LCOE versus lull duration

BESS onlyH₂ onlyHybrid (lower envelope)

Dunkelflaute length

48 h
Sweep nodes only — the model solves integer lull durations.
—

Adequacy regimes (168 h, annualised)

RegimeSolar MWWind MWBESS MWhH₂ MWheLCOE

Chronological dispatch in the lull

Hybrid portfolio over 168 hours; hover to read hour-level detail.

GasSolarWindBESS dischargeH₂ turbine
03 · Module 3 · cap-and-trade duality & MACC

What a tonne of CO₂ costs when the cap binds

Relaxing the annual CO₂ cap with its KKT multiplier reproduces the Pigouvian carbon tax exactly: at the dual price the tax optimum equals the cap optimum plus the value of the allowance. The marginal abatement cost curve is the derivative of the frontier, and its right-hand limit at zero emissions is the minimum net-zero price.

Pareto frontier: emissions versus resource cost

Solid: resource LCOE (left axis). Dashed: the dual CO₂ shadow price (right axis) as the cap tightens.

Resource LCOEShadow carbon price μ*

Abatement target

96%
Sweep nodes span 0–100% abatement in 12 steps.
Shadow price—
Residual CO₂—
LCOE—
—

Marginal abatement cost curve

Step marginal cost of each abatement increment, and the average cost of the whole programme.

Fuel-switching stages

Installed capacity by carrier at the selected cap.

04 · Module 4 · ramping, must-run floors & spinning reserves

The duck curve is a flexibility problem

Intertemporal ramp envelopes, minimum stable generation and co-optimised upward reserves are added to the capacity problem. Reserve co-optimisation is paid for in the energy market, and a binding downward ramp can push a nodal price below zero even when every fuel cost is non-negative.

Net load and price shape (48 h)

Regime metrics

—

Reserve requirement vs. price

Reserve requirement αload·Lt + αvre·VREt, and the reserve shadow price.

Regime comparison

RegimeLCOEPeak LMPReserve priceBESS MWh
05 · Module 5 · 4-zone meshed DC-OPF & AC-TEP

Loop flows, congestion rent and Braess's paradox

Four zones, five 380 kV corridors and two independent Kirchhoff voltage loops. Passive flows follow the PTDF matrix, zonal prices decompose into a reference price minus congestion terms, and the ISO merchandising surplus recovers exactly the cost of the grid it co-optimises.

Network state hour 0

A zero nodal price means the marginal unit at that zone is non-binding (curtailed VRE sets the price). Drag the snapshot slider to watch congestion appear.

00:00

Regime

×1.00
Fixed-grid sweep: 0.5×, 0.75×, 1×, 1.5×, 2.5× of 400 MW total.
—

Congestion rent versus corridor capacity

PTDF matrix

Sensitivity of each corridor flow to a 1 MW injection at a zone (withdrawal at the East Metro slack).

Corridor
07 · Module 7 · explicit mean-CVaR stochastic program

Hedging the tail instead of the average

Eight weather-and-demand regimes are solved jointly. The objective is (1−β)·E[Z] + β·CVaR95%(Z) through its Rockafellar–Uryasev epigraph, in one 1,357-variable LP with explicit sparse matrices. The dual risk kernel q* shows exactly which futures the hedge is buying, and where it hits the π/(1−α) bound.

Efficient frontier: expected cost versus tail cost

Mean–CVaR efficient setSelected β

Risk aversion β

0.35
LCOE—
CVaR95—
EENS—

Dual risk kernel q*

Bars: optimal scenario weights q* at the selected β (left axis). Circles: the prior π. Dashed ticks: the theoretical ceiling π/(1−α) that q* reaches at β = 1 (right axis).

q* at prior levelq* bidding above priorprior πceiling π/(1−α)

Stochastic bounds

Wait-and-see ≤ recourse ≤ expected-value plan evaluated across futures.

—
99 · verification, provenance & limits

How to reproduce every pixel

The page is a build artifact: workbench/data/etm_workbench_data.json holds solved values, and energy_model/workbench.py renders them into this single HTML file. A CI job re-renders the committed page from the committed data and fails on any drift, so the figure you are reading cannot silently diverge from the repository.

Surrogate validation

Exact LP solves at 16 node positions never used for interpolation.

Holdout solvesy = x

Build provenance

Reproduce locally

python -m venv .venv
.venv/bin/pip install --no-build-isolation -e ".[dev]"
.venv/bin/etm-workbench build    # re-solve + re-render
.venv/bin/etm-workbench render   # template only
.venv/bin/etm-workbench check    # verify no drift

Test inventory

Test moduleTests

Scope & honest limits

  1. Inputs are deterministic synthetic profiles and illustrative annualised costs in an unspecified currency; they are not metered SCADA data or investment quotes.
  2. Module 7's CVaR panel is an independent explicit-LP re-derivation on its own declared 8-regime set. It is not a numerical replica of the Module 6 run, which remains the reference implementation and CLI.
  3. The cost surface is exactly solved at 25 nodes and bilinearly interpolated between them; the reported holdout error bounds the interpolation, it does not remove it.
  4. Single-node copper-plate dispatch in M1–M4 and lossless DC power flow in M5; no AC power flow, unit commitment or integer investment.

Licence MIT · cite via CITATION.cff. Built as the practical implementation of the Politecnico di Milano EQF-7 microcredential “Modelling the Energy Transition: the revolution of open-source software”.