Compare commits

..

11 Commits

Author SHA1 Message Date
d751c2af88 Reformat source code 2021-08-03 14:23:47 -05:00
1397ae438e Remove references to formulation_status_vars.always_create_vars 2021-08-03 14:09:58 -05:00
7308ff6477 Revert unrelated changes 2021-08-03 14:08:09 -05:00
Aleksandr Kazachkov
8b6cbe8c1b Change order of variable arguments in startstop eqns to be alphabetical, for consistency. 2021-07-28 15:12:08 -04:00
Aleksandr Kazachkov
c2557a64d1 Added GenMorRam2017 and MorLatRam2013 startstop implementations. 2021-07-28 15:08:16 -04:00
Aleksandr Kazachkov
5afb2363af Missed function definition 2021-07-26 18:40:09 -04:00
Aleksandr Kazachkov
860c47b7e3 Shutdown cost not in this commit 2021-07-26 18:38:38 -04:00
Aleksandr Kazachkov
37b21853be Added mising formulation_status_vars 2021-07-26 18:37:06 -04:00
Aleksandr Kazachkov
c8c7350096 Added fix_vars to src/model/formulations/Gar1962/status.jl 2021-07-26 18:32:09 -04:00
Aleksandr Kazachkov
7302fabe37 Added fix vars to unit.jl 2021-07-26 18:30:24 -04:00
Aleksandr Kazachkov
4ed13d6e95 Added fix_vars_via_constraint option 2021-07-26 18:29:15 -04:00
27 changed files with 538 additions and 767 deletions

1
.gitignore vendored
View File

@@ -18,4 +18,3 @@ TODO.md
docs/_build docs/_build
.vscode .vscode
Manifest.toml Manifest.toml
*/Manifest.toml

View File

@@ -2,22 +2,31 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
JULIA := julia --color=yes --project=@.
VERSION := 0.2 VERSION := 0.2
build/sysimage.so: src/utils/sysimage.jl Project.toml Manifest.toml
mkdir -p build
mkdir -p benchmark/results/test
cd benchmark; $(JULIA) --trace-compile=../build/precompile.jl benchmark.jl test/case14
$(JULIA) src/utils/sysimage.jl
clean: clean:
rm -rfv build rm -rf build/*
docs: docs:
cd docs; make clean; make dirhtml cd docs; make clean; make dirhtml
rsync -avP --delete-after docs/_build/dirhtml/ ../docs/$(VERSION)/ rsync -avP --delete-after docs/_build/dirhtml/ ../docs/$(VERSION)/
test: build/sysimage.so
@echo Running tests...
$(JULIA) --sysimage build/sysimage.so -e 'using Pkg; Pkg.test("UnitCommitment")' | tee build/test.log
format: format:
cd deps/formatter; ../../juliaw format.jl julia -e 'using JuliaFormatter; format(["src", "test", "benchmark"], verbose=true);'
test: test/Manifest.toml install-deps:
./juliaw test/runtests.jl julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.14.4"))'
test/Manifest.toml: test/Project.toml
julia --project=test -e "using Pkg; Pkg.instantiate()"
.PHONY: docs test format install-deps .PHONY: docs test format install-deps

View File

@@ -6,7 +6,6 @@ version = "0.2.2"
[deps] [deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63" GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
@@ -16,10 +15,10 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
[compat] [compat]
Cbc = "0.7"
DataStructures = "0.18" DataStructures = "0.18"
Distributions = "0.25" Distributions = "0.25"
GZip = "0.5" GZip = "0.5"
@@ -28,3 +27,11 @@ JuMP = "0.21"
MathOptInterface = "0.9" MathOptInterface = "0.9"
PackageCompiler = "1" PackageCompiler = "1"
julia = "1" julia = "1"
[extras]
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
[targets]
test = ["Cbc", "Test", "Gurobi"]

View File

@@ -1,5 +1,4 @@
[deps] [deps]
DocOpt = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b" Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuMP = "4076af6c-e467-56ae-b986-b466b2749572"

158
benchmark/benchmark.jl Normal file
View File

@@ -0,0 +1,158 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using Distributed
using Pkg
Pkg.activate(".")
@everywhere using Pkg
@everywhere Pkg.activate(".")
@everywhere using UnitCommitment
@everywhere using JuMP
@everywhere using Gurobi
@everywhere using JSON
@everywhere using Logging
@everywhere using Printf
@everywhere using LinearAlgebra
@everywhere using Random
@everywhere import UnitCommitment:
ArrCon2000,
CarArr2006,
DamKucRajAta2016,
Formulation,
Gar1962,
KnuOstWat2018,
MorLatRam2013,
PanGua2016,
XavQiuWanThi2019
@everywhere UnitCommitment._setup_logger()
function main()
cases = [
"pglib-uc/ca/2014-09-01_reserves_0",
"pglib-uc/ca/2014-09-01_reserves_1",
"pglib-uc/ca/2015-03-01_reserves_0",
"pglib-uc/ca/2015-06-01_reserves_0",
"pglib-uc/ca/Scenario400_reserves_1",
"pglib-uc/ferc/2015-01-01_lw",
"pglib-uc/ferc/2015-05-01_lw",
"pglib-uc/ferc/2015-07-01_hw",
"pglib-uc/ferc/2015-10-01_lw",
"pglib-uc/ferc/2015-12-01_lw",
"pglib-uc/rts_gmlc/2020-04-03",
"pglib-uc/rts_gmlc/2020-09-20",
"pglib-uc/rts_gmlc/2020-10-27",
"pglib-uc/rts_gmlc/2020-11-25",
"pglib-uc/rts_gmlc/2020-12-23",
"or-lib/20_0_1_w",
"or-lib/20_0_5_w",
"or-lib/50_0_2_w",
"or-lib/75_0_2_w",
"or-lib/100_0_1_w",
"or-lib/100_0_4_w",
"or-lib/100_0_5_w",
"or-lib/200_0_3_w",
"or-lib/200_0_7_w",
"or-lib/200_0_9_w",
"tejada19/UC_24h_290g",
"tejada19/UC_24h_623g",
"tejada19/UC_24h_959g",
"tejada19/UC_24h_1577g",
"tejada19/UC_24h_1888g",
"tejada19/UC_168h_72g",
"tejada19/UC_168h_86g",
"tejada19/UC_168h_130g",
"tejada19/UC_168h_131g",
"tejada19/UC_168h_199g",
]
formulations = Dict(
"Default" => Formulation(),
"ArrCon2000" => Formulation(ramping = ArrCon2000.Ramping()),
"CarArr2006" => Formulation(pwl_costs = CarArr2006.PwlCosts()),
"DamKucRajAta2016" =>
Formulation(ramping = DamKucRajAta2016.Ramping()),
"Gar1962" => Formulation(pwl_costs = Gar1962.PwlCosts()),
"KnuOstWat2018" =>
Formulation(pwl_costs = KnuOstWat2018.PwlCosts()),
"MorLatRam2013" => Formulation(ramping = MorLatRam2013.Ramping()),
"PanGua2016" => Formulation(ramping = PanGua2016.Ramping()),
)
trials = [i for i in 1:5]
combinations = [
(c, f.first, f.second, t) for c in cases for f in formulations for
t in trials
]
shuffle!(combinations)
@sync @distributed for c in combinations
_run_combination(c...)
end
end
@everywhere function _run_combination(
case,
formulation_name,
formulation,
trial,
)
name = "$formulation_name/$case"
dirname = "results/$name"
mkpath(dirname)
if isfile("$dirname/$trial.json")
@info @sprintf("%-4s %-16s %s", "skip", formulation_name, case)
return
end
@info @sprintf("%-4s %-16s %s", "run", formulation_name, case)
open("$dirname/$trial.log", "w") do file
redirect_stdout(file) do
redirect_stderr(file) do
return _run_sample(case, formulation, "$dirname/$trial")
end
end
end
@info @sprintf("%-4s %-16s %s", "done", formulation_name, case)
end
@everywhere function _run_sample(case, formulation, prefix)
total_time = @elapsed begin
@info "Reading: $case"
time_read = @elapsed begin
instance = UnitCommitment.read_benchmark(case)
end
@info @sprintf("Read problem in %.2f seconds", time_read)
BLAS.set_num_threads(4)
model = UnitCommitment.build_model(
instance = instance,
formulation = formulation,
optimizer = optimizer_with_attributes(
Gurobi.Optimizer,
"Threads" => 4,
"Seed" => rand(1:1000),
),
variable_names = true,
)
@info "Optimizing..."
BLAS.set_num_threads(1)
UnitCommitment.optimize!(
model,
XavQiuWanThi2019.Method(time_limit = 3600.0, gap_limit = 1e-4),
)
end
@info @sprintf("Total time was %.2f seconds", total_time)
@info "Writing solution: $prefix.json"
solution = UnitCommitment.solution(model)
UnitCommitment.write("$prefix.json", solution)
@info "Verifying solution..."
return UnitCommitment.validate(instance, solution)
# @info "Exporting model..."
# return JuMP.write_to_file(model, model_filename)
end
if length(ARGS) > 0
_run_sample(ARGS[1], UnitCommitment.Formulation(), "tmp")
else
main()
end

View File

@@ -1,207 +0,0 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
doc = """UnitCommitment.jl Benchmark Runner
Usage:
run.jl [-s ARG]... [-m ARG]... [-c ARG]... [-f ARG]... [options]
Examples:
1. Benchmark all solvers, methods and formulations:
julia run.jl
2. Benchmark formulations "default" and "ArrCon200" using Gurobi:
julia run.jl -s gurobi -f default -f ArrCon2000
3. Benchmark a few test cases, using all solvers, methods and formulations:
julia run.jl -c or-lib/20_0_1_w -c matpower/case1888rte/2017-02-01
4. Solve 4 test cases in parallel, with 2 threads available per worker:
JULIA_NUM_THREADS=2 julia --procs 4 run.jl
Options:
-h --help Show this screen.
-s --solver=ARG Mixed-integer linear solver (e.g. gurobi)
-c --case=ARG Unit commitment test case (e.g. or-lib/20_0_1_w)
-m --method=ARG Solution method (e.g. default)
-f --formulation=ARG Formulation (e.g. ArrCon2000)
--time-limit=ARG Time limit in seconds [default: 3600]
--gap=ARG Relative MIP gap tolerance [default: 0.001]
--trials=ARG Number of trials [default: 5]
"""
using Distributed
using Pkg
Pkg.activate(".")
@everywhere using Pkg
@everywhere Pkg.activate(".")
using DocOpt
args = docopt(doc)
@everywhere using UnitCommitment
@everywhere UnitCommitment._setup_logger()
using UnitCommitment
using Gurobi
using Logging
using JuMP
import UnitCommitment:
ArrCon2000,
CarArr2006,
DamKucRajAta2016,
Formulation,
Gar1962,
KnuOstWat2018,
MorLatRam2013,
PanGua2016,
XavQiuWanThi2019
# Benchmark test cases
# -----------------------------------------------------------------------------
cases = [
"pglib-uc/ca/2014-09-01_reserves_0",
"pglib-uc/ca/2014-09-01_reserves_1",
"pglib-uc/ca/2015-03-01_reserves_0",
"pglib-uc/ca/2015-06-01_reserves_0",
"pglib-uc/ca/Scenario400_reserves_1",
"pglib-uc/ferc/2015-01-01_lw",
"pglib-uc/ferc/2015-05-01_lw",
"pglib-uc/ferc/2015-07-01_hw",
"pglib-uc/ferc/2015-10-01_lw",
"pglib-uc/ferc/2015-12-01_lw",
"pglib-uc/rts_gmlc/2020-04-03",
"pglib-uc/rts_gmlc/2020-09-20",
"pglib-uc/rts_gmlc/2020-10-27",
"pglib-uc/rts_gmlc/2020-11-25",
"pglib-uc/rts_gmlc/2020-12-23",
"or-lib/20_0_1_w",
"or-lib/20_0_5_w",
"or-lib/50_0_2_w",
"or-lib/75_0_2_w",
"or-lib/100_0_1_w",
"or-lib/100_0_4_w",
"or-lib/100_0_5_w",
"or-lib/200_0_3_w",
"or-lib/200_0_7_w",
"or-lib/200_0_9_w",
"tejada19/UC_24h_290g",
"tejada19/UC_24h_623g",
"tejada19/UC_24h_959g",
"tejada19/UC_24h_1577g",
"tejada19/UC_24h_1888g",
"tejada19/UC_168h_72g",
"tejada19/UC_168h_86g",
"tejada19/UC_168h_130g",
"tejada19/UC_168h_131g",
"tejada19/UC_168h_199g",
"matpower/case1888rte/2017-02-01",
"matpower/case1951rte/2017-02-01",
"matpower/case2848rte/2017-02-01",
"matpower/case3012wp/2017-02-01",
"matpower/case3375wp/2017-02-01",
"matpower/case6468rte/2017-02-01",
"matpower/case6515rte/2017-02-01",
]
# Formulations
# -----------------------------------------------------------------------------
formulations = Dict(
"default" => Formulation(),
"ArrCon2000" => Formulation(ramping = ArrCon2000.Ramping()),
"CarArr2006" => Formulation(pwl_costs = CarArr2006.PwlCosts()),
"DamKucRajAta2016" => Formulation(ramping = DamKucRajAta2016.Ramping()),
"Gar1962" => Formulation(pwl_costs = Gar1962.PwlCosts()),
"KnuOstWat2018" => Formulation(pwl_costs = KnuOstWat2018.PwlCosts()),
"MorLatRam2013" => Formulation(ramping = MorLatRam2013.Ramping()),
"PanGua2016" => Formulation(ramping = PanGua2016.Ramping()),
)
# Solution methods
# -----------------------------------------------------------------------------
methods = Dict(
"default" =>
XavQiuWanThi2019.Method(time_limit = 3600.0, gap_limit = 1e-4),
)
# MIP solvers
# -----------------------------------------------------------------------------
optimizers = Dict(
"gurobi" => optimizer_with_attributes(
Gurobi.Optimizer,
"Threads" => Threads.nthreads(),
),
)
# Parse command line arguments
# -----------------------------------------------------------------------------
if !isempty(args["--case"])
cases = args["--case"]
end
if !isempty(args["--formulation"])
formulations = filter(p -> p.first in args["--formulation"], formulations)
end
if !isempty(args["--method"])
methods = filter(p -> p.first in args["--method"], methods)
end
if !isempty(args["--solver"])
optimizers = filter(p -> p.first in args["--solver"], optimizers)
end
const time_limit = parse(Float64, args["--time-limit"])
const gap_limit = parse(Float64, args["--gap"])
const ntrials = parse(Int, args["--trials"])
# Print benchmark settings
# -----------------------------------------------------------------------------
function printlist(d::Dict)
for key in keys(d)
@info " - $key"
end
end
function printlist(d::Vector)
for key in d
@info " - $key"
end
end
@info "Computational environment:"
@info " - CPU: $(Sys.cpu_info()[1].model)"
@info " - Logical CPU cores: $(length(Sys.cpu_info()))"
@info " - System memory: $(round(Sys.total_memory() / 2^30, digits=2)) GiB"
@info " - Available workers: $(nworkers())"
@info " - Available threads per worker: $(Threads.nthreads())"
@info "Parameters:"
@info " - Number of trials: $ntrials"
@info " - Time limit (s): $time_limit"
@info " - Relative MIP gap tolerance: $gap_limit"
@info "Solvers:"
printlist(optimizers)
@info "Methods:"
printlist(methods)
@info "Formulations:"
printlist(formulations)
@info "Cases:"
printlist(cases)
# Run benchmarks
# -----------------------------------------------------------------------------
UnitCommitment._run_benchmarks(
cases = cases,
formulations = formulations,
methods = methods,
optimizers = optimizers,
trials = 1:ntrials,
)

View File

@@ -1,5 +0,0 @@
[deps]
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
[compat]
JuliaFormatter = "0.14.4"

View File

@@ -1,9 +0,0 @@
using JuliaFormatter
format(
[
"../../src",
"../../test",
"../../benchmark/run.jl",
],
verbose=true,
)

68
juliaw
View File

@@ -1,68 +0,0 @@
#!/bin/bash
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
if [ ! -e Project.toml ]; then
echo "juliaw: Project.toml not found"
exit 1
fi
if [ ! -e Manifest.toml ]; then
julia --project=. -e 'using Pkg; Pkg.instantiate()' || exit 1
fi
if [ ! -e build/sysimage.so -o Project.toml -nt build/sysimage.so ]; then
echo "juliaw: rebuilding system image..."
# Generate temporary project folder
rm -rf $HOME/.juliaw
mkdir -p $HOME/.juliaw/src
cp Project.toml Manifest.toml $HOME/.juliaw
NAME=$(julia -e 'using TOML; toml = TOML.parsefile("Project.toml"); "name" in keys(toml) && print(toml["name"])')
if [ ! -z $NAME ]; then
cat > $HOME/.juliaw/src/$NAME.jl << EOF
module $NAME
end
EOF
fi
# Add PackageCompiler dependencies to temporary project
julia --project=$HOME/.juliaw -e 'using Pkg; Pkg.add(["PackageCompiler", "TOML", "Logging"])'
# Generate system image scripts
cat > $HOME/.juliaw/sysimage.jl << EOF
using PackageCompiler
using TOML
using Logging
Logging.disable_logging(Logging.Info)
mkpath("$PWD/build")
println("juliaw: generating precompilation statements...")
run(\`julia --project="$PWD" --trace-compile="$PWD"/build/precompile.jl \$(ARGS)\`)
println("juliaw: finding dependencies...")
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])
println(" - \$(dep) [skip]")
else
println(" - \$(dep)")
push!(deps, Symbol(dep))
end
end
println("juliaw: building system image...")
create_sysimage(
deps,
precompile_statements_file = "$PWD/build/precompile.jl",
sysimage_path = "$PWD/build/sysimage.so",
)
EOF
julia --project=$HOME/.juliaw $HOME/.juliaw/sysimage.jl $*
else
julia --project=. --sysimage build/sysimage.so $*
fi

View File

@@ -48,9 +48,8 @@ include("solution/warmstart.jl")
include("solution/write.jl") include("solution/write.jl")
include("transform/initcond.jl") include("transform/initcond.jl")
include("transform/slice.jl") include("transform/slice.jl")
include("transform/randomize/XavQiuAhm2021.jl") include("transform/randomize.jl")
include("utils/log.jl") include("utils/log.jl")
include("utils/benchmark.jl")
include("validation/repair.jl") include("validation/repair.jl")
include("validation/validate.jl") include("validation/validate.jl")

View File

@@ -266,20 +266,15 @@ function _from_json(json; repair = true)
end end
instance = UnitCommitmentInstance( instance = UnitCommitmentInstance(
buses_by_name = Dict(b.name => b for b in buses), T,
buses = buses, power_balance_penalty,
contingencies_by_name = Dict(c.name => c for c in contingencies), shortfall_penalty,
contingencies = contingencies, units,
lines_by_name = Dict(l.name => l for l in lines), buses,
lines = lines, lines,
power_balance_penalty = power_balance_penalty, reserves,
price_sensitive_loads_by_name = Dict(ps.name => ps for ps in loads), contingencies,
price_sensitive_loads = loads, loads,
reserves = reserves,
shortfall_penalty = shortfall_penalty,
time = T,
units_by_name = Dict(g.name => g for g in units),
units = units,
) )
if repair if repair
UnitCommitment.repair!(instance) UnitCommitment.repair!(instance)

View File

@@ -69,21 +69,17 @@ mutable struct PriceSensitiveLoad
revenue::Vector{Float64} revenue::Vector{Float64}
end end
Base.@kwdef mutable struct UnitCommitmentInstance mutable struct UnitCommitmentInstance
buses_by_name::Dict{AbstractString,Bus}
buses::Vector{Bus}
contingencies_by_name::Dict{AbstractString,Contingency}
contingencies::Vector{Contingency}
lines_by_name::Dict{AbstractString,TransmissionLine}
lines::Vector{TransmissionLine}
power_balance_penalty::Vector{Float64}
price_sensitive_loads_by_name::Dict{AbstractString,PriceSensitiveLoad}
price_sensitive_loads::Vector{PriceSensitiveLoad}
reserves::Reserves
shortfall_penalty::Vector{Float64}
time::Int time::Int
units_by_name::Dict{AbstractString,Unit} power_balance_penalty::Vector{Float64}
"Penalty for failing to meet reserve requirement."
shortfall_penalty::Vector{Float64}
units::Vector{Unit} units::Vector{Unit}
buses::Vector{Bus}
lines::Vector{TransmissionLine}
reserves::Reserves
contingencies::Vector{Contingency}
price_sensitive_loads::Vector{PriceSensitiveLoad}
end end
function Base.show(io::IO, instance::UnitCommitmentInstance) function Base.show(io::IO, instance::UnitCommitmentInstance)

View File

@@ -0,0 +1,96 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
"""
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
Startup and shutdown limits from Gentile et al. (2017).
Eqns. (20), (23a), and (23b) in Knueven et al. (2020).
Creates constraints `eq_startstop_limit`, `eq_startup_limit`, and `eq_shutdown_limit`
using variables `Gar1962.StatusVars`, `prod_above` from `Gar1962.ProdVars`, and `reserve`.
Constraints
---
* `eq_startstop_limit`
* `eq_startup_limit`
* `eq_shutdown_limit`
"""
function _add_startup_shutdown_limit_eqs!(
model::JuMP.Model,
g::Unit,
formulation_prod_vars::Gar1962.ProdVars,
formulation_status_vars::Gar1962.StatusVars,
)::Nothing
# TODO: Move upper case constants to model[:instance]
RESERVES_WHEN_START_UP = true
RESERVES_WHEN_RAMP_UP = true
RESERVES_WHEN_RAMP_DOWN = true
RESERVES_WHEN_SHUT_DOWN = true
eq_startstop_limit = _init(model, :eq_startstop_limit)
eq_shutdown_limit = _init(model, :eq_shutdown_limit)
eq_startup_limit = _init(model, :eq_startup_limit)
is_on = model[:is_on]
prod_above = model[:prod_above]
reserve = model[:reserve]
switch_off = model[:switch_off]
switch_on = model[:switch_on]
T = model[:instance].time
gi = g.name
if g.initial_power > g.shutdown_limit
eqs.shutdown_limit[gi, 0] =
@constraint(mip, vars.switch_off[gi, 1] <= 0)
end
for t in 1:T
## 2020-10-09 amk: added eqn (20) and check of g.min_uptime
# Not present in (23) in Kneueven et al.
if g.min_uptime > 1
# Equation (20) in Knueven et al. (2020)
eqs.startstop_limit[gi, t] = @constraint(
model,
prod_above[gi, t] + reserve[gi, t] <=
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t] - (
t < T ?
max(0, g.max_power[t] - g.shutdown_limit) *
switch_off[gi, t+1] : 0.0
)
)
else
## Startup limits
# Equation (23a) in Knueven et al. (2020)
eqs.startup_limit[gi, t] = @constraint(
model,
prod_above[gi, t] + reserve[gi, t] <=
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t] - (
t < T ?
max(0, g.startup_limit - g.shutdown_limit) *
switch_off[gi, t+1] : 0.0
)
)
## Shutdown limits
if t < T
# Equation (23b) in Knueven et al. (2020)
eqs.shutdown_limit[gi, t] = @constraint(
model,
prod_above[gi, t] + reserve[gi, t] <=
(g.max_power[t] - g.min_power[t]) * xis_on[gi, t] - (
t < T ?
max(0, g.max_power[t] - g.shutdown_limit) *
switch_off[gi, t+1] : 0.0
) -
max(0, g.shutdown_limit - g.startup_limit) *
switch_on[gi, t]
)
end
end
end
end

View File

@@ -0,0 +1,89 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
"""
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
Startup and shutdown limits from Morales-España et al. (2013a).
Eqns. (20), (21a), and (21b) in Knueven et al. (2020).
Uses variable `prod_above` from `Gar1962.ProdVars`, the variables in `Gar1962.StatusVars`, and `reserve`
to generate constraints below.
Constraints
---
* :eq_startstop_limit
* :eq_startup_limit
* :eq_shutdown_limit
"""
function _add_startup_shutdown_limit_eqs!(
model::JuMP.Model,
g::Unit,
formulation_prod_vars::Gar1962.ProdVars,
formulation_status_vars::Gar1962.StatusVars,
)::Nothing
# TODO: Move upper case constants to model[:instance]
RESERVES_WHEN_START_UP = true
RESERVES_WHEN_RAMP_UP = true
RESERVES_WHEN_RAMP_DOWN = true
RESERVES_WHEN_SHUT_DOWN = true
eq_startstop_limit = _init(model, :eq_startstop_limit)
eq_shutdown_limit = _init(model, :eq_shutdown_limit)
eq_startup_limit = _init(model, :eq_startup_limit)
is_on = model[:is_on]
prod_above = model[:prod_above]
reserve = model[:reserve]
switch_off = model[:switch_off]
switch_on = model[:switch_on]
T = model[:instance].time
gi = g.name
for t in 1:T
## 2020-10-09 amk: added eqn (20) and check of g.min_uptime
if g.min_uptime > 1 && t < T
# Equation (20) in Knueven et al. (2020)
# UT > 1 required, to guarantee that vars.switch_on[gi, t] and vars.switch_off[gi, t+1] are not both = 1 at the same time
eq_startstop_limit[gi, t] = @constraint(
model,
prod_above[gi, t] + reserve[gi, t] <=
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t] -
max(0, g.max_power[t] - g.shutdown_limit) * switch_off[gi, t+1]
)
else
## Startup limits
# Equation (21a) in Knueven et al. (2020)
# Proposed by Morales-España et al. (2013a)
eqs_startup_limit[gi, t] = @constraint(
model,
prod_above[gi, t] + reserve[gi, t] <=
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t]
)
## Shutdown limits
if t < T
# Equation (21b) in Knueven et al. (2020)
# TODO different from what was in previous model, due to reserve variable
# ax: ideally should have reserve_up and reserve_down variables
# i.e., the generator should be able to increase/decrease production as specified
# (this is a heuristic for a "robust" solution,
# in case there is an outage or a surge, and flow has to be redirected)
# amk: if shutdown_limit is the max prod of generator in time period before shutting down,
# then it makes sense to count reserves, because otherwise, if reserves ≠ 0,
# then the generator will actually produce more than the limit
eqs.shutdown_limit[gi, t] = @constraint(
model,
prod_above[gi, t] +
(RESERVES_WHEN_SHUT_DOWN ? reserve[gi, t] : 0.0) <=
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
max(0, g.max_power[t] - g.shutdown_limit) *
switch_off[gi, t+1]
)
end
end
end
end

View File

@@ -35,7 +35,12 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation)
formulation.status_vars, formulation.status_vars,
) )
_add_startup_cost_eqs!(model, g, formulation.startup_costs) _add_startup_cost_eqs!(model, g, formulation.startup_costs)
_add_startup_shutdown_limit_eqs!(model, g) _add_startup_shutdown_limit_eqs!(
model,
g,
formulation.prod_vars,
formulation.status_vars,
)
_add_status_eqs!(model, g, formulation.status_vars) _add_status_eqs!(model, g, formulation.status_vars)
return return
end end
@@ -76,7 +81,22 @@ function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing
return return
end end
function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing """
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
Creates startup/shutdown limit constraints below based on variables `Gar1962.StatusVars`, `prod_above` from `Gar1962.ProdVars`, and `reserve`.
Constraints
---
* :eq_startup_limit
* :eq_shutdown_limit
"""
function _add_startup_shutdown_limit_eqs!(
model::JuMP.Model,
g::Unit,
formulation_prod_vars::Gar1962.ProdVars,
formulation_status_vars::Gar1962.StatusVars,
)::Nothing
eq_shutdown_limit = _init(model, :eq_shutdown_limit) eq_shutdown_limit = _init(model, :eq_shutdown_limit)
eq_startup_limit = _init(model, :eq_startup_limit) eq_startup_limit = _init(model, :eq_startup_limit)
is_on = model[:is_on] is_on = model[:is_on]

View File

@@ -0,0 +1,53 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using Distributions
function randomize_unit_costs!(
instance::UnitCommitmentInstance;
distribution = Uniform(0.95, 1.05),
)::Nothing
for unit in instance.units
α = rand(distribution)
unit.min_power_cost *= α
for k in unit.cost_segments
k.cost *= α
end
for s in unit.startup_categories
s.cost *= α
end
end
return
end
function randomize_load_distribution!(
instance::UnitCommitmentInstance;
distribution = Uniform(0.90, 1.10),
)::Nothing
α = rand(distribution, length(instance.buses))
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)
)
for (i, bus) in enumerate(instance.buses)
bus.load[t] *= α[i] / den
end
end
return
end
function randomize_peak_load!(
instance::UnitCommitmentInstance;
distribution = Uniform(0.925, 1.075),
)::Nothing
α = rand(distribution)
for bus in instance.buses
bus.load *= α
end
return
end
export randomize_unit_costs!, randomize_load_distribution!, randomize_peak_load!

View File

@@ -1,209 +0,0 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# 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
import ..UnitCommitmentInstance
"""
struct Randomization
cost = Uniform(0.95, 1.05)
load_profile_mu = [...]
load_profile_sigma = [...]
load_share = Uniform(0.90, 1.10)
peak_load = Uniform(0.6 * 0.925, 0.6 * 1.075)
randomize_costs = true
randomize_load_profile = true
randomize_load_share = true
end
Randomization method that changes: (1) production and startup costs, (2)
share of load coming from each bus, (3) peak system load, and (4) temporal
load profile, as follows:
1. **Production and startup costs:**
For each unit `u`, the vectors `u.min_power_cost` and `u.cost_segments`
are multiplied by a constant `α[u]` sampled from the provided `cost`
distribution. If `randomize_costs` is false, skips this step.
2. **Load share:**
For each bus `b` and time `t`, the value `b.load[t]` is multiplied by
`(β[b] * b.load[t]) / sum(β[b2] * b2.load[t] for b2 in buses)`, where
`β[b]` is sampled from the provided `load_share` distribution. If
`randomize_load_share` is false, skips this step.
3. **Peak system load and temporal load profile:**
Sets the peak load to `ρ * C`, where `ρ` is sampled from `peak_load` and `C`
is the maximum system capacity, at any time. Also scales the loads of all
buses, so that `system_load[t+1]` becomes equal to `system_load[t] * γ[t]`,
where `γ[t]` is sampled from `Normal(load_profile_mu[t], load_profile_sigma[t])`.
The system load for the first time period is set so that the peak load
matches `ρ * C`. If `load_profile_sigma` and `load_profile_mu` have fewer
elements than `instance.time`, wraps around. If `randomize_load_profile`
is false, skips this step.
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.
"""
Base.@kwdef struct Randomization
cost = Uniform(0.95, 1.05)
load_profile_mu::Vector{Float64} = [
1.0,
0.978,
0.98,
1.004,
1.02,
1.078,
1.132,
1.018,
0.999,
1.006,
0.999,
0.987,
0.975,
0.984,
0.995,
1.005,
1.045,
1.106,
0.981,
0.981,
0.978,
0.948,
0.928,
0.953,
]
load_profile_sigma::Vector{Float64} = [
0.0,
0.011,
0.015,
0.01,
0.012,
0.029,
0.055,
0.027,
0.026,
0.023,
0.013,
0.012,
0.014,
0.011,
0.008,
0.008,
0.02,
0.02,
0.016,
0.012,
0.014,
0.015,
0.017,
0.024,
]
load_share = Uniform(0.90, 1.10)
peak_load = Uniform(0.6 * 0.925, 0.6 * 1.075)
randomize_load_profile::Bool = true
randomize_costs::Bool = true
randomize_load_share::Bool = true
end
function _randomize_costs(
instance::UnitCommitmentInstance,
distribution,
)::Nothing
for unit in instance.units
α = rand(distribution)
unit.min_power_cost *= α
for k in unit.cost_segments
k.cost *= α
end
for s in unit.startup_categories
s.cost *= α
end
end
return
end
function _randomize_load_share(
instance::UnitCommitmentInstance,
distribution,
)::Nothing
α = rand(distribution, length(instance.buses))
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)
)
for (i, bus) in enumerate(instance.buses)
bus.load[t] *= α[i] / den
end
end
return
end
function _randomize_load_profile(
instance::UnitCommitmentInstance,
params::Randomization,
)::Nothing
# Generate new system load
system_load = [1.0]
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]),
)
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
system_load = system_load ./ maximum(system_load) .* peak_load
# 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
b.load[t] *= system_load[t] / prev_system_load[t]
end
end
return
end
end
"""
function randomize!(
instance::UnitCommitment.UnitCommitmentInstance,
method::XavQiuAhm2021.Randomization,
)::Nothing
Randomize costs and loads based on the method described in XavQiuAhm2021.
"""
function randomize!(
instance::UnitCommitment.UnitCommitmentInstance,
method::XavQiuAhm2021.Randomization,
)::Nothing
if method.randomize_costs
XavQiuAhm2021._randomize_costs(instance, method.cost)
end
if method.randomize_load_share
XavQiuAhm2021._randomize_load_share(instance, method.load_share)
end
if method.randomize_load_profile
XavQiuAhm2021._randomize_load_profile(instance, method)
end
return
end
export randomize!

View File

@@ -1,116 +0,0 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using Distributed
using Random
function _run_benchmark_sample(;
case::String,
method::SolutionMethod,
formulation::Formulation,
solution_filename::String,
optimizer,
)::Nothing
total_time = @elapsed begin
@info "Reading: $case"
time_read = @elapsed begin
instance = read_benchmark(case)
end
@info @sprintf("Read problem in %.2f seconds", time_read)
BLAS.set_num_threads(Threads.nthreads())
model = build_model(
instance = instance,
formulation = formulation,
optimizer = optimizer,
variable_names = true,
)
@info "Optimizing..."
BLAS.set_num_threads(1)
optimize!(model, method)
end
@info @sprintf("Total time was %.2f seconds", total_time)
@info "Writing solution: $solution_filename"
solution = UnitCommitment.solution(model)
write("$solution_filename", solution)
@info "Verifying solution..."
validate(instance, solution)
return
end
function _run_benchmark_combination(
case::String,
optimizer_name::String,
optimizer,
method_name::String,
method::SolutionMethod,
formulation_name::String,
formulation::Formulation,
trial,
)
dirname = "results/$optimizer_name/$method_name/$formulation_name/$case"
function info(msg)
@info @sprintf(
"%-8s %-16s %-16s %-16s %-8s %s",
msg,
optimizer_name,
method_name,
formulation_name,
trial,
case
)
end
mkpath(dirname)
trial_filename = @sprintf("%s/%03d.json", dirname, trial)
if isfile(trial_filename)
info("skip")
return
end
info("run")
open("$trial_filename.log", "w") do file
redirect_stdout(file) do
redirect_stderr(file) do
return _run_benchmark_sample(
case = case,
method = method,
formulation = formulation,
solution_filename = trial_filename,
optimizer = optimizer,
)
end
end
end
return info("done")
end
function _run_benchmarks(;
cases::Vector{String},
optimizers::Dict,
formulations::Dict,
methods::Dict,
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
]
shuffle!(combinations)
if nworkers() > 1
@printf("%24s", "")
end
@info @sprintf(
"%-8s %-16s %-16s %-16s %-8s %s",
"STATUS",
"SOLVER",
"METHOD",
"FORMULATION",
"TRIAL",
"CASE"
)
@sync @distributed for c in combinations
_run_benchmark_combination(c...)
end
end

28
src/utils/sysimage.jl Normal file
View File

@@ -0,0 +1,28 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using PackageCompiler
using DataStructures
using Distributions
using JSON
using JuMP
using MathOptInterface
using SparseArrays
pkg = [
:DataStructures,
:Distributions,
:JSON,
:JuMP,
:MathOptInterface,
:SparseArrays,
]
@info "Building system image..."
create_sysimage(
pkg,
precompile_statements_file = "build/precompile.jl",
sysimage_path = "build/sysimage.so",
)

View File

@@ -1,26 +0,0 @@
[deps]
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"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[compat]
DataStructures = "0.18"
Distributions = "0.25"
GZip = "0.5"
JSON = "0.21"
JuMP = "0.21"
MathOptInterface = "0.9"
PackageCompiler = "1"
julia = "1"

View File

@@ -4,12 +4,9 @@
using UnitCommitment using UnitCommitment
basedir = @__DIR__
@testset "read_egret_solution" begin @testset "read_egret_solution" begin
solution = UnitCommitment.read_egret_solution( solution =
"$basedir/../fixtures/egret_output.json.gz", UnitCommitment.read_egret_solution("fixtures/egret_output.json.gz")
)
for attr in ["Is on", "Production (MW)", "Production cost (\$)"] for attr in ["Is on", "Production (MW)", "Production cost (\$)"]
@test attr in keys(solution) @test attr in keys(solution)
@test "115_STEAM_1" in keys(solution[attr]) @test "115_STEAM_1" in keys(solution[attr])

View File

@@ -22,7 +22,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
@test instance.lines[5].normal_flow_limit == [1e8 for t in 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].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].flow_limit_penalty == [5e3 for t in 1:4]
@test instance.lines_by_name["l5"].name == "l5"
@test instance.lines[1].name == "l1" @test instance.lines[1].name == "l1"
@test instance.lines[1].source.name == "b1" @test instance.lines[1].source.name == "b1"
@@ -35,7 +34,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
@test instance.buses[9].name == "b9" @test instance.buses[9].name == "b9"
@test instance.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] @test instance.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353]
@test instance.buses_by_name["b9"].name == "b9"
unit = instance.units[1] unit = instance.units[1]
@test unit.name == "g1" @test unit.name == "g1"
@@ -64,7 +62,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
@test unit.startup_categories[1].cost == 1000.0 @test unit.startup_categories[1].cost == 1000.0
@test unit.startup_categories[2].cost == 1500.0 @test unit.startup_categories[2].cost == 1500.0
@test unit.startup_categories[3].cost == 2000.0 @test unit.startup_categories[3].cost == 2000.0
@test instance.units_by_name["g1"].name == "g1"
unit = instance.units[2] unit = instance.units[2]
@test unit.name == "g2" @test unit.name == "g2"
@@ -95,15 +92,12 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
@test instance.contingencies[1].lines == [instance.lines[1]] @test instance.contingencies[1].lines == [instance.lines[1]]
@test instance.contingencies[1].units == [] @test instance.contingencies[1].units == []
@test instance.contingencies[1].name == "c1"
@test instance.contingencies_by_name["c1"].name == "c1"
load = instance.price_sensitive_loads[1] load = instance.price_sensitive_loads[1]
@test load.name == "ps1" @test load.name == "ps1"
@test load.bus.name == "b3" @test load.bus.name == "b3"
@test load.revenue == [100.0 for t in 1:4] @test load.revenue == [100.0 for t in 1:4]
@test load.demand == [50.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 end
@testset "read_benchmark sub-hourly" begin @testset "read_benchmark sub-hourly" begin

View File

@@ -5,7 +5,6 @@
using Test using Test
using UnitCommitment using UnitCommitment
push!(Base.LOAD_PATH, @__DIR__)
UnitCommitment._setup_logger() UnitCommitment._setup_logger()
const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV)) const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV))
@@ -29,9 +28,7 @@ const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV))
@testset "transform" begin @testset "transform" begin
include("transform/initcond_test.jl") include("transform/initcond_test.jl")
include("transform/slice_test.jl") include("transform/slice_test.jl")
@testset "randomize" begin include("transform/randomize_test.jl")
include("transform/randomize/XavQiuAhm2021_test.jl")
end
end end
@testset "validation" begin @testset "validation" begin
include("validation/repair_test.jl") include("validation/repair_test.jl")

View File

@@ -4,12 +4,9 @@
using UnitCommitment, Cbc, JuMP using UnitCommitment, Cbc, JuMP
basedir = @__DIR__
@testset "generate_initial_conditions!" begin @testset "generate_initial_conditions!" begin
# Load instance # Load instance
instance = instance = UnitCommitment.read("$(pwd())/fixtures/case118-initcond.json.gz")
UnitCommitment.read("$basedir/../fixtures/case118-initcond.json.gz")
optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
# All units should have unknown initial conditions # All units should have unknown initial conditions

View File

@@ -1,63 +0,0 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
import Random
import UnitCommitment: XavQiuAhm2021
using Distributions
using UnitCommitment, Cbc, JuMP
get_instance() = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
system_load(instance) = sum(b.load for b in instance.buses)
test_approx(x, y) = @test isapprox(x, y, atol = 1e-3)
@testset "XavQiuAhm2021" begin
@testset "cost and load share" begin
instance = get_instance()
# Check original costs
unit = instance.units[10]
test_approx(unit.min_power_cost[1], 825.023)
test_approx(unit.cost_segments[1].cost[1], 36.659)
test_approx(unit.startup_categories[1].cost[1], 7570.42)
# Check original load share
bus = instance.buses[1]
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),
)
# Check randomized costs
test_approx(unit.min_power_cost[1], 831.977)
test_approx(unit.cost_segments[1].cost[1], 36.968)
test_approx(unit.startup_categories[1].cost[1], 7634.226)
# Check randomized load share
curr_system_load = system_load(instance)
test_approx(bus.load[1] / curr_system_load[1], 0.013)
# System load should not change
@test prev_system_load curr_system_load
end
@testset "load profile" begin
instance = get_instance()
# Check original load profile
@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())
# Check randomized load profile
@test round.(system_load(instance), digits = 1)[1:8]
[4854.7, 4849.2, 4732.7, 4848.2, 4948.4, 5231.1, 5874.8, 5934.8]
end
end

View File

@@ -0,0 +1,43 @@
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using UnitCommitment, Cbc, JuMP
_get_instance() = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
_total_load(instance) = sum(b.load[1] for b in instance.buses)
@testset "randomize_unit_costs!" begin
instance = _get_instance()
unit = instance.units[10]
prev_min_power_cost = unit.min_power_cost
prev_prod_cost = unit.cost_segments[1].cost
prev_startup_cost = unit.startup_categories[1].cost
randomize_unit_costs!(instance)
@test prev_min_power_cost != unit.min_power_cost
@test prev_prod_cost != unit.cost_segments[1].cost
@test prev_startup_cost != unit.startup_categories[1].cost
end
@testset "randomize_load_distribution!" begin
instance = _get_instance()
bus = instance.buses[1]
prev_load = instance.buses[1].load[1]
prev_total_load = _total_load(instance)
randomize_load_distribution!(instance)
curr_total_load = _total_load(instance)
@test prev_load != instance.buses[1].load[1]
@test abs(prev_total_load - curr_total_load) < 1e-3
end
@testset "randomize_peak_load!" begin
instance = _get_instance()
bus = instance.buses[1]
prev_total_load = _total_load(instance)
prev_share = bus.load[1] / prev_total_load
randomize_peak_load!(instance)
curr_total_load = _total_load(instance)
curr_share = bus.load[1] / prev_total_load
@test curr_total_load != prev_total_load
@test abs(curr_share - prev_share) < 1e-3
end

View File

@@ -4,11 +4,9 @@
using UnitCommitment, JSON, GZip, DataStructures using UnitCommitment, JSON, GZip, DataStructures
basedir = @__DIR__
function parse_case14() function parse_case14()
return JSON.parse( return JSON.parse(
GZip.gzopen("$basedir/../../instances/test/case14.json.gz"), GZip.gzopen("../instances/test/case14.json.gz"),
dicttype = () -> DefaultOrderedDict(nothing), dicttype = () -> DefaultOrderedDict(nothing),
) )
end end