|
|
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
|
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
|
|
# Released under the modified BSD license. See COPYING.md for more details.
|
|
|
|
|
|
"""
|
|
|
_add_ramp_eqs!
|
|
|
|
|
|
Ensure constraints on ramping are met.
|
|
|
Based on Damcı-Kurt et al. (2016).
|
|
|
Eqns. (35), (36) in Kneuven et al. (2020).
|
|
|
|
|
|
Variables
|
|
|
---
|
|
|
* :prod_above
|
|
|
* :reserve
|
|
|
* :is_on
|
|
|
* :switch_on
|
|
|
* :switch_off],
|
|
|
|
|
|
Constraints
|
|
|
---
|
|
|
* :eq_str_ramp_up
|
|
|
* :eq_str_ramp_down
|
|
|
"""
|
|
|
function _add_ramp_eqs!(
|
|
|
model::JuMP.Model,
|
|
|
g::Unit,
|
|
|
formulation_prod_vars::Gar1962.ProdVars,
|
|
|
formulation_ramping::DamKucRajAta2016.Ramping,
|
|
|
formulation_status_vars::Gar1962.StatusVars,
|
|
|
)::Nothing
|
|
|
# TODO: Move upper case constants to model[:instance]
|
|
|
RESERVES_WHEN_START_UP = true
|
|
|
RESERVES_WHEN_RAMP_UP = true
|
|
|
RESERVES_WHEN_RAMP_DOWN = true
|
|
|
RESERVES_WHEN_SHUT_DOWN = true
|
|
|
is_initially_on = _is_initially_on(g)
|
|
|
|
|
|
# The following are the same for generator g across all time periods
|
|
|
SU = g.startup_limit # startup rate
|
|
|
SD = g.shutdown_limit # shutdown rate
|
|
|
RU = g.ramp_up_limit # ramp up rate
|
|
|
RD = g.ramp_down_limit # ramp down rate
|
|
|
|
|
|
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]
|
|
|
|
|
|
# Gar1962.ProdVars
|
|
|
prod_above = model[:prod_above]
|
|
|
|
|
|
# Gar1962.StatusVars
|
|
|
is_on = model[:is_on]
|
|
|
switch_off = model[:switch_off]
|
|
|
switch_on = model[:switch_on]
|
|
|
|
|
|
for t in 1:model[:instance].time
|
|
|
time_invariant =
|
|
|
(t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true
|
|
|
|
|
|
# if t > 1 && !time_invariant
|
|
|
# @warn(
|
|
|
# "Ramping according to Damcı-Kurt et al. (2016) requires " *
|
|
|
# "time-invariant minimum power. This does not hold for " *
|
|
|
# "generator $(gn): min_power[$t] = $(g.min_power[t]); " *
|
|
|
# "min_power[$(t-1)] = $(g.min_power[t-1]). Reverting to " *
|
|
|
# "Arroyo and Conejo (2000) formulation for this generator.",
|
|
|
# )
|
|
|
# end
|
|
|
|
|
|
max_prod_this_period =
|
|
|
prod_above[gn, t] + (
|
|
|
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
|
|
|
reserve[gn, t] : 0.0
|
|
|
)
|
|
|
min_prod_last_period = 0.0
|
|
|
if t > 1 && time_invariant
|
|
|
min_prod_last_period = prod_above[gn, t-1]
|
|
|
|
|
|
# Equation (35) in Kneuven et al. (2020)
|
|
|
# Sparser version of (24)
|
|
|
eq_str_ramp_up[gn, t] = @constraint(
|
|
|
model,
|
|
|
max_prod_this_period - min_prod_last_period <=
|
|
|
(SU - g.min_power[t] - RU) * switch_on[gn, t] +
|
|
|
RU * is_on[gn, t]
|
|
|
)
|
|
|
elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant)
|
|
|
if t > 1
|
|
|
min_prod_last_period =
|
|
|
prod_above[gn, t-1] + g.min_power[t-1] * is_on[gn, t-1]
|
|
|
else
|
|
|
min_prod_last_period = max(g.initial_power, 0.0)
|
|
|
end
|
|
|
|
|
|
# Add the min prod at time t back in to max_prod_this_period to get _total_ production
|
|
|
# (instead of using the amount above minimum, as min prod for t < 1 is unknown)
|
|
|
max_prod_this_period += g.min_power[t] * is_on[gn, t]
|
|
|
|
|
|
# Modified version of equation (35) in Kneuven et al. (2020)
|
|
|
# Equivalent to (24)
|
|
|
eq_str_ramp_up[gn, t] = @constraint(
|
|
|
model,
|
|
|
max_prod_this_period - min_prod_last_period <=
|
|
|
(SU - RU) * switch_on[gn, t] + RU * is_on[gn, t]
|
|
|
)
|
|
|
end
|
|
|
|
|
|
max_prod_last_period =
|
|
|
min_prod_last_period + (
|
|
|
t > 1 && (RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN) ?
|
|
|
reserve[gn, t-1] : 0.0
|
|
|
)
|
|
|
min_prod_this_period = prod_above[gn, t]
|
|
|
on_last_period = 0.0
|
|
|
if t > 1
|
|
|
on_last_period = is_on[gn, t-1]
|
|
|
elseif is_initially_on
|
|
|
on_last_period = 1.0
|
|
|
end
|
|
|
|
|
|
if t > 1 && time_invariant
|
|
|
# Equation (36) in Kneuven et al. (2020)
|
|
|
eq_str_ramp_down[gn, t] = @constraint(
|
|
|
model,
|
|
|
max_prod_last_period - min_prod_this_period <=
|
|
|
(SD - g.min_power[t] - RD) * switch_off[gn, t] +
|
|
|
RD * on_last_period
|
|
|
)
|
|
|
elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant)
|
|
|
# Add back in min power
|
|
|
min_prod_this_period += g.min_power[t] * is_on[gn, t]
|
|
|
|
|
|
# Modified version of equation (36) in Kneuven et al. (2020)
|
|
|
# Equivalent to (25)
|
|
|
eq_str_ramp_down[gn, t] = @constraint(
|
|
|
model,
|
|
|
max_prod_last_period - min_prod_this_period <=
|
|
|
(SD - RD) * switch_off[gn, t] + RD * on_last_period
|
|
|
)
|
|
|
end
|
|
|
end
|
|
|
end
|