-
Notifications
You must be signed in to change notification settings - Fork 1
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
Total PowerAnalytics Redesign: ComponentSelector
s and Metric
s
#24
base: main
Are you sure you want to change the base?
Changes from all commits
8caef0d
73297b4
207194a
62a4a28
4b3422e
8f57c88
40b81fb
e8e0c8d
d601fbc
ffb3a83
846b24b
135b895
c605790
bfae18a
269b885
ebfca89
803e303
51ffbb9
aa992aa
7fe1039
262562a
515ea8d
984cee8
e39f0ed
a36df4b
51c04d3
df4757e
62358e7
75de83b
a60408b
feeacaf
abdf5b9
76619aa
cfc6df4
666ae25
b04adae
af83f76
3d233c5
3da6164
3e737db
78a986f
3547749
8f1e5bb
5823217
5272ea2
40a886c
00e72e5
15f6fa0
7ebc456
eab6286
43d3b2d
c5e1b48
e09fc86
ec352cd
3de0edb
e11dde2
5db41ca
ca4faf3
d80988c
b0d78ad
5cc6047
c387507
d96c3a7
85241c1
1cae840
c509cb7
a005d09
57dd698
4d45fdc
abd4467
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
|
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!, time_df, time_vec, data_cols, data_df, | ||
data_vec, 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_set, compute_all, hcat_timed, 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's already public. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not in older minor versions we commit to supporting (see NREL-Sienna/InfrastructureSystems.jl#390) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. @jd-lara Is it time to force users to upgrade? 1.11 is out. If people haven't upgraded to at least 1.9, they probably should. |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does there need to be an old and new? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to maintain a strict separation between the two interfaces until we decide what to do with the old one (e.g., should it be deprecated once PowerGraphics no longer depends on it)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be a 1.0 vs 2.0 kinda thing? Basically, do we need to maintain support for the old methods in the new version if those users can continue to use the old version? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One issue is that the current latest version of PowerGraphics depends on old PowerAnalytics and I don't want to keep PowerGraphics users from using new PowerAnalytics. I'm thinking something like this:
but not committed to that, open to discussion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -> #28 |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
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; | ||
root_type::Type{<:Component} = PSY.StaticInjection, | ||
) | ||
# TODO support types beyond PowerSystems | ||
gen_type = getproperty(PowerSystems, Symbol(get(category_spec, "gentype", Component))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't Sourabh say that he could have custom types from another package? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I should make this more flexible somehow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thoughts on adding this as a minor update later? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is that I don't see how it could ever be solved. I'm guessing that the PSY table data parser does something like this, and that's OK because it's only parsing types that it knows about. Would this function ever because in a situation with custom types? I'm ok with moving forward if we can bound the problem. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We definitely do something like this to load serialized systems, I think by checking the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then that file will have to include the name of the package so that you know what to pass to getproperty. That is a change that could/should be made now. |
||
(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 = join(ifelse.(isnothing.(parse_results), "", string.(parse_results)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So if prime_mover and fuel_category are both nothing, the name will be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Secondly, calling |
||
COMPONENT_NAME_DELIMITER) | ||
|
||
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 && length(subselectors) == 0 && 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" | ||
all_loads::ComponentSelector = make_selector(PSY.ElectricLoad) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
"A ComponentSelector representing all the storage in a System" | ||
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` | ||
""" | ||
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) | ||
""" | ||
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` | ||
""" | ||
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` | ||
""" | ||
categorized_generators::Union{ComponentSelector, Nothing} = | ||
if isnothing(generator_categories) | ||
nothing | ||
else | ||
make_selector(values(generator_categories)...) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many of these function names are ripe for collision with users' local variables. Perhaps
get_time_df
instead oftime_df
?