From 90b4039737924d23c4437e7e78010864070c7c8f Mon Sep 17 00:00:00 2001 From: juflorez Date: Sun, 26 Oct 2025 11:55:31 -0600 Subject: [PATCH 01/16] add initial level setting func --- PRASCore.jl/src/Simulations/Simulations.jl | 20 +++- PRASCore.jl/src/Systems/assets.jl | 130 +++++++++++++-------- 2 files changed, 100 insertions(+), 50 deletions(-) diff --git a/PRASCore.jl/src/Simulations/Simulations.jl b/PRASCore.jl/src/Simulations/Simulations.jl index 99b70e96..0d892d0a 100644 --- a/PRASCore.jl/src/Simulations/Simulations.jl +++ b/PRASCore.jl/src/Simulations/Simulations.jl @@ -182,10 +182,24 @@ function initialize!( initialize_availability!( rng, state.lines_available, state.lines_nexttransition, system.lines, N) + if size(system.storages.energy_capacity, 1) > 0 + state.stors_energy .= (system.storages.initial_soc .* system.storages.energy_capacity[1,:]) + else + fill!(state.stors_energy, 0.0) + end + + if size(system.generatorstorages.energy_capacity, 1) > 0 + state.genstors_energy .= (system.generatorstorages.initial_soc .* system.generatorstorages.energy_capacity[1,:]) + else + fill!(state.genstors_energy, 0.0) + end + + if size(system.demandresponses.energy_capacity, 1) > 0 + state.drs_energy .= (system.demandresponses.initial_borrowed_load .* system.demandresponses.energy_capacity[1,:]) + else + fill!(state.drs_energy, 0.0) + end - fill!(state.stors_energy, 0) - fill!(state.genstors_energy, 0) - fill!(state.drs_energy, 0) fill!(state.drs_unservedenergy, 0) fill!(state.drs_paybackcounter, -1) return diff --git a/PRASCore.jl/src/Systems/assets.jl b/PRASCore.jl/src/Systems/assets.jl index a794a473..0a2dfe6a 100644 --- a/PRASCore.jl/src/Systems/assets.jl +++ b/PRASCore.jl/src/Systems/assets.jl @@ -178,6 +178,8 @@ A struct representing storage devices in the system. - `μ` (repair probability): Probability the unit transitions from forced outage to operational during a given simulation timestep, for each storage unit in each timeperiod. Unitless. + - 'initial_soc': Optional keyword for initial state of charge as a ratio [0,1.0] of `energy_capacity` at first timestep for + each storage unit at the beginning of the simulation. Default is zero. """ struct Storages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L,T,P} @@ -195,12 +197,15 @@ struct Storages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L, λ::Matrix{Float64} μ::Matrix{Float64} + initial_soc::Vector{Float64} # energy + function Storages{N,L,T,P,E}( names::Vector{<:AbstractString}, categories::Vector{<:AbstractString}, chargecapacity::Matrix{Int}, dischargecapacity::Matrix{Int}, energycapacity::Matrix{Int}, chargeefficiency::Matrix{Float64}, dischargeefficiency::Matrix{Float64}, carryoverefficiency::Matrix{Float64}, - λ::Matrix{Float64}, μ::Matrix{Float64} + λ::Matrix{Float64}, μ::Matrix{Float64}; + initial_soc::Vector{Float64} = zeros(Float64, length(names)) ) where {N,L,T,P,E} n_stors = length(names) @@ -226,10 +231,14 @@ struct Storages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L, @assert all(isfractional, λ) @assert all(isfractional, μ) + @assert length(initial_soc) == n_stors + @assert all(isfractional, initial_soc) + + #order of this is tied to how struct order is defined new{N,L,T,P,E}(string.(names), string.(categories), chargecapacity, dischargecapacity, energycapacity, chargeefficiency, dischargeefficiency, carryoverefficiency, - λ, μ) + λ, μ, initial_soc) end @@ -242,7 +251,8 @@ function Storages{N,L,T,P,E}() where {N,L,T,P,E} String[], String[], zeros(Int, 0, N), zeros(Int, 0, N), zeros(Int, 0, N), zeros(Float64, 0, N), zeros(Float64, 0, N), zeros(Float64, 0, N), - zeros(Float64, 0, N), zeros(Float64, 0, N)) + zeros(Float64, 0, N), zeros(Float64, 0, N); + initial_soc = zeros(Float64, 0)) end Base.:(==)(x::T, y::T) where {T <: Storages} = @@ -255,13 +265,15 @@ Base.:(==)(x::T, y::T) where {T <: Storages} = x.discharge_efficiency == y.discharge_efficiency && x.carryover_efficiency == y.carryover_efficiency && x.λ == y.λ && - x.μ == y.μ + x.μ == y.μ && + x.initial_soc == y.initial_soc Base.getindex(s::S, idxs::AbstractVector{Int}) where {S <: Storages} = S(s.names[idxs], s.categories[idxs],s.charge_capacity[idxs,:], s.discharge_capacity[idxs, :],s.energy_capacity[idxs, :], s.charge_efficiency[idxs, :], s.discharge_efficiency[idxs, :], - s.carryover_efficiency[idxs, :],s.λ[idxs, :], s.μ[idxs, :]) + s.carryover_efficiency[idxs, :],s.λ[idxs, :], s.μ[idxs, :]; + initial_soc = s.initial_soc[idxs]) function Base.vcat(stors::Storages{N,L,T,P,E}...) where {N, L, T, P, E} @@ -281,6 +293,8 @@ function Base.vcat(stors::Storages{N,L,T,P,E}...) where {N, L, T, P, E} λ = Matrix{Float64}(undef, n_stors, N) μ = Matrix{Float64}(undef, n_stors, N) + initial_soc = Vector{Float64}(undef, n_stors) + last_idx = 0 for s in stors @@ -302,12 +316,15 @@ function Base.vcat(stors::Storages{N,L,T,P,E}...) where {N, L, T, P, E} λ[rows, :] = s.λ μ[rows, :] = s.μ + initial_soc[rows] = s.initial_soc + last_idx += n end return Storages{N,L,T,P,E}(names, categories, charge_capacity, discharge_capacity, energy_capacity, charge_efficiency, discharge_efficiency, - carryover_efficiency, λ, μ) + carryover_efficiency, λ, μ; + initial_soc = initial_soc) end @@ -351,6 +368,8 @@ A struct representing generator-storage hybrid devices within a power system. - `μ` (repair probability): Probability the unit transitions from forced outage to operational during a given simulation timestep, for each generator-storage unit in each timeperiod. Unitless. + - 'initial_soc': Optional keywrod arg initial state of charge as a ratio [0,1.0] of `energy_capacity` at first timestep for + each storage unit at the beginning of the simulation. Default is zero. """ struct GeneratorStorages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L,T,P} @@ -372,6 +391,8 @@ struct GeneratorStorages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAs λ::Matrix{Float64} μ::Matrix{Float64} + initial_soc::Vector{Float64} # energy + function GeneratorStorages{N,L,T,P,E}( names::Vector{<:AbstractString}, categories::Vector{<:AbstractString}, charge_capacity::Matrix{Int}, discharge_capacity::Matrix{Int}, @@ -380,7 +401,8 @@ struct GeneratorStorages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAs carryover_efficiency::Matrix{Float64}, inflow::Matrix{Int}, gridwithdrawal_capacity::Matrix{Int}, gridinjection_capacity::Matrix{Int}, - λ::Matrix{Float64}, μ::Matrix{Float64} + λ::Matrix{Float64}, μ::Matrix{Float64},; + initial_soc::Vector{Float64} = zeros(Float64, length(names)), ) where {N,L,T,P,E} n_stors = length(names) @@ -416,12 +438,15 @@ struct GeneratorStorages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAs @assert all(isfractional, λ) @assert all(isfractional, μ) + @assert length(initial_soc) == n_stors + @assert all(isfractional, initial_soc) + new{N,L,T,P,E}( string.(names), string.(categories), charge_capacity, discharge_capacity, energy_capacity, charge_efficiency, discharge_efficiency, carryover_efficiency, inflow, gridwithdrawal_capacity, gridinjection_capacity, - λ, μ) + λ, μ,initial_soc) end @@ -435,7 +460,7 @@ function GeneratorStorages{N,L,T,P,E}() where {N,L,T,P,E} zeros(Int, 0, N), zeros(Int, 0, N), zeros(Int, 0, N), zeros(Float64, 0, N), zeros(Float64, 0, N), zeros(Float64, 0, N), zeros(Int, 0, N), zeros(Int, 0, N), zeros(Int, 0, N), - zeros(Float64, 0, N), zeros(Float64, 0, N)) + zeros(Float64, 0, N), zeros(Float64, 0, N); initial_soc = zeros(Float64, 0)) end @@ -452,7 +477,8 @@ Base.:(==)(x::T, y::T) where {T <: GeneratorStorages} = x.gridwithdrawal_capacity == y.gridwithdrawal_capacity && x.gridinjection_capacity == y.gridinjection_capacity && x.λ == y.λ && - x.μ == y.μ + x.μ == y.μ && + x.initial_soc == y.initial_soc Base.getindex(g_s::G, idxs::AbstractVector{Int}) where {G <: GeneratorStorages} = G(g_s.names[idxs], g_s.categories[idxs], g_s.charge_capacity[idxs,:], @@ -460,7 +486,7 @@ Base.getindex(g_s::G, idxs::AbstractVector{Int}) where {G <: GeneratorStorages} g_s.charge_efficiency[idxs, :], g_s.discharge_efficiency[idxs, :], g_s.carryover_efficiency[idxs, :],g_s.inflow[idxs, :], g_s.gridwithdrawal_capacity[idxs, :],g_s.gridinjection_capacity[idxs, :], - g_s.λ[idxs, :], g_s.μ[idxs, :]) + g_s.λ[idxs, :], g_s.μ[idxs, :]; initial_soc = g_s.initial_soc[idxs]) function Base.vcat(gen_stors::GeneratorStorages{N,L,T,P,E}...) where {N, L, T, P, E} @@ -484,6 +510,8 @@ function Base.vcat(gen_stors::GeneratorStorages{N,L,T,P,E}...) where {N, L, T, P λ = Matrix{Float64}(undef, n_gen_stors, N) μ = Matrix{Float64}(undef, n_gen_stors, N) + initial_soc = Vector{Float64}(undef, n_gen_stors) + last_idx = 0 for g_s in gen_stors @@ -509,12 +537,14 @@ function Base.vcat(gen_stors::GeneratorStorages{N,L,T,P,E}...) where {N, L, T, P λ[rows, :] = g_s.λ μ[rows, :] = g_s.μ + initial_soc[rows] = g_s.initial_soc + last_idx += n end return GeneratorStorages{N,L,T,P,E}(names, categories, charge_capacity, discharge_capacity, energy_capacity, charge_efficiency, discharge_efficiency, - carryover_efficiency,inflow, gridwithdrawal_capacity, gridinjection_capacity, λ, μ) + carryover_efficiency,inflow, gridwithdrawal_capacity, gridinjection_capacity, λ, μ; initial_soc = initial_soc) end @@ -551,6 +581,8 @@ A struct representing demand response devices in the system. - `μ` (repair probability): Probability the unit transitions from forced outage to operational during a given simulation timestep, for each storage unit in each timeperiod. Unitless. + - 'initial_borrowed_load': Optional initial state of borrowed load as a ratio [0,1.0] of `energy_capacity` at first timestep for + each demand response unit at the beginning of the simulation. Default is zero. """ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L,T,P} @@ -568,6 +600,8 @@ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAsse λ::Matrix{Float64} μ::Matrix{Float64} + initial_borrowed_load::Vector{Float64} # energy + borrow_efficiency::Matrix{Float64} payback_efficiency::Matrix{Float64} @@ -576,8 +610,10 @@ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAsse borrowcapacity::Matrix{Int}, paybackcapacity::Matrix{Int}, energycapacity::Matrix{Int}, borrowedenergyinterest::Matrix{Float64}, allowablepaybackperiod::Matrix{Int}, - λ::Matrix{Float64}, μ::Matrix{Float64}, - borrowefficiency::Matrix{Float64},paybackefficiency::Matrix{Float64} + λ::Matrix{Float64}, μ::Matrix{Float64},; + initial_borrowed_load::Vector{Float64} = zeros(Float64, length(names)), + borrow_efficiency::Matrix{Float64} = ones(Float64, size(borrowcapacity)), + payback_efficiency::Matrix{Float64} = ones(Float64, size(paybackcapacity)) ) where {N,L,T,P,E} n_drs = length(names) @@ -592,11 +628,11 @@ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAsse @assert all(isnonnegative, paybackcapacity) @assert all(isnonnegative, energycapacity) - @assert size(borrowefficiency) == (n_drs, N) - @assert size(paybackefficiency) == (n_drs, N) + @assert size(borrow_efficiency) == (n_drs, N) + @assert size(payback_efficiency) == (n_drs, N) @assert size(borrowedenergyinterest) == (n_drs, N) - @assert all(isfractional, borrowefficiency) - @assert all(isfractional, paybackefficiency) + @assert all(isfractional, borrow_efficiency) + @assert all(isfractional, payback_efficiency) @assert all(borrowedenergyinterest .<= 1.0) @assert all(borrowedenergyinterest .>= -1.0) @@ -609,41 +645,31 @@ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAsse @assert all(isfractional, λ) @assert all(isfractional, μ) + @assert length(initial_borrowed_load) == n_drs + @assert all(isfractional, initial_borrowed_load) + new{N,L,T,P,E}(string.(names), string.(categories), borrowcapacity, paybackcapacity, energycapacity, borrowedenergyinterest, allowablepaybackperiod, - λ, μ,borrowefficiency, paybackefficiency,) + λ, μ, + initial_borrowed_load, + borrow_efficiency, + payback_efficiency) end end -# second constructor if borrow and payback efficiencies are not provided -function DemandResponses{N,L,T,P,E}( - names::Vector{<:AbstractString}, categories::Vector{<:AbstractString}, - borrowcapacity::Matrix{Int}, paybackcapacity::Matrix{Int}, - energycapacity::Matrix{Int}, borrowedenergyinterest::Matrix{Float64}, - allowablepaybackperiod::Matrix{Int}, - λ::Matrix{Float64}, μ::Matrix{Float64} -) where {N,L,T,P,E} - return DemandResponses{N,L,T,P,E}( - names, categories, - borrowcapacity, paybackcapacity, energycapacity, - borrowedenergyinterest, allowablepaybackperiod, - λ, μ, - ones(Float64, size(borrowcapacity)), ones(Float64, size(paybackcapacity)) - ) -end - - # Empty DemandResponses constructor function DemandResponses{N,L,T,P,E}() where {N,L,T,P,E} - return DemandResponses{N,L,T,P,E}( - String[], String[], - Matrix{Int}(undef, 0, N),Matrix{Int}(undef, 0, N),Matrix{Int}(undef, 0, N), - Matrix{Float64}(undef, 0, N), - Matrix{Int}(undef, 0, N),Matrix{Float64}(undef, 0, N),Matrix{Float64}(undef, 0, N), - Matrix{Float64}(undef, 0, N),Matrix{Float64}(undef, 0, N)) + return DemandResponses{N,L,T,P,E}( + String[], String[], + Matrix{Int}(undef, 0, N),Matrix{Int}(undef, 0, N),Matrix{Int}(undef, 0, N), + Matrix{Float64}(undef, 0, N), + Matrix{Int}(undef, 0, N),Matrix{Float64}(undef, 0, N),Matrix{Float64}(undef, 0, N); + initial_borrowed_load = zeros(Float64, 0), + borrow_efficiency = Matrix{Float64}(undef, 0, N), + payback_efficiency = Matrix{Float64}(undef, 0, N)) end Base.:(==)(x::T, y::T) where {T <: DemandResponses} = @@ -657,13 +683,16 @@ Base.:(==)(x::T, y::T) where {T <: DemandResponses} = x.borrowed_energy_interest == y.borrowed_energy_interest && x.allowable_payback_period == y.allowable_payback_period && x.λ == y.λ && - x.μ == y.μ + x.μ == y.μ && + x.initial_borrowed_load == y.initial_borrowed_load Base.getindex(dr::DR, idxs::AbstractVector{Int}) where {DR <: DemandResponses} = DR(dr.names[idxs], dr.categories[idxs],dr.borrow_capacity[idxs,:], dr.payback_capacity[idxs, :],dr.energy_capacity[idxs, :], - dr.borrowed_energy_interest[idxs, :],dr.allowable_payback_period[idxs, :],dr.λ[idxs, :], dr.μ[idxs, :], - dr.borrow_efficiency[idxs, :], dr.payback_efficiency[idxs, :]) + dr.borrowed_energy_interest[idxs, :],dr.allowable_payback_period[idxs, :],dr.λ[idxs, :], dr.μ[idxs, :]; + initial_borrowed_load = dr.initial_borrowed_load[idxs], + borrow_efficiency = dr.borrow_efficiency[idxs, :], + payback_efficiency = dr.payback_efficiency[idxs, :]) function Base.vcat(drs::DemandResponses{N,L,T,P,E}...) where {N, L, T, P, E} @@ -686,6 +715,8 @@ function Base.vcat(drs::DemandResponses{N,L,T,P,E}...) where {N, L, T, P, E} λ = Matrix{Float64}(undef, n_drs, N) μ = Matrix{Float64}(undef, n_drs, N) + initial_borrowed_load = Vector{Float64}(undef, n_drs) + last_idx = 0 for dr in drs @@ -709,12 +740,17 @@ function Base.vcat(drs::DemandResponses{N,L,T,P,E}...) where {N, L, T, P, E} λ[rows, :] = dr.λ μ[rows, :] = dr.μ + initial_borrowed_load[rows] = dr.initial_borrowed_load + last_idx += n end return DemandResponses{N,L,T,P,E}(names, categories, borrow_capacity, payback_capacity, energy_capacity, - borrowed_energy_interest,allowable_payback_period, λ, μ, borrow_efficiency, payback_efficiency) + borrowed_energy_interest,allowable_payback_period, λ, μ; + initial_borrowed_load = initial_borrowed_load, + borrow_efficiency = borrow_efficiency, + payback_efficiency = payback_efficiency) end From f42e42acc37521ccd8b99e1a9813daf179bf215a Mon Sep 17 00:00:00 2001 From: juflorez Date: Sun, 26 Oct 2025 12:01:34 -0600 Subject: [PATCH 02/16] update tests --- PRASCore.jl/test/Systems/SystemModel.jl | 6 +++-- PRASCore.jl/test/Systems/assets.jl | 30 ++++++++++++------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/PRASCore.jl/test/Systems/SystemModel.jl b/PRASCore.jl/test/Systems/SystemModel.jl index 0bf5f577..d3a8b80b 100644 --- a/PRASCore.jl/test/Systems/SystemModel.jl +++ b/PRASCore.jl/test/Systems/SystemModel.jl @@ -21,8 +21,10 @@ ["S1", "S2"], ["HVAC", "Industrial"], rand(1:10, 2, 10), rand(1:10, 2, 10), rand(1:10, 2, 10), fill(0.99, 2, 10), - fill(4, 2, 10),fill(0.1, 2, 10), fill(0.5, 2, 10), - fill(0.9, 2, 10), fill(1.0, 2, 10)) + fill(4, 2, 10),fill(0.1, 2, 10), fill(0.5, 2, 10); + initial_borrowed_load = zeros(Float64, 2), + borrow_efficiency = fill(0.9, 2, 10), + payback_efficiency = fill(1.0, 2, 10)) tz = tz"UTC" timestamps = ZonedDateTime(2020, 1, 1, 0, tz):Hour(1):ZonedDateTime(2020,1,1,9, tz) diff --git a/PRASCore.jl/test/Systems/assets.jl b/PRASCore.jl/test/Systems/assets.jl index 33b5df29..e788ffa6 100644 --- a/PRASCore.jl/test/Systems/assets.jl +++ b/PRASCore.jl/test/Systems/assets.jl @@ -5,7 +5,7 @@ vals_int = rand(1:100, 3, 10) vals_float = rand(3, 10) - + vals_float_soc = rand(0.0:0.1:1.0, length(names)) @testset "Generators" begin gens = Generators{10,1,Hour,MW}( @@ -46,24 +46,24 @@ stors = Storages{10,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, - vals_float, vals_float, vals_float, vals_float, vals_float) + vals_float, vals_float, vals_float, vals_float, vals_float;initial_soc = vals_float_soc) @test stors isa Storages @test_throws AssertionError Storages{5,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, - vals_float, vals_float, vals_float, vals_float, vals_float) + vals_float, vals_float, vals_float, vals_float, vals_float;initial_soc = vals_float_soc) @test_throws AssertionError Storages{10,1,Hour,MW,MWh}( names, categories[1:2], vals_int, vals_int, vals_int, - vals_float, vals_float, vals_float, vals_float, vals_float) + vals_float, vals_float, vals_float, vals_float, vals_float;initial_soc = vals_float_soc) @test_throws AssertionError Storages{10,1,Hour,MW,MWh}( names[1:2], categories[1:2], vals_int, vals_int, vals_int, - vals_float, vals_float, vals_float, vals_float, vals_float) + vals_float, vals_float, vals_float, vals_float, vals_float;initial_soc = vals_float_soc) @test_throws AssertionError Storages{10,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, - vals_float, vals_float, -vals_float, vals_float, vals_float) + vals_float, vals_float, -vals_float, vals_float, vals_float;initial_soc = vals_float_soc) @testset "Printing" begin io = IOBuffer() @@ -89,29 +89,29 @@ gen_stors = GeneratorStorages{10,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, vals_float, vals_float, vals_float, - vals_int, vals_int, vals_int, vals_float, vals_float) + vals_int, vals_int, vals_int, vals_float, vals_float;initial_soc = vals_float_soc) @test gen_stors isa GeneratorStorages @test_throws AssertionError GeneratorStorages{5,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, vals_float, vals_float, vals_float, - vals_int, vals_int, vals_int, vals_float, vals_float) + vals_int, vals_int, vals_int, vals_float, vals_float;initial_soc = vals_float_soc) @test_throws AssertionError GeneratorStorages{10,1,Hour,MW,MWh}( names, categories[1:2], vals_int, vals_int, vals_int, vals_float, vals_float, vals_float, - vals_int, vals_int, vals_int, vals_float, vals_float) + vals_int, vals_int, vals_int, vals_float, vals_float;initial_soc = vals_float_soc) @test_throws AssertionError GeneratorStorages{10,1,Hour,MW,MWh}( names[1:2], categories[1:2], vals_int, vals_int, vals_int, vals_float, vals_float, vals_float, - vals_int, vals_int, vals_int, vals_float, vals_float) + vals_int, vals_int, vals_int, vals_float, vals_float;initial_soc = vals_float_soc) @test_throws AssertionError GeneratorStorages{10,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, vals_float, vals_float, -vals_float, - vals_int, vals_int, vals_int, vals_float, vals_float) + vals_int, vals_int, vals_int, vals_float, vals_float;initial_soc = vals_float_soc) @testset "Printing" begin io = IOBuffer() @@ -135,19 +135,19 @@ DemandResponses{10,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, - vals_float, vals_int, vals_float, vals_float, vals_float, vals_float) + vals_float, vals_int, vals_float, vals_float;initial_borrowed_load = vals_float_soc, borrow_efficiency = vals_float, payback_efficiency = vals_float) @test_throws AssertionError DemandResponses{5,1,Hour,MW,MWh}( names, categories, vals_int, vals_int, vals_int, - vals_float, vals_int, vals_float, vals_float, vals_float, vals_float,) + vals_float, vals_int, vals_float, vals_float;initial_borrowed_load = vals_float_soc, borrow_efficiency = vals_float, payback_efficiency = vals_float) @test_throws AssertionError DemandResponses{10,1,Hour,MW,MWh}( names, categories[1:2], vals_int, vals_int, vals_int, - vals_float, vals_int, vals_float, vals_float,vals_float, vals_float) + vals_float, vals_int, vals_float, vals_float;initial_borrowed_load = vals_float_soc, borrow_efficiency = vals_float, payback_efficiency = vals_float) @test_throws AssertionError DemandResponses{10,1,Hour,MW,MWh}( names[1:2], categories[1:2], vals_int, vals_int, vals_int, - vals_float, vals_int, vals_float, vals_float,vals_float, vals_float) + vals_float, vals_int, vals_float, vals_float;initial_borrowed_load = vals_float_soc, borrow_efficiency = vals_float, payback_efficiency = vals_float) end @testset "Lines" begin From 2b4ce6922317aee33c8bae04e0503afe2f87df94 Mon Sep 17 00:00:00 2001 From: juflorez Date: Sun, 26 Oct 2025 12:03:22 -0600 Subject: [PATCH 03/16] add file reading/writing v1-need to fix versioning --- PRASFiles.jl/src/Systems/read.jl | 273 ++++++++++++++++++++++++++++++ PRASFiles.jl/src/Systems/write.jl | 7 + 2 files changed, 280 insertions(+) diff --git a/PRASFiles.jl/src/Systems/read.jl b/PRASFiles.jl/src/Systems/read.jl index d49f26cc..95daa7fe 100644 --- a/PRASFiles.jl/src/Systems/read.jl +++ b/PRASFiles.jl/src/Systems/read.jl @@ -22,6 +22,8 @@ function SystemModel(inputfile::String) systemmodel_0_5(f) elseif (0,8,0) <= version < (0,9,0) systemmodel_0_8(f) + elseif (0,8,1) <= version + systemmodel_0_8_1(f) else error("PRAS file format $versionstring not supported by this version of PRASBase.") end @@ -335,6 +337,277 @@ function systemmodel_0_8(f::File) end +""" +Read a SystemModel from a PRAS file in version 0.8.1 format. Optional params required +""" +function systemmodel_0_8_1(f::File) + + metadata = attributes(f) + + start_timestamp = ZonedDateTime(read(metadata["start_timestamp"]), + dateformat"yyyy-mm-ddTHH:MM:SSz") + + N = Int(read(metadata["timestep_count"])) + L = Int(read(metadata["timestep_length"])) + T = timeunits[read(metadata["timestep_unit"])] + P = powerunits[read(metadata["power_unit"])] + E = energyunits[read(metadata["energy_unit"])] + + type_params = (N,L,T,P,E) + + timestep = T(L) + end_timestamp = start_timestamp + (N-1)*timestep + timestamps = StepRange(start_timestamp, timestep, end_timestamp) + + has_regions = haskey(f, "regions") + has_generators = haskey(f, "generators") + has_storages = haskey(f, "storages") + has_generatorstorages = haskey(f, "generatorstorages") + has_interfaces = haskey(f, "interfaces") + has_lines = haskey(f, "lines") + + has_regions || + error("Region data must be provided") + + has_generators || has_generatorstorages || + error("Generator or generator storage data (or both) must be provided") + + xor(has_interfaces, has_lines) && + error("Both (or neither) interface and line data must be provided") + + regionnames = readvector(f["regions/_core"], :name) + regions = Regions{N,P}( + regionnames, + Int.(read(f["regions/load"])) + ) + regionlookup = Dict(n=>i for (i, n) in enumerate(regionnames)) + n_regions = length(regions) + + if has_generators + + gen_core = read(f["generators/_core"]) + gen_names, gen_categories, gen_regionnames = readvector.( + Ref(gen_core), [:name, :category, :region]) + + gen_regions = getindex.(Ref(regionlookup), gen_regionnames) + region_order = sortperm(gen_regions) + + generators = Generators{N,L,T,P}( + gen_names[region_order], gen_categories[region_order], + load_matrix(f["generators/capacity"], region_order, Int), + load_matrix(f["generators/failureprobability"], region_order, Float64), + load_matrix(f["generators/repairprobability"], region_order, Float64) + ) + + region_gen_idxs = makeidxlist(gen_regions[region_order], n_regions) + + else + + generators = Generators{N,L,T,P}() + + region_gen_idxs = fill(1:0, n_regions) + + end + + if has_storages + + stor_core = read(f["storages/_core"]) + stor_names, stor_categories, stor_regionnames = readvector.( + Ref(stor_core), [:name, :category, :region]) + + stor_regions = getindex.(Ref(regionlookup), stor_regionnames) + region_order = sortperm(stor_regions) + + storages = Storages{N,L,T,P,E}( + stor_names[region_order], stor_categories[region_order], + load_matrix(f["storages/chargecapacity"], region_order, Int), + load_matrix(f["storages/dischargecapacity"], region_order, Int), + load_matrix(f["storages/energycapacity"], region_order, Int), + load_matrix(f["storages/chargeefficiency"], region_order, Float64), + load_matrix(f["storages/dischargeefficiency"], region_order, Float64), + load_matrix(f["storages/carryoverefficiency"], region_order, Float64), + load_matrix(f["storages/failureprobability"], region_order, Float64), + load_matrix(f["storages/repairprobability"], region_order, Float64); + initial_soc = load_matrix(f["storages/initialsoc"], region_order, Float64) #optional param required + ) + + region_stor_idxs = makeidxlist(stor_regions[region_order], n_regions) + + else + + storages = Storages{N,L,T,P,E}() + + region_stor_idxs = fill(1:0, n_regions) + + end + + + if has_generatorstorages + + genstor_core = read(f["generatorstorages/_core"]) + genstor_names, genstor_categories, genstor_regionnames = readvector.( + Ref(genstor_core), [:name, :category, :region]) + + genstor_regions = getindex.(Ref(regionlookup), genstor_regionnames) + region_order = sortperm(genstor_regions) + + generatorstorages = GeneratorStorages{N,L,T,P,E}( + genstor_names[region_order], genstor_categories[region_order], + load_matrix(f["generatorstorages/chargecapacity"], region_order, Int), + load_matrix(f["generatorstorages/dischargecapacity"], region_order, Int), + load_matrix(f["generatorstorages/energycapacity"], region_order, Int), + load_matrix(f["generatorstorages/chargeefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/dischargeefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/carryoverefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/inflow"], region_order, Int), + load_matrix(f["generatorstorages/gridwithdrawalcapacity"], region_order, Int), + load_matrix(f["generatorstorages/gridinjectioncapacity"], region_order, Int), + load_matrix(f["generatorstorages/failureprobability"], region_order, Float64), + load_matrix(f["generatorstorages/repairprobability"], region_order, Float64); + initial_soc = load_matrix(f["generatorstorages/initialsoc"], region_order, Float64) #optional param required + ) + + region_genstor_idxs = makeidxlist(genstor_regions[region_order], n_regions) + + else + + generatorstorages = GeneratorStorages{N,L,T,P,E}() + + region_genstor_idxs = fill(1:0, n_regions) + + end + + if haskey(f, "demandresponses") + + + dr_core = read(f["demandresponses/_core"]) + dr_names, dr_categories, dr_regionnames = readvector.( + Ref(dr_core), [:name, :category, :region]) + + dr_regions = getindex.(Ref(regionlookup), dr_regionnames) + region_order = sortperm(dr_regions) + + demandresponses = DemandResponses{N,L,T,P,E}( + dr_names[region_order], dr_categories[region_order], + load_matrix(f["demandresponses/borrowcapacity"], region_order, Int), + load_matrix(f["demandresponses/paybackcapacity"], region_order, Int), + load_matrix(f["demandresponses/energycapacity"], region_order, Int), + load_matrix(f["demandresponses/borrowedenergyinterest"], region_order, Float64), + load_matrix(f["demandresponses/allowablepaybackperiod"], region_order, Int), + load_matrix(f["demandresponses/failureprobability"], region_order, Float64), + load_matrix(f["demandresponses/repairprobability"], region_order, Float64); + initial_borrowed_load = load_matrix(f["demandresponses/initialborrowedload"], region_order, Float64), #optional param required + borrow_efficiency = load_matrix(f["demandresponses/borrowefficiency"], region_order, Float64), #optional param required + payback_efficiency = load_matrix(f["demandresponses/paybackefficiency"], region_order, Float64), #optional param required + ) + + region_dr_idxs = makeidxlist(dr_regions[region_order], n_regions) + + else + demandresponses = DemandResponses{N,L,T,P,E}() + + region_dr_idxs = fill(1:0, n_regions) + + end + + if has_interfaces + + interfaces_core = read(f["interfaces/_core"]) + from_regionnames, to_regionnames = + readvector.(Ref(interfaces_core), [:region_from, :region_to]) + forwardcapacity = Int.(read(f["interfaces/forwardcapacity"])) + backwardcapacity = Int.(read(f["interfaces/backwardcapacity"])) + + n_interfaces = length(from_regionnames) + from_regions = getindex.(Ref(regionlookup), from_regionnames) + to_regions = getindex.(Ref(regionlookup), to_regionnames) + + # Force interface definitions as smaller => larger region numbers + for i in 1:n_interfaces + from_region = from_regions[i] + to_region = to_regions[i] + if from_region > to_region + from_regions[i] = to_region + to_regions[i] = from_region + new_forwardcapacity = backwardcapacity[i, :] + backwardcapacity[i, :] .= forwardcapacity[i, :] + forwardcapacity[i, :] .= new_forwardcapacity + elseif from_region == to_region + error("Cannot have an interface to and from " * + "the same region ($(from_region))") + end + end + + interfaces = Interfaces{N,P}( + from_regions, to_regions, forwardcapacity, backwardcapacity) + + interface_lookup = Dict((r1, r2) => i for (i, (r1, r2)) + in enumerate(tuple.(from_regions, to_regions))) + + lines_core = read(f["lines/_core"]) + line_names, line_categories, line_fromregionnames, line_toregionnames = + readvector.(Ref(lines_core), [:name, :category, :region_from, :region_to]) + line_forwardcapacity = Int.(read(f["lines/forwardcapacity"])) + line_backwardcapacity = Int.(read(f["lines/backwardcapacity"])) + + n_lines = length(line_names) + line_fromregions = getindex.(Ref(regionlookup), line_fromregionnames) + line_toregions = getindex.(Ref(regionlookup), line_toregionnames) + + # Force line definitions as smaller => larger region numbers + for i in 1:n_lines + from_region = line_fromregions[i] + to_region = line_toregions[i] + if from_region > to_region + line_fromregions[i] = to_region + line_toregions[i] = from_region + new_forwardcapacity = line_backwardcapacity[i, :] + line_backwardcapacity[i, :] .= line_forwardcapacity[i, :] + line_forwardcapacity[i, :] = new_forwardcapacity + elseif from_region == to_region + error("Cannot have a line ($(line_names[i])) to and from " * + "the same region ($(from_region))") + end + end + + line_interfaces = getindex.(Ref(interface_lookup), + tuple.(line_fromregions, line_toregions)) + interface_order = sortperm(line_interfaces) + + lines = Lines{N,L,T,P}( + line_names[interface_order], line_categories[interface_order], + line_forwardcapacity[interface_order, :], + line_backwardcapacity[interface_order, :], + load_matrix(f["lines/failureprobability"], interface_order, Float64), + load_matrix(f["lines/repairprobability"], interface_order, Float64) + ) + + interface_line_idxs = makeidxlist(line_interfaces[interface_order], n_interfaces) + + else + + interfaces = Interfaces{N,P}() + + lines = Lines{N,L,T,P}() + + interface_line_idxs = UnitRange{Int}[] + + end + + attrs = read_attrs(f) + + return SystemModel( + regions, interfaces, + generators, region_gen_idxs, + storages, region_stor_idxs, + generatorstorages, region_genstor_idxs, + demandresponses, region_dr_idxs, + lines, interface_line_idxs, + timestamps, attrs) + +end + + """ Attempts to parse the file's "vX.Y.Z" version label into (x::Int, y::Int, z::Int). Errors if the label cannot be found or parsed as expected. diff --git a/PRASFiles.jl/src/Systems/write.jl b/PRASFiles.jl/src/Systems/write.jl index 1b3cb8c0..80aa84bf 100644 --- a/PRASFiles.jl/src/Systems/write.jl +++ b/PRASFiles.jl/src/Systems/write.jl @@ -164,6 +164,9 @@ function process_storages!( storages["repairprobability", deflate = compression] = sys.storages.μ + storages["initialsoc", deflate = compression] = + sys.storages.initial_soc + return end @@ -217,6 +220,8 @@ function process_generatorstorages!( generatorstorages["repairprobability", deflate = compression] = sys.generatorstorages.μ + generatorstorages["initialsoc", deflate = compression] = + sys.generatorstorages.initial_soc return end @@ -261,6 +266,8 @@ function process_demandresponses!( demandresponses["repairprobability", deflate = compression] = sys.demandresponses.μ + generatorstorages["initialborrowedload", deflate = compression] = + sys.generatorstorages.initial_borrowed_load return end From 2ec905b88a891ae1648b66895aa01984748c59fe Mon Sep 17 00:00:00 2001 From: juflorez Date: Thu, 20 Nov 2025 15:46:24 -0700 Subject: [PATCH 04/16] add versioning structure for soc reading --- PRASFiles.jl/src/Systems/read.jl | 402 +++++++++++++------------------ 1 file changed, 170 insertions(+), 232 deletions(-) diff --git a/PRASFiles.jl/src/Systems/read.jl b/PRASFiles.jl/src/Systems/read.jl index 95daa7fe..7e09313a 100644 --- a/PRASFiles.jl/src/Systems/read.jl +++ b/PRASFiles.jl/src/Systems/read.jl @@ -20,9 +20,9 @@ function SystemModel(inputfile::String) # Determine the appropriate version of the importer to use return if (0,5,0) <= version < (0,8,0) systemmodel_0_5(f) - elseif (0,8,0) <= version < (0,9,0) - systemmodel_0_8(f) - elseif (0,8,1) <= version + elseif version == (0,8,0) + systemmodel_0_8_0(f) + elseif version >= (0,8,1) systemmodel_0_8_1(f) else error("PRAS file format $versionstring not supported by this version of PRASBase.") @@ -60,7 +60,6 @@ function _systemmodel_core(f::File) has_regions = haskey(f, "regions") has_generators = haskey(f, "generators") - has_storages = haskey(f, "storages") has_generatorstorages = haskey(f, "generatorstorages") has_interfaces = haskey(f, "interfaces") has_lines = haskey(f, "lines") @@ -108,72 +107,6 @@ function _systemmodel_core(f::File) end - if has_storages - - stor_core = read(f["storages/_core"]) - stor_names, stor_categories, stor_regionnames = readvector.( - Ref(stor_core), [:name, :category, :region]) - - stor_regions = getindex.(Ref(regionlookup), stor_regionnames) - region_order = sortperm(stor_regions) - - storages = Storages{N,L,T,P,E}( - stor_names[region_order], stor_categories[region_order], - load_matrix(f["storages/chargecapacity"], region_order, Int), - load_matrix(f["storages/dischargecapacity"], region_order, Int), - load_matrix(f["storages/energycapacity"], region_order, Int), - load_matrix(f["storages/chargeefficiency"], region_order, Float64), - load_matrix(f["storages/dischargeefficiency"], region_order, Float64), - load_matrix(f["storages/carryoverefficiency"], region_order, Float64), - load_matrix(f["storages/failureprobability"], region_order, Float64), - load_matrix(f["storages/repairprobability"], region_order, Float64) - ) - - region_stor_idxs = makeidxlist(stor_regions[region_order], n_regions) - - else - - storages = Storages{N,L,T,P,E}() - - region_stor_idxs = fill(1:0, n_regions) - - end - - - if has_generatorstorages - - genstor_core = read(f["generatorstorages/_core"]) - genstor_names, genstor_categories, genstor_regionnames = readvector.( - Ref(genstor_core), [:name, :category, :region]) - - genstor_regions = getindex.(Ref(regionlookup), genstor_regionnames) - region_order = sortperm(genstor_regions) - - generatorstorages = GeneratorStorages{N,L,T,P,E}( - genstor_names[region_order], genstor_categories[region_order], - load_matrix(f["generatorstorages/chargecapacity"], region_order, Int), - load_matrix(f["generatorstorages/dischargecapacity"], region_order, Int), - load_matrix(f["generatorstorages/energycapacity"], region_order, Int), - load_matrix(f["generatorstorages/chargeefficiency"], region_order, Float64), - load_matrix(f["generatorstorages/dischargeefficiency"], region_order, Float64), - load_matrix(f["generatorstorages/carryoverefficiency"], region_order, Float64), - load_matrix(f["generatorstorages/inflow"], region_order, Int), - load_matrix(f["generatorstorages/gridwithdrawalcapacity"], region_order, Int), - load_matrix(f["generatorstorages/gridinjectioncapacity"], region_order, Int), - load_matrix(f["generatorstorages/failureprobability"], region_order, Float64), - load_matrix(f["generatorstorages/repairprobability"], region_order, Float64) - ) - - region_genstor_idxs = makeidxlist(genstor_regions[region_order], n_regions) - - else - - generatorstorages = GeneratorStorages{N,L,T,P,E}() - - region_genstor_idxs = fill(1:0, n_regions) - - end - if has_interfaces interfaces_core = read(f["interfaces/_core"]) @@ -260,8 +193,6 @@ function _systemmodel_core(f::File) return (regions, interfaces, generators, region_gen_idxs, - storages, region_stor_idxs, - generatorstorages, region_genstor_idxs, lines, interface_line_idxs, timestamps),type_params end @@ -271,29 +202,179 @@ Read a SystemModel from a PRAS file in version 0.5.x - 0.7.x format. """ function systemmodel_0_5(f::File) - systemmodel_0_5_objs, _ = _systemmodel_core(f) + (regions, interfaces, + generators, region_gen_idxs, + lines, interface_line_idxs, + timestamps), (N,L,T,P,E) = _systemmodel_core(f) - return SystemModel(systemmodel_0_5_objs...) + n_regions = length(regions) + regionlookup = Dict(n=>i for (i, n) in enumerate(regions.names)) + attrs = read_attrs(f) + + has_storages = haskey(f, "storages") + has_generatorstorages = haskey(f, "generatorstorages") + + if has_storages + + stor_core = read(f["storages/_core"]) + stor_names, stor_categories, stor_regionnames = readvector.( + Ref(stor_core), [:name, :category, :region]) + + stor_regions = getindex.(Ref(regionlookup), stor_regionnames) + region_order = sortperm(stor_regions) + + storages = Storages{N,L,T,P,E}( + stor_names[region_order], stor_categories[region_order], + load_matrix(f["storages/chargecapacity"], region_order, Int), + load_matrix(f["storages/dischargecapacity"], region_order, Int), + load_matrix(f["storages/energycapacity"], region_order, Int), + load_matrix(f["storages/chargeefficiency"], region_order, Float64), + load_matrix(f["storages/dischargeefficiency"], region_order, Float64), + load_matrix(f["storages/carryoverefficiency"], region_order, Float64), + load_matrix(f["storages/failureprobability"], region_order, Float64), + load_matrix(f["storages/repairprobability"], region_order, Float64) + ) + + region_stor_idxs = makeidxlist(stor_regions[region_order], n_regions) + + else + + storages = Storages{N,L,T,P,E}() + + region_stor_idxs = fill(1:0, n_regions) + + end + + + if has_generatorstorages + + genstor_core = read(f["generatorstorages/_core"]) + genstor_names, genstor_categories, genstor_regionnames = readvector.( + Ref(genstor_core), [:name, :category, :region]) + + genstor_regions = getindex.(Ref(regionlookup), genstor_regionnames) + region_order = sortperm(genstor_regions) + + generatorstorages = GeneratorStorages{N,L,T,P,E}( + genstor_names[region_order], genstor_categories[region_order], + load_matrix(f["generatorstorages/chargecapacity"], region_order, Int), + load_matrix(f["generatorstorages/dischargecapacity"], region_order, Int), + load_matrix(f["generatorstorages/energycapacity"], region_order, Int), + load_matrix(f["generatorstorages/chargeefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/dischargeefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/carryoverefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/inflow"], region_order, Int), + load_matrix(f["generatorstorages/gridwithdrawalcapacity"], region_order, Int), + load_matrix(f["generatorstorages/gridinjectioncapacity"], region_order, Int), + load_matrix(f["generatorstorages/failureprobability"], region_order, Float64), + load_matrix(f["generatorstorages/repairprobability"], region_order, Float64) + ) + + region_genstor_idxs = makeidxlist(genstor_regions[region_order], n_regions) + + else + + generatorstorages = GeneratorStorages{N,L,T,P,E}() + + region_genstor_idxs = fill(1:0, n_regions) + + end + + return SystemModel( + regions, interfaces, + generators, region_gen_idxs, + storages, region_stor_idxs, + generatorstorages, region_genstor_idxs, + lines, interface_line_idxs, + timestamps, attrs) end """ -Read a SystemModel from a PRAS file in version 0.8.x format. +Read a SystemModel from a PRAS file in version 0.8.0 format. Adds Demand Response component info """ -function systemmodel_0_8(f::File) +function systemmodel_0_8_0(f::File) (regions, interfaces, generators, region_gen_idxs, - storages, region_stor_idxs, - generatorstorages, region_genstor_idxs, lines, interface_line_idxs, timestamps), (N,L,T,P,E) = _systemmodel_core(f) n_regions = length(regions) regionlookup = Dict(n=>i for (i, n) in enumerate(regions.names)) + attrs = read_attrs(f) + + has_storages = haskey(f, "storages") + has_generatorstorages = haskey(f, "generatorstorages") + has_demandresponses = haskey(f, "demandresponses") + + if has_storages + + stor_core = read(f["storages/_core"]) + stor_names, stor_categories, stor_regionnames = readvector.( + Ref(stor_core), [:name, :category, :region]) + + stor_regions = getindex.(Ref(regionlookup), stor_regionnames) + region_order = sortperm(stor_regions) + + storages = Storages{N,L,T,P,E}( + stor_names[region_order], stor_categories[region_order], + load_matrix(f["storages/chargecapacity"], region_order, Int), + load_matrix(f["storages/dischargecapacity"], region_order, Int), + load_matrix(f["storages/energycapacity"], region_order, Int), + load_matrix(f["storages/chargeefficiency"], region_order, Float64), + load_matrix(f["storages/dischargeefficiency"], region_order, Float64), + load_matrix(f["storages/carryoverefficiency"], region_order, Float64), + load_matrix(f["storages/failureprobability"], region_order, Float64), + load_matrix(f["storages/repairprobability"], region_order, Float64) + ) + + region_stor_idxs = makeidxlist(stor_regions[region_order], n_regions) + + else + + storages = Storages{N,L,T,P,E}() + + region_stor_idxs = fill(1:0, n_regions) + + end + + + if has_generatorstorages - if haskey(f, "demandresponses") + genstor_core = read(f["generatorstorages/_core"]) + genstor_names, genstor_categories, genstor_regionnames = readvector.( + Ref(genstor_core), [:name, :category, :region]) + + genstor_regions = getindex.(Ref(regionlookup), genstor_regionnames) + region_order = sortperm(genstor_regions) + + generatorstorages = GeneratorStorages{N,L,T,P,E}( + genstor_names[region_order], genstor_categories[region_order], + load_matrix(f["generatorstorages/chargecapacity"], region_order, Int), + load_matrix(f["generatorstorages/dischargecapacity"], region_order, Int), + load_matrix(f["generatorstorages/energycapacity"], region_order, Int), + load_matrix(f["generatorstorages/chargeefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/dischargeefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/carryoverefficiency"], region_order, Float64), + load_matrix(f["generatorstorages/inflow"], region_order, Int), + load_matrix(f["generatorstorages/gridwithdrawalcapacity"], region_order, Int), + load_matrix(f["generatorstorages/gridinjectioncapacity"], region_order, Int), + load_matrix(f["generatorstorages/failureprobability"], region_order, Float64), + load_matrix(f["generatorstorages/repairprobability"], region_order, Float64) + ) + + region_genstor_idxs = makeidxlist(genstor_regions[region_order], n_regions) + + else + + generatorstorages = GeneratorStorages{N,L,T,P,E}() + + region_genstor_idxs = fill(1:0, n_regions) + + end + if has_demandresponses dr_core = read(f["demandresponses/_core"]) dr_names, dr_categories, dr_regionnames = readvector.( @@ -324,8 +405,6 @@ function systemmodel_0_8(f::File) end - attrs = read_attrs(f) - return SystemModel( regions, interfaces, generators, region_gen_idxs, @@ -338,76 +417,22 @@ function systemmodel_0_8(f::File) end """ -Read a SystemModel from a PRAS file in version 0.8.1 format. Optional params required +Read a SystemModel from a PRAS file in version 0.8.1+ format. Optional params (initial soc levels and borrowing efficiencies optional params required) """ function systemmodel_0_8_1(f::File) - metadata = attributes(f) - - start_timestamp = ZonedDateTime(read(metadata["start_timestamp"]), - dateformat"yyyy-mm-ddTHH:MM:SSz") - - N = Int(read(metadata["timestep_count"])) - L = Int(read(metadata["timestep_length"])) - T = timeunits[read(metadata["timestep_unit"])] - P = powerunits[read(metadata["power_unit"])] - E = energyunits[read(metadata["energy_unit"])] - - type_params = (N,L,T,P,E) + (regions, interfaces, + generators, region_gen_idxs, + lines, interface_line_idxs, + timestamps), (N,L,T,P,E) = _systemmodel_core(f) - timestep = T(L) - end_timestamp = start_timestamp + (N-1)*timestep - timestamps = StepRange(start_timestamp, timestep, end_timestamp) + n_regions = length(regions) + regionlookup = Dict(n=>i for (i, n) in enumerate(regions.names)) + attrs = read_attrs(f) - has_regions = haskey(f, "regions") - has_generators = haskey(f, "generators") has_storages = haskey(f, "storages") has_generatorstorages = haskey(f, "generatorstorages") - has_interfaces = haskey(f, "interfaces") - has_lines = haskey(f, "lines") - - has_regions || - error("Region data must be provided") - - has_generators || has_generatorstorages || - error("Generator or generator storage data (or both) must be provided") - - xor(has_interfaces, has_lines) && - error("Both (or neither) interface and line data must be provided") - - regionnames = readvector(f["regions/_core"], :name) - regions = Regions{N,P}( - regionnames, - Int.(read(f["regions/load"])) - ) - regionlookup = Dict(n=>i for (i, n) in enumerate(regionnames)) - n_regions = length(regions) - - if has_generators - - gen_core = read(f["generators/_core"]) - gen_names, gen_categories, gen_regionnames = readvector.( - Ref(gen_core), [:name, :category, :region]) - - gen_regions = getindex.(Ref(regionlookup), gen_regionnames) - region_order = sortperm(gen_regions) - - generators = Generators{N,L,T,P}( - gen_names[region_order], gen_categories[region_order], - load_matrix(f["generators/capacity"], region_order, Int), - load_matrix(f["generators/failureprobability"], region_order, Float64), - load_matrix(f["generators/repairprobability"], region_order, Float64) - ) - - region_gen_idxs = makeidxlist(gen_regions[region_order], n_regions) - - else - - generators = Generators{N,L,T,P}() - - region_gen_idxs = fill(1:0, n_regions) - - end + has_demandresponses = haskey(f, "demandresponses") if has_storages @@ -477,9 +502,8 @@ function systemmodel_0_8_1(f::File) end - if haskey(f, "demandresponses") - + if has_demandresponses dr_core = read(f["demandresponses/_core"]) dr_names, dr_categories, dr_regionnames = readvector.( Ref(dr_core), [:name, :category, :region]) @@ -509,92 +533,6 @@ function systemmodel_0_8_1(f::File) region_dr_idxs = fill(1:0, n_regions) end - - if has_interfaces - - interfaces_core = read(f["interfaces/_core"]) - from_regionnames, to_regionnames = - readvector.(Ref(interfaces_core), [:region_from, :region_to]) - forwardcapacity = Int.(read(f["interfaces/forwardcapacity"])) - backwardcapacity = Int.(read(f["interfaces/backwardcapacity"])) - - n_interfaces = length(from_regionnames) - from_regions = getindex.(Ref(regionlookup), from_regionnames) - to_regions = getindex.(Ref(regionlookup), to_regionnames) - - # Force interface definitions as smaller => larger region numbers - for i in 1:n_interfaces - from_region = from_regions[i] - to_region = to_regions[i] - if from_region > to_region - from_regions[i] = to_region - to_regions[i] = from_region - new_forwardcapacity = backwardcapacity[i, :] - backwardcapacity[i, :] .= forwardcapacity[i, :] - forwardcapacity[i, :] .= new_forwardcapacity - elseif from_region == to_region - error("Cannot have an interface to and from " * - "the same region ($(from_region))") - end - end - - interfaces = Interfaces{N,P}( - from_regions, to_regions, forwardcapacity, backwardcapacity) - - interface_lookup = Dict((r1, r2) => i for (i, (r1, r2)) - in enumerate(tuple.(from_regions, to_regions))) - - lines_core = read(f["lines/_core"]) - line_names, line_categories, line_fromregionnames, line_toregionnames = - readvector.(Ref(lines_core), [:name, :category, :region_from, :region_to]) - line_forwardcapacity = Int.(read(f["lines/forwardcapacity"])) - line_backwardcapacity = Int.(read(f["lines/backwardcapacity"])) - - n_lines = length(line_names) - line_fromregions = getindex.(Ref(regionlookup), line_fromregionnames) - line_toregions = getindex.(Ref(regionlookup), line_toregionnames) - - # Force line definitions as smaller => larger region numbers - for i in 1:n_lines - from_region = line_fromregions[i] - to_region = line_toregions[i] - if from_region > to_region - line_fromregions[i] = to_region - line_toregions[i] = from_region - new_forwardcapacity = line_backwardcapacity[i, :] - line_backwardcapacity[i, :] .= line_forwardcapacity[i, :] - line_forwardcapacity[i, :] = new_forwardcapacity - elseif from_region == to_region - error("Cannot have a line ($(line_names[i])) to and from " * - "the same region ($(from_region))") - end - end - - line_interfaces = getindex.(Ref(interface_lookup), - tuple.(line_fromregions, line_toregions)) - interface_order = sortperm(line_interfaces) - - lines = Lines{N,L,T,P}( - line_names[interface_order], line_categories[interface_order], - line_forwardcapacity[interface_order, :], - line_backwardcapacity[interface_order, :], - load_matrix(f["lines/failureprobability"], interface_order, Float64), - load_matrix(f["lines/repairprobability"], interface_order, Float64) - ) - - interface_line_idxs = makeidxlist(line_interfaces[interface_order], n_interfaces) - - else - - interfaces = Interfaces{N,P}() - - lines = Lines{N,L,T,P}() - - interface_line_idxs = UnitRange{Int}[] - - end - - attrs = read_attrs(f) return SystemModel( regions, interfaces, From 75728a523b34907dc8621be9c3fe0792ac834f8e Mon Sep 17 00:00:00 2001 From: juflorez Date: Mon, 24 Nov 2025 15:38:28 -0700 Subject: [PATCH 05/16] update tests for prior versioning compat --- PRASFiles.jl/test/runtests.jl | 19 ++++++++++++++++++ .../test/versioned_toy/toymodel_v0_7_0.pras | Bin 0 -> 17368 bytes .../test/versioned_toy/toymodel_v0_8_0.pras | Bin 0 -> 17368 bytes .../test/versioned_toy/toymodel_v0_8_1.pras | Bin 0 -> 17368 bytes 4 files changed, 19 insertions(+) create mode 100644 PRASFiles.jl/test/versioned_toy/toymodel_v0_7_0.pras create mode 100644 PRASFiles.jl/test/versioned_toy/toymodel_v0_8_0.pras create mode 100644 PRASFiles.jl/test/versioned_toy/toymodel_v0_8_1.pras diff --git a/PRASFiles.jl/test/runtests.jl b/PRASFiles.jl/test/runtests.jl index 2249c6dd..8279e3d8 100644 --- a/PRASFiles.jl/test/runtests.jl +++ b/PRASFiles.jl/test/runtests.jl @@ -30,6 +30,25 @@ using JSON3 end + + @testset "Read version 0.7.0 to 0.8.1 compatibility" begin + + path = dirname(@__FILE__) + version_0_7_0 = SystemModel(path * "/versioned_toy/toymodel_v0_7_0.pras") + version_0_8_1 = SystemModel(path * "/versioned_toy/toymodel_v0_8_1.pras") + + @test version_0_7_0 == version_0_8_1 + end + + @testset "Read version 0.8.0 to 0.8.1 compatibility" begin + + path = dirname(@__FILE__) + version_0_8_0 = SystemModel(path * "/versioned_toy/toymodel_v0_7_0.pras") + version_0_8_1 = SystemModel(path * "/versioned_toy/toymodel_v0_8_1.pras") + + @test version_0_8_0 == version_0_8_1 + end + @testset "Run RTS-GMLC" begin assess(PRASFiles.rts_gmlc(), SequentialMonteCarlo(samples=100), Shortfall()) diff --git a/PRASFiles.jl/test/versioned_toy/toymodel_v0_7_0.pras b/PRASFiles.jl/test/versioned_toy/toymodel_v0_7_0.pras new file mode 100644 index 0000000000000000000000000000000000000000..080f1e69d8c72ed280d6bf7da9112819087ce749 GIT binary patch literal 17368 zcmeHOZA=tL7@oa_yAuw$Lu*hmo{cs(iaA;c+9tSYp=VknB_xtGEv&HOh2!qBw;-h2 zf=wE18g2T+YT7DIn%br*EmaXsln-O2X+v6T!B{Q$1LOzL)@n6Hu=9Dh99(D*KZ>0R z?99%*?>p~2J3Bn@%(Bb*OioqpO>ADYM6bNQ#GFb&k=BDXXPRY3MdX{ns?B7MsM+4LuD+? zDyNaN`MS?Uj4-xIT=8p)udvu#;n9E0p(3@9Nu znbL+$$WJDZMhW{-Jc?Rc9aPZwvSHI!3vg2VRwD1$WK}C9=*Zz1(^DKz zrlh9jI~-5ppXK<9&A|TIIBnElRrVK_$eLWKsD78%gAE#W1}hyaGgdkTV8Y`FU3YGg zhZ2|{eBB{9^XudYgj;tcx*jks_Rg^Nfz?Nv8v=hZ*r~w$QYq=$ zP*DIzB2SeBYF`jY1+&F+5w35jJQrRhn0w@M1!hyG*-%1)ztPk`gTa7JnrVNc;G9K_ zAgN$Vgz_jM;lz)ZxPmJ^uDEeR>hvDl%fSGqEsau?fNjT#xchz z_q!$QoQBvUr({dCydj(bhBNL*PG+?Kw0-#6d+(-I3@!cUKtrF@bME58%lk+7?myMH zwnshWJ^sV7BbCqIENbb??z#4nxBACpE3T&${RZ=y?!;#U4THztA9!*1 z^_0(FTdyszyYx=_TG_9-y>dxJPD7=XNENBxbaBg7kkWs6(Z6x@dN0({FLzD+5M9ok1-V4^jVZ#j|LT}wvBFRel}42e}` zU_4>D-wlB-~`)B^MzLz5ix8Lue`|7N37FV!&#p)x?b@7r( z#rw$q3sE{_y7rh-bOtADUe(``%tQnjDI%ZD)UnY0eR|xE4+W8q2u|8AzdVEjt(Kpc|#3 zQEs?%6zNf7QVLimg?}37GraBed9eF?80Xhemr<9fyZaL7GkgnH6MRDP)y-RTvFwwJ zU0StcaS+sS@qS}aWYUsk8DAZWGameS5q`%J;0SO8I0762jsQo1BQS#zm_83r!tsyI zzJ{I+Ngyp2q}`!JJrH+9e(q*nN${P5k4tyuDNOY}lM&c)Kk&RQaf|G7SE!0l^=_6o zyWEhGH?Lq$gyZAsOdw4DJLltJkm1!F0geDifFr;W;0SO89!>;i03WZXTh#Z3kL&im z$@gy^;}4K@eB1`9xa?AOble&I?}BiAJZFk&^cecYS(o_!xDE_n;&)pl&MQ3|ftf2$^Z8t ze81oGJ)e8-;qyJ`veWTQPHgnNXn>O03?hIP7un@DO?_)g4ZCOUMYJH$ypHA{8%RL_ z@c^|Y(RwSjZxczyQL31qmz|Ajn0=O0HJ<&?5pZN@#utpO`^koYM1eX_Ba1ZoEKtwH)O z#a*hEO=%!9Fc1r{)BfXNKl=VF-A)b6vI2nKFxF#*(@k_OU@6s$A$nfV7DX-eD3HwppidtF|P|)|Xeq)&hIH-Lqk@sn`sudD+?(mH@T6ZM69xyHT&am}?)rXrK{C_doUqY4>i_z#^)EQ^%RNX>SDe2l! zQ2<6FPn85}Ul2$Iv&C`|u5YM32VNwYyX6W6X49rwP(p&gk<>qf!GKMgX@8>Nh$2Ri zR3Ifnd6bawcONZw23C4peb+DH;#-hNTp_D}z^(i;#g?a(q6I0eR4p9o{UEm1KHDf? zc1hORjWI0)@SEom6l$!eX?YARFo1;d%w>|H;yz|$edykEs9X~f( zepE9(Z9j0a*mQZ`)}hw(ukT*4xAeCr@elK=s-j=oI!11#7_N6ccje40Z)UEGThV-Q zeAQvm7X=%;+B)LCef(zMz603}BcE$8?~-2~YE9k&^akwbHnFSNzIb))Ch>G<{~(a>Pd%6X7P*6)2HSywtBPj_K%g6e|ffY(OA~Pq3vSwMPORlD#UqfCl|M0iL+e# zWV|ci*R2AiRj}-B!cHdsbHPyi*$W|yofxDDy~`IbR^n^G1Vw2!*LL~AnNav-(4mW+&FT7^g$606GK zM8a&O`#^$Kh(P?qkwCge@faEpW4kP`p2o8}$|{){8Z1gQqwJE@@heAwBft^h2yg^A z0vrL3z|2QL0I`_s1@HG?w2`r~eObQWKl7jUy&OTP{eC;$S7&{*xPr|qRv&Jzi2W(g6ht~AXu$GwU_&ds|F`@5 z#W2_`^{6$-7D^JnhQ6?K0WvlKYD0_S7AEhHe|CUiZj=T` zxuMQcq(_NK$#0ny{%M@g@V3$C!S3&2oL@^_MqQ%r?n|7{@GZzB_ypsto44j-*((=2 zwHo`P0H~qj{icA(lqJbBu{s!MJot$s{Ej2Q5#R`L1ULd50geDiUIUf&!46o)0a0EC490861M}Q;na3U}R_;>@|qP{PDT(|E{ zzJKeScz~qi;|Y+8%T85C#~p$HE(pcPbEcU_kD*VTb&2nf>%ibejsQo1Bft^h2yg^A J0uLbqe*=F~KMDW< literal 0 HcmV?d00001 diff --git a/PRASFiles.jl/test/versioned_toy/toymodel_v0_8_1.pras b/PRASFiles.jl/test/versioned_toy/toymodel_v0_8_1.pras new file mode 100644 index 0000000000000000000000000000000000000000..b809daad604fac9348eb74ba293ef1368aa4c7ec GIT binary patch literal 17368 zcmeHOZA=tL7@oa_yAuvL&>BB5o{cs(eB@{$YMbbSg`R1Rl#oEuw6MYo7mmBj-hz^9 z3pQ!6X|(AND`~4VX<|)NTB-t?C||MAv>`3EXsj0ff%*e!Yqgpx(D}Su4lcBZAH~iD zc4lYZ_nmj1ogJQcX4&O2woa3s_0@qKTf@vsF=x{7MK; zrnIpD`N`zbC}BT}TT#nu0t)(GHWrjyfP>n%5_z8{t6C93N3Qf@&lc)BdCH!akz#*3 zB`rPQZhr>a3Q}s(prKD>^ zMFALzJXI2?eL)}<%ofW?dv`siV@NrD7eV$Ri z?2@eWnxcyxQhb8tE#WvYoN_&OVpZ2q+efZ{@P2yLwH4p&ZyJ&Y`_C=8&@j5U;p9-( zpnA}A?1$DP)z9B5ZXe1Xy#9%&=Ev6L8!1J9?OnIy+dU_T)(`d{|LkhhaO;OxU*3Hq zXq*-$=MMh^vBt+X>nrKX{)Zam^n%bYv?J6?2L-u3Ix1II?sjGenv z(WaT6wI8@xV!FI&`*6qkH}@>rSN2=8_=kCIb@4BqJtKFM4LAEXx7TclUDa}M zZ0!-zHwQNMb@s%5_vG!fA05bU9Qi_fb+`Qba7W@!sQ(JoKO6@|S~`2y7Vq42?a!@8 zPec@+89BT#@j_?pJGV}^cgJk&z%z_BNLE{L%7va7L64A$vNY2lrL}Dl! z*svU>ZLo}Zpa4phEiPG8a2wL|^DTq;E~P9sXdiWfiPmJkk4G7$kripVE3bu4&)pB}g4LqVh?f<`PqA2xKr`+vK? zUjl>8GLKq=Y@sCKYv>C*7a(H;pfC-S;h)C&3~w8K9_;=;#`(3>Wz;3=?t#Sl4Bx^v1fO7hb@SF-B75Z$ zr&eQM763I=yx$xUnX)8VCRPXIj0Znagx_%lI0762jsQo1Bft^h2+U#xX3oPG;P@xr zwvL_+NgzEMq>odg?vFddKX Date: Mon, 24 Nov 2025 15:39:12 -0700 Subject: [PATCH 06/16] update gitignore for versioned toy test --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c725c05e..9b0f6caa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Manifest.toml *.DS_Store *.pras +!PRASFiles.jl/test/versioned_toy/* PRASFiles.jl/test/PRAS_Results_Export/ docs/build/ From fa98b673a7f5ffbad95d6838fd4ac5cc09ca843c Mon Sep 17 00:00:00 2001 From: juflorez Date: Mon, 24 Nov 2025 16:13:16 -0700 Subject: [PATCH 07/16] add docs for new optional soc --- docs/src/SystemModel_HDF5_spec.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/src/SystemModel_HDF5_spec.md b/docs/src/SystemModel_HDF5_spec.md index 898818c5..45285994 100644 --- a/docs/src/SystemModel_HDF5_spec.md +++ b/docs/src/SystemModel_HDF5_spec.md @@ -298,6 +298,11 @@ The `storages` group should also contain the following datasets describing transitions from forced outage to operational during a given simulation timestep, for each storage unit in each timeperiod. Unitless. +An optional parameter is available to set the initial state of charge for the unit: + + - `initialsoc`, as 64-bit floats representing the initial percentage state of charge of + the unit [0.0, 1.0], for each storage unit, expressed in percentage of energycapacity at timestep 1. + #### `generatorstorages` group Information relating to the combination generation-storage resources in the @@ -362,6 +367,11 @@ generator-storage devices: transitions from forced outage to operational during a given simulation timestep, for each generator-storage unit in each timeperiod. Unitless. +An optional parameter is available to set the initial state of charge for the unit: + + - `initialsoc`, as 64-bit floats representing the initial percentage state of charge of + the unit [0.0, 1.0], for each generatorstorage unit, expressed in percentage of energycapacity at timestep 1. + #### `demandresponses` group Information relating to the demand response only devices of the represented system is @@ -406,6 +416,11 @@ The `demandresponse` group should also contain the following datasets describing transitions from forced outage to operational during a given simulation timestep, for each demand response unit in each timeperiod. Unitless. +An optional parameter is available to set the initial state of borrowed load for the unit: + + - `initialborrowedload`, as 64-bit floats representing the initial percentage borrowed load + for the unit [0.0, 1.0], for each demandresponse unit, expressed in percentage of energycapacity at timestep 1. + #### `interfaces` group Information relating to transmission interfaces between regions of the From 0f090dbb7c5b1359275bc54b260ee510e47e7a33 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:41:37 -0700 Subject: [PATCH 08/16] fix indexing bug --- PRASCore.jl/src/Simulations/Simulations.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PRASCore.jl/src/Simulations/Simulations.jl b/PRASCore.jl/src/Simulations/Simulations.jl index 0d892d0a..b3ed302f 100644 --- a/PRASCore.jl/src/Simulations/Simulations.jl +++ b/PRASCore.jl/src/Simulations/Simulations.jl @@ -183,19 +183,19 @@ function initialize!( rng, state.lines_available, state.lines_nexttransition, system.lines, N) if size(system.storages.energy_capacity, 1) > 0 - state.stors_energy .= (system.storages.initial_soc .* system.storages.energy_capacity[1,:]) + state.stors_energy .= (system.storages.initial_soc .* system.storages.energy_capacity[:,1]) else fill!(state.stors_energy, 0.0) end if size(system.generatorstorages.energy_capacity, 1) > 0 - state.genstors_energy .= (system.generatorstorages.initial_soc .* system.generatorstorages.energy_capacity[1,:]) + state.genstors_energy .= (system.generatorstorages.initial_soc .* system.generatorstorages.energy_capacity[:,1]) else fill!(state.genstors_energy, 0.0) end if size(system.demandresponses.energy_capacity, 1) > 0 - state.drs_energy .= (system.demandresponses.initial_borrowed_load .* system.demandresponses.energy_capacity[1,:]) + state.drs_energy .= (system.demandresponses.initial_borrowed_load .* system.demandresponses.energy_capacity[:,1]) else fill!(state.drs_energy, 0.0) end From de95c679a2378a0e882a75b26a929e22d862c33f Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:45:15 -0700 Subject: [PATCH 09/16] fix file test fpath --- PRASFiles.jl/test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRASFiles.jl/test/runtests.jl b/PRASFiles.jl/test/runtests.jl index 8279e3d8..626f6ccf 100644 --- a/PRASFiles.jl/test/runtests.jl +++ b/PRASFiles.jl/test/runtests.jl @@ -43,7 +43,7 @@ using JSON3 @testset "Read version 0.8.0 to 0.8.1 compatibility" begin path = dirname(@__FILE__) - version_0_8_0 = SystemModel(path * "/versioned_toy/toymodel_v0_7_0.pras") + version_0_8_0 = SystemModel(path * "/versioned_toy/toymodel_v0_8_0.pras") version_0_8_1 = SystemModel(path * "/versioned_toy/toymodel_v0_8_1.pras") @test version_0_8_0 == version_0_8_1 From d690c53a8a7d230ac015b850d00e16c26f1eccb6 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:46:37 -0700 Subject: [PATCH 10/16] fix syntax --- PRASCore.jl/src/Systems/assets.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PRASCore.jl/src/Systems/assets.jl b/PRASCore.jl/src/Systems/assets.jl index 0a2dfe6a..8d49c29a 100644 --- a/PRASCore.jl/src/Systems/assets.jl +++ b/PRASCore.jl/src/Systems/assets.jl @@ -401,7 +401,7 @@ struct GeneratorStorages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAs carryover_efficiency::Matrix{Float64}, inflow::Matrix{Int}, gridwithdrawal_capacity::Matrix{Int}, gridinjection_capacity::Matrix{Int}, - λ::Matrix{Float64}, μ::Matrix{Float64},; + λ::Matrix{Float64}, μ::Matrix{Float64}; initial_soc::Vector{Float64} = zeros(Float64, length(names)), ) where {N,L,T,P,E} @@ -610,7 +610,7 @@ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAsse borrowcapacity::Matrix{Int}, paybackcapacity::Matrix{Int}, energycapacity::Matrix{Int}, borrowedenergyinterest::Matrix{Float64}, allowablepaybackperiod::Matrix{Int}, - λ::Matrix{Float64}, μ::Matrix{Float64},; + λ::Matrix{Float64}, μ::Matrix{Float64}; initial_borrowed_load::Vector{Float64} = zeros(Float64, length(names)), borrow_efficiency::Matrix{Float64} = ones(Float64, size(borrowcapacity)), payback_efficiency::Matrix{Float64} = ones(Float64, size(paybackcapacity)) From 6b1aed465748c46f7b07fbd89213d89657a5bf15 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:48:05 -0700 Subject: [PATCH 11/16] remove confusing comments --- PRASFiles.jl/src/Systems/read.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PRASFiles.jl/src/Systems/read.jl b/PRASFiles.jl/src/Systems/read.jl index 7e09313a..8064f062 100644 --- a/PRASFiles.jl/src/Systems/read.jl +++ b/PRASFiles.jl/src/Systems/read.jl @@ -453,7 +453,7 @@ function systemmodel_0_8_1(f::File) load_matrix(f["storages/carryoverefficiency"], region_order, Float64), load_matrix(f["storages/failureprobability"], region_order, Float64), load_matrix(f["storages/repairprobability"], region_order, Float64); - initial_soc = load_matrix(f["storages/initialsoc"], region_order, Float64) #optional param required + initial_soc = load_matrix(f["storages/initialsoc"], region_order, Float64) ) region_stor_idxs = makeidxlist(stor_regions[region_order], n_regions) @@ -489,7 +489,7 @@ function systemmodel_0_8_1(f::File) load_matrix(f["generatorstorages/gridinjectioncapacity"], region_order, Int), load_matrix(f["generatorstorages/failureprobability"], region_order, Float64), load_matrix(f["generatorstorages/repairprobability"], region_order, Float64); - initial_soc = load_matrix(f["generatorstorages/initialsoc"], region_order, Float64) #optional param required + initial_soc = load_matrix(f["generatorstorages/initialsoc"], region_order, Float64) ) region_genstor_idxs = makeidxlist(genstor_regions[region_order], n_regions) @@ -520,9 +520,9 @@ function systemmodel_0_8_1(f::File) load_matrix(f["demandresponses/allowablepaybackperiod"], region_order, Int), load_matrix(f["demandresponses/failureprobability"], region_order, Float64), load_matrix(f["demandresponses/repairprobability"], region_order, Float64); - initial_borrowed_load = load_matrix(f["demandresponses/initialborrowedload"], region_order, Float64), #optional param required - borrow_efficiency = load_matrix(f["demandresponses/borrowefficiency"], region_order, Float64), #optional param required - payback_efficiency = load_matrix(f["demandresponses/paybackefficiency"], region_order, Float64), #optional param required + initial_borrowed_load = load_matrix(f["demandresponses/initialborrowedload"], region_order, Float64), + borrow_efficiency = load_matrix(f["demandresponses/borrowefficiency"], region_order, Float64), + payback_efficiency = load_matrix(f["demandresponses/paybackefficiency"], region_order, Float64), ) region_dr_idxs = makeidxlist(dr_regions[region_order], n_regions) From 398f73af9f5fedbcdb676274cd221d20769e4e56 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:56:02 -0700 Subject: [PATCH 12/16] update details in systemmodel hdf5 --- docs/src/SystemModel_HDF5_spec.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/SystemModel_HDF5_spec.md b/docs/src/SystemModel_HDF5_spec.md index 45285994..69e64d19 100644 --- a/docs/src/SystemModel_HDF5_spec.md +++ b/docs/src/SystemModel_HDF5_spec.md @@ -300,8 +300,8 @@ The `storages` group should also contain the following datasets describing An optional parameter is available to set the initial state of charge for the unit: - - `initialsoc`, as 64-bit floats representing the initial percentage state of charge of - the unit [0.0, 1.0], for each storage unit, expressed in percentage of energycapacity at timestep 1. + - `initialsoc`, as 64-bit floats representing the initial state of charge of + the unit as a fraction [0.0, 1.0] of energy capacity at timestep 1, for each storage unit. #### `generatorstorages` group @@ -369,8 +369,8 @@ generator-storage devices: An optional parameter is available to set the initial state of charge for the unit: - - `initialsoc`, as 64-bit floats representing the initial percentage state of charge of - the unit [0.0, 1.0], for each generatorstorage unit, expressed in percentage of energycapacity at timestep 1. + - `initialsoc`, as 64-bit floats representing the initial state of charge of + the unit as a fraction [0.0, 1.0] of energy capacity at timestep 1, for each generator-storage unit. #### `demandresponses` group @@ -418,8 +418,8 @@ The `demandresponse` group should also contain the following datasets describing An optional parameter is available to set the initial state of borrowed load for the unit: - - `initialborrowedload`, as 64-bit floats representing the initial percentage borrowed load - for the unit [0.0, 1.0], for each demandresponse unit, expressed in percentage of energycapacity at timestep 1. + - `initialborrowedload`, as 64-bit floats representing the initial borrowed load + for the unit as a fraction [0.0, 1.0] of energy capacity at timestep 1, for each demand response unit. #### `interfaces` group From 75a58b92634ff11c69a9b7867995434c814793c9 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:58:37 -0700 Subject: [PATCH 13/16] clarify writing method for 0_8_1 --- PRASFiles.jl/src/Systems/read.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRASFiles.jl/src/Systems/read.jl b/PRASFiles.jl/src/Systems/read.jl index 8064f062..8b2af244 100644 --- a/PRASFiles.jl/src/Systems/read.jl +++ b/PRASFiles.jl/src/Systems/read.jl @@ -417,7 +417,7 @@ function systemmodel_0_8_0(f::File) end """ -Read a SystemModel from a PRAS file in version 0.8.1+ format. Optional params (initial soc levels and borrowing efficiencies optional params required) +Read a SystemModel from a PRAS file in version 0.8.1+ format. Requires initial SOC levels for storages/generator-storages and initial borrowed load/efficiency parameters for demand responses. """ function systemmodel_0_8_1(f::File) From 413119cb08667aba583f9069cb2826c099e1a7a5 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 14:59:48 -0700 Subject: [PATCH 14/16] fix writing demandresponses bug --- PRASFiles.jl/src/Systems/write.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRASFiles.jl/src/Systems/write.jl b/PRASFiles.jl/src/Systems/write.jl index 80aa84bf..14ba2d0b 100644 --- a/PRASFiles.jl/src/Systems/write.jl +++ b/PRASFiles.jl/src/Systems/write.jl @@ -267,7 +267,7 @@ function process_demandresponses!( demandresponses["repairprobability", deflate = compression] = sys.demandresponses.μ generatorstorages["initialborrowedload", deflate = compression] = - sys.generatorstorages.initial_borrowed_load + sys.demandresponses.initial_borrowed_load return end From b5c4c9fba97e374918afc94fd5452f0e97de39a5 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 15:02:16 -0700 Subject: [PATCH 15/16] fix writing demandresponses bug follow on --- PRASFiles.jl/src/Systems/write.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRASFiles.jl/src/Systems/write.jl b/PRASFiles.jl/src/Systems/write.jl index 14ba2d0b..93b0e57c 100644 --- a/PRASFiles.jl/src/Systems/write.jl +++ b/PRASFiles.jl/src/Systems/write.jl @@ -266,7 +266,7 @@ function process_demandresponses!( demandresponses["repairprobability", deflate = compression] = sys.demandresponses.μ - generatorstorages["initialborrowedload", deflate = compression] = + demandresponses["initialborrowedload", deflate = compression] = sys.demandresponses.initial_borrowed_load return From 2458a029e94a7cefca7989140f8c6cbd913f9994 Mon Sep 17 00:00:00 2001 From: juflorez Date: Tue, 25 Nov 2025 15:04:03 -0700 Subject: [PATCH 16/16] fix docstring format --- PRASCore.jl/src/Systems/assets.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PRASCore.jl/src/Systems/assets.jl b/PRASCore.jl/src/Systems/assets.jl index 8d49c29a..ba1dc6ad 100644 --- a/PRASCore.jl/src/Systems/assets.jl +++ b/PRASCore.jl/src/Systems/assets.jl @@ -178,7 +178,7 @@ A struct representing storage devices in the system. - `μ` (repair probability): Probability the unit transitions from forced outage to operational during a given simulation timestep, for each storage unit in each timeperiod. Unitless. - - 'initial_soc': Optional keyword for initial state of charge as a ratio [0,1.0] of `energy_capacity` at first timestep for + - `initial_soc`: Optional keyword for initial state of charge as a fraction [0,1.0] of `energy_capacity` at first timestep for each storage unit at the beginning of the simulation. Default is zero. """ struct Storages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L,T,P} @@ -368,8 +368,8 @@ A struct representing generator-storage hybrid devices within a power system. - `μ` (repair probability): Probability the unit transitions from forced outage to operational during a given simulation timestep, for each generator-storage unit in each timeperiod. Unitless. - - 'initial_soc': Optional keywrod arg initial state of charge as a ratio [0,1.0] of `energy_capacity` at first timestep for - each storage unit at the beginning of the simulation. Default is zero. + - `initial_soc`: Optional keyword for initial state of charge as a fraction [0.0, 1.0] of + `energy_capacity` at the first timestep for each storage unit. Default is zero. """ struct GeneratorStorages{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L,T,P} @@ -581,7 +581,7 @@ A struct representing demand response devices in the system. - `μ` (repair probability): Probability the unit transitions from forced outage to operational during a given simulation timestep, for each storage unit in each timeperiod. Unitless. - - 'initial_borrowed_load': Optional initial state of borrowed load as a ratio [0,1.0] of `energy_capacity` at first timestep for + - `initial_borrowed_load`: Optional initial state of borrowed load as a fraction [0,1.0] of `energy_capacity` at first timestep for each demand response unit at the beginning of the simulation. Default is zero. """ struct DemandResponses{N,L,T<:Period,P<:PowerUnit,E<:EnergyUnit} <: AbstractAssets{N,L,T,P}