new formatting

This commit is contained in:
oyurdakul
2022-04-01 15:22:42 +02:00
parent febb4f1aad
commit b4bc50c865
41 changed files with 487 additions and 256 deletions

View File

@@ -32,7 +32,7 @@ function _add_ramp_eqs!(
switch_off = model[:switch_off]
switch_on = model[:switch_on]
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Ramp up limit
if t == 1
if is_initially_on
@@ -49,8 +49,12 @@ function _add_ramp_eqs!(
max_prod_this_period =
g.min_power[t] * is_on[gn, t] +
prod_above[gn, t] +
(RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0)
min_prod_last_period = g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
(
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
reserve[gn, t] : 0.0
)
min_prod_last_period =
g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
# Equation (24) in Kneuven et al. (2020)
eq_ramp_up[gn, t] = @constraint(
@@ -77,10 +81,11 @@ function _add_ramp_eqs!(
g.min_power[t-1] * is_on[gn, t-1] +
prod_above[gn, t-1] +
(
RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? reserve[gn, t-1] :
0.0
RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ?
reserve[gn, t-1] : 0.0
)
min_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
min_prod_this_period =
g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
# Equation (25) in Kneuven et al. (2020)
eq_ramp_down[gn, t] = @constraint(

View File

@@ -18,16 +18,18 @@ function _add_production_piecewise_linear_eqs!(
prod_above = model[:prod_above]
K = length(g.cost_segments)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
gn = g.name
for k = 1:K
for k in 1:K
# Equation (45) in Kneuven et al. (2020)
# NB: when reading instance, UnitCommitment.jl already calculates
# difference between max power for segments k and k-1 so the
# value of cost_segments[k].mw[t] is the max production *for
# that segment*
eq_segprod_limit[gn, t, k] =
@constraint(model, segprod[gn, t, k] <= g.cost_segments[k].mw[t])
eq_segprod_limit[gn, t, k] = @constraint(
model,
segprod[gn, t, k] <= g.cost_segments[k].mw[t]
)
# Also add this as an explicit upper bound on segprod to make the
# solver's work a bit easier
@@ -35,12 +37,18 @@ function _add_production_piecewise_linear_eqs!(
# Definition of production
# Equation (43) in Kneuven et al. (2020)
eq_prod_above_def[gn, t] =
@constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K))
eq_prod_above_def[gn, t] = @constraint(
model,
prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
)
# Objective function
# Equation (44) in Kneuven et al. (2020)
add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t])
add_to_expression!(
model[:obj],
segprod[gn, t, k],
g.cost_segments[k].cost[t],
)
end
end
end

View File

@@ -33,8 +33,9 @@ function _add_ramp_eqs!(
switch_off = model[:switch_off]
switch_on = model[:switch_on]
for t = 1:model[:instance].time
time_invariant = (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true
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(
@@ -47,8 +48,10 @@ 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[gn, t] : 0.0
)
min_prod_last_period = 0.0
if t > 1 && time_invariant
min_prod_last_period = prod_above[gn, t-1]
@@ -58,7 +61,8 @@ function _add_ramp_eqs!(
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]
(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
@@ -99,7 +103,8 @@ function _add_ramp_eqs!(
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
(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

View File

@@ -9,8 +9,8 @@ function _add_production_vars!(
)::Nothing
prod_above = _init(model, :prod_above)
segprod = _init(model, :segprod)
for t = 1:model[:instance].time
for k = 1:length(g.cost_segments)
for t in 1:model[:instance].time
for k in 1:length(g.cost_segments)
segprod[g.name, t, k] = @variable(model, lower_bound = 0)
end
prod_above[g.name, t] = @variable(model, lower_bound = 0)
@@ -28,7 +28,7 @@ function _add_production_limit_eqs!(
prod_above = model[:prod_above]
reserve = model[:reserve]
gn = g.name
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Objective function terms for production costs
# Part of (69) of Kneuven et al. (2020) as C^R_g * u_g(t) term
add_to_expression!(model[:obj], is_on[gn, t], g.min_power_cost[t])

View File

@@ -21,13 +21,15 @@ function _add_production_piecewise_linear_eqs!(
is_on = model[:is_on]
K = length(g.cost_segments)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Definition of production
# Equation (43) in Kneuven et al. (2020)
eq_prod_above_def[gn, t] =
@constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K))
eq_prod_above_def[gn, t] = @constraint(
model,
prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
)
for k = 1:K
for k in 1:K
# Equation (42) in Kneuven et al. (2020)
# Without this, solvers will add a lot of implied bound cuts to
# have this same effect.
@@ -46,7 +48,11 @@ function _add_production_piecewise_linear_eqs!(
# Objective function
# Equation (44) in Kneuven et al. (2020)
add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t])
add_to_expression!(
model[:obj],
segprod[gn, t, k],
g.cost_segments[k].cost[t],
)
end
end
return

View File

@@ -10,7 +10,7 @@ function _add_status_vars!(
is_on = _init(model, :is_on)
switch_on = _init(model, :switch_on)
switch_off = _init(model, :switch_off)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
if g.must_run[t]
is_on[g.name, t] = 1.0
switch_on[g.name, t] = (t == 1 ? 1.0 - _is_initially_on(g) : 0.0)
@@ -34,7 +34,7 @@ function _add_status_eqs!(
is_on = model[:is_on]
switch_off = model[:switch_off]
switch_on = model[:switch_on]
for t = 1:model[:instance].time
for t in 1:model[:instance].time
if !g.must_run[t]
# Link binary variables
if t == 1
@@ -51,8 +51,10 @@ function _add_status_eqs!(
)
end
# Cannot switch on and off at the same time
eq_switch_on_off[g.name, t] =
@constraint(model, switch_on[g.name, t] + switch_off[g.name, t] <= 1)
eq_switch_on_off[g.name, t] = @constraint(
model,
switch_on[g.name, t] + switch_off[g.name, t] <= 1
)
end
end
return

View File

@@ -26,12 +26,12 @@ function _add_production_piecewise_linear_eqs!(
switch_on = model[:switch_on]
switch_off = model[:switch_off]
for t = 1:T
for k = 1:K
for t in 1:T
for k in 1:K
# Pbar^{k-1)
Pbar0 =
g.min_power[t] +
(k > 1 ? sum(g.cost_segments[ell].mw[t] for ell = 1:k-1) : 0.0)
(k > 1 ? sum(g.cost_segments[ell].mw[t] for ell in 1:k-1) : 0.0)
# Pbar^k
Pbar1 = g.cost_segments[k].mw[t] + Pbar0
@@ -61,7 +61,8 @@ function _add_production_piecewise_linear_eqs!(
eq_segprod_limit_a[gn, t, k] = @constraint(
model,
segprod[gn, t, k] <=
g.cost_segments[k].mw[t] * is_on[gn, t] - Cv * switch_on[gn, t] -
g.cost_segments[k].mw[t] * is_on[gn, t] -
Cv * switch_on[gn, t] -
(t < T ? Cw * switch_off[gn, t+1] : 0.0)
)
else
@@ -69,7 +70,8 @@ function _add_production_piecewise_linear_eqs!(
eq_segprod_limit_b[gn, t, k] = @constraint(
model,
segprod[gn, t, k] <=
g.cost_segments[k].mw[t] * is_on[gn, t] - Cv * switch_on[gn, t] -
g.cost_segments[k].mw[t] * is_on[gn, t] -
Cv * switch_on[gn, t] -
(t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0)
)
@@ -85,12 +87,18 @@ function _add_production_piecewise_linear_eqs!(
# Definition of production
# Equation (43) in Kneuven et al. (2020)
eq_prod_above_def[gn, t] =
@constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K))
eq_prod_above_def[gn, t] = @constraint(
model,
prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
)
# Objective function
# Equation (44) in Kneuven et al. (2020)
add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t])
add_to_expression!(
model[:obj],
segprod[gn, t, k],
g.cost_segments[k].cost[t],
)
# Also add an explicit upper bound on segprod to make the solver's
# work a bit easier

View File

@@ -32,8 +32,9 @@ function _add_ramp_eqs!(
switch_off = model[:switch_off]
switch_on = model[:switch_on]
for t = 1:model[:instance].time
time_invariant = (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true
for t in 1:model[:instance].time
time_invariant =
(t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true
# Ramp up limit
if t == 1
@@ -59,8 +60,8 @@ function _add_ramp_eqs!(
g.min_power[t] * is_on[gn, t] +
prod_above[gn, t] +
(
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] :
0.0
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
reserve[gn, t] : 0.0
)
min_prod_last_period =
g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
@@ -75,7 +76,8 @@ function _add_ramp_eqs!(
# prod_above[gn, t] when starting up, and creates diff with (24).
eq_ramp_up[gn, t] = @constraint(
model,
prod_above[gn, t] + (RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) -
prod_above[gn, t] +
(RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) -
prod_above[gn, t-1] <= RU
)
end
@@ -105,7 +107,8 @@ function _add_ramp_eqs!(
RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ?
reserve[gn, t-1] : 0.0
)
min_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
min_prod_this_period =
g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
eq_ramp_down[gn, t] = @constraint(
model,
max_prod_last_period - min_prod_this_period <=

View File

@@ -11,27 +11,30 @@ function _add_startup_cost_eqs!(
eq_startup_restrict = _init(model, :eq_startup_restrict)
S = length(g.startup_categories)
startup = model[:startup]
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# If unit is switching on, we must choose a startup category
eq_startup_choose[g.name, t] = @constraint(
model,
model[:switch_on][g.name, t] == sum(startup[g.name, t, s] for s = 1:S)
model[:switch_on][g.name, t] ==
sum(startup[g.name, t, s] for s in 1:S)
)
for s = 1:S
for s in 1:S
# If unit has not switched off in the last `delay` time periods, startup category is forbidden.
# The last startup category is always allowed.
if s < S
range_start = t - g.startup_categories[s+1].delay + 1
range_end = t - g.startup_categories[s].delay
range = (range_start:range_end)
initial_sum =
(g.initial_status < 0 && (g.initial_status + 1 in range) ? 1.0 : 0.0)
initial_sum = (
g.initial_status < 0 && (g.initial_status + 1 in range) ? 1.0 : 0.0
)
eq_startup_restrict[g.name, t, s] = @constraint(
model,
startup[g.name, t, s] <=
initial_sum +
sum(model[:switch_off][g.name, i] for i in range if i >= 1)
initial_sum + sum(
model[:switch_off][g.name, i] for i in range if i >= 1
)
)
end

View File

@@ -14,8 +14,10 @@ function _add_ramp_eqs!(
gn = g.name
reserve = model[:reserve]
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)
eq_prod_limit_shutdown_trajectory = _init(model, :eq_prod_limit_shutdown_trajectory)
eq_prod_limit_ramp_up_extra_period =
_init(model, :eq_prod_limit_ramp_up_extra_period)
eq_prod_limit_shutdown_trajectory =
_init(model, :eq_prod_limit_shutdown_trajectory)
UT = g.min_uptime
SU = g.startup_limit # startup rate, i.e., max production right after startup
SD = g.shutdown_limit # shutdown rate, i.e., max production right before shutdown
@@ -31,7 +33,7 @@ function _add_ramp_eqs!(
switch_off = model[:switch_off]
switch_on = model[:switch_on]
for t = 1:T
for t in 1:T
Pbar = g.max_power[t]
if Pbar < 1e-7
# Skip this time period if max power = 0
@@ -52,10 +54,13 @@ function _add_ramp_eqs!(
# then switch_off[gn, t+1] = 0
eq_str_prod_limit[gn, t] = @constraint(
model,
prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + reserve[gn, t] <=
Pbar * is_on[gn, t] - (t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum(
prod_above[gn, t] +
g.min_power[t] * is_on[gn, t] +
reserve[gn, 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
i = 0:min(UT - 2, TRU, t - 1)
i in 0:min(UT - 2, TRU, t - 1)
)
)
@@ -64,10 +69,12 @@ function _add_ramp_eqs!(
# Covers an additional time period of the ramp-up trajectory, compared to (38)
eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint(
model,
prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + reserve[gn, t] <=
prod_above[gn, t] +
g.min_power[t] * is_on[gn, t] +
reserve[gn, t] <=
Pbar * is_on[gn, t] - sum(
(Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
i = 0:min(UT - 1, TRU, t - 1)
i in 0:min(UT - 1, TRU, t - 1)
)
)
end
@@ -82,9 +89,13 @@ function _add_ramp_eqs!(
prod_above[gn, t] +
g.min_power[t] * is_on[gn, t] +
(RESERVES_WHEN_SHUT_DOWN ? reserve[gn, t] : 0.0) <=
Pbar * is_on[gn, t] -
sum((Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for i = 0:KSD) -
sum((Pbar - (SU + i * RU)) * switch_on[gn, t-i] for i = 0:KSU) - (
Pbar * is_on[gn, t] - sum(
(Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for
i in 0:KSD
) - sum(
(Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
i in 0:KSU
) - (
(KSU >= TRU || KSU > t - 2) ? 0.0 :
max(0, (SU + (KSU + 1) * RU) - (SD + TRD * RD)) *
switch_on[gn, t-(KSU+1)]

View File

@@ -8,7 +8,7 @@ function _add_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing
mfg = _init(model, :mfg)
dwflexiramp = _init(model, :dwflexiramp)
dwflexiramp_shortfall = _init(model, :dwflexiramp_shortfall)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# maximum feasible generation, \bar{g_{its}} in Wang & Hobbs (2016)
mfg[g.name, t] = @variable(model, lower_bound = 0)
if g.provides_flexiramp_reserves[t]
@@ -51,28 +51,37 @@ function _add_ramp_eqs!(
dwflexiramp = model[:dwflexiramp]
mfg = model[:mfg]
for t = 1:model[:instance].time
@constraint(model, prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t]) # Eq. (19) in Wang & Hobbs (2016)
for t in 1:model[:instance].time
@constraint(
model,
prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t]
) # Eq. (19) in Wang & Hobbs (2016)
@constraint(model, mfg[gn, t] <= is_on[gn, t] * maxp[t]) # Eq. (22) in Wang & Hobbs (2016)
if t != model[:instance].time
@constraint(
model,
minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <=
prod_above[gn, t] - dwflexiramp[gn, t] + (is_on[gn, t] * minp[t])
prod_above[gn, t] - dwflexiramp[gn, t] +
(is_on[gn, t] * minp[t])
) # first inequality of Eq. (20) in Wang & Hobbs (2016)
@constraint(
model,
prod_above[gn, t] - dwflexiramp[gn, t] + (is_on[gn, t] * minp[t]) <=
prod_above[gn, t] - dwflexiramp[gn, t] +
(is_on[gn, t] * minp[t]) <=
mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1]))
) # second inequality of Eq. (20) in Wang & Hobbs (2016)
@constraint(
model,
minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <=
prod_above[gn, t] + upflexiramp[gn, t] + (is_on[gn, t] * minp[t])
prod_above[gn, t] +
upflexiramp[gn, t] +
(is_on[gn, t] * minp[t])
) # first inequality of Eq. (21) in Wang & Hobbs (2016)
@constraint(
model,
prod_above[gn, t] + upflexiramp[gn, t] + (is_on[gn, t] * minp[t]) <=
prod_above[gn, t] +
upflexiramp[gn, t] +
(is_on[gn, t] * minp[t]) <=
mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1]))
) # second inequality of Eq. (21) in Wang & Hobbs (2016)
if t != 1
@@ -104,7 +113,8 @@ function _add_ramp_eqs!(
) # Eq. (23) in Wang & Hobbs (2016) for the first time period
@constraint(
model,
initial_power - (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <=
initial_power -
(prod_above[gn, t] + (is_on[gn, t] * minp[t])) <=
RD * is_on[gn, t] +
SD * (is_initially_on - is_on[gn, t]) +
maxp[t] * (1 - is_initially_on)
@@ -113,7 +123,8 @@ function _add_ramp_eqs!(
@constraint(
model,
mfg[gn, t] <=
(SD * (is_on[gn, t] - is_on[gn, t+1])) + (maxp[t] * is_on[gn, t+1])
(SD * (is_on[gn, t] - is_on[gn, t+1])) +
(maxp[t] * is_on[gn, t+1])
) # Eq. (24) in Wang & Hobbs (2016)
@constraint(
model,
@@ -141,13 +152,15 @@ function _add_ramp_eqs!(
) # second inequality of Eq. (27) in Wang & Hobbs (2016)
@constraint(
model,
-maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <= upflexiramp[gn, t]
-maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <=
upflexiramp[gn, t]
) # first inequality of Eq. (28) in Wang & Hobbs (2016)
@constraint(model, upflexiramp[gn, t] <= maxp[t] * is_on[gn, t+1]) # second inequality of Eq. (28) in Wang & Hobbs (2016)
@constraint(model, -maxp[t] * is_on[gn, t+1] <= dwflexiramp[gn, t]) # first inequality of Eq. (29) in Wang & Hobbs (2016)
@constraint(
model,
dwflexiramp[gn, t] <= (maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1])
dwflexiramp[gn, t] <=
(maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1])
) # second inequality of Eq. (29) in Wang & Hobbs (2016)
else
@constraint(

View File

@@ -5,12 +5,13 @@
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
net_injection = _init(model, :expr_net_injection)
curtail = _init(model, :curtail)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Fixed load
net_injection[b.name, t] = AffExpr(-b.load[t])
# Load curtailment
curtail[b.name, t] = @variable(model, lower_bound = 0, upper_bound = b.load[t])
curtail[b.name, t] =
@variable(model, lower_bound = 0, upper_bound = b.load[t])
add_to_expression!(net_injection[b.name, t], curtail[b.name, t], 1.0)
add_to_expression!(

View File

@@ -8,9 +8,13 @@ function _add_transmission_line!(
f::ShiftFactorsFormulation,
)::Nothing
overflow = _init(model, :overflow)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
overflow[lm.name, t] = @variable(model, lower_bound = 0)
add_to_expression!(model[:obj], overflow[lm.name, t], lm.flow_limit_penalty[t])
add_to_expression!(
model[:obj],
overflow[lm.name, t],
lm.flow_limit_penalty[t],
)
end
return
end

View File

@@ -2,18 +2,26 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
function _add_price_sensitive_load!(model::JuMP.Model, ps::PriceSensitiveLoad)::Nothing
function _add_price_sensitive_load!(
model::JuMP.Model,
ps::PriceSensitiveLoad,
)::Nothing
loads = _init(model, :loads)
net_injection = _init(model, :expr_net_injection)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Decision variable
loads[ps.name, t] = @variable(model, lower_bound = 0, upper_bound = ps.demand[t])
loads[ps.name, t] =
@variable(model, lower_bound = 0, upper_bound = ps.demand[t])
# Objective function terms
add_to_expression!(model[:obj], loads[ps.name, t], -ps.revenue[t])
# Net injection
add_to_expression!(net_injection[ps.bus.name, t], loads[ps.name, t], -1.0)
add_to_expression!(
net_injection[ps.bus.name, t],
loads[ps.name, t],
-1.0,
)
end
return
end

View File

@@ -13,7 +13,10 @@ M[l.offset, b.offset] indicates the amount of power (in MW) that flows through
transmission line l when 1 MW of power is injected at the slack bus (the bus
that has offset zero) and withdrawn from b.
"""
function _injection_shift_factors(; buses::Array{Bus}, lines::Array{TransmissionLine})
function _injection_shift_factors(;
buses::Array{Bus},
lines::Array{TransmissionLine},
)
susceptance = _susceptance_matrix(lines)
incidence = _reduced_incidence_matrix(lines = lines, buses = buses)
laplacian = transpose(incidence) * susceptance * incidence
@@ -30,7 +33,10 @@ is the number of buses and L is the number of lines. For each row, there is a 1
element and a -1 element, indicating the source and target buses, respectively,
for that line.
"""
function _reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine})
function _reduced_incidence_matrix(;
buses::Array{Bus},
lines::Array{TransmissionLine},
)
matrix = spzeros(Float64, length(lines), length(buses) - 1)
for line in lines
if line.source.offset > 0
@@ -69,7 +75,7 @@ function _line_outage_factors(;
incidence = Array(_reduced_incidence_matrix(lines = lines, buses = buses))
lodf::Array{Float64,2} = isf * transpose(incidence)
_, n = size(lodf)
for i = 1:n
for i in 1:n
lodf[:, i] *= 1.0 / (1.0 - lodf[i, i])
lodf[i, i] = -1
end

View File

@@ -25,7 +25,14 @@ struct Formulation
status_vars::StatusVarsFormulation = Gar1962.StatusVars(),
transmission::TransmissionFormulation = ShiftFactorsFormulation(),
)
return new(prod_vars, pwl_costs, ramping, startup_costs, status_vars, transmission)
return new(
prod_vars,
pwl_costs,
ramping,
startup_costs,
status_vars,
transmission,
)
end
end

View File

@@ -14,12 +14,12 @@ function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
net_injection = _init(model, :net_injection)
eq_net_injection = _init(model, :eq_net_injection)
eq_power_balance = _init(model, :eq_power_balance)
for t = 1:T, b in model[:instance].buses
for t in 1:T, b in model[:instance].buses
n = net_injection[b.name, t] = @variable(model)
eq_net_injection[b.name, t] =
@constraint(model, -n + model[:expr_net_injection][b.name, t] == 0)
end
for t = 1:T
for t in 1:T
eq_power_balance[t] = @constraint(
model,
sum(net_injection[b.name, t] for b in model[:instance].buses) == 0
@@ -31,7 +31,7 @@ end
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
eq_min_reserve = _init(model, :eq_min_reserve)
instance = model[:instance]
for t = 1:instance.time
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
@@ -46,7 +46,11 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing
# Account for shortfall contribution to objective
if shortfall_penalty >= 0
add_to_expression!(model[:obj], shortfall_penalty, model[:reserve_shortfall][t])
add_to_expression!(
model[:obj],
shortfall_penalty,
model[:reserve_shortfall][t],
)
end
end
return
@@ -61,20 +65,24 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing
eq_min_upflexiramp = _init(model, :eq_min_upflexiramp)
eq_min_dwflexiramp = _init(model, :eq_min_dwflexiramp)
instance = model[:instance]
for t = 1:instance.time
for t in 1:instance.time
flexiramp_shortfall_penalty = instance.flexiramp_shortfall_penalty[t]
# Eq. (17) in Wang & Hobbs (2016)
eq_min_upflexiramp[t] = @constraint(
model,
sum(model[:upflexiramp][g.name, t] for g in instance.units) + (
flexiramp_shortfall_penalty >= 0 ? model[:upflexiramp_shortfall][t] : 0.0
sum(model[:upflexiramp][g.name, t] for g in instance.units) +
(
flexiramp_shortfall_penalty >= 0 ?
model[:upflexiramp_shortfall][t] : 0.0
) >= instance.reserves.upflexiramp[t]
)
# Eq. (18) in Wang & Hobbs (2016)
eq_min_dwflexiramp[t] = @constraint(
model,
sum(model[:dwflexiramp][g.name, t] for g in instance.units) + (
flexiramp_shortfall_penalty >= 0 ? model[:dwflexiramp_shortfall][t] : 0.0
sum(model[:dwflexiramp][g.name, t] for g in instance.units) +
(
flexiramp_shortfall_penalty >= 0 ?
model[:dwflexiramp_shortfall][t] : 0.0
) >= instance.reserves.dwflexiramp[t]
)
@@ -83,7 +91,10 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing
add_to_expression!(
model[:obj],
flexiramp_shortfall_penalty,
(model[:upflexiramp_shortfall][t] + model[:dwflexiramp_shortfall][t]),
(
model[:upflexiramp_shortfall][t] +
model[:dwflexiramp_shortfall][t]
),
)
end
end

View File

@@ -46,7 +46,7 @@ _is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0)
function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
reserve = _init(model, :reserve)
reserve_shortfall = _init(model, :reserve_shortfall)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
if g.provides_spinning_reserves[t]
reserve[g.name, t] = @variable(model, lower_bound = 0)
else
@@ -61,7 +61,7 @@ end
function _add_reserve_eqs!(model::JuMP.Model, g::Unit)::Nothing
reserve = model[:reserve]
for t = 1:model[:instance].time
for t in 1:model[:instance].time
add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0)
end
return
@@ -69,8 +69,8 @@ end
function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing
startup = _init(model, :startup)
for t = 1:model[:instance].time
for s = 1:length(g.startup_categories)
for t in 1:model[:instance].time
for s in 1:length(g.startup_categories)
startup[g.name, t, s] = @variable(model, binary = true)
end
end
@@ -86,7 +86,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
switch_off = model[:switch_off]
switch_on = model[:switch_on]
T = model[:instance].time
for t = 1:T
for t in 1:T
# Startup limit
eq_startup_limit[g.name, t] = @constraint(
model,
@@ -96,14 +96,16 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
)
# Shutdown limit
if g.initial_power > g.shutdown_limit
eq_shutdown_limit[g.name, 0] = @constraint(model, switch_off[g.name, 1] <= 0)
eq_shutdown_limit[g.name, 0] =
@constraint(model, switch_off[g.name, 1] <= 0)
end
if t < T
eq_shutdown_limit[g.name, t] = @constraint(
model,
prod_above[g.name, t] <=
(g.max_power[t] - g.min_power[t]) * is_on[g.name, t] -
max(0, g.max_power[t] - g.shutdown_limit) * switch_off[g.name, t+1]
max(0, g.max_power[t] - g.shutdown_limit) *
switch_off[g.name, t+1]
)
end
end
@@ -119,7 +121,7 @@ function _add_ramp_eqs!(
reserve = model[:reserve]
eq_ramp_up = _init(model, :eq_ramp_up)
eq_ramp_down = _init(model, :eq_ramp_down)
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Ramp up limit
if t == 1
if _is_initially_on(g) == 1
@@ -149,7 +151,8 @@ function _add_ramp_eqs!(
else
eq_ramp_down[g.name, t] = @constraint(
model,
prod_above[g.name, t] >= prod_above[g.name, t-1] - g.ramp_down_limit
prod_above[g.name, t] >=
prod_above[g.name, t-1] - g.ramp_down_limit
)
end
end
@@ -162,18 +165,18 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing
eq_min_uptime = _init(model, :eq_min_uptime)
eq_min_downtime = _init(model, :eq_min_downtime)
T = model[:instance].time
for t = 1:T
for t in 1:T
# Minimum up-time
eq_min_uptime[g.name, t] = @constraint(
model,
sum(switch_on[g.name, i] for i in (t-g.min_uptime+1):t if i >= 1) <=
is_on[g.name, t]
sum(switch_on[g.name, i] for i in (t-g.min_uptime+1):t if i >= 1) <= is_on[g.name, t]
)
# Minimum down-time
eq_min_downtime[g.name, t] = @constraint(
model,
sum(switch_off[g.name, i] for i in (t-g.min_downtime+1):t if i >= 1) <=
1 - is_on[g.name, t]
sum(
switch_off[g.name, i] for i in (t-g.min_downtime+1):t if i >= 1
) <= 1 - is_on[g.name, t]
)
# Minimum up/down-time for initial periods
if t == 1
@@ -200,7 +203,7 @@ end
function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing
expr_net_injection = model[:expr_net_injection]
for t = 1:model[:instance].time
for t in 1:model[:instance].time
# Add to net injection expression
add_to_expression!(
expr_net_injection[g.bus.name, t],