Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat 195 cf lim #196

Merged
merged 6 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ makedocs(
"Arbitrary Temporal Adjustments" => "types/modifications/adjust.md",
"Endogenous Fuel Prices" => "types/modifications/fuel-price.md",
"Reserve Requirements" => "types/modifications/reserve-requirement.md",
"Annual Capacity Factor Limit" => "types/modifications/annual-cf-lim.md",
],
"Technologies"=>Any[
"CO₂ Capture, Utilization & Storage"=>"types/modifications/ccus.md",
Expand Down
7 changes: 7 additions & 0 deletions docs/src/types/modifications/annual-cf-lim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Annual Capacity Factor Limit
==================================

```@docs
AnnualCapacityFactorLimit
summarize_table(::Val{:annual_cf_lim})
```
1 change: 1 addition & 0 deletions src/E4ST.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ include("types/Retrofit.jl")
# Include Modifications
include("types/modifications/DCLine.jl")
include("types/modifications/AggregationTemplate.jl")
include("types/modifications/AnnualCapacityFactorLimit.jl")
include("types/modifications/GenerationConstraint.jl")
include("types/modifications/GenerationStandard.jl")
include("types/modifications/YearlyTable.jl")
Expand Down
107 changes: 107 additions & 0 deletions src/types/modifications/AnnualCapacityFactorLimit.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@

"""
struct AnnualCapacityFactorLimit <: Modification

AnnualCapacityFactorLimit(;name, file)

Sets annual capacity factor limits for generators. Annual capacity factor is defined as the total energy generated in a year divided by the total amount of energy capacity (power capacity times the number of hours in a year).

* `modify_raw_data!` - Loads in a table from `file`, stores it into `data[<name>]`. See summarize_table(::Val{:annual_cf_lim})
* `modify_model!` - sets up the following constraints and expressions
* Sets up expression `model[:egen_gen_annual]` (ngen x nhr) for annual energy generation for each generator
* Creates constraint `model[:cons_<name>_min]` for each generator covered by each row of the table specified in `file`, if the `annual_cf_min` column is given.
* Creates constraint `model[:cons_<name>_max]` for each generator covered by each row of the table specified in `file`, if the `annual_cf_max` column is given.
"""
Base.@kwdef struct AnnualCapacityFactorLimit <: Modification
name::Symbol
file::String
end
export AnnualCapacityFactorLimit

@doc """
summarize_table(::Val{:annual_cf_lim})

$(table2markdown(summarize_table(Val(:annual_cf_lim))))
"""
function summarize_table(::Val{:annual_cf_lim})
df = TableSummary()
push!(df,
(:genfuel, AbstractString, NA, false, "The fuel type that the generator uses. Leave blank to not filter by genfuel."),
(:gentype, String, NA, false, "The generation technology type that the generator uses. Leave blank to not filter by gentype."),
(:area, AbstractString, NA, false, "The area with which to filter by. I.e. \"state\". Leave blank to not filter by area."),
(:subarea, AbstractString, NA, false, "The subarea to include in the filter. I.e. \"maryland\". Leave blank to not filter by area."),
(:filter_, String, NA, false, "There can be multiple filter conditions - `filter1`, `filter2`, etc. It denotes a comparison used for selecting the table rows to apply the adjustment to. See `parse_comparison` for examples"),
(:status, Bool, NA, false, "Whether or not to use this adjustment"),
(:annual_cf_min, Float64, MWhGeneratedPerMWhCapacity, false, "The minimum annual capacity factor ∈ [0,1]"),
(:annual_cf_max, Float64, MWhGeneratedPerMWhCapacity, false, "The maximum annual capacity factor ∈ [0,1]"),
)
return df
end

function modify_raw_data!(m::AnnualCapacityFactorLimit, config, data)
file = m.file
name = m.name
table = read_table(data, file, :annual_cf_lim)
data[name] = table
end

function modify_model!(m::AnnualCapacityFactorLimit, config, data, model)
table = get_table(data, m.name)
gen = get_table(data, :gen)
pcap = model[:pcap_gen]::Array{VariableRef,2} # ngen x nyr
pgen = model[:pgen_gen]::Array{VariableRef,3} # ngen x nyr x nhr
nyr = get_num_years(data)
nhr = get_num_hours(data)
hour_weights = get_hour_weights(data)
hrs_per_yr = sum(hour_weights)

# Find gen indexes to apply the constraint to
gen_idx_sets = map(eachrow(table)) do row
get_row_idxs(gen, parse_comparisons(row))
end
table.gen_idx_sets = gen_idx_sets

if haskey(model, :egen_gen_annual)
egen_gen_annual = model[:egen_gen_annual]::Matrix{AffExpr}
else
@expression(
model,
egen_gen_annual[
gen_idx in axes(gen,1),
yr_idx in 1:nyr
],
sum(hr_idx -> (hour_weights[hr_idx] * pgen[gen_idx, yr_idx, hr_idx]), 1:nhr)
)
end


# Set the min annual capacity limit, if applicable.
if hasproperty(table, :annual_cf_min)
annual_cf_min = table.annual_cf_min::Vector{Float64}
model[Symbol("cons_$(m.name)_min")] = @constraint(
model,
[
row_idx in axes(table,1),
gen_idx in gen_idx_sets[row_idx],
yr_idx in 1:nyr;
annual_cf_min[row_idx] > 0 # Only set it if the min is > 0.
],
egen_gen_annual[gen_idx, yr_idx] >= pcap[gen_idx] * hrs_per_yr * annual_cf_min[row_idx]
)
end

# Set the max annual capacity limit, if applicable.
if hasproperty(table, :annual_cf_max)
annual_cf_max = table.annual_cf_max::Vector{Float64}
model[Symbol("cons_$(m.name)_max")] = @constraint(
model,
[
row_idx in axes(table,1),
gen_idx in gen_idx_sets[row_idx],
yr_idx in 1:nyr;
annual_cf_max[row_idx] < 1 # Only set it if the max is < 1.
],
egen_gen_annual[gen_idx, yr_idx] <= pcap[gen_idx] * hrs_per_yr * annual_cf_max[row_idx]
)
end
end
4 changes: 4 additions & 0 deletions test/config/config_3bus_cflim.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mods:
annual_cf_lim_ng:
type: AnnualCapacityFactorLimit
file: ../data/3bus/annual_cf_lim.csv
2 changes: 2 additions & 0 deletions test/data/3bus/annual_cf_lim.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
filter1,annual_cf_min,annual_cf_max
genfuel=>ng,0,0.9
18 changes: 18 additions & 0 deletions test/testoptimizemodel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,22 @@
@test compute_result(data, :interface_limit, :pflow_line_max, 1) <= compute_result(data, :interface_limit, :pflow_if_max, 1)
end

@testset "Test AnnualCapacityFactorLimit" begin
# Test that the average capacity factor is above 0.9
@test compute_result(data, :gen, :cf_avg, :genfuel=>"ng") > 0.9

# Run the model with the capacity factor limit
config_file_cflim = joinpath(@__DIR__, "config", "config_3bus_cflim.yml")
config = read_config(config_file, config_file_cflim)
data = read_data(config)
model = setup_model(config, data)
optimize!(model)
parse_results!(config, data, model)
process_results!(config, data)

@test compute_result(data, :gen, :cf_avg, :genfuel=>"ng") <= 0.9
@test compute_result(data, :gen, :cf_hourly_max, :genfuel=>"ng") > 0.9 # Want to make sure we're not limiting hourly

end

end