diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index 32f8a6d23e..9d61947807 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -18,6 +18,8 @@ export AggregationTopology export Area export LoadZone export get_aggregation_topology_accessor +export SupplementalAttribute +export GeographicInfo export Component export Device @@ -204,6 +206,11 @@ export PriorityCurrentLimiter export Source export PeriodicVariableSource +# Outages +export Outage +export ForcedOutage +export PlannedOutage + export Service export AbstractReserve export Reserve @@ -241,6 +248,7 @@ export ForecastCache export StaticTimeSeriesCache export NormalizationFactor export NormalizationTypes +export TimeSeriesCounts export get_dynamic_components @@ -282,6 +290,13 @@ export get_forecast_horizon export get_forecast_initial_timestamp export get_forecast_interval export get_forecast_window_count +export add_supplemental_attribute! +export remove_supplemental_attribute! +export remove_supplemental_attributes! +export get_supplemental_attribute +export get_supplemental_attributes +export has_supplemental_attributes +export iterate_supplemental_attributes export get_time_series export get_time_series_array export get_time_series_resolution @@ -318,6 +333,8 @@ export CompressionTypes export get_bus_numbers export get_name export set_name! +export get_component_uuids +export get_supplemental_attributes_container export get_description export set_description! export get_base_power @@ -420,15 +437,18 @@ import InfrastructureSystems: Scenarios, ForecastCache, StaticTimeSeriesCache, + TimeSeriesCounts, InfrastructureSystemsComponent, InfrastructureSystemsType, InfrastructureSystemsInternal, + SupplementalAttribute, DeviceParameter, FlattenIteratorWrapper, LazyDictFromIterator, DataFormatError, InvalidRange, InvalidValue, + GeographicInfo, copy_time_series!, get_count, get_data, @@ -436,6 +456,10 @@ import InfrastructureSystems: get_resolution, get_window, get_name, + get_component_uuids, + get_supplemental_attribute, + get_supplemental_attributes, + get_supplemental_attributes_container, set_name!, get_internal, set_internal!, @@ -451,6 +475,7 @@ import InfrastructureSystems: get_percentiles, # Probabilistic Forecast Exports get_next_time_series_array!, get_next_time, + has_supplemental_attributes, get_units_info, set_units_info!, to_json, @@ -548,6 +573,9 @@ include("models/dynamic_branch.jl") include("models/supplemental_constructors.jl") include("models/supplemental_accessors.jl") +# Supplemental attributes +include("outages.jl") + # Definitions of PowerSystem include("base.jl") include("data_format_conversions.jl") diff --git a/src/base.jl b/src/base.jl index 67f428295d..85fac43f8f 100644 --- a/src/base.jl +++ b/src/base.jl @@ -210,13 +210,13 @@ function System(file_path::AbstractString; assign_new_uuids = false, kwargs...) ) runchecks && check(sys) if assign_new_uuids - IS.assign_new_uuid!(sys) + IS.assign_new_uuid_internal!(sys) for component in get_components(Component, sys) - IS.assign_new_uuid!(component) + assign_new_uuid!(sys, component) end for component in IS.get_masked_components(InfrastructureSystemsComponent, sys.data) - IS.assign_new_uuid!(component) + assign_new_uuid!(sys, component) end # Note: this does not change UUIDs for time series data because they are # shared with components. @@ -914,11 +914,24 @@ function get_components( return IS.get_components(T, sys.data, filter_func) end -# These are helper functions for debugging problems. -# Searches components linearly, and so is slow compared to the other get_component functions +""" +Return a vector of components that are attached to the supplemental attribute. +""" +function get_components(sys::System, attribute::SupplementalAttribute) + return IS.get_components(sys.data, attribute) +end + +""" +Get the component by UUID. +""" get_component(sys::System, uuid::Base.UUID) = IS.get_component(sys.data, uuid) get_component(sys::System, uuid::String) = IS.get_component(sys.data, Base.UUID(uuid)) +""" +Change the UUID of a component. +""" +assign_new_uuid!(sys::System, x::Component) = IS.assign_new_uuid!(sys.data, x) + function _get_components_by_name(abstract_types, data::IS.SystemData, name::AbstractString) _components = [] for subtype in abstract_types @@ -1258,6 +1271,104 @@ function transform_single_time_series!(sys::System, horizon::Int, interval::Date return end +""" +Add a supplemental attribute to the component. The attribute may already be attached to a +different component. +""" +function add_supplemental_attribute!( + sys::System, + component::Component, + attribute::IS.SupplementalAttribute, +) + return IS.add_supplemental_attribute!(sys.data, component, attribute) +end + +""" +Remove the supplemental attribute from the component. The attribute will be removed from the +system if it is not attached to any other component. +""" +function remove_supplemental_attribute!( + sys::System, + component::Component, + attribute::IS.SupplementalAttribute, +) + return IS.remove_supplemental_attribute!(sys.data, component, attribute) +end + +""" +Remove the supplemental attribute from the system and all attached components. +""" +function remove_supplemental_attribute!(sys::System, attribute::IS.SupplementalAttribute) + return IS.remove_supplemental_attribute!(sys.data, attribute) +end + +""" +Remove all supplemental attributes with the given type from the system. +""" +function remove_supplemental_attributes!( + ::Type{T}, + sys::System, +) where {T <: IS.SupplementalAttribute} + return IS.remove_supplemental_attributes!(T, sys.data) +end + +""" +Returns an iterator of supplemental attributes. T can be concrete or abstract. +Call collect on the result if an array is desired. + +# Examples +```julia +iter = get_supplemental_attributes(ForcedOutage, sys) +iter = get_supplemental_attributes(Outage, sys) +iter = get_supplemental_attributes(x -> x.forced_outage_rate >= 0.5, ForcedOutage, sys) +outages = get_supplemental_attributes(ForcedOutage, sys) do outage + x.forced_outage_rate >= 0.5 +end +outages = collect(get_supplemental_attributes(ForcedOutage, sys)) +``` + +See also: [`iterate_supplemental_attributes`](@ref) +""" +function get_supplemental_attributes( + filter_func::Function, + ::Type{T}, + sys::System, +) where {T <: IS.SupplementalAttribute} + return IS.get_supplemental_attributes(filter_func, T, sys.data) +end + +function get_supplemental_attributes( + ::Type{T}, + sys::System, +) where {T <: IS.SupplementalAttribute} + return IS.get_supplemental_attributes(T, sys.data) +end + +""" +Return the supplemental attribute with the given uuid. + +Throws ArgumentError if the attribute is not stored. +""" +function get_supplemental_attribute(sys::System, uuid::Base.UUID) + return IS.get_supplemental_attribute(sys.data, uuid) +end + +""" +Iterates over all supplemental_attributes. + +# Examples +```julia +for supplemental_attribute in iterate_supplemental_attributes(sys) + @show supplemental_attribute +end +``` + +See also: [`get_supplemental_attributes`](@ref) +""" +function iterate_supplemental_attributes(sys::System) + return IS.iterate_supplemental_attributes(sys.data) +end + """ Sanitize component values. """ @@ -1507,6 +1618,11 @@ function deserialize_components!(sys::System, raw) end # Run in order based on type composition. + # Bus and AGC instances can have areas and LoadZones. + # Most components have buses. + # Static injection devices can contain dynamic injection devices. + # StaticInjectionSubsystem instances have StaticInjection subcomponents. + # RegulationDevice instances have one StaticInjection subcomponent. deserialize_and_add!(; include_types = [Area, LoadZone]) deserialize_and_add!(; include_types = [AGC]) deserialize_and_add!(; include_types = [Bus]) @@ -1514,12 +1630,10 @@ function deserialize_components!(sys::System, raw) include_types = [Arc, Service], skip_types = [StaticReserveGroup], ) - deserialize_and_add!(; include_types = [Branch], skip_types = [DynamicBranch]) + deserialize_and_add!(; include_types = [Branch]) deserialize_and_add!(; include_types = [DynamicBranch]) - # Static injection devices can contain dynamic injection devices. deserialize_and_add!(; include_types = [StaticReserveGroup, DynamicInjection]) - # StaticInjectionSubsystem instances have StaticInjection subcomponents. - deserialize_and_add!(; skip_types = [StaticInjectionSubsystem]) + deserialize_and_add!(; skip_types = [StaticInjectionSubsystem, RegulationDevice]) deserialize_and_add!() for subsystem in get_components(StaticInjectionSubsystem, sys) @@ -1529,6 +1643,10 @@ function deserialize_components!(sys::System, raw) IS.mask_component!(sys.data, subcomponent) end end + + for component in get_components(RegulationDevice, sys) + IS.mask_component!(sys.data, component.device) + end end """ @@ -1731,9 +1849,7 @@ function handle_component_addition!(sys::System, component::RegulationDevice; kw copy_time_series!(component, component.device) if !isnothing(get_component(typeof(component.device), sys, get_name(component.device))) # This will not be true during deserialization, and so won't run then. - remove_component!(sys, component.device) - # The line above removed the component setting so needs to be added back - set_units_setting!(component.device, component.internal.units_info) + IS.mask_component!(sys.data, component.device; remove_time_series = true) end return end @@ -1942,7 +2058,7 @@ function convert_component!( InfrastructureSystems.TimeSeriesContainer(), deepcopy(line.internal), ) - IS.assign_new_uuid!(line) + assign_new_uuid!(sys, line) add_component!(sys, new_line) copy_time_series!(new_line, line) remove_component!(sys, line) @@ -1951,7 +2067,7 @@ end """ Converts a MonitoredLine component to a Line component and replaces the original in the -system +system. """ function convert_component!( sys::System, @@ -1984,7 +2100,7 @@ function convert_component!( InfrastructureSystems.TimeSeriesContainer(), deepcopy(line.internal), ) - IS.assign_new_uuid!(line) + assign_new_uuid!(sys, line) add_component!(sys, new_line) copy_time_series!(new_line, line) remove_component!(sys, line) @@ -1993,7 +2109,7 @@ end """ Converts a PowerLoad component to a StandardLoad component and replaces the original in the -system. Does not set any fields in StandardLoad that lack a PowerLoad equivalent +system. Does not set any fields in StandardLoad that lack a PowerLoad equivalent. """ function convert_component!( sys::System, @@ -2015,7 +2131,7 @@ function convert_component!( services = Device[], time_series_container = InfrastructureSystems.TimeSeriesContainer(), ) - IS.assign_new_uuid!(new_load) + assign_new_uuid!(sys, old_load) add_component!(sys, new_load) copy_time_series!(new_load, old_load) for service in get_services(old_load) @@ -2048,7 +2164,9 @@ function _validate_or_skip!(sys, component, skip_validation) end """ -Return a tuple of counts of components with time series and total time series and forecasts. +Return an instance of TimeSeriesCounts. + +See also: [`TimeSeriesCounts`](@ref) """ get_time_series_counts(sys::System) = IS.get_time_series_counts(sys.data) diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index c9bd1765fd..1641da6ad0 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -438,6 +438,13 @@ "null_value": "Dict{String, Any}()", "default": "Dict{String, Any}()" }, + { + "name": "supplemental_attributes_container", + "comment": "container for supplemental attributes", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, { "name": "internal", "comment": "power system internal reference, do not modify", @@ -510,6 +517,13 @@ "null_value": "Dict{String, Any}()", "default": "Dict{String, Any}()" }, + { + "name": "supplemental_attributes_container", + "comment": "container for supplemental attributes", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, { "name": "internal", "comment": "power system internal reference, do not modify", @@ -3913,6 +3927,13 @@ "data_type": "InfrastructureSystems.TimeSeriesContainer", "default": "InfrastructureSystems.TimeSeriesContainer()" }, + { + "name": "supplemental_attributes_container", + "comment": "container for supplemental attributes", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, { "name": "internal", "comment": "power system internal reference, do not modify", diff --git a/src/models/generated/ACBus.jl b/src/models/generated/ACBus.jl index 087086aa95..fc0aa8961c 100644 --- a/src/models/generated/ACBus.jl +++ b/src/models/generated/ACBus.jl @@ -16,6 +16,7 @@ This file is auto-generated. Do not edit. area::Union{Nothing, Area} load_zone::Union{Nothing, LoadZone} ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer internal::InfrastructureSystemsInternal end @@ -32,6 +33,7 @@ A power-system bus. - `area::Union{Nothing, Area}`: the area containing the bus - `load_zone::Union{Nothing, LoadZone}`: the load zone containing the bus - `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: container for supplemental attributes - `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify """ mutable struct ACBus <: Bus @@ -54,11 +56,13 @@ mutable struct ACBus <: Bus "the load zone containing the bus" load_zone::Union{Nothing, LoadZone} ext::Dict{String, Any} + "container for supplemental attributes" + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer "power system internal reference, do not modify" internal::InfrastructureSystemsInternal - function ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, internal, ) - (number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, internal, ) = check_bus_params( + function ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, internal, ) + (number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, internal, ) = check_bus_params( number, name, bustype, @@ -69,18 +73,19 @@ mutable struct ACBus <: Bus area, load_zone, ext, + supplemental_attributes_container, internal, ) - new(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, internal, ) + new(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, internal, ) end end -function ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), ) - ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, InfrastructureSystemsInternal(), ) +function ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), ) + ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, InfrastructureSystemsInternal(), ) end -function ACBus(; number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) - ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, internal, ) +function ACBus(; number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), internal=InfrastructureSystemsInternal(), ) + ACBus(number, name, bustype, angle, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, internal, ) end # Constructor for demo purposes; non-functional. @@ -96,6 +101,7 @@ function ACBus(::Nothing) area=nothing, load_zone=nothing, ext=Dict{String, Any}(), + supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), ) end @@ -119,6 +125,8 @@ get_area(value::ACBus) = value.area get_load_zone(value::ACBus) = value.load_zone """Get [`ACBus`](@ref) `ext`.""" get_ext(value::ACBus) = value.ext +"""Get [`ACBus`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::ACBus) = value.supplemental_attributes_container """Get [`ACBus`](@ref) `internal`.""" get_internal(value::ACBus) = value.internal @@ -140,3 +148,5 @@ set_area!(value::ACBus, val) = value.area = val set_load_zone!(value::ACBus, val) = value.load_zone = val """Set [`ACBus`](@ref) `ext`.""" set_ext!(value::ACBus, val) = value.ext = val +"""Set [`ACBus`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::ACBus, val) = value.supplemental_attributes_container = val diff --git a/src/models/generated/DCBus.jl b/src/models/generated/DCBus.jl index 7b260bdd5e..929306b442 100644 --- a/src/models/generated/DCBus.jl +++ b/src/models/generated/DCBus.jl @@ -14,6 +14,7 @@ This file is auto-generated. Do not edit. area::Union{Nothing, Area} load_zone::Union{Nothing, LoadZone} ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer internal::InfrastructureSystemsInternal end @@ -28,6 +29,7 @@ A power-system DC bus. - `area::Union{Nothing, Area}`: the area containing the DC bus - `load_zone::Union{Nothing, LoadZone}`: the load zone containing the DC bus - `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: container for supplemental attributes - `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify """ mutable struct DCBus <: Bus @@ -46,16 +48,18 @@ mutable struct DCBus <: Bus "the load zone containing the DC bus" load_zone::Union{Nothing, LoadZone} ext::Dict{String, Any} + "container for supplemental attributes" + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer "power system internal reference, do not modify" internal::InfrastructureSystemsInternal end -function DCBus(number, name, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), ) - DCBus(number, name, magnitude, voltage_limits, base_voltage, area, load_zone, ext, InfrastructureSystemsInternal(), ) +function DCBus(number, name, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), ) + DCBus(number, name, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, InfrastructureSystemsInternal(), ) end -function DCBus(; number, name, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) - DCBus(number, name, magnitude, voltage_limits, base_voltage, area, load_zone, ext, internal, ) +function DCBus(; number, name, magnitude, voltage_limits, base_voltage, area=nothing, load_zone=nothing, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), internal=InfrastructureSystemsInternal(), ) + DCBus(number, name, magnitude, voltage_limits, base_voltage, area, load_zone, ext, supplemental_attributes_container, internal, ) end # Constructor for demo purposes; non-functional. @@ -69,6 +73,7 @@ function DCBus(::Nothing) area=nothing, load_zone=nothing, ext=Dict{String, Any}(), + supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), ) end @@ -88,6 +93,8 @@ get_area(value::DCBus) = value.area get_load_zone(value::DCBus) = value.load_zone """Get [`DCBus`](@ref) `ext`.""" get_ext(value::DCBus) = value.ext +"""Get [`DCBus`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::DCBus) = value.supplemental_attributes_container """Get [`DCBus`](@ref) `internal`.""" get_internal(value::DCBus) = value.internal @@ -105,3 +112,5 @@ set_area!(value::DCBus, val) = value.area = val set_load_zone!(value::DCBus, val) = value.load_zone = val """Set [`DCBus`](@ref) `ext`.""" set_ext!(value::DCBus, val) = value.ext = val +"""Set [`DCBus`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::DCBus, val) = value.supplemental_attributes_container = val diff --git a/src/models/generated/ThermalStandard.jl b/src/models/generated/ThermalStandard.jl index d6f99d7baa..9aefa17b33 100644 --- a/src/models/generated/ThermalStandard.jl +++ b/src/models/generated/ThermalStandard.jl @@ -27,6 +27,7 @@ This file is auto-generated. Do not edit. dynamic_injector::Union{Nothing, DynamicInjection} ext::Dict{String, Any} time_series_container::InfrastructureSystems.TimeSeriesContainer + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer internal::InfrastructureSystemsInternal end @@ -54,6 +55,7 @@ Data Structure for thermal generation technologies. - `dynamic_injector::Union{Nothing, DynamicInjection}`: corresponding dynamic injection device - `ext::Dict{String, Any}` - `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: container for supplemental attributes - `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify """ mutable struct ThermalStandard <: ThermalGen @@ -87,16 +89,18 @@ mutable struct ThermalStandard <: ThermalGen ext::Dict{String, Any} "internal time_series storage" time_series_container::InfrastructureSystems.TimeSeriesContainer + "container for supplemental attributes" + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer "power system internal reference, do not modify" internal::InfrastructureSystemsInternal end -function ThermalStandard(name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits=nothing, must_run=false, prime_mover_type=PrimeMovers.OT, fuel=ThermalFuels.OTHER, services=Device[], time_at_status=INFINITE_TIME, dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), ) - ThermalStandard(name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits, must_run, prime_mover_type, fuel, services, time_at_status, dynamic_injector, ext, time_series_container, InfrastructureSystemsInternal(), ) +function ThermalStandard(name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits=nothing, must_run=false, prime_mover_type=PrimeMovers.OT, fuel=ThermalFuels.OTHER, services=Device[], time_at_status=INFINITE_TIME, dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), ) + ThermalStandard(name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits, must_run, prime_mover_type, fuel, services, time_at_status, dynamic_injector, ext, time_series_container, supplemental_attributes_container, InfrastructureSystemsInternal(), ) end -function ThermalStandard(; name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits=nothing, must_run=false, prime_mover_type=PrimeMovers.OT, fuel=ThermalFuels.OTHER, services=Device[], time_at_status=INFINITE_TIME, dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), ) - ThermalStandard(name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits, must_run, prime_mover_type, fuel, services, time_at_status, dynamic_injector, ext, time_series_container, internal, ) +function ThermalStandard(; name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits=nothing, must_run=false, prime_mover_type=PrimeMovers.OT, fuel=ThermalFuels.OTHER, services=Device[], time_at_status=INFINITE_TIME, dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), internal=InfrastructureSystemsInternal(), ) + ThermalStandard(name, available, status, bus, active_power, reactive_power, rating, active_power_limits, reactive_power_limits, ramp_limits, operation_cost, base_power, time_limits, must_run, prime_mover_type, fuel, services, time_at_status, dynamic_injector, ext, time_series_container, supplemental_attributes_container, internal, ) end # Constructor for demo purposes; non-functional. @@ -123,6 +127,7 @@ function ThermalStandard(::Nothing) dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), + supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), ) end @@ -168,6 +173,8 @@ get_dynamic_injector(value::ThermalStandard) = value.dynamic_injector get_ext(value::ThermalStandard) = value.ext """Get [`ThermalStandard`](@ref) `time_series_container`.""" get_time_series_container(value::ThermalStandard) = value.time_series_container +"""Get [`ThermalStandard`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::ThermalStandard) = value.supplemental_attributes_container """Get [`ThermalStandard`](@ref) `internal`.""" get_internal(value::ThermalStandard) = value.internal @@ -209,3 +216,5 @@ set_time_at_status!(value::ThermalStandard, val) = value.time_at_status = val set_ext!(value::ThermalStandard, val) = value.ext = val """Set [`ThermalStandard`](@ref) `time_series_container`.""" set_time_series_container!(value::ThermalStandard, val) = value.time_series_container = val +"""Set [`ThermalStandard`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::ThermalStandard, val) = value.supplemental_attributes_container = val diff --git a/src/models/generated/includes.jl b/src/models/generated/includes.jl index 6e9a600074..3e674fa6a7 100644 --- a/src/models/generated/includes.jl +++ b/src/models/generated/includes.jl @@ -619,6 +619,7 @@ export get_states_types export get_status export get_storage_capacity export get_storage_target +export get_supplemental_attributes_container export get_sustained_time export get_switch export get_tF_delay @@ -1155,6 +1156,7 @@ export set_states_types! export set_status! export set_storage_capacity! export set_storage_target! +export set_supplemental_attributes_container! export set_sustained_time! export set_switch! export set_tF_delay! diff --git a/src/models/serialization.jl b/src/models/serialization.jl index b19f9e8f97..f961e5e124 100644 --- a/src/models/serialization.jl +++ b/src/models/serialization.jl @@ -4,11 +4,12 @@ const _ENCODE_AS_UUID_A = ( Union{Nothing, Bus}, Union{Nothing, LoadZone}, Union{Nothing, DynamicInjection}, + Union{Nothing, StaticInjection}, Vector{Service}, ) const _ENCODE_AS_UUID_B = - (Arc, Area, Bus, LoadZone, DynamicInjection, Vector{Service}) + (Arc, Area, Bus, LoadZone, DynamicInjection, StaticInjection, Vector{Service}) @assert length(_ENCODE_AS_UUID_A) == length(_ENCODE_AS_UUID_B) should_encode_as_uuid(val) = any(x -> val isa x, _ENCODE_AS_UUID_B) diff --git a/src/models/supplemental_constructors.jl b/src/models/supplemental_constructors.jl index bba1268350..e355bfc8bb 100644 --- a/src/models/supplemental_constructors.jl +++ b/src/models/supplemental_constructors.jl @@ -76,6 +76,7 @@ function ACBus( area, load_zone, ext, + IS.SupplementalAttributesContainer(), InfrastructureSystemsInternal(), ) end diff --git a/src/models/topological_elements.jl b/src/models/topological_elements.jl index 8b9a1c3b22..fed1bd592f 100644 --- a/src/models/topological_elements.jl +++ b/src/models/topological_elements.jl @@ -34,6 +34,7 @@ function check_bus_params( area, load_zone, ext, + attributes, internal, ) if !isnothing(bustype) @@ -55,5 +56,6 @@ function check_bus_params( area, load_zone, ext, + attributes, internal end diff --git a/src/outages.jl b/src/outages.jl new file mode 100644 index 0000000000..302c98fcd4 --- /dev/null +++ b/src/outages.jl @@ -0,0 +1,63 @@ +abstract type Outage <: SupplementalAttribute end + +struct ForcedOutage <: Outage + forced_outage_rate::Float64 + mean_time_to_recovery::Int + outage_probability::Float64 + recovery_probability::Float64 + time_series_container::InfrastructureSystems.TimeSeriesContainer + component_uuids::InfrastructureSystems.ComponentUUIDs + internal::InfrastructureSystemsInternal +end + +function ForcedOutage(; + forced_outage_rate = 0.0, + mean_time_to_recovery = 0, + outage_probability = 0.0, + recovery_probability = 0.0, + time_series_container = InfrastructureSystems.TimeSeriesContainer(), + component_uuids = InfrastructureSystems.ComponentUUIDs(), + internal = InfrastructureSystemsInternal(), +) + return ForcedOutage( + forced_outage_rate, + mean_time_to_recovery, + outage_probability, + recovery_probability, + time_series_container, + component_uuids, + internal, + ) +end + +get_component_uuids(x::ForcedOutage) = x.component_uuids +get_internal(x::ForcedOutage) = x.internal +get_time_series_container(x::ForcedOutage) = x.time_series_container + +struct PlannedOutage <: Outage + time_to_recovery::Int + outage_schedule::Int + time_series_container::InfrastructureSystems.TimeSeriesContainer + component_uuids::InfrastructureSystems.ComponentUUIDs + internal::InfrastructureSystemsInternal +end + +function PlannedOutage(; + time_to_recovery = 0, + outage_schedule = 0.0, + time_series_container = InfrastructureSystems.TimeSeriesContainer(), + component_uuids = InfrastructureSystems.ComponentUUIDs(), + internal = InfrastructureSystemsInternal(), +) + return PlannedOutage( + time_to_recovery, + outage_schedule, + time_series_container, + component_uuids, + internal, + ) +end + +get_component_uuids(x::PlannedOutage) = x.component_uuids +get_internal(x::PlannedOutage) = x.internal +get_time_series_container(x::PlannedOutage) = x.time_series_container diff --git a/test/common.jl b/test/common.jl index 7ff431b963..037635f167 100644 --- a/test/common.jl +++ b/test/common.jl @@ -156,6 +156,50 @@ function create_system_with_dynamic_inverter() return sys end +""" +Create a system with supplemental attributes with the criteria below. + +- Two GeographicInfo instances each assigned to multiple components. +- Two ThermalStandards each with two ForcedOutage instances and two PlannedOutage instances. +- Each outage has time series. +""" +function create_system_with_outages() + sys = PSB.build_system( + PSITestSystems, + "c_sys5_uc"; + add_forecasts = true, + ) + gens = collect(get_components(ThermalStandard, sys)) + gen1 = gens[1] + gen2 = gens[2] + geo1 = GeographicInfo(; geo_json = Dict("x" => 1.0, "y" => 2.0)) + geo2 = GeographicInfo(; geo_json = Dict("x" => 3.0, "y" => 4.0)) + add_supplemental_attribute!(sys, gen1, geo1) + add_supplemental_attribute!(sys, gen1.bus, geo1) + add_supplemental_attribute!(sys, gen2, geo2) + add_supplemental_attribute!(sys, gen2.bus, geo2) + initial_time = Dates.DateTime("2020-01-01T00:00:00") + end_time = Dates.DateTime("2020-01-01T23:00:00") + dates = collect(initial_time:Dates.Hour(1):end_time) + fo1 = ForcedOutage(; forced_outage_rate = 1.0) + fo2 = ForcedOutage(; forced_outage_rate = 2.0) + po1 = PlannedOutage(; outage_schedule = 3.0) + po2 = PlannedOutage(; outage_schedule = 4.0) + add_supplemental_attribute!(sys, gen1, fo1) + add_supplemental_attribute!(sys, gen1, po1) + add_supplemental_attribute!(sys, gen2, fo2) + add_supplemental_attribute!(sys, gen2, po2) + for (i, outage) in enumerate((fo1, fo2, po1, po2)) + data = collect(i:(i + 23)) + ta = TimeSeries.TimeArray(dates, data, ["1"]) + name = "ts_$(i)" + ts = SingleTimeSeries(; name = name, data = ta) + add_time_series!(sys, outage, ts) + end + + return sys +end + function test_accessors(component) ps_type = typeof(component) diff --git a/test/runtests.jl b/test/runtests.jl index 329d2640bd..02ac00a124 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -98,8 +98,8 @@ function run_tests() else config = IS.LoggingConfiguration(; filename = LOG_FILE, - file_level = Logging.Info, - console_level = Logging.Error, + file_level = get_logging_level_from_env("SIIP_FILE_LOG_LEVEL", "Info"), + console_level = get_logging_level_from_env("SIIP_CONSOLE_LOG_LEVEL", "Error"), ) end console_logger = ConsoleLogger(config.console_stream, config.console_level) diff --git a/test/test_outages.jl b/test/test_outages.jl new file mode 100644 index 0000000000..d25f115294 --- /dev/null +++ b/test/test_outages.jl @@ -0,0 +1,51 @@ +@testset "Test outages" begin + sys = create_system_with_outages() + gens = collect(get_components(ThermalStandard, sys)) + gen1 = gens[1] + gen2 = gens[2] + @test length(get_supplemental_attributes(Outage, sys)) == 4 + forced_outages = collect(get_supplemental_attributes(ForcedOutage, sys)) + @test length(forced_outages) == 2 + @test get_supplemental_attribute(sys, IS.get_uuid(forced_outages[1])) == + forced_outages[1] + planned_outages = collect(get_supplemental_attributes(PlannedOutage, sys)) + @test length(planned_outages) == 2 + @test get_supplemental_attribute(sys, IS.get_uuid(planned_outages[1])) == + planned_outages[1] + + geos = get_supplemental_attributes(GeographicInfo, sys) + for geo in geos + @test length(get_components(sys, geo)) == 2 + end + + for gen in (gen1, gen2) + for type in (ForcedOutage, PlannedOutage, GeographicInfo) + attributes = collect(get_supplemental_attributes(type, gen)) + @test length(attributes) == 1 + uuid = IS.get_uuid(attributes[1]) + get_supplemental_attribute(sys, uuid) + get_supplemental_attribute(gen, uuid) + @test get_supplemental_attribute(gen, uuid) == + get_supplemental_attribute(sys, uuid) + end + end + + @test length( + get_supplemental_attributes(x -> x.forced_outage_rate == 2.0, ForcedOutage, sys), + ) == 1 + @test length( + get_supplemental_attributes(x -> x.forced_outage_rate == 2.0, ForcedOutage, gen1), + ) == 0 + @test length( + get_supplemental_attributes(x -> x.forced_outage_rate == 2.0, ForcedOutage, gen2), + ) == 1 + @test length( + get_supplemental_attributes(x -> x.outage_schedule == 4.0, PlannedOutage, sys), + ) == 1 + @test length( + get_supplemental_attributes(x -> x.outage_schedule == 4.0, PlannedOutage, gen1), + ) == 0 + @test length( + get_supplemental_attributes(x -> x.outage_schedule == 4.0, PlannedOutage, gen2), + ) == 1 +end diff --git a/test/test_serialization.jl b/test/test_serialization.jl index 30e0166c95..baaa62b484 100644 --- a/test/test_serialization.jl +++ b/test/test_serialization.jl @@ -1,4 +1,4 @@ -@testset "Test JSON serialization of RTS data with mutable time series" begin +@testset "Test JSON serialization of RTS data with RegulationDevice" begin sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys"; add_forecasts = false) # Add an AGC service to cover its special serialization. control_area = get_component(Area, sys, "1") @@ -222,6 +222,12 @@ end end end +@testset "Test serialization of supplemental attributes" begin + sys = create_system_with_outages() + sys2, result = validate_serialization(sys; assign_new_uuids = true) + @test result +end + @testset "Test verification of invalid ext fields" begin sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys"; add_forecasts = false) gen = first(get_components(ThermalStandard, sys)) diff --git a/test/test_system.jl b/test/test_system.jl index 3bd02dd4a7..30256329eb 100644 --- a/test/test_system.jl +++ b/test/test_system.jl @@ -325,11 +325,10 @@ end PSITestSystems, "c_sys5_uc"; add_forecasts = true, - force_build = true, ) - _, ts_count, forecast_count = get_time_series_counts(c_sys5) - @test ts_count == 0 - @test forecast_count == 3 + counts = get_time_series_counts(c_sys5) + @test counts.static_time_series_count == 0 + @test counts.forecast_count == 3 end @testset "Test deepcopy with time series options" begin