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

Use formulations as an interface #59

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

josephmckinsey
Copy link
Collaborator

@josephmckinsey josephmckinsey commented Feb 11, 2025

Should solve #58 along with making everything more extensible.

This involved a lot of refactoring, some documentation, and more testing to make sure it works like the old version.

I also made some departures from how Sienna does it:

  • Formulations have data, not DeviceModels directly. This allows you to configure formulations more independently.
  • The device models in a problem template are actually a list. We traverse the list in reverse order and find the first formulation that applies to figure out which formulation to use for each component.

Copy link

codecov bot commented Feb 11, 2025

Codecov Report

Attention: Patch coverage is 85.15625% with 38 lines in your changes missing coverage. Please review.

Project coverage is 52.17%. Comparing base (90fd4dc) to head (d699663).

Files with missing lines Patch % Lines
src/PowerSystems2PRAS.jl 83.91% 32 Missing ⚠️
src/formulation_definitions.jl 88.23% 6 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #59      +/-   ##
==========================================
+ Coverage   48.73%   52.17%   +3.44%     
==========================================
  Files          12       13       +1     
  Lines         788      851      +63     
==========================================
+ Hits          384      444      +60     
- Misses        404      407       +3     
Flag Coverage Δ
unittests 52.17% <85.15%> (+3.44%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/SiennaPRASInterface.jl 100.00% <100.00%> (ø)
src/formulation_definitions.jl 88.23% <88.23%> (ø)
src/PowerSystems2PRAS.jl 83.05% <83.91%> (+0.69%) ⬆️

... and 2 files with indirect coverage changes

@josephmckinsey josephmckinsey changed the title WIP: Use formulations as an interface Use formulations as an interface Feb 11, 2025
@josephmckinsey josephmckinsey marked this pull request as ready for review February 11, 2025 23:20
# Check if any GeometricDistributionForcedOutage objects exist in the System
outages = PSY.get_supplemental_attributes(PSY.GeometricDistributionForcedOutage, sys)
function add_default_data!(sys::PSY.System)
@warn "No forced outage data available in the Sienna/Data PowerSystems System. Using generic outage data ..."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can take this out of this function and make something more clean where we pass the CSV file? I don't really like that we rely on a CONST file definition for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying we should let the user pass a CSV with default values? If that is the case, we might need to specify a set of necessary columns or something to map from custom column names to the columns the parser expects.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I made it a parameter with a default value of the const value. Do we want to make this function public or exported? I didn't do that yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to make this function public. It is up to the user to format the CSV in a way we can ingest it. We might want to add column names and description of this CSV to documentation. Before we parse, we want to make sure the CSV has the necessary columns. Happy to discuss if you have any questions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could probably handle this in a separate PR. #64

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets not make CSV parser a default thing here. The data and models are separate. @scdhulipala if you want to make a data model to parse outages and put it in PSY that's what we need to do.


# FIXME
for (idx, region) in enumerate(regions)
reg_load_comps =
get_available_components_in_aggregation_topology(PSY.StaticLoad, sys, region)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be aware that a system can contain other load types. This should probably support all subtypes of ElectricLoad

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not plan on messing with load or lines yet #61

@@ -396,33 +302,29 @@ function generate_pras_system(
λ_gen = Matrix{Float64}(undef, n_gen, s2p_meta.N)
μ_gen = Matrix{Float64}(undef, n_gen, s2p_meta.N)

#FIXME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josephmckinsey is this pending? Do we need to add an issue or a comment to this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This needs to be fixed but this logic works for now. We will implement the component map solution we discussed for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open an issue for this please

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#60

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #60

@@ -434,12 +336,12 @@ function generate_pras_system(
PSY.get_time_series_multiple(
g,
s2p_meta.filter_func,
name="max_active_power",
name=component_to_formulation[g].max_active_power,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use getter function for these fields?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

PSY.get_prime_mover_type(g) == PSY.PrimeMovers.PVe
)
)
if haskey(PSY.get_ext(g), "region_gens")
reg_gens_DA = PSY.get_ext(g)["region_gens"]
gen_cap_array[idx, :] =
round.(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop gives me a headache to follow what's up. We should abstract this into a function because it impossible to check if the logic is reasonable. @daniel-thom you might have some insight on the use of these functions for time series here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. We will not need this logic if we implement the component map solution for lumping.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a mix of loop for the weird indexing PRAS needs and the lumped renewable generation. Later, I think we should spit the indexing into its own logic somehow later.

Apply HydroEnergyReservoir Formulation to fill in a row of a PRAS Matrix.
Views should be passed in for all arrays.
"""
function assign_to_gen_stor_matrices!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is also a bit unclear. Needs more comments to understand why some operations are happening

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add more comments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I just pretty much copy pasted here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#65

src/PowerSystems2PRAS.jl Outdated Show resolved Hide resolved
Copy link
Member

@jd-lara jd-lara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the design for the formulations looks ok to me. There are several methods that grab time series that definitely need to be put into separate methods.

There are some parts that also need more comments because I can't really review the logic without knowing what I am looking at.

@@ -50,6 +50,15 @@ export add_csv_time_series!
export add_csv_time_series_single_stage!
export make_generator_outage_draws!

export GeneratorFormulation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment for all the formulation names, to follow PSI convention, can we call these GeneratorResourceAdequacy, etc. Do we need to call it PRASProblemTemplate? RATemplate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to GeneratorPRAS but have a AbstractRAFormulation. The generator, generator storage, storage classification is very specific to PRAS.

src/SiennaPRASInterface.jl Outdated Show resolved Hide resolved
src/SiennaPRASInterface.jl Show resolved Hide resolved

Objects in Sienna that behave like storage are mapped to storage in PRAS.
"""
struct StorageFormulation <: AbstractPRASFormulation end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add carry over efficiency to the formulation because currently we just assume it is 1.0?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call this LossLess formulation

src/formulation_definitions.jl Outdated Show resolved Hide resolved
"""
Check whether a DevicePRASModel applies to a given type
"""
function appliestodevice(::DevicePRASModel{D}, ::Type{T}) where {D, T}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow how this doesn't allow PSY.EnergyReservoirStorage to not have a GeneratorFormulation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can totally do that in this framework. This only tells you about the PSY.Device subtype D and has nothing about the formulation type itself. Unless we specify some relation between the type parameters in DevicePRASModel, then it will error when it tries to actually apply a model.

for (idx, region) in enumerate(regions)
reg_ren_comps = filter(
x -> haskey(component_to_formulation, x),
get_available_components_in_aggregation_topology(PSY.RenewableGen, sys, region),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this again get_available_components_in_aggregation_topology ? Or can we get all components from the Dict with this formulation and filter for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we have a function to group by aggregation topology from a dictionary yet. It might be appropriate, but I didn't want to get into that here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#62

Apply HydroEnergyReservoir Formulation to fill in a row of a PRAS Matrix.
Views should be passed in for all arrays.
"""
function assign_to_gen_stor_matrices!(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add more comments.

src/PowerSystems2PRAS.jl Outdated Show resolved Hide resolved
src/PowerSystems2PRAS.jl Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants