diff --git a/src/UnitCommitment.jl b/src/UnitCommitment.jl index 8e573e1..9877392 100644 --- a/src/UnitCommitment.jl +++ b/src/UnitCommitment.jl @@ -31,6 +31,7 @@ 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/Gar1962/prod.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 58db4e4..e9bb288 100644 --- a/src/model/formulations/ArrCon2000/ramp.jl +++ b/src/model/formulations/ArrCon2000/ramp.jl @@ -5,8 +5,9 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation_status_vars::Gar1962.StatusVars, + formulation_prod_vars::Gar1962.ProdVars, formulation_ramping::ArrCon2000.Ramping, + formulation_status_vars::Gar1962.StatusVars, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true @@ -18,12 +19,14 @@ function _add_ramp_eqs!( RD = g.ramp_down_limit SU = g.startup_limit SD = g.shutdown_limit - prod_above = model[:prod_above] reserve = model[:reserve] eq_ramp_down = _init(model, :eq_ramp_down) eq_ramp_up = _init(model, :eq_ramp_up) is_initially_on = (g.initial_status > 0) + # Gar1962.ProdVars + prod_above = model[:prod_above] + # Gar1962.StatusVars is_on = model[:is_on] switch_off = model[:switch_off] diff --git a/src/model/formulations/CarArr2006/pwlcosts.jl b/src/model/formulations/CarArr2006/pwlcosts.jl index 1d043f7..2f13e9a 100644 --- a/src/model/formulations/CarArr2006/pwlcosts.jl +++ b/src/model/formulations/CarArr2006/pwlcosts.jl @@ -5,14 +5,18 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, + formulation_prod_vars::Gar1962.ProdVars, + formulation_pwl_costs::CarArr2006.PwlCosts, formulation_status_vars::StatusVarsFormulation, - formulation::CarArr2006.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) eq_segprod_limit = _init(model, :eq_segprod_limit) - prod_above = model[:prod_above] segprod = model[:segprod] gn = g.name + + # Gar1962.ProdVars + prod_above = model[:prod_above] + K = length(g.cost_segments) for t in 1:model[:instance].time gn = g.name diff --git a/src/model/formulations/DamKucRajAta2016/ramp.jl b/src/model/formulations/DamKucRajAta2016/ramp.jl index d0fe949..9afd247 100644 --- a/src/model/formulations/DamKucRajAta2016/ramp.jl +++ b/src/model/formulations/DamKucRajAta2016/ramp.jl @@ -5,8 +5,9 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation_status_vars::Gar1962.StatusVars, + formulation_prod_vars::Gar1962.ProdVars, formulation_ramping::DamKucRajAta2016.Ramping, + formulation_status_vars::Gar1962.StatusVars, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true @@ -22,9 +23,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) - prod_above = model[:prod_above] reserve = model[:reserve] + # Gar1962.ProdVars + prod_above = model[:prod_above] + # Gar1962.StatusVars is_on = model[:is_on] switch_off = model[:switch_off] diff --git a/src/model/formulations/Gar1962/prod.jl b/src/model/formulations/Gar1962/prod.jl new file mode 100644 index 0000000..e39a90e --- /dev/null +++ b/src/model/formulations/Gar1962/prod.jl @@ -0,0 +1,50 @@ +# 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_production_vars!( + model::JuMP.Model, + g::Unit, + formulation_prod_vars::Gar1962.ProdVars, +)::Nothing + prod_above = _init(model, :prod_above) + segprod = _init(model, :segprod) + for t in 1:model[:instance].time + for k in 1:length(g.cost_segments) + segprod[g.name, t, k] = @variable(model, lower_bound = 0) + end + prod_above[g.name, t] = @variable(model, lower_bound = 0) + end + return +end + +function _add_production_limit_eqs!( + model::JuMP.Model, + g::Unit, + formulation_prod_vars::Gar1962.ProdVars, +)::Nothing + eq_prod_limit = _init(model, :eq_prod_limit) + is_on = model[:is_on] + prod_above = model[:prod_above] + reserve = model[:reserve] + 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 + add_to_expression!(model[:obj], is_on[gn, t], g.min_power_cost[t]) + + # Production limit + # Equation (18) in Kneuven 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) + # 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 + power_diff = 0.0 + end + eq_prod_limit[gn, t] = @constraint( + model, + prod_above[gn, t] + reserve[gn, t] <= power_diff * is_on[gn, t] + ) + end +end diff --git a/src/model/formulations/Gar1962/pwlcosts.jl b/src/model/formulations/Gar1962/pwlcosts.jl index 454b575..3ac4871 100644 --- a/src/model/formulations/Gar1962/pwlcosts.jl +++ b/src/model/formulations/Gar1962/pwlcosts.jl @@ -5,15 +5,21 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, + formulation_prod_vars::Gar1962.ProdVars, + formulation_pwl_costs::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) - prod_above = model[:prod_above] segprod = model[:segprod] gn = g.name + + # Gar1962.ProdVars + prod_above = model[:prod_above] + + # Gar1962.StatusVars 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/structs.jl b/src/model/formulations/Gar1962/structs.jl index e7a8ef0..db847ee 100644 --- a/src/model/formulations/Gar1962/structs.jl +++ b/src/model/formulations/Gar1962/structs.jl @@ -14,8 +14,10 @@ Formulation described in: module Gar1962 import ..PiecewiseLinearCostsFormulation +import ..ProductionVarsFormulation import ..StatusVarsFormulation +struct ProdVars <: ProductionVarsFormulation end struct PwlCosts <: PiecewiseLinearCostsFormulation end struct StatusVars <: StatusVarsFormulation end diff --git a/src/model/formulations/KnuOstWat2018/pwlcosts.jl b/src/model/formulations/KnuOstWat2018/pwlcosts.jl index 203677c..85afa1e 100644 --- a/src/model/formulations/KnuOstWat2018/pwlcosts.jl +++ b/src/model/formulations/KnuOstWat2018/pwlcosts.jl @@ -5,19 +5,22 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, + formulation_prod_vars::Gar1962.ProdVars, + formulation_pwl_costs::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) eq_segprod_limit_b = _init(model, :eq_segprod_limit_b) eq_segprod_limit_c = _init(model, :eq_segprod_limit_c) - prod_above = model[:prod_above] segprod = model[:segprod] gn = g.name K = length(g.cost_segments) T = model[:instance].time + # Gar1962.ProdVars + prod_above = model[:prod_above] + # Gar1962.StatusVars is_on = model[:is_on] switch_on = model[:switch_on] diff --git a/src/model/formulations/MorLatRam2013/ramp.jl b/src/model/formulations/MorLatRam2013/ramp.jl index 8f622e7..cbb8f94 100644 --- a/src/model/formulations/MorLatRam2013/ramp.jl +++ b/src/model/formulations/MorLatRam2013/ramp.jl @@ -5,8 +5,9 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation_status_vars::Gar1962.StatusVars, + formulation_prod_vars::Gar1962.ProdVars, formulation_ramping::MorLatRam2013.Ramping, + formulation_status_vars::Gar1962.StatusVars, )::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_ramp_down = _init(model, :eq_ramp_down) eq_ramp_up = _init(model, :eq_str_ramp_up) - prod_above = model[:prod_above] reserve = model[:reserve] + # Gar1962.ProdVars + prod_above = model[:prod_above] + # Gar1962.StatusVars is_on = model[:is_on] switch_off = model[:switch_off] diff --git a/src/model/formulations/PanGua2016/ramp.jl b/src/model/formulations/PanGua2016/ramp.jl index ba7257c..f040824 100644 --- a/src/model/formulations/PanGua2016/ramp.jl +++ b/src/model/formulations/PanGua2016/ramp.jl @@ -5,13 +5,13 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation_status_vars::Gar1962.StatusVars, + formulation_prod_vars::Gar1962.ProdVars, formulation_ramping::PanGua2016.Ramping, + formulation_status_vars::Gar1962.StatusVars, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_SHUT_DOWN = true gn = g.name - prod_above = model[:prod_above] reserve = model[:reserve] eq_str_prod_limit = _init(model, :eq_str_prod_limit) eq_prod_limit_ramp_up_extra_period = @@ -25,6 +25,9 @@ function _add_ramp_eqs!( RD = g.ramp_down_limit # ramp down rate T = model[:instance].time + # Gar1962.ProdVars + prod_above = model[:prod_above] + # Gar1962.StatusVars is_on = model[:is_on] switch_off = model[:switch_off] diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index 1275c20..2cc7e44 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -7,8 +7,10 @@ abstract type RampingFormulation end abstract type PiecewiseLinearCostsFormulation end abstract type StartupCostsFormulation end abstract type StatusVarsFormulation end +abstract type ProductionVarsFormulation end struct Formulation + prod_vars::ProductionVarsFormulation pwl_costs::PiecewiseLinearCostsFormulation ramping::RampingFormulation startup_costs::StartupCostsFormulation @@ -16,13 +18,21 @@ struct Formulation transmission::TransmissionFormulation function Formulation(; + prod_vars::ProductionVarsFormulation = Gar1962.ProdVars(), 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, status_vars, transmission) + return new( + prod_vars, + 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 3352bb1..ad00d44 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -11,7 +11,7 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation) end # Variables - _add_production_vars!(model, g) + _add_production_vars!(model, g, formulation.prod_vars) _add_reserve_vars!(model, g) _add_startup_shutdown_vars!(model, g) _add_status_vars!(model, g, formulation.status_vars) @@ -19,14 +19,21 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation) # 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_limit_eqs!(model, g, formulation.prod_vars) _add_production_piecewise_linear_eqs!( model, g, - formulation.status_vars, + formulation.prod_vars, formulation.pwl_costs, + formulation.status_vars, + ) + _add_ramp_eqs!( + model, + g, + formulation.prod_vars, + formulation.ramping, + formulation.status_vars, ) - _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, formulation.status_vars) @@ -35,45 +42,6 @@ end _is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0) -function _add_production_vars!(model::JuMP.Model, g::Unit)::Nothing - prod_above = _init(model, :prod_above) - segprod = _init(model, :segprod) - for t in 1:model[:instance].time - for k in 1:length(g.cost_segments) - segprod[g.name, t, k] = @variable(model, lower_bound = 0) - end - prod_above[g.name, t] = @variable(model, lower_bound = 0) - end - return -end - -function _add_production_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing - eq_prod_limit = _init(model, :eq_prod_limit) - is_on = model[:is_on] - prod_above = model[:prod_above] - reserve = model[:reserve] - 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 - add_to_expression!(model[:obj], is_on[gn, t], g.min_power_cost[t]) - - # Production limit - # Equation (18) in Kneuven 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) - # 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 - power_diff = 0.0 - end - eq_prod_limit[gn, t] = @constraint( - model, - prod_above[gn, t] + reserve[gn, t] <= power_diff * is_on[gn, t] - ) - end -end - function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing reserve = _init(model, :reserve) for t in 1:model[:instance].time @@ -149,7 +117,7 @@ function _add_ramp_eqs!( eq_ramp_up = _init(model, :eq_ramp_up) eq_ramp_down = _init(model, :eq_ramp_down) for t in 1:model[:instance].time - # Ramp up limit + # Ramp up limit if t == 1 if _is_initially_on(g) == 1 eq_ramp_up[g.name, t] = @constraint(