From 4947bff4609319af6cebc1b2a94c2efeaaabf0ff Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Tue, 9 Mar 2021 10:53:10 -0600 Subject: [PATCH] Implement sub-hourly commitment --- instances/test/case14-sub-hourly.json.gz | Bin 0 -> 1802 bytes instances/test/case14.json.gz | Bin 1765 -> 1775 bytes src/instance.jl | 77 ++++++++++++++--------- test/instance_test.jl | 14 +++++ 4 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 instances/test/case14-sub-hourly.json.gz diff --git a/instances/test/case14-sub-hourly.json.gz b/instances/test/case14-sub-hourly.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b788e4ddf9dd2052b34a74d47d2caad531a1467a GIT binary patch literal 1802 zcmV+l2leHo zo5v;}&l3Ecze=lglQmgYpMR5l+&$Ypf0J*r^kz&-eRHr$D*xTkG97KIyBtY|^S}x5+A7r+4+e58f&i zUL-Ik+#Oawo+G6c-AQ);Q}2Gqtc+nZx6WEp?vg*Nl`FQZB z#S`!9aqsGR-bJ9sFOp~wxEXlmsAG5jv`)yK4gdkD$V0ROBHO)&4TRjP{7_6vK7g2%yJ;Ji!k2lGZ`uu_Y&uHq?KdMroyKEg>7RSC zG+*B(9RaWJx}$f(MY~=fm9IO~YFd(x(QrIMhaq#Dud+I+w|P5Qi6~qDM87Rlpq9Ow&=*JEk?qEWQ^i5W+3)2GsgXQ8Dqv7$RCzM4S0rnXVpH3ED|lL<9A7-`j(w z?xV5KQC9oKaK#AYw9>>12pzq)#EKSChUgC#-qBEr8h0G3Qm7rYgWpTS~2xX&D)PmGtGzTU@=ZV)3!f3Clx37i6hMmBJD^eRWER+ZuV&FjaCz3IoxG&Ci z4d7_v9QCGfdePQIf~uf|^NQSqgfU=&;~FJrzeXuMW(XqOa77tw2}Dsir;Vc)tW-v^ zkWQr3AyR`W#YrNnKt#kzJ@?i-g7P3-G%>PZ&d%VNXcVTJ;i*mp?+m$sAwfMxrUc7E zIX`)0f)0k{s1+Ks9+99GR-g!u2nbx!7Hbm=>+IaEya#h@7=S9CUSQAlvS+>=`h4;XMNeHwvTyD*EUrK?f0(Q#zusc1oE@gC;0B6SU!h_70{! z9Yu%%Gi8LRT+~t1pozw6CTL!$DD1hyBxtOb61=q%0Bg1M&eEufKC6d%@cvJ9wUT|t zlc=@CrW;H&&Rg(~MoqL_J=6nKC;3_$t)^3(Q8+2MC|$HZfVQ3nP4s&+L3>^bF+m4^un~k8JE}fI8 zdA$e%3DYFSc2{eR5@oxl(W2u=a8FmvN!-kmi9O_WjNn`g(PLgQ4x6U#-v+>?zJO=Txx$L^-5i>H8@+>WE3u9{P*c{xQXPLqnwcyQQNd5uLDv@>JDJzX{j zu=+tFc@-%1M>MLpmICvj516?Txm07BaJpCn}zFj9Ba zbmbg+^VAHeyJcx7(lATAV`(SSB}=Bv1dzbmj4tYEF6jd5oo+NS=NkXQ{S{r=Q0ws=wjs=P`@w zM0omn+@d-Lo_-#?sM5~U&*K+WgLwsIoL>i}@>P{Dvln$%)OnNNX34rtSG|(Ct&jX? sMxrgb&5AZVxK$n8!v2_T(&F)E-5%V!?%{P`XP?gg20bW!61XV<0Pogyi~s-t literal 0 HcmV?d00001 diff --git a/instances/test/case14.json.gz b/instances/test/case14.json.gz index 99f1c258b003dc7977fe297a09655bce42bbe5ad..11a33a8c61ceb7d5fd7fdb7f90e93cd88cdcec27 100644 GIT binary patch delta 1187 zcmV;U1YG;&4et#IABzYG#-B%Ekq9t<*aRfXAStI#kpJGH{D?&k#i5WEMU_B?xwEt1 z4CkB2CLf<<^f`H%*ZI0=i@G`aHv71HwtN1%To>8prY=8hs_fb2*R*PPtDhFP8-FdT zqR!h*f8z!0{l0tl!?qJTIfIkM{=&<8bG}`+C7rR{G;Oxr*4IV$?8Tqmd2c{}>qV9U z&^O@j%e|E;czV0vb_&3E-^T2|=}BM3X`9#W_9{CsR{2eHXX_gv;YEgH!rfub!#P@O z)t%&Sf12IzwDqgqp5LmnE%Q~@w0XO|Kj3+1Tut=A6+e_A~9t{L~Pnde;s zYQrK+mVn!VR}MOM=TGOA-swPpR7i>fCZ{lo-D^d@#XH~Hd!O!Yd6loPGTO2#*JXQe zS&FoQkvreKuO=n$K}^bBZd;MU7iCqhw`)S#c9goK(uh!}@sL>l*R5Dyu5Pl9fEPF2 z(L3R4yILKTZ#vT&UXqW|csN3bA#+`x7fse&l~q+%7g=321%*xX9Z9!;^|lz>P;IK` zqnmgh;5ZL(HV+UNnfJtlVRo3}0RW&1jtGH1DNQ`Um_WJF=pOQ5k;PeQ#@zixIVFOY zQi73WDZ$CHlwbs3Y{`-Jwi*cwmN6h>%s}j4W{m&uGRBTG#?NESv2;1am4BdAx^aW! z|I%Rp#SETpsNUtGFWB>c?Dd*tMbT?B&zLsz_f3BOpi1#hI%68B$NS#wEp;D5eGWJi z7Q+>*tT$ko6;vh##!+n&VV#}3A*e{%mU)q6MM&OpSe;6VvL(9`P>*BX>jhrd zdDX1Trs+D7Rax~LgB26Sr<-lPEV8q)YpBw}A2lvhQ*Z1aj#JlkBZo4eHH z5(NGE(;q!fx6QJ+y4!Zz?J-nNlaF49fW!~ouhS=gZ0a@jMDJFc_xnQ>>fsdi^O}a4 zqFUZC@|KbR<+IdPQRSu&GX^%h?D_2ko&zX39ZHgTXS007lI BV5a~8 delta 1196 zcmV;d1XKI(4do38ABzYG5{{r>kq9tcmg*>YRo&~Y#R?9O`wS}(E;fW85LcVF(U zOu^Ire%mPk-+ddi`=%#-73XbUx7+LNvRLJ}&7G}pfP@zrjtO^%H4o=#tyOoD+y6AX z-)ZYtyFI^EWn1Q}tZDOhdw;<5&bXey_2xrS-&?OA4*s-w@dXx06-NS5dwWuns|UQfpVkKJ>2i!K|3Im9;|9n7rNRD-89dui zy~{;ku;)4GHOY#i*JfUSFm2}VoBZ-YmExUr!8A~h_q{n->OO}09B?KqhAUQCZ@@4s zs7wmRF)KzT9aA`1c*{d&YTR+C0Hhs_$Kko4y@I4MaZ8{8kufkOWo-m20n4P9<6vD- zsGVcH=TOCH4FlDILWmfd6;1B~waBkbVmJ;psgB8ZcxqJ62M$huRN#^YZb2KgMgo(e z^Tg{%Wlb>L+c(Nn!_MHL6>TgV7NDZR6eUo@iDZl??n`rBgEX3W&%G&{UW~Jep(=v% z0oXl67>f=it^qy!HEI;pn3%suLs#%v!({&=N zvg$VmD<+E1H`{tyWEW%CP_Og)s%Rh92Nx-?ihSAT6{&brwt2s9?oyLW5cKCyfAl!t zHp}ArZrgdc$51&*X-RotTr~1s66gOwdNbq~p3PXiaUEAwnV=e3VNL zrsE$sRa;iwSXlN4<0Vr=zkm0xShmIG!yr8z^}B)k;FBx{L;+-zaRxd=gHrjjE|fjdkr(&H~4>#-X;MR2yulsuP>B-;a K?bAM(DF6V>T~x{d diff --git a/src/instance.jl b/src/instance.jl index 558193a..5028f68 100644 --- a/src/instance.jl +++ b/src/instance.jl @@ -97,12 +97,14 @@ end function Base.show(io::IO, instance::UnitCommitmentInstance) - print(io, "UnitCommitmentInstance with ") + print(io, "UnitCommitmentInstance(") print(io, "$(length(instance.units)) units, ") print(io, "$(length(instance.buses)) buses, ") print(io, "$(length(instance.lines)) lines, ") print(io, "$(length(instance.contingencies)) contingencies, ") - print(io, "$(length(instance.price_sensitive_loads)) price sensitive loads") + print(io, "$(length(instance.price_sensitive_loads)) price sensitive loads, ") + print(io, "$(instance.time) time steps") + print(io, ")") end @@ -131,7 +133,21 @@ function from_json(json; fix=true) contingencies = Contingency[] lines = TransmissionLine[] loads = PriceSensitiveLoad[] - T = json["Parameters"]["Time (h)"] + + function scalar(x; default=nothing) + x !== nothing || return default + x + end + + time_horizon = json["Parameters"]["Time (h)"] + if time_horizon === nothing + time_horizon = json["Parameters"]["Time horizon (h)"] + end + time_horizon !== nothing || error("Missing required parameter: Time horizon (h)") + time_step = scalar(json["Parameters"]["Time step (min)"], default=60) + (60 % time_step == 0) || error("Time step $time_step is not a divisor of 60") + time_multiplier = 60 รท time_step + T = time_horizon * time_multiplier name_to_bus = Dict{String, Bus}() name_to_line = Dict{String, TransmissionLine}() @@ -143,11 +159,6 @@ function from_json(json; fix=true) return x end - function scalar(x; default=nothing) - x !== nothing || return default - x - end - # Read parameters power_balance_penalty = timeseries(json["Parameters"]["Power balance penalty (\$/MW)"], default=[1000.0 for t in 1:T]) @@ -187,8 +198,13 @@ function from_json(json; fix=true) startup_costs = scalar(dict["Startup costs (\$)"], default=[0.]) startup_categories = StartupCategory[] for k in 1:length(startup_delays) - push!(startup_categories, StartupCategory(startup_delays[k], - startup_costs[k])) + push!( + startup_categories, + StartupCategory( + startup_delays[k] .* time_multiplier, + startup_costs[k], + ), + ) end # Read and validate initial conditions @@ -202,26 +218,31 @@ function from_json(json; fix=true) if initial_status < 0 && initial_power > 1e-3 error("unit $unit_name has invalid initial power") end + initial_status *= time_multiplier end - unit = Unit(unit_name, - bus, - max_power, - min_power, - timeseries(dict["Must run?"], default=[false for t in 1:T]), - min_power_cost, - segments, - scalar(dict["Minimum uptime (h)"], default=1), - scalar(dict["Minimum downtime (h)"], default=1), - scalar(dict["Ramp up limit (MW)"], default=1e6), - scalar(dict["Ramp down limit (MW)"], default=1e6), - scalar(dict["Startup limit (MW)"], default=1e6), - scalar(dict["Shutdown limit (MW)"], default=1e6), - initial_status, - initial_power, - timeseries(dict["Provides spinning reserves?"], - default=[true for t in 1:T]), - startup_categories) + unit = Unit( + unit_name, + bus, + max_power, + min_power, + timeseries(dict["Must run?"], default=[false for t in 1:T]), + min_power_cost, + segments, + scalar(dict["Minimum uptime (h)"], default=1) * time_multiplier, + scalar(dict["Minimum downtime (h)"], default=1) * time_multiplier, + scalar(dict["Ramp up limit (MW)"], default=1e6), + scalar(dict["Ramp down limit (MW)"], default=1e6), + scalar(dict["Startup limit (MW)"], default=1e6), + scalar(dict["Shutdown limit (MW)"], default=1e6), + initial_status, + initial_power, + timeseries( + dict["Provides spinning reserves?"], + default=[true for t in 1:T], + ), + startup_categories, + ) push!(bus.units, unit) name_to_unit[unit_name] = unit push!(units, unit) diff --git a/test/instance_test.jl b/test/instance_test.jl index 17b1685..d4658ee 100644 --- a/test/instance_test.jl +++ b/test/instance_test.jl @@ -100,6 +100,20 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test load.revenue == [100. for t in 1:4] @test load.demand == [50. for t in 1:4] end + + @testset "read sub-hourly" begin + instance = UnitCommitment.read_benchmark("test/case14-sub-hourly") + @test instance.time == 4 + unit = instance.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 "slice" begin instance = UnitCommitment.read_benchmark("test/case14")