From a51dafd422ced75b1ea2478b14677efee98328f1 Mon Sep 17 00:00:00 2001 From: Jun He Date: Thu, 10 Aug 2023 16:44:07 -0400 Subject: [PATCH] interface --- src/UnitCommitment.jl | 1 + src/instance/read.jl | 43 ++++++++++++++++ src/instance/structs.jl | 13 +++++ src/model/build.jl | 3 ++ src/model/formulations/base/interface.jl | 46 ++++++++++++++++++ src/model/formulations/base/line.jl | 11 +++++ src/model/formulations/base/sensitivity.jl | 23 +++++++++ src/transform/slice.jl | 5 ++ test/fixtures/case14-interface.json.gz | Bin 0 -> 1956 bytes test/fixtures/case3-interface-2.json.gz | Bin 0 -> 705 bytes test/fixtures/case3-interface.json.gz | Bin 0 -> 700 bytes test/src/UnitCommitmentT.jl | 2 + test/src/instance/read_test.jl | 16 ++++++ test/src/model/interface_optimization_test.jl | 40 +++++++++++++++ .../XavQiuWanThi19/sensitivity_test.jl | 15 ++++++ test/src/transform/slice_test.jl | 41 ++++++++++++++-- 16 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 src/model/formulations/base/interface.jl create mode 100644 test/fixtures/case14-interface.json.gz create mode 100644 test/fixtures/case3-interface-2.json.gz create mode 100644 test/fixtures/case3-interface.json.gz create mode 100644 test/src/model/interface_optimization_test.jl diff --git a/src/UnitCommitment.jl b/src/UnitCommitment.jl index d46d71e..2a85320 100644 --- a/src/UnitCommitment.jl +++ b/src/UnitCommitment.jl @@ -37,6 +37,7 @@ include("model/formulations/base/system.jl") include("model/formulations/base/unit.jl") include("model/formulations/base/punit.jl") include("model/formulations/base/storage.jl") +include("model/formulations/base/interface.jl") include("model/formulations/CarArr2006/pwlcosts.jl") include("model/formulations/DamKucRajAta2016/ramp.jl") include("model/formulations/Gar1962/pwlcosts.jl") diff --git a/src/instance/read.jl b/src/instance/read.jl index 62b0230..12c9733 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -137,6 +137,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario reserves = Reserve[] profiled_units = ProfiledUnit[] storage_units = StorageUnit[] + interfaces = Interface[] function scalar(x; default = nothing) x !== nothing || return default @@ -452,6 +453,45 @@ function _from_json(json; repair = true)::UnitCommitmentScenario end end + # Read interfaces + if "Interfaces" in keys(json) + for (int_name, dict) in json["Interfaces"] + outbound_lines = TransmissionLine[] + inbound_lines = TransmissionLine[] + if "Outbound lines" in keys(dict) + outbound_lines = [ + name_to_line[l] for + l in scalar(dict["Outbound lines"], default = []) + ] + end + if "Inbound lines" in keys(dict) + inbound_lines = [ + name_to_line[l] for + l in scalar(dict["Inbound lines"], default = []) + ] + end + interface = Interface( + int_name, + length(interfaces) + 1, + outbound_lines, + inbound_lines, + timeseries( + dict["Net flow upper limit (MW)"], + default = [1e8 for t in 1:T], + ), + timeseries( + dict["Net flow lower limit (MW)"], + default = [-1e8 for t in 1:T], + ), + timeseries( + dict["Flow limit penalty (\$/MW)"], + default = [5000.0 for t in 1:T], + ), + ) + push!(interfaces, interface) + end + end + scenario = UnitCommitmentScenario( name = scenario_name, probability = probability, @@ -474,8 +514,11 @@ function _from_json(json; repair = true)::UnitCommitmentScenario profiled_units = profiled_units, storage_units_by_name = Dict(su.name => su for su in storage_units), storage_units = storage_units, + interfaces_by_name = Dict(i.name => i for i in interfaces), + interfaces = interfaces, isf = spzeros(Float64, length(lines), length(buses) - 1), lodf = spzeros(Float64, length(lines), length(lines)), + interface_isf = spzeros(Float64, length(interfaces), length(buses) - 1), ) if repair UnitCommitment.repair!(scenario) diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 4082fb0..bbb0bcd 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -103,6 +103,16 @@ mutable struct StorageUnit max_ending_level::Float64 end +mutable struct Interface + name::String + offset::Int + outbound_lines::Vector{TransmissionLine} + inbound_lines::Vector{TransmissionLine} + net_flow_upper_limit::Vector{Float64} + net_flow_lower_limit::Vector{Float64} + flow_limit_penalty::Vector{Float64} +end + Base.@kwdef mutable struct UnitCommitmentScenario buses_by_name::Dict{AbstractString,Bus} buses::Vector{Bus} @@ -125,6 +135,9 @@ Base.@kwdef mutable struct UnitCommitmentScenario thermal_units::Vector{ThermalUnit} storage_units_by_name::Dict{AbstractString,StorageUnit} storage_units::Vector{StorageUnit} + interfaces_by_name::Dict{AbstractString,Interface} + interfaces::Vector{Interface} + interface_isf::Array{Float64,2} time::Int time_step::Int end diff --git a/src/model/build.jl b/src/model/build.jl index 0e27d42..089db92 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -103,6 +103,9 @@ function build_model(; _add_storage_unit!(model, su, sc) end _add_system_wide_eqs!(model, sc) + for ifc in sc.interfaces + _add_interface!(model, ifc, formulation.transmission, sc) + end end @objective(model, Min, model[:obj]) end diff --git a/src/model/formulations/base/interface.jl b/src/model/formulations/base/interface.jl new file mode 100644 index 0000000..00c2b8e --- /dev/null +++ b/src/model/formulations/base/interface.jl @@ -0,0 +1,46 @@ +# 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. + +function _add_interface!( + model::JuMP.Model, + ifc::Interface, + f::ShiftFactorsFormulation, + sc::UnitCommitmentScenario, +)::Nothing + overflow = _init(model, :interface_overflow) + net_injection = _init(model, :net_injection) + for t in 1:model[:instance].time + # define the net flow variable + flow = @variable(model, base_name = "interface_flow[$(ifc.name),$t]") + # define the overflow variable + overflow[sc.name, ifc.name, t] = @variable(model, lower_bound = 0) + # constraints: lb - v <= flow <= ub + v + @constraint( + model, + flow <= + ifc.net_flow_upper_limit[t] + overflow[sc.name, ifc.name, t] + ) + @constraint( + model, + -flow <= + -ifc.net_flow_lower_limit[t] + overflow[sc.name, ifc.name, t] + ) + # constraint: flow value is calculated from the interface ISF matrix + @constraint( + model, + flow == sum( + net_injection[sc.name, b.name, t] * + sc.interface_isf[ifc.offset, b.offset] for + b in sc.buses if b.offset > 0 + ) + ) + # make overflow part of the objective as a punishment term + add_to_expression!( + model[:obj], + overflow[sc.name, ifc.name, t], + ifc.flow_limit_penalty[t] * sc.probability, + ) + end + return +end diff --git a/src/model/formulations/base/line.jl b/src/model/formulations/base/line.jl index 98d5f2c..52eb199 100644 --- a/src/model/formulations/base/line.jl +++ b/src/model/formulations/base/line.jl @@ -26,9 +26,11 @@ function _setup_transmission( )::Nothing isf = formulation.precomputed_isf lodf = formulation.precomputed_lodf + interface_isf = nothing if length(sc.buses) == 1 isf = zeros(0, 0) lodf = zeros(0, 0) + interface_isf = zeros(0, 0) elseif isf === nothing @info "Computing injection shift factors..." time_isf = @elapsed begin @@ -36,6 +38,11 @@ function _setup_transmission( buses = sc.buses, lines = sc.lines, ) + interface_isf = + UnitCommitment._interface_injection_shift_factors( + interfaces = sc.interfaces, + isf = isf, + ) end @info @sprintf("Computed ISF in %.2f seconds", time_isf) @info "Computing line outage factors..." @@ -53,9 +60,13 @@ function _setup_transmission( formulation.lodf_cutoff ) isf[abs.(isf).N37a%N#;WiD!SZ*BnXT1{`;Mi4zWzd{gl zNC9WNyR)CY6zL}dG-(hU?IGxamT8*+MG6#|)D80AJEU#dB8TEgj@uln0-(@j- zcKJ1$nC|JPYoqxuX^~b*UEcWHyzxS9{l8oLzG;o-r|RA(T!X60vu05vgxR88*Rw@a zy-#P)Uj5O2-n%W^_NAme8ZIho5y(Zmwe}|u&C?MuhD772RjtWY^sp1qah+6kvzncy zd2+SBIR*j=;d;0PtdD9pz8>GRlu}T;w)DF0?00_f%Ob0@B%iJ8q;78a_|$J`4G|w)w z(=5;GEArUqm6~rKE?wy`Ug_}pD;;3C<{VM!6TJT5N>@sO@Rpzea8XF2(ji18EUzLD zsPy4dc*J(2N7ms$Qt$(BW28V>lB7V{k)%KvzG^Tfs;2l3AJb_53~>4b_BBK1fK1=N5QqcA2Y!vRb5rQKky7a*-AqGv&bU^D^M zDgi1$Af8A(3y`e167;(pY7{NOQ8H0j=P5WrC>sr-HF1j4U^E9N!7vlY55j1#sngfO z;do8p!6#B1GA)z{8e-r;^#>MXIPiUO-_U>+;2d>QfTe0pBuW*Oa9)w`!GbYhffE`f z_dZ4`JTe4z2P5%wttAje;hZ)^pO3;yWfTkP1f>p<8l)5_iL3$<5hu0ot#{P*o^a8` z$bz{ygCo%>q#8mPiQt_fA7DyQ%UCJFvQX|%-bm2FkTq(BM%E(;T44u@;E064C)#3f zVqx8zyOkFK*_Jp++=Y+MQdq5s5!sS_;z5pM-O>UlRZ^^%*}A>?$6>VS3xk}<;^VTZ z7U}GC>>~dpsV>rb^L%j2`Zi4#byA>IE9Cnlk`SToTAVBxqxZ-A)8-UbE--rK<7GLGDU ziaz>D&_M*{l#Xbuol+*!pox;)6SU!h_70{a9Yu%%nKD9DF6yXh(1fts6ErVX6l$*i z1?~_uc1sD~S_y!?T6$+`)P&CJp&p$7fuUBiU-2YrEm3rXiN<*g-qEND$<;$WKy^^8 zrO|4d+Kj?U!A0q!^#Qc?G-yKa?FriRxI{*!;@yi=mPjGeS%-gBG}6!;AcW_h8;Gn8 zSVS?M;ec~nK+who*`~T12|J}hd$0$|(1Sg=TcqP|wcIc6PDE|NNf~jh(=ft@lr-&a zU;wbU0X67SBP1RoBU!vRIL2$+)+cJS5!BPIa}qUAiy)9NU8E>?wZ<$_wiS&Q13!X$ zx?@h_X0}Y!kkdJWb1g)VykZ=RruJLk5!};Fb3h+G+=HvTv8Ji)B<$GU^tAaDkjd>h z>gldIg__4HN^wLgit*r3Re6m~7PK>C$vxdR2eA4{B6%Gs^hY#GTT6jF=mRo0BA056 z38&lUBx+u_M-ct11F|G>PL??AJ004d6-N4wn(my#XdcRdx_@C$CbtYYvV-;7;h(W2 zneLZEhC7f-Vq|DSWNuI)U^Oa@^Vdk0=;?krM0Hr2fC-FA$F^6{5@nUff&|d_ehRid z9ltE7U^@2Ll&w zVDuUT(@m+(YL!;ovugWw_?UU#ea*0C{=EIQ)*hp6D?{#&wp-!)p)BgGXs76`A2BLk zFV4@?MV;Q;?AG(nCTD$pv>>Db%e!WICmJ}*JFvVHZI?=s%()yU#G=7tF!kh zw&vul*D_b@9lxF@RNVWtXwt1q=)tA+kLfZgHeWMxZWgmFDhuYvZNVtHbT($~GrvWw{|T!^~d3m10#UN;7m4Q79)V^#Re6f|m zQes|Yv@n;I!zj1YVL&8{dW*5xWJOh}i8qaGXO%@l z@&jD~x444jmS2Tebts?Hg3ocrkn%L&I!ZM9?H?q7)7R&46afXY}Rbdt10 z=!`R11b>JK?`%-72+{LN09fn~eK1gh&@M3~!Tg`upw%tcTz}@JII+8grU}p|-%1sW zlCSE%x;O3Aoo^kG-vQld9joNZJlFHBm15^cIH6fJ4O{3-WZx@i<(TWISB{T@E6Ue| zC)8F1(&H*kNuo$2)$ZhF{gb!h#sp}{LA(2db_cvZX!l^)t#P=9EX5?C*zucZkyFmK n(sl=3W!gG*X%S%ch5o0ZQp{Mlv^DiwFn+N7ZBi17l%xWiu^lZggdGW?^GxE^2dcZUD_!U2oGc6n&<@!isnp zQdja>THXc{XcCNRt>9te!AYIgl4D1X!&IjJ_t|NiCeBS2(1yrcuaE6>&pr1heHfv$EU}nPFxx-%7D66QvoVL=*}orTj(E#n&H( z&V37O9(UXcMJ^EZTrdh^eT{7ee-7jLdN9Snh3ObR5TOW;hbDZU>Ox_u${ei-!`tFe z)h&T+w>7G@S&22JV_-^u5qNdv)2>^@zp$`U15ZWb%B$)p*8??IRn(NQJ6eo6<&@=)1ur|z=0R1-Tbbrw4fRzWG9t?Xa9NsdPViHj7_(`+KDd$>gyFsop iZJ)Zb9^mAK{v)AM%viUy9%$szZuA7Jc*+a63;+P?xL0!k literal 0 HcmV?d00001 diff --git a/test/src/UnitCommitmentT.jl b/test/src/UnitCommitmentT.jl index 77ee669..5d92195 100644 --- a/test/src/UnitCommitmentT.jl +++ b/test/src/UnitCommitmentT.jl @@ -10,6 +10,7 @@ include("instance/read_test.jl") include("instance/migrate_test.jl") include("model/formulations_test.jl") include("model/storage_optimization_test.jl") +include("model/interface_optimization_test.jl") include("solution/methods/XavQiuWanThi19/filter_test.jl") include("solution/methods/XavQiuWanThi19/find_test.jl") include("solution/methods/XavQiuWanThi19/sensitivity_test.jl") @@ -41,6 +42,7 @@ function runtests() instance_migrate_test() model_formulations_test() storage_optimization_test() + interface_optimization_test() solution_methods_XavQiuWanThi19_filter_test() solution_methods_XavQiuWanThi19_find_test() solution_methods_XavQiuWanThi19_sensitivity_test() diff --git a/test/src/instance/read_test.jl b/test/src/instance/read_test.jl index bcb3eb4..cc377d9 100644 --- a/test/src/instance/read_test.jl +++ b/test/src/instance/read_test.jl @@ -224,4 +224,20 @@ function instance_read_test() @test su4.simultaneous_charge_and_discharge == [false, false, true, true] end + + @testset "read_benchmark interface" begin + instance = UnitCommitment.read(fixture("case14-interface.json.gz")) + sc = instance.scenarios[1] + @test length(sc.interfaces) == 1 + @test sc.interfaces_by_name["ifc1"].name == "ifc1" + + ifc = sc.interfaces[1] + @test ifc.name == "ifc1" + @test ifc.offset == 1 + @test length(ifc.outbound_lines) == 6 + @test length(ifc.inbound_lines) == 1 + @test ifc.net_flow_upper_limit == [2000 for t in 1:4] + @test ifc.net_flow_lower_limit == [-1500 for t in 1:4] + @test ifc.flow_limit_penalty == [9999.0 for t in 1:4] + end end diff --git a/test/src/model/interface_optimization_test.jl b/test/src/model/interface_optimization_test.jl new file mode 100644 index 0000000..80ad5c8 --- /dev/null +++ b/test/src/model/interface_optimization_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 +using JuMP +using HiGHS +using JSON + +function interface_optimization_test() + @testset "interface_optimization" begin + # case3-interface: only outbounds + instance = UnitCommitment.read(fixture("case3-interface.json.gz")) + model = UnitCommitment.build_model( + instance = instance, + optimizer = HiGHS.Optimizer, + variable_names = true, + ) + set_silent(model) + UnitCommitment.optimize!(model) + @test value(variable_by_name(model, "interface_flow[ifc1,3]")) ≈ 20.0 atol = + 0.1 + @test value(variable_by_name(model, "interface_flow[ifc1,4]")) ≈ 20.0 atol = + 0.1 + + # case3-interface-2: one outbound, one inbound + instance = UnitCommitment.read(fixture("case3-interface-2.json.gz")) + model = UnitCommitment.build_model( + instance = instance, + optimizer = HiGHS.Optimizer, + variable_names = true, + ) + set_silent(model) + UnitCommitment.optimize!(model) + @test value(variable_by_name(model, "interface_flow[ifc1,1]")) ≈ 95.0 atol = + 0.1 + @test value(variable_by_name(model, "interface_flow[ifc1,2]")) ≈ 95.0 atol = + 0.1 + end +end diff --git a/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl b/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl index 0eef038..318b69d 100644 --- a/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl +++ b/test/src/solution/methods/XavQiuWanThi19/sensitivity_test.jl @@ -146,4 +146,19 @@ function solution_methods_XavQiuWanThi19_sensitivity_test() end end end + + @testset "_interface_injection_shift_factors" begin + instance = UnitCommitment.read(fixture("/case14-interface.json.gz")) + sc = instance.scenarios[1] + actual = UnitCommitment._interface_injection_shift_factors( + interfaces = sc.interfaces, + isf = UnitCommitment._injection_shift_factors( + lines = sc.lines, + buses = sc.buses, + ), + ) + @test size(actual) == (1, 13) + @test round.(collect(actual), digits = 2) == + [0.0 -1.0 0.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0] + end end diff --git a/test/src/transform/slice_test.jl b/test/src/transform/slice_test.jl index fdd86b4..f0e6a6e 100644 --- a/test/src/transform/slice_test.jl +++ b/test/src/transform/slice_test.jl @@ -2,7 +2,7 @@ # 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 +using UnitCommitment, LinearAlgebra, HiGHS, JuMP, JSON, GZip function transform_slice_test() @testset "slice" begin @@ -38,7 +38,10 @@ function transform_slice_test() end # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) model = UnitCommitment.build_model( instance = modified, optimizer = optimizer, @@ -58,7 +61,10 @@ function transform_slice_test() end # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) model = UnitCommitment.build_model( instance = modified, optimizer = optimizer, @@ -88,7 +94,34 @@ function transform_slice_test() end # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) + model = UnitCommitment.build_model( + instance = modified, + optimizer = optimizer, + variable_names = true, + ) + end + + @testset "slice interfaces" begin + instance = UnitCommitment.read(fixture("case14-interface.json.gz")) + modified = UnitCommitment.slice(instance, 1:3) + sc = modified.scenarios[1] + + # Should update all time-dependent fields + for ifc in sc.interfaces + @test length(ifc.net_flow_upper_limit) == 3 + @test length(ifc.net_flow_lower_limit) == 3 + @test length(ifc.flow_limit_penalty) == 3 + end + + # Should be able to build model without errors + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) model = UnitCommitment.build_model( instance = modified, optimizer = optimizer,