From 8cdd88d6de70783b786dc937668945eddf5b0487 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Tue, 1 Jun 2021 14:20:36 -0500 Subject: [PATCH] Make papers into modules, instead of structs; add StartupCostsFormulation --- benchmark/benchmark.jl | 41 +++++++++------ src/UnitCommitment.jl | 1 + src/model/formulations/ArrCon2000/ramp.jl | 2 +- src/model/formulations/ArrCon2000/structs.jl | 8 ++- src/model/formulations/CarArr2006/pwlcosts.jl | 2 +- src/model/formulations/CarArr2006/structs.jl | 8 ++- .../formulations/DamKucRajAta2016/ramp.jl | 2 +- .../formulations/DamKucRajAta2016/structs.jl | 8 ++- src/model/formulations/Gar1962/pwlcosts.jl | 2 +- src/model/formulations/Gar1962/structs.jl | 8 ++- .../formulations/KnuOstWat2018/pwlcosts.jl | 2 +- .../formulations/KnuOstWat2018/structs.jl | 8 ++- src/model/formulations/MorLatRam2013/ramp.jl | 2 +- .../formulations/MorLatRam2013/scosts.jl | 50 +++++++++++++++++++ .../formulations/MorLatRam2013/structs.jl | 10 +++- src/model/formulations/PanGua2016/ramp.jl | 2 +- src/model/formulations/PanGua2016/structs.jl | 8 ++- src/model/formulations/base/structs.jl | 9 ++-- src/model/formulations/base/unit.jl | 45 +---------------- .../methods/XavQiuWanThi2019/optimize.jl | 2 +- .../methods/XavQiuWanThi2019/structs.jl | 25 ++++++---- src/solution/optimize.jl | 2 +- test/model/formulations_test.jl | 31 ++++++++---- 23 files changed, 181 insertions(+), 97 deletions(-) create mode 100644 src/model/formulations/MorLatRam2013/scosts.jl diff --git a/benchmark/benchmark.jl b/benchmark/benchmark.jl index 9123c73..73541d8 100644 --- a/benchmark/benchmark.jl +++ b/benchmark/benchmark.jl @@ -18,6 +18,17 @@ Pkg.activate(".") @everywhere using LinearAlgebra @everywhere using Random +@everywhere import UnitCommitment: + ArrCon2000, + CarArr2006, + DamKucRajAta2016, + Formulation, + Gar1962, + KnuOstWat2018, + MorLatRam2013, + PanGua2016, + XavQiuWanThi2019 + @everywhere UnitCommitment._setup_logger() function main() @@ -59,26 +70,26 @@ function main() "tejada19/UC_168h_199g", ] formulations = Dict( - "ArrCon2000" => UnitCommitment.Formulation( - ramping = UnitCommitment.ArrCon2000(), + "ArrCon2000" => Formulation( + ramping = ArrCon2000.Ramping(), ), - "CarArr2006" => UnitCommitment.Formulation( - pwl_costs = UnitCommitment.CarArr2006(), + "CarArr2006" => Formulation( + pwl_costs = CarArr2006.PwlCosts(), ), - "DamKucRajAta2016" => UnitCommitment.Formulation( - ramping = UnitCommitment.DamKucRajAta2016(), + "DamKucRajAta2016" => Formulation( + ramping = DamKucRajAta2016.Ramping(), ), - "Gar1962" => UnitCommitment.Formulation( - pwl_costs = UnitCommitment.Gar1962(), + "Gar1962" => Formulation( + pwl_costs = Gar1962.PwlCosts(), ), - "KnuOstWat2018" => UnitCommitment.Formulation( - pwl_costs = UnitCommitment.KnuOstWat2018(), + "KnuOstWat2018" => Formulation( + pwl_costs = KnuOstWat2018.PwlCosts(), ), - "MorLatRam2013" => UnitCommitment.Formulation( - ramping = UnitCommitment.MorLatRam2013(), + "MorLatRam2013" => Formulation( + ramping = MorLatRam2013.Ramping(), ), - "PanGua2016" => UnitCommitment.Formulation( - ramping = UnitCommitment.PanGua2016(), + "PanGua2016" => Formulation( + ramping = PanGua2016.Ramping(), ), ) trials = [i for i in 1:5] @@ -138,7 +149,7 @@ end BLAS.set_num_threads(1) UnitCommitment.optimize!( model, - UnitCommitment.XavQiuWanThi2019( + XavQiuWanThi2019.Method( time_limit = 3600.0, gap_limit = 1e-4, ), diff --git a/src/UnitCommitment.jl b/src/UnitCommitment.jl index ebdedc4..5d3d70c 100644 --- a/src/UnitCommitment.jl +++ b/src/UnitCommitment.jl @@ -32,6 +32,7 @@ include("model/formulations/DamKucRajAta2016/ramp.jl") include("model/formulations/Gar1962/pwlcosts.jl") include("model/formulations/KnuOstWat2018/pwlcosts.jl") include("model/formulations/MorLatRam2013/ramp.jl") +include("model/formulations/MorLatRam2013/scosts.jl") include("model/formulations/PanGua2016/ramp.jl") include("model/jumpext.jl") include("solution/fix.jl") diff --git a/src/model/formulations/ArrCon2000/ramp.jl b/src/model/formulations/ArrCon2000/ramp.jl index ee39d92..cee94db 100644 --- a/src/model/formulations/ArrCon2000/ramp.jl +++ b/src/model/formulations/ArrCon2000/ramp.jl @@ -5,7 +5,7 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::ArrCon2000, + formulation::ArrCon2000.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true diff --git a/src/model/formulations/ArrCon2000/structs.jl b/src/model/formulations/ArrCon2000/structs.jl index 044d623..1580462 100644 --- a/src/model/formulations/ArrCon2000/structs.jl +++ b/src/model/formulations/ArrCon2000/structs.jl @@ -9,4 +9,10 @@ Formulation described in: to an electricity spot market. IEEE Transactions on power systems, 15(3), 1098-1104. """ -struct ArrCon2000 <: RampingFormulation end +module ArrCon2000 + +import ..RampingFormulation + +struct Ramping <: RampingFormulation end + +end diff --git a/src/model/formulations/CarArr2006/pwlcosts.jl b/src/model/formulations/CarArr2006/pwlcosts.jl index 2cad492..ff5187a 100644 --- a/src/model/formulations/CarArr2006/pwlcosts.jl +++ b/src/model/formulations/CarArr2006/pwlcosts.jl @@ -5,7 +5,7 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, - formulation::CarArr2006, + formulation::CarArr2006.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) eq_segprod_limit = _init(model, :eq_segprod_limit) diff --git a/src/model/formulations/CarArr2006/structs.jl b/src/model/formulations/CarArr2006/structs.jl index 92100df..f028a32 100644 --- a/src/model/formulations/CarArr2006/structs.jl +++ b/src/model/formulations/CarArr2006/structs.jl @@ -9,4 +9,10 @@ Formulation described in: mixed-integer linear formulation for the thermal unit commitment problem. IEEE Transactions on power systems, 21(3), 1371-1378. """ -struct CarArr2006 <: PiecewiseLinearCostsFormulation end +module CarArr2006 + +import ..PiecewiseLinearCostsFormulation + +struct PwlCosts <: PiecewiseLinearCostsFormulation end + +end diff --git a/src/model/formulations/DamKucRajAta2016/ramp.jl b/src/model/formulations/DamKucRajAta2016/ramp.jl index cf456eb..cfe545a 100644 --- a/src/model/formulations/DamKucRajAta2016/ramp.jl +++ b/src/model/formulations/DamKucRajAta2016/ramp.jl @@ -5,7 +5,7 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::DamKucRajAta2016, + formulation::DamKucRajAta2016.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true diff --git a/src/model/formulations/DamKucRajAta2016/structs.jl b/src/model/formulations/DamKucRajAta2016/structs.jl index 5b9df01..1f223fb 100644 --- a/src/model/formulations/DamKucRajAta2016/structs.jl +++ b/src/model/formulations/DamKucRajAta2016/structs.jl @@ -8,4 +8,10 @@ Formulation described in: Damcı-Kurt, P., Küçükyavuz, S., Rajan, D., & Atamtürk, A. (2016). A polyhedral study of production ramping. Mathematical Programming, 158(1), 175-205. """ -struct DamKucRajAta2016 <: RampingFormulation end +module DamKucRajAta2016 + +import ..RampingFormulation + +struct Ramping <: RampingFormulation end + +end diff --git a/src/model/formulations/Gar1962/pwlcosts.jl b/src/model/formulations/Gar1962/pwlcosts.jl index ca48fef..e26211c 100644 --- a/src/model/formulations/Gar1962/pwlcosts.jl +++ b/src/model/formulations/Gar1962/pwlcosts.jl @@ -5,7 +5,7 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, - formulation::Gar1962, + formulation::Gar1962.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) eq_segprod_limit = _init(model, :eq_segprod_limit) diff --git a/src/model/formulations/Gar1962/structs.jl b/src/model/formulations/Gar1962/structs.jl index de81434..502a67c 100644 --- a/src/model/formulations/Gar1962/structs.jl +++ b/src/model/formulations/Gar1962/structs.jl @@ -10,4 +10,10 @@ Formulation described in: of Electrical Engineers. Part III: Power Apparatus and Systems, 81(3), 730-734. """ -struct Gar1962 <: PiecewiseLinearCostsFormulation end +module Gar1962 + +import ..PiecewiseLinearCostsFormulation + +struct PwlCosts <: PiecewiseLinearCostsFormulation end + +end diff --git a/src/model/formulations/KnuOstWat2018/pwlcosts.jl b/src/model/formulations/KnuOstWat2018/pwlcosts.jl index a1cd97a..663d329 100644 --- a/src/model/formulations/KnuOstWat2018/pwlcosts.jl +++ b/src/model/formulations/KnuOstWat2018/pwlcosts.jl @@ -5,7 +5,7 @@ function _add_production_piecewise_linear_eqs!( model::JuMP.Model, g::Unit, - formulation::KnuOstWat2018, + formulation::KnuOstWat2018.PwlCosts, )::Nothing eq_prod_above_def = _init(model, :eq_prod_above_def) eq_segprod_limit_a = _init(model, :eq_segprod_limit_a) diff --git a/src/model/formulations/KnuOstWat2018/structs.jl b/src/model/formulations/KnuOstWat2018/structs.jl index ca7283a..5155995 100644 --- a/src/model/formulations/KnuOstWat2018/structs.jl +++ b/src/model/formulations/KnuOstWat2018/structs.jl @@ -9,4 +9,10 @@ Formulation described in: generators in unit commitment. IEEE Transactions on Power Systems, 33(4), 4496-4507. """ -struct KnuOstWat2018 <: PiecewiseLinearCostsFormulation end +module KnuOstWat2018 + +import ..PiecewiseLinearCostsFormulation + +struct PwlCosts <: PiecewiseLinearCostsFormulation end + +end diff --git a/src/model/formulations/MorLatRam2013/ramp.jl b/src/model/formulations/MorLatRam2013/ramp.jl index 32fffde..3a31458 100644 --- a/src/model/formulations/MorLatRam2013/ramp.jl +++ b/src/model/formulations/MorLatRam2013/ramp.jl @@ -5,7 +5,7 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::MorLatRam2013, + formulation::MorLatRam2013.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_START_UP = true diff --git a/src/model/formulations/MorLatRam2013/scosts.jl b/src/model/formulations/MorLatRam2013/scosts.jl new file mode 100644 index 0000000..f1cba19 --- /dev/null +++ b/src/model/formulations/MorLatRam2013/scosts.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_startup_cost_eqs!( + model::JuMP.Model, + g::Unit, + formulation::MorLatRam2013.StartupCosts, +)::Nothing + eq_startup_choose = _init(model, :eq_startup_choose) + eq_startup_restrict = _init(model, :eq_startup_restrict) + S = length(g.startup_categories) + startup = model[:startup] + for t in 1:model[:instance].time + for s in 1:S + # If unit is switching on, we must choose a startup category + eq_startup_choose[g.name, t, s] = @constraint( + model, + model[:switch_on][g.name, t] == + sum(startup[g.name, t, s] for s in 1:S) + ) + + # If unit has not switched off in the last `delay` time periods, startup category is forbidden. + # The last startup category is always allowed. + if s < S + range_start = t - g.startup_categories[s+1].delay + 1 + range_end = t - g.startup_categories[s].delay + range = (range_start:range_end) + initial_sum = ( + g.initial_status < 0 && (g.initial_status + 1 in range) ? 1.0 : 0.0 + ) + eq_startup_restrict[g.name, t, s] = @constraint( + model, + startup[g.name, t, s] <= + initial_sum + sum( + model[:switch_off][g.name, i] for i in range if i >= 1 + ) + ) + end + + # Objective function terms for start-up costs + add_to_expression!( + model[:obj], + startup[g.name, t, s], + g.startup_categories[s].cost, + ) + end + end + return +end diff --git a/src/model/formulations/MorLatRam2013/structs.jl b/src/model/formulations/MorLatRam2013/structs.jl index a91d3fc..fe68b89 100644 --- a/src/model/formulations/MorLatRam2013/structs.jl +++ b/src/model/formulations/MorLatRam2013/structs.jl @@ -9,4 +9,12 @@ Formulation described in: MILP formulation for the thermal unit commitment problem. IEEE Transactions on Power Systems, 28(4), 4897-4908. """ -struct MorLatRam2013 <: RampingFormulation end +module MorLatRam2013 + +import ..RampingFormulation +import ..StartupCostsFormulation + +struct Ramping <: RampingFormulation end +struct StartupCosts <: StartupCostsFormulation end + +end diff --git a/src/model/formulations/PanGua2016/ramp.jl b/src/model/formulations/PanGua2016/ramp.jl index f8f7eca..52b434b 100644 --- a/src/model/formulations/PanGua2016/ramp.jl +++ b/src/model/formulations/PanGua2016/ramp.jl @@ -1,7 +1,7 @@ function _add_ramp_eqs!( model::JuMP.Model, g::Unit, - formulation::PanGua2016, + formulation::PanGua2016.Ramping, )::Nothing # TODO: Move upper case constants to model[:instance] RESERVES_WHEN_SHUT_DOWN = true diff --git a/src/model/formulations/PanGua2016/structs.jl b/src/model/formulations/PanGua2016/structs.jl index ca3094a..11d9084 100644 --- a/src/model/formulations/PanGua2016/structs.jl +++ b/src/model/formulations/PanGua2016/structs.jl @@ -8,4 +8,10 @@ Formulation described in: Pan, K., & Guan, Y. (2016). Strong formulations for multistage stochastic self-scheduling unit commitment. Operations Research, 64(6), 1482-1498. """ -struct PanGua2016 <: RampingFormulation end +module PanGua2016 + +import ..RampingFormulation + +struct Ramping <: RampingFormulation end + +end diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index 8e1a05d..7fdc4f5 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -5,18 +5,21 @@ abstract type TransmissionFormulation end abstract type RampingFormulation end abstract type PiecewiseLinearCostsFormulation end +abstract type StartupCostsFormulation end struct Formulation pwl_costs::PiecewiseLinearCostsFormulation ramping::RampingFormulation + startup_costs::StartupCostsFormulation transmission::TransmissionFormulation function Formulation(; - pwl_costs::PiecewiseLinearCostsFormulation = Gar1962(), - ramping::RampingFormulation = MorLatRam2013(), + pwl_costs::PiecewiseLinearCostsFormulation = Gar1962.PwlCosts(), + ramping::RampingFormulation = MorLatRam2013.Ramping(), + startup_costs::StartupCostsFormulation = MorLatRam2013.StartupCosts(), transmission::TransmissionFormulation = ShiftFactorsFormulation(), ) - return new(pwl_costs, ramping, transmission) + return new(pwl_costs, ramping, startup_costs, transmission) end end diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index ed4fa47..f2a67de 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -22,7 +22,7 @@ function _add_unit!(model::JuMP.Model, g::Unit, f::Formulation) _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_shutdown_costs_eqs!(model, g) + _add_startup_cost_eqs!(model, g, f.startup_costs) _add_startup_shutdown_limit_eqs!(model, g) _add_status_eqs!(model, g) return @@ -134,49 +134,6 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing return end -function _add_startup_shutdown_costs_eqs!(model::JuMP.Model, g::Unit)::Nothing - eq_startup_choose = _init(model, :eq_startup_choose) - eq_startup_restrict = _init(model, :eq_startup_restrict) - S = length(g.startup_categories) - startup = model[:startup] - for t in 1:model[:instance].time - for s in 1:S - # If unit is switching on, we must choose a startup category - eq_startup_choose[g.name, t, s] = @constraint( - model, - model[:switch_on][g.name, t] == - sum(startup[g.name, t, s] for s in 1:S) - ) - - # If unit has not switched off in the last `delay` time periods, startup category is forbidden. - # The last startup category is always allowed. - if s < S - range_start = t - g.startup_categories[s+1].delay + 1 - range_end = t - g.startup_categories[s].delay - range = (range_start:range_end) - initial_sum = ( - g.initial_status < 0 && (g.initial_status + 1 in range) ? 1.0 : 0.0 - ) - eq_startup_restrict[g.name, t, s] = @constraint( - model, - startup[g.name, t, s] <= - initial_sum + sum( - model[:switch_off][g.name, i] for i in range if i >= 1 - ) - ) - end - - # Objective function terms for start-up costs - add_to_expression!( - model[:obj], - startup[g.name, t, s], - g.startup_categories[s].cost, - ) - end - end - return -end - function _add_status_vars!(model::JuMP.Model, g::Unit)::Nothing is_on = _init(model, :is_on) switch_on = _init(model, :switch_on) diff --git a/src/solution/methods/XavQiuWanThi2019/optimize.jl b/src/solution/methods/XavQiuWanThi2019/optimize.jl index 481b9ee..b513bd1 100644 --- a/src/solution/methods/XavQiuWanThi2019/optimize.jl +++ b/src/solution/methods/XavQiuWanThi2019/optimize.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 optimize!(model::JuMP.Model, method::XavQiuWanThi2019)::Nothing +function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing function set_gap(gap) try JuMP.set_optimizer_attribute(model, "MIPGap", gap) diff --git a/src/solution/methods/XavQiuWanThi2019/structs.jl b/src/solution/methods/XavQiuWanThi2019/structs.jl index 52a6654..c5e607a 100644 --- a/src/solution/methods/XavQiuWanThi2019/structs.jl +++ b/src/solution/methods/XavQiuWanThi2019/structs.jl @@ -2,10 +2,18 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -import DataStructures: PriorityQueue """ - struct XavQiuWanThi2019 <: SolutionMethod +Lazy constraint solution method described in: + + Xavier, A. S., Qiu, F., Wang, F., & Thimmapuram, P. R. (2019). Transmission + constraint filtering in large-scale security-constrained unit commitment. + IEEE Transactions on Power Systems, 34(3), 2457-2460. +""" +module XavQiuWanThi2019 +import ..SolutionMethod +""" + struct Method time_limit::Float64 gap_limit::Float64 two_phase_gap::Bool @@ -13,12 +21,6 @@ import DataStructures: PriorityQueue max_violations_per_period::Int end -Lazy constraint solution method described in: - - Xavier, A. S., Qiu, F., Wang, F., & Thimmapuram, P. R. (2019). Transmission - constraint filtering in large-scale security-constrained unit commitment. - IEEE Transactions on Power Systems, 34(3), 2457-2460. - Fields ------ @@ -37,14 +39,14 @@ Fields formulation per time period. """ -struct XavQiuWanThi2019 +struct Method <: SolutionMethod time_limit::Float64 gap_limit::Float64 two_phase_gap::Bool max_violations_per_line::Int max_violations_per_period::Int - function XavQiuWanThi2019(; + function Method(; time_limit::Float64 = 86400.0, gap_limit::Float64 = 1e-3, two_phase_gap::Bool = true, @@ -60,6 +62,9 @@ struct XavQiuWanThi2019 ) end end +end + +import DataStructures: PriorityQueue struct _Violation time::Int diff --git a/src/solution/optimize.jl b/src/solution/optimize.jl index 89174f5..e78a62d 100644 --- a/src/solution/optimize.jl +++ b/src/solution/optimize.jl @@ -10,5 +10,5 @@ advanced methods to accelerate the solution process and to enforce transmission and N-1 security constraints. """ function optimize!(model::JuMP.Model)::Nothing - return UnitCommitment.optimize!(model, XavQiuWanThi2019()) + return UnitCommitment.optimize!(model, XavQiuWanThi2019.Method()) end diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl index 5409fee..e9bdc12 100644 --- a/test/model/formulations_test.jl +++ b/test/model/formulations_test.jl @@ -3,7 +3,15 @@ # Released under the modified BSD license. See COPYING.md for more details. using UnitCommitment -import UnitCommitment: Formulation +import UnitCommitment: + ArrCon2000, + CarArr2006, + DamKucRajAta2016, + Formulation, + Gar1962, + KnuOstWat2018, + MorLatRam2013, + PanGua2016 function _test(formulation::Formulation)::Nothing instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01") @@ -12,11 +20,16 @@ function _test(formulation::Formulation)::Nothing end @testset "formulations" begin - _test(Formulation(ramping = UnitCommitment.ArrCon2000())) - _test(Formulation(ramping = UnitCommitment.DamKucRajAta2016())) - _test(Formulation(ramping = UnitCommitment.MorLatRam2013())) - _test(Formulation(ramping = UnitCommitment.PanGua2016())) - _test(Formulation(pwl_costs = UnitCommitment.Gar1962())) - _test(Formulation(pwl_costs = UnitCommitment.CarArr2006())) - _test(Formulation(pwl_costs = UnitCommitment.KnuOstWat2018())) -end + _test(Formulation(ramping = ArrCon2000.Ramping())) + _test(Formulation(ramping = DamKucRajAta2016.Ramping())) + _test( + Formulation( + ramping = MorLatRam2013.Ramping(), + startup_costs = MorLatRam2013.StartupCosts(), + ), + ) + _test(Formulation(ramping = PanGua2016.Ramping())) + _test(Formulation(pwl_costs = Gar1962.PwlCosts())) + _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) + _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) +end \ No newline at end of file