From a3a71ff5a9ec4aefc32ab0887cb7734a22603d30 Mon Sep 17 00:00:00 2001 From: oyurdakul Date: Thu, 3 Feb 2022 09:45:06 +0100 Subject: [PATCH 01/22] add flexiramp --- .DS_Store | Bin 0 -> 6148 bytes docs/format.md | 35 ++++- src/UnitCommitment.jl | 2 + src/instance/read.jl | 24 ++- src/instance/structs.jl | 4 + src/model/.DS_Store | Bin 0 -> 6148 bytes src/model/build.jl | 8 + src/model/formulations/WanHob2016/ramp.jl | 152 +++++++++++++++++++ src/model/formulations/WanHob2016/structs.jl | 18 +++ src/model/formulations/base/system.jl | 39 +++++ src/model/formulations/base/unit.jl | 1 + src/solution/solution.jl | 36 ++++- src/validation/validate.jl | 34 +++++ 13 files changed, 338 insertions(+), 15 deletions(-) create mode 100644 .DS_Store create mode 100644 src/model/.DS_Store create mode 100644 src/model/formulations/WanHob2016/ramp.jl create mode 100644 src/model/formulations/WanHob2016/structs.jl diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ab5cefd05e24dccaf15a94eedbadbff4e72f9891 GIT binary patch literal 6148 zcmeHKu};G<5WQZze!_Wpc$8FF1;*XvEchOc~%7l)_M*PEPgf8@6h18w*Np){o(JyMfpom^d? zm|;$M-)8SAr^R*sDdRbtN!Hj~7ggrIwJGU|1POMKCbdP@smgl^Cqy@CTb$8Ae47C${2)E%SHr!YMn} z4{17arRcpY;0ojlO!aXt=l?B!nb9IY5Ai2gz!mss3UE=c>LotP&epTfle0FU-Jyxe qyeI=0#@QtR53-M3CQ|tzW6Y}zqoS-L{uB=Mi$D^@J6GTr6!-#Or$Mg( literal 0 HcmV?d00001 diff --git a/docs/format.md b/docs/format.md index c13bdc7..985511e 100644 --- a/docs/format.md +++ b/docs/format.md @@ -36,6 +36,7 @@ This section describes system-wide parameters, such as power balance and reserve | `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. Negative value implies reserve constraints must always be satisfied. | `-1` | Y +| `Flexiramp penalty ($/MW)` | Penalty for system-wide shortage in meeting flexible ramping product requirements (in $/MW). This is charged per time step. | `500` | Y #### Example @@ -44,7 +45,8 @@ This section describes system-wide parameters, such as power balance and reserve "Parameters": { "Time horizon (h)": 4, "Power balance penalty ($/MW)": 1000.0, - "Reserve shortfall penalty ($/MW)": -1.0 + "Reserve shortfall penalty ($/MW)": -1.0, + "Flexiramp penalty ($/MW)": 100.0 } } ``` @@ -97,6 +99,8 @@ This section describes all generators in the system, including thermal units, re | `Initial power (MW)` | Amount of power the generator at time step `-1`, immediately before the planning horizon starts. | Required | N | `Must run?` | If `true`, the generator should be committed, even if that is not economical (Boolean). | `false` | Y | `Provides spinning reserves?` | If `true`, this generator may provide spinning reserves (Boolean). | `true` | Y +| `Provides flexible capacity?` | If `true`, this generator may provide flexible ramping product (Boolean). | `true` | Y + #### Production costs and limits @@ -136,6 +140,7 @@ Note that this curve also specifies the production limits. Specifically, the fir "Initial status (h)": 12, "Must run?": false, "Provides spinning reserves?": true, + "Provides flexible capacity?": false, }, "gen2": { "Bus": "b5", @@ -206,14 +211,16 @@ This section describes the characteristics of transmission system, such as its t ### Reserves -This section describes the hourly amount of operating reserves required. +This section describes the hourly amount of reserves required. | Key | Description | Default | Time series? | :-------------------- | :------------------------------------------------- | --------- | :----: | `Spinning (MW)` | Minimum amount of system-wide spinning reserves (in MW). Only generators which are online may provide this reserve. | `0.0` | Y +| `Up-flexiramp (MW)` | Minimum amount of system-wide upward flexible ramping product (in MW). Only generators which are online may provide this reserve. | `0.0` | Y +| `Down-flexiramp (MW)` | Minimum amount of system-wide downward flexible ramping product (in MW). Only generators which are online may provide this reserve. | `0.0` | Y -#### Example +#### Example 1 ```json { @@ -228,6 +235,27 @@ This section describes the hourly amount of operating reserves required. } ``` +#### Example 2 + +```json +{ + "Reserves": { + "up-flexiramp (MW)": [ + 20.31042, + 23.65273, + 27.41784, + 25.34057 + ], + "down-flexiramp (MW)": [ + 19.41546, + 21.45377, + 23.53402, + 24.80973 + ] + } +} +``` + ### Contingencies This section describes credible contingency scenarios in the optimization, such as the loss of a transmission line or generator. @@ -287,6 +315,7 @@ Current limitations ------------------- * All reserves are system-wide. Zonal reserves are not currently supported. +* Upward and downward flexible ramping products can only be acquired under the WanHob2016 formulation, which does not support spinning reserves. * Network topology remains the same for all time periods * Only N-1 transmission contingencies are supported. Generator contingencies are not currently supported. * Time-varying minimum production amounts are not currently compatible with ramp/startup/shutdown limits. diff --git a/src/UnitCommitment.jl b/src/UnitCommitment.jl index 9ada14c..89049a1 100644 --- a/src/UnitCommitment.jl +++ b/src/UnitCommitment.jl @@ -16,6 +16,7 @@ include("model/formulations/KnuOstWat2018/structs.jl") include("model/formulations/MorLatRam2013/structs.jl") include("model/formulations/PanGua2016/structs.jl") include("solution/methods/XavQiuWanThi2019/structs.jl") +include("model/formulations/WanHob2016/structs.jl") include("import/egret.jl") include("instance/read.jl") @@ -36,6 +37,7 @@ 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/formulations/WanHob2016/ramp.jl") include("model/jumpext.jl") include("solution/fix.jl") include("solution/methods/XavQiuWanThi2019/enforce.jl") diff --git a/src/instance/read.jl b/src/instance/read.jl index 3ca522c..2a6f448 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -108,6 +108,11 @@ function _from_json(json; repair = true) json["Parameters"]["Power balance penalty (\$/MW)"], default = [1000.0 for t in 1:T], ) + # Penalty price for shortage in meeting system-wide flexiramp requirements + flexiramp_shortfall_penalty = timeseries( + json["Parameters"]["Flexiramp penalty (\$/MW)"], + default = [500.0 for t in 1:T], + ) shortfall_penalty = timeseries( json["Parameters"]["Reserve shortfall penalty (\$/MW)"], default = [-1.0 for t in 1:T], @@ -200,6 +205,10 @@ function _from_json(json; repair = true) dict["Provides spinning reserves?"], default = [true for t in 1:T], ), + timeseries( + dict["Provides flexible capacity?"], + default = [true for t in 1:T], + ), startup_categories, ) push!(bus.units, unit) @@ -207,12 +216,16 @@ function _from_json(json; repair = true) push!(units, unit) end - # Read reserves - reserves = Reserves(zeros(T)) + # Read spinning, up-flexiramp, and down-flexiramp reserve requirements + reserves = Reserves(zeros(T), zeros(T), zeros(T)) if "Reserves" in keys(json) - reserves.spinning = - timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) - end + reserves.spinning = + timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) + reserves.upflexiramp = + timeseries(json["Reserves"]["Up-flexiramp (MW)"], default = zeros(T)) + reserves.dwflexiramp = + timeseries(json["Reserves"]["Down-flexiramp (MW)"], default = zeros(T)) + end # Read transmission lines if "Transmission lines" in keys(json) @@ -287,6 +300,7 @@ function _from_json(json; repair = true) price_sensitive_loads = loads, reserves = reserves, shortfall_penalty = shortfall_penalty, + flexiramp_shortfall_penalty = flexiramp_shortfall_penalty, time = T, units_by_name = Dict(g.name => g for g in units), units = units, diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 1e4cd5c..8555316 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -37,6 +37,7 @@ mutable struct Unit initial_status::Union{Int,Nothing} initial_power::Union{Float64,Nothing} provides_spinning_reserves::Vector{Bool} + provides_flexiramp_reserves::Vector{Bool} # binary variable indicating whether the unit provides flexiramp startup_categories::Vector{StartupCategory} end @@ -54,6 +55,8 @@ end mutable struct Reserves spinning::Vector{Float64} + upflexiramp::Vector{Float64} # up-flexiramp reserve requirements + dwflexiramp::Vector{Float64} # down-flexiramp reserve requirements end mutable struct Contingency @@ -81,6 +84,7 @@ Base.@kwdef mutable struct UnitCommitmentInstance price_sensitive_loads::Vector{PriceSensitiveLoad} reserves::Reserves shortfall_penalty::Vector{Float64} + flexiramp_shortfall_penalty::Vector{Float64} # penalty price for flexiramp shortfall time::Int units_by_name::Dict{AbstractString,Unit} units::Vector{Unit} diff --git a/src/model/.DS_Store b/src/model/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..04abbce9608c659e5912cce00bfb1671bd6d1a6a GIT binary patch literal 6148 zcmeHK%Sr=55Ukc50(!{NNwKv*^&Jb006=&qTb zu35GY+uHzaeSEwHRsfcCM|^mgo1eSS?4mM8r1Op=c6h)$Ua+54pHDdV8c(c0;B~?u z@OIiBhTVR6&il__psW;-0#ZNOzYldUEci>LGc7Ui&> zs3--bz*K?D+^)R;-_d`V|EDDFq<|FoR|?o-v)!!uO4VCuFXz3s(eLSA^GSE(Iw%a$ lj)~EZx$$;<6Gd6qe9iM-I3@<2`JfZ^GvK<&q`+S*@CEsA7Nr0H literal 0 HcmV?d00001 diff --git a/src/model/build.jl b/src/model/build.jl index 87a9a66..a9b766e 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -32,6 +32,14 @@ function build_model(; formulation = Formulation(), variable_names::Bool = false, )::JuMP.Model + if formulation.ramping ==WanHob2016.Ramping() && instance.reserves.spinning!=zeros(instance.time) + error("Spinning reserves are not supported by the WanHob2016 ramping formulation") + end + @show formulation.ramping + if formulation.ramping !== WanHob2016.Ramping() && (instance.reserves.upflexiramp!=zeros(instance.time) || instance.reserves.dwflexiramp!=zeros(instance.time)) + error("Flexiramp is supported only by the WanHob2016 ramping formulation") + end + @info "Building model..." time_model = @elapsed begin model = Model() diff --git a/src/model/formulations/WanHob2016/ramp.jl b/src/model/formulations/WanHob2016/ramp.jl new file mode 100644 index 0000000..b3d7198 --- /dev/null +++ b/src/model/formulations/WanHob2016/ramp.jl @@ -0,0 +1,152 @@ +# UnitCommitmentFL.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_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing + upflexiramp = _init(model, :upflexiramp) + upflexiramp_shortfall = _init(model, :upflexiramp_shortfall) + mfg=_init(model,:mfg) + dwflexiramp = _init(model, :dwflexiramp) + dwflexiramp_shortfall = _init(model, :dwflexiramp_shortfall) + for t in 1:model[:instance].time + # maximum feasible generation, \bar{g_{its}} in Wang & Hobbs (2016) + mfg[g.name,t]=@variable(model, lower_bound = 0) + if g.provides_flexiramp_reserves[t] + upflexiramp[g.name, t] = @variable(model) # up-flexiramp, ur_{it} in Wang & Hobbs (2016) + dwflexiramp[g.name, t] = @variable(model) # down-flexiramp, dr_{it} in Wang & Hobbs (2016) + else + upflexiramp[g.name, t] = 0.0 + dwflexiramp[g.name, t] = 0.0 + end + upflexiramp_shortfall[t] = + (model[:instance].flexiramp_shortfall_penalty[t] >= 0) ? + @variable(model, lower_bound = 0) : 0.0 + dwflexiramp_shortfall[t] = + (model[:instance].flexiramp_shortfall_penalty[t] >= 0) ? + @variable(model, lower_bound = 0) : 0.0 + end + return +end + + + +function _add_ramp_eqs!( + model::JuMP.Model, + g::Unit, + formulation_prod_vars::Gar1962.ProdVars, + formulation_ramping::WanHob2016.Ramping, + formulation_status_vars::Gar1962.StatusVars, +)::Nothing + is_initially_on = (g.initial_status > 0) + SU = g.startup_limit + SD = g.shutdown_limit + RU = g.ramp_up_limit + RD = g.ramp_down_limit + gn = g.name + minp=g.min_power + maxp=g.max_power + initial_power=g.initial_power + + is_on = model[:is_on] + prod_above = model[:prod_above] + upflexiramp=model[:upflexiramp] + dwflexiramp=model[:dwflexiramp] + mfg=model[:mfg] + + for t in 1:model[:instance].time + + @constraint(model, prod_above[gn, t] + (is_on[gn,t]*minp[t]) + <=mfg[gn,t]) # Eq. (19) in Wang & Hobbs (2016) + @constraint(model, mfg[gn,t]<= is_on[gn,t]* maxp[t]) # Eq. (22) in Wang & Hobbs (2016) + if t!=model[:instance].time + @constraint(model, minp[t] * (is_on[gn,t+1]+is_on[gn,t]-1) <= + prod_above[gn, t] - dwflexiramp[gn,t] +(is_on[gn,t]*minp[t]) + ) # first inequality of Eq. (20) in Wang & Hobbs (2016) + @constraint(model, prod_above[gn, t] - dwflexiramp[gn,t] + (is_on[gn,t]*minp[t]) <= + mfg[gn,t+1] + + (maxp[t] * (1-is_on[gn,t+1])) + ) # second inequality of Eq. (20) in Wang & Hobbs (2016) + @constraint(model, minp[t] * (is_on[gn,t+1]+is_on[gn,t]-1) <= + prod_above[gn, t] + upflexiramp[gn,t] + (is_on[gn,t]*minp[t]) + ) # first inequality of Eq. (21) in Wang & Hobbs (2016) + @constraint(model, prod_above[gn, t] + upflexiramp[gn,t] +(is_on[gn,t]*minp[t]) <= + mfg[gn,t+1] + (maxp[t] * (1-is_on[gn,t+1])) + ) # second inequality of Eq. (21) in Wang & Hobbs (2016) + if t!=1 + @constraint(model, mfg[gn,t]<=prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t]) + + (RU * is_on[gn,t-1]) + + (SU*(is_on[gn,t] - is_on[gn,t-1])) + + maxp[t] * (1-is_on[gn,t]) + ) # Eq. (23) in Wang & Hobbs (2016) + @constraint(model, (prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t])) + - (prod_above[gn,t] + (is_on[gn,t]*minp[t])) + <= RD * is_on[gn,t] + + SD * (is_on[gn,t-1] - is_on[gn,t]) + + maxp[t] * (1-is_on[gn,t-1]) + ) # Eq. (25) in Wang & Hobbs (2016) + else + @constraint(model, mfg[gn,t]<=initial_power + + (RU * is_initially_on) + + (SU*(is_on[gn,t] - is_initially_on)) + + maxp[t] * (1-is_on[gn,t]) + ) # Eq. (23) in Wang & Hobbs (2016) for the first time period + @constraint(model, initial_power + - (prod_above[gn,t] + (is_on[gn,t]*minp[t])) + <= RD * is_on[gn,t] + + SD * (is_initially_on - is_on[gn,t]) + + maxp[t] * (1-is_initially_on) + ) # Eq. (25) in Wang & Hobbs (2016) for the first time period + end + @constraint(model, mfg[gn,t]<= + (SD*(is_on[gn,t] - is_on[gn,t+1])) + + (maxp[t] * is_on[gn,t+1]) + ) # Eq. (24) in Wang & Hobbs (2016) + @constraint(model, -RD * is_on[gn,t+1] + -SD * (is_on[gn,t]-is_on[gn,t+1]) + -maxp[t] * (1-is_on[gn,t]) + <= upflexiramp[gn,t] + ) # first inequality of Eq. (26) in Wang & Hobbs (2016) + @constraint(model, upflexiramp[gn,t] <= + RU * is_on[gn,t] + + SU * (is_on[gn,t+1]-is_on[gn,t]) + + maxp[t] * (1-is_on[gn,t+1]) + ) # second inequality of Eq. (26) in Wang & Hobbs (2016) + @constraint(model, -RU * is_on[gn,t] + -SU * (is_on[gn,t+1]-is_on[gn,t]) + -maxp[t] * (1-is_on[gn,t+1]) + <= dwflexiramp[gn,t] + ) # first inequality of Eq. (27) in Wang & Hobbs (2016) + @constraint(model, dwflexiramp[gn,t] <= + RD * is_on[gn,t+1] + + SD * (is_on[gn,t]-is_on[gn,t+1]) + + maxp[t] * (1-is_on[gn,t]) + ) # second inequality of Eq. (27) in Wang & Hobbs (2016) + @constraint(model, -maxp[t] * is_on[gn,t] + +minp[t] * is_on[gn,t+1] + <= upflexiramp[gn,t] + ) # first inequality of Eq. (28) in Wang & Hobbs (2016) + @constraint(model, upflexiramp[gn,t] <= + maxp[t] * is_on[gn,t+1] + ) # second inequality of Eq. (28) in Wang & Hobbs (2016) + @constraint(model, -maxp[t] * is_on[gn,t+1] + <= dwflexiramp[gn,t] + ) # first inequality of Eq. (29) in Wang & Hobbs (2016) + @constraint(model, dwflexiramp[gn,t] <= + (maxp[t] * is_on[gn,t]) + -(minp[t] * is_on[gn,t+1]) + ) # second inequality of Eq. (29) in Wang & Hobbs (2016) + else + @constraint(model, mfg[gn,t]<=prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t]) + + (RU * is_on[gn,t-1]) + + (SU*(is_on[gn,t] - is_on[gn,t-1])) + + maxp[t] * (1-is_on[gn,t]) + ) # Eq. (23) in Wang & Hobbs (2016) for the last time period + @constraint(model, (prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t])) + - (prod_above[gn,t] + (is_on[gn,t]*minp[t])) + <= RD * is_on[gn,t] + + SD * (is_on[gn,t-1] - is_on[gn,t]) + + maxp[t] * (1-is_on[gn,t-1]) + ) # Eq. (25) in Wang & Hobbs (2016) for the last time period + end + end +end \ No newline at end of file diff --git a/src/model/formulations/WanHob2016/structs.jl b/src/model/formulations/WanHob2016/structs.jl new file mode 100644 index 0000000..a8c33f8 --- /dev/null +++ b/src/model/formulations/WanHob2016/structs.jl @@ -0,0 +1,18 @@ +# UnitCommitmentFL.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. + +""" +Formulation described in: + B. Wang and B. F. Hobbs, "Real-Time Markets for Flexiramp: A Stochastic + Unit Commitment-Based Analysis," in IEEE Transactions on Power Systems, + vol. 31, no. 2, pp. 846-860, March 2016, doi: 10.1109/TPWRS.2015.2411268. +""" +module WanHob2016 + +import ..RampingFormulation + + +struct Ramping <: RampingFormulation end + +end diff --git a/src/model/formulations/base/system.jl b/src/model/formulations/base/system.jl index d6bf573..d4944e1 100644 --- a/src/model/formulations/base/system.jl +++ b/src/model/formulations/base/system.jl @@ -5,6 +5,7 @@ function _add_system_wide_eqs!(model::JuMP.Model)::Nothing _add_net_injection_eqs!(model) _add_reserve_eqs!(model) + _add_flexiramp_eqs!(model) # Add system-wide flexiramp requirements return end @@ -54,3 +55,41 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing end return end + +function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing + # Note: The flexpramp requirements in Wang & Hobbs (2016) are imposed as hard constraints + # through Eq. (17) and Eq. (18). The constraints eq_min_upflexiramp[t] and eq_min_dwflexiramp[t] + # provided below are modified versions of Eq. (17) and Eq. (18), respectively, in that + # they include slack variables for flexiramp shortfall, which are penalized in the + # objective function. + eq_min_upflexiramp = _init(model, :eq_min_upflexiramp) + eq_min_dwflexiramp = _init(model, :eq_min_dwflexiramp) + instance = model[:instance] + for t in 1:instance.time + flexiramp_shortfall_penalty = instance.flexiramp_shortfall_penalty[t] + # Eq. (17) in Wang & Hobbs (2016) + eq_min_upflexiramp[t] = @constraint( + model, + sum(model[:upflexiramp][g.name, t] for g in instance.units) + + (flexiramp_shortfall_penalty >= 0 ? model[:upflexiramp_shortfall][t] : 0.0) >= + instance.reserves.upflexiramp[t] + ) + # Eq. (18) in Wang & Hobbs (2016) + eq_min_dwflexiramp[t] = @constraint( + model, + sum(model[:dwflexiramp][g.name, t] for g in instance.units) + + (flexiramp_shortfall_penalty >= 0 ? model[:dwflexiramp_shortfall][t] : 0.0) >= + instance.reserves.dwflexiramp[t] + ) + + # Account for flexiramp shortfall contribution to objective + if flexiramp_shortfall_penalty >= 0 + add_to_expression!( + model[:obj], + flexiramp_shortfall_penalty, + (model[:upflexiramp_shortfall][t]+model[:dwflexiramp_shortfall][t]), + ) + end + end + return +end diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index e701977..5da9ffa 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -13,6 +13,7 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation) # Variables _add_production_vars!(model, g, formulation.prod_vars) _add_reserve_vars!(model, g) + _add_flexiramp_vars!(model, g) # Add variables for flexiramp _add_startup_shutdown_vars!(model, g) _add_status_vars!(model, g, formulation.status_vars) diff --git a/src/solution/solution.jl b/src/solution/solution.jl index 5fd8bbd..0e95fa0 100644 --- a/src/solution/solution.jl +++ b/src/solution/solution.jl @@ -50,13 +50,35 @@ function solution(model::JuMP.Model)::OrderedDict sol["Is on"] = timeseries(model[:is_on], instance.units) sol["Switch on"] = timeseries(model[:switch_on], instance.units) sol["Switch off"] = timeseries(model[:switch_off], instance.units) - sol["Reserve (MW)"] = timeseries(model[:reserve], instance.units) - sol["Reserve shortfall (MW)"] = OrderedDict( - t => - (instance.shortfall_penalty[t] >= 0) ? - round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for - t in 1:instance.time - ) + if instance.reserves.upflexiramp != zeros(T) || instance.reserves.dwflexiramp != zeros(T) + # Report flexiramp solutions only if either of the up-flexiramp and + # down-flexiramp requirements is not a default array of zeros + sol["Up-flexiramp (MW)"] = timeseries(model[:upflexiramp], instance.units) + sol["Up-flexiramp shortfall (MW)"] = OrderedDict( + t => + (instance.flexiramp_shortfall_penalty[t] >= 0) ? + round(value(model[:upflexiramp_shortfall][t]), digits = 5) : 0.0 for + t in 1:instance.time + ) + sol["Down-flexiramp (MW)"] = timeseries(model[:dwflexiramp], instance.units) + sol["Down-flexiramp shortfall (MW)"] = OrderedDict( + t => + (instance.flexiramp_shortfall_penalty[t] >= 0) ? + round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : 0.0 for + t in 1:instance.time + ) + else + # Report spinning reserve solutions only if both up-flexiramp and + # down-flexiramp requirements are arrays of zeros. + sol["Reserve (MW)"] = timeseries(model[:reserve], instance.units) + sol["Reserve shortfall (MW)"] = OrderedDict( + t => + (instance.shortfall_penalty[t] >= 0) ? + round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for + t in 1:instance.time + ) + + end sol["Net injection (MW)"] = timeseries(model[:net_injection], instance.buses) sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses) diff --git a/src/validation/validate.jl b/src/validation/validate.jl index d342ef9..39546a9 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -338,6 +338,40 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) ) err_count += 1 end + + upflexiramp = + sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) + upflexiramp_shortfall = + (instance.flexiramp_shortfall_penalty[t] >= 0) ? + solution["Up-flexiramp shortfall (MW)"][t] : 0 + + if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol + @error @sprintf( + "Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)", + t, + upflexiramp, + upflexiramp_shortfall, + instance.reserves.upflexiramp[t], + ) + err_count += 1 + end + + dwflexiramp = + sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) + dwflexiramp_shortfall = + (instance.flexiramp_shortfall_penalty[t] >= 0) ? + solution["Down-flexiramp shortfall (MW)"][t] : 0 + + if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol + @error @sprintf( + "Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)", + t, + dwflexiramp, + dwflexiramp_shortfall, + instance.reserves.dwflexiramp[t], + ) + err_count += 1 + end end return err_count From 360308ef4af3d6afd284ee5fed894a430a1b0db6 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Tue, 1 Mar 2022 16:26:51 -0600 Subject: [PATCH 02/22] Reformat source code --- src/instance/read.jl | 23 +- src/model/build.jl | 16 +- src/model/formulations/WanHob2016/ramp.jl | 237 +++++++++++-------- src/model/formulations/WanHob2016/structs.jl | 1 - src/model/formulations/base/system.jl | 19 +- src/solution/solution.jl | 24 +- src/validation/validate.jl | 16 +- 7 files changed, 198 insertions(+), 138 deletions(-) diff --git a/src/instance/read.jl b/src/instance/read.jl index 91b711f..09c2da4 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -23,7 +23,10 @@ Example import UnitCommitment instance = UnitCommitment.read_benchmark("matpower/case3375wp/2017-02-01") """ -function read_benchmark(name::AbstractString; quiet::Bool=false)::UnitCommitmentInstance +function read_benchmark( + name::AbstractString; + quiet::Bool = false, +)::UnitCommitmentInstance basedir = dirname(@__FILE__) filename = "$basedir/../../instances/$name.json.gz" url = "$INSTANCES_URL/$name.json.gz" @@ -225,13 +228,17 @@ function _from_json(json; repair = true) # Read spinning, up-flexiramp, and down-flexiramp reserve requirements reserves = Reserves(zeros(T), zeros(T), zeros(T)) if "Reserves" in keys(json) - reserves.spinning = - timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) - reserves.upflexiramp = - timeseries(json["Reserves"]["Up-flexiramp (MW)"], default = zeros(T)) - reserves.dwflexiramp = - timeseries(json["Reserves"]["Down-flexiramp (MW)"], default = zeros(T)) - end + reserves.spinning = + timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) + reserves.upflexiramp = timeseries( + json["Reserves"]["Up-flexiramp (MW)"], + default = zeros(T), + ) + reserves.dwflexiramp = timeseries( + json["Reserves"]["Down-flexiramp (MW)"], + default = zeros(T), + ) + end # Read transmission lines if "Transmission lines" in keys(json) diff --git a/src/model/build.jl b/src/model/build.jl index a9b766e..9dd271c 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -32,12 +32,20 @@ function build_model(; formulation = Formulation(), variable_names::Bool = false, )::JuMP.Model - if formulation.ramping ==WanHob2016.Ramping() && instance.reserves.spinning!=zeros(instance.time) - error("Spinning reserves are not supported by the WanHob2016 ramping formulation") + if formulation.ramping == WanHob2016.Ramping() && + instance.reserves.spinning != zeros(instance.time) + error( + "Spinning reserves are not supported by the WanHob2016 ramping formulation", + ) end @show formulation.ramping - if formulation.ramping !== WanHob2016.Ramping() && (instance.reserves.upflexiramp!=zeros(instance.time) || instance.reserves.dwflexiramp!=zeros(instance.time)) - error("Flexiramp is supported only by the WanHob2016 ramping formulation") + if formulation.ramping !== WanHob2016.Ramping() && ( + instance.reserves.upflexiramp != zeros(instance.time) || + instance.reserves.dwflexiramp != zeros(instance.time) + ) + error( + "Flexiramp is supported only by the WanHob2016 ramping formulation", + ) end @info "Building model..." diff --git a/src/model/formulations/WanHob2016/ramp.jl b/src/model/formulations/WanHob2016/ramp.jl index b3d7198..8a999f1 100644 --- a/src/model/formulations/WanHob2016/ramp.jl +++ b/src/model/formulations/WanHob2016/ramp.jl @@ -5,12 +5,12 @@ function _add_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing upflexiramp = _init(model, :upflexiramp) upflexiramp_shortfall = _init(model, :upflexiramp_shortfall) - mfg=_init(model,:mfg) + mfg = _init(model, :mfg) dwflexiramp = _init(model, :dwflexiramp) dwflexiramp_shortfall = _init(model, :dwflexiramp_shortfall) for t in 1:model[:instance].time # maximum feasible generation, \bar{g_{its}} in Wang & Hobbs (2016) - mfg[g.name,t]=@variable(model, lower_bound = 0) + mfg[g.name, t] = @variable(model, lower_bound = 0) if g.provides_flexiramp_reserves[t] upflexiramp[g.name, t] = @variable(model) # up-flexiramp, ur_{it} in Wang & Hobbs (2016) dwflexiramp[g.name, t] = @variable(model) # down-flexiramp, dr_{it} in Wang & Hobbs (2016) @@ -28,8 +28,6 @@ function _add_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing return end - - function _add_ramp_eqs!( model::JuMP.Model, g::Unit, @@ -43,110 +41,145 @@ function _add_ramp_eqs!( RU = g.ramp_up_limit RD = g.ramp_down_limit gn = g.name - minp=g.min_power - maxp=g.max_power - initial_power=g.initial_power + minp = g.min_power + maxp = g.max_power + initial_power = g.initial_power is_on = model[:is_on] prod_above = model[:prod_above] - upflexiramp=model[:upflexiramp] - dwflexiramp=model[:dwflexiramp] - mfg=model[:mfg] - + upflexiramp = model[:upflexiramp] + dwflexiramp = model[:dwflexiramp] + mfg = model[:mfg] + for t in 1:model[:instance].time - - @constraint(model, prod_above[gn, t] + (is_on[gn,t]*minp[t]) - <=mfg[gn,t]) # Eq. (19) in Wang & Hobbs (2016) - @constraint(model, mfg[gn,t]<= is_on[gn,t]* maxp[t]) # Eq. (22) in Wang & Hobbs (2016) - if t!=model[:instance].time - @constraint(model, minp[t] * (is_on[gn,t+1]+is_on[gn,t]-1) <= - prod_above[gn, t] - dwflexiramp[gn,t] +(is_on[gn,t]*minp[t]) - ) # first inequality of Eq. (20) in Wang & Hobbs (2016) - @constraint(model, prod_above[gn, t] - dwflexiramp[gn,t] + (is_on[gn,t]*minp[t]) <= - mfg[gn,t+1] - + (maxp[t] * (1-is_on[gn,t+1])) - ) # second inequality of Eq. (20) in Wang & Hobbs (2016) - @constraint(model, minp[t] * (is_on[gn,t+1]+is_on[gn,t]-1) <= - prod_above[gn, t] + upflexiramp[gn,t] + (is_on[gn,t]*minp[t]) - ) # first inequality of Eq. (21) in Wang & Hobbs (2016) - @constraint(model, prod_above[gn, t] + upflexiramp[gn,t] +(is_on[gn,t]*minp[t]) <= - mfg[gn,t+1] + (maxp[t] * (1-is_on[gn,t+1])) - ) # second inequality of Eq. (21) in Wang & Hobbs (2016) - if t!=1 - @constraint(model, mfg[gn,t]<=prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t]) - + (RU * is_on[gn,t-1]) - + (SU*(is_on[gn,t] - is_on[gn,t-1])) - + maxp[t] * (1-is_on[gn,t]) - ) # Eq. (23) in Wang & Hobbs (2016) - @constraint(model, (prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t])) - - (prod_above[gn,t] + (is_on[gn,t]*minp[t])) - <= RD * is_on[gn,t] - + SD * (is_on[gn,t-1] - is_on[gn,t]) - + maxp[t] * (1-is_on[gn,t-1]) - ) # Eq. (25) in Wang & Hobbs (2016) + @constraint( + model, + prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t] + ) # Eq. (19) in Wang & Hobbs (2016) + @constraint(model, mfg[gn, t] <= is_on[gn, t] * maxp[t]) # Eq. (22) in Wang & Hobbs (2016) + if t != model[:instance].time + @constraint( + model, + minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <= + prod_above[gn, t] - dwflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) + ) # first inequality of Eq. (20) in Wang & Hobbs (2016) + @constraint( + model, + prod_above[gn, t] - dwflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) <= + mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1])) + ) # second inequality of Eq. (20) in Wang & Hobbs (2016) + @constraint( + model, + minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <= + prod_above[gn, t] + + upflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) + ) # first inequality of Eq. (21) in Wang & Hobbs (2016) + @constraint( + model, + prod_above[gn, t] + + upflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) <= + mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1])) + ) # second inequality of Eq. (21) in Wang & Hobbs (2016) + if t != 1 + @constraint( + model, + mfg[gn, t] <= + prod_above[gn, t-1] + + (is_on[gn, t-1] * minp[t]) + + (RU * is_on[gn, t-1]) + + (SU * (is_on[gn, t] - is_on[gn, t-1])) + + maxp[t] * (1 - is_on[gn, t]) + ) # Eq. (23) in Wang & Hobbs (2016) + @constraint( + model, + (prod_above[gn, t-1] + (is_on[gn, t-1] * minp[t])) - + (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= + RD * is_on[gn, t] + + SD * (is_on[gn, t-1] - is_on[gn, t]) + + maxp[t] * (1 - is_on[gn, t-1]) + ) # Eq. (25) in Wang & Hobbs (2016) else - @constraint(model, mfg[gn,t]<=initial_power - + (RU * is_initially_on) - + (SU*(is_on[gn,t] - is_initially_on)) - + maxp[t] * (1-is_on[gn,t]) - ) # Eq. (23) in Wang & Hobbs (2016) for the first time period - @constraint(model, initial_power - - (prod_above[gn,t] + (is_on[gn,t]*minp[t])) - <= RD * is_on[gn,t] - + SD * (is_initially_on - is_on[gn,t]) - + maxp[t] * (1-is_initially_on) - ) # Eq. (25) in Wang & Hobbs (2016) for the first time period + @constraint( + model, + mfg[gn, t] <= + initial_power + + (RU * is_initially_on) + + (SU * (is_on[gn, t] - is_initially_on)) + + maxp[t] * (1 - is_on[gn, t]) + ) # Eq. (23) in Wang & Hobbs (2016) for the first time period + @constraint( + model, + initial_power - + (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= + RD * is_on[gn, t] + + SD * (is_initially_on - is_on[gn, t]) + + maxp[t] * (1 - is_initially_on) + ) # Eq. (25) in Wang & Hobbs (2016) for the first time period end - @constraint(model, mfg[gn,t]<= - (SD*(is_on[gn,t] - is_on[gn,t+1])) - + (maxp[t] * is_on[gn,t+1]) - ) # Eq. (24) in Wang & Hobbs (2016) - @constraint(model, -RD * is_on[gn,t+1] - -SD * (is_on[gn,t]-is_on[gn,t+1]) - -maxp[t] * (1-is_on[gn,t]) - <= upflexiramp[gn,t] - ) # first inequality of Eq. (26) in Wang & Hobbs (2016) - @constraint(model, upflexiramp[gn,t] <= - RU * is_on[gn,t] - + SU * (is_on[gn,t+1]-is_on[gn,t]) - + maxp[t] * (1-is_on[gn,t+1]) - ) # second inequality of Eq. (26) in Wang & Hobbs (2016) - @constraint(model, -RU * is_on[gn,t] - -SU * (is_on[gn,t+1]-is_on[gn,t]) - -maxp[t] * (1-is_on[gn,t+1]) - <= dwflexiramp[gn,t] - ) # first inequality of Eq. (27) in Wang & Hobbs (2016) - @constraint(model, dwflexiramp[gn,t] <= - RD * is_on[gn,t+1] - + SD * (is_on[gn,t]-is_on[gn,t+1]) - + maxp[t] * (1-is_on[gn,t]) - ) # second inequality of Eq. (27) in Wang & Hobbs (2016) - @constraint(model, -maxp[t] * is_on[gn,t] - +minp[t] * is_on[gn,t+1] - <= upflexiramp[gn,t] - ) # first inequality of Eq. (28) in Wang & Hobbs (2016) - @constraint(model, upflexiramp[gn,t] <= - maxp[t] * is_on[gn,t+1] - ) # second inequality of Eq. (28) in Wang & Hobbs (2016) - @constraint(model, -maxp[t] * is_on[gn,t+1] - <= dwflexiramp[gn,t] - ) # first inequality of Eq. (29) in Wang & Hobbs (2016) - @constraint(model, dwflexiramp[gn,t] <= - (maxp[t] * is_on[gn,t]) - -(minp[t] * is_on[gn,t+1]) - ) # second inequality of Eq. (29) in Wang & Hobbs (2016) + @constraint( + model, + mfg[gn, t] <= + (SD * (is_on[gn, t] - is_on[gn, t+1])) + + (maxp[t] * is_on[gn, t+1]) + ) # Eq. (24) in Wang & Hobbs (2016) + @constraint( + model, + -RD * is_on[gn, t+1] - SD * (is_on[gn, t] - is_on[gn, t+1]) - + maxp[t] * (1 - is_on[gn, t]) <= upflexiramp[gn, t] + ) # first inequality of Eq. (26) in Wang & Hobbs (2016) + @constraint( + model, + upflexiramp[gn, t] <= + RU * is_on[gn, t] + + SU * (is_on[gn, t+1] - is_on[gn, t]) + + maxp[t] * (1 - is_on[gn, t+1]) + ) # second inequality of Eq. (26) in Wang & Hobbs (2016) + @constraint( + model, + -RU * is_on[gn, t] - SU * (is_on[gn, t+1] - is_on[gn, t]) - + maxp[t] * (1 - is_on[gn, t+1]) <= dwflexiramp[gn, t] + ) # first inequality of Eq. (27) in Wang & Hobbs (2016) + @constraint( + model, + dwflexiramp[gn, t] <= + RD * is_on[gn, t+1] + + SD * (is_on[gn, t] - is_on[gn, t+1]) + + maxp[t] * (1 - is_on[gn, t]) + ) # second inequality of Eq. (27) in Wang & Hobbs (2016) + @constraint( + model, + -maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <= + upflexiramp[gn, t] + ) # first inequality of Eq. (28) in Wang & Hobbs (2016) + @constraint(model, upflexiramp[gn, t] <= maxp[t] * is_on[gn, t+1]) # second inequality of Eq. (28) in Wang & Hobbs (2016) + @constraint(model, -maxp[t] * is_on[gn, t+1] <= dwflexiramp[gn, t]) # first inequality of Eq. (29) in Wang & Hobbs (2016) + @constraint( + model, + dwflexiramp[gn, t] <= + (maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1]) + ) # second inequality of Eq. (29) in Wang & Hobbs (2016) else - @constraint(model, mfg[gn,t]<=prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t]) - + (RU * is_on[gn,t-1]) - + (SU*(is_on[gn,t] - is_on[gn,t-1])) - + maxp[t] * (1-is_on[gn,t]) - ) # Eq. (23) in Wang & Hobbs (2016) for the last time period - @constraint(model, (prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t])) - - (prod_above[gn,t] + (is_on[gn,t]*minp[t])) - <= RD * is_on[gn,t] - + SD * (is_on[gn,t-1] - is_on[gn,t]) - + maxp[t] * (1-is_on[gn,t-1]) - ) # Eq. (25) in Wang & Hobbs (2016) for the last time period + @constraint( + model, + mfg[gn, t] <= + prod_above[gn, t-1] + + (is_on[gn, t-1] * minp[t]) + + (RU * is_on[gn, t-1]) + + (SU * (is_on[gn, t] - is_on[gn, t-1])) + + maxp[t] * (1 - is_on[gn, t]) + ) # Eq. (23) in Wang & Hobbs (2016) for the last time period + @constraint( + model, + (prod_above[gn, t-1] + (is_on[gn, t-1] * minp[t])) - + (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= + RD * is_on[gn, t] + + SD * (is_on[gn, t-1] - is_on[gn, t]) + + maxp[t] * (1 - is_on[gn, t-1]) + ) # Eq. (25) in Wang & Hobbs (2016) for the last time period end end -end \ No newline at end of file +end diff --git a/src/model/formulations/WanHob2016/structs.jl b/src/model/formulations/WanHob2016/structs.jl index a8c33f8..52f621b 100644 --- a/src/model/formulations/WanHob2016/structs.jl +++ b/src/model/formulations/WanHob2016/structs.jl @@ -12,7 +12,6 @@ module WanHob2016 import ..RampingFormulation - struct Ramping <: RampingFormulation end end diff --git a/src/model/formulations/base/system.jl b/src/model/formulations/base/system.jl index d4944e1..c526040 100644 --- a/src/model/formulations/base/system.jl +++ b/src/model/formulations/base/system.jl @@ -57,7 +57,7 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing end function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing - # Note: The flexpramp requirements in Wang & Hobbs (2016) are imposed as hard constraints + # Note: The flexpramp requirements in Wang & Hobbs (2016) are imposed as hard constraints # through Eq. (17) and Eq. (18). The constraints eq_min_upflexiramp[t] and eq_min_dwflexiramp[t] # provided below are modified versions of Eq. (17) and Eq. (18), respectively, in that # they include slack variables for flexiramp shortfall, which are penalized in the @@ -71,15 +71,19 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing eq_min_upflexiramp[t] = @constraint( model, sum(model[:upflexiramp][g.name, t] for g in instance.units) + - (flexiramp_shortfall_penalty >= 0 ? model[:upflexiramp_shortfall][t] : 0.0) >= - instance.reserves.upflexiramp[t] + ( + flexiramp_shortfall_penalty >= 0 ? + model[:upflexiramp_shortfall][t] : 0.0 + ) >= instance.reserves.upflexiramp[t] ) # Eq. (18) in Wang & Hobbs (2016) eq_min_dwflexiramp[t] = @constraint( model, sum(model[:dwflexiramp][g.name, t] for g in instance.units) + - (flexiramp_shortfall_penalty >= 0 ? model[:dwflexiramp_shortfall][t] : 0.0) >= - instance.reserves.dwflexiramp[t] + ( + flexiramp_shortfall_penalty >= 0 ? + model[:dwflexiramp_shortfall][t] : 0.0 + ) >= instance.reserves.dwflexiramp[t] ) # Account for flexiramp shortfall contribution to objective @@ -87,7 +91,10 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing add_to_expression!( model[:obj], flexiramp_shortfall_penalty, - (model[:upflexiramp_shortfall][t]+model[:dwflexiramp_shortfall][t]), + ( + model[:upflexiramp_shortfall][t] + + model[:dwflexiramp_shortfall][t] + ), ) end end diff --git a/src/solution/solution.jl b/src/solution/solution.jl index 0e95fa0..0283eac 100644 --- a/src/solution/solution.jl +++ b/src/solution/solution.jl @@ -50,22 +50,25 @@ function solution(model::JuMP.Model)::OrderedDict sol["Is on"] = timeseries(model[:is_on], instance.units) sol["Switch on"] = timeseries(model[:switch_on], instance.units) sol["Switch off"] = timeseries(model[:switch_off], instance.units) - if instance.reserves.upflexiramp != zeros(T) || instance.reserves.dwflexiramp != zeros(T) + if instance.reserves.upflexiramp != zeros(T) || + instance.reserves.dwflexiramp != zeros(T) # Report flexiramp solutions only if either of the up-flexiramp and # down-flexiramp requirements is not a default array of zeros - sol["Up-flexiramp (MW)"] = timeseries(model[:upflexiramp], instance.units) + sol["Up-flexiramp (MW)"] = + timeseries(model[:upflexiramp], instance.units) sol["Up-flexiramp shortfall (MW)"] = OrderedDict( t => (instance.flexiramp_shortfall_penalty[t] >= 0) ? - round(value(model[:upflexiramp_shortfall][t]), digits = 5) : 0.0 for - t in 1:instance.time + round(value(model[:upflexiramp_shortfall][t]), digits = 5) : + 0.0 for t in 1:instance.time ) - sol["Down-flexiramp (MW)"] = timeseries(model[:dwflexiramp], instance.units) + sol["Down-flexiramp (MW)"] = + timeseries(model[:dwflexiramp], instance.units) sol["Down-flexiramp shortfall (MW)"] = OrderedDict( t => (instance.flexiramp_shortfall_penalty[t] >= 0) ? - round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : 0.0 for - t in 1:instance.time + round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : + 0.0 for t in 1:instance.time ) else # Report spinning reserve solutions only if both up-flexiramp and @@ -74,10 +77,9 @@ function solution(model::JuMP.Model)::OrderedDict sol["Reserve shortfall (MW)"] = OrderedDict( t => (instance.shortfall_penalty[t] >= 0) ? - round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for - t in 1:instance.time - ) - + round(value(model[:reserve_shortfall][t]), digits = 5) : + 0.0 for t in 1:instance.time + ) end sol["Net injection (MW)"] = timeseries(model[:net_injection], instance.buses) diff --git a/src/validation/validate.jl b/src/validation/validate.jl index 39546a9..3708149 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -339,13 +339,15 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - upflexiramp = - sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) + upflexiramp = sum( + solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units + ) upflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Up-flexiramp shortfall (MW)"][t] : 0 - if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol + if upflexiramp + upflexiramp_shortfall < + instance.reserves.upflexiramp[t] - tol @error @sprintf( "Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)", t, @@ -356,13 +358,15 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - dwflexiramp = - sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) + dwflexiramp = sum( + solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units + ) dwflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Down-flexiramp shortfall (MW)"][t] : 0 - if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol + if dwflexiramp + dwflexiramp_shortfall < + instance.reserves.dwflexiramp[t] - tol @error @sprintf( "Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)", t, From 0046c4ca2add2a8752cfeb738fe003736dff69de Mon Sep 17 00:00:00 2001 From: oyurdakul Date: Tue, 22 Mar 2022 19:01:20 +0100 Subject: [PATCH 03/22] change the validation of reserves --- src/validation/validate.jl | 58 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/validation/validate.jl b/src/validation/validate.jl index 3708149..2b816a1 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -321,33 +321,17 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - # Verify spinning reserves - reserve = - sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) - reserve_shortfall = - (instance.shortfall_penalty[t] >= 0) ? - solution["Reserve shortfall (MW)"][t] : 0 - - if reserve + reserve_shortfall < instance.reserves.spinning[t] - tol - @error @sprintf( - "Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)", - t, - reserve, - reserve_shortfall, - instance.reserves.spinning[t], - ) - err_count += 1 - end - - upflexiramp = sum( - solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units - ) + + # Verify flexiramp solutions only if either of the up-flexiramp and + # down-flexiramp requirements is not a default array of zeros + if instance.reserves.upflexiramp != zeros(T) || instance.reserves.dwflexiramp != zeros(T) + upflexiramp = + sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) upflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Up-flexiramp shortfall (MW)"][t] : 0 - if upflexiramp + upflexiramp_shortfall < - instance.reserves.upflexiramp[t] - tol + if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol @error @sprintf( "Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)", t, @@ -358,15 +342,13 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - dwflexiramp = sum( - solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units - ) + dwflexiramp = + sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) dwflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Down-flexiramp shortfall (MW)"][t] : 0 - if dwflexiramp + dwflexiramp_shortfall < - instance.reserves.dwflexiramp[t] - tol + if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol @error @sprintf( "Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)", t, @@ -376,7 +358,27 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) ) err_count += 1 end + # Verify spinning reserve solutions only if both up-flexiramp and + # down-flexiramp requirements are arrays of zeros. + else + reserve = + sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) + reserve_shortfall = + (instance.shortfall_penalty[t] >= 0) ? + solution["Reserve shortfall (MW)"][t] : 0 + + if reserve + reserve_shortfall < instance.reserves.spinning[t] - tol + @error @sprintf( + "Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)", + t, + reserve, + reserve_shortfall, + instance.reserves.spinning[t], + ) + err_count += 1 + end end + return err_count end From 8988b00b0794f1f27b5e7d5948e37d8bf62a0759 Mon Sep 17 00:00:00 2001 From: oyurdakul Date: Wed, 23 Mar 2022 02:39:24 +0100 Subject: [PATCH 04/22] modified validation, error scripts --- src/model/build.jl | 8 ++-- src/validation/validate.jl | 98 +++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/model/build.jl b/src/model/build.jl index 9dd271c..de81600 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -33,15 +33,15 @@ function build_model(; variable_names::Bool = false, )::JuMP.Model if formulation.ramping == WanHob2016.Ramping() && - instance.reserves.spinning != zeros(instance.time) + instance.reserves.spinning >= ones(instance.time).*1e-6 error( "Spinning reserves are not supported by the WanHob2016 ramping formulation", ) end - @show formulation.ramping + if formulation.ramping !== WanHob2016.Ramping() && ( - instance.reserves.upflexiramp != zeros(instance.time) || - instance.reserves.dwflexiramp != zeros(instance.time) + instance.reserves.upflexiramp >= ones(instance.time).*1e-6 || + instance.reserves.dwflexiramp >= ones(instance.time).*1e-6 ) error( "Flexiramp is supported only by the WanHob2016 ramping formulation", diff --git a/src/validation/validate.jl b/src/validation/validate.jl index 2b816a1..72da3c7 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -324,61 +324,63 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) # Verify flexiramp solutions only if either of the up-flexiramp and # down-flexiramp requirements is not a default array of zeros - if instance.reserves.upflexiramp != zeros(T) || instance.reserves.dwflexiramp != zeros(T) + if instance.reserves.upflexiramp != zeros(instance.time) || instance.reserves.dwflexiramp != zeros(instance.time) upflexiramp = sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) - upflexiramp_shortfall = - (instance.flexiramp_shortfall_penalty[t] >= 0) ? - solution["Up-flexiramp shortfall (MW)"][t] : 0 + upflexiramp_shortfall = + (instance.flexiramp_shortfall_penalty[t] >= 0) ? + solution["Up-flexiramp shortfall (MW)"][t] : 0 - if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol - @error @sprintf( - "Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)", - t, - upflexiramp, - upflexiramp_shortfall, - instance.reserves.upflexiramp[t], - ) - err_count += 1 - end + if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol + @error @sprintf( + "Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)", + t, + upflexiramp, + upflexiramp_shortfall, + instance.reserves.upflexiramp[t], + ) + err_count += 1 + end + - dwflexiramp = - sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) - dwflexiramp_shortfall = - (instance.flexiramp_shortfall_penalty[t] >= 0) ? - solution["Down-flexiramp shortfall (MW)"][t] : 0 + dwflexiramp = + sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) + dwflexiramp_shortfall = + (instance.flexiramp_shortfall_penalty[t] >= 0) ? + solution["Down-flexiramp shortfall (MW)"][t] : 0 - if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol - @error @sprintf( - "Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)", - t, - dwflexiramp, - dwflexiramp_shortfall, - instance.reserves.dwflexiramp[t], - ) - err_count += 1 - end - # Verify spinning reserve solutions only if both up-flexiramp and - # down-flexiramp requirements are arrays of zeros. - else - reserve = - sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) - reserve_shortfall = - (instance.shortfall_penalty[t] >= 0) ? - solution["Reserve shortfall (MW)"][t] : 0 - - if reserve + reserve_shortfall < instance.reserves.spinning[t] - tol - @error @sprintf( - "Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)", - t, - reserve, - reserve_shortfall, - instance.reserves.spinning[t], - ) - err_count += 1 + if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol + @error @sprintf( + "Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)", + t, + dwflexiramp, + dwflexiramp_shortfall, + instance.reserves.dwflexiramp[t], + ) + err_count += 1 + end + # Verify spinning reserve solutions only if both up-flexiramp and + # down-flexiramp requirements are arrays of zeros. + else + reserve = + sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) + reserve_shortfall = + (instance.shortfall_penalty[t] >= 0) ? + solution["Reserve shortfall (MW)"][t] : 0 + + if reserve + reserve_shortfall < instance.reserves.spinning[t] - tol + @error @sprintf( + "Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)", + t, + reserve, + reserve_shortfall, + instance.reserves.spinning[t], + ) + err_count += 1 + end end - end + end return err_count end From febb4f1aad307abacd9bb6c7d15d6a0a64fb66ed Mon Sep 17 00:00:00 2001 From: oyurdakul Date: Fri, 1 Apr 2022 15:17:14 +0200 Subject: [PATCH 05/22] new formatting --- benchmark/.DS_Store | Bin 0 -> 6148 bytes benchmark/run.jl | 12 +-- src/import/egret.jl | 2 +- src/instance/read.jl | 83 ++++++------------ src/instance/structs.jl | 5 +- src/model/build.jl | 16 ++-- src/model/formulations/ArrCon2000/ramp.jl | 17 ++-- src/model/formulations/CarArr2006/pwlcosts.jl | 22 ++--- .../formulations/DamKucRajAta2016/ramp.jl | 17 ++-- src/model/formulations/Gar1962/prod.jl | 6 +- src/model/formulations/Gar1962/pwlcosts.jl | 16 ++-- src/model/formulations/Gar1962/status.jl | 10 +-- .../formulations/KnuOstWat2018/pwlcosts.jl | 24 ++--- src/model/formulations/MorLatRam2013/ramp.jl | 15 ++-- .../formulations/MorLatRam2013/scosts.jl | 17 ++-- src/model/formulations/PanGua2016/ramp.jl | 33 +++---- src/model/formulations/WanHob2016/ramp.jl | 35 +++----- src/model/formulations/base/bus.jl | 5 +- src/model/formulations/base/line.jl | 8 +- src/model/formulations/base/psload.jl | 16 +--- src/model/formulations/base/sensitivity.jl | 12 +-- src/model/formulations/base/structs.jl | 9 +- src/model/formulations/base/system.jl | 31 +++---- src/model/formulations/base/unit.jl | 33 ++++--- src/solution/fix.jl | 8 +- .../methods/XavQiuWanThi2019/enforce.jl | 5 +- .../methods/XavQiuWanThi2019/filter.jl | 6 +- src/solution/methods/XavQiuWanThi2019/find.jl | 55 ++++-------- .../methods/XavQiuWanThi2019/optimize.jl | 5 +- src/solution/solution.jl | 46 +++++----- src/solution/warmstart.jl | 12 +-- src/transform/initcond.jl | 11 +-- src/transform/randomize/XavQiuAhm2021.jl | 25 ++---- src/utils/benchmark.jl | 5 +- src/validation/repair.jl | 6 +- src/validation/validate.jl | 62 +++++-------- test/import/egret_test.jl | 5 +- test/instance/read_test.jl | 34 +++---- test/model/formulations_test.jl | 10 +++ test/runtests.jl | 2 + .../methods/XavQiuWanThi19/find_test.jl | 6 +- test/transform/initcond_test.jl | 3 +- .../transform/randomize/XavQiuAhm2021_test.jl | 5 +- test/transform/slice_test.jl | 4 +- test/usage.jl | 2 +- 45 files changed, 274 insertions(+), 487 deletions(-) create mode 100644 benchmark/.DS_Store diff --git a/benchmark/.DS_Store b/benchmark/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e22e5657b00f358ef81453a5bffad12090cac619 GIT binary patch literal 6148 zcmeHK%}N6?5T4OW7kVk4Jb1{>Yx@RiS)X8EptOZ5Y`aUVp7*VM0H4Xb-(;rX+8l&} zNST4;n`C~H{a}*}5%K(WJtZ0wQGq7NqGUv-v!*L^z5#N|(b5YY>dmxTHJL>JaZ2_+ zqyw#JPtWvi|Jzo#`w&k(woO?s>t+c{@xB<|-hY0~a$f6Sc(qT$)^W87()`@dTeaWS z&29q=Saxv+oB?OR8E^*9WWa8dVsvI64|fKffy-k+&WC^|7!5ncbabFgB>-@M=_Jsl zmXMfW7!5l`SRkySKn-OpF<8SfAIvWrc8VHKY{duL%E#h`)9T0{k~?u!^xhe82Ko$K z>u@ah|08~xY?0p&@sTs&3|trkJSeMjj*qgt_2l#9t_^4lG!gOZM1er>JpwS0bL1`= c)gDBLUo`9#C5z0baG*Z~5+U9>1AoB47yD^5=Kufz literal 0 HcmV?d00001 diff --git a/benchmark/run.jl b/benchmark/run.jl index 9c2a662..aff8b3b 100644 --- a/benchmark/run.jl +++ b/benchmark/run.jl @@ -129,19 +129,15 @@ formulations = Dict( const gap_limit = parse(Float64, args["--gap"]) const time_limit = parse(Float64, args["--time-limit"]) methods = Dict( - "default" => XavQiuWanThi2019.Method( - time_limit = time_limit, - gap_limit = gap_limit, - ), + "default" => + XavQiuWanThi2019.Method(time_limit = time_limit, gap_limit = gap_limit), ) # MIP solvers # ----------------------------------------------------------------------------- optimizers = Dict( - "gurobi" => optimizer_with_attributes( - Gurobi.Optimizer, - "Threads" => Threads.nthreads(), - ), + "gurobi" => + optimizer_with_attributes(Gurobi.Optimizer, "Threads" => Threads.nthreads()), ) # Parse command line arguments diff --git a/src/import/egret.jl b/src/import/egret.jl index d11bdea..97c7438 100644 --- a/src/import/egret.jl +++ b/src/import/egret.jl @@ -41,7 +41,7 @@ function read_egret_solution(path::String)::OrderedDict startup_cost[gen_name] = zeros(T) production_cost[gen_name] = zeros(T) if "commitment_cost" in keys(gen_dict) - for t in 1:T + for t = 1:T x = gen_dict["commitment"]["values"][t] commitment_cost = gen_dict["commitment_cost"]["values"][t] prod_above_cost = gen_dict["production_cost"]["values"][t] diff --git a/src/instance/read.jl b/src/instance/read.jl index 09c2da4..19249ed 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -23,10 +23,7 @@ Example import UnitCommitment instance = UnitCommitment.read_benchmark("matpower/case3375wp/2017-02-01") """ -function read_benchmark( - name::AbstractString; - quiet::Bool = false, -)::UnitCommitmentInstance +function read_benchmark(name::AbstractString; quiet::Bool = false)::UnitCommitmentInstance basedir = dirname(@__FILE__) filename = "$basedir/../../instances/$name.json.gz" url = "$INSTANCES_URL/$name.json.gz" @@ -65,9 +62,7 @@ function read(path::AbstractString)::UnitCommitmentInstance end function _read(file::IO)::UnitCommitmentInstance - return _from_json( - JSON.parse(file, dicttype = () -> DefaultOrderedDict(nothing)), - ) + return _from_json(JSON.parse(file, dicttype = () -> DefaultOrderedDict(nothing))) end function _read_json(path::String)::OrderedDict @@ -97,8 +92,7 @@ function _from_json(json; repair = true) end time_horizon !== nothing || error("Missing parameter: Time horizon (h)") time_step = scalar(json["Parameters"]["Time step (min)"], default = 60) - (60 % time_step == 0) || - error("Time step $time_step is not a divisor of 60") + (60 % time_step == 0) || error("Time step $time_step is not a divisor of 60") time_multiplier = 60 ÷ time_step T = time_horizon * time_multiplier @@ -108,23 +102,23 @@ function _from_json(json; repair = true) function timeseries(x; default = nothing) x !== nothing || return default - x isa Array || return [x for t in 1:T] + x isa Array || return [x for t = 1:T] return x end # Read parameters power_balance_penalty = timeseries( json["Parameters"]["Power balance penalty (\$/MW)"], - default = [1000.0 for t in 1:T], + default = [1000.0 for t = 1:T], ) # Penalty price for shortage in meeting system-wide flexiramp requirements flexiramp_shortfall_penalty = timeseries( json["Parameters"]["Flexiramp penalty (\$/MW)"], - default = [500.0 for t in 1:T], + default = [500.0 for t = 1:T], ) shortfall_penalty = timeseries( json["Parameters"]["Reserve shortfall penalty (\$/MW)"], - default = [-1.0 for t in 1:T], + default = [-1.0 for t = 1:T], ) # Read buses @@ -146,17 +140,14 @@ function _from_json(json; repair = true) # Read production cost curve K = length(dict["Production cost curve (MW)"]) - curve_mw = hcat( - [timeseries(dict["Production cost curve (MW)"][k]) for k in 1:K]..., - ) - curve_cost = hcat( - [timeseries(dict["Production cost curve (\$)"][k]) for k in 1:K]..., - ) + curve_mw = hcat([timeseries(dict["Production cost curve (MW)"][k]) for k = 1:K]...) + curve_cost = + hcat([timeseries(dict["Production cost curve (\$)"][k]) for k = 1:K]...) min_power = curve_mw[:, 1] max_power = curve_mw[:, K] min_power_cost = curve_cost[:, 1] segments = CostSegment[] - for k in 2:K + for k = 2:K amount = curve_mw[:, k] - curve_mw[:, k-1] cost = (curve_cost[:, k] - curve_cost[:, k-1]) ./ amount replace!(cost, NaN => 0.0) @@ -167,13 +158,10 @@ function _from_json(json; repair = true) startup_delays = scalar(dict["Startup delays (h)"], default = [1]) startup_costs = scalar(dict["Startup costs (\$)"], default = [0.0]) startup_categories = StartupCategory[] - for k in 1:length(startup_delays) + for k = 1:length(startup_delays) push!( startup_categories, - StartupCategory( - startup_delays[k] .* time_multiplier, - startup_costs[k], - ), + StartupCategory(startup_delays[k] .* time_multiplier, startup_costs[k]), ) end @@ -186,8 +174,7 @@ function _from_json(json; repair = true) else initial_status !== nothing || error("unit $unit_name has initial power but no initial status") - initial_status != 0 || - error("unit $unit_name has invalid initial status") + initial_status != 0 || error("unit $unit_name has invalid initial status") if initial_status < 0 && initial_power > 1e-3 error("unit $unit_name has invalid initial power") end @@ -199,7 +186,7 @@ function _from_json(json; repair = true) bus, max_power, min_power, - timeseries(dict["Must run?"], default = [false for t in 1:T]), + timeseries(dict["Must run?"], default = [false for t = 1:T]), min_power_cost, segments, scalar(dict["Minimum uptime (h)"], default = 1) * time_multiplier, @@ -210,14 +197,8 @@ function _from_json(json; repair = true) scalar(dict["Shutdown limit (MW)"], default = 1e6), initial_status, initial_power, - timeseries( - dict["Provides spinning reserves?"], - default = [true for t in 1:T], - ), - timeseries( - dict["Provides flexible capacity?"], - default = [true for t in 1:T], - ), + timeseries(dict["Provides spinning reserves?"], default = [true for t = 1:T]), + timeseries(dict["Provides flexible capacity?"], default = [true for t = 1:T]), startup_categories, ) push!(bus.units, unit) @@ -230,14 +211,10 @@ function _from_json(json; repair = true) if "Reserves" in keys(json) reserves.spinning = timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) - reserves.upflexiramp = timeseries( - json["Reserves"]["Up-flexiramp (MW)"], - default = zeros(T), - ) - reserves.dwflexiramp = timeseries( - json["Reserves"]["Down-flexiramp (MW)"], - default = zeros(T), - ) + reserves.upflexiramp = + timeseries(json["Reserves"]["Up-flexiramp (MW)"], default = zeros(T)) + reserves.dwflexiramp = + timeseries(json["Reserves"]["Down-flexiramp (MW)"], default = zeros(T)) end # Read transmission lines @@ -250,17 +227,11 @@ function _from_json(json; repair = true) name_to_bus[dict["Target bus"]], scalar(dict["Reactance (ohms)"]), scalar(dict["Susceptance (S)"]), - timeseries( - dict["Normal flow limit (MW)"], - default = [1e8 for t in 1:T], - ), - timeseries( - dict["Emergency flow limit (MW)"], - default = [1e8 for t in 1:T], - ), + timeseries(dict["Normal flow limit (MW)"], default = [1e8 for t = 1:T]), + timeseries(dict["Emergency flow limit (MW)"], default = [1e8 for t = 1:T]), timeseries( dict["Flow limit penalty (\$/MW)"], - default = [5000.0 for t in 1:T], + default = [5000.0 for t = 1:T], ), ) name_to_line[line_name] = line @@ -274,12 +245,10 @@ function _from_json(json; repair = true) affected_units = Unit[] affected_lines = TransmissionLine[] if "Affected lines" in keys(dict) - affected_lines = - [name_to_line[l] for l in dict["Affected lines"]] + affected_lines = [name_to_line[l] for l in dict["Affected lines"]] end if "Affected units" in keys(dict) - affected_units = - [name_to_unit[u] for u in dict["Affected units"]] + affected_units = [name_to_unit[u] for u in dict["Affected units"]] end cont = Contingency(cont_name, affected_lines, affected_units) push!(contingencies, cont) diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 8555316..03a5af4 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -96,10 +96,7 @@ function Base.show(io::IO, instance::UnitCommitmentInstance) print(io, "$(length(instance.buses)) buses, ") print(io, "$(length(instance.lines)) lines, ") print(io, "$(length(instance.contingencies)) contingencies, ") - print( - io, - "$(length(instance.price_sensitive_loads)) price sensitive loads, ", - ) + print(io, "$(length(instance.price_sensitive_loads)) price sensitive loads, ") print(io, "$(instance.time) time steps") print(io, ")") return diff --git a/src/model/build.jl b/src/model/build.jl index de81600..5352b29 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -33,19 +33,15 @@ function build_model(; variable_names::Bool = false, )::JuMP.Model if formulation.ramping == WanHob2016.Ramping() && - instance.reserves.spinning >= ones(instance.time).*1e-6 - error( - "Spinning reserves are not supported by the WanHob2016 ramping formulation", - ) + instance.reserves.spinning >= ones(instance.time) .* 1e-6 + error("Spinning reserves are not supported by the WanHob2016 ramping formulation") end - + if formulation.ramping !== WanHob2016.Ramping() && ( - instance.reserves.upflexiramp >= ones(instance.time).*1e-6 || - instance.reserves.dwflexiramp >= ones(instance.time).*1e-6 + instance.reserves.upflexiramp >= ones(instance.time) .* 1e-6 || + instance.reserves.dwflexiramp >= ones(instance.time) .* 1e-6 ) - error( - "Flexiramp is supported only by the WanHob2016 ramping formulation", - ) + error("Flexiramp is supported only by the WanHob2016 ramping formulation") end @info "Building model..." diff --git a/src/model/formulations/ArrCon2000/ramp.jl b/src/model/formulations/ArrCon2000/ramp.jl index e9bb288..c8aed14 100644 --- a/src/model/formulations/ArrCon2000/ramp.jl +++ b/src/model/formulations/ArrCon2000/ramp.jl @@ -32,7 +32,7 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t in 1:model[:instance].time + for t = 1:model[:instance].time # Ramp up limit if t == 1 if is_initially_on @@ -49,12 +49,8 @@ function _add_ramp_eqs!( max_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + - ( - RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? - reserve[gn, t] : 0.0 - ) - min_prod_last_period = - g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] + (RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) + 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) eq_ramp_up[gn, t] = @constraint( @@ -81,11 +77,10 @@ function _add_ramp_eqs!( g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] + ( - RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? - reserve[gn, t-1] : 0.0 + RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? reserve[gn, t-1] : + 0.0 ) - min_prod_this_period = - g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + min_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t] # Equation (25) in Kneuven et al. (2020) eq_ramp_down[gn, t] = @constraint( diff --git a/src/model/formulations/CarArr2006/pwlcosts.jl b/src/model/formulations/CarArr2006/pwlcosts.jl index 2f13e9a..ad2e1c9 100644 --- a/src/model/formulations/CarArr2006/pwlcosts.jl +++ b/src/model/formulations/CarArr2006/pwlcosts.jl @@ -18,18 +18,16 @@ function _add_production_piecewise_linear_eqs!( prod_above = model[:prod_above] K = length(g.cost_segments) - for t in 1:model[:instance].time + for t = 1:model[:instance].time gn = g.name - for k in 1:K + for k = 1:K # Equation (45) 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* - eq_segprod_limit[gn, t, k] = @constraint( - model, - segprod[gn, t, k] <= g.cost_segments[k].mw[t] - ) + eq_segprod_limit[gn, t, k] = + @constraint(model, segprod[gn, t, k] <= g.cost_segments[k].mw[t]) # Also add this as an explicit upper bound on segprod to make the # solver's work a bit easier @@ -37,18 +35,12 @@ function _add_production_piecewise_linear_eqs!( # Definition of production # Equation (43) in Kneuven 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) - ) + eq_prod_above_def[gn, t] = + @constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K)) # Objective function # Equation (44) in Kneuven et al. (2020) - add_to_expression!( - model[:obj], - segprod[gn, t, k], - g.cost_segments[k].cost[t], - ) + add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t]) end end end diff --git a/src/model/formulations/DamKucRajAta2016/ramp.jl b/src/model/formulations/DamKucRajAta2016/ramp.jl index 9afd247..d4fa5f6 100644 --- a/src/model/formulations/DamKucRajAta2016/ramp.jl +++ b/src/model/formulations/DamKucRajAta2016/ramp.jl @@ -33,9 +33,8 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t in 1:model[:instance].time - time_invariant = - (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true + for t = 1:model[:instance].time + time_invariant = (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true # if t > 1 && !time_invariant # @warn( @@ -48,10 +47,8 @@ function _add_ramp_eqs!( # end max_prod_this_period = - prod_above[gn, t] + ( - RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? - reserve[gn, t] : 0.0 - ) + prod_above[gn, t] + + (RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) min_prod_last_period = 0.0 if t > 1 && time_invariant min_prod_last_period = prod_above[gn, t-1] @@ -61,8 +58,7 @@ function _add_ramp_eqs!( eq_str_ramp_up[gn, t] = @constraint( model, max_prod_this_period - min_prod_last_period <= - (SU - g.min_power[t] - RU) * switch_on[gn, t] + - RU * is_on[gn, t] + (SU - g.min_power[t] - RU) * switch_on[gn, t] + RU * is_on[gn, t] ) elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant) if t > 1 @@ -103,8 +99,7 @@ function _add_ramp_eqs!( eq_str_ramp_down[gn, t] = @constraint( model, max_prod_last_period - min_prod_this_period <= - (SD - g.min_power[t] - RD) * switch_off[gn, t] + - RD * on_last_period + (SD - g.min_power[t] - RD) * switch_off[gn, t] + RD * on_last_period ) elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant) # Add back in min power diff --git a/src/model/formulations/Gar1962/prod.jl b/src/model/formulations/Gar1962/prod.jl index e39a90e..a56e5da 100644 --- a/src/model/formulations/Gar1962/prod.jl +++ b/src/model/formulations/Gar1962/prod.jl @@ -9,8 +9,8 @@ function _add_production_vars!( )::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) + for t = 1:model[:instance].time + for k = 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) @@ -28,7 +28,7 @@ function _add_production_limit_eqs!( prod_above = model[:prod_above] reserve = model[:reserve] gn = g.name - for t in 1:model[:instance].time + for t = 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]) diff --git a/src/model/formulations/Gar1962/pwlcosts.jl b/src/model/formulations/Gar1962/pwlcosts.jl index 3ac4871..c55d449 100644 --- a/src/model/formulations/Gar1962/pwlcosts.jl +++ b/src/model/formulations/Gar1962/pwlcosts.jl @@ -21,15 +21,13 @@ function _add_production_piecewise_linear_eqs!( is_on = model[:is_on] K = length(g.cost_segments) - for t in 1:model[:instance].time + for t = 1:model[:instance].time # Definition of production # Equation (43) in Kneuven 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) - ) + eq_prod_above_def[gn, t] = + @constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K)) - for k in 1:K + for k = 1:K # Equation (42) in Kneuven et al. (2020) # Without this, solvers will add a lot of implied bound cuts to # have this same effect. @@ -48,11 +46,7 @@ function _add_production_piecewise_linear_eqs!( # Objective function # Equation (44) in Kneuven et al. (2020) - add_to_expression!( - model[:obj], - segprod[gn, t, k], - g.cost_segments[k].cost[t], - ) + add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t]) end end return diff --git a/src/model/formulations/Gar1962/status.jl b/src/model/formulations/Gar1962/status.jl index 14c055f..c2b1a32 100644 --- a/src/model/formulations/Gar1962/status.jl +++ b/src/model/formulations/Gar1962/status.jl @@ -10,7 +10,7 @@ function _add_status_vars!( 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 + for t = 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) @@ -34,7 +34,7 @@ function _add_status_eqs!( is_on = model[:is_on] switch_off = model[:switch_off] switch_on = model[:switch_on] - for t in 1:model[:instance].time + for t = 1:model[:instance].time if !g.must_run[t] # Link binary variables if t == 1 @@ -51,10 +51,8 @@ function _add_status_eqs!( ) 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 - ) + eq_switch_on_off[g.name, t] = + @constraint(model, switch_on[g.name, t] + switch_off[g.name, t] <= 1) end end return diff --git a/src/model/formulations/KnuOstWat2018/pwlcosts.jl b/src/model/formulations/KnuOstWat2018/pwlcosts.jl index 85afa1e..42e76a4 100644 --- a/src/model/formulations/KnuOstWat2018/pwlcosts.jl +++ b/src/model/formulations/KnuOstWat2018/pwlcosts.jl @@ -26,12 +26,12 @@ function _add_production_piecewise_linear_eqs!( switch_on = model[:switch_on] switch_off = model[:switch_off] - for t in 1:T - for k in 1:K + for t = 1:T + for k = 1:K # Pbar^{k-1) Pbar0 = g.min_power[t] + - (k > 1 ? sum(g.cost_segments[ell].mw[t] for ell in 1:k-1) : 0.0) + (k > 1 ? sum(g.cost_segments[ell].mw[t] for ell = 1:k-1) : 0.0) # Pbar^k Pbar1 = g.cost_segments[k].mw[t] + Pbar0 @@ -61,8 +61,7 @@ function _add_production_piecewise_linear_eqs!( eq_segprod_limit_a[gn, t, k] = @constraint( model, segprod[gn, t, k] <= - g.cost_segments[k].mw[t] * is_on[gn, t] - - Cv * switch_on[gn, t] - + g.cost_segments[k].mw[t] * is_on[gn, t] - Cv * switch_on[gn, t] - (t < T ? Cw * switch_off[gn, t+1] : 0.0) ) else @@ -70,8 +69,7 @@ function _add_production_piecewise_linear_eqs!( eq_segprod_limit_b[gn, t, k] = @constraint( model, segprod[gn, t, k] <= - g.cost_segments[k].mw[t] * is_on[gn, t] - - Cv * switch_on[gn, t] - + g.cost_segments[k].mw[t] * is_on[gn, t] - Cv * switch_on[gn, t] - (t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0) ) @@ -87,18 +85,12 @@ function _add_production_piecewise_linear_eqs!( # Definition of production # Equation (43) in Kneuven 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) - ) + eq_prod_above_def[gn, t] = + @constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K)) # Objective function # Equation (44) in Kneuven et al. (2020) - add_to_expression!( - model[:obj], - segprod[gn, t, k], - g.cost_segments[k].cost[t], - ) + add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t]) # Also add an explicit upper bound on segprod to make the solver's # work a bit easier diff --git a/src/model/formulations/MorLatRam2013/ramp.jl b/src/model/formulations/MorLatRam2013/ramp.jl index cbb8f94..b8e07ec 100644 --- a/src/model/formulations/MorLatRam2013/ramp.jl +++ b/src/model/formulations/MorLatRam2013/ramp.jl @@ -32,9 +32,8 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t in 1:model[:instance].time - time_invariant = - (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true + for t = 1:model[:instance].time + time_invariant = (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true # Ramp up limit if t == 1 @@ -60,8 +59,8 @@ function _add_ramp_eqs!( g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + ( - RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? - reserve[gn, t] : 0.0 + RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : + 0.0 ) min_prod_last_period = g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] @@ -76,8 +75,7 @@ function _add_ramp_eqs!( # prod_above[gn, t] when starting up, and creates diff with (24). eq_ramp_up[gn, t] = @constraint( model, - prod_above[gn, t] + - (RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) - + prod_above[gn, t] + (RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) - prod_above[gn, t-1] <= RU ) end @@ -107,8 +105,7 @@ function _add_ramp_eqs!( RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? reserve[gn, t-1] : 0.0 ) - min_prod_this_period = - g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + min_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t] eq_ramp_down[gn, t] = @constraint( model, max_prod_last_period - min_prod_this_period <= diff --git a/src/model/formulations/MorLatRam2013/scosts.jl b/src/model/formulations/MorLatRam2013/scosts.jl index 2d68747..77ef984 100644 --- a/src/model/formulations/MorLatRam2013/scosts.jl +++ b/src/model/formulations/MorLatRam2013/scosts.jl @@ -11,30 +11,27 @@ function _add_startup_cost_eqs!( eq_startup_restrict = _init(model, :eq_startup_restrict) S = length(g.startup_categories) startup = model[:startup] - for t in 1:model[:instance].time + for t = 1:model[:instance].time # If unit is switching on, we must choose a startup category eq_startup_choose[g.name, t] = @constraint( model, - model[:switch_on][g.name, t] == - sum(startup[g.name, t, s] for s in 1:S) + model[:switch_on][g.name, t] == sum(startup[g.name, t, s] for s = 1:S) ) - for s in 1:S + for s = 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 - ) + 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 - ) + initial_sum + + sum(model[:switch_off][g.name, i] for i in range if i >= 1) ) end diff --git a/src/model/formulations/PanGua2016/ramp.jl b/src/model/formulations/PanGua2016/ramp.jl index f040824..36415fe 100644 --- a/src/model/formulations/PanGua2016/ramp.jl +++ b/src/model/formulations/PanGua2016/ramp.jl @@ -14,10 +14,8 @@ function _add_ramp_eqs!( gn = g.name reserve = model[:reserve] 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) - eq_prod_limit_shutdown_trajectory = - _init(model, :eq_prod_limit_shutdown_trajectory) + eq_prod_limit_ramp_up_extra_period = _init(model, :eq_prod_limit_ramp_up_extra_period) + eq_prod_limit_shutdown_trajectory = _init(model, :eq_prod_limit_shutdown_trajectory) UT = g.min_uptime SU = g.startup_limit # startup rate, i.e., max production right after startup SD = g.shutdown_limit # shutdown rate, i.e., max production right before shutdown @@ -33,7 +31,7 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t in 1:T + for t = 1:T Pbar = g.max_power[t] if Pbar < 1e-7 # Skip this time period if max power = 0 @@ -54,13 +52,10 @@ function _add_ramp_eqs!( # then switch_off[gn, t+1] = 0 eq_str_prod_limit[gn, t] = @constraint( model, - prod_above[gn, t] + - g.min_power[t] * is_on[gn, t] + - reserve[gn, t] <= - Pbar * is_on[gn, t] - - (t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum( + prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + reserve[gn, t] <= + Pbar * is_on[gn, t] - (t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum( (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for - i in 0:min(UT - 2, TRU, t - 1) + i = 0:min(UT - 2, TRU, t - 1) ) ) @@ -69,12 +64,10 @@ function _add_ramp_eqs!( # Covers an additional time period of the ramp-up trajectory, compared to (38) eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint( model, - prod_above[gn, t] + - g.min_power[t] * is_on[gn, t] + - reserve[gn, t] <= + prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + reserve[gn, t] <= Pbar * is_on[gn, t] - sum( (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for - i in 0:min(UT - 1, TRU, t - 1) + i = 0:min(UT - 1, TRU, t - 1) ) ) end @@ -89,13 +82,9 @@ function _add_ramp_eqs!( prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + (RESERVES_WHEN_SHUT_DOWN ? reserve[gn, t] : 0.0) <= - Pbar * is_on[gn, t] - sum( - (Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for - i in 0:KSD - ) - sum( - (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for - i in 0:KSU - ) - ( + Pbar * is_on[gn, t] - + sum((Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for i = 0:KSD) - + sum((Pbar - (SU + i * RU)) * switch_on[gn, t-i] for i = 0:KSU) - ( (KSU >= TRU || KSU > t - 2) ? 0.0 : max(0, (SU + (KSU + 1) * RU) - (SD + TRD * RD)) * switch_on[gn, t-(KSU+1)] diff --git a/src/model/formulations/WanHob2016/ramp.jl b/src/model/formulations/WanHob2016/ramp.jl index 8a999f1..0580875 100644 --- a/src/model/formulations/WanHob2016/ramp.jl +++ b/src/model/formulations/WanHob2016/ramp.jl @@ -8,7 +8,7 @@ function _add_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing mfg = _init(model, :mfg) dwflexiramp = _init(model, :dwflexiramp) dwflexiramp_shortfall = _init(model, :dwflexiramp_shortfall) - for t in 1:model[:instance].time + for t = 1:model[:instance].time # maximum feasible generation, \bar{g_{its}} in Wang & Hobbs (2016) mfg[g.name, t] = @variable(model, lower_bound = 0) if g.provides_flexiramp_reserves[t] @@ -51,37 +51,28 @@ function _add_ramp_eqs!( dwflexiramp = model[:dwflexiramp] mfg = model[:mfg] - for t in 1:model[:instance].time - @constraint( - model, - prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t] - ) # Eq. (19) in Wang & Hobbs (2016) + for t = 1:model[:instance].time + @constraint(model, prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t]) # Eq. (19) in Wang & Hobbs (2016) @constraint(model, mfg[gn, t] <= is_on[gn, t] * maxp[t]) # Eq. (22) in Wang & Hobbs (2016) if t != model[:instance].time @constraint( model, minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <= - prod_above[gn, t] - dwflexiramp[gn, t] + - (is_on[gn, t] * minp[t]) + prod_above[gn, t] - dwflexiramp[gn, t] + (is_on[gn, t] * minp[t]) ) # first inequality of Eq. (20) in Wang & Hobbs (2016) @constraint( model, - prod_above[gn, t] - dwflexiramp[gn, t] + - (is_on[gn, t] * minp[t]) <= + prod_above[gn, t] - dwflexiramp[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1])) ) # second inequality of Eq. (20) in Wang & Hobbs (2016) @constraint( model, minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <= - prod_above[gn, t] + - upflexiramp[gn, t] + - (is_on[gn, t] * minp[t]) + prod_above[gn, t] + upflexiramp[gn, t] + (is_on[gn, t] * minp[t]) ) # first inequality of Eq. (21) in Wang & Hobbs (2016) @constraint( model, - prod_above[gn, t] + - upflexiramp[gn, t] + - (is_on[gn, t] * minp[t]) <= + prod_above[gn, t] + upflexiramp[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1])) ) # second inequality of Eq. (21) in Wang & Hobbs (2016) if t != 1 @@ -113,8 +104,7 @@ function _add_ramp_eqs!( ) # Eq. (23) in Wang & Hobbs (2016) for the first time period @constraint( model, - initial_power - - (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= + initial_power - (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= RD * is_on[gn, t] + SD * (is_initially_on - is_on[gn, t]) + maxp[t] * (1 - is_initially_on) @@ -123,8 +113,7 @@ function _add_ramp_eqs!( @constraint( model, mfg[gn, t] <= - (SD * (is_on[gn, t] - is_on[gn, t+1])) + - (maxp[t] * is_on[gn, t+1]) + (SD * (is_on[gn, t] - is_on[gn, t+1])) + (maxp[t] * is_on[gn, t+1]) ) # Eq. (24) in Wang & Hobbs (2016) @constraint( model, @@ -152,15 +141,13 @@ function _add_ramp_eqs!( ) # second inequality of Eq. (27) in Wang & Hobbs (2016) @constraint( model, - -maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <= - upflexiramp[gn, t] + -maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <= upflexiramp[gn, t] ) # first inequality of Eq. (28) in Wang & Hobbs (2016) @constraint(model, upflexiramp[gn, t] <= maxp[t] * is_on[gn, t+1]) # second inequality of Eq. (28) in Wang & Hobbs (2016) @constraint(model, -maxp[t] * is_on[gn, t+1] <= dwflexiramp[gn, t]) # first inequality of Eq. (29) in Wang & Hobbs (2016) @constraint( model, - dwflexiramp[gn, t] <= - (maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1]) + dwflexiramp[gn, t] <= (maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1]) ) # second inequality of Eq. (29) in Wang & Hobbs (2016) else @constraint( diff --git a/src/model/formulations/base/bus.jl b/src/model/formulations/base/bus.jl index d881fc6..f938c9c 100644 --- a/src/model/formulations/base/bus.jl +++ b/src/model/formulations/base/bus.jl @@ -5,13 +5,12 @@ function _add_bus!(model::JuMP.Model, b::Bus)::Nothing net_injection = _init(model, :expr_net_injection) curtail = _init(model, :curtail) - for t in 1:model[:instance].time + for t = 1:model[:instance].time # Fixed load net_injection[b.name, t] = AffExpr(-b.load[t]) # Load curtailment - curtail[b.name, t] = - @variable(model, lower_bound = 0, upper_bound = b.load[t]) + curtail[b.name, t] = @variable(model, lower_bound = 0, upper_bound = b.load[t]) add_to_expression!(net_injection[b.name, t], curtail[b.name, t], 1.0) add_to_expression!( diff --git a/src/model/formulations/base/line.jl b/src/model/formulations/base/line.jl index 6398c8d..fe6d24b 100644 --- a/src/model/formulations/base/line.jl +++ b/src/model/formulations/base/line.jl @@ -8,13 +8,9 @@ function _add_transmission_line!( f::ShiftFactorsFormulation, )::Nothing overflow = _init(model, :overflow) - for t in 1:model[:instance].time + for t = 1:model[:instance].time overflow[lm.name, t] = @variable(model, lower_bound = 0) - add_to_expression!( - model[:obj], - overflow[lm.name, t], - lm.flow_limit_penalty[t], - ) + add_to_expression!(model[:obj], overflow[lm.name, t], lm.flow_limit_penalty[t]) end return end diff --git a/src/model/formulations/base/psload.jl b/src/model/formulations/base/psload.jl index 3748111..de9355e 100644 --- a/src/model/formulations/base/psload.jl +++ b/src/model/formulations/base/psload.jl @@ -2,26 +2,18 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -function _add_price_sensitive_load!( - model::JuMP.Model, - ps::PriceSensitiveLoad, -)::Nothing +function _add_price_sensitive_load!(model::JuMP.Model, ps::PriceSensitiveLoad)::Nothing loads = _init(model, :loads) net_injection = _init(model, :expr_net_injection) - for t in 1:model[:instance].time + for t = 1:model[:instance].time # Decision variable - loads[ps.name, t] = - @variable(model, lower_bound = 0, upper_bound = ps.demand[t]) + loads[ps.name, t] = @variable(model, lower_bound = 0, upper_bound = ps.demand[t]) # Objective function terms add_to_expression!(model[:obj], loads[ps.name, t], -ps.revenue[t]) # Net injection - add_to_expression!( - net_injection[ps.bus.name, t], - loads[ps.name, t], - -1.0, - ) + add_to_expression!(net_injection[ps.bus.name, t], loads[ps.name, t], -1.0) end return end diff --git a/src/model/formulations/base/sensitivity.jl b/src/model/formulations/base/sensitivity.jl index f2fca9a..6bf153f 100644 --- a/src/model/formulations/base/sensitivity.jl +++ b/src/model/formulations/base/sensitivity.jl @@ -13,10 +13,7 @@ M[l.offset, b.offset] indicates the amount of power (in MW) that flows through transmission line l when 1 MW of power is injected at the slack bus (the bus that has offset zero) and withdrawn from b. """ -function _injection_shift_factors(; - buses::Array{Bus}, - lines::Array{TransmissionLine}, -) +function _injection_shift_factors(; buses::Array{Bus}, lines::Array{TransmissionLine}) susceptance = _susceptance_matrix(lines) incidence = _reduced_incidence_matrix(lines = lines, buses = buses) laplacian = transpose(incidence) * susceptance * incidence @@ -33,10 +30,7 @@ is the number of buses and L is the number of lines. For each row, there is a 1 element and a -1 element, indicating the source and target buses, respectively, for that line. """ -function _reduced_incidence_matrix(; - buses::Array{Bus}, - lines::Array{TransmissionLine}, -) +function _reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine}) matrix = spzeros(Float64, length(lines), length(buses) - 1) for line in lines if line.source.offset > 0 @@ -75,7 +69,7 @@ function _line_outage_factors(; incidence = Array(_reduced_incidence_matrix(lines = lines, buses = buses)) lodf::Array{Float64,2} = isf * transpose(incidence) _, n = size(lodf) - for i in 1:n + for i = 1:n lodf[:, i] *= 1.0 / (1.0 - lodf[i, i]) lodf[i, i] = -1 end diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index 2cc7e44..37358de 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -25,14 +25,7 @@ struct Formulation status_vars::StatusVarsFormulation = Gar1962.StatusVars(), transmission::TransmissionFormulation = ShiftFactorsFormulation(), ) - return new( - prod_vars, - 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/system.jl b/src/model/formulations/base/system.jl index c526040..8de941f 100644 --- a/src/model/formulations/base/system.jl +++ b/src/model/formulations/base/system.jl @@ -14,12 +14,12 @@ function _add_net_injection_eqs!(model::JuMP.Model)::Nothing net_injection = _init(model, :net_injection) eq_net_injection = _init(model, :eq_net_injection) eq_power_balance = _init(model, :eq_power_balance) - for t in 1:T, b in model[:instance].buses + for t = 1:T, b in model[:instance].buses n = net_injection[b.name, t] = @variable(model) eq_net_injection[b.name, t] = @constraint(model, -n + model[:expr_net_injection][b.name, t] == 0) end - for t in 1:T + for t = 1:T eq_power_balance[t] = @constraint( model, sum(net_injection[b.name, t] for b in model[:instance].buses) == 0 @@ -31,7 +31,7 @@ end function _add_reserve_eqs!(model::JuMP.Model)::Nothing eq_min_reserve = _init(model, :eq_min_reserve) instance = model[:instance] - for t in 1:instance.time + for t = 1:instance.time # Equation (68) in Kneuven et al. (2020) # As in Morales-España et al. (2013a) # Akin to the alternative formulation with max_power_avail @@ -46,11 +46,7 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing # Account for shortfall contribution to objective if shortfall_penalty >= 0 - add_to_expression!( - model[:obj], - shortfall_penalty, - model[:reserve_shortfall][t], - ) + add_to_expression!(model[:obj], shortfall_penalty, model[:reserve_shortfall][t]) end end return @@ -65,24 +61,20 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing eq_min_upflexiramp = _init(model, :eq_min_upflexiramp) eq_min_dwflexiramp = _init(model, :eq_min_dwflexiramp) instance = model[:instance] - for t in 1:instance.time + for t = 1:instance.time flexiramp_shortfall_penalty = instance.flexiramp_shortfall_penalty[t] # Eq. (17) in Wang & Hobbs (2016) eq_min_upflexiramp[t] = @constraint( model, - sum(model[:upflexiramp][g.name, t] for g in instance.units) + - ( - flexiramp_shortfall_penalty >= 0 ? - model[:upflexiramp_shortfall][t] : 0.0 + sum(model[:upflexiramp][g.name, t] for g in instance.units) + ( + flexiramp_shortfall_penalty >= 0 ? model[:upflexiramp_shortfall][t] : 0.0 ) >= instance.reserves.upflexiramp[t] ) # Eq. (18) in Wang & Hobbs (2016) eq_min_dwflexiramp[t] = @constraint( model, - sum(model[:dwflexiramp][g.name, t] for g in instance.units) + - ( - flexiramp_shortfall_penalty >= 0 ? - model[:dwflexiramp_shortfall][t] : 0.0 + sum(model[:dwflexiramp][g.name, t] for g in instance.units) + ( + flexiramp_shortfall_penalty >= 0 ? model[:dwflexiramp_shortfall][t] : 0.0 ) >= instance.reserves.dwflexiramp[t] ) @@ -91,10 +83,7 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing add_to_expression!( model[:obj], flexiramp_shortfall_penalty, - ( - model[:upflexiramp_shortfall][t] + - model[:dwflexiramp_shortfall][t] - ), + (model[:upflexiramp_shortfall][t] + model[:dwflexiramp_shortfall][t]), ) end end diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index 5da9ffa..e3bbe45 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -46,7 +46,7 @@ _is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0) function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing reserve = _init(model, :reserve) reserve_shortfall = _init(model, :reserve_shortfall) - for t in 1:model[:instance].time + for t = 1:model[:instance].time if g.provides_spinning_reserves[t] reserve[g.name, t] = @variable(model, lower_bound = 0) else @@ -61,7 +61,7 @@ end function _add_reserve_eqs!(model::JuMP.Model, g::Unit)::Nothing reserve = model[:reserve] - for t in 1:model[:instance].time + for t = 1:model[:instance].time add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0) end return @@ -69,8 +69,8 @@ end function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing startup = _init(model, :startup) - for t in 1:model[:instance].time - for s in 1:length(g.startup_categories) + for t = 1:model[:instance].time + for s = 1:length(g.startup_categories) startup[g.name, t, s] = @variable(model, binary = true) end end @@ -86,7 +86,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing switch_off = model[:switch_off] switch_on = model[:switch_on] T = model[:instance].time - for t in 1:T + for t = 1:T # Startup limit eq_startup_limit[g.name, t] = @constraint( model, @@ -96,16 +96,14 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing ) # Shutdown limit if g.initial_power > g.shutdown_limit - eq_shutdown_limit[g.name, 0] = - @constraint(model, switch_off[g.name, 1] <= 0) + eq_shutdown_limit[g.name, 0] = @constraint(model, switch_off[g.name, 1] <= 0) end if t < T eq_shutdown_limit[g.name, t] = @constraint( model, prod_above[g.name, t] <= (g.max_power[t] - g.min_power[t]) * is_on[g.name, t] - - max(0, g.max_power[t] - g.shutdown_limit) * - switch_off[g.name, t+1] + max(0, g.max_power[t] - g.shutdown_limit) * switch_off[g.name, t+1] ) end end @@ -121,7 +119,7 @@ function _add_ramp_eqs!( reserve = model[:reserve] eq_ramp_up = _init(model, :eq_ramp_up) eq_ramp_down = _init(model, :eq_ramp_down) - for t in 1:model[:instance].time + for t = 1:model[:instance].time # Ramp up limit if t == 1 if _is_initially_on(g) == 1 @@ -151,8 +149,7 @@ function _add_ramp_eqs!( else eq_ramp_down[g.name, t] = @constraint( model, - prod_above[g.name, t] >= - prod_above[g.name, t-1] - g.ramp_down_limit + prod_above[g.name, t] >= prod_above[g.name, t-1] - g.ramp_down_limit ) end end @@ -165,18 +162,18 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing eq_min_uptime = _init(model, :eq_min_uptime) eq_min_downtime = _init(model, :eq_min_downtime) T = model[:instance].time - for t in 1:T + for t = 1:T # Minimum up-time 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] + 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 eq_min_downtime[g.name, t] = @constraint( model, - sum( - switch_off[g.name, i] for i in (t-g.min_downtime+1):t if i >= 1 - ) <= 1 - is_on[g.name, t] + sum(switch_off[g.name, i] for i in (t-g.min_downtime+1):t if i >= 1) <= + 1 - is_on[g.name, t] ) # Minimum up/down-time for initial periods if t == 1 @@ -203,7 +200,7 @@ end function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing expr_net_injection = model[:expr_net_injection] - for t in 1:model[:instance].time + for t = 1:model[:instance].time # Add to net injection expression add_to_expression!( expr_net_injection[g.bus.name, t], diff --git a/src/solution/fix.jl b/src/solution/fix.jl index f9deacb..dbead66 100644 --- a/src/solution/fix.jl +++ b/src/solution/fix.jl @@ -14,12 +14,10 @@ function fix!(model::JuMP.Model, solution::AbstractDict)::Nothing prod_above = model[:prod_above] reserve = model[:reserve] for g in instance.units - for t in 1:T + for t = 1:T is_on_value = round(solution["Is on"][g.name][t]) - prod_value = - round(solution["Production (MW)"][g.name][t], digits = 5) - reserve_value = - round(solution["Reserve (MW)"][g.name][t], digits = 5) + prod_value = round(solution["Production (MW)"][g.name][t], digits = 5) + reserve_value = round(solution["Reserve (MW)"][g.name][t], digits = 5) JuMP.fix(is_on[g.name, t], is_on_value, force = true) JuMP.fix( prod_above[g.name, t], diff --git a/src/solution/methods/XavQiuWanThi2019/enforce.jl b/src/solution/methods/XavQiuWanThi2019/enforce.jl index 526274f..08ae1af 100644 --- a/src/solution/methods/XavQiuWanThi2019/enforce.jl +++ b/src/solution/methods/XavQiuWanThi2019/enforce.jl @@ -2,10 +2,7 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -function _enforce_transmission( - model::JuMP.Model, - violations::Vector{_Violation}, -)::Nothing +function _enforce_transmission(model::JuMP.Model, violations::Vector{_Violation})::Nothing for v in violations _enforce_transmission( model = model, diff --git a/src/solution/methods/XavQiuWanThi2019/filter.jl b/src/solution/methods/XavQiuWanThi2019/filter.jl index 576e8fc..a70ef78 100644 --- a/src/solution/methods/XavQiuWanThi2019/filter.jl +++ b/src/solution/methods/XavQiuWanThi2019/filter.jl @@ -4,11 +4,9 @@ function _offer(filter::_ViolationFilter, v::_Violation)::Nothing if v.monitored_line.offset ∉ keys(filter.queues) - filter.queues[v.monitored_line.offset] = - PriorityQueue{_Violation,Float64}() + filter.queues[v.monitored_line.offset] = PriorityQueue{_Violation,Float64}() end - q::PriorityQueue{_Violation,Float64} = - filter.queues[v.monitored_line.offset] + q::PriorityQueue{_Violation,Float64} = filter.queues[v.monitored_line.offset] if length(q) < filter.max_per_line enqueue!(q, v => v.amount) else diff --git a/src/solution/methods/XavQiuWanThi2019/find.jl b/src/solution/methods/XavQiuWanThi2019/find.jl index 3cf9094..11f8bd9 100644 --- a/src/solution/methods/XavQiuWanThi2019/find.jl +++ b/src/solution/methods/XavQiuWanThi2019/find.jl @@ -4,11 +4,7 @@ import Base.Threads: @threads -function _find_violations( - model::JuMP.Model; - max_per_line::Int, - max_per_period::Int, -) +function _find_violations(model::JuMP.Model; max_per_line::Int, max_per_period::Int) instance = model[:instance] net_injection = model[:net_injection] overflow = model[:overflow] @@ -18,13 +14,10 @@ function _find_violations( time_screening = @elapsed begin non_slack_buses = [b for b in instance.buses if b.offset > 0] net_injection_values = [ - value(net_injection[b.name, t]) for b in non_slack_buses, - t in 1:instance.time - ] - overflow_values = [ - value(overflow[lm.name, t]) for lm in instance.lines, - t in 1:instance.time + value(net_injection[b.name, t]) for b in non_slack_buses, t = 1:instance.time ] + overflow_values = + [value(overflow[lm.name, t]) for lm in instance.lines, t = 1:instance.time] violations = UnitCommitment._find_violations( instance = instance, net_injections = net_injection_values, @@ -35,10 +28,7 @@ function _find_violations( max_per_period = max_per_period, ) end - @info @sprintf( - "Verified transmission limits in %.2f seconds", - time_screening - ) + @info @sprintf("Verified transmission limits in %.2f seconds", time_screening) return violations end @@ -81,10 +71,8 @@ function _find_violations(; size(lodf) == (L, L) || error("lodf has incorrect size") filters = Dict( - t => _ViolationFilter( - max_total = max_per_period, - max_per_line = max_per_line, - ) for t in 1:T + t => _ViolationFilter(max_total = max_per_period, max_per_line = max_per_line) + for t = 1:T ) pre_flow::Array{Float64} = zeros(L, K) # pre_flow[lm, thread] @@ -92,35 +80,30 @@ function _find_violations(; pre_v::Array{Float64} = zeros(L, K) # pre_v[lm, thread] post_v::Array{Float64} = zeros(L, L, K) # post_v[lm, lc, thread] - normal_limits::Array{Float64,2} = [ - l.normal_flow_limit[t] + overflow[l.offset, t] for - l in instance.lines, t in 1:T - ] + normal_limits::Array{Float64,2} = + [l.normal_flow_limit[t] + overflow[l.offset, t] for l in instance.lines, t = 1:T] - emergency_limits::Array{Float64,2} = [ - l.emergency_flow_limit[t] + overflow[l.offset, t] for - l in instance.lines, t in 1:T - ] + emergency_limits::Array{Float64,2} = + [l.emergency_flow_limit[t] + overflow[l.offset, t] for l in instance.lines, t = 1:T] is_vulnerable::Array{Bool} = zeros(Bool, L) for c in instance.contingencies is_vulnerable[c.lines[1].offset] = true end - @threads for t in 1:T + @threads for t = 1:T k = threadid() # Pre-contingency flows pre_flow[:, k] = isf * net_injections[:, t] # Post-contingency flows - for lc in 1:L, lm in 1:L - post_flow[lm, lc, k] = - pre_flow[lm, k] + pre_flow[lc, k] * lodf[lm, lc] + for lc = 1:L, lm = 1:L + post_flow[lm, lc, k] = pre_flow[lm, k] + pre_flow[lc, k] * lodf[lm, lc] end # Pre-contingency violations - for lm in 1:L + for lm = 1:L pre_v[lm, k] = max( 0.0, pre_flow[lm, k] - normal_limits[lm, t], @@ -129,7 +112,7 @@ function _find_violations(; end # Post-contingency violations - for lc in 1:L, lm in 1:L + for lc = 1:L, lm = 1:L post_v[lm, lc, k] = max( 0.0, post_flow[lm, lc, k] - emergency_limits[lm, t], @@ -138,7 +121,7 @@ function _find_violations(; end # Offer pre-contingency violations - for lm in 1:L + for lm = 1:L if pre_v[lm, k] > 1e-5 _offer( filters[t], @@ -153,7 +136,7 @@ function _find_violations(; end # Offer post-contingency violations - for lm in 1:L, lc in 1:L + for lm = 1:L, lc = 1:L if post_v[lm, lc, k] > 1e-5 && is_vulnerable[lc] _offer( filters[t], @@ -169,7 +152,7 @@ function _find_violations(; end violations = _Violation[] - for t in 1:instance.time + for t = 1:instance.time append!(violations, _query(filters[t])) end diff --git a/src/solution/methods/XavQiuWanThi2019/optimize.jl b/src/solution/methods/XavQiuWanThi2019/optimize.jl index b513bd1..9362da6 100644 --- a/src/solution/methods/XavQiuWanThi2019/optimize.jl +++ b/src/solution/methods/XavQiuWanThi2019/optimize.jl @@ -27,10 +27,7 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing @info "Time limit exceeded" break end - @info @sprintf( - "Setting MILP time limit to %.2f seconds", - time_remaining - ) + @info @sprintf("Setting MILP time limit to %.2f seconds", time_remaining) JuMP.set_time_limit_sec(model, time_remaining) @info "Solving MILP..." JuMP.optimize!(model) diff --git a/src/solution/solution.jl b/src/solution/solution.jl index 0283eac..0fdb772 100644 --- a/src/solution/solution.jl +++ b/src/solution/solution.jl @@ -6,43 +6,40 @@ function solution(model::JuMP.Model)::OrderedDict instance, T = model[:instance], model[:instance].time function timeseries(vars, collection) return OrderedDict( - b.name => [round(value(vars[b.name, t]), digits = 5) for t in 1:T] - for b in collection + b.name => [round(value(vars[b.name, t]), digits = 5) for t = 1:T] for + b in collection ) end function production_cost(g) return [ value(model[:is_on][g.name, t]) * g.min_power_cost[t] + sum( Float64[ - value(model[:segprod][g.name, t, k]) * - g.cost_segments[k].cost[t] for - k in 1:length(g.cost_segments) + value(model[:segprod][g.name, t, k]) * g.cost_segments[k].cost[t] for + k = 1:length(g.cost_segments) ], - ) for t in 1:T + ) for t = 1:T ] end function production(g) return [ value(model[:is_on][g.name, t]) * g.min_power[t] + sum( Float64[ - value(model[:segprod][g.name, t, k]) for - k in 1:length(g.cost_segments) + value(model[:segprod][g.name, t, k]) for k = 1:length(g.cost_segments) ], - ) for t in 1:T + ) for t = 1:T ] end function startup_cost(g) S = length(g.startup_categories) return [ sum( - g.startup_categories[s].cost * - value(model[:startup][g.name, t, s]) for s in 1:S - ) for t in 1:T + g.startup_categories[s].cost * value(model[:startup][g.name, t, s]) for + s = 1:S + ) for t = 1:T ] end sol = OrderedDict() - sol["Production (MW)"] = - OrderedDict(g.name => production(g) for g in instance.units) + sol["Production (MW)"] = OrderedDict(g.name => production(g) for g in instance.units) sol["Production cost (\$)"] = OrderedDict(g.name => production_cost(g) for g in instance.units) sol["Startup cost (\$)"] = @@ -54,21 +51,19 @@ function solution(model::JuMP.Model)::OrderedDict instance.reserves.dwflexiramp != zeros(T) # Report flexiramp solutions only if either of the up-flexiramp and # down-flexiramp requirements is not a default array of zeros - sol["Up-flexiramp (MW)"] = - timeseries(model[:upflexiramp], instance.units) + sol["Up-flexiramp (MW)"] = timeseries(model[:upflexiramp], instance.units) sol["Up-flexiramp shortfall (MW)"] = OrderedDict( t => (instance.flexiramp_shortfall_penalty[t] >= 0) ? - round(value(model[:upflexiramp_shortfall][t]), digits = 5) : - 0.0 for t in 1:instance.time + round(value(model[:upflexiramp_shortfall][t]), digits = 5) : 0.0 for + t = 1:instance.time ) - sol["Down-flexiramp (MW)"] = - timeseries(model[:dwflexiramp], instance.units) + sol["Down-flexiramp (MW)"] = timeseries(model[:dwflexiramp], instance.units) sol["Down-flexiramp shortfall (MW)"] = OrderedDict( t => (instance.flexiramp_shortfall_penalty[t] >= 0) ? - round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : - 0.0 for t in 1:instance.time + round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : 0.0 for + t = 1:instance.time ) else # Report spinning reserve solutions only if both up-flexiramp and @@ -77,12 +72,11 @@ function solution(model::JuMP.Model)::OrderedDict sol["Reserve shortfall (MW)"] = OrderedDict( t => (instance.shortfall_penalty[t] >= 0) ? - round(value(model[:reserve_shortfall][t]), digits = 5) : - 0.0 for t in 1:instance.time + round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for + t = 1:instance.time ) end - sol["Net injection (MW)"] = - timeseries(model[:net_injection], instance.buses) + sol["Net injection (MW)"] = timeseries(model[:net_injection], instance.buses) sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses) if !isempty(instance.lines) sol["Line overflow (MW)"] = timeseries(model[:overflow], instance.lines) diff --git a/src/solution/warmstart.jl b/src/solution/warmstart.jl index 678f250..38c9135 100644 --- a/src/solution/warmstart.jl +++ b/src/solution/warmstart.jl @@ -6,16 +6,10 @@ function set_warm_start!(model::JuMP.Model, solution::AbstractDict)::Nothing instance, T = model[:instance], model[:instance].time is_on = model[:is_on] for g in instance.units - for t in 1:T + for t = 1:T JuMP.set_start_value(is_on[g.name, t], solution["Is on"][g.name][t]) - JuMP.set_start_value( - switch_on[g.name, t], - solution["Switch on"][g.name][t], - ) - JuMP.set_start_value( - switch_off[g.name, t], - solution["Switch off"][g.name][t], - ) + JuMP.set_start_value(switch_on[g.name, t], solution["Switch on"][g.name][t]) + JuMP.set_start_value(switch_off[g.name, t], solution["Switch off"][g.name][t]) end end return diff --git a/src/transform/initcond.jl b/src/transform/initcond.jl index 6bbf0ea..9ca733e 100644 --- a/src/transform/initcond.jl +++ b/src/transform/initcond.jl @@ -11,10 +11,7 @@ Generates feasible initial conditions for the given instance, by constructing and solving a single-period mixed-integer optimization problem, using the given optimizer. The instance is modified in-place. """ -function generate_initial_conditions!( - instance::UnitCommitmentInstance, - optimizer, -)::Nothing +function generate_initial_conditions!(instance::UnitCommitmentInstance, optimizer)::Nothing G = instance.units B = instance.buses t = 1 @@ -31,11 +28,7 @@ function generate_initial_conditions!( @constraint(mip, max_power[g in G], p[g] <= g.max_power[t] * x[g]) # Constraint: Production equals demand - @constraint( - mip, - power_balance, - sum(b.load[t] for b in B) == sum(p[g] for g in G) - ) + @constraint(mip, power_balance, sum(b.load[t] for b in B) == sum(p[g] for g in G)) # Constraint: Must run for g in G diff --git a/src/transform/randomize/XavQiuAhm2021.jl b/src/transform/randomize/XavQiuAhm2021.jl index adedcd2..73bc5d5 100644 --- a/src/transform/randomize/XavQiuAhm2021.jl +++ b/src/transform/randomize/XavQiuAhm2021.jl @@ -117,10 +117,7 @@ Base.@kwdef struct Randomization randomize_load_share::Bool = true end -function _randomize_costs( - instance::UnitCommitmentInstance, - distribution, -)::Nothing +function _randomize_costs(instance::UnitCommitmentInstance, distribution)::Nothing for unit in instance.units α = rand(distribution) unit.min_power_cost *= α @@ -134,17 +131,11 @@ function _randomize_costs( return end -function _randomize_load_share( - instance::UnitCommitmentInstance, - distribution, -)::Nothing +function _randomize_load_share(instance::UnitCommitmentInstance, distribution)::Nothing α = rand(distribution, length(instance.buses)) - for t in 1:instance.time + for t = 1:instance.time total = sum(bus.load[t] for bus in instance.buses) - den = sum( - bus.load[t] / total * α[i] for - (i, bus) in enumerate(instance.buses) - ) + den = sum(bus.load[t] / total * α[i] for (i, bus) in enumerate(instance.buses)) for (i, bus) in enumerate(instance.buses) bus.load[t] *= α[i] / den end @@ -158,11 +149,9 @@ function _randomize_load_profile( )::Nothing # Generate new system load system_load = [1.0] - for t in 2:instance.time + for t = 2:instance.time idx = (t - 1) % length(params.load_profile_mu) + 1 - gamma = rand( - Normal(params.load_profile_mu[idx], params.load_profile_sigma[idx]), - ) + gamma = rand(Normal(params.load_profile_mu[idx], params.load_profile_sigma[idx])) push!(system_load, system_load[t-1] * gamma) end capacity = sum(maximum(u.max_power) for u in instance.units) @@ -172,7 +161,7 @@ function _randomize_load_profile( # Scale bus loads to match the new system load prev_system_load = sum(b.load for b in instance.buses) for b in instance.buses - for t in 1:instance.time + for t = 1:instance.time b.load[t] *= system_load[t] / prev_system_load[t] end end diff --git a/src/utils/benchmark.jl b/src/utils/benchmark.jl index ba26f1d..159febb 100644 --- a/src/utils/benchmark.jl +++ b/src/utils/benchmark.jl @@ -93,9 +93,8 @@ function _run_benchmarks(; trials, ) combinations = [ - (c, s.first, s.second, m.first, m.second, f.first, f.second, t) for - c in cases for s in optimizers for f in formulations for - m in methods for t in trials + (c, s.first, s.second, m.first, m.second, f.first, f.second, t) for c in cases + for s in optimizers for f in formulations for m in methods for t in trials ] shuffle!(combinations) if nworkers() > 1 diff --git a/src/validation/repair.jl b/src/validation/repair.jl index e1dd964..5d37523 100644 --- a/src/validation/repair.jl +++ b/src/validation/repair.jl @@ -18,7 +18,7 @@ function repair!(instance::UnitCommitmentInstance)::Int for g in instance.units # Startup costs and delays must be increasing - for s in 2:length(g.startup_categories) + for s = 2:length(g.startup_categories) if g.startup_categories[s].delay <= g.startup_categories[s-1].delay prev_value = g.startup_categories[s].delay new_value = g.startup_categories[s-1].delay + 1 @@ -38,9 +38,9 @@ function repair!(instance::UnitCommitmentInstance)::Int end end - for t in 1:instance.time + for t = 1:instance.time # Production cost curve should be convex - for k in 2:length(g.cost_segments) + for k = 2:length(g.cost_segments) cost = g.cost_segments[k].cost[t] min_cost = g.cost_segments[k-1].cost[t] if cost < min_cost - 1e-5 diff --git a/src/validation/validate.jl b/src/validation/validate.jl index 72da3c7..395bd48 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -24,10 +24,7 @@ This function is implemented independently from the optimization model in producing valid solutions. It can also be used to verify the solutions produced by other optimization packages. """ -function validate( - instance::UnitCommitmentInstance, - solution::Union{Dict,OrderedDict}, -)::Bool +function validate(instance::UnitCommitmentInstance, solution::Union{Dict,OrderedDict})::Bool err_count = 0 err_count += _validate_units(instance, solution) err_count += _validate_reserve_and_demand(instance, solution) @@ -50,13 +47,12 @@ function _validate_units(instance, solution; tol = 0.01) actual_startup_cost = solution["Startup cost (\$)"][unit.name] is_on = bin(solution["Is on"][unit.name]) - for t in 1:instance.time + for t = 1:instance.time # Auxiliary variables if t == 1 is_starting_up = (unit.initial_status < 0) && is_on[t] is_shutting_down = (unit.initial_status > 0) && !is_on[t] - ramp_up = - max(0, production[t] + reserve[t] - unit.initial_power) + ramp_up = max(0, production[t] + reserve[t] - unit.initial_power) ramp_down = max(0, unit.initial_power - production[t]) else is_starting_up = !is_on[t-1] && is_on[t] @@ -90,11 +86,7 @@ function _validate_units(instance, solution; tol = 0.01) # Verify must-run if !is_on[t] && unit.must_run[t] - @error @sprintf( - "Must-run unit %s is offline at time %d", - unit.name, - t - ) + @error @sprintf("Must-run unit %s is offline at time %d", unit.name, t) err_count += 1 end @@ -121,8 +113,7 @@ function _validate_units(instance, solution; tol = 0.01) end # If unit is on, must produce at most its maximum power - if is_on[t] && - (production[t] + reserve[t] > unit.max_power[t] + tol) + if is_on[t] && (production[t] + reserve[t] > unit.max_power[t] + tol) @error @sprintf( "Unit %s produces above its maximum limit at time %d (%.2f + %.2f> %.2f)", unit.name, @@ -136,11 +127,7 @@ function _validate_units(instance, solution; tol = 0.01) # If unit is off, must produce zero if !is_on[t] && production[t] + reserve[t] > tol - @error @sprintf( - "Unit %s produces power at time %d while off", - unit.name, - t - ) + @error @sprintf("Unit %s produces power at time %d while off", unit.name, t) err_count += 1 end @@ -169,9 +156,7 @@ function _validate_units(instance, solution; tol = 0.01) end # Ramp-up limit - if !is_starting_up && - !is_shutting_down && - (ramp_up > unit.ramp_up_limit + tol) + if !is_starting_up && !is_shutting_down && (ramp_up > unit.ramp_up_limit + tol) @error @sprintf( "Unit %s exceeds ramp up limit at time %d (%.2f > %.2f)", unit.name, @@ -201,7 +186,7 @@ function _validate_units(instance, solution; tol = 0.01) # Calculate how much time the unit has been offline time_down = 0 - for k in 1:(t-1) + for k = 1:(t-1) if !is_on[t-k] time_down += 1 else @@ -235,7 +220,7 @@ function _validate_units(instance, solution; tol = 0.01) # Calculate how much time the unit has been online time_up = 0 - for k in 1:(t-1) + for k = 1:(t-1) if is_on[t-k] time_up += 1 else @@ -288,7 +273,7 @@ end function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count = 0 - for t in 1:instance.time + for t = 1:instance.time load_curtail = 0 fixed_load = sum(b.load[t] for b in instance.buses) ps_load = 0 @@ -298,13 +283,10 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) ps in instance.price_sensitive_loads ) end - production = - sum(solution["Production (MW)"][g.name][t] for g in instance.units) + production = sum(solution["Production (MW)"][g.name][t] for g in instance.units) if "Load curtail (MW)" in keys(solution) - load_curtail = sum( - solution["Load curtail (MW)"][b.name][t] for - b in instance.buses - ) + load_curtail = + sum(solution["Load curtail (MW)"][b.name][t] for b in instance.buses) end balance = fixed_load - load_curtail - production + ps_load @@ -321,12 +303,13 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - + # Verify flexiramp solutions only if either of the up-flexiramp and # down-flexiramp requirements is not a default array of zeros - if instance.reserves.upflexiramp != zeros(instance.time) || instance.reserves.dwflexiramp != zeros(instance.time) + if instance.reserves.upflexiramp != zeros(instance.time) || + instance.reserves.dwflexiramp != zeros(instance.time) upflexiramp = - sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) + sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) upflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Up-flexiramp shortfall (MW)"][t] : 0 @@ -341,7 +324,7 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) ) err_count += 1 end - + dwflexiramp = sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) @@ -359,11 +342,10 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) ) err_count += 1 end - # Verify spinning reserve solutions only if both up-flexiramp and - # down-flexiramp requirements are arrays of zeros. + # Verify spinning reserve solutions only if both up-flexiramp and + # down-flexiramp requirements are arrays of zeros. else - reserve = - sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) + reserve = sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) reserve_shortfall = (instance.shortfall_penalty[t] >= 0) ? solution["Reserve shortfall (MW)"][t] : 0 @@ -379,7 +361,7 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end end - + end return err_count diff --git a/test/import/egret_test.jl b/test/import/egret_test.jl index 494067a..b4ecf9d 100644 --- a/test/import/egret_test.jl +++ b/test/import/egret_test.jl @@ -7,9 +7,8 @@ using UnitCommitment basedir = @__DIR__ @testset "read_egret_solution" begin - solution = UnitCommitment.read_egret_solution( - "$basedir/../fixtures/egret_output.json.gz", - ) + solution = + UnitCommitment.read_egret_solution("$basedir/../fixtures/egret_output.json.gz") for attr in ["Is on", "Production (MW)", "Production cost (\$)"] @test attr in keys(solution) @test "115_STEAM_1" in keys(solution[attr]) diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl index 676a808..cf86463 100644 --- a/test/instance/read_test.jl +++ b/test/instance/read_test.jl @@ -19,9 +19,9 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test instance.lines[5].target.name == "b5" @test instance.lines[5].reactance ≈ 0.17388 @test instance.lines[5].susceptance ≈ 10.037550333 - @test instance.lines[5].normal_flow_limit == [1e8 for t in 1:4] - @test instance.lines[5].emergency_flow_limit == [1e8 for t in 1:4] - @test instance.lines[5].flow_limit_penalty == [5e3 for t in 1:4] + @test instance.lines[5].normal_flow_limit == [1e8 for t = 1:4] + @test instance.lines[5].emergency_flow_limit == [1e8 for t = 1:4] + @test instance.lines[5].flow_limit_penalty == [5e3 for t = 1:4] @test instance.lines_by_name["l5"].name == "l5" @test instance.lines[1].name == "l1" @@ -29,9 +29,9 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test instance.lines[1].target.name == "b2" @test instance.lines[1].reactance ≈ 0.059170 @test instance.lines[1].susceptance ≈ 29.496860773945 - @test instance.lines[1].normal_flow_limit == [300.0 for t in 1:4] - @test instance.lines[1].emergency_flow_limit == [400.0 for t in 1:4] - @test instance.lines[1].flow_limit_penalty == [1e3 for t in 1:4] + @test instance.lines[1].normal_flow_limit == [300.0 for t = 1:4] + @test instance.lines[1].emergency_flow_limit == [400.0 for t = 1:4] + @test instance.lines[1].flow_limit_penalty == [1e3 for t = 1:4] @test instance.buses[9].name == "b9" @test instance.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] @@ -44,12 +44,12 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.ramp_down_limit == 1e6 @test unit.startup_limit == 1e6 @test unit.shutdown_limit == 1e6 - @test unit.must_run == [false for t in 1:4] - @test unit.min_power_cost == [1400.0 for t in 1:4] + @test unit.must_run == [false for t = 1:4] + @test unit.min_power_cost == [1400.0 for t = 1:4] @test unit.min_uptime == 1 @test unit.min_downtime == 1 - @test unit.provides_spinning_reserves == [true for t in 1:4] - for t in 1:1 + @test unit.provides_spinning_reserves == [true for t = 1:4] + for t = 1:1 @test unit.cost_segments[1].mw[t] == 10.0 @test unit.cost_segments[2].mw[t] == 20.0 @test unit.cost_segments[3].mw[t] == 5.0 @@ -68,7 +68,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip unit = instance.units[2] @test unit.name == "g2" - @test unit.must_run == [false for t in 1:4] + @test unit.must_run == [false for t = 1:4] unit = instance.units[3] @test unit.name == "g3" @@ -77,12 +77,12 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.ramp_down_limit == 70.0 @test unit.startup_limit == 70.0 @test unit.shutdown_limit == 70.0 - @test unit.must_run == [true for t in 1:4] - @test unit.min_power_cost == [0.0 for t in 1:4] + @test unit.must_run == [true for t = 1:4] + @test unit.min_power_cost == [0.0 for t = 1:4] @test unit.min_uptime == 1 @test unit.min_downtime == 1 - @test unit.provides_spinning_reserves == [true for t in 1:4] - for t in 1:4 + @test unit.provides_spinning_reserves == [true for t = 1:4] + for t = 1:4 @test unit.cost_segments[1].mw[t] ≈ 33 @test unit.cost_segments[2].mw[t] ≈ 33 @test unit.cost_segments[3].mw[t] ≈ 34 @@ -101,8 +101,8 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip load = instance.price_sensitive_loads[1] @test load.name == "ps1" @test load.bus.name == "b3" - @test load.revenue == [100.0 for t in 1:4] - @test load.demand == [50.0 for t in 1:4] + @test load.revenue == [100.0 for t = 1:4] + @test load.demand == [50.0 for t = 1:4] @test instance.price_sensitive_loads_by_name["ps1"].name == "ps1" end diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl index 3b08dc5..01277a2 100644 --- a/test/model/formulations_test.jl +++ b/test/model/formulations_test.jl @@ -23,6 +23,7 @@ function _small_test(formulation::Formulation)::Nothing instances = ["matpower/case118/2017-02-01", "test/case14"] for instance in instances # Should not crash + @show "$(instance)" UnitCommitment.build_model( instance = UnitCommitment.read_benchmark(instance), formulation = formulation, @@ -58,17 +59,26 @@ function _test(formulation::Formulation)::Nothing end @testset "formulations" begin + @show "testset formulations" _test(Formulation()) + @show "ArrCon2000 ramping" _test(Formulation(ramping = ArrCon2000.Ramping())) + # _test(Formulation(ramping = DamKucRajAta2016.Ramping())) + @show "MorLatRam2013 ramping" _test( Formulation( ramping = MorLatRam2013.Ramping(), startup_costs = MorLatRam2013.StartupCosts(), ), ) + @show "PanGua2016 ramping" _test(Formulation(ramping = PanGua2016.Ramping())) + @show "Gar1962 PwlCosts" _test(Formulation(pwl_costs = Gar1962.PwlCosts())) + @show "CarArr2006 PwlCosts" _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) + @show "KnuOstWat2018 PwlCosts" _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) + @show "formulations completed" end diff --git a/test/runtests.jl b/test/runtests.jl index 836eda2..66098e3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,7 @@ UnitCommitment._setup_logger() const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV)) @testset "UnitCommitment" begin + @show "running runtests.jl" include("usage.jl") @testset "import" begin include("import/egret_test.jl") @@ -27,6 +28,7 @@ const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV)) include("solution/methods/XavQiuWanThi19/sensitivity_test.jl") end @testset "transform" begin + @show "beginning transform" include("transform/initcond_test.jl") include("transform/slice_test.jl") @testset "randomize" begin diff --git a/test/solution/methods/XavQiuWanThi19/find_test.jl b/test/solution/methods/XavQiuWanThi19/find_test.jl index ddd02c1..57755c5 100644 --- a/test/solution/methods/XavQiuWanThi19/find_test.jl +++ b/test/solution/methods/XavQiuWanThi19/find_test.jl @@ -7,7 +7,7 @@ import UnitCommitment: _Violation, _offer, _query @testset "find_violations" begin instance = UnitCommitment.read_benchmark("test/case14") - for line in instance.lines, t in 1:instance.time + for line in instance.lines, t = 1:instance.time line.normal_flow_limit[t] = 1.0 line.emergency_flow_limit[t] = 1.0 end @@ -20,8 +20,8 @@ import UnitCommitment: _Violation, _offer, _query buses = instance.buses, isf = isf, ) - inj = [1000.0 for b in 1:13, t in 1:instance.time] - overflow = [0.0 for l in instance.lines, t in 1:instance.time] + inj = [1000.0 for b = 1:13, t = 1:instance.time] + overflow = [0.0 for l in instance.lines, t = 1:instance.time] violations = UnitCommitment._find_violations( instance = instance, net_injections = inj, diff --git a/test/transform/initcond_test.jl b/test/transform/initcond_test.jl index 3d891f3..326fa91 100644 --- a/test/transform/initcond_test.jl +++ b/test/transform/initcond_test.jl @@ -8,8 +8,7 @@ basedir = @__DIR__ @testset "generate_initial_conditions!" begin # Load instance - instance = - UnitCommitment.read("$basedir/../fixtures/case118-initcond.json.gz") + instance = UnitCommitment.read("$basedir/../fixtures/case118-initcond.json.gz") optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) # All units should have unknown initial conditions diff --git a/test/transform/randomize/XavQiuAhm2021_test.jl b/test/transform/randomize/XavQiuAhm2021_test.jl index a3107c4..ff88eb5 100644 --- a/test/transform/randomize/XavQiuAhm2021_test.jl +++ b/test/transform/randomize/XavQiuAhm2021_test.jl @@ -28,10 +28,7 @@ test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) test_approx(bus.load[1] / prev_system_load[1], 0.012) Random.seed!(42) - randomize!( - instance, - XavQiuAhm2021.Randomization(randomize_load_profile = false), - ) + randomize!(instance, XavQiuAhm2021.Randomization(randomize_load_profile = false)) # Check randomized costs test_approx(unit.min_power_cost[1], 831.977) diff --git a/test/transform/slice_test.jl b/test/transform/slice_test.jl index f330bda..69b5fc4 100644 --- a/test/transform/slice_test.jl +++ b/test/transform/slice_test.jl @@ -5,6 +5,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @testset "slice" begin + @show "beginning slice" instance = UnitCommitment.read_benchmark("test/case14") modified = UnitCommitment.slice(instance, 1:2) @@ -35,7 +36,8 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test length(ps.demand) == 2 @test length(ps.revenue) == 2 end - + @show "beginning building model under slice" + @show instance.reserves # Should be able to build model without errors optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) model = UnitCommitment.build_model( diff --git a/test/usage.jl b/test/usage.jl index 5ff589d..d9dfad7 100644 --- a/test/usage.jl +++ b/test/usage.jl @@ -6,7 +6,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON @testset "build_model" begin instance = UnitCommitment.read_benchmark("test/case14") - for line in instance.lines, t in 1:4 + for line in instance.lines, t = 1:4 line.normal_flow_limit[t] = 10.0 end optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) From b4bc50c865e236c46732c601ff0ec07c3e6f0931 Mon Sep 17 00:00:00 2001 From: oyurdakul Date: Fri, 1 Apr 2022 15:22:42 +0200 Subject: [PATCH 06/22] new formatting --- benchmark/run.jl | 12 ++- src/import/egret.jl | 2 +- src/instance/read.jl | 83 +++++++++++++------ src/instance/structs.jl | 5 +- src/model/build.jl | 8 +- src/model/formulations/ArrCon2000/ramp.jl | 17 ++-- src/model/formulations/CarArr2006/pwlcosts.jl | 22 +++-- .../formulations/DamKucRajAta2016/ramp.jl | 17 ++-- src/model/formulations/Gar1962/prod.jl | 6 +- src/model/formulations/Gar1962/pwlcosts.jl | 16 ++-- src/model/formulations/Gar1962/status.jl | 10 ++- .../formulations/KnuOstWat2018/pwlcosts.jl | 24 ++++-- src/model/formulations/MorLatRam2013/ramp.jl | 15 ++-- .../formulations/MorLatRam2013/scosts.jl | 17 ++-- src/model/formulations/PanGua2016/ramp.jl | 33 +++++--- src/model/formulations/WanHob2016/ramp.jl | 35 +++++--- src/model/formulations/base/bus.jl | 5 +- src/model/formulations/base/line.jl | 8 +- src/model/formulations/base/psload.jl | 16 +++- src/model/formulations/base/sensitivity.jl | 12 ++- src/model/formulations/base/structs.jl | 9 +- src/model/formulations/base/system.jl | 31 ++++--- src/model/formulations/base/unit.jl | 33 ++++---- src/solution/fix.jl | 8 +- .../methods/XavQiuWanThi2019/enforce.jl | 5 +- .../methods/XavQiuWanThi2019/filter.jl | 6 +- src/solution/methods/XavQiuWanThi2019/find.jl | 55 +++++++----- .../methods/XavQiuWanThi2019/optimize.jl | 5 +- src/solution/solution.jl | 46 +++++----- src/solution/warmstart.jl | 12 ++- src/transform/initcond.jl | 11 ++- src/transform/randomize/XavQiuAhm2021.jl | 25 ++++-- src/utils/benchmark.jl | 5 +- src/validation/repair.jl | 6 +- src/validation/validate.jl | 68 ++++++++++----- test/import/egret_test.jl | 5 +- test/instance/read_test.jl | 34 ++++---- .../methods/XavQiuWanThi19/find_test.jl | 6 +- test/transform/initcond_test.jl | 3 +- .../transform/randomize/XavQiuAhm2021_test.jl | 5 +- test/usage.jl | 2 +- 41 files changed, 487 insertions(+), 256 deletions(-) diff --git a/benchmark/run.jl b/benchmark/run.jl index aff8b3b..9c2a662 100644 --- a/benchmark/run.jl +++ b/benchmark/run.jl @@ -129,15 +129,19 @@ formulations = Dict( const gap_limit = parse(Float64, args["--gap"]) const time_limit = parse(Float64, args["--time-limit"]) methods = Dict( - "default" => - XavQiuWanThi2019.Method(time_limit = time_limit, gap_limit = gap_limit), + "default" => XavQiuWanThi2019.Method( + time_limit = time_limit, + gap_limit = gap_limit, + ), ) # MIP solvers # ----------------------------------------------------------------------------- optimizers = Dict( - "gurobi" => - optimizer_with_attributes(Gurobi.Optimizer, "Threads" => Threads.nthreads()), + "gurobi" => optimizer_with_attributes( + Gurobi.Optimizer, + "Threads" => Threads.nthreads(), + ), ) # Parse command line arguments diff --git a/src/import/egret.jl b/src/import/egret.jl index 97c7438..d11bdea 100644 --- a/src/import/egret.jl +++ b/src/import/egret.jl @@ -41,7 +41,7 @@ function read_egret_solution(path::String)::OrderedDict startup_cost[gen_name] = zeros(T) production_cost[gen_name] = zeros(T) if "commitment_cost" in keys(gen_dict) - for t = 1:T + for t in 1:T x = gen_dict["commitment"]["values"][t] commitment_cost = gen_dict["commitment_cost"]["values"][t] prod_above_cost = gen_dict["production_cost"]["values"][t] diff --git a/src/instance/read.jl b/src/instance/read.jl index 19249ed..09c2da4 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -23,7 +23,10 @@ Example import UnitCommitment instance = UnitCommitment.read_benchmark("matpower/case3375wp/2017-02-01") """ -function read_benchmark(name::AbstractString; quiet::Bool = false)::UnitCommitmentInstance +function read_benchmark( + name::AbstractString; + quiet::Bool = false, +)::UnitCommitmentInstance basedir = dirname(@__FILE__) filename = "$basedir/../../instances/$name.json.gz" url = "$INSTANCES_URL/$name.json.gz" @@ -62,7 +65,9 @@ function read(path::AbstractString)::UnitCommitmentInstance end function _read(file::IO)::UnitCommitmentInstance - return _from_json(JSON.parse(file, dicttype = () -> DefaultOrderedDict(nothing))) + return _from_json( + JSON.parse(file, dicttype = () -> DefaultOrderedDict(nothing)), + ) end function _read_json(path::String)::OrderedDict @@ -92,7 +97,8 @@ function _from_json(json; repair = true) end time_horizon !== nothing || error("Missing parameter: Time horizon (h)") time_step = scalar(json["Parameters"]["Time step (min)"], default = 60) - (60 % time_step == 0) || error("Time step $time_step is not a divisor of 60") + (60 % time_step == 0) || + error("Time step $time_step is not a divisor of 60") time_multiplier = 60 ÷ time_step T = time_horizon * time_multiplier @@ -102,23 +108,23 @@ function _from_json(json; repair = true) function timeseries(x; default = nothing) x !== nothing || return default - x isa Array || return [x for t = 1:T] + x isa Array || return [x for t in 1:T] return x end # Read parameters power_balance_penalty = timeseries( json["Parameters"]["Power balance penalty (\$/MW)"], - default = [1000.0 for t = 1:T], + default = [1000.0 for t in 1:T], ) # Penalty price for shortage in meeting system-wide flexiramp requirements flexiramp_shortfall_penalty = timeseries( json["Parameters"]["Flexiramp penalty (\$/MW)"], - default = [500.0 for t = 1:T], + default = [500.0 for t in 1:T], ) shortfall_penalty = timeseries( json["Parameters"]["Reserve shortfall penalty (\$/MW)"], - default = [-1.0 for t = 1:T], + default = [-1.0 for t in 1:T], ) # Read buses @@ -140,14 +146,17 @@ function _from_json(json; repair = true) # Read production cost curve K = length(dict["Production cost curve (MW)"]) - curve_mw = hcat([timeseries(dict["Production cost curve (MW)"][k]) for k = 1:K]...) - curve_cost = - hcat([timeseries(dict["Production cost curve (\$)"][k]) for k = 1:K]...) + curve_mw = hcat( + [timeseries(dict["Production cost curve (MW)"][k]) for k in 1:K]..., + ) + curve_cost = hcat( + [timeseries(dict["Production cost curve (\$)"][k]) for k in 1:K]..., + ) min_power = curve_mw[:, 1] max_power = curve_mw[:, K] min_power_cost = curve_cost[:, 1] segments = CostSegment[] - for k = 2:K + for k in 2:K amount = curve_mw[:, k] - curve_mw[:, k-1] cost = (curve_cost[:, k] - curve_cost[:, k-1]) ./ amount replace!(cost, NaN => 0.0) @@ -158,10 +167,13 @@ function _from_json(json; repair = true) startup_delays = scalar(dict["Startup delays (h)"], default = [1]) startup_costs = scalar(dict["Startup costs (\$)"], default = [0.0]) startup_categories = StartupCategory[] - for k = 1:length(startup_delays) + for k in 1:length(startup_delays) push!( startup_categories, - StartupCategory(startup_delays[k] .* time_multiplier, startup_costs[k]), + StartupCategory( + startup_delays[k] .* time_multiplier, + startup_costs[k], + ), ) end @@ -174,7 +186,8 @@ function _from_json(json; repair = true) else initial_status !== nothing || error("unit $unit_name has initial power but no initial status") - initial_status != 0 || error("unit $unit_name has invalid initial status") + initial_status != 0 || + error("unit $unit_name has invalid initial status") if initial_status < 0 && initial_power > 1e-3 error("unit $unit_name has invalid initial power") end @@ -186,7 +199,7 @@ function _from_json(json; repair = true) bus, max_power, min_power, - timeseries(dict["Must run?"], default = [false for t = 1:T]), + timeseries(dict["Must run?"], default = [false for t in 1:T]), min_power_cost, segments, scalar(dict["Minimum uptime (h)"], default = 1) * time_multiplier, @@ -197,8 +210,14 @@ function _from_json(json; repair = true) scalar(dict["Shutdown limit (MW)"], default = 1e6), initial_status, initial_power, - timeseries(dict["Provides spinning reserves?"], default = [true for t = 1:T]), - timeseries(dict["Provides flexible capacity?"], default = [true for t = 1:T]), + timeseries( + dict["Provides spinning reserves?"], + default = [true for t in 1:T], + ), + timeseries( + dict["Provides flexible capacity?"], + default = [true for t in 1:T], + ), startup_categories, ) push!(bus.units, unit) @@ -211,10 +230,14 @@ function _from_json(json; repair = true) if "Reserves" in keys(json) reserves.spinning = timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) - reserves.upflexiramp = - timeseries(json["Reserves"]["Up-flexiramp (MW)"], default = zeros(T)) - reserves.dwflexiramp = - timeseries(json["Reserves"]["Down-flexiramp (MW)"], default = zeros(T)) + reserves.upflexiramp = timeseries( + json["Reserves"]["Up-flexiramp (MW)"], + default = zeros(T), + ) + reserves.dwflexiramp = timeseries( + json["Reserves"]["Down-flexiramp (MW)"], + default = zeros(T), + ) end # Read transmission lines @@ -227,11 +250,17 @@ function _from_json(json; repair = true) name_to_bus[dict["Target bus"]], scalar(dict["Reactance (ohms)"]), scalar(dict["Susceptance (S)"]), - timeseries(dict["Normal flow limit (MW)"], default = [1e8 for t = 1:T]), - timeseries(dict["Emergency flow limit (MW)"], default = [1e8 for t = 1:T]), + timeseries( + dict["Normal flow limit (MW)"], + default = [1e8 for t in 1:T], + ), + timeseries( + dict["Emergency flow limit (MW)"], + default = [1e8 for t in 1:T], + ), timeseries( dict["Flow limit penalty (\$/MW)"], - default = [5000.0 for t = 1:T], + default = [5000.0 for t in 1:T], ), ) name_to_line[line_name] = line @@ -245,10 +274,12 @@ function _from_json(json; repair = true) affected_units = Unit[] affected_lines = TransmissionLine[] if "Affected lines" in keys(dict) - affected_lines = [name_to_line[l] for l in dict["Affected lines"]] + affected_lines = + [name_to_line[l] for l in dict["Affected lines"]] end if "Affected units" in keys(dict) - affected_units = [name_to_unit[u] for u in dict["Affected units"]] + affected_units = + [name_to_unit[u] for u in dict["Affected units"]] end cont = Contingency(cont_name, affected_lines, affected_units) push!(contingencies, cont) diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 03a5af4..8555316 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -96,7 +96,10 @@ function Base.show(io::IO, instance::UnitCommitmentInstance) print(io, "$(length(instance.buses)) buses, ") print(io, "$(length(instance.lines)) lines, ") print(io, "$(length(instance.contingencies)) contingencies, ") - print(io, "$(length(instance.price_sensitive_loads)) price sensitive loads, ") + print( + io, + "$(length(instance.price_sensitive_loads)) price sensitive loads, ", + ) print(io, "$(instance.time) time steps") print(io, ")") return diff --git a/src/model/build.jl b/src/model/build.jl index 5352b29..6b86b84 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -34,14 +34,18 @@ function build_model(; )::JuMP.Model if formulation.ramping == WanHob2016.Ramping() && instance.reserves.spinning >= ones(instance.time) .* 1e-6 - error("Spinning reserves are not supported by the WanHob2016 ramping formulation") + error( + "Spinning reserves are not supported by the WanHob2016 ramping formulation", + ) end if formulation.ramping !== WanHob2016.Ramping() && ( instance.reserves.upflexiramp >= ones(instance.time) .* 1e-6 || instance.reserves.dwflexiramp >= ones(instance.time) .* 1e-6 ) - error("Flexiramp is supported only by the WanHob2016 ramping formulation") + error( + "Flexiramp is supported only by the WanHob2016 ramping formulation", + ) end @info "Building model..." diff --git a/src/model/formulations/ArrCon2000/ramp.jl b/src/model/formulations/ArrCon2000/ramp.jl index c8aed14..e9bb288 100644 --- a/src/model/formulations/ArrCon2000/ramp.jl +++ b/src/model/formulations/ArrCon2000/ramp.jl @@ -32,7 +32,7 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t = 1:model[:instance].time + for t in 1:model[:instance].time # Ramp up limit if t == 1 if is_initially_on @@ -49,8 +49,12 @@ function _add_ramp_eqs!( max_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + - (RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) - min_prod_last_period = g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] + ( + RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? + reserve[gn, t] : 0.0 + ) + 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) eq_ramp_up[gn, t] = @constraint( @@ -77,10 +81,11 @@ function _add_ramp_eqs!( g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] + ( - RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? reserve[gn, t-1] : - 0.0 + RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? + reserve[gn, t-1] : 0.0 ) - min_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + min_prod_this_period = + g.min_power[t] * is_on[gn, t] + prod_above[gn, t] # Equation (25) in Kneuven et al. (2020) eq_ramp_down[gn, t] = @constraint( diff --git a/src/model/formulations/CarArr2006/pwlcosts.jl b/src/model/formulations/CarArr2006/pwlcosts.jl index ad2e1c9..2f13e9a 100644 --- a/src/model/formulations/CarArr2006/pwlcosts.jl +++ b/src/model/formulations/CarArr2006/pwlcosts.jl @@ -18,16 +18,18 @@ function _add_production_piecewise_linear_eqs!( prod_above = model[:prod_above] K = length(g.cost_segments) - for t = 1:model[:instance].time + for t in 1:model[:instance].time gn = g.name - for k = 1:K + for k in 1:K # Equation (45) 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* - eq_segprod_limit[gn, t, k] = - @constraint(model, segprod[gn, t, k] <= g.cost_segments[k].mw[t]) + eq_segprod_limit[gn, t, k] = @constraint( + model, + segprod[gn, t, k] <= g.cost_segments[k].mw[t] + ) # Also add this as an explicit upper bound on segprod to make the # solver's work a bit easier @@ -35,12 +37,18 @@ function _add_production_piecewise_linear_eqs!( # Definition of production # Equation (43) in Kneuven et al. (2020) - eq_prod_above_def[gn, t] = - @constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K)) + 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) - add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t]) + add_to_expression!( + model[:obj], + segprod[gn, t, k], + g.cost_segments[k].cost[t], + ) end end end diff --git a/src/model/formulations/DamKucRajAta2016/ramp.jl b/src/model/formulations/DamKucRajAta2016/ramp.jl index d4fa5f6..9afd247 100644 --- a/src/model/formulations/DamKucRajAta2016/ramp.jl +++ b/src/model/formulations/DamKucRajAta2016/ramp.jl @@ -33,8 +33,9 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t = 1:model[:instance].time - time_invariant = (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true + for t in 1:model[:instance].time + time_invariant = + (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true # if t > 1 && !time_invariant # @warn( @@ -47,8 +48,10 @@ function _add_ramp_eqs!( # end max_prod_this_period = - prod_above[gn, t] + - (RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) + prod_above[gn, t] + ( + RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? + reserve[gn, t] : 0.0 + ) min_prod_last_period = 0.0 if t > 1 && time_invariant min_prod_last_period = prod_above[gn, t-1] @@ -58,7 +61,8 @@ function _add_ramp_eqs!( eq_str_ramp_up[gn, t] = @constraint( model, max_prod_this_period - min_prod_last_period <= - (SU - g.min_power[t] - RU) * switch_on[gn, t] + RU * is_on[gn, t] + (SU - g.min_power[t] - RU) * switch_on[gn, t] + + RU * is_on[gn, t] ) elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant) if t > 1 @@ -99,7 +103,8 @@ function _add_ramp_eqs!( eq_str_ramp_down[gn, t] = @constraint( model, max_prod_last_period - min_prod_this_period <= - (SD - g.min_power[t] - RD) * switch_off[gn, t] + RD * on_last_period + (SD - g.min_power[t] - RD) * switch_off[gn, t] + + RD * on_last_period ) elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant) # Add back in min power diff --git a/src/model/formulations/Gar1962/prod.jl b/src/model/formulations/Gar1962/prod.jl index a56e5da..e39a90e 100644 --- a/src/model/formulations/Gar1962/prod.jl +++ b/src/model/formulations/Gar1962/prod.jl @@ -9,8 +9,8 @@ function _add_production_vars!( )::Nothing prod_above = _init(model, :prod_above) segprod = _init(model, :segprod) - for t = 1:model[:instance].time - for k = 1:length(g.cost_segments) + 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) @@ -28,7 +28,7 @@ function _add_production_limit_eqs!( prod_above = model[:prod_above] reserve = model[:reserve] gn = g.name - for t = 1:model[:instance].time + 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]) diff --git a/src/model/formulations/Gar1962/pwlcosts.jl b/src/model/formulations/Gar1962/pwlcosts.jl index c55d449..3ac4871 100644 --- a/src/model/formulations/Gar1962/pwlcosts.jl +++ b/src/model/formulations/Gar1962/pwlcosts.jl @@ -21,13 +21,15 @@ function _add_production_piecewise_linear_eqs!( is_on = model[:is_on] K = length(g.cost_segments) - for t = 1:model[:instance].time + for t in 1:model[:instance].time # Definition of production # Equation (43) in Kneuven et al. (2020) - eq_prod_above_def[gn, t] = - @constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K)) + eq_prod_above_def[gn, t] = @constraint( + model, + prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K) + ) - for k = 1:K + for k in 1:K # Equation (42) in Kneuven et al. (2020) # Without this, solvers will add a lot of implied bound cuts to # have this same effect. @@ -46,7 +48,11 @@ function _add_production_piecewise_linear_eqs!( # Objective function # Equation (44) in Kneuven et al. (2020) - add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t]) + add_to_expression!( + model[:obj], + segprod[gn, t, k], + g.cost_segments[k].cost[t], + ) end end return diff --git a/src/model/formulations/Gar1962/status.jl b/src/model/formulations/Gar1962/status.jl index c2b1a32..14c055f 100644 --- a/src/model/formulations/Gar1962/status.jl +++ b/src/model/formulations/Gar1962/status.jl @@ -10,7 +10,7 @@ function _add_status_vars!( is_on = _init(model, :is_on) switch_on = _init(model, :switch_on) switch_off = _init(model, :switch_off) - for t = 1:model[:instance].time + 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) @@ -34,7 +34,7 @@ function _add_status_eqs!( is_on = model[:is_on] switch_off = model[:switch_off] switch_on = model[:switch_on] - for t = 1:model[:instance].time + for t in 1:model[:instance].time if !g.must_run[t] # Link binary variables if t == 1 @@ -51,8 +51,10 @@ function _add_status_eqs!( ) 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) + eq_switch_on_off[g.name, t] = @constraint( + model, + switch_on[g.name, t] + switch_off[g.name, t] <= 1 + ) end end return diff --git a/src/model/formulations/KnuOstWat2018/pwlcosts.jl b/src/model/formulations/KnuOstWat2018/pwlcosts.jl index 42e76a4..85afa1e 100644 --- a/src/model/formulations/KnuOstWat2018/pwlcosts.jl +++ b/src/model/formulations/KnuOstWat2018/pwlcosts.jl @@ -26,12 +26,12 @@ function _add_production_piecewise_linear_eqs!( switch_on = model[:switch_on] switch_off = model[:switch_off] - for t = 1:T - for k = 1:K + for t in 1:T + for k in 1:K # Pbar^{k-1) Pbar0 = g.min_power[t] + - (k > 1 ? sum(g.cost_segments[ell].mw[t] for ell = 1:k-1) : 0.0) + (k > 1 ? sum(g.cost_segments[ell].mw[t] for ell in 1:k-1) : 0.0) # Pbar^k Pbar1 = g.cost_segments[k].mw[t] + Pbar0 @@ -61,7 +61,8 @@ function _add_production_piecewise_linear_eqs!( eq_segprod_limit_a[gn, t, k] = @constraint( model, segprod[gn, t, k] <= - g.cost_segments[k].mw[t] * is_on[gn, t] - Cv * switch_on[gn, t] - + g.cost_segments[k].mw[t] * is_on[gn, t] - + Cv * switch_on[gn, t] - (t < T ? Cw * switch_off[gn, t+1] : 0.0) ) else @@ -69,7 +70,8 @@ function _add_production_piecewise_linear_eqs!( eq_segprod_limit_b[gn, t, k] = @constraint( model, segprod[gn, t, k] <= - g.cost_segments[k].mw[t] * is_on[gn, t] - Cv * switch_on[gn, t] - + g.cost_segments[k].mw[t] * is_on[gn, t] - + Cv * switch_on[gn, t] - (t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0) ) @@ -85,12 +87,18 @@ function _add_production_piecewise_linear_eqs!( # Definition of production # Equation (43) in Kneuven et al. (2020) - eq_prod_above_def[gn, t] = - @constraint(model, prod_above[gn, t] == sum(segprod[gn, t, k] for k = 1:K)) + 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) - add_to_expression!(model[:obj], segprod[gn, t, k], g.cost_segments[k].cost[t]) + add_to_expression!( + model[:obj], + segprod[gn, t, k], + g.cost_segments[k].cost[t], + ) # Also add an explicit upper bound on segprod to make the solver's # work a bit easier diff --git a/src/model/formulations/MorLatRam2013/ramp.jl b/src/model/formulations/MorLatRam2013/ramp.jl index b8e07ec..cbb8f94 100644 --- a/src/model/formulations/MorLatRam2013/ramp.jl +++ b/src/model/formulations/MorLatRam2013/ramp.jl @@ -32,8 +32,9 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t = 1:model[:instance].time - time_invariant = (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true + for t in 1:model[:instance].time + time_invariant = + (t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true # Ramp up limit if t == 1 @@ -59,8 +60,8 @@ function _add_ramp_eqs!( g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + ( - RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : - 0.0 + RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ? + reserve[gn, t] : 0.0 ) min_prod_last_period = g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1] @@ -75,7 +76,8 @@ function _add_ramp_eqs!( # prod_above[gn, t] when starting up, and creates diff with (24). eq_ramp_up[gn, t] = @constraint( model, - prod_above[gn, t] + (RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) - + prod_above[gn, t] + + (RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) - prod_above[gn, t-1] <= RU ) end @@ -105,7 +107,8 @@ function _add_ramp_eqs!( RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ? reserve[gn, t-1] : 0.0 ) - min_prod_this_period = g.min_power[t] * is_on[gn, t] + prod_above[gn, t] + min_prod_this_period = + g.min_power[t] * is_on[gn, t] + prod_above[gn, t] eq_ramp_down[gn, t] = @constraint( model, max_prod_last_period - min_prod_this_period <= diff --git a/src/model/formulations/MorLatRam2013/scosts.jl b/src/model/formulations/MorLatRam2013/scosts.jl index 77ef984..2d68747 100644 --- a/src/model/formulations/MorLatRam2013/scosts.jl +++ b/src/model/formulations/MorLatRam2013/scosts.jl @@ -11,27 +11,30 @@ function _add_startup_cost_eqs!( eq_startup_restrict = _init(model, :eq_startup_restrict) S = length(g.startup_categories) startup = model[:startup] - for t = 1:model[:instance].time + for t in 1:model[:instance].time # If unit is switching on, we must choose a startup category eq_startup_choose[g.name, t] = @constraint( model, - model[:switch_on][g.name, t] == sum(startup[g.name, t, s] for s = 1:S) + model[:switch_on][g.name, t] == + sum(startup[g.name, t, s] for s in 1:S) ) - for s = 1: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) + 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) + initial_sum + sum( + model[:switch_off][g.name, i] for i in range if i >= 1 + ) ) end diff --git a/src/model/formulations/PanGua2016/ramp.jl b/src/model/formulations/PanGua2016/ramp.jl index 36415fe..f040824 100644 --- a/src/model/formulations/PanGua2016/ramp.jl +++ b/src/model/formulations/PanGua2016/ramp.jl @@ -14,8 +14,10 @@ function _add_ramp_eqs!( gn = g.name reserve = model[:reserve] 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) - eq_prod_limit_shutdown_trajectory = _init(model, :eq_prod_limit_shutdown_trajectory) + eq_prod_limit_ramp_up_extra_period = + _init(model, :eq_prod_limit_ramp_up_extra_period) + eq_prod_limit_shutdown_trajectory = + _init(model, :eq_prod_limit_shutdown_trajectory) UT = g.min_uptime SU = g.startup_limit # startup rate, i.e., max production right after startup SD = g.shutdown_limit # shutdown rate, i.e., max production right before shutdown @@ -31,7 +33,7 @@ function _add_ramp_eqs!( switch_off = model[:switch_off] switch_on = model[:switch_on] - for t = 1:T + for t in 1:T Pbar = g.max_power[t] if Pbar < 1e-7 # Skip this time period if max power = 0 @@ -52,10 +54,13 @@ function _add_ramp_eqs!( # then switch_off[gn, t+1] = 0 eq_str_prod_limit[gn, t] = @constraint( model, - prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + reserve[gn, t] <= - Pbar * is_on[gn, t] - (t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum( + prod_above[gn, t] + + g.min_power[t] * is_on[gn, t] + + reserve[gn, t] <= + Pbar * is_on[gn, t] - + (t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum( (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for - i = 0:min(UT - 2, TRU, t - 1) + i in 0:min(UT - 2, TRU, t - 1) ) ) @@ -64,10 +69,12 @@ function _add_ramp_eqs!( # Covers an additional time period of the ramp-up trajectory, compared to (38) eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint( model, - prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + reserve[gn, t] <= + prod_above[gn, t] + + g.min_power[t] * is_on[gn, t] + + reserve[gn, t] <= Pbar * is_on[gn, t] - sum( (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for - i = 0:min(UT - 1, TRU, t - 1) + i in 0:min(UT - 1, TRU, t - 1) ) ) end @@ -82,9 +89,13 @@ function _add_ramp_eqs!( prod_above[gn, t] + g.min_power[t] * is_on[gn, t] + (RESERVES_WHEN_SHUT_DOWN ? reserve[gn, t] : 0.0) <= - Pbar * is_on[gn, t] - - sum((Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for i = 0:KSD) - - sum((Pbar - (SU + i * RU)) * switch_on[gn, t-i] for i = 0:KSU) - ( + Pbar * is_on[gn, t] - sum( + (Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for + i in 0:KSD + ) - sum( + (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for + i in 0:KSU + ) - ( (KSU >= TRU || KSU > t - 2) ? 0.0 : max(0, (SU + (KSU + 1) * RU) - (SD + TRD * RD)) * switch_on[gn, t-(KSU+1)] diff --git a/src/model/formulations/WanHob2016/ramp.jl b/src/model/formulations/WanHob2016/ramp.jl index 0580875..8a999f1 100644 --- a/src/model/formulations/WanHob2016/ramp.jl +++ b/src/model/formulations/WanHob2016/ramp.jl @@ -8,7 +8,7 @@ function _add_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing mfg = _init(model, :mfg) dwflexiramp = _init(model, :dwflexiramp) dwflexiramp_shortfall = _init(model, :dwflexiramp_shortfall) - for t = 1:model[:instance].time + for t in 1:model[:instance].time # maximum feasible generation, \bar{g_{its}} in Wang & Hobbs (2016) mfg[g.name, t] = @variable(model, lower_bound = 0) if g.provides_flexiramp_reserves[t] @@ -51,28 +51,37 @@ function _add_ramp_eqs!( dwflexiramp = model[:dwflexiramp] mfg = model[:mfg] - for t = 1:model[:instance].time - @constraint(model, prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t]) # Eq. (19) in Wang & Hobbs (2016) + for t in 1:model[:instance].time + @constraint( + model, + prod_above[gn, t] + (is_on[gn, t] * minp[t]) <= mfg[gn, t] + ) # Eq. (19) in Wang & Hobbs (2016) @constraint(model, mfg[gn, t] <= is_on[gn, t] * maxp[t]) # Eq. (22) in Wang & Hobbs (2016) if t != model[:instance].time @constraint( model, minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <= - prod_above[gn, t] - dwflexiramp[gn, t] + (is_on[gn, t] * minp[t]) + prod_above[gn, t] - dwflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) ) # first inequality of Eq. (20) in Wang & Hobbs (2016) @constraint( model, - prod_above[gn, t] - dwflexiramp[gn, t] + (is_on[gn, t] * minp[t]) <= + prod_above[gn, t] - dwflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) <= mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1])) ) # second inequality of Eq. (20) in Wang & Hobbs (2016) @constraint( model, minp[t] * (is_on[gn, t+1] + is_on[gn, t] - 1) <= - prod_above[gn, t] + upflexiramp[gn, t] + (is_on[gn, t] * minp[t]) + prod_above[gn, t] + + upflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) ) # first inequality of Eq. (21) in Wang & Hobbs (2016) @constraint( model, - prod_above[gn, t] + upflexiramp[gn, t] + (is_on[gn, t] * minp[t]) <= + prod_above[gn, t] + + upflexiramp[gn, t] + + (is_on[gn, t] * minp[t]) <= mfg[gn, t+1] + (maxp[t] * (1 - is_on[gn, t+1])) ) # second inequality of Eq. (21) in Wang & Hobbs (2016) if t != 1 @@ -104,7 +113,8 @@ function _add_ramp_eqs!( ) # Eq. (23) in Wang & Hobbs (2016) for the first time period @constraint( model, - initial_power - (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= + initial_power - + (prod_above[gn, t] + (is_on[gn, t] * minp[t])) <= RD * is_on[gn, t] + SD * (is_initially_on - is_on[gn, t]) + maxp[t] * (1 - is_initially_on) @@ -113,7 +123,8 @@ function _add_ramp_eqs!( @constraint( model, mfg[gn, t] <= - (SD * (is_on[gn, t] - is_on[gn, t+1])) + (maxp[t] * is_on[gn, t+1]) + (SD * (is_on[gn, t] - is_on[gn, t+1])) + + (maxp[t] * is_on[gn, t+1]) ) # Eq. (24) in Wang & Hobbs (2016) @constraint( model, @@ -141,13 +152,15 @@ function _add_ramp_eqs!( ) # second inequality of Eq. (27) in Wang & Hobbs (2016) @constraint( model, - -maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <= upflexiramp[gn, t] + -maxp[t] * is_on[gn, t] + minp[t] * is_on[gn, t+1] <= + upflexiramp[gn, t] ) # first inequality of Eq. (28) in Wang & Hobbs (2016) @constraint(model, upflexiramp[gn, t] <= maxp[t] * is_on[gn, t+1]) # second inequality of Eq. (28) in Wang & Hobbs (2016) @constraint(model, -maxp[t] * is_on[gn, t+1] <= dwflexiramp[gn, t]) # first inequality of Eq. (29) in Wang & Hobbs (2016) @constraint( model, - dwflexiramp[gn, t] <= (maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1]) + dwflexiramp[gn, t] <= + (maxp[t] * is_on[gn, t]) - (minp[t] * is_on[gn, t+1]) ) # second inequality of Eq. (29) in Wang & Hobbs (2016) else @constraint( diff --git a/src/model/formulations/base/bus.jl b/src/model/formulations/base/bus.jl index f938c9c..d881fc6 100644 --- a/src/model/formulations/base/bus.jl +++ b/src/model/formulations/base/bus.jl @@ -5,12 +5,13 @@ function _add_bus!(model::JuMP.Model, b::Bus)::Nothing net_injection = _init(model, :expr_net_injection) curtail = _init(model, :curtail) - for t = 1:model[:instance].time + for t in 1:model[:instance].time # Fixed load net_injection[b.name, t] = AffExpr(-b.load[t]) # Load curtailment - curtail[b.name, t] = @variable(model, lower_bound = 0, upper_bound = b.load[t]) + curtail[b.name, t] = + @variable(model, lower_bound = 0, upper_bound = b.load[t]) add_to_expression!(net_injection[b.name, t], curtail[b.name, t], 1.0) add_to_expression!( diff --git a/src/model/formulations/base/line.jl b/src/model/formulations/base/line.jl index fe6d24b..6398c8d 100644 --- a/src/model/formulations/base/line.jl +++ b/src/model/formulations/base/line.jl @@ -8,9 +8,13 @@ function _add_transmission_line!( f::ShiftFactorsFormulation, )::Nothing overflow = _init(model, :overflow) - for t = 1:model[:instance].time + for t in 1:model[:instance].time overflow[lm.name, t] = @variable(model, lower_bound = 0) - add_to_expression!(model[:obj], overflow[lm.name, t], lm.flow_limit_penalty[t]) + add_to_expression!( + model[:obj], + overflow[lm.name, t], + lm.flow_limit_penalty[t], + ) end return end diff --git a/src/model/formulations/base/psload.jl b/src/model/formulations/base/psload.jl index de9355e..3748111 100644 --- a/src/model/formulations/base/psload.jl +++ b/src/model/formulations/base/psload.jl @@ -2,18 +2,26 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -function _add_price_sensitive_load!(model::JuMP.Model, ps::PriceSensitiveLoad)::Nothing +function _add_price_sensitive_load!( + model::JuMP.Model, + ps::PriceSensitiveLoad, +)::Nothing loads = _init(model, :loads) net_injection = _init(model, :expr_net_injection) - for t = 1:model[:instance].time + for t in 1:model[:instance].time # Decision variable - loads[ps.name, t] = @variable(model, lower_bound = 0, upper_bound = ps.demand[t]) + loads[ps.name, t] = + @variable(model, lower_bound = 0, upper_bound = ps.demand[t]) # Objective function terms add_to_expression!(model[:obj], loads[ps.name, t], -ps.revenue[t]) # Net injection - add_to_expression!(net_injection[ps.bus.name, t], loads[ps.name, t], -1.0) + add_to_expression!( + net_injection[ps.bus.name, t], + loads[ps.name, t], + -1.0, + ) end return end diff --git a/src/model/formulations/base/sensitivity.jl b/src/model/formulations/base/sensitivity.jl index 6bf153f..f2fca9a 100644 --- a/src/model/formulations/base/sensitivity.jl +++ b/src/model/formulations/base/sensitivity.jl @@ -13,7 +13,10 @@ M[l.offset, b.offset] indicates the amount of power (in MW) that flows through transmission line l when 1 MW of power is injected at the slack bus (the bus that has offset zero) and withdrawn from b. """ -function _injection_shift_factors(; buses::Array{Bus}, lines::Array{TransmissionLine}) +function _injection_shift_factors(; + buses::Array{Bus}, + lines::Array{TransmissionLine}, +) susceptance = _susceptance_matrix(lines) incidence = _reduced_incidence_matrix(lines = lines, buses = buses) laplacian = transpose(incidence) * susceptance * incidence @@ -30,7 +33,10 @@ is the number of buses and L is the number of lines. For each row, there is a 1 element and a -1 element, indicating the source and target buses, respectively, for that line. """ -function _reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine}) +function _reduced_incidence_matrix(; + buses::Array{Bus}, + lines::Array{TransmissionLine}, +) matrix = spzeros(Float64, length(lines), length(buses) - 1) for line in lines if line.source.offset > 0 @@ -69,7 +75,7 @@ function _line_outage_factors(; incidence = Array(_reduced_incidence_matrix(lines = lines, buses = buses)) lodf::Array{Float64,2} = isf * transpose(incidence) _, n = size(lodf) - for i = 1:n + for i in 1:n lodf[:, i] *= 1.0 / (1.0 - lodf[i, i]) lodf[i, i] = -1 end diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index 37358de..2cc7e44 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -25,7 +25,14 @@ struct Formulation status_vars::StatusVarsFormulation = Gar1962.StatusVars(), transmission::TransmissionFormulation = ShiftFactorsFormulation(), ) - return new(prod_vars, 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/system.jl b/src/model/formulations/base/system.jl index 8de941f..c526040 100644 --- a/src/model/formulations/base/system.jl +++ b/src/model/formulations/base/system.jl @@ -14,12 +14,12 @@ function _add_net_injection_eqs!(model::JuMP.Model)::Nothing net_injection = _init(model, :net_injection) eq_net_injection = _init(model, :eq_net_injection) eq_power_balance = _init(model, :eq_power_balance) - for t = 1:T, b in model[:instance].buses + for t in 1:T, b in model[:instance].buses n = net_injection[b.name, t] = @variable(model) eq_net_injection[b.name, t] = @constraint(model, -n + model[:expr_net_injection][b.name, t] == 0) end - for t = 1:T + for t in 1:T eq_power_balance[t] = @constraint( model, sum(net_injection[b.name, t] for b in model[:instance].buses) == 0 @@ -31,7 +31,7 @@ end function _add_reserve_eqs!(model::JuMP.Model)::Nothing eq_min_reserve = _init(model, :eq_min_reserve) instance = model[:instance] - for t = 1:instance.time + for t in 1:instance.time # Equation (68) in Kneuven et al. (2020) # As in Morales-España et al. (2013a) # Akin to the alternative formulation with max_power_avail @@ -46,7 +46,11 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing # Account for shortfall contribution to objective if shortfall_penalty >= 0 - add_to_expression!(model[:obj], shortfall_penalty, model[:reserve_shortfall][t]) + add_to_expression!( + model[:obj], + shortfall_penalty, + model[:reserve_shortfall][t], + ) end end return @@ -61,20 +65,24 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing eq_min_upflexiramp = _init(model, :eq_min_upflexiramp) eq_min_dwflexiramp = _init(model, :eq_min_dwflexiramp) instance = model[:instance] - for t = 1:instance.time + for t in 1:instance.time flexiramp_shortfall_penalty = instance.flexiramp_shortfall_penalty[t] # Eq. (17) in Wang & Hobbs (2016) eq_min_upflexiramp[t] = @constraint( model, - sum(model[:upflexiramp][g.name, t] for g in instance.units) + ( - flexiramp_shortfall_penalty >= 0 ? model[:upflexiramp_shortfall][t] : 0.0 + sum(model[:upflexiramp][g.name, t] for g in instance.units) + + ( + flexiramp_shortfall_penalty >= 0 ? + model[:upflexiramp_shortfall][t] : 0.0 ) >= instance.reserves.upflexiramp[t] ) # Eq. (18) in Wang & Hobbs (2016) eq_min_dwflexiramp[t] = @constraint( model, - sum(model[:dwflexiramp][g.name, t] for g in instance.units) + ( - flexiramp_shortfall_penalty >= 0 ? model[:dwflexiramp_shortfall][t] : 0.0 + sum(model[:dwflexiramp][g.name, t] for g in instance.units) + + ( + flexiramp_shortfall_penalty >= 0 ? + model[:dwflexiramp_shortfall][t] : 0.0 ) >= instance.reserves.dwflexiramp[t] ) @@ -83,7 +91,10 @@ function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing add_to_expression!( model[:obj], flexiramp_shortfall_penalty, - (model[:upflexiramp_shortfall][t] + model[:dwflexiramp_shortfall][t]), + ( + model[:upflexiramp_shortfall][t] + + model[:dwflexiramp_shortfall][t] + ), ) end end diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index e3bbe45..5da9ffa 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -46,7 +46,7 @@ _is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0) function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing reserve = _init(model, :reserve) reserve_shortfall = _init(model, :reserve_shortfall) - for t = 1:model[:instance].time + for t in 1:model[:instance].time if g.provides_spinning_reserves[t] reserve[g.name, t] = @variable(model, lower_bound = 0) else @@ -61,7 +61,7 @@ end function _add_reserve_eqs!(model::JuMP.Model, g::Unit)::Nothing reserve = model[:reserve] - for t = 1:model[:instance].time + for t in 1:model[:instance].time add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0) end return @@ -69,8 +69,8 @@ end function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing startup = _init(model, :startup) - for t = 1:model[:instance].time - for s = 1:length(g.startup_categories) + for t in 1:model[:instance].time + for s in 1:length(g.startup_categories) startup[g.name, t, s] = @variable(model, binary = true) end end @@ -86,7 +86,7 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing switch_off = model[:switch_off] switch_on = model[:switch_on] T = model[:instance].time - for t = 1:T + for t in 1:T # Startup limit eq_startup_limit[g.name, t] = @constraint( model, @@ -96,14 +96,16 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing ) # Shutdown limit if g.initial_power > g.shutdown_limit - eq_shutdown_limit[g.name, 0] = @constraint(model, switch_off[g.name, 1] <= 0) + eq_shutdown_limit[g.name, 0] = + @constraint(model, switch_off[g.name, 1] <= 0) end if t < T eq_shutdown_limit[g.name, t] = @constraint( model, prod_above[g.name, t] <= (g.max_power[t] - g.min_power[t]) * is_on[g.name, t] - - max(0, g.max_power[t] - g.shutdown_limit) * switch_off[g.name, t+1] + max(0, g.max_power[t] - g.shutdown_limit) * + switch_off[g.name, t+1] ) end end @@ -119,7 +121,7 @@ function _add_ramp_eqs!( reserve = model[:reserve] eq_ramp_up = _init(model, :eq_ramp_up) eq_ramp_down = _init(model, :eq_ramp_down) - for t = 1:model[:instance].time + for t in 1:model[:instance].time # Ramp up limit if t == 1 if _is_initially_on(g) == 1 @@ -149,7 +151,8 @@ function _add_ramp_eqs!( else eq_ramp_down[g.name, t] = @constraint( model, - prod_above[g.name, t] >= prod_above[g.name, t-1] - g.ramp_down_limit + prod_above[g.name, t] >= + prod_above[g.name, t-1] - g.ramp_down_limit ) end end @@ -162,18 +165,18 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing eq_min_uptime = _init(model, :eq_min_uptime) eq_min_downtime = _init(model, :eq_min_downtime) T = model[:instance].time - for t = 1:T + for t in 1:T # Minimum up-time 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] + 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 eq_min_downtime[g.name, t] = @constraint( model, - sum(switch_off[g.name, i] for i in (t-g.min_downtime+1):t if i >= 1) <= - 1 - is_on[g.name, t] + sum( + switch_off[g.name, i] for i in (t-g.min_downtime+1):t if i >= 1 + ) <= 1 - is_on[g.name, t] ) # Minimum up/down-time for initial periods if t == 1 @@ -200,7 +203,7 @@ end function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing expr_net_injection = model[:expr_net_injection] - for t = 1:model[:instance].time + for t in 1:model[:instance].time # Add to net injection expression add_to_expression!( expr_net_injection[g.bus.name, t], diff --git a/src/solution/fix.jl b/src/solution/fix.jl index dbead66..f9deacb 100644 --- a/src/solution/fix.jl +++ b/src/solution/fix.jl @@ -14,10 +14,12 @@ function fix!(model::JuMP.Model, solution::AbstractDict)::Nothing prod_above = model[:prod_above] reserve = model[:reserve] for g in instance.units - for t = 1:T + for t in 1:T is_on_value = round(solution["Is on"][g.name][t]) - prod_value = round(solution["Production (MW)"][g.name][t], digits = 5) - reserve_value = round(solution["Reserve (MW)"][g.name][t], digits = 5) + prod_value = + round(solution["Production (MW)"][g.name][t], digits = 5) + reserve_value = + round(solution["Reserve (MW)"][g.name][t], digits = 5) JuMP.fix(is_on[g.name, t], is_on_value, force = true) JuMP.fix( prod_above[g.name, t], diff --git a/src/solution/methods/XavQiuWanThi2019/enforce.jl b/src/solution/methods/XavQiuWanThi2019/enforce.jl index 08ae1af..526274f 100644 --- a/src/solution/methods/XavQiuWanThi2019/enforce.jl +++ b/src/solution/methods/XavQiuWanThi2019/enforce.jl @@ -2,7 +2,10 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -function _enforce_transmission(model::JuMP.Model, violations::Vector{_Violation})::Nothing +function _enforce_transmission( + model::JuMP.Model, + violations::Vector{_Violation}, +)::Nothing for v in violations _enforce_transmission( model = model, diff --git a/src/solution/methods/XavQiuWanThi2019/filter.jl b/src/solution/methods/XavQiuWanThi2019/filter.jl index a70ef78..576e8fc 100644 --- a/src/solution/methods/XavQiuWanThi2019/filter.jl +++ b/src/solution/methods/XavQiuWanThi2019/filter.jl @@ -4,9 +4,11 @@ function _offer(filter::_ViolationFilter, v::_Violation)::Nothing if v.monitored_line.offset ∉ keys(filter.queues) - filter.queues[v.monitored_line.offset] = PriorityQueue{_Violation,Float64}() + filter.queues[v.monitored_line.offset] = + PriorityQueue{_Violation,Float64}() end - q::PriorityQueue{_Violation,Float64} = filter.queues[v.monitored_line.offset] + q::PriorityQueue{_Violation,Float64} = + filter.queues[v.monitored_line.offset] if length(q) < filter.max_per_line enqueue!(q, v => v.amount) else diff --git a/src/solution/methods/XavQiuWanThi2019/find.jl b/src/solution/methods/XavQiuWanThi2019/find.jl index 11f8bd9..3cf9094 100644 --- a/src/solution/methods/XavQiuWanThi2019/find.jl +++ b/src/solution/methods/XavQiuWanThi2019/find.jl @@ -4,7 +4,11 @@ import Base.Threads: @threads -function _find_violations(model::JuMP.Model; max_per_line::Int, max_per_period::Int) +function _find_violations( + model::JuMP.Model; + max_per_line::Int, + max_per_period::Int, +) instance = model[:instance] net_injection = model[:net_injection] overflow = model[:overflow] @@ -14,10 +18,13 @@ function _find_violations(model::JuMP.Model; max_per_line::Int, max_per_period:: time_screening = @elapsed begin non_slack_buses = [b for b in instance.buses if b.offset > 0] net_injection_values = [ - value(net_injection[b.name, t]) for b in non_slack_buses, t = 1:instance.time + value(net_injection[b.name, t]) for b in non_slack_buses, + t in 1:instance.time + ] + overflow_values = [ + value(overflow[lm.name, t]) for lm in instance.lines, + t in 1:instance.time ] - overflow_values = - [value(overflow[lm.name, t]) for lm in instance.lines, t = 1:instance.time] violations = UnitCommitment._find_violations( instance = instance, net_injections = net_injection_values, @@ -28,7 +35,10 @@ function _find_violations(model::JuMP.Model; max_per_line::Int, max_per_period:: max_per_period = max_per_period, ) end - @info @sprintf("Verified transmission limits in %.2f seconds", time_screening) + @info @sprintf( + "Verified transmission limits in %.2f seconds", + time_screening + ) return violations end @@ -71,8 +81,10 @@ function _find_violations(; size(lodf) == (L, L) || error("lodf has incorrect size") filters = Dict( - t => _ViolationFilter(max_total = max_per_period, max_per_line = max_per_line) - for t = 1:T + t => _ViolationFilter( + max_total = max_per_period, + max_per_line = max_per_line, + ) for t in 1:T ) pre_flow::Array{Float64} = zeros(L, K) # pre_flow[lm, thread] @@ -80,30 +92,35 @@ function _find_violations(; pre_v::Array{Float64} = zeros(L, K) # pre_v[lm, thread] post_v::Array{Float64} = zeros(L, L, K) # post_v[lm, lc, thread] - normal_limits::Array{Float64,2} = - [l.normal_flow_limit[t] + overflow[l.offset, t] for l in instance.lines, t = 1:T] + normal_limits::Array{Float64,2} = [ + l.normal_flow_limit[t] + overflow[l.offset, t] for + l in instance.lines, t in 1:T + ] - emergency_limits::Array{Float64,2} = - [l.emergency_flow_limit[t] + overflow[l.offset, t] for l in instance.lines, t = 1:T] + emergency_limits::Array{Float64,2} = [ + l.emergency_flow_limit[t] + overflow[l.offset, t] for + l in instance.lines, t in 1:T + ] is_vulnerable::Array{Bool} = zeros(Bool, L) for c in instance.contingencies is_vulnerable[c.lines[1].offset] = true end - @threads for t = 1:T + @threads for t in 1:T k = threadid() # Pre-contingency flows pre_flow[:, k] = isf * net_injections[:, t] # Post-contingency flows - for lc = 1:L, lm = 1:L - post_flow[lm, lc, k] = pre_flow[lm, k] + pre_flow[lc, k] * lodf[lm, lc] + for lc in 1:L, lm in 1:L + post_flow[lm, lc, k] = + pre_flow[lm, k] + pre_flow[lc, k] * lodf[lm, lc] end # Pre-contingency violations - for lm = 1:L + for lm in 1:L pre_v[lm, k] = max( 0.0, pre_flow[lm, k] - normal_limits[lm, t], @@ -112,7 +129,7 @@ function _find_violations(; end # Post-contingency violations - for lc = 1:L, lm = 1:L + for lc in 1:L, lm in 1:L post_v[lm, lc, k] = max( 0.0, post_flow[lm, lc, k] - emergency_limits[lm, t], @@ -121,7 +138,7 @@ function _find_violations(; end # Offer pre-contingency violations - for lm = 1:L + for lm in 1:L if pre_v[lm, k] > 1e-5 _offer( filters[t], @@ -136,7 +153,7 @@ function _find_violations(; end # Offer post-contingency violations - for lm = 1:L, lc = 1:L + for lm in 1:L, lc in 1:L if post_v[lm, lc, k] > 1e-5 && is_vulnerable[lc] _offer( filters[t], @@ -152,7 +169,7 @@ function _find_violations(; end violations = _Violation[] - for t = 1:instance.time + for t in 1:instance.time append!(violations, _query(filters[t])) end diff --git a/src/solution/methods/XavQiuWanThi2019/optimize.jl b/src/solution/methods/XavQiuWanThi2019/optimize.jl index 9362da6..b513bd1 100644 --- a/src/solution/methods/XavQiuWanThi2019/optimize.jl +++ b/src/solution/methods/XavQiuWanThi2019/optimize.jl @@ -27,7 +27,10 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing @info "Time limit exceeded" break end - @info @sprintf("Setting MILP time limit to %.2f seconds", time_remaining) + @info @sprintf( + "Setting MILP time limit to %.2f seconds", + time_remaining + ) JuMP.set_time_limit_sec(model, time_remaining) @info "Solving MILP..." JuMP.optimize!(model) diff --git a/src/solution/solution.jl b/src/solution/solution.jl index 0fdb772..0283eac 100644 --- a/src/solution/solution.jl +++ b/src/solution/solution.jl @@ -6,40 +6,43 @@ function solution(model::JuMP.Model)::OrderedDict instance, T = model[:instance], model[:instance].time function timeseries(vars, collection) return OrderedDict( - b.name => [round(value(vars[b.name, t]), digits = 5) for t = 1:T] for - b in collection + b.name => [round(value(vars[b.name, t]), digits = 5) for t in 1:T] + for b in collection ) end function production_cost(g) return [ value(model[:is_on][g.name, t]) * g.min_power_cost[t] + sum( Float64[ - value(model[:segprod][g.name, t, k]) * g.cost_segments[k].cost[t] for - k = 1:length(g.cost_segments) + value(model[:segprod][g.name, t, k]) * + g.cost_segments[k].cost[t] for + k in 1:length(g.cost_segments) ], - ) for t = 1:T + ) for t in 1:T ] end function production(g) return [ value(model[:is_on][g.name, t]) * g.min_power[t] + sum( Float64[ - value(model[:segprod][g.name, t, k]) for k = 1:length(g.cost_segments) + value(model[:segprod][g.name, t, k]) for + k in 1:length(g.cost_segments) ], - ) for t = 1:T + ) for t in 1:T ] end function startup_cost(g) S = length(g.startup_categories) return [ sum( - g.startup_categories[s].cost * value(model[:startup][g.name, t, s]) for - s = 1:S - ) for t = 1:T + g.startup_categories[s].cost * + value(model[:startup][g.name, t, s]) for s in 1:S + ) for t in 1:T ] end sol = OrderedDict() - sol["Production (MW)"] = OrderedDict(g.name => production(g) for g in instance.units) + sol["Production (MW)"] = + OrderedDict(g.name => production(g) for g in instance.units) sol["Production cost (\$)"] = OrderedDict(g.name => production_cost(g) for g in instance.units) sol["Startup cost (\$)"] = @@ -51,19 +54,21 @@ function solution(model::JuMP.Model)::OrderedDict instance.reserves.dwflexiramp != zeros(T) # Report flexiramp solutions only if either of the up-flexiramp and # down-flexiramp requirements is not a default array of zeros - sol["Up-flexiramp (MW)"] = timeseries(model[:upflexiramp], instance.units) + sol["Up-flexiramp (MW)"] = + timeseries(model[:upflexiramp], instance.units) sol["Up-flexiramp shortfall (MW)"] = OrderedDict( t => (instance.flexiramp_shortfall_penalty[t] >= 0) ? - round(value(model[:upflexiramp_shortfall][t]), digits = 5) : 0.0 for - t = 1:instance.time + round(value(model[:upflexiramp_shortfall][t]), digits = 5) : + 0.0 for t in 1:instance.time ) - sol["Down-flexiramp (MW)"] = timeseries(model[:dwflexiramp], instance.units) + sol["Down-flexiramp (MW)"] = + timeseries(model[:dwflexiramp], instance.units) sol["Down-flexiramp shortfall (MW)"] = OrderedDict( t => (instance.flexiramp_shortfall_penalty[t] >= 0) ? - round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : 0.0 for - t = 1:instance.time + round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : + 0.0 for t in 1:instance.time ) else # Report spinning reserve solutions only if both up-flexiramp and @@ -72,11 +77,12 @@ function solution(model::JuMP.Model)::OrderedDict sol["Reserve shortfall (MW)"] = OrderedDict( t => (instance.shortfall_penalty[t] >= 0) ? - round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for - t = 1:instance.time + round(value(model[:reserve_shortfall][t]), digits = 5) : + 0.0 for t in 1:instance.time ) end - sol["Net injection (MW)"] = timeseries(model[:net_injection], instance.buses) + sol["Net injection (MW)"] = + timeseries(model[:net_injection], instance.buses) sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses) if !isempty(instance.lines) sol["Line overflow (MW)"] = timeseries(model[:overflow], instance.lines) diff --git a/src/solution/warmstart.jl b/src/solution/warmstart.jl index 38c9135..678f250 100644 --- a/src/solution/warmstart.jl +++ b/src/solution/warmstart.jl @@ -6,10 +6,16 @@ function set_warm_start!(model::JuMP.Model, solution::AbstractDict)::Nothing instance, T = model[:instance], model[:instance].time is_on = model[:is_on] for g in instance.units - for t = 1:T + for t in 1:T JuMP.set_start_value(is_on[g.name, t], solution["Is on"][g.name][t]) - JuMP.set_start_value(switch_on[g.name, t], solution["Switch on"][g.name][t]) - JuMP.set_start_value(switch_off[g.name, t], solution["Switch off"][g.name][t]) + JuMP.set_start_value( + switch_on[g.name, t], + solution["Switch on"][g.name][t], + ) + JuMP.set_start_value( + switch_off[g.name, t], + solution["Switch off"][g.name][t], + ) end end return diff --git a/src/transform/initcond.jl b/src/transform/initcond.jl index 9ca733e..6bbf0ea 100644 --- a/src/transform/initcond.jl +++ b/src/transform/initcond.jl @@ -11,7 +11,10 @@ Generates feasible initial conditions for the given instance, by constructing and solving a single-period mixed-integer optimization problem, using the given optimizer. The instance is modified in-place. """ -function generate_initial_conditions!(instance::UnitCommitmentInstance, optimizer)::Nothing +function generate_initial_conditions!( + instance::UnitCommitmentInstance, + optimizer, +)::Nothing G = instance.units B = instance.buses t = 1 @@ -28,7 +31,11 @@ function generate_initial_conditions!(instance::UnitCommitmentInstance, optimize @constraint(mip, max_power[g in G], p[g] <= g.max_power[t] * x[g]) # Constraint: Production equals demand - @constraint(mip, power_balance, sum(b.load[t] for b in B) == sum(p[g] for g in G)) + @constraint( + mip, + power_balance, + sum(b.load[t] for b in B) == sum(p[g] for g in G) + ) # Constraint: Must run for g in G diff --git a/src/transform/randomize/XavQiuAhm2021.jl b/src/transform/randomize/XavQiuAhm2021.jl index 73bc5d5..adedcd2 100644 --- a/src/transform/randomize/XavQiuAhm2021.jl +++ b/src/transform/randomize/XavQiuAhm2021.jl @@ -117,7 +117,10 @@ Base.@kwdef struct Randomization randomize_load_share::Bool = true end -function _randomize_costs(instance::UnitCommitmentInstance, distribution)::Nothing +function _randomize_costs( + instance::UnitCommitmentInstance, + distribution, +)::Nothing for unit in instance.units α = rand(distribution) unit.min_power_cost *= α @@ -131,11 +134,17 @@ function _randomize_costs(instance::UnitCommitmentInstance, distribution)::Nothi return end -function _randomize_load_share(instance::UnitCommitmentInstance, distribution)::Nothing +function _randomize_load_share( + instance::UnitCommitmentInstance, + distribution, +)::Nothing α = rand(distribution, length(instance.buses)) - for t = 1:instance.time + for t in 1:instance.time total = sum(bus.load[t] for bus in instance.buses) - den = sum(bus.load[t] / total * α[i] for (i, bus) in enumerate(instance.buses)) + den = sum( + bus.load[t] / total * α[i] for + (i, bus) in enumerate(instance.buses) + ) for (i, bus) in enumerate(instance.buses) bus.load[t] *= α[i] / den end @@ -149,9 +158,11 @@ function _randomize_load_profile( )::Nothing # Generate new system load system_load = [1.0] - for t = 2:instance.time + for t in 2:instance.time idx = (t - 1) % length(params.load_profile_mu) + 1 - gamma = rand(Normal(params.load_profile_mu[idx], params.load_profile_sigma[idx])) + gamma = rand( + Normal(params.load_profile_mu[idx], params.load_profile_sigma[idx]), + ) push!(system_load, system_load[t-1] * gamma) end capacity = sum(maximum(u.max_power) for u in instance.units) @@ -161,7 +172,7 @@ function _randomize_load_profile( # Scale bus loads to match the new system load prev_system_load = sum(b.load for b in instance.buses) for b in instance.buses - for t = 1:instance.time + for t in 1:instance.time b.load[t] *= system_load[t] / prev_system_load[t] end end diff --git a/src/utils/benchmark.jl b/src/utils/benchmark.jl index 159febb..ba26f1d 100644 --- a/src/utils/benchmark.jl +++ b/src/utils/benchmark.jl @@ -93,8 +93,9 @@ function _run_benchmarks(; trials, ) combinations = [ - (c, s.first, s.second, m.first, m.second, f.first, f.second, t) for c in cases - for s in optimizers for f in formulations for m in methods for t in trials + (c, s.first, s.second, m.first, m.second, f.first, f.second, t) for + c in cases for s in optimizers for f in formulations for + m in methods for t in trials ] shuffle!(combinations) if nworkers() > 1 diff --git a/src/validation/repair.jl b/src/validation/repair.jl index 5d37523..e1dd964 100644 --- a/src/validation/repair.jl +++ b/src/validation/repair.jl @@ -18,7 +18,7 @@ function repair!(instance::UnitCommitmentInstance)::Int for g in instance.units # Startup costs and delays must be increasing - for s = 2:length(g.startup_categories) + for s in 2:length(g.startup_categories) if g.startup_categories[s].delay <= g.startup_categories[s-1].delay prev_value = g.startup_categories[s].delay new_value = g.startup_categories[s-1].delay + 1 @@ -38,9 +38,9 @@ function repair!(instance::UnitCommitmentInstance)::Int end end - for t = 1:instance.time + for t in 1:instance.time # Production cost curve should be convex - for k = 2:length(g.cost_segments) + for k in 2:length(g.cost_segments) cost = g.cost_segments[k].cost[t] min_cost = g.cost_segments[k-1].cost[t] if cost < min_cost - 1e-5 diff --git a/src/validation/validate.jl b/src/validation/validate.jl index 395bd48..114bf39 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -24,7 +24,10 @@ This function is implemented independently from the optimization model in producing valid solutions. It can also be used to verify the solutions produced by other optimization packages. """ -function validate(instance::UnitCommitmentInstance, solution::Union{Dict,OrderedDict})::Bool +function validate( + instance::UnitCommitmentInstance, + solution::Union{Dict,OrderedDict}, +)::Bool err_count = 0 err_count += _validate_units(instance, solution) err_count += _validate_reserve_and_demand(instance, solution) @@ -47,12 +50,13 @@ function _validate_units(instance, solution; tol = 0.01) actual_startup_cost = solution["Startup cost (\$)"][unit.name] is_on = bin(solution["Is on"][unit.name]) - for t = 1:instance.time + for t in 1:instance.time # Auxiliary variables if t == 1 is_starting_up = (unit.initial_status < 0) && is_on[t] is_shutting_down = (unit.initial_status > 0) && !is_on[t] - ramp_up = max(0, production[t] + reserve[t] - unit.initial_power) + ramp_up = + max(0, production[t] + reserve[t] - unit.initial_power) ramp_down = max(0, unit.initial_power - production[t]) else is_starting_up = !is_on[t-1] && is_on[t] @@ -86,7 +90,11 @@ function _validate_units(instance, solution; tol = 0.01) # Verify must-run if !is_on[t] && unit.must_run[t] - @error @sprintf("Must-run unit %s is offline at time %d", unit.name, t) + @error @sprintf( + "Must-run unit %s is offline at time %d", + unit.name, + t + ) err_count += 1 end @@ -113,7 +121,8 @@ function _validate_units(instance, solution; tol = 0.01) end # If unit is on, must produce at most its maximum power - if is_on[t] && (production[t] + reserve[t] > unit.max_power[t] + tol) + if is_on[t] && + (production[t] + reserve[t] > unit.max_power[t] + tol) @error @sprintf( "Unit %s produces above its maximum limit at time %d (%.2f + %.2f> %.2f)", unit.name, @@ -127,7 +136,11 @@ function _validate_units(instance, solution; tol = 0.01) # If unit is off, must produce zero if !is_on[t] && production[t] + reserve[t] > tol - @error @sprintf("Unit %s produces power at time %d while off", unit.name, t) + @error @sprintf( + "Unit %s produces power at time %d while off", + unit.name, + t + ) err_count += 1 end @@ -156,7 +169,9 @@ function _validate_units(instance, solution; tol = 0.01) end # Ramp-up limit - if !is_starting_up && !is_shutting_down && (ramp_up > unit.ramp_up_limit + tol) + if !is_starting_up && + !is_shutting_down && + (ramp_up > unit.ramp_up_limit + tol) @error @sprintf( "Unit %s exceeds ramp up limit at time %d (%.2f > %.2f)", unit.name, @@ -186,7 +201,7 @@ function _validate_units(instance, solution; tol = 0.01) # Calculate how much time the unit has been offline time_down = 0 - for k = 1:(t-1) + for k in 1:(t-1) if !is_on[t-k] time_down += 1 else @@ -220,7 +235,7 @@ function _validate_units(instance, solution; tol = 0.01) # Calculate how much time the unit has been online time_up = 0 - for k = 1:(t-1) + for k in 1:(t-1) if is_on[t-k] time_up += 1 else @@ -273,7 +288,7 @@ end function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count = 0 - for t = 1:instance.time + for t in 1:instance.time load_curtail = 0 fixed_load = sum(b.load[t] for b in instance.buses) ps_load = 0 @@ -283,10 +298,13 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) ps in instance.price_sensitive_loads ) end - production = sum(solution["Production (MW)"][g.name][t] for g in instance.units) + production = + sum(solution["Production (MW)"][g.name][t] for g in instance.units) if "Load curtail (MW)" in keys(solution) - load_curtail = - sum(solution["Load curtail (MW)"][b.name][t] for b in instance.buses) + load_curtail = sum( + solution["Load curtail (MW)"][b.name][t] for + b in instance.buses + ) end balance = fixed_load - load_curtail - production + ps_load @@ -303,18 +321,20 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - # Verify flexiramp solutions only if either of the up-flexiramp and # down-flexiramp requirements is not a default array of zeros if instance.reserves.upflexiramp != zeros(instance.time) || instance.reserves.dwflexiramp != zeros(instance.time) - upflexiramp = - sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units) + upflexiramp = sum( + solution["Up-flexiramp (MW)"][g.name][t] for + g in instance.units + ) upflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Up-flexiramp shortfall (MW)"][t] : 0 - if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol + if upflexiramp + upflexiramp_shortfall < + instance.reserves.upflexiramp[t] - tol @error @sprintf( "Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)", t, @@ -325,14 +345,16 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end - - dwflexiramp = - sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units) + dwflexiramp = sum( + solution["Down-flexiramp (MW)"][g.name][t] for + g in instance.units + ) dwflexiramp_shortfall = (instance.flexiramp_shortfall_penalty[t] >= 0) ? solution["Down-flexiramp shortfall (MW)"][t] : 0 - if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol + if dwflexiramp + dwflexiramp_shortfall < + instance.reserves.dwflexiramp[t] - tol @error @sprintf( "Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)", t, @@ -345,7 +367,8 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) # Verify spinning reserve solutions only if both up-flexiramp and # down-flexiramp requirements are arrays of zeros. else - reserve = sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) + reserve = + sum(solution["Reserve (MW)"][g.name][t] for g in instance.units) reserve_shortfall = (instance.shortfall_penalty[t] >= 0) ? solution["Reserve shortfall (MW)"][t] : 0 @@ -361,7 +384,6 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01) err_count += 1 end end - end return err_count diff --git a/test/import/egret_test.jl b/test/import/egret_test.jl index b4ecf9d..494067a 100644 --- a/test/import/egret_test.jl +++ b/test/import/egret_test.jl @@ -7,8 +7,9 @@ using UnitCommitment basedir = @__DIR__ @testset "read_egret_solution" begin - solution = - UnitCommitment.read_egret_solution("$basedir/../fixtures/egret_output.json.gz") + solution = UnitCommitment.read_egret_solution( + "$basedir/../fixtures/egret_output.json.gz", + ) for attr in ["Is on", "Production (MW)", "Production cost (\$)"] @test attr in keys(solution) @test "115_STEAM_1" in keys(solution[attr]) diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl index cf86463..676a808 100644 --- a/test/instance/read_test.jl +++ b/test/instance/read_test.jl @@ -19,9 +19,9 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test instance.lines[5].target.name == "b5" @test instance.lines[5].reactance ≈ 0.17388 @test instance.lines[5].susceptance ≈ 10.037550333 - @test instance.lines[5].normal_flow_limit == [1e8 for t = 1:4] - @test instance.lines[5].emergency_flow_limit == [1e8 for t = 1:4] - @test instance.lines[5].flow_limit_penalty == [5e3 for t = 1:4] + @test instance.lines[5].normal_flow_limit == [1e8 for t in 1:4] + @test instance.lines[5].emergency_flow_limit == [1e8 for t in 1:4] + @test instance.lines[5].flow_limit_penalty == [5e3 for t in 1:4] @test instance.lines_by_name["l5"].name == "l5" @test instance.lines[1].name == "l1" @@ -29,9 +29,9 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test instance.lines[1].target.name == "b2" @test instance.lines[1].reactance ≈ 0.059170 @test instance.lines[1].susceptance ≈ 29.496860773945 - @test instance.lines[1].normal_flow_limit == [300.0 for t = 1:4] - @test instance.lines[1].emergency_flow_limit == [400.0 for t = 1:4] - @test instance.lines[1].flow_limit_penalty == [1e3 for t = 1:4] + @test instance.lines[1].normal_flow_limit == [300.0 for t in 1:4] + @test instance.lines[1].emergency_flow_limit == [400.0 for t in 1:4] + @test instance.lines[1].flow_limit_penalty == [1e3 for t in 1:4] @test instance.buses[9].name == "b9" @test instance.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] @@ -44,12 +44,12 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.ramp_down_limit == 1e6 @test unit.startup_limit == 1e6 @test unit.shutdown_limit == 1e6 - @test unit.must_run == [false for t = 1:4] - @test unit.min_power_cost == [1400.0 for t = 1:4] + @test unit.must_run == [false for t in 1:4] + @test unit.min_power_cost == [1400.0 for t in 1:4] @test unit.min_uptime == 1 @test unit.min_downtime == 1 - @test unit.provides_spinning_reserves == [true for t = 1:4] - for t = 1:1 + @test unit.provides_spinning_reserves == [true for t in 1:4] + for t in 1:1 @test unit.cost_segments[1].mw[t] == 10.0 @test unit.cost_segments[2].mw[t] == 20.0 @test unit.cost_segments[3].mw[t] == 5.0 @@ -68,7 +68,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip unit = instance.units[2] @test unit.name == "g2" - @test unit.must_run == [false for t = 1:4] + @test unit.must_run == [false for t in 1:4] unit = instance.units[3] @test unit.name == "g3" @@ -77,12 +77,12 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.ramp_down_limit == 70.0 @test unit.startup_limit == 70.0 @test unit.shutdown_limit == 70.0 - @test unit.must_run == [true for t = 1:4] - @test unit.min_power_cost == [0.0 for t = 1:4] + @test unit.must_run == [true for t in 1:4] + @test unit.min_power_cost == [0.0 for t in 1:4] @test unit.min_uptime == 1 @test unit.min_downtime == 1 - @test unit.provides_spinning_reserves == [true for t = 1:4] - for t = 1:4 + @test unit.provides_spinning_reserves == [true for t in 1:4] + for t in 1:4 @test unit.cost_segments[1].mw[t] ≈ 33 @test unit.cost_segments[2].mw[t] ≈ 33 @test unit.cost_segments[3].mw[t] ≈ 34 @@ -101,8 +101,8 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip load = instance.price_sensitive_loads[1] @test load.name == "ps1" @test load.bus.name == "b3" - @test load.revenue == [100.0 for t = 1:4] - @test load.demand == [50.0 for t = 1:4] + @test load.revenue == [100.0 for t in 1:4] + @test load.demand == [50.0 for t in 1:4] @test instance.price_sensitive_loads_by_name["ps1"].name == "ps1" end diff --git a/test/solution/methods/XavQiuWanThi19/find_test.jl b/test/solution/methods/XavQiuWanThi19/find_test.jl index 57755c5..ddd02c1 100644 --- a/test/solution/methods/XavQiuWanThi19/find_test.jl +++ b/test/solution/methods/XavQiuWanThi19/find_test.jl @@ -7,7 +7,7 @@ import UnitCommitment: _Violation, _offer, _query @testset "find_violations" begin instance = UnitCommitment.read_benchmark("test/case14") - for line in instance.lines, t = 1:instance.time + for line in instance.lines, t in 1:instance.time line.normal_flow_limit[t] = 1.0 line.emergency_flow_limit[t] = 1.0 end @@ -20,8 +20,8 @@ import UnitCommitment: _Violation, _offer, _query buses = instance.buses, isf = isf, ) - inj = [1000.0 for b = 1:13, t = 1:instance.time] - overflow = [0.0 for l in instance.lines, t = 1:instance.time] + inj = [1000.0 for b in 1:13, t in 1:instance.time] + overflow = [0.0 for l in instance.lines, t in 1:instance.time] violations = UnitCommitment._find_violations( instance = instance, net_injections = inj, diff --git a/test/transform/initcond_test.jl b/test/transform/initcond_test.jl index 326fa91..3d891f3 100644 --- a/test/transform/initcond_test.jl +++ b/test/transform/initcond_test.jl @@ -8,7 +8,8 @@ basedir = @__DIR__ @testset "generate_initial_conditions!" begin # Load instance - instance = UnitCommitment.read("$basedir/../fixtures/case118-initcond.json.gz") + instance = + UnitCommitment.read("$basedir/../fixtures/case118-initcond.json.gz") optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) # All units should have unknown initial conditions diff --git a/test/transform/randomize/XavQiuAhm2021_test.jl b/test/transform/randomize/XavQiuAhm2021_test.jl index ff88eb5..a3107c4 100644 --- a/test/transform/randomize/XavQiuAhm2021_test.jl +++ b/test/transform/randomize/XavQiuAhm2021_test.jl @@ -28,7 +28,10 @@ test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) test_approx(bus.load[1] / prev_system_load[1], 0.012) Random.seed!(42) - randomize!(instance, XavQiuAhm2021.Randomization(randomize_load_profile = false)) + randomize!( + instance, + XavQiuAhm2021.Randomization(randomize_load_profile = false), + ) # Check randomized costs test_approx(unit.min_power_cost[1], 831.977) diff --git a/test/usage.jl b/test/usage.jl index d9dfad7..5ff589d 100644 --- a/test/usage.jl +++ b/test/usage.jl @@ -6,7 +6,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON @testset "build_model" begin instance = UnitCommitment.read_benchmark("test/case14") - for line in instance.lines, t = 1:4 + for line in instance.lines, t in 1:4 line.normal_flow_limit[t] = 10.0 end optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) From 099fb4e3cbcb1b4085c00da1a3ef337f0255b646 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 09:52:08 -0500 Subject: [PATCH 07/22] Add case14-flex test case --- docs/format.md | 4 ++-- instances/test/case14-flex.json.gz | Bin 0 -> 1891 bytes test/model/formulations_test.jl | 36 +++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 instances/test/case14-flex.json.gz diff --git a/docs/format.md b/docs/format.md index 985511e..dc02a0d 100644 --- a/docs/format.md +++ b/docs/format.md @@ -240,13 +240,13 @@ This section describes the hourly amount of reserves required. ```json { "Reserves": { - "up-flexiramp (MW)": [ + "Up-flexiramp (MW)": [ 20.31042, 23.65273, 27.41784, 25.34057 ], - "down-flexiramp (MW)": [ + "Down-flexiramp (MW)": [ 19.41546, 21.45377, 23.53402, diff --git a/instances/test/case14-flex.json.gz b/instances/test/case14-flex.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b02c6dfa8590dfa1044a8883b0dcaad52c6b48a9 GIT binary patch literal 1891 zcmV-p2b}mHiwFo8*IHr#17l%xWid1@W^846E^2dcZUEg{OK;mo5I)zxf-rJ$0eQQ- zv(H|NwrPt3O%cQ?dI)l$W!fenkp@ZGaRdMN4kbS%awrakv?wYYGR&Qw{dRV~d93vL zD8bk9+q6oTS(8=u@wdt6^|ST!_xUnQuFER_t1Oa}>#uRt%#OY~ewWo*b()dQrd6O^`>yr+9iMbnoHc3H ztZtHvY?0pAkB0f6P-LExrmmA!^t+tWHB*E#?Mpdt^^3W#j; z8kTM0(YEHvraM#KrOTTHr!4Yi-aPFr2An|1U6gmlsN@rfQMv0?!&3MrFY@JTi71 z5_ZtL`#hK>VN#gUkAHzr2`{;nAS6jjP;w+C2*EciG-S0Z2Hb*VjN&n-C-yHh#{G90 zV}=>yrZMJFy6of1KaeWz@P@MS6ZFurV-?AzBJZ3%4$FBj~HQ`R+<<4ST0BJ2@JqscPqY0>12~Yt7F(UCSK(g#g z1cv=kqiC6I`l|+Eou}Xgp=>mY8ju={=D;NAJaYUXjP{y3eJvao>;xVhk=l@Mp+wLS z0|%-ED{>AJ#()KmX_TCNj8b@v5QMwoh%(j^ zh@x;#8%GUTsf=PF9SNy@qy|HZlZ01+h=`MV?yYwOOzuB0ww5KoK0_5ICYO<|Y=_$+25`5ir^k2Z_7z z(OC+s6)|G8Bu6~RVXQl~!22pK>SbQnZ6&hEi*8}CAiVgjTvhWdIUl-)dY@KTS@S&J zyGVJLrSm2&P{m1ky{sQYla0#Dmshv-I9t{8?B;RWSvyB#I*l$k={+DnWH(PA|6W#0 ztcflc<=t)*1+w2o{j|hpCM)LmguEf-e}0kLcfYq~>y14Lx=pmFW!Y0}kY>xCdeoli zgxVr!q6I+WxIk}!U}9$jy#w!Tpm(D{Za_sJ{V3=lf^ten6xL2D6KT){B`1P5JkZ|3 zxTT{AF<_*O5S5EMY8o_ASWN`Ya}|X(R~QA2*;0bHRsvwImfl$!HBo1EQ1{ONKvOH( zWju*mODwvAn+f{W5c>jP-(Y0yNyHxabw#S$5n ziZ3q?Eir^hXC3~kXr!SxKop)6HxOAHu!v&Z!vXuYfS`>FvUPPgV0Nko?ZECiLkD*6 ziiozm)pDBMorv0klQLpkhwa9|2F8-k1 zQPjLz1c8Kcl47~5HF}A%ZP94a@B_HVGv+96X3E4Ga@X+`W6Up`@woDD2qY^tApIFp}G0)ZH*R)z(sA9P|MrHzJp6C=!mR%~906Y>yziR|g~|u}_xR>^mLWoD~N0 zjvCLLeQO>X0ks_GWW-~^mL06u4u6K6WISK?Biw;sq2*^gIvHu$I5DcxPT4GtHk&plywqnUncl={nGX$Ve>hz|v0S4VHGp(oV#8mUhe1PK05WcE{3AgiDro&(cl= zHJ0|k(oV!4miEZfPSw;rI!T`XgXzlSD^;BG^z#@?6_GssJkC;O6Hh;nS5$q&)6ZiT zRf+KQ^SDJ-3_Sfjc2TLFr=Q0!DhKlt$}qonQsvt!pJ%6aR@8Zu-)708OfNbq^QPYO zWk#Ybxy_1IwsWi6yM=w7Ez{!pX5H@Gy6(Vx{8ncDB)SFbkDJrWMfNGL+OP1Ccl1~d zgOa9GSZKf+S$A*XwHHQV;oNy@u{4oe_c+_%2RMFR-W6jQR7AbD`*6&6i(>!sG27-SAC4Z5{szYH6`JcR003n0qCfxu literal 0 HcmV?d00001 diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl index 01277a2..f46717b 100644 --- a/test/model/formulations_test.jl +++ b/test/model/formulations_test.jl @@ -4,6 +4,7 @@ using UnitCommitment using JuMP +using Cbc import UnitCommitment: ArrCon2000, CarArr2006, @@ -13,23 +14,35 @@ import UnitCommitment: KnuOstWat2018, MorLatRam2013, PanGua2016, - XavQiuWanThi2019 + XavQiuWanThi2019, + WanHob2016 if ENABLE_LARGE_TESTS using Gurobi end -function _small_test(formulation::Formulation)::Nothing - instances = ["matpower/case118/2017-02-01", "test/case14"] - for instance in instances - # Should not crash - @show "$(instance)" - UnitCommitment.build_model( - instance = UnitCommitment.read_benchmark(instance), +function _small_test( + formulation::Formulation; + instances::Array{String} = ["test/case14"], + optimizer = optimizer_with_attributes( + Cbc.Optimizer, + "logLevel" => 0, + ) +)::Nothing + for instance_name in instances + instance = UnitCommitment.read_benchmark(instance_name) + model = UnitCommitment.build_model( + instance = instance, formulation = formulation, + optimizer = optimizer, + ) + UnitCommitment.optimize!( + model, + XavQiuWanThi2019.Method(two_phase_gap = false, gap_limit = 0.1), ) + solution = UnitCommitment.solution(model) + @test UnitCommitment.validate(instance, solution) end - return end function _large_test(formulation::Formulation)::Nothing @@ -80,5 +93,8 @@ end _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) @show "KnuOstWat2018 PwlCosts" _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) - @show "formulations completed" + _small_test( + Formulation(ramping = WanHob2016.Ramping()), + instances=["test/case14-flex"], + ) end From cda1e368fe6fb5aaec69207dec0a1d08fe5251e6 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 09:55:28 -0500 Subject: [PATCH 08/22] Remove some redundant comments --- src/instance/structs.jl | 8 ++++---- src/model/formulations/base/system.jl | 2 +- src/model/formulations/base/unit.jl | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 8555316..85700ae 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -37,7 +37,7 @@ mutable struct Unit initial_status::Union{Int,Nothing} initial_power::Union{Float64,Nothing} provides_spinning_reserves::Vector{Bool} - provides_flexiramp_reserves::Vector{Bool} # binary variable indicating whether the unit provides flexiramp + provides_flexiramp_reserves::Vector{Bool} startup_categories::Vector{StartupCategory} end @@ -55,8 +55,8 @@ end mutable struct Reserves spinning::Vector{Float64} - upflexiramp::Vector{Float64} # up-flexiramp reserve requirements - dwflexiramp::Vector{Float64} # down-flexiramp reserve requirements + upflexiramp::Vector{Float64} + dwflexiramp::Vector{Float64} end mutable struct Contingency @@ -84,7 +84,7 @@ Base.@kwdef mutable struct UnitCommitmentInstance price_sensitive_loads::Vector{PriceSensitiveLoad} reserves::Reserves shortfall_penalty::Vector{Float64} - flexiramp_shortfall_penalty::Vector{Float64} # penalty price for flexiramp shortfall + flexiramp_shortfall_penalty::Vector{Float64} time::Int units_by_name::Dict{AbstractString,Unit} units::Vector{Unit} diff --git a/src/model/formulations/base/system.jl b/src/model/formulations/base/system.jl index c526040..20cd986 100644 --- a/src/model/formulations/base/system.jl +++ b/src/model/formulations/base/system.jl @@ -5,7 +5,7 @@ function _add_system_wide_eqs!(model::JuMP.Model)::Nothing _add_net_injection_eqs!(model) _add_reserve_eqs!(model) - _add_flexiramp_eqs!(model) # Add system-wide flexiramp requirements + _add_flexiramp_eqs!(model) return end diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index 5da9ffa..040d970 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -13,7 +13,7 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation) # Variables _add_production_vars!(model, g, formulation.prod_vars) _add_reserve_vars!(model, g) - _add_flexiramp_vars!(model, g) # Add variables for flexiramp + _add_flexiramp_vars!(model, g) _add_startup_shutdown_vars!(model, g) _add_status_vars!(model, g, formulation.status_vars) From 16b0fec6cd5d7c92c3f6c3027392bcc5c76ea754 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 10:11:33 -0500 Subject: [PATCH 09/22] Make tests completely silent; remove set_gap warnings on Cbc --- .../methods/XavQiuWanThi2019/optimize.jl | 13 ++++------ .../methods/XavQiuWanThi2019/structs.jl | 6 ++--- src/utils/log.jl | 26 ++++++++----------- test/model/formulations_test.jl | 7 ----- test/runtests.jl | 4 +-- test/transform/slice_test.jl | 3 --- 6 files changed, 20 insertions(+), 39 deletions(-) diff --git a/src/solution/methods/XavQiuWanThi2019/optimize.jl b/src/solution/methods/XavQiuWanThi2019/optimize.jl index b513bd1..78c8381 100644 --- a/src/solution/methods/XavQiuWanThi2019/optimize.jl +++ b/src/solution/methods/XavQiuWanThi2019/optimize.jl @@ -3,13 +3,12 @@ # Released under the modified BSD license. See COPYING.md for more details. function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing + if !occursin("Gurobi", JuMP.solver_name(model)) + method.two_phase_gap = false + end function set_gap(gap) - try - JuMP.set_optimizer_attribute(model, "MIPGap", gap) - @info @sprintf("MIP gap tolerance set to %f", gap) - catch - @warn "Could not change MIP gap tolerance" - end + JuMP.set_optimizer_attribute(model, "MIPGap", gap) + @info @sprintf("MIP gap tolerance set to %f", gap) end initial_time = time() large_gap = false @@ -17,8 +16,6 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing if has_transmission && method.two_phase_gap set_gap(1e-2) large_gap = true - else - set_gap(method.gap_limit) end while true time_elapsed = time() - initial_time diff --git a/src/solution/methods/XavQiuWanThi2019/structs.jl b/src/solution/methods/XavQiuWanThi2019/structs.jl index fc16383..4125563 100644 --- a/src/solution/methods/XavQiuWanThi2019/structs.jl +++ b/src/solution/methods/XavQiuWanThi2019/structs.jl @@ -13,7 +13,7 @@ Lazy constraint solution method described in: module XavQiuWanThi2019 import ..SolutionMethod """ - struct Method + mutable struct Method time_limit::Float64 gap_limit::Float64 two_phase_gap::Bool @@ -27,7 +27,7 @@ Fields - `time_limit`: the time limit over the entire optimization procedure. - `gap_limit`: - the desired relative optimality gap. + the desired relative optimality gap. Only used when `two_phase_gap=true`. - `two_phase_gap`: if true, solve the problem with large gap tolerance first, then reduce the gap tolerance when no further violated constraints are found. @@ -39,7 +39,7 @@ Fields formulation per time period. """ -struct Method <: SolutionMethod +mutable struct Method <: SolutionMethod time_limit::Float64 gap_limit::Float64 two_phase_gap::Bool diff --git a/src/utils/log.jl b/src/utils/log.jl index b69a7d5..1508156 100644 --- a/src/utils/log.jl +++ b/src/utils/log.jl @@ -5,20 +5,11 @@ import Logging: min_enabled_level, shouldlog, handle_message using Base.CoreLogging, Logging, Printf -struct TimeLogger <: AbstractLogger +Base.@kwdef struct TimeLogger <: AbstractLogger initial_time::Float64 - file::Union{Nothing,IOStream} - screen_log_level::Any - io_log_level::Any -end - -function TimeLogger(; - initial_time::Float64, - file::Union{Nothing,IOStream} = nothing, - screen_log_level = CoreLogging.Info, - io_log_level = CoreLogging.Info, -)::TimeLogger - return TimeLogger(initial_time, file, screen_log_level, io_log_level) + file::Union{Nothing,IOStream} = nothing + screen_log_level::Any = CoreLogging.Info + io_log_level::Any = CoreLogging.Info end min_enabled_level(logger::TimeLogger) = logger.io_log_level @@ -61,7 +52,12 @@ function handle_message( end end -function _setup_logger() +function _setup_logger(; level = CoreLogging.Info) initial_time = time() - return global_logger(TimeLogger(initial_time = initial_time)) + return global_logger( + TimeLogger( + initial_time = initial_time, + screen_log_level = level, + ) + ) end diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl index f46717b..df86f09 100644 --- a/test/model/formulations_test.jl +++ b/test/model/formulations_test.jl @@ -72,26 +72,19 @@ function _test(formulation::Formulation)::Nothing end @testset "formulations" begin - @show "testset formulations" _test(Formulation()) - @show "ArrCon2000 ramping" _test(Formulation(ramping = ArrCon2000.Ramping())) # _test(Formulation(ramping = DamKucRajAta2016.Ramping())) - @show "MorLatRam2013 ramping" _test( Formulation( ramping = MorLatRam2013.Ramping(), startup_costs = MorLatRam2013.StartupCosts(), ), ) - @show "PanGua2016 ramping" _test(Formulation(ramping = PanGua2016.Ramping())) - @show "Gar1962 PwlCosts" _test(Formulation(pwl_costs = Gar1962.PwlCosts())) - @show "CarArr2006 PwlCosts" _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) - @show "KnuOstWat2018 PwlCosts" _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) _small_test( Formulation(ramping = WanHob2016.Ramping()), diff --git a/test/runtests.jl b/test/runtests.jl index 66098e3..56cade9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,12 +6,11 @@ using Test using UnitCommitment push!(Base.LOAD_PATH, @__DIR__) -UnitCommitment._setup_logger() +UnitCommitment._setup_logger(level = Base.CoreLogging.Error) const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV)) @testset "UnitCommitment" begin - @show "running runtests.jl" include("usage.jl") @testset "import" begin include("import/egret_test.jl") @@ -28,7 +27,6 @@ const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV)) include("solution/methods/XavQiuWanThi19/sensitivity_test.jl") end @testset "transform" begin - @show "beginning transform" include("transform/initcond_test.jl") include("transform/slice_test.jl") @testset "randomize" begin diff --git a/test/transform/slice_test.jl b/test/transform/slice_test.jl index 69b5fc4..49f8099 100644 --- a/test/transform/slice_test.jl +++ b/test/transform/slice_test.jl @@ -5,7 +5,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @testset "slice" begin - @show "beginning slice" instance = UnitCommitment.read_benchmark("test/case14") modified = UnitCommitment.slice(instance, 1:2) @@ -36,8 +35,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test length(ps.demand) == 2 @test length(ps.revenue) == 2 end - @show "beginning building model under slice" - @show instance.reserves # Should be able to build model without errors optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) model = UnitCommitment.build_model( From db106f1a38306e523990d590e399819caf3dc087 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 10:12:09 -0500 Subject: [PATCH 10/22] Make juliaw executable --- juliaw | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 juliaw diff --git a/juliaw b/juliaw old mode 100644 new mode 100755 From 3b1d2d184570dfffc6519f7619037b31c311fec2 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 10:15:32 -0500 Subject: [PATCH 11/22] =?UTF-8?q?Add=20author:=20Og=C3=BCn=20Yurdakul?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a2427d7..39248fa 100755 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ UnitCommitment.write("/tmp/output.json", solution) ## Authors * **Alinson S. Xavier** (Argonne National Laboratory) * **Aleksandr M. Kazachkov** (University of Florida) +* **Ogün Yurdakul** (Technische Universität Berlin) * **Feng Qiu** (Argonne National Laboratory) ## Acknowledgments From 3260fa29ad6a24ae28a0418dcf05228bf35d0f31 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 10:16:53 -0500 Subject: [PATCH 12/22] Remove temporary files --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 31 ++++++++++++++++++++++++------- benchmark/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 24 insertions(+), 7 deletions(-) delete mode 100644 .DS_Store delete mode 100644 benchmark/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ab5cefd05e24dccaf15a94eedbadbff4e72f9891..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu};G<5WQZze!_Wpc$8FF1;*XvEchOc~%7l)_M*PEPgf8@6h18w*Np){o(JyMfpom^d? zm|;$M-)8SAr^R*sDdRbtN!Hj~7ggrIwJGU|1POMKCbdP@smgl^Cqy@CTb$8Ae47C${2)E%SHr!YMn} z4{17arRcpY;0ojlO!aXt=l?B!nb9IY5Ai2gz!mss3UE=c>LotP&epTfle0FU-Jyxe qyeI=0#@QtR53-M3CQ|tzW6Y}zqoS-L{uB=Mi$D^@J6GTr6!-#Or$Mg( diff --git a/.gitignore b/.gitignore index bfcf178..4ce337b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,38 @@ *.bak *.gz +*.ipynb *.lastrun -*.so *.mps -*.ipynb +*.so +*/Manifest.toml +.AppleDB +.AppleDesktop +.AppleDouble +.DS_Store +.DocumentRevisions-V100 +.LSOverride +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +._* +.apdisk +.com.apple.timemachine.donotpresent +.fseventsd .ipy* +.vscode +Icon +Manifest.toml +Network Trash Folder +TODO.md +Temporary Items benchmark/results benchmark/runs benchmark/tables benchmark/tmp.json build +docs/_build instances/**/*.json instances/_source local notebooks -TODO.md -docs/_build -.vscode -Manifest.toml -*/Manifest.toml diff --git a/benchmark/.DS_Store b/benchmark/.DS_Store deleted file mode 100644 index e22e5657b00f358ef81453a5bffad12090cac619..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}N6?5T4OW7kVk4Jb1{>Yx@RiS)X8EptOZ5Y`aUVp7*VM0H4Xb-(;rX+8l&} zNST4;n`C~H{a}*}5%K(WJtZ0wQGq7NqGUv-v!*L^z5#N|(b5YY>dmxTHJL>JaZ2_+ zqyw#JPtWvi|Jzo#`w&k(woO?s>t+c{@xB<|-hY0~a$f6Sc(qT$)^W87()`@dTeaWS z&29q=Saxv+oB?OR8E^*9WWa8dVsvI64|fKffy-k+&WC^|7!5ncbabFgB>-@M=_Jsl zmXMfW7!5l`SRkySKn-OpF<8SfAIvWrc8VHKY{duL%E#h`)9T0{k~?u!^xhe82Ko$K z>u@ah|08~xY?0p&@sTs&3|trkJSeMjj*qgt_2l#9t_^4lG!gOZM1er>JpwS0bL1`= c)gDBLUo`9#C5z0baG*Z~5+U9>1AoB47yD^5=Kufz From 74b8a8ae2c1e045da3d2a9ab2cebd0544bfea838 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 10:23:58 -0500 Subject: [PATCH 13/22] Fix formatting --- src/utils/log.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/utils/log.jl b/src/utils/log.jl index 1508156..6b5f7c5 100644 --- a/src/utils/log.jl +++ b/src/utils/log.jl @@ -55,9 +55,6 @@ end function _setup_logger(; level = CoreLogging.Info) initial_time = time() return global_logger( - TimeLogger( - initial_time = initial_time, - screen_log_level = level, - ) + TimeLogger(initial_time = initial_time, screen_log_level = level), ) end From 2367e5a348fa1e2c668a41fa5191a6efe8feec42 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 10:27:46 -0500 Subject: [PATCH 14/22] Fix formatting --- test/model/formulations_test.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl index df86f09..47fb373 100644 --- a/test/model/formulations_test.jl +++ b/test/model/formulations_test.jl @@ -24,10 +24,7 @@ end function _small_test( formulation::Formulation; instances::Array{String} = ["test/case14"], - optimizer = optimizer_with_attributes( - Cbc.Optimizer, - "logLevel" => 0, - ) + optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0), )::Nothing for instance_name in instances instance = UnitCommitment.read_benchmark(instance_name) @@ -88,6 +85,6 @@ end _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) _small_test( Formulation(ramping = WanHob2016.Ramping()), - instances=["test/case14-flex"], + instances = ["test/case14-flex"], ) end From 58a7567c16091f0a310a796c8c0d7ad961aab28a Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:14:06 -0500 Subject: [PATCH 15/22] Randomization: Explicitly use MersenneTwister; allow other RNGs --- src/transform/randomize/XavQiuAhm2021.jl | 20 ++++++++++++------- .../transform/randomize/XavQiuAhm2021_test.jl | 10 +++++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/transform/randomize/XavQiuAhm2021.jl b/src/transform/randomize/XavQiuAhm2021.jl index adedcd2..a3716c3 100644 --- a/src/transform/randomize/XavQiuAhm2021.jl +++ b/src/transform/randomize/XavQiuAhm2021.jl @@ -118,11 +118,12 @@ Base.@kwdef struct Randomization end function _randomize_costs( + rng, instance::UnitCommitmentInstance, distribution, )::Nothing for unit in instance.units - α = rand(distribution) + α = rand(rng, distribution) unit.min_power_cost *= α for k in unit.cost_segments k.cost *= α @@ -135,10 +136,11 @@ function _randomize_costs( end function _randomize_load_share( + rng, instance::UnitCommitmentInstance, distribution, )::Nothing - α = rand(distribution, length(instance.buses)) + α = rand(rng, distribution, length(instance.buses)) for t in 1:instance.time total = sum(bus.load[t] for bus in instance.buses) den = sum( @@ -153,6 +155,7 @@ function _randomize_load_share( end function _randomize_load_profile( + rng, instance::UnitCommitmentInstance, params::Randomization, )::Nothing @@ -161,12 +164,13 @@ function _randomize_load_profile( for t in 2:instance.time idx = (t - 1) % length(params.load_profile_mu) + 1 gamma = rand( + rng, Normal(params.load_profile_mu[idx], params.load_profile_sigma[idx]), ) push!(system_load, system_load[t-1] * gamma) end capacity = sum(maximum(u.max_power) for u in instance.units) - peak_load = rand(params.peak_load) * capacity + peak_load = rand(rng, params.peak_load) * capacity system_load = system_load ./ maximum(system_load) .* peak_load # Scale bus loads to match the new system load @@ -186,22 +190,24 @@ end function randomize!( instance::UnitCommitment.UnitCommitmentInstance, method::XavQiuAhm2021.Randomization, + rng = MersenneTwister(), )::Nothing Randomize costs and loads based on the method described in XavQiuAhm2021. """ function randomize!( instance::UnitCommitment.UnitCommitmentInstance, - method::XavQiuAhm2021.Randomization, + method::XavQiuAhm2021.Randomization; + rng = MersenneTwister(), )::Nothing if method.randomize_costs - XavQiuAhm2021._randomize_costs(instance, method.cost) + XavQiuAhm2021._randomize_costs(rng, instance, method.cost) end if method.randomize_load_share - XavQiuAhm2021._randomize_load_share(instance, method.load_share) + XavQiuAhm2021._randomize_load_share(rng, instance, method.load_share) end if method.randomize_load_profile - XavQiuAhm2021._randomize_load_profile(instance, method) + XavQiuAhm2021._randomize_load_profile(rng, instance, method) end return end diff --git a/test/transform/randomize/XavQiuAhm2021_test.jl b/test/transform/randomize/XavQiuAhm2021_test.jl index a3107c4..667fcf1 100644 --- a/test/transform/randomize/XavQiuAhm2021_test.jl +++ b/test/transform/randomize/XavQiuAhm2021_test.jl @@ -6,6 +6,7 @@ import Random import UnitCommitment: XavQiuAhm2021 using Distributions +using Random using UnitCommitment, Cbc, JuMP get_instance() = UnitCommitment.read_benchmark("matpower/case118/2017-02-01") @@ -27,10 +28,10 @@ test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) prev_system_load = system_load(instance) test_approx(bus.load[1] / prev_system_load[1], 0.012) - Random.seed!(42) randomize!( instance, XavQiuAhm2021.Randomization(randomize_load_profile = false), + rng = MersenneTwister(42), ) # Check randomized costs @@ -53,8 +54,11 @@ test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) @test round.(system_load(instance), digits = 1)[1:8] ≈ [3059.5, 2983.2, 2937.5, 2953.9, 3073.1, 3356.4, 4068.5, 4018.8] - Random.seed!(42) - randomize!(instance, XavQiuAhm2021.Randomization()) + randomize!( + instance, + XavQiuAhm2021.Randomization(), + rng = MersenneTwister(42), + ) # Check randomized load profile @test round.(system_load(instance), digits = 1)[1:8] ≈ From 86ae1d042941d1d7ee9417e732272f419e6d23d9 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:33:57 -0500 Subject: [PATCH 16/22] juliaw: Make it compatible with Julia 1.7 --- juliaw | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/juliaw b/juliaw index ad5826e..b78bc72 100755 --- a/juliaw +++ b/juliaw @@ -47,7 +47,14 @@ project = TOML.parsefile("Project.toml") manifest = TOML.parsefile("Manifest.toml") deps = Symbol[] for dep in keys(project["deps"]) - if "path" in keys(manifest[dep][1]) + if dep in keys(manifest) + # Up to Julia 1.6 + dep_entry = manifest[dep][1] + else + # Julia 1.7+ + dep_entry = manifest["deps"][dep][1] + end + if "path" in keys(dep_entry) println(" - \$(dep) [skip]") else println(" - \$(dep)") From b7d9083335407fb6ddbceec0626fdabf390b1491 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:34:14 -0500 Subject: [PATCH 17/22] Makefile: Update clean target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a331d19..0d2446f 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ VERSION := 0.2 clean: - rm -rfv build + rm -rfv build Manifest.toml test/Manifest.toml deps/formatter/build deps/formatter/Manifest.toml docs: cd docs; make clean; make dirhtml From 46d754dbcf892c54358a9a06311e60a0aa59edcf Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:34:25 -0500 Subject: [PATCH 18/22] GitHub Actions: Add Julia 1.7 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ee6045..1d06c70 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: ['1.4', '1.5', '1.6'] + julia-version: ['1.4', '1.5', '1.6', '1.7'] julia-arch: [x64] os: [ubuntu-latest, windows-latest, macOS-latest] exclude: From 1ce1cddaf32ee1fdf343eab19f053ab8d7b80760 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:43:09 -0500 Subject: [PATCH 19/22] Remove Gurobi from test dependencies; remove large tests --- test/Project.toml | 1 - test/model/formulations_test.jl | 34 ++------------------------------- test/runtests.jl | 2 -- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index 7d70fe1..260aa24 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -3,7 +3,6 @@ Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63" -Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl index 47fb373..4080004 100644 --- a/test/model/formulations_test.jl +++ b/test/model/formulations_test.jl @@ -17,11 +17,7 @@ import UnitCommitment: XavQiuWanThi2019, WanHob2016 -if ENABLE_LARGE_TESTS - using Gurobi -end - -function _small_test( +function _test( formulation::Formulation; instances::Array{String} = ["test/case14"], optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0), @@ -42,32 +38,6 @@ function _small_test( end end -function _large_test(formulation::Formulation)::Nothing - instances = ["pglib-uc/ca/Scenario400_reserves_1"] - for instance in instances - instance = UnitCommitment.read_benchmark(instance) - model = UnitCommitment.build_model( - instance = instance, - formulation = formulation, - optimizer = Gurobi.Optimizer, - ) - UnitCommitment.optimize!( - model, - XavQiuWanThi2019.Method(two_phase_gap = false, gap_limit = 0.1), - ) - solution = UnitCommitment.solution(model) - @test UnitCommitment.validate(instance, solution) - end - return -end - -function _test(formulation::Formulation)::Nothing - _small_test(formulation) - if ENABLE_LARGE_TESTS - _large_test(formulation) - end -end - @testset "formulations" begin _test(Formulation()) _test(Formulation(ramping = ArrCon2000.Ramping())) @@ -83,7 +53,7 @@ end _test(Formulation(pwl_costs = Gar1962.PwlCosts())) _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) - _small_test( + _test( Formulation(ramping = WanHob2016.Ramping()), instances = ["test/case14-flex"], ) diff --git a/test/runtests.jl b/test/runtests.jl index 56cade9..d00f9ac 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,8 +8,6 @@ using UnitCommitment push!(Base.LOAD_PATH, @__DIR__) UnitCommitment._setup_logger(level = Base.CoreLogging.Error) -const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV)) - @testset "UnitCommitment" begin include("usage.jl") @testset "import" begin From cce6a874b93e71e5c9ae418aa5af580067dd78a7 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:52:21 -0500 Subject: [PATCH 20/22] Bump JuMP version to 1.0 --- Project.toml | 4 ++-- test/Project.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index cd9584f..ce218c9 100644 --- a/Project.toml +++ b/Project.toml @@ -24,7 +24,7 @@ DataStructures = "0.18" Distributions = "0.25" GZip = "0.5" JSON = "0.21" -JuMP = "0.21" -MathOptInterface = "0.9" +JuMP = "1" +MathOptInterface = "1" PackageCompiler = "1" julia = "1" diff --git a/test/Project.toml b/test/Project.toml index 260aa24..b87293d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -19,7 +19,7 @@ DataStructures = "0.18" Distributions = "0.25" GZip = "0.5" JSON = "0.21" -JuMP = "0.21" -MathOptInterface = "0.9" +JuMP = "1" +MathOptInterface = "1" PackageCompiler = "1" julia = "1" From 5c3c8f0d6392426f7c17e631d730e2939764849c Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 16 Apr 2022 11:53:12 -0500 Subject: [PATCH 21/22] GitHub Actions: Remove older non-LTS Julia versions --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d06c70..a217247 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: ['1.4', '1.5', '1.6', '1.7'] + julia-version: ['1.6', '1.7'] julia-arch: [x64] os: [ubuntu-latest, windows-latest, macOS-latest] exclude: From 15de1901c8ef92e7a55009423d353b9e5f384c74 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 14 Jun 2022 14:55:59 -0500 Subject: [PATCH 22/22] Remove temporary files --- src/model/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/model/.DS_Store diff --git a/src/model/.DS_Store b/src/model/.DS_Store deleted file mode 100644 index 04abbce9608c659e5912cce00bfb1671bd6d1a6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%Sr=55Ukc50(!{NNwKv*^&Jb006=&qTb zu35GY+uHzaeSEwHRsfcCM|^mgo1eSS?4mM8r1Op=c6h)$Ua+54pHDdV8c(c0;B~?u z@OIiBhTVR6&il__psW;-0#ZNOzYldUEci>LGc7Ui&> zs3--bz*K?D+^)R;-_d`V|EDDFq<|FoR|?o-v)!!uO4VCuFXz3s(eLSA^GSE(Iw%a$ lj)~EZx$$;<6Gd6qe9iM-I3@<2`JfZ^GvK<&q`+S*@CEsA7Nr0H