mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
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).
This commit is contained in:
@@ -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)
|
||||||
|
elseif 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
|
end
|
||||||
else
|
else
|
||||||
# If vars are not created, then replace them by a constant
|
# Add explicit constraint if !FIX_VARS
|
||||||
if t == 1
|
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)
|
||||||
|
switch_off[g.name, t] = 0.0
|
||||||
|
elseif t == 1
|
||||||
if _is_initially_on(g)
|
if _is_initially_on(g)
|
||||||
switch_on[g.name, t] = 0.0
|
switch_on[g.name, t] = 0.0
|
||||||
else
|
else
|
||||||
switch_off[g.name, t] = 0.0
|
switch_off[g.name, t] = 0.0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if g.must_run[t]
|
end
|
||||||
is_on[g.name, t] = 1.0
|
|
||||||
switch_on[g.name, t] =
|
|
||||||
(t == 1 ? 1.0 - _is_initially_on(g) : 0.0)
|
|
||||||
switch_off[g.name, t] = 0.0
|
|
||||||
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)
|
||||||
fix(switch_off[gi, 1], 0.0; force = true)
|
if formulation_status_vars.always_create_vars
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user