Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interface #44

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion docs/src/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ An instance of the stochastic security-constrained unit commitment (SCUC) proble
* [Storage units](#Storage-units)
* [Price-sensitive loads](#Price-sensitive-loads)
* [Transmission lines](#Transmission-lines)
* [Interfaces](#Interfaces)
* [Reserves](#Reserves)
* [Contingencies](#Contingencies)

Expand Down Expand Up @@ -283,7 +284,7 @@ This section describes the characteristics of transmission system, such as its t
| `Target bus` | Identifier of the bus where the transmission line reaches. | Required | No | Yes
| `Susceptance (S)` | Susceptance of the transmission line (in siemens). | Required | No | Yes
| `Normal flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in its regular, fully-operational state. | `+inf` | Yes | Yes
| `Emergency flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in degraded state (for example, after the failure of another transmission line). | `+inf` | Y | Yes
| `Emergency flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in degraded state (for example, after the failure of another transmission line). | `+inf` | Yes | Yes
| `Flow limit penalty ($/MW)` | Penalty for violating the flow limits of the transmission line (in $/MW). This is charged per time step. For example, if there is a thermal violation of 1 MW for three time steps, then three times this amount will be charged. | `5000.0` | Yes | Yes

#### Example
Expand All @@ -303,6 +304,33 @@ This section describes the characteristics of transmission system, such as its t
}
```

### Interfaces

This section describes the characteristics of interfaces, such as its topology and the flow limits of each interface. An interface is a set of transmission lines that, when opened, split the network into different islands. A postive direction is pre-defined by the user between every 2 interfaces. The net flow through an interface is the sum of the flows through the positive lines minus the sum of the flows through the negative lines.

| Key | Description | Default | Time series? | Uncertain?
| :--------------------- | :----------------------------------------------- | ------- | :------------: | :---:
| `Outbound lines` | List of transmission lines flow out from a region, which is defined as the positve direction. May be omitted if no outbound lines available. | `[]` | No | Yes
| `Inbound lines` | List of transmission lines flow into a region, which is defined as the negative direction. May be omitted if no inbound lines available. | `[]` | No | Yes
| `Net flow upper limit (MW)` | Maximum net amount of power (in MW) allowed to flow through the interface. | `+inf` | Yes | Yes
| `Net flow lower limit (MW)` | Minimum net amount of power (in MW) allowed to flow through the interface. | `-inf` | Yes | Yes
| `Flow limit penalty ($/MW)` | Penalty for violating the flow limits of the interface (in $/MW). This is charged per time step. For example, if there is a thermal violation of 1 MW for three time steps, then three times this amount will be charged. | `5000.0` | Yes | Yes

#### Example

```json
{
"Interfaces": {
"ifc1": {
"Outbound lines": ["l2", "l3", "l5", "l7", "l8", "l9"],
"Inbound lines": ["l6"],
"Net flow upper limit (MW)": 2000,
"Net flow lower limit (MW)": -1500,
"Flow limit penalty ($/MW)": 9999.0
}
}
}
```

### Reserves

Expand Down
1 change: 1 addition & 0 deletions src/UnitCommitment.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
43 changes: 43 additions & 0 deletions src/instance/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions src/instance/structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/model/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions src/model/formulations/base/interface.jl
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions src/model/formulations/base/line.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,23 @@ 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
isf = UnitCommitment._injection_shift_factors(
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..."
Expand All @@ -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
23 changes: 23 additions & 0 deletions src/model/formulations/base/sensitivity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 = zeros(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})

Expand Down
17 changes: 17 additions & 0 deletions src/solution/solution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,26 @@ function solution(model::JuMP.Model)::OrderedDict
sol[sc.name]["Load curtail (MW)"] =
timeseries(model[:curtail], sc.buses, sc = sc)
end
# get matrix of bus net injection (BxT)
bus_net_injection = [
value(model[:net_injection][sc.name, bus.name, t]) for
bus in sc.buses, t in 1:T
]
if !isempty(sc.lines)
sol[sc.name]["Line overflow (MW)"] =
timeseries(model[:overflow], sc.lines, sc = sc)
# get the matrix of line flows (Lx(B-1))x((B-1)xT)=(LxT)
line_flows = sc.isf * bus_net_injection[2:end, :]
sol[sc.name]["Transmission line flow (MW)"] =
OrderedDict(l.name => line_flows[l.offset, :] for l in sc.lines)
end
if !isempty(sc.interfaces)
# get the matrix of interface flows (Ix(B-1))x((B-1)xT)=(IxT)
interface_flows = sc.interface_isf * bus_net_injection[2:end, :]
sol[sc.name]["Interface flow (MW)"] = OrderedDict(
ifc.name => interface_flows[ifc.offset, :] for
ifc in sc.interfaces
)
end
if !isempty(sc.price_sensitive_loads)
sol[sc.name]["Price-sensitive loads (MW)"] =
Expand Down
5 changes: 5 additions & 0 deletions src/transform/slice.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 added test/fixtures/case14-interface.json.gz
Binary file not shown.
Binary file added test/fixtures/case24-iberian-storage.json.gz
Binary file not shown.
Binary file added test/fixtures/case3-interface-2.json.gz
Binary file not shown.
Binary file added test/fixtures/case3-interface.json.gz
Binary file not shown.
4 changes: 4 additions & 0 deletions test/src/UnitCommitmentT.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ include("import/egret_test.jl")
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")
Expand Down Expand Up @@ -39,6 +41,8 @@ function runtests()
instance_read_test()
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()
Expand Down
2 changes: 1 addition & 1 deletion test/src/instance/migrate_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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, JuMP, JSON, GZip

function instance_migrate_test()
@testset "read v0.2" begin
Expand Down
18 changes: 17 additions & 1 deletion test/src/instance/read_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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, JuMP, JSON, GZip

function instance_read_test()
@testset "read_benchmark" begin
Expand Down Expand Up @@ -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
6 changes: 3 additions & 3 deletions test/src/lmp/aelmp_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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, Cbc, HiGHS, JuMP
using UnitCommitment, HiGHS, JuMP
import UnitCommitment: AELMP

function lmp_aelmp_test()
Expand All @@ -13,8 +13,8 @@ function lmp_aelmp_test()
model = UnitCommitment.build_model(
instance = instance,
optimizer = optimizer_with_attributes(
Cbc.Optimizer,
"logLevel" => 0,
HiGHS.Optimizer,
"log_to_console" => false,
),
variable_names = true,
)
Expand Down
Loading