diff --git a/instances/test/case14-sub-hourly.json.gz b/instances/test/case14-sub-hourly.json.gz index b788e4d..eb35ed3 100644 Binary files a/instances/test/case14-sub-hourly.json.gz and b/instances/test/case14-sub-hourly.json.gz differ diff --git a/instances/test/case14.json.gz b/instances/test/case14.json.gz index 9876d59..e4d49b8 100644 Binary files a/instances/test/case14.json.gz and b/instances/test/case14.json.gz differ diff --git a/src/instance/read.jl b/src/instance/read.jl index 7f89838..a7c9359 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -66,6 +66,7 @@ function _from_json(json; repair = true) contingencies = Contingency[] lines = TransmissionLine[] loads = PriceSensitiveLoad[] + reserves2 = Reserve[] function scalar(x; default = nothing) x !== nothing || return default @@ -86,6 +87,7 @@ function _from_json(json; repair = true) name_to_bus = Dict{String,Bus}() name_to_line = Dict{String,TransmissionLine}() name_to_unit = Dict{String,Unit}() + name_to_reserve = Dict{String,Reserve}() function timeseries(x; default = nothing) x !== nothing || return default @@ -116,6 +118,19 @@ function _from_json(json; repair = true) push!(buses, bus) end + # Read reserves + if "Reserves2" in keys(json) + for (reserve_name, dict) in json["Reserves2"] + reserve = Reserve( + name = reserve_name, + type = lowercase(dict["Type"]), + amount = timeseries(dict["Amount (MW)"]), + ) + name_to_reserve[reserve_name] = reserve + push!(reserves2, reserve) + end + end + # Read units for (unit_name, dict) in json["Generators"] bus = name_to_bus[dict["Bus"]] @@ -153,6 +168,12 @@ function _from_json(json; repair = true) ) end + # Read reserves + unit_reserves = Reserve[] + if "Reserve eligibility" in keys(dict) + unit_reserves = [name_to_reserve[n] for n in dict["Reserve eligibility"]] + end + # Read and validate initial conditions initial_power = scalar(dict["Initial power (MW)"], default = nothing) initial_status = scalar(dict["Initial status (h)"], default = nothing) @@ -191,6 +212,7 @@ function _from_json(json; repair = true) default = [true for t in 1:T], ), startup_categories, + unit_reserves, ) push!(bus.units, unit) name_to_unit[unit_name] = unit @@ -276,6 +298,8 @@ function _from_json(json; repair = true) price_sensitive_loads_by_name = Dict(ps.name => ps for ps in loads), price_sensitive_loads = loads, reserves = reserves, + reserves2 = reserves2, + reserves_by_name = name_to_reserve, shortfall_penalty = shortfall_penalty, time = T, units_by_name = Dict(g.name => g for g in units), diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 1e4cd5c..8b92777 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -20,6 +20,12 @@ mutable struct StartupCategory cost::Float64 end +Base.@kwdef mutable struct Reserve + name::String + type::String + amount::Vector{Float64} +end + mutable struct Unit name::String bus::Bus @@ -38,6 +44,7 @@ mutable struct Unit initial_power::Union{Float64,Nothing} provides_spinning_reserves::Vector{Bool} startup_categories::Vector{StartupCategory} + reserves::Vector{Reserve} end mutable struct TransmissionLine @@ -80,6 +87,8 @@ Base.@kwdef mutable struct UnitCommitmentInstance price_sensitive_loads_by_name::Dict{AbstractString,PriceSensitiveLoad} price_sensitive_loads::Vector{PriceSensitiveLoad} reserves::Reserves + reserves2::Vector{Reserve} + reserves_by_name::Dict{AbstractString,Reserve} shortfall_penalty::Vector{Float64} time::Int units_by_name::Dict{AbstractString,Unit} diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl index 676a808..6984435 100644 --- a/test/instance/read_test.jl +++ b/test/instance/read_test.jl @@ -12,6 +12,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test length(instance.units) == 6 @test length(instance.contingencies) == 19 @test length(instance.price_sensitive_loads) == 1 + @test length(instance.reserves2) == 1 @test instance.time == 4 @test instance.lines[5].name == "l5" @@ -37,6 +38,11 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test instance.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] @test instance.buses_by_name["b9"].name == "b9" + @test instance.reserves2[1].name == "r1" + @test instance.reserves2[1].type == "spinning" + @test instance.reserves2[1].amount == [100.0, 100.0, 100.0, 100.0] + @test instance.reserves_by_name["r1"].name == "r1" + unit = instance.units[1] @test unit.name == "g1" @test unit.bus.name == "b1" @@ -64,11 +70,13 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.startup_categories[1].cost == 1000.0 @test unit.startup_categories[2].cost == 1500.0 @test unit.startup_categories[3].cost == 2000.0 + @test length(unit.reserves) == 0 @test instance.units_by_name["g1"].name == "g1" unit = instance.units[2] @test unit.name == "g2" @test unit.must_run == [false for t in 1:4] + @test length(unit.reserves) == 1 unit = instance.units[3] @test unit.name == "g3" @@ -90,6 +98,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.cost_segments[2].cost[t] ≈ 38.04 @test unit.cost_segments[3].cost[t] ≈ 44.77853 end + @test length(unit.reserves) == 1 @test instance.reserves.spinning == zeros(4)