Will coal and nuclear power survive the energy transition?

To curb carbon emissions, many European countries have rapidly increased renewable energy capacity in the electricity sector and put a price on carbon. This transition from fossil fuels to renewable energy is often referred to as “energy transition”. As energy transition advances, what will happen to conventional power sources such as nuclear and coal? To answer this question, let us turn to Denmark, which has the largest share of low-carbon energy (solar, wind, hydro, biomass, waste, geothermal) in the EU, reaching 77.3% of the total generation in 2019. (Data source: Our World in Data). Based on real-time data of Denmark’s electricity demand and supply and screening curve model, we will drive the optimal capacity mix for conventional power plants (coal, nuclear and gas).

Data and methodology

Screening Curve Model is used to derive the least-cost generation capacity in the long run. This model joins two graphs on the same x-axis: load duration curve and screening curves of conventional power plants. The load duration curve is the hourly electricity demand over the year, ordered by size. Screening curves show the change in annualized full costs of generating electricity with respect to the full load hours of the power plant. Open Electricity Economics provides an excellent explanation of this model.

To run this model, we will use data from the following sources:

  • Open Power System Data gives hour-by-hour electricity demand (load), total capacity, total generation, solar and wind generation and capacity in the EU.
  • International Energy Agency provides the estimates of the electricity generation cost for the year 2019 by technology in Europe.

For this exercise, we will assume that the discount rate is 5% and each power plant has a lifetime of 40 years.

raw <- read.csv("data/openpowersystemdata.csv")

# Subset the dataset for Denmark in 2019.
denmark <- raw %>% filter(str_detect(cet_cest_timestamp, "2019")) %>%  
         load = DK_load_actual_entsoe_transparency, # in MW
         solar_gen = DK_solar_generation_actual,
         wind_gen = DK_wind_generation_actual) 

Screening curves show the annualized full cost (discounted fixed cost per unit of capacity AND variable cost per unit of generation) of running the power plant with respect to the running hours of the power plant (=Full load hours).

The slope of a screening curve represents the variable cost of electricity generation ($/MWh). The y-intercepts represent the discounted fixed cost of electricity. We do not draw screening curves for solar and wind. The reason why is because we cannot determine when to turn on and off these power plants. Nature does. Besides, since the sun and wind do not cost any money, the slope of the screening curves would be 0. We will account for solar and wind energy production in the second piece, the load duration curve.

Each technology has different fixed and variable costs. Baseload plants such as coal and nuclear have high investment costs but low variable costs. Peaking plants such as gas have low investment costs but high variable costs. In other words, it is more economical to run baseload plants for longer hours. For fewer full load hours, it is cheaper to run peaking plants.

The following code produces the screening curves for nuclear, coal and gas based on the IEA data for the year 2019.

# IEA data on generation cost
Technology <- c("nuclear", "coal", "gas")
`Fixed Cost $/MW` <- c(4500000, 2000000, 1000000) # capital costs in $/MW
`Variable Cost $/MWh` <- c(35, 90, 85) # variable costs in $/MWh
screen_data <- tibble(Technology, `Fixed Cost $/MW`, `Variable Cost $/MWh`) %>%
  mutate(`Annualized Fixed Cost` = `Fixed Cost $/MW`*0.05*(1+0.05)^40/((1+0.05)^40-1)) # Calculate the discounted investment cost, assuming the discount rate of 5% and 40 years of lifetime 
screen_data %>% kable() %>% kable_styling()

# Construct equations for each line
nuke_eq <- function(x){as.numeric(screen_data[1,3])*x+as.numeric(screen_data[1,4])}
coal_eq <- function(x){as.numeric(screen_data[2,3])*x+as.numeric(screen_data[2,4])}
gas_eq <- function(x){as.numeric(screen_data[3,3])*x+as.numeric(screen_data[3,4])}

# Create a ggplot base
base <- ggplot() + xlim(0, 8760)
screen <- base + 
  geom_function(fun=nuke_eq, color="red") +
  geom_function(fun=coal_eq, color="black") +
  geom_function(fun=gas_eq, color="blue") +
       y="Full Cost (USD/MW)") + 
  theme_minimal() +
  geom_vline(xintercept=4079, linetype="dashed", color="grey")

(Blue: gas / Red: nuclear / Black: coal)

The screening curves of conventional power plants based on the IEA’s 2019 scenario show that it is most economical to run nuclear power plant if is run more than 4079 hours (that is the x value of the point where the gas curve meets the nuclear curve). For under 4079 hours, it is more economical to run gas power plant. Coal is NOT economical to run at any hour because its variable cost is higher than any other plants, probably because of higher carbon prices.

Load duration curve

The load duration curve plots the hourly demand for electricity over a year in descending order. To read this graph, we can pick a point on the x-axis and read the corresponding value on the y-axis as the demand until that hour. For example, for 4,000 hours of the year, the electricity demand exceeded 3,914.5 MW. We can see that the maximum energy demand in Denmark was 6,749.6 GW, while the minimum energy demand was 1,693 GW.

We can account for solar and wind generation by calculating the “residual load”. When renewable energies produce energy, it is dispatched before any other plant because their variable costs are (almost) 0. In other words, when solar and wind power plants are at work, conventional power plants only have to meet the demand remaining after subtracting solar and power generation. This remaining demand is called “residual load”. When renewable energy produces more energy than demand, the residual load will be negative (!). The graph below shows the difference in total and residual loads.

# Order the demand (load) in descending order 
denmark_load <- denmark %>% 

# Create a vector to name each hour of the year for the net load
hr <- 1:8760 
denmark_load <- cbind(hr, denmark_load)

# Calculate the residual load and arrange them 
denmark_residual <- denmark %>% 
  mutate(residual_load = load - solar_gen - wind_gen) %>%
denmark_residual <- cbind(hr, denmark_residual) # Add FLH

# Plot the load duration curve 
load_all <- ggplot() + 
  geom_line(data=denmark_load, aes(x=hr, y=load), color="purple") +
  geom_area(data=denmark_residual, aes(x=hr, y=residual_load), fill="yellow", alpha=0.5) +
       y="Hourly Demand (MW)") +

We can estimate the optimal capacity mix of conventional power plants by putting the graphs on the same x-axis. We draw a vertical line where the screening curves (top) meet through the load duration curve (bottom). We draw a horizontal line where the residual load curve meets the vertical line (x=4079). The y value of this intersection is the optimal capacity for the base load, in this case, nuclear. The difference between the maximum residual load and the nuclear power capacity is for the peak load, gas.

# Add color values for gas (blue) and nuclear (gas)
denmark_residual <- denmark_residual %>% mutate(color=ifelse(residual_load > 2188.55, "blue", "red"))

loadcurve <- ggplot() + 
  geom_area(data=denmark_residual, aes(x=hr, y=residual_load), fill="yellow", alpha=0.5) +
  labs(x="FLH", y="Hourly Demand (MW)") +
  theme_minimal()  +
  ylim(0, max(denmark_residual$residual_load)) +
  xlim(0, 8760) +
  geom_vline(xintercept=4079, linetype="dashed", color="grey") +
  geom_hline(yintercept=2188.55, linetype="dashed", color="grey") 

cowplot::plot_grid(screen, loadcurve, align = "v", ncol = 1, rel_heights = c(1, 1)) 

No coal, less nuclear, more gas power

When renewable energy share is close 80% as in the case of Denmark, the optimal capacity mix is 0GW of coal, 2.2GW of nuclear, and 3.2 GW of gas. Coal does NOT have a role to play here because its variable cost is high across all full load hours, mostly due to high social and environmental costs.

Note that the size of the baseload is smaller than peak load. As the renewable energy share increases, it is more economical to have more peak load plants that can be dispatched flexibly to match the changes in supply and demand.

technology <- c("Coal", "Nuclear", "Gas")
capacity <- c(0, 2200, 3200)
optimal <- tibble(technology, capacity)

ggplot(optimal, aes(x=technology, y=capacity, fill=technology)) +
  geom_bar(stat="identity") +
  scale_fill_manual(values=c("black", "blue", "r
This text will be hidden
ed")) +
  theme_minimal() +
  theme(axis.title.y = element_blank(),
        legend.position="none") +
  labs(title = "Optimal Electricity Generation Capacity when Renewable Share is 80%",
       x="Capaity (MW)") 

This post was originally published on my Blog, “Uni Lee’s Conscious Table”.