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

RFC: Define AncillaryServices types to support different sets of ancillary services #33

Closed
wants to merge 1 commit into from

Conversation

BSnelling
Copy link
Member

@BSnelling BSnelling commented Oct 19, 2022

Different markets have different sets of ancillary services and related ancillary service requirements.

Let's assume we want one SystemDA type to support multiple markets. The proposal here is to define a type hierarchy for ancillary services that allows us to represent the different ancillary service markets. But this causes a potential difficulty with the accessor functions, because instances of System with different types of AncillaryServices won't have certain ancillary service fields.

The solution to that issue is to have a single accessor for the user to get the ancillary services from the System. Then each AncillaryServices subtype has its own set of accessors.

Example: accessing the regulation offers

# currently - a single accessor acting on `System`
get_regulation_offers(system::System) = system.generator_time_series.regulation_offers

# proposal - the user needs two accessors - one to get AncillaryServices - then another to get the regulation offers
get_ancillary_services(system::System) = system.generator_time_series.ancillary_services
get_regulation_offers(as_offers::FourServices) = as_offers.regulation_offers

@@ -2,6 +2,9 @@ const MARKET_WIDE_ZONE = -9999
const BidName = InlineString31
const ZoneNum = Int64


abstract type SystemRequirements end
Copy link
Member Author

Choose a reason for hiding this comment

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

Different sets of ancillary services have different sets of requirements associated with them. So this needs to become a type hierarchy too.

Copy link
Member

Choose a reason for hiding this comment

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

Since the requirements and ancillary services are linked across multiple fields, would it be better to develop the new market as a separate subtype of System to avoid major breaking changes to the package?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm debating the pros and cons of that. Having a separate System subtype would keep us free from major breaking changes while developing the second market. On the other hand it's probably good for us to think about common parts of the API from the start, because it'll save us time and energy later when we come to reconcile the two.

off_supplemental_offers::KeyedArray{Union{Missing, Float64}, 2}
end

struct FiveServices <: AncillaryServices
Copy link
Member Author

Choose a reason for hiding this comment

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

FourServices and FiveServices are placeholder names. We can think of something more descriptive.

loads::KeyedArray{Float64, 2}
fixed_loads::KeyedArray{Float64, 2}

load_services::Union{Missing, LoadTimeSeries}
Copy link
Member Author

Choose a reason for hiding this comment

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

Another addition we'll have to think about. One market has fixed_loads and no load services, the other has no fixed loads and does have load services which submit ancillary service bids.

Copy link
Member

Choose a reason for hiding this comment

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

Are we likely to get into a situation where we need both? Should we unify them into one field or are the use cases too different?

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe we won't need both in the new market, they don't have any fixed loads. I am hesitant to unify them though, just because I think they are quite different concepts. I'll need input from Power Systems to be sure on that.

@@ -30,3 +30,13 @@ function get_bids(system::SystemDA, type_of_bid::Symbol)
return getproperty(system, type_of_bid)
end
end

@deprecate get_regulation_offers(system::System) get_regulation_offers(get_ancillary_services(system))
Copy link
Member Author

Choose a reason for hiding this comment

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

We might not want to bother deprecating. I'm not sure it would work well given the name of the function has stayed the same but it acts on a different type in the new design.

Copy link
Member

Choose a reason for hiding this comment

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

Deprecating based on types works just fine. I wouldn't worry too much about getters during an RFC PR though since things might change drastically

@@ -236,6 +249,39 @@ end

###### Time Series types ######

abstract type AncillaryServices end
Copy link
Member

Choose a reason for hiding this comment

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

As opposed to creating different AncillaryServices types, it might be better to use a more flexible structure. Something like a Dict would allow us to account for markets with any number of services. We could key it with a with a suitable enum or group of singleton types if we needed to limit the possible keys and switch to a more flexible get_offers(::System, ::Service) getter.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting, my concern with a Dict/Dictionary was that the keys would be too unrestricted but using enums or singletons would get around that. I think having the keys defined like that would help a lot with the readability and reliability of the formulation implementation too.

Copy link
Member

Choose a reason for hiding this comment

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

Update: After a chat with our researchers, it seems like we might not need to worry about a separate set of services once we generalize names/practices across markets.

Copy link
Member

Choose a reason for hiding this comment

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

Update to the update: after further research these are indeed subject to change so probably the Dict solution is the safer way to go.

loads::KeyedArray{Float64, 2}
fixed_loads::KeyedArray{Float64, 2}

load_services::Union{Missing, LoadTimeSeries}
Copy link
Member

Choose a reason for hiding this comment

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

Are we likely to get into a situation where we need both? Should we unify them into one field or are the use cases too different?

@@ -22,7 +25,17 @@ Base.@kwdef struct Zone
"Zonal good utility practice requirement (regulation + spinning) (pu)"
good_utility::Float64
end
const Zones = Dictionary{ZoneNum, Zone}

struct ZonalRequirements <: SystemRequirements
Copy link
Member

Choose a reason for hiding this comment

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

This split seems off to me. It seems like we might want to view the new market as a single-zone market and either split the set of service requirements into separate types or put them in a more variable structure.

We can still split out Zonal and SystemWide into types, it just feels like we're combining layout and service requirements. We also already have zones in our generators so assuming everything is one big zone could cut down on some workarounds there.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point. I see how this mixes two different concepts. I think changing it so the new market can be treated as a single zone makes sense. We can ditch the different layout concept for now.

@@ -2,6 +2,9 @@ const MARKET_WIDE_ZONE = -9999
const BidName = InlineString31
const ZoneNum = Int64


abstract type SystemRequirements end
Copy link
Member

Choose a reason for hiding this comment

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

Since the requirements and ancillary services are linked across multiple fields, would it be better to develop the new market as a separate subtype of System to avoid major breaking changes to the package?

@@ -30,3 +30,13 @@ function get_bids(system::SystemDA, type_of_bid::Symbol)
return getproperty(system, type_of_bid)
end
end

@deprecate get_regulation_offers(system::System) get_regulation_offers(get_ancillary_services(system))
Copy link
Member

Choose a reason for hiding this comment

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

Deprecating based on types works just fine. I wouldn't worry too much about getters during an RFC PR though since things might change drastically

Copy link
Member

@raphaelsaavedra raphaelsaavedra left a comment

Choose a reason for hiding this comment

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

I'm not particularly a fan of this approach because it seems too rigid. With this we'd have a services type for MISO and one for ERCOT, and we'll probably have one for ISO X once we implement it too, etc. Not to mention, names such as SystemWideRequirements make it seem that we're dealing with a general thing, but it is very much ISO-specific. For example, after these changes Zone still contains regulation, operating_reserve, good_utility which is MISO-specific.

I wonder, why not have it flexible by allowing custom service names to be input? This way we can have the user define what services are there and what are their requirements. For example, let the requirements be defined as a collection (Dict, Array, w/e) of :my_reserve => [(Zone1, 5.0), (Zone2, 10.0), (Zone3, 0.0), (SystemWide, 20.0)] (the data structure doesn't matter here).

@BSnelling BSnelling closed this Nov 7, 2022
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