mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 00:08:52 -06:00
Rename internal methods to _something; reformat code
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
### Added
|
||||
- Sub-hourly unit commitment
|
||||
- Added function `UnitCommitment.write(filename, solution)` to simplify saving the solution to a file
|
||||
- Added function `UnitCommitment.write(filename, solution)`
|
||||
- Added mathematical formulation to the documentation
|
||||
|
||||
### Changed
|
||||
@@ -18,7 +18,8 @@
|
||||
- `model.isf` becomes `model[:isf]`
|
||||
- `model.lodf` becomes `model[:lodf]`
|
||||
- The function `UnitCommitment.get_solution` has been renamed to `UnitCommitment.solution`
|
||||
- The function `UnitCommitment.fix!(instance)`, which attempts to repair an invalid instance, has been renamed to `UnitCommitment.repair!(instance)`
|
||||
- All function that do not appear in the documentation have been renamed to `_something`.
|
||||
These functions are not part of the public API and may change without notice.
|
||||
|
||||
## [0.1.1] - 2020-11-16
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ using Logging
|
||||
using Printf
|
||||
using LinearAlgebra
|
||||
|
||||
UnitCommitment._setup_logger()
|
||||
|
||||
function main()
|
||||
basename, suffix = split(ARGS[1], ".")
|
||||
solution_filename = "results/$basename.$suffix.sol.json"
|
||||
@@ -18,7 +20,6 @@ function main()
|
||||
time_limit = 60 * 20
|
||||
|
||||
BLAS.set_num_threads(4)
|
||||
global_logger(TimeLogger(initial_time = time()))
|
||||
|
||||
total_time = @elapsed begin
|
||||
@info "Reading: $basename"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
using DataStructures, JSON, GZip
|
||||
|
||||
function read_json(path::String)::OrderedDict
|
||||
function _read_json(path::String)::OrderedDict
|
||||
if endswith(path, ".gz")
|
||||
file = GZip.gzopen(path)
|
||||
else
|
||||
@@ -13,16 +13,16 @@ function read_json(path::String)::OrderedDict
|
||||
return JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing))
|
||||
end
|
||||
|
||||
function read_egret_solution(path::String)::OrderedDict
|
||||
egret = read_json(path)
|
||||
function _read_egret_solution(path::String)::OrderedDict
|
||||
egret = _read_json(path)
|
||||
T = length(egret["system"]["time_keys"])
|
||||
|
||||
solution = OrderedDict()
|
||||
is_on = solution["Is on"] = OrderedDict()
|
||||
solution = OrderedDict()
|
||||
is_on = solution["Is on"] = OrderedDict()
|
||||
production = solution["Production (MW)"] = OrderedDict()
|
||||
reserve = solution["Reserve (MW)"] = OrderedDict()
|
||||
reserve = solution["Reserve (MW)"] = OrderedDict()
|
||||
production_cost = solution["Production cost (\$)"] = OrderedDict()
|
||||
startup_cost = solution["Startup cost (\$)"] = OrderedDict()
|
||||
startup_cost = solution["Startup cost (\$)"] = OrderedDict()
|
||||
|
||||
for (gen_name, gen_dict) in egret["elements"]["generator"]
|
||||
if endswith(gen_name, "_T") || endswith(gen_name, "_R")
|
||||
|
||||
@@ -11,8 +11,10 @@ Generates feasible initial conditions for the given instance, by constructing
|
||||
and solving a single-period mixed-integer optimization problem, using the given
|
||||
optimizer. The instance is modified in-place.
|
||||
"""
|
||||
function generate_initial_conditions!(instance::UnitCommitmentInstance,
|
||||
optimizer)
|
||||
function generate_initial_conditions!(
|
||||
instance::UnitCommitmentInstance,
|
||||
optimizer,
|
||||
)::Nothing
|
||||
G = instance.units
|
||||
B = instance.buses
|
||||
t = 1
|
||||
@@ -73,4 +75,5 @@ function generate_initial_conditions!(instance::UnitCommitmentInstance,
|
||||
g.initial_status = -24
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
103
src/instance.jl
103
src/instance.jl
@@ -5,8 +5,8 @@
|
||||
using Printf
|
||||
using JSON
|
||||
using DataStructures
|
||||
using GZip
|
||||
import Base: getindex, time
|
||||
import GZip
|
||||
|
||||
|
||||
mutable struct Bus
|
||||
@@ -116,18 +116,19 @@ end
|
||||
|
||||
function read(path::AbstractString)::UnitCommitmentInstance
|
||||
if endswith(path, ".gz")
|
||||
return read(GZip.gzopen(path))
|
||||
return _read(gzopen(path))
|
||||
else
|
||||
return read(open(path))
|
||||
return _read(open(path))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function read(file::IO)::UnitCommitmentInstance
|
||||
return from_json(JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing)))
|
||||
function _read(file::IO)::UnitCommitmentInstance
|
||||
return _from_json(JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing)))
|
||||
end
|
||||
|
||||
function from_json(json; repair=true)
|
||||
|
||||
function _from_json(json; repair=true)
|
||||
units = Unit[]
|
||||
buses = Bus[]
|
||||
contingencies = Contingency[]
|
||||
@@ -160,16 +161,20 @@ function from_json(json; repair=true)
|
||||
end
|
||||
|
||||
# Read parameters
|
||||
power_balance_penalty = timeseries(json["Parameters"]["Power balance penalty (\$/MW)"],
|
||||
default=[1000.0 for t in 1:T])
|
||||
power_balance_penalty = timeseries(
|
||||
json["Parameters"]["Power balance penalty (\$/MW)"],
|
||||
default=[1000.0 for t in 1:T],
|
||||
)
|
||||
|
||||
# Read buses
|
||||
for (bus_name, dict) in json["Buses"]
|
||||
bus = Bus(bus_name,
|
||||
length(buses),
|
||||
timeseries(dict["Load (MW)"]),
|
||||
Unit[],
|
||||
PriceSensitiveLoad[])
|
||||
bus = Bus(
|
||||
bus_name,
|
||||
length(buses),
|
||||
timeseries(dict["Load (MW)"]),
|
||||
Unit[],
|
||||
PriceSensitiveLoad[],
|
||||
)
|
||||
name_to_bus[bus_name] = bus
|
||||
push!(buses, bus)
|
||||
end
|
||||
@@ -251,25 +256,35 @@ function from_json(json; repair=true)
|
||||
# Read reserves
|
||||
reserves = Reserves(zeros(T))
|
||||
if "Reserves" in keys(json)
|
||||
reserves.spinning = timeseries(json["Reserves"]["Spinning (MW)"],
|
||||
default=zeros(T))
|
||||
reserves.spinning = timeseries(
|
||||
json["Reserves"]["Spinning (MW)"],
|
||||
default=zeros(T),
|
||||
)
|
||||
end
|
||||
|
||||
# Read transmission lines
|
||||
if "Transmission lines" in keys(json)
|
||||
for (line_name, dict) in json["Transmission lines"]
|
||||
line = TransmissionLine(line_name,
|
||||
length(lines) + 1,
|
||||
name_to_bus[dict["Source bus"]],
|
||||
name_to_bus[dict["Target bus"]],
|
||||
scalar(dict["Reactance (ohms)"]),
|
||||
scalar(dict["Susceptance (S)"]),
|
||||
timeseries(dict["Normal flow limit (MW)"],
|
||||
default=[1e8 for t in 1:T]),
|
||||
timeseries(dict["Emergency flow limit (MW)"],
|
||||
default=[1e8 for t in 1:T]),
|
||||
timeseries(dict["Flow limit penalty (\$/MW)"],
|
||||
default=[5000.0 for t in 1:T]))
|
||||
line = TransmissionLine(
|
||||
line_name,
|
||||
length(lines) + 1,
|
||||
name_to_bus[dict["Source bus"]],
|
||||
name_to_bus[dict["Target bus"]],
|
||||
scalar(dict["Reactance (ohms)"]),
|
||||
scalar(dict["Susceptance (S)"]),
|
||||
timeseries(
|
||||
dict["Normal flow limit (MW)"],
|
||||
default=[1e8 for t in 1:T],
|
||||
),
|
||||
timeseries(
|
||||
dict["Emergency flow limit (MW)"],
|
||||
default=[1e8 for t in 1:T],
|
||||
),
|
||||
timeseries(
|
||||
dict["Flow limit penalty (\$/MW)"],
|
||||
default=[5000.0 for t in 1:T],
|
||||
),
|
||||
)
|
||||
name_to_line[line_name] = line
|
||||
push!(lines, line)
|
||||
end
|
||||
@@ -295,24 +310,27 @@ function from_json(json; repair=true)
|
||||
if "Price-sensitive loads" in keys(json)
|
||||
for (load_name, dict) in json["Price-sensitive loads"]
|
||||
bus = name_to_bus[dict["Bus"]]
|
||||
load = PriceSensitiveLoad(load_name,
|
||||
bus,
|
||||
timeseries(dict["Demand (MW)"]),
|
||||
timeseries(dict["Revenue (\$/MW)"]),
|
||||
)
|
||||
load = PriceSensitiveLoad(
|
||||
load_name,
|
||||
bus,
|
||||
timeseries(dict["Demand (MW)"]),
|
||||
timeseries(dict["Revenue (\$/MW)"]),
|
||||
)
|
||||
push!(bus.price_sensitive_loads, load)
|
||||
push!(loads, load)
|
||||
end
|
||||
end
|
||||
|
||||
instance = UnitCommitmentInstance(T,
|
||||
power_balance_penalty,
|
||||
units,
|
||||
buses,
|
||||
lines,
|
||||
reserves,
|
||||
contingencies,
|
||||
loads)
|
||||
instance = UnitCommitmentInstance(
|
||||
T,
|
||||
power_balance_penalty,
|
||||
units,
|
||||
buses,
|
||||
lines,
|
||||
reserves,
|
||||
contingencies,
|
||||
loads,
|
||||
)
|
||||
if repair
|
||||
UnitCommitment.repair!(instance)
|
||||
end
|
||||
@@ -335,7 +353,10 @@ Example
|
||||
modified = UnitCommitment.slice(instance, 1:2)
|
||||
|
||||
"""
|
||||
function slice(instance::UnitCommitmentInstance, range::UnitRange{Int})::UnitCommitmentInstance
|
||||
function slice(
|
||||
instance::UnitCommitmentInstance,
|
||||
range::UnitRange{Int},
|
||||
)::UnitCommitmentInstance
|
||||
modified = deepcopy(instance)
|
||||
modified.time = length(range)
|
||||
modified.power_balance_penalty = modified.power_balance_penalty[range]
|
||||
|
||||
@@ -56,9 +56,7 @@ function handle_message(logger::TimeLogger,
|
||||
end
|
||||
end
|
||||
|
||||
function setup_logger()
|
||||
function _setup_logger()
|
||||
initial_time = time()
|
||||
global_logger(TimeLogger(initial_time=initial_time))
|
||||
end
|
||||
|
||||
export TimeLogger
|
||||
20
src/model.jl
20
src/model.jl
@@ -50,7 +50,7 @@ function build_model(;
|
||||
if isf === nothing
|
||||
@info "Computing injection shift factors..."
|
||||
time_isf = @elapsed begin
|
||||
isf = UnitCommitment.injection_shift_factors(
|
||||
isf = UnitCommitment._injection_shift_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
)
|
||||
@@ -59,7 +59,7 @@ function build_model(;
|
||||
|
||||
@info "Computing line outage factors..."
|
||||
time_lodf = @elapsed begin
|
||||
lodf = UnitCommitment.line_outage_factors(
|
||||
lodf = UnitCommitment._line_outage_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
isf=isf,
|
||||
@@ -439,7 +439,7 @@ function _build_reserve_eqs!(model::JuMP.Model)
|
||||
end
|
||||
|
||||
|
||||
function enforce_transmission(
|
||||
function _enforce_transmission(
|
||||
;
|
||||
model::JuMP.Model,
|
||||
violation::Violation,
|
||||
@@ -667,7 +667,7 @@ function optimize!(
|
||||
|
||||
has_transmission || break
|
||||
|
||||
violations = find_violations(model)
|
||||
violations = _find_violations(model)
|
||||
if isempty(violations)
|
||||
@info "No violations found"
|
||||
if large_gap
|
||||
@@ -677,7 +677,7 @@ function optimize!(
|
||||
break
|
||||
end
|
||||
else
|
||||
enforce_transmission(model, violations)
|
||||
_enforce_transmission(model, violations)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -685,7 +685,7 @@ function optimize!(
|
||||
end
|
||||
|
||||
|
||||
function find_violations(model::JuMP.Model)
|
||||
function _find_violations(model::JuMP.Model)
|
||||
instance = model[:instance]
|
||||
net_injection = model[:net_injection]
|
||||
overflow = model[:overflow]
|
||||
@@ -702,7 +702,7 @@ function find_violations(model::JuMP.Model)
|
||||
value(overflow[lm.name, t])
|
||||
for lm in instance.lines, t in 1:instance.time
|
||||
]
|
||||
violations = UnitCommitment.find_violations(
|
||||
violations = UnitCommitment._find_violations(
|
||||
instance=instance,
|
||||
net_injections=net_injection_values,
|
||||
overflow=overflow_values,
|
||||
@@ -715,12 +715,12 @@ function find_violations(model::JuMP.Model)
|
||||
end
|
||||
|
||||
|
||||
function enforce_transmission(
|
||||
function _enforce_transmission(
|
||||
model::JuMP.Model,
|
||||
violations::Vector{Violation},
|
||||
)::Nothing
|
||||
for v in violations
|
||||
enforce_transmission(
|
||||
_enforce_transmission(
|
||||
model=model,
|
||||
violation=v,
|
||||
isf=model[:isf],
|
||||
@@ -730,4 +730,4 @@ function enforce_transmission(
|
||||
return
|
||||
end
|
||||
|
||||
export build_model
|
||||
export build_model
|
||||
|
||||
123
src/screening.jl
123
src/screening.jl
@@ -22,7 +22,7 @@ function Violation(;
|
||||
monitored_line::TransmissionLine,
|
||||
outage_line::Union{TransmissionLine, Nothing},
|
||||
amount::Float64,
|
||||
) :: Violation
|
||||
)::Violation
|
||||
return Violation(time, monitored_line, outage_line, amount)
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ function ViolationFilter(;
|
||||
end
|
||||
|
||||
|
||||
function offer(filter::ViolationFilter, v::Violation)::Nothing
|
||||
function _offer(filter::ViolationFilter, v::Violation)::Nothing
|
||||
if v.monitored_line.offset ∉ keys(filter.queues)
|
||||
filter.queues[v.monitored_line.offset] = PriorityQueue{Violation, Float64}()
|
||||
end
|
||||
@@ -59,7 +59,7 @@ function offer(filter::ViolationFilter, v::Violation)::Nothing
|
||||
end
|
||||
|
||||
|
||||
function query(filter::ViolationFilter)::Array{Violation, 1}
|
||||
function _query(filter::ViolationFilter)::Array{Violation, 1}
|
||||
violations = Array{Violation,1}()
|
||||
time_queue = PriorityQueue{Violation, Float64}()
|
||||
for l in keys(filter.queues)
|
||||
@@ -85,31 +85,35 @@ end
|
||||
|
||||
"""
|
||||
|
||||
function find_violations(instance::UnitCommitmentInstance,
|
||||
net_injections::Array{Float64, 2};
|
||||
isf::Array{Float64,2},
|
||||
lodf::Array{Float64,2},
|
||||
max_per_line::Int = 1,
|
||||
max_per_period::Int = 5,
|
||||
) :: Array{Violation, 1}
|
||||
function _find_violations(
|
||||
instance::UnitCommitmentInstance,
|
||||
net_injections::Array{Float64, 2};
|
||||
isf::Array{Float64,2},
|
||||
lodf::Array{Float64,2},
|
||||
max_per_line::Int = 1,
|
||||
max_per_period::Int = 5,
|
||||
)::Array{Violation, 1}
|
||||
|
||||
Find transmission constraint violations (both pre-contingency, as well as post-contingency).
|
||||
Find transmission constraint violations (both pre-contingency, as well as
|
||||
post-contingency).
|
||||
|
||||
The argument `net_injection` should be a (B-1) x T matrix, where B is the number of buses
|
||||
and T is the number of time periods. The arguments `isf` and `lodf` can be computed using
|
||||
UnitCommitment.injection_shift_factors and UnitCommitment.line_outage_factors.
|
||||
The argument `overflow` specifies how much flow above the transmission limits (in MW) is allowed.
|
||||
It should be an L x T matrix, where L is the number of transmission lines.
|
||||
The argument `net_injection` should be a (B-1) x T matrix, where B is the
|
||||
number of buses and T is the number of time periods. The arguments `isf` and
|
||||
`lodf` can be computed using UnitCommitment.injection_shift_factors and
|
||||
UnitCommitment.line_outage_factors. The argument `overflow` specifies how much
|
||||
flow above the transmission limits (in MW) is allowed. It should be an L x T
|
||||
matrix, where L is the number of transmission lines.
|
||||
"""
|
||||
function find_violations(;
|
||||
instance::UnitCommitmentInstance,
|
||||
net_injections::Array{Float64, 2},
|
||||
overflow::Array{Float64, 2},
|
||||
isf::Array{Float64,2},
|
||||
lodf::Array{Float64,2},
|
||||
max_per_line::Int = 1,
|
||||
max_per_period::Int = 5,
|
||||
)::Array{Violation, 1}
|
||||
function _find_violations(
|
||||
;
|
||||
instance::UnitCommitmentInstance,
|
||||
net_injections::Array{Float64, 2},
|
||||
overflow::Array{Float64, 2},
|
||||
isf::Array{Float64,2},
|
||||
lodf::Array{Float64,2},
|
||||
max_per_line::Int = 1,
|
||||
max_per_period::Int = 5,
|
||||
)::Array{Violation, 1}
|
||||
|
||||
B = length(instance.buses) - 1
|
||||
L = length(instance.lines)
|
||||
@@ -120,20 +124,28 @@ function find_violations(;
|
||||
size(isf) == (L, B) || error("isf has incorrect size")
|
||||
size(lodf) == (L, L) || error("lodf has incorrect size")
|
||||
|
||||
filters = Dict(t => ViolationFilter(max_total=max_per_period,
|
||||
max_per_line=max_per_line)
|
||||
for t in 1:T)
|
||||
filters = Dict(
|
||||
t => ViolationFilter(
|
||||
max_total=max_per_period,
|
||||
max_per_line=max_per_line,
|
||||
)
|
||||
for t in 1:T
|
||||
)
|
||||
|
||||
pre_flow::Array{Float64} = zeros(L, K) # pre_flow[lm, thread]
|
||||
post_flow::Array{Float64} = zeros(L, L, K) # post_flow[lm, lc, thread]
|
||||
pre_v::Array{Float64} = zeros(L, K) # pre_v[lm, thread]
|
||||
post_v::Array{Float64} = zeros(L, L, K) # post_v[lm, lc, thread]
|
||||
|
||||
normal_limits::Array{Float64,2} = [l.normal_flow_limit[t] + overflow[l.offset, t]
|
||||
for l in instance.lines, t in 1:T]
|
||||
normal_limits::Array{Float64,2} = [
|
||||
l.normal_flow_limit[t] + overflow[l.offset, t]
|
||||
for l in instance.lines, t in 1:T
|
||||
]
|
||||
|
||||
emergency_limits::Array{Float64,2} = [l.emergency_flow_limit[t] + overflow[l.offset, t]
|
||||
for l in instance.lines, t in 1:T]
|
||||
emergency_limits::Array{Float64,2} = [
|
||||
l.emergency_flow_limit[t] + overflow[l.offset, t]
|
||||
for l in instance.lines, t in 1:T
|
||||
]
|
||||
|
||||
is_vulnerable::Array{Bool} = zeros(Bool, L)
|
||||
for c in instance.contingencies
|
||||
@@ -153,46 +165,57 @@ function find_violations(;
|
||||
|
||||
# Pre-contingency violations
|
||||
for lm in 1:L
|
||||
pre_v[lm, k] = max(0.0,
|
||||
pre_flow[lm, k] - normal_limits[lm, t],
|
||||
- pre_flow[lm, k] - normal_limits[lm, t])
|
||||
pre_v[lm, k] = max(
|
||||
0.0,
|
||||
pre_flow[lm, k] - normal_limits[lm, t],
|
||||
- pre_flow[lm, k] - normal_limits[lm, t],
|
||||
)
|
||||
end
|
||||
|
||||
# Post-contingency violations
|
||||
for lc in 1:L, lm in 1:L
|
||||
post_v[lm, lc, k] = max(0.0,
|
||||
post_flow[lm, lc, k] - emergency_limits[lm, t],
|
||||
- post_flow[lm, lc, k] - emergency_limits[lm, t])
|
||||
post_v[lm, lc, k] = max(
|
||||
0.0,
|
||||
post_flow[lm, lc, k] - emergency_limits[lm, t],
|
||||
- post_flow[lm, lc, k] - emergency_limits[lm, t],
|
||||
)
|
||||
end
|
||||
|
||||
# Offer pre-contingency violations
|
||||
for lm in 1:L
|
||||
if pre_v[lm, k] > 1e-5
|
||||
offer(filters[t], Violation(time=t,
|
||||
monitored_line=instance.lines[lm],
|
||||
outage_line=nothing,
|
||||
amount=pre_v[lm, k]))
|
||||
_offer(
|
||||
filters[t],
|
||||
Violation(
|
||||
time=t,
|
||||
monitored_line=instance.lines[lm],
|
||||
outage_line=nothing,
|
||||
amount=pre_v[lm, k],
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Offer post-contingency violations
|
||||
for lm in 1:L, lc in 1:L
|
||||
if post_v[lm, lc, k] > 1e-5 && is_vulnerable[lc]
|
||||
offer(filters[t], Violation(time=t,
|
||||
monitored_line=instance.lines[lm],
|
||||
outage_line=instance.lines[lc],
|
||||
amount=post_v[lm, lc, k]))
|
||||
_offer(
|
||||
filters[t],
|
||||
Violation(
|
||||
time=t,
|
||||
monitored_line=instance.lines[lm],
|
||||
outage_line=instance.lines[lc],
|
||||
amount=post_v[lm, lc, k],
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
violations = Violation[]
|
||||
for t in 1:instance.time
|
||||
append!(violations, query(filters[t]))
|
||||
append!(violations, _query(filters[t]))
|
||||
end
|
||||
|
||||
return violations
|
||||
end
|
||||
|
||||
|
||||
export Violation, ViolationFilter, offer, query, find_violations
|
||||
@@ -5,16 +5,17 @@
|
||||
using SparseArrays, Base.Threads, LinearAlgebra, JuMP
|
||||
|
||||
"""
|
||||
injection_shift_factors(; buses, lines)
|
||||
_injection_shift_factors(; buses, lines)
|
||||
|
||||
Returns a (B-1)xL matrix M, where B is the number of buses and L is the number of transmission
|
||||
lines. For a given bus b and transmission line l, the entry M[l.offset, b.offset] indicates
|
||||
the amount of power (in MW) that flows through transmission line l when 1 MW of power is
|
||||
injected at the slack bus (the bus that has offset zero) and withdrawn from b.
|
||||
Returns a (B-1)xL matrix M, where B is the number of buses and L is the number
|
||||
of transmission lines. For a given bus b and transmission line l, the entry
|
||||
M[l.offset, b.offset] indicates the amount of power (in MW) that flows through
|
||||
transmission line l when 1 MW of power is injected at the slack bus (the bus
|
||||
that has offset zero) and withdrawn from b.
|
||||
"""
|
||||
function injection_shift_factors(; buses, lines)
|
||||
susceptance = susceptance_matrix(lines)
|
||||
incidence = reduced_incidence_matrix(lines = lines, buses = buses)
|
||||
function _injection_shift_factors(; buses::Array{Bus}, lines::Array{TransmissionLine})
|
||||
susceptance = _susceptance_matrix(lines)
|
||||
incidence = _reduced_incidence_matrix(lines=lines, buses=buses)
|
||||
laplacian = transpose(incidence) * susceptance * incidence
|
||||
isf = susceptance * incidence * inv(Array(laplacian))
|
||||
return isf
|
||||
@@ -22,14 +23,15 @@ end
|
||||
|
||||
|
||||
"""
|
||||
reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine})
|
||||
_reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine})
|
||||
|
||||
Returns the incidence matrix for the network, with the column corresponding to the slack
|
||||
bus is removed. More precisely, returns a (B-1) x L matrix, where B is the number of buses
|
||||
and L is the number of lines. For each row, there is a 1 element and a -1 element, indicating
|
||||
the source and target buses, respectively, for that line.
|
||||
Returns the incidence matrix for the network, with the column corresponding to
|
||||
the slack bus is removed. More precisely, returns a (B-1) x L matrix, where B
|
||||
is the number of buses and L is the number of lines. For each row, there is a 1
|
||||
element and a -1 element, indicating the source and target buses, respectively,
|
||||
for that line.
|
||||
"""
|
||||
function reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine})
|
||||
function _reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{TransmissionLine})
|
||||
matrix = spzeros(Float64, length(lines), length(buses) - 1)
|
||||
for line in lines
|
||||
if line.source.offset > 0
|
||||
@@ -43,33 +45,37 @@ function reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{Transmission
|
||||
end
|
||||
|
||||
"""
|
||||
susceptance_matrix(lines::Array{TransmissionLine})
|
||||
_susceptance_matrix(lines::Array{TransmissionLine})
|
||||
|
||||
Returns a LxL diagonal matrix, where each diagonal entry is the susceptance of the
|
||||
corresponding transmission line.
|
||||
Returns a LxL diagonal matrix, where each diagonal entry is the susceptance of
|
||||
the corresponding transmission line.
|
||||
"""
|
||||
function susceptance_matrix(lines::Array{TransmissionLine})
|
||||
function _susceptance_matrix(lines::Array{TransmissionLine})
|
||||
return Diagonal([l.susceptance for l in lines])
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
|
||||
line_outage_factors(; buses, lines, isf)
|
||||
_line_outage_factors(; buses, lines, isf)
|
||||
|
||||
Returns a LxL matrix containing the Line Outage Distribution Factors (LODFs) for the
|
||||
given network. This matrix how does the pre-contingency flow change when each individual
|
||||
transmission line is removed.
|
||||
Returns a LxL matrix containing the Line Outage Distribution Factors (LODFs)
|
||||
for the given network. This matrix how does the pre-contingency flow change
|
||||
when each individual transmission line is removed.
|
||||
"""
|
||||
function line_outage_factors(;
|
||||
buses::Array{Bus, 1},
|
||||
lines::Array{TransmissionLine, 1},
|
||||
isf::Array{Float64,2},
|
||||
) :: Array{Float64,2}
|
||||
|
||||
function _line_outage_factors(
|
||||
;
|
||||
buses::Array{Bus, 1},
|
||||
lines::Array{TransmissionLine, 1},
|
||||
isf::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)
|
||||
m, n = size(lodf)
|
||||
for i in 1:n
|
||||
|
||||
@@ -10,14 +10,17 @@ using JuMP
|
||||
using MathOptInterface
|
||||
using SparseArrays
|
||||
|
||||
pkg = [:DataStructures,
|
||||
:JSON,
|
||||
:JuMP,
|
||||
:MathOptInterface,
|
||||
:SparseArrays,
|
||||
]
|
||||
pkg = [
|
||||
:DataStructures,
|
||||
:JSON,
|
||||
:JuMP,
|
||||
:MathOptInterface,
|
||||
:SparseArrays,
|
||||
]
|
||||
|
||||
@info "Building system image..."
|
||||
create_sysimage(pkg,
|
||||
precompile_statements_file="build/precompile.jl",
|
||||
sysimage_path="build/sysimage.so")
|
||||
create_sysimage(
|
||||
pkg,
|
||||
precompile_statements_file="build/precompile.jl",
|
||||
sysimage_path="build/sysimage.so",
|
||||
)
|
||||
|
||||
@@ -9,9 +9,10 @@ bin(x) = [xi > 0.5 for xi in x]
|
||||
"""
|
||||
repair!(instance)
|
||||
|
||||
Verifies that the given unit commitment instance is valid and automatically fixes
|
||||
some validation errors if possible, issuing a warning for each error found.
|
||||
If a validation error cannot be automatically fixed, issues an exception.
|
||||
Verifies that the given unit commitment instance is valid and automatically
|
||||
fixes some validation errors if possible, issuing a warning for each error
|
||||
found. If a validation error cannot be automatically fixed, issues an
|
||||
exception.
|
||||
|
||||
Returns the number of validation errors found.
|
||||
"""
|
||||
@@ -86,16 +87,17 @@ Verifies that the given solution is feasible for the problem. If feasible,
|
||||
silently returns true. In infeasible, returns false and prints the validation
|
||||
errors to the screen.
|
||||
|
||||
This function is implemented independently from the optimization model in `model.jl`, and
|
||||
therefore can be used to verify that the model is indeed producing valid solutions. It
|
||||
can also be used to verify the solutions produced by other optimization packages.
|
||||
This function is implemented independently from the optimization model in
|
||||
`model.jl`, and therefore can be used to verify that the model is indeed
|
||||
producing valid solutions. It can also be used to verify the solutions produced
|
||||
by other optimization packages.
|
||||
"""
|
||||
function validate(instance::UnitCommitmentInstance,
|
||||
solution::Union{Dict,OrderedDict};
|
||||
)::Bool
|
||||
err_count = 0
|
||||
err_count += validate_units(instance, solution)
|
||||
err_count += validate_reserve_and_demand(instance, solution)
|
||||
err_count += _validate_units(instance, solution)
|
||||
err_count += _validate_reserve_and_demand(instance, solution)
|
||||
|
||||
if err_count > 0
|
||||
@error "Found $err_count validation errors"
|
||||
@@ -106,7 +108,7 @@ function validate(instance::UnitCommitmentInstance,
|
||||
end
|
||||
|
||||
|
||||
function validate_units(instance, solution; tol=0.01)
|
||||
function _validate_units(instance, solution; tol=0.01)
|
||||
err_count = 0
|
||||
|
||||
for unit in instance.units
|
||||
@@ -300,7 +302,7 @@ function validate_units(instance, solution; tol=0.01)
|
||||
end
|
||||
|
||||
|
||||
function validate_reserve_and_demand(instance, solution, tol=0.01)
|
||||
function _validate_reserve_and_demand(instance, solution, tol=0.01)
|
||||
err_count = 0
|
||||
for t in 1:instance.time
|
||||
load_curtail = 0
|
||||
|
||||
@@ -6,7 +6,7 @@ using UnitCommitment
|
||||
|
||||
@testset "convert" begin
|
||||
@testset "EGRET solution" begin
|
||||
solution = UnitCommitment.read_egret_solution("fixtures/egret_output.json.gz")
|
||||
solution = UnitCommitment._read_egret_solution("fixtures/egret_output.json.gz")
|
||||
for attr in ["Is on", "Production (MW)", "Production cost (\$)"]
|
||||
@test attr in keys(solution)
|
||||
@test "115_STEAM_1" in keys(solution[attr])
|
||||
@@ -16,4 +16,4 @@ using UnitCommitment
|
||||
@test solution["Startup cost (\$)"]["315_CT_6"][15:20] == [0., 0., 5665.23, 0., 0., 0.]
|
||||
@test length(keys(solution["Is on"])) == 154
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
using Test
|
||||
using UnitCommitment
|
||||
|
||||
UnitCommitment.setup_logger()
|
||||
UnitCommitment._setup_logger()
|
||||
|
||||
@testset "UnitCommitment" begin
|
||||
include("instance_test.jl")
|
||||
|
||||
@@ -3,51 +3,83 @@
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
using UnitCommitment, Test, LinearAlgebra
|
||||
import UnitCommitment: Violation, _offer, _query
|
||||
|
||||
@testset "Screening" begin
|
||||
@testset "Violation filter" begin
|
||||
instance = UnitCommitment.read_benchmark("test/case14")
|
||||
filter = ViolationFilter(max_per_line=1, max_total=2)
|
||||
filter = UnitCommitment.ViolationFilter(max_per_line=1, max_total=2)
|
||||
|
||||
offer(filter, Violation(time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=nothing,
|
||||
amount=100.))
|
||||
|
||||
offer(filter, Violation(time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[1],
|
||||
amount=300.))
|
||||
|
||||
offer(filter, Violation(time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[5],
|
||||
amount=500.))
|
||||
|
||||
offer(filter, Violation(time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[4],
|
||||
amount=400.))
|
||||
|
||||
offer(filter, Violation(time=1,
|
||||
monitored_line=instance.lines[2],
|
||||
outage_line=instance.lines[1],
|
||||
amount=200.))
|
||||
|
||||
offer(filter, Violation(time=1,
|
||||
monitored_line=instance.lines[2],
|
||||
outage_line=instance.lines[8],
|
||||
amount=100.))
|
||||
_offer(
|
||||
filter,
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=nothing,
|
||||
amount=100.,
|
||||
),
|
||||
)
|
||||
_offer(
|
||||
filter,
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[1],
|
||||
amount=300.,
|
||||
),
|
||||
)
|
||||
_offer(
|
||||
filter,
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[5],
|
||||
amount=500.,
|
||||
),
|
||||
)
|
||||
_offer(
|
||||
filter,
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[4],
|
||||
amount=400.,
|
||||
),
|
||||
)
|
||||
_offer(
|
||||
filter,
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[2],
|
||||
outage_line=instance.lines[1],
|
||||
amount=200.,
|
||||
),
|
||||
)
|
||||
_offer(
|
||||
filter,
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[2],
|
||||
outage_line=instance.lines[8],
|
||||
amount=100.,
|
||||
)
|
||||
)
|
||||
|
||||
actual = query(filter)
|
||||
expected = [Violation(time=1,
|
||||
monitored_line=instance.lines[2],
|
||||
outage_line=instance.lines[1],
|
||||
amount=200.),
|
||||
Violation(time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[5],
|
||||
amount=500.)]
|
||||
actual = _query(filter)
|
||||
expected = [
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[2],
|
||||
outage_line=instance.lines[1],
|
||||
amount=200.,
|
||||
),
|
||||
Violation(
|
||||
time=1,
|
||||
monitored_line=instance.lines[1],
|
||||
outage_line=instance.lines[5],
|
||||
amount=500.,
|
||||
),
|
||||
]
|
||||
@test actual == expected
|
||||
end
|
||||
|
||||
@@ -57,19 +89,24 @@ using UnitCommitment, Test, LinearAlgebra
|
||||
line.normal_flow_limit[t] = 1.0
|
||||
line.emergency_flow_limit[t] = 1.0
|
||||
end
|
||||
isf = UnitCommitment.injection_shift_factors(lines=instance.lines,
|
||||
buses=instance.buses)
|
||||
lodf = UnitCommitment.line_outage_factors(lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
isf=isf)
|
||||
isf = UnitCommitment._injection_shift_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
)
|
||||
lodf = UnitCommitment._line_outage_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
isf=isf,
|
||||
)
|
||||
inj = [1000.0 for b in 1:13, t in 1:instance.time]
|
||||
overflow = [0.0 for l in instance.lines, t in 1:instance.time]
|
||||
violations = UnitCommitment.find_violations(instance=instance,
|
||||
net_injections=inj,
|
||||
overflow=overflow,
|
||||
isf=isf,
|
||||
lodf=lodf)
|
||||
|
||||
violations = UnitCommitment._find_violations(
|
||||
instance=instance,
|
||||
net_injections=inj,
|
||||
overflow=overflow,
|
||||
isf=isf,
|
||||
lodf=lodf,
|
||||
)
|
||||
@test length(violations) == 20
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ using UnitCommitment, Test, LinearAlgebra
|
||||
@testset "Sensitivity" begin
|
||||
@testset "Susceptance matrix" begin
|
||||
instance = UnitCommitment.read_benchmark("test/case14")
|
||||
actual = UnitCommitment.susceptance_matrix(instance.lines)
|
||||
actual = UnitCommitment._susceptance_matrix(instance.lines)
|
||||
@test size(actual) == (20, 20)
|
||||
expected = Diagonal([29.5, 7.83, 8.82, 9.9, 10.04,
|
||||
10.2, 41.45, 8.35, 3.14, 6.93,
|
||||
@@ -18,8 +18,10 @@ using UnitCommitment, Test, LinearAlgebra
|
||||
|
||||
@testset "Reduced incidence matrix" begin
|
||||
instance = UnitCommitment.read_benchmark("test/case14")
|
||||
actual = UnitCommitment.reduced_incidence_matrix(lines=instance.lines,
|
||||
buses=instance.buses)
|
||||
actual = UnitCommitment._reduced_incidence_matrix(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
)
|
||||
@test size(actual) == (20, 13)
|
||||
@test actual[1, 1] == -1.0
|
||||
@test actual[3, 1] == 1.0
|
||||
@@ -63,8 +65,10 @@ using UnitCommitment, Test, LinearAlgebra
|
||||
|
||||
@testset "Injection Shift Factors (ISF)" begin
|
||||
instance = UnitCommitment.read_benchmark("test/case14")
|
||||
actual = UnitCommitment.injection_shift_factors(lines=instance.lines,
|
||||
buses=instance.buses)
|
||||
actual = UnitCommitment._injection_shift_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
)
|
||||
@test size(actual) == (20, 13)
|
||||
@test round.(actual, digits=2) == [
|
||||
-0.84 -0.75 -0.67 -0.61 -0.63 -0.66 -0.66 -0.65 -0.65 -0.64 -0.63 -0.63 -0.64;
|
||||
@@ -91,17 +95,23 @@ using UnitCommitment, Test, LinearAlgebra
|
||||
|
||||
@testset "Line Outage Distribution Factors (LODF)" begin
|
||||
instance = UnitCommitment.read_benchmark("test/case14")
|
||||
isf_before = UnitCommitment.injection_shift_factors(lines=instance.lines,
|
||||
buses=instance.buses)
|
||||
lodf = UnitCommitment.line_outage_factors(lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
isf=isf_before)
|
||||
isf_before = UnitCommitment._injection_shift_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
)
|
||||
lodf = UnitCommitment._line_outage_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
isf=isf_before,
|
||||
)
|
||||
for contingency in instance.contingencies
|
||||
for lc in contingency.lines
|
||||
prev_susceptance = lc.susceptance
|
||||
lc.susceptance = 0.0
|
||||
isf_after = UnitCommitment.injection_shift_factors(lines=instance.lines,
|
||||
buses=instance.buses)
|
||||
isf_after = UnitCommitment._injection_shift_factors(
|
||||
lines=instance.lines,
|
||||
buses=instance.buses,
|
||||
)
|
||||
lc.susceptance = prev_susceptance
|
||||
for lm in instance.lines
|
||||
expected = isf_after[lm.offset, :]
|
||||
|
||||
@@ -14,7 +14,7 @@ parse_case14() = JSON.parse(GZip.gzopen("../instances/test/case14.json.gz"),
|
||||
json = parse_case14()
|
||||
json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150, 200]
|
||||
json["Generators"]["g1"]["Production cost curve (\$)"] = [10, 25, 30]
|
||||
instance = UnitCommitment.from_json(json, repair=false)
|
||||
instance = UnitCommitment._from_json(json, repair=false)
|
||||
@test UnitCommitment.repair!(instance) == 4
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ parse_case14() = JSON.parse(GZip.gzopen("../instances/test/case14.json.gz"),
|
||||
json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150]
|
||||
json["Generators"]["g1"]["Production cost curve (\$)"] = [100, 150]
|
||||
json["Generators"]["g1"]["Startup limit (MW)"] = 80
|
||||
instance = UnitCommitment.from_json(json, repair=false)
|
||||
instance = UnitCommitment._from_json(json, repair=false)
|
||||
@test UnitCommitment.repair!(instance) == 1
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@ parse_case14() = JSON.parse(GZip.gzopen("../instances/test/case14.json.gz"),
|
||||
json = parse_case14()
|
||||
json["Generators"]["g1"]["Startup costs (\$)"] = [300, 200, 100]
|
||||
json["Generators"]["g1"]["Startup delays (h)"] = [8, 4, 2]
|
||||
instance = UnitCommitment.from_json(json, repair=false)
|
||||
instance = UnitCommitment._from_json(json, repair=false)
|
||||
@test UnitCommitment.repair!(instance) == 4
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user