From 7201acde78f8462060c1ee72818380dd967bb79f Mon Sep 17 00:00:00 2001 From: Jun He Date: Sat, 27 May 2023 14:49:43 -0400 Subject: [PATCH] time decomp bug fix --- docs/src/usage.md | 2 +- src/instance/read.jl | 21 +- src/instance/structs.jl | 1 + .../methods/TimeDecomposition/optimize.jl | 23 +- test/instance/read_test.jl | 1 + .../TimeDecomposition/initial_status_test.jl | 198 ++++++------------ 6 files changed, 93 insertions(+), 153 deletions(-) diff --git a/docs/src/usage.md b/docs/src/usage.md index 7eb369b..fde2833 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -233,7 +233,7 @@ The `optimize!` function takes three parameters: a unit commitment instance, a ` The code snippet below illustrates an example of solving an instance by decomposing the model into multiple 36-hour sub-problems using the `XavQiuWanThi2019` method. Each sub-problem advances 24 hours at a time. The first sub-problem covers time steps 1 to 36, the second covers time steps 25 to 60, the third covers time steps 49 to 84, and so on. The initial power levels and statuses of the second and subsequent sub-problems are set based on the results of the first 24 hours from each of their immediate prior sub-problems. In essence, this approach addresses the complexity of solving a large problem by tackling it in 24-hour intervals, while incorporating an additional 12-hour buffer to mitigate the closing window effect for each sub-problem. -> **Warning** +> **Warning** > Specifying `TimeDecomposition` as the value of the `inner_method` field of another `TimeDecomposition` causes errors when calling the `optimize!` function due to the different argument structures between the two `optimize!` functions. ```julia diff --git a/src/instance/read.jl b/src/instance/read.jl index 024e478..8c33657 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -142,16 +142,28 @@ function _from_json(json; repair = true)::UnitCommitmentScenario return x end - time_horizon = json["Parameters"]["Time (h)"] + time_horizon = json["Parameters"]["Time horizon (min)"] if time_horizon === nothing - time_horizon = json["Parameters"]["Time horizon (h)"] + time_horizon = json["Parameters"]["Time (h)"] + if time_horizon === nothing + time_horizon = json["Parameters"]["Time horizon (h)"] + end + if time_horizon !== nothing + time_horizon *= 60 + end end - time_horizon !== nothing || error("Missing parameter: Time horizon (h)") + time_horizon !== nothing || error("Missing parameter: Time horizon (min)") + isinteger(time_horizon) || + error("Time horizon must be an integer in minutes") + time_horizon = Int(time_horizon) 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_horizon % time_step == 0) || error( + "Time step $time_step is not a divisor of time horizon $time_horizon", + ) time_multiplier = 60 ÷ time_step - T = time_horizon * time_multiplier + T = time_horizon ÷ time_step probability = json["Parameters"]["Scenario weight"] probability !== nothing || (probability = 1) @@ -408,6 +420,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario reserves = reserves, reserves_by_name = name_to_reserve, time = T, + time_step = time_step, thermal_units_by_name = Dict(g.name => g for g in thermal_units), thermal_units = thermal_units, profiled_units_by_name = Dict(pu.name => pu for pu in profiled_units), diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 8323a15..39725af 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -104,6 +104,7 @@ Base.@kwdef mutable struct UnitCommitmentScenario thermal_units_by_name::Dict{AbstractString,ThermalUnit} thermal_units::Vector{ThermalUnit} time::Int + time_step::Int end Base.@kwdef mutable struct UnitCommitmentInstance diff --git a/src/solution/methods/TimeDecomposition/optimize.jl b/src/solution/methods/TimeDecomposition/optimize.jl index 20ac850..840846e 100644 --- a/src/solution/methods/TimeDecomposition/optimize.jl +++ b/src/solution/methods/TimeDecomposition/optimize.jl @@ -47,7 +47,6 @@ function optimize!( # get instance total length T = instance.time solution = OrderedDict() - iter = 0 if length(instance.scenarios) > 1 for sc in instance.scenarios solution[sc.name] = OrderedDict() @@ -55,10 +54,6 @@ function optimize!( end # for each iteration, time increment by method.time_increment for t_start in 1:method.time_increment:T - # set the initial status - if iter > 0 - _set_initial_status!(instance, solution, method.time_increment) - end t_end = t_start + method.time_window - 1 # if t_end exceed total T t_end = t_end > T ? T : t_end @@ -84,7 +79,8 @@ function optimize!( ) end end - iter += 1 # increment iteration counter + # set the initial status for the next sub-problem + _set_initial_status!(instance, solution, method.time_increment) end return solution end @@ -96,7 +92,7 @@ end time_increment::Int, ) -Set the thermal units' initial power levels and statuses based on the first bunch of time slots +Set the thermal units' initial power levels and statuses based on the last bunch of time slots specified by time_increment in the solution dictionary. """ function _set_initial_status!( @@ -114,11 +110,10 @@ function _set_initial_status!( solution[sc.name]["Thermal production (MW)"][thermal_unit.name] is_on = solution[sc.name]["Is on"][thermal_unit.name] end - thermal_unit.initial_power = prod[time_increment] + thermal_unit.initial_power = prod[end] thermal_unit.initial_status = _determine_initial_status( thermal_unit.initial_status, - is_on, - time_increment, + is_on[end-time_increment+1:end], ) end end @@ -128,16 +123,14 @@ end _determine_initial_status( prev_initial_status::Union{Float64,Int}, status_sequence::Vector{Float64}, - time_increment::Int, )::Union{Float64,Int} Determines a thermal unit's initial status based on its previous initial status, and -the on/off statuses in first bunch of time slots. +the on/off statuses in the last operation. """ function _determine_initial_status( prev_initial_status::Union{Float64,Int}, status_sequence::Vector{Float64}, - time_increment::Int, )::Union{Float64,Int} # initialize the two flags on_status = prev_initial_status @@ -147,8 +140,8 @@ function _determine_initial_status( # if the on_status < 0, set it to 1.0 # at each time if the unit is off, reset on_status, decrement off_status # if the off_status > 0, set it to -1.0 - for t in 1:time_increment - if status_sequence[t] == 1.0 + for status in status_sequence + if status == 1.0 on_status = on_status < 0.0 ? 1.0 : on_status + 1.0 off_status = 0.0 else diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl index 1ad347b..5adb774 100644 --- a/test/instance/read_test.jl +++ b/test/instance/read_test.jl @@ -20,6 +20,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test length(sc.contingencies) == 19 @test length(sc.price_sensitive_loads) == 1 @test instance.time == 4 + @test sc.time_step == 60 @test sc.lines[5].name == "l5" @test sc.lines[5].source.name == "b2" diff --git a/test/solution/methods/TimeDecomposition/initial_status_test.jl b/test/solution/methods/TimeDecomposition/initial_status_test.jl index cd253d8..1e0f17f 100644 --- a/test/solution/methods/TimeDecomposition/initial_status_test.jl +++ b/test/solution/methods/TimeDecomposition/initial_status_test.jl @@ -5,134 +5,66 @@ using UnitCommitment, DataStructures @testset "determine_initial_status" begin - t_increment = 24 - t_model = 36 hot_start = 100 cold_start = -100 # all on throughout - stat_seq = ones(t_model) + stat_seq = ones(36) # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == 124 + new_stat = UnitCommitment._determine_initial_status(hot_start, stat_seq) + @test new_stat == 136 # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == 24 + new_stat = UnitCommitment._determine_initial_status(cold_start, stat_seq) + @test new_stat == 36 # off in the last 12 periods - stat_seq = ones(t_model) + stat_seq = ones(36) stat_seq[25:end] .= 0 # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == 124 + new_stat = UnitCommitment._determine_initial_status(hot_start, stat_seq) + @test new_stat == -12 # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == 24 + new_stat = UnitCommitment._determine_initial_status(cold_start, stat_seq) + @test new_stat == -12 - # off in one of the first 24 periods - stat_seq = ones(t_model) + # off in one period + stat_seq = ones(36) stat_seq[10] = 0 # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == 14 + new_stat = UnitCommitment._determine_initial_status(hot_start, stat_seq) + @test new_stat == 26 # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == 14 + new_stat = UnitCommitment._determine_initial_status(cold_start, stat_seq) + @test new_stat == 26 # off in several of the first 24 periods - stat_seq = ones(t_model) + stat_seq = ones(36) stat_seq[[10, 11, 20]] .= 0 # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == 4 + new_stat = UnitCommitment._determine_initial_status(hot_start, stat_seq) + @test new_stat == 16 # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == 4 - - # off in several of the first 24 periods - stat_seq = ones(t_model) - stat_seq[20:24] .= 0 - # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == -5 - # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == -5 + new_stat = UnitCommitment._determine_initial_status(cold_start, stat_seq) + @test new_stat == 16 # all off throughout - stat_seq = zeros(t_model) + stat_seq = zeros(36) # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == -24 + new_stat = UnitCommitment._determine_initial_status(hot_start, stat_seq) + @test new_stat == -36 # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == -124 + new_stat = UnitCommitment._determine_initial_status(cold_start, stat_seq) + @test new_stat == -136 # on in the last 12 periods - stat_seq = zeros(t_model) + stat_seq = zeros(36) stat_seq[25:end] .= 1 # hot start - new_stat = UnitCommitment._determine_initial_status( - hot_start, - stat_seq, - t_increment, - ) - @test new_stat == -24 + new_stat = UnitCommitment._determine_initial_status(hot_start, stat_seq) + @test new_stat == 12 # cold start - new_stat = UnitCommitment._determine_initial_status( - cold_start, - stat_seq, - t_increment, - ) - @test new_stat == -124 + new_stat = UnitCommitment._determine_initial_status(cold_start, stat_seq) + @test new_stat == 12 end @testset "set_initial_status" begin @@ -140,28 +72,28 @@ end instance = UnitCommitment.read("$FIXTURES/case14.json.gz") psuedo_solution = OrderedDict( "Thermal production (MW)" => OrderedDict( - "g1" => [110.0, 112.0, 114.0, 116.0], - "g2" => [100.0, 102.0, 0.0, 0.0], + "g1" => [0.0, 112.0, 114.0, 116.0], + "g2" => [0.0, 102.0, 0.0, 0.0], "g3" => [0.0, 0.0, 0.0, 0.0], - "g4" => [33.0, 34.0, 66.0, 99.0], - "g5" => [33.0, 34.0, 66.0, 99.0], - "g6" => [100.0, 100.0, 100.0, 100.0], + "g4" => [0.0, 34.0, 66.0, 99.0], + "g5" => [0.0, 34.0, 66.0, 99.0], + "g6" => [0.0, 100.0, 100.0, 100.0], ), "Is on" => OrderedDict( - "g1" => [1.0, 1.0, 1.0, 1.0], - "g2" => [1.0, 1.0, 0.0, 0.0], + "g1" => [0.0, 1.0, 1.0, 1.0], + "g2" => [0.0, 1.0, 0.0, 0.0], "g3" => [0.0, 0.0, 0.0, 0.0], - "g4" => [1.0, 1.0, 1.0, 1.0], - "g5" => [1.0, 1.0, 1.0, 1.0], - "g6" => [1.0, 1.0, 1.0, 1.0], + "g4" => [0.0, 1.0, 1.0, 1.0], + "g5" => [0.0, 1.0, 1.0, 1.0], + "g6" => [0.0, 1.0, 1.0, 1.0], ), ) UnitCommitment._set_initial_status!(instance, psuedo_solution, 3) thermal_units = instance.scenarios[1].thermal_units - @test thermal_units[1].initial_power == 114.0 + @test thermal_units[1].initial_power == 116.0 @test thermal_units[1].initial_status == 3.0 @test thermal_units[2].initial_power == 0.0 - @test thermal_units[2].initial_status == -1.0 + @test thermal_units[2].initial_status == -2.0 @test thermal_units[3].initial_power == 0.0 @test thermal_units[3].initial_status == -9.0 @@ -173,47 +105,47 @@ end psuedo_solution = OrderedDict( "case14" => OrderedDict( "Thermal production (MW)" => OrderedDict( - "g1" => [110.0, 112.0, 114.0, 116.0], - "g2" => [100.0, 102.0, 0.0, 0.0], + "g1" => [0.0, 112.0, 114.0, 116.0], + "g2" => [0.0, 102.0, 0.0, 0.0], "g3" => [0.0, 0.0, 0.0, 0.0], - "g4" => [33.0, 34.0, 66.0, 99.0], - "g5" => [33.0, 34.0, 66.0, 99.0], - "g6" => [100.0, 100.0, 100.0, 100.0], + "g4" => [0.0, 34.0, 66.0, 99.0], + "g5" => [0.0, 34.0, 66.0, 99.0], + "g6" => [0.0, 100.0, 100.0, 100.0], ), "Is on" => OrderedDict( - "g1" => [1.0, 1.0, 1.0, 1.0], - "g2" => [1.0, 1.0, 0.0, 0.0], + "g1" => [0.0, 1.0, 1.0, 1.0], + "g2" => [0.0, 1.0, 0.0, 0.0], "g3" => [0.0, 0.0, 0.0, 0.0], - "g4" => [1.0, 1.0, 1.0, 1.0], - "g5" => [1.0, 1.0, 1.0, 1.0], - "g6" => [1.0, 1.0, 1.0, 1.0], + "g4" => [0.0, 1.0, 1.0, 1.0], + "g5" => [0.0, 1.0, 1.0, 1.0], + "g6" => [0.0, 1.0, 1.0, 1.0], ), ), "case14-profiled" => OrderedDict( "Thermal production (MW)" => OrderedDict( - "g1" => [112.0, 113.0, 116.0, 115.0], + "g1" => [0.0, 113.0, 116.0, 115.0], "g2" => [0.0, 0.0, 0.0, 0.0], "g3" => [0.0, 0.0, 0.0, 20.0], - "g4" => [33.0, 34.0, 66.0, 99.0], - "g5" => [33.0, 34.0, 66.0, 99.0], - "g6" => [100.0, 100.0, 100.0, 100.0], + "g4" => [0.0, 34.0, 66.0, 98.0], + "g5" => [0.0, 34.0, 66.0, 97.0], + "g6" => [0.0, 100.0, 100.0, 100.0], ), "Is on" => OrderedDict( - "g1" => [1.0, 1.0, 1.0, 1.0], + "g1" => [0.0, 1.0, 1.0, 1.0], "g2" => [0.0, 0.0, 0.0, 0.0], - "g3" => [0.0, 0.0, 0.0, 0.0], - "g4" => [1.0, 1.0, 1.0, 1.0], - "g5" => [1.0, 1.0, 1.0, 1.0], - "g6" => [1.0, 1.0, 1.0, 1.0], + "g3" => [0.0, 0.0, 0.0, 1.0], + "g4" => [0.0, 1.0, 1.0, 1.0], + "g5" => [0.0, 1.0, 1.0, 1.0], + "g6" => [0.0, 1.0, 1.0, 1.0], ), ), ) UnitCommitment._set_initial_status!(instance, psuedo_solution, 3) thermal_units_sc2 = instance.scenarios[2].thermal_units - @test thermal_units_sc2[1].initial_power == 116.0 + @test thermal_units_sc2[1].initial_power == 115.0 @test thermal_units_sc2[1].initial_status == 3.0 @test thermal_units_sc2[2].initial_power == 0.0 @test thermal_units_sc2[2].initial_status == -11.0 - @test thermal_units_sc2[3].initial_power == 0.0 - @test thermal_units_sc2[3].initial_status == -9.0 + @test thermal_units_sc2[3].initial_power == 20.0 + @test thermal_units_sc2[3].initial_status == 1.0 end