Skip to content

Commit

Permalink
Merge pull request #156 from e4st-dev/feat-13-welfare
Browse files Browse the repository at this point in the history
Feat 13 welfare
  • Loading branch information
sallyrobson authored Jun 14, 2023
2 parents 00b3559 + ca2a465 commit 59207b9
Show file tree
Hide file tree
Showing 41 changed files with 912 additions and 189 deletions.
6 changes: 5 additions & 1 deletion benchmark/benchmark_helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ function make_random_inputs(;n_bus = 100, n_gen = 100, n_branch=100, n_af=100, n
bus = DataFrame(
ref_bus = fill(false, n_bus),
plnom = rand(n_bus),
country = rand(countries(), n_bus)
country = rand(countries(), n_bus),
reg_factor = rand(n_bus),
)
ref_bus_idx = rand(1:n_bus)
bus.ref_bus[ref_bus_idx] = true

gen = DataFrame(
bus_idx = rand(1:n_bus, n_gen),
reg_factor = rand(n_gen),
status = trues(n_gen),
build_status = rand(build_status_opts(), n_gen),
build_type = rand(build_type_opts(), n_gen),
Expand All @@ -34,6 +36,8 @@ function make_random_inputs(;n_bus = 100, n_gen = 100, n_branch=100, n_af=100, n
heat_rate = 10*rand(n_gen),
fom = rand(n_gen),
capex = rand(n_gen),
transmission_capex=rand(n_gen),
routine_capex=rand(n_gen),
year_on = year2str.(rand(2000:2023, n_gen)),
year_off = fill("y9999", n_gen),
year_shutdown = fill("y9999", n_gen),
Expand Down
Binary file added docs/src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion docs/src/results/formulas.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ get_results_formulas
get_results_formula
compute_result
ResultsFormula
```

## Result Aggregation Functions
```@docs
Sum
SumYearly
AverageYearly
MinYearly
MaxYearly
SumHourly
SumHourlyWeighted
AverageHourly
MinHourly
AverageYearly
MaxHourly
CostOfServiceRebate
```
6 changes: 6 additions & 0 deletions docs/src/types/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ Container

```@docs
Container
ByNothing
ByYear
ByHour
ByYearAndHour
to_container
to_container!
```
1 change: 1 addition & 0 deletions docs/src/types/iterable.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Iterable
```@docs
Iterable
init!
issequential
should_iterate
iterate!
should_reread_data
Expand Down
61 changes: 49 additions & 12 deletions src/io/data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,34 @@ Creates age column which is a ByYear column. Unbuilt generators have a negative
function setup_table!(config, data, ::Val{:gen})
bus = get_table(data, :bus)
gen = get_table(data, :gen)
years = get_years(data)

data[:gen_table_original_cols] = propertynames(gen)
# Set up year_unbuilt before setting up new gens. Plus we will want to save the column
hasproperty(gen, :year_unbuilt) || (gen.year_unbuilt = map(y->add_to_year(y, -1), gen.year_on))

# Set up past capex cost and subsidy to be for built generators only
# Make columns as needed
hasproperty(gen, :past_invest_cost) || (gen.past_invest_cost = zeros(nrow(gen)))
hasproperty(gen, :past_invest_subsidy) || (gen.past_invest_subsidy = zeros(nrow(gen)))
z = Container(0.0)
to_container!(gen, :past_invest_cost)
to_container!(gen, :past_invest_subsidy)
for (idx_g, g) in enumerate(eachrow(gen))
if g.build_status == "unbuilt"
if any(!=(0), g.past_invest_cost) || any(!=(0), g.past_invest_subsidy)
@warn "Generator $idx_g is unbuilt yet has past capex cost/subsidy, setting to zero"
g.past_invest_cost = z
g.past_invest_subsidy = z
end
else
past_invest_percentages = get_past_invest_percentages(g, years)
g.past_invest_cost = g.past_invest_cost .* past_invest_percentages
g.past_invest_subsidy = g.past_invest_subsidy .* past_invest_percentages
end
end

original_cols = propertynames(gen)
data[:gen_table_original_cols] = original_cols

#removes capex_obj if read in from previous sim
:capex_obj in propertynames(data[:gen]) && select!(data[:gen], Not(:capex_obj))
Expand All @@ -440,23 +466,24 @@ function setup_table!(config, data, ::Val{:gen})
### Create capex_obj (the capex used in the optimization/objective function)
# set to capex for unbuilt generators in and after the year_on
# set to 0 for already built capacity because capacity expansion isn't considered for existing generators

capex_obj = Container[ByNothing(0.0) for i in 1:nrow(gen)]
for idx_g in 1:nrow(gen)
g = gen[idx_g,:]
transmission_capex_obj = Container[ByNothing(0.0) for i in 1:nrow(gen)]
for (idx_g, g) in enumerate(eachrow(gen))
g.build_status == "unbuilt" || continue
g_capex_obj = [g.capex * (year >= g.year_on && year < add_to_year(g.year_on, g.econ_life)) for year in get_years(data)]
# g_capex_obj = [g.capex * (year == g.year_on) for year in get_years(data)]
capex_obj[idx_g] = ByYear(g_capex_obj)
capex_obj[idx_g] = ByYear([g.capex * (year >= g.year_on && year < add_to_year(g.year_on, g.econ_life)) for year in get_years(data)])
transmission_capex_obj[idx_g] = ByYear([g.transmission_capex * (year >= g.year_on && year < add_to_year(g.year_on, g.econ_life)) for year in get_years(data)])
end
add_table_col!(data, :gen, :capex_obj, capex_obj, DollarsPerMWBuiltCapacity, "Hourly capital expenditures that is passed into the objective function. 0 for already built capacity")
add_table_col!(data, :gen, :capex_obj, capex_obj, DollarsPerMWBuiltCapacityPerHour, "Hourly capital expenditures that is passed into the objective function. 0 for already built capacity")
add_table_col!(data, :gen, :transmission_capex_obj, transmission_capex_obj, DollarsPerMWBuiltCapacityPerHour, "Hourly capital expenditures for transmission that is passed into the objective function. 0 for already built capacity")

# capex_econ = Container[ByNothing(0.0) for i in 1:nrow(gen)]
# for idx_g in 1:nrow(gen)
# g = gen[idx_g,:]
# g_capex = [g.capex * (year >= g.year_on && year < g.year_off) for year in get_years(data)] # Possibly change this to be based on economic lifetime
# capex_econ[idx_g] = ByYear(g_capex)
# end
# add_table_col!(data, :gen, :capex_econ, capex_econ, DollarsPerMWBuiltCapacity, "Hourly capital expenditures to be paid between year_on and year_off")
# add_table_col!(data, :gen, :capex_econ, capex_econ, DollarsPerMWBuiltCapacityPerHour, "Hourly capital expenditures to be paid between year_on and year_off")


### Add age column as by ByYear based on year_on
Expand All @@ -472,7 +499,7 @@ function setup_table!(config, data, ::Val{:gen})

### Map bus characteristics to generators
names_before = propertynames(gen)
leftjoin!(gen, bus, on=:bus_idx)
leftjoin!(gen, select(bus, Not(:reg_factor)), on=:bus_idx)
select!(gen, Not(:plnom))
disallowmissing!(gen)
names_after = propertynames(gen)
Expand All @@ -485,6 +512,7 @@ function setup_table!(config, data, ::Val{:gen})
# Add necessary columns if they don't exist.
hasproperty(gen, :af) || (gen.af = fill(ByNothing(1.0), nrow(gen)))
hasproperty(gen, :fuel_price) || (gen.fuel_price = fill(0.0, nrow(gen)))

return gen
end
export setup_table!
Expand Down Expand Up @@ -664,6 +692,7 @@ function summarize_table(::Val{:gen})
(:build_type, AbstractString, NA, true, "Whether the generator is 'real', 'exog' (exogenously built), or 'endog' (endogenously built)"),
(:build_id, AbstractString, NA, true, "Identifier of the build row. For pre-existing generators not specified in the build file, this is usually left empty"),
(:year_on, YearString, Year, true, "The first year of operation for the generator. (For new gens this is also the year it was built)"),
(:year_unbuilt,YearString, Year, false, "The latest year the generator was known not to be built. Defaults to year_on - 1. Used for past capex accounting."),
(:econ_life, Float64, NumYears, true, "The number of years in the economic lifetime of the generator."),
(:year_off, YearString, Year, true, "The first year that the generator is no longer operating in the simulation, computed from the simulation. Leave as y9999 if an existing generator that has not been retired in the simulation yet."),
(:year_shutdown, YearString, Year, true, "The forced (exogenous) shutdown year for the generator. Often equal to the year_on plus the econ_life"),
Expand All @@ -677,15 +706,20 @@ function summarize_table(::Val{:gen})
(:fuel_price, Float64, DollarsPerMMBtu, false, "Fuel cost per MMBtu of fuel used. `heat_rate` column also necessary when supplying `fuel_price`"),
(:heat_rate, Float64, MMBtuPerMWhGenerated, false, "Heat rate, or MMBtu of fuel consumed per MWh electricity generated (0 for generators that don't use combustion)"),
(:fom, Float64, DollarsPerMWCapacityPerHour, true, "Hourly fixed operation and maintenance cost for a MW of generation capacity"),
(:capex, Float64, DollarsPerMWBuiltCapacity, false, "Hourly capital expenditures for a MW of generation capacity"),
(:capex, Float64, DollarsPerMWBuiltCapacityPerHour, true, "Hourly capital expenditures for a MW of generation capacity. For already-built generators, this is not accounted for in the optimization or accounting. For accounting for investment costs and subsidies in built generators, use `past_invest_cost` and `past_invest_subsidy`"),
(:transmission_capex, Float64, DollarsPerMWBuiltCapacityPerHour, true, "Hourly capital expenditures for the transmission supporting a MW of generation capacity"),
(:routine_capex, Float64, DollarsPerMWCapacityPerHour, true, "Routine capital expenditures for a MW of discharge capacity"),
(:past_invest_cost, Float64, DollarsPerMWCapacityPerHour, false, "Investment costs per MW of initial capacity per hour, for past investments"),
(:past_invest_subsidy, Float64, DollarsPerMWCapacityPerHour, false, "Investment subsidies from govt. per MW of initial capacity per hour, for past investments"),
(:cf_min, Float64, MWhGeneratedPerMWhCapacity, false, "The minimum capacity factor, or operable ratio of power generation to capacity for the generator to operate. Take care to ensure this is not above the hourly availability factor in any of the hours, or else the model may be infeasible. Set to zero by default."),
(:cf_max, Float64, MWhGeneratedPerMWhCapacity, false, "The maximum capacity factor, or operable ratio of power generation to capacity for the generator to operate"),
(:cf_hist, Float64, MWhGeneratedPerMWhCapacity, false, "The historical capacity factor for the generator, or the gentype if no previous data is available. Primarily used to calculate estimate policy value (PTC and EmissionPrice capex_adj)"),
(:af, Float64, MWhGeneratedPerMWhCapacity, false, "The availability factor, or maximum available ratio of pewer generation to nameplate capacity for the generator."),
(:emis_co2, Float64, ShortTonsPerMWhGenerated, false, "The emission rate per MWh of CO2"),
(:capt_co2_percent, Float64, NA, false, "The percentage of co2 emissions captured, to be sequestered."),
(:heat_rate, Float64, MMBtuPerMWhGenerated, false, "Heat rate, or MMBtu of fuel consumed per MWh electricity generated (0 for generators that don't use combustion)"),
(:chp_co2_multi,Float64,NA,false,"The percentage of CO2 emissions from CHP attributed to the power generation. Used to calculate CO2e")
(:chp_co2_multi,Float64,NA,false,"The percentage of CO2 emissions from CHP attributed to the power generation. Used to calculate CO2e"),
(:reg_factor, Float64, NA, true, "The percentage of generation that dispatches to a cost-of-service regulated market"),
)
return df
end
Expand All @@ -700,6 +734,7 @@ function summarize_table(::Val{:bus})
df = TableSummary()
push!(df,
(:ref_bus, Bool, NA, true, "Whether or not the bus is a reference bus. There should be a single reference bus for each island."),
(:reg_factor, Float64, NA, true, "The percentage of generation that dispatches to a cost-of-service regulated market"),
)
return df
end
Expand Down Expand Up @@ -776,7 +811,9 @@ function summarize_table(::Val{:build_gen})
(:vom, Float64, DollarsPerMWhGenerated, true, "Variable operation and maintenance cost per MWh of generation"),
(:fuel_price, Float64, DollarsPerMMBtu, false, "Fuel cost per MMBtu of fuel used. `heat_rate` column also necessary when supplying `fuel_price`"),
(:fom, Float64, DollarsPerMWCapacityPerHour, true, "Hourly fixed operation and maintenance cost for a MW of generation capacity"),
(:capex, Float64, DollarsPerMWBuiltCapacity, false, "Hourly capital expenditures for a MW of generation capacity"),
(:capex, Float64, DollarsPerMWBuiltCapacityPerHour, true, "Hourly capital expenditures for a MW of generation capacity"),
(:transmission_capex, Float64, DollarsPerMWBuiltCapacityPerHour, true, "Hourly capital expenditures for the transmission supporting a MW of generation capacity"),
(:routine_capex, Float64, DollarsPerMWCapacityPerHour, true, "Routing capital expenditures for a MW of discharge capacity"),
(:cf_min, Float64, MWhGeneratedPerMWhCapacity, false, "The minimum capacity factor, or operable ratio of power generation to capacity for the generator to operate. Take care to ensure this is not above the hourly availability factor in any of the hours, or else the model may be infeasible. Set to zero by default."),
(:cf_max, Float64, MWhGeneratedPerMWhCapacity, false, "The maximum capacity factor, or operable ratio of power generation to capacity for the generator to operate"),
(:cf_hist, Float64, MWhGeneratedPerMWhCapacity, false, "The historical capacity factor for the generator or the gentype. Primarily used to calculate estimate policy value (PTC and EmissionPrice capex_adj)"),
Expand Down
17 changes: 17 additions & 0 deletions src/io/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,20 @@ export anyany
function Base.convert(T::Type{Symbol}, x::String)
return Symbol(x)
end

"""
get_past_invest_percentages(g, years) -> ::ByYear
Computes the percentage of past investment costs and/or subsidies to still be paid in each `year`, given the `year_on`, `year_unbuilt` and `econ_life` of `g`.
"""
function get_past_invest_percentages(g, years)
year_on = g.year_on::AbstractString
year_unbuilt = g.year_unbuilt::AbstractString
econ_life = g.econ_life::Float64
diff = diff_years(year_on, year_unbuilt)
v = map(years) do y
percent = (diff_years(year_on, y) + econ_life) / diff
return min(1.0, max(0.0, percent))
end
return OriginalContainer(0.0, ByYear(v))
end
3 changes: 2 additions & 1 deletion src/model/dcopf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ function setup_dcopf!(config, data, model)
pgen_scalar * pgen_gen[gen_idx, yr_idx, hr_idx] <= # Scale by pgen_scalar in this constraint to improve matrix coefficient range. Some af values are very small.
pgen_scalar * get_cf_max(config, data, gen_idx, yr_idx, hr_idx) * pcap_gen[gen_idx, yr_idx]
)
# TODO: Add af_threshold here.

# Constrain Reference Bus
for ref_bus_idx in get_ref_bus_idxs(data), yr_idx in 1:nyear, hr_idx in 1:nhour
Expand Down Expand Up @@ -154,6 +153,7 @@ function setup_dcopf!(config, data, model)
end

add_obj_term!(data, model, PerMWCap(), :fom, oper = +)
add_obj_term!(data, model, PerMWCap(), :routine_capex, oper = +)

@expression(model,
pcap_gen_inv_sim[gen_idx in axes(gen,1)],
Expand All @@ -167,6 +167,7 @@ function setup_dcopf!(config, data, model)
)

add_obj_term!(data, model, PerMWCapInv(), :capex_obj, oper = +)
add_obj_term!(data, model, PerMWCapInv(), :transmission_capex_obj, oper = +)

# Curtailment Cost
add_obj_term!(data, model, PerMWhCurtailed(), :curtailment_cost, oper = +)
Expand Down
23 changes: 19 additions & 4 deletions src/model/newgens.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ function make_newgens!(config, data, newgen)
gen = get_table(data, :gen)
years = get_years(data)

# Filter any already-built exogenous generators
filter!(build_gen) do row
row.build_type == "exog" && row.year_on >= config[:year_gen_data] && return false
return true
end

#get the names of specifications that will be pulled from the build_gen table
spec_names = filter!(
!in((:bus_idx, :gen_latitude, :gen_longitude, :year_off, :year_shutdown, :pcap_inv)),
!in((:bus_idx, :gen_latitude, :gen_longitude, :reg_factor, :year_off, :year_shutdown, :pcap_inv, :year_unbuilt, :past_invest_cost, :past_invest_subsidy)),
propertynames(newgen)
) #this needs to be updated if there is anything else in gen that isn't a spec

Expand All @@ -73,21 +79,25 @@ function make_newgens!(config, data, newgen)
for bus_idx in bus_idxs
if spec_row.build_type == "endog"
# for endogenous new builds, a new gen is created for each sim year
for year in years
for (yr_idx, year) in enumerate(years)
year < year_on_min && continue
year > year_on_max && continue
#populate newgen_row with specs
newgen_row = Dict{}(:bus_idx => bus_idx, (spec_name=>spec_row[spec_name] for spec_name in spec_names)...)

#set year_on and off
newgen_row[:year_on] = year
newgen_row[:year_shutdown] = add_to_year(year, spec_row.age_shutdown)
newgen_row[:year_unbuilt] = get(years, yr_idx - 1, config[:year_gen_data])
newgen_row[:year_shutdown] = "y9999" # add_to_year(year, spec_row.age_shutdown)
newgen_row[:year_off] = "y9999"
newgen_row[:pcap_inv] = 0.0
newgen_row[:past_invest_cost] = Container(0.0)
newgen_row[:past_invest_subsidy] = Container(0.0)

#add gen location
hasproperty(newgen, :gen_latitude) && (newgen_row[:gen_latitude] = bus.bus_latitude[bus_idx])
hasproperty(newgen, :gen_longitude) && (newgen_row[:gen_longitude] = bus.bus_longitude[bus_idx])
hasproperty(newgen, :reg_factor) && (newgen_row[:reg_factor] = bus.reg_factor[bus_idx])

push!(newgen, newgen_row, promote=true)
end
Expand All @@ -101,9 +111,14 @@ function make_newgens!(config, data, newgen)
newgen_row = Dict{}(:bus_idx => bus_idx, (spec_name=>spec_row[spec_name] for spec_name in spec_names)...)
hasproperty(newgen, :gen_latitude) && (newgen_row[:gen_latitude] = bus.bus_latitude[bus_idx])
hasproperty(newgen, :gen_longitude) && (newgen_row[:gen_longitude] = bus.bus_longitude[bus_idx])
newgen_row[:year_shutdown] = add_to_year(spec_row.year_on, spec_row.age_shutdown)
hasproperty(newgen, :reg_factor) && (newgen_row[:reg_factor] = bus.reg_factor[bus_idx])

newgen_row[:year_shutdown] = "y9999" #add_to_year(spec_row.year_on, spec_row.age_shutdown)
newgen_row[:year_off] = "y9999"
newgen_row[:pcap_inv] = 0.0
newgen_row[:year_unbuilt] = add_to_year(newgen_row[:year_on], -1)
newgen_row[:past_invest_cost] = Container(0.0)
newgen_row[:past_invest_subsidy] = Container(0.0)

push!(newgen, newgen_row, promote=true)
end
Expand Down
9 changes: 8 additions & 1 deletion src/model/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ function add_build_constraints!(data, model, table_name::Symbol, pcap_name::Symb
end
export add_build_constraints!

function Base.getindex(ex::GenericAffExpr{V, K}, x::K) where {K, V}
return get(ex.terms, x, zero(V))
end

function Base.getindex(ex::GenericAffExpr{V, K}, x) where {K, V}
return 0.0
end


"""
Expand Down Expand Up @@ -178,4 +185,4 @@ function get_gentype_cf_hist(gentype::AbstractString)
@warn "No default cf_hist provided for $(gentype) in E4ST, setting to 0.35"
return 0.35 # overall system capacity factor
end
export get_gentype_cf_hist
export get_gentype_cf_hist
Loading

0 comments on commit 59207b9

Please sign in to comment.