mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 16:28:51 -06:00
Implement new reserves
This commit is contained in:
@@ -19,10 +19,10 @@ function _add_ramp_eqs!(
|
||||
RD = g.ramp_down_limit
|
||||
SU = g.startup_limit
|
||||
SD = g.shutdown_limit
|
||||
reserve = model[:reserve]
|
||||
eq_ramp_down = _init(model, :eq_ramp_down)
|
||||
eq_ramp_up = _init(model, :eq_ramp_up)
|
||||
is_initially_on = (g.initial_status > 0)
|
||||
reserve = _total_reserves(model, g)
|
||||
|
||||
# Gar1962.ProdVars
|
||||
prod_above = model[:prod_above]
|
||||
@@ -41,7 +41,7 @@ function _add_ramp_eqs!(
|
||||
model,
|
||||
g.min_power[t] +
|
||||
prod_above[gn, t] +
|
||||
(RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) <=
|
||||
(RESERVES_WHEN_RAMP_UP ? reserve[t] : 0.0) <=
|
||||
g.initial_power + RU
|
||||
)
|
||||
end
|
||||
@@ -51,7 +51,7 @@ function _add_ramp_eqs!(
|
||||
prod_above[gn, t] +
|
||||
(
|
||||
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
|
||||
reserve[gn, t] : 0.0
|
||||
reserve[t] : 0.0
|
||||
)
|
||||
min_prod_last_period =
|
||||
g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
|
||||
@@ -82,7 +82,7 @@ function _add_ramp_eqs!(
|
||||
prod_above[gn, t-1] +
|
||||
(
|
||||
RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ?
|
||||
reserve[gn, t-1] : 0.0
|
||||
reserve[t-1] : 0.0
|
||||
)
|
||||
min_prod_this_period =
|
||||
g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
|
||||
|
||||
@@ -23,7 +23,7 @@ function _add_ramp_eqs!(
|
||||
gn = g.name
|
||||
eq_str_ramp_down = _init(model, :eq_str_ramp_down)
|
||||
eq_str_ramp_up = _init(model, :eq_str_ramp_up)
|
||||
reserve = model[:reserve]
|
||||
reserve = _total_reserves(model, g)
|
||||
|
||||
# Gar1962.ProdVars
|
||||
prod_above = model[:prod_above]
|
||||
@@ -48,10 +48,8 @@ function _add_ramp_eqs!(
|
||||
# end
|
||||
|
||||
max_prod_this_period =
|
||||
prod_above[gn, t] + (
|
||||
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
|
||||
reserve[gn, t] : 0.0
|
||||
)
|
||||
prod_above[gn, t] +
|
||||
(RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[t] : 0.0)
|
||||
min_prod_last_period = 0.0
|
||||
if t > 1 && time_invariant
|
||||
min_prod_last_period = prod_above[gn, t-1]
|
||||
@@ -88,7 +86,7 @@ function _add_ramp_eqs!(
|
||||
max_prod_last_period =
|
||||
min_prod_last_period + (
|
||||
t > 1 && (RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN) ?
|
||||
reserve[gn, t-1] : 0.0
|
||||
reserve[t-1] : 0.0
|
||||
)
|
||||
min_prod_this_period = prod_above[gn, t]
|
||||
on_last_period = 0.0
|
||||
|
||||
@@ -26,7 +26,7 @@ function _add_production_limit_eqs!(
|
||||
eq_prod_limit = _init(model, :eq_prod_limit)
|
||||
is_on = model[:is_on]
|
||||
prod_above = model[:prod_above]
|
||||
reserve = model[:reserve]
|
||||
reserve = _total_reserves(model, g)
|
||||
gn = g.name
|
||||
for t in 1:model[:instance].time
|
||||
# Objective function terms for production costs
|
||||
@@ -44,7 +44,7 @@ function _add_production_limit_eqs!(
|
||||
end
|
||||
eq_prod_limit[gn, t] = @constraint(
|
||||
model,
|
||||
prod_above[gn, t] + reserve[gn, t] <= power_diff * is_on[gn, t]
|
||||
prod_above[gn, t] + reserve[t] <= power_diff * is_on[gn, t]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ function _add_ramp_eqs!(
|
||||
gn = g.name
|
||||
eq_ramp_down = _init(model, :eq_ramp_down)
|
||||
eq_ramp_up = _init(model, :eq_str_ramp_up)
|
||||
reserve = model[:reserve]
|
||||
reserve = _total_reserves(model, g)
|
||||
|
||||
# Gar1962.ProdVars
|
||||
prod_above = model[:prod_above]
|
||||
@@ -43,7 +43,7 @@ function _add_ramp_eqs!(
|
||||
model,
|
||||
g.min_power[t] +
|
||||
prod_above[gn, t] +
|
||||
(RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) <=
|
||||
(RESERVES_WHEN_RAMP_UP ? reserve[t] : 0.0) <=
|
||||
g.initial_power + RU
|
||||
)
|
||||
end
|
||||
@@ -61,7 +61,7 @@ function _add_ramp_eqs!(
|
||||
prod_above[gn, t] +
|
||||
(
|
||||
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
|
||||
reserve[gn, t] : 0.0
|
||||
reserve[t] : 0.0
|
||||
)
|
||||
min_prod_last_period =
|
||||
g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
|
||||
@@ -77,7 +77,7 @@ function _add_ramp_eqs!(
|
||||
eq_ramp_up[gn, t] = @constraint(
|
||||
model,
|
||||
prod_above[gn, t] +
|
||||
(RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) -
|
||||
(RESERVES_WHEN_RAMP_UP ? reserve[t] : 0.0) -
|
||||
prod_above[gn, t-1] <= RU
|
||||
)
|
||||
end
|
||||
@@ -105,7 +105,7 @@ function _add_ramp_eqs!(
|
||||
prod_above[gn, t-1] +
|
||||
(
|
||||
RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ?
|
||||
reserve[gn, t-1] : 0.0
|
||||
reserve[t-1] : 0.0
|
||||
)
|
||||
min_prod_this_period =
|
||||
g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
|
||||
@@ -121,7 +121,7 @@ function _add_ramp_eqs!(
|
||||
eq_ramp_down[gn, t] = @constraint(
|
||||
model,
|
||||
prod_above[gn, t-1] +
|
||||
(RESERVES_WHEN_RAMP_DOWN ? reserve[gn, t-1] : 0.0) -
|
||||
(RESERVES_WHEN_RAMP_DOWN ? reserve[t-1] : 0.0) -
|
||||
prod_above[gn, t] <= RD
|
||||
)
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ function _add_ramp_eqs!(
|
||||
# TODO: Move upper case constants to model[:instance]
|
||||
RESERVES_WHEN_SHUT_DOWN = true
|
||||
gn = g.name
|
||||
reserve = model[:reserve]
|
||||
reserve = _total_reserves(model, g)
|
||||
eq_str_prod_limit = _init(model, :eq_str_prod_limit)
|
||||
eq_prod_limit_ramp_up_extra_period =
|
||||
_init(model, :eq_prod_limit_ramp_up_extra_period)
|
||||
@@ -56,7 +56,7 @@ function _add_ramp_eqs!(
|
||||
model,
|
||||
prod_above[gn, t] +
|
||||
g.min_power[t] * is_on[gn, t] +
|
||||
reserve[gn, t] <=
|
||||
reserve[t] <=
|
||||
Pbar * is_on[gn, t] -
|
||||
(t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum(
|
||||
(Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
|
||||
@@ -71,7 +71,7 @@ function _add_ramp_eqs!(
|
||||
model,
|
||||
prod_above[gn, t] +
|
||||
g.min_power[t] * is_on[gn, t] +
|
||||
reserve[gn, t] <=
|
||||
reserve[t] <=
|
||||
Pbar * is_on[gn, t] - sum(
|
||||
(Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
|
||||
i in 0:min(UT - 1, TRU, t - 1)
|
||||
@@ -88,7 +88,7 @@ function _add_ramp_eqs!(
|
||||
model,
|
||||
prod_above[gn, t] +
|
||||
g.min_power[t] * is_on[gn, t] +
|
||||
(RESERVES_WHEN_SHUT_DOWN ? reserve[gn, t] : 0.0) <=
|
||||
(RESERVES_WHEN_SHUT_DOWN ? reserve[t] : 0.0) <=
|
||||
Pbar * is_on[gn, t] - sum(
|
||||
(Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for
|
||||
i in 0:KSD
|
||||
|
||||
@@ -52,5 +52,29 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
eq_min_reserve2 = _init(model, :eq_min_reserve2)
|
||||
for r in instance.reserves2
|
||||
for t in 1:instance.time
|
||||
# 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)
|
||||
eq_min_reserve2[r.name, t] = @constraint(
|
||||
model,
|
||||
sum(model[:reserve2][r.name, g.name, t] for g in r.units) +
|
||||
model[:reserve_shortfall2][r.name, t] >= r.amount[t]
|
||||
)
|
||||
|
||||
# Account for shortfall contribution to objective
|
||||
if r.shortfall_penalty >= 0
|
||||
add_to_expression!(
|
||||
model[:obj],
|
||||
r.shortfall_penalty,
|
||||
model[:reserve_shortfall2][r.name, t],
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -55,13 +55,19 @@ function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
|
||||
(model[:instance].shortfall_penalty[t] >= 0) ?
|
||||
@variable(model, lower_bound = 0) : 0.0
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function _add_reserve_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
reserve = model[:reserve]
|
||||
for t in 1:model[:instance].time
|
||||
add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0)
|
||||
reserve2 = _init(model, :reserve2)
|
||||
reserve_shortfall2 = _init(model, :reserve_shortfall2)
|
||||
for r in g.reserves
|
||||
for t in 1:model[:instance].time
|
||||
reserve2[r.name, g.name, t] = @variable(model, lower_bound = 0)
|
||||
if (r.name, t) ∉ keys(reserve_shortfall2)
|
||||
reserve_shortfall2[r.name, t] = @variable(model, lower_bound = 0)
|
||||
if r.shortfall_penalty < 0
|
||||
set_upper_bound(reserve_shortfall2[r.name, t], 0.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -81,7 +87,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
eq_startup_limit = _init(model, :eq_startup_limit)
|
||||
is_on = model[:is_on]
|
||||
prod_above = model[:prod_above]
|
||||
reserve = model[:reserve]
|
||||
reserve = _total_reserves(model, g)
|
||||
switch_off = model[:switch_off]
|
||||
switch_on = model[:switch_on]
|
||||
T = model[:instance].time
|
||||
@@ -89,7 +95,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
# Startup limit
|
||||
eq_startup_limit[g.name, t] = @constraint(
|
||||
model,
|
||||
prod_above[g.name, t] + reserve[g.name, t] <=
|
||||
prod_above[g.name, t] + reserve[t] <=
|
||||
(g.max_power[t] - g.min_power[t]) * is_on[g.name, t] -
|
||||
max(0, g.max_power[t] - g.startup_limit) * switch_on[g.name, t]
|
||||
)
|
||||
@@ -117,7 +123,7 @@ function _add_ramp_eqs!(
|
||||
formulation::RampingFormulation,
|
||||
)::Nothing
|
||||
prod_above = model[:prod_above]
|
||||
reserve = model[:reserve]
|
||||
reserve = _total_reserves(model, g)
|
||||
eq_ramp_up = _init(model, :eq_ramp_up)
|
||||
eq_ramp_down = _init(model, :eq_ramp_down)
|
||||
for t in 1:model[:instance].time
|
||||
@@ -126,14 +132,14 @@ function _add_ramp_eqs!(
|
||||
if _is_initially_on(g) == 1
|
||||
eq_ramp_up[g.name, t] = @constraint(
|
||||
model,
|
||||
prod_above[g.name, t] + reserve[g.name, t] <=
|
||||
prod_above[g.name, t] + reserve[t] <=
|
||||
(g.initial_power - g.min_power[t]) + g.ramp_up_limit
|
||||
)
|
||||
end
|
||||
else
|
||||
eq_ramp_up[g.name, t] = @constraint(
|
||||
model,
|
||||
prod_above[g.name, t] + reserve[g.name, t] <=
|
||||
prod_above[g.name, t] + reserve[t] <=
|
||||
prod_above[g.name, t-1] + g.ramp_up_limit
|
||||
)
|
||||
end
|
||||
@@ -216,3 +222,15 @@ function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function _total_reserves(model, g)::Vector
|
||||
T = model[:instance].time
|
||||
reserve = [model[:reserve][g.name, t] for t in 1:T]
|
||||
if !isempty(g.reserves)
|
||||
reserve += [
|
||||
sum(model[:reserve2][r.name, g.name, t] for r in g.reserves) for
|
||||
t in 1:model[:instance].time
|
||||
]
|
||||
end
|
||||
return reserve
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user