diff --git a/src/PowerSystems2PRAS.jl b/src/PowerSystems2PRAS.jl index 903f06f..e83cb35 100644 --- a/src/PowerSystems2PRAS.jl +++ b/src/PowerSystems2PRAS.jl @@ -8,7 +8,6 @@ Sienna/Data PowerSystems.jl System is the input and an object of PRAS SystemMode - `sys::PSY.System`: Sienna/Data PowerSystems.jl System - `aggregation<:PSY.AggregationTopology`: "PSY.Area" (or) "PSY.LoadZone" {Optional} - - `availability::Bool`: Takes into account avaialability of StaticInjection components when building the PRAS System {Optional} - `lump_region_renewable_gens::Bool`: Whether to lumps PV and Wind generators in a region because usually these generators don't have FOR data {Optional} - `export_location::String`: Export location of the .pras file ... @@ -27,7 +26,6 @@ PRAS SystemModel function generate_pras_system( sys::PSY.System, aggregation::Type{AT}; - availability=true, lump_region_renewable_gens=false, export_location::Union{Nothing, String}=nothing, )::PRASCore.SystemModel where {AT <: PSY.AggregationTopology} @@ -160,12 +158,21 @@ function generate_pras_system( add_N!(s2p_meta) # TODO: Is it okay to just get the first elemnt of vector returned by PSY.get_time_series_resolutions? - sys_res_in_hour = + sys_res = round(Dates.Millisecond(first(PSY.get_time_series_resolutions(sys))), Dates.Hour) + if iszero(sys_res.value) + sys_res = round( + Dates.Millisecond(first(PSY.get_time_series_resolutions(sys))), + Dates.Minute, + ) + s2p_meta.pras_resolution = Dates.Minute + end + s2p_meta.pras_timestep = sys_res.value start_datetime_tz = TimeZones.ZonedDateTime(s2p_meta.first_timestamp, TimeZones.tz"UTC") - finish_datetime_tz = start_datetime_tz + Dates.Hour((s2p_meta.N - 1) * sys_res_in_hour) + finish_datetime_tz = + start_datetime_tz + s2p_meta.pras_resolution((s2p_meta.N - 1) * sys_res) my_timestamps = - StepRange(start_datetime_tz, Dates.Hour(sys_res_in_hour), finish_datetime_tz) + StepRange(start_datetime_tz, s2p_meta.pras_resolution(sys_res), finish_datetime_tz) @info "The first timestamp of PRAS System being built is : $(start_datetime_tz) and last timestamp is : $(finish_datetime_tz) " ####################################################### @@ -173,9 +180,7 @@ function generate_pras_system( # TODO: Not sure if we need this anymore. ####################################################### dup_uuids = Base.UUID[] - h_s_comps = - availability ? PSY.get_components(PSY.get_available, PSY.HybridSystem, sys) : - PSY.get_components(PSY.HybridSystem, sys) + h_s_comps = PSY.get_available_components(PSY.HybridSystem, sys) for h_s in h_s_comps push!(dup_uuids, PSY.IS.get_uuid.(PSY._get_components(h_s))...) end @@ -183,12 +188,6 @@ function generate_pras_system( if ~(isempty(dup_uuids)) s2p_meta.hs_uuids = dup_uuids end - - # TODO: Do we still need to do this? From now, PSS/e parser - # will return PSY.StandardLoad objects - if (length(PSY.get_components(PSY.PowerLoad, sys)) > 0) - s2p_meta.load_type = PSY.PowerLoad - end ####################################################### # PRAS Regions - Areas in PowerSystems.jl ####################################################### @@ -209,12 +208,7 @@ function generate_pras_system( for (idx, region) in enumerate(regions) reg_load_comps = - availability ? - get_available_components_in_aggregation_topology( - s2p_meta.load_type, - sys, - region, - ) : PSY.get_components_in_aggregation_topology(s2p_meta.load_type, sys, region) + get_available_components_in_aggregation_topology(PSY.StaticLoad, sys, region) if (length(reg_load_comps) > 0) region_load[idx, :] = floor.( @@ -248,14 +242,11 @@ function generate_pras_system( if (lump_region_renewable_gens) for (idx, region) in enumerate(regions) - reg_ren_comps = - availability ? - get_available_components_in_aggregation_topology( - PSY.RenewableGen, - sys, - region, - ) : - PSY.get_components_in_aggregation_topology(PSY.RenewableGen, sys, region) + reg_ren_comps = get_available_components_in_aggregation_topology( + PSY.RenewableGen, + sys, + region, + ) wind_gs = filter( x -> (PSY.get_prime_mover_type(x) == PSY.PrimeMovers.WT), reg_ren_comps, @@ -265,12 +256,7 @@ function generate_pras_system( reg_ren_comps, ) reg_gen_comps = - availability ? - get_available_components_in_aggregation_topology( - PSY.Generator, - sys, - region, - ) : PSY.get_components_in_aggregation_topology(PSY.Generator, sys, region) + get_available_components_in_aggregation_topology(PSY.Generator, sys, region) gs = filter( x -> ( ~(typeof(x) == PSY.HydroEnergyReservoir) && @@ -312,12 +298,7 @@ function generate_pras_system( else for (idx, region) in enumerate(regions) reg_gen_comps = - availability ? - get_available_components_in_aggregation_topology( - PSY.Generator, - sys, - region, - ) : PSY.get_components_in_aggregation_topology(PSY.Generator, sys, region) + get_available_components_in_aggregation_topology(PSY.Generator, sys, region) gs = filter( x -> ( ~(typeof(x) == PSY.HydroEnergyReservoir) && @@ -341,9 +322,7 @@ function generate_pras_system( for (idx, region) in enumerate(regions) reg_stor_comps = - availability ? - get_available_components_in_aggregation_topology(PSY.Storage, sys, region) : - PSY.get_components_in_aggregation_topology(PSY.Storage, sys, region) + get_available_components_in_aggregation_topology(PSY.Storage, sys, region) push!(stors, filter(x -> (PSY.IS.get_uuid(x) ∉ dup_uuids), reg_stor_comps)) idx == 1 ? start_id[idx] = 1 : start_id[idx] = start_id[idx - 1] + length(stors[idx - 1]) @@ -358,9 +337,7 @@ function generate_pras_system( for (idx, region) in enumerate(regions) reg_gen_stor_comps = - availability ? - get_available_components_in_aggregation_topology(PSY.Generator, sys, region) : - PSY.get_components_in_aggregation_topology(PSY.Generator, sys, region) + get_available_components_in_aggregation_topology(PSY.Generator, sys, region) gs = filter( x -> (typeof(x) == PSY.HydroEnergyReservoir || typeof(x) == PSY.HybridSystem), reg_gen_stor_comps, @@ -480,7 +457,12 @@ function generate_pras_system( λ_gen[idx, :], μ_gen[idx, :] = get_outage_time_series_data(g, s2p_meta) end - new_generators = PRASCore.Generators{s2p_meta.N, 1, PRASCore.Hour, PRASCore.MW}( + new_generators = PRASCore.Generators{ + s2p_meta.N, + s2p_meta.pras_timestep, + s2p_meta.pras_resolution, + PRASCore.MW, + }( gen_names, get_generator_category.(gen), gen_cap_array, @@ -549,19 +531,24 @@ function generate_pras_system( stor_cryovr_eff = ones(n_stor, s2p_meta.N) # Not currently available/ defined in PowerSystems - new_storage = - PRASCore.Storages{s2p_meta.N, 1, PRASCore.Hour, PRASCore.MW, PRASCore.MWh}( - stor_names, - get_generator_category.(stor), - stor_charge_cap_array, - stor_discharge_cap_array, - stor_energy_cap_array, - stor_chrg_eff_array, - stor_dischrg_eff_array, - stor_cryovr_eff, - λ_stor, - μ_stor, - ) + new_storage = PRASCore.Storages{ + s2p_meta.N, + s2p_meta.pras_timestep, + s2p_meta.pras_resolution, + PRASCore.MW, + PRASCore.MWh, + }( + stor_names, + get_generator_category.(stor), + stor_charge_cap_array, + stor_discharge_cap_array, + stor_energy_cap_array, + stor_chrg_eff_array, + stor_dischrg_eff_array, + stor_cryovr_eff, + λ_stor, + μ_stor, + ) ####################################################### # PRAS Generator Storages @@ -792,22 +779,27 @@ function generate_pras_system( gen_stor_discharge_eff = ones(n_genstors, s2p_meta.N) # Not currently available/ defined in PowerSystems gen_stor_cryovr_eff = ones(n_genstors, s2p_meta.N) # Not currently available/ defined in PowerSystems - new_gen_stors = - PRASCore.GeneratorStorages{s2p_meta.N, 1, PRASCore.Hour, PRASCore.MW, PRASCore.MWh}( - gen_stor_names, - get_generator_category.(gen_stor), - gen_stor_charge_cap_array, - gen_stor_discharge_cap_array, - gen_stor_enrgy_cap_array, - gen_stor_charge_eff, - gen_stor_discharge_eff, - gen_stor_cryovr_eff, - gen_stor_inflow_array, - gen_stor_gridwdr_cap_array, - gen_stor_gridinj_cap_array, - λ_genstors, - μ_genstors, - ) + new_gen_stors = PRASCore.GeneratorStorages{ + s2p_meta.N, + s2p_meta.pras_timestep, + s2p_meta.pras_resolution, + PRASCore.MW, + PRASCore.MWh, + }( + gen_stor_names, + get_generator_category.(gen_stor), + gen_stor_charge_cap_array, + gen_stor_discharge_cap_array, + gen_stor_enrgy_cap_array, + gen_stor_charge_eff, + gen_stor_discharge_eff, + gen_stor_cryovr_eff, + gen_stor_inflow_array, + gen_stor_gridwdr_cap_array, + gen_stor_gridinj_cap_array, + λ_genstors, + μ_genstors, + ) ####################################################### # Network ####################################################### @@ -817,19 +809,13 @@ function generate_pras_system( ####################################################### @info "Collecting all inter regional lines in Sienna/Data PowerSystems System..." - lines = - availability ? - collect( - PSY.get_components( - x -> (typeof(x) ∉ TransformerTypes && PSY.get_available(x)), - PSY.Branch, - sys, - ), - ) : - collect( - PSY.get_components(x -> (typeof(x) ∉ TransformerTypes), PSY.Branch, sys), - ) - + lines = collect( + PSY.get_components( + x -> (typeof(x) ∉ TransformerTypes && PSY.get_available(x)), + PSY.Branch, + sys, + ), + ) ####################################################### # Inter-Regional Line Processing ####################################################### @@ -890,7 +876,6 @@ Generate a PRAS SystemModel from a Sienna/Data PowerSystems System JSON file. - `sys_location::String`: Location of the Sienna/Data PowerSystems System JSON file - `aggregation::Type{AT}`: Aggregation topology type - - `availability::Bool`: Availability of components in the System - `lump_region_renewable_gens::Bool`: Lumping of region renewable generators - `export_location::Union{Nothing, String}`: Export location of the .pras file @@ -901,7 +886,6 @@ Generate a PRAS SystemModel from a Sienna/Data PowerSystems System JSON file. function generate_pras_system( sys_location::String, aggregation::Type{AT}; - availability=true, lump_region_renewable_gens=false, export_location::Union{Nothing, String}=nothing, ) where {AT <: PSY.AggregationTopology} @@ -920,7 +904,6 @@ function generate_pras_system( generate_pras_system( sys, aggregation, - availability=availability, lump_region_renewable_gens=lump_region_renewable_gens, export_location=export_location, ) diff --git a/src/util/parsing/Sienna_PRAS_metadata.jl b/src/util/parsing/Sienna_PRAS_metadata.jl index 5e9f229..50f2f46 100644 --- a/src/util/parsing/Sienna_PRAS_metadata.jl +++ b/src/util/parsing/Sienna_PRAS_metadata.jl @@ -11,7 +11,8 @@ mutable struct S2P_metadata first_timestamp::Union{Nothing, Dates.DateTime} first_timeseries::Union{Nothing, Union{<:PSY.Forecast, <:PSY.StaticTimeSeries}} hs_uuids::Vector{Base.UUID} - load_type::Union{Nothing, Type{<:PSY.StaticLoad}} + pras_resolution::Type{T} where {T <: Dates.Period} + pras_timestep::Int64 S2P_metadata( has_st_timeseries=false, @@ -21,7 +22,8 @@ mutable struct S2P_metadata first_timestamp=nothing, first_ts=nothing, hs_uuids=Vector{Base.UUID}[], - load_type=PSY.StandardLoad, + pras_res=Dates.Hour, + pras_ts=1, ) = new( has_st_timeseries, has_forecasts, @@ -30,7 +32,8 @@ mutable struct S2P_metadata first_timestamp, first_ts, hs_uuids, - load_type, + pras_res, + pras_ts, ) end diff --git a/src/util/parsing/lines_and_interfaces.jl b/src/util/parsing/lines_and_interfaces.jl index e5e5659..f3fb583 100644 --- a/src/util/parsing/lines_and_interfaces.jl +++ b/src/util/parsing/lines_and_interfaces.jl @@ -105,7 +105,12 @@ function make_pras_interfaces( line_λ[i, :], line_μ[i, :] = get_outage_time_series_data(sorted_lines[i], s2p_meta) end - new_lines = PRASCore.Lines{s2p_meta.N, 1, PRASCore.Hour, PRASCore.MW}( + new_lines = PRASCore.Lines{ + s2p_meta.N, + s2p_meta.pras_timestep, + s2p_meta.pras_resolution, + PRASCore.MW, + }( line_names, line_cats, line_forward_cap, @@ -113,7 +118,6 @@ function make_pras_interfaces( line_λ, line_μ, ) - interface_forward_capacity_array = Matrix{Int64}(undef, num_interfaces, s2p_meta.N) interface_backward_capacity_array = Matrix{Int64}(undef, num_interfaces, s2p_meta.N) diff --git a/test/rts_gmlc.jl b/test/rts_gmlc.jl index 74dd390..d12697d 100644 --- a/test/rts_gmlc.jl +++ b/test/rts_gmlc.jl @@ -2,8 +2,9 @@ Use PSCB to build RTS-GMLC System and add outage data as a Supplmental Attribute """ -function get_rts_gmlc_outage() - rts_da_sys = PSCB.build_system(PSCB.PSISystems, "RTS_GMLC_DA_sys") +function get_rts_gmlc_outage(sys_type::String) + sys_name = "RTS_GMLC_$(sys_type)_sys" + rts_sys = PSCB.build_system(PSCB.PSISystems, sys_name) ########################################### # Parse the gen.csv and add OutageData @@ -18,14 +19,14 @@ function get_rts_gmlc_outage() mean_time_to_recovery=row["MTTR Hr"], outage_transition_probability=λ, ) - comp = PSY.get_component(PSY.Generator, rts_da_sys, row["GEN UID"]) + comp = PSY.get_component(PSY.Generator, rts_sys, row["GEN UID"]) if ~(isnothing(comp)) - PSY.add_supplemental_attribute!(rts_da_sys, comp, transition_data) + PSY.add_supplemental_attribute!(rts_sys, comp, transition_data) @info "Added outage data supplemental attribute to $(row["GEN UID"]) generator" else @warn "$(row["GEN UID"]) generator doesn't exist in the System." end end - return rts_da_sys + return rts_sys end diff --git a/test/test-assess-system.jl b/test/test-assess-system.jl index b6f099a..385bdb2 100644 --- a/test/test-assess-system.jl +++ b/test/test-assess-system.jl @@ -1,5 +1,5 @@ -@testset "test assess(::PSY.System, ::Area, ...)" begin - rts_da_sys = get_rts_gmlc_outage() +@testset "test assess(::PSY.System, ::Area, ...) Hourly PRAS System" begin + rts_da_sys = get_rts_gmlc_outage("DA") sequential_monte_carlo = SiennaPRASInterface.SequentialMonteCarlo(samples=2, seed=1) shortfalls, = SiennaPRASInterface.assess( @@ -20,3 +20,26 @@ @test SiennaPRASInterface.val(eue) >= 0 && SiennaPRASInterface.val(eue) <= 10 @test SiennaPRASInterface.stderror(eue) >= 0 && SiennaPRASInterface.stderror(eue) <= 10 end + +@testset "test assess(::PSY.System, ::Area, ...) Sub-Hourly System" begin + rts_rt_sys = get_rts_gmlc_outage("RT") + + sequential_monte_carlo = SiennaPRASInterface.SequentialMonteCarlo(samples=2, seed=1) + shortfalls, = SiennaPRASInterface.assess( + rts_rt_sys, + PSY.Area, + sequential_monte_carlo, + SiennaPRASInterface.Shortfall(), + ) + @test shortfalls isa SiennaPRASInterface.PRASCore.Results.ShortfallResult + + lole = SiennaPRASInterface.LOLE(shortfalls) + eue = SiennaPRASInterface.EUE(shortfalls) + @test lole isa SiennaPRASInterface.PRASCore.ReliabilityMetric + @test eue isa SiennaPRASInterface.PRASCore.ReliabilityMetric + @test SiennaPRASInterface.val(lole) >= 0 && SiennaPRASInterface.val(lole) <= 10 + @test SiennaPRASInterface.stderror(lole) >= 0 && + SiennaPRASInterface.stderror(lole) <= 10 + @test SiennaPRASInterface.val(eue) >= 0 && SiennaPRASInterface.val(eue) <= 10 + @test SiennaPRASInterface.stderror(eue) >= 0 && SiennaPRASInterface.stderror(eue) <= 10 +end diff --git a/test/test-generate-pras.jl b/test/test-generate-pras.jl index 31dbc9b..a246738 100644 --- a/test/test-generate-pras.jl +++ b/test/test-generate-pras.jl @@ -37,8 +37,8 @@ function array_all_equal(x::AbstractVector{T}, v::T) where {T} return true end -@testset "RTS GMLC" begin - rts_da_sys = get_rts_gmlc_outage() +@testset "RTS GMLC DA" begin + rts_da_sys = get_rts_gmlc_outage("DA") area_names = PSY.get_name.(PSY.get_components(PSY.Area, rts_da_sys)) generator_names = PSY.get_name.( @@ -118,39 +118,14 @@ end end @test all(rts_pras_sys.regions.load .== Int.(floor.(load_values))) - # Test Assess Run - sequential_monte_carlo = - SiennaPRASInterface.PRASCore.SequentialMonteCarlo(samples=2, seed=1) - shortfalls, = SiennaPRASInterface.PRASCore.assess( - rts_pras_sys, - sequential_monte_carlo, - SiennaPRASInterface.PRASCore.Shortfall(), - ) - lole = SiennaPRASInterface.PRASCore.LOLE(shortfalls) - eue = SiennaPRASInterface.PRASCore.EUE(shortfalls) - @test lole isa SiennaPRASInterface.PRASCore.ReliabilityMetric - @test eue isa SiennaPRASInterface.PRASCore.ReliabilityMetric - @test SiennaPRASInterface.PRASCore.val(lole) >= 0 && - SiennaPRASInterface.PRASCore.val(lole) <= 10 - @test SiennaPRASInterface.PRASCore.stderror(lole) >= 0 && - SiennaPRASInterface.PRASCore.stderror(lole) <= 10 - @test SiennaPRASInterface.PRASCore.val(eue) >= 0 && - SiennaPRASInterface.PRASCore.val(eue) <= 10 - @test SiennaPRASInterface.PRASCore.stderror(eue) >= 0 && - SiennaPRASInterface.PRASCore.stderror(eue) <= 10 - @testset "Lumped Renewable Generators" begin rts_pras_sys = generate_pras_system(rts_da_sys, PSY.Area, lump_region_renewable_gens=true) @test rts_pras_sys isa SiennaPRASInterface.PRASCore.SystemModel @test test_names_equal(rts_pras_sys.regions.names, area_names) - rts_pras_sys = generate_pras_system( - rts_da_sys, - PSY.Area, - lump_region_renewable_gens=true, - availability=false, - ) + rts_pras_sys = + generate_pras_system(rts_da_sys, PSY.Area, lump_region_renewable_gens=true) @test rts_pras_sys isa SiennaPRASInterface.PRASCore.SystemModel @test test_names_equal(rts_pras_sys.regions.names, area_names) @@ -158,7 +133,6 @@ end rts_da_sys, PSY.Area, lump_region_renewable_gens=true, - availability=false, export_location=joinpath(@__DIR__, "rts.pras"), ) @test rts_pras_sys isa SiennaPRASInterface.PRASCore.SystemModel @@ -169,7 +143,7 @@ end end end -@testset "RTS GMLC with default data" begin +@testset "RTS GMLC DA with default data" begin rts_da_sys = PSCB.build_system(PSCB.PSISystems, "RTS_GMLC_DA_sys") area_names = PSY.get_name.(PSY.get_components(PSY.Area, rts_da_sys)) generator_names = @@ -194,6 +168,20 @@ end @test array_all_equal(rts_pras_sys.generators.μ[idx, :], μ) end +@testset "RTS GMLC RT with default data" begin + rts_rt_sys = PSCB.build_system(PSCB.PSISystems, "RTS_GMLC_RT_sys") + + rts_pras_sys = generate_pras_system(rts_rt_sys, PSY.Area) + @test rts_pras_sys isa SiennaPRASInterface.PRASCore.SystemModel + + # Test that timestamps look right + # get time series length + psy_ts = first(PSY.get_time_series_multiple(rts_rt_sys, type=PSY.SingleTimeSeries)) + @test all( + TimeSeries.timestamp(psy_ts.data) .== collect(DateTime.(rts_pras_sys.timestamps)), + ) +end + # TODO: We want to test time-series λ, μ # TODO: test HybridSystems # TODO: Unit test line_and_interfaces.jl