diff --git a/.gitignore b/.gitignore index b1fb0632..41718123 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.jl.mem Manifest.toml *.DS_Store +*.pras diff --git a/PRAS/Project.toml b/PRAS/Project.toml new file mode 100644 index 00000000..a2e7f9d1 --- /dev/null +++ b/PRAS/Project.toml @@ -0,0 +1,26 @@ +name = "PRAS" +uuid = "05348d26-1c52-11e9-35e3-9d51842d34b9" +authors = [ + "Gord Stephen ", + "Surya Chandan Dhulipala ", + "Hari Sundar " +] +version = "0.7.0" + +[deps] +PRASCapacityCredits = "2e1a2ed5-e89d-4cd3-bc86-c0e88a73d3a3" +PRASCore = "c5c32b99-e7c3-4530-a685-6f76e19f7fe2" +PRASFiles = "a2806276-6d43-4ef5-91c0-491704cd7cf1" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" + +[compat] +PRASCapacityCredits = "0.7" +PRASCore = "0.7" +PRASFiles = "0.7" +julia = "1" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/PRAS/src/PRAS.jl b/PRAS/src/PRAS.jl new file mode 100644 index 00000000..ac096c66 --- /dev/null +++ b/PRAS/src/PRAS.jl @@ -0,0 +1,10 @@ +module PRAS + +using Reexport + +@reexport using PRASCore +@reexport using PRASCapacityCredits + +import PRASFiles: toymodel, rts_gmlc + +end diff --git a/PRAS/test/runtests.jl b/PRAS/test/runtests.jl new file mode 100644 index 00000000..f11a6c26 --- /dev/null +++ b/PRAS/test/runtests.jl @@ -0,0 +1,12 @@ +using PRAS +using Test + +sys = PRAS.rts_gmlc() + +sf, = assess(sys, SequentialMonteCarlo(samples=100), Shortfall()) + +eue = EUE(sf) +lole = LOLE(sf) + +@test val(eue) isa Float64 +@test stderror(eue) isa Float64 diff --git a/PRASCapacityCredits/Project.toml b/PRASCapacityCredits/Project.toml new file mode 100644 index 00000000..bc2f66f3 --- /dev/null +++ b/PRASCapacityCredits/Project.toml @@ -0,0 +1,19 @@ +name = "PRASCapacityCredits" +uuid = "2e1a2ed5-e89d-4cd3-bc86-c0e88a73d3a3" +authors = ["Gord Stephen "] +version = "0.7.0" + +[deps] +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +PRASCore = "c5c32b99-e7c3-4530-a685-6f76e19f7fe2" + +[compat] +Distributions = "0.25" +PRASCore = "0.7" +julia = "1" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/src/CapacityCredit/CapacityCreditResult.jl b/PRASCapacityCredits/src/CapacityCreditResult.jl similarity index 100% rename from src/CapacityCredit/CapacityCreditResult.jl rename to PRASCapacityCredits/src/CapacityCreditResult.jl diff --git a/src/CapacityCredit/EFC.jl b/PRASCapacityCredits/src/EFC.jl similarity index 98% rename from src/CapacityCredit/EFC.jl rename to PRASCapacityCredits/src/EFC.jl index 2ff9f12d..f445e4cd 100644 --- a/src/CapacityCredit/EFC.jl +++ b/PRASCapacityCredits/src/EFC.jl @@ -28,7 +28,7 @@ function EFC{M}( end function assess(sys_baseline::S, sys_augmented::S, - params::EFC{M}, simulationspec::SimulationSpec + params::EFC{M}, simulationspec::SequentialMonteCarlo ) where {N, L, T, P, S <: SystemModel{N,L,T,P}, M <: ReliabilityMetric} _, powerunit, _ = unitsymbol(sys_baseline) diff --git a/src/CapacityCredit/ELCC.jl b/PRASCapacityCredits/src/ELCC.jl similarity index 98% rename from src/CapacityCredit/ELCC.jl rename to PRASCapacityCredits/src/ELCC.jl index 746290e6..3b2cd0c6 100644 --- a/src/CapacityCredit/ELCC.jl +++ b/PRASCapacityCredits/src/ELCC.jl @@ -28,7 +28,7 @@ function ELCC{M}( end function assess(sys_baseline::S, sys_augmented::S, - params::ELCC{M}, simulationspec::SimulationSpec + params::ELCC{M}, simulationspec::SequentialMonteCarlo ) where {N, L, T, P, S <: SystemModel{N,L,T,P}, M <: ReliabilityMetric} _, powerunit, _ = unitsymbol(sys_baseline) diff --git a/src/CapacityCredit/CapacityCredit.jl b/PRASCapacityCredits/src/PRASCapacityCredits.jl similarity index 51% rename from src/CapacityCredit/CapacityCredit.jl rename to PRASCapacityCredits/src/PRASCapacityCredits.jl index 49426458..cad35694 100644 --- a/src/CapacityCredit/CapacityCredit.jl +++ b/PRASCapacityCredits/src/PRASCapacityCredits.jl @@ -1,10 +1,11 @@ -@reexport module CapacityCredit +module PRASCapacityCredits + +import PRASCore.Systems: Generators, PowerUnit, Regions, SystemModel, unitsymbol +import PRASCore.Simulations: assess, SequentialMonteCarlo +import PRASCore.Results: ReliabilityMetric, Result, Shortfall, stderror, val import Base: minimum, maximum, extrema import Distributions: ccdf, Normal -import ..PRASBase: Generators, PowerUnit, Regions, SystemModel, unitsymbol -import ..ResourceAdequacy: assess, ReliabilityMetric, Result, Shortfall, - SimulationSpec, stderror, val export EFC, ELCC diff --git a/src/CapacityCredit/utils.jl b/PRASCapacityCredits/src/utils.jl similarity index 100% rename from src/CapacityCredit/utils.jl rename to PRASCapacityCredits/src/utils.jl diff --git a/test/CapacityCredit/runtests.jl b/PRASCapacityCredits/test/runtests.jl similarity index 70% rename from test/CapacityCredit/runtests.jl rename to PRASCapacityCredits/test/runtests.jl index d3084d00..0424de93 100644 --- a/test/CapacityCredit/runtests.jl +++ b/PRASCapacityCredits/test/runtests.jl @@ -1,4 +1,10 @@ -@testset "CapacityCredit" begin +using PRASCapacityCredits +using PRASCore +using Test + +import PRASCore.Systems: TestData + +@testset "PRASCapacityCredits" begin empty_str = String[] empty_int(x) = Matrix{Int}(undef, 0, x) @@ -31,7 +37,7 @@ load = [25, 28, 27, 24] - tz = TimeZone("UTC") + tz = tz"UTC" timestamps = ZonedDateTime(2010,1,1,0,tz):Hour(1):ZonedDateTime(2010,1,1,3,tz) sys_before = SystemModel( @@ -40,41 +46,27 @@ sys_after = SystemModel( gens_after, emptystors, emptygenstors, timestamps, load) - threenode2 = deepcopy(TestSystems.threenode) + threenode2 = deepcopy(TestData.threenode) threenode2.generators.capacity[1, :] .+= 5 - conv = Convolution(threaded=false) smc = SequentialMonteCarlo(samples=100_000, threaded=false) @testset "EFC" begin - cc = assess(sys_before, sys_before, EFC{EUE}(10, "Region"), conv) - @test extrema(cc) == (0, 1) - - cc = assess(sys_before, sys_after, EFC{EUE}(10, ["Region" => 1.0]), conv) - @test extrema(cc) == (8, 9) - cc = assess(sys_before, sys_after, EFC{EUE}(10, "Region"), smc) @test extrema(cc) == (8, 9) - cc = assess(TestSystems.threenode, threenode2, EFC{EUE}(10, "Region A"), smc) + cc = assess(TestData.threenode, threenode2, EFC{EUE}(10, "Region A"), smc) @test extrema(cc) == (3, 4) end @testset "ELCC" begin - - cc = assess(sys_before, sys_before, ELCC{EUE}(10, "Region"), conv) - @test extrema(cc) == (0, 1) - - cc = assess(sys_before, sys_after, ELCC{EUE}(10, ["Region" => 1.0]), conv) - @test extrema(cc) == (7, 8) - cc = assess(sys_before, sys_after, ELCC{EUE}(10, "Region"), smc) @test extrema(cc) == (7, 8) - cc = assess(TestSystems.threenode, threenode2, ELCC{EUE}(10, "Region A"), smc) + cc = assess(TestData.threenode, threenode2, ELCC{EUE}(10, "Region A"), smc) @test extrema(cc) == (3, 4) end diff --git a/Project.toml b/PRASCore/Project.toml similarity index 77% rename from Project.toml rename to PRASCore/Project.toml index efe509f1..73ec31e4 100644 --- a/Project.toml +++ b/PRASCore/Project.toml @@ -1,12 +1,10 @@ -name = "PRAS" -uuid = "05348d26-1c52-11e9-35e3-9d51842d34b9" +name = "PRASCore" +uuid = "c5c32b99-e7c3-4530-a685-6f76e19f7fe2" authors = ["Gord Stephen "] -version = "0.6.4" +version = "0.7.0" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" -Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" -HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" MinCostFlows = "62286e6e-1779-56f1-888a-1c0056788ce0" OnlineStats = "a15396b6-48d5-5d58-9928-6d29437db91e" OnlineStatsBase = "925886fa-5bf2-5e8e-b522-a9147a512338" @@ -18,7 +16,7 @@ StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53" [compat] -HDF5 = "0.16,0.17" +Reexport = "1.2.2" julia = "1.10" [extras] diff --git a/PRASCore/src/PRASCore.jl b/PRASCore/src/PRASCore.jl new file mode 100644 index 00000000..d7c8746e --- /dev/null +++ b/PRASCore/src/PRASCore.jl @@ -0,0 +1,9 @@ +module PRASCore + +import Reexport: @reexport + +include("Systems/Systems.jl") +include("Results/Results.jl") +include("Simulations/Simulations.jl") + +end diff --git a/PRASCore/src/Results/Flow.jl b/PRASCore/src/Results/Flow.jl new file mode 100644 index 00000000..3ecab7a6 --- /dev/null +++ b/PRASCore/src/Results/Flow.jl @@ -0,0 +1,92 @@ +""" + Flow + +Flow metric represents the flow between interfaces at timestamps +in a FlowResult with a (interfaces, timestamps) matrix API. + +Separate samples are averaged together into mean and std values. + +See [`FlowSamples`](@ref) for all flow samples. +""" +struct Flow <: ResultSpec end + +struct FlowAccumulator <: ResultAccumulator{Flow} + + flow_interface::Vector{MeanVariance} + flow_interfaceperiod::Matrix{MeanVariance} + + flow_interface_currentsim::Vector{Int} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::Flow +) where {N} + + n_interfaces = length(sys.interfaces) + flow_interface = [meanvariance() for _ in 1:n_interfaces] + flow_interfaceperiod = [meanvariance() for _ in 1:n_interfaces, _ in 1:N] + + flow_interface_currentsim = zeros(Int, n_interfaces) + + return FlowAccumulator( + flow_interface, flow_interfaceperiod, flow_interface_currentsim) + +end + +function merge!( + x::FlowAccumulator, y::FlowAccumulator +) + + foreach(merge!, x.flow_interface, y.flow_interface) + foreach(merge!, x.flow_interfaceperiod, y.flow_interfaceperiod) + +end + +accumulatortype(::Flow) = FlowAccumulator + +struct FlowResult{N,L,T<:Period,P<:PowerUnit} <: AbstractFlowResult{N,L,T} + + nsamples::Union{Int,Nothing} + interfaces::Vector{Pair{String,String}} + timestamps::StepRange{ZonedDateTime,T} + + flow_mean::Matrix{Float64} + + flow_interface_std::Vector{Float64} + flow_interfaceperiod_std::Matrix{Float64} + +end + +function getindex(x::FlowResult, i::Pair{<:AbstractString,<:AbstractString}) + i_i, reverse = findfirstunique_directional(x.interfaces, i) + flow = mean(view(x.flow_mean, i_i, :)) + return reverse ? -flow : flow, x.flow_interface_std[i_i] +end + +function getindex(x::FlowResult, i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) + i_i, reverse = findfirstunique_directional(x.interfaces, i) + i_t = findfirstunique(x.timestamps, t) + flow = x.flow_mean[i_i, i_t] + return reverse ? -flow : flow, x.flow_interfaceperiod_std[i_i, i_t] +end + +function finalize( + acc::FlowAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + nsamples = length(system.interfaces) > 0 ? + first(acc.flow_interface[1].stats).n : nothing + + flow_mean, flow_interfaceperiod_std = mean_std(acc.flow_interfaceperiod) + flow_interface_std = last(mean_std(acc.flow_interface)) / N + + fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) + toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) + + return FlowResult{N,L,T,P}( + nsamples, Pair.(fromregions, toregions), system.timestamps, + flow_mean, flow_interface_std, flow_interfaceperiod_std) + +end diff --git a/PRASCore/src/Results/FlowSamples.jl b/PRASCore/src/Results/FlowSamples.jl new file mode 100644 index 00000000..a3bc7fd8 --- /dev/null +++ b/PRASCore/src/Results/FlowSamples.jl @@ -0,0 +1,74 @@ +""" + FlowSamples + +Flow samples represent the flow between interfaces at timestamps, which has +not been averaged across different samples. This presents a +3D matrix API (interfaces, timestamps, samples). + +See [`Flow`](@ref) for sample-averaged flow data. +""" +struct FlowSamples <: ResultSpec end + +struct FlowSamplesAccumulator <: ResultAccumulator{FlowSamples} + + flow::Array{Int,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::FlowSamples +) where {N} + + ninterfaces = length(sys.interfaces) + flow = zeros(Int, ninterfaces, N, nsamples) + + return FlowSamplesAccumulator(flow) + +end + +function merge!( + x::FlowSamplesAccumulator, y::FlowSamplesAccumulator +) + + x.flow .+= y.flow + return + +end + +accumulatortype(::FlowSamples) = FlowSamplesAccumulator + +struct FlowSamplesResult{N,L,T<:Period,P<:PowerUnit} <: AbstractFlowResult{N,L,T} + + interfaces::Vector{Pair{String,String}} + timestamps::StepRange{ZonedDateTime,T} + + flow::Array{Int,3} + +end + +function getindex(x::FlowSamplesResult, i::Pair{<:AbstractString,<:AbstractString}) + i_i, reverse = findfirstunique_directional(x.interfaces, i) + flow = vec(mean(view(x.flow, i_i, :, :), dims=1)) + return reverse ? -flow : flow +end + + +function getindex(x::FlowSamplesResult, i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) + i_i, reverse = findfirstunique_directional(x.interfaces, i) + i_t = findfirstunique(x.timestamps, t) + flow = vec(x.flow[i_i, i_t, :]) + return reverse ? -flow : flow +end + +function finalize( + acc::FlowSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) + toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) + + return FlowSamplesResult{N,L,T,P}( + Pair.(fromregions, toregions), system.timestamps, acc.flow) + +end diff --git a/PRASCore/src/Results/GeneratorAvailability.jl b/PRASCore/src/Results/GeneratorAvailability.jl new file mode 100644 index 00000000..7d6b95ed --- /dev/null +++ b/PRASCore/src/Results/GeneratorAvailability.jl @@ -0,0 +1,65 @@ +""" + GeneratorAvailability + +Generator availability represents the availability of generators at timestamps +in a GeneratorAvailabilityResult with a (generators, timestamps, samples) matrix API. + +No averaging occurs. +""" +struct GeneratorAvailability <: ResultSpec end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::GeneratorAvailability +) where {N} + + ngens = length(sys.generators) + available = zeros(Bool, ngens, N, nsamples) + + return GenAvailabilityAccumulator(available) + +end + +struct GenAvailabilityAccumulator <: + ResultAccumulator{GeneratorAvailability} + + available::Array{Bool,3} + +end + +function merge!( + x::GenAvailabilityAccumulator, y::GenAvailabilityAccumulator +) + + x.available .|= y.available + return + +end + +accumulatortype(::GeneratorAvailability) = GenAvailabilityAccumulator + +struct GeneratorAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} + + generators::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + available::Array{Bool,3} + +end + +names(x::GeneratorAvailabilityResult) = x.generators + +function getindex(x::GeneratorAvailabilityResult, g::AbstractString, t::ZonedDateTime) + i_g = findfirstunique(x.generators, g) + i_t = findfirstunique(x.timestamps, t) + return vec(x.available[i_g, i_t, :]) +end + +function finalize( + acc::GenAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return GeneratorAvailabilityResult{N,L,T}( + system.generators.names, system.timestamps, acc.available) + +end diff --git a/PRASCore/src/Results/GeneratorStorageAvailability.jl b/PRASCore/src/Results/GeneratorStorageAvailability.jl new file mode 100644 index 00000000..3cff9d62 --- /dev/null +++ b/PRASCore/src/Results/GeneratorStorageAvailability.jl @@ -0,0 +1,64 @@ +""" + GeneratorStorageAvailability + +Generator storage availability represents the availability of generatorstorage resources at timestamps +in a GeneratorStorageAvailabilityResult with a (generatorstorages, timestamps, samples) matrix API. + +No averaging occurs +""" +struct GeneratorStorageAvailability <: ResultSpec end + +struct GenStorAvailabilityAccumulator <: ResultAccumulator{GeneratorStorageAvailability} + + available::Array{Bool,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::GeneratorStorageAvailability +) where {N} + + ngenstors = length(sys.generatorstorages) + available = zeros(Bool, ngenstors, N, nsamples) + + return GenStorAvailabilityAccumulator(available) + +end + +function merge!( + x::GenStorAvailabilityAccumulator, y::GenStorAvailabilityAccumulator +) + + x.available .|= y.available + return + +end + +accumulatortype(::GeneratorStorageAvailability) = GenStorAvailabilityAccumulator + +struct GeneratorStorageAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} + + generatorstorages::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + available::Array{Bool,3} + +end + +names(x::GeneratorStorageAvailabilityResult) = x.generatorstorages + +function getindex(x::GeneratorStorageAvailabilityResult, gs::AbstractString, t::ZonedDateTime) + i_gs = findfirstunique(x.generatorstorages, gs) + i_t = findfirstunique(x.timestamps, t) + return vec(x.available[i_gs, i_t, :]) +end + +function finalize( + acc::GenStorAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return GeneratorStorageAvailabilityResult{N,L,T}( + system.generatorstorages.names, system.timestamps, acc.available) + +end diff --git a/PRASCore/src/Results/GeneratorStorageEnergy.jl b/PRASCore/src/Results/GeneratorStorageEnergy.jl new file mode 100644 index 00000000..3aa5e40f --- /dev/null +++ b/PRASCore/src/Results/GeneratorStorageEnergy.jl @@ -0,0 +1,91 @@ +""" + GeneratorStorageEnergy + +Generator storage energy represents state-of-charge of generatorstorage +resources at timestamps in a StorageEnergyResult with a (generatorstorages, timestamps) +matrix API. + +Separate samples are averaged together into mean and std values. + +See [`GeneratorStorageEnergySamples`](@ref) for all generator storage energy samples. + +See [`StorageEnergy`](@ref) for storage energy. +""" +struct GeneratorStorageEnergy <: ResultSpec end + +mutable struct GenStorageEnergyAccumulator <: ResultAccumulator{GeneratorStorageEnergy} + + # Cross-simulation energy mean/variances + energy_period::Vector{MeanVariance} + energy_genstorperiod::Matrix{MeanVariance} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::GeneratorStorageEnergy +) where {N} + + ngenstors = length(sys.generatorstorages) + + energy_period = [meanvariance() for _ in 1:N] + energy_genstorperiod = [meanvariance() for _ in 1:ngenstors, _ in 1:N] + + return GenStorageEnergyAccumulator( + energy_period, energy_genstorperiod) + +end + +function merge!( + x::GenStorageEnergyAccumulator, y::GenStorageEnergyAccumulator +) + + foreach(merge!, x.energy_period, y.energy_period) + foreach(merge!, x.energy_genstorperiod, y.energy_genstorperiod) + + return + +end + +accumulatortype(::GeneratorStorageEnergy) = GenStorageEnergyAccumulator + +struct GeneratorStorageEnergyResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} + + nsamples::Union{Int,Nothing} + generatorstorages::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + energy_mean::Matrix{Float64} + + energy_period_std::Vector{Float64} + energy_regionperiod_std::Matrix{Float64} + +end + +names(x::GeneratorStorageEnergyResult) = x.generatorstorages + +function getindex(x::GeneratorStorageEnergyResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return sum(view(x.energy_mean, :, i_t)), x.energy_period_std[i_t] +end + +function getindex(x::GeneratorStorageEnergyResult, gs::AbstractString, t::ZonedDateTime) + i_gs = findfirstunique(x.generatorstorages, gs) + i_t = findfirstunique(x.timestamps, t) + return x.energy_mean[i_gs, i_t], x.energy_regionperiod_std[i_gs, i_t] +end + +function finalize( + acc::GenStorageEnergyAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + _, period_std = mean_std(acc.energy_period) + genstorperiod_mean, genstorperiod_std = mean_std(acc.energy_genstorperiod) + + nsamples = first(first(acc.energy_period).stats).n + + return GeneratorStorageEnergyResult{N,L,T,E}( + nsamples, system.generatorstorages.names, system.timestamps, + genstorperiod_mean, period_std, genstorperiod_std) + +end diff --git a/PRASCore/src/Results/GeneratorStorageEnergySamples.jl b/PRASCore/src/Results/GeneratorStorageEnergySamples.jl new file mode 100644 index 00000000..62f71d03 --- /dev/null +++ b/PRASCore/src/Results/GeneratorStorageEnergySamples.jl @@ -0,0 +1,71 @@ +""" + GeneratorStorageEnergySamples + +Generator storage energy samples represent the state-of-charge of generatorstorage +resources at timestamps, which has not been averaged across different samples. +This presents a 3D matrix API (generatorstorages, timestamps, samples). + +See [`GeneratorStorageEnergy`](@ref) for sample-averaged generator storage energy. +""" +struct GeneratorStorageEnergySamples <: ResultSpec end + +struct GenStorageEnergySamplesAccumulator <: ResultAccumulator{GeneratorStorageEnergySamples} + + energy::Array{Float64,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::GeneratorStorageEnergySamples +) where {N} + + ngenstors = length(sys.generatorstorages) + energy = zeros(Int, ngenstors, N, nsamples) + + return GenStorageEnergySamplesAccumulator(energy) + +end + +function merge!( + x::GenStorageEnergySamplesAccumulator, + y::GenStorageEnergySamplesAccumulator +) + + x.energy .+= y.energy + return + +end + +accumulatortype(::GeneratorStorageEnergySamples) = GenStorageEnergySamplesAccumulator + +struct GeneratorStorageEnergySamplesResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} + + generatorstorages::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + energy::Array{Int,3} + +end + +names(x::GeneratorStorageEnergySamplesResult) = x.generatorstorages + +function getindex(x::GeneratorStorageEnergySamplesResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return vec(sum(view(x.energy, :, i_t, :), dims=1)) +end + +function getindex(x::GeneratorStorageEnergySamplesResult, gs::AbstractString, t::ZonedDateTime) + i_gs = findfirstunique(x.generatorstorages, gs) + i_t = findfirstunique(x.timestamps, t) + return vec(x.energy[i_gs, i_t, :]) +end + +function finalize( + acc::GenStorageEnergySamplesAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return GeneratorStorageEnergySamplesResult{N,L,T,E}( + system.generatorstorages.names, system.timestamps, acc.energy) + +end diff --git a/PRASCore/src/Results/LineAvailability.jl b/PRASCore/src/Results/LineAvailability.jl new file mode 100644 index 00000000..9af1e062 --- /dev/null +++ b/PRASCore/src/Results/LineAvailability.jl @@ -0,0 +1,64 @@ +""" + LineAvailability + +Line availability represents the availability of lines at timestamps +in a LineAvailabilityResult with a (lines, timestamps, samples) matrix API. + +No averaging occurs. +""" +struct LineAvailability <: ResultSpec end + +struct LineAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} + + lines::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + available::Array{Bool,3} + +end + +names(x::LineAvailabilityResult) = x.lines + +function getindex(x::LineAvailabilityResult, l::AbstractString, t::ZonedDateTime) + i_l = findfirstunique(x.lines, l) + i_t = findfirstunique(x.timestamps, t) + return vec(x.available[i_l, i_t, :]) +end + +struct LineAvailabilityAccumulator <: ResultAccumulator{LineAvailability} + + available::Array{Bool,3} + +end + +accumulatortype(::LineAvailability) = LineAvailabilityAccumulator + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::LineAvailability +) where {N} + + nlines = length(sys.lines) + available = zeros(Bool, nlines, N, nsamples) + + return LineAvailabilityAccumulator(available) + +end + +function merge!( + x::LineAvailabilityAccumulator, y::LineAvailabilityAccumulator +) + + x.available .|= y.available + return + +end + +function finalize( + acc::LineAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return LineAvailabilityResult{N,L,T}( + system.lines.names, system.timestamps, acc.available) + +end diff --git a/PRASCore/src/Results/Results.jl b/PRASCore/src/Results/Results.jl new file mode 100644 index 00000000..9199dd60 --- /dev/null +++ b/PRASCore/src/Results/Results.jl @@ -0,0 +1,191 @@ +@reexport module Results + +import Base: broadcastable, getindex, merge! +import OnlineStats: Series +import OnlineStatsBase: EqualWeight, Mean, Variance, value +import Printf: @sprintf +import StatsBase: mean, std, stderror + +import ..Systems: SystemModel, ZonedDateTime, Period, + PowerUnit, EnergyUnit, conversionfactor, unitsymbol +export + + # Metrics + ReliabilityMetric, LOLE, EUE, + val, stderror, + + # Result specifications + Shortfall, ShortfallSamples, Surplus, SurplusSamples, + Flow, FlowSamples, Utilization, UtilizationSamples, + StorageEnergy, StorageEnergySamples, + GeneratorStorageEnergy, GeneratorStorageEnergySamples, + GeneratorAvailability, StorageAvailability, + GeneratorStorageAvailability, LineAvailability + +include("utils.jl") +include("metrics.jl") + +abstract type ResultSpec end + +abstract type ResultAccumulator{R<:ResultSpec} end + +abstract type Result{ + N, # Number of timesteps simulated + L, # Length of each simulation timestep + T <: Period, # Units of each simulation timestep +} end + +broadcastable(x::ResultSpec) = Ref(x) +broadcastable(x::Result) = Ref(x) + +abstract type AbstractShortfallResult{N,L,T} <: Result{N,L,T} end + +getindex(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) = + getindex.(x, x.regions, t) + +getindex(x::AbstractShortfallResult, r::AbstractString, ::Colon) = + getindex.(x, r, x.timestamps) + +getindex(x::AbstractShortfallResult, ::Colon, ::Colon) = + getindex.(x, x.regions, permutedims(x.timestamps)) + + +LOLE(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) = + LOLE.(x, x.regions, t) + +LOLE(x::AbstractShortfallResult, r::AbstractString, ::Colon) = + LOLE.(x, r, x.timestamps) + +LOLE(x::AbstractShortfallResult, ::Colon, ::Colon) = + LOLE.(x, x.regions, permutedims(x.timestamps)) + + +EUE(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) = + EUE.(x, x.regions, t) + +EUE(x::AbstractShortfallResult, r::AbstractString, ::Colon) = + EUE.(x, r, x.timestamps) + +EUE(x::AbstractShortfallResult, ::Colon, ::Colon) = + EUE.(x, x.regions, permutedims(x.timestamps)) + +include("Shortfall.jl") +include("ShortfallSamples.jl") + +abstract type AbstractSurplusResult{N,L,T} <: Result{N,L,T} end + +getindex(x::AbstractSurplusResult, ::Colon) = + getindex.(x, x.timestamps) + +getindex(x::AbstractSurplusResult, ::Colon, t::ZonedDateTime) = + getindex.(x, x.regions, t) + +getindex(x::AbstractSurplusResult, r::AbstractString, ::Colon) = + getindex.(x, r, x.timestamps) + +getindex(x::AbstractSurplusResult, ::Colon, ::Colon) = + getindex.(x, x.regions, permutedims(x.timestamps)) + +include("Surplus.jl") +include("SurplusSamples.jl") + +abstract type AbstractFlowResult{N,L,T} <: Result{N,L,T} end + +getindex(x::AbstractFlowResult, ::Colon) = + getindex.(x, x.interfaces) + +getindex(x::AbstractFlowResult, ::Colon, t::ZonedDateTime) = + getindex.(x, x.interfaces, t) + +getindex(x::AbstractFlowResult, i::Pair{<:AbstractString,<:AbstractString}, ::Colon) = + getindex.(x, i, x.timestamps) + +getindex(x::AbstractFlowResult, ::Colon, ::Colon) = + getindex.(x, x.interfaces, permutedims(x.timestamps)) + +include("Flow.jl") +include("FlowSamples.jl") + +abstract type AbstractUtilizationResult{N,L,T} <: Result{N,L,T} end + +getindex(x::AbstractUtilizationResult, ::Colon) = + getindex.(x, x.interfaces) + +getindex(x::AbstractUtilizationResult, ::Colon, t::ZonedDateTime) = + getindex.(x, x.interfaces, t) + +getindex(x::AbstractUtilizationResult, i::Pair{<:AbstractString,<:AbstractString}, ::Colon) = + getindex.(x, i, x.timestamps) + +getindex(x::AbstractUtilizationResult, ::Colon, ::Colon) = + getindex.(x, x.interfaces, permutedims(x.timestamps)) + +include("Utilization.jl") +include("UtilizationSamples.jl") + +abstract type AbstractAvailabilityResult{N,L,T} <: Result{N,L,T} end + +getindex(x::AbstractAvailabilityResult, ::Colon, t::ZonedDateTime) = + getindex.(x, names(x), t) + +getindex(x::AbstractAvailabilityResult, name::String, ::Colon) = + getindex.(x, name, x.timestamps) + +getindex(x::AbstractAvailabilityResult, ::Colon, ::Colon) = + getindex.(x, names(x), permutedims(x.timestamps)) + +include("GeneratorAvailability.jl") +include("StorageAvailability.jl") +include("GeneratorStorageAvailability.jl") +include("LineAvailability.jl") + +abstract type AbstractEnergyResult{N,L,T} <: Result{N,L,T} end + +getindex(x::AbstractEnergyResult, ::Colon) = + getindex.(x, x.timestamps) + +getindex(x::AbstractEnergyResult, ::Colon, t::ZonedDateTime) = + getindex.(x, names(x), t) + +getindex(x::AbstractEnergyResult, name::String, ::Colon) = + getindex.(x, name, x.timestamps) + +getindex(x::AbstractEnergyResult, ::Colon, ::Colon) = + getindex.(x, names(x), permutedims(x.timestamps)) + +include("StorageEnergy.jl") +include("GeneratorStorageEnergy.jl") +include("StorageEnergySamples.jl") +include("GeneratorStorageEnergySamples.jl") + +function resultchannel( + results::T, threads::Int +) where T <: Tuple{Vararg{ResultSpec}} + + types = accumulatortype.(results) + return Channel{Tuple{types...}}(threads) + +end + +merge!(xs::T, ys::T) where T <: Tuple{Vararg{ResultAccumulator}} = + foreach(merge!, xs, ys) + +function finalize( + results::Channel{<:Tuple{Vararg{ResultAccumulator}}}, + system::SystemModel{N,L,T,P,E}, + threads::Int +) where {N,L,T,P,E} + + total_result = take!(results) + + for _ in 2:threads + thread_result = take!(results) + merge!(total_result, thread_result) + end + close(results) + + return finalize.(total_result, system) + +end + +end diff --git a/src/ResourceAdequacy/results/shortfall.jl b/PRASCore/src/Results/Shortfall.jl similarity index 59% rename from src/ResourceAdequacy/results/shortfall.jl rename to PRASCore/src/Results/Shortfall.jl index e9504bd2..567be50e 100644 --- a/src/ResourceAdequacy/results/shortfall.jl +++ b/PRASCore/src/Results/Shortfall.jl @@ -1,4 +1,3 @@ - """ Shortfall @@ -10,40 +9,82 @@ Separate samples are averaged together into mean and std values. See [`ShortfallSamples`](@ref) for all shortfall samples. """ struct Shortfall <: ResultSpec end -abstract type AbstractShortfallResult{N,L,T} <: Result{N,L,T} end -# Colon indexing +mutable struct ShortfallAccumulator <: ResultAccumulator{Shortfall} + + # Cross-simulation LOL period count mean/variances + periodsdropped_total::MeanVariance + periodsdropped_region::Vector{MeanVariance} + periodsdropped_period::Vector{MeanVariance} + periodsdropped_regionperiod::Matrix{MeanVariance} -getindex(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) = - getindex.(x, x.regions, t) + # Running LOL period counts for current simulation + periodsdropped_total_currentsim::Int + periodsdropped_region_currentsim::Vector{Int} -getindex(x::AbstractShortfallResult, r::AbstractString, ::Colon) = - getindex.(x, r, x.timestamps) + # Cross-simulation UE mean/variances + unservedload_total::MeanVariance + unservedload_region::Vector{MeanVariance} + unservedload_period::Vector{MeanVariance} + unservedload_regionperiod::Matrix{MeanVariance} -getindex(x::AbstractShortfallResult, ::Colon, ::Colon) = - getindex.(x, x.regions, permutedims(x.timestamps)) + # Running UE totals for current simulation + unservedload_total_currentsim::Int + unservedload_region_currentsim::Vector{Int} + +end +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::Shortfall +) where {N} -LOLE(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) = - LOLE.(x, x.regions, t) + nregions = length(sys.regions) -LOLE(x::AbstractShortfallResult, r::AbstractString, ::Colon) = - LOLE.(x, r, x.timestamps) + periodsdropped_total = meanvariance() + periodsdropped_region = [meanvariance() for _ in 1:nregions] + periodsdropped_period = [meanvariance() for _ in 1:N] + periodsdropped_regionperiod = [meanvariance() for _ in 1:nregions, _ in 1:N] -LOLE(x::AbstractShortfallResult, ::Colon, ::Colon) = - LOLE.(x, x.regions, permutedims(x.timestamps)) + periodsdropped_total_currentsim = 0 + periodsdropped_region_currentsim = zeros(Int, nregions) + unservedload_total = meanvariance() + unservedload_region = [meanvariance() for _ in 1:nregions] + unservedload_period = [meanvariance() for _ in 1:N] + unservedload_regionperiod = [meanvariance() for _ in 1:nregions, _ in 1:N] -EUE(x::AbstractShortfallResult, ::Colon, t::ZonedDateTime) = - EUE.(x, x.regions, t) + unservedload_total_currentsim = 0 + unservedload_region_currentsim = zeros(Int, nregions) + + return ShortfallAccumulator( + periodsdropped_total, periodsdropped_region, + periodsdropped_period, periodsdropped_regionperiod, + periodsdropped_total_currentsim, periodsdropped_region_currentsim, + unservedload_total, unservedload_region, + unservedload_period, unservedload_regionperiod, + unservedload_total_currentsim, unservedload_region_currentsim) + +end -EUE(x::AbstractShortfallResult, r::AbstractString, ::Colon) = - EUE.(x, r, x.timestamps) +function merge!( + x::ShortfallAccumulator, y::ShortfallAccumulator +) -EUE(x::AbstractShortfallResult, ::Colon, ::Colon) = - EUE.(x, x.regions, permutedims(x.timestamps)) + merge!(x.periodsdropped_total, y.periodsdropped_total) + foreach(merge!, x.periodsdropped_region, y.periodsdropped_region) + foreach(merge!, x.periodsdropped_period, y.periodsdropped_period) + foreach(merge!, x.periodsdropped_regionperiod, y.periodsdropped_regionperiod) -# Sample-averaged shortfall data + merge!(x.unservedload_total, y.unservedload_total) + foreach(merge!, x.unservedload_region, y.unservedload_region) + foreach(merge!, x.unservedload_period, y.unservedload_period) + foreach(merge!, x.unservedload_regionperiod, y.unservedload_regionperiod) + + return + +end + +accumulatortype(::Shortfall) = ShortfallAccumulator """ ShortfallResult @@ -189,91 +230,38 @@ EUE(x::ShortfallResult{N,L,T,E}, t::ZonedDateTime) where {N,L,T,E} = EUE(x::ShortfallResult{N,L,T,E}, r::AbstractString, t::ZonedDateTime) where {N,L,T,E} = EUE{1,L,T,E}(MeanEstimate(x[r, t]..., x.nsamples)) -""" - ShortfallSamples - -ShortfallSamples metric represents lost load at regions and timesteps -in ShortfallSamplesResult with a (regions, timestamps, samples) matrix API. - -See [`Shortfall`](@ref) for averaged shortfall samples. -""" -struct ShortfallSamples <: ResultSpec end - -struct ShortfallSamplesResult{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractShortfallResult{N,L,T} - - regions::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - shortfall::Array{Int,3} # r x t x s - -end - -function getindex( - x::ShortfallSamplesResult{N,L,T,P,E} -) where {N,L,T,P,E} - p2e = conversionfactor(L, T, P, E) - return vec(p2e * sum(x.shortfall, dims=1:2)) -end - -function getindex( - x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString +function finalize( + acc::ShortfallAccumulator, + system::SystemModel{N,L,T,P,E}, ) where {N,L,T,P,E} - i_r = findfirstunique(x.regions, r) - p2e = conversionfactor(L, T, P, E) - return vec(p2e * sum(view(x.shortfall, i_r, :, :), dims=1)) -end - -function getindex( - x::ShortfallSamplesResult{N,L,T,P,E}, t::ZonedDateTime -) where {N,L,T,P,E} - i_t = findfirstunique(x.timestamps, t) - p2e = conversionfactor(L, T, P, E) - return vec(p2e * sum(view(x.shortfall, :, i_t, :), dims=1)) -end - -function getindex( - x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString, t::ZonedDateTime -) where {N,L,T,P,E} - i_r = findfirstunique(x.regions, r) - i_t = findfirstunique(x.timestamps, t) - p2e = conversionfactor(L, T, P, E) - return vec(p2e * x.shortfall[i_r, i_t, :]) -end - - -function LOLE(x::ShortfallSamplesResult{N,L,T}) where {N,L,T} - eventperiods = sum(sum(x.shortfall, dims=1) .> 0, dims=2) - return LOLE{N,L,T}(MeanEstimate(eventperiods)) -end -function LOLE(x::ShortfallSamplesResult{N,L,T}, r::AbstractString) where {N,L,T} - i_r = findfirstunique(x.regions, r) - eventperiods = sum(view(x.shortfall, i_r, :, :) .> 0, dims=1) - return LOLE{N,L,T}(MeanEstimate(eventperiods)) -end + ep_total_mean, ep_total_std = mean_std(acc.periodsdropped_total) + ep_region_mean, ep_region_std = mean_std(acc.periodsdropped_region) + ep_period_mean, ep_period_std = mean_std(acc.periodsdropped_period) + ep_regionperiod_mean, ep_regionperiod_std = + mean_std(acc.periodsdropped_regionperiod) + + _, ue_total_std = mean_std(acc.unservedload_total) + _, ue_region_std = mean_std(acc.unservedload_region) + _, ue_period_std = mean_std(acc.unservedload_period) + ue_regionperiod_mean, ue_regionperiod_std = + mean_std(acc.unservedload_regionperiod) + + nsamples = first(acc.unservedload_total.stats).n + + p2e = conversionfactor(L,T,P,E) + ue_regionperiod_mean .*= p2e + ue_total_std *= p2e + ue_region_std .*= p2e + ue_period_std .*= p2e + ue_regionperiod_std .*= p2e + + return ShortfallResult{N,L,T,E}( + nsamples, system.regions.names, system.timestamps, + ep_total_mean, ep_total_std, ep_region_mean, ep_region_std, + ep_period_mean, ep_period_std, + ep_regionperiod_mean, ep_regionperiod_std, + ue_regionperiod_mean, ue_total_std, + ue_region_std, ue_period_std, ue_regionperiod_std) -function LOLE(x::ShortfallSamplesResult{N,L,T}, t::ZonedDateTime) where {N,L,T} - i_t = findfirstunique(x.timestamps, t) - eventperiods = sum(view(x.shortfall, :, i_t, :), dims=1) .> 0 - return LOLE{1,L,T}(MeanEstimate(eventperiods)) end - -function LOLE(x::ShortfallSamplesResult{N,L,T}, r::AbstractString, t::ZonedDateTime) where {N,L,T} - i_r = findfirstunique(x.regions, r) - i_t = findfirstunique(x.timestamps, t) - eventperiods = view(x.shortfall, i_r, i_t, :) .> 0 - return LOLE{1,L,T}(MeanEstimate(eventperiods)) -end - - -EUE(x::ShortfallSamplesResult{N,L,T,P,E}) where {N,L,T,P,E} = - EUE{N,L,T,E}(MeanEstimate(x[])) - -EUE(x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString) where {N,L,T,P,E} = - EUE{N,L,T,E}(MeanEstimate(x[r])) - -EUE(x::ShortfallSamplesResult{N,L,T,P,E}, t::ZonedDateTime) where {N,L,T,P,E} = - EUE{1,L,T,E}(MeanEstimate(x[t])) - -EUE(x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString, t::ZonedDateTime) where {N,L,T,P,E} = - EUE{1,L,T,E}(MeanEstimate(x[r, t])) diff --git a/PRASCore/src/Results/ShortfallSamples.jl b/PRASCore/src/Results/ShortfallSamples.jl new file mode 100644 index 00000000..04087f87 --- /dev/null +++ b/PRASCore/src/Results/ShortfallSamples.jl @@ -0,0 +1,126 @@ +""" + ShortfallSamples + +ShortfallSamples metric represents lost load at regions and timesteps +in ShortfallSamplesResult with a (regions, timestamps, samples) matrix API. + +See [`Shortfall`](@ref) for averaged shortfall samples. +""" +struct ShortfallSamples <: ResultSpec end + +struct ShortfallSamplesAccumulator <: ResultAccumulator{ShortfallSamples} + + shortfall::Array{Int,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::ShortfallSamples +) where {N} + + nregions = length(sys.regions) + shortfall = zeros(Int, nregions, N, nsamples) + + return ShortfallSamplesAccumulator(shortfall) + +end + +function merge!( + x::ShortfallSamplesAccumulator, y::ShortfallSamplesAccumulator +) + + x.shortfall .+= y.shortfall + return + +end + +accumulatortype(::ShortfallSamples) = ShortfallSamplesAccumulator + +struct ShortfallSamplesResult{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractShortfallResult{N,L,T} + + regions::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + shortfall::Array{Int,3} # r x t x s + +end + +function getindex( + x::ShortfallSamplesResult{N,L,T,P,E} +) where {N,L,T,P,E} + p2e = conversionfactor(L, T, P, E) + return vec(p2e * sum(x.shortfall, dims=1:2)) +end + +function getindex( + x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString +) where {N,L,T,P,E} + i_r = findfirstunique(x.regions, r) + p2e = conversionfactor(L, T, P, E) + return vec(p2e * sum(view(x.shortfall, i_r, :, :), dims=1)) +end + +function getindex( + x::ShortfallSamplesResult{N,L,T,P,E}, t::ZonedDateTime +) where {N,L,T,P,E} + i_t = findfirstunique(x.timestamps, t) + p2e = conversionfactor(L, T, P, E) + return vec(p2e * sum(view(x.shortfall, :, i_t, :), dims=1)) +end + +function getindex( + x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString, t::ZonedDateTime +) where {N,L,T,P,E} + i_r = findfirstunique(x.regions, r) + i_t = findfirstunique(x.timestamps, t) + p2e = conversionfactor(L, T, P, E) + return vec(p2e * x.shortfall[i_r, i_t, :]) +end + + +function LOLE(x::ShortfallSamplesResult{N,L,T}) where {N,L,T} + eventperiods = sum(sum(x.shortfall, dims=1) .> 0, dims=2) + return LOLE{N,L,T}(MeanEstimate(eventperiods)) +end + +function LOLE(x::ShortfallSamplesResult{N,L,T}, r::AbstractString) where {N,L,T} + i_r = findfirstunique(x.regions, r) + eventperiods = sum(view(x.shortfall, i_r, :, :) .> 0, dims=1) + return LOLE{N,L,T}(MeanEstimate(eventperiods)) +end + +function LOLE(x::ShortfallSamplesResult{N,L,T}, t::ZonedDateTime) where {N,L,T} + i_t = findfirstunique(x.timestamps, t) + eventperiods = sum(view(x.shortfall, :, i_t, :), dims=1) .> 0 + return LOLE{1,L,T}(MeanEstimate(eventperiods)) +end + +function LOLE(x::ShortfallSamplesResult{N,L,T}, r::AbstractString, t::ZonedDateTime) where {N,L,T} + i_r = findfirstunique(x.regions, r) + i_t = findfirstunique(x.timestamps, t) + eventperiods = view(x.shortfall, i_r, i_t, :) .> 0 + return LOLE{1,L,T}(MeanEstimate(eventperiods)) +end + + +EUE(x::ShortfallSamplesResult{N,L,T,P,E}) where {N,L,T,P,E} = + EUE{N,L,T,E}(MeanEstimate(x[])) + +EUE(x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString) where {N,L,T,P,E} = + EUE{N,L,T,E}(MeanEstimate(x[r])) + +EUE(x::ShortfallSamplesResult{N,L,T,P,E}, t::ZonedDateTime) where {N,L,T,P,E} = + EUE{1,L,T,E}(MeanEstimate(x[t])) + +EUE(x::ShortfallSamplesResult{N,L,T,P,E}, r::AbstractString, t::ZonedDateTime) where {N,L,T,P,E} = + EUE{1,L,T,E}(MeanEstimate(x[r, t])) + +function finalize( + acc::ShortfallSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return ShortfallSamplesResult{N,L,T,P,E}( + system.regions.names, system.timestamps, acc.shortfall) + +end diff --git a/PRASCore/src/Results/StorageAvailability.jl b/PRASCore/src/Results/StorageAvailability.jl new file mode 100644 index 00000000..1a053d05 --- /dev/null +++ b/PRASCore/src/Results/StorageAvailability.jl @@ -0,0 +1,64 @@ +""" + StorageAvailability + +Storage availability represents the availability of storage resources at timestamps +in a StorageAvailabilityResult with a (storages, timestamps, samples) matrix API. + +No averaging occurs. +""" +struct StorageAvailability <: ResultSpec end + +struct StorAvailabilityAccumulator <: ResultAccumulator{StorageAvailability} + + available::Array{Bool,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::StorageAvailability +) where {N} + + nstors = length(sys.storages) + available = zeros(Bool, nstors, N, nsamples) + + return StorAvailabilityAccumulator(available) + +end + +function merge!( + x::StorAvailabilityAccumulator, y::StorAvailabilityAccumulator +) + + x.available .|= y.available + return + +end + +accumulatortype(::StorageAvailability) = StorAvailabilityAccumulator + +struct StorageAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} + + storages::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + available::Array{Bool,3} + +end + +names(x::StorageAvailabilityResult) = x.storages + +function getindex(x::StorageAvailabilityResult, s::AbstractString, t::ZonedDateTime) + i_s = findfirstunique(x.storages, s) + i_t = findfirstunique(x.timestamps, t) + return vec(x.available[i_s, i_t, :]) +end + +function finalize( + acc::StorAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return StorageAvailabilityResult{N,L,T}( + system.storages.names, system.timestamps, acc.available) + +end diff --git a/PRASCore/src/Results/StorageEnergy.jl b/PRASCore/src/Results/StorageEnergy.jl new file mode 100644 index 00000000..7ca4ef3d --- /dev/null +++ b/PRASCore/src/Results/StorageEnergy.jl @@ -0,0 +1,91 @@ +""" + StorageEnergy + +Storage energy represents the state-of-charge of storage +resources at timestamps in a StorageEnergyResult with a (storages, timestamps) +matrix API. + +Separate samples are averaged together into mean and std values. + +See [`StorageEnergySamples`](@ref) for all storage energy samples. + +See [`GeneratorStorageEnergy`](@ref) for generator storage energy. +""" +struct StorageEnergy <: ResultSpec end + +mutable struct StorageEnergyAccumulator <: ResultAccumulator{StorageEnergy} + + # Cross-simulation energy mean/variances + energy_period::Vector{MeanVariance} + energy_storageperiod::Matrix{MeanVariance} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::StorageEnergy +) where {N} + + nstorages = length(sys.storages) + + energy_period = [meanvariance() for _ in 1:N] + energy_storageperiod = [meanvariance() for _ in 1:nstorages, _ in 1:N] + + return StorageEnergyAccumulator( + energy_period, energy_storageperiod) + +end + +function merge!( + x::StorageEnergyAccumulator, y::StorageEnergyAccumulator +) + + foreach(merge!, x.energy_period, y.energy_period) + foreach(merge!, x.energy_storageperiod, y.energy_storageperiod) + + return + +end + +accumulatortype(::StorageEnergy) = StorageEnergyAccumulator + +struct StorageEnergyResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} + + nsamples::Union{Int,Nothing} + storages::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + energy_mean::Matrix{Float64} + + energy_period_std::Vector{Float64} + energy_regionperiod_std::Matrix{Float64} + +end + +names(x::StorageEnergyResult) = x.storages + +function getindex(x::StorageEnergyResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return sum(view(x.energy_mean, :, i_t)), x.energy_period_std[i_t] +end + +function getindex(x::StorageEnergyResult, s::AbstractString, t::ZonedDateTime) + i_s = findfirstunique(x.storages, s) + i_t = findfirstunique(x.timestamps, t) + return x.energy_mean[i_s, i_t], x.energy_regionperiod_std[i_s, i_t] +end + +function finalize( + acc::StorageEnergyAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + _, period_std = mean_std(acc.energy_period) + storageperiod_mean, storageperiod_std = mean_std(acc.energy_storageperiod) + + nsamples = first(first(acc.energy_period).stats).n + + return StorageEnergyResult{N,L,T,E}( + nsamples, system.storages.names, system.timestamps, + storageperiod_mean, period_std, storageperiod_std) + +end diff --git a/PRASCore/src/Results/StorageEnergySamples.jl b/PRASCore/src/Results/StorageEnergySamples.jl new file mode 100644 index 00000000..043eb6d9 --- /dev/null +++ b/PRASCore/src/Results/StorageEnergySamples.jl @@ -0,0 +1,70 @@ +""" + StorageEnergySamples + +Storage energy samples represent the state-of-charge of storage +resources at timestamps, which has not been averaged across different samples. +This presents a 3D matrix API (storages, timestamps, samples). + +See [`StorageEnergy`](@ref) for sample-averaged storage energy. +""" +struct StorageEnergySamples <: ResultSpec end + +struct StorageEnergySamplesAccumulator <: ResultAccumulator{StorageEnergySamples} + + energy::Array{Float64,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::StorageEnergySamples +) where {N} + + nstors = length(sys.storages) + energy = zeros(Int, nstors, N, nsamples) + + return StorageEnergySamplesAccumulator(energy) + +end + +function merge!( + x::StorageEnergySamplesAccumulator, y::StorageEnergySamplesAccumulator +) + + x.energy .+= y.energy + return + +end + +accumulatortype(::StorageEnergySamples) = StorageEnergySamplesAccumulator + +struct StorageEnergySamplesResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} + + storages::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + energy::Array{Int,3} + +end + +names(x::StorageEnergySamplesResult) = x.storages + +function getindex(x::StorageEnergySamplesResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return vec(sum(view(x.energy, :, i_t, :), dims=1)) +end + +function getindex(x::StorageEnergySamplesResult, s::AbstractString, t::ZonedDateTime) + i_s = findfirstunique(x.storages, s) + i_t = findfirstunique(x.timestamps, t) + return vec(x.energy[i_s, i_t, :]) +end + +function finalize( + acc::StorageEnergySamplesAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return StorageEnergySamplesResult{N,L,T,E}( + system.storages.names, system.timestamps, acc.energy) + +end diff --git a/PRASCore/src/Results/Surplus.jl b/PRASCore/src/Results/Surplus.jl new file mode 100644 index 00000000..a167410d --- /dev/null +++ b/PRASCore/src/Results/Surplus.jl @@ -0,0 +1,86 @@ +""" + Surplus + +Surplus metric represents extra generation at regions and timestamps +in a SurplusResults with a (regions, timestamps) matrix API. + +Separate samples are averaged together into mean and std values. + +See [`SurplusSamples`](@ref) for all surplus samples. +""" +struct Surplus <: ResultSpec end + +mutable struct SurplusAccumulator <: ResultAccumulator{Surplus} + + # Cross-simulation surplus mean/variances + surplus_period::Vector{MeanVariance} + surplus_regionperiod::Matrix{MeanVariance} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::Surplus +) where {N} + + nregions = length(sys.regions) + + surplus_period = [meanvariance() for _ in 1:N] + surplus_regionperiod = [meanvariance() for _ in 1:nregions, _ in 1:N] + + return SurplusAccumulator( + surplus_period, surplus_regionperiod) + +end + +function merge!( + x::SurplusAccumulator, y::SurplusAccumulator +) + + foreach(merge!, x.surplus_period, y.surplus_period) + foreach(merge!, x.surplus_regionperiod, y.surplus_regionperiod) + + return + +end + +accumulatortype(::Surplus) = SurplusAccumulator + +struct SurplusResult{N,L,T<:Period,P<:PowerUnit} <: AbstractSurplusResult{N,L,T} + + nsamples::Union{Int,Nothing} + regions::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + surplus_mean::Matrix{Float64} + + surplus_period_std::Vector{Float64} + surplus_regionperiod_std::Matrix{Float64} + +end + +function getindex(x::SurplusResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return sum(view(x.surplus_mean, :, i_t)), x.surplus_period_std[i_t] +end + +function getindex(x::SurplusResult, r::AbstractString, t::ZonedDateTime) + i_r = findfirstunique(x.regions, r) + i_t = findfirstunique(x.timestamps, t) + return x.surplus_mean[i_r, i_t], x.surplus_regionperiod_std[i_r, i_t] +end + +function finalize( + acc::SurplusAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + _, period_std = mean_std(acc.surplus_period) + regionperiod_mean, regionperiod_std = mean_std(acc.surplus_regionperiod) + + nsamples = first(first(acc.surplus_period).stats).n + + return SurplusResult{N,L,T,P}( + nsamples, system.regions.names, system.timestamps, + regionperiod_mean, period_std, regionperiod_std) + +end diff --git a/PRASCore/src/Results/SurplusSamples.jl b/PRASCore/src/Results/SurplusSamples.jl new file mode 100644 index 00000000..e0202df3 --- /dev/null +++ b/PRASCore/src/Results/SurplusSamples.jl @@ -0,0 +1,67 @@ +""" + SurplusSamples + +Surplus samples represent extra generation at regions and timestamps +in a SurplusSamplesResult with a (regions, timestamps, samples) matrix API. + +See [`Surplus`](@ref) for sample-averaged surplus data. +""" +struct SurplusSamples <: ResultSpec end + +struct SurplusSamplesAccumulator <: ResultAccumulator{SurplusSamples} + + surplus::Array{Int,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::SurplusSamples +) where {N} + + nregions = length(sys.regions) + surplus = zeros(Int, nregions, N, nsamples) + + return SurplusSamplesAccumulator(surplus) + +end + +function merge!( + x::SurplusSamplesAccumulator, y::SurplusSamplesAccumulator +) + + x.surplus .+= y.surplus + return + +end + +accumulatortype(::SurplusSamples) = SurplusSamplesAccumulator + +struct SurplusSamplesResult{N,L,T<:Period,P<:PowerUnit} <: AbstractSurplusResult{N,L,T} + + regions::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + surplus::Array{Int,3} + +end + +function getindex(x::SurplusSamplesResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return vec(sum(view(x.surplus, :, i_t, :), dims=1)) +end + +function getindex(x::SurplusSamplesResult, r::AbstractString, t::ZonedDateTime) + i_r = findfirstunique(x.regions, r) + i_t = findfirstunique(x.timestamps, t) + return vec(x.surplus[i_r, i_t, :]) +end + +function finalize( + acc::SurplusSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + return SurplusSamplesResult{N,L,T,P}( + system.regions.names, system.timestamps, acc.surplus) + +end diff --git a/PRASCore/src/Results/Utilization.jl b/PRASCore/src/Results/Utilization.jl new file mode 100644 index 00000000..e86a042b --- /dev/null +++ b/PRASCore/src/Results/Utilization.jl @@ -0,0 +1,90 @@ +""" + Utilization + +Utilization metric represents how much an interface between regions is used +across timestamps in a UtilizationResult with a (interfaces, timestamps) matrix API. + +Separate samples are averaged together into mean and std values. + +See [`UtilizationSamples`](@ref) for all utilization samples. +""" +struct Utilization <: ResultSpec end + +struct UtilizationAccumulator <: ResultAccumulator{Utilization} + + util_interface::Vector{MeanVariance} + util_interfaceperiod::Matrix{MeanVariance} + + util_interface_currentsim::Vector{Float64} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::Utilization +) where {N} + + n_interfaces = length(sys.interfaces) + util_interface = [meanvariance() for _ in 1:n_interfaces] + util_interfaceperiod = [meanvariance() for _ in 1:n_interfaces, _ in 1:N] + + util_interface_currentsim = zeros(Int, n_interfaces) + + return UtilizationAccumulator( + util_interface, util_interfaceperiod, util_interface_currentsim) + +end + +function merge!( + x::UtilizationAccumulator, y::UtilizationAccumulator +) + + foreach(merge!, x.util_interface, y.util_interface) + foreach(merge!, x.util_interfaceperiod, y.util_interfaceperiod) + +end + +accumulatortype(::Utilization) = UtilizationAccumulator + +struct UtilizationResult{N,L,T<:Period} <: AbstractUtilizationResult{N,L,T} + + nsamples::Union{Int,Nothing} + interfaces::Vector{Pair{String,String}} + timestamps::StepRange{ZonedDateTime,T} + + utilization_mean::Matrix{Float64} + + utilization_interface_std::Vector{Float64} + utilization_interfaceperiod_std::Matrix{Float64} + +end + +function getindex(x::UtilizationResult, i::Pair{<:AbstractString,<:AbstractString}) + i_i, _ = findfirstunique_directional(x.interfaces, i) + return mean(view(x.utilization_mean, i_i, :)), x.utilization_interface_std[i_i] +end + +function getindex(x::UtilizationResult, i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) + i_i, _ = findfirstunique_directional(x.interfaces, i) + i_t = findfirstunique(x.timestamps, t) + return x.utilization_mean[i_i, i_t], x.utilization_interfaceperiod_std[i_i, i_t] +end + +function finalize( + acc::UtilizationAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + nsamples = length(system.interfaces) > 0 ? + first(acc.util_interface[1].stats).n : nothing + + util_mean, util_interfaceperiod_std = mean_std(acc.util_interfaceperiod) + util_interface_std = last(mean_std(acc.util_interface)) / N + + fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) + toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) + + return UtilizationResult{N,L,T}( + nsamples, Pair.(fromregions, toregions), system.timestamps, + util_mean, util_interface_std, util_interfaceperiod_std) + +end diff --git a/PRASCore/src/Results/UtilizationSamples.jl b/PRASCore/src/Results/UtilizationSamples.jl new file mode 100644 index 00000000..8bcab203 --- /dev/null +++ b/PRASCore/src/Results/UtilizationSamples.jl @@ -0,0 +1,74 @@ +""" + UtilizationSamples + +Utilization samples represent the utilization between interfaces at timestamps, which has +not been averaged across different samples. This presents a +3D matrix API (interfaces, timestamps, samples). + +See [`Utilization`](@ref) for averaged utilization samples. +""" +struct UtilizationSamples <: ResultSpec end + +struct UtilizationSamplesAccumulator <: ResultAccumulator{UtilizationSamples} + + utilization::Array{Float64,3} + +end + +function accumulator( + sys::SystemModel{N}, nsamples::Int, ::UtilizationSamples +) where {N} + + ninterfaces = length(sys.interfaces) + utilization = zeros(Float64, ninterfaces, N, nsamples) + + return UtilizationSamplesAccumulator(utilization) + +end + +function merge!( + x::UtilizationSamplesAccumulator, y::UtilizationSamplesAccumulator +) + + x.utilization .+= y.utilization + return + +end + +accumulatortype(::UtilizationSamples) = UtilizationSamplesAccumulator + +struct UtilizationSamplesResult{N,L,T<:Period} <: AbstractUtilizationResult{N,L,T} + + interfaces::Vector{Pair{String,String}} + timestamps::StepRange{ZonedDateTime,T} + + utilization::Array{Float64,3} + +end + +function getindex(x::UtilizationSamplesResult, + i::Pair{<:AbstractString,<:AbstractString}) + i_i, _ = findfirstunique_directional(x.interfaces, i) + return vec(mean(view(x.utilization, i_i, :, :), dims=1)) +end + + +function getindex(x::UtilizationSamplesResult, + i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) + i_i, _ = findfirstunique_directional(x.interfaces, i) + i_t = findfirstunique(x.timestamps, t) + return vec(x.utilization[i_i, i_t, :]) +end + +function finalize( + acc::UtilizationSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, +) where {N,L,T,P,E} + + fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) + toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) + + return UtilizationSamplesResult{N,L,T}( + Pair.(fromregions, toregions), system.timestamps, acc.utilization) + +end diff --git a/src/ResourceAdequacy/metrics.jl b/PRASCore/src/Results/metrics.jl similarity index 98% rename from src/ResourceAdequacy/metrics.jl rename to PRASCore/src/Results/metrics.jl index d7338507..6e252a4d 100644 --- a/src/ResourceAdequacy/metrics.jl +++ b/PRASCore/src/Results/metrics.jl @@ -1,3 +1,5 @@ +abstract type ReliabilityMetric end + struct MeanEstimate estimate::Float64 diff --git a/src/ResourceAdequacy/utils.jl b/PRASCore/src/Results/utils.jl similarity index 66% rename from src/ResourceAdequacy/utils.jl rename to PRASCore/src/Results/utils.jl index 4fc10a07..41782995 100644 --- a/src/ResourceAdequacy/utils.jl +++ b/PRASCore/src/Results/utils.jl @@ -1,54 +1,42 @@ -meanvariance() = Series(Mean(), Variance()) - -function mean_std(x::MeanVariance) - m, v = value(x) - return m, sqrt(v) -end - -function mean_std(x::AbstractArray{<:MeanVariance}) - - means = similar(x, Float64) - vars = similar(means) - - for i in eachindex(x) - m, v = mean_std(x[i]) - means[i] = m - vars[i] = v - end - - return means, vars - -end - -function findfirstunique_directional(a::AbstractVector{<:Pair}, i::Pair) - i_idx = findfirst(isequal(i), a) - if isnothing(i_idx) - i_idx = findfirstunique(a, last(i) => first(i)) - reverse = true - else - reverse = false - end - return i_idx, reverse -end - -function findfirstunique(a::AbstractVector{T}, i::T) where T - i_idx = findfirst(isequal(i), a) - i_idx === nothing && throw(BoundsError(a)) - return i_idx -end - -function assetgrouplist(idxss::Vector{UnitRange{Int}}) - results = Vector{Int}(undef, last(idxss[end])) - for (g, idxs) in enumerate(idxss) - results[idxs] .= g - end - return results -end - -function colsum(x::Matrix{T}, col::Int) where {T} - result = zero(T) - for i in 1:size(x, 1) - result += x[i, col] - end - return result -end +const MeanVariance = Series{ + Number, Tuple{Mean{Float64, EqualWeight}, + Variance{Float64, Float64, EqualWeight}}} + +meanvariance() = Series(Mean(), Variance()) + +function mean_std(x::MeanVariance) + m, v = value(x) + return m, sqrt(v) +end + +function mean_std(x::AbstractArray{<:MeanVariance}) + + means = similar(x, Float64) + vars = similar(means) + + for i in eachindex(x) + m, v = mean_std(x[i]) + means[i] = m + vars[i] = v + end + + return means, vars + +end + +function findfirstunique_directional(a::AbstractVector{<:Pair}, i::Pair) + i_idx = findfirst(isequal(i), a) + if isnothing(i_idx) + i_idx = findfirstunique(a, last(i) => first(i)) + reverse = true + else + reverse = false + end + return i_idx, reverse +end + +function findfirstunique(a::AbstractVector{T}, i::T) where T + i_idx = findfirst(isequal(i), a) + i_idx === nothing && throw(BoundsError(a)) + return i_idx +end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/DispatchProblem.jl b/PRASCore/src/Simulations/DispatchProblem.jl similarity index 98% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/DispatchProblem.jl rename to PRASCore/src/Simulations/DispatchProblem.jl index ad86e0b0..28240c14 100644 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/DispatchProblem.jl +++ b/PRASCore/src/Simulations/DispatchProblem.jl @@ -418,3 +418,11 @@ function update_state!( end end + +function assetgrouplist(idxss::Vector{UnitRange{Int}}) + results = Vector{Int}(undef, last(idxss[end])) + for (g, idxs) in enumerate(idxss) + results[idxs] .= g + end + return results +end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/SequentialMonteCarlo.jl b/PRASCore/src/Simulations/Simulations.jl similarity index 84% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/SequentialMonteCarlo.jl rename to PRASCore/src/Simulations/Simulations.jl index eec56232..0178c75e 100644 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/SequentialMonteCarlo.jl +++ b/PRASCore/src/Simulations/Simulations.jl @@ -1,5 +1,26 @@ +@reexport module Simulations + +import ..Systems: SystemModel, AbstractAssets, Generators, Lines, + conversionfactor, energytopower + +import ..Results +import ..Results: ResultSpec, ResultAccumulator, + accumulator, resultchannel, finalize + +import Base: broadcastable +import Base.Threads: nthreads, @spawn +import MinCostFlows +import MinCostFlows: FlowProblem, solveflows!, + updateinjection!, updateflowlimit!, updateflowcost! +import OnlineStatsBase: fit! +import Random: AbstractRNG, rand, seed! +import Random123: Philox4x + +export assess, SequentialMonteCarlo + include("SystemState.jl") include("DispatchProblem.jl") +include("recording.jl") include("utils.jl") """ @@ -23,9 +44,9 @@ It it recommended that you fix the random seed for reproducibility. # Returns - - `SequentialMonteCarlo`: PRAS analysis method + - `SequentialMonteCarlo`: PRAS simulation specification """ -struct SequentialMonteCarlo <: SimulationSpec +struct SequentialMonteCarlo nsamples::Int seed::UInt64 @@ -42,6 +63,8 @@ struct SequentialMonteCarlo <: SimulationSpec end end +broadcastable(x::SequentialMonteCarlo) = Ref(x) + """ assess(system::SystemModel, method::SequentialMonteCarlo, resultspecs::ResultSpec...) @@ -66,7 +89,7 @@ function assess( threads = nthreads() sampleseeds = Channel{Int}(2*threads) - results = resultchannel(method, resultspecs, threads) + results = resultchannel(resultspecs, threads) @spawn makeseeds(sampleseeds, method.nsamples) @@ -100,13 +123,13 @@ end function assess( system::SystemModel{N}, method::SequentialMonteCarlo, sampleseeds::Channel{Int}, - results::Channel{<:Tuple{Vararg{ResultAccumulator{SequentialMonteCarlo}}}}, + results::Channel{<:Tuple{Vararg{ResultAccumulator}}}, resultspecs::ResultSpec... ) where N dispatchproblem = DispatchProblem(system) systemstate = SystemState(system) - recorders = accumulator.(system, method, resultspecs) + recorders = accumulator.(system, method.nsamples, resultspecs) # TODO: Test performance of Philox vs Threefry, choice of rounds # Also consider implementing an efficient Bernoulli trial with direct @@ -200,9 +223,4 @@ function solve!( update_state!(state, dispatchproblem, system, t) end -include("result_shortfall.jl") -include("result_surplus.jl") -include("result_flow.jl") -include("result_utilization.jl") -include("result_energy.jl") -include("result_availability.jl") +end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/SystemState.jl b/PRASCore/src/Simulations/SystemState.jl similarity index 100% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/SystemState.jl rename to PRASCore/src/Simulations/SystemState.jl diff --git a/PRASCore/src/Simulations/recording.jl b/PRASCore/src/Simulations/recording.jl new file mode 100644 index 00000000..613dda86 --- /dev/null +++ b/PRASCore/src/Simulations/recording.jl @@ -0,0 +1,431 @@ +# Shortfall + +function record!( + acc::Results.ShortfallAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + totalshortfall = 0 + isshortfall = false + + edges = problem.fp.edges + + for r in problem.region_unserved_edges + + regionshortfall = edges[r].flow + isregionshortfall = regionshortfall > 0 + + fit!(acc.periodsdropped_regionperiod[r,t], isregionshortfall) + fit!(acc.unservedload_regionperiod[r,t], regionshortfall) + + if isregionshortfall + + isshortfall = true + totalshortfall += regionshortfall + + acc.periodsdropped_region_currentsim[r] += 1 + acc.unservedload_region_currentsim[r] += regionshortfall + + end + + end + + if isshortfall + acc.periodsdropped_total_currentsim += 1 + acc.unservedload_total_currentsim += totalshortfall + end + + fit!(acc.periodsdropped_period[t], isshortfall) + fit!(acc.unservedload_period[t], totalshortfall) + + return + +end + +function reset!(acc::Results.ShortfallAccumulator, sampleid::Int) + + # Store regional / total sums for current simulation + fit!(acc.periodsdropped_total, acc.periodsdropped_total_currentsim) + fit!(acc.unservedload_total, acc.unservedload_total_currentsim) + + for r in eachindex(acc.periodsdropped_region) + fit!(acc.periodsdropped_region[r], acc.periodsdropped_region_currentsim[r]) + fit!(acc.unservedload_region[r], acc.unservedload_region_currentsim[r]) + end + + # Reset for new simulation + acc.periodsdropped_total_currentsim = 0 + fill!(acc.periodsdropped_region_currentsim, 0) + acc.unservedload_total_currentsim = 0 + fill!(acc.unservedload_region_currentsim, 0) + + return + +end + +# ShortfallSamples + +function record!( + acc::Results.ShortfallSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + for (r, e) in enumerate(problem.region_unserved_edges) + acc.shortfall[r, t, sampleid] = problem.fp.edges[e].flow + end + + return + +end + +reset!(acc::Results.ShortfallSamplesAccumulator, sampleid::Int) = nothing + +# Surplus + +function record!( + acc::Results.SurplusAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + totalsurplus = 0 + edges = problem.fp.edges + + for (r, e_idx) in enumerate(problem.region_unused_edges) + + regionsurplus = edges[e_idx].flow + + for s in system.region_stor_idxs[r] + se_idx = problem.storage_dischargeunused_edges[s] + regionsurplus += edges[se_idx].flow + end + + for gs in system.region_genstor_idxs[r] + + gse_discharge_idx = problem.genstorage_dischargeunused_edges[gs] + gse_inflow_idx = problem.genstorage_inflowunused_edges[gs] + + grid_limit = system.generatorstorages.gridinjection_capacity[gs, t] + total_unused = edges[gse_discharge_idx].flow + edges[gse_inflow_idx].flow + + regionsurplus += min(grid_limit, total_unused) + + end + + fit!(acc.surplus_regionperiod[r,t], regionsurplus) + totalsurplus += regionsurplus + + end + + fit!(acc.surplus_period[t], totalsurplus) + + return + +end + +reset!(acc::Results.SurplusAccumulator, sampleid::Int) = nothing + +# SurplusSamples + +function record!( + acc::Results.SurplusSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + edges = problem.fp.edges + + for (r, e) in enumerate(problem.region_unused_edges) + + regionsurplus = edges[e].flow + + for s in system.region_stor_idxs[r] + se_idx = problem.storage_dischargeunused_edges[s] + regionsurplus += edges[se_idx].flow + end + + for gs in system.region_genstor_idxs[r] + + gse_discharge_idx = problem.genstorage_dischargeunused_edges[gs] + gse_inflow_idx = problem.genstorage_inflowunused_edges[gs] + + grid_limit = system.generatorstorages.gridinjection_capacity[gs, t] + total_unused = edges[gse_discharge_idx].flow + edges[gse_inflow_idx].flow + + regionsurplus += min(grid_limit, total_unused) + + end + + acc.surplus[r, t, sampleid] = regionsurplus + + end + + return + +end + +reset!(acc::Results.SurplusSamplesAccumulator, sampleid::Int) = nothing + +# Flow + +function record!( + acc::Results.FlowAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + edges = problem.fp.edges + + for (i, (f, b)) in enumerate(zip(problem.interface_forward_edges, + problem.interface_reverse_edges)) + + flow = edges[f].flow - edges[b].flow + acc.flow_interface_currentsim[i] += flow + fit!(acc.flow_interfaceperiod[i,t], flow) + + end + +end + +function reset!(acc::Results.FlowAccumulator, sampleid::Int) + + for i in eachindex(acc.flow_interface_currentsim) + fit!(acc.flow_interface[i], acc.flow_interface_currentsim[i]) + acc.flow_interface_currentsim[i] = 0 + end + +end + +# FlowSamples + +function record!( + acc::Results.FlowSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + for (i, (e_f, e_r)) in enumerate(zip(problem.interface_forward_edges, + problem.interface_reverse_edges)) + acc.flow[i, t, sampleid] = problem.fp.edges[e_f].flow - + problem.fp.edges[e_r].flow + end + + return + +end + +reset!(acc::Results.FlowSamplesAccumulator, sampleid::Int) = nothing + +# Utilization + +function record!( + acc::Results.UtilizationAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + edges = problem.fp.edges + + for (i, (f, b)) in enumerate(zip(problem.interface_forward_edges, + problem.interface_reverse_edges)) + + util = utilization(problem.fp.edges[f], problem.fp.edges[b]) + acc.util_interface_currentsim[i] += util + fit!(acc.util_interfaceperiod[i,t], util) + + end + +end + +function reset!(acc::Results.UtilizationAccumulator, sampleid::Int) + + for i in eachindex(acc.util_interface_currentsim) + fit!(acc.util_interface[i], acc.util_interface_currentsim[i]) + acc.util_interface_currentsim[i] = 0 + end + +end + +# UtilizationSamples + +function record!( + acc::Results.UtilizationSamplesAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + for (i, (e_f, e_r)) in enumerate(zip(problem.interface_forward_edges, + problem.interface_reverse_edges)) + + acc.utilization[i, t, sampleid] = + utilization(problem.fp.edges[e_f], problem.fp.edges[e_r]) + + end + + return + +end + +reset!(acc::Results.UtilizationSamplesAccumulator, sampleid::Int) = nothing + +# GeneratorAvailability + +function record!( + acc::Results.GenAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + acc.available[:, t, sampleid] .= state.gens_available + return + +end + +reset!(acc::Results.GenAvailabilityAccumulator, sampleid::Int) = nothing + +# StorageAvailability + +function record!( + acc::Results.StorAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + acc.available[:, t, sampleid] .= state.stors_available + return + +end + +reset!(acc::Results.StorAvailabilityAccumulator, sampleid::Int) = nothing + +# GeneratorStorageAvailability + +function record!( + acc::Results.GenStorAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + acc.available[:, t, sampleid] .= state.genstors_available + return + +end + +reset!(acc::Results.GenStorAvailabilityAccumulator, sampleid::Int) = nothing + +# LineAvailability + +function record!( + acc::Results.LineAvailabilityAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + acc.available[:, t, sampleid] .= state.lines_available + return + +end + +reset!(acc::Results.LineAvailabilityAccumulator, sampleid::Int) = nothing + +# StorageEnergy + +function record!( + acc::Results.StorageEnergyAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + totalenergy = 0 + nstorages = length(system.storages) + + for s in 1:nstorages + + storageenergy = state.stors_energy[s] + fit!(acc.energy_storageperiod[s,t], storageenergy) + totalenergy += storageenergy + + end + + fit!(acc.energy_period[t], totalenergy) + + return + +end + +reset!(acc::Results.StorageEnergyAccumulator, sampleid::Int) = nothing + +# GeneratorStorageEnergy + +function record!( + acc::Results.GenStorageEnergyAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + totalenergy = 0 + ngenstors = length(system.generatorstorages) + + for s in 1:ngenstors + + genstorenergy = state.genstors_energy[s] + fit!(acc.energy_genstorperiod[s,t], genstorenergy) + totalenergy += genstorenergy + + end + + fit!(acc.energy_period[t], totalenergy) + + return + +end + +reset!(acc::Results.GenStorageEnergyAccumulator, sampleid::Int) = nothing + +# StorageEnergySamples + +function record!( + acc::Results.StorageEnergySamplesAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + acc.energy[:, t, sampleid] .= state.stors_energy + return + +end + +reset!(acc::Results.StorageEnergySamplesAccumulator, sampleid::Int) = nothing + +# GeneratorStorageEnergySamples + +function record!( + acc::Results.GenStorageEnergySamplesAccumulator, + system::SystemModel{N,L,T,P,E}, + state::SystemState, problem::DispatchProblem, + sampleid::Int, t::Int +) where {N,L,T,P,E} + + acc.energy[:, t, sampleid] .= state.genstors_energy + return + +end + +reset!(acc::Results.GenStorageEnergySamplesAccumulator, sampleid::Int) = nothing diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/utils.jl b/PRASCore/src/Simulations/utils.jl similarity index 91% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/utils.jl rename to PRASCore/src/Simulations/utils.jl index 8bf81bd7..533c9315 100644 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/utils.jl +++ b/PRASCore/src/Simulations/utils.jl @@ -176,3 +176,25 @@ function maxtimetocharge_discharge(system::SystemModel) max(stor_discharge_max, genstor_discharge_max)) end + +function utilization(f::MinCostFlows.Edge, b::MinCostFlows.Edge) + + flow_forward = f.flow + max_forward = f.limit + + flow_back = b.flow + max_back = b.limit + + util = if flow_forward > 0 + flow_forward/max_forward + elseif flow_back > 0 + flow_back/max_back + elseif iszero(max_forward) && iszero(max_back) + 1.0 + else + 0.0 + end + + return util + +end diff --git a/src/PRASBase/SystemModel.jl b/PRASCore/src/Systems/SystemModel.jl similarity index 98% rename from src/PRASBase/SystemModel.jl rename to PRASCore/src/Systems/SystemModel.jl index 3bd12f99..57b1d5f6 100644 --- a/src/PRASBase/SystemModel.jl +++ b/PRASCore/src/Systems/SystemModel.jl @@ -83,7 +83,7 @@ function SystemModel( "time zone for the system timestamps, provide a range of " * "`ZonedDateTime` instead of `DateTime`." - utc = TimeZone("UTC") + utc = tz"UTC" time_start = ZonedDateTime(first(timestamps), utc) time_end = ZonedDateTime(last(timestamps), utc) timestamps_tz = time_start:step(timestamps):time_end @@ -142,6 +142,9 @@ unitsymbol(::SystemModel{N,L,T,P,E}) where { N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} = unitsymbol(T), unitsymbol(P), unitsymbol(E) +isnonnegative(x::Real) = x >= 0 +isfractional(x::Real) = 0 <= x <= 1 + function consistent_idxs(idxss::Vector{UnitRange{Int}}, nitems::Int, ngroups::Int) length(idxss) == ngroups || return false @@ -165,4 +168,4 @@ end function toymodel() path = dirname(@__FILE__) return SystemModel(path * "/toymodel.pras") -end \ No newline at end of file +end diff --git a/src/PRASBase/PRASBase.jl b/PRASCore/src/Systems/Systems.jl similarity index 55% rename from src/PRASBase/PRASBase.jl rename to PRASCore/src/Systems/Systems.jl index 75e9f971..b5ccc6e6 100644 --- a/src/PRASBase/PRASBase.jl +++ b/PRASCore/src/Systems/Systems.jl @@ -1,19 +1,11 @@ -@reexport module PRASBase - -import ..PRAS_VERSION +@reexport module Systems import Base: broadcastable import Dates: @dateformat_str, AbstractDateTime, DateTime, Period, Minute, Hour, Day, Year -import HDF5: HDF5, attributes, File, Group, Dataset, Datatype, dataspace, - h5open, create_group, create_dataset, hdf5_type_id - -import HDF5.API: h5t_create, h5t_copy, h5t_insert, h5t_set_size, H5T_COMPOUND, - h5d_write, H5S_ALL, H5P_DEFAULT - -import TimeZones: TimeZone, ZonedDateTime +import TimeZones: ZonedDateTime, @tz_str export @@ -28,16 +20,15 @@ export unitsymbol, conversionfactor, powertoenergy, energytopower, # Main data structure - SystemModel, savemodel + SystemModel, + + # Convenience re-exports + ZonedDateTime, @tz_str include("units.jl") include("collections.jl") include("assets.jl") include("SystemModel.jl") - -include("read.jl") -include("write.jl") - -include("utils.jl") +include("TestData.jl") end diff --git a/test/testsystems.jl b/PRASCore/src/Systems/TestData.jl similarity index 90% rename from test/testsystems.jl rename to PRASCore/src/Systems/TestData.jl index 861da1cf..bf9d004c 100644 --- a/test/testsystems.jl +++ b/PRASCore/src/Systems/TestData.jl @@ -1,6 +1,6 @@ -module TestSystems +module TestData -using PRAS +using ..Systems using TimeZones const tz = tz"UTC" @@ -28,7 +28,7 @@ emptygenstors1 = GeneratorStorages{4,1,Hour,MW,MWh}( singlenode_a = SystemModel( gens1, emptystors1, emptygenstors1, - DateTime(2010,1,1,0):Hour(1):DateTime(2010,1,1,3), + ZonedDateTime(2010,1,1,0,tz):Hour(1):ZonedDateTime(2010,1,1,3,tz), [25, 28, 27, 24]) singlenode_a_lole = 0.355 @@ -53,9 +53,9 @@ emptygenstors1_5min = GeneratorStorages{4,5,Minute,MW,MWh}( (empty_int(4) for _ in 1:3)..., (empty_float(4) for _ in 1:3)..., (empty_int(4) for _ in 1:3)..., (empty_float(4) for _ in 1:2)...) -singlenode_a_5min = ResourceAdequacy.SystemModel( +singlenode_a_5min = SystemModel( gens1_5min, emptystors1_5min, emptygenstors1_5min, - DateTime(2010,1,1,0,0):Minute(5):DateTime(2010,1,1,0,15), + ZonedDateTime(2010,1,1,0,0,tz):Minute(5):ZonedDateTime(2010,1,1,0,15,tz), [25, 28, 27, 24]) singlenode_a_lole = 0.355 @@ -89,7 +89,7 @@ genstors2 = GeneratorStorages{6,1,Hour,MW,MWh}( singlenode_b = SystemModel( gens2, emptystors2, emptygenstors2, - DateTime(2015,6,1,0):Hour(1):DateTime(2015,6,1,5), + ZonedDateTime(2015,6,1,0,tz):Hour(1):ZonedDateTime(2015,6,1,5,tz), [28,29,30,31,32,33]) singlenode_b_lole = 0.96 @@ -109,7 +109,7 @@ stors2 = Storages{6,1,Hour,MW,MWh}( singlenode_stor = SystemModel( gens2, stors2, genstors2, - DateTime(2015,6,1,0):Hour(1):DateTime(2015,6,1,5), + ZonedDateTime(2015,6,1,0,tz):Hour(1):ZonedDateTime(2015,6,1,5,tz), [28,29,30,31,32,33]) @@ -143,7 +143,7 @@ threenode = regions, interfaces, generators, [1:2, 3:5, 6:8], emptystors1, fill(1:0, 3), emptygenstors1, fill(1:0, 3), lines, [1:1, 2:2, 3:3], - DateTime(2018,10,30,0):Hour(1):DateTime(2018,10,30,3)) + ZonedDateTime(2018,10,30,0,tz):Hour(1):ZonedDateTime(2018,10,30,3,tz)) threenode_lole = 1.3756 threenode_lolps = [0.14707, 0.40951, 0.40951, 0.40951] @@ -180,7 +180,7 @@ lines = Lines{1,1,Hour,MW}( fill(8, 1, 1), fill(8, 1, 1), fill(0.1, 1, 1), fill(0.9, 1, 1) ) -zdt = ZonedDateTime(2020,1,1,0, tz"UTC") +zdt = ZonedDateTime(2020,1,1,0, tz) test1 = SystemModel(regions, interfaces, gens, [1:1, 2:2], emptystors, fill(1:0, 2), emptygenstors, fill(1:0, 2), lines, [1:1], zdt:Hour(1):zdt @@ -200,7 +200,7 @@ test1_i1_util = 0.231625 # Test System 2 (Gen + Stor, 1 Region) -timestamps = ZonedDateTime(2020,1,1,0, tz"UTC"):Hour(1):ZonedDateTime(2020,1,1,1, tz"UTC") +timestamps = ZonedDateTime(2020,1,1,0, tz):Hour(1):ZonedDateTime(2020,1,1,1, tz) gen = Generators{2,1,Hour,MW}( ["Gen 1"], ["Generators"], @@ -268,5 +268,3 @@ test3_util_t = [0.8614, 0.626674] test3_eenergy = [6.561, 7.682202] end - -import .TestSystems diff --git a/src/PRASBase/assets.jl b/PRASCore/src/Systems/assets.jl similarity index 100% rename from src/PRASBase/assets.jl rename to PRASCore/src/Systems/assets.jl diff --git a/src/PRASBase/collections.jl b/PRASCore/src/Systems/collections.jl similarity index 100% rename from src/PRASBase/collections.jl rename to PRASCore/src/Systems/collections.jl diff --git a/src/PRASBase/units.jl b/PRASCore/src/Systems/units.jl similarity index 100% rename from src/PRASBase/units.jl rename to PRASCore/src/Systems/units.jl diff --git a/test/ResourceAdequacy/results/availability.jl b/PRASCore/test/Results/availability.jl similarity index 85% rename from test/ResourceAdequacy/results/availability.jl rename to PRASCore/test/Results/availability.jl index 8d88ed63..fd06020e 100644 --- a/test/ResourceAdequacy/results/availability.jl +++ b/PRASCore/test/Results/availability.jl @@ -7,7 +7,7 @@ # Generators - result = ResourceAdequacy.GeneratorAvailabilityResult{N,1,Hour}( + result = PRASCore.Results.GeneratorAvailabilityResult{N,1,Hour}( DD.resourcenames, DD.periods, available) @test length(result[r, t]) == DD.nsamples @@ -19,7 +19,7 @@ # Storages - result = ResourceAdequacy.StorageAvailabilityResult{N,1,Hour}( + result = PRASCore.Results.StorageAvailabilityResult{N,1,Hour}( DD.resourcenames, DD.periods, available) @test length(result[r, t]) == DD.nsamples @@ -31,7 +31,7 @@ # GeneratorStorages - result = ResourceAdequacy.GeneratorStorageAvailabilityResult{N,1,Hour}( + result = PRASCore.Results.GeneratorStorageAvailabilityResult{N,1,Hour}( DD.resourcenames, DD.periods, available) @test length(result[r, t]) == DD.nsamples @@ -43,7 +43,7 @@ # Lines - result = ResourceAdequacy.LineAvailabilityResult{N,1,Hour}( + result = PRASCore.Results.LineAvailabilityResult{N,1,Hour}( DD.resourcenames, DD.periods, available) @test length(result[r, t]) == DD.nsamples diff --git a/test/ResourceAdequacy/results/energy.jl b/PRASCore/test/Results/energy.jl similarity index 90% rename from test/ResourceAdequacy/results/energy.jl rename to PRASCore/test/Results/energy.jl index f5e5a28e..15f620f1 100644 --- a/test/ResourceAdequacy/results/energy.jl +++ b/PRASCore/test/Results/energy.jl @@ -6,7 +6,7 @@ # Storages - result = ResourceAdequacy.StorageEnergyResult{N,1,Hour,MWh}( + result = PRASCore.Results.StorageEnergyResult{N,1,Hour,MWh}( DD.nsamples, DD.resourcenames, DD.periods, DD.d1_resourceperiod, DD.d2_period, DD.d2_resourceperiod) @@ -21,7 +21,7 @@ # GeneratorStorages - result = ResourceAdequacy.GeneratorStorageEnergyResult{N,1,Hour,MWh}( + result = PRASCore.Results.GeneratorStorageEnergyResult{N,1,Hour,MWh}( DD.nsamples, DD.resourcenames, DD.periods, DD.d1_resourceperiod, DD.d2_period, DD.d2_resourceperiod) @@ -44,7 +44,7 @@ end # Storages - result = ResourceAdequacy.StorageEnergySamplesResult{N,1,Hour,MWh}( + result = PRASCore.Results.StorageEnergySamplesResult{N,1,Hour,MWh}( DD.resourcenames, DD.periods, DD.d) @test length(result[t]) == DD.nsamples @@ -60,7 +60,7 @@ end # GeneratorStorages - result = ResourceAdequacy.GeneratorStorageEnergySamplesResult{N,1,Hour,MWh}( + result = PRASCore.Results.GeneratorStorageEnergySamplesResult{N,1,Hour,MWh}( DD.resourcenames, DD.periods, DD.d) @test length(result[t]) == DD.nsamples diff --git a/test/ResourceAdequacy/results/flow.jl b/PRASCore/test/Results/flow.jl similarity index 92% rename from test/ResourceAdequacy/results/flow.jl rename to PRASCore/test/Results/flow.jl index fb1b7ab6..09158d8e 100644 --- a/test/ResourceAdequacy/results/flow.jl +++ b/PRASCore/test/Results/flow.jl @@ -4,7 +4,7 @@ i, i_idx, i_bad = DD.testinterface, DD.testinterface_idx, DD.notaninterface t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.FlowResult{N,1,Hour,MW}( + result = PRASCore.Results.FlowResult{N,1,Hour,MW}( DD.nsamples, DD.interfacenames, DD.periods, DD.d1_resourceperiod, DD.d2_resource, DD.d2_resourceperiod) @@ -30,7 +30,7 @@ end i, i_idx, i_bad = DD.testinterface, DD.testinterface_idx, DD.notaninterface t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.FlowSamplesResult{N,1,Hour,MW}( + result = PRASCore.Results.FlowSamplesResult{N,1,Hour,MW}( DD.interfacenames, DD.periods, DD.d) # Interface-specific diff --git a/test/ResourceAdequacy/metrics.jl b/PRASCore/test/Results/metrics.jl similarity index 100% rename from test/ResourceAdequacy/metrics.jl rename to PRASCore/test/Results/metrics.jl diff --git a/test/ResourceAdequacy/results/results.jl b/PRASCore/test/Results/runtests.jl similarity index 88% rename from test/ResourceAdequacy/results/results.jl rename to PRASCore/test/Results/runtests.jl index 303ddd89..efec7599 100644 --- a/test/ResourceAdequacy/results/results.jl +++ b/PRASCore/test/Results/runtests.jl @@ -1,5 +1,6 @@ @testset "Results" begin + include("metrics.jl") include("shortfall.jl") include("surplus.jl") include("flow.jl") diff --git a/test/ResourceAdequacy/results/shortfall.jl b/PRASCore/test/Results/shortfall.jl similarity index 97% rename from test/ResourceAdequacy/results/shortfall.jl rename to PRASCore/test/Results/shortfall.jl index f1bdf1ce..900903a7 100644 --- a/test/ResourceAdequacy/results/shortfall.jl +++ b/PRASCore/test/Results/shortfall.jl @@ -5,7 +5,7 @@ r, r_idx, r_bad = DD.testresource, DD.testresource_idx, DD.notaresource t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.ShortfallResult{N,1,Hour,MWh}( + result = PRASCore.Results.ShortfallResult{N,1,Hour,MWh}( DD.nsamples, DD.resourcenames, DD.periods, DD.d1, DD.d2, DD.d1_resource, DD.d2_resource, DD.d1_period, DD.d2_period, DD.d1_resourceperiod, DD.d2_resourceperiod, @@ -91,7 +91,7 @@ end r, r_idx, r_bad = DD.testresource, DD.testresource_idx, DD.notaresource t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.ShortfallSamplesResult{N,1,Hour,MW,MWh}( + result = PRASCore.Results.ShortfallSamplesResult{N,1,Hour,MW,MWh}( DD.resourcenames, DD.periods, DD.d) # Overall diff --git a/test/ResourceAdequacy/results/surplus.jl b/PRASCore/test/Results/surplus.jl similarity index 92% rename from test/ResourceAdequacy/results/surplus.jl rename to PRASCore/test/Results/surplus.jl index c00bd314..685bd0a9 100644 --- a/test/ResourceAdequacy/results/surplus.jl +++ b/PRASCore/test/Results/surplus.jl @@ -4,7 +4,7 @@ r, r_idx, r_bad = DD.testresource, DD.testresource_idx, DD.notaresource t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.SurplusResult{N,1,Hour,MW}( + result = PRASCore.Results.SurplusResult{N,1,Hour,MW}( DD.nsamples, DD.resourcenames, DD.periods, DD.d1_resourceperiod, DD.d2_period, DD.d2_resourceperiod) @@ -30,7 +30,7 @@ end r, r_idx, r_bad = DD.testresource, DD.testresource_idx, DD.notaresource t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.SurplusSamplesResult{N,1,Hour,MW}( + result = PRASCore.Results.SurplusSamplesResult{N,1,Hour,MW}( DD.resourcenames, DD.periods, DD.d) # Period-specific diff --git a/test/ResourceAdequacy/results/utilization.jl b/PRASCore/test/Results/utilization.jl similarity index 92% rename from test/ResourceAdequacy/results/utilization.jl rename to PRASCore/test/Results/utilization.jl index 2a4f6318..41678e6c 100644 --- a/test/ResourceAdequacy/results/utilization.jl +++ b/PRASCore/test/Results/utilization.jl @@ -4,7 +4,7 @@ i, i_idx, i_bad = DD.testinterface, DD.testinterface_idx, DD.notaninterface t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.UtilizationResult{N,1,Hour}( + result = PRASCore.Results.UtilizationResult{N,1,Hour}( DD.nsamples, DD.interfacenames, DD.periods, DD.d1_resourceperiod, DD.d2_resource, DD.d2_resourceperiod) @@ -30,7 +30,7 @@ end i, i_idx, i_bad = DD.testinterface, DD.testinterface_idx, DD.notaninterface t, t_idx, t_bad = DD.testperiod, DD.testperiod_idx, DD.notaperiod - result = ResourceAdequacy.UtilizationSamplesResult{N,1,Hour}( + result = PRASCore.Results.UtilizationSamplesResult{N,1,Hour}( DD.interfacenames, DD.periods, DD.d) # Interface-specific diff --git a/test/ResourceAdequacy/simulation/sequentialmontecarlo.jl b/PRASCore/test/Simulations/runtests.jl similarity index 64% rename from test/ResourceAdequacy/simulation/sequentialmontecarlo.jl rename to PRASCore/test/Simulations/runtests.jl index 53b52b1f..519945a1 100644 --- a/test/ResourceAdequacy/simulation/sequentialmontecarlo.jl +++ b/PRASCore/test/Simulations/runtests.jl @@ -1,4 +1,4 @@ -@testset "SequentialMonteCarlo" begin +@testset "Simulations" begin @testset "DispatchProblem" begin @@ -7,46 +7,50 @@ nstderr_tol = 3 simspec = SequentialMonteCarlo(samples=100_000, seed=1, threaded=false) - smallsample = SequentialMonteCarlo(samples=10, seed=123) + smallsample = SequentialMonteCarlo(samples=10, seed=123, threaded=false) resultspecs = (Shortfall(), Surplus(), Flow(), Utilization(), ShortfallSamples(), SurplusSamples(), FlowSamples(), UtilizationSamples(), GeneratorAvailability()) - timestamps_a = TestSystems.singlenode_a.timestamps - timestamps_a5 = TestSystems.singlenode_a_5min.timestamps - timestamps_b = TestSystems.singlenode_b.timestamps - timestamps_3 = TestSystems.threenode.timestamps + timestamps_a = TestData.singlenode_a.timestamps + timestamps_a5 = TestData.singlenode_a_5min.timestamps + timestamps_b = TestData.singlenode_b.timestamps + timestamps_3 = TestData.threenode.timestamps timestamprow_a = permutedims(timestamps_a) timestamprow_a5 = permutedims(timestamps_a5) timestamprow_b = permutedims(timestamps_b) timestamprow_3 = permutedims(timestamps_3) - regionscol = TestSystems.threenode.regions.names + regionscol = TestData.threenode.regions.names - assess(TestSystems.singlenode_a, smallsample, resultspecs...) + assess(TestData.singlenode_a, smallsample, resultspecs...) shortfall_1a, _, flow_1a, util_1a, shortfall2_1a, _, flow2_1a, util2_1a, _ = - assess(TestSystems.singlenode_a, simspec, resultspecs...) + assess(TestData.singlenode_a, simspec, resultspecs...) - assess(TestSystems.singlenode_a_5min, smallsample, resultspecs...) + assess(TestData.singlenode_a_5min, smallsample, resultspecs...) shortfall_1a5, _, flow_1a5, util_1a5, shortfall2_1a5, _, flow2_1a5, util2_1a5, _ = - assess(TestSystems.singlenode_a_5min, simspec, resultspecs...) + assess(TestData.singlenode_a_5min, simspec, resultspecs...) - assess(TestSystems.singlenode_b, smallsample, resultspecs...) + assess(TestData.singlenode_b, smallsample, resultspecs...) shortfall_1b, _, flow_1b, util_1b, shortfall2_1b, _, flow2_1b, util2_1b, _ = - assess(TestSystems.singlenode_b, simspec, resultspecs...) + assess(TestData.singlenode_b, simspec, resultspecs...) - assess(TestSystems.threenode, smallsample, resultspecs...) + assess(TestData.threenode, smallsample, resultspecs...) shortfall_3, _, flow_3, util_3, shortfall2_3, _, flow2_3, util2_3, _ = - assess(TestSystems.threenode, simspec, resultspecs...) - + assess(TestData.threenode, simspec, resultspecs...) + assess(TestData.threenode, smallsample, + GeneratorAvailability(), LineAvailability(), + StorageAvailability(), GeneratorStorageAvailability(), + StorageEnergy(), GeneratorStorageEnergy(), + StorageEnergySamples(), GeneratorStorageEnergySamples()) @testset "Shortfall Results" begin @@ -58,13 +62,13 @@ @test EUE(shortfall_1a, "Region") ≈ EUE(shortfall2_1a, "Region") @test withinrange(LOLE(shortfall_1a), - TestSystems.singlenode_a_lole, nstderr_tol) + TestData.singlenode_a_lole, nstderr_tol) @test withinrange(EUE(shortfall_1a), - TestSystems.singlenode_a_eue, nstderr_tol) + TestData.singlenode_a_eue, nstderr_tol) @test withinrange(LOLE(shortfall_1a, "Region"), - TestSystems.singlenode_a_lole, nstderr_tol) + TestData.singlenode_a_lole, nstderr_tol) @test withinrange(EUE(shortfall_1a, "Region"), - TestSystems.singlenode_a_eue, nstderr_tol) + TestData.singlenode_a_eue, nstderr_tol) @test all(LOLE.(shortfall_1a, timestamps_a) .≈ LOLE.(shortfall2_1a, timestamps_a)) @@ -76,13 +80,13 @@ EUE(shortfall2_1a, "Region", :)) @test all(withinrange.(LOLE.(shortfall_1a, timestamps_a), - TestSystems.singlenode_a_lolps, nstderr_tol)) + TestData.singlenode_a_lolps, nstderr_tol)) @test all(withinrange.(EUE.(shortfall_1a, timestamps_a), - TestSystems.singlenode_a_eues, nstderr_tol)) + TestData.singlenode_a_eues, nstderr_tol)) @test all(withinrange.(LOLE(shortfall_1a, "Region", :), - TestSystems.singlenode_a_lolps, nstderr_tol)) + TestData.singlenode_a_lolps, nstderr_tol)) @test all(withinrange.(EUE(shortfall_1a, "Region", :), - TestSystems.singlenode_a_eues, nstderr_tol)) + TestData.singlenode_a_eues, nstderr_tol)) # Single-region system A - 5 min version @@ -92,13 +96,13 @@ @test EUE(shortfall_1a5, "Region") ≈ EUE(shortfall2_1a5, "Region") @test withinrange(LOLE(shortfall_1a5), - TestSystems.singlenode_a_lole, nstderr_tol) + TestData.singlenode_a_lole, nstderr_tol) @test withinrange(EUE(shortfall_1a5), - TestSystems.singlenode_a_eue/12, nstderr_tol) + TestData.singlenode_a_eue/12, nstderr_tol) @test withinrange(LOLE(shortfall_1a5, "Region"), - TestSystems.singlenode_a_lole, nstderr_tol) + TestData.singlenode_a_lole, nstderr_tol) @test withinrange(EUE(shortfall_1a5, "Region"), - TestSystems.singlenode_a_eue/12, nstderr_tol) + TestData.singlenode_a_eue/12, nstderr_tol) @test all(LOLE.(shortfall_1a5, timestamps_a5) .≈ LOLE.(shortfall2_1a5, timestamps_a5)) @@ -110,13 +114,13 @@ EUE(shortfall2_1a5, "Region", :)) @test all(withinrange.(LOLE.(shortfall_1a5, timestamps_a5), - TestSystems.singlenode_a_lolps, nstderr_tol)) + TestData.singlenode_a_lolps, nstderr_tol)) @test all(withinrange.(EUE.(shortfall_1a5, timestamps_a5), - TestSystems.singlenode_a_eues ./ 12, nstderr_tol)) + TestData.singlenode_a_eues ./ 12, nstderr_tol)) @test all(withinrange.(LOLE(shortfall_1a5, "Region", :), - TestSystems.singlenode_a_lolps, nstderr_tol)) + TestData.singlenode_a_lolps, nstderr_tol)) @test all(withinrange.(EUE(shortfall_1a5, "Region", :), - TestSystems.singlenode_a_eues ./ 12, nstderr_tol)) + TestData.singlenode_a_eues ./ 12, nstderr_tol)) # Single-region system B @@ -126,13 +130,13 @@ @test EUE(shortfall_1b, "Region") ≈ EUE(shortfall2_1b, "Region") @test withinrange(LOLE(shortfall_1b), - TestSystems.singlenode_b_lole, nstderr_tol) + TestData.singlenode_b_lole, nstderr_tol) @test withinrange(EUE(shortfall_1b), - TestSystems.singlenode_b_eue, nstderr_tol) + TestData.singlenode_b_eue, nstderr_tol) @test withinrange(LOLE(shortfall_1b, "Region"), - TestSystems.singlenode_b_lole, nstderr_tol) + TestData.singlenode_b_lole, nstderr_tol) @test withinrange(EUE(shortfall_1b, "Region"), - TestSystems.singlenode_b_eue, nstderr_tol) + TestData.singlenode_b_eue, nstderr_tol) @test all(LOLE.(shortfall_1b, timestamps_b) .≈ LOLE.(shortfall2_1b, timestamps_b)) @@ -144,13 +148,13 @@ EUE(shortfall2_1b, "Region", :)) @test all(withinrange.(LOLE.(shortfall_1b, timestamps_b), - TestSystems.singlenode_b_lolps, nstderr_tol)) + TestData.singlenode_b_lolps, nstderr_tol)) @test all(withinrange.(EUE.(shortfall_1b, timestamps_b), - TestSystems.singlenode_b_eues, nstderr_tol)) + TestData.singlenode_b_eues, nstderr_tol)) @test all(withinrange.(LOLE(shortfall_1b, "Region", :), - TestSystems.singlenode_b_lolps, nstderr_tol)) + TestData.singlenode_b_lolps, nstderr_tol)) @test all(withinrange.(EUE(shortfall_1b, "Region", :), - TestSystems.singlenode_b_eues, nstderr_tol)) + TestData.singlenode_b_eues, nstderr_tol)) # Three-region system @@ -160,13 +164,13 @@ @test all(EUE.(shortfall_3, regionscol) .≈ EUE.(shortfall2_3, regionscol)) @test withinrange(LOLE(shortfall_3), - TestSystems.threenode_lole, nstderr_tol) + TestData.threenode_lole, nstderr_tol) @test withinrange(EUE(shortfall_3), - TestSystems.threenode_eue, nstderr_tol) + TestData.threenode_eue, nstderr_tol) @test all(withinrange.(LOLE.(shortfall_3, timestamps_3), - TestSystems.threenode_lolps, nstderr_tol)) + TestData.threenode_lolps, nstderr_tol)) @test all(withinrange.(EUE.(shortfall_3, timestamps_3), - TestSystems.threenode_eues, nstderr_tol)) + TestData.threenode_eues, nstderr_tol)) @test all(LOLE.(shortfall_3, timestamps_3) .≈ LOLE.(shortfall2_3, timestamps_3)) @@ -176,10 +180,10 @@ @test all(EUE(shortfall_3, :, :) .≈ EUE(shortfall2_3, :, :)) @test withinrange( - LOLE(shortfall_3, "Region C", ZonedDateTime(2018,10,30,1,TestSystems.tz)), + LOLE(shortfall_3, "Region C", ZonedDateTime(2018,10,30,1,TestData.tz)), 0.1, nstderr_tol) @test withinrange( - LOLE(shortfall_3, "Region C", ZonedDateTime(2018,10,30,2,TestSystems.tz)), + LOLE(shortfall_3, "Region C", ZonedDateTime(2018,10,30,2,TestData.tz)), 0.1, nstderr_tol) # TODO: Test spatially-disaggregated results - may need to develop @@ -245,63 +249,51 @@ end - @testset "RTS" begin - - sys = PRAS.rts_gmlc() - - assess(sys, SequentialMonteCarlo(samples=100), - GeneratorAvailability(), LineAvailability(), - StorageAvailability(), GeneratorStorageAvailability(), - StorageEnergy(), GeneratorStorageEnergy(), - StorageEnergySamples(), GeneratorStorageEnergySamples()) - - end - @testset "Test System 1: 2 Gens, 2 Regions" begin simspec = SequentialMonteCarlo(samples=1_000_000, seed=111) - dt = first(TestSystems.test1.timestamps) - regions = TestSystems.test1.regions.names + dt = first(TestData.test1.timestamps) + regions = TestData.test1.regions.names shortfall, surplus, flow, utilization = - assess(TestSystems.test1, simspec, + assess(TestData.test1, simspec, Shortfall(), Surplus(), Flow(), Utilization()) # Shortfall - LOLE - @test withinrange(LOLE(shortfall), TestSystems.test1_lole, nstderr_tol) - @test withinrange(LOLE(shortfall, dt), TestSystems.test1_lole, nstderr_tol) + @test withinrange(LOLE(shortfall), TestData.test1_lole, nstderr_tol) + @test withinrange(LOLE(shortfall, dt), TestData.test1_lole, nstderr_tol) @test all(withinrange.(LOLE.(shortfall, regions), - TestSystems.test1_loles, nstderr_tol)) + TestData.test1_loles, nstderr_tol)) @test all(withinrange.(LOLE.(shortfall, regions, dt), - TestSystems.test1_loles, nstderr_tol)) + TestData.test1_loles, nstderr_tol)) # Shortfall - EUE - @test withinrange(EUE(shortfall), TestSystems.test1_eue, nstderr_tol) - @test withinrange(EUE(shortfall, dt), TestSystems.test1_eue, nstderr_tol) + @test withinrange(EUE(shortfall), TestData.test1_eue, nstderr_tol) + @test withinrange(EUE(shortfall, dt), TestData.test1_eue, nstderr_tol) @test all(withinrange.(EUE.(shortfall, regions), - TestSystems.test1_eues, nstderr_tol)) + TestData.test1_eues, nstderr_tol)) @test all(withinrange.(EUE.(shortfall, regions, dt), - TestSystems.test1_eues, nstderr_tol)) + TestData.test1_eues, nstderr_tol)) # Surplus - @test withinrange(surplus[dt], TestSystems.test1_esurplus, + @test withinrange(surplus[dt], TestData.test1_esurplus, simspec.nsamples, nstderr_tol) @test all(withinrange.(getindex.(surplus, regions, dt), - TestSystems.test1_esurpluses, + TestData.test1_esurpluses, simspec.nsamples, nstderr_tol)) # Flow @test withinrange(flow["Region A" => "Region B"], - TestSystems.test1_i1_flow, + TestData.test1_i1_flow, simspec.nsamples, nstderr_tol) @test withinrange(flow["Region A" => "Region B", dt], - TestSystems.test1_i1_flow, + TestData.test1_i1_flow, simspec.nsamples, nstderr_tol) # Utilization @test withinrange(utilization["Region A" => "Region B"], - TestSystems.test1_i1_util, + TestData.test1_i1_util, simspec.nsamples, nstderr_tol) @test withinrange(utilization["Region A" => "Region B", dt], - TestSystems.test1_i1_util, + TestData.test1_i1_util, simspec.nsamples, nstderr_tol) end @@ -309,47 +301,47 @@ @testset "Test System 2: Gen + Storage, 1 Region" begin simspec = SequentialMonteCarlo(samples=1_000_000, seed=112) - region = first(TestSystems.test2.regions.names) - stor = first(TestSystems.test2.storages.names) - dts = TestSystems.test2.timestamps + region = first(TestData.test2.regions.names) + stor = first(TestData.test2.storages.names) + dts = TestData.test2.timestamps shortfall, surplus, energy = - assess(TestSystems.test2, simspec, + assess(TestData.test2, simspec, Shortfall(), Surplus(), StorageEnergy()) # Shortfall - LOLE @test withinrange(LOLE(shortfall), - TestSystems.test2_lole, nstderr_tol) + TestData.test2_lole, nstderr_tol) @test withinrange(LOLE(shortfall, region), - TestSystems.test2_lole, nstderr_tol) + TestData.test2_lole, nstderr_tol) @test all(withinrange.(LOLE.(shortfall, dts), - TestSystems.test2_lolps, nstderr_tol)) + TestData.test2_lolps, nstderr_tol)) @test all(withinrange.(LOLE.(shortfall, region, dts), - TestSystems.test2_lolps, nstderr_tol)) + TestData.test2_lolps, nstderr_tol)) # Shortfall - EUE @test withinrange(EUE(shortfall), - TestSystems.test2_eue, nstderr_tol) + TestData.test2_eue, nstderr_tol) @test withinrange(EUE(shortfall, region), - TestSystems.test2_eue, nstderr_tol) + TestData.test2_eue, nstderr_tol) @test all(withinrange.(EUE.(shortfall, dts), - TestSystems.test2_eues, nstderr_tol)) + TestData.test2_eues, nstderr_tol)) @test all(withinrange.(EUE.(shortfall, region, dts), - TestSystems.test2_eues, nstderr_tol)) + TestData.test2_eues, nstderr_tol)) # Surplus @test all(withinrange.(getindex.(surplus, dts), - TestSystems.test2_esurplus, + TestData.test2_esurplus, simspec.nsamples, nstderr_tol)) @test all(withinrange.(getindex.(surplus, region, dts), - TestSystems.test2_esurplus, + TestData.test2_esurplus, simspec.nsamples, nstderr_tol)) # Energy @test all(withinrange.(getindex.(energy, dts), - TestSystems.test2_eenergy, + TestData.test2_eenergy, simspec.nsamples, nstderr_tol)) @test all(withinrange.(getindex.(energy, stor, dts), - TestSystems.test2_eenergy, + TestData.test2_eenergy, simspec.nsamples, nstderr_tol)) end @@ -357,64 +349,64 @@ @testset "Test System 3: Gen + Storage, 2 Regions" begin simspec = SequentialMonteCarlo(samples=1_000_000, seed=113) - regions = TestSystems.test3.regions.names - stor = first(TestSystems.test3.storages.names) - dts = TestSystems.test3.timestamps + regions = TestData.test3.regions.names + stor = first(TestData.test3.storages.names) + dts = TestData.test3.timestamps shortfall, surplus, flow, utilization, energy = - assess(TestSystems.test3, simspec, + assess(TestData.test3, simspec, Shortfall(), Surplus(), Flow(), Utilization(), StorageEnergy()) # Shortfall - LOLE @test withinrange(LOLE(shortfall), - TestSystems.test3_lole, nstderr_tol) + TestData.test3_lole, nstderr_tol) @test all(withinrange.(LOLE.(shortfall, regions), - TestSystems.test3_lole_r, nstderr_tol)) + TestData.test3_lole_r, nstderr_tol)) @test all(withinrange.(LOLE.(shortfall, dts), - TestSystems.test3_lole_t, nstderr_tol)) + TestData.test3_lole_t, nstderr_tol)) @test all(withinrange.(LOLE.(shortfall, regions, permutedims(dts)), - TestSystems.test3_lole_rt, nstderr_tol)) + TestData.test3_lole_rt, nstderr_tol)) # Shortfall - EUE @test withinrange(EUE(shortfall), - TestSystems.test3_eue, nstderr_tol) + TestData.test3_eue, nstderr_tol) @test all(withinrange.(EUE.(shortfall, regions), - TestSystems.test3_eue_r, nstderr_tol)) + TestData.test3_eue_r, nstderr_tol)) @test all(withinrange.(EUE.(shortfall, dts), - TestSystems.test3_eue_t, nstderr_tol)) + TestData.test3_eue_t, nstderr_tol)) @test all(withinrange.(EUE.(shortfall, regions, permutedims(dts)), - TestSystems.test3_eue_rt, nstderr_tol)) + TestData.test3_eue_rt, nstderr_tol)) # Surplus @test all(withinrange.(getindex.(surplus, dts), # fails? - TestSystems.test3_esurplus_t, + TestData.test3_esurplus_t, simspec.nsamples, nstderr_tol)) @test all(withinrange.(getindex.(surplus, regions, permutedims(dts)), # fails? - TestSystems.test3_esurplus_rt, + TestData.test3_esurplus_rt, simspec.nsamples, nstderr_tol)) # Flow @test all(withinrange.(getindex.(flow, "Region A"=>"Region B"), - TestSystems.test3_flow, + TestData.test3_flow, simspec.nsamples, nstderr_tol)) @test all(withinrange.(getindex.(flow, "Region A"=>"Region B", dts), - TestSystems.test3_flow_t, + TestData.test3_flow_t, simspec.nsamples, nstderr_tol)) # Utilization @test all(withinrange.(getindex.(utilization, "Region A"=>"Region B"), - TestSystems.test3_util, + TestData.test3_util, simspec.nsamples, nstderr_tol)) @test all(withinrange.(getindex.(utilization, "Region A"=>"Region B", dts), - TestSystems.test3_util_t, + TestData.test3_util_t, simspec.nsamples, nstderr_tol)) # Energy @test all(withinrange.(getindex.(energy, dts), # fails? - TestSystems.test3_eenergy, + TestData.test3_eenergy, simspec.nsamples, nstderr_tol)) @test all(withinrange.(getindex.(energy, stor, dts), # fails? - TestSystems.test3_eenergy, + TestData.test3_eenergy, simspec.nsamples, nstderr_tol)) end diff --git a/test/PRASBase/SystemModel.jl b/PRASCore/test/Systems/SystemModel.jl similarity index 93% rename from test/PRASBase/SystemModel.jl rename to PRASCore/test/Systems/SystemModel.jl index 1a11697b..86e63369 100644 --- a/test/PRASBase/SystemModel.jl +++ b/PRASCore/test/Systems/SystemModel.jl @@ -17,7 +17,8 @@ rand(1:10, 1, 10), rand(1:10, 1, 10), rand(1:10, 1, 10), fill(0.1, 1, 10), fill(0.5, 1, 10)) - timestamps = DateTime(2020, 1, 1, 0):Hour(1):DateTime(2020,1,1,9) + tz = tz"UTC" + timestamps = ZonedDateTime(2020, 1, 1, 0, tz):Hour(1):ZonedDateTime(2020,1,1,9, tz) # Single-region constructor SystemModel( diff --git a/test/PRASBase/assets.jl b/PRASCore/test/Systems/assets.jl similarity index 100% rename from test/PRASBase/assets.jl rename to PRASCore/test/Systems/assets.jl diff --git a/test/PRASBase/collections.jl b/PRASCore/test/Systems/collections.jl similarity index 100% rename from test/PRASBase/collections.jl rename to PRASCore/test/Systems/collections.jl diff --git a/test/PRASBase/runtests.jl b/PRASCore/test/Systems/runtests.jl similarity index 70% rename from test/PRASBase/runtests.jl rename to PRASCore/test/Systems/runtests.jl index b37b8ccd..6e10f9e8 100644 --- a/test/PRASBase/runtests.jl +++ b/PRASCore/test/Systems/runtests.jl @@ -1,9 +1,8 @@ -@testset "PRASBase" begin +@testset "Systems" begin include("units.jl") include("assets.jl") include("collections.jl") include("SystemModel.jl") - include("io.jl") end diff --git a/test/PRASBase/units.jl b/PRASCore/test/Systems/units.jl similarity index 100% rename from test/PRASBase/units.jl rename to PRASCore/test/Systems/units.jl diff --git a/test/dummydata.jl b/PRASCore/test/dummydata.jl similarity index 100% rename from test/dummydata.jl rename to PRASCore/test/dummydata.jl diff --git a/test/runtests.jl b/PRASCore/test/runtests.jl similarity index 74% rename from test/runtests.jl rename to PRASCore/test/runtests.jl index b602f4c7..41cb2f32 100644 --- a/test/runtests.jl +++ b/PRASCore/test/runtests.jl @@ -1,11 +1,11 @@ using Dates -using Distributions -using PRAS +using PRASCore using StatsBase using Test using TimeZones -import PRAS.ResourceAdequacy: MeanEstimate +import PRASCore.Results: MeanEstimate, ReliabilityMetric +import PRASCore.Systems: TestData withinrange(x::ReliabilityMetric, y::Real, n::Real) = isapprox(val(x), y, atol=n*stderror(x)) @@ -22,9 +22,8 @@ Base.isapprox(x::Tuple{Float64,Float64}, y::Vector{<:Real}) = isapprox(x[1], mean(y)) && isapprox(x[2], std(y)) @testset "PRAS" begin - include("PRASBase/runtests.jl") - include("testsystems.jl") include("dummydata.jl") - include("ResourceAdequacy/runtests.jl") - include("CapacityCredit/runtests.jl") + include("Systems/runtests.jl") + include("Results/runtests.jl") + include("Simulations/runtests.jl") end diff --git a/PRASFiles/Project.toml b/PRASFiles/Project.toml new file mode 100644 index 00000000..302c0f27 --- /dev/null +++ b/PRASFiles/Project.toml @@ -0,0 +1,26 @@ +name = "PRASFiles" +uuid = "a2806276-6d43-4ef5-91c0-491704cd7cf1" +authors = [ + "Gord Stephen ", + "Surya Chandan Dhulipala " +] +version = "0.7.0" + +[deps] +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" +PRASCore = "c5c32b99-e7c3-4530-a685-6f76e19f7fe2" +TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53" + +[compat] +Dates = "1" +HDF5 = "0.16,0.17" +PRASCore = "0.7" +TimeZones = "1" +julia = "1.10" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/PRASFiles/src/PRASFiles.jl b/PRASFiles/src/PRASFiles.jl new file mode 100644 index 00000000..db7e8469 --- /dev/null +++ b/PRASFiles/src/PRASFiles.jl @@ -0,0 +1,32 @@ +module PRASFiles + +const PRASFILE_VERSION = "v0.7.0" + +import PRASCore.Systems: SystemModel, Regions, Interfaces, + Generators, Storages, GeneratorStorages, Lines, + timeunits, powerunits, energyunits, unitsymbol + +import Dates: @dateformat_str +import TimeZones: ZonedDateTime +import HDF5: HDF5, attributes, File, Group, Dataset, Datatype, dataspace, + h5open, create_group, create_dataset, hdf5_type_id +import HDF5.API: h5t_create, h5t_copy, h5t_insert, h5t_set_size, + H5T_COMPOUND, h5d_write, H5S_ALL, H5P_DEFAULT + +export savemodel, toymodel, rts_gmlc + +include("read.jl") +include("write.jl") +include("utils.jl") + +function toymodel() + path = dirname(@__FILE__) + return SystemModel(path * "/toymodel.pras") +end + +function rts_gmlc() + path = dirname(@__FILE__) + return SystemModel(path * "/rts.pras") +end + +end diff --git a/src/PRASBase/read.jl b/PRASFiles/src/read.jl similarity index 99% rename from src/PRASBase/read.jl rename to PRASFiles/src/read.jl index 0e384845..cc95b6ce 100644 --- a/src/PRASBase/read.jl +++ b/PRASFiles/src/read.jl @@ -5,7 +5,7 @@ function SystemModel(inputfile::String) version, versionstring = readversion(f) # Determine the appropriate version of the importer to use - return if (0,5,0) <= version < (0,7,0) + return if (0,5,0) <= version < (0,8,0) systemmodel_0_5(f) else error("PRAS file format $versionstring not supported by this version of PRASBase.") diff --git a/src/PRASBase/rts.pras b/PRASFiles/src/rts.pras similarity index 100% rename from src/PRASBase/rts.pras rename to PRASFiles/src/rts.pras diff --git a/src/PRASBase/toymodel.pras b/PRASFiles/src/toymodel.pras similarity index 100% rename from src/PRASBase/toymodel.pras rename to PRASFiles/src/toymodel.pras diff --git a/src/PRASBase/utils.jl b/PRASFiles/src/utils.jl similarity index 95% rename from src/PRASBase/utils.jl rename to PRASFiles/src/utils.jl index 88cf4c5b..c7b6720b 100644 --- a/src/PRASBase/utils.jl +++ b/PRASFiles/src/utils.jl @@ -29,9 +29,6 @@ function makeidxlist(collectionidxs::Vector{Int}, n_collections::Int) end -isnonnegative(x::Real) = x >= 0 -isfractional(x::Real) = 0 <= x <= 1 - function load_matrix(data::HDF5.Dataset, roworder::Vector{Int}, T::DataType) result = read(data) diff --git a/src/PRASBase/write.jl b/PRASFiles/src/write.jl similarity index 99% rename from src/PRASBase/write.jl rename to PRASFiles/src/write.jl index c1ec757c..ed4d6bff 100644 --- a/src/PRASBase/write.jl +++ b/PRASFiles/src/write.jl @@ -65,7 +65,7 @@ function process_metadata!( attrs["energy_unit"] = unitsymbol(E) attrs["start_timestamp"] = string(sys.timestamps.start); - attrs["pras_dataversion"] = PRAS_VERSION + attrs["pras_dataversion"] = PRASFILE_VERSION return diff --git a/PRASFiles/test/runtests.jl b/PRASFiles/test/runtests.jl new file mode 100644 index 00000000..2dacc22d --- /dev/null +++ b/PRASFiles/test/runtests.jl @@ -0,0 +1,30 @@ +using PRASCore +using PRASFiles +using Test + +@testset "PRASFiles" begin + + @testset "Roundtrip .pras files to/from disk" begin + + # TODO: Verify systems accurately depicted? + path = dirname(@__FILE__) + + toy = toymodel() + savemodel(toy, path * "/toymodel2.pras") + toy2 = SystemModel(path * "/toymodel2.pras") + @test toy == toy2 + + rts = rts_gmlc() + savemodel(rts, path * "/rts2.pras") + rts2 = SystemModel(path * "/rts2.pras") + @test rts == rts2 + + end + + @testset "Run RTS-GMLC" begin + + assess(rts_gmlc(), SequentialMonteCarlo(samples=100), Shortfall()) + + end + +end diff --git a/src/PRAS.jl b/src/PRAS.jl deleted file mode 100644 index c5e729f7..00000000 --- a/src/PRAS.jl +++ /dev/null @@ -1,13 +0,0 @@ -module PRAS - -using Reexport - -const PRAS_VERSION = "v0.6.0" - -include("PRASBase/PRASBase.jl") -include("ResourceAdequacy/ResourceAdequacy.jl") -include("CapacityCredit/CapacityCredit.jl") - -import .PRASBase: rts_gmlc,toymodel - -end diff --git a/src/ResourceAdequacy/ResourceAdequacy.jl b/src/ResourceAdequacy/ResourceAdequacy.jl deleted file mode 100644 index 611e40aa..00000000 --- a/src/ResourceAdequacy/ResourceAdequacy.jl +++ /dev/null @@ -1,59 +0,0 @@ -@reexport module ResourceAdequacy - -using MinCostFlows -using ..PRASBase - -import Base: -, broadcastable, getindex, merge! -import Base.Threads: nthreads, @spawn -import Dates: DateTime, Period -import Distributions: DiscreteNonParametric, probs, support -import OnlineStatsBase: EqualWeight, fit!, Mean, value, Variance -import OnlineStats: Series -import Printf: @sprintf -import Random: AbstractRNG, rand, seed! -import Random123: Philox4x -import StatsBase: mean, std, stderror -import TimeZones: ZonedDateTime, @tz_str - -export - - assess, - - # Metrics - ReliabilityMetric, LOLE, EUE, - val, stderror, - - # Simulation specifications - Convolution, SequentialMonteCarlo, - - # Result specifications - Shortfall, ShortfallSamples, Surplus, SurplusSamples, - Flow, FlowSamples, Utilization, UtilizationSamples, - StorageEnergy, StorageEnergySamples, - GeneratorStorageEnergy, GeneratorStorageEnergySamples, - GeneratorAvailability, StorageAvailability, - GeneratorStorageAvailability, LineAvailability, - - # Convenience re-exports - ZonedDateTime, @tz_str - -abstract type ReliabilityMetric end -abstract type SimulationSpec end -abstract type ResultSpec end -abstract type ResultAccumulator{S<:SimulationSpec,R<:ResultSpec} end -abstract type Result{ - N, # Number of timesteps simulated - L, # Length of each simulation timestep - T <: Period, # Units of each simulation timestep -} end - -MeanVariance = Series{ - Number, Tuple{Mean{Float64, EqualWeight}, - Variance{Float64, Float64, EqualWeight}}} - -include("metrics.jl") -include("results/results.jl") -include("simulations/simulations.jl") -include("utils.jl") - -end diff --git a/src/ResourceAdequacy/results/availability.jl b/src/ResourceAdequacy/results/availability.jl deleted file mode 100644 index b748d51d..00000000 --- a/src/ResourceAdequacy/results/availability.jl +++ /dev/null @@ -1,120 +0,0 @@ -abstract type AbstractAvailabilityResult{N,L,T} <: Result{N,L,T} end - -# Colon indexing - -getindex(x::AbstractAvailabilityResult, ::Colon, t::ZonedDateTime) = - getindex.(x, names(x), t) - -getindex(x::AbstractAvailabilityResult, name::String, ::Colon) = - getindex.(x, name, x.timestamps) - -getindex(x::AbstractAvailabilityResult, ::Colon, ::Colon) = - getindex.(x, names(x), permutedims(x.timestamps)) - -""" - GeneratorAvailability - -Generator availability represents the availability of generators at timestamps -in a GeneratorAvailabilityResult with a (generators, timestamps, samples) matrix API. - -No averaging occurs. -""" -struct GeneratorAvailability <: ResultSpec end - -struct GeneratorAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} - - generators::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - available::Array{Bool,3} - -end - -names(x::GeneratorAvailabilityResult) = x.generators - -function getindex(x::GeneratorAvailabilityResult, g::AbstractString, t::ZonedDateTime) - i_g = findfirstunique(x.generators, g) - i_t = findfirstunique(x.timestamps, t) - return vec(x.available[i_g, i_t, :]) -end - -""" - StorageAvailability - -Storage availability represents the availability of storage resources at timestamps -in a StorageAvailabilityResult with a (storages, timestamps, samples) matrix API. - -No averaging occurs. -""" -struct StorageAvailability <: ResultSpec end - -struct StorageAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} - - storages::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - available::Array{Bool,3} - -end - -names(x::StorageAvailabilityResult) = x.storages - -function getindex(x::StorageAvailabilityResult, s::AbstractString, t::ZonedDateTime) - i_s = findfirstunique(x.storages, s) - i_t = findfirstunique(x.timestamps, t) - return vec(x.available[i_s, i_t, :]) -end - -""" - GeneratorStorageAvailability - -Generator storage availability represents the availability of generatorstorage resources at timestamps -in a GeneratorStorageAvailabilityResult with a (generatorstorages, timestamps, samples) matrix API. - -No averaging occurs -""" -struct GeneratorStorageAvailability <: ResultSpec end - -struct GeneratorStorageAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} - - generatorstorages::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - available::Array{Bool,3} - -end - -names(x::GeneratorStorageAvailabilityResult) = x.generatorstorages - -function getindex(x::GeneratorStorageAvailabilityResult, gs::AbstractString, t::ZonedDateTime) - i_gs = findfirstunique(x.generatorstorages, gs) - i_t = findfirstunique(x.timestamps, t) - return vec(x.available[i_gs, i_t, :]) -end - -""" - LineAvailability - -Line availability represents the availability of lines at timestamps -in a LineAvailabilityResult with a (lines, timestamps, samples) matrix API. - -No averaging occurs. -""" -struct LineAvailability <: ResultSpec end - -struct LineAvailabilityResult{N,L,T<:Period} <: AbstractAvailabilityResult{N,L,T} - - lines::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - available::Array{Bool,3} - -end - -names(x::LineAvailabilityResult) = x.lines - -function getindex(x::LineAvailabilityResult, l::AbstractString, t::ZonedDateTime) - i_l = findfirstunique(x.lines, l) - i_t = findfirstunique(x.timestamps, t) - return vec(x.available[i_l, i_t, :]) -end diff --git a/src/ResourceAdequacy/results/energy.jl b/src/ResourceAdequacy/results/energy.jl deleted file mode 100644 index f94af4a8..00000000 --- a/src/ResourceAdequacy/results/energy.jl +++ /dev/null @@ -1,166 +0,0 @@ -abstract type AbstractEnergyResult{N,L,T} <: Result{N,L,T} end - -# Colon indexing - -getindex(x::AbstractEnergyResult, ::Colon) = - getindex.(x, x.timestamps) - -getindex(x::AbstractEnergyResult, ::Colon, t::ZonedDateTime) = - getindex.(x, names(x), t) - -getindex(x::AbstractEnergyResult, name::String, ::Colon) = - getindex.(x, name, x.timestamps) - -getindex(x::AbstractEnergyResult, ::Colon, ::Colon) = - getindex.(x, names(x), permutedims(x.timestamps)) - -# Sample-averaged Storage state-of-charge data - -""" - StorageEnergy - -Storage energy represents the state-of-charge of storage -resources at timestamps in a StorageEnergyResult with a (storages, timestamps) -matrix API. - -Separate samples are averaged together into mean and std values. - -See [`StorageEnergySamples`](@ref) for all storage energy samples. - -See [`GeneratorStorageEnergy`](@ref) for generator storage energy. -""" -struct StorageEnergy <: ResultSpec end - -struct StorageEnergyResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} - - nsamples::Union{Int,Nothing} - storages::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - energy_mean::Matrix{Float64} - - energy_period_std::Vector{Float64} - energy_regionperiod_std::Matrix{Float64} - -end - -names(x::StorageEnergyResult) = x.storages - -function getindex(x::StorageEnergyResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return sum(view(x.energy_mean, :, i_t)), x.energy_period_std[i_t] -end - -function getindex(x::StorageEnergyResult, s::AbstractString, t::ZonedDateTime) - i_s = findfirstunique(x.storages, s) - i_t = findfirstunique(x.timestamps, t) - return x.energy_mean[i_s, i_t], x.energy_regionperiod_std[i_s, i_t] -end - -# Sample-averaged GeneratorStorage state-of-charge data -""" - GeneratorStorageEnergy - -Generator storage energy represents state-of-charge of generatorstorage -resources at timestamps in a StorageEnergyResult with a (generatorstorages, timestamps) -matrix API. - -Separate samples are averaged together into mean and std values. - -See [`GeneratorStorageEnergySamples`](@ref) for all generator storage energy samples. - -See [`StorageEnergy`](@ref) for storage energy. -""" -struct GeneratorStorageEnergy <: ResultSpec end - -struct GeneratorStorageEnergyResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} - - nsamples::Union{Int,Nothing} - generatorstorages::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - energy_mean::Matrix{Float64} - - energy_period_std::Vector{Float64} - energy_regionperiod_std::Matrix{Float64} - -end - -names(x::GeneratorStorageEnergyResult) = x.generatorstorages - -function getindex(x::GeneratorStorageEnergyResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return sum(view(x.energy_mean, :, i_t)), x.energy_period_std[i_t] -end - -function getindex(x::GeneratorStorageEnergyResult, gs::AbstractString, t::ZonedDateTime) - i_gs = findfirstunique(x.generatorstorages, gs) - i_t = findfirstunique(x.timestamps, t) - return x.energy_mean[i_gs, i_t], x.energy_regionperiod_std[i_gs, i_t] -end - -""" - StorageEnergySamples - -Storage energy samples represent the state-of-charge of storage -resources at timestamps, which has not been averaged across different samples. -This presents a 3D matrix API (storages, timestamps, samples). - -See [`StorageEnergy`](@ref) for sample-averaged storage energy. -""" -struct StorageEnergySamples <: ResultSpec end - -struct StorageEnergySamplesResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} - - storages::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - energy::Array{Int,3} - -end - -names(x::StorageEnergySamplesResult) = x.storages - -function getindex(x::StorageEnergySamplesResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return vec(sum(view(x.energy, :, i_t, :), dims=1)) -end - -function getindex(x::StorageEnergySamplesResult, s::AbstractString, t::ZonedDateTime) - i_s = findfirstunique(x.storages, s) - i_t = findfirstunique(x.timestamps, t) - return vec(x.energy[i_s, i_t, :]) -end - -""" - GeneratorStorageEnergySamples - -Generator storage energy samples represent the state-of-charge of generatorstorage -resources at timestamps, which has not been averaged across different samples. -This presents a 3D matrix API (generatorstorages, timestamps, samples). - -See [`GeneratorStorageEnergy`](@ref) for sample-averaged generator storage energy. -""" -struct GeneratorStorageEnergySamples <: ResultSpec end - -struct GeneratorStorageEnergySamplesResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractEnergyResult{N,L,T} - - generatorstorages::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - energy::Array{Int,3} - -end - -names(x::GeneratorStorageEnergySamplesResult) = x.generatorstorages - -function getindex(x::GeneratorStorageEnergySamplesResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return vec(sum(view(x.energy, :, i_t, :), dims=1)) -end - -function getindex(x::GeneratorStorageEnergySamplesResult, gs::AbstractString, t::ZonedDateTime) - i_gs = findfirstunique(x.generatorstorages, gs) - i_t = findfirstunique(x.timestamps, t) - return vec(x.energy[i_gs, i_t, :]) -end diff --git a/src/ResourceAdequacy/results/flow.jl b/src/ResourceAdequacy/results/flow.jl deleted file mode 100644 index 06e4477c..00000000 --- a/src/ResourceAdequacy/results/flow.jl +++ /dev/null @@ -1,89 +0,0 @@ -""" - Flow - -Flow metric represents the flow between interfaces at timestamps -in a FlowResult with a (interfaces, timestamps) matrix API. - -Separate samples are averaged together into mean and std values. - -See [`FlowSamples`](@ref) for all flow samples. -""" -struct Flow <: ResultSpec end -abstract type AbstractFlowResult{N,L,T} <: Result{N,L,T} end - -# Colon indexing - -getindex(x::AbstractFlowResult, ::Colon) = - getindex.(x, x.interfaces) - -getindex(x::AbstractFlowResult, ::Colon, t::ZonedDateTime) = - getindex.(x, x.interfaces, t) - -getindex(x::AbstractFlowResult, i::Pair{<:AbstractString,<:AbstractString}, ::Colon) = - getindex.(x, i, x.timestamps) - -getindex(x::AbstractFlowResult, ::Colon, ::Colon) = - getindex.(x, x.interfaces, permutedims(x.timestamps)) - -# Sample-averaged flow data - -struct FlowResult{N,L,T<:Period,P<:PowerUnit} <: AbstractFlowResult{N,L,T} - - nsamples::Union{Int,Nothing} - interfaces::Vector{Pair{String,String}} - timestamps::StepRange{ZonedDateTime,T} - - flow_mean::Matrix{Float64} - - flow_interface_std::Vector{Float64} - flow_interfaceperiod_std::Matrix{Float64} - -end - -function getindex(x::FlowResult, i::Pair{<:AbstractString,<:AbstractString}) - i_i, reverse = findfirstunique_directional(x.interfaces, i) - flow = mean(view(x.flow_mean, i_i, :)) - return reverse ? -flow : flow, x.flow_interface_std[i_i] -end - -function getindex(x::FlowResult, i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) - i_i, reverse = findfirstunique_directional(x.interfaces, i) - i_t = findfirstunique(x.timestamps, t) - flow = x.flow_mean[i_i, i_t] - return reverse ? -flow : flow, x.flow_interfaceperiod_std[i_i, i_t] -end - -# Full flow data -""" - FlowSamples - -Flow samples represent the flow between interfaces at timestamps, which has -not been averaged across different samples. This presents a -3D matrix API (interfaces, timestamps, samples). - -See [`Flow`](@ref) for sample-averaged flow data. -""" -struct FlowSamples <: ResultSpec end - -struct FlowSamplesResult{N,L,T<:Period,P<:PowerUnit} <: AbstractFlowResult{N,L,T} - - interfaces::Vector{Pair{String,String}} - timestamps::StepRange{ZonedDateTime,T} - - flow::Array{Int,3} - -end - -function getindex(x::FlowSamplesResult, i::Pair{<:AbstractString,<:AbstractString}) - i_i, reverse = findfirstunique_directional(x.interfaces, i) - flow = vec(mean(view(x.flow, i_i, :, :), dims=1)) - return reverse ? -flow : flow -end - - -function getindex(x::FlowSamplesResult, i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) - i_i, reverse = findfirstunique_directional(x.interfaces, i) - i_t = findfirstunique(x.timestamps, t) - flow = vec(x.flow[i_i, i_t, :]) - return reverse ? -flow : flow -end diff --git a/src/ResourceAdequacy/results/results.jl b/src/ResourceAdequacy/results/results.jl deleted file mode 100644 index 895da22f..00000000 --- a/src/ResourceAdequacy/results/results.jl +++ /dev/null @@ -1,39 +0,0 @@ -broadcastable(x::ResultSpec) = Ref(x) -broadcastable(x::Result) = Ref(x) - -include("shortfall.jl") -include("surplus.jl") -include("flow.jl") -include("utilization.jl") -include("availability.jl") -include("energy.jl") - -function resultchannel( - method::SimulationSpec, results::T, threads::Int -) where T <: Tuple{Vararg{ResultSpec}} - - types = accumulatortype.(method, results) - return Channel{Tuple{types...}}(threads) - -end - -merge!(xs::T, ys::T) where T <: Tuple{Vararg{ResultAccumulator}} = - foreach(merge!, xs, ys) - -function finalize( - results::Channel{<:Tuple{Vararg{ResultAccumulator}}}, - system::SystemModel{N,L,T,P,E}, - threads::Int -) where {N,L,T,P,E} - - total_result = take!(results) - - for _ in 2:threads - thread_result = take!(results) - merge!(total_result, thread_result) - end - close(results) - - return finalize.(total_result, system) - -end diff --git a/src/ResourceAdequacy/results/surplus.jl b/src/ResourceAdequacy/results/surplus.jl deleted file mode 100644 index 918ed2a4..00000000 --- a/src/ResourceAdequacy/results/surplus.jl +++ /dev/null @@ -1,83 +0,0 @@ -""" - Surplus - -Surplus metric represents extra generation at regions and timestamps -in a SurplusResults with a (regions, timestamps) matrix API. - -Separate samples are averaged together into mean and std values. - -See [`SurplusSamples`](@ref) for all surplus samples. -""" -struct Surplus <: ResultSpec end -abstract type AbstractSurplusResult{N,L,T} <: Result{N,L,T} end - -# Colon indexing - -getindex(x::AbstractSurplusResult, ::Colon) = - getindex.(x, x.timestamps) - -getindex(x::AbstractSurplusResult, ::Colon, t::ZonedDateTime) = - getindex.(x, x.regions, t) - -getindex(x::AbstractSurplusResult, r::AbstractString, ::Colon) = - getindex.(x, r, x.timestamps) - -getindex(x::AbstractSurplusResult, ::Colon, ::Colon) = - getindex.(x, x.regions, permutedims(x.timestamps)) - -# Sample-averaged surplus data - -struct SurplusResult{N,L,T<:Period,P<:PowerUnit} <: AbstractSurplusResult{N,L,T} - - nsamples::Union{Int,Nothing} - regions::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - surplus_mean::Matrix{Float64} - - surplus_period_std::Vector{Float64} - surplus_regionperiod_std::Matrix{Float64} - -end - -function getindex(x::SurplusResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return sum(view(x.surplus_mean, :, i_t)), x.surplus_period_std[i_t] -end - -function getindex(x::SurplusResult, r::AbstractString, t::ZonedDateTime) - i_r = findfirstunique(x.regions, r) - i_t = findfirstunique(x.timestamps, t) - return x.surplus_mean[i_r, i_t], x.surplus_regionperiod_std[i_r, i_t] -end - -# Full surplus data -""" - SurplusSamples - -Surplus samples represent extra generation at regions and timestamps -in a SurplusSamplesResult with a (regions, timestamps, samples) matrix API. - -See [`Surplus`](@ref) for sample-averaged surplus data. -""" -struct SurplusSamples <: ResultSpec end - -struct SurplusSamplesResult{N,L,T<:Period,P<:PowerUnit} <: AbstractSurplusResult{N,L,T} - - regions::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - surplus::Array{Int,3} - -end - -function getindex(x::SurplusSamplesResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return vec(sum(view(x.surplus, :, i_t, :), dims=1)) -end - -function getindex(x::SurplusSamplesResult, r::AbstractString, t::ZonedDateTime) - i_r = findfirstunique(x.regions, r) - i_t = findfirstunique(x.timestamps, t) - return vec(x.surplus[i_r, i_t, :]) -end diff --git a/src/ResourceAdequacy/results/utilization.jl b/src/ResourceAdequacy/results/utilization.jl deleted file mode 100644 index 0bb81256..00000000 --- a/src/ResourceAdequacy/results/utilization.jl +++ /dev/null @@ -1,86 +0,0 @@ -""" - Utilization - -Utilization metric represents how much an interface between regions is used -across timestamps in a UtilizationResult with a (interfaces, timestamps) matrix API. - -Separate samples are averaged together into mean and std values. - -See [`UtilizationSamples`](@ref) for all utilization samples. -""" -struct Utilization <: ResultSpec end -abstract type AbstractUtilizationResult{N,L,T} <: Result{N,L,T} end - -# Colon indexing - -getindex(x::AbstractUtilizationResult, ::Colon) = - getindex.(x, x.interfaces) - -getindex(x::AbstractUtilizationResult, ::Colon, t::ZonedDateTime) = - getindex.(x, x.interfaces, t) - -getindex(x::AbstractUtilizationResult, i::Pair{<:AbstractString,<:AbstractString}, ::Colon) = - getindex.(x, i, x.timestamps) - -getindex(x::AbstractUtilizationResult, ::Colon, ::Colon) = - getindex.(x, x.interfaces, permutedims(x.timestamps)) - -# Sample-averaged utilization data - -struct UtilizationResult{N,L,T<:Period} <: AbstractUtilizationResult{N,L,T} - - nsamples::Union{Int,Nothing} - interfaces::Vector{Pair{String,String}} - timestamps::StepRange{ZonedDateTime,T} - - utilization_mean::Matrix{Float64} - - utilization_interface_std::Vector{Float64} - utilization_interfaceperiod_std::Matrix{Float64} - -end - -function getindex(x::UtilizationResult, i::Pair{<:AbstractString,<:AbstractString}) - i_i, _ = findfirstunique_directional(x.interfaces, i) - return mean(view(x.utilization_mean, i_i, :)), x.utilization_interface_std[i_i] -end - -function getindex(x::UtilizationResult, i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) - i_i, _ = findfirstunique_directional(x.interfaces, i) - i_t = findfirstunique(x.timestamps, t) - return x.utilization_mean[i_i, i_t], x.utilization_interfaceperiod_std[i_i, i_t] -end - -""" - UtilizationSamples - -Utilization samples represent the utilization between interfaces at timestamps, which has -not been averaged across different samples. This presents a -3D matrix API (interfaces, timestamps, samples). - -See [`Utilization`](@ref) for averaged utilization samples. -""" -struct UtilizationSamples <: ResultSpec end - -struct UtilizationSamplesResult{N,L,T<:Period} <: AbstractUtilizationResult{N,L,T} - - interfaces::Vector{Pair{String,String}} - timestamps::StepRange{ZonedDateTime,T} - - utilization::Array{Float64,3} - -end - -function getindex(x::UtilizationSamplesResult, - i::Pair{<:AbstractString,<:AbstractString}) - i_i, _ = findfirstunique_directional(x.interfaces, i) - return vec(mean(view(x.utilization, i_i, :, :), dims=1)) -end - - -function getindex(x::UtilizationSamplesResult, - i::Pair{<:AbstractString,<:AbstractString}, t::ZonedDateTime) - i_i, _ = findfirstunique_directional(x.interfaces, i) - i_t = findfirstunique(x.timestamps, t) - return vec(x.utilization[i_i, i_t, :]) -end diff --git a/src/ResourceAdequacy/simulations/convolution/Convolution.jl b/src/ResourceAdequacy/simulations/convolution/Convolution.jl deleted file mode 100644 index 4b0224b0..00000000 --- a/src/ResourceAdequacy/simulations/convolution/Convolution.jl +++ /dev/null @@ -1,89 +0,0 @@ -include("conv.jl") - -struct Convolution <: SimulationSpec - - verbose::Bool - threaded::Bool - - Convolution(;verbose::Bool=false, threaded::Bool=true) = - new(verbose, threaded) - -end - -function assess( - system::SystemModel{N}, - method::Convolution, - resultspecs::ResultSpec... -) where {N} - - nregions = length(system.regions) - nstors = length(system.storages) - ngenstors = length(system.generatorstorages) - - if nregions > 1 - @warn "$method is a copper plate simulation method. " * - "Transmission constraints between the system's $nregions " * - "regions will be ignored in this assessment." - end - - if nstors + ngenstors > 0 - resources = String[] - nstors > 0 && push!(resources, "$nstors Storage") - ngenstors > 0 && push!(resources, "$ngenstors GeneratorStorage") - @warn "$method is a non-sequential simulation method. " * - "The system's " * join(resources, " and ") * " resources " * - "will be ignored in this assessment." - end - - threads = nthreads() - periods = Channel{Int}(2*threads) - results = resultchannel(method, resultspecs, threads) - - @spawn makeperiods(periods, N) - - if method.threaded - - if (threads == 1) - @warn "It looks like you haven't configured JULIA_NUM_THREADS before you started the julia repl. \n If you want to use multi-threading, stop the execution and start your julia repl using : \n julia --project --threads auto" - end - - for _ in 1:threads - @spawn assess(system, method, periods, results, resultspecs...) - end - else - assess(system, method, periods, results, resultspecs...) - end - - return finalize(results, system, method.threaded ? threads : 1) - -end - -function makeperiods(periods::Channel{Int}, N::Int) - for t in 1:N - put!(periods, t) - end - close(periods) -end - -function assess( - system::SystemModel{N,L,T,P,E}, method::Convolution, - periods::Channel{Int}, - results::Channel{<:Tuple{Vararg{ResultAccumulator{Convolution}}}}, - resultspecs::ResultSpec... -) where {N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} - - accs = accumulator.(system, method, resultspecs) - - for t in periods - - distr = CapacityDistribution(system, t) - foreach(acc -> record!(acc, t, distr), accs) - - end - - put!(results, accs) - -end - -include("result_shortfall.jl") -include("result_surplus.jl") diff --git a/src/ResourceAdequacy/simulations/convolution/conv.jl b/src/ResourceAdequacy/simulations/convolution/conv.jl deleted file mode 100644 index b2892f10..00000000 --- a/src/ResourceAdequacy/simulations/convolution/conv.jl +++ /dev/null @@ -1,152 +0,0 @@ -CapacityDistribution = - DiscreteNonParametric{Int,Float64,Vector{Int},Vector{Float64}} - -function (::Type{CapacityDistribution})(system::SystemModel, t::Int) - - capacities = system.generators.capacity[:, t] - - μ = system.generators.μ[:, t] - λ = system.generators.λ[:, t] - availabilities = μ ./ (μ .+ λ) - - result = spconv(capacities, availabilities) - support(result) .-= colsum(system.regions.load, t) - - return result - -end - -function assess(distr::CapacityDistribution) - - xs = support(distr) - ps = probs(distr) - - i = 1 - lolp = 0. - eul = 0. - - while i <= length(xs) - - xs[i] >= 0 && break - lolp += ps[i] - eul -= ps[i] * xs[i] - i += 1 - - end - - return lolp, eul - -end - -function surplus(distr::CapacityDistribution) - - xs = support(distr) - ps = probs(distr) - - i = 1 - es = 0. - - for i in 1:length(xs) - xs[i] <= 0 && continue - es += ps[i] * xs[i] - end - - return es - -end - -function spconv(hvsraw::AbstractVector{Int}, hpsraw::AbstractVector{Float64}) - - zeroidxs = hvsraw .!= 0 - hvs = hvsraw[zeroidxs] - hps = hpsraw[zeroidxs] - - length(hvs) == 0 && - return DiscreteNonParametric([0], [1.], check_args=false) - - max_n = sum(hvs) + 1 - current_probs = Vector{Float64}(undef, max_n) - prev_probs = Vector{Float64}(undef, max_n) - current_values = Vector{Int}(undef, max_n) - prev_values = Vector{Int}(undef, max_n) - - current_n = 2 - current_values[1:current_n] = [0, hvs[1]] - current_probs[1:current_n] = [1 - hps[1], hps[1]] - - for (hv, hp) in zip(hvs[2:end], hps[2:end]) - current_values, current_probs, current_n, prev_values, prev_probs = - spconv!(prev_values, prev_probs, hv, hp, - current_values, current_probs, current_n) - end - - resize!(current_values, current_n) - resize!(current_probs, current_n) - nonzeroprob_idxs = findall(x -> x>0, current_probs) - - return DiscreteNonParametric( - current_values[nonzeroprob_idxs], - current_probs[nonzeroprob_idxs], - check_args=false) - -end - -function spconv!(y_values::Vector{Int}, y_probs::Vector{Float64}, - h_value::Int, h_prob::Float64, - x_values::Vector{Int}, x_probs::Vector{Float64}, nx::Int) - - h_q = 1 - h_prob - - ix = ixsh = 1 - iy = 0 - lastval = -1 - - @inbounds while ix <= nx - - x = x_values[ix] - xsh = x_values[ixsh] + h_value - - if lastval == x - @fastmath y_probs[iy] += h_q * x_probs[ix] - ix += 1 - - elseif lastval == xsh - @fastmath y_probs[iy] += h_prob * x_probs[ixsh] - ixsh += 1 - - elseif x == xsh - iy += 1 - y_values[iy] = x - @fastmath y_probs[iy] = h_q * x_probs[ix] + h_prob * x_probs[ixsh] - lastval = x - ix += 1 - ixsh += 1 - - elseif x < xsh - iy += 1 - y_values[iy] = x - @fastmath y_probs[iy] = h_q * x_probs[ix] - lastval = x - ix += 1 - - elseif xsh < x - iy += 1 - y_values[iy] = xsh - @fastmath y_probs[iy] = h_prob * x_probs[ixsh] - lastval = xsh - ixsh += 1 - - end - - end - - @inbounds while ixsh <= nx - iy += 1 - y_values[iy] = x_values[ixsh] + h_value - @fastmath y_probs[iy] = h_prob * x_probs[ixsh] - ixsh += 1 - end - - return y_values, y_probs, iy, x_values, x_probs - -end diff --git a/src/ResourceAdequacy/simulations/convolution/result_shortfall.jl b/src/ResourceAdequacy/simulations/convolution/result_shortfall.jl deleted file mode 100644 index 9821b654..00000000 --- a/src/ResourceAdequacy/simulations/convolution/result_shortfall.jl +++ /dev/null @@ -1,55 +0,0 @@ -struct ConvolutionShortfallAccumulator <: ResultAccumulator{Convolution,Shortfall} - - lolps::Vector{Float64} - euls::Vector{Float64} - -end - -function merge!( - x::ConvolutionShortfallAccumulator, y::ConvolutionShortfallAccumulator -) - - x.lolps .+= y.lolps - x.euls .+= y.euls - return - -end - -accumulatortype(::Convolution, ::Shortfall) = ConvolutionShortfallAccumulator - -accumulator(::SystemModel{N}, ::Convolution, ::Shortfall) where {N} = - ConvolutionShortfallAccumulator(zeros(N), zeros(N)) - -function record!( - acc::ConvolutionShortfallAccumulator, - t::Int, distr::CapacityDistribution -) - - lolp, eul = assess(distr) - acc.lolps[t] = lolp - acc.euls[t] = eul - return - -end - -function finalize( - acc::ConvolutionShortfallAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - lole = sum(acc.lolps) - - p2e = conversionfactor(L,T,P,E) - eues = acc.euls .* p2e - eue = sum(eues) - - allzeros = zeros(length(acc.lolps)) - - return ShortfallResult{N,L,T,E}( - nothing, ["[PRAS] Entire System"], system.timestamps, - lole, 0., [lole], [0.], acc.lolps, allzeros, - reshape(acc.lolps, 1, :), reshape(allzeros, 1, :), - reshape(eues, 1, :), 0., [0.], allzeros, reshape(allzeros, 1, :) - ) - -end diff --git a/src/ResourceAdequacy/simulations/convolution/result_surplus.jl b/src/ResourceAdequacy/simulations/convolution/result_surplus.jl deleted file mode 100644 index 2ff4763c..00000000 --- a/src/ResourceAdequacy/simulations/convolution/result_surplus.jl +++ /dev/null @@ -1,43 +0,0 @@ -struct ConvolutionSurplusAccumulator <: ResultAccumulator{Convolution,Surplus} - - surplus::Vector{Float64} - -end - -function merge!( - x::ConvolutionSurplusAccumulator, y::ConvolutionSurplusAccumulator -) - - x.surplus .+= y.surplus - return - -end - -accumulatortype(::Convolution, ::Surplus) = ConvolutionSurplusAccumulator - -accumulator(::SystemModel{N}, ::Convolution, ::Surplus) where {N} = - ConvolutionSurplusAccumulator(zeros(N)) - -function record!( - acc::ConvolutionSurplusAccumulator, - t::Int, distr::CapacityDistribution -) - - acc.surplus[t] = surplus(distr) - return - -end - -function finalize( - acc::ConvolutionSurplusAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - allzeros = zeros(length(acc.surplus)) - - return SurplusResult{N,L,T,P}( - nothing, ["__EntireSystem"], system.timestamps, - reshape(acc.surplus, 1, :), allzeros, reshape(allzeros, 1, :) - ) - -end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_availability.jl b/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_availability.jl deleted file mode 100644 index 8c8a6ecb..00000000 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_availability.jl +++ /dev/null @@ -1,219 +0,0 @@ -# GeneratorAvailability - -struct SMCGenAvailabilityAccumulator <: - ResultAccumulator{SequentialMonteCarlo,GeneratorAvailability} - - available::Array{Bool,3} - -end - -function merge!( - x::SMCGenAvailabilityAccumulator, y::SMCGenAvailabilityAccumulator -) - - x.available .|= y.available - return - -end - -accumulatortype(::SequentialMonteCarlo, ::GeneratorAvailability) = SMCGenAvailabilityAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::GeneratorAvailability -) where {N} - - ngens = length(sys.generators) - available = zeros(Bool, ngens, N, simspec.nsamples) - - return SMCGenAvailabilityAccumulator(available) - -end - -function record!( - acc::SMCGenAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - acc.available[:, t, sampleid] .= state.gens_available - return - -end - -reset!(acc::SMCGenAvailabilityAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCGenAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return GeneratorAvailabilityResult{N,L,T}( - system.generators.names, system.timestamps, acc.available) - -end - -# StorageAvailability - -struct SMCStorAvailabilityAccumulator <: - ResultAccumulator{SequentialMonteCarlo,StorageAvailability} - - available::Array{Bool,3} - -end - -function merge!( - x::SMCStorAvailabilityAccumulator, y::SMCStorAvailabilityAccumulator -) - - x.available .|= y.available - return - -end - -accumulatortype(::SequentialMonteCarlo, ::StorageAvailability) = SMCStorAvailabilityAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::StorageAvailability -) where {N} - - nstors = length(sys.storages) - available = zeros(Bool, nstors, N, simspec.nsamples) - - return SMCStorAvailabilityAccumulator(available) - -end - -function record!( - acc::SMCStorAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - acc.available[:, t, sampleid] .= state.stors_available - return - -end - -reset!(acc::SMCStorAvailabilityAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCStorAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return StorageAvailabilityResult{N,L,T}( - system.storages.names, system.timestamps, acc.available) - -end - -# GeneratorStorageAvailability - -struct SMCGenStorAvailabilityAccumulator <: - ResultAccumulator{SequentialMonteCarlo,GeneratorStorageAvailability} - - available::Array{Bool,3} - -end - -function merge!( - x::SMCGenStorAvailabilityAccumulator, y::SMCGenStorAvailabilityAccumulator -) - - x.available .|= y.available - return - -end - -accumulatortype(::SequentialMonteCarlo, ::GeneratorStorageAvailability) = SMCGenStorAvailabilityAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::GeneratorStorageAvailability -) where {N} - - ngenstors = length(sys.generatorstorages) - available = zeros(Bool, ngenstors, N, simspec.nsamples) - - return SMCGenStorAvailabilityAccumulator(available) - -end - -function record!( - acc::SMCGenStorAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - acc.available[:, t, sampleid] .= state.genstors_available - return - -end - -reset!(acc::SMCGenStorAvailabilityAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCGenStorAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return GeneratorStorageAvailabilityResult{N,L,T}( - system.generatorstorages.names, system.timestamps, acc.available) - -end - -# LineAvailability - -struct SMCLineAvailabilityAccumulator <: - ResultAccumulator{SequentialMonteCarlo,LineAvailability} - - available::Array{Bool,3} - -end - -function merge!( - x::SMCLineAvailabilityAccumulator, y::SMCLineAvailabilityAccumulator -) - - x.available .|= y.available - return - -end - -accumulatortype(::SequentialMonteCarlo, ::LineAvailability) = SMCLineAvailabilityAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::LineAvailability -) where {N} - - nlines = length(sys.lines) - available = zeros(Bool, nlines, N, simspec.nsamples) - - return SMCLineAvailabilityAccumulator(available) - -end - -function record!( - acc::SMCLineAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - acc.available[:, t, sampleid] .= state.lines_available - return - -end - -reset!(acc::SMCLineAvailabilityAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCLineAvailabilityAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return LineAvailabilityResult{N,L,T}( - system.lines.names, system.timestamps, acc.available) - -end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_energy.jl b/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_energy.jl deleted file mode 100644 index 5fc3e9b3..00000000 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_energy.jl +++ /dev/null @@ -1,273 +0,0 @@ -# StorageEnergy - -mutable struct SMCStorageEnergyAccumulator <: - ResultAccumulator{SequentialMonteCarlo,StorageEnergy} - - # Cross-simulation energy mean/variances - energy_period::Vector{MeanVariance} - energy_storageperiod::Matrix{MeanVariance} - -end - -function merge!( - x::SMCStorageEnergyAccumulator, y::SMCStorageEnergyAccumulator -) - - foreach(merge!, x.energy_period, y.energy_period) - foreach(merge!, x.energy_storageperiod, y.energy_storageperiod) - - return - -end - -accumulatortype(::SequentialMonteCarlo, ::StorageEnergy) = SMCStorageEnergyAccumulator - -function accumulator( - sys::SystemModel{N}, ::SequentialMonteCarlo, ::StorageEnergy -) where {N} - - nstorages = length(sys.storages) - - energy_period = [meanvariance() for _ in 1:N] - energy_storageperiod = [meanvariance() for _ in 1:nstorages, _ in 1:N] - - return SMCStorageEnergyAccumulator( - energy_period, energy_storageperiod) - -end - -function record!( - acc::SMCStorageEnergyAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - totalenergy = 0 - nstorages = length(system.storages) - - for s in 1:nstorages - - storageenergy = state.stors_energy[s] - fit!(acc.energy_storageperiod[s,t], storageenergy) - totalenergy += storageenergy - - end - - fit!(acc.energy_period[t], totalenergy) - - return - -end - -reset!(acc::SMCStorageEnergyAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCStorageEnergyAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - _, period_std = mean_std(acc.energy_period) - storageperiod_mean, storageperiod_std = mean_std(acc.energy_storageperiod) - - nsamples = first(first(acc.energy_period).stats).n - - return StorageEnergyResult{N,L,T,E}( - nsamples, system.storages.names, system.timestamps, - storageperiod_mean, period_std, storageperiod_std) - -end - -# GeneratorStorageEnergy - -mutable struct SMCGenStorageEnergyAccumulator <: - ResultAccumulator{SequentialMonteCarlo,GeneratorStorageEnergy} - - # Cross-simulation energy mean/variances - energy_period::Vector{MeanVariance} - energy_genstorperiod::Matrix{MeanVariance} - -end - -function merge!( - x::SMCGenStorageEnergyAccumulator, y::SMCGenStorageEnergyAccumulator -) - - foreach(merge!, x.energy_period, y.energy_period) - foreach(merge!, x.energy_genstorperiod, y.energy_genstorperiod) - - return - -end - -accumulatortype(::SequentialMonteCarlo, ::GeneratorStorageEnergy) = - SMCGenStorageEnergyAccumulator - -function accumulator( - sys::SystemModel{N}, ::SequentialMonteCarlo, ::GeneratorStorageEnergy -) where {N} - - ngenstors = length(sys.generatorstorages) - - energy_period = [meanvariance() for _ in 1:N] - energy_genstorperiod = [meanvariance() for _ in 1:ngenstors, _ in 1:N] - - return SMCGenStorageEnergyAccumulator( - energy_period, energy_genstorperiod) - -end - -function record!( - acc::SMCGenStorageEnergyAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - totalenergy = 0 - ngenstors = length(system.generatorstorages) - - for s in 1:ngenstors - - genstorenergy = state.genstors_energy[s] - fit!(acc.energy_genstorperiod[s,t], genstorenergy) - totalenergy += genstorenergy - - end - - fit!(acc.energy_period[t], totalenergy) - - return - -end - -reset!(acc::SMCGenStorageEnergyAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCGenStorageEnergyAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - _, period_std = mean_std(acc.energy_period) - genstorperiod_mean, genstorperiod_std = mean_std(acc.energy_genstorperiod) - - nsamples = first(first(acc.energy_period).stats).n - - return GeneratorStorageEnergyResult{N,L,T,E}( - nsamples, system.generatorstorages.names, system.timestamps, - genstorperiod_mean, period_std, genstorperiod_std) - -end - -# StorageEnergySamples - -struct SMCStorageEnergySamplesAccumulator <: - ResultAccumulator{SequentialMonteCarlo,StorageEnergySamples} - - energy::Array{Float64,3} - -end - -function merge!( - x::SMCStorageEnergySamplesAccumulator, y::SMCStorageEnergySamplesAccumulator -) - - x.energy .+= y.energy - return - -end - -accumulatortype(::SequentialMonteCarlo, ::StorageEnergySamples) = - SMCStorageEnergySamplesAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::StorageEnergySamples -) where {N} - - nstors = length(sys.storages) - energy = zeros(Int, nstors, N, simspec.nsamples) - - return SMCStorageEnergySamplesAccumulator(energy) - -end - -function record!( - acc::SMCStorageEnergySamplesAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - acc.energy[:, t, sampleid] .= state.stors_energy - return - -end - -reset!(acc::SMCStorageEnergySamplesAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCStorageEnergySamplesAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return StorageEnergySamplesResult{N,L,T,E}( - system.storages.names, system.timestamps, acc.energy) - -end - -# GeneratorStorageEnergySamples - -struct SMCGenStorageEnergySamplesAccumulator <: - ResultAccumulator{SequentialMonteCarlo,GeneratorStorageEnergySamples} - - energy::Array{Float64,3} - -end - -function merge!( - x::SMCGenStorageEnergySamplesAccumulator, - y::SMCGenStorageEnergySamplesAccumulator -) - - x.energy .+= y.energy - return - -end - -accumulatortype(::SequentialMonteCarlo, ::GeneratorStorageEnergySamples) = - SMCGenStorageEnergySamplesAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::GeneratorStorageEnergySamples -) where {N} - - ngenstors = length(sys.generatorstorages) - energy = zeros(Int, ngenstors, N, simspec.nsamples) - - return SMCGenStorageEnergySamplesAccumulator(energy) - -end - -function record!( - acc::SMCGenStorageEnergySamplesAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - acc.energy[:, t, sampleid] .= state.genstors_energy - return - -end - -reset!(acc::SMCGenStorageEnergySamplesAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCGenStorageEnergySamplesAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return GeneratorStorageEnergySamplesResult{N,L,T,E}( - system.generatorstorages.names, system.timestamps, acc.energy) - -end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_flow.jl b/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_flow.jl deleted file mode 100644 index 7e6a7eaa..00000000 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_flow.jl +++ /dev/null @@ -1,148 +0,0 @@ -# Flow - -struct SMCFlowAccumulator <: ResultAccumulator{SequentialMonteCarlo,Flow} - - flow_interface::Vector{MeanVariance} - flow_interfaceperiod::Matrix{MeanVariance} - - flow_interface_currentsim::Vector{Int} - -end - -function merge!( - x::SMCFlowAccumulator, y::SMCFlowAccumulator -) - - foreach(merge!, x.flow_interface, y.flow_interface) - foreach(merge!, x.flow_interfaceperiod, y.flow_interfaceperiod) - -end - -accumulatortype(::SequentialMonteCarlo, ::Flow) = SMCFlowAccumulator - -function accumulator( - sys::SystemModel{N}, ::SequentialMonteCarlo, ::Flow -) where {N} - - n_interfaces = length(sys.interfaces) - flow_interface = [meanvariance() for _ in 1:n_interfaces] - flow_interfaceperiod = [meanvariance() for _ in 1:n_interfaces, _ in 1:N] - - flow_interface_currentsim = zeros(Int, n_interfaces) - - return SMCFlowAccumulator( - flow_interface, flow_interfaceperiod, flow_interface_currentsim) - -end - -function record!( - acc::SMCFlowAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - edges = problem.fp.edges - - for (i, (f, b)) in enumerate(zip(problem.interface_forward_edges, - problem.interface_reverse_edges)) - - flow = edges[f].flow - edges[b].flow - acc.flow_interface_currentsim[i] += flow - fit!(acc.flow_interfaceperiod[i,t], flow) - - end - -end - -function reset!(acc::SMCFlowAccumulator, sampleid::Int) - - for i in eachindex(acc.flow_interface_currentsim) - fit!(acc.flow_interface[i], acc.flow_interface_currentsim[i]) - acc.flow_interface_currentsim[i] = 0 - end - -end - -function finalize( - acc::SMCFlowAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - nsamples = length(system.interfaces) > 0 ? - first(acc.flow_interface[1].stats).n : nothing - - flow_mean, flow_interfaceperiod_std = mean_std(acc.flow_interfaceperiod) - flow_interface_std = last(mean_std(acc.flow_interface)) / N - - fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) - toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) - - return FlowResult{N,L,T,P}( - nsamples, Pair.(fromregions, toregions), system.timestamps, - flow_mean, flow_interface_std, flow_interfaceperiod_std) - -end - -# FlowSamples - -struct SMCFlowSamplesAccumulator <: - ResultAccumulator{SequentialMonteCarlo,FlowSamples} - - flow::Array{Int,3} - -end - -function merge!( - x::SMCFlowSamplesAccumulator, y::SMCFlowSamplesAccumulator -) - - x.flow .+= y.flow - return - -end - -accumulatortype(::SequentialMonteCarlo, ::FlowSamples) = SMCFlowSamplesAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::FlowSamples -) where {N} - - ninterfaces = length(sys.interfaces) - flow = zeros(Int, ninterfaces, N, simspec.nsamples) - - return SMCFlowSamplesAccumulator(flow) - -end - -function record!( - acc::SMCFlowSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - for (i, (e_f, e_r)) in enumerate(zip(problem.interface_forward_edges, - problem.interface_reverse_edges)) - acc.flow[i, t, sampleid] = problem.fp.edges[e_f].flow - - problem.fp.edges[e_r].flow - end - - return - -end - -reset!(acc::SMCFlowSamplesAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCFlowSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) - toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) - - return FlowSamplesResult{N,L,T,P}( - Pair.(fromregions, toregions), system.timestamps, acc.flow) - -end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_shortfall.jl b/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_shortfall.jl deleted file mode 100644 index dd58b192..00000000 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_shortfall.jl +++ /dev/null @@ -1,236 +0,0 @@ -# Shortfall - -mutable struct SMCShortfallAccumulator <: ResultAccumulator{SequentialMonteCarlo,Shortfall} - - # Cross-simulation LOL period count mean/variances - periodsdropped_total::MeanVariance - periodsdropped_region::Vector{MeanVariance} - periodsdropped_period::Vector{MeanVariance} - periodsdropped_regionperiod::Matrix{MeanVariance} - - # Running LOL period counts for current simulation - periodsdropped_total_currentsim::Int - periodsdropped_region_currentsim::Vector{Int} - - # Cross-simulation UE mean/variances - unservedload_total::MeanVariance - unservedload_region::Vector{MeanVariance} - unservedload_period::Vector{MeanVariance} - unservedload_regionperiod::Matrix{MeanVariance} - - # Running UE totals for current simulation - unservedload_total_currentsim::Int - unservedload_region_currentsim::Vector{Int} - -end - -function merge!( - x::SMCShortfallAccumulator, y::SMCShortfallAccumulator -) - - merge!(x.periodsdropped_total, y.periodsdropped_total) - foreach(merge!, x.periodsdropped_region, y.periodsdropped_region) - foreach(merge!, x.periodsdropped_period, y.periodsdropped_period) - foreach(merge!, x.periodsdropped_regionperiod, y.periodsdropped_regionperiod) - - merge!(x.unservedload_total, y.unservedload_total) - foreach(merge!, x.unservedload_region, y.unservedload_region) - foreach(merge!, x.unservedload_period, y.unservedload_period) - foreach(merge!, x.unservedload_regionperiod, y.unservedload_regionperiod) - - return - -end - -accumulatortype(::SequentialMonteCarlo, ::Shortfall) = SMCShortfallAccumulator - -function accumulator( - sys::SystemModel{N}, ::SequentialMonteCarlo, ::Shortfall -) where {N} - - nregions = length(sys.regions) - - periodsdropped_total = meanvariance() - periodsdropped_region = [meanvariance() for _ in 1:nregions] - periodsdropped_period = [meanvariance() for _ in 1:N] - periodsdropped_regionperiod = [meanvariance() for _ in 1:nregions, _ in 1:N] - - periodsdropped_total_currentsim = 0 - periodsdropped_region_currentsim = zeros(Int, nregions) - - unservedload_total = meanvariance() - unservedload_region = [meanvariance() for _ in 1:nregions] - unservedload_period = [meanvariance() for _ in 1:N] - unservedload_regionperiod = [meanvariance() for _ in 1:nregions, _ in 1:N] - - unservedload_total_currentsim = 0 - unservedload_region_currentsim = zeros(Int, nregions) - - return SMCShortfallAccumulator( - periodsdropped_total, periodsdropped_region, - periodsdropped_period, periodsdropped_regionperiod, - periodsdropped_total_currentsim, periodsdropped_region_currentsim, - unservedload_total, unservedload_region, - unservedload_period, unservedload_regionperiod, - unservedload_total_currentsim, unservedload_region_currentsim) - -end - -function record!( - acc::SMCShortfallAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - totalshortfall = 0 - isshortfall = false - - edges = problem.fp.edges - - for r in problem.region_unserved_edges - - regionshortfall = edges[r].flow - isregionshortfall = regionshortfall > 0 - - fit!(acc.periodsdropped_regionperiod[r,t], isregionshortfall) - fit!(acc.unservedload_regionperiod[r,t], regionshortfall) - - if isregionshortfall - - isshortfall = true - totalshortfall += regionshortfall - - acc.periodsdropped_region_currentsim[r] += 1 - acc.unservedload_region_currentsim[r] += regionshortfall - - end - - end - - if isshortfall - acc.periodsdropped_total_currentsim += 1 - acc.unservedload_total_currentsim += totalshortfall - end - - fit!(acc.periodsdropped_period[t], isshortfall) - fit!(acc.unservedload_period[t], totalshortfall) - - return - -end - -function reset!(acc::SMCShortfallAccumulator, sampleid::Int) - - # Store regional / total sums for current simulation - fit!(acc.periodsdropped_total, acc.periodsdropped_total_currentsim) - fit!(acc.unservedload_total, acc.unservedload_total_currentsim) - - for r in eachindex(acc.periodsdropped_region) - fit!(acc.periodsdropped_region[r], acc.periodsdropped_region_currentsim[r]) - fit!(acc.unservedload_region[r], acc.unservedload_region_currentsim[r]) - end - - # Reset for new simulation - acc.periodsdropped_total_currentsim = 0 - fill!(acc.periodsdropped_region_currentsim, 0) - acc.unservedload_total_currentsim = 0 - fill!(acc.unservedload_region_currentsim, 0) - - return - -end - -function finalize( - acc::SMCShortfallAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - ep_total_mean, ep_total_std = mean_std(acc.periodsdropped_total) - ep_region_mean, ep_region_std = mean_std(acc.periodsdropped_region) - ep_period_mean, ep_period_std = mean_std(acc.periodsdropped_period) - ep_regionperiod_mean, ep_regionperiod_std = - mean_std(acc.periodsdropped_regionperiod) - - _, ue_total_std = mean_std(acc.unservedload_total) - _, ue_region_std = mean_std(acc.unservedload_region) - _, ue_period_std = mean_std(acc.unservedload_period) - ue_regionperiod_mean, ue_regionperiod_std = - mean_std(acc.unservedload_regionperiod) - - nsamples = first(acc.unservedload_total.stats).n - - p2e = conversionfactor(L,T,P,E) - ue_regionperiod_mean .*= p2e - ue_total_std *= p2e - ue_region_std .*= p2e - ue_period_std .*= p2e - ue_regionperiod_std .*= p2e - - return ShortfallResult{N,L,T,E}( - nsamples, system.regions.names, system.timestamps, - ep_total_mean, ep_total_std, ep_region_mean, ep_region_std, - ep_period_mean, ep_period_std, - ep_regionperiod_mean, ep_regionperiod_std, - ue_regionperiod_mean, ue_total_std, - ue_region_std, ue_period_std, ue_regionperiod_std) - -end - -# ShortfallSamples - -struct SMCShortfallSamplesAccumulator <: - ResultAccumulator{SequentialMonteCarlo,ShortfallSamples} - - shortfall::Array{Int,3} - -end - -function merge!( - x::SMCShortfallSamplesAccumulator, y::SMCShortfallSamplesAccumulator -) - - x.shortfall .+= y.shortfall - return - -end - -accumulatortype(::SequentialMonteCarlo, ::ShortfallSamples) = SMCShortfallSamplesAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::ShortfallSamples -) where {N} - - nregions = length(sys.regions) - shortfall = zeros(Int, nregions, N, simspec.nsamples) - - return SMCShortfallSamplesAccumulator(shortfall) - -end - -function record!( - acc::SMCShortfallSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - for (r, e) in enumerate(problem.region_unserved_edges) - acc.shortfall[r, t, sampleid] = problem.fp.edges[e].flow - end - - return - -end - -reset!(acc::SMCShortfallSamplesAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCShortfallSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return ShortfallSamplesResult{N,L,T,P,E}( - system.regions.names, system.timestamps, acc.shortfall) - -end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_surplus.jl b/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_surplus.jl deleted file mode 100644 index 4386e872..00000000 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_surplus.jl +++ /dev/null @@ -1,177 +0,0 @@ -# Surplus - -mutable struct SMCSurplusAccumulator <: ResultAccumulator{SequentialMonteCarlo,Surplus} - - # Cross-simulation surplus mean/variances - surplus_period::Vector{MeanVariance} - surplus_regionperiod::Matrix{MeanVariance} - -end - -function merge!( - x::SMCSurplusAccumulator, y::SMCSurplusAccumulator -) - - foreach(merge!, x.surplus_period, y.surplus_period) - foreach(merge!, x.surplus_regionperiod, y.surplus_regionperiod) - - return - -end - -accumulatortype(::SequentialMonteCarlo, ::Surplus) = SMCSurplusAccumulator - -function accumulator( - sys::SystemModel{N}, ::SequentialMonteCarlo, ::Surplus -) where {N} - - nregions = length(sys.regions) - - surplus_period = [meanvariance() for _ in 1:N] - surplus_regionperiod = [meanvariance() for _ in 1:nregions, _ in 1:N] - - return SMCSurplusAccumulator( - surplus_period, surplus_regionperiod) - -end - -function record!( - acc::SMCSurplusAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - totalsurplus = 0 - edges = problem.fp.edges - - for (r, e_idx) in enumerate(problem.region_unused_edges) - - regionsurplus = edges[e_idx].flow - - for s in system.region_stor_idxs[r] - se_idx = problem.storage_dischargeunused_edges[s] - regionsurplus += edges[se_idx].flow - end - - for gs in system.region_genstor_idxs[r] - - gse_discharge_idx = problem.genstorage_dischargeunused_edges[gs] - gse_inflow_idx = problem.genstorage_inflowunused_edges[gs] - - grid_limit = system.generatorstorages.gridinjection_capacity[gs, t] - total_unused = edges[gse_discharge_idx].flow + edges[gse_inflow_idx].flow - - regionsurplus += min(grid_limit, total_unused) - - end - - fit!(acc.surplus_regionperiod[r,t], regionsurplus) - totalsurplus += regionsurplus - - end - - fit!(acc.surplus_period[t], totalsurplus) - - return - -end - -reset!(acc::SMCSurplusAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCSurplusAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - _, period_std = mean_std(acc.surplus_period) - regionperiod_mean, regionperiod_std = mean_std(acc.surplus_regionperiod) - - nsamples = first(first(acc.surplus_period).stats).n - - return SurplusResult{N,L,T,P}( - nsamples, system.regions.names, system.timestamps, - regionperiod_mean, period_std, regionperiod_std) - -end - -# SurplusSamples - -struct SMCSurplusSamplesAccumulator <: - ResultAccumulator{SequentialMonteCarlo,SurplusSamples} - - surplus::Array{Int,3} - -end - -function merge!( - x::SMCSurplusSamplesAccumulator, y::SMCSurplusSamplesAccumulator -) - - x.surplus .+= y.surplus - return - -end - -accumulatortype(::SequentialMonteCarlo, ::SurplusSamples) = SMCSurplusSamplesAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::SurplusSamples -) where {N} - - nregions = length(sys.regions) - surplus = zeros(Int, nregions, N, simspec.nsamples) - - return SMCSurplusSamplesAccumulator(surplus) - -end - -function record!( - acc::SMCSurplusSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - edges = problem.fp.edges - - for (r, e) in enumerate(problem.region_unused_edges) - - regionsurplus = edges[e].flow - - for s in system.region_stor_idxs[r] - se_idx = problem.storage_dischargeunused_edges[s] - regionsurplus += edges[se_idx].flow - end - - for gs in system.region_genstor_idxs[r] - - gse_discharge_idx = problem.genstorage_dischargeunused_edges[gs] - gse_inflow_idx = problem.genstorage_inflowunused_edges[gs] - - grid_limit = system.generatorstorages.gridinjection_capacity[gs, t] - total_unused = edges[gse_discharge_idx].flow + edges[gse_inflow_idx].flow - - regionsurplus += min(grid_limit, total_unused) - - end - - acc.surplus[r, t, sampleid] = regionsurplus - - end - - return - -end - -reset!(acc::SMCSurplusSamplesAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCSurplusSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - return SurplusSamplesResult{N,L,T,P}( - system.regions.names, system.timestamps, acc.surplus) - -end diff --git a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_utilization.jl b/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_utilization.jl deleted file mode 100644 index 778cd8cf..00000000 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/result_utilization.jl +++ /dev/null @@ -1,173 +0,0 @@ -# Utilization - -struct SMCUtilizationAccumulator <: ResultAccumulator{SequentialMonteCarlo,Utilization} - - util_interface::Vector{MeanVariance} - util_interfaceperiod::Matrix{MeanVariance} - - util_interface_currentsim::Vector{Float64} - -end - -function merge!( - x::SMCUtilizationAccumulator, y::SMCUtilizationAccumulator -) - - foreach(merge!, x.util_interface, y.util_interface) - foreach(merge!, x.util_interfaceperiod, y.util_interfaceperiod) - -end - -accumulatortype(::SequentialMonteCarlo, ::Utilization) = SMCUtilizationAccumulator - -function accumulator( - sys::SystemModel{N}, ::SequentialMonteCarlo, ::Utilization -) where {N} - - n_interfaces = length(sys.interfaces) - util_interface = [meanvariance() for _ in 1:n_interfaces] - util_interfaceperiod = [meanvariance() for _ in 1:n_interfaces, _ in 1:N] - - util_interface_currentsim = zeros(Int, n_interfaces) - - return SMCUtilizationAccumulator( - util_interface, util_interfaceperiod, util_interface_currentsim) - -end - -function record!( - acc::SMCUtilizationAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - edges = problem.fp.edges - - for (i, (f, b)) in enumerate(zip(problem.interface_forward_edges, - problem.interface_reverse_edges)) - - util = utilization(problem.fp.edges[f], problem.fp.edges[b]) - acc.util_interface_currentsim[i] += util - fit!(acc.util_interfaceperiod[i,t], util) - - end - -end - -function reset!(acc::SMCUtilizationAccumulator, sampleid::Int) - - for i in eachindex(acc.util_interface_currentsim) - fit!(acc.util_interface[i], acc.util_interface_currentsim[i]) - acc.util_interface_currentsim[i] = 0 - end - -end - -function finalize( - acc::SMCUtilizationAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - nsamples = length(system.interfaces) > 0 ? - first(acc.util_interface[1].stats).n : nothing - - util_mean, util_interfaceperiod_std = mean_std(acc.util_interfaceperiod) - util_interface_std = last(mean_std(acc.util_interface)) / N - - fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) - toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) - - return UtilizationResult{N,L,T}( - nsamples, Pair.(fromregions, toregions), system.timestamps, - util_mean, util_interface_std, util_interfaceperiod_std) - -end - -# UtilizationSamples - -struct SMCUtilizationSamplesAccumulator <: - ResultAccumulator{SequentialMonteCarlo,UtilizationSamples} - - utilization::Array{Float64,3} - -end - -function merge!( - x::SMCUtilizationSamplesAccumulator, y::SMCUtilizationSamplesAccumulator -) - - x.utilization .+= y.utilization - return - -end - -accumulatortype(::SequentialMonteCarlo, ::UtilizationSamples) = SMCUtilizationSamplesAccumulator - -function accumulator( - sys::SystemModel{N}, simspec::SequentialMonteCarlo, ::UtilizationSamples -) where {N} - - ninterfaces = length(sys.interfaces) - utilization = zeros(Float64, ninterfaces, N, simspec.nsamples) - - return SMCUtilizationSamplesAccumulator(utilization) - -end - -function record!( - acc::SMCUtilizationSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, - state::SystemState, problem::DispatchProblem, - sampleid::Int, t::Int -) where {N,L,T,P,E} - - for (i, (e_f, e_r)) in enumerate(zip(problem.interface_forward_edges, - problem.interface_reverse_edges)) - - acc.utilization[i, t, sampleid] = - utilization(problem.fp.edges[e_f], problem.fp.edges[e_r]) - - end - - return - -end - -reset!(acc::SMCUtilizationSamplesAccumulator, sampleid::Int) = nothing - -function finalize( - acc::SMCUtilizationSamplesAccumulator, - system::SystemModel{N,L,T,P,E}, -) where {N,L,T,P,E} - - fromregions = getindex.(Ref(system.regions.names), system.interfaces.regions_from) - toregions = getindex.(Ref(system.regions.names), system.interfaces.regions_to) - - return UtilizationSamplesResult{N,L,T}( - Pair.(fromregions, toregions), system.timestamps, acc.utilization) - -end - - -function utilization(f::MinCostFlows.Edge, b::MinCostFlows.Edge) - - flow_forward = f.flow - max_forward = f.limit - - flow_back = b.flow - max_back = b.limit - - util = if flow_forward > 0 - flow_forward/max_forward - elseif flow_back > 0 - flow_back/max_back - elseif iszero(max_forward) && iszero(max_back) - 1.0 - else - 0.0 - end - - return util - -end diff --git a/src/ResourceAdequacy/simulations/simulations.jl b/src/ResourceAdequacy/simulations/simulations.jl deleted file mode 100644 index ba0ccec6..00000000 --- a/src/ResourceAdequacy/simulations/simulations.jl +++ /dev/null @@ -1,4 +0,0 @@ -broadcastable(x::SimulationSpec) = Ref(x) - -include("convolution/Convolution.jl") -include("sequentialmontecarlo/SequentialMonteCarlo.jl") diff --git a/test/PRASBase/io.jl b/test/PRASBase/io.jl deleted file mode 100644 index 828f961b..00000000 --- a/test/PRASBase/io.jl +++ /dev/null @@ -1,16 +0,0 @@ -@testset "Roundtrip .pras files to/from disk" begin - - # TODO: Verify systems accurately depicted? - path = dirname(@__FILE__) - - toy = PRAS.toymodel() - savemodel(toy, path * "/toymodel2.pras") - toy2 = SystemModel(path * "/toymodel2.pras") - @test toy == toy2 - - rts = PRAS.rts_gmlc() - savemodel(rts, path * "/rts2.pras") - rts2 = SystemModel(path * "/rts2.pras") - @test rts == rts2 - -end diff --git a/test/ResourceAdequacy/runtests.jl b/test/ResourceAdequacy/runtests.jl deleted file mode 100644 index 5eb9ed3b..00000000 --- a/test/ResourceAdequacy/runtests.jl +++ /dev/null @@ -1,8 +0,0 @@ -@testset "ResourceAdequacy" begin - - include("utils.jl") - include("metrics.jl") - include("results/results.jl") - include("simulation.jl") - -end diff --git a/test/ResourceAdequacy/simulation.jl b/test/ResourceAdequacy/simulation.jl deleted file mode 100644 index 2785ea69..00000000 --- a/test/ResourceAdequacy/simulation.jl +++ /dev/null @@ -1,6 +0,0 @@ -@testset "Simulation" begin - - include("simulation/convolution.jl") - include("simulation/sequentialmontecarlo.jl") - -end diff --git a/test/ResourceAdequacy/simulation/convolution.jl b/test/ResourceAdequacy/simulation/convolution.jl deleted file mode 100644 index d41ba2e4..00000000 --- a/test/ResourceAdequacy/simulation/convolution.jl +++ /dev/null @@ -1,53 +0,0 @@ -@testset "Convolution" begin - - simspec = Convolution(threaded=false) - simspec_threaded = Convolution(threaded=true) - resultspecs = (Shortfall(), Surplus()) - - result_1a, surplus_1a = - assess(TestSystems.singlenode_a, simspec, resultspecs...) - - @test LOLE(result_1a) ≈ LOLE{4,1,Hour}(MeanEstimate(0.355)) - @test all(LOLE.(result_1a, result_1a.timestamps) .≈ - LOLE{1,1,Hour}.(MeanEstimate.([0.028, 0.271, 0.028, 0.028]))) - @test EUE(result_1a) ≈ EUE{4,1,Hour,MWh}(MeanEstimate(1.59)) - @test all(EUE.(result_1a, result_1a.timestamps) .≈ - EUE{1,1,Hour,MWh}.(MeanEstimate.([0.29, 0.832, 0.29, 0.178]))) - - result_1a5, surplus_1a5 = - assess(TestSystems.singlenode_a_5min, simspec, resultspecs...) - - @test LOLE(result_1a5) ≈ - LOLE{4,5,Minute}(MeanEstimate(TestSystems.singlenode_a_lole)) - @test all(LOLE.(result_1a5, result_1a5.timestamps) .≈ - LOLE{1,5,Minute}.(MeanEstimate.(TestSystems.singlenode_a_lolps))) - @test EUE(result_1a5) ≈ - EUE{4,5,Minute,MWh}(MeanEstimate.(TestSystems.singlenode_a_eue/12)) - @test all(EUE.(result_1a5, result_1a5.timestamps) .≈ - EUE{1,5,Minute,MWh}.(MeanEstimate.(TestSystems.singlenode_a_eues ./ 12))) - - result_1b, surplus_1b = - assess(TestSystems.singlenode_b, simspec, resultspecs...) - - @test LOLE(result_1b) ≈ LOLE{6,1,Hour}(MeanEstimate(0.96)) - @test all(LOLE.(result_1b, result_1b.timestamps) .≈ - LOLE{1,1,Hour}.(MeanEstimate.([0.19, 0.19, 0.19, 0.1, 0.1, 0.19]))) - @test EUE(result_1b) ≈ EUE{6,1,Hour,MWh}(MeanEstimate(7.11)) - @test all(EUE.(result_1b, result_1b.timestamps) .≈ - EUE{1,1,Hour,MWh}.(MeanEstimate.([1.29, 1.29, 1.29, 0.85, 1.05, 1.34]))) - - result_3, surplus_3 = - assess(TestSystems.threenode, simspec, resultspecs...) - - @test LOLE(result_3) ≈ LOLE{4,1,Hour}(MeanEstimate(1.17877)) - @test all(LOLE.(result_3, result_3.timestamps) .≈ - LOLE{1,1,Hour}.(MeanEstimate.([.14707, .40951, .21268, .40951]))) - @test EUE(result_3) ≈ EUE{4,1,Hour,MWh}(MeanEstimate(11.73276)) - @test all(EUE.(result_3, result_3.timestamps) .≈ - EUE{1,1,Hour,MWh}.(MeanEstimate.([1.75783, 3.13343, 2.47954, 4.36196]))) - - # TODO: Surplus tests - - assess(TestSystems.singlenode_a, simspec_threaded, resultspecs...) - -end diff --git a/test/ResourceAdequacy/utils.jl b/test/ResourceAdequacy/utils.jl deleted file mode 100644 index 4845bb25..00000000 --- a/test/ResourceAdequacy/utils.jl +++ /dev/null @@ -1,33 +0,0 @@ -@testset "Utils" begin - - @testset "Convolution" begin - - # x = rand(10000) - # a = DiscreteNonParametric(cumsum(rand(1:100, 10000)), x ./ sum(x)) - - # y = rand(10000) - # b = DiscreteNonParametric(cumsum(rand(1:100, 10000)), y ./ sum(y)) - - end - - @testset "Distribution Assessment" begin - - distr = DiscreteNonParametric([-2, -1, 0, 1, 2], fill(0.2, 5)) - lolp, eul = assess(distr) - es = ResourceAdequacy.surplus(distr) - - @test isapprox(lolp, 0.4) - @test isapprox(eul, 0.6) - @test isapprox(es, 0.6) - - distr = DiscreteNonParametric([1, 2, 3, 4, 5], fill(0.2, 5)) - lolp, eul = assess(distr) - es = ResourceAdequacy.surplus(distr) - - @test isapprox(lolp, 0.0) - @test isapprox(eul, 0.0) - @test isapprox(es, 3) - - end - -end