mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
Compare commits
11 Commits
feature/ra
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
| d751c2af88 | |||
| 1397ae438e | |||
| 7308ff6477 | |||
|
|
8b6cbe8c1b | ||
|
|
c2557a64d1 | ||
|
|
5afb2363af | ||
|
|
860c47b7e3 | ||
|
|
37b21853be | ||
|
|
c8c7350096 | ||
|
|
7302fabe37 | ||
|
|
4ed13d6e95 |
@@ -15,7 +15,6 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
|
||||
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
||||
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
|
||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
|
||||
|
||||
[compat]
|
||||
@@ -31,8 +30,8 @@ julia = "1"
|
||||
|
||||
[extras]
|
||||
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
|
||||
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
|
||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
|
||||
|
||||
[targets]
|
||||
test = ["Cbc", "Test", "Gurobi"]
|
||||
|
||||
@@ -48,7 +48,7 @@ include("solution/warmstart.jl")
|
||||
include("solution/write.jl")
|
||||
include("transform/initcond.jl")
|
||||
include("transform/slice.jl")
|
||||
include("transform/randomize/XavQiuAhm2021.jl")
|
||||
include("transform/randomize.jl")
|
||||
include("utils/log.jl")
|
||||
include("validation/repair.jl")
|
||||
include("validation/validate.jl")
|
||||
|
||||
96
src/model/formulations/GenMorRam2017/startstop.jl
Normal file
96
src/model/formulations/GenMorRam2017/startstop.jl
Normal file
@@ -0,0 +1,96 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
|
||||
Startup and shutdown limits from Gentile et al. (2017).
|
||||
Eqns. (20), (23a), and (23b) in Knueven et al. (2020).
|
||||
|
||||
Creates constraints `eq_startstop_limit`, `eq_startup_limit`, and `eq_shutdown_limit`
|
||||
using variables `Gar1962.StatusVars`, `prod_above` from `Gar1962.ProdVars`, and `reserve`.
|
||||
|
||||
Constraints
|
||||
---
|
||||
* `eq_startstop_limit`
|
||||
* `eq_startup_limit`
|
||||
* `eq_shutdown_limit`
|
||||
"""
|
||||
function _add_startup_shutdown_limit_eqs!(
|
||||
model::JuMP.Model,
|
||||
g::Unit,
|
||||
formulation_prod_vars::Gar1962.ProdVars,
|
||||
formulation_status_vars::Gar1962.StatusVars,
|
||||
)::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
|
||||
|
||||
eq_startstop_limit = _init(model, :eq_startstop_limit)
|
||||
eq_shutdown_limit = _init(model, :eq_shutdown_limit)
|
||||
eq_startup_limit = _init(model, :eq_startup_limit)
|
||||
|
||||
is_on = model[:is_on]
|
||||
prod_above = model[:prod_above]
|
||||
reserve = model[:reserve]
|
||||
switch_off = model[:switch_off]
|
||||
switch_on = model[:switch_on]
|
||||
|
||||
T = model[:instance].time
|
||||
gi = g.name
|
||||
|
||||
if g.initial_power > g.shutdown_limit
|
||||
eqs.shutdown_limit[gi, 0] =
|
||||
@constraint(mip, vars.switch_off[gi, 1] <= 0)
|
||||
end
|
||||
|
||||
for t in 1:T
|
||||
## 2020-10-09 amk: added eqn (20) and check of g.min_uptime
|
||||
# Not present in (23) in Kneueven et al.
|
||||
if g.min_uptime > 1
|
||||
# Equation (20) in Knueven et al. (2020)
|
||||
eqs.startstop_limit[gi, t] = @constraint(
|
||||
model,
|
||||
prod_above[gi, t] + reserve[gi, t] <=
|
||||
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
|
||||
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t] - (
|
||||
t < T ?
|
||||
max(0, g.max_power[t] - g.shutdown_limit) *
|
||||
switch_off[gi, t+1] : 0.0
|
||||
)
|
||||
)
|
||||
else
|
||||
## Startup limits
|
||||
# Equation (23a) in Knueven et al. (2020)
|
||||
eqs.startup_limit[gi, t] = @constraint(
|
||||
model,
|
||||
prod_above[gi, t] + reserve[gi, t] <=
|
||||
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
|
||||
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t] - (
|
||||
t < T ?
|
||||
max(0, g.startup_limit - g.shutdown_limit) *
|
||||
switch_off[gi, t+1] : 0.0
|
||||
)
|
||||
)
|
||||
|
||||
## Shutdown limits
|
||||
if t < T
|
||||
# Equation (23b) in Knueven et al. (2020)
|
||||
eqs.shutdown_limit[gi, t] = @constraint(
|
||||
model,
|
||||
prod_above[gi, t] + reserve[gi, t] <=
|
||||
(g.max_power[t] - g.min_power[t]) * xis_on[gi, t] - (
|
||||
t < T ?
|
||||
max(0, g.max_power[t] - g.shutdown_limit) *
|
||||
switch_off[gi, t+1] : 0.0
|
||||
) -
|
||||
max(0, g.shutdown_limit - g.startup_limit) *
|
||||
switch_on[gi, t]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
89
src/model/formulations/MorLatRam2013/startstop.jl
Normal file
89
src/model/formulations/MorLatRam2013/startstop.jl
Normal file
@@ -0,0 +1,89 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
|
||||
Startup and shutdown limits from Morales-España et al. (2013a).
|
||||
Eqns. (20), (21a), and (21b) in Knueven et al. (2020).
|
||||
|
||||
Uses variable `prod_above` from `Gar1962.ProdVars`, the variables in `Gar1962.StatusVars`, and `reserve`
|
||||
to generate constraints below.
|
||||
|
||||
Constraints
|
||||
---
|
||||
* :eq_startstop_limit
|
||||
* :eq_startup_limit
|
||||
* :eq_shutdown_limit
|
||||
"""
|
||||
function _add_startup_shutdown_limit_eqs!(
|
||||
model::JuMP.Model,
|
||||
g::Unit,
|
||||
formulation_prod_vars::Gar1962.ProdVars,
|
||||
formulation_status_vars::Gar1962.StatusVars,
|
||||
)::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
|
||||
|
||||
eq_startstop_limit = _init(model, :eq_startstop_limit)
|
||||
eq_shutdown_limit = _init(model, :eq_shutdown_limit)
|
||||
eq_startup_limit = _init(model, :eq_startup_limit)
|
||||
|
||||
is_on = model[:is_on]
|
||||
prod_above = model[:prod_above]
|
||||
reserve = model[:reserve]
|
||||
switch_off = model[:switch_off]
|
||||
switch_on = model[:switch_on]
|
||||
|
||||
T = model[:instance].time
|
||||
gi = g.name
|
||||
for t in 1:T
|
||||
## 2020-10-09 amk: added eqn (20) and check of g.min_uptime
|
||||
if g.min_uptime > 1 && t < T
|
||||
# Equation (20) in Knueven et al. (2020)
|
||||
# UT > 1 required, to guarantee that vars.switch_on[gi, t] and vars.switch_off[gi, t+1] are not both = 1 at the same time
|
||||
eq_startstop_limit[gi, t] = @constraint(
|
||||
model,
|
||||
prod_above[gi, t] + reserve[gi, t] <=
|
||||
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
|
||||
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t] -
|
||||
max(0, g.max_power[t] - g.shutdown_limit) * switch_off[gi, t+1]
|
||||
)
|
||||
else
|
||||
## Startup limits
|
||||
# Equation (21a) in Knueven et al. (2020)
|
||||
# Proposed by Morales-España et al. (2013a)
|
||||
eqs_startup_limit[gi, t] = @constraint(
|
||||
model,
|
||||
prod_above[gi, t] + reserve[gi, t] <=
|
||||
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
|
||||
max(0, g.max_power[t] - g.startup_limit) * switch_on[gi, t]
|
||||
)
|
||||
|
||||
## Shutdown limits
|
||||
if t < T
|
||||
# Equation (21b) in Knueven et al. (2020)
|
||||
# TODO different from what was in previous model, due to reserve variable
|
||||
# ax: ideally should have reserve_up and reserve_down variables
|
||||
# i.e., the generator should be able to increase/decrease production as specified
|
||||
# (this is a heuristic for a "robust" solution,
|
||||
# in case there is an outage or a surge, and flow has to be redirected)
|
||||
# amk: if shutdown_limit is the max prod of generator in time period before shutting down,
|
||||
# then it makes sense to count reserves, because otherwise, if reserves ≠ 0,
|
||||
# then the generator will actually produce more than the limit
|
||||
eqs.shutdown_limit[gi, t] = @constraint(
|
||||
model,
|
||||
prod_above[gi, t] +
|
||||
(RESERVES_WHEN_SHUT_DOWN ? reserve[gi, t] : 0.0) <=
|
||||
(g.max_power[t] - g.min_power[t]) * is_on[gi, t] -
|
||||
max(0, g.max_power[t] - g.shutdown_limit) *
|
||||
switch_off[gi, t+1]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -35,7 +35,12 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation)
|
||||
formulation.status_vars,
|
||||
)
|
||||
_add_startup_cost_eqs!(model, g, formulation.startup_costs)
|
||||
_add_startup_shutdown_limit_eqs!(model, g)
|
||||
_add_startup_shutdown_limit_eqs!(
|
||||
model,
|
||||
g,
|
||||
formulation.prod_vars,
|
||||
formulation.status_vars,
|
||||
)
|
||||
_add_status_eqs!(model, g, formulation.status_vars)
|
||||
return
|
||||
end
|
||||
@@ -76,7 +81,22 @@ function _add_startup_shutdown_vars!(model::JuMP.Model, g::Unit)::Nothing
|
||||
return
|
||||
end
|
||||
|
||||
function _add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
"""
|
||||
_add_startup_shutdown_limit_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
||||
|
||||
Creates startup/shutdown limit constraints below based on variables `Gar1962.StatusVars`, `prod_above` from `Gar1962.ProdVars`, and `reserve`.
|
||||
|
||||
Constraints
|
||||
---
|
||||
* :eq_startup_limit
|
||||
* :eq_shutdown_limit
|
||||
"""
|
||||
function _add_startup_shutdown_limit_eqs!(
|
||||
model::JuMP.Model,
|
||||
g::Unit,
|
||||
formulation_prod_vars::Gar1962.ProdVars,
|
||||
formulation_status_vars::Gar1962.StatusVars,
|
||||
)::Nothing
|
||||
eq_shutdown_limit = _init(model, :eq_shutdown_limit)
|
||||
eq_startup_limit = _init(model, :eq_startup_limit)
|
||||
is_on = model[:is_on]
|
||||
|
||||
53
src/transform/randomize.jl
Normal file
53
src/transform/randomize.jl
Normal file
@@ -0,0 +1,53 @@
|
||||
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
using Distributions
|
||||
|
||||
function randomize_unit_costs!(
|
||||
instance::UnitCommitmentInstance;
|
||||
distribution = Uniform(0.95, 1.05),
|
||||
)::Nothing
|
||||
for unit in instance.units
|
||||
α = rand(distribution)
|
||||
unit.min_power_cost *= α
|
||||
for k in unit.cost_segments
|
||||
k.cost *= α
|
||||
end
|
||||
for s in unit.startup_categories
|
||||
s.cost *= α
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function randomize_load_distribution!(
|
||||
instance::UnitCommitmentInstance;
|
||||
distribution = Uniform(0.90, 1.10),
|
||||
)::Nothing
|
||||
α = rand(distribution, length(instance.buses))
|
||||
for t in 1:instance.time
|
||||
total = sum(bus.load[t] for bus in instance.buses)
|
||||
den = sum(
|
||||
bus.load[t] / total * α[i] for
|
||||
(i, bus) in enumerate(instance.buses)
|
||||
)
|
||||
for (i, bus) in enumerate(instance.buses)
|
||||
bus.load[t] *= α[i] / den
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function randomize_peak_load!(
|
||||
instance::UnitCommitmentInstance;
|
||||
distribution = Uniform(0.925, 1.075),
|
||||
)::Nothing
|
||||
α = rand(distribution)
|
||||
for bus in instance.buses
|
||||
bus.load *= α
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
export randomize_unit_costs!, randomize_load_distribution!, randomize_peak_load!
|
||||
@@ -1,209 +0,0 @@
|
||||
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
"""
|
||||
Methods described in:
|
||||
|
||||
Xavier, Álinson S., Feng Qiu, and Shabbir Ahmed. "Learning to solve
|
||||
large-scale security-constrained unit commitment problems." INFORMS
|
||||
Journal on Computing 33.2 (2021): 739-756. DOI: 10.1287/ijoc.2020.0976
|
||||
"""
|
||||
module XavQiuAhm2021
|
||||
|
||||
using Distributions
|
||||
import ..UnitCommitmentInstance
|
||||
|
||||
"""
|
||||
struct Randomization
|
||||
cost = Uniform(0.95, 1.05)
|
||||
load_profile_mu = [...]
|
||||
load_profile_sigma = [...]
|
||||
load_share = Uniform(0.90, 1.10)
|
||||
peak_load = Uniform(0.6 * 0.925, 0.6 * 1.075)
|
||||
randomize_costs = true
|
||||
randomize_load_profile = true
|
||||
randomize_load_share = true
|
||||
end
|
||||
|
||||
Randomization method that changes: (1) production and startup costs, (2)
|
||||
share of load coming from each bus, (3) peak system load, and (4) temporal
|
||||
load profile, as follows:
|
||||
|
||||
1. **Production and startup costs:**
|
||||
For each unit `u`, the vectors `u.min_power_cost` and `u.cost_segments`
|
||||
are multiplied by a constant `α[u]` sampled from the provided `cost`
|
||||
distribution. If `randomize_costs` is false, skips this step.
|
||||
|
||||
2. **Load share:**
|
||||
For each bus `b` and time `t`, the value `b.load[t]` is multiplied by
|
||||
`(β[b] * b.load[t]) / sum(β[b2] * b2.load[t] for b2 in buses)`, where
|
||||
`β[b]` is sampled from the provided `load_share` distribution. If
|
||||
`randomize_load_share` is false, skips this step.
|
||||
|
||||
3. **Peak system load and temporal load profile:**
|
||||
Sets the peak load to `ρ * C`, where `ρ` is sampled from `peak_load` and `C`
|
||||
is the maximum system capacity, at any time. Also scales the loads of all
|
||||
buses, so that `system_load[t+1]` becomes equal to `system_load[t] * γ[t]`,
|
||||
where `γ[t]` is sampled from `Normal(load_profile_mu[t], load_profile_sigma[t])`.
|
||||
|
||||
The system load for the first time period is set so that the peak load
|
||||
matches `ρ * C`. If `load_profile_sigma` and `load_profile_mu` have fewer
|
||||
elements than `instance.time`, wraps around. If `randomize_load_profile`
|
||||
is false, skips this step.
|
||||
|
||||
The default parameters were obtained based on an analysis of publicly available
|
||||
bid and hourly data from PJM, corresponding to the month of January, 2017. For
|
||||
more details, see Section 4.2 of the paper.
|
||||
"""
|
||||
Base.@kwdef struct Randomization
|
||||
cost = Uniform(0.95, 1.05)
|
||||
load_profile_mu::Vector{Float64} = [
|
||||
1.0,
|
||||
0.978,
|
||||
0.98,
|
||||
1.004,
|
||||
1.02,
|
||||
1.078,
|
||||
1.132,
|
||||
1.018,
|
||||
0.999,
|
||||
1.006,
|
||||
0.999,
|
||||
0.987,
|
||||
0.975,
|
||||
0.984,
|
||||
0.995,
|
||||
1.005,
|
||||
1.045,
|
||||
1.106,
|
||||
0.981,
|
||||
0.981,
|
||||
0.978,
|
||||
0.948,
|
||||
0.928,
|
||||
0.953,
|
||||
]
|
||||
load_profile_sigma::Vector{Float64} = [
|
||||
0.0,
|
||||
0.011,
|
||||
0.015,
|
||||
0.01,
|
||||
0.012,
|
||||
0.029,
|
||||
0.055,
|
||||
0.027,
|
||||
0.026,
|
||||
0.023,
|
||||
0.013,
|
||||
0.012,
|
||||
0.014,
|
||||
0.011,
|
||||
0.008,
|
||||
0.008,
|
||||
0.02,
|
||||
0.02,
|
||||
0.016,
|
||||
0.012,
|
||||
0.014,
|
||||
0.015,
|
||||
0.017,
|
||||
0.024,
|
||||
]
|
||||
load_share = Uniform(0.90, 1.10)
|
||||
peak_load = Uniform(0.6 * 0.925, 0.6 * 1.075)
|
||||
randomize_load_profile::Bool = true
|
||||
randomize_costs::Bool = true
|
||||
randomize_load_share::Bool = true
|
||||
end
|
||||
|
||||
function _randomize_costs(
|
||||
instance::UnitCommitmentInstance,
|
||||
distribution,
|
||||
)::Nothing
|
||||
for unit in instance.units
|
||||
α = rand(distribution)
|
||||
unit.min_power_cost *= α
|
||||
for k in unit.cost_segments
|
||||
k.cost *= α
|
||||
end
|
||||
for s in unit.startup_categories
|
||||
s.cost *= α
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function _randomize_load_share(
|
||||
instance::UnitCommitmentInstance,
|
||||
distribution,
|
||||
)::Nothing
|
||||
α = rand(distribution, length(instance.buses))
|
||||
for t in 1:instance.time
|
||||
total = sum(bus.load[t] for bus in instance.buses)
|
||||
den = sum(
|
||||
bus.load[t] / total * α[i] for
|
||||
(i, bus) in enumerate(instance.buses)
|
||||
)
|
||||
for (i, bus) in enumerate(instance.buses)
|
||||
bus.load[t] *= α[i] / den
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
function _randomize_load_profile(
|
||||
instance::UnitCommitmentInstance,
|
||||
params::Randomization,
|
||||
)::Nothing
|
||||
# Generate new system load
|
||||
system_load = [1.0]
|
||||
for t in 2:instance.time
|
||||
idx = (t - 1) % length(params.load_profile_mu) + 1
|
||||
gamma = rand(
|
||||
Normal(params.load_profile_mu[idx], params.load_profile_sigma[idx]),
|
||||
)
|
||||
push!(system_load, system_load[t-1] * gamma)
|
||||
end
|
||||
capacity = sum(maximum(u.max_power) for u in instance.units)
|
||||
peak_load = rand(params.peak_load) * capacity
|
||||
system_load = system_load ./ maximum(system_load) .* peak_load
|
||||
|
||||
# Scale bus loads to match the new system load
|
||||
prev_system_load = sum(b.load for b in instance.buses)
|
||||
for b in instance.buses
|
||||
for t in 1:instance.time
|
||||
b.load[t] *= system_load[t] / prev_system_load[t]
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
"""
|
||||
function randomize!(
|
||||
instance::UnitCommitment.UnitCommitmentInstance,
|
||||
method::XavQiuAhm2021.Randomization,
|
||||
)::Nothing
|
||||
|
||||
Randomize costs and loads based on the method described in XavQiuAhm2021.
|
||||
"""
|
||||
function randomize!(
|
||||
instance::UnitCommitment.UnitCommitmentInstance,
|
||||
method::XavQiuAhm2021.Randomization,
|
||||
)::Nothing
|
||||
if method.randomize_costs
|
||||
XavQiuAhm2021._randomize_costs(instance, method.cost)
|
||||
end
|
||||
if method.randomize_load_share
|
||||
XavQiuAhm2021._randomize_load_share(instance, method.load_share)
|
||||
end
|
||||
if method.randomize_load_profile
|
||||
XavQiuAhm2021._randomize_load_profile(instance, method)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
export randomize!
|
||||
@@ -28,9 +28,7 @@ const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV))
|
||||
@testset "transform" begin
|
||||
include("transform/initcond_test.jl")
|
||||
include("transform/slice_test.jl")
|
||||
@testset "randomize" begin
|
||||
include("transform/randomize/XavQiuAhm2021_test.jl")
|
||||
end
|
||||
include("transform/randomize_test.jl")
|
||||
end
|
||||
@testset "validation" begin
|
||||
include("validation/repair_test.jl")
|
||||
|
||||
@@ -1,63 +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.
|
||||
|
||||
import Random
|
||||
import UnitCommitment: XavQiuAhm2021
|
||||
|
||||
using Distributions
|
||||
using UnitCommitment, Cbc, JuMP
|
||||
|
||||
get_instance() = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
|
||||
system_load(instance) = sum(b.load for b in instance.buses)
|
||||
test_approx(x, y) = @test isapprox(x, y, atol = 1e-3)
|
||||
|
||||
@testset "XavQiuAhm2021" begin
|
||||
@testset "cost and load share" begin
|
||||
instance = get_instance()
|
||||
|
||||
# Check original costs
|
||||
unit = instance.units[10]
|
||||
test_approx(unit.min_power_cost[1], 825.023)
|
||||
test_approx(unit.cost_segments[1].cost[1], 36.659)
|
||||
test_approx(unit.startup_categories[1].cost[1], 7570.42)
|
||||
|
||||
# Check original load share
|
||||
bus = instance.buses[1]
|
||||
prev_system_load = system_load(instance)
|
||||
test_approx(bus.load[1] / prev_system_load[1], 0.012)
|
||||
|
||||
Random.seed!(42)
|
||||
randomize!(
|
||||
instance,
|
||||
XavQiuAhm2021.Randomization(randomize_load_profile = false),
|
||||
)
|
||||
|
||||
# Check randomized costs
|
||||
test_approx(unit.min_power_cost[1], 831.977)
|
||||
test_approx(unit.cost_segments[1].cost[1], 36.968)
|
||||
test_approx(unit.startup_categories[1].cost[1], 7634.226)
|
||||
|
||||
# Check randomized load share
|
||||
curr_system_load = system_load(instance)
|
||||
test_approx(bus.load[1] / curr_system_load[1], 0.013)
|
||||
|
||||
# System load should not change
|
||||
@test prev_system_load ≈ curr_system_load
|
||||
end
|
||||
|
||||
@testset "load profile" begin
|
||||
instance = get_instance()
|
||||
|
||||
# Check original load profile
|
||||
@test round.(system_load(instance), digits = 1)[1:8] ≈
|
||||
[3059.5, 2983.2, 2937.5, 2953.9, 3073.1, 3356.4, 4068.5, 4018.8]
|
||||
|
||||
Random.seed!(42)
|
||||
randomize!(instance, XavQiuAhm2021.Randomization())
|
||||
|
||||
# Check randomized load profile
|
||||
@test round.(system_load(instance), digits = 1)[1:8] ≈
|
||||
[4854.7, 4849.2, 4732.7, 4848.2, 4948.4, 5231.1, 5874.8, 5934.8]
|
||||
end
|
||||
end
|
||||
43
test/transform/randomize_test.jl
Normal file
43
test/transform/randomize_test.jl
Normal file
@@ -0,0 +1,43 @@
|
||||
# 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, Cbc, JuMP
|
||||
|
||||
_get_instance() = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
|
||||
_total_load(instance) = sum(b.load[1] for b in instance.buses)
|
||||
|
||||
@testset "randomize_unit_costs!" begin
|
||||
instance = _get_instance()
|
||||
unit = instance.units[10]
|
||||
prev_min_power_cost = unit.min_power_cost
|
||||
prev_prod_cost = unit.cost_segments[1].cost
|
||||
prev_startup_cost = unit.startup_categories[1].cost
|
||||
randomize_unit_costs!(instance)
|
||||
@test prev_min_power_cost != unit.min_power_cost
|
||||
@test prev_prod_cost != unit.cost_segments[1].cost
|
||||
@test prev_startup_cost != unit.startup_categories[1].cost
|
||||
end
|
||||
|
||||
@testset "randomize_load_distribution!" begin
|
||||
instance = _get_instance()
|
||||
bus = instance.buses[1]
|
||||
prev_load = instance.buses[1].load[1]
|
||||
prev_total_load = _total_load(instance)
|
||||
randomize_load_distribution!(instance)
|
||||
curr_total_load = _total_load(instance)
|
||||
@test prev_load != instance.buses[1].load[1]
|
||||
@test abs(prev_total_load - curr_total_load) < 1e-3
|
||||
end
|
||||
|
||||
@testset "randomize_peak_load!" begin
|
||||
instance = _get_instance()
|
||||
bus = instance.buses[1]
|
||||
prev_total_load = _total_load(instance)
|
||||
prev_share = bus.load[1] / prev_total_load
|
||||
randomize_peak_load!(instance)
|
||||
curr_total_load = _total_load(instance)
|
||||
curr_share = bus.load[1] / prev_total_load
|
||||
@test curr_total_load != prev_total_load
|
||||
@test abs(curr_share - prev_share) < 1e-3
|
||||
end
|
||||
Reference in New Issue
Block a user