diff --git a/src/UnitCommitment.jl b/src/UnitCommitment.jl index 5d3d70c..8e573e1 100644 --- a/src/UnitCommitment.jl +++ b/src/UnitCommitment.jl @@ -30,6 +30,7 @@ include("model/formulations/base/unit.jl") include("model/formulations/CarArr2006/pwlcosts.jl") include("model/formulations/DamKucRajAta2016/ramp.jl") include("model/formulations/Gar1962/pwlcosts.jl") +include("model/formulations/Gar1962/status.jl") include("model/formulations/KnuOstWat2018/pwlcosts.jl") include("model/formulations/MorLatRam2013/ramp.jl") include("model/formulations/MorLatRam2013/scosts.jl") diff --git a/src/model/formulations/ArrCon2000/ramp.jl b/src/model/formulations/ArrCon2000/ramp.jl index cee94db..58db4e4 100644 --- a/src/model/formulations/ArrCon2000/ramp.jl +++ b/src/model/formulations/ArrCon2000/ramp.jl @@ -5,7 +5,8 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::ArrCon2000.Ramping, + formulation_status_vars::Gar1962.StatusVars, + formulation_ramping::ArrCon2000.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true @@ -17,15 +18,17 @@ function _add_ramp_eqs!( RD = g.ramp_down_limit SU = g.startup_limit SD = g.shutdown_limit - is_on = model[:is_on] prod_above = model[:prod_above] reserve = model[:reserve] - switch_off = model[:switch_off] - switch_on = model[:switch_on] eq_ramp_down = _init(model, :eq_ramp_down) eq_ramp_up = _init(model, :eq_ramp_up) is_initially_on = (g.initial_status > 0) + # Gar1962.StatusVars + is_on = model[:is_on] + switch_off = model[:switch_off] + switch_on = model[:switch_on] + for t in 1:model[:instance].time # Ramp up limit if t == 1 diff --git a/src/model/formulations/CarArr2006/pwlcosts.jl b/src/model/formulations/CarArr2006/pwlcosts.jl index ff5187a..1d043f7 100644 --- a/src/model/formulations/CarArr2006/pwlcosts.jl +++ b/src/model/formulations/CarArr2006/pwlcosts.jl @@ -5,6 +5,7 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, + formulation_status_vars::StatusVarsFormulation, formulation::CarArr2006.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) diff --git a/src/model/formulations/DamKucRajAta2016/ramp.jl b/src/model/formulations/DamKucRajAta2016/ramp.jl index cfe545a..d0fe949 100644 --- a/src/model/formulations/DamKucRajAta2016/ramp.jl +++ b/src/model/formulations/DamKucRajAta2016/ramp.jl @@ -5,7 +5,8 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::DamKucRajAta2016.Ramping, + formulation_status_vars::Gar1962.StatusVars, + formulation_ramping::DamKucRajAta2016.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true @@ -21,9 +22,11 @@ function _add_ramp_eqs!( gn = g.name eq_str_ramp_down = _init(model, :eq_str_ramp_down) eq_str_ramp_up = _init(model, :eq_str_ramp_up) - is_on = model[:is_on] prod_above = model[:prod_above] reserve = model[:reserve] + + # Gar1962.StatusVars + is_on = model[:is_on] switch_off = model[:switch_off] switch_on = model[:switch_on] diff --git a/src/model/formulations/Gar1962/pwlcosts.jl b/src/model/formulations/Gar1962/pwlcosts.jl index e26211c..454b575 100644 --- a/src/model/formulations/Gar1962/pwlcosts.jl +++ b/src/model/formulations/Gar1962/pwlcosts.jl @@ -5,14 +5,15 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, - formulation::Gar1962.PwlCosts, + formulation_status_vars::Gar1962.StatusVars, + formulation_pwd::Gar1962.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) eq_segprod_limit = _init(model, :eq_segprod_limit) - is_on = model[:is_on] prod_above = model[:prod_above] segprod = model[:segprod] gn = g.name + is_on = model[:is_on] K = length(g.cost_segments) for t in 1:model[:instance].time # Definition of production diff --git a/src/model/formulations/Gar1962/status.jl b/src/model/formulations/Gar1962/status.jl new file mode 100644 index 0000000..14c055f --- /dev/null +++ b/src/model/formulations/Gar1962/status.jl @@ -0,0 +1,61 @@ +# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment +# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. +# Released under the modified BSD license. See COPYING.md for more details. + +function _add_status_vars!( + model::JuMP.Model, + g::Unit, + formulation_status_vars::Gar1962.StatusVars, +)::Nothing + is_on = _init(model, :is_on) + switch_on = _init(model, :switch_on) + switch_off = _init(model, :switch_off) + for t in 1:model[:instance].time + if g.must_run[t] + is_on[g.name, t] = 1.0 + switch_on[g.name, t] = (t == 1 ? 1.0 - _is_initially_on(g) : 0.0) + switch_off[g.name, t] = 0.0 + else + 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 + end + return +end + +function _add_status_eqs!( + model::JuMP.Model, + g::Unit, + formulation_status_vars::Gar1962.StatusVars, +)::Nothing + eq_binary_link = _init(model, :eq_binary_link) + eq_switch_on_off = _init(model, :eq_switch_on_off) + is_on = model[:is_on] + switch_off = model[:switch_off] + switch_on = model[:switch_on] + for t in 1:model[:instance].time + if !g.must_run[t] + # Link binary variables + if t == 1 + eq_binary_link[g.name, t] = @constraint( + model, + is_on[g.name, t] - _is_initially_on(g) == + switch_on[g.name, t] - switch_off[g.name, t] + ) + else + eq_binary_link[g.name, t] = @constraint( + model, + is_on[g.name, t] - is_on[g.name, t-1] == + switch_on[g.name, t] - switch_off[g.name, t] + ) + end + # Cannot switch on and off at the same time + eq_switch_on_off[g.name, t] = @constraint( + model, + switch_on[g.name, t] + switch_off[g.name, t] <= 1 + ) + end + end + return +end diff --git a/src/model/formulations/Gar1962/structs.jl b/src/model/formulations/Gar1962/structs.jl index 25b7ac4..e7a8ef0 100644 --- a/src/model/formulations/Gar1962/structs.jl +++ b/src/model/formulations/Gar1962/structs.jl @@ -14,7 +14,9 @@ Formulation described in: module Gar1962 import ..PiecewiseLinearCostsFormulation +import ..StatusVarsFormulation struct PwlCosts <: PiecewiseLinearCostsFormulation end +struct StatusVars <: StatusVarsFormulation end end diff --git a/src/model/formulations/KnuOstWat2018/pwlcosts.jl b/src/model/formulations/KnuOstWat2018/pwlcosts.jl index 663d329..203677c 100644 --- a/src/model/formulations/KnuOstWat2018/pwlcosts.jl +++ b/src/model/formulations/KnuOstWat2018/pwlcosts.jl @@ -5,7 +5,8 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, - formulation::KnuOstWat2018.PwlCosts, + formulation_status_vars::Gar1962.StatusVars, + formulation_pwl::KnuOstWat2018.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) eq_segprod_limit_a = _init(model, :eq_segprod_limit_a) @@ -13,13 +14,15 @@ function _add_production_piecewise_linear_eqs!( eq_segprod_limit_c = _init(model, :eq_segprod_limit_c) prod_above = model[:prod_above] segprod = model[:segprod] - is_on = model[:is_on] - switch_on = model[:switch_on] - switch_off = model[:switch_off] gn = g.name K = length(g.cost_segments) T = model[:instance].time + # Gar1962.StatusVars + is_on = model[:is_on] + switch_on = model[:switch_on] + switch_off = model[:switch_off] + for t in 1:T for k in 1:K # Pbar^{k-1) diff --git a/src/model/formulations/MorLatRam2013/ramp.jl b/src/model/formulations/MorLatRam2013/ramp.jl index 3a31458..8f622e7 100644 --- a/src/model/formulations/MorLatRam2013/ramp.jl +++ b/src/model/formulations/MorLatRam2013/ramp.jl @@ -5,7 +5,8 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::MorLatRam2013.Ramping, + formulation_status_vars::Gar1962.StatusVars, + formulation_ramping::MorLatRam2013.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true @@ -20,9 +21,11 @@ function _add_ramp_eqs!( gn = g.name eq_ramp_down = _init(model, :eq_ramp_down) eq_ramp_up = _init(model, :eq_str_ramp_up) - is_on = model[:is_on] prod_above = model[:prod_above] reserve = model[:reserve] + + # Gar1962.StatusVars + is_on = model[:is_on] switch_off = model[:switch_off] switch_on = model[:switch_on] diff --git a/src/model/formulations/PanGua2016/ramp.jl b/src/model/formulations/PanGua2016/ramp.jl index 52b434b..ba7257c 100644 --- a/src/model/formulations/PanGua2016/ramp.jl +++ b/src/model/formulations/PanGua2016/ramp.jl @@ -1,16 +1,18 @@ +# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment +# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. +# Released under the modified BSD license. See COPYING.md for more details. + function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::PanGua2016.Ramping, + formulation_status_vars::Gar1962.StatusVars, + formulation_ramping::PanGua2016.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_SHUT_DOWN = true gn = g.name - is_on = model[:is_on] prod_above = model[:prod_above] reserve = model[:reserve] - switch_off = model[:switch_off] - switch_on = model[:switch_on] eq_str_prod_limit = _init(model, :eq_str_prod_limit) eq_prod_limit_ramp_up_extra_period = _init(model, :eq_prod_limit_ramp_up_extra_period) @@ -23,6 +25,11 @@ function _add_ramp_eqs!( RD = g.ramp_down_limit # ramp down rate T = model[:instance].time + # Gar1962.StatusVars + is_on = model[:is_on] + switch_off = model[:switch_off] + switch_on = model[:switch_on] + for t in 1:T Pbar = g.max_power[t] if Pbar < 1e-7 diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index e15c64b..1275c20 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -6,20 +6,23 @@ abstract type TransmissionFormulation end abstract type RampingFormulation end abstract type PiecewiseLinearCostsFormulation end abstract type StartupCostsFormulation end +abstract type StatusVarsFormulation end struct Formulation pwl_costs::PiecewiseLinearCostsFormulation ramping::RampingFormulation startup_costs::StartupCostsFormulation + status_vars::StatusVarsFormulation transmission::TransmissionFormulation function Formulation(; pwl_costs::PiecewiseLinearCostsFormulation = KnuOstWat2018.PwlCosts(), ramping::RampingFormulation = MorLatRam2013.Ramping(), startup_costs::StartupCostsFormulation = MorLatRam2013.StartupCosts(), + status_vars::StatusVarsFormulation = Gar1962.StatusVars(), transmission::TransmissionFormulation = ShiftFactorsFormulation(), ) - return new(pwl_costs, ramping, startup_costs, transmission) + return new(pwl_costs, ramping, startup_costs, status_vars, transmission) end end diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index f2a67de..3352bb1 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -2,7 +2,7 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -function _add_unit!(model::JuMP.Model, g::Unit, f::Formulation) +function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation) if !all(g.must_run) && any(g.must_run) error("Partially must-run units are not currently supported") end @@ -14,17 +14,22 @@ function _add_unit!(model::JuMP.Model, g::Unit, f::Formulation) _add_production_vars!(model, g) _add_reserve_vars!(model, g) _add_startup_shutdown_vars!(model, g) - _add_status_vars!(model, g) + _add_status_vars!(model, g, formulation.status_vars) # Constraints and objective function _add_min_uptime_downtime_eqs!(model, g) _add_net_injection_eqs!(model, g) _add_production_limit_eqs!(model, g) - _add_production_piecewise_linear_eqs!(model, g, f.pwl_costs) - _add_ramp_eqs!(model, g, f.ramping) - _add_startup_cost_eqs!(model, g, f.startup_costs) + _add_production_piecewise_linear_eqs!( + model, + g, + formulation.status_vars, + formulation.pwl_costs, + ) + _add_ramp_eqs!(model, g, formulation.status_vars, formulation.ramping) + _add_startup_cost_eqs!(model, g, formulation.startup_costs) _add_startup_shutdown_limit_eqs!(model, g) - _add_status_eqs!(model, g) + _add_status_eqs!(model, g, formulation.status_vars) return end @@ -134,56 +139,6 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing return end -function _add_status_vars!(model::JuMP.Model, g::Unit)::Nothing - is_on = _init(model, :is_on) - switch_on = _init(model, :switch_on) - switch_off = _init(model, :switch_off) - for t in 1:model[:instance].time - if g.must_run[t] - is_on[g.name, t] = 1.0 - switch_on[g.name, t] = (t == 1 ? 1.0 - _is_initially_on(g) : 0.0) - switch_off[g.name, t] = 0.0 - else - 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 - end - return -end - -function _add_status_eqs!(model::JuMP.Model, g::Unit)::Nothing - eq_binary_link = _init(model, :eq_binary_link) - eq_switch_on_off = _init(model, :eq_switch_on_off) - is_on = model[:is_on] - switch_off = model[:switch_off] - switch_on = model[:switch_on] - for t in 1:model[:instance].time - if !g.must_run[t] - # Link binary variables - if t == 1 - eq_binary_link[g.name, t] = @constraint( - model, - is_on[g.name, t] - _is_initially_on(g) == - switch_on[g.name, t] - switch_off[g.name, t] - ) - else - eq_binary_link[g.name, t] = @constraint( - model, - is_on[g.name, t] - is_on[g.name, t-1] == - switch_on[g.name, t] - switch_off[g.name, t] - ) - end - # Cannot switch on and off at the same time - eq_switch_on_off[g.name, t] = @constraint( - model, - switch_on[g.name, t] + switch_off[g.name, t] <= 1 - ) - end - end - return -end - function _add_ramp_eqs!( model::JuMP.Model, g::Unit,