diff --git a/docs/Project.toml b/docs/Project.toml index aa949e8..f9afcb4 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,3 +1,4 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" UnitCommitment = "64606440-39ea-11e9-0f29-3303a1d3d877" diff --git a/docs/src/api.md b/docs/src/api.md index 4cb85ac..c237b7b 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -16,13 +16,15 @@ UnitCommitment.write ```@docs UnitCommitment.slice -UnitCommitment.randomize! -generate_initial_conditions! +UnitCommitment.randomize!(::UnitCommitment.UnitCommitmentInstance) +UnitCommitment.generate_initial_conditions! ``` ## Formulations ```@docs +UnitCommitment.Formulation +UnitCommitment.ShiftFactorsFormulation UnitCommitment.ArrCon2000 UnitCommitment.CarArr2006 UnitCommitment.DamKucRajAta2016 @@ -36,13 +38,11 @@ UnitCommitment.WanHob2016 ## Solution Methods ```@docs -UnitCommitment.XavQiuWanThi2019 UnitCommitment.XavQiuWanThi2019.Method ``` ## Randomization Methods ```@docs -UnitCommitment.XavQiuAhm2021 UnitCommitment.XavQiuAhm2021.Randomization -``` \ No newline at end of file +``` diff --git a/src/instance/read.jl b/src/instance/read.jl index b50cdd5..1586e63 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -13,15 +13,13 @@ const INSTANCES_URL = "https://axavier.org/UnitCommitment.jl/0.3/instances" """ read_benchmark(name::AbstractString)::UnitCommitmentInstance -Read one of the benchmark unit commitment instances included in the package. -See "Instances" section of the documentation for the entire list of benchmark -instances available. +Read one of the benchmark instances included in the package. See +[Instances](instances.md) for the entire list of benchmark instances available. -Example -------- - - import UnitCommitment - instance = UnitCommitment.read_benchmark("matpower/case3375wp/2017-02-01") +# Example +```julia +instance = UnitCommitment.read_benchmark("matpower/case3375wp/2017-02-01") +``` """ function read_benchmark( name::AbstractString; @@ -48,13 +46,13 @@ end """ read(path::AbstractString)::UnitCommitmentInstance -Read a unit commitment instance from a file. The file may be gzipped. +Read instance from a file. The file may be gzipped. -Example -------- +# Example - import UnitCommitment - instance = UnitCommitment.read("/path/to/input.json.gz") +```julia +instance = UnitCommitment.read("/path/to/input.json.gz") +``` """ function read(path::AbstractString)::UnitCommitmentInstance if endswith(path, ".gz") diff --git a/src/model/build.jl b/src/model/build.jl index 87a9a66..8ee7235 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -9,22 +9,59 @@ import JuMP: value, fix, set_name function build_model(; instance::UnitCommitmentInstance, optimizer = nothing, + formulation = Formulation(), variable_names::Bool = false, )::JuMP.Model Build the JuMP model corresponding to the given unit commitment instance. Arguments -========= +--------- + - `instance`: the instance. - `optimizer`: the optimizer factory that should be attached to this model (e.g. Cbc.Optimizer). If not provided, no optimizer will be attached. +- `formulation`: + the MIP formulation to use. By default, uses a formulation that combines + modeling components from different publications that provides good + performance across a wide variety of instances. An alternative formulation + may also be provided. - `variable_names`: - If true, set variable and constraint names. Important if the model is going + if true, set variable and constraint names. Important if the model is going to be exported to an MPS file. For large models, this can take significant time, so it's disabled by default. + +Examples +-------- + +```julia +# Read benchmark instance +instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01") + +# Construct model (using state-of-the-art defaults) +model = UnitCommitment.build_model( + instance = instance, + optimizer = Cbc.Optimizer, +) + +# Construct model (using customized formulation) +model = UnitCommitment.build_model( + instance = instance, + optimizer = Cbc.Optimizer, + formulation = Formulation( + pwl_costs = KnuOstWat2018.PwlCosts(), + ramping = MorLatRam2013.Ramping(), + startup_costs = MorLatRam2013.StartupCosts(), + transmission = ShiftFactorsFormulation( + isf_cutoff = 0.005, + lodf_cutoff = 0.001, + ), + ), +) +``` + """ function build_model(; instance::UnitCommitmentInstance, diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index 2cc7e44..09b8481 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -9,6 +9,27 @@ abstract type StartupCostsFormulation end abstract type StatusVarsFormulation end abstract type ProductionVarsFormulation end +""" + struct Formulation + prod_vars::ProductionVarsFormulation + pwl_costs::PiecewiseLinearCostsFormulation + ramping::RampingFormulation + startup_costs::StartupCostsFormulation + status_vars::StatusVarsFormulation + transmission::TransmissionFormulation + end + +Struct provided to `build_model` that holds various formulation components. + +# Fields + +- `prod_vars`: Formulation for the production decision variables +- `pwl_costs`: Formulation for the piecewise linear costs +- `ramping`: Formulation for ramping constraints +- `startup_costs`: Formulation for time-dependent start-up costs +- `status_vars`: Formulation for the status variables (e.g. `is_on`, `is_off`) +- `transmission`: Formulation for transmission and N-1 security constraints +""" struct Formulation prod_vars::ProductionVarsFormulation pwl_costs::PiecewiseLinearCostsFormulation @@ -38,10 +59,10 @@ end """ struct ShiftFactorsFormulation <: TransmissionFormulation - isf_cutoff::Float64 - lodf_cutoff::Float64 - precomputed_isf::Union{Nothing,Matrix{Float64}} - precomputed_lodf::Union{Nothing,Matrix{Float64}} + isf_cutoff::Float64 = 0.005 + lodf_cutoff::Float64 = 0.001 + precomputed_isf=nothing + precomputed_lodf=nothing end Transmission formulation based on Injection Shift Factors (ISF) and Line @@ -49,15 +70,15 @@ Outage Distribution Factors (LODF). Constraints are enforced in a lazy way. Arguments --------- -- `precomputed_isf::Union{Matrix{Float64},Nothing} = nothing`: +- `precomputed_isf`: the injection shift factors matrix. If not provided, it will be computed. -- `precomputed_lodf::Union{Matrix{Float64},Nothing} = nothing`: +- `precomputed_lodf`: the line outage distribution factors matrix. If not provided, it will be computed. -- `isf_cutoff::Float64 = 0.005`: +- `isf_cutoff`: the cutoff that should be applied to the ISF matrix. Entries with magnitude smaller than this value will be set to zero. -- `lodf_cutoff::Float64 = 0.001`: +- `lodf_cutoff`: the cutoff that should be applied to the LODF matrix. Entries with magnitude smaller than this value will be set to zero. """ diff --git a/src/solution/methods/XavQiuWanThi2019/structs.jl b/src/solution/methods/XavQiuWanThi2019/structs.jl index 4125563..cacd7d3 100644 --- a/src/solution/methods/XavQiuWanThi2019/structs.jl +++ b/src/solution/methods/XavQiuWanThi2019/structs.jl @@ -2,14 +2,6 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -""" -Lazy constraint solution method described in: - - Xavier, A. S., Qiu, F., Wang, F., & Thimmapuram, P. R. (2019). Transmission - constraint filtering in large-scale security-constrained unit commitment. - IEEE Transactions on Power Systems, 34(3), 2457-2460. - DOI: https://doi.org/10.1109/TPWRS.2019.2892620 -""" module XavQiuWanThi2019 import ..SolutionMethod """ @@ -21,6 +13,13 @@ import ..SolutionMethod max_violations_per_period::Int end +Lazy constraint solution method described in: + + Xavier, A. S., Qiu, F., Wang, F., & Thimmapuram, P. R. (2019). Transmission + constraint filtering in large-scale security-constrained unit commitment. + IEEE Transactions on Power Systems, 34(3), 2457-2460. + DOI: https://doi.org/10.1109/TPWRS.2019.2892620 + Fields ------ diff --git a/src/solution/optimize.jl b/src/solution/optimize.jl index e78a62d..635e5e2 100644 --- a/src/solution/optimize.jl +++ b/src/solution/optimize.jl @@ -3,9 +3,9 @@ # Released under the modified BSD license. See COPYING.md for more details. """ - function optimize!(model::JuMP.Model)::Nothing + optimize!(model::JuMP.Model)::Nothing -Solve the given unit commitment model. Unlike JuMP.optimize!, this uses more +Solve the given unit commitment model. Unlike `JuMP.optimize!`, this uses more advanced methods to accelerate the solution process and to enforce transmission and N-1 security constraints. """ diff --git a/src/solution/solution.jl b/src/solution/solution.jl index ff2196d..0b76d3a 100644 --- a/src/solution/solution.jl +++ b/src/solution/solution.jl @@ -2,6 +2,18 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. +""" + solution(model::JuMP.Model)::OrderedDict + +Extracts the optimal solution from the UC.jl model. The model must be solved beforehand. + +# Example + +```julia +UnitCommitment.optimize!(model) +solution = UnitCommitment.solution(model) +``` +""" function solution(model::JuMP.Model)::OrderedDict instance, T = model[:instance], model[:instance].time function timeseries(vars, collection) diff --git a/src/solution/write.jl b/src/solution/write.jl index c58a2bc..270159d 100644 --- a/src/solution/write.jl +++ b/src/solution/write.jl @@ -2,6 +2,18 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. +""" + write(filename::AbstractString, solution::AbstractDict)::Nothing + +Write the given solution to a JSON file. + +# Example + +```julia +solution = UnitCommitment.solution(model) +UnitCommitment.write("/tmp/output.json", solution) +``` +""" function write(filename::AbstractString, solution::AbstractDict)::Nothing open(filename, "w") do file return JSON.print(file, solution, 2) diff --git a/src/transform/randomize/XavQiuAhm2021.jl b/src/transform/randomize/XavQiuAhm2021.jl index a3716c3..27bade1 100644 --- a/src/transform/randomize/XavQiuAhm2021.jl +++ b/src/transform/randomize/XavQiuAhm2021.jl @@ -2,13 +2,6 @@ # Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -""" -Methods described in: - - Xavier, Álinson S., Feng Qiu, and Shabbir Ahmed. "Learning to solve - large-scale security-constrained unit commitment problems." INFORMS - Journal on Computing 33.2 (2021): 739-756. DOI: 10.1287/ijoc.2020.0976 -""" module XavQiuAhm2021 using Distributions @@ -55,6 +48,13 @@ load profile, as follows: The default parameters were obtained based on an analysis of publicly available bid and hourly data from PJM, corresponding to the month of January, 2017. For more details, see Section 4.2 of the paper. + +# References + +- **Xavier, Álinson S., Feng Qiu, and Shabbir Ahmed.** *"Learning to solve + large-scale security-constrained unit commitment problems."* INFORMS Journal + on Computing 33.2 (2021): 739-756. DOI: 10.1287/ijoc.2020.0976 + """ Base.@kwdef struct Randomization cost = Uniform(0.95, 1.05) @@ -212,4 +212,31 @@ function randomize!( return end +""" + function randomize!( + instance::UnitCommitmentInstance; + method = UnitCommitment.XavQiuAhm2021.Randomization(); + rng = MersenneTwister(), + )::Nothing + +Randomizes instance parameters according to the provided randomization method. + +# Example + +```julia +instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01") +UnitCommitment.randomize!(instance) +model = UnitCommitment.build_model(; instance) +``` + +""" +function randomize!( + instance::UnitCommitment.UnitCommitmentInstance; + method = XavQiuAhm2021.Randomization(), + rng = MersenneTwister(), +)::Nothing + randomize!(instance, method; rng) + return +end + export randomize! diff --git a/src/transform/slice.jl b/src/transform/slice.jl index 336817f..0ae861f 100644 --- a/src/transform/slice.jl +++ b/src/transform/slice.jl @@ -12,10 +12,11 @@ conditions are also not modified. Example ------- - # Build a 2-hour UC instance - instance = UnitCommitment.read_benchmark("test/case14") - modified = UnitCommitment.slice(instance, 1:2) - +```julia +# Build a 2-hour UC instance +instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01") +modified = UnitCommitment.slice(instance, 1:2) +``` """ function slice( instance::UnitCommitmentInstance, diff --git a/test/transform/randomize/XavQiuAhm2021_test.jl b/test/transform/randomize/XavQiuAhm2021_test.jl index 667fcf1..6d35829 100644 --- a/test/transform/randomize/XavQiuAhm2021_test.jl +++ b/test/transform/randomize/XavQiuAhm2021_test.jl @@ -30,7 +30,9 @@ test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) randomize!( instance, - XavQiuAhm2021.Randomization(randomize_load_profile = false), + method = XavQiuAhm2021.Randomization( + randomize_load_profile = false, + ), rng = MersenneTwister(42), )