Documented reserve shortfall, soved comments on variables/constraints to structs.jl files, simplified loops, removed extra comments, started replacement of constant-subsitution with @constraint (with option to use fix).

pull/13/head
Aleksandr Kazachkov 4 years ago
parent 77f2f625fd
commit 9649387561

@ -28,13 +28,14 @@ Each section is described in detail below. For a complete example, see [case14](
### Parameters ### Parameters
This section describes system-wide parameters, such as power balance penalties, optimization parameters, such as the length of the planning horizon and the time. This section describes system-wide parameters, such as power balance and reserve shortfall penalties, and optimization parameters, such as the length of the planning horizon and the time.
| Key | Description | Default | Time series? | Key | Description | Default | Time series?
| :----------------------------- | :------------------------------------------------ | :------: | :------------: | :----------------------------- | :------------------------------------------------ | :------: | :------------:
| `Time horizon (h)` | Length of the planning horizon (in hours). | Required | N | `Time horizon (h)` | Length of the planning horizon (in hours). | Required | N
| `Time step (min)` | Length of each time step (in minutes). Must be a divisor of 60 (e.g. 60, 30, 20, 15, etc). | `60` | N | `Time step (min)` | Length of each time step (in minutes). Must be a divisor of 60 (e.g. 60, 30, 20, 15, etc). | `60` | N
| `Power balance penalty ($/MW)` | Penalty for system-wide shortage or surplus in production (in $/MW). This is charged per time step. For example, if there is a shortage of 1 MW for three time steps, three times this amount will be charged. | `1000.0` | Y | `Power balance penalty ($/MW)` | Penalty for system-wide shortage or surplus in production (in $/MW). This is charged per time step. For example, if there is a shortage of 1 MW for three time steps, three times this amount will be charged. | `1000.0` | Y
| `Reserve shortfall penalty (\$/MW)` | Penalty for system-wide shortage in meeting reserve requirements (in $/MW). This is charged per time step. | `0` | Y
#### Example #### Example
@ -43,6 +44,7 @@ This section describes system-wide parameters, such as power balance penalties,
"Parameters": { "Parameters": {
"Time horizon (h)": 4, "Time horizon (h)": 4,
"Power balance penalty ($/MW)": 1000.0 "Power balance penalty ($/MW)": 1000.0
"Reserve shortfall penalty ($/MW)": 0.0
} }
} }
``` ```

@ -3,24 +3,13 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
""" """
_add_ramp_eqs! _add_ramp_eqs!(model, unit, formulation_prod_vars, formulation_ramping, formulation_status_vars)::Nothing
Ensure constraints on ramping are met. Ensure constraints on ramping are met.
Based on Arroyo and Conejo (2000). Based on Arroyo and Conejo (2000).
Eqns. (24), (25) in Kneuven et al. (2020). Eqns. (24), (25) in Knueven et al. (2020).
Variables Adds constraints identified by `ArrCon200.Ramping` to `model` using variables `Gar1962.ProdVars` and `is_on` from `Gar1962.StatusVars`.
---
* :is_on
* :switch_off
* :switch_on
* :prod_above
* :reserve
Constraints
---
* :eq_ramp_up
* :eq_ramp_down
""" """
function _add_ramp_eqs!( function _add_ramp_eqs!(
model::JuMP.Model, model::JuMP.Model,
@ -75,7 +64,7 @@ function _add_ramp_eqs!(
min_prod_last_period = min_prod_last_period =
g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
# Equation (24) in Kneuven et al. (2020) # Equation (24) in Knueven et al. (2020)
eq_ramp_up[gn, t] = @constraint( eq_ramp_up[gn, t] = @constraint(
model, model,
max_prod_this_period - min_prod_last_period <= max_prod_this_period - min_prod_last_period <=
@ -106,7 +95,7 @@ function _add_ramp_eqs!(
min_prod_this_period = min_prod_this_period =
g.min_power[t] * is_on[gn, t] + prod_above[gn, t] g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
# Equation (25) in Kneuven et al. (2020) # Equation (25) in Knueven et al. (2020)
eq_ramp_down[gn, t] = @constraint( eq_ramp_down[gn, t] = @constraint(
model, model,
max_prod_last_period - min_prod_this_period <= max_prod_last_period - min_prod_this_period <=

@ -13,6 +13,12 @@ module ArrCon2000
import ..RampingFormulation import ..RampingFormulation
"""
Constraints
---
* `eq_ramp_up`: Equation (24) in Knueven et al. (2020)
* `eq_ramp_down`: Equation (25) in Knueven et al. (2020)
"""
struct Ramping <: RampingFormulation end struct Ramping <: RampingFormulation end
end end

@ -6,23 +6,7 @@
_add_production_piecewise_linear_eqs! _add_production_piecewise_linear_eqs!
Ensure respect of production limits along each segment. Ensure respect of production limits along each segment.
Based on Garver (1962) and Carrión and Arryo (2006), Creates constraints `CarArr2006.PwlCosts` using variables `Gar1962.StatusVars`
which replaces (42) in Kneuven et al. (2020) with a weaker version missing the on/off variable.
Equations (45), (43), (44) 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*.
===
Variables
* :segprod
* :is_on
* :prod_above
===
Constraints
* :eq_prod_above_def
* :eq_segprod_limit
""" """
function _add_production_piecewise_linear_eqs!( function _add_production_piecewise_linear_eqs!(
model::JuMP.Model, model::JuMP.Model,
@ -43,7 +27,7 @@ function _add_production_piecewise_linear_eqs!(
for t in 1:model[:instance].time for t in 1:model[:instance].time
gn = g.name gn = g.name
for k in 1:K for k in 1:K
# Equation (45) in Kneuven et al. (2020) # Equation (45) in Knueven et al. (2020)
# NB: when reading instance, UnitCommitment.jl already calculates # NB: when reading instance, UnitCommitment.jl already calculates
# difference between max power for segments k and k-1 so the # difference between max power for segments k and k-1 so the
# value of cost_segments[k].mw[t] is the max production *for # value of cost_segments[k].mw[t] is the max production *for
@ -58,14 +42,14 @@ function _add_production_piecewise_linear_eqs!(
set_upper_bound(segprod[gn, t, k], g.cost_segments[k].mw[t]) set_upper_bound(segprod[gn, t, k], g.cost_segments[k].mw[t])
# Definition of production # Definition of production
# Equation (43) in Kneuven et al. (2020) # Equation (43) in Knueven et al. (2020)
eq_prod_above_def[gn, t] = @constraint( eq_prod_above_def[gn, t] = @constraint(
model, model,
prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K) prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
) )
# Objective function # Objective function
# Equation (44) in Kneuven et al. (2020) # Equation (44) in Knueven et al. (2020)
add_to_expression!( add_to_expression!(
model[:obj], model[:obj],
segprod[gn, t, k], segprod[gn, t, k],

@ -14,6 +14,16 @@ module CarArr2006
import ..PiecewiseLinearCostsFormulation import ..PiecewiseLinearCostsFormulation
"""
Based on Garver (1962) and Carrión and Arryo (2006),
which replaces (42) in Knueven et al. (2020) with a weaker version missing the on/off variable.
Equations (45), (43), (44) in Knueven et al. (2020).
Constraints
---
* `eq_prod_above_def`: Equation (43) in Knueven et al. (2020)
* `eq_segprod_limit`: Equation (45) in Knueven et al. (2020)
"""
struct PwlCosts <: PiecewiseLinearCostsFormulation end struct PwlCosts <: PiecewiseLinearCostsFormulation end
end end

@ -7,7 +7,7 @@
Ensure constraints on ramping are met. Ensure constraints on ramping are met.
Based on Damcı-Kurt et al. (2016). Based on Damcı-Kurt et al. (2016).
Eqns. (35), (36) in Kneuven et al. (2020). Eqns. (35), (36) in Knueven et al. (2020).
Variables Variables
--- ---
@ -78,7 +78,7 @@ function _add_ramp_eqs!(
if t > 1 && time_invariant if t > 1 && time_invariant
min_prod_last_period = prod_above[gn, t-1] min_prod_last_period = prod_above[gn, t-1]
# Equation (35) in Kneuven et al. (2020) # Equation (35) in Knueven et al. (2020)
# Sparser version of (24) # Sparser version of (24)
eq_str_ramp_up[gn, t] = @constraint( eq_str_ramp_up[gn, t] = @constraint(
model, model,
@ -98,7 +98,7 @@ function _add_ramp_eqs!(
# (instead of using the amount above minimum, as min prod for t < 1 is unknown) # (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] max_prod_this_period += g.min_power[t] * is_on[gn, t]
# Modified version of equation (35) in Kneuven et al. (2020) # Modified version of equation (35) in Knueven et al. (2020)
# Equivalent to (24) # Equivalent to (24)
eq_str_ramp_up[gn, t] = @constraint( eq_str_ramp_up[gn, t] = @constraint(
model, model,
@ -121,7 +121,7 @@ function _add_ramp_eqs!(
end end
if t > 1 && time_invariant if t > 1 && time_invariant
# Equation (36) in Kneuven et al. (2020) # Equation (36) in Knueven et al. (2020)
eq_str_ramp_down[gn, t] = @constraint( eq_str_ramp_down[gn, t] = @constraint(
model, model,
max_prod_last_period - min_prod_this_period <= max_prod_last_period - min_prod_this_period <=
@ -132,7 +132,7 @@ function _add_ramp_eqs!(
# Add back in min power # Add back in min power
min_prod_this_period += g.min_power[t] * is_on[gn, t] min_prod_this_period += g.min_power[t] * is_on[gn, t]
# Modified version of equation (36) in Kneuven et al. (2020) # Modified version of equation (36) in Knueven et al. (2020)
# Equivalent to (25) # Equivalent to (25)
eq_str_ramp_down[gn, t] = @constraint( eq_str_ramp_down[gn, t] = @constraint(
model, model,

@ -5,7 +5,7 @@
""" """
_add_production_vars!(model, unit, formulation_prod_vars) _add_production_vars!(model, unit, formulation_prod_vars)
Creates variables `:prod_above` and `:segprod`. Adds symbols identified by `Gar1962.ProdVars` to `model`.
""" """
function _add_production_vars!( function _add_production_vars!(
model::JuMP.Model, model::JuMP.Model,
@ -28,7 +28,7 @@ end
Ensure production limit constraints are met. Ensure production limit constraints are met.
Based on Garver (1962) and Morales-España et al. (2013). Based on Garver (1962) and Morales-España et al. (2013).
Eqns. (18), part of (69) in Kneuven et al. (2020). Eqns. (18), part of (69) in Knueven et al. (2020).
=== ===
Variables Variables
@ -52,13 +52,13 @@ function _add_production_limit_eqs!(
gn = g.name gn = g.name
for t in 1:model[:instance].time for t in 1:model[:instance].time
# Objective function terms for production costs # Objective function terms for production costs
# Part of (69) of Kneuven et al. (2020) as C^R_g * u_g(t) term # Part of (69) of Knueven 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]) add_to_expression!(model[:obj], is_on[gn, t], g.min_power_cost[t])
# Production limit # Production limit
# Equation (18) in Kneuven et al. (2020) # Equation (18) in Knueven et al. (2020)
# as \bar{p}_g(t) \le \bar{P}_g u_g(t) # as \bar{p}_g(t) \le \bar{P}_g u_g(t)
# amk: this is a weaker version of (20) and (21) in Kneuven et al. (2020) # amk: this is a weaker version of (20) and (21) in Knueven et al. (2020)
# but keeping it here in case those are not present # but keeping it here in case those are not present
power_diff = max(g.max_power[t], 0.0) - max(g.min_power[t], 0.0) power_diff = max(g.max_power[t], 0.0) - max(g.min_power[t], 0.0)
if power_diff < 1e-7 if power_diff < 1e-7

@ -7,7 +7,7 @@
Ensure respect of production limits along each segment. Ensure respect of production limits along each segment.
Based on Garver (1962). Based on Garver (1962).
Equations (42), (43), (44) in Kneuven et al. (2020). Equations (42), (43), (44) in Knueven et al. (2020).
NB: when reading instance, UnitCommitment.jl already calculates difference between max power for segments k and k-1, 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*. so the value of cost_segments[k].mw[t] is the max production *for that segment*.
@ -45,14 +45,14 @@ function _add_production_piecewise_linear_eqs!(
K = length(g.cost_segments) K = length(g.cost_segments)
for t in 1:model[:instance].time for t in 1:model[:instance].time
# Definition of production # Definition of production
# Equation (43) in Kneuven et al. (2020) # Equation (43) in Knueven et al. (2020)
eq_prod_above_def[gn, t] = @constraint( eq_prod_above_def[gn, t] = @constraint(
model, model,
prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K) prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
) )
for k in 1:K for k in 1:K
# Equation (42) in Kneuven et al. (2020) # Equation (42) in Knueven et al. (2020)
# Without this, solvers will add a lot of implied bound cuts to # Without this, solvers will add a lot of implied bound cuts to
# have this same effect. # have this same effect.
# NB: when reading instance, UnitCommitment.jl already calculates # NB: when reading instance, UnitCommitment.jl already calculates
@ -69,7 +69,7 @@ function _add_production_piecewise_linear_eqs!(
set_upper_bound(segprod[gn, t, k], g.cost_segments[k].mw[t]) set_upper_bound(segprod[gn, t, k], g.cost_segments[k].mw[t])
# Objective function # Objective function
# Equation (44) in Kneuven et al. (2020) # Equation (44) in Knueven et al. (2020)
add_to_expression!( add_to_expression!(
model[:obj], model[:obj],
segprod[gn, t, k], segprod[gn, t, k],

@ -5,39 +5,26 @@
""" """
_add_status_vars! _add_status_vars!
Create `is_on`, `switch_on`, and `switch_off` variables. Adds symbols identified by `Gar1962.StatusVars` to `model`.
Fix variables if a certain generator _must_ run or based on initial conditions. Fix variables if a certain generator _must_ run or based on initial conditions.
""" """
function _add_status_vars!( function _add_status_vars!(
model::JuMP.Model, model::JuMP.Model,
g::Unit, g::Unit,
formulation_status_vars::Gar1962.StatusVars, formulation_status_vars::Gar1962.StatusVars,
ALWAYS_CREATE_VARS = false,
)::Nothing )::Nothing
is_on = _init(model, :is_on) is_on = _init(model, :is_on)
switch_on = _init(model, :switch_on) switch_on = _init(model, :switch_on)
switch_off = _init(model, :switch_off) switch_off = _init(model, :switch_off)
FIX_VARS = !formulation_status_vars.fix_vars_via_constraint
for t in 1:model[:instance].time for t in 1:model[:instance].time
if ALWAYS_CREATE_VARS || !g.must_run[t]
is_on[g.name, t] = @variable(model, binary = true) is_on[g.name, t] = @variable(model, binary = true)
switch_on[g.name, t] = @variable(model, binary = true) switch_on[g.name, t] = @variable(model, binary = true)
switch_off[g.name, t] = @variable(model, binary = true) switch_off[g.name, t] = @variable(model, binary = true)
end
if ALWAYS_CREATE_VARS
# If variables are created, use initial conditions to fix some values
if t == 1
if _is_initially_on(g)
# Generator was on (for g.initial_status time periods),
# so cannot be more switched on until the period after the first time it can be turned off
fix(switch_on[g.name, 1], 0.0; force = true)
else
# Generator is initially off (for -g.initial_status time periods)
# Cannot be switched off more
fix(switch_off[g.name, 1], 0.0; force = true)
end
end
# Use initial conditions and whether a unit must run to fix variables
if FIX_VARS
# Fix variables using fix function
if g.must_run[t] if g.must_run[t]
# If the generator _must_ run, then it is obviously on and cannot be switched off # If the generator _must_ run, then it is obviously on and cannot be switched off
# In the first time period, force unit to switch on if was off before # In the first time period, force unit to switch on if was off before
@ -49,23 +36,32 @@ function _add_status_vars!(
force = true, force = true,
) )
fix(switch_off[g.name, t], 0.0; force = true) fix(switch_off[g.name, t], 0.0; force = true)
end elseif t == 1
else
# If vars are not created, then replace them by a constant
if t == 1
if _is_initially_on(g) if _is_initially_on(g)
switch_on[g.name, t] = 0.0 # Generator was on (for g.initial_status time periods),
# so cannot be more switched on until the period after the first time it can be turned off
fix(switch_on[g.name, 1], 0.0; force = true)
else else
switch_off[g.name, t] = 0.0 # Generator is initially off (for -g.initial_status time periods)
# Cannot be switched off more
fix(switch_off[g.name, 1], 0.0; force = true)
end end
end end
else
# Add explicit constraint if !FIX_VARS
if g.must_run[t] if g.must_run[t]
is_on[g.name, t] = 1.0 is_on[g.name, t] = 1.0
switch_on[g.name, t] = switch_on[g.name, t] =
(t == 1 ? 1.0 - _is_initially_on(g) : 0.0) (t == 1 ? 1.0 - _is_initially_on(g) : 0.0)
switch_off[g.name, t] = 0.0 switch_off[g.name, t] = 0.0
elseif t == 1
if _is_initially_on(g)
switch_on[g.name, t] = 0.0
else
switch_off[g.name, t] = 0.0
end
end
end end
end # check if ALWAYS_CREATE_VARS
end end
return return
end end
@ -73,16 +69,12 @@ end
""" """
_add_status_eqs! _add_status_eqs!
Variables Creates constraints `eq_binary_link` and `eq_switch_on_off` using variables in `Gar1962.StatusVars`.
---
* is_on
* switch_off
* switch_on
Constraints Constraints
--- ---
* eq_binary_link * `eq_binary_link`
* eq_switch_on_off * `eq_switch_on_off`
""" """
function _add_status_eqs!( function _add_status_eqs!(
model::JuMP.Model, model::JuMP.Model,
@ -100,7 +92,7 @@ function _add_status_eqs!(
end end
# Link binary variables # Link binary variables
# Equation (2) in Kneuven et al. (2020), originally from Garver (1962) # Equation (2) in Knueven et al. (2020), originally from Garver (1962)
if t == 1 if t == 1
eq_binary_link[g.name, t] = @constraint( eq_binary_link[g.name, t] = @constraint(
model, model,
@ -116,7 +108,7 @@ function _add_status_eqs!(
end end
# Cannot switch on and off at the same time # Cannot switch on and off at the same time
# amk: I am not sure this is in Kneuven et al. (2020) # amk: I am not sure this is in Knueven et al. (2020)
eq_switch_on_off[g.name, t] = @constraint( eq_switch_on_off[g.name, t] = @constraint(
model, model,
switch_on[g.name, t] + switch_off[g.name, t] <= 1 switch_on[g.name, t] + switch_off[g.name, t] <= 1

@ -17,8 +17,53 @@ import ..PiecewiseLinearCostsFormulation
import ..ProductionVarsFormulation import ..ProductionVarsFormulation
import ..StatusVarsFormulation import ..StatusVarsFormulation
"""
Variables
---
* `prod_above`:
[gen, t];
*production above minimum required level*;
lb: 0, ub: Inf.
KnuOstWat2020: `p'_g(t)`
* `segprod`:
[gen, segment, t];
*how much generator produces on cost segment in time t*;
lb: 0, ub: Inf.
KnuOstWat2020: `p_g^l(t)`
"""
struct ProdVars <: ProductionVarsFormulation end struct ProdVars <: ProductionVarsFormulation end
struct PwlCosts <: PiecewiseLinearCostsFormulation end struct PwlCosts <: PiecewiseLinearCostsFormulation end
struct StatusVars <: StatusVarsFormulation end
"""
Variables
---
* `is_on`:
[gen, t];
*is generator on at time t?*
lb: 0, ub: 1, binary.
KnuOstWat2020: `u_g(t)`
* `switch_on`:
[gen, t];
*indicator that generator will be turned on at t*;
lb: 0, ub: 1, binary.
KnuOstWat2020: `v_g(t)`
* `switch_off`: binary;
[gen, t];
*indicator that generator will be turned off at t*;
lb: 0, ub: 1, binary.
KnuOstWat2020: `w_g(t)`
Arguments
---
* `fix_vars_via_constraint`:
indicator for whether to set vars to a constant using `fix` or by adding an explicit constraint
(particulary useful for debugging purposes).
"""
struct StatusVars <: StatusVarsFormulation
fix_vars_via_constraint::Bool
StatusVars() = new(false)
end
end end

@ -6,23 +6,21 @@
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
Startup and shutdown limits from Gentile et al. (2017). Startup and shutdown limits from Gentile et al. (2017).
Eqns. (20), (23a), and (23b) in Kneuven et al. (2020). Eqns. (20), (23a), and (23b) in Knueven et al. (2020).
Variables Creates constraints `eq_startstop_limit`, `eq_startup_limit`, and `eq_shutdown_limit`
--- using variables `Gar1962.StatusVars`, `prod_above` from `Gar1962.ProdVars`, and `reserve`.
* :is_on
* :prod_above
* :reserve
* :switch_on
* :switch_off
Constraints Constraints
--- ---
* :eq_startstop_limit * `eq_startstop_limit`
* :eq_startup_limit * `eq_startup_limit`
* :eq_shutdown_limit * `eq_shutdown_limit`
""" """
function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing function _add_startup_shutdown_limit_eqs!(
model::JuMP.Model,
g::Unit,
formulation_status_vars::Gar1962.StatusVars)::Nothing
# TODO: Move upper case constants to model[:instance] # TODO: Move upper case constants to model[:instance]
RESERVES_WHEN_START_UP = true RESERVES_WHEN_START_UP = true
RESERVES_WHEN_RAMP_UP = true RESERVES_WHEN_RAMP_UP = true
@ -44,14 +42,19 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
if g.initial_power > g.shutdown_limit if g.initial_power > g.shutdown_limit
#eqs.shutdown_limit[gi, 0] = @constraint(mip, vars.switch_off[gi, 1] <= 0) #eqs.shutdown_limit[gi, 0] = @constraint(mip, vars.switch_off[gi, 1] <= 0)
if formulation_status_vars.always_create_vars
fix(switch_off[gi, 1], 0.0; force = true) fix(switch_off[gi, 1], 0.0; force = true)
@constraint(mip, vars.switch_off[gi, 1] <= 0)
else
switch_off[gi, 1] = 0.0
end
end end
for t in 1:T for t in 1:T
## 2020-10-09 amk: added eqn (20) and check of g.min_uptime ## 2020-10-09 amk: added eqn (20) and check of g.min_uptime
# Not present in (23) in Kneueven et al. # Not present in (23) in Kneueven et al.
if g.min_uptime > 1 if g.min_uptime > 1
# Equation (20) in Kneuven et al. (2020) # Equation (20) in Knueven et al. (2020)
eqs.startstop_limit[gi, t] = @constraint( eqs.startstop_limit[gi, t] = @constraint(
model, model,
prod_above[gi, t] + reserve[gi, t] <= prod_above[gi, t] + reserve[gi, t] <=
@ -64,7 +67,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
) )
else else
## Startup limits ## Startup limits
# Equation (23a) in Kneuven et al. (2020) # Equation (23a) in Knueven et al. (2020)
eqs.startup_limit[gi, t] = @constraint( eqs.startup_limit[gi, t] = @constraint(
model, model,
prod_above[gi, t] + reserve[gi, t] <= prod_above[gi, t] + reserve[gi, t] <=
@ -78,7 +81,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
## Shutdown limits ## Shutdown limits
if t < T if t < T
# Equation (23b) in Kneuven et al. (2020) # Equation (23b) in Knueven et al. (2020)
eqs.shutdown_limit[gi, t] = @constraint( eqs.shutdown_limit[gi, t] = @constraint(
model, model,
prod_above[gi, t] + reserve[gi, t] <= prod_above[gi, t] + reserve[gi, t] <=
@ -91,6 +94,6 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
switch_on[gi, t] switch_on[gi, t]
) )
end end
end # check if g.min_uptime > 1 end
end # loop over time end
end # _add_startup_shutdown_limit_eqs! end

@ -6,8 +6,8 @@
_add_production_piecewise_linear_eqs! _add_production_piecewise_linear_eqs!
Ensure respect of production limits along each segment. Ensure respect of production limits along each segment.
Based on Kneuven et al. (2018b). Based on Knueven et al. (2018b).
Eqns. (43), (44), (46), (48) in Kneuven et al. (2020). Eqns. (43), (44), (46), (48) in Knueven et al. (2020).
NB: when reading instance, UnitCommitment.jl already calculates difference between max power for segments k and k-1 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*. so the value of cost_segments[k].mw[t] is the max production *for that segment*.
@ -81,7 +81,7 @@ function _add_production_piecewise_linear_eqs!(
end end
if g.min_uptime > 1 if g.min_uptime > 1
# Equation (46) in Kneuven et al. (2020) # Equation (46) in Knueven et al. (2020)
eq_segprod_limit_a[gn, t, k] = @constraint( eq_segprod_limit_a[gn, t, k] = @constraint(
model, model,
segprod[gn, t, k] <= segprod[gn, t, k] <=
@ -90,7 +90,7 @@ function _add_production_piecewise_linear_eqs!(
(t < T ? Cw * switch_off[gn, t+1] : 0.0) (t < T ? Cw * switch_off[gn, t+1] : 0.0)
) )
else else
# Equation (47a)/(48a) in Kneuven et al. (2020) # Equation (47a)/(48a) in Knueven et al. (2020)
eq_segprod_limit_b[gn, t, k] = @constraint( eq_segprod_limit_b[gn, t, k] = @constraint(
model, model,
segprod[gn, t, k] <= segprod[gn, t, k] <=
@ -99,7 +99,7 @@ function _add_production_piecewise_linear_eqs!(
(t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0) (t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0)
) )
# Equation (47b)/(48b) in Kneuven et al. (2020) # Equation (47b)/(48b) in Knueven et al. (2020)
eq_segprod_limit_c[gn, t, k] = @constraint( eq_segprod_limit_c[gn, t, k] = @constraint(
model, model,
segprod[gn, t, k] <= segprod[gn, t, k] <=
@ -110,14 +110,14 @@ function _add_production_piecewise_linear_eqs!(
end end
# Definition of production # Definition of production
# Equation (43) in Kneuven et al. (2020) # Equation (43) in Knueven et al. (2020)
eq_prod_above_def[gn, t] = @constraint( eq_prod_above_def[gn, t] = @constraint(
model, model,
prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K) prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
) )
# Objective function # Objective function
# Equation (44) in Kneuven et al. (2020) # Equation (44) in Knueven et al. (2020)
add_to_expression!( add_to_expression!(
model[:obj], model[:obj],
segprod[gn, t, k], segprod[gn, t, k],

@ -6,7 +6,7 @@
_add_startup_cost_eqs! _add_startup_cost_eqs!
Extended formulation of startup costs using indicator variables Extended formulation of startup costs using indicator variables
based on Kneuven, Ostrowski, and Watson, 2020 based on Knueven, Ostrowski, and Watson, 2020
--- equations (59), (60), (61). --- equations (59), (60), (61).
Variables Variables
@ -58,7 +58,7 @@ function _add_startup_cost_eqs!(
# fix(vars.downtime_arc[gn, t, tmp_t], 0.; force = true) # fix(vars.downtime_arc[gn, t, tmp_t], 0.; force = true)
#end #end
# Equation (59) in Kneuven et al. (2020) # Equation (59) in Knueven et al. (2020)
# Relate downtime_arc with switch_on # Relate downtime_arc with switch_on
# "switch_on[g,t] >= x_g(t',t) for all t' \in [t-TC+1, t-DT]" # "switch_on[g,t] >= x_g(t',t) for all t' \in [t-TC+1, t-DT]"
eq_startup_at_t[gn, t] = @constraint( eq_startup_at_t[gn, t] = @constraint(
@ -69,7 +69,7 @@ function _add_startup_cost_eqs!(
) )
) )
# Equation (60) in Kneuven et al. (2020) # Equation (60) in Knueven et al. (2020)
# "switch_off[g,t] >= x_g(t,t') for all t' \in [t+DT, t+TC-1]" # "switch_off[g,t] >= x_g(t,t') for all t' \in [t+DT, t+TC-1]"
eqs.shutdown_at_t[gn, t] = @constraint( eqs.shutdown_at_t[gn, t] = @constraint(
model, model,
@ -80,7 +80,7 @@ function _add_startup_cost_eqs!(
) )
# Objective function terms for start-up costs # Objective function terms for start-up costs
# Equation (61) in Kneuven et al. (2020) # Equation (61) in Knueven et al. (2020)
default_category = S default_category = S
if initial_time_shutdown > 0 && t + initial_time_shutdown - 1 < TC if initial_time_shutdown > 0 && t + initial_time_shutdown - 1 < TC
for s in 1:S-1 for s in 1:S-1
@ -104,7 +104,7 @@ function _add_startup_cost_eqs!(
for s in 1:S-1 for s in 1:S-1
# Objective function terms for start-up costs # Objective function terms for start-up costs
# Equation (61) in Kneuven et al. (2020) # Equation (61) in Knueven et al. (2020)
# Says to replace the cost of last category with cost of category s # Says to replace the cost of last category with cost of category s
start_range = max((t - g.startup_categories[s+1].delay + 1), 1) start_range = max((t - g.startup_categories[s+1].delay + 1), 1)
end_range = min((t - g.startup_categories[s].delay), T - 1) end_range = min((t - g.startup_categories[s].delay), T - 1)

@ -6,9 +6,9 @@
_add_ramp_eqs! _add_ramp_eqs!
Ensure constraints on ramping are met. Ensure constraints on ramping are met.
Needs to be used in combination with shutdown rate constraints, e.g., (21b) in Kneuven et al. (2020). Needs to be used in combination with shutdown rate constraints, e.g., (21b) in Knueven et al. (2020).
Based on Morales-España, Latorre, and Ramos, 2013. Based on Morales-España, Latorre, and Ramos, 2013.
Eqns. (26)+(27) [replaced by (24)+(25) if time-varying min demand] in Kneuven et al. (2020). Eqns. (26)+(27) [replaced by (24)+(25) if time-varying min demand] in Knueven et al. (2020).
Variables Variables
--- ---
@ -92,7 +92,7 @@ function _add_ramp_eqs!(
RU * is_on[gn, t-1] + SU * switch_on[gn, t] RU * is_on[gn, t-1] + SU * switch_on[gn, t]
) )
else else
# Equation (26) in Kneuven et al. (2020) # Equation (26) in Knueven et al. (2020)
# TODO: what if RU < SU? places too stringent upper bound # TODO: what if RU < SU? places too stringent upper bound
# prod_above[gn, t] when starting up, and creates diff with (24). # prod_above[gn, t] when starting up, and creates diff with (24).
eq_ramp_up[gn, t] = @constraint( eq_ramp_up[gn, t] = @constraint(
@ -136,7 +136,7 @@ function _add_ramp_eqs!(
RD * is_on[gn, t] + SD * switch_off[gn, t] RD * is_on[gn, t] + SD * switch_off[gn, t]
) )
else else
# Equation (27) in Kneuven et al. (2020) # Equation (27) in Knueven et al. (2020)
# TODO: Similar to above, what to do if shutting down in time t # TODO: Similar to above, what to do if shutting down in time t
# and RD < SD? There is a difference with (25). # and RD < SD? There is a difference with (25).
eq_ramp_down[gn, t] = @constraint( eq_ramp_down[gn, t] = @constraint(

@ -8,7 +8,7 @@
Extended formulation of startup costs using indicator variables Extended formulation of startup costs using indicator variables
based on Muckstadt and Wilson, 1968; based on Muckstadt and Wilson, 1968;
this version by Morales-España, Latorre, and Ramos, 2013. this version by Morales-España, Latorre, and Ramos, 2013.
Eqns. (54), (55), and (56) in Kneuven et al. (2020). Eqns. (54), (55), and (56) in Knueven et al. (2020).
Note that the last 'constraint' is actually setting the objective. Note that the last 'constraint' is actually setting the objective.
\tstartup[gi,s,t] sum_{i=s.delay}^{(s+1).delay-1} switch_off[gi,t-i] \tstartup[gi,s,t] sum_{i=s.delay}^{(s+1).delay-1} switch_off[gi,t-i]
@ -48,7 +48,7 @@ function _add_startup_cost_eqs!(
gn = g.name gn = g.name
for t in 1:model[:instance].time for t in 1:model[:instance].time
# If unit is switching on, we must choose a startup category # If unit is switching on, we must choose a startup category
# Equation (55) in Kneuven et al. (2020) # Equation (55) in Knueven et al. (2020)
eq_startup_choose[gn, t] = @constraint( eq_startup_choose[gn, t] = @constraint(
model, model,
switch_on[gn, t] == sum(startup[gn, t, s] for s in 1:S) switch_on[gn, t] == sum(startup[gn, t, s] for s in 1:S)
@ -65,7 +65,7 @@ function _add_startup_cost_eqs!(
initial_sum = ( initial_sum = (
g.initial_status < 0 && (g.initial_status + 1 in range) ? 1.0 : 0.0 g.initial_status < 0 && (g.initial_status + 1 in range) ? 1.0 : 0.0
) )
# Change of index version of equation (54) in Kneuven et al. (2020): # Change of index version of equation (54) in Knueven et al. (2020):
# startup[gi,s,t] ≤ sum_{i=s.delay}^{(s+1).delay-1} switch_off[gi,t-i] # startup[gi,s,t] ≤ sum_{i=s.delay}^{(s+1).delay-1} switch_off[gi,t-i]
eq_startup_restrict[gn, t, s] = @constraint( eq_startup_restrict[gn, t, s] = @constraint(
model, model,
@ -76,7 +76,7 @@ function _add_startup_cost_eqs!(
end # if s < S (not the last category) end # if s < S (not the last category)
# Objective function terms for start-up costs # Objective function terms for start-up costs
# Equation (56) in Kneuven et al. (2020) # Equation (56) in Knueven et al. (2020)
add_to_expression!( add_to_expression!(
model[:obj], model[:obj],
startup[gn, t, s], startup[gn, t, s],

@ -6,7 +6,7 @@
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
Startup and shutdown limits from Morales-España et al. (2013a). Startup and shutdown limits from Morales-España et al. (2013a).
Eqns. (20), (21a), and (21b) in Kneuven et al. (2020). Eqns. (20), (21a), and (21b) in Knueven et al. (2020).
Variables Variables
--- ---
@ -44,7 +44,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
for t in 1:T for t in 1:T
## 2020-10-09 amk: added eqn (20) and check of g.min_uptime ## 2020-10-09 amk: added eqn (20) and check of g.min_uptime
if g.min_uptime > 1 && t < T if g.min_uptime > 1 && t < T
# Equation (20) in Kneuven et al. (2020) # Equation (20) in Knueven et al. (2020)
# UT > 1 required, to guarantee that vars.switch_on[gi, t] and vars.switch_off[gi, t+1] are not both = 1 at the same time # UT > 1 required, to guarantee that vars.switch_on[gi, t] and vars.switch_off[gi, t+1] are not both = 1 at the same time
eq_startstop_limit[gi, t] = @constraint( eq_startstop_limit[gi, t] = @constraint(
model, model,
@ -55,7 +55,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
) )
else else
## Startup limits ## Startup limits
# Equation (21a) in Kneuven et al. (2020) # Equation (21a) in Knueven et al. (2020)
# Proposed by Morales-España et al. (2013a) # Proposed by Morales-España et al. (2013a)
eqs_startup_limit[gi, t] = @constraint( eqs_startup_limit[gi, t] = @constraint(
model, model,
@ -66,7 +66,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
## Shutdown limits ## Shutdown limits
if t < T if t < T
# Equation (21b) in Kneuven et al. (2020) # Equation (21b) in Knueven et al. (2020)
# TODO different from what was in previous model, due to reserve variable # TODO different from what was in previous model, due to reserve variable
# ax: ideally should have reserve_up and reserve_down variables # ax: ideally should have reserve_up and reserve_down variables
# i.e., the generator should be able to increase/decrease production as specified # i.e., the generator should be able to increase/decrease production as specified

@ -7,7 +7,7 @@
Ensure constraints on ramping are met. Ensure constraints on ramping are met.
Based on Ostrowski, Anjos, Vannelli (2012). Based on Ostrowski, Anjos, Vannelli (2012).
Eqn (37) in Kneuven et al. (2020). Eqn (37) in Knueven et al. (2020).
Variables Variables
--- ---
@ -59,7 +59,7 @@ function _add_ramp_eqs!(
Pbar = g.max_power[t] Pbar = g.max_power[t]
#TRD = floor((Pbar - SU)/RD) #TRD = floor((Pbar - SU)/RD)
# TODO check amk changed TRD wrt Kneuven et al. # TODO check amk changed TRD wrt Knueven et al.
TRD = ceil((Pbar - SD) / RD) # ramp down time TRD = ceil((Pbar - SD) / RD) # ramp down time
if Pbar < 1e-7 if Pbar < 1e-7
@ -68,7 +68,7 @@ function _add_ramp_eqs!(
end end
if UT >= 1 if UT >= 1
# Equation (37) in Kneuven et al. (2020) # Equation (37) in Knueven et al. (2020)
KSD = min(TRD, UT - 1, T - t - 1) KSD = min(TRD, UT - 1, T - t - 1)
eq_str_prod_limit[gn, t] = @constraint( eq_str_prod_limit[gn, t] = @constraint(
model, model,

@ -7,8 +7,8 @@
Add tighter upper bounds on production based on ramp-down trajectory. Add tighter upper bounds on production based on ramp-down trajectory.
Based on (28) in Pan and Guan (2016). Based on (28) in Pan and Guan (2016).
But there is an extra time period covered using (40) of Kneuven et al. (2020). But there is an extra time period covered using (40) of Knueven et al. (2020).
Eqns. (38), (40), (41) in Kneuven et al. (2020). Eqns. (38), (40), (41) in Knueven et al. (2020).
Variables Variables
--- ---
@ -63,14 +63,14 @@ function _add_ramp_eqs!(
end end
#TRD = floor((Pbar - SU) / RD) # ramp down time #TRD = floor((Pbar - SU) / RD) # ramp down time
# TODO check amk changed TRD wrt Kneuven et al. # TODO check amk changed TRD wrt Knueven et al.
TRD = ceil((Pbar - SD) / RD) # ramp down time TRD = ceil((Pbar - SD) / RD) # ramp down time
TRU = floor((Pbar - SU) / RU) # ramp up time, can be negative if Pbar < SU TRU = floor((Pbar - SU) / RU) # ramp up time, can be negative if Pbar < SU
# TODO check initial time periods: what if generator has been running for x periods? # TODO check initial time periods: what if generator has been running for x periods?
# But maybe ok as long as (35) and (36) are also used... # But maybe ok as long as (35) and (36) are also used...
if UT > 1 if UT > 1
# Equation (38) in Kneuven et al. (2020) # Equation (38) in Knueven et al. (2020)
# Generalization of (20) # Generalization of (20)
# Necessary that if any of the switch_on = 1 in the sum, # Necessary that if any of the switch_on = 1 in the sum,
# then switch_off[gn, t+1] = 0 # then switch_off[gn, t+1] = 0
@ -87,7 +87,7 @@ function _add_ramp_eqs!(
) )
if UT - 2 < TRU if UT - 2 < TRU
# Equation (40) in Kneuven et al. (2020) # Equation (40) in Knueven et al. (2020)
# Covers an additional time period of the ramp-up trajectory, compared to (38) # Covers an additional time period of the ramp-up trajectory, compared to (38)
eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint( eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint(
model, model,
@ -105,7 +105,7 @@ function _add_ramp_eqs!(
KSD = min(TRD, UT - 1, T - t - 1) KSD = min(TRD, UT - 1, T - t - 1)
if KSD > 0 if KSD > 0
KSU = min(TRU, UT - 2 - KSD, t - 1) KSU = min(TRU, UT - 2 - KSD, t - 1)
# Equation (41) in Kneuven et al. (2020) # Equation (41) in Knueven et al. (2020)
eq_prod_limit_shutdown_trajectory[gn, t] = @constraint( eq_prod_limit_shutdown_trajectory[gn, t] = @constraint(
model, model,
prod_above[gn, t] + prod_above[gn, t] +

@ -2,6 +2,11 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
"""
_add_bus!(model::JuMP.Model, b::Bus)::Nothing
Creates `expr_net_injection` and adds `curtail` variable to `model`.
"""
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
net_injection = _init(model, :expr_net_injection) net_injection = _init(model, :expr_net_injection)
curtail = _init(model, :curtail) curtail = _init(model, :curtail)

@ -5,7 +5,7 @@
""" """
_add_system_wide_eqs!(model::JuMP.Model)::Nothing _add_system_wide_eqs!(model::JuMP.Model)::Nothing
Calls `_add_net_injection_eqs!` and `add_reserve_eqs!`. Adds constraints that apply to the whole system, such as relating to net injection and reserves.
""" """
function _add_system_wide_eqs!(model::JuMP.Model)::Nothing function _add_system_wide_eqs!(model::JuMP.Model)::Nothing
_add_net_injection_eqs!(model) _add_net_injection_eqs!(model)
@ -20,13 +20,13 @@ Adds `net_injection`, `eq_net_injection_def`, and `eq_power_balance` identifiers
Variables Variables
--- ---
* expr_net_injection * `expr_net_injection`
* net_injection * `net_injection`
Constraints Constraints
--- ---
* eq_net_injection_def * `eq_net_injection_def`
* eq_power_balance * `eq_power_balance`
""" """
function _add_net_injection_eqs!(model::JuMP.Model)::Nothing function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
T = model[:instance].time T = model[:instance].time
@ -52,24 +52,24 @@ end
Ensure constraints on reserves are met. Ensure constraints on reserves are met.
Based on Morales-España et al. (2013a). Based on Morales-España et al. (2013a).
Eqn. (68) from Kneuven et al. (2020). Eqn. (68) from Knueven et al. (2020).
Adds `eq_min_reserve` identifier to `model`, and corresponding constraint. Adds `eq_min_reserve` identifier to `model`, and corresponding constraint.
Variables Variables
--- ---
* reserve * `reserve`
* reserve_shortfall * `reserve_shortfall`
Constraints Constraints
--- ---
* eq_min_reserve * `eq_min_reserve`
""" """
function _add_reserve_eqs!(model::JuMP.Model)::Nothing function _add_reserve_eqs!(model::JuMP.Model)::Nothing
instance = model[:instance] instance = model[:instance]
eq_min_reserve = _init(model, :eq_min_reserve) eq_min_reserve = _init(model, :eq_min_reserve)
for t in 1:instance.time for t in 1:instance.time
# Equation (68) in Kneuven et al. (2020) # Equation (68) in Knueven 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)

@ -159,11 +159,11 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
end end
""" """
_add_shutdown_cost_eqs! _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing
Variables Variables
--- ---
* :switch_off * `switch_off`
""" """
function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing
T = model[:instance].time T = model[:instance].time
@ -171,7 +171,7 @@ function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing
for t in 1:T for t in 1:T
shutdown_cost = 0.0 shutdown_cost = 0.0
if shutdown_cost > 1e-7 if shutdown_cost > 1e-7
# Equation (62) in Kneuven et al. (2020) # Equation (62) in Knueven et al. (2020)
add_to_expression!( add_to_expression!(
model[:obj], model[:obj],
model[:switch_off][gi, t], model[:switch_off][gi, t],
@ -179,7 +179,7 @@ function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing
) )
end end
end # loop over time end # loop over time
end # _add_shutdown_cost_eqs! end
""" """
_add_ramp_eqs!(model, unit, formulation) _add_ramp_eqs!(model, unit, formulation)
@ -235,20 +235,19 @@ end
Ensure constraints on up/down time are met. Ensure constraints on up/down time are met.
Based on Garver (1962), Malkin (2003), and Rajan and Takritti (2005). Based on Garver (1962), Malkin (2003), and Rajan and Takritti (2005).
Eqns. (3), (4), (5) in Kneuven et al. (2020). Eqns. (3), (4), (5) in Knueven et al. (2020).
Variables Variables
--- ---
* :is_on * `is_on`
* :switch_off * `switch_off`
* :switch_on * `switch_on`
Constraints Constraints
--- ---
* :eq_min_uptime * `eq_min_uptime`
* :eq_min_downtime * `eq_min_downtime`
""" """
function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing
is_on = model[:is_on] is_on = model[:is_on]
@ -259,14 +258,14 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing
T = model[:instance].time T = model[:instance].time
for t in 1:T for t in 1:T
# Minimum up-time # Minimum up-time
# Equation (4) in Kneuven et al. (2020) # Equation (4) in Knueven et al. (2020)
eq_min_uptime[g.name, t] = @constraint( eq_min_uptime[g.name, t] = @constraint(
model, 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 # Minimum down-time
# Equation (5) in Kneuven et al. (2020) # Equation (5) in Knueven et al. (2020)
eq_min_downtime[g.name, t] = @constraint( eq_min_downtime[g.name, t] = @constraint(
model, model,
sum( sum(
@ -275,7 +274,7 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing
) )
# Minimum up/down-time for initial periods # Minimum up/down-time for initial periods
# Equations (3a) and (3b) in Kneuven et al. (2020) # Equations (3a) and (3b) in Knueven et al. (2020)
# (using :switch_on and :switch_off instead of :is_on) # (using :switch_on and :switch_off instead of :is_on)
if t == 1 if t == 1
if g.initial_status > 0 if g.initial_status > 0

Loading…
Cancel
Save