From 96493875613ac261095798adc0f8b7b7f66136b2 Mon Sep 17 00:00:00 2001 From: Aleksandr Kazachkov Date: Fri, 23 Jul 2021 16:30:49 -0400 Subject: [PATCH] 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). --- docs/format.md | 6 +- src/model/formulations/ArrCon2000/ramp.jl | 21 ++---- src/model/formulations/ArrCon2000/structs.jl | 6 ++ src/model/formulations/CarArr2006/pwlcosts.jl | 24 ++----- src/model/formulations/CarArr2006/structs.jl | 10 +++ .../formulations/DamKucRajAta2016/ramp.jl | 10 +-- src/model/formulations/Gar1962/prod.jl | 10 +-- src/model/formulations/Gar1962/pwlcosts.jl | 8 +-- src/model/formulations/Gar1962/status.jl | 66 ++++++++----------- src/model/formulations/Gar1962/structs.jl | 47 ++++++++++++- .../formulations/GenMorRam2017/startstop.jl | 41 ++++++------ .../formulations/KnuOstWat2018/pwlcosts.jl | 14 ++-- .../formulations/KnuOstWat2018/scosts.jl | 10 +-- src/model/formulations/MorLatRam2013/ramp.jl | 8 +-- .../formulations/MorLatRam2013/scosts.jl | 8 +-- .../formulations/MorLatRam2013/startstop.jl | 8 +-- src/model/formulations/OstAnjVan2012/ramp.jl | 6 +- src/model/formulations/PanGua2016/ramp.jl | 12 ++-- src/model/formulations/base/bus.jl | 5 ++ src/model/formulations/base/system.jl | 20 +++--- src/model/formulations/base/unit.jl | 27 ++++---- 21 files changed, 201 insertions(+), 166 deletions(-) diff --git a/docs/format.md b/docs/format.md index 0733fb5..8d82794 100644 --- a/docs/format.md +++ b/docs/format.md @@ -28,13 +28,14 @@ Each section is described in detail below. For a complete example, see [case14]( ### 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? | :----------------------------- | :------------------------------------------------ | :------: | :------------: -| `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 | `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 @@ -43,6 +44,7 @@ This section describes system-wide parameters, such as power balance penalties, "Parameters": { "Time horizon (h)": 4, "Power balance penalty ($/MW)": 1000.0 + "Reserve shortfall penalty ($/MW)": 0.0 } } ``` diff --git a/src/model/formulations/ArrCon2000/ramp.jl b/src/model/formulations/ArrCon2000/ramp.jl index a342efd..9afc7b7 100644 --- a/src/model/formulations/ArrCon2000/ramp.jl +++ b/src/model/formulations/ArrCon2000/ramp.jl @@ -3,24 +3,13 @@ # 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. Based on Arroyo and Conejo (2000). -Eqns. (24), (25) in Kneuven et al. (2020). +Eqns. (24), (25) in Knueven et al. (2020). -Variables ---- -* :is_on -* :switch_off -* :switch_on -* :prod_above -* :reserve - -Constraints ---- -* :eq_ramp_up -* :eq_ramp_down +Adds constraints identified by `ArrCon200.Ramping` to `model` using variables `Gar1962.ProdVars` and `is_on` from `Gar1962.StatusVars`. """ function _add_ramp_eqs!( model::JuMP.Model, @@ -75,7 +64,7 @@ function _add_ramp_eqs!( min_prod_last_period = g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] - # Equation (24) in Kneuven et al. (2020) + # Equation (24) in Knueven et al. (2020) eq_ramp_up[gn, t] = @constraint( model, max_prod_this_period - min_prod_last_period <= @@ -106,7 +95,7 @@ function _add_ramp_eqs!( min_prod_this_period = 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( model, max_prod_last_period - min_prod_this_period <= diff --git a/src/model/formulations/ArrCon2000/structs.jl b/src/model/formulations/ArrCon2000/structs.jl index 29e3ebb..7d9eeff 100644 --- a/src/model/formulations/ArrCon2000/structs.jl +++ b/src/model/formulations/ArrCon2000/structs.jl @@ -13,6 +13,12 @@ module ArrCon2000 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 end diff --git a/src/model/formulations/CarArr2006/pwlcosts.jl b/src/model/formulations/CarArr2006/pwlcosts.jl index 0025d0f..d59b46d 100644 --- a/src/model/formulations/CarArr2006/pwlcosts.jl +++ b/src/model/formulations/CarArr2006/pwlcosts.jl @@ -6,23 +6,7 @@ _add_production_piecewise_linear_eqs! Ensure respect of production limits along each segment. -Based on Garver (1962) and Carrión and Arryo (2006), -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 +Creates constraints `CarArr2006.PwlCosts` using variables `Gar1962.StatusVars` """ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, @@ -43,7 +27,7 @@ function _add_production_piecewise_linear_eqs!( for t in 1:model[:instance].time gn = g.name 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 # difference between max power for segments k and k-1 so the # 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]) # Definition of production - # Equation (43) in Kneuven et al. (2020) + # Equation (43) in Knueven et al. (2020) eq_prod_above_def[gn, t] = @constraint( model, prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K) ) # Objective function - # Equation (44) in Kneuven et al. (2020) + # Equation (44) in Knueven et al. (2020) add_to_expression!( model[:obj], segprod[gn, t, k], diff --git a/src/model/formulations/CarArr2006/structs.jl b/src/model/formulations/CarArr2006/structs.jl index 61909ee..ba6d23c 100644 --- a/src/model/formulations/CarArr2006/structs.jl +++ b/src/model/formulations/CarArr2006/structs.jl @@ -14,6 +14,16 @@ module CarArr2006 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 end diff --git a/src/model/formulations/DamKucRajAta2016/ramp.jl b/src/model/formulations/DamKucRajAta2016/ramp.jl index 5fc221e..497958c 100644 --- a/src/model/formulations/DamKucRajAta2016/ramp.jl +++ b/src/model/formulations/DamKucRajAta2016/ramp.jl @@ -7,7 +7,7 @@ Ensure constraints on ramping are met. Based on Damcı-Kurt et al. (2016). -Eqns. (35), (36) in Kneuven et al. (2020). +Eqns. (35), (36) in Knueven et al. (2020). Variables --- @@ -78,7 +78,7 @@ function _add_ramp_eqs!( if t > 1 && time_invariant 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) eq_str_ramp_up[gn, t] = @constraint( model, @@ -98,7 +98,7 @@ function _add_ramp_eqs!( # (instead of using the amount above minimum, as min prod for t < 1 is unknown) max_prod_this_period += g.min_power[t] * is_on[gn, t] - # Modified version of equation (35) in Kneuven et al. (2020) + # Modified version of equation (35) in Knueven et al. (2020) # Equivalent to (24) eq_str_ramp_up[gn, t] = @constraint( model, @@ -121,7 +121,7 @@ function _add_ramp_eqs!( end 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( model, max_prod_last_period - min_prod_this_period <= @@ -132,7 +132,7 @@ function _add_ramp_eqs!( # Add back in min power min_prod_this_period += g.min_power[t] * is_on[gn, t] - # Modified version of equation (36) in Kneuven et al. (2020) + # Modified version of equation (36) in Knueven et al. (2020) # Equivalent to (25) eq_str_ramp_down[gn, t] = @constraint( model, diff --git a/src/model/formulations/Gar1962/prod.jl b/src/model/formulations/Gar1962/prod.jl index 613a987..6a4cf2c 100644 --- a/src/model/formulations/Gar1962/prod.jl +++ b/src/model/formulations/Gar1962/prod.jl @@ -5,7 +5,7 @@ """ _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!( model::JuMP.Model, @@ -28,7 +28,7 @@ end Ensure production limit constraints are met. 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 @@ -52,13 +52,13 @@ function _add_production_limit_eqs!( gn = g.name for t in 1:model[:instance].time # Objective function terms for production costs - # Part of (69) of Kneuven et al. (2020) as C^R_g * u_g(t) term + # 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]) # 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) - # 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 power_diff = max(g.max_power[t], 0.0) - max(g.min_power[t], 0.0) if power_diff < 1e-7 diff --git a/src/model/formulations/Gar1962/pwlcosts.jl b/src/model/formulations/Gar1962/pwlcosts.jl index b0319b9..fa91300 100644 --- a/src/model/formulations/Gar1962/pwlcosts.jl +++ b/src/model/formulations/Gar1962/pwlcosts.jl @@ -7,7 +7,7 @@ Ensure respect of production limits along each segment. 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, 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) for t in 1:model[:instance].time # Definition of production - # Equation (43) in Kneuven et al. (2020) + # Equation (43) in Knueven et al. (2020) eq_prod_above_def[gn, t] = @constraint( model, prod_above[gn, t] == sum(segprod[gn, t, 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 # have this same effect. # 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]) # Objective function - # Equation (44) in Kneuven et al. (2020) + # Equation (44) in Knueven et al. (2020) add_to_expression!( model[:obj], segprod[gn, t, k], diff --git a/src/model/formulations/Gar1962/status.jl b/src/model/formulations/Gar1962/status.jl index 2224f3f..1349e45 100644 --- a/src/model/formulations/Gar1962/status.jl +++ b/src/model/formulations/Gar1962/status.jl @@ -5,39 +5,26 @@ """ _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. """ function _add_status_vars!( model::JuMP.Model, g::Unit, formulation_status_vars::Gar1962.StatusVars, - ALWAYS_CREATE_VARS = false, )::Nothing is_on = _init(model, :is_on) switch_on = _init(model, :switch_on) switch_off = _init(model, :switch_off) + FIX_VARS = !formulation_status_vars.fix_vars_via_constraint for t in 1:model[:instance].time - if ALWAYS_CREATE_VARS || !g.must_run[t] - is_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) - 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 + is_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) + # 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 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 @@ -49,23 +36,32 @@ function _add_status_vars!( force = true, ) fix(switch_off[g.name, t], 0.0; force = true) - end - else - # If vars are not created, then replace them by a constant - if t == 1 + elseif t == 1 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 - 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 + else + # Add explicit constraint if !FIX_VARS 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) + switch_on[g.name, t] = 0.0 + else + switch_off[g.name, t] = 0.0 + end end - end # check if ALWAYS_CREATE_VARS + end end return end @@ -73,16 +69,12 @@ end """ _add_status_eqs! -Variables ---- -* is_on -* switch_off -* switch_on +Creates constraints `eq_binary_link` and `eq_switch_on_off` using variables in `Gar1962.StatusVars`. Constraints --- -* eq_binary_link -* eq_switch_on_off +* `eq_binary_link` +* `eq_switch_on_off` """ function _add_status_eqs!( model::JuMP.Model, @@ -100,7 +92,7 @@ function _add_status_eqs!( end # 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 eq_binary_link[g.name, t] = @constraint( model, @@ -116,7 +108,7 @@ function _add_status_eqs!( end # 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( model, switch_on[g.name, t] + switch_off[g.name, t] <= 1 diff --git a/src/model/formulations/Gar1962/structs.jl b/src/model/formulations/Gar1962/structs.jl index db847ee..8cc0c31 100644 --- a/src/model/formulations/Gar1962/structs.jl +++ b/src/model/formulations/Gar1962/structs.jl @@ -17,8 +17,53 @@ import ..PiecewiseLinearCostsFormulation import ..ProductionVarsFormulation 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 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 diff --git a/src/model/formulations/GenMorRam2017/startstop.jl b/src/model/formulations/GenMorRam2017/startstop.jl index 7a7ac31..5ee0b34 100644 --- a/src/model/formulations/GenMorRam2017/startstop.jl +++ b/src/model/formulations/GenMorRam2017/startstop.jl @@ -6,23 +6,21 @@ _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing 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 ---- -* :is_on -* :prod_above -* :reserve -* :switch_on -* :switch_off +Creates constraints `eq_startstop_limit`, `eq_startup_limit`, and `eq_shutdown_limit` +using variables `Gar1962.StatusVars`, `prod_above` from `Gar1962.ProdVars`, and `reserve`. Constraints --- -* :eq_startstop_limit -* :eq_startup_limit -* :eq_shutdown_limit +* `eq_startstop_limit` +* `eq_startup_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] RESERVES_WHEN_START_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 #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 for t in 1:T ## 2020-10-09 amk: added eqn (20) and check of g.min_uptime # Not present in (23) in Kneueven et al. 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( model, prod_above[gi, t] + reserve[gi, t] <= @@ -64,7 +67,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing ) else ## Startup limits - # Equation (23a) in Kneuven et al. (2020) + # Equation (23a) in Knueven et al. (2020) eqs.startup_limit[gi, t] = @constraint( model, 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 if t < T - # Equation (23b) in Kneuven et al. (2020) + # Equation (23b) in Knueven et al. (2020) eqs.shutdown_limit[gi, t] = @constraint( model, 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] ) end - end # check if g.min_uptime > 1 - end # loop over time -end # _add_startup_shutdown_limit_eqs! + end + end +end diff --git a/src/model/formulations/KnuOstWat2018/pwlcosts.jl b/src/model/formulations/KnuOstWat2018/pwlcosts.jl index 3232cd0..8cc029f 100644 --- a/src/model/formulations/KnuOstWat2018/pwlcosts.jl +++ b/src/model/formulations/KnuOstWat2018/pwlcosts.jl @@ -6,8 +6,8 @@ _add_production_piecewise_linear_eqs! Ensure respect of production limits along each segment. -Based on Kneuven et al. (2018b). -Eqns. (43), (44), (46), (48) in Kneuven et al. (2020). +Based on Knueven et al. (2018b). +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 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 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( model, segprod[gn, t, k] <= @@ -90,7 +90,7 @@ function _add_production_piecewise_linear_eqs!( (t < T ? Cw * switch_off[gn, t+1] : 0.0) ) 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( model, 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) ) - # Equation (47b)/(48b) in Kneuven et al. (2020) + # Equation (47b)/(48b) in Knueven et al. (2020) eq_segprod_limit_c[gn, t, k] = @constraint( model, segprod[gn, t, k] <= @@ -110,14 +110,14 @@ function _add_production_piecewise_linear_eqs!( end # Definition of production - # Equation (43) in Kneuven et al. (2020) + # Equation (43) in Knueven et al. (2020) eq_prod_above_def[gn, t] = @constraint( model, prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K) ) # Objective function - # Equation (44) in Kneuven et al. (2020) + # Equation (44) in Knueven et al. (2020) add_to_expression!( model[:obj], segprod[gn, t, k], diff --git a/src/model/formulations/KnuOstWat2018/scosts.jl b/src/model/formulations/KnuOstWat2018/scosts.jl index 570e7ea..6f24195 100644 --- a/src/model/formulations/KnuOstWat2018/scosts.jl +++ b/src/model/formulations/KnuOstWat2018/scosts.jl @@ -6,7 +6,7 @@ _add_startup_cost_eqs! 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). Variables @@ -58,7 +58,7 @@ function _add_startup_cost_eqs!( # fix(vars.downtime_arc[gn, t, tmp_t], 0.; force = true) #end - # Equation (59) in Kneuven et al. (2020) + # Equation (59) in Knueven et al. (2020) # Relate downtime_arc with switch_on # "switch_on[g,t] >= x_g(t',t) for all t' \in [t-TC+1, t-DT]" 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]" eqs.shutdown_at_t[gn, t] = @constraint( model, @@ -80,7 +80,7 @@ function _add_startup_cost_eqs!( ) # Objective function terms for start-up costs - # Equation (61) in Kneuven et al. (2020) + # Equation (61) in Knueven et al. (2020) default_category = S if initial_time_shutdown > 0 && t + initial_time_shutdown - 1 < TC for s in 1:S-1 @@ -104,7 +104,7 @@ function _add_startup_cost_eqs!( for s in 1:S-1 # 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 start_range = max((t - g.startup_categories[s+1].delay + 1), 1) end_range = min((t - g.startup_categories[s].delay), T - 1) diff --git a/src/model/formulations/MorLatRam2013/ramp.jl b/src/model/formulations/MorLatRam2013/ramp.jl index 48f58cc..7339466 100644 --- a/src/model/formulations/MorLatRam2013/ramp.jl +++ b/src/model/formulations/MorLatRam2013/ramp.jl @@ -6,9 +6,9 @@ _add_ramp_eqs! 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. -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 --- @@ -92,7 +92,7 @@ function _add_ramp_eqs!( RU * is_on[gn, t-1] + SU * switch_on[gn, t] ) 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 # prod_above[gn, t] when starting up, and creates diff with (24). eq_ramp_up[gn, t] = @constraint( @@ -136,7 +136,7 @@ function _add_ramp_eqs!( RD * is_on[gn, t] + SD * switch_off[gn, t] ) 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 # and RD < SD? There is a difference with (25). eq_ramp_down[gn, t] = @constraint( diff --git a/src/model/formulations/MorLatRam2013/scosts.jl b/src/model/formulations/MorLatRam2013/scosts.jl index 17b7584..2085dbc 100644 --- a/src/model/formulations/MorLatRam2013/scosts.jl +++ b/src/model/formulations/MorLatRam2013/scosts.jl @@ -8,7 +8,7 @@ Extended formulation of startup costs using indicator variables based on Muckstadt and Wilson, 1968; 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. \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 for t in 1:model[:instance].time # 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( model, 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 = ( 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] eq_startup_restrict[gn, t, s] = @constraint( model, @@ -76,7 +76,7 @@ function _add_startup_cost_eqs!( end # if s < S (not the last category) # Objective function terms for start-up costs - # Equation (56) in Kneuven et al. (2020) + # Equation (56) in Knueven et al. (2020) add_to_expression!( model[:obj], startup[gn, t, s], diff --git a/src/model/formulations/MorLatRam2013/startstop.jl b/src/model/formulations/MorLatRam2013/startstop.jl index 941a9d0..4026d04 100644 --- a/src/model/formulations/MorLatRam2013/startstop.jl +++ b/src/model/formulations/MorLatRam2013/startstop.jl @@ -6,7 +6,7 @@ _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing 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 --- @@ -44,7 +44,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing for t in 1:T ## 2020-10-09 amk: added eqn (20) and check of g.min_uptime 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 eq_startstop_limit[gi, t] = @constraint( model, @@ -55,7 +55,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing ) else ## Startup limits - # Equation (21a) in Kneuven et al. (2020) + # Equation (21a) in Knueven et al. (2020) # Proposed by Morales-España et al. (2013a) eqs_startup_limit[gi, t] = @constraint( model, @@ -66,7 +66,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing ## Shutdown limits 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 # ax: ideally should have reserve_up and reserve_down variables # i.e., the generator should be able to increase/decrease production as specified diff --git a/src/model/formulations/OstAnjVan2012/ramp.jl b/src/model/formulations/OstAnjVan2012/ramp.jl index 706b544..3079e9c 100644 --- a/src/model/formulations/OstAnjVan2012/ramp.jl +++ b/src/model/formulations/OstAnjVan2012/ramp.jl @@ -7,7 +7,7 @@ Ensure constraints on ramping are met. Based on Ostrowski, Anjos, Vannelli (2012). -Eqn (37) in Kneuven et al. (2020). +Eqn (37) in Knueven et al. (2020). Variables --- @@ -59,7 +59,7 @@ function _add_ramp_eqs!( Pbar = g.max_power[t] #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 if Pbar < 1e-7 @@ -68,7 +68,7 @@ function _add_ramp_eqs!( end 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) eq_str_prod_limit[gn, t] = @constraint( model, diff --git a/src/model/formulations/PanGua2016/ramp.jl b/src/model/formulations/PanGua2016/ramp.jl index e7ec923..41b0089 100644 --- a/src/model/formulations/PanGua2016/ramp.jl +++ b/src/model/formulations/PanGua2016/ramp.jl @@ -7,8 +7,8 @@ Add tighter upper bounds on production based on ramp-down trajectory. Based on (28) in Pan and Guan (2016). -But there is an extra time period covered using (40) of Kneuven et al. (2020). -Eqns. (38), (40), (41) in Kneuven et al. (2020). +But there is an extra time period covered using (40) of Knueven et al. (2020). +Eqns. (38), (40), (41) in Knueven et al. (2020). Variables --- @@ -63,14 +63,14 @@ function _add_ramp_eqs!( end #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 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? # But maybe ok as long as (35) and (36) are also used... if UT > 1 - # Equation (38) in Kneuven et al. (2020) + # Equation (38) in Knueven et al. (2020) # Generalization of (20) # Necessary that if any of the switch_on = 1 in the sum, # then switch_off[gn, t+1] = 0 @@ -87,7 +87,7 @@ function _add_ramp_eqs!( ) 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) eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint( model, @@ -105,7 +105,7 @@ function _add_ramp_eqs!( KSD = min(TRD, UT - 1, T - t - 1) if KSD > 0 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( model, prod_above[gn, t] + diff --git a/src/model/formulations/base/bus.jl b/src/model/formulations/base/bus.jl index 358279e..dde369f 100644 --- a/src/model/formulations/base/bus.jl +++ b/src/model/formulations/base/bus.jl @@ -2,6 +2,11 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # 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 net_injection = _init(model, :expr_net_injection) curtail = _init(model, :curtail) diff --git a/src/model/formulations/base/system.jl b/src/model/formulations/base/system.jl index 621c735..c11c1bb 100644 --- a/src/model/formulations/base/system.jl +++ b/src/model/formulations/base/system.jl @@ -5,7 +5,7 @@ """ _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 _add_net_injection_eqs!(model) @@ -20,13 +20,13 @@ Adds `net_injection`, `eq_net_injection_def`, and `eq_power_balance` identifiers Variables --- -* expr_net_injection -* net_injection +* `expr_net_injection` +* `net_injection` Constraints --- -* eq_net_injection_def -* eq_power_balance +* `eq_net_injection_def` +* `eq_power_balance` """ function _add_net_injection_eqs!(model::JuMP.Model)::Nothing T = model[:instance].time @@ -52,24 +52,24 @@ end Ensure constraints on reserves are met. 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. Variables --- -* reserve -* reserve_shortfall +* `reserve` +* `reserve_shortfall` Constraints --- -* eq_min_reserve +* `eq_min_reserve` """ function _add_reserve_eqs!(model::JuMP.Model)::Nothing instance = model[:instance] eq_min_reserve = _init(model, :eq_min_reserve) 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) # Akin to the alternative formulation with max_power_avail # from Carrión and Arroyo (2006) and Ostrowski et al. (2012) diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index 6f0169b..4d8254b 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -159,11 +159,11 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing end """ - _add_shutdown_cost_eqs! + _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing Variables --- -* :switch_off +* `switch_off` """ function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing T = model[:instance].time @@ -171,7 +171,7 @@ function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing for t in 1:T shutdown_cost = 0.0 if shutdown_cost > 1e-7 - # Equation (62) in Kneuven et al. (2020) + # Equation (62) in Knueven et al. (2020) add_to_expression!( model[:obj], model[:switch_off][gi, t], @@ -179,7 +179,7 @@ function _add_shutdown_cost_eqs!(model::JuMP.Model, g::Unit)::Nothing ) end end # loop over time -end # _add_shutdown_cost_eqs! +end """ _add_ramp_eqs!(model, unit, formulation) @@ -235,20 +235,19 @@ end Ensure constraints on up/down time are met. 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 --- -* :is_on -* :switch_off -* :switch_on +* `is_on` +* `switch_off` +* `switch_on` Constraints --- -* :eq_min_uptime -* :eq_min_downtime - +* `eq_min_uptime` +* `eq_min_downtime` """ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing 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 for t in 1:T # Minimum up-time - # Equation (4) in Kneuven et al. (2020) + # Equation (4) in Knueven et al. (2020) eq_min_uptime[g.name, t] = @constraint( model, sum(switch_on[g.name, i] for i in (t-g.min_uptime+1):t if i >= 1) <= is_on[g.name, t] ) # Minimum down-time - # Equation (5) in Kneuven et al. (2020) + # Equation (5) in Knueven et al. (2020) eq_min_downtime[g.name, t] = @constraint( model, sum( @@ -275,7 +274,7 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing ) # 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) if t == 1 if g.initial_status > 0