pull/44/head
Jun He 2 years ago
parent 1b2a07b617
commit a51dafd422

@ -37,6 +37,7 @@ include("model/formulations/base/system.jl")
include("model/formulations/base/unit.jl")
include("model/formulations/base/punit.jl")
include("model/formulations/base/storage.jl")
include("model/formulations/base/interface.jl")
include("model/formulations/CarArr2006/pwlcosts.jl")
include("model/formulations/DamKucRajAta2016/ramp.jl")
include("model/formulations/Gar1962/pwlcosts.jl")

@ -137,6 +137,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
reserves = Reserve[]
profiled_units = ProfiledUnit[]
storage_units = StorageUnit[]
interfaces = Interface[]
function scalar(x; default = nothing)
x !== nothing || return default
@ -452,6 +453,45 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
end
end
# Read interfaces
if "Interfaces" in keys(json)
for (int_name, dict) in json["Interfaces"]
outbound_lines = TransmissionLine[]
inbound_lines = TransmissionLine[]
if "Outbound lines" in keys(dict)
outbound_lines = [
name_to_line[l] for
l in scalar(dict["Outbound lines"], default = [])
]
end
if "Inbound lines" in keys(dict)
inbound_lines = [
name_to_line[l] for
l in scalar(dict["Inbound lines"], default = [])
]
end
interface = Interface(
int_name,
length(interfaces) + 1,
outbound_lines,
inbound_lines,
timeseries(
dict["Net flow upper limit (MW)"],
default = [1e8 for t in 1:T],
),
timeseries(
dict["Net flow lower limit (MW)"],
default = [-1e8 for t in 1:T],
),
timeseries(
dict["Flow limit penalty (\$/MW)"],
default = [5000.0 for t in 1:T],
),
)
push!(interfaces, interface)
end
end
scenario = UnitCommitmentScenario(
name = scenario_name,
probability = probability,
@ -474,8 +514,11 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
profiled_units = profiled_units,
storage_units_by_name = Dict(su.name => su for su in storage_units),
storage_units = storage_units,
interfaces_by_name = Dict(i.name => i for i in interfaces),
interfaces = interfaces,
isf = spzeros(Float64, length(lines), length(buses) - 1),
lodf = spzeros(Float64, length(lines), length(lines)),
interface_isf = spzeros(Float64, length(interfaces), length(buses) - 1),
)
if repair
UnitCommitment.repair!(scenario)

@ -103,6 +103,16 @@ mutable struct StorageUnit
max_ending_level::Float64
end
mutable struct Interface
name::String
offset::Int
outbound_lines::Vector{TransmissionLine}
inbound_lines::Vector{TransmissionLine}
net_flow_upper_limit::Vector{Float64}
net_flow_lower_limit::Vector{Float64}
flow_limit_penalty::Vector{Float64}
end
Base.@kwdef mutable struct UnitCommitmentScenario
buses_by_name::Dict{AbstractString,Bus}
buses::Vector{Bus}
@ -125,6 +135,9 @@ Base.@kwdef mutable struct UnitCommitmentScenario
thermal_units::Vector{ThermalUnit}
storage_units_by_name::Dict{AbstractString,StorageUnit}
storage_units::Vector{StorageUnit}
interfaces_by_name::Dict{AbstractString,Interface}
interfaces::Vector{Interface}
interface_isf::Array{Float64,2}
time::Int
time_step::Int
end

@ -103,6 +103,9 @@ function build_model(;
_add_storage_unit!(model, su, sc)
end
_add_system_wide_eqs!(model, sc)
for ifc in sc.interfaces
_add_interface!(model, ifc, formulation.transmission, sc)
end
end
@objective(model, Min, model[:obj])
end

@ -0,0 +1,46 @@
# 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_interface!(
model::JuMP.Model,
ifc::Interface,
f::ShiftFactorsFormulation,
sc::UnitCommitmentScenario,
)::Nothing
overflow = _init(model, :interface_overflow)
net_injection = _init(model, :net_injection)
for t in 1:model[:instance].time
# define the net flow variable
flow = @variable(model, base_name = "interface_flow[$(ifc.name),$t]")
# define the overflow variable
overflow[sc.name, ifc.name, t] = @variable(model, lower_bound = 0)
# constraints: lb - v <= flow <= ub + v
@constraint(
model,
flow <=
ifc.net_flow_upper_limit[t] + overflow[sc.name, ifc.name, t]
)
@constraint(
model,
-flow <=
-ifc.net_flow_lower_limit[t] + overflow[sc.name, ifc.name, t]
)
# constraint: flow value is calculated from the interface ISF matrix
@constraint(
model,
flow == sum(
net_injection[sc.name, b.name, t] *
sc.interface_isf[ifc.offset, b.offset] for
b in sc.buses if b.offset > 0
)
)
# make overflow part of the objective as a punishment term
add_to_expression!(
model[:obj],
overflow[sc.name, ifc.name, t],
ifc.flow_limit_penalty[t] * sc.probability,
)
end
return
end

@ -26,9 +26,11 @@ function _setup_transmission(
)::Nothing
isf = formulation.precomputed_isf
lodf = formulation.precomputed_lodf
interface_isf = nothing
if length(sc.buses) == 1
isf = zeros(0, 0)
lodf = zeros(0, 0)
interface_isf = zeros(0, 0)
elseif isf === nothing
@info "Computing injection shift factors..."
time_isf = @elapsed begin
@ -36,6 +38,11 @@ function _setup_transmission(
buses = sc.buses,
lines = sc.lines,
)
interface_isf =
UnitCommitment._interface_injection_shift_factors(
interfaces = sc.interfaces,
isf = isf,
)
end
@info @sprintf("Computed ISF in %.2f seconds", time_isf)
@info "Computing line outage factors..."
@ -53,9 +60,13 @@ function _setup_transmission(
formulation.lodf_cutoff
)
isf[abs.(isf).<formulation.isf_cutoff] .= 0
interface_isf[abs.(interface_isf).<formulation.isf_cutoff] .= 0
lodf[abs.(lodf).<formulation.lodf_cutoff] .= 0
end
sc.isf = isf
sc.lodf = lodf
if interface_isf !== nothing
sc.interface_isf = round.(interface_isf, digits = 5)
end
return
end

@ -24,6 +24,29 @@ function _injection_shift_factors(;
return isf
end
"""
_interface_injection_shift_factors(; interfaces, isf)
Returns a Ix(B-1) matrix M, where B is the number of buses and I is the number
of interfaces. For a given bus b and interface ifc, the entry M[ifc.offset, b.offset]
indicates the amount of power (in MW) that net flows through transmission lines of
the interface ifc when 1 MW of power is injected at b and withdrawn from the
slack bus (the bus that has offset zero).
"""
function _interface_injection_shift_factors(;
interfaces::Array{Interface},
isf::Array{Float64,2},
)
interface_isf = spzeros(Float64, length(interfaces), size(isf, 2))
for ifc in interfaces
outbound_lines = [l.offset for l in ifc.outbound_lines]
inbound_lines = [l.offset for l in ifc.inbound_lines]
interface_isf[ifc.offset, :] += sum(isf[outbound_lines, :], dims = 1)'
interface_isf[ifc.offset, :] -= sum(isf[inbound_lines, :], dims = 1)'
end
return interface_isf
end
"""
_reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine})

@ -71,6 +71,11 @@ function slice(
su.min_discharge_rate = su.min_discharge_rate[range]
su.max_discharge_rate = su.max_discharge_rate[range]
end
for ifc in sc.interfaces
ifc.net_flow_upper_limit = ifc.net_flow_upper_limit[range]
ifc.net_flow_lower_limit = ifc.net_flow_lower_limit[range]
ifc.flow_limit_penalty = ifc.flow_limit_penalty[range]
end
end
return modified
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -10,6 +10,7 @@ include("instance/read_test.jl")
include("instance/migrate_test.jl")
include("model/formulations_test.jl")
include("model/storage_optimization_test.jl")
include("model/interface_optimization_test.jl")
include("solution/methods/XavQiuWanThi19/filter_test.jl")
include("solution/methods/XavQiuWanThi19/find_test.jl")
include("solution/methods/XavQiuWanThi19/sensitivity_test.jl")
@ -41,6 +42,7 @@ function runtests()
instance_migrate_test()
model_formulations_test()
storage_optimization_test()
interface_optimization_test()
solution_methods_XavQiuWanThi19_filter_test()
solution_methods_XavQiuWanThi19_find_test()
solution_methods_XavQiuWanThi19_sensitivity_test()

@ -224,4 +224,20 @@ function instance_read_test()
@test su4.simultaneous_charge_and_discharge ==
[false, false, true, true]
end
@testset "read_benchmark interface" begin
instance = UnitCommitment.read(fixture("case14-interface.json.gz"))
sc = instance.scenarios[1]
@test length(sc.interfaces) == 1
@test sc.interfaces_by_name["ifc1"].name == "ifc1"
ifc = sc.interfaces[1]
@test ifc.name == "ifc1"
@test ifc.offset == 1
@test length(ifc.outbound_lines) == 6
@test length(ifc.inbound_lines) == 1
@test ifc.net_flow_upper_limit == [2000 for t in 1:4]
@test ifc.net_flow_lower_limit == [-1500 for t in 1:4]
@test ifc.flow_limit_penalty == [9999.0 for t in 1:4]
end
end

@ -0,0 +1,40 @@
# 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
using JuMP
using HiGHS
using JSON
function interface_optimization_test()
@testset "interface_optimization" begin
# case3-interface: only outbounds
instance = UnitCommitment.read(fixture("case3-interface.json.gz"))
model = UnitCommitment.build_model(
instance = instance,
optimizer = HiGHS.Optimizer,
variable_names = true,
)
set_silent(model)
UnitCommitment.optimize!(model)
@test value(variable_by_name(model, "interface_flow[ifc1,3]")) 20.0 atol =
0.1
@test value(variable_by_name(model, "interface_flow[ifc1,4]")) 20.0 atol =
0.1
# case3-interface-2: one outbound, one inbound
instance = UnitCommitment.read(fixture("case3-interface-2.json.gz"))
model = UnitCommitment.build_model(
instance = instance,
optimizer = HiGHS.Optimizer,
variable_names = true,
)
set_silent(model)
UnitCommitment.optimize!(model)
@test value(variable_by_name(model, "interface_flow[ifc1,1]")) 95.0 atol =
0.1
@test value(variable_by_name(model, "interface_flow[ifc1,2]")) 95.0 atol =
0.1
end
end

@ -146,4 +146,19 @@ function solution_methods_XavQiuWanThi19_sensitivity_test()
end
end
end
@testset "_interface_injection_shift_factors" begin
instance = UnitCommitment.read(fixture("/case14-interface.json.gz"))
sc = instance.scenarios[1]
actual = UnitCommitment._interface_injection_shift_factors(
interfaces = sc.interfaces,
isf = UnitCommitment._injection_shift_factors(
lines = sc.lines,
buses = sc.buses,
),
)
@test size(actual) == (1, 13)
@test round.(collect(actual), digits = 2) ==
[0.0 -1.0 0.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0]
end
end

@ -2,7 +2,7 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
using UnitCommitment, LinearAlgebra, HiGHS, JuMP, JSON, GZip
function transform_slice_test()
@testset "slice" begin
@ -38,7 +38,10 @@ function transform_slice_test()
end
# Should be able to build model without errors
optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
optimizer = optimizer_with_attributes(
HiGHS.Optimizer,
"log_to_console" => false,
)
model = UnitCommitment.build_model(
instance = modified,
optimizer = optimizer,
@ -58,7 +61,10 @@ function transform_slice_test()
end
# Should be able to build model without errors
optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
optimizer = optimizer_with_attributes(
HiGHS.Optimizer,
"log_to_console" => false,
)
model = UnitCommitment.build_model(
instance = modified,
optimizer = optimizer,
@ -88,7 +94,34 @@ function transform_slice_test()
end
# Should be able to build model without errors
optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
optimizer = optimizer_with_attributes(
HiGHS.Optimizer,
"log_to_console" => false,
)
model = UnitCommitment.build_model(
instance = modified,
optimizer = optimizer,
variable_names = true,
)
end
@testset "slice interfaces" begin
instance = UnitCommitment.read(fixture("case14-interface.json.gz"))
modified = UnitCommitment.slice(instance, 1:3)
sc = modified.scenarios[1]
# Should update all time-dependent fields
for ifc in sc.interfaces
@test length(ifc.net_flow_upper_limit) == 3
@test length(ifc.net_flow_lower_limit) == 3
@test length(ifc.flow_limit_penalty) == 3
end
# Should be able to build model without errors
optimizer = optimizer_with_attributes(
HiGHS.Optimizer,
"log_to_console" => false,
)
model = UnitCommitment.build_model(
instance = modified,
optimizer = optimizer,

Loading…
Cancel
Save