Code
Generator | Fixed Cost ($) | Variable Cost ($/MW) |
---|---|---|
Geothermal | 450000 | 0 |
Coal | 220000 | 21 |
NG CCGT | 82000 | 25 |
NG CT | 65000 | 35 |
Lecture 14
October 21, 2024
Text: VSRIKRISH to 22333
Capacity expansion involves adding resources to generate or transmit electricity to meet anticipated demand (load) in the future.
Typical objective: Minimize cost
But other constraints may apply, e.g. reducing CO2 emissions or increasing fuel diversity.
In general, we have many fuel options:
Generator | Fixed Cost ($) | Variable Cost ($/MW) |
---|---|---|
Geothermal | 450000 | 0 |
Coal | 220000 | 21 |
NG CCGT | 82000 | 25 |
NG CT | 65000 | 35 |
NY_demand = DataFrame(CSV.File("data/capacity_expansion/2020_hourly_load_NY.csv"))
rename!(NY_demand, :"Time Stamp" => :Date)
demand = NY_demand[:, [:Date, :C]]
rename!(demand, :C => :Demand)
@df demand plot(:Date, :Demand, xlabel="Date", ylabel="Demand (MWh)", label=:false, xrot=45, bottom_margin=15mm)
plot!(size=(1200, 500))
The chronological demand curve makes it hard to understand what levels of load occur with lower or greater frequency.
Instead, we can sort demand from high to low, which creates a load duration curve.
We want to find the installed capacity of each technology that meets demand at all hours at minimal total cost.
Although…
What are our variables?
Variable | Meaning |
---|---|
\(x_g\) | installed capacity (MW) from each generator type \(g \in \mathcal{G}\) |
\(y_{g,t}\) | production (MWh) from each generator type \(g\) in hour \(t \in \mathcal{T}\) |
\(NSE_t\) | non-served energy (MWh) in hour \(t \in \mathcal{T}\) |
\[ \begin{align} \min_{x, y, NSE} Z &= {\color{red}\text{investment cost} } + {\color{blue}\text{operating cost} } \\ &= {\color{red} \sum_{g \in \mathcal{G}} \times \text{FixedCost}_g x_g} + \\ & \qquad{\color{blue} \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} \text{VarCost}_g \times y_{g,t} + \sum_{t \in \mathcal{T}} \text{NSECost} \times NSE_t} \end{align} \]
\[ \begin{align} \min_{x, y, NSE} \quad & \sum_{g \in \mathcal{G}} \text{FixedCost}_g \times x_g + \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} \text{VarCost}_g \times y_{g,t} & \\ & \quad + \sum_{t \in \mathcal{T}} \text{NSECost} \times NSE_t & \\[0.5em] \text {subject to:} \quad & \sum_{g \in \mathcal{G}} y_{g,t} + NSE_t \geq d_t \qquad \forall t \in \mathcal{T} \\[0.5em] & y_{g,t} \leq x_g \qquad \qquad \qquad\qquad \forall g \in {G}, \forall t \in \mathcal{T} \\[0.5em] & x_g, y_{g,t}, NSE_t \geq 0 \qquad \qquad \forall g \in {G}, \forall t \in \mathcal{T} \end{align} \]
Real problems can get much more complex, particularly if we try to model making decisions under renewable or load uncertainty.
# define sets
G = 1:nrow(gens[1:end-2, :])
T = 1:nrow(demand)
NSECost = 9000
gencap = Model(HiGHS.Optimizer)
# define variables
@variables(gencap, begin
x[g in G] >= 0
y[g in G, t in T] >= 0
NSE[t in T] >= 0
end)
@objective(gencap, Min,
sum(gens[G, :FixedCost] .* x) + sum(gens[G, :VarCost] .* sum(y[:, t] for t in T)) + NSECost * sum(NSE)
)
@constraint(gencap, load[t in T], sum(y[:, t]) + NSE[t] >= demand.Demand[t])
@constraint(gencap, availability[g in G, t in T], y[g, t] <= x[g])
optimize!(gencap)
Resource | Installed (MW) | Percent (%) | Generation (GWh) | Percent (%) |
---|---|---|---|---|
Geothermal | -0.0 | -0.0 | 0.0 | 0.0 |
Coal | 0.0 | 0.0 | -0.0 | -0.0 |
NG CCGT | 2016.9 | 72.5 | 15157.7 | 98.1 |
NG CT | 733.0 | 26.4 | 292.2 | 1.9 |
NSE | 31.2 | 1.1 | 0.1 | 0.0 |
\(\text{NSE}\) is non-zero for 7 hours and a total of 94 MWh.
Why do we think there is any NSE given the high NSE Cost?
Renewables make this problem more complicated because their capacity is variable:
How would this change our existing capacity expansion formulation?
This will change the capacity constraint from \[y_{g,t} \leq x_g \qquad \forall g \in {G}, \forall t \in \mathcal{T}\] to \[y_{g,t} \leq x_g \times c_{g,t} \qquad \forall g \in {G}, \forall t \in \mathcal{T}\] where \(c_{g,t}\) is the capacity factor in time period \(t\) for generator type \(g\).
I recommend using vector notation in JuMP
to specify these constraints, e.g. for capacity:
# define sets
G = 1:num_gen # num_gen is the number of generators
T = 1:num_times # number of time periods
c = ... # G x T matrix of capacity factors
@variable(..., x[g in G] >= 0) # installed capacity
@variable(..., y[g in G, t in T] >= 0) # generated power
@constraint(..., capacity[g in G, t in T],
y[g,t] <= x[g] * c[g,t]) # capacity constraint
Wednesday: Economic Dispatch
Next Week: Managing Air Pollution