Modular Expansion¶
In this example, we demonstrate how to handle the modular expansion feature in PyPSA. Modular expansion allows you to specify discrete steps for the expansion of components. This is particularly useful for technologies that can only be built in fixed block sizes, such as nuclear generators.
We start by loading the 3-hourly resolved model.energy example and adding a nuclear generator as expansion option (with ramp limits of 1%/h and a minimum part load of 70%). Initially, we allow the nuclear generator to be built in continuous sizes.
import pypsa
from pypsa.costs import annuity
n = pypsa.examples.model_energy()
n.add(
"Generator",
"nuclear",
bus="electricity",
p_nom_extendable=True,
marginal_cost=15,
capital_cost=annuity(0.07, 50) * 8_000_000,
p_min_pu=0.7,
ramp_limit_up=0.03,
ramp_limit_down=0.03,
)
n.optimize(solver_name="gurobi")
n.generators.p_nom_opt
As optimal capacity for the nuclear generator, we obtain 7,639 MW.
Now, let's assume that the nuclear generator can only be built in discrete steps of 1,000 MW. We can set the p_nom_mod attribute to 1,000 MW, which introduces an integer variable that constraints the optimised capacity to be a multiple of 1,000 MW. To constrain the option space for the integer variable, and help the solver a bit, we can also set the upper limit p_nom_max to 10,000 MW.
This problem is solved as a mixed-integer linear program (MILP), which will take longer to solve than the previous continuous linear program (LP).
n.generators.loc["nuclear", "p_nom_mod"] = 1000
n.generators.loc["nuclear", "p_nom_max"] = 10000
n.optimize(solver_name="gurobi")
n.generators.p_nom_opt
From the re-optimised results, we can see that the optimal capacity for the nuclear generator is now 8,000 MW, a multiple of 1,000 MW.
As a short concluding excursion into the results, we can also illustrate how the optimal dispatch of this nuclear generator is constrained by ramp limits and minimum part loads in January 2019.
n.generators_t.p.loc["2019-01"].plot(figsize=(6, 3))