mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 16:28:51 -06:00
Add reserve shortfall penalty
This commit is contained in:
@@ -100,7 +100,7 @@ function _from_json(json; repair = true)
|
||||
)
|
||||
shortfall_penalty = timeseries(
|
||||
json["Parameters"]["Reserve shortfall penalty (\$/MW)"],
|
||||
default = [0.0 for t in 1:T],
|
||||
default = [-1.0 for t in 1:T],
|
||||
)
|
||||
|
||||
# Read buses
|
||||
|
||||
@@ -68,30 +68,28 @@ Constraints
|
||||
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
||||
instance = model[:instance]
|
||||
eq_min_reserve = _init(model, :eq_min_reserve)
|
||||
instance = model[:instance]
|
||||
for t in 1:instance.time
|
||||
# Equation (68) in Knueven et al. (2020)
|
||||
# Equation (68) in Kneuven et al. (2020)
|
||||
# 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 > 1e-7 ? model[:reserve_shortfall][t] : 0.0
|
||||
) >= instance.reserves.spinning[t]
|
||||
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 > 1e-7
|
||||
if shortfall_penalty >= 0
|
||||
add_to_expression!(
|
||||
model[:obj],
|
||||
shortfall_penalty,
|
||||
model[:reserve_shortfall][t],
|
||||
)
|
||||
else
|
||||
# Not added to the model at all
|
||||
#fix(model.vars.reserve_shortfall[t], 0.; force=true)
|
||||
end
|
||||
end # loop over time
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -75,6 +75,9 @@ function _add_reserve_vars!(
|
||||
reserve[g.name, t] = 0.0
|
||||
end
|
||||
end
|
||||
reserve_shortfall[t] =
|
||||
(model[:instance].shortfall_penalty[t] >= 0) ?
|
||||
@variable(model, lower_bound = 0) : 0.0
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -51,6 +51,12 @@ function solution(model::JuMP.Model)::OrderedDict
|
||||
sol["Switch on"] = timeseries(model[:switch_on], 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)"] =
|
||||
timeseries(model[:net_injection], instance.buses)
|
||||
sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses)
|
||||
|
||||
@@ -324,11 +324,16 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01)
|
||||
# Verify spinning reserves
|
||||
reserve =
|
||||
sum(solution["Reserve (MW)"][g.name][t] for g in instance.units)
|
||||
if reserve < instance.reserves.spinning[t] - tol
|
||||
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 should be %.2f)",
|
||||
"Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)",
|
||||
t,
|
||||
reserve,
|
||||
reserve_shortfall,
|
||||
instance.reserves.spinning[t],
|
||||
)
|
||||
err_count += 1
|
||||
|
||||
Reference in New Issue
Block a user