System analysis¶
OpenEnergy can also look at the whole GB system: how wind and solar reshape the demand left for other plant, when they produce more than the system can use, how much storage that calls for, and what storage does to emissions.
Net load¶
Net load is demand minus wind and solar output. Scaling wind and solar models more installed capacity with the same weather:
uv run openenergy netload data/time_series/time_series_60min_singleindex_filtered.csv
| Year | Renewable share | Min net load (GW) | 1 h ramp, p99 (GW) | Price per GW of net load |
|---|---|---|---|---|
| 2015 | 12.0% | 11.3 | 7.3 | £0.80/MWh |
| 2017 | 16.5% | 10.1 | 6.5 | £1.23/MWh |
| 2019 | 18.7% | 6.7 | 7.1 | £1.21/MWh |
| 2020 (to Sep) | 24.0% | 3.1 | 7.1 | £1.13/MWh |
Each extra GW of net load raised the GB day-ahead price by about £1/MWh. Ramps are 99th percentiles of absolute changes, so a single bad reading cannot set them.
Load data quality
ENTSO-E's GB actual load has telemetry glitches, such as hours at 511 MW against real
demand of 15 GW or more. load_system removes load readings that deviate from their
5-hour median by more than 25% of the 99th percentile, or fall below half their 7-day
median (133 of 50,394 hours), and reports the count. Wind and solar are unchanged.
The load is total demand, so subtracting solar does not double count it.
Surplus as renewables grow¶
Surplus is renewable output the system cannot use: how far net load falls below a
must-run floor \(F\) of inflexible generation, \(U_t = \max(0, F - N_t)\).
examples/renewables_scaling.py
scales 2019 wind and solar with an 8 GW floor:
| Scale | Renewable share | Min net load (GW) | 1 h ramp, p99 (GW) | Surplus (TWh) | Share of renewable output |
|---|---|---|---|---|---|
| ×1 | 18.7% | 6.7 | 7.0 | 0.00 | 0.0% |
| ×2 | 37.4% | −3.8 | 10.0 | 1.61 | 1.5% |
| ×3 | 56.1% | −20.2 | 14.1 | 14.39 | 8.6% |
| ×4 | 74.8% | −36.6 | 18.3 | 43.34 | 19.5% |
Surplus grows much faster than capacity: doubling from ×2 to ×4 multiplies it by 27, and ramps steepen as renewables set more of the shape of net load.
Storage to absorb surplus¶
A storage fleet charges from surplus and releases as soon as net load is back above the floor, never creating new surplus:
uv run openenergy storage data/time_series/time_series_60min_singleindex_filtered.csv \
--scale-wind 3 --scale-solar 3 --must-run 8000 --power 2000,5000,10000 --hours 4,8,24
For 2019 with renewables ×3:
| Power | 4 h | 8 h | 24 h |
|---|---|---|---|
| 2 GW | 12.6% | 17.5% | 23.1% |
| 5 GW | 25.7% | 35.7% | 48.0% |
| 10 GW | 39.8% | 54.4% | 73.2% |
Returns diminish with both power and duration, and duration matters: much of the surplus arrives in multi-day windy spells that short-duration storage cannot hold.
Sizing a site¶
openenergy sweep runs any scenario over a grid of settings:
uv run openenergy sweep examples/gb-2019-naive.yaml \
--set battery.energy_mwh=1,2,4 --set dispatch.degradation_cost=0,5,15
For a 1 MW battery in GB 2019 without a degradation cost, revenue per MW-year rises from £8.5k (1 h) to £15.8k (2 h) and £24.3k (4 h), so each extra hour earns less. A £15/MWh degradation cost exceeds most daily spreads and all but stops arbitrage.
Emissions¶
With carbon intensity \(e_t\) in gCO2/kWh, a battery or site's net emissions are \(-\sum_t e_t\, x_t\, \Delta t / 1000\) tCO2 for net export \(x_t\): exports displace grid generation and imports add to it. Negative means net emissions avoided.
carbon.price_per_t steers dispatch by adding \(\lambda \hat e_t / 1000\) to the planning
price. For the GB 2019 battery (examples/gb-2019-carbon.yaml at £50/t):
| Carbon price | Revenue | Net emissions |
|---|---|---|
| £0/t | £13,444 | −19.4 tCO2 |
| £50/t | £13,293 | −21.2 tCO2 |
| £200/t | £12,619 | −23.3 tCO2 |
Even without a carbon price, price arbitrage avoids emissions, because GB prices and carbon intensity move together (correlation 0.62). Each further tonne costs more: about £84 per tonne at £50/t and £211 at £200/t.
Average, not marginal
These use NESO's average grid intensity. Storage changes the marginal plant, whose emissions can differ, so results show direction and rough scale, not causal impact.
Why not NESO's forecast?
NESO's archived forecast is the last forecast issued before each half-hour, not the
one available the day before. Planning on it at noon the day before raised the
battery's revenue as the carbon price rose, which only leaked information can do. By
default dispatch plans on the same hour's actual intensity a week earlier, cut off at
the decision time like prices; planning: actual is an explicit upper bound.