-
Notifications
You must be signed in to change notification settings - Fork 0
Per-type service models and merged sparse/dense reserve containers #141
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
Changes from all commits
af038ff
6a59348
3c3ade4
4b5943b
5e8455d
17c257e
4069022
12b3835
3960e3b
8ec2575
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 |
|---|---|---|
|
|
@@ -13,10 +13,9 @@ function _check_service_formulation(::Type{D}) where {D} | |
| end | ||
|
|
||
| """ | ||
| Establishes the model for a particular service specified by type. The optional | ||
| `service_name` positional argument assigns the model to a service with that name in the | ||
| template. Uses the keyword argument `feedforwards` to enable passing values between | ||
| operation models at simulation time. | ||
| Establishes the model for all services of a particular type. A `ServiceModel` represents | ||
| every service of its type in the system. Uses the keyword argument `feedforwards` to | ||
| enable passing values between operation models at simulation time. | ||
|
|
||
| # Arguments | ||
|
|
||
|
|
@@ -34,30 +33,29 @@ reserves = ServiceModel(PSY.VariableReserve{PSY.ReserveUp}, RangeReserve) | |
| mutable struct ServiceModel{D <: IS.InfrastructureSystemsComponent, B} | ||
| # Heterogeneous by design: concrete Vector of the abstract type, not a UnionAll field. | ||
| feedforwards::Vector{AbstractAffectFeedforward} | ||
| service_name::String | ||
| use_slacks::Bool | ||
| duals::Vector{DataType} | ||
| time_series_names::Dict{Type{<:TimeSeriesParameter}, String} | ||
| attributes::Dict{String, Any} | ||
| # Per service: service name -> device type -> contributing devices. | ||
| contributing_devices_map::Dict{ | ||
| Type{<:IS.InfrastructureSystemsComponent}, | ||
| Vector{<:IS.InfrastructureSystemsComponent}, | ||
| String, | ||
| Dict{DataType, Vector{<:IS.InfrastructureSystemsComponent}}, | ||
| } | ||
| subsystem::Union{Nothing, String} | ||
| # Maps outage UUIDs to monitored components grouped by device type. PNM indexes DF matrices with UUIDs. | ||
| outages::Dict{Base.UUID, Dict{DataType, Set{String}}} | ||
| function ServiceModel( | ||
| ::Type{D}, | ||
| ::Type{B}, | ||
| service_name::String; | ||
| ::Type{B}; | ||
| use_slacks = false, | ||
| feedforwards = Vector{AbstractAffectFeedforward}(), | ||
| duals = Vector{DataType}(), | ||
| time_series_names = get_default_time_series_names(D, B), | ||
| attributes = Dict{String, Any}(), | ||
| contributing_devices_map = Dict{ | ||
| Type{<:IS.InfrastructureSystemsComponent}, | ||
| Vector{<:IS.InfrastructureSystemsComponent}, | ||
| String, | ||
| Dict{DataType, Vector{<:IS.InfrastructureSystemsComponent}}, | ||
| }(), | ||
| ) where {D <: IS.InfrastructureSystemsComponent, B} | ||
| attributes_for_model = get_default_attributes(D, B) | ||
|
|
@@ -69,7 +67,6 @@ mutable struct ServiceModel{D <: IS.InfrastructureSystemsComponent, B} | |
| _check_service_formulation(B) | ||
| new{D, B}( | ||
| convert(Vector{AbstractAffectFeedforward}, feedforwards), | ||
| service_name, | ||
| use_slacks, | ||
| duals, | ||
| time_series_names, | ||
|
|
@@ -88,53 +85,37 @@ get_formulation( | |
| ::ServiceModel{D, B}, | ||
| ) where {D <: IS.InfrastructureSystemsComponent, B} = B | ||
| get_feedforwards(m::ServiceModel) = m.feedforwards | ||
| get_service_name(m::ServiceModel) = m.service_name | ||
| get_use_slacks(m::ServiceModel) = m.use_slacks | ||
| get_duals(m::ServiceModel) = m.duals | ||
| get_time_series_names(m::ServiceModel) = m.time_series_names | ||
| get_attributes(m::ServiceModel) = m.attributes | ||
| get_attribute(m::ServiceModel, key::String) = get(m.attributes, key, nothing) | ||
| # Whole nested map: service name -> device type -> contributing devices. | ||
| get_contributing_devices_map(m::ServiceModel) = m.contributing_devices_map | ||
| get_contributing_devices_map(m::ServiceModel, key) = | ||
| get(m.contributing_devices_map, key, nothing) | ||
| # Returned for a service with no entry in the map. Callers treat it as read-only (shared). | ||
| const _EMPTY_CONTRIBUTING_DEVICES_MAP = | ||
| Dict{DataType, Vector{<:IS.InfrastructureSystemsComponent}}() | ||
| # One service's inner `device type -> devices` map (the empty const if the service is absent). | ||
| get_contributing_devices_map(m::ServiceModel, service_name::AbstractString) = | ||
| get(m.contributing_devices_map, service_name, _EMPTY_CONTRIBUTING_DEVICES_MAP) | ||
| # All contributing devices across ALL services (flatten the nested map). | ||
| # TODO(services stability): flattening across device types yields a Vector whose element | ||
| # type widens to the abstract common ancestor when a service has more than one contributing | ||
| # device type, so downstream builders lose type stability. Revisit by iterating the | ||
| # per-(device type) map groups (each concretely typed) instead of flattening. | ||
| get_contributing_devices(m::ServiceModel) = | ||
|
Collaborator
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. Flattening makes this heterogeneous--a mix of different subtypes of
Contributor
Author
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. This is true if a service have different types for contributing devices (e.g. Hydro + Thermal). However, if all the contributing devices are the same type, then it narrows to a concrete I asked Claude about this and basically:
After some discussion I think this is a temporary solution so we don't implement ton of complexity here Two spots flatten the device map for no good reason, and fixing them is pure allocation removal with no behavior or stability question:
What you want is to modify our flatten map and use the map per type as follows:
for (device_type, devices) in get_contributing_devices_map(model, service_name) # devices::Vector{ThermalStandard}, etc.
add_service_variables!(container, ActivePowerReserveVariable, service, devices, F) # D binds concretely
endI also asked Claude about why this is a major change and basically:
I agree that this is better it could be changing too much the structure that we have for slicing sparse containers. What do you think @jd-lara
Member
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. if we want to fix this to be properly type stable we need to change PSY to have a mapping by type correctly. I think we need to get this logic correct and then make the performance improvement work. I prefer we make it work and open an issue |
||
| [z for x in values(m.contributing_devices_map) for z in x] | ||
| [z for inner in values(m.contributing_devices_map) for x in values(inner) for z in x] | ||
| # One service's contributing devices (flattened Vector). | ||
| # TODO(services stability): same multi-device-type widening as the all-services flatten | ||
| # above; revisit to iterate the concretely-typed per-device-type map groups. | ||
| get_contributing_devices(m::ServiceModel, service_name::AbstractString) = | ||
| [z for x in values(get_contributing_devices_map(m, service_name)) for z in x] | ||
| get_subsystem(m::ServiceModel) = m.subsystem | ||
| get_outages(m::ServiceModel) = m.outages | ||
|
|
||
| set_subsystem!(m::ServiceModel, id::String) = m.subsystem = id | ||
|
|
||
| function ServiceModel( | ||
| service_type::Type{D}, | ||
| formulation_type::Type{B}; | ||
| use_slacks = false, | ||
| feedforwards = Vector{AbstractAffectFeedforward}(), | ||
| duals = Vector{DataType}(), | ||
| time_series_names = get_default_time_series_names(D, B), | ||
| attributes = get_default_attributes(D, B), | ||
| ) where {D <: IS.InfrastructureSystemsComponent, B} | ||
| # If more attributes are used later, move free form string to const and organize | ||
| # attributes | ||
| attributes_for_model = get_default_attributes(D, B) | ||
| for (k, v) in attributes | ||
| attributes_for_model[k] = v | ||
| end | ||
| if !haskey(attributes_for_model, "aggregated_service_model") | ||
| push!(attributes_for_model, "aggregated_service_model" => true) | ||
| end | ||
| return ServiceModel( | ||
| service_type, | ||
| formulation_type, | ||
| NO_SERVICE_NAME_PROVIDED; | ||
| use_slacks, | ||
| feedforwards, | ||
| duals, | ||
| time_series_names, | ||
| attributes = attributes_for_model, | ||
| ) | ||
| end | ||
|
|
||
| function set_model!(dict::Dict, key::Tuple{String, Symbol}, model::ServiceModel) | ||
| function set_model!(dict::Dict, key::Symbol, model::ServiceModel) | ||
| if haskey(dict, key) | ||
| @warn "Overwriting $(key) existing model" | ||
| end | ||
|
|
@@ -146,6 +127,6 @@ function set_model!( | |
| dict::Dict, | ||
| model::ServiceModel{D, B}, | ||
| ) where {D <: IS.InfrastructureSystemsComponent, B} | ||
| set_model!(dict, (get_service_name(model), Symbol(D)), model) | ||
| set_model!(dict, Symbol(D), model) | ||
| return | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
Do a type stability audit on these nested loops. Thoughts:
duals::Vector{DataType}, so all the compiler knows isconstraint_type::DataType. Can we give it something more specific?keyget_entry_type(key): I suspect that's justconstraint_typeOther ways to make this more type stable and compiler-friendly....maintain a list of all meta's for each
(constraint_type, component_type)combination? Then we could loop over those (a compile time value) and skip those that aren't present, instead of accumulating the ones that are present via_existing_constraint_keys(a runtime value).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.
You are right about the get_entry_type. I will fix that.
I ask Claude about the instability and the warntype and:
I agree with Claude here, the effort is larger than the sparse refactoring here. Are you ok with this @jd-lara?
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.
This is not a performance critical operation, it is more of a convenience and for now works correctly.