mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
Set up multi-formulation architecture; start merging akazachk's code
This commit is contained in:
@@ -6,6 +6,9 @@ module UnitCommitment
|
|||||||
|
|
||||||
include("instance/structs.jl")
|
include("instance/structs.jl")
|
||||||
include("solution/structs.jl")
|
include("solution/structs.jl")
|
||||||
|
include("model/formulations/base/structs.jl")
|
||||||
|
include("model/formulations/ArrCon00/structs.jl")
|
||||||
|
include("model/formulations/DamKucRajAta16/structs.jl")
|
||||||
include("solution/methods/XaQiWaTh19/structs.jl")
|
include("solution/methods/XaQiWaTh19/structs.jl")
|
||||||
|
|
||||||
include("import/egret.jl")
|
include("import/egret.jl")
|
||||||
@@ -16,6 +19,9 @@ include("model/formulations/base/line.jl")
|
|||||||
include("model/formulations/base/psload.jl")
|
include("model/formulations/base/psload.jl")
|
||||||
include("model/formulations/base/system.jl")
|
include("model/formulations/base/system.jl")
|
||||||
include("model/formulations/base/unit.jl")
|
include("model/formulations/base/unit.jl")
|
||||||
|
include("model/formulations/base/sensitivity.jl")
|
||||||
|
include("model/formulations/DamKucRajAta16/unit.jl")
|
||||||
|
include("model/formulations/ArrCon00/unit.jl")
|
||||||
include("model/jumpext.jl")
|
include("model/jumpext.jl")
|
||||||
include("solution/fix.jl")
|
include("solution/fix.jl")
|
||||||
include("solution/methods/XaQiWaTh19/enforce.jl")
|
include("solution/methods/XaQiWaTh19/enforce.jl")
|
||||||
@@ -28,7 +34,6 @@ include("solution/warmstart.jl")
|
|||||||
include("solution/write.jl")
|
include("solution/write.jl")
|
||||||
include("transform/initcond.jl")
|
include("transform/initcond.jl")
|
||||||
include("transform/slice.jl")
|
include("transform/slice.jl")
|
||||||
include("transmission/sensitivity.jl")
|
|
||||||
include("utils/log.jl")
|
include("utils/log.jl")
|
||||||
include("validation/repair.jl")
|
include("validation/repair.jl")
|
||||||
include("validation/validate.jl")
|
include("validation/validate.jl")
|
||||||
|
|||||||
@@ -55,45 +55,25 @@ julia> model = UnitCommitment.build_model(
|
|||||||
"""
|
"""
|
||||||
function build_model(;
|
function build_model(;
|
||||||
instance::UnitCommitmentInstance,
|
instance::UnitCommitmentInstance,
|
||||||
isf::Union{Matrix{Float64},Nothing} = nothing,
|
|
||||||
lodf::Union{Matrix{Float64},Nothing} = nothing,
|
|
||||||
isf_cutoff::Float64 = 0.005,
|
|
||||||
lodf_cutoff::Float64 = 0.001,
|
|
||||||
optimizer = nothing,
|
optimizer = nothing,
|
||||||
variable_names::Bool = false,
|
variable_names::Bool = false,
|
||||||
)::JuMP.Model
|
)::JuMP.Model
|
||||||
if length(instance.buses) == 1
|
return _build_model(
|
||||||
isf = zeros(0, 0)
|
instance,
|
||||||
lodf = zeros(0, 0)
|
_GeneratorFormulation(),
|
||||||
else
|
_ShiftFactorsFormulation(),
|
||||||
if isf === nothing
|
optimizer = optimizer,
|
||||||
@info "Computing injection shift factors..."
|
variable_names = variable_names,
|
||||||
time_isf = @elapsed begin
|
|
||||||
isf = UnitCommitment._injection_shift_factors(
|
|
||||||
lines = instance.lines,
|
|
||||||
buses = instance.buses,
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@info @sprintf("Computed ISF in %.2f seconds", time_isf)
|
|
||||||
@info "Computing line outage factors..."
|
|
||||||
time_lodf = @elapsed begin
|
|
||||||
lodf = UnitCommitment._line_outage_factors(
|
|
||||||
lines = instance.lines,
|
|
||||||
buses = instance.buses,
|
|
||||||
isf = isf,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
@info @sprintf("Computed LODF in %.2f seconds", time_lodf)
|
|
||||||
|
|
||||||
@info @sprintf(
|
function _build_model(
|
||||||
"Applying PTDF and LODF cutoffs (%.5f, %.5f)",
|
instance::UnitCommitmentInstance,
|
||||||
isf_cutoff,
|
fg::_GeneratorFormulation,
|
||||||
lodf_cutoff
|
ft::_TransmissionFormulation;
|
||||||
)
|
optimizer = nothing,
|
||||||
isf[abs.(isf).<isf_cutoff] .= 0
|
variable_names::Bool = false,
|
||||||
lodf[abs.(lodf).<lodf_cutoff] .= 0
|
)::JuMP.Model
|
||||||
end
|
|
||||||
end
|
|
||||||
@info "Building model..."
|
@info "Building model..."
|
||||||
time_model = @elapsed begin
|
time_model = @elapsed begin
|
||||||
model = Model()
|
model = Model()
|
||||||
@@ -102,12 +82,19 @@ function build_model(;
|
|||||||
end
|
end
|
||||||
model[:obj] = AffExpr()
|
model[:obj] = AffExpr()
|
||||||
model[:instance] = instance
|
model[:instance] = instance
|
||||||
model[:isf] = isf
|
_setup_transmission(model, ft)
|
||||||
model[:lodf] = lodf
|
for l in instance.lines
|
||||||
_add_transmission_line!.(model, instance.lines)
|
_add_transmission_line!(model, l, ft)
|
||||||
_add_bus!.(model, instance.buses)
|
end
|
||||||
_add_unit!.(model, instance.units)
|
for b in instance.buses
|
||||||
_add_price_sensitive_load!.(model, instance.price_sensitive_loads)
|
_add_bus!(model, b)
|
||||||
|
end
|
||||||
|
for g in instance.units
|
||||||
|
_add_unit!(model, g, fg)
|
||||||
|
end
|
||||||
|
for ps in instance.price_sensitive_loads
|
||||||
|
_add_price_sensitive_load!(model, ps)
|
||||||
|
end
|
||||||
_add_system_wide_eqs!(model)
|
_add_system_wide_eqs!(model)
|
||||||
@objective(model, Min, model[:obj])
|
@objective(model, Min, model[:obj])
|
||||||
end
|
end
|
||||||
|
|||||||
12
src/model/formulations/ArrCon00/structs.jl
Normal file
12
src/model/formulations/ArrCon00/structs.jl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulation described in:
|
||||||
|
|
||||||
|
Arroyo, J. M., & Conejo, A. J. (2000). Optimal response of a thermal unit
|
||||||
|
to an electricity spot market. IEEE Transactions on power systems, 15(3),
|
||||||
|
1098-1104.
|
||||||
|
"""
|
||||||
|
mutable struct _ArrCon00 <: _RampingFormulation end
|
||||||
92
src/model/formulations/ArrCon00/unit.jl
Normal file
92
src/model/formulations/ArrCon00/unit.jl
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# 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_ramp_eqs!(
|
||||||
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_ArrCon00,
|
||||||
|
)::Nothing
|
||||||
|
# TODO: Move upper case constants to model[:instance]
|
||||||
|
RESERVES_WHEN_START_UP = true
|
||||||
|
RESERVES_WHEN_RAMP_UP = true
|
||||||
|
RESERVES_WHEN_RAMP_DOWN = true
|
||||||
|
RESERVES_WHEN_SHUT_DOWN = true
|
||||||
|
gn = g.name
|
||||||
|
RU = g.ramp_up_limit
|
||||||
|
RD = g.ramp_down_limit
|
||||||
|
SU = g.startup_limit
|
||||||
|
SD = g.shutdown_limit
|
||||||
|
is_on = model[:is_on]
|
||||||
|
prod_above = model[:prod_above]
|
||||||
|
reserve = model[:reserve]
|
||||||
|
switch_off = model[:switch_off]
|
||||||
|
switch_on = model[:switch_on]
|
||||||
|
eq_ramp_down = _init(model, :eq_ramp_down)
|
||||||
|
eq_ramp_up = _init(model, :eq_ramp_up)
|
||||||
|
is_initially_on = (g.initial_status > 0)
|
||||||
|
|
||||||
|
for t in 1:model[:instance].time
|
||||||
|
# Ramp up limit
|
||||||
|
if t == 1
|
||||||
|
if is_initially_on
|
||||||
|
# min power is _not_ multiplied by is_on because if !is_on, then ramp up is irrelevant
|
||||||
|
eq_ramp_up[gn, t] = @constraint(
|
||||||
|
mip,
|
||||||
|
g.min_power[t] +
|
||||||
|
prod_above[gn, t] +
|
||||||
|
(RESERVES_WHEN_RAMP_UP ? reserve[gn, t] : 0.0) <=
|
||||||
|
g.initial_power + RU
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
max_prod_this_period =
|
||||||
|
g.min_power[t] * is_on[gn, t] +
|
||||||
|
prod_above[gn, t] +
|
||||||
|
(
|
||||||
|
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
|
||||||
|
reserve[gn, t] : 0.0
|
||||||
|
)
|
||||||
|
min_prod_last_period =
|
||||||
|
g.min_power[t-1] * is_on[gn, t-1] + prod_above[gn, t-1]
|
||||||
|
|
||||||
|
# Equation (24) in Kneuven et al. (2020)
|
||||||
|
eq_ramp_up[gn, t] = @constraint(
|
||||||
|
mip,
|
||||||
|
max_prod_this_period - min_prod_last_period <=
|
||||||
|
RU * is_on[gn, t-1] + SU * switch_on[gn, t]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ramp down limit
|
||||||
|
if t == 1
|
||||||
|
if is_initially_on
|
||||||
|
# TODO If RD < SD, or more specifically if
|
||||||
|
# min_power + RD < initial_power < SD
|
||||||
|
# then the generator should be able to shut down at time t = 1,
|
||||||
|
# but the constraint below will force the unit to produce power
|
||||||
|
eq_ramp_down[gn, t] = @constraint(
|
||||||
|
mip,
|
||||||
|
g.initial_power - (g.min_power[t] + prod_above[gn, t]) <= RD
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
max_prod_last_period =
|
||||||
|
g.min_power[t-1] * is_on[gn, t-1] +
|
||||||
|
prod_above[gn, t-1] +
|
||||||
|
(
|
||||||
|
RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN ?
|
||||||
|
reserve[gn, t-1] : 0.0
|
||||||
|
)
|
||||||
|
min_prod_this_period =
|
||||||
|
g.min_power[t] * is_on[gn, t] + prod_above[gn, t]
|
||||||
|
|
||||||
|
# Equation (25) in Kneuven et al. (2020)
|
||||||
|
eq_ramp_down[gn, t] = @constraint(
|
||||||
|
mip,
|
||||||
|
max_prod_last_period - min_prod_this_period <=
|
||||||
|
RD * is_on[gn, t] + SD * switch_off[gn, t]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
11
src/model/formulations/DamKucRajAta16/structs.jl
Normal file
11
src/model/formulations/DamKucRajAta16/structs.jl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulation described in:
|
||||||
|
|
||||||
|
Damcı-Kurt, P., Küçükyavuz, S., Rajan, D., & Atamtürk, A. (2016). A polyhedral
|
||||||
|
study of production ramping. Mathematical Programming, 158(1), 175-205.
|
||||||
|
"""
|
||||||
|
mutable struct _DamKucRajAta16 <: _RampingFormulation end
|
||||||
116
src/model/formulations/DamKucRajAta16/unit.jl
Normal file
116
src/model/formulations/DamKucRajAta16/unit.jl
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# 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_ramp_eqs!(
|
||||||
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_DamKucRajAta16,
|
||||||
|
)::Nothing
|
||||||
|
# TODO: Move upper case constants to model[:instance]
|
||||||
|
RESERVES_WHEN_START_UP = true
|
||||||
|
RESERVES_WHEN_RAMP_UP = true
|
||||||
|
RESERVES_WHEN_RAMP_DOWN = true
|
||||||
|
RESERVES_WHEN_SHUT_DOWN = true
|
||||||
|
known_initial_conditions = true
|
||||||
|
is_initially_on = (g.initial_status > 0)
|
||||||
|
SU = g.startup_limit
|
||||||
|
SD = g.shutdown_limit
|
||||||
|
RU = g.ramp_up_limit
|
||||||
|
RD = g.ramp_down_limit
|
||||||
|
gn = g.name
|
||||||
|
eq_str_ramp_down = _init(model, :eq_str_ramp_down)
|
||||||
|
eq_str_ramp_up = _init(model, :eq_str_ramp_up)
|
||||||
|
is_on = model[:is_on]
|
||||||
|
prod_above = model[:prod_above]
|
||||||
|
reserve = model[:reserve]
|
||||||
|
switch_off = model[:switch_off]
|
||||||
|
switch_on = model[:switch_on]
|
||||||
|
|
||||||
|
for t in 1:model[:instance].time
|
||||||
|
time_invariant =
|
||||||
|
(t > 1) ? (abs(g.min_power[t] - g.min_power[t-1]) < 1e-7) : true
|
||||||
|
|
||||||
|
if t > 1 && !time_invariant
|
||||||
|
warning(
|
||||||
|
"Ramping according to Damcı-Kurt et al. (2016) requires " *
|
||||||
|
"time-invariant minimum power. This does not hold for " *
|
||||||
|
"generator $(gn): min_power[$t] = $(g.min_power[t]); " *
|
||||||
|
"min_power[$(t-1)] = $(g.min_power[t-1]). Reverting to " *
|
||||||
|
"Arroyo and Conejo (2000) formulation for this generator.",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
max_prod_this_period =
|
||||||
|
prod_above[gn, t] + (
|
||||||
|
RESERVES_WHEN_START_UP || RESERVES_WHEN_RAMP_UP ?
|
||||||
|
reserve[gn, t] : 0.0
|
||||||
|
)
|
||||||
|
min_prod_last_period = 0.0
|
||||||
|
if t > 1 && time_invariant
|
||||||
|
min_prod_last_period = prod_above[gn, t-1]
|
||||||
|
|
||||||
|
# Equation (35) in Kneuven et al. (2020)
|
||||||
|
# Sparser version of (24)
|
||||||
|
eq_str_ramp_up[gn, t] = @constraint(
|
||||||
|
model,
|
||||||
|
max_prod_this_period - min_prod_last_period <=
|
||||||
|
(SU - g.min_power[t] - RU) * switch_on[gn, t] +
|
||||||
|
RU * is_on[gn, t]
|
||||||
|
)
|
||||||
|
elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant)
|
||||||
|
if t > 1
|
||||||
|
min_prod_last_period =
|
||||||
|
prod_above[gn, t-1] + g.min_power[t-1] * is_on[gn, t-1]
|
||||||
|
else
|
||||||
|
min_prod_last_period = max(g.initial_power, 0.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add the min prod at time t back in to max_prod_this_period to get _total_ production
|
||||||
|
# (instead of using the amount above minimum, as min prod for t < 1 is unknown)
|
||||||
|
max_prod_this_period += g.min_power[t] * is_on[gn, t]
|
||||||
|
|
||||||
|
# Modified version of equation (35) in Kneuven et al. (2020)
|
||||||
|
# Equivalent to (24)
|
||||||
|
eq_str_ramp_up[gn, t] = @constraint(
|
||||||
|
model,
|
||||||
|
max_prod_this_period - min_prod_last_period <=
|
||||||
|
(SU - RU) * switch_on[gn, t] + RU * is_on[gn, t]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
max_prod_last_period =
|
||||||
|
min_prod_last_period + (
|
||||||
|
t > 1 && (RESERVES_WHEN_SHUT_DOWN || RESERVES_WHEN_RAMP_DOWN) ?
|
||||||
|
reserve[gn, t-1] : 0.0
|
||||||
|
)
|
||||||
|
min_prod_this_period = prod_above[gn, t]
|
||||||
|
on_last_period = 0.0
|
||||||
|
if t > 1
|
||||||
|
on_last_period = is_on[gn, t-1]
|
||||||
|
elseif (known_initial_conditions && g.initial_status > 0)
|
||||||
|
on_last_period = 1.0
|
||||||
|
end
|
||||||
|
|
||||||
|
if t > 1 && time_invariant
|
||||||
|
# Equation (36) in Kneuven et al. (2020)
|
||||||
|
eq_str_ramp_down[gn, t] = @constraint(
|
||||||
|
model,
|
||||||
|
max_prod_last_period - min_prod_this_period <=
|
||||||
|
(SD - g.min_power[t] - RD) * switch_off[gn, t] +
|
||||||
|
RD * on_last_period
|
||||||
|
)
|
||||||
|
elseif (t == 1 && is_initially_on) || (t > 1 && !time_invariant)
|
||||||
|
# Add back in min power
|
||||||
|
min_prod_this_period += g.min_power[t] * is_on[gn, t]
|
||||||
|
|
||||||
|
# Modified version of equation (36) in Kneuven et al. (2020)
|
||||||
|
# Equivalent to (25)
|
||||||
|
eq_str_ramp_down[gn, t] = @constraint(
|
||||||
|
model,
|
||||||
|
max_prod_last_period - min_prod_this_period <=
|
||||||
|
(SD - RD) * switch_off[gn, t] + RD * on_last_period
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
|
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
|
||||||
net_injection = _get(model, :expr_net_injection)
|
net_injection = _init(model, :expr_net_injection)
|
||||||
reserve = _get(model, :expr_reserve)
|
reserve = _init(model, :expr_reserve)
|
||||||
curtail = _get(model, :curtail)
|
curtail = _init(model, :curtail)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
# Fixed load
|
# Fixed load
|
||||||
net_injection[b.name, t] = AffExpr(-b.load[t])
|
net_injection[b.name, t] = AffExpr(-b.load[t])
|
||||||
|
|||||||
@@ -2,11 +2,60 @@
|
|||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
function _add_transmission_line!(model, lm)::Nothing
|
function _add_transmission_line!(
|
||||||
overflow = _get(model, :overflow)
|
model::JuMP.Model,
|
||||||
|
lm::TransmissionLine,
|
||||||
|
f::_TransmissionFormulation,
|
||||||
|
)::Nothing
|
||||||
|
overflow = _init(model, :overflow)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
v = overflow[lm.name, t] = @variable(model, lower_bound = 0)
|
overflow[lm.name, t] = @variable(model, lower_bound = 0)
|
||||||
add_to_expression!(model[:obj], v, lm.flow_limit_penalty[t])
|
add_to_expression!(
|
||||||
|
model[:obj],
|
||||||
|
overflow[lm.name, t],
|
||||||
|
lm.flow_limit_penalty[t],
|
||||||
|
)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function _setup_transmission(
|
||||||
|
model::JuMP.Model,
|
||||||
|
formulation::_TransmissionFormulation,
|
||||||
|
)::Nothing
|
||||||
|
instance = model[:instance]
|
||||||
|
isf = formulation.precomputed_isf
|
||||||
|
lodf = formulation.precomputed_lodf
|
||||||
|
if length(instance.buses) == 1
|
||||||
|
isf = zeros(0, 0)
|
||||||
|
lodf = zeros(0, 0)
|
||||||
|
elseif isf === nothing
|
||||||
|
@info "Computing injection shift factors..."
|
||||||
|
time_isf = @elapsed begin
|
||||||
|
isf = UnitCommitment._injection_shift_factors(
|
||||||
|
lines = instance.lines,
|
||||||
|
buses = instance.buses,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
@info @sprintf("Computed ISF in %.2f seconds", time_isf)
|
||||||
|
@info "Computing line outage factors..."
|
||||||
|
time_lodf = @elapsed begin
|
||||||
|
lodf = UnitCommitment._line_outage_factors(
|
||||||
|
lines = instance.lines,
|
||||||
|
buses = instance.buses,
|
||||||
|
isf = isf,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
@info @sprintf("Computed LODF in %.2f seconds", time_lodf)
|
||||||
|
@info @sprintf(
|
||||||
|
"Applying PTDF and LODF cutoffs (%.5f, %.5f)",
|
||||||
|
formulation.isf_cutoff,
|
||||||
|
formulation.lodf_cutoff
|
||||||
|
)
|
||||||
|
isf[abs.(isf).<formulation.isf_cutoff] .= 0
|
||||||
|
lodf[abs.(lodf).<formulation.lodf_cutoff] .= 0
|
||||||
|
end
|
||||||
|
model[:isf] = isf
|
||||||
|
model[:lodf] = lodf
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ function _add_price_sensitive_load!(
|
|||||||
model::JuMP.Model,
|
model::JuMP.Model,
|
||||||
ps::PriceSensitiveLoad,
|
ps::PriceSensitiveLoad,
|
||||||
)::Nothing
|
)::Nothing
|
||||||
loads = _get(model, :loads)
|
loads = _init(model, :loads)
|
||||||
net_injection = _get(model, :expr_net_injection)
|
net_injection = _init(model, :expr_net_injection)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
# Decision variable
|
# Decision variable
|
||||||
loads[ps.name, t] =
|
loads[ps.name, t] =
|
||||||
|
|||||||
@@ -72,10 +72,9 @@ function _line_outage_factors(;
|
|||||||
lines::Array{TransmissionLine,1},
|
lines::Array{TransmissionLine,1},
|
||||||
isf::Array{Float64,2},
|
isf::Array{Float64,2},
|
||||||
)::Array{Float64,2}
|
)::Array{Float64,2}
|
||||||
n_lines, n_buses = size(isf)
|
|
||||||
incidence = Array(_reduced_incidence_matrix(lines = lines, buses = buses))
|
incidence = Array(_reduced_incidence_matrix(lines = lines, buses = buses))
|
||||||
lodf::Array{Float64,2} = isf * transpose(incidence)
|
lodf::Array{Float64,2} = isf * transpose(incidence)
|
||||||
m, n = size(lodf)
|
_, n = size(lodf)
|
||||||
for i in 1:n
|
for i in 1:n
|
||||||
lodf[:, i] *= 1.0 / (1.0 - lodf[i, i])
|
lodf[:, i] *= 1.0 / (1.0 - lodf[i, i])
|
||||||
lodf[i, i] = -1
|
lodf[i, i] = -1
|
||||||
57
src/model/formulations/base/structs.jl
Normal file
57
src/model/formulations/base/structs.jl
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
abstract type _RampingFormulation end
|
||||||
|
abstract type _TransmissionFormulation end
|
||||||
|
|
||||||
|
struct _GeneratorFormulation
|
||||||
|
ramping::_RampingFormulation
|
||||||
|
|
||||||
|
function _GeneratorFormulation(
|
||||||
|
ramping::_RampingFormulation = _DamKucRajAta16(),
|
||||||
|
)
|
||||||
|
return new(ramping)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
mutable struct _ShiftFactorsFormulation <: _TransmissionFormulation
|
||||||
|
isf_cutoff::Float64
|
||||||
|
lodf_cutoff::Float64
|
||||||
|
precomputed_isf::Union{Nothing,Matrix{Float64}}
|
||||||
|
precomputed_lodf::Union{Nothing,Matrix{Float64}}
|
||||||
|
end
|
||||||
|
|
||||||
|
Transmission formulation based on Injection Shift Factors (ISF) and Line
|
||||||
|
Outage Distribution Factors (LODF). Constraints are enforced in a lazy way.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
- `precomputed_isf::Union{Matrix{Float64},Nothing} = nothing`:
|
||||||
|
the injection shift factors matrix. If not provided, it will be computed.
|
||||||
|
- `precomputed_lodf::Union{Matrix{Float64},Nothing} = nothing`:
|
||||||
|
the line outage distribution factors matrix. If not provided, it will be
|
||||||
|
computed.
|
||||||
|
- `isf_cutoff::Float64 = 0.005`:
|
||||||
|
the cutoff that should be applied to the ISF matrix. Entries with magnitude
|
||||||
|
smaller than this value will be set to zero.
|
||||||
|
- `lodf_cutoff::Float64 = 0.001`:
|
||||||
|
the cutoff that should be applied to the LODF matrix. Entries with magnitude
|
||||||
|
smaller than this value will be set to zero.
|
||||||
|
"""
|
||||||
|
mutable struct _ShiftFactorsFormulation <: _TransmissionFormulation
|
||||||
|
isf_cutoff::Float64
|
||||||
|
lodf_cutoff::Float64
|
||||||
|
precomputed_isf::Union{Nothing,Matrix{Float64}}
|
||||||
|
precomputed_lodf::Union{Nothing,Matrix{Float64}}
|
||||||
|
|
||||||
|
function _ShiftFactorsFormulation(;
|
||||||
|
isf_cutoff = 0.005,
|
||||||
|
lodf_cutoff = 0.001,
|
||||||
|
precomputed_isf = nothing,
|
||||||
|
precomputed_lodf = nothing,
|
||||||
|
)
|
||||||
|
return new(isf_cutoff, lodf_cutoff, precomputed_isf, precomputed_lodf)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,9 +10,9 @@ end
|
|||||||
|
|
||||||
function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
|
function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
|
||||||
T = model[:instance].time
|
T = model[:instance].time
|
||||||
net_injection = _get(model, :net_injection)
|
net_injection = _init(model, :net_injection)
|
||||||
eq_net_injection_def = _get(model, :eq_net_injection_def)
|
eq_net_injection_def = _init(model, :eq_net_injection_def)
|
||||||
eq_power_balance = _get(model, :eq_power_balance)
|
eq_power_balance = _init(model, :eq_power_balance)
|
||||||
for t in 1:T, b in model[:instance].buses
|
for t in 1:T, b in model[:instance].buses
|
||||||
n = net_injection[b.name, t] = @variable(model)
|
n = net_injection[b.name, t] = @variable(model)
|
||||||
eq_net_injection_def[t, b.name] =
|
eq_net_injection_def[t, b.name] =
|
||||||
@@ -28,7 +28,7 @@ function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
|
|||||||
end
|
end
|
||||||
|
|
||||||
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
||||||
eq_min_reserve = _get(model, :eq_min_reserve)
|
eq_min_reserve = _init(model, :eq_min_reserve)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
eq_min_reserve[t] = @constraint(
|
eq_min_reserve[t] = @constraint(
|
||||||
model,
|
model,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
function _add_unit!(model::JuMP.Model, g::Unit)
|
function _add_unit!(model::JuMP.Model, g::Unit, f::_GeneratorFormulation)
|
||||||
if !all(g.must_run) && any(g.must_run)
|
if !all(g.must_run) && any(g.must_run)
|
||||||
error("Partially must-run units are not currently supported")
|
error("Partially must-run units are not currently supported")
|
||||||
end
|
end
|
||||||
@@ -11,26 +11,31 @@ function _add_unit!(model::JuMP.Model, g::Unit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
_add_production_vars!(model, g)
|
_add_production_vars!(model, g, f)
|
||||||
_add_reserve_vars!(model, g)
|
_add_reserve_vars!(model, g, f)
|
||||||
_add_startup_shutdown_vars!(model, g)
|
_add_startup_shutdown_vars!(model, g, f)
|
||||||
_add_status_vars!(model, g)
|
_add_status_vars!(model, g, f)
|
||||||
|
|
||||||
# Constraints and objective function
|
# Constraints and objective function
|
||||||
_add_min_uptime_downtime_eqs!(model, g)
|
_add_min_uptime_downtime_eqs!(model, g, f)
|
||||||
_add_net_injection_eqs!(model, g)
|
_add_net_injection_eqs!(model, g, f)
|
||||||
_add_production_eqs!(model, g)
|
_add_production_eqs!(model, g, f)
|
||||||
_add_ramp_eqs!(model, g)
|
_add_ramp_eqs!(model, g, f.ramping)
|
||||||
_add_startup_shutdown_costs_eqs!(model, g)
|
_add_startup_shutdown_costs_eqs!(model, g, f)
|
||||||
_add_startup_shutdown_limit_eqs!(model, g)
|
_add_startup_shutdown_limit_eqs!(model, g, f)
|
||||||
return _add_status_eqs!(model, g)
|
_add_status_eqs!(model, g, f)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
_is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0)
|
_is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0)
|
||||||
|
|
||||||
function _add_production_vars!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_production_vars!(
|
||||||
prod_above = _get(model, :prod_above)
|
model::JuMP.Model,
|
||||||
segprod = _get(model, :segprod)
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
prod_above = _init(model, :prod_above)
|
||||||
|
segprod = _init(model, :segprod)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
for k in 1:length(g.cost_segments)
|
for k in 1:length(g.cost_segments)
|
||||||
segprod[g.name, t, k] = @variable(model, lower_bound = 0)
|
segprod[g.name, t, k] = @variable(model, lower_bound = 0)
|
||||||
@@ -40,10 +45,14 @@ function _add_production_vars!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_production_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_production_eqs!(
|
||||||
eq_prod_above_def = _get(model, :eq_prod_above_def)
|
model::JuMP.Model,
|
||||||
eq_prod_limit = _get(model, :eq_prod_limit)
|
g::Unit,
|
||||||
eq_segprod_limit = _get(model, :eq_segprod_limit)
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
eq_prod_above_def = _init(model, :eq_prod_above_def)
|
||||||
|
eq_prod_limit = _init(model, :eq_prod_limit)
|
||||||
|
eq_segprod_limit = _init(model, :eq_segprod_limit)
|
||||||
is_on = model[:is_on]
|
is_on = model[:is_on]
|
||||||
K = length(g.cost_segments)
|
K = length(g.cost_segments)
|
||||||
prod_above = model[:prod_above]
|
prod_above = model[:prod_above]
|
||||||
@@ -82,8 +91,12 @@ function _add_production_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_reserve_vars!(
|
||||||
reserve = _get(model, :reserve)
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
reserve = _init(model, :reserve)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
if g.provides_spinning_reserves[t]
|
if g.provides_spinning_reserves[t]
|
||||||
reserve[g.name, t] = @variable(model, lower_bound = 0)
|
reserve[g.name, t] = @variable(model, lower_bound = 0)
|
||||||
@@ -94,7 +107,11 @@ function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_reserve_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_reserve_eqs!(
|
||||||
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
reserve = model[:reserve]
|
reserve = model[:reserve]
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0)
|
add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0)
|
||||||
@@ -102,8 +119,12 @@ function _add_reserve_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_startup_shutdown_vars!(
|
||||||
startup = _get(model, :startup)
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
startup = _init(model, :startup)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
for s in 1:length(g.startup_categories)
|
for s in 1:length(g.startup_categories)
|
||||||
startup[g.name, t, s] = @variable(model, binary = true)
|
startup[g.name, t, s] = @variable(model, binary = true)
|
||||||
@@ -112,9 +133,13 @@ function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_startup_shutdown_limit_eqs!(
|
||||||
eq_shutdown_limit = _get(model, :eq_shutdown_limit)
|
model::JuMP.Model,
|
||||||
eq_startup_limit = _get(model, :eq_startup_limit)
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
eq_shutdown_limit = _init(model, :eq_shutdown_limit)
|
||||||
|
eq_startup_limit = _init(model, :eq_startup_limit)
|
||||||
is_on = model[:is_on]
|
is_on = model[:is_on]
|
||||||
prod_above = model[:prod_above]
|
prod_above = model[:prod_above]
|
||||||
reserve = model[:reserve]
|
reserve = model[:reserve]
|
||||||
@@ -147,9 +172,13 @@ function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_startup_shutdown_costs_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_startup_shutdown_costs_eqs!(
|
||||||
eq_startup_choose = _get(model, :eq_startup_choose)
|
model::JuMP.Model,
|
||||||
eq_startup_restrict = _get(model, :eq_startup_restrict)
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
eq_startup_choose = _init(model, :eq_startup_choose)
|
||||||
|
eq_startup_restrict = _init(model, :eq_startup_restrict)
|
||||||
S = length(g.startup_categories)
|
S = length(g.startup_categories)
|
||||||
startup = model[:startup]
|
startup = model[:startup]
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
@@ -190,10 +219,14 @@ function _add_startup_shutdown_costs_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_status_vars!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_status_vars!(
|
||||||
is_on = _get(model, :is_on)
|
model::JuMP.Model,
|
||||||
switch_on = _get(model, :switch_on)
|
g::Unit,
|
||||||
switch_off = _get(model, :switch_off)
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
is_on = _init(model, :is_on)
|
||||||
|
switch_on = _init(model, :switch_on)
|
||||||
|
switch_off = _init(model, :switch_off)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
if g.must_run[t]
|
if g.must_run[t]
|
||||||
is_on[g.name, t] = 1.0
|
is_on[g.name, t] = 1.0
|
||||||
@@ -208,9 +241,13 @@ function _add_status_vars!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_status_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_status_eqs!(
|
||||||
eq_binary_link = _get(model, :eq_binary_link)
|
model::JuMP.Model,
|
||||||
eq_switch_on_off = _get(model, :eq_switch_on_off)
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
|
eq_binary_link = _init(model, :eq_binary_link)
|
||||||
|
eq_switch_on_off = _init(model, :eq_switch_on_off)
|
||||||
is_on = model[:is_on]
|
is_on = model[:is_on]
|
||||||
switch_off = model[:switch_off]
|
switch_off = model[:switch_off]
|
||||||
switch_on = model[:switch_on]
|
switch_on = model[:switch_on]
|
||||||
@@ -240,11 +277,15 @@ function _add_status_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_ramp_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_ramp_eqs!(
|
||||||
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
prod_above = model[:prod_above]
|
prod_above = model[:prod_above]
|
||||||
reserve = model[:reserve]
|
reserve = model[:reserve]
|
||||||
eq_ramp_up = _get(model, :eq_ramp_up)
|
eq_ramp_up = _init(model, :eq_ramp_up)
|
||||||
eq_ramp_down = _get(model, :eq_ramp_down)
|
eq_ramp_down = _init(model, :eq_ramp_down)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
# Ramp up limit
|
# Ramp up limit
|
||||||
if t == 1
|
if t == 1
|
||||||
@@ -282,12 +323,16 @@ function _add_ramp_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_min_uptime_downtime_eqs!(
|
||||||
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
is_on = model[:is_on]
|
is_on = model[:is_on]
|
||||||
switch_off = model[:switch_off]
|
switch_off = model[:switch_off]
|
||||||
switch_on = model[:switch_on]
|
switch_on = model[:switch_on]
|
||||||
eq_min_uptime = _get(model, :eq_min_uptime)
|
eq_min_uptime = _init(model, :eq_min_uptime)
|
||||||
eq_min_downtime = _get(model, :eq_min_downtime)
|
eq_min_downtime = _init(model, :eq_min_downtime)
|
||||||
T = model[:instance].time
|
T = model[:instance].time
|
||||||
for t in 1:T
|
for t in 1:T
|
||||||
# Minimum up-time
|
# Minimum up-time
|
||||||
@@ -325,25 +370,29 @@ function _add_min_uptime_downtime_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_net_injection_eqs!(
|
||||||
|
model::JuMP.Model,
|
||||||
|
g::Unit,
|
||||||
|
formulation::_GeneratorFormulation,
|
||||||
|
)::Nothing
|
||||||
expr_net_injection = model[:expr_net_injection]
|
expr_net_injection = model[:expr_net_injection]
|
||||||
expr_reserve = model[:expr_reserve]
|
|
||||||
is_on = model[:is_on]
|
|
||||||
prod_above = model[:prod_above]
|
|
||||||
reserve = model[:reserve]
|
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
# Add to net injection expression
|
# Add to net injection expression
|
||||||
add_to_expression!(
|
add_to_expression!(
|
||||||
expr_net_injection[g.bus.name, t],
|
expr_net_injection[g.bus.name, t],
|
||||||
prod_above[g.name, t],
|
model[:prod_above][g.name, t],
|
||||||
1.0,
|
1.0,
|
||||||
)
|
)
|
||||||
add_to_expression!(
|
add_to_expression!(
|
||||||
expr_net_injection[g.bus.name, t],
|
expr_net_injection[g.bus.name, t],
|
||||||
is_on[g.name, t],
|
model[:is_on][g.name, t],
|
||||||
g.min_power[t],
|
g.min_power[t],
|
||||||
)
|
)
|
||||||
# Add to reserves expression
|
# Add to reserves expression
|
||||||
add_to_expression!(expr_reserve[g.bus.name, t], reserve[g.name, t], 1.0)
|
add_to_expression!(
|
||||||
|
model[:expr_reserve][g.bus.name, t],
|
||||||
|
model[:reserve][g.name, t],
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function set_name(x::Float64, n::String)
|
|||||||
# nop
|
# nop
|
||||||
end
|
end
|
||||||
|
|
||||||
function _get(model::JuMP.Model, key::Symbol)::OrderedDict
|
function _init(model::JuMP.Model, key::Symbol)::OrderedDict
|
||||||
if !(key in keys(object_dictionary(model)))
|
if !(key in keys(object_dictionary(model)))
|
||||||
model[key] = OrderedDict()
|
model[key] = OrderedDict()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +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.
|
|
||||||
|
|
||||||
abstract type Formulation end
|
|
||||||
Reference in New Issue
Block a user