class: center, middle .title[LP Examples: Generating Capacity Expansion]
.subtitle[BEE 4750/5750]
.subtitle[Environmental Systems Analysis, Fall 2022]
.author[Vivek Srikrishnan]
.date[October 5, 2022] --- name: toc class: left # Outline
1. Questions? 2. Using JuMP --- name: poll-answer layout: true class: left # Poll
.left-column[{{content}} URL: [https://pollev.com/vsrikrish](https://pollev.com/vsrikrish) Text: **VSRIKRISH** to 22333, then message] .right-column[.center[]] --- name: questions template: poll-answer ***Any questions?*** --- layout: false # Last Class
* Coding Exercise with JuMP --- class: left # Overview of Electric Power Systems
.center[] .center[.cite[Source: [Wikipedia](https://en.wikipedia.org/wiki/Electric_power_transmission)]] --- class: left # Power Systems Decision Problems
.center[] .center[.cite[Adapted from Perez-Arriaga, Ignacio J., Hugh Rudnick, and Michel Rivier (2009).]] --- class: left # Electricity Generation by Source
.center[] --- class: left # Capacity Expansion
*Capacity expansion* involves adding resources to generate or transmit electricity to meet anticipated demand (load) in the future. Typical objective is to minimize cost, but other constraints (such as reducing CO$_2$ emissions or increasing fuel diversity) may apply. --- class: left # Simple Capacity Expansion Example
Let's look at a *very* simplified example first. --- # Simple Capacity Expansion Example
In general, we have many possible fuel options: * Gas (combined cycle or simple cycle) * Coal * Nuclear * Renewables (wind, solar, hydro) Each of these different types of plants has a different *capacity factor*, which reflects how much of the installed capacity can be relied upon at various times. --- class: left # Simple Capacity Expansion Example
Assume we can simplify demand to two scenarios: * On-peak: 3,760 hours with 3,000 MW of demand * Off-peak: 5,000 hours with 2,000 MW of demand Generator types (only three): | Tech | Peak CF | Off-Peak CF | Investment Cost $(\\$\text{/MW-yr})$ | Operating Cost $(\\$\text{/MWh})$ | |:-----:| -------:| -----------:| ------------------------------------:| ---------------------------------:| | Gas | 0.9 | 0.9 | 100,000 | 50 | | Wind | 0.2 | 0.5 | 150,000 | 0 | | Solar | 0.7 | 0 | 150,000 | 0 | --- class: left # Simple Capacity Expansion Example
**Goal**: Find an installed capacity of each technology that meets demand at all periods at minimal *total cost*. What are our variables? -- | Variable | Meaning | |:---------:|:----------------------------------------------------- | | $x_g$ | installed capacity (MW) of generator type $g$ | | $y_{g,t}$ | production (MW) from generator type $g$ in period $t$ | --- class: left # Simple Capacity Expansion Example
The objective function (to minimize cost): $$ \begin{aligned} \min\_{x\_g, y\_{g,t}} Z &= \color{red}\text{investment cost} + \color{blue}\text{operating cost} \\\\ &= \color{red}100000 x\_\text{gas} + 150000 x\_\text{wind} + 150000 x\_\text{solar} \\\\ & \color{blue}\quad + (3760 \times 50) y\_\text{gas,peak} + (5000 \times 50) y\_\text{gas,off} \end{aligned} $$ --- class: left # Simple Capacity Expansion Example
More generally (as the problem size gets larger), we want to write this more compactly. If $C^{INV}_g$ is the investment cost for generator $g$, $C^{OP}_g$ is the operating cost for generator $g$, and $L_t$ is the length of time period $t$: $$ \min Z = \sum\_g C^\text{INV}\_g x\_g + \sum\_g \sum\_t L\_t C^\text{OP}\_g y\_{g,t} $$ --- # Simple Capacity Expansion Example
What are the constraints? -- * Generators cannot produce more than their installed capacity and availability, *e.g.* $$ y\_\text{wind, peak} \leq 0.2 x\_\text{wind} $$ * Need to serve load in both peak and off-peak hours: $$ \begin{aligned} y\_\text{gas, peak} + y\_\text{wind, peak} + y\_\text{solar, peak} &= 3000 \\\\ y\_\text{gas, off} + y\_\text{wind, off} + y\_\text{solar, off} &= 2000 \end{aligned} $$ --- # Simple Capacity Expansion Example
And don't forget non-negativity! $$ \begin{aligned} x\_g &\geq 0 \\\\ y\_{g,t} &\geq 0 \end{aligned} $$ --- # Simple Capacity Expansion Example
Notice that this is an LP! * Linear (costs scale linearly); * Divisible (since we're modeling installed capacity, not number of individual units); * Certainty (no uncertainty about renewables) Things can get more complex for real decisions, particularly with renewables... --- # Simple Capacity Expansion Example
```julia using JuMP using HiGHS gencap = Model(HiGHS.Optimizer) generators = ["gas", "wind", "solar"] periods = ["peak", "off"] G = 1:length(generators) T = 1:length(periods) @variable(gencap, x[G] >= 0) @variable(gencap, y[G, T] >= 0) @objective(gencap, Min, [100000; 150000; 150000]' * x + sum([188000 0 0; 250000 0 0]' .* y)) ``` ``` 100000 x[1] + 150000 x[2] + 150000 x[3] + 188000 y[1,1] + 250000 y[1,2] ``` --- # Simple Capacity Expansion Example
```julia avail = [0.9 0.2 0.5; 0.9 0.5 0]' # availability factors @constraint(gencap, availability[g in G, t in T], y[g, t] <= avail[g, t] * x[g] ) ``` ``` 2-dimensional DenseAxisArray{JuMP.ConstraintRef{JuMP.Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, JuMP.ScalarShape},2,...} with index sets: Dimension 1, 1:3 Dimension 2, 1:2 And data, a 3×2 Matrix{JuMP.ConstraintRef{JuMP.Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, JuMP.ScalarShape}}: availability[1,1] : -0.9 x[1] + y[1,1] ≤ 0.0 … availability[1,2] : -0.9 x[1] + y[1,2] ≤ 0.0 availability[2,1] : -0.2 x[2] + y[2,1] ≤ 0.0 availability[2,2] : -0.5 x[2] + y[2,2] ≤ 0.0 availability[3,1] : -0.5 x[3] + y[3,1] ≤ 0.0 availability[3,2] : y[3,2] ≤ 0.0 ``` --- # Simple Capacity Expansion Example
```julia demand = [3000; 2000] # load @constraint(gencap, load[t in T], sum(y[:, t]) == demand[t]) ``` ``` 1-dimensional DenseAxisArray{JuMP.ConstraintRef{JuMP.Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, JuMP.ScalarShape},1,...} with index sets: Dimension 1, 1:2 And data, a 2-element Vector{JuMP.ConstraintRef{JuMP.Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, JuMP.ScalarShape}}: load[1] : y[1,1] + y[2,1] + y[3,1] = 3000.0 load[2] : y[1,2] + y[2,2] + y[3,2] = 2000.0 ``` --- # Simple Capacity Expansion Example
```julia print(gencap) ``` ``` Min 100000 x[1] + 150000 x[2] + 150000 x[3] + 188000 y[1,1] + 250000 y[1,2] Subject to load[1] : y[1,1] + y[2,1] + y[3,1] = 3000.0 load[2] : y[1,2] + y[2,2] + y[3,2] = 2000.0 availability[1,1] : -0.9 x[1] + y[1,1] ≤ 0.0 availability[2,1] : -0.2 x[2] + y[2,1] ≤ 0.0 availability[3,1] : -0.5 x[3] + y[3,1] ≤ 0.0 availability[1,2] : -0.9 x[1] + y[1,2] ≤ 0.0 availability[2,2] : -0.5 x[2] + y[2,2] ≤ 0.0 availability[3,2] : y[3,2] ≤ 0.0 x[1] ≥ 0.0 x[2] ≥ 0.0 x[3] ≥ 0.0 y[1,1] ≥ 0.0 y[2,1] ≥ 0.0 y[3,1] ≥ 0.0 y[1,2] ≥ 0.0 y[2,2] ≥ 0.0 y[3,2] ≥ 0.0 ``` --- # Simple Capacity Expansion Example
```julia optimize!(gencap) objective_value(gencap) ``` ``` 1.2580444444444444e9 ``` --- # Simple Capacity Expansion Example
```julia value.(x) ``` ``` 1-dimensional DenseAxisArray{Float64,1,...} with index sets: Dimension 1, 1:3 And data, a 3-element Vector{Float64}: 2444.4444444444443 4000.0 -0.0 ``` --- # Simple Capacity Expansion Example
```julia value.(y) ``` ``` 2-dimensional DenseAxisArray{Float64,2,...} with index sets: Dimension 1, 1:3 Dimension 2, 1:2 And data, a 3×2 Matrix{Float64}: 2200.0 0.0 800.0 2000.0 0.0 -0.0 ``` --- # Simple Capacity Expansion Example
In summary: * We want to install 2444.4 MW of gas, 4000 MW of wind, and no solar. * During peak hours, we rely on all of the gas and wind we can. * Off-peak, we rely on only wind. **Does this solution make sense**? --- # Simple Capacity Expansion Example
How can we display these results more easily? Use `DataFrames.jl`: ```julia using DataFrames # compute total generation by generation resource period_hours = [3760; 5000] generation = [sum(value.(y).data[g, :] .* period_hours) for g in G] results = DataFrame( "Resource" => generators, "Installed (MW)" => value.(x).data, "Generated (GWh)" => generation/1000 ) ``` --- # Simple Capacity Expansion Example
3×3 DataFrame
Row
Resource
Installed (MW)
Generated (GWh)
String
Float64
Float64
1
gas
2444.44
8272.0
2
wind
4000.0
13008.0
3
solar
-0.0
0.0
--- # Simple Capacity Expansion Example
```julia shadow_price.(load) ``` ``` 1-dimensional DenseAxisArray{Float64,1,...} with index sets: Dimension 1, 1:2 And data, a 2-element Vector{Float64}: -299111.1111111111 -180355.55555555556 ``` --- # Simple Capacity Expansion Example
```julia shadow_price.(availability) ``` ``` 2-dimensional DenseAxisArray{Float64,2,...} with index sets: Dimension 1, 1:3 Dimension 2, 1:2 And data, a 3×2 Matrix{Float64}: -1.11111e5 0.0 -2.99111e5 -1.80356e5 -300000.0 -1.80356e5 ``` --- # Capacity Expansion
**What modifications can we make to this setup?** --- template: poll-answer **What modifications can we make to our basic generation capacity example?** --- # Capacity Expansion
**What modifications can we make to this setup?** * More hours (not just peak/off-peak) * More generator types * Add in non-served demand (usually penalized by a cost in the objective) * Emissions (CO$_2$ or air pollutant) * Plant retirements (if we have existing plants) * Dynamic fuel prices --- # Emissions
We could add in emissions as a constraint or try to minimize emissions (instead of cost). **How would we do this?** -- * Would need information on emissions per generated Wh. --- # Non-Served Energy
It could be that it's cheaper to not serve energy during some periods than to build additional capacity. **How would we implement this?** -- * Add non-served energy penalty ($NSECost \times NSE_t$) to objective; * Generated electricity plus non-served energy equal demand. --- # Non-Served Energy
New objective: $$ \begin{aligned} \min\_{x\_g, y\_{g,t}, nse\_t} Z &= \text{investment cost} + \text{operating cost} + \text{NSE cost}\\\\ &= 100000 x\_\text{gas} + 150000 x\_\text{wind} + 150000 x\_\text{solar} \\\\ & \quad + (3760 \times 50) y\_\text{gas,peak} + (5000 \times 50) y\_\text{gas,off} \\\\ & \quad + NSECost \times nse\_t \end{aligned} $$ --- # Non-Served Energy
New load constraints: $$ \begin{aligned} y\_\text{gas, peak} + y\_\text{wind, peak} + y\_\text{solar, peak} + nse\_\text{peak}&= 3000 \\\\ y\_\text{gas, off} + y\_\text{wind, off} + y\_\text{solar, off} + nse\_\text{off} &= 2000 \end{aligned} $$ --- class: middle # Next Class
* Air Pollution Modeling Example or Economic Dispatch