diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a217247..094770a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Tests +name: Build & Test on: push: pull_request: @@ -6,19 +6,30 @@ on: - cron: '45 10 * * *' jobs: test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: ['1.6', '1.7'] - julia-arch: [x64] - os: [ubuntu-latest, windows-latest, macOS-latest] - exclude: - - os: macOS-latest - julia-arch: x86 + version: ['1.6', '1.7', '1.8', '1.9'] + os: + - ubuntu-latest + arch: + - x64 steps: - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@latest + - uses: julia-actions/setup-julia@v1 with: - version: ${{ matrix.julia-version }} - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-runtest@latest + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - name: Run tests + shell: julia --color=yes --project=test {0} + run: | + using Pkg + Pkg.develop(path=".") + Pkg.update() + using UnitCommitmentT + try + runtests() + catch + exit(1) + end \ No newline at end of file diff --git a/Makefile b/Makefile index 41f1275..d689bad 100644 --- a/Makefile +++ b/Makefile @@ -4,20 +4,8 @@ VERSION := 0.3 -clean: - rm -rfv build Manifest.toml test/Manifest.toml deps/formatter/build deps/formatter/Manifest.toml - docs: cd docs; julia --project=. make.jl; cd .. rsync -avP --delete-after docs/build/ ../docs/$(VERSION)/ -format: - cd deps/formatter; ../../juliaw format.jl - -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 +.PHONY: docs diff --git a/deps/formatter/Project.toml b/deps/formatter/Project.toml deleted file mode 100644 index 4bc5f25..0000000 --- a/deps/formatter/Project.toml +++ /dev/null @@ -1,5 +0,0 @@ -[deps] -JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" - -[compat] -JuliaFormatter = "0.14.4" diff --git a/deps/formatter/format.jl b/deps/formatter/format.jl deleted file mode 100644 index 59ebd54..0000000 --- a/deps/formatter/format.jl +++ /dev/null @@ -1,9 +0,0 @@ -using JuliaFormatter -format( - [ - "../../src", - "../../test", - "../../benchmark/run.jl", - ], - verbose=true, -) diff --git a/juliaw b/juliaw deleted file mode 100755 index b78bc72..0000000 --- a/juliaw +++ /dev/null @@ -1,75 +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 dep in keys(manifest) - # Up to Julia 1.6 - dep_entry = manifest[dep][1] - else - # Julia 1.7+ - dep_entry = manifest["deps"][dep][1] - end - if "path" in keys(dep_entry) - println(" - \$(dep) [skip]") - else - println(" - \$(dep)") - 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 diff --git a/test/Project.toml b/test/Project.toml index 5a175be..7b68ed9 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,3 +1,8 @@ +name = "UnitCommitmentT" +uuid = "a3b7a17a-ab64-45e4-a924-cd5ae7dc644e" +authors = ["Alinson S. Xavier "] +version = "0.1.0" + [deps] Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" @@ -6,21 +11,10 @@ GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" 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" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[compat] -DataStructures = "0.18" -Distributions = "0.25" -GZip = "0.5" -JSON = "0.21" -JuMP = "1" -MathOptInterface = "1" -PackageCompiler = "1" -julia = "1" +UnitCommitment = "64606440-39ea-11e9-0f29-3303a1d3d877" diff --git a/test/import/egret_test.jl b/test/import/egret_test.jl deleted file mode 100644 index a0a61bf..0000000 --- a/test/import/egret_test.jl +++ /dev/null @@ -1,21 +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 - -@testset "read_egret_solution" begin - solution = - UnitCommitment.read_egret_solution("$FIXTURES/egret_output.json.gz") - for attr in - ["Is on", "Thermal production (MW)", "Thermal production cost (\$)"] - @test attr in keys(solution) - @test "115_STEAM_1" in keys(solution[attr]) - @test length(solution[attr]["115_STEAM_1"]) == 48 - end - @test solution["Thermal production cost (\$)"]["315_CT_6"][15:20] == - [0.0, 0.0, 884.44, 1470.71, 1470.71, 884.44] - @test solution["Startup cost (\$)"]["315_CT_6"][15:20] == - [0.0, 0.0, 5665.23, 0.0, 0.0, 0.0] - @test length(keys(solution["Is on"])) == 154 -end diff --git a/test/instance/migrate_test.jl b/test/instance/migrate_test.jl deleted file mode 100644 index 1176a01..0000000 --- a/test/instance/migrate_test.jl +++ /dev/null @@ -1,22 +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, LinearAlgebra, Cbc, JuMP, JSON, GZip - -@testset "read v0.2" begin - instance = UnitCommitment.read("$FIXTURES/ucjl-0.2.json.gz") - @test length(instance.scenarios) == 1 - sc = instance.scenarios[1] - @test length(sc.reserves_by_name["r1"].amount) == 4 - @test sc.thermal_units_by_name["g2"].reserves[1].name == "r1" -end - -@testset "read v0.3" begin - instance = UnitCommitment.read("$FIXTURES/ucjl-0.3.json.gz") - @test length(instance.scenarios) == 1 - sc = instance.scenarios[1] - @test length(sc.thermal_units) == 6 - @test length(sc.buses) == 14 - @test length(sc.lines) == 20 -end diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl deleted file mode 100644 index 1ad347b..0000000 --- a/test/instance/read_test.jl +++ /dev/null @@ -1,166 +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, LinearAlgebra, Cbc, JuMP, JSON, GZip - -@testset "read_benchmark" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - - @test repr(instance) == ( - "UnitCommitmentInstance(1 scenarios, 6 thermal units, 0 profiled units, 14 buses, " * - "20 lines, 19 contingencies, 1 price sensitive loads, 4 time steps)" - ) - - @test length(instance.scenarios) == 1 - sc = instance.scenarios[1] - @test length(sc.lines) == 20 - @test length(sc.buses) == 14 - @test length(sc.thermal_units) == 6 - @test length(sc.contingencies) == 19 - @test length(sc.price_sensitive_loads) == 1 - @test instance.time == 4 - - @test sc.lines[5].name == "l5" - @test sc.lines[5].source.name == "b2" - @test sc.lines[5].target.name == "b5" - @test sc.lines[5].reactance ≈ 0.17388 - @test sc.lines[5].susceptance ≈ 10.037550333 - @test sc.lines[5].normal_flow_limit == [1e8 for t in 1:4] - @test sc.lines[5].emergency_flow_limit == [1e8 for t in 1:4] - @test sc.lines[5].flow_limit_penalty == [5e3 for t in 1:4] - @test sc.lines_by_name["l5"].name == "l5" - - @test sc.lines[1].name == "l1" - @test sc.lines[1].source.name == "b1" - @test sc.lines[1].target.name == "b2" - @test sc.lines[1].reactance ≈ 0.059170 - @test sc.lines[1].susceptance ≈ 29.496860773945 - @test sc.lines[1].normal_flow_limit == [300.0 for t in 1:4] - @test sc.lines[1].emergency_flow_limit == [400.0 for t in 1:4] - @test sc.lines[1].flow_limit_penalty == [1e3 for t in 1:4] - - @test sc.buses[9].name == "b9" - @test sc.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] - @test sc.buses_by_name["b9"].name == "b9" - - @test sc.reserves[1].name == "r1" - @test sc.reserves[1].type == "spinning" - @test sc.reserves[1].amount == [100.0, 100.0, 100.0, 100.0] - @test sc.reserves_by_name["r1"].name == "r1" - - unit = sc.thermal_units[1] - @test unit.name == "g1" - @test unit.bus.name == "b1" - @test unit.ramp_up_limit == 1e6 - @test unit.ramp_down_limit == 1e6 - @test unit.startup_limit == 1e6 - @test unit.shutdown_limit == 1e6 - @test unit.must_run == [false for t in 1:4] - @test unit.min_power_cost == [1400.0 for t in 1:4] - @test unit.min_uptime == 1 - @test unit.min_downtime == 1 - for t in 1:1 - @test unit.cost_segments[1].mw[t] == 10.0 - @test unit.cost_segments[2].mw[t] == 20.0 - @test unit.cost_segments[3].mw[t] == 5.0 - @test unit.cost_segments[1].cost[t] ≈ 20.0 - @test unit.cost_segments[2].cost[t] ≈ 30.0 - @test unit.cost_segments[3].cost[t] ≈ 40.0 - end - @test length(unit.startup_categories) == 3 - @test unit.startup_categories[1].delay == 1 - @test unit.startup_categories[2].delay == 2 - @test unit.startup_categories[3].delay == 3 - @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 length(unit.reserves) == 0 - @test sc.thermal_units_by_name["g1"].name == "g1" - - unit = sc.thermal_units[2] - @test unit.name == "g2" - @test unit.must_run == [false for t in 1:4] - @test length(unit.reserves) == 1 - - unit = sc.thermal_units[3] - @test unit.name == "g3" - @test unit.bus.name == "b3" - @test unit.ramp_up_limit == 70.0 - @test unit.ramp_down_limit == 70.0 - @test unit.startup_limit == 70.0 - @test unit.shutdown_limit == 70.0 - @test unit.must_run == [true for t in 1:4] - @test unit.min_power_cost == [0.0 for t in 1:4] - @test unit.min_uptime == 1 - @test unit.min_downtime == 1 - for t in 1:4 - @test unit.cost_segments[1].mw[t] ≈ 33 - @test unit.cost_segments[2].mw[t] ≈ 33 - @test unit.cost_segments[3].mw[t] ≈ 34 - @test unit.cost_segments[1].cost[t] ≈ 33.75 - @test unit.cost_segments[2].cost[t] ≈ 38.04 - @test unit.cost_segments[3].cost[t] ≈ 44.77853 - end - @test length(unit.reserves) == 1 - @test unit.reserves[1].name == "r1" - - @test sc.contingencies[1].lines == [sc.lines[1]] - @test sc.contingencies[1].thermal_units == [] - @test sc.contingencies[1].name == "c1" - @test sc.contingencies_by_name["c1"].name == "c1" - - load = sc.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 sc.price_sensitive_loads_by_name["ps1"].name == "ps1" -end - -@testset "read_benchmark sub-hourly" begin - instance = UnitCommitment.read("$FIXTURES/case14-sub-hourly.json.gz") - @test instance.time == 4 - unit = instance.scenarios[1].thermal_units[1] - @test unit.name == "g1" - @test unit.min_uptime == 2 - @test unit.min_downtime == 2 - @test length(unit.startup_categories) == 3 - @test unit.startup_categories[1].delay == 2 - @test unit.startup_categories[2].delay == 4 - @test unit.startup_categories[3].delay == 6 - @test unit.initial_status == -200 -end - -@testset "read_benchmark profiled-units" begin - instance = UnitCommitment.read("$FIXTURES/case14-profiled.json.gz") - sc = instance.scenarios[1] - @test length(sc.profiled_units) == 2 - - first_pu = sc.profiled_units[1] - @test first_pu.name == "g7" - @test first_pu.bus.name == "b4" - @test first_pu.cost == [100.0 for t in 1:4] - @test first_pu.min_power == [60.0 for t in 1:4] - @test first_pu.max_power == [100.0 for t in 1:4] - @test sc.profiled_units_by_name["g7"].name == "g7" - - second_pu = sc.profiled_units[2] - @test second_pu.name == "g8" - @test second_pu.bus.name == "b5" - @test second_pu.cost == [50.0 for t in 1:4] - @test second_pu.min_power == [0.0 for t in 1:4] - @test second_pu.max_power == [120.0 for t in 1:4] - @test sc.profiled_units_by_name["g8"].name == "g8" -end - -@testset "read_benchmark commitmemt-status" begin - instance = UnitCommitment.read("$FIXTURES/case14-fixed-status.json.gz") - sc = instance.scenarios[1] - - @test sc.thermal_units[1].commitment_status == [nothing for t in 1:4] - @test sc.thermal_units[2].commitment_status == [true for t in 1:4] - @test sc.thermal_units[4].commitment_status == [false for t in 1:4] - @test sc.thermal_units[6].commitment_status == - [false, nothing, true, nothing] -end diff --git a/test/lmp/aelmp_test.jl b/test/lmp/aelmp_test.jl deleted file mode 100644 index 484275c..0000000 --- a/test/lmp/aelmp_test.jl +++ /dev/null @@ -1,35 +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, HiGHS, JuMP -import UnitCommitment: AELMP - -@testset "aelmp" begin - path = "$FIXTURES/aelmp_simple.json.gz" - # model has to be solved first - instance = UnitCommitment.read(path) - model = UnitCommitment.build_model( - instance = instance, - optimizer = Cbc.Optimizer, - variable_names = true, - ) - JuMP.set_silent(model) - UnitCommitment.optimize!(model) - - # policy 1: allow offlines; consider startups - aelmp_1 = - UnitCommitment.compute_lmp(model, AELMP(), optimizer = HiGHS.Optimizer) - @test aelmp_1["s1", "B1", 1] ≈ 231.7 atol = 0.1 - - # policy 2: do not allow offlines; but consider startups - aelmp_2 = UnitCommitment.compute_lmp( - model, - AELMP( - allow_offline_participation = false, - consider_startup_costs = true, - ), - optimizer = HiGHS.Optimizer, - ) - @test aelmp_2["s1", "B1", 1] ≈ 274.3 atol = 0.1 -end diff --git a/test/lmp/conventional_test.jl b/test/lmp/conventional_test.jl deleted file mode 100644 index 80a4ce5..0000000 --- a/test/lmp/conventional_test.jl +++ /dev/null @@ -1,51 +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, HiGHS, JuMP -import UnitCommitment: ConventionalLMP - -function solve_conventional_testcase(path::String) - instance = UnitCommitment.read(path) - model = UnitCommitment.build_model( - instance = instance, - optimizer = Cbc.Optimizer, - variable_names = true, - ) - JuMP.set_silent(model) - UnitCommitment.optimize!(model) - lmp = UnitCommitment.compute_lmp( - model, - ConventionalLMP(), - optimizer = HiGHS.Optimizer, - ) - return lmp -end - -@testset "conventional" begin - # instance 1 - path = "$FIXTURES/lmp_simple_test_1.json.gz" - lmp = solve_conventional_testcase(path) - @test lmp["s1", "A", 1] == 50.0 - @test lmp["s1", "B", 1] == 50.0 - - # instance 2 - path = "$FIXTURES/lmp_simple_test_2.json.gz" - lmp = solve_conventional_testcase(path) - @test lmp["s1", "A", 1] == 50.0 - @test lmp["s1", "B", 1] == 60.0 - - # instance 3 - path = "$FIXTURES/lmp_simple_test_3.json.gz" - lmp = solve_conventional_testcase(path) - @test lmp["s1", "A", 1] == 50.0 - @test lmp["s1", "B", 1] == 70.0 - @test lmp["s1", "C", 1] == 100.0 - - # instance 4 - path = "$FIXTURES/lmp_simple_test_4.json.gz" - lmp = solve_conventional_testcase(path) - @test lmp["s1", "A", 1] == 50.0 - @test lmp["s1", "B", 1] == 70.0 - @test lmp["s1", "C", 1] == 90.0 -end diff --git a/test/model/formulations_test.jl b/test/model/formulations_test.jl deleted file mode 100644 index 09cfeca..0000000 --- a/test/model/formulations_test.jl +++ /dev/null @@ -1,84 +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 -using JuMP -using Cbc -using JSON -import UnitCommitment: - ArrCon2000, - CarArr2006, - DamKucRajAta2016, - Formulation, - Gar1962, - KnuOstWat2018, - MorLatRam2013, - PanGua2016, - XavQiuWanThi2019, - WanHob2016 - -function _test( - formulation::Formulation; - instances = ["case14"], - dump::Bool = false, -)::Nothing - for instance_name in instances - instance = UnitCommitment.read("$(FIXTURES)/$(instance_name).json.gz") - model = UnitCommitment.build_model( - instance = instance, - formulation = formulation, - optimizer = Cbc.Optimizer, - variable_names = true, - ) - set_silent(model) - UnitCommitment.optimize!(model) - solution = UnitCommitment.solution(model) - if dump - open("/tmp/ucjl.json", "w") do f - return write(f, JSON.json(solution, 2)) - end - write_to_file(model, "/tmp/ucjl.lp") - end - @test UnitCommitment.validate(instance, solution) - end - return -end - -@testset "formulations" begin - @testset "default" begin - _test(Formulation()) - end - @testset "ArrCon2000" begin - _test(Formulation(ramping = ArrCon2000.Ramping())) - end - @testset "DamKucRajAta2016" begin - _test(Formulation(ramping = DamKucRajAta2016.Ramping())) - end - @testset "MorLatRam2013" begin - _test( - Formulation( - ramping = MorLatRam2013.Ramping(), - startup_costs = MorLatRam2013.StartupCosts(), - ), - ) - end - @testset "PanGua2016" begin - _test(Formulation(ramping = PanGua2016.Ramping())) - end - @testset "Gar1962" begin - _test(Formulation(pwl_costs = Gar1962.PwlCosts())) - end - @testset "CarArr2006" begin - _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) - end - @testset "KnuOstWat2018" begin - _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) - end - @testset "WanHob2016" begin - _test( - Formulation(ramping = WanHob2016.Ramping()), - instances = ["case14-flex"], - ) - end -end diff --git a/test/runtests.jl b/test/runtests.jl deleted file mode 100644 index 08ade97..0000000 --- a/test/runtests.jl +++ /dev/null @@ -1,46 +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 Test -using UnitCommitment - -push!(Base.LOAD_PATH, @__DIR__) -UnitCommitment._setup_logger(level = Base.CoreLogging.Error) - -FIXTURES = "$(@__DIR__)/fixtures" - -@testset "UnitCommitment" begin - include("usage.jl") - @testset "import" begin - include("import/egret_test.jl") - end - @testset "instance" begin - include("instance/read_test.jl") - include("instance/migrate_test.jl") - end - @testset "model" begin - include("model/formulations_test.jl") - end - @testset "solution" begin - @testset "XavQiuWanThi19" begin - include("solution/methods/XavQiuWanThi19/filter_test.jl") - include("solution/methods/XavQiuWanThi19/find_test.jl") - include("solution/methods/XavQiuWanThi19/sensitivity_test.jl") - end - end - @testset "transform" begin - include("transform/initcond_test.jl") - include("transform/slice_test.jl") - @testset "randomize" begin - include("transform/randomize/XavQiuAhm2021_test.jl") - end - end - @testset "validation" begin - include("validation/repair_test.jl") - end - @testset "lmp" begin - include("lmp/conventional_test.jl") - include("lmp/aelmp_test.jl") - end -end diff --git a/test/solution/methods/XavQiuWanThi19/filter_test.jl b/test/solution/methods/XavQiuWanThi19/filter_test.jl deleted file mode 100644 index 4ed69e7..0000000 --- a/test/solution/methods/XavQiuWanThi19/filter_test.jl +++ /dev/null @@ -1,83 +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, Test, LinearAlgebra -import UnitCommitment: _Violation, _offer, _query - -@testset "_ViolationFilter" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - sc = instance.scenarios[1] - filter = UnitCommitment._ViolationFilter(max_per_line = 1, max_total = 2) - _offer( - filter, - _Violation( - time = 1, - monitored_line = sc.lines[1], - outage_line = nothing, - amount = 100.0, - ), - ) - _offer( - filter, - _Violation( - time = 1, - monitored_line = sc.lines[1], - outage_line = sc.lines[1], - amount = 300.0, - ), - ) - _offer( - filter, - _Violation( - time = 1, - monitored_line = sc.lines[1], - outage_line = sc.lines[5], - amount = 500.0, - ), - ) - _offer( - filter, - _Violation( - time = 1, - monitored_line = sc.lines[1], - outage_line = sc.lines[4], - amount = 400.0, - ), - ) - _offer( - filter, - _Violation( - time = 1, - monitored_line = sc.lines[2], - outage_line = sc.lines[1], - amount = 200.0, - ), - ) - _offer( - filter, - _Violation( - time = 1, - monitored_line = sc.lines[2], - outage_line = sc.lines[8], - amount = 100.0, - ), - ) - - actual = _query(filter) - expected = [ - _Violation( - time = 1, - monitored_line = sc.lines[2], - outage_line = sc.lines[1], - amount = 200.0, - ), - _Violation( - time = 1, - monitored_line = sc.lines[1], - outage_line = sc.lines[5], - amount = 500.0, - ), - ] - @test actual == expected -end diff --git a/test/solution/methods/XavQiuWanThi19/find_test.jl b/test/solution/methods/XavQiuWanThi19/find_test.jl deleted file mode 100644 index d3d3452..0000000 --- a/test/solution/methods/XavQiuWanThi19/find_test.jl +++ /dev/null @@ -1,37 +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, Test, LinearAlgebra -import UnitCommitment: _Violation, _offer, _query - -@testset "find_violations" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - sc = instance.scenarios[1] - for line in sc.lines, t in 1:instance.time - line.normal_flow_limit[t] = 1.0 - line.emergency_flow_limit[t] = 1.0 - end - isf = UnitCommitment._injection_shift_factors( - lines = sc.lines, - buses = sc.buses, - ) - lodf = UnitCommitment._line_outage_factors( - lines = sc.lines, - buses = sc.buses, - isf = isf, - ) - inj = [1000.0 for b in 1:13, t in 1:instance.time] - overflow = [0.0 for l in sc.lines, t in 1:instance.time] - violations = UnitCommitment._find_violations( - instance = instance, - sc = sc, - net_injections = inj, - overflow = overflow, - isf = isf, - lodf = lodf, - max_per_line = 1, - max_per_period = 5, - ) - @test length(violations) == 20 -end diff --git a/test/solution/methods/XavQiuWanThi19/sensitivity_test.jl b/test/solution/methods/XavQiuWanThi19/sensitivity_test.jl deleted file mode 100644 index 1ab5950..0000000 --- a/test/solution/methods/XavQiuWanThi19/sensitivity_test.jl +++ /dev/null @@ -1,147 +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, Test, LinearAlgebra - -@testset "_susceptance_matrix" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - sc = instance.scenarios[1] - actual = UnitCommitment._susceptance_matrix(sc.lines) - @test size(actual) == (20, 20) - expected = Diagonal([ - 29.5, - 7.83, - 8.82, - 9.9, - 10.04, - 10.2, - 41.45, - 8.35, - 3.14, - 6.93, - 8.77, - 6.82, - 13.4, - 9.91, - 15.87, - 20.65, - 6.46, - 9.09, - 8.73, - 5.02, - ]) - @test round.(actual, digits = 2) == expected -end - -@testset "_reduced_incidence_matrix" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - sc = instance.scenarios[1] - actual = UnitCommitment._reduced_incidence_matrix( - lines = sc.lines, - buses = sc.buses, - ) - @test size(actual) == (20, 13) - @test actual[1, 1] == -1.0 - @test actual[3, 1] == 1.0 - @test actual[4, 1] == 1.0 - @test actual[5, 1] == 1.0 - @test actual[3, 2] == -1.0 - @test actual[6, 2] == 1.0 - @test actual[4, 3] == -1.0 - @test actual[6, 3] == -1.0 - @test actual[7, 3] == 1.0 - @test actual[8, 3] == 1.0 - @test actual[9, 3] == 1.0 - @test actual[2, 4] == -1.0 - @test actual[5, 4] == -1.0 - @test actual[7, 4] == -1.0 - @test actual[10, 4] == 1.0 - @test actual[10, 5] == -1.0 - @test actual[11, 5] == 1.0 - @test actual[12, 5] == 1.0 - @test actual[13, 5] == 1.0 - @test actual[8, 6] == -1.0 - @test actual[14, 6] == 1.0 - @test actual[15, 6] == 1.0 - @test actual[14, 7] == -1.0 - @test actual[9, 8] == -1.0 - @test actual[15, 8] == -1.0 - @test actual[16, 8] == 1.0 - @test actual[17, 8] == 1.0 - @test actual[16, 9] == -1.0 - @test actual[18, 9] == 1.0 - @test actual[11, 10] == -1.0 - @test actual[18, 10] == -1.0 - @test actual[12, 11] == -1.0 - @test actual[19, 11] == 1.0 - @test actual[13, 12] == -1.0 - @test actual[19, 12] == -1.0 - @test actual[20, 12] == 1.0 - @test actual[17, 13] == -1.0 - @test actual[20, 13] == -1.0 -end - -@testset "_injection_shift_factors" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - sc = instance.scenarios[1] - actual = UnitCommitment._injection_shift_factors( - lines = sc.lines, - buses = sc.buses, - ) - @test size(actual) == (20, 13) - @test round.(actual, digits = 2) == [ - -0.84 -0.75 -0.67 -0.61 -0.63 -0.66 -0.66 -0.65 -0.65 -0.64 -0.63 -0.63 -0.64 - -0.16 -0.25 -0.33 -0.39 -0.37 -0.34 -0.34 -0.35 -0.35 -0.36 -0.37 -0.37 -0.36 - 0.03 -0.53 -0.15 -0.1 -0.12 -0.14 -0.14 -0.14 -0.13 -0.13 -0.12 -0.12 -0.13 - 0.06 -0.14 -0.32 -0.22 -0.25 -0.3 -0.3 -0.29 -0.28 -0.27 -0.25 -0.26 -0.27 - 0.08 -0.07 -0.2 -0.29 -0.26 -0.22 -0.22 -0.22 -0.23 -0.25 -0.26 -0.26 -0.24 - 0.03 0.47 -0.15 -0.1 -0.12 -0.14 -0.14 -0.14 -0.13 -0.13 -0.12 -0.12 -0.13 - 0.08 0.31 0.5 -0.3 -0.03 0.36 0.36 0.28 0.23 0.1 -0.0 0.02 0.17 - 0.0 0.01 0.02 -0.01 -0.22 -0.63 -0.63 -0.45 -0.41 -0.32 -0.24 -0.25 -0.36 - 0.0 0.01 0.01 -0.01 -0.12 -0.17 -0.17 -0.26 -0.24 -0.18 -0.14 -0.14 -0.21 - -0.0 -0.02 -0.03 0.02 -0.66 -0.2 -0.2 -0.29 -0.36 -0.5 -0.63 -0.61 -0.43 - -0.0 -0.01 -0.02 0.01 0.21 -0.12 -0.12 -0.17 -0.28 -0.53 0.18 0.15 -0.03 - -0.0 -0.0 -0.0 0.0 0.03 -0.02 -0.02 -0.03 -0.02 0.01 -0.52 -0.17 -0.09 - -0.0 -0.01 -0.01 0.01 0.11 -0.06 -0.06 -0.09 -0.05 0.02 -0.28 -0.59 -0.31 - -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -1.0 -0.0 -0.0 -0.0 -0.0 -0.0 0.0 - 0.0 0.01 0.02 -0.01 -0.22 0.37 0.37 -0.45 -0.41 -0.32 -0.24 -0.25 -0.36 - 0.0 0.01 0.02 -0.01 -0.21 0.12 0.12 0.17 -0.72 -0.47 -0.18 -0.15 0.03 - 0.0 0.01 0.01 -0.01 -0.14 0.08 0.08 0.12 0.07 -0.03 -0.2 -0.24 -0.6 - 0.0 0.01 0.02 -0.01 -0.21 0.12 0.12 0.17 0.28 -0.47 -0.18 -0.15 0.03 - -0.0 -0.0 -0.0 0.0 0.03 -0.02 -0.02 -0.03 -0.02 0.01 0.48 -0.17 -0.09 - -0.0 -0.01 -0.01 0.01 0.14 -0.08 -0.08 -0.12 -0.07 0.03 0.2 0.24 -0.4 - ] -end - -@testset "_line_outage_factors" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - sc = instance.scenarios[1] - isf_before = UnitCommitment._injection_shift_factors( - lines = sc.lines, - buses = sc.buses, - ) - lodf = UnitCommitment._line_outage_factors( - lines = sc.lines, - buses = sc.buses, - isf = isf_before, - ) - for contingency in sc.contingencies - for lc in contingency.lines - prev_susceptance = lc.susceptance - lc.susceptance = 0.0 - isf_after = UnitCommitment._injection_shift_factors( - lines = sc.lines, - buses = sc.buses, - ) - lc.susceptance = prev_susceptance - for lm in sc.lines - expected = isf_after[lm.offset, :] - actual = - isf_before[lm.offset, :] + - lodf[lm.offset, lc.offset] * isf_before[lc.offset, :] - @test norm(expected - actual) < 1e-6 - end - end - end -end diff --git a/test/src/UnitCommitmentT.jl b/test/src/UnitCommitmentT.jl new file mode 100644 index 0000000..c07f547 --- /dev/null +++ b/test/src/UnitCommitmentT.jl @@ -0,0 +1,58 @@ +module UnitCommitmentT + +using JuliaFormatter +using UnitCommitment +using Test + +include("usage.jl") +include("import/egret_test.jl") +include("instance/read_test.jl") +include("instance/migrate_test.jl") +include("model/formulations_test.jl") +include("solution/methods/XavQiuWanThi19/filter_test.jl") +include("solution/methods/XavQiuWanThi19/find_test.jl") +include("solution/methods/XavQiuWanThi19/sensitivity_test.jl") +include("transform/initcond_test.jl") +include("transform/slice_test.jl") +include("transform/randomize/XavQiuAhm2021_test.jl") +include("validation/repair_test.jl") +include("lmp/conventional_test.jl") +include("lmp/aelmp_test.jl") + +basedir = dirname(@__FILE__) + +function fixture(path::String)::String + return "$basedir/../fixtures/$path" +end + +function runtests() + println("Running tests...") + UnitCommitment._setup_logger(level = Base.CoreLogging.Error) + @testset "UnitCommitment" begin + usage_test() + import_egret_test() + instance_read_test() + instance_migrate_test() + model_formulations_test() + solution_methods_XavQiuWanThi19_filter_test() + solution_methods_XavQiuWanThi19_find_test() + solution_methods_XavQiuWanThi19_sensitivity_test() + transform_initcond_test() + transform_slice_test() + transform_randomize_XavQiuAhm2021_test() + validation_repair_test() + lmp_conventional_test() + lmp_aelmp_test() + end + return +end + +function format() + JuliaFormatter.format(basedir, verbose = true) + JuliaFormatter.format("$basedir/../../src", verbose = true) + return +end + +export runtests, format + +end # module UnitCommitmentT diff --git a/test/src/import/egret_test.jl b/test/src/import/egret_test.jl new file mode 100644 index 0000000..fb05780 --- /dev/null +++ b/test/src/import/egret_test.jl @@ -0,0 +1,23 @@ +# 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 + +function import_egret_test() + @testset "read_egret_solution" begin + solution = + UnitCommitment.read_egret_solution(fixture("egret_output.json.gz")) + for attr in + ["Is on", "Thermal production (MW)", "Thermal production cost (\$)"] + @test attr in keys(solution) + @test "115_STEAM_1" in keys(solution[attr]) + @test length(solution[attr]["115_STEAM_1"]) == 48 + end + @test solution["Thermal production cost (\$)"]["315_CT_6"][15:20] == + [0.0, 0.0, 884.44, 1470.71, 1470.71, 884.44] + @test solution["Startup cost (\$)"]["315_CT_6"][15:20] == + [0.0, 0.0, 5665.23, 0.0, 0.0, 0.0] + @test length(keys(solution["Is on"])) == 154 + end +end diff --git a/test/src/instance/migrate_test.jl b/test/src/instance/migrate_test.jl new file mode 100644 index 0000000..8328e53 --- /dev/null +++ b/test/src/instance/migrate_test.jl @@ -0,0 +1,24 @@ +# 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, LinearAlgebra, Cbc, JuMP, JSON, GZip + +function instance_migrate_test() + @testset "read v0.2" begin + instance = UnitCommitment.read(fixture("/ucjl-0.2.json.gz")) + @test length(instance.scenarios) == 1 + sc = instance.scenarios[1] + @test length(sc.reserves_by_name["r1"].amount) == 4 + @test sc.thermal_units_by_name["g2"].reserves[1].name == "r1" + end + + @testset "read v0.3" begin + instance = UnitCommitment.read(fixture("/ucjl-0.3.json.gz")) + @test length(instance.scenarios) == 1 + sc = instance.scenarios[1] + @test length(sc.thermal_units) == 6 + @test length(sc.buses) == 14 + @test length(sc.lines) == 20 + end +end diff --git a/test/src/instance/read_test.jl b/test/src/instance/read_test.jl new file mode 100644 index 0000000..85f44ff --- /dev/null +++ b/test/src/instance/read_test.jl @@ -0,0 +1,168 @@ +# 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, LinearAlgebra, Cbc, JuMP, JSON, GZip + +function instance_read_test() + @testset "read_benchmark" begin + instance = UnitCommitment.read(fixture("case14.json.gz")) + + @test repr(instance) == ( + "UnitCommitmentInstance(1 scenarios, 6 thermal units, 0 profiled units, 14 buses, " * + "20 lines, 19 contingencies, 1 price sensitive loads, 4 time steps)" + ) + + @test length(instance.scenarios) == 1 + sc = instance.scenarios[1] + @test length(sc.lines) == 20 + @test length(sc.buses) == 14 + @test length(sc.thermal_units) == 6 + @test length(sc.contingencies) == 19 + @test length(sc.price_sensitive_loads) == 1 + @test instance.time == 4 + + @test sc.lines[5].name == "l5" + @test sc.lines[5].source.name == "b2" + @test sc.lines[5].target.name == "b5" + @test sc.lines[5].reactance ≈ 0.17388 + @test sc.lines[5].susceptance ≈ 10.037550333 + @test sc.lines[5].normal_flow_limit == [1e8 for t in 1:4] + @test sc.lines[5].emergency_flow_limit == [1e8 for t in 1:4] + @test sc.lines[5].flow_limit_penalty == [5e3 for t in 1:4] + @test sc.lines_by_name["l5"].name == "l5" + + @test sc.lines[1].name == "l1" + @test sc.lines[1].source.name == "b1" + @test sc.lines[1].target.name == "b2" + @test sc.lines[1].reactance ≈ 0.059170 + @test sc.lines[1].susceptance ≈ 29.496860773945 + @test sc.lines[1].normal_flow_limit == [300.0 for t in 1:4] + @test sc.lines[1].emergency_flow_limit == [400.0 for t in 1:4] + @test sc.lines[1].flow_limit_penalty == [1e3 for t in 1:4] + + @test sc.buses[9].name == "b9" + @test sc.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] + @test sc.buses_by_name["b9"].name == "b9" + + @test sc.reserves[1].name == "r1" + @test sc.reserves[1].type == "spinning" + @test sc.reserves[1].amount == [100.0, 100.0, 100.0, 100.0] + @test sc.reserves_by_name["r1"].name == "r1" + + unit = sc.thermal_units[1] + @test unit.name == "g1" + @test unit.bus.name == "b1" + @test unit.ramp_up_limit == 1e6 + @test unit.ramp_down_limit == 1e6 + @test unit.startup_limit == 1e6 + @test unit.shutdown_limit == 1e6 + @test unit.must_run == [false for t in 1:4] + @test unit.min_power_cost == [1400.0 for t in 1:4] + @test unit.min_uptime == 1 + @test unit.min_downtime == 1 + for t in 1:1 + @test unit.cost_segments[1].mw[t] == 10.0 + @test unit.cost_segments[2].mw[t] == 20.0 + @test unit.cost_segments[3].mw[t] == 5.0 + @test unit.cost_segments[1].cost[t] ≈ 20.0 + @test unit.cost_segments[2].cost[t] ≈ 30.0 + @test unit.cost_segments[3].cost[t] ≈ 40.0 + end + @test length(unit.startup_categories) == 3 + @test unit.startup_categories[1].delay == 1 + @test unit.startup_categories[2].delay == 2 + @test unit.startup_categories[3].delay == 3 + @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 length(unit.reserves) == 0 + @test sc.thermal_units_by_name["g1"].name == "g1" + + unit = sc.thermal_units[2] + @test unit.name == "g2" + @test unit.must_run == [false for t in 1:4] + @test length(unit.reserves) == 1 + + unit = sc.thermal_units[3] + @test unit.name == "g3" + @test unit.bus.name == "b3" + @test unit.ramp_up_limit == 70.0 + @test unit.ramp_down_limit == 70.0 + @test unit.startup_limit == 70.0 + @test unit.shutdown_limit == 70.0 + @test unit.must_run == [true for t in 1:4] + @test unit.min_power_cost == [0.0 for t in 1:4] + @test unit.min_uptime == 1 + @test unit.min_downtime == 1 + for t in 1:4 + @test unit.cost_segments[1].mw[t] ≈ 33 + @test unit.cost_segments[2].mw[t] ≈ 33 + @test unit.cost_segments[3].mw[t] ≈ 34 + @test unit.cost_segments[1].cost[t] ≈ 33.75 + @test unit.cost_segments[2].cost[t] ≈ 38.04 + @test unit.cost_segments[3].cost[t] ≈ 44.77853 + end + @test length(unit.reserves) == 1 + @test unit.reserves[1].name == "r1" + + @test sc.contingencies[1].lines == [sc.lines[1]] + @test sc.contingencies[1].thermal_units == [] + @test sc.contingencies[1].name == "c1" + @test sc.contingencies_by_name["c1"].name == "c1" + + load = sc.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 sc.price_sensitive_loads_by_name["ps1"].name == "ps1" + end + + @testset "read_benchmark sub-hourly" begin + instance = UnitCommitment.read(fixture("case14-sub-hourly.json.gz")) + @test instance.time == 4 + unit = instance.scenarios[1].thermal_units[1] + @test unit.name == "g1" + @test unit.min_uptime == 2 + @test unit.min_downtime == 2 + @test length(unit.startup_categories) == 3 + @test unit.startup_categories[1].delay == 2 + @test unit.startup_categories[2].delay == 4 + @test unit.startup_categories[3].delay == 6 + @test unit.initial_status == -200 + end + + @testset "read_benchmark profiled-units" begin + instance = UnitCommitment.read(fixture("case14-profiled.json.gz")) + sc = instance.scenarios[1] + @test length(sc.profiled_units) == 2 + + first_pu = sc.profiled_units[1] + @test first_pu.name == "g7" + @test first_pu.bus.name == "b4" + @test first_pu.cost == [100.0 for t in 1:4] + @test first_pu.min_power == [60.0 for t in 1:4] + @test first_pu.max_power == [100.0 for t in 1:4] + @test sc.profiled_units_by_name["g7"].name == "g7" + + second_pu = sc.profiled_units[2] + @test second_pu.name == "g8" + @test second_pu.bus.name == "b5" + @test second_pu.cost == [50.0 for t in 1:4] + @test second_pu.min_power == [0.0 for t in 1:4] + @test second_pu.max_power == [120.0 for t in 1:4] + @test sc.profiled_units_by_name["g8"].name == "g8" + end + + @testset "read_benchmark commitmemt-status" begin + instance = UnitCommitment.read(fixture("case14-fixed-status.json.gz")) + sc = instance.scenarios[1] + + @test sc.thermal_units[1].commitment_status == [nothing for t in 1:4] + @test sc.thermal_units[2].commitment_status == [true for t in 1:4] + @test sc.thermal_units[4].commitment_status == [false for t in 1:4] + @test sc.thermal_units[6].commitment_status == + [false, nothing, true, nothing] + end +end diff --git a/test/src/lmp/aelmp_test.jl b/test/src/lmp/aelmp_test.jl new file mode 100644 index 0000000..a7494bf --- /dev/null +++ b/test/src/lmp/aelmp_test.jl @@ -0,0 +1,40 @@ +# 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, HiGHS, JuMP +import UnitCommitment: AELMP + +function lmp_aelmp_test() + @testset "aelmp" begin + path = fixture("aelmp_simple.json.gz") + # model has to be solved first + instance = UnitCommitment.read(path) + model = UnitCommitment.build_model( + instance = instance, + optimizer = Cbc.Optimizer, + variable_names = true, + ) + JuMP.set_silent(model) + UnitCommitment.optimize!(model) + + # policy 1: allow offlines; consider startups + aelmp_1 = UnitCommitment.compute_lmp( + model, + AELMP(), + optimizer = HiGHS.Optimizer, + ) + @test aelmp_1["s1", "B1", 1] ≈ 231.7 atol = 0.1 + + # policy 2: do not allow offlines; but consider startups + aelmp_2 = UnitCommitment.compute_lmp( + model, + AELMP( + allow_offline_participation = false, + consider_startup_costs = true, + ), + optimizer = HiGHS.Optimizer, + ) + @test aelmp_2["s1", "B1", 1] ≈ 274.3 atol = 0.1 + end +end diff --git a/test/src/lmp/conventional_test.jl b/test/src/lmp/conventional_test.jl new file mode 100644 index 0000000..b1135c5 --- /dev/null +++ b/test/src/lmp/conventional_test.jl @@ -0,0 +1,53 @@ +# 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, HiGHS, JuMP +import UnitCommitment: ConventionalLMP + +function solve_conventional_testcase(path::String) + instance = UnitCommitment.read(path) + model = UnitCommitment.build_model( + instance = instance, + optimizer = Cbc.Optimizer, + variable_names = true, + ) + JuMP.set_silent(model) + UnitCommitment.optimize!(model) + lmp = UnitCommitment.compute_lmp( + model, + ConventionalLMP(), + optimizer = HiGHS.Optimizer, + ) + return lmp +end + +function lmp_conventional_test() + @testset "conventional" begin + # instance 1 + path = fixture("lmp_simple_test_1.json.gz") + lmp = solve_conventional_testcase(path) + @test lmp["s1", "A", 1] == 50.0 + @test lmp["s1", "B", 1] == 50.0 + + # instance 2 + path = fixture("lmp_simple_test_2.json.gz") + lmp = solve_conventional_testcase(path) + @test lmp["s1", "A", 1] == 50.0 + @test lmp["s1", "B", 1] == 60.0 + + # instance 3 + path = fixture("lmp_simple_test_3.json.gz") + lmp = solve_conventional_testcase(path) + @test lmp["s1", "A", 1] == 50.0 + @test lmp["s1", "B", 1] == 70.0 + @test lmp["s1", "C", 1] == 100.0 + + # instance 4 + path = fixture("lmp_simple_test_4.json.gz") + lmp = solve_conventional_testcase(path) + @test lmp["s1", "A", 1] == 50.0 + @test lmp["s1", "B", 1] == 70.0 + @test lmp["s1", "C", 1] == 90.0 + end +end diff --git a/test/src/model/formulations_test.jl b/test/src/model/formulations_test.jl new file mode 100644 index 0000000..2b9307d --- /dev/null +++ b/test/src/model/formulations_test.jl @@ -0,0 +1,86 @@ +# 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 +using JuMP +using Cbc +using JSON +import UnitCommitment: + ArrCon2000, + CarArr2006, + DamKucRajAta2016, + Formulation, + Gar1962, + KnuOstWat2018, + MorLatRam2013, + PanGua2016, + XavQiuWanThi2019, + WanHob2016 + +function _test( + formulation::Formulation; + instances = ["case14"], + dump::Bool = false, +)::Nothing + for instance_name in instances + instance = UnitCommitment.read(fixture("$(instance_name).json.gz")) + model = UnitCommitment.build_model( + instance = instance, + formulation = formulation, + optimizer = Cbc.Optimizer, + variable_names = true, + ) + set_silent(model) + UnitCommitment.optimize!(model) + solution = UnitCommitment.solution(model) + if dump + open("/tmp/ucjl.json", "w") do f + return write(f, JSON.json(solution, 2)) + end + write_to_file(model, "/tmp/ucjl.lp") + end + @test UnitCommitment.validate(instance, solution) + end + return +end + +function model_formulations_test() + @testset "formulations" begin + @testset "default" begin + _test(Formulation()) + end + @testset "ArrCon2000" begin + _test(Formulation(ramping = ArrCon2000.Ramping())) + end + @testset "DamKucRajAta2016" begin + _test(Formulation(ramping = DamKucRajAta2016.Ramping())) + end + @testset "MorLatRam2013" begin + _test( + Formulation( + ramping = MorLatRam2013.Ramping(), + startup_costs = MorLatRam2013.StartupCosts(), + ), + ) + end + @testset "PanGua2016" begin + _test(Formulation(ramping = PanGua2016.Ramping())) + end + @testset "Gar1962" begin + _test(Formulation(pwl_costs = Gar1962.PwlCosts())) + end + @testset "CarArr2006" begin + _test(Formulation(pwl_costs = CarArr2006.PwlCosts())) + end + @testset "KnuOstWat2018" begin + _test(Formulation(pwl_costs = KnuOstWat2018.PwlCosts())) + end + @testset "WanHob2016" begin + _test( + Formulation(ramping = WanHob2016.Ramping()), + instances = ["case14-flex"], + ) + end + end +end diff --git a/test/src/solution/methods/XavQiuWanThi19/filter_test.jl b/test/src/solution/methods/XavQiuWanThi19/filter_test.jl new file mode 100644 index 0000000..7e37ca9 --- /dev/null +++ b/test/src/solution/methods/XavQiuWanThi19/filter_test.jl @@ -0,0 +1,86 @@ +# 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, Test, LinearAlgebra +import UnitCommitment: _Violation, _offer, _query + +function solution_methods_XavQiuWanThi19_filter_test() + @testset "_ViolationFilter" begin + instance = UnitCommitment.read(fixture("case14.json.gz")) + sc = instance.scenarios[1] + filter = + UnitCommitment._ViolationFilter(max_per_line = 1, max_total = 2) + _offer( + filter, + _Violation( + time = 1, + monitored_line = sc.lines[1], + outage_line = nothing, + amount = 100.0, + ), + ) + _offer( + filter, + _Violation( + time = 1, + monitored_line = sc.lines[1], + outage_line = sc.lines[1], + amount = 300.0, + ), + ) + _offer( + filter, + _Violation( + time = 1, + monitored_line = sc.lines[1], + outage_line = sc.lines[5], + amount = 500.0, + ), + ) + _offer( + filter, + _Violation( + time = 1, + monitored_line = sc.lines[1], + outage_line = sc.lines[4], + amount = 400.0, + ), + ) + _offer( + filter, + _Violation( + time = 1, + monitored_line = sc.lines[2], + outage_line = sc.lines[1], + amount = 200.0, + ), + ) + _offer( + filter, + _Violation( + time = 1, + monitored_line = sc.lines[2], + outage_line = sc.lines[8], + amount = 100.0, + ), + ) + + actual = _query(filter) + expected = [ + _Violation( + time = 1, + monitored_line = sc.lines[2], + outage_line = sc.lines[1], + amount = 200.0, + ), + _Violation( + time = 1, + monitored_line = sc.lines[1], + outage_line = sc.lines[5], + amount = 500.0, + ), + ] + @test actual == expected + end +end diff --git a/test/src/solution/methods/XavQiuWanThi19/find_test.jl b/test/src/solution/methods/XavQiuWanThi19/find_test.jl new file mode 100644 index 0000000..a4a67bf --- /dev/null +++ b/test/src/solution/methods/XavQiuWanThi19/find_test.jl @@ -0,0 +1,39 @@ +# 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, Test, LinearAlgebra +import UnitCommitment: _Violation, _offer, _query + +function solution_methods_XavQiuWanThi19_find_test() + @testset "find_violations" begin + instance = UnitCommitment.read(fixture("case14.json.gz")) + sc = instance.scenarios[1] + for line in sc.lines, t in 1:instance.time + line.normal_flow_limit[t] = 1.0 + line.emergency_flow_limit[t] = 1.0 + end + isf = UnitCommitment._injection_shift_factors( + lines = sc.lines, + buses = sc.buses, + ) + lodf = UnitCommitment._line_outage_factors( + lines = sc.lines, + buses = sc.buses, + isf = isf, + ) + inj = [1000.0 for b in 1:13, t in 1:instance.time] + overflow = [0.0 for l in sc.lines, t in 1:instance.time] + violations = UnitCommitment._find_violations( + instance = instance, + sc = sc, + net_injections = inj, + overflow = overflow, + isf = isf, + lodf = lodf, + max_per_line = 1, + max_per_period = 5, + ) + @test length(violations) == 20 + end +end diff --git a/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl b/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl new file mode 100644 index 0000000..0eef038 --- /dev/null +++ b/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl @@ -0,0 +1,149 @@ +# 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, Test, LinearAlgebra + +function solution_methods_XavQiuWanThi19_sensitivity_test() + @testset "_susceptance_matrix" begin + instance = UnitCommitment.read(fixture("/case14.json.gz")) + sc = instance.scenarios[1] + actual = UnitCommitment._susceptance_matrix(sc.lines) + @test size(actual) == (20, 20) + expected = Diagonal([ + 29.5, + 7.83, + 8.82, + 9.9, + 10.04, + 10.2, + 41.45, + 8.35, + 3.14, + 6.93, + 8.77, + 6.82, + 13.4, + 9.91, + 15.87, + 20.65, + 6.46, + 9.09, + 8.73, + 5.02, + ]) + @test round.(actual, digits = 2) == expected + end + + @testset "_reduced_incidence_matrix" begin + instance = UnitCommitment.read(fixture("/case14.json.gz")) + sc = instance.scenarios[1] + actual = UnitCommitment._reduced_incidence_matrix( + lines = sc.lines, + buses = sc.buses, + ) + @test size(actual) == (20, 13) + @test actual[1, 1] == -1.0 + @test actual[3, 1] == 1.0 + @test actual[4, 1] == 1.0 + @test actual[5, 1] == 1.0 + @test actual[3, 2] == -1.0 + @test actual[6, 2] == 1.0 + @test actual[4, 3] == -1.0 + @test actual[6, 3] == -1.0 + @test actual[7, 3] == 1.0 + @test actual[8, 3] == 1.0 + @test actual[9, 3] == 1.0 + @test actual[2, 4] == -1.0 + @test actual[5, 4] == -1.0 + @test actual[7, 4] == -1.0 + @test actual[10, 4] == 1.0 + @test actual[10, 5] == -1.0 + @test actual[11, 5] == 1.0 + @test actual[12, 5] == 1.0 + @test actual[13, 5] == 1.0 + @test actual[8, 6] == -1.0 + @test actual[14, 6] == 1.0 + @test actual[15, 6] == 1.0 + @test actual[14, 7] == -1.0 + @test actual[9, 8] == -1.0 + @test actual[15, 8] == -1.0 + @test actual[16, 8] == 1.0 + @test actual[17, 8] == 1.0 + @test actual[16, 9] == -1.0 + @test actual[18, 9] == 1.0 + @test actual[11, 10] == -1.0 + @test actual[18, 10] == -1.0 + @test actual[12, 11] == -1.0 + @test actual[19, 11] == 1.0 + @test actual[13, 12] == -1.0 + @test actual[19, 12] == -1.0 + @test actual[20, 12] == 1.0 + @test actual[17, 13] == -1.0 + @test actual[20, 13] == -1.0 + end + + @testset "_injection_shift_factors" begin + instance = UnitCommitment.read(fixture("/case14.json.gz")) + sc = instance.scenarios[1] + actual = UnitCommitment._injection_shift_factors( + lines = sc.lines, + buses = sc.buses, + ) + @test size(actual) == (20, 13) + @test round.(actual, digits = 2) == [ + -0.84 -0.75 -0.67 -0.61 -0.63 -0.66 -0.66 -0.65 -0.65 -0.64 -0.63 -0.63 -0.64 + -0.16 -0.25 -0.33 -0.39 -0.37 -0.34 -0.34 -0.35 -0.35 -0.36 -0.37 -0.37 -0.36 + 0.03 -0.53 -0.15 -0.1 -0.12 -0.14 -0.14 -0.14 -0.13 -0.13 -0.12 -0.12 -0.13 + 0.06 -0.14 -0.32 -0.22 -0.25 -0.3 -0.3 -0.29 -0.28 -0.27 -0.25 -0.26 -0.27 + 0.08 -0.07 -0.2 -0.29 -0.26 -0.22 -0.22 -0.22 -0.23 -0.25 -0.26 -0.26 -0.24 + 0.03 0.47 -0.15 -0.1 -0.12 -0.14 -0.14 -0.14 -0.13 -0.13 -0.12 -0.12 -0.13 + 0.08 0.31 0.5 -0.3 -0.03 0.36 0.36 0.28 0.23 0.1 -0.0 0.02 0.17 + 0.0 0.01 0.02 -0.01 -0.22 -0.63 -0.63 -0.45 -0.41 -0.32 -0.24 -0.25 -0.36 + 0.0 0.01 0.01 -0.01 -0.12 -0.17 -0.17 -0.26 -0.24 -0.18 -0.14 -0.14 -0.21 + -0.0 -0.02 -0.03 0.02 -0.66 -0.2 -0.2 -0.29 -0.36 -0.5 -0.63 -0.61 -0.43 + -0.0 -0.01 -0.02 0.01 0.21 -0.12 -0.12 -0.17 -0.28 -0.53 0.18 0.15 -0.03 + -0.0 -0.0 -0.0 0.0 0.03 -0.02 -0.02 -0.03 -0.02 0.01 -0.52 -0.17 -0.09 + -0.0 -0.01 -0.01 0.01 0.11 -0.06 -0.06 -0.09 -0.05 0.02 -0.28 -0.59 -0.31 + -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -1.0 -0.0 -0.0 -0.0 -0.0 -0.0 0.0 + 0.0 0.01 0.02 -0.01 -0.22 0.37 0.37 -0.45 -0.41 -0.32 -0.24 -0.25 -0.36 + 0.0 0.01 0.02 -0.01 -0.21 0.12 0.12 0.17 -0.72 -0.47 -0.18 -0.15 0.03 + 0.0 0.01 0.01 -0.01 -0.14 0.08 0.08 0.12 0.07 -0.03 -0.2 -0.24 -0.6 + 0.0 0.01 0.02 -0.01 -0.21 0.12 0.12 0.17 0.28 -0.47 -0.18 -0.15 0.03 + -0.0 -0.0 -0.0 0.0 0.03 -0.02 -0.02 -0.03 -0.02 0.01 0.48 -0.17 -0.09 + -0.0 -0.01 -0.01 0.01 0.14 -0.08 -0.08 -0.12 -0.07 0.03 0.2 0.24 -0.4 + ] + end + + @testset "_line_outage_factors" begin + instance = UnitCommitment.read(fixture("/case14.json.gz")) + sc = instance.scenarios[1] + isf_before = UnitCommitment._injection_shift_factors( + lines = sc.lines, + buses = sc.buses, + ) + lodf = UnitCommitment._line_outage_factors( + lines = sc.lines, + buses = sc.buses, + isf = isf_before, + ) + for contingency in sc.contingencies + for lc in contingency.lines + prev_susceptance = lc.susceptance + lc.susceptance = 0.0 + isf_after = UnitCommitment._injection_shift_factors( + lines = sc.lines, + buses = sc.buses, + ) + lc.susceptance = prev_susceptance + for lm in sc.lines + expected = isf_after[lm.offset, :] + actual = + isf_before[lm.offset, :] + + lodf[lm.offset, lc.offset] * isf_before[lc.offset, :] + @test norm(expected - actual) < 1e-6 + end + end + end + end +end diff --git a/test/src/transform/initcond_test.jl b/test/src/transform/initcond_test.jl new file mode 100644 index 0000000..24c23dd --- /dev/null +++ b/test/src/transform/initcond_test.jl @@ -0,0 +1,30 @@ +# 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 + +function transform_initcond_test() + @testset "generate_initial_conditions!" begin + # Load instance + instance = UnitCommitment.read(fixture("case118-initcond.json.gz")) + optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + sc = instance.scenarios[1] + # All units should have unknown initial conditions + for g in sc.thermal_units + @test g.initial_power === nothing + @test g.initial_status === nothing + end + + # Generate initial conditions + UnitCommitment.generate_initial_conditions!(sc, optimizer) + + # All units should now have known initial conditions + for g in sc.thermal_units + @test g.initial_power !== nothing + @test g.initial_status !== nothing + end + + # TODO: Check that initial conditions are feasible + end +end diff --git a/test/src/transform/randomize/XavQiuAhm2021_test.jl b/test/src/transform/randomize/XavQiuAhm2021_test.jl new file mode 100644 index 0000000..7819529 --- /dev/null +++ b/test/src/transform/randomize/XavQiuAhm2021_test.jl @@ -0,0 +1,106 @@ +# 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 Random +using UnitCommitment, Cbc, JuMP + +function get_scenario() + return UnitCommitment.read_benchmark( + "matpower/case118/2017-02-01", + ).scenarios[1] +end +system_load(sc) = sum(b.load for b in sc.buses) +test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) + +function transform_randomize_XavQiuAhm2021_test() + @testset "XavQiuAhm2021" begin + @testset "cost and load share" begin + sc = get_scenario() + # Check original costs + unit = sc.thermal_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 = sc.buses[1] + prev_system_load = system_load(sc) + test_approx(bus.load[1] / prev_system_load[1], 0.012) + + randomize!( + sc, + XavQiuAhm2021.Randomization(randomize_load_profile = false), + rng = MersenneTwister(42), + ) + + # 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(sc) + 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 + sc = get_scenario() + # Check original load profile + @test round.(system_load(sc), digits = 1)[1:8] ≈ [ + 3059.5, + 2983.2, + 2937.5, + 2953.9, + 3073.1, + 3356.4, + 4068.5, + 4018.8, + ] + + randomize!( + sc, + XavQiuAhm2021.Randomization(); + rng = MersenneTwister(42), + ) + + # Check randomized load profile + @test round.(system_load(sc), digits = 1)[1:8] ≈ [ + 4854.7, + 4849.2, + 4732.7, + 4848.2, + 4948.4, + 5231.1, + 5874.8, + 5934.8, + ] + end + + @testset "profiled unit cost" begin + sc = UnitCommitment.read( + fixture("case14-profiled.json.gz"), + ).scenarios[1] + # Check original costs + pu1 = sc.profiled_units[1] + pu2 = sc.profiled_units[2] + test_approx(pu1.cost[1], 100.0) + test_approx(pu2.cost[1], 50.0) + randomize!( + sc, + XavQiuAhm2021.Randomization(randomize_load_profile = false), + rng = MersenneTwister(42), + ) + # Check randomized costs + test_approx(pu1.cost[1], 98.039) + test_approx(pu2.cost[1], 48.385) + end + end +end diff --git a/test/src/transform/slice_test.jl b/test/src/transform/slice_test.jl new file mode 100644 index 0000000..affe864 --- /dev/null +++ b/test/src/transform/slice_test.jl @@ -0,0 +1,68 @@ +# 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, LinearAlgebra, Cbc, JuMP, JSON, GZip + +function transform_slice_test() + @testset "slice" begin + instance = UnitCommitment.read(fixture("case14.json.gz")) + modified = UnitCommitment.slice(instance, 1:2) + sc = modified.scenarios[1] + + # Should update all time-dependent fields + @test modified.time == 2 + @test length(sc.power_balance_penalty) == 2 + @test length(sc.reserves_by_name["r1"].amount) == 2 + for u in sc.thermal_units + @test length(u.max_power) == 2 + @test length(u.min_power) == 2 + @test length(u.must_run) == 2 + @test length(u.min_power_cost) == 2 + for s in u.cost_segments + @test length(s.mw) == 2 + @test length(s.cost) == 2 + end + end + for b in sc.buses + @test length(b.load) == 2 + end + for l in sc.lines + @test length(l.normal_flow_limit) == 2 + @test length(l.emergency_flow_limit) == 2 + @test length(l.flow_limit_penalty) == 2 + end + for ps in sc.price_sensitive_loads + @test length(ps.demand) == 2 + @test length(ps.revenue) == 2 + end + + # Should be able to build model without errors + optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + model = UnitCommitment.build_model( + instance = modified, + optimizer = optimizer, + variable_names = true, + ) + end + + @testset "slice profiled units" begin + instance = UnitCommitment.read(fixture("case14-profiled.json.gz")) + modified = UnitCommitment.slice(instance, 1:2) + sc = modified.scenarios[1] + + # Should update all time-dependent fields + for pu in sc.profiled_units + @test length(pu.max_power) == 2 + @test length(pu.min_power) == 2 + end + + # Should be able to build model without errors + optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + model = UnitCommitment.build_model( + instance = modified, + optimizer = optimizer, + variable_names = true, + ) + end +end diff --git a/test/src/usage.jl b/test/src/usage.jl new file mode 100644 index 0000000..ea4eb20 --- /dev/null +++ b/test/src/usage.jl @@ -0,0 +1,66 @@ +# 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, LinearAlgebra, Cbc, JuMP, JSON + +function _set_flow_limits!(instance) + for sc in instance.scenarios + sc.power_balance_penalty = [100_000 for _ in 1:instance.time] + for line in sc.lines, t in 1:4 + line.normal_flow_limit[t] = 10.0 + end + end +end + +function usage_test() + @testset "usage" begin + @testset "deterministic" begin + instance = UnitCommitment.read(fixture("case14.json.gz")) + _set_flow_limits!(instance) + optimizer = + optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + model = UnitCommitment.build_model( + instance = instance, + optimizer = optimizer, + variable_names = true, + ) + @test name(model[:is_on]["g1", 1]) == "is_on[g1,1]" + + # Optimize and retrieve solution + UnitCommitment.optimize!(model) + solution = UnitCommitment.solution(model) + + # Write solution to a file + filename = tempname() + UnitCommitment.write(filename, solution) + loaded = JSON.parsefile(filename) + @test length(loaded["Is on"]) == 6 + + # Verify solution + @test UnitCommitment.validate(instance, solution) + + # Reoptimize with fixed solution + UnitCommitment.fix!(model, solution) + UnitCommitment.optimize!(model) + @test UnitCommitment.validate(instance, solution) + end + + @testset "stochastic" begin + instance = UnitCommitment.read([ + fixture("case14.json.gz"), + fixture("case14.json.gz"), + ]) + _set_flow_limits!(instance) + @test length(instance.scenarios) == 2 + optimizer = + optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + model = UnitCommitment.build_model( + instance = instance, + optimizer = optimizer, + ) + UnitCommitment.optimize!(model) + solution = UnitCommitment.solution(model) + end + end +end diff --git a/test/src/validation/repair_test.jl b/test/src/validation/repair_test.jl new file mode 100644 index 0000000..9d34aeb --- /dev/null +++ b/test/src/validation/repair_test.jl @@ -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, JSON, GZip, DataStructures + +function parse_case14() + return JSON.parse( + GZip.gzopen(fixture("case14.json.gz")), + dicttype = () -> DefaultOrderedDict(nothing), + ) +end + +function validation_repair_test() + @testset "repair!" begin + @testset "Cost curve should be convex" begin + json = parse_case14() + json["Generators"]["g1"]["Production cost curve (MW)"] = + [100, 150, 200] + json["Generators"]["g1"]["Production cost curve (\$)"] = + [10, 25, 30] + sc = UnitCommitment._from_json(json, repair = false) + @test UnitCommitment.repair!(sc) == 4 + end + + @testset "Startup limit must be greater than Pmin" begin + json = parse_case14() + json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150] + json["Generators"]["g1"]["Production cost curve (\$)"] = [100, 150] + json["Generators"]["g1"]["Startup limit (MW)"] = 80 + sc = UnitCommitment._from_json(json, repair = false) + @test UnitCommitment.repair!(sc) == 1 + end + + @testset "Startup costs and delays must be increasing" begin + json = parse_case14() + json["Generators"]["g1"]["Startup costs (\$)"] = [300, 200, 100] + json["Generators"]["g1"]["Startup delays (h)"] = [8, 4, 2] + sc = UnitCommitment._from_json(json, repair = false) + @test UnitCommitment.repair!(sc) == 4 + end + end +end diff --git a/test/transform/initcond_test.jl b/test/transform/initcond_test.jl deleted file mode 100644 index 744eaf7..0000000 --- a/test/transform/initcond_test.jl +++ /dev/null @@ -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 UnitCommitment, Cbc, JuMP - -@testset "generate_initial_conditions!" begin - # Load instance - instance = UnitCommitment.read("$FIXTURES/case118-initcond.json.gz") - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) - sc = instance.scenarios[1] - # All units should have unknown initial conditions - for g in sc.thermal_units - @test g.initial_power === nothing - @test g.initial_status === nothing - end - - # Generate initial conditions - UnitCommitment.generate_initial_conditions!(sc, optimizer) - - # All units should now have known initial conditions - for g in sc.thermal_units - @test g.initial_power !== nothing - @test g.initial_status !== nothing - end - - # TODO: Check that initial conditions are feasible -end diff --git a/test/transform/randomize/XavQiuAhm2021_test.jl b/test/transform/randomize/XavQiuAhm2021_test.jl deleted file mode 100644 index ea85000..0000000 --- a/test/transform/randomize/XavQiuAhm2021_test.jl +++ /dev/null @@ -1,83 +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 Random -using UnitCommitment, Cbc, JuMP - -function get_scenario() - return UnitCommitment.read_benchmark( - "matpower/case118/2017-02-01", - ).scenarios[1] -end -system_load(sc) = sum(b.load for b in sc.buses) -test_approx(x, y) = @test isapprox(x, y, atol = 1e-3) - -@testset "XavQiuAhm2021" begin - @testset "cost and load share" begin - sc = get_scenario() - # Check original costs - unit = sc.thermal_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 = sc.buses[1] - prev_system_load = system_load(sc) - test_approx(bus.load[1] / prev_system_load[1], 0.012) - - randomize!( - sc, - XavQiuAhm2021.Randomization(randomize_load_profile = false), - rng = MersenneTwister(42), - ) - - # 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(sc) - 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 - sc = get_scenario() - # Check original load profile - @test round.(system_load(sc), digits = 1)[1:8] ≈ - [3059.5, 2983.2, 2937.5, 2953.9, 3073.1, 3356.4, 4068.5, 4018.8] - - randomize!(sc, XavQiuAhm2021.Randomization(); rng = MersenneTwister(42)) - - # Check randomized load profile - @test round.(system_load(sc), digits = 1)[1:8] ≈ - [4854.7, 4849.2, 4732.7, 4848.2, 4948.4, 5231.1, 5874.8, 5934.8] - end - - @testset "profiled unit cost" begin - sc = - UnitCommitment.read("$FIXTURES/case14-profiled.json.gz").scenarios[1] - # Check original costs - pu1 = sc.profiled_units[1] - pu2 = sc.profiled_units[2] - test_approx(pu1.cost[1], 100.0) - test_approx(pu2.cost[1], 50.0) - randomize!( - sc, - XavQiuAhm2021.Randomization(randomize_load_profile = false), - rng = MersenneTwister(42), - ) - # Check randomized costs - test_approx(pu1.cost[1], 98.039) - test_approx(pu2.cost[1], 48.385) - end -end diff --git a/test/transform/slice_test.jl b/test/transform/slice_test.jl deleted file mode 100644 index 6519cbd..0000000 --- a/test/transform/slice_test.jl +++ /dev/null @@ -1,66 +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, LinearAlgebra, Cbc, JuMP, JSON, GZip - -@testset "slice" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - modified = UnitCommitment.slice(instance, 1:2) - sc = modified.scenarios[1] - - # Should update all time-dependent fields - @test modified.time == 2 - @test length(sc.power_balance_penalty) == 2 - @test length(sc.reserves_by_name["r1"].amount) == 2 - for u in sc.thermal_units - @test length(u.max_power) == 2 - @test length(u.min_power) == 2 - @test length(u.must_run) == 2 - @test length(u.min_power_cost) == 2 - for s in u.cost_segments - @test length(s.mw) == 2 - @test length(s.cost) == 2 - end - end - for b in sc.buses - @test length(b.load) == 2 - end - for l in sc.lines - @test length(l.normal_flow_limit) == 2 - @test length(l.emergency_flow_limit) == 2 - @test length(l.flow_limit_penalty) == 2 - end - for ps in sc.price_sensitive_loads - @test length(ps.demand) == 2 - @test length(ps.revenue) == 2 - end - - # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) - model = UnitCommitment.build_model( - instance = modified, - optimizer = optimizer, - variable_names = true, - ) -end - -@testset "slice profiled units" begin - instance = UnitCommitment.read("$FIXTURES/case14-profiled.json.gz") - modified = UnitCommitment.slice(instance, 1:2) - sc = modified.scenarios[1] - - # Should update all time-dependent fields - for pu in sc.profiled_units - @test length(pu.max_power) == 2 - @test length(pu.min_power) == 2 - end - - # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) - model = UnitCommitment.build_model( - instance = modified, - optimizer = optimizer, - variable_names = true, - ) -end diff --git a/test/usage.jl b/test/usage.jl deleted file mode 100644 index 63b3f36..0000000 --- a/test/usage.jl +++ /dev/null @@ -1,62 +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, LinearAlgebra, Cbc, JuMP, JSON - -function _set_flow_limits!(instance) - for sc in instance.scenarios - sc.power_balance_penalty = [100_000 for _ in 1:instance.time] - for line in sc.lines, t in 1:4 - line.normal_flow_limit[t] = 10.0 - end - end -end - -@testset "usage" begin - @testset "deterministic" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") - _set_flow_limits!(instance) - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) - model = UnitCommitment.build_model( - instance = instance, - optimizer = optimizer, - variable_names = true, - ) - @test name(model[:is_on]["g1", 1]) == "is_on[g1,1]" - - # Optimize and retrieve solution - UnitCommitment.optimize!(model) - solution = UnitCommitment.solution(model) - - # Write solution to a file - filename = tempname() - UnitCommitment.write(filename, solution) - loaded = JSON.parsefile(filename) - @test length(loaded["Is on"]) == 6 - - # Verify solution - @test UnitCommitment.validate(instance, solution) - - # Reoptimize with fixed solution - UnitCommitment.fix!(model, solution) - UnitCommitment.optimize!(model) - @test UnitCommitment.validate(instance, solution) - end - - @testset "stochastic" begin - instance = UnitCommitment.read([ - "$FIXTURES/case14.json.gz", - "$FIXTURES/case14.json.gz", - ]) - _set_flow_limits!(instance) - @test length(instance.scenarios) == 2 - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) - model = UnitCommitment.build_model( - instance = instance, - optimizer = optimizer, - ) - UnitCommitment.optimize!(model) - solution = UnitCommitment.solution(model) - end -end diff --git a/test/validation/repair_test.jl b/test/validation/repair_test.jl deleted file mode 100644 index 060ca9f..0000000 --- a/test/validation/repair_test.jl +++ /dev/null @@ -1,39 +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, JSON, GZip, DataStructures - -function parse_case14() - return JSON.parse( - GZip.gzopen("$FIXTURES/case14.json.gz"), - dicttype = () -> DefaultOrderedDict(nothing), - ) -end - -@testset "repair!" begin - @testset "Cost curve should be convex" begin - json = parse_case14() - json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150, 200] - json["Generators"]["g1"]["Production cost curve (\$)"] = [10, 25, 30] - sc = UnitCommitment._from_json(json, repair = false) - @test UnitCommitment.repair!(sc) == 4 - end - - @testset "Startup limit must be greater than Pmin" begin - json = parse_case14() - json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150] - json["Generators"]["g1"]["Production cost curve (\$)"] = [100, 150] - json["Generators"]["g1"]["Startup limit (MW)"] = 80 - sc = UnitCommitment._from_json(json, repair = false) - @test UnitCommitment.repair!(sc) == 1 - end - - @testset "Startup costs and delays must be increasing" begin - json = parse_case14() - json["Generators"]["g1"]["Startup costs (\$)"] = [300, 200, 100] - json["Generators"]["g1"]["Startup delays (h)"] = [8, 4, 2] - sc = UnitCommitment._from_json(json, repair = false) - @test UnitCommitment.repair!(sc) == 4 - end -end