-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from GabrielKS/gks/entity-metric-redesign-psy4
Total PowerAnalytics Redesign: `ComponentSelector`s and `Metric`s
- Loading branch information
Showing
18 changed files
with
2,613 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
name = "PowerAnalytics" | ||
uuid = "56ce1300-00bc-47e4-ba8c-b166ccc19f51" | ||
authors = ["cbarrows <[email protected]>"] | ||
authors = ["Gabriel Konar-Steenberg <[email protected]>", "cbarrows <[email protected]>"] | ||
version = "0.8.1" | ||
|
||
[deps] | ||
|
@@ -11,13 +11,14 @@ InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" | |
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" | ||
PowerSimulations = "e690365d-45e2-57bb-ac84-44ba829e73c4" | ||
PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" | ||
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" | ||
TimeSeries = "9e3dc215-6440-5c97-bce1-76c03772f85e" | ||
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" | ||
|
||
[compat] | ||
Dates = "1" | ||
DataFrames = "1" | ||
DataStructures = "0.18" | ||
Dates = "1" | ||
InfrastructureSystems = "2" | ||
InteractiveUtils = "1" | ||
PowerSimulations = "^0.29" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,75 @@ | ||
module PowerAnalytics | ||
|
||
# EXPORTS | ||
export make_fuel_dictionary | ||
export get_generation_data | ||
export get_load_data | ||
export get_service_data | ||
export categorize_data | ||
export no_datetime | ||
|
||
#I/O Imports | ||
export ComponentSelector, SingularComponentSelector, PluralComponentSelector | ||
export make_selector, get_name, get_subselectors | ||
export Metric, TimedMetric, TimelessMetric, ComponentSelectorTimedMetric, | ||
ComponentTimedMetric, | ||
SystemTimedMetric, ResultsTimelessMetric, CustomTimedMetric | ||
export DATETIME_COL, META_COL_KEY, SYSTEM_COL, RESULTS_COL, AGG_META_KEY | ||
export is_col_meta, set_col_meta, set_col_meta!, get_time_df, get_time_vec, get_data_cols, | ||
get_data_df, get_data_vec, get_data_mat, get_description, get_component_agg_fn, | ||
get_time_agg_fn, with_component_agg_fn, with_time_agg_fn, metric_selector_to_string, | ||
get_agg_meta, set_agg_meta!, rebuild_metric | ||
export compute, compute_all, hcat_timed_dfs, aggregate_time, compose_metrics | ||
export create_problem_results_dict | ||
export parse_generator_mapping_file, parse_injector_categories, parse_generator_categories | ||
export mean, weighted_mean, unweighted_sum | ||
|
||
# IMPORTS | ||
import Base: @kwdef | ||
import Dates | ||
import Dates: DateTime | ||
import TimeSeries | ||
import Statistics | ||
import Statistics: mean | ||
import DataFrames | ||
import DataFrames: DataFrame, metadata, metadata!, colmetadata, colmetadata! | ||
import YAML | ||
import DataStructures: OrderedDict, SortedDict | ||
import DataStructures: SortedDict | ||
import PowerSystems | ||
import PowerSystems: | ||
Component, | ||
ComponentSelector, | ||
make_selector, get_name, get_groups, | ||
get_component, get_components, | ||
get_available, | ||
COMPONENT_NAME_DELIMITER, | ||
rebuild_selector | ||
|
||
import InfrastructureSystems | ||
import PowerSimulations | ||
import PowerSimulations: | ||
get_system | ||
import InteractiveUtils | ||
|
||
# ALIASES | ||
const PSY = PowerSystems | ||
const IS = InfrastructureSystems | ||
const PSI = PowerSimulations | ||
|
||
# INCLUDES | ||
# Old PowerAnalytics | ||
include("definitions.jl") | ||
include("get_data.jl") | ||
include("fuel_results.jl") | ||
|
||
greet() = print("Hello World!") | ||
# New PowerAnalytics | ||
include("input_utils.jl") | ||
include("output_utils.jl") | ||
include("metrics.jl") | ||
include("builtin_component_selectors.jl") | ||
include("builtin_metrics.jl") | ||
|
||
# SUBMODULES | ||
using .Selectors | ||
using .Metrics | ||
|
||
end # module PowerAnalytics |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
const FUEL_TYPES_DATA_FILE = | ||
joinpath(dirname(dirname(pathof(PowerAnalytics))), "deps", "generator_mapping.yaml") | ||
const FUEL_TYPES_META_KEY = "__META" | ||
|
||
""" | ||
Parse the `gentype` to a type. This is done by first checking whether gentype is qualified | ||
(`ModuleName.TypeName`). If so, the module is fetched from the `Main` scope and the type | ||
name is fetched from the module. If not, we default to fetching from `PowerSystems` for | ||
convenience. | ||
""" | ||
function lookup_gentype(gentype::AbstractString) | ||
if occursin(".", gentype) | ||
splitted = split(gentype, ".") | ||
(length(splitted) == 2) || throw(ArgumentError("Cannot parse gentype '$gentype'")) | ||
mod, typename = splitted | ||
return getproperty(getproperty(Main, Symbol(mod)), Symbol(typename)) | ||
end | ||
return getproperty(PowerSystems, Symbol(gentype)) | ||
end | ||
|
||
# Parse the strings in generator_mapping.yaml into types and enum items | ||
function parse_fuel_category( | ||
category_spec::Dict; | ||
root_type::Type{<:Component} = PSY.StaticInjection, | ||
) | ||
gen_type = lookup_gentype(get(category_spec, "gentype", "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)) | ||
|
||
fc = get(category_spec, "fuel", nothing) | ||
isnothing(fc) || (fc = PSY.parse_enum_mapping(PSY.ThermalFuels, fc)) | ||
|
||
return gen_type, pm, fc | ||
end | ||
|
||
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)} | ||
if !isnothing(prime_mover) | ||
hasmethod(PowerSystems.get_prime_mover_type, comp_sig) || return false | ||
(PowerSystems.get_prime_mover_type(comp) == prime_mover) || return false | ||
end | ||
if !isnothing(fuel_category) | ||
hasmethod(PowerSystems.get_fuel, comp_sig) || return false | ||
(PowerSystems.get_fuel(comp) == fuel_category) || return false | ||
end | ||
return true | ||
end | ||
|
||
# Create a nice name that is guaranteed to never collide with fully-qualified component names | ||
selector_name = string(nameof(parse_results[1])) | ||
if !all(isnothing.(parse_results[2:end])) | ||
selector_name *= | ||
COMPONENT_NAME_DELIMITER * join( | ||
ifelse.( | ||
isnothing.(parse_results[2:end]), | ||
"Any", | ||
string.(parse_results[2:end])), | ||
COMPONENT_NAME_DELIMITER) | ||
end | ||
|
||
return make_selector(filter_closure, gen_type; name = selector_name) | ||
end | ||
|
||
# Based on old PowerAnalytics' get_generator_mapping | ||
""" | ||
Parse a `generator_mapping.yaml` file into a dictionary of `ComponentSelector`s and a | ||
dictionary of metadata if present | ||
""" | ||
function parse_generator_mapping_file( | ||
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) | ||
(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 && isempty(subselectors)) && continue | ||
mappings[top_level] = make_selector(subselectors...; name = top_level) | ||
end | ||
return mappings, get(in_data, FUEL_TYPES_META_KEY, nothing) | ||
end | ||
|
||
""" | ||
Use [`parse_generator_mapping_file`](@ref) to parse a `generator_mapping.yaml` file into a | ||
dictionary of all `ComponentSelector`s | ||
""" | ||
parse_injector_categories(filename; root_type::Type{<:Component} = PSY.StaticInjection) = | ||
first(parse_generator_mapping_file(filename; root_type = root_type)) | ||
|
||
""" | ||
Use [`parse_generator_mapping_file`](@ref) to parse a `generator_mapping.yaml` file into a | ||
dictionary of `ComponentSelector`, excluding categories in the 'non_generators' list in | ||
metadata | ||
""" | ||
function parse_generator_categories(filename; | ||
root_type::Type{<:Component} = PSY.StaticInjection) | ||
categories, meta = parse_generator_mapping_file(filename; root_type = root_type) | ||
(isnothing(meta) || !haskey(meta, "non_generators")) && return nothing | ||
return filter(pair -> !(first(pair) in meta["non_generators"]), categories) | ||
end | ||
|
||
# SELECTORS MODULE | ||
"`PowerAnalytics` built-in `ComponentSelector`s. Use `names` to list what is available." | ||
module Selectors | ||
import | ||
..make_selector, | ||
..PSY, | ||
..parse_generator_mapping_file, | ||
..parse_injector_categories, | ||
..parse_generator_categories, | ||
..ComponentSelector, | ||
..FUEL_TYPES_DATA_FILE | ||
export | ||
all_loads, | ||
all_storage, | ||
injector_categories, | ||
generator_categories, | ||
categorized_injectors, | ||
categorized_generators | ||
|
||
"A ComponentSelector representing all the electric load in a System" | ||
const all_loads::ComponentSelector = make_selector(PSY.ElectricLoad) | ||
|
||
"A ComponentSelector representing all the storage in a System" | ||
const all_storage::ComponentSelector = make_selector(PSY.Storage) | ||
|
||
""" | ||
A dictionary of `ComponentSelector`s, each of which corresponds to one of the static | ||
injector categories in `generator_mapping.yaml` | ||
""" | ||
const injector_categories::AbstractDict{String, ComponentSelector} = | ||
parse_injector_categories(FUEL_TYPES_DATA_FILE) | ||
|
||
""" | ||
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 | ||
generators (no storage or load) | ||
""" | ||
const generator_categories::Union{AbstractDict{String, ComponentSelector}, Nothing} = let | ||
result = parse_generator_categories(FUEL_TYPES_DATA_FILE) | ||
isnothing(result) && @warn "Could not construct generator categories" | ||
result | ||
end | ||
|
||
""" | ||
A single `ComponentSelector` representing the static injectors in a `System` grouped by the | ||
categories in `generator_mapping.yaml` | ||
""" | ||
const categorized_injectors::ComponentSelector = | ||
make_selector(values(injector_categories)...) | ||
|
||
""" | ||
A single `ComponentSelector` representing the generators in a `System` (no storage or load) | ||
grouped by the categories in `generator_mapping.yaml` | ||
""" | ||
const categorized_generators::Union{ComponentSelector, Nothing} = | ||
if isnothing(generator_categories) | ||
nothing | ||
else | ||
make_selector(values(generator_categories)...) | ||
end | ||
end |
Oops, something went wrong.