mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 00:08:52 -06:00
Remove old reserves
This commit is contained in:
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
|
||||||
|
|||||||
Reference in New Issue
Block a user