diff --git a/.github/workflows/PRAS.jl.yml b/.github/workflows/PRAS.jl.yml new file mode 100644 index 00000000..2c52e261 --- /dev/null +++ b/.github/workflows/PRAS.jl.yml @@ -0,0 +1,72 @@ +name: PRAS.jl tests +# Run on master, tags, or any pull request +on: + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST) + push: + branches: [main] + tags: ["*"] + pull_request: +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - "lts" # Latest LTS release, min supported + - "1" # Latest release + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + - aarch64 + exclude: + - os: windows-latest + arch: aarch64 + - os: ubuntu-latest + arch: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v4 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}- + ${{ runner.os }}-${{ matrix.arch }}-test- + ${{ runner.os }}-${{ matrix.arch }}- + ${{ runner.os }}- + - run: julia --project=PRAS.jl -e 'import Pkg; + Pkg.develop([ + (path="PRASCore.jl",), + (path="PRASFiles.jl",), + (path="PRASCapacityCredits.jl",) + ])' + shell: bash + - uses: julia-actions/julia-buildpkg@latest + with: + project: PRAS.jl + - run: | + git config --global user.name Tester + git config --global user.email te@st.er + - uses: julia-actions/julia-runtest@latest + with: + project: PRAS.jl + env: + JULIA_NUM_THREADS: 2 + - uses: julia-actions/julia-processcoverage@v1 + with: + directories: PRAS.jl/src + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/PRASCapacityCredits.jl.yml b/.github/workflows/PRASCapacityCredits.jl.yml new file mode 100644 index 00000000..758233da --- /dev/null +++ b/.github/workflows/PRASCapacityCredits.jl.yml @@ -0,0 +1,67 @@ +name: PRASCapacityCredits.jl tests +# Run on master, tags, or any pull request +on: + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST) + push: + branches: [main] + tags: ["*"] + pull_request: +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - "lts" # Latest LTS release, min supported + - "1" # Latest release + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + - aarch64 + exclude: + - os: windows-latest + arch: aarch64 + - os: ubuntu-latest + arch: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v4 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}- + ${{ runner.os }}-${{ matrix.arch }}-test- + ${{ runner.os }}-${{ matrix.arch }}- + ${{ runner.os }}- + - run: julia --project=PRASCapacityCredits.jl -e 'import Pkg; Pkg.develop(path="PRASCore.jl")' + shell: bash + - uses: julia-actions/julia-buildpkg@latest + with: + project: PRASCapacityCredits.jl + - run: | + git config --global user.name Tester + git config --global user.email te@st.er + - uses: julia-actions/julia-runtest@latest + with: + project: PRASCapacityCredits.jl + env: + JULIA_NUM_THREADS: 2 + - uses: julia-actions/julia-processcoverage@v1 + with: + directories: PRASCapacityCredits.jl/src + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/CI.yml b/.github/workflows/PRASCore.jl.yml similarity index 87% rename from .github/workflows/CI.yml rename to .github/workflows/PRASCore.jl.yml index bddc6183..ced28356 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/PRASCore.jl.yml @@ -1,10 +1,10 @@ -name: CI +name: PRASCore.jl tests # Run on master, tags, or any pull request on: schedule: - cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST) push: - branches: [master] + branches: [main] tags: ["*"] pull_request: jobs: @@ -46,18 +46,20 @@ jobs: ${{ runner.os }}-${{ matrix.arch }}-test- ${{ runner.os }}-${{ matrix.arch }}- ${{ runner.os }}- - - run: julia -e 'import Pkg; - Pkg.Registry.add("General"); - Pkg.Registry.add(Pkg.RegistrySpec(url="https://github.com/NREL/JuliaRegistry.git"))' - shell: bash - uses: julia-actions/julia-buildpkg@latest + with: + project: PRASCore.jl - run: | git config --global user.name Tester git config --global user.email te@st.er - uses: julia-actions/julia-runtest@latest + with: + project: PRASCore.jl env: JULIA_NUM_THREADS: 2 - uses: julia-actions/julia-processcoverage@v1 + with: + directories: PRASCore.jl/src - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/PRASFiles.jl.yml b/.github/workflows/PRASFiles.jl.yml new file mode 100644 index 00000000..bd0c2a80 --- /dev/null +++ b/.github/workflows/PRASFiles.jl.yml @@ -0,0 +1,67 @@ +name: PRASFiles.jl tests +# Run on master, tags, or any pull request +on: + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST) + push: + branches: [main] + tags: ["*"] + pull_request: +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - "lts" # Latest LTS release, min supported + - "1" # Latest release + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + - aarch64 + exclude: + - os: windows-latest + arch: aarch64 + - os: ubuntu-latest + arch: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v4 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-test-${{ env.cache-name }}- + ${{ runner.os }}-${{ matrix.arch }}-test- + ${{ runner.os }}-${{ matrix.arch }}- + ${{ runner.os }}- + - run: julia --project=PRASFiles.jl -e 'import Pkg; Pkg.develop(path="PRASCore.jl")' + shell: bash + - uses: julia-actions/julia-buildpkg@latest + with: + project: PRASFiles.jl + - run: | + git config --global user.name Tester + git config --global user.email te@st.er + - uses: julia-actions/julia-runtest@latest + with: + project: PRASFiles.jl + env: + JULIA_NUM_THREADS: 2 + - uses: julia-actions/julia-processcoverage@v1 + with: + directories: PRASFiles.jl/src + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} 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/LICENSE.md b/LICENSE.md deleted file mode 100644 index 61effd79..00000000 --- a/LICENSE.md +++ /dev/null @@ -1,23 +0,0 @@ -The PRAS package is licensed under a modified MIT "Expat" License: - -> Copyright 2020 Alliance for Sustainable Energy, LLC -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS, THE COPYRIGHT HOLDERS, THE UNITED STATES, THE UNITED STATES DEPARTMENT -> OF ENERGY, OR ANY OF THEIR EMPLOYEES BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. -> diff --git a/PRAS.jl/LICENSE.md b/PRAS.jl/LICENSE.md new file mode 100644 index 00000000..29b607f4 --- /dev/null +++ b/PRAS.jl/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Alliance for Sustainable Energy, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PRAS.jl/Project.toml b/PRAS.jl/Project.toml new file mode 100644 index 00000000..3e8aa751 --- /dev/null +++ b/PRAS.jl/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.10" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/PRAS.jl/src/PRAS.jl b/PRAS.jl/src/PRAS.jl new file mode 100644 index 00000000..b843575f --- /dev/null +++ b/PRAS.jl/src/PRAS.jl @@ -0,0 +1,11 @@ +module PRAS + +using Reexport + +@reexport using PRASCore +@reexport using PRASFiles +@reexport using PRASCapacityCredits + +import PRASFiles: toymodel, rts_gmlc + +end diff --git a/PRAS.jl/test/runtests.jl b/PRAS.jl/test/runtests.jl new file mode 100644 index 00000000..f11a6c26 --- /dev/null +++ b/PRAS.jl/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.jl/LICENSE.md b/PRASCapacityCredits.jl/LICENSE.md new file mode 100644 index 00000000..29b607f4 --- /dev/null +++ b/PRASCapacityCredits.jl/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Alliance for Sustainable Energy, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PRASCapacityCredits.jl/Project.toml b/PRASCapacityCredits.jl/Project.toml new file mode 100644 index 00000000..886ca700 --- /dev/null +++ b/PRASCapacityCredits.jl/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.10" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/src/CapacityCredit/CapacityCreditResult.jl b/PRASCapacityCredits.jl/src/CapacityCreditResult.jl similarity index 100% rename from src/CapacityCredit/CapacityCreditResult.jl rename to PRASCapacityCredits.jl/src/CapacityCreditResult.jl diff --git a/src/CapacityCredit/EFC.jl b/PRASCapacityCredits.jl/src/EFC.jl similarity index 98% rename from src/CapacityCredit/EFC.jl rename to PRASCapacityCredits.jl/src/EFC.jl index 2ff9f12d..f445e4cd 100644 --- a/src/CapacityCredit/EFC.jl +++ b/PRASCapacityCredits.jl/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.jl/src/ELCC.jl similarity index 98% rename from src/CapacityCredit/ELCC.jl rename to PRASCapacityCredits.jl/src/ELCC.jl index 746290e6..3b2cd0c6 100644 --- a/src/CapacityCredit/ELCC.jl +++ b/PRASCapacityCredits.jl/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.jl/src/PRASCapacityCredits.jl similarity index 51% rename from src/CapacityCredit/CapacityCredit.jl rename to PRASCapacityCredits.jl/src/PRASCapacityCredits.jl index 49426458..cad35694 100644 --- a/src/CapacityCredit/CapacityCredit.jl +++ b/PRASCapacityCredits.jl/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.jl/src/utils.jl similarity index 100% rename from src/CapacityCredit/utils.jl rename to PRASCapacityCredits.jl/src/utils.jl diff --git a/test/CapacityCredit/runtests.jl b/PRASCapacityCredits.jl/test/runtests.jl similarity index 70% rename from test/CapacityCredit/runtests.jl rename to PRASCapacityCredits.jl/test/runtests.jl index d3084d00..0424de93 100644 --- a/test/CapacityCredit/runtests.jl +++ b/PRASCapacityCredits.jl/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/PRASCore.jl/LICENSE.md b/PRASCore.jl/LICENSE.md new file mode 100644 index 00000000..29b607f4 --- /dev/null +++ b/PRASCore.jl/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Alliance for Sustainable Energy, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Project.toml b/PRASCore.jl/Project.toml similarity index 72% rename from Project.toml rename to PRASCore.jl/Project.toml index efe509f1..1cd455c2 100644 --- a/Project.toml +++ b/PRASCore.jl/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,16 @@ StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53" [compat] -HDF5 = "0.16,0.17" +Dates = "1" +MinCostFlows = "0.1.2" +OnlineStats = "1" +OnlineStatsBase = "1" +Printf = "1.10" +Random = "1" +Random123 = "1" +Reexport = "1" +StatsBase = "0.34" +TimeZones = "1" julia = "1.10" [extras] diff --git a/PRASCore.jl/src/PRASCore.jl b/PRASCore.jl/src/PRASCore.jl new file mode 100644 index 00000000..d7c8746e --- /dev/null +++ b/PRASCore.jl/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.jl/src/Results/Flow.jl b/PRASCore.jl/src/Results/Flow.jl new file mode 100644 index 00000000..4ea28e7d --- /dev/null +++ b/PRASCore.jl/src/Results/Flow.jl @@ -0,0 +1,111 @@ +""" + Flow + +The `Flow` result specification reports the estimated average flow across +transmission `Interfaces`, producing a `FlowResult`. + +A `FlowResult` can be indexed by a directional `Pair` of region names and a +timestamp to retrieve a tuple of sample mean and standard deviation, estimating +the average net flow magnitude and direction relative to the given directed +interface in that timestep. For a query of `"Region A" => "Region B"`, if +estimated average flow was from A to B, the reported value would be positive, +while if average flow was in the reverse direction, from B to A, the value +would be negative. + +Example: + +```julia +flows, = + assess(sys, SequentialMonteCarlo(samples=1000), Flow()) + +flow_mean, flow_std = + flows["Region A" => "Region B", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] +flow2_mean, flow2_std = + flows["Region B" => "Region A", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] +@assert flow_mean == -flow2_mean +``` + +See [`FlowSamples`](@ref) for sample-level flow results. +""" +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.jl/src/Results/FlowSamples.jl b/PRASCore.jl/src/Results/FlowSamples.jl new file mode 100644 index 00000000..ba94f5cc --- /dev/null +++ b/PRASCore.jl/src/Results/FlowSamples.jl @@ -0,0 +1,98 @@ +""" + FlowSamples + +The `FlowSamples` result specification reports the sample-level magnitude and +direction of power flows across `Interfaces`, producing a `FlowSamplesResult`. + +A `FlowSamplesResult` can be indexed by a directional `Pair` of region names and a +timestamp to retrieve a vector of sample-level net flow magnitudes and +directions relative to the given directed interface in that timestep. For a +query of `"Region A" => "Region B"`, if flow in one sample was from A to B, the +reported value would be positive, while if flow was in the reverse direction, +from B to A, the value would be negative. + +Example: + +```julia +flows, = + assess(sys, SequentialMonteCarlo(samples=10), FlowSamples()) + +samples = flows["Region A" => "Region B", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Float64} +@assert length(samples) == 10 + +samples2 = flows["Region B" => "Region A", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples == -samples2 +``` + +Note that this result specification requires large amounts of memory for +larger sample sizes. See [`Flow`](@ref) for estimated average flow results +when sample-level granularity isn't required. +""" +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.jl/src/Results/GeneratorAvailability.jl b/PRASCore.jl/src/Results/GeneratorAvailability.jl new file mode 100644 index 00000000..1728a7fd --- /dev/null +++ b/PRASCore.jl/src/Results/GeneratorAvailability.jl @@ -0,0 +1,81 @@ +""" + GeneratorAvailability + +The `GeneratorAvailability` result specification reports the sample-level +discrete availability of `Generators`, producing a `GeneratorAvailabilityResult`. + +A `GeneratorAvailabilityResult` can be indexed by generator name and +timestamp to retrieve a vector of sample-level availability states for +the unit in the given timestep. States are provided as a boolean with +`true` indicating that the unit is available and `false` indicating that +it's unavailable. + +Example: + +```julia +genavail, = + assess(sys, SequentialMonteCarlo(samples=10), GeneratorAvailability()) + +samples = genavail["MyGenerator123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Bool} +@assert length(samples) == 10 +``` +""" +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.jl/src/Results/GeneratorStorageAvailability.jl b/PRASCore.jl/src/Results/GeneratorStorageAvailability.jl new file mode 100644 index 00000000..ba040873 --- /dev/null +++ b/PRASCore.jl/src/Results/GeneratorStorageAvailability.jl @@ -0,0 +1,81 @@ +""" + GeneratorStorageAvailability + +The `GeneratorStorageAvailability` result specification reports the sample-level +discrete availability of `GeneratorStorages`, producing a +`GeneratorStorageAvailabilityResult`. + +A `GeneratorStorageAvailabilityResult` can be indexed by generator-storage +name and timestamp to retrieve a vector of sample-level availability states for +the unit in the given timestep. States are provided as a boolean with +`true` indicating that the unit is available and `false` indicating that +it's unavailable. + +Example: + +```julia +genstoravail, = + assess(sys, SequentialMonteCarlo(samples=10), GeneratorStorageAvailability()) + +samples = genstoravail["MyGenerator123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Bool} +@assert length(samples) == 10 +``` +""" +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.jl/src/Results/GeneratorStorageEnergy.jl b/PRASCore.jl/src/Results/GeneratorStorageEnergy.jl new file mode 100644 index 00000000..c943ec02 --- /dev/null +++ b/PRASCore.jl/src/Results/GeneratorStorageEnergy.jl @@ -0,0 +1,103 @@ +""" + GeneratorStorageEnergy + +The `GeneratorStorageEnergy` result specification reports the average state of +charge of `GeneratorStorages`, producing a `GeneratorStorageEnergyResult`. + +A `GeneratorStorageEnergyResult` can be indexed by generator-storage device +name and a timestamp to retrieve a tuple of sample mean and standard deviation, +estimating the average energy level for the given generator-storage device in +that timestep. + +Example: + +```julia +genstorenergy, = + assess(sys, SequentialMonteCarlo(samples=1000), GeneratorStorageEnergy()) + +soc_mean, soc_std = + genstorenergy["MyGeneratorStorage123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] +``` +See [`GeneratorStorageEnergySamples`](@ref) for sample-level generator-storage +states of charge. + +See [`StorageEnergy`](@ref) for average storage states of charge. +""" +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.jl/src/Results/GeneratorStorageEnergySamples.jl b/PRASCore.jl/src/Results/GeneratorStorageEnergySamples.jl new file mode 100644 index 00000000..0273c12e --- /dev/null +++ b/PRASCore.jl/src/Results/GeneratorStorageEnergySamples.jl @@ -0,0 +1,89 @@ +""" + GeneratorStorageEnergySamples + +The `GeneratorStorageEnergySamples` result specification reports the +sample-level state of charge of `GeneratorStorages`, producing a +`GeneratorStorageEnergySamplesResult`. + +A `GeneratorStorageEnergySamplesResult` can be indexed by generator-storage +device name and a timestamp to retrieve a vector of sample-level charge states +for the device in the given timestep. + +Example: + +```julia +genstorenergy, = + assess(sys, SequentialMonteCarlo(samples=10), GeneratorStorageEnergySamples()) + +samples = genstorenergy["MyGeneratorStorage123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Float64} +@assert length(samples) == 10 +``` + +Note that this result specification requires large amounts of memory for +larger sample sizes. See [`GeneratorStorageEnergy`](@ref) for estimated average +generator-storage state of charge when sample-level granularity isn't required. +""" +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.jl/src/Results/LineAvailability.jl b/PRASCore.jl/src/Results/LineAvailability.jl new file mode 100644 index 00000000..f03c9ed9 --- /dev/null +++ b/PRASCore.jl/src/Results/LineAvailability.jl @@ -0,0 +1,80 @@ +""" + LineAvailability + +The `LineAvailability` result specification reports the sample-level +discrete availability of `Lines`, producing a `LineAvailabilityResult`. + +A `LineAvailabilityResult` can be indexed by line name and +timestamp to retrieve a vector of sample-level availability states for +the unit in the given timestep. States are provided as a boolean with +`true` indicating that the unit is available and `false` indicating that +it's unavailable. + +Example: + +```julia +lineavail, = + assess(sys, SequentialMonteCarlo(samples=10), LineAvailability()) + +samples = lineavail["MyLine123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Bool} +@assert length(samples) == 10 +``` +""" +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.jl/src/Results/Results.jl b/PRASCore.jl/src/Results/Results.jl new file mode 100644 index 00000000..9199dd60 --- /dev/null +++ b/PRASCore.jl/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/PRASCore.jl/src/Results/Shortfall.jl b/PRASCore.jl/src/Results/Shortfall.jl new file mode 100644 index 00000000..801644c3 --- /dev/null +++ b/PRASCore.jl/src/Results/Shortfall.jl @@ -0,0 +1,293 @@ +""" + Shortfall + +The `Shortfall` result specification reports expectation-based resource +adequacy risk metrics such as EUE and LOLE, producing a `ShortfallResult`. + +A `ShortfallResult` can be directly indexed by a region name and a timestamp to retrieve a tuple of sample mean and standard deviation, estimating + the average unserved energy in that region and timestep. However, in most +cases it's simpler to use [`EUE`](@ref) and [`LOLE`](@ref) constructors to +directly retrieve standard risk metrics. + +Example: + +```julia +shortfall, = + assess(sys, SequentialMonteCarlo(samples=1000), Shortfall()) + +period = ZonedDateTime(2020, 1, 1, 0, tz"UTC") + +# Unserved energy mean and standard deviation +sf_mean, sf_std = shortfall["Region A", period] + +# System-wide risk metrics +eue = EUE(shortfall) +lole = LOLE(shortfall) + +# Regional risk metrics +regional_eue = EUE(shortfall, "Region A") +regional_lole = LOLE(shortfall, "Region A") + +# Period-specific risk metrics +period_eue = EUE(shortfall, period) +period_lolp = LOLE(shortfall, period) + +# Region- and period-specific risk metrics +period_eue = EUE(shortfall, "Region A", period) +period_lolp = LOLE(shortfall, "Region A", period) +``` + +See [`ShortfallSamples`](@ref) for recording sample-level shortfall results. +""" +struct Shortfall <: ResultSpec end + +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} + + # 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 accumulator( + sys::SystemModel{N}, nsamples::Int, ::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 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 + +function merge!( + x::ShortfallAccumulator, y::ShortfallAccumulator +) + + 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(::Shortfall) = ShortfallAccumulator + +struct ShortfallResult{N, L, T <: Period, E <: EnergyUnit} <: + AbstractShortfallResult{N, L, T} + nsamples::Union{Int, Nothing} + regions::Vector{String} + timestamps::StepRange{ZonedDateTime,T} + + eventperiod_mean::Float64 + eventperiod_std::Float64 + + eventperiod_region_mean::Vector{Float64} + eventperiod_region_std::Vector{Float64} + + eventperiod_period_mean::Vector{Float64} + eventperiod_period_std::Vector{Float64} + + eventperiod_regionperiod_mean::Matrix{Float64} + eventperiod_regionperiod_std::Matrix{Float64} + + + shortfall_mean::Matrix{Float64} # r x t + + shortfall_std::Float64 + shortfall_region_std::Vector{Float64} + shortfall_period_std::Vector{Float64} + shortfall_regionperiod_std::Matrix{Float64} + + function ShortfallResult{N,L,T,E}( + nsamples::Union{Int,Nothing}, + regions::Vector{String}, + timestamps::StepRange{ZonedDateTime,T}, + eventperiod_mean::Float64, + eventperiod_std::Float64, + eventperiod_region_mean::Vector{Float64}, + eventperiod_region_std::Vector{Float64}, + eventperiod_period_mean::Vector{Float64}, + eventperiod_period_std::Vector{Float64}, + eventperiod_regionperiod_mean::Matrix{Float64}, + eventperiod_regionperiod_std::Matrix{Float64}, + shortfall_mean::Matrix{Float64}, + shortfall_std::Float64, + shortfall_region_std::Vector{Float64}, + shortfall_period_std::Vector{Float64}, + shortfall_regionperiod_std::Matrix{Float64} + ) where {N,L,T<:Period,E<:EnergyUnit} + + isnothing(nsamples) || nsamples > 0 || + throw(DomainError("Sample count must be positive or `nothing`.")) + + + length(timestamps) == N || + error("The provided timestamp range does not match the simulation length") + + nregions = length(regions) + + length(eventperiod_region_mean) == nregions && + length(eventperiod_region_std) == nregions && + length(eventperiod_period_mean) == N && + length(eventperiod_period_std) == N && + size(eventperiod_regionperiod_mean) == (nregions, N) && + size(eventperiod_regionperiod_std) == (nregions, N) && + length(shortfall_region_std) == nregions && + length(shortfall_period_std) == N && + size(shortfall_regionperiod_std) == (nregions, N) || + error("Inconsistent input data sizes") + + new{N,L,T,E}(nsamples, regions, timestamps, + eventperiod_mean, eventperiod_std, + eventperiod_region_mean, eventperiod_region_std, + eventperiod_period_mean, eventperiod_period_std, + eventperiod_regionperiod_mean, eventperiod_regionperiod_std, + shortfall_mean, shortfall_std, + shortfall_region_std, shortfall_period_std, + shortfall_regionperiod_std) + + end + +end + +function getindex(x::ShortfallResult) + return sum(x.shortfall_mean), x.shortfall_std +end + +function getindex(x::ShortfallResult, r::AbstractString) + i_r = findfirstunique(x.regions, r) + return sum(view(x.shortfall_mean, i_r, :)), x.shortfall_region_std[i_r] +end + +function getindex(x::ShortfallResult, t::ZonedDateTime) + i_t = findfirstunique(x.timestamps, t) + return sum(view(x.shortfall_mean, :, i_t)), x.shortfall_period_std[i_t] +end + +function getindex(x::ShortfallResult, r::AbstractString, t::ZonedDateTime) + i_r = findfirstunique(x.regions, r) + i_t = findfirstunique(x.timestamps, t) + return x.shortfall_mean[i_r, i_t], x.shortfall_regionperiod_std[i_r, i_t] +end + + +LOLE(x::ShortfallResult{N,L,T}) where {N,L,T} = + LOLE{N,L,T}(MeanEstimate(x.eventperiod_mean, + x.eventperiod_std, + x.nsamples)) + +function LOLE(x::ShortfallResult{N,L,T}, r::AbstractString) where {N,L,T} + i_r = findfirstunique(x.regions, r) + return LOLE{N,L,T}(MeanEstimate(x.eventperiod_region_mean[i_r], + x.eventperiod_region_std[i_r], + x.nsamples)) +end + +function LOLE(x::ShortfallResult{N,L,T}, t::ZonedDateTime) where {N,L,T} + i_t = findfirstunique(x.timestamps, t) + return LOLE{1,L,T}(MeanEstimate(x.eventperiod_period_mean[i_t], + x.eventperiod_period_std[i_t], + x.nsamples)) +end + +function LOLE(x::ShortfallResult{N,L,T}, r::AbstractString, t::ZonedDateTime) where {N,L,T} + i_r = findfirstunique(x.regions, r) + i_t = findfirstunique(x.timestamps, t) + return LOLE{1,L,T}(MeanEstimate(x.eventperiod_regionperiod_mean[i_r, i_t], + x.eventperiod_regionperiod_std[i_r, i_t], + x.nsamples)) +end + + +EUE(x::ShortfallResult{N,L,T,E}) where {N,L,T,E} = + EUE{N,L,T,E}(MeanEstimate(x[]..., x.nsamples)) + +EUE(x::ShortfallResult{N,L,T,E}, r::AbstractString) where {N,L,T,E} = + EUE{N,L,T,E}(MeanEstimate(x[r]..., x.nsamples)) + +EUE(x::ShortfallResult{N,L,T,E}, t::ZonedDateTime) where {N,L,T,E} = + EUE{1,L,T,E}(MeanEstimate(x[t]..., x.nsamples)) + +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)) + +function finalize( + acc::ShortfallAccumulator, + 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 diff --git a/PRASCore.jl/src/Results/ShortfallSamples.jl b/PRASCore.jl/src/Results/ShortfallSamples.jl new file mode 100644 index 00000000..486d5649 --- /dev/null +++ b/PRASCore.jl/src/Results/ShortfallSamples.jl @@ -0,0 +1,161 @@ +""" + ShortfallSamples + +The `ShortfallSamples` result specification reports sample-level unserved energy outcomes, producing a `ShortfallSamplesResult`. + +A `ShortfallSamplesResult` can be directly indexed by a region name and a +timestamp to retrieve a vector of sample-level unserved energy results in that +region and timestep. [`EUE`](@ref) and [`LOLE`](@ref) constructors can also +be used to retrieve standard risk metrics. + +Example: + +```julia +shortfall, = + assess(sys, SequentialMonteCarlo(samples=10), ShortfallSamples()) + +period = ZonedDateTime(2020, 1, 1, 0, tz"UTC") + +samples = shortfall["Region A", period] + +@assert samples isa Vector{Float64} +@assert length(samples) == 10 + +# System-wide risk metrics +eue = EUE(shortfall) +lole = LOLE(shortfall) + +# Regional risk metrics +regional_eue = EUE(shortfall, "Region A") +regional_lole = LOLE(shortfall, "Region A") + +# Period-specific risk metrics +period_eue = EUE(shortfall, period) +period_lolp = LOLE(shortfall, period) + +# Region- and period-specific risk metrics +period_eue = EUE(shortfall, "Region A", period) +period_lolp = LOLE(shortfall, "Region A", period) +``` + +Note that this result specification requires large amounts of memory for +larger sample sizes. See [`Shortfall`](@ref) for average shortfall outcomes when sample-level granularity isn't required. +""" +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.jl/src/Results/StorageAvailability.jl b/PRASCore.jl/src/Results/StorageAvailability.jl new file mode 100644 index 00000000..46b5aa27 --- /dev/null +++ b/PRASCore.jl/src/Results/StorageAvailability.jl @@ -0,0 +1,80 @@ +""" + StorageAvailability + +The `StorageAvailability` result specification reports the sample-level +discrete availability of `Storages`, producing a `StorageAvailabilityResult`. + +A `StorageAvailabilityResult` can be indexed by storage device name and +a timestamp to retrieve a vector of sample-level availability states for +the unit in the given timestep. States are provided as a boolean with +`true` indicating that the unit is available and `false` indicating that +it's unavailable. + +Example: + +```julia +storavail, = + assess(sys, SequentialMonteCarlo(samples=10), StorageAvailability()) + +samples = storavail["MyStorage123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Bool} +@assert length(samples) == 10 +``` +""" +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.jl/src/Results/StorageEnergy.jl b/PRASCore.jl/src/Results/StorageEnergy.jl new file mode 100644 index 00000000..206e708b --- /dev/null +++ b/PRASCore.jl/src/Results/StorageEnergy.jl @@ -0,0 +1,103 @@ +""" + StorageEnergy + +The `StorageEnergy` result specification reports the average state of charge +of `Storages`, producing a `StorageEnergyResult`. + +A `StorageEnergyResult` can be indexed by storage device name and a timestamp to +retrieve a tuple of sample mean and standard deviation, estimating the average +energy level for the given storage device in that timestep. + +Example: + +```julia +storenergy, = + assess(sys, SequentialMonteCarlo(samples=1000), StorageEnergy()) + +soc_mean, soc_std = + storenergy["MyStorage123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] +``` + +See [`StorageEnergySamples`](@ref) for sample-level storage states of charge. + +See [`GeneratorStorageEnergy`](@ref) for average generator-storage states +of charge. +""" +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.jl/src/Results/StorageEnergySamples.jl b/PRASCore.jl/src/Results/StorageEnergySamples.jl new file mode 100644 index 00000000..688f5ef6 --- /dev/null +++ b/PRASCore.jl/src/Results/StorageEnergySamples.jl @@ -0,0 +1,87 @@ +""" + StorageEnergySamples + +The `StorageEnergySamples` result specification reports the sample-level state +of charge of `Storages`, producing a `StorageEnergySamplesResult`. + +A `StorageEnergySamplesResult` can be indexed by storage device name and +a timestamp to retrieve a vector of sample-level charge states for +the device in the given timestep. + +Example: + +```julia +storenergy, = + assess(sys, SequentialMonteCarlo(samples=10), StorageEnergySamples()) + +samples = storenergy["MyStorage123", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Float64} +@assert length(samples) == 10 +``` + +Note that this result specification requires large amounts of memory for +larger sample sizes. See [`StorageEnergy`](@ref) for estimated average storage +state of charge when sample-level granularity isn't required. +""" +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.jl/src/Results/Surplus.jl b/PRASCore.jl/src/Results/Surplus.jl new file mode 100644 index 00000000..6c1e065b --- /dev/null +++ b/PRASCore.jl/src/Results/Surplus.jl @@ -0,0 +1,98 @@ +""" + Surplus + +The `Surplus` result specification reports unused generation and storage +discharge capability of `Regions`, producing a `SurplusResult`. + +A `SurplusResult` can be indexed by region name and timestamp to retrieve +a tuple of sample mean and standard deviation, estimating the average +unused capacity in that region and timestep. + +Example: + +```julia +surplus, = + assess(sys, SequentialMonteCarlo(samples=1000), Surplus()) + +surplus_mean, surplus_std = + surplus["Region A", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] +``` + +See [`SurplusSamples`](@ref) for sample-level surplus results. +""" +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.jl/src/Results/SurplusSamples.jl b/PRASCore.jl/src/Results/SurplusSamples.jl new file mode 100644 index 00000000..2d5b02f1 --- /dev/null +++ b/PRASCore.jl/src/Results/SurplusSamples.jl @@ -0,0 +1,85 @@ +""" + SurplusSamples + +The `SurplusSamples` result specification reports sample-level unused +generation and storage discharge capability of `Regions`, producing a +`SurplusSamplesResult`. + +A `SurplusSamplesResult` can be indexed by region name and timestamp to retrieve +a vector of sample-level surplus values in that region and timestep. + +Example: + +```julia +surplus, = + assess(sys, SequentialMonteCarlo(samples=10), SurplusSamples()) + +samples = surplus["Region A", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Float64} +@assert length(samples) == 10 +``` + +Note that this result specification requires large amounts of memory for +larger sample sizes. See [`Surplus`](@ref) for estimated average surplus values +when sample-level granularity isn't required. +""" +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.jl/src/Results/Utilization.jl b/PRASCore.jl/src/Results/Utilization.jl new file mode 100644 index 00000000..ec5b2a4e --- /dev/null +++ b/PRASCore.jl/src/Results/Utilization.jl @@ -0,0 +1,118 @@ +""" + Utilization + +The `Utilization` result specification reports the estimated average +absolute utilization of `Interfaces`, producing a `UtilizationResult`. + +Whereas `Flow` reports the average directional power transfer across an +interface, `Utilization` reports the absolute value of flow relative to the +interface's transfer capability (counting the effects of line outages). +For example, a symmetrically-constrained interface which is fully congested +with max power flowing in one direction in half of the samples, and the other +direction in the remaining samples, would have an average flow of 0 MW, but +an average utilization of 100%. + +A `UtilizationResult` can be indexed by a `Pair` of region names and a +timestamp to retrieve a tuple of sample mean and standard deviation, estimating +the average utilization of the interface. Given the absolute value nature of +the outcome, results are independent of direction. Querying +`"Region A" => "Region B"` will yield the same result as +`"Region B" => "Region A"`. + +Example: + +```julia +utils, = + assess(sys, SequentialMonteCarlo(samples=1000), Utilization()) + +util_mean, util_std = + utils["Region A" => "Region B", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +util2_mean, util2_std = + utils["Region B" => "Region A", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert util_mean == util2_mean +``` + +See [`UtilizationSamples`](@ref) for sample-level utilization results. +""" +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.jl/src/Results/UtilizationSamples.jl b/PRASCore.jl/src/Results/UtilizationSamples.jl new file mode 100644 index 00000000..b56410dc --- /dev/null +++ b/PRASCore.jl/src/Results/UtilizationSamples.jl @@ -0,0 +1,108 @@ +""" + UtilizationSamples + +The `UtilizationSamples` result specification reports the sample-level +absolute utilization of `Interfaces`, producing a `UtilizationSamplesResult`. + +Whereas `FlowSamples` reports the directional power transfer across an +interface, `UtilizationSamples` reports the absolute value of flow relative to the +interface's transfer capability (counting the effects of line outages). +For example, a 100 MW symmetrically-constrained interface which is fully +congested may have a flow of +100 or -100 MW, but in both cases the utilization +will be 100%. If a 50 MW line in the interface went on outage, flow may drop +to +50 or -50 MW, but utilization would remain at 100%. + +A `UtilizationSamplesResult` can be indexed by a `Pair` of region +names and a timestamp to retrieve a vector of sample-level utilizations of the +interface in that timestep. Given the absolute value nature of the outcome, +results are independent of direction. Querying +`"Region A" => "Region B"` will yield the same result as +`"Region B" => "Region A"`. + +Example: + +```julia +utils, = + assess(sys, SequentialMonteCarlo(samples=10), UtilizationSamples()) + +samples = + utils["Region A" => "Region B", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples isa Vector{Float64} +@assert length(samples) == 10 + +samples2 = + utils["Region B" => "Region A", ZonedDateTime(2020, 1, 1, 0, tz"UTC")] + +@assert samples == samples2 +``` + +Note that this result specification requires large amounts of memory for +larger sample sizes. See [`Utilization`](@ref) for sample-averaged utilization +results when sample-level granularity isn't required. +""" +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.jl/src/Results/metrics.jl similarity index 79% rename from src/ResourceAdequacy/metrics.jl rename to PRASCore.jl/src/Results/metrics.jl index f8885b17..a8e035af 100644 --- a/src/ResourceAdequacy/metrics.jl +++ b/PRASCore.jl/src/Results/metrics.jl @@ -1,3 +1,5 @@ +abstract type ReliabilityMetric end + struct MeanEstimate estimate::Float64 @@ -64,10 +66,18 @@ end Base.isapprox(x::ReliabilityMetric, y::ReliabilityMetric) = isapprox(val(x), val(y)) && isapprox(stderror(x), stderror(y)) -# Loss-of-Load Expectation +""" + LOLE -struct LOLE{N,L,T<:Period} <: ReliabilityMetric +`LOLE` reports loss of load expectation over a particular time period +and regional extent. When the reporting period is a single simulation +timestep, the metric is equivalent to loss of load probability (LOLP). +Contains both the estimated value itself as well as the standard error +of that estimate, which can be extracted with `val` and `stderror`, +respectively. +""" +struct LOLE{N, L, T <: Period} <: ReliabilityMetric lole::MeanEstimate function LOLE{N,L,T}(lole::MeanEstimate) where {N,L,T<:Period} @@ -90,8 +100,16 @@ function Base.show(io::IO, x::LOLE{N,L,T}) where {N,L,T} end -# Expected Unserved Energy +""" + EUE + +`EUE` reports expected unserved energy over a particular time period and +regional extent. +Contains both the estimated value itself as well as the standard error +of that estimate, which can be extracted with `val` and `stderror`, +respectively. +""" struct EUE{N,L,T<:Period,E<:EnergyUnit} <: ReliabilityMetric eue::MeanEstimate diff --git a/src/ResourceAdequacy/utils.jl b/PRASCore.jl/src/Results/utils.jl similarity index 66% rename from src/ResourceAdequacy/utils.jl rename to PRASCore.jl/src/Results/utils.jl index 4fc10a07..41782995 100644 --- a/src/ResourceAdequacy/utils.jl +++ b/PRASCore.jl/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.jl/src/Simulations/DispatchProblem.jl similarity index 98% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/DispatchProblem.jl rename to PRASCore.jl/src/Simulations/DispatchProblem.jl index a261a84f..28240c14 100644 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/DispatchProblem.jl +++ b/PRASCore.jl/src/Simulations/DispatchProblem.jl @@ -1,5 +1,4 @@ """ - DispatchProblem(sys::SystemModel) Create a min-cost flow problem for the multi-region max power delivery problem @@ -68,7 +67,6 @@ Edges are ordered as: 14. GenerationStorage charge from inflow (GeneratorStorage order) 15. GenerationStorage charge unused (GeneratorStorage order) 16. GenerationStorage inflow unused (GeneratorStorage order) - """ struct DispatchProblem @@ -420,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.jl/src/Simulations/Simulations.jl similarity index 68% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/SequentialMonteCarlo.jl rename to PRASCore.jl/src/Simulations/Simulations.jl index 95d5853e..0178c75e 100644 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/SequentialMonteCarlo.jl +++ b/PRASCore.jl/src/Simulations/Simulations.jl @@ -1,8 +1,52 @@ +@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") -struct SequentialMonteCarlo <: SimulationSpec +""" + SequentialMonteCarlo(; + samples::Int=10_000, + seed::Integer=rand(UInt64), + verbose::Bool=false, + threaded::Bool=true + ) + +Sequential Monte Carlo simulation parameters for PRAS analysis + +It it recommended that you fix the random seed for reproducibility. + +# Arguments + + - `samples::Int=10_000`: Number of samples + - `seed::Integer=rand(UInt64)`: Random seed + - `verbose::Bool=false`: Print progress + - `threaded::Bool=true`: Use multi-threading + +# Returns + + - `SequentialMonteCarlo`: PRAS simulation specification +""" +struct SequentialMonteCarlo nsamples::Int seed::UInt64 @@ -17,9 +61,26 @@ struct SequentialMonteCarlo <: SimulationSpec seed < 0 && throw(DomainError("Random seed must be non-negative")) new(samples, UInt64(seed), verbose, threaded) end - end +broadcastable(x::SequentialMonteCarlo) = Ref(x) + +""" + assess(system::SystemModel, method::SequentialMonteCarlo, resultspecs::ResultSpec...) + +Run a Sequential Monte Carlo simulation on a `system` using the `method` data +and return `resultspecs`. + +# Arguments + + - `system::SystemModel`: PRAS data structure + - `method::SequentialMonteCarlo`: method for PRAS analysis + - `resultspecs::ResultSpec...`: PRAS metric for metrics like [`Shortfall`](@ref) missing generation + +# Returns + + - `results::Tuple{Vararg{ResultAccumulator{SequentialMonteCarlo}}}`: PRAS metric results +""" function assess( system::SystemModel, method::SequentialMonteCarlo, @@ -28,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) @@ -62,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 @@ -162,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.jl/src/Simulations/SystemState.jl similarity index 100% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/SystemState.jl rename to PRASCore.jl/src/Simulations/SystemState.jl diff --git a/PRASCore.jl/src/Simulations/recording.jl b/PRASCore.jl/src/Simulations/recording.jl new file mode 100644 index 00000000..613dda86 --- /dev/null +++ b/PRASCore.jl/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.jl/src/Simulations/utils.jl similarity index 91% rename from src/ResourceAdequacy/simulations/sequentialmontecarlo/utils.jl rename to PRASCore.jl/src/Simulations/utils.jl index 8bf81bd7..533c9315 100644 --- a/src/ResourceAdequacy/simulations/sequentialmontecarlo/utils.jl +++ b/PRASCore.jl/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.jl/src/Systems/SystemModel.jl similarity index 93% rename from src/PRASBase/SystemModel.jl rename to PRASCore.jl/src/Systems/SystemModel.jl index 119c4657..e1465d30 100644 --- a/src/PRASBase/SystemModel.jl +++ b/PRASCore.jl/src/Systems/SystemModel.jl @@ -1,7 +1,12 @@ -struct SystemModel{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} +""" + SystemModel - regions::Regions{N,P} - interfaces::Interfaces{N,P} +A `SystemModel` contains a representation of a power system to be studied +with PRAS. +""" +struct SystemModel{N, L, T <: Period, P <: PowerUnit, E <: EnergyUnit} + regions::Regions{N, P} + interfaces::Interfaces{N, P} generators::Generators{N,L,T,P} region_gen_idxs::Vector{UnitRange{Int}} @@ -71,7 +76,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 @@ -130,6 +135,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 @@ -144,13 +152,3 @@ function consistent_idxs(idxss::Vector{UnitRange{Int}}, nitems::Int, ngroups::In return true end - -function rts_gmlc() - path = dirname(@__FILE__) - return SystemModel(path * "/rts.pras") -end - -function toymodel() - path = dirname(@__FILE__) - return SystemModel(path * "/toymodel.pras") -end \ No newline at end of file diff --git a/src/PRASBase/PRASBase.jl b/PRASCore.jl/src/Systems/Systems.jl similarity index 55% rename from src/PRASBase/PRASBase.jl rename to PRASCore.jl/src/Systems/Systems.jl index 75e9f971..b5ccc6e6 100644 --- a/src/PRASBase/PRASBase.jl +++ b/PRASCore.jl/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.jl/src/Systems/TestData.jl similarity index 90% rename from test/testsystems.jl rename to PRASCore.jl/src/Systems/TestData.jl index 861da1cf..bf9d004c 100644 --- a/test/testsystems.jl +++ b/PRASCore.jl/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.jl/src/Systems/assets.jl similarity index 100% rename from src/PRASBase/assets.jl rename to PRASCore.jl/src/Systems/assets.jl diff --git a/src/PRASBase/collections.jl b/PRASCore.jl/src/Systems/collections.jl similarity index 100% rename from src/PRASBase/collections.jl rename to PRASCore.jl/src/Systems/collections.jl diff --git a/src/PRASBase/units.jl b/PRASCore.jl/src/Systems/units.jl similarity index 100% rename from src/PRASBase/units.jl rename to PRASCore.jl/src/Systems/units.jl diff --git a/test/ResourceAdequacy/results/availability.jl b/PRASCore.jl/test/Results/availability.jl similarity index 85% rename from test/ResourceAdequacy/results/availability.jl rename to PRASCore.jl/test/Results/availability.jl index 8d88ed63..fd06020e 100644 --- a/test/ResourceAdequacy/results/availability.jl +++ b/PRASCore.jl/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.jl/test/Results/energy.jl similarity index 90% rename from test/ResourceAdequacy/results/energy.jl rename to PRASCore.jl/test/Results/energy.jl index f5e5a28e..15f620f1 100644 --- a/test/ResourceAdequacy/results/energy.jl +++ b/PRASCore.jl/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.jl/test/Results/flow.jl similarity index 92% rename from test/ResourceAdequacy/results/flow.jl rename to PRASCore.jl/test/Results/flow.jl index fb1b7ab6..09158d8e 100644 --- a/test/ResourceAdequacy/results/flow.jl +++ b/PRASCore.jl/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.jl/test/Results/metrics.jl similarity index 100% rename from test/ResourceAdequacy/metrics.jl rename to PRASCore.jl/test/Results/metrics.jl diff --git a/test/ResourceAdequacy/results/results.jl b/PRASCore.jl/test/Results/runtests.jl similarity index 88% rename from test/ResourceAdequacy/results/results.jl rename to PRASCore.jl/test/Results/runtests.jl index 303ddd89..efec7599 100644 --- a/test/ResourceAdequacy/results/results.jl +++ b/PRASCore.jl/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.jl/test/Results/shortfall.jl similarity index 97% rename from test/ResourceAdequacy/results/shortfall.jl rename to PRASCore.jl/test/Results/shortfall.jl index f1bdf1ce..900903a7 100644 --- a/test/ResourceAdequacy/results/shortfall.jl +++ b/PRASCore.jl/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.jl/test/Results/surplus.jl similarity index 92% rename from test/ResourceAdequacy/results/surplus.jl rename to PRASCore.jl/test/Results/surplus.jl index c00bd314..685bd0a9 100644 --- a/test/ResourceAdequacy/results/surplus.jl +++ b/PRASCore.jl/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.jl/test/Results/utilization.jl similarity index 92% rename from test/ResourceAdequacy/results/utilization.jl rename to PRASCore.jl/test/Results/utilization.jl index 2a4f6318..41678e6c 100644 --- a/test/ResourceAdequacy/results/utilization.jl +++ b/PRASCore.jl/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.jl/test/Simulations/runtests.jl similarity index 64% rename from test/ResourceAdequacy/simulation/sequentialmontecarlo.jl rename to PRASCore.jl/test/Simulations/runtests.jl index 53b52b1f..519945a1 100644 --- a/test/ResourceAdequacy/simulation/sequentialmontecarlo.jl +++ b/PRASCore.jl/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.jl/test/Systems/SystemModel.jl similarity index 93% rename from test/PRASBase/SystemModel.jl rename to PRASCore.jl/test/Systems/SystemModel.jl index 1a11697b..86e63369 100644 --- a/test/PRASBase/SystemModel.jl +++ b/PRASCore.jl/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.jl/test/Systems/assets.jl similarity index 100% rename from test/PRASBase/assets.jl rename to PRASCore.jl/test/Systems/assets.jl diff --git a/test/PRASBase/collections.jl b/PRASCore.jl/test/Systems/collections.jl similarity index 100% rename from test/PRASBase/collections.jl rename to PRASCore.jl/test/Systems/collections.jl diff --git a/test/PRASBase/runtests.jl b/PRASCore.jl/test/Systems/runtests.jl similarity index 70% rename from test/PRASBase/runtests.jl rename to PRASCore.jl/test/Systems/runtests.jl index b37b8ccd..6e10f9e8 100644 --- a/test/PRASBase/runtests.jl +++ b/PRASCore.jl/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.jl/test/Systems/units.jl similarity index 100% rename from test/PRASBase/units.jl rename to PRASCore.jl/test/Systems/units.jl diff --git a/test/dummydata.jl b/PRASCore.jl/test/dummydata.jl similarity index 100% rename from test/dummydata.jl rename to PRASCore.jl/test/dummydata.jl diff --git a/test/runtests.jl b/PRASCore.jl/test/runtests.jl similarity index 74% rename from test/runtests.jl rename to PRASCore.jl/test/runtests.jl index b602f4c7..41cb2f32 100644 --- a/test/runtests.jl +++ b/PRASCore.jl/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.jl/LICENSE.md b/PRASFiles.jl/LICENSE.md new file mode 100644 index 00000000..29b607f4 --- /dev/null +++ b/PRASFiles.jl/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Alliance for Sustainable Energy, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PRASFiles.jl/Project.toml b/PRASFiles.jl/Project.toml new file mode 100644 index 00000000..302c0f27 --- /dev/null +++ b/PRASFiles.jl/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.jl/src/PRASFiles.jl b/PRASFiles.jl/src/PRASFiles.jl new file mode 100644 index 00000000..a596fbbe --- /dev/null +++ b/PRASFiles.jl/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 + +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.jl/src/read.jl similarity index 98% rename from src/PRASBase/read.jl rename to PRASFiles.jl/src/read.jl index 1b1dd0f6..493e8c1d 100644 --- a/src/PRASBase/read.jl +++ b/PRASFiles.jl/src/read.jl @@ -3,6 +3,13 @@ SystemModel(filename::String) Load a `SystemModel` from an appropriately-formatted HDF5 file on disk. +These files typically have a .pras filename extension. + +# Examples + +```julia +sys = SystemModel("path/to/prasfile.pras") +``` """ function SystemModel(inputfile::String) @@ -11,7 +18,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/PRASFiles.jl/src/rts.pras b/PRASFiles.jl/src/rts.pras new file mode 100644 index 00000000..916b5d92 Binary files /dev/null and b/PRASFiles.jl/src/rts.pras differ diff --git a/src/PRASBase/toymodel.pras b/PRASFiles.jl/src/toymodel.pras similarity index 82% rename from src/PRASBase/toymodel.pras rename to PRASFiles.jl/src/toymodel.pras index c7d0fdef..2106e48c 100644 Binary files a/src/PRASBase/toymodel.pras and b/PRASFiles.jl/src/toymodel.pras differ diff --git a/src/PRASBase/utils.jl b/PRASFiles.jl/src/utils.jl similarity index 95% rename from src/PRASBase/utils.jl rename to PRASFiles.jl/src/utils.jl index 88cf4c5b..c7b6720b 100644 --- a/src/PRASBase/utils.jl +++ b/PRASFiles.jl/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.jl/src/write.jl similarity index 99% rename from src/PRASBase/write.jl rename to PRASFiles.jl/src/write.jl index c1ec757c..ed4d6bff 100644 --- a/src/PRASBase/write.jl +++ b/PRASFiles.jl/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.jl/test/runtests.jl b/PRASFiles.jl/test/runtests.jl new file mode 100644 index 00000000..c8eaf10e --- /dev/null +++ b/PRASFiles.jl/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 = PRASFiles.toymodel() + savemodel(toy, path * "/toymodel2.pras") + toy2 = SystemModel(path * "/toymodel2.pras") + @test toy == toy2 + + rts = PRASFiles.rts_gmlc() + savemodel(rts, path * "/rts2.pras") + rts2 = SystemModel(path * "/rts2.pras") + @test rts == rts2 + + end + + @testset "Run RTS-GMLC" begin + + assess(PRASFiles.rts_gmlc(), SequentialMonteCarlo(samples=100), Shortfall()) + + end + +end diff --git a/README.md b/README.md index 844a16d7..94826901 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Probabilistic Resource Adequacy Suite -[![Build Status](https://github.com/NREL/PRAS/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/NREL/PRAS/actions/workflows/CI.yml) +[![PRAS.jl Tests](https://github.com/NREL/PRAS/actions/workflows/PRAS.jl.yml/badge.svg?branch=main)](https://github.com/NREL/PRAS/actions/workflows/PRAS.jl.yml) +[![PRASCore.jl Tests](https://github.com/NREL/PRAS/actions/workflows/PRASCore.jl.yml/badge.svg?branch=main)](https://github.com/NREL/PRAS/actions/workflows/PRASCore.jl.yml) +[![PRASFiles.jl Tests](https://github.com/NREL/PRAS/actions/workflows/PRASFiles.jl.yml/badge.svg?branch=main)](https://github.com/NREL/PRAS/actions/workflows/PRASFiles.jl.yml) +[![PRASCapacityCredits.jl Tests](https://github.com/NREL/PRAS/actions/workflows/PRASCapacityCredits.jl.yml/badge.svg?branch=main)](https://github.com/NREL/PRAS/actions/workflows/PRASCapacityCredits.jl.yml) + [![codecov](https://codecov.io/gh/NREL/PRAS/branch/master/graph/badge.svg?token=WiP3quRaIA)](https://codecov.io/gh/NREL/PRAS) [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://nrel.github.io/PRAS) [![DOI](https://img.shields.io/badge/DOI-10.11578/dc.20190814.1-blue.svg)](https://www.osti.gov/biblio/1557438) 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/PRASBase/rts.pras b/src/PRASBase/rts.pras deleted file mode 100644 index 5a70afff..00000000 Binary files a/src/PRASBase/rts.pras and /dev/null differ 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 774513be..00000000 --- a/src/ResourceAdequacy/results/availability.jl +++ /dev/null @@ -1,96 +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)) - -# Full Generator availability data - -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 - -# Full Storage availability data - -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 - -# Full GeneratorStorage availability data - -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 - -# Full Line availability data - -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 b121c4a8..00000000 --- a/src/ResourceAdequacy/results/energy.jl +++ /dev/null @@ -1,127 +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 - -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 - -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 - -# Full Storage state-of-charge data - -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 - -# Full GeneratorStorage state-of-charge data - -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 d8c11fca..00000000 --- a/src/ResourceAdequacy/results/flow.jl +++ /dev/null @@ -1,71 +0,0 @@ -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 - -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/shortfall.jl b/src/ResourceAdequacy/results/shortfall.jl deleted file mode 100644 index 2eb89357..00000000 --- a/src/ResourceAdequacy/results/shortfall.jl +++ /dev/null @@ -1,257 +0,0 @@ -struct Shortfall <: ResultSpec end -abstract type AbstractShortfallResult{N,L,T} <: Result{N,L,T} end - -# Colon indexing - -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)) - -# Sample-averaged shortfall data - -struct ShortfallResult{N,L,T<:Period,E<:EnergyUnit} <: AbstractShortfallResult{N,L,T} - - nsamples::Union{Int,Nothing} - regions::Vector{String} - timestamps::StepRange{ZonedDateTime,T} - - eventperiod_mean::Float64 - eventperiod_std::Float64 - - eventperiod_region_mean::Vector{Float64} - eventperiod_region_std::Vector{Float64} - - eventperiod_period_mean::Vector{Float64} - eventperiod_period_std::Vector{Float64} - - eventperiod_regionperiod_mean::Matrix{Float64} - eventperiod_regionperiod_std::Matrix{Float64} - - - shortfall_mean::Matrix{Float64} # r x t - - shortfall_std::Float64 - shortfall_region_std::Vector{Float64} - shortfall_period_std::Vector{Float64} - shortfall_regionperiod_std::Matrix{Float64} - - function ShortfallResult{N,L,T,E}( - nsamples::Union{Int,Nothing}, - regions::Vector{String}, - timestamps::StepRange{ZonedDateTime,T}, - eventperiod_mean::Float64, - eventperiod_std::Float64, - eventperiod_region_mean::Vector{Float64}, - eventperiod_region_std::Vector{Float64}, - eventperiod_period_mean::Vector{Float64}, - eventperiod_period_std::Vector{Float64}, - eventperiod_regionperiod_mean::Matrix{Float64}, - eventperiod_regionperiod_std::Matrix{Float64}, - shortfall_mean::Matrix{Float64}, - shortfall_std::Float64, - shortfall_region_std::Vector{Float64}, - shortfall_period_std::Vector{Float64}, - shortfall_regionperiod_std::Matrix{Float64} - ) where {N,L,T<:Period,E<:EnergyUnit} - - isnothing(nsamples) || nsamples > 0 || - throw(DomainError("Sample count must be positive or `nothing`.")) - - - length(timestamps) == N || - error("The provided timestamp range does not match the simulation length") - - nregions = length(regions) - - length(eventperiod_region_mean) == nregions && - length(eventperiod_region_std) == nregions && - length(eventperiod_period_mean) == N && - length(eventperiod_period_std) == N && - size(eventperiod_regionperiod_mean) == (nregions, N) && - size(eventperiod_regionperiod_std) == (nregions, N) && - length(shortfall_region_std) == nregions && - length(shortfall_period_std) == N && - size(shortfall_regionperiod_std) == (nregions, N) || - error("Inconsistent input data sizes") - - new{N,L,T,E}(nsamples, regions, timestamps, - eventperiod_mean, eventperiod_std, - eventperiod_region_mean, eventperiod_region_std, - eventperiod_period_mean, eventperiod_period_std, - eventperiod_regionperiod_mean, eventperiod_regionperiod_std, - shortfall_mean, shortfall_std, - shortfall_region_std, shortfall_period_std, - shortfall_regionperiod_std) - - end - -end - -function getindex(x::ShortfallResult) - return sum(x.shortfall_mean), x.shortfall_std -end - -function getindex(x::ShortfallResult, r::AbstractString) - i_r = findfirstunique(x.regions, r) - return sum(view(x.shortfall_mean, i_r, :)), x.shortfall_region_std[i_r] -end - -function getindex(x::ShortfallResult, t::ZonedDateTime) - i_t = findfirstunique(x.timestamps, t) - return sum(view(x.shortfall_mean, :, i_t)), x.shortfall_period_std[i_t] -end - -function getindex(x::ShortfallResult, r::AbstractString, t::ZonedDateTime) - i_r = findfirstunique(x.regions, r) - i_t = findfirstunique(x.timestamps, t) - return x.shortfall_mean[i_r, i_t], x.shortfall_regionperiod_std[i_r, i_t] -end - - -LOLE(x::ShortfallResult{N,L,T}) where {N,L,T} = - LOLE{N,L,T}(MeanEstimate(x.eventperiod_mean, - x.eventperiod_std, - x.nsamples)) - -function LOLE(x::ShortfallResult{N,L,T}, r::AbstractString) where {N,L,T} - i_r = findfirstunique(x.regions, r) - return LOLE{N,L,T}(MeanEstimate(x.eventperiod_region_mean[i_r], - x.eventperiod_region_std[i_r], - x.nsamples)) -end - -function LOLE(x::ShortfallResult{N,L,T}, t::ZonedDateTime) where {N,L,T} - i_t = findfirstunique(x.timestamps, t) - return LOLE{1,L,T}(MeanEstimate(x.eventperiod_period_mean[i_t], - x.eventperiod_period_std[i_t], - x.nsamples)) -end - -function LOLE(x::ShortfallResult{N,L,T}, r::AbstractString, t::ZonedDateTime) where {N,L,T} - i_r = findfirstunique(x.regions, r) - i_t = findfirstunique(x.timestamps, t) - return LOLE{1,L,T}(MeanEstimate(x.eventperiod_regionperiod_mean[i_r, i_t], - x.eventperiod_regionperiod_std[i_r, i_t], - x.nsamples)) -end - - -EUE(x::ShortfallResult{N,L,T,E}) where {N,L,T,E} = - EUE{N,L,T,E}(MeanEstimate(x[]..., x.nsamples)) - -EUE(x::ShortfallResult{N,L,T,E}, r::AbstractString) where {N,L,T,E} = - EUE{N,L,T,E}(MeanEstimate(x[r]..., x.nsamples)) - -EUE(x::ShortfallResult{N,L,T,E}, t::ZonedDateTime) where {N,L,T,E} = - EUE{1,L,T,E}(MeanEstimate(x[t]..., x.nsamples)) - -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)) - -# Full shortfall data - -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 -) 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])) diff --git a/src/ResourceAdequacy/results/surplus.jl b/src/ResourceAdequacy/results/surplus.jl deleted file mode 100644 index a3ab03ca..00000000 --- a/src/ResourceAdequacy/results/surplus.jl +++ /dev/null @@ -1,66 +0,0 @@ -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 - -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 e17e007a..00000000 --- a/src/ResourceAdequacy/results/utilization.jl +++ /dev/null @@ -1,69 +0,0 @@ -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 - -# Full utilization data - -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