Nuclear
This example shows how greater flexibility in refuelling schedules can reduce the backup capacity required by the system. Three makenuclear units and a CCGT backup make that concrete. Unlike gas plants, nuclear units need periodic refuelling outages (and the maintenance that usually accompanies them). Each outage lasts many consecutive hours, often weeks in reality. With several units, when those windows sit relative to each other and to the demand peak changes how much dispatchable backup the system needs.
Other technologies also have planned outages, but usually for different reasons. Coal and CCGT plants schedule maintenance. They do not stop for weeks because they need fresh fuel. Wind, solar, and hydro have no refuelling concept at all. Posy2 therefore gives nuclear dedicated refuelling parameters that model this outage, rather than a generic maintenance switch shared by every generator.
Refuelling is optional and off by default. This example turns it on. Once it is on, the yearly outage is fixed in length and must be taken. The optimiser only chooses when each unit refuels within the allowed starts. The example setup is:
- three 1 000 MW nuclear units
- hourly
country1demand from the time-series workbook, scaled withprofile_multiplier=5.5 - expandable CCGT backup
- a 720-hour refuelling outage per unit once refuelling is enabled
Scaling keeps the workbook demand shape while raising its magnitude, so the nuclear fleet covers only part of the peak and refuelling timing still changes how much CCGT is built.
This page runs two related studies so that the capacity difference is easy to read:
- forced overlap:
refuel_slot_spacing=8760leaves only one allowed start, so all three units refuel together from 1 February. Demand in that February–March outage window reaches about 5.64 GW, and the model builds about 5.64 GW of CCGT. - flexible schedule:
refuel_slot_spacing=730opens about twelve monthly starts, so the optimiser can keep the full fleet online at the peak and limit how much the outages overlap. It builds about 2.78 GW of CCGT.
Forced overlap
With only one allowed refuelling start in the year (hour 1, 1 February after the demand shift), every unit must take that slot, so the three 1 000 MW outages coincide. The 720-hour window runs through early March; demand there reaches about 5.64 GW. Nuclear output drops to 0 MW there, and CCGT must cover demand in that window.
using Posy2
using Nosy
using HiGHS
import JuMP: set_silent
# Simulation and Posy2 input configuration
sim = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
set_silent(model(sim))
example_data_dir = joinpath(pkgdir(Posy2), "data")
snapshot = Snapshot(sim, Dict(:posy => Posy2Options(
data_dir=example_data_dir,
techdata_file="tech_data.xlsx",
timeseries_file="time_series.xlsx",
tech_mode=:excel,
timeseries_mode=:excel,
discount_rate=0.05,
)))
# Electricity node and CO2 sink
electricity = Node("country1", EnergyCarrier("electricity country1", sim), rule=:curtailed, tags=[:electricity])
co2 = Node("CO2", CO2Carrier("CO2", sim), rule=:curtailed, tags=[:co2])
# Workbook demand shape, scaled up and shifted so hour 1 is 1 February
makedemand("Demand", "country1", electricity, snapshot; profile_multiplier=5.5, profile_shift_hours=-744)
# Three fixed 1 GW units with refuelling on (720 h each). Only the start hour is chosen.
makenuclear(
"NucA", electricity, snapshot; co2_node=co2, tech_column="Nuclear",
cap=1_000.0,
unit_size=1_000.0,
uc=true,
integer_uc=true,
refuel_fraction_per_year=1.0, # >=1 refuelling outage per unit per year
refuel_duration=720.0, # fixed outage length (~30 days), not shortened by the optimiser
refuel_slot_spacing=8760, # only one allowed start (1 February), so all three coincide
)
makenuclear(
"NucB", electricity, snapshot; co2_node=co2, tech_column="Nuclear",
cap=1_000.0,
unit_size=1_000.0,
uc=true,
integer_uc=true,
refuel_fraction_per_year=1.0,
refuel_duration=720.0,
refuel_slot_spacing=8760,
)
makenuclear(
"NucC", electricity, snapshot; co2_node=co2, tech_column="Nuclear",
cap=1_000.0,
unit_size=1_000.0,
uc=true,
integer_uc=true,
refuel_fraction_per_year=1.0,
refuel_duration=720.0,
refuel_slot_spacing=8760,
)
# Expandable CCGT backup for residual demand
makedispatchable(
"CCGT", electricity, snapshot; co2_node=co2, tech_column="CCGT",
maxcap=8_000.0,
unit_size=0.0,
)
# Minimise total system cost and extract solved values
optimize!(snapshot, cost(snapshot))
result_overlap = extract(snapshot)julia> capacity(result_overlap, "CCGT country1")
5644.594999999999CCGT capacity matches demand in that February–March window: with all three nuclear units offline, backup must cover load in that window. The figure shows the same story in time. Unit outage bars sit on top. Nuclear and CCGT stack against hourly demand underneath. The three grey refuelling blocks coincide in February–March, and CCGT fills the high demand inside that window.
Flexible schedule
Keep the same plants, demand, and costs, but allow about one refuelling start per month (roughly twelve options over the year).
using Posy2
using Nosy
using HiGHS
import JuMP: set_silent
# Simulation and Posy2 input configuration
sim = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
set_silent(model(sim))
example_data_dir = joinpath(pkgdir(Posy2), "data")
snapshot = Snapshot(sim, Dict(:posy => Posy2Options(
data_dir=example_data_dir,
techdata_file="tech_data.xlsx",
timeseries_file="time_series.xlsx",
tech_mode=:excel,
timeseries_mode=:excel,
discount_rate=0.05,
)))
# Electricity node and CO2 sink
electricity = Node("country1", EnergyCarrier("electricity country1", sim), rule=:curtailed, tags=[:electricity])
co2 = Node("CO2", CO2Carrier("CO2", sim), rule=:curtailed, tags=[:co2])
# Same scaled workbook demand as in the forced case
makedemand("Demand", "country1", electricity, snapshot; profile_multiplier=5.5, profile_shift_hours=-744)
# Same three units and 720 h refuelling outage. Only the allowed starts change.
makenuclear(
"NucA", electricity, snapshot; co2_node=co2, tech_column="Nuclear",
cap=1_000.0,
unit_size=1_000.0,
uc=true,
integer_uc=true,
refuel_fraction_per_year=1.0, # >=1 refuelling outage per unit per year
refuel_duration=720.0, # same fixed outage length as above
refuel_slot_spacing=730, # ~12 allowed starts per year (~monthly)
)
makenuclear(
"NucB", electricity, snapshot; co2_node=co2, tech_column="Nuclear",
cap=1_000.0,
unit_size=1_000.0,
uc=true,
integer_uc=true,
refuel_fraction_per_year=1.0,
refuel_duration=720.0,
refuel_slot_spacing=730,
)
makenuclear(
"NucC", electricity, snapshot; co2_node=co2, tech_column="Nuclear",
cap=1_000.0,
unit_size=1_000.0,
uc=true,
integer_uc=true,
refuel_fraction_per_year=1.0,
refuel_duration=720.0,
refuel_slot_spacing=730,
)
# Same expandable CCGT backup
makedispatchable(
"CCGT", electricity, snapshot; co2_node=co2, tech_column="CCGT",
maxcap=8_000.0,
unit_size=0.0,
)
# Minimise total system cost and extract solved values
optimize!(snapshot, cost(snapshot))
result_nonoverlap = extract(snapshot)julia> capacity(result_nonoverlap, "CCGT country1")
2777.8640000000005The refuelling duration remains fixed at 720 h for every unit. The scheduling flexibility comes only from having more possible start times. The optimiser uses that choice to keep the full nuclear fleet available at the annual demand peak and to avoid a simultaneous outage of all three units. Required CCGT capacity therefore falls to about 2.78 GW. The figure shows how the three refuelling periods are distributed over the year and how CCGT fills the remaining demand.
Forced refuelling timing needs about 5.64 GW of CCGT, compared with about 2.78 GW under a flexible schedule. Both cases impose the same 720-hour refuelling requirement on every nuclear unit. The difference comes only from when those outages occur. By choosing their timing, the optimiser reduces the coincidence of nuclear outages with each other and with high-demand periods, lowering the backup capacity the system needs to build. The actual scheduling pattern emerges from cost minimisation.