mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 00:08:52 -06:00
Compare commits
7 Commits
v0.2.2
...
feature/ju
| Author | SHA1 | Date | |
|---|---|---|---|
| 93d3e5987d | |||
| f235333551 | |||
| 6c566e0e79 | |||
| 5c3f7b15d3 | |||
| 7c907a6eb5 | |||
| b1498c50b3 | |||
|
|
000215e991 |
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -9,8 +9,8 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
julia-version: ['1.3', '1.4', '1.5', '1.6']
|
||||
julia-arch: [x64, x86]
|
||||
julia-version: ['1.4', '1.5', '1.6']
|
||||
julia-arch: [x64]
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
exclude:
|
||||
- os: macOS-latest
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ TODO.md
|
||||
docs/_build
|
||||
.vscode
|
||||
Manifest.toml
|
||||
*/Manifest.toml
|
||||
|
||||
23
Makefile
23
Makefile
@@ -2,31 +2,22 @@
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
JULIA := julia --color=yes --project=@.
|
||||
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:
|
||||
rm -rf build/*
|
||||
rm -rfv build
|
||||
|
||||
docs:
|
||||
cd docs; make clean; make dirhtml
|
||||
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:
|
||||
julia -e 'using JuliaFormatter; format(["src", "test", "benchmark"], verbose=true);'
|
||||
cd deps/formatter; ../../juliaw format.jl
|
||||
|
||||
install-deps:
|
||||
julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.14.4"))'
|
||||
test: test/Manifest.toml
|
||||
./juliaw test/runtests.jl
|
||||
|
||||
test/Manifest.toml: test/Project.toml
|
||||
julia --project=test -e "using Pkg; Pkg.instantiate()"
|
||||
|
||||
.PHONY: docs test format install-deps
|
||||
|
||||
11
Project.toml
11
Project.toml
@@ -6,6 +6,7 @@ version = "0.2.2"
|
||||
|
||||
[deps]
|
||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
||||
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
|
||||
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
|
||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||
@@ -15,10 +16,10 @@ 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"
|
||||
|
||||
[compat]
|
||||
Cbc = "0.7"
|
||||
DataStructures = "0.18"
|
||||
Distributions = "0.25"
|
||||
GZip = "0.5"
|
||||
@@ -27,11 +28,3 @@ JuMP = "0.21"
|
||||
MathOptInterface = "0.9"
|
||||
PackageCompiler = "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"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[deps]
|
||||
DocOpt = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
|
||||
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
|
||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||
|
||||
@@ -1,158 +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 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
|
||||
207
benchmark/run.jl
Normal file
207
benchmark/run.jl
Normal file
@@ -0,0 +1,207 @@
|
||||
# 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,
|
||||
)
|
||||
5
deps/formatter/Project.toml
vendored
Normal file
5
deps/formatter/Project.toml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[deps]
|
||||
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
|
||||
|
||||
[compat]
|
||||
JuliaFormatter = "0.14.4"
|
||||
9
deps/formatter/format.jl
vendored
Normal file
9
deps/formatter/format.jl
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
using JuliaFormatter
|
||||
format(
|
||||
[
|
||||
"../../src",
|
||||
"../../test",
|
||||
"../../benchmark/run.jl",
|
||||
],
|
||||
verbose=true,
|
||||
)
|
||||
@@ -28,13 +28,14 @@ Each section is described in detail below. For a complete example, see [case14](
|
||||
|
||||
### Parameters
|
||||
|
||||
This section describes system-wide parameters, such as power balance penalties, optimization parameters, such as the length of the planning horizon and the time.
|
||||
This section describes system-wide parameters, such as power balance and reserve shortfall penalties, and optimization parameters, such as the length of the planning horizon and the time.
|
||||
|
||||
| Key | Description | Default | Time series?
|
||||
| :----------------------------- | :------------------------------------------------ | :------: | :------------:
|
||||
| `Time horizon (h)` | Length of the planning horizon (in hours). | Required | N
|
||||
| `Time horizon (h)` | Length of the planning horizon (in hours). | Required | N
|
||||
| `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
|
||||
|
||||
|
||||
#### Example
|
||||
@@ -42,7 +43,8 @@ This section describes system-wide parameters, such as power balance penalties,
|
||||
{
|
||||
"Parameters": {
|
||||
"Time horizon (h)": 4,
|
||||
"Power balance penalty ($/MW)": 1000.0
|
||||
"Power balance penalty ($/MW)": 1000.0,
|
||||
"Reserve shortfall penalty ($/MW)": -1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Binary file not shown.
68
juliaw
Normal file
68
juliaw
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/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
|
||||
@@ -48,8 +48,9 @@ include("solution/warmstart.jl")
|
||||
include("solution/write.jl")
|
||||
include("transform/initcond.jl")
|
||||
include("transform/slice.jl")
|
||||
include("transform/randomize.jl")
|
||||
include("transform/randomize/XavQiuAhm2021.jl")
|
||||
include("utils/log.jl")
|
||||
include("utils/benchmark.jl")
|
||||
include("validation/repair.jl")
|
||||
include("validation/validate.jl")
|
||||
|
||||
|
||||
@@ -98,6 +98,10 @@ function _from_json(json; repair = true)
|
||||
json["Parameters"]["Power balance penalty (\$/MW)"],
|
||||
default = [1000.0 for t in 1:T],
|
||||
)
|
||||
shortfall_penalty = timeseries(
|
||||
json["Parameters"]["Reserve shortfall penalty (\$/MW)"],
|
||||
default = [-1.0 for t in 1:T],
|
||||
)
|
||||
|
||||
# Read buses
|
||||
for (bus_name, dict) in json["Buses"]
|
||||
@@ -262,14 +266,20 @@ function _from_json(json; repair = true)
|
||||
end
|
||||
|
||||
instance = UnitCommitmentInstance(
|
||||
T,
|
||||
power_balance_penalty,
|
||||
units,
|
||||
buses,
|
||||
lines,
|
||||
reserves,
|
||||
contingencies,
|
||||
loads,
|
||||
buses_by_name = Dict(b.name => b for b in buses),
|
||||
buses = buses,
|
||||
contingencies_by_name = Dict(c.name => c for c in contingencies),
|
||||
contingencies = contingencies,
|
||||
lines_by_name = Dict(l.name => l for l in lines),
|
||||
lines = lines,
|
||||
power_balance_penalty = power_balance_penalty,
|
||||
price_sensitive_loads_by_name = Dict(ps.name => ps for ps in loads),
|
||||
price_sensitive_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
|
||||
UnitCommitment.repair!(instance)
|
||||
|
||||
@@ -69,15 +69,21 @@ mutable struct PriceSensitiveLoad
|
||||
revenue::Vector{Float64}
|
||||
end
|
||||
|
||||
mutable struct UnitCommitmentInstance
|
||||
time::Int
|
||||
power_balance_penalty::Vector{Float64}
|
||||
units::Vector{Unit}
|
||||
Base.@kwdef mutable struct UnitCommitmentInstance
|
||||
buses_by_name::Dict{AbstractString,Bus}
|
||||
buses::Vector{Bus}
|
||||
lines::Vector{TransmissionLine}
|
||||
reserves::Reserves
|
||||
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
|
||||
units_by_name::Dict{AbstractString,Unit}
|
||||
units::Vector{Unit}
|
||||
end
|
||||
|
||||
function Base.show(io::IO, instance::UnitCommitmentInstance)
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
|
||||
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
|
||||
net_injection = _init(model, :expr_net_injection)
|
||||
reserve = _init(model, :expr_reserve)
|
||||
curtail = _init(model, :curtail)
|
||||
for t in 1:model[:instance].time
|
||||
# Fixed load
|
||||
net_injection[b.name, t] = AffExpr(-b.load[t])
|
||||
|
||||
# Reserves
|
||||
reserve[b.name, t] = AffExpr()
|
||||
|
||||
# Load curtailment
|
||||
curtail[b.name, t] =
|
||||
@variable(model, lower_bound = 0, upper_bound = b.load[t])
|
||||
|
||||
@@ -29,13 +29,28 @@ end
|
||||
|
||||
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
||||
eq_min_reserve = _init(model, :eq_min_reserve)
|
||||
for t in 1:model[:instance].time
|
||||
instance = model[:instance]
|
||||
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
|
||||
# from Carrión and Arroyo (2006) and Ostrowski et al. (2012)
|
||||
shortfall_penalty = instance.shortfall_penalty[t]
|
||||
eq_min_reserve[t] = @constraint(
|
||||
model,
|
||||
sum(
|
||||
model[:expr_reserve][b.name, t] for b in model[:instance].buses
|
||||
) >= model[:instance].reserves.spinning[t]
|
||||
sum(model[:reserve][g.name, t] for g in instance.units) +
|
||||
(shortfall_penalty >= 0 ? model[:reserve_shortfall][t] : 0.0) >=
|
||||
instance.reserves.spinning[t]
|
||||
)
|
||||
|
||||
# Account for shortfall contribution to objective
|
||||
if shortfall_penalty >= 0
|
||||
add_to_expression!(
|
||||
model[:obj],
|
||||
shortfall_penalty,
|
||||
model[:reserve_shortfall][t],
|
||||
)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -44,12 +44,16 @@ _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
|
||||
if g.provides_spinning_reserves[t]
|
||||
reserve[g.name, t] = @variable(model, lower_bound = 0)
|
||||
else
|
||||
reserve[g.name, t] = 0.0
|
||||
end
|
||||
reserve_shortfall[t] =
|
||||
(model[:instance].shortfall_penalty[t] >= 0) ?
|
||||
@variable(model, lower_bound = 0) : 0.0
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -210,11 +214,5 @@ function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
model[:is_on][g.name, t],
|
||||
g.min_power[t],
|
||||
)
|
||||
# Add to reserves expression
|
||||
add_to_expression!(
|
||||
model[:expr_reserve][g.bus.name, t],
|
||||
model[:reserve][g.name, t],
|
||||
1.0,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,6 +51,12 @@ function solution(model::JuMP.Model)::OrderedDict
|
||||
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
|
||||
)
|
||||
sol["Net injection (MW)"] =
|
||||
timeseries(model[:net_injection], instance.buses)
|
||||
sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses)
|
||||
|
||||
@@ -1,53 +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.
|
||||
|
||||
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!
|
||||
209
src/transform/randomize/XavQiuAhm2021.jl
Normal file
209
src/transform/randomize/XavQiuAhm2021.jl
Normal file
@@ -0,0 +1,209 @@
|
||||
# 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!
|
||||
116
src/utils/benchmark.jl
Normal file
116
src/utils/benchmark.jl
Normal file
@@ -0,0 +1,116 @@
|
||||
# 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
|
||||
@@ -1,28 +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 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",
|
||||
)
|
||||
@@ -324,11 +324,16 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01)
|
||||
# Verify spinning reserves
|
||||
reserve =
|
||||
sum(solution["Reserve (MW)"][g.name][t] for g in instance.units)
|
||||
if reserve < instance.reserves.spinning[t] - tol
|
||||
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 should be %.2f)",
|
||||
"Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)",
|
||||
t,
|
||||
reserve,
|
||||
reserve_shortfall,
|
||||
instance.reserves.spinning[t],
|
||||
)
|
||||
err_count += 1
|
||||
|
||||
26
test/Project.toml
Normal file
26
test/Project.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[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"
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
using UnitCommitment
|
||||
|
||||
basedir = @__DIR__
|
||||
|
||||
@testset "read_egret_solution" begin
|
||||
solution =
|
||||
UnitCommitment.read_egret_solution("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])
|
||||
|
||||
@@ -22,6 +22,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
|
||||
@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"
|
||||
@test instance.lines[1].source.name == "b1"
|
||||
@@ -34,6 +35,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
|
||||
|
||||
@test instance.buses[9].name == "b9"
|
||||
@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]
|
||||
@test unit.name == "g1"
|
||||
@@ -62,6 +64,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
|
||||
@test unit.startup_categories[1].cost == 1000.0
|
||||
@test unit.startup_categories[2].cost == 1500.0
|
||||
@test unit.startup_categories[3].cost == 2000.0
|
||||
@test instance.units_by_name["g1"].name == "g1"
|
||||
|
||||
unit = instance.units[2]
|
||||
@test unit.name == "g2"
|
||||
@@ -92,12 +95,15 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
|
||||
|
||||
@test instance.contingencies[1].lines == [instance.lines[1]]
|
||||
@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]
|
||||
@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 instance.price_sensitive_loads_by_name["ps1"].name == "ps1"
|
||||
end
|
||||
|
||||
@testset "read_benchmark sub-hourly" begin
|
||||
|
||||
@@ -20,8 +20,14 @@ if ENABLE_LARGE_TESTS
|
||||
end
|
||||
|
||||
function _small_test(formulation::Formulation)::Nothing
|
||||
instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
|
||||
UnitCommitment.build_model(instance = instance, formulation = formulation) # should not crash
|
||||
instances = ["matpower/case118/2017-02-01", "test/case14"]
|
||||
for instance in instances
|
||||
# Should not crash
|
||||
UnitCommitment.build_model(
|
||||
instance = UnitCommitment.read_benchmark(instance),
|
||||
formulation = formulation,
|
||||
)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using Test
|
||||
using UnitCommitment
|
||||
|
||||
push!(Base.LOAD_PATH, @__DIR__)
|
||||
UnitCommitment._setup_logger()
|
||||
|
||||
const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV))
|
||||
@@ -28,7 +29,9 @@ const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV))
|
||||
@testset "transform" begin
|
||||
include("transform/initcond_test.jl")
|
||||
include("transform/slice_test.jl")
|
||||
include("transform/randomize_test.jl")
|
||||
@testset "randomize" begin
|
||||
include("transform/randomize/XavQiuAhm2021_test.jl")
|
||||
end
|
||||
end
|
||||
@testset "validation" begin
|
||||
include("validation/repair_test.jl")
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
using UnitCommitment, Cbc, JuMP
|
||||
|
||||
basedir = @__DIR__
|
||||
|
||||
@testset "generate_initial_conditions!" begin
|
||||
# Load instance
|
||||
instance = UnitCommitment.read("$(pwd())/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
|
||||
|
||||
63
test/transform/randomize/XavQiuAhm2021_test.jl
Normal file
63
test/transform/randomize/XavQiuAhm2021_test.jl
Normal file
@@ -0,0 +1,63 @@
|
||||
# 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
|
||||
@@ -1,43 +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 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
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
using UnitCommitment, JSON, GZip, DataStructures
|
||||
|
||||
basedir = @__DIR__
|
||||
|
||||
function parse_case14()
|
||||
return JSON.parse(
|
||||
GZip.gzopen("../instances/test/case14.json.gz"),
|
||||
GZip.gzopen("$basedir/../../instances/test/case14.json.gz"),
|
||||
dicttype = () -> DefaultOrderedDict(nothing),
|
||||
)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user