Remove old reserves

feature/reserves
Alinson S. Xavier 4 years ago
parent 3220650e39
commit 5ca566f147

Binary file not shown.

@ -66,7 +66,7 @@ function _from_json(json; repair = true)
contingencies = Contingency[] contingencies = Contingency[]
lines = TransmissionLine[] lines = TransmissionLine[]
loads = PriceSensitiveLoad[] loads = PriceSensitiveLoad[]
reserves2 = Reserve[] reserves = Reserve[]
function scalar(x; default = nothing) function scalar(x; default = nothing)
x !== nothing || return default x !== nothing || return default
@ -119,9 +119,9 @@ function _from_json(json; repair = true)
end end
# Read reserves # Read reserves
if "Reserves2" in keys(json) if "Reserves" in keys(json)
for (reserve_name, dict) in json["Reserves2"] for (reserve_name, dict) in json["Reserves"]
reserve = Reserve( r = Reserve(
name = reserve_name, name = reserve_name,
type = lowercase(dict["Type"]), type = lowercase(dict["Type"]),
amount = timeseries(dict["Amount (MW)"]), amount = timeseries(dict["Amount (MW)"]),
@ -131,8 +131,8 @@ function _from_json(json; repair = true)
default = -1, default = -1,
), ),
) )
name_to_reserve[reserve_name] = reserve name_to_reserve[reserve_name] = r
push!(reserves2, reserve) push!(reserves, r)
end end
end end
@ -173,7 +173,7 @@ function _from_json(json; repair = true)
) )
end end
# Read reserves # Read reserve eligibility
unit_reserves = Reserve[] unit_reserves = Reserve[]
if "Reserve eligibility" in keys(dict) if "Reserve eligibility" in keys(dict)
unit_reserves = unit_reserves =
@ -213,10 +213,6 @@ function _from_json(json; repair = true)
scalar(dict["Shutdown limit (MW)"], default = 1e6), scalar(dict["Shutdown limit (MW)"], default = 1e6),
initial_status, initial_status,
initial_power, initial_power,
timeseries(
dict["Provides spinning reserves?"],
default = [true for t in 1:T],
),
startup_categories, startup_categories,
unit_reserves, unit_reserves,
) )
@ -228,13 +224,6 @@ function _from_json(json; repair = true)
push!(units, unit) push!(units, unit)
end end
# Read reserves
reserves = Reserves(zeros(T))
if "Reserves" in keys(json)
reserves.spinning =
timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T))
end
# Read transmission lines # Read transmission lines
if "Transmission lines" in keys(json) if "Transmission lines" in keys(json)
for (line_name, dict) in json["Transmission lines"] for (line_name, dict) in json["Transmission lines"]
@ -307,7 +296,6 @@ function _from_json(json; repair = true)
price_sensitive_loads_by_name = Dict(ps.name => ps for ps in loads), price_sensitive_loads_by_name = Dict(ps.name => ps for ps in loads),
price_sensitive_loads = loads, price_sensitive_loads = loads,
reserves = reserves, reserves = reserves,
reserves2 = reserves2,
reserves_by_name = name_to_reserve, reserves_by_name = name_to_reserve,
shortfall_penalty = shortfall_penalty, shortfall_penalty = shortfall_penalty,
time = T, time = T,

@ -44,7 +44,6 @@ mutable struct Unit
shutdown_limit::Float64 shutdown_limit::Float64
initial_status::Union{Int,Nothing} initial_status::Union{Int,Nothing}
initial_power::Union{Float64,Nothing} initial_power::Union{Float64,Nothing}
provides_spinning_reserves::Vector{Bool}
startup_categories::Vector{StartupCategory} startup_categories::Vector{StartupCategory}
reserves::Vector{Reserve} reserves::Vector{Reserve}
end end
@ -61,10 +60,6 @@ mutable struct TransmissionLine
flow_limit_penalty::Vector{Float64} flow_limit_penalty::Vector{Float64}
end end
mutable struct Reserves
spinning::Vector{Float64}
end
mutable struct Contingency mutable struct Contingency
name::String name::String
lines::Vector{TransmissionLine} lines::Vector{TransmissionLine}
@ -88,8 +83,7 @@ Base.@kwdef mutable struct UnitCommitmentInstance
power_balance_penalty::Vector{Float64} power_balance_penalty::Vector{Float64}
price_sensitive_loads_by_name::Dict{AbstractString,PriceSensitiveLoad} price_sensitive_loads_by_name::Dict{AbstractString,PriceSensitiveLoad}
price_sensitive_loads::Vector{PriceSensitiveLoad} price_sensitive_loads::Vector{PriceSensitiveLoad}
reserves::Reserves reserves::Vector{Reserve}
reserves2::Vector{Reserve}
reserves_by_name::Dict{AbstractString,Reserve} reserves_by_name::Dict{AbstractString,Reserve}
shortfall_penalty::Vector{Float64} shortfall_penalty::Vector{Float64}
time::Int time::Int

@ -28,42 +28,18 @@ function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
end end
function _add_reserve_eqs!(model::JuMP.Model)::Nothing function _add_reserve_eqs!(model::JuMP.Model)::Nothing
eq_min_reserve = _init(model, :eq_min_reserve)
instance = model[:instance] instance = model[:instance]
for t in 1:instance.time eq_min_reserve = _init(model, :eq_min_reserve)
# Equation (68) in Kneuven et al. (2020) for r in instance.reserves
# As in Morales-España et al. (2013a)
# Akin to the alternative formulation with max_power_avail
# from Carrión and Arroyo (2006) and Ostrowski et al. (2012)
shortfall_penalty = instance.shortfall_penalty[t]
eq_min_reserve[t] = @constraint(
model,
sum(model[:reserve][g.name, t] for g in instance.units) +
(shortfall_penalty >= 0 ? model[:reserve_shortfall][t] : 0.0) >=
instance.reserves.spinning[t]
)
# Account for shortfall contribution to objective
if shortfall_penalty >= 0
add_to_expression!(
model[:obj],
shortfall_penalty,
model[:reserve_shortfall][t],
)
end
end
eq_min_reserve2 = _init(model, :eq_min_reserve2)
for r in instance.reserves2
for t in 1:instance.time for t in 1:instance.time
# Equation (68) in Kneuven et al. (2020) # Equation (68) in Kneuven et al. (2020)
# As in Morales-España et al. (2013a) # As in Morales-España et al. (2013a)
# Akin to the alternative formulation with max_power_avail # Akin to the alternative formulation with max_power_avail
# from Carrión and Arroyo (2006) and Ostrowski et al. (2012) # from Carrión and Arroyo (2006) and Ostrowski et al. (2012)
eq_min_reserve2[r.name, t] = @constraint( eq_min_reserve[r.name, t] = @constraint(
model, model,
sum(model[:reserve2][r.name, g.name, t] for g in r.units) + sum(model[:reserve][r.name, g.name, t] for g in r.units) +
model[:reserve_shortfall2][r.name, t] >= r.amount[t] model[:reserve_shortfall][r.name, t] >= r.amount[t]
) )
# Account for shortfall contribution to objective # Account for shortfall contribution to objective
@ -71,7 +47,7 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing
add_to_expression!( add_to_expression!(
model[:obj], model[:obj],
r.shortfall_penalty, r.shortfall_penalty,
model[:reserve_shortfall2][r.name, t], model[:reserve_shortfall][r.name, t],
) )
end end
end end

@ -45,26 +45,13 @@ _is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0)
function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
reserve = _init(model, :reserve) reserve = _init(model, :reserve)
reserve_shortfall = _init(model, :reserve_shortfall) reserve_shortfall = _init(model, :reserve_shortfall)
for t in 1:model[:instance].time
if g.provides_spinning_reserves[t]
reserve[g.name, t] = @variable(model, lower_bound = 0)
else
reserve[g.name, t] = 0.0
end
reserve_shortfall[t] =
(model[:instance].shortfall_penalty[t] >= 0) ?
@variable(model, lower_bound = 0) : 0.0
end
reserve2 = _init(model, :reserve2)
reserve_shortfall2 = _init(model, :reserve_shortfall2)
for r in g.reserves for r in g.reserves
for t in 1:model[:instance].time for t in 1:model[:instance].time
reserve2[r.name, g.name, t] = @variable(model, lower_bound = 0) reserve[r.name, g.name, t] = @variable(model, lower_bound = 0)
if (r.name, t) keys(reserve_shortfall2) if (r.name, t) keys(reserve_shortfall)
reserve_shortfall2[r.name, t] = @variable(model, lower_bound = 0) reserve_shortfall[r.name, t] = @variable(model, lower_bound = 0)
if r.shortfall_penalty < 0 if r.shortfall_penalty < 0
set_upper_bound(reserve_shortfall2[r.name, t], 0.0) set_upper_bound(reserve_shortfall[r.name, t], 0.0)
end end
end end
end end
@ -225,10 +212,10 @@ end
function _total_reserves(model, g)::Vector function _total_reserves(model, g)::Vector
T = model[:instance].time T = model[:instance].time
reserve = [model[:reserve][g.name, t] for t in 1:T] reserve = 0.0
if !isempty(g.reserves) if !isempty(g.reserves)
reserve += [ reserve += [
sum(model[:reserve2][r.name, g.name, t] for r in g.reserves) for sum(model[:reserve][r.name, g.name, t] for r in g.reserves) for
t in 1:model[:instance].time t in 1:model[:instance].time
] ]
end end

@ -50,13 +50,6 @@ function solution(model::JuMP.Model)::OrderedDict
sol["Is on"] = timeseries(model[:is_on], instance.units) sol["Is on"] = timeseries(model[:is_on], instance.units)
sol["Switch on"] = timeseries(model[:switch_on], instance.units) sol["Switch on"] = timeseries(model[:switch_on], instance.units)
sol["Switch off"] = timeseries(model[:switch_off], instance.units) sol["Switch off"] = timeseries(model[:switch_off], instance.units)
sol["Reserve (MW)"] = timeseries(model[:reserve], instance.units)
sol["Reserve shortfall (MW)"] = OrderedDict(
t =>
(instance.shortfall_penalty[t] >= 0) ?
round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for
t in 1:instance.time
)
sol["Net injection (MW)"] = sol["Net injection (MW)"] =
timeseries(model[:net_injection], instance.buses) timeseries(model[:net_injection], instance.buses)
sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses) sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses)
@ -67,19 +60,19 @@ function solution(model::JuMP.Model)::OrderedDict
sol["Price-sensitive loads (MW)"] = sol["Price-sensitive loads (MW)"] =
timeseries(model[:loads], instance.price_sensitive_loads) timeseries(model[:loads], instance.price_sensitive_loads)
end end
sol["Reserve 2 (MW)"] = OrderedDict( sol["Reserve (MW)"] = OrderedDict(
r.name => OrderedDict( r.name => OrderedDict(
g.name => [ g.name => [
value(model[:reserve2][r.name, g.name, t]) for value(model[:reserve][r.name, g.name, t]) for
t in 1:instance.time t in 1:instance.time
] for g in r.units ] for g in r.units
) for r in instance.reserves2 ) for r in instance.reserves
) )
sol["Reserve shortfall 2 (MW)"] = OrderedDict( sol["Reserve shortfall (MW)"] = OrderedDict(
r.name => [ r.name => [
value(model[:reserve_shortfall2][r.name, t]) for value(model[:reserve_shortfall][r.name, t]) for
t in 1:instance.time t in 1:instance.time
] for r in instance.reserves2 ] for r in instance.reserves
) )
return sol return sol
end end

@ -329,30 +329,12 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01)
err_count += 1 err_count += 1
end end
# Verify spinning reserves
reserve =
sum(solution["Reserve (MW)"][g.name][t] for g in instance.units)
reserve_shortfall =
(instance.shortfall_penalty[t] >= 0) ?
solution["Reserve shortfall (MW)"][t] : 0
if reserve + reserve_shortfall < instance.reserves.spinning[t] - tol
@error @sprintf(
"Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)",
t,
reserve,
reserve_shortfall,
instance.reserves.spinning[t],
)
err_count += 1
end
# Verify reserves # Verify reserves
for r in instance.reserves2 for r in instance.reserves
provided = sum( provided = sum(
solution["Reserve 2 (MW)"][r.name][g.name][t] for g in r.units solution["Reserve (MW)"][r.name][g.name][t] for g in r.units
) )
shortfall = solution["Reserve shortfall 2 (MW)"][r.name][t] shortfall = solution["Reserve shortfall (MW)"][r.name][t]
required = r.amount[t] required = r.amount[t]
if provided + shortfall < required - tol if provided + shortfall < required - tol

Loading…
Cancel
Save