Rename internal methods to _something; reformat code

bugfix/formulations
Alinson S. Xavier 4 years ago
parent 11514b5de8
commit 7eb1019410

@ -4,7 +4,7 @@
### Added ### Added
- Sub-hourly unit commitment - 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 - Added mathematical formulation to the documentation
### Changed ### Changed
@ -18,7 +18,8 @@
- `model.isf` becomes `model[:isf]` - `model.isf` becomes `model[:isf]`
- `model.lodf` becomes `model[:lodf]` - `model.lodf` becomes `model[:lodf]`
- The function `UnitCommitment.get_solution` has been renamed to `UnitCommitment.solution` - 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 ## [0.1.1] - 2020-11-16

@ -10,6 +10,8 @@ using Logging
using Printf using Printf
using LinearAlgebra using LinearAlgebra
UnitCommitment._setup_logger()
function main() function main()
basename, suffix = split(ARGS[1], ".") basename, suffix = split(ARGS[1], ".")
solution_filename = "results/$basename.$suffix.sol.json" solution_filename = "results/$basename.$suffix.sol.json"
@ -18,7 +20,6 @@ function main()
time_limit = 60 * 20 time_limit = 60 * 20
BLAS.set_num_threads(4) BLAS.set_num_threads(4)
global_logger(TimeLogger(initial_time = time()))
total_time = @elapsed begin total_time = @elapsed begin
@info "Reading: $basename" @info "Reading: $basename"

@ -4,7 +4,7 @@
using DataStructures, JSON, GZip using DataStructures, JSON, GZip
function read_json(path::String)::OrderedDict function _read_json(path::String)::OrderedDict
if endswith(path, ".gz") if endswith(path, ".gz")
file = GZip.gzopen(path) file = GZip.gzopen(path)
else else
@ -13,8 +13,8 @@ function read_json(path::String)::OrderedDict
return JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing)) return JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing))
end end
function read_egret_solution(path::String)::OrderedDict function _read_egret_solution(path::String)::OrderedDict
egret = read_json(path) egret = _read_json(path)
T = length(egret["system"]["time_keys"]) T = length(egret["system"]["time_keys"])
solution = OrderedDict() solution = OrderedDict()

@ -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 and solving a single-period mixed-integer optimization problem, using the given
optimizer. The instance is modified in-place. optimizer. The instance is modified in-place.
""" """
function generate_initial_conditions!(instance::UnitCommitmentInstance, function generate_initial_conditions!(
optimizer) instance::UnitCommitmentInstance,
optimizer,
)::Nothing
G = instance.units G = instance.units
B = instance.buses B = instance.buses
t = 1 t = 1
@ -73,4 +75,5 @@ function generate_initial_conditions!(instance::UnitCommitmentInstance,
g.initial_status = -24 g.initial_status = -24
end end
end end
return
end end

@ -5,8 +5,8 @@
using Printf using Printf
using JSON using JSON
using DataStructures using DataStructures
using GZip
import Base: getindex, time import Base: getindex, time
import GZip
mutable struct Bus mutable struct Bus
@ -116,18 +116,19 @@ end
function read(path::AbstractString)::UnitCommitmentInstance function read(path::AbstractString)::UnitCommitmentInstance
if endswith(path, ".gz") if endswith(path, ".gz")
return read(GZip.gzopen(path)) return _read(gzopen(path))
else else
return read(open(path)) return _read(open(path))
end end
end end
function read(file::IO)::UnitCommitmentInstance function _read(file::IO)::UnitCommitmentInstance
return from_json(JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing))) return _from_json(JSON.parse(file, dicttype=()->DefaultOrderedDict(nothing)))
end end
function from_json(json; repair=true)
function _from_json(json; repair=true)
units = Unit[] units = Unit[]
buses = Bus[] buses = Bus[]
contingencies = Contingency[] contingencies = Contingency[]
@ -160,16 +161,20 @@ function from_json(json; repair=true)
end end
# Read parameters # Read parameters
power_balance_penalty = timeseries(json["Parameters"]["Power balance penalty (\$/MW)"], power_balance_penalty = timeseries(
default=[1000.0 for t in 1:T]) json["Parameters"]["Power balance penalty (\$/MW)"],
default=[1000.0 for t in 1:T],
)
# Read buses # Read buses
for (bus_name, dict) in json["Buses"] for (bus_name, dict) in json["Buses"]
bus = Bus(bus_name, bus = Bus(
bus_name,
length(buses), length(buses),
timeseries(dict["Load (MW)"]), timeseries(dict["Load (MW)"]),
Unit[], Unit[],
PriceSensitiveLoad[]) PriceSensitiveLoad[],
)
name_to_bus[bus_name] = bus name_to_bus[bus_name] = bus
push!(buses, bus) push!(buses, bus)
end end
@ -251,25 +256,35 @@ function from_json(json; repair=true)
# Read reserves # Read reserves
reserves = Reserves(zeros(T)) reserves = Reserves(zeros(T))
if "Reserves" in keys(json) if "Reserves" in keys(json)
reserves.spinning = timeseries(json["Reserves"]["Spinning (MW)"], reserves.spinning = timeseries(
default=zeros(T)) json["Reserves"]["Spinning (MW)"],
default=zeros(T),
)
end end
# Read transmission lines # Read transmission lines
if "Transmission lines" in keys(json) if "Transmission lines" in keys(json)
for (line_name, dict) in json["Transmission lines"] for (line_name, dict) in json["Transmission lines"]
line = TransmissionLine(line_name, line = TransmissionLine(
line_name,
length(lines) + 1, length(lines) + 1,
name_to_bus[dict["Source bus"]], name_to_bus[dict["Source bus"]],
name_to_bus[dict["Target bus"]], name_to_bus[dict["Target bus"]],
scalar(dict["Reactance (ohms)"]), scalar(dict["Reactance (ohms)"]),
scalar(dict["Susceptance (S)"]), scalar(dict["Susceptance (S)"]),
timeseries(dict["Normal flow limit (MW)"], timeseries(
default=[1e8 for t in 1:T]), dict["Normal flow limit (MW)"],
timeseries(dict["Emergency flow limit (MW)"], default=[1e8 for t in 1:T],
default=[1e8 for t in 1:T]), ),
timeseries(dict["Flow limit penalty (\$/MW)"], timeseries(
default=[5000.0 for t in 1:T])) 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 name_to_line[line_name] = line
push!(lines, line) push!(lines, line)
end end
@ -295,7 +310,8 @@ function from_json(json; repair=true)
if "Price-sensitive loads" in keys(json) if "Price-sensitive loads" in keys(json)
for (load_name, dict) in json["Price-sensitive loads"] for (load_name, dict) in json["Price-sensitive loads"]
bus = name_to_bus[dict["Bus"]] bus = name_to_bus[dict["Bus"]]
load = PriceSensitiveLoad(load_name, load = PriceSensitiveLoad(
load_name,
bus, bus,
timeseries(dict["Demand (MW)"]), timeseries(dict["Demand (MW)"]),
timeseries(dict["Revenue (\$/MW)"]), timeseries(dict["Revenue (\$/MW)"]),
@ -305,14 +321,16 @@ function from_json(json; repair=true)
end end
end end
instance = UnitCommitmentInstance(T, instance = UnitCommitmentInstance(
T,
power_balance_penalty, power_balance_penalty,
units, units,
buses, buses,
lines, lines,
reserves, reserves,
contingencies, contingencies,
loads) loads,
)
if repair if repair
UnitCommitment.repair!(instance) UnitCommitment.repair!(instance)
end end
@ -335,7 +353,10 @@ Example
modified = UnitCommitment.slice(instance, 1:2) 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 = deepcopy(instance)
modified.time = length(range) modified.time = length(range)
modified.power_balance_penalty = modified.power_balance_penalty[range] modified.power_balance_penalty = modified.power_balance_penalty[range]

@ -56,9 +56,7 @@ function handle_message(logger::TimeLogger,
end end
end end
function setup_logger() function _setup_logger()
initial_time = time() initial_time = time()
global_logger(TimeLogger(initial_time=initial_time)) global_logger(TimeLogger(initial_time=initial_time))
end end
export TimeLogger

@ -50,7 +50,7 @@ function build_model(;
if isf === nothing if isf === nothing
@info "Computing injection shift factors..." @info "Computing injection shift factors..."
time_isf = @elapsed begin time_isf = @elapsed begin
isf = UnitCommitment.injection_shift_factors( isf = UnitCommitment._injection_shift_factors(
lines=instance.lines, lines=instance.lines,
buses=instance.buses, buses=instance.buses,
) )
@ -59,7 +59,7 @@ function build_model(;
@info "Computing line outage factors..." @info "Computing line outage factors..."
time_lodf = @elapsed begin time_lodf = @elapsed begin
lodf = UnitCommitment.line_outage_factors( lodf = UnitCommitment._line_outage_factors(
lines=instance.lines, lines=instance.lines,
buses=instance.buses, buses=instance.buses,
isf=isf, isf=isf,
@ -439,7 +439,7 @@ function _build_reserve_eqs!(model::JuMP.Model)
end end
function enforce_transmission( function _enforce_transmission(
; ;
model::JuMP.Model, model::JuMP.Model,
violation::Violation, violation::Violation,
@ -667,7 +667,7 @@ function optimize!(
has_transmission || break has_transmission || break
violations = find_violations(model) violations = _find_violations(model)
if isempty(violations) if isempty(violations)
@info "No violations found" @info "No violations found"
if large_gap if large_gap
@ -677,7 +677,7 @@ function optimize!(
break break
end end
else else
enforce_transmission(model, violations) _enforce_transmission(model, violations)
end end
end end
@ -685,7 +685,7 @@ function optimize!(
end end
function find_violations(model::JuMP.Model) function _find_violations(model::JuMP.Model)
instance = model[:instance] instance = model[:instance]
net_injection = model[:net_injection] net_injection = model[:net_injection]
overflow = model[:overflow] overflow = model[:overflow]
@ -702,7 +702,7 @@ function find_violations(model::JuMP.Model)
value(overflow[lm.name, t]) value(overflow[lm.name, t])
for lm in instance.lines, t in 1:instance.time for lm in instance.lines, t in 1:instance.time
] ]
violations = UnitCommitment.find_violations( violations = UnitCommitment._find_violations(
instance=instance, instance=instance,
net_injections=net_injection_values, net_injections=net_injection_values,
overflow=overflow_values, overflow=overflow_values,
@ -715,12 +715,12 @@ function find_violations(model::JuMP.Model)
end end
function enforce_transmission( function _enforce_transmission(
model::JuMP.Model, model::JuMP.Model,
violations::Vector{Violation}, violations::Vector{Violation},
)::Nothing )::Nothing
for v in violations for v in violations
enforce_transmission( _enforce_transmission(
model=model, model=model,
violation=v, violation=v,
isf=model[:isf], isf=model[:isf],

@ -42,7 +42,7 @@ function ViolationFilter(;
end end
function offer(filter::ViolationFilter, v::Violation)::Nothing function _offer(filter::ViolationFilter, v::Violation)::Nothing
if v.monitored_line.offset keys(filter.queues) if v.monitored_line.offset keys(filter.queues)
filter.queues[v.monitored_line.offset] = PriorityQueue{Violation, Float64}() filter.queues[v.monitored_line.offset] = PriorityQueue{Violation, Float64}()
end end
@ -59,7 +59,7 @@ function offer(filter::ViolationFilter, v::Violation)::Nothing
end end
function query(filter::ViolationFilter)::Array{Violation, 1} function _query(filter::ViolationFilter)::Array{Violation, 1}
violations = Array{Violation,1}() violations = Array{Violation,1}()
time_queue = PriorityQueue{Violation, Float64}() time_queue = PriorityQueue{Violation, Float64}()
for l in keys(filter.queues) for l in keys(filter.queues)
@ -85,7 +85,8 @@ end
""" """
function find_violations(instance::UnitCommitmentInstance, function _find_violations(
instance::UnitCommitmentInstance,
net_injections::Array{Float64, 2}; net_injections::Array{Float64, 2};
isf::Array{Float64,2}, isf::Array{Float64,2},
lodf::Array{Float64,2}, lodf::Array{Float64,2},
@ -93,15 +94,18 @@ end
max_per_period::Int = 5, max_per_period::Int = 5,
)::Array{Violation, 1} )::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 The argument `net_injection` should be a (B-1) x T matrix, where B is the
and T is the number of time periods. The arguments `isf` and `lodf` can be computed using number of buses and T is the number of time periods. The arguments `isf` and
UnitCommitment.injection_shift_factors and UnitCommitment.line_outage_factors. `lodf` can be computed using UnitCommitment.injection_shift_factors and
The argument `overflow` specifies how much flow above the transmission limits (in MW) is allowed. UnitCommitment.line_outage_factors. The argument `overflow` specifies how much
It should be an L x T matrix, where L is the number of transmission lines. 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(; function _find_violations(
;
instance::UnitCommitmentInstance, instance::UnitCommitmentInstance,
net_injections::Array{Float64, 2}, net_injections::Array{Float64, 2},
overflow::Array{Float64, 2}, overflow::Array{Float64, 2},
@ -120,20 +124,28 @@ function find_violations(;
size(isf) == (L, B) || error("isf has incorrect size") size(isf) == (L, B) || error("isf has incorrect size")
size(lodf) == (L, L) || error("lodf has incorrect size") size(lodf) == (L, L) || error("lodf has incorrect size")
filters = Dict(t => ViolationFilter(max_total=max_per_period, filters = Dict(
max_per_line=max_per_line) t => ViolationFilter(
for t in 1:T) 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] pre_flow::Array{Float64} = zeros(L, K) # pre_flow[lm, thread]
post_flow::Array{Float64} = zeros(L, L, K) # post_flow[lm, lc, 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] pre_v::Array{Float64} = zeros(L, K) # pre_v[lm, thread]
post_v::Array{Float64} = zeros(L, L, K) # post_v[lm, lc, 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] normal_limits::Array{Float64,2} = [
for l in instance.lines, t in 1:T] 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] emergency_limits::Array{Float64,2} = [
for l in instance.lines, t in 1:T] 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) is_vulnerable::Array{Bool} = zeros(Bool, L)
for c in instance.contingencies for c in instance.contingencies
@ -153,46 +165,57 @@ function find_violations(;
# Pre-contingency violations # Pre-contingency violations
for lm in 1:L for lm in 1:L
pre_v[lm, k] = max(0.0, pre_v[lm, k] = max(
0.0,
pre_flow[lm, k] - normal_limits[lm, t], pre_flow[lm, k] - normal_limits[lm, t],
- pre_flow[lm, k] - normal_limits[lm, t]) - pre_flow[lm, k] - normal_limits[lm, t],
)
end end
# Post-contingency violations # Post-contingency violations
for lc in 1:L, lm in 1:L for lc in 1:L, lm in 1:L
post_v[lm, lc, k] = max(0.0, 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_flow[lm, lc, k] - emergency_limits[lm, t]) - post_flow[lm, lc, k] - emergency_limits[lm, t],
)
end end
# Offer pre-contingency violations # Offer pre-contingency violations
for lm in 1:L for lm in 1:L
if pre_v[lm, k] > 1e-5 if pre_v[lm, k] > 1e-5
offer(filters[t], Violation(time=t, _offer(
filters[t],
Violation(
time=t,
monitored_line=instance.lines[lm], monitored_line=instance.lines[lm],
outage_line=nothing, outage_line=nothing,
amount=pre_v[lm, k])) amount=pre_v[lm, k],
),
)
end end
end end
# Offer post-contingency violations # Offer post-contingency violations
for lm in 1:L, lc in 1:L for lm in 1:L, lc in 1:L
if post_v[lm, lc, k] > 1e-5 && is_vulnerable[lc] if post_v[lm, lc, k] > 1e-5 && is_vulnerable[lc]
offer(filters[t], Violation(time=t, _offer(
filters[t],
Violation(
time=t,
monitored_line=instance.lines[lm], monitored_line=instance.lines[lm],
outage_line=instance.lines[lc], outage_line=instance.lines[lc],
amount=post_v[lm, lc, k])) amount=post_v[lm, lc, k],
),
)
end end
end end
end end
violations = Violation[] violations = Violation[]
for t in 1:instance.time for t in 1:instance.time
append!(violations, query(filters[t])) append!(violations, _query(filters[t]))
end end
return violations return violations
end end
export Violation, ViolationFilter, offer, query, find_violations

@ -5,16 +5,17 @@
using SparseArrays, Base.Threads, LinearAlgebra, JuMP 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 Returns a (B-1)xL matrix M, where B is the number of buses and L is the number
lines. For a given bus b and transmission line l, the entry M[l.offset, b.offset] indicates of transmission lines. For a given bus b and transmission line l, the entry
the amount of power (in MW) that flows through transmission line l when 1 MW of power is M[l.offset, b.offset] indicates the amount of power (in MW) that flows through
injected at the slack bus (the bus that has offset zero) and withdrawn from b. 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) function _injection_shift_factors(; buses::Array{Bus}, lines::Array{TransmissionLine})
susceptance = susceptance_matrix(lines) susceptance = _susceptance_matrix(lines)
incidence = reduced_incidence_matrix(lines = lines, buses = buses) incidence = _reduced_incidence_matrix(lines=lines, buses=buses)
laplacian = transpose(incidence) * susceptance * incidence laplacian = transpose(incidence) * susceptance * incidence
isf = susceptance * incidence * inv(Array(laplacian)) isf = susceptance * incidence * inv(Array(laplacian))
return isf 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 Returns the incidence matrix for the network, with the column corresponding to
bus is removed. More precisely, returns a (B-1) x L matrix, where B is the number of buses the slack bus is removed. More precisely, returns a (B-1) x L matrix, where B
and L is the number of lines. For each row, there is a 1 element and a -1 element, indicating is the number of buses and L is the number of lines. For each row, there is a 1
the source and target buses, respectively, for that line. 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) matrix = spzeros(Float64, length(lines), length(buses) - 1)
for line in lines for line in lines
if line.source.offset > 0 if line.source.offset > 0
@ -43,33 +45,37 @@ function reduced_incidence_matrix(; buses::Array{Bus}, lines::Array{Transmission
end 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 Returns a LxL diagonal matrix, where each diagonal entry is the susceptance of
corresponding transmission line. the corresponding transmission line.
""" """
function susceptance_matrix(lines::Array{TransmissionLine}) function _susceptance_matrix(lines::Array{TransmissionLine})
return Diagonal([l.susceptance for l in lines]) return Diagonal([l.susceptance for l in lines])
end 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 Returns a LxL matrix containing the Line Outage Distribution Factors (LODFs)
given network. This matrix how does the pre-contingency flow change when each individual for the given network. This matrix how does the pre-contingency flow change
transmission line is removed. when each individual transmission line is removed.
""" """
function line_outage_factors(; function _line_outage_factors(
;
buses::Array{Bus, 1}, buses::Array{Bus, 1},
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) n_lines, n_buses = size(isf)
incidence = Array(reduced_incidence_matrix(lines=lines, incidence = Array(
buses=buses)) _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) m, n = size(lodf)
for i in 1:n for i in 1:n

@ -10,7 +10,8 @@ using JuMP
using MathOptInterface using MathOptInterface
using SparseArrays using SparseArrays
pkg = [:DataStructures, pkg = [
:DataStructures,
:JSON, :JSON,
:JuMP, :JuMP,
:MathOptInterface, :MathOptInterface,
@ -18,6 +19,8 @@ pkg = [:DataStructures,
] ]
@info "Building system image..." @info "Building system image..."
create_sysimage(pkg, create_sysimage(
pkg,
precompile_statements_file="build/precompile.jl", precompile_statements_file="build/precompile.jl",
sysimage_path="build/sysimage.so") sysimage_path="build/sysimage.so",
)

@ -9,9 +9,10 @@ bin(x) = [xi > 0.5 for xi in x]
""" """
repair!(instance) repair!(instance)
Verifies that the given unit commitment instance is valid and automatically fixes Verifies that the given unit commitment instance is valid and automatically
some validation errors if possible, issuing a warning for each error found. fixes some validation errors if possible, issuing a warning for each error
If a validation error cannot be automatically fixed, issues an exception. found. If a validation error cannot be automatically fixed, issues an
exception.
Returns the number of validation errors found. 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 silently returns true. In infeasible, returns false and prints the validation
errors to the screen. errors to the screen.
This function is implemented independently from the optimization model in `model.jl`, and This function is implemented independently from the optimization model in
therefore can be used to verify that the model is indeed producing valid solutions. It `model.jl`, and therefore can be used to verify that the model is indeed
can also be used to verify the solutions produced by other optimization packages. producing valid solutions. It can also be used to verify the solutions produced
by other optimization packages.
""" """
function validate(instance::UnitCommitmentInstance, function validate(instance::UnitCommitmentInstance,
solution::Union{Dict,OrderedDict}; solution::Union{Dict,OrderedDict};
)::Bool )::Bool
err_count = 0 err_count = 0
err_count += validate_units(instance, solution) err_count += _validate_units(instance, solution)
err_count += validate_reserve_and_demand(instance, solution) err_count += _validate_reserve_and_demand(instance, solution)
if err_count > 0 if err_count > 0
@error "Found $err_count validation errors" @error "Found $err_count validation errors"
@ -106,7 +108,7 @@ function validate(instance::UnitCommitmentInstance,
end end
function validate_units(instance, solution; tol=0.01) function _validate_units(instance, solution; tol=0.01)
err_count = 0 err_count = 0
for unit in instance.units for unit in instance.units
@ -300,7 +302,7 @@ function validate_units(instance, solution; tol=0.01)
end end
function validate_reserve_and_demand(instance, solution, tol=0.01) function _validate_reserve_and_demand(instance, solution, tol=0.01)
err_count = 0 err_count = 0
for t in 1:instance.time for t in 1:instance.time
load_curtail = 0 load_curtail = 0

@ -6,7 +6,7 @@ using UnitCommitment
@testset "convert" begin @testset "convert" begin
@testset "EGRET solution" 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 (\$)"] for attr in ["Is on", "Production (MW)", "Production cost (\$)"]
@test attr in keys(solution) @test attr in keys(solution)
@test "115_STEAM_1" in keys(solution[attr]) @test "115_STEAM_1" in keys(solution[attr])

@ -5,7 +5,7 @@
using Test using Test
using UnitCommitment using UnitCommitment
UnitCommitment.setup_logger() UnitCommitment._setup_logger()
@testset "UnitCommitment" begin @testset "UnitCommitment" begin
include("instance_test.jl") include("instance_test.jl")

@ -3,51 +3,83 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
using UnitCommitment, Test, LinearAlgebra using UnitCommitment, Test, LinearAlgebra
import UnitCommitment: Violation, _offer, _query
@testset "Screening" begin @testset "Screening" begin
@testset "Violation filter" begin @testset "Violation filter" begin
instance = UnitCommitment.read_benchmark("test/case14") 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, _offer(
filter,
Violation(
time=1,
monitored_line=instance.lines[1], monitored_line=instance.lines[1],
outage_line=nothing, outage_line=nothing,
amount=100.)) amount=100.,
),
offer(filter, Violation(time=1, )
_offer(
filter,
Violation(
time=1,
monitored_line=instance.lines[1], monitored_line=instance.lines[1],
outage_line=instance.lines[1], outage_line=instance.lines[1],
amount=300.)) amount=300.,
),
offer(filter, Violation(time=1, )
_offer(
filter,
Violation(
time=1,
monitored_line=instance.lines[1], monitored_line=instance.lines[1],
outage_line=instance.lines[5], outage_line=instance.lines[5],
amount=500.)) amount=500.,
),
offer(filter, Violation(time=1, )
_offer(
filter,
Violation(
time=1,
monitored_line=instance.lines[1], monitored_line=instance.lines[1],
outage_line=instance.lines[4], outage_line=instance.lines[4],
amount=400.)) amount=400.,
),
offer(filter, Violation(time=1, )
_offer(
filter,
Violation(
time=1,
monitored_line=instance.lines[2], monitored_line=instance.lines[2],
outage_line=instance.lines[1], outage_line=instance.lines[1],
amount=200.)) amount=200.,
),
offer(filter, Violation(time=1, )
_offer(
filter,
Violation(
time=1,
monitored_line=instance.lines[2], monitored_line=instance.lines[2],
outage_line=instance.lines[8], outage_line=instance.lines[8],
amount=100.)) amount=100.,
)
)
actual = query(filter) actual = _query(filter)
expected = [Violation(time=1, expected = [
Violation(
time=1,
monitored_line=instance.lines[2], monitored_line=instance.lines[2],
outage_line=instance.lines[1], outage_line=instance.lines[1],
amount=200.), amount=200.,
Violation(time=1, ),
Violation(
time=1,
monitored_line=instance.lines[1], monitored_line=instance.lines[1],
outage_line=instance.lines[5], outage_line=instance.lines[5],
amount=500.)] amount=500.,
),
]
@test actual == expected @test actual == expected
end end
@ -57,19 +89,24 @@ using UnitCommitment, Test, LinearAlgebra
line.normal_flow_limit[t] = 1.0 line.normal_flow_limit[t] = 1.0
line.emergency_flow_limit[t] = 1.0 line.emergency_flow_limit[t] = 1.0
end end
isf = UnitCommitment.injection_shift_factors(lines=instance.lines, isf = UnitCommitment._injection_shift_factors(
buses=instance.buses) lines=instance.lines,
lodf = UnitCommitment.line_outage_factors(lines=instance.lines, buses=instance.buses,
)
lodf = UnitCommitment._line_outage_factors(
lines=instance.lines,
buses=instance.buses, buses=instance.buses,
isf=isf) isf=isf,
)
inj = [1000.0 for b in 1:13, t in 1:instance.time] 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] overflow = [0.0 for l in instance.lines, t in 1:instance.time]
violations = UnitCommitment.find_violations(instance=instance, violations = UnitCommitment._find_violations(
instance=instance,
net_injections=inj, net_injections=inj,
overflow=overflow, overflow=overflow,
isf=isf, isf=isf,
lodf=lodf) lodf=lodf,
)
@test length(violations) == 20 @test length(violations) == 20
end end
end end

@ -7,7 +7,7 @@ using UnitCommitment, Test, LinearAlgebra
@testset "Sensitivity" begin @testset "Sensitivity" begin
@testset "Susceptance matrix" begin @testset "Susceptance matrix" begin
instance = UnitCommitment.read_benchmark("test/case14") instance = UnitCommitment.read_benchmark("test/case14")
actual = UnitCommitment.susceptance_matrix(instance.lines) actual = UnitCommitment._susceptance_matrix(instance.lines)
@test size(actual) == (20, 20) @test size(actual) == (20, 20)
expected = Diagonal([29.5, 7.83, 8.82, 9.9, 10.04, expected = Diagonal([29.5, 7.83, 8.82, 9.9, 10.04,
10.2, 41.45, 8.35, 3.14, 6.93, 10.2, 41.45, 8.35, 3.14, 6.93,
@ -18,8 +18,10 @@ using UnitCommitment, Test, LinearAlgebra
@testset "Reduced incidence matrix" begin @testset "Reduced incidence matrix" begin
instance = UnitCommitment.read_benchmark("test/case14") instance = UnitCommitment.read_benchmark("test/case14")
actual = UnitCommitment.reduced_incidence_matrix(lines=instance.lines, actual = UnitCommitment._reduced_incidence_matrix(
buses=instance.buses) lines=instance.lines,
buses=instance.buses,
)
@test size(actual) == (20, 13) @test size(actual) == (20, 13)
@test actual[1, 1] == -1.0 @test actual[1, 1] == -1.0
@test actual[3, 1] == 1.0 @test actual[3, 1] == 1.0
@ -63,8 +65,10 @@ using UnitCommitment, Test, LinearAlgebra
@testset "Injection Shift Factors (ISF)" begin @testset "Injection Shift Factors (ISF)" begin
instance = UnitCommitment.read_benchmark("test/case14") instance = UnitCommitment.read_benchmark("test/case14")
actual = UnitCommitment.injection_shift_factors(lines=instance.lines, actual = UnitCommitment._injection_shift_factors(
buses=instance.buses) lines=instance.lines,
buses=instance.buses,
)
@test size(actual) == (20, 13) @test size(actual) == (20, 13)
@test round.(actual, digits=2) == [ @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; -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 @testset "Line Outage Distribution Factors (LODF)" begin
instance = UnitCommitment.read_benchmark("test/case14") instance = UnitCommitment.read_benchmark("test/case14")
isf_before = UnitCommitment.injection_shift_factors(lines=instance.lines, isf_before = UnitCommitment._injection_shift_factors(
buses=instance.buses) lines=instance.lines,
lodf = UnitCommitment.line_outage_factors(lines=instance.lines,
buses=instance.buses, buses=instance.buses,
isf=isf_before) )
lodf = UnitCommitment._line_outage_factors(
lines=instance.lines,
buses=instance.buses,
isf=isf_before,
)
for contingency in instance.contingencies for contingency in instance.contingencies
for lc in contingency.lines for lc in contingency.lines
prev_susceptance = lc.susceptance prev_susceptance = lc.susceptance
lc.susceptance = 0.0 lc.susceptance = 0.0
isf_after = UnitCommitment.injection_shift_factors(lines=instance.lines, isf_after = UnitCommitment._injection_shift_factors(
buses=instance.buses) lines=instance.lines,
buses=instance.buses,
)
lc.susceptance = prev_susceptance lc.susceptance = prev_susceptance
for lm in instance.lines for lm in instance.lines
expected = isf_after[lm.offset, :] expected = isf_after[lm.offset, :]

@ -14,7 +14,7 @@ parse_case14() = JSON.parse(GZip.gzopen("../instances/test/case14.json.gz"),
json = parse_case14() json = parse_case14()
json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150, 200] json["Generators"]["g1"]["Production cost curve (MW)"] = [100, 150, 200]
json["Generators"]["g1"]["Production cost curve (\$)"] = [10, 25, 30] 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 @test UnitCommitment.repair!(instance) == 4
end 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 (MW)"] = [100, 150]
json["Generators"]["g1"]["Production cost curve (\$)"] = [100, 150] json["Generators"]["g1"]["Production cost curve (\$)"] = [100, 150]
json["Generators"]["g1"]["Startup limit (MW)"] = 80 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 @test UnitCommitment.repair!(instance) == 1
end end
@ -31,7 +31,7 @@ parse_case14() = JSON.parse(GZip.gzopen("../instances/test/case14.json.gz"),
json = parse_case14() json = parse_case14()
json["Generators"]["g1"]["Startup costs (\$)"] = [300, 200, 100] json["Generators"]["g1"]["Startup costs (\$)"] = [300, 200, 100]
json["Generators"]["g1"]["Startup delays (h)"] = [8, 4, 2] 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 @test UnitCommitment.repair!(instance) == 4
end end

Loading…
Cancel
Save