Skip to content

Commit

Permalink
Refactor generator_mapping.yaml, prototype new built-in selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielKS committed Jan 22, 2025
1 parent 57dd698 commit 4d45fdc
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 71 deletions.
12 changes: 7 additions & 5 deletions deps/generator_mapping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ NG-CC:
- {gentype: Any, primemover: CC, fuel: NATURAL_GAS}
NG-Steam:
- {gentype: Any, primemover: ST, fuel: NATURAL_GAS}
Natural gas:
- {gentype: Any, primemover: null, fuel: NATURAL_GAS}
- {gentype: Any, primemover: null, fuel: OTHER_GAS}
Hydropower:
- {gentype: Any, primemover: HY, fuel: null}
Coal:
Expand All @@ -36,6 +33,11 @@ Biopower:
- {gentype: Any, primemover: null, fuel: WOOD_WASTE}
CSP:
- {gentype: Any, primemover: CP, fuel: null}
Load:
- {gentype: ElectricLoad, primemover: null, fuel: null}
Other:
- {gentype: Any, primemover: OT, fuel: OTHER}
- {gentype: Any, primemover: null, fuel: null}
- {gentype: Generator, primemover: OT, fuel: OTHER}
__META:
non_generators:
- Storage
- Load
10 changes: 2 additions & 8 deletions src/PowerAnalytics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ export is_col_meta, set_col_meta, set_col_meta!, time_df, time_vec, data_cols, d
export compute, compute_set, compute_all, hcat_timed, aggregate_time, compose_metrics
export create_problem_results_dict
export parse_generator_mapping
export calc_active_power, calc_production_cost, calc_startup_cost, calc_shutdown_cost,
calc_discharge_cycles, calc_system_slack_up, calc_load_forecast, calc_active_power_in,
calc_active_power_out, calc_stored_energy, calc_active_power_forecast, calc_curtailment,
calc_sum_objective_value, calc_sum_solve_time, calc_sum_bytes_alloc, calc_total_cost,
make_calc_is_slack_up, calc_is_slack_up, calc_system_load_forecast,
calc_net_load_forecast, calc_curtailment_frac, calc_load_from_storage,
calc_system_load_from_storage, calc_integration, calc_capacity_factor
export mean, weighted_mean, unweighted_sum

# IMPORTS
Expand All @@ -48,7 +41,8 @@ import PowerSystems:
make_selector, get_name, get_groups,
get_component, get_components,
get_available,
COMPONENT_NAME_DELIMITER
COMPONENT_NAME_DELIMITER,
rebuild_selector

import InfrastructureSystems
import PowerSimulations
Expand Down
90 changes: 68 additions & 22 deletions src/builtin_component_selectors.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
const FUEL_TYPES_DATA_FILE =
joinpath(dirname(dirname(pathof(PowerAnalytics))), "deps", "generator_mapping.yaml")
const FUEL_TYPES_META_KEY = "__META"

# Parse the strings in generator_mapping.yaml into types and enum items
function parse_fuel_category(category_spec::Dict)
function parse_fuel_category(
category_spec::Dict;
root_type::Type{<:Component} = PSY.StaticInjection,
)
# TODO support types beyond PowerSystems
gen_type = getproperty(PowerSystems, Symbol(get(category_spec, "gentype", Component)))
(gen_type === Any) && (gen_type = Component)
@assert gen_type <: Component
(gen_type === Any) && (gen_type = root_type)
# Constrain gen_type such that gen_type <: root_type
gen_type = typeintersect(gen_type, root_type)

pm = get(category_spec, "primemover", nothing)
isnothing(pm) || (pm = PSY.parse_enum_mapping(PSY.PrimeMovers, pm))
Expand All @@ -17,9 +22,14 @@ function parse_fuel_category(category_spec::Dict)
return gen_type, pm, fc
end

function make_fuel_component_selector(category_spec::Dict)
parse_results = parse_fuel_category(category_spec)
function make_fuel_component_selector(
category_spec::Dict;
root_type::Type{<:Component} = PSY.StaticInjection,
)
parse_results = parse_fuel_category(category_spec; root_type = root_type)
(gen_type, prime_mover, fuel_category) = parse_results
# If gen_type is the bottom type, it means it doesn't fit in root_type and we shouldn't include the selector at all
(gen_type <: Union{}) && return nothing

function filter_closure(comp::Component)
comp_sig = Tuple{typeof(comp)}
Expand All @@ -42,15 +52,25 @@ function make_fuel_component_selector(category_spec::Dict)
end

# Based on old PowerAnalytics' get_generator_mapping
function parse_generator_mapping(filename)
function parse_generator_mapping(
filename;
root_type::Type{<:Component} = PSY.StaticInjection,
)
# NOTE the YAML library does not support ordered loading
in_data = open(YAML.load, filename)
mappings = Dict{String, ComponentSelector}()
for top_level in keys(in_data)
subselectors = make_fuel_component_selector.(in_data[top_level])
(top_level == FUEL_TYPES_META_KEY) && continue
subselectors =
make_fuel_component_selector.(
in_data[top_level]; root_type = PSY.StaticInjection)
# A subselector will be nothing if it doesn't fit under root_type
subselectors = filter(!isnothing, subselectors)
# Omit the category entirely if root_type causes elimination of all subselectors
length(in_data[top_level]) > 0 && length(subselectors) == 0 && continue
mappings[top_level] = make_selector(subselectors...; name = top_level)
end
return mappings
return mappings, get(in_data, FUEL_TYPES_META_KEY, nothing)
end

# SELECTORS MODULE
Expand All @@ -65,28 +85,54 @@ import
export
all_loads,
all_storage,
generators_of_category,
generators_by_category
injector_categories,
generator_categories,
categorized_injectors,
categorized_generators

"A ComponentSelector representing all the electric load in a System"
all_loads::ComponentSelector = make_selector(PSY.ElectricLoad; groupby = :all)
all_loads::ComponentSelector = make_selector(PSY.ElectricLoad)

"A ComponentSelector representing all the storage in a System"
all_storage::ComponentSelector = make_selector(PSY.Storage; groupby = :all)
all_storage::ComponentSelector = make_selector(PSY.Storage)

"""
A dictionary of `ComponentSelector`s, each of which corresponds to one of the generator
reporting categories in `generator_mapping.yaml`. Use `generators_by_reporting_category`
if instead a single selector grouped by all the categories is desired.
A dictionary of `ComponentSelector`s, each of which corresponds to one of the static
injector categories in `generator_mapping.yaml`
"""
generators_of_category::AbstractDict{String, ComponentSelector} =
parse_generator_mapping(FUEL_TYPES_DATA_FILE)
injector_categories::AbstractDict{String, ComponentSelector} =
first(parse_generator_mapping(FUEL_TYPES_DATA_FILE))

"""
A single `ComponentSelector` representing the generators in a `System` grouped by the
reporting categories in `generator_mapping.yaml`. Use `generators_of_reporting_category`
if instead an individual category is desired.
A dictionary of `ComponentSelector`s, each of which corresponds to one of the categories in
`generator_mapping.yaml`, only considering the components and categories that represent
`Generator`s (no storage or load)
"""
generators_by_category::ComponentSelector =
make_selector(values(generators_of_category)...)
generator_categories::Union{AbstractDict{String, ComponentSelector}, Nothing} = let
categories, meta =
parse_generator_mapping(FUEL_TYPES_DATA_FILE)
if isnothing(meta) || !haskey(meta, "non_generators")
nothing
else
filter(pair -> !(first(pair) in meta["non_generators"]), categories)
end
end

"""
A single `ComponentSelector` representing the static injectors in a `System` grouped by the
categories in `generator_mapping.yaml`
"""
categorized_injectors::ComponentSelector =
make_selector(values(injector_categories)...)

"""
A single `ComponentSelector` representing the `Generator`s in a `System` grouped by the
categories in `generator_mapping.yaml`
"""
categorized_generators::Union{ComponentSelector, Nothing} =
if isnothing(generator_categories)
nothing
else
make_selector(values(generator_categories)...)
end
end
7 changes: 5 additions & 2 deletions src/builtin_metrics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import
..unweighted_sum,
..mean,
..read_component_result,
..rebuild_selector,
...Selectors.all_loads, # number of dots obtained by trial and error
...Selectors.all_storage
export calc_active_power,
Expand Down Expand Up @@ -150,7 +151,8 @@ const calc_load_forecast = ComponentTimedMetric(;
const calc_system_load_forecast = SystemTimedMetric(;
name = "SystemLoadForecast",
eval_fn = (res::IS.Results; kwargs...) ->
compute(calc_load_forecast, res, all_loads; kwargs...),
compute(calc_load_forecast, res,
rebuild_selector(all_loads; groupby = :all); kwargs...),
)

"Fetch the LoadFromStorage of all storage in the system"
Expand All @@ -160,7 +162,8 @@ const calc_system_load_from_storage = let
eval_fn = (
res::IS.Results; kwargs...
) ->
compute(calc_load_from_storage, res, all_storage; kwargs...),
compute(calc_load_from_storage, res,
rebuild_selector(all_storage; groupby = :all); kwargs...),
)
end

Expand Down
24 changes: 10 additions & 14 deletions src/fuel_results.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function get_generator_mapping(filename = nothing)

mappings = Dict{NamedTuple, String}()
for (gen_type, vals) in genmap
(gen_type == "__META") && continue
for val in vals
pm = get(val, "primemover", nothing)
pm = isnothing(pm) ? nothing : uppercase(string(pm))
Expand Down Expand Up @@ -92,22 +93,17 @@ function make_fuel_dictionary(
for category in unique(values(mapping))
gen_categories["$category"] = []
end
gen_categories["Load"] = []

for gen in generators
if gen isa PSY.ElectricLoad
category = "Load"
else
fuel = hasmethod(PSY.get_fuel, Tuple{typeof(gen)}) ? PSY.get_fuel(gen) : nothing
pm =
if hasmethod(PSY.get_prime_mover_type, Tuple{typeof(gen)})
PSY.get_prime_mover_type(gen)
else
nothing
end
ext = get(PSY.get_ext(gen), "ext_category", nothing)
category = get_generator_category(typeof(gen), fuel, pm, ext, mapping)
end
fuel = hasmethod(PSY.get_fuel, Tuple{typeof(gen)}) ? PSY.get_fuel(gen) : nothing
pm =
if hasmethod(PSY.get_prime_mover_type, Tuple{typeof(gen)})
PSY.get_prime_mover_type(gen)
else
nothing
end
ext = get(PSY.get_ext(gen), "ext_category", nothing)
category = get_generator_category(typeof(gen), fuel, pm, ext, mapping)
push!(gen_categories["$category"], (string(nameof(typeof(gen))), PSY.get_name(gen)))
end
[delete!(gen_categories, "$k") for (k, v) in gen_categories if isempty(v)]
Expand Down
41 changes: 21 additions & 20 deletions test/test_builtin_component_selectors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@ name_and_type = component -> (typeof(component), get_name(component))
Set([(EnergyReservoirStorage, "Bat")])
end

@testset "Test `generators_of_category` and `generators_by_category`" begin
@test isfile(PA.FUEL_TYPES_DATA_FILE)
@test Set(keys(generators_of_category)) ==
Set(["Biopower", "CSP", "Coal", "Geothermal", "Hydropower", "NG-CC", "NG-CT",
"NG-Steam", "Natural gas", "Nuclear", "Other", "PV", "Petroleum", "Storage",
"Wind"])
@test Set(
name_and_type.(get_components(generators_of_category["Wind"], test_sys)),
) == Set([(RenewableDispatch, "WindBusB"), (RenewableDispatch, "WindBusC"),
(RenewableDispatch, "WindBusA")])
@test Set(
name_and_type.(get_components(generators_of_category["Coal"], test_sys)),
) == Set([(ThermalStandard, "Park City"), (ThermalStandard, "Sundance"),
(ThermalStandard, "Alta"), (ThermalStandard, "Solitude"),
(ThermalStandard, "Brighton")])
@test Set(get_groups(generators_by_category, test_sys)) ==
Set(values(generators_of_category))
@test Set(keys(parse_generator_mapping(PA.FUEL_TYPES_DATA_FILE))) ==
Set(keys(generators_of_category))
end
# TODO rewrite based on refactored selectors
# @testset "Test `generators_of_category` and `generators_by_category`" begin
# @test isfile(PA.FUEL_TYPES_DATA_FILE)
# @test Set(keys(generators_of_category)) ==
# Set(["Biopower", "CSP", "Coal", "Geothermal", "Hydropower", "NG-CC", "NG-CT",
# "NG-Steam", "Natural gas", "Nuclear", "Other", "PV", "Petroleum", "Storage",
# "Wind"])
# @test Set(
# name_and_type.(get_components(generators_of_category["Wind"], test_sys)),
# ) == Set([(RenewableDispatch, "WindBusB"), (RenewableDispatch, "WindBusC"),
# (RenewableDispatch, "WindBusA")])
# @test Set(
# name_and_type.(get_components(generators_of_category["Coal"], test_sys)),
# ) == Set([(ThermalStandard, "Park City"), (ThermalStandard, "Sundance"),
# (ThermalStandard, "Alta"), (ThermalStandard, "Solitude"),
# (ThermalStandard, "Brighton")])
# @test Set(get_groups(generators_by_category, test_sys)) ==
# Set(values(generators_of_category))
# @test Set(keys(parse_generator_mapping(PA.FUEL_TYPES_DATA_FILE))) ==
# Set(keys(generators_of_category))
# end

0 comments on commit 4d45fdc

Please sign in to comment.