mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 00:08:52 -06:00
time decomp bug fix
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user