LinDistFlow.jl
LinDistFlow builds the linear distflow constraints using JuMP. The intent of this package is to allow users to build mathematical programs that include LinDistFlow constraints. No objective is added to the JuMP model in this package and so solving any problem defined by the constraints built by LinDistFlow.jl is a feasibility problem. Dictionaries of constraints are provided so that one can delete and/or modify the base constraints to fit their problem.
Inputs
There are two methods for creating Inputs:
- Using openDSS files
- Providing the network topology
CommonOPF.Inputs — MethodInputs(
dssfilepath::String,
substation_bus::String;
Pload::AbstractDict=Dict(),
Qload::AbstractDict=Dict(),
Sbase=1,
Vbase=1,
v0,
v_lolim=0.95,
v_uplim=1.05,
Ntimesteps=1,
P_up_bound=1e4,
Q_up_bound=1e4,
P_lo_bound=-1e4,
Q_lo_bound=-1e4,
relaxed=true,
extract_phase::Int=0 # set to 1, 2, or 3
)Inputs constructor that parses a openDSS file for the network. If Pload and Qload are not provided then the loads are also parsed from the openDSS file.
If extract_phase is set to 1, 2, 3 then the loads for that phase are put into Pload and Qload and the impedance values are set to the positive sequence impedance for each line. Note that single phase lines and two phase lines do not have positive sequence definitions but single phase lines only have one impedance value anyway and for two phase lines we use zmutual = z12 and zself = 1/2(z11 + z22).
CommonOPF.Inputs — MethodInputs(
edges::Array{Tuple},
linecodes::Array{String},
linelengths::Array{Float64},
phases::Vector{Vector},
substation_bus::String;
Pload,
Qload,
Sbase=1,
Vbase=1,
Zdict,
v0,
v_lolim=0.95,
v_uplim=1.05,
Ntimesteps=1,
P_up_bound=1e4,
Q_up_bound=1e4,
P_lo_bound=-1e4,
Q_lo_bound=-1e4,
Isquared_up_bounds=Dict{String, Float64}(),
relaxed=true
)Lowest level Inputs constructor (the only one that returns the Inputs struct).
The real and reactive loads provided are normalized using Sbase.
Both of the Inputs functions return a mutable Inputs struct:
CommonOPF.Inputs — Typemutable struct Inputs{T<:Phases} <: AbstractInputs
edges::Array{Tuple, 1}
linecodes::Array{String, 1}
linelengths::Array{Float64, 1}
busses::Array{String}
phases::Vector{Vector}
substation_bus::String
Pload::Dict{String, Any}
Qload::Dict{String, Any}
Sbase::Real
Vbase::Real
Ibase::Real
Zdict::Dict{String, Dict{String, Any}}
v0::Real
v_lolim::Real
v_uplim::Real
Zbase::Real
Ntimesteps::Int
pf::Float64
Nnodes::Int
P_up_bound::Float64
Q_up_bound::Float64
P_lo_bound::Float64
Q_lo_bound::Float64
Isquared_up_bounds::Dict{String, <:Real}
phases_into_bus::Dict{String, Vector{Int}}
relaxed::Bool
edge_keys::Vector{String}
regulators::Dict
shunt_susceptance::Dict
endInputs
edgesVector{Tuple} e.g.[("0", "1"), ("1", "2")]linecodesvector of string keys for the Zdict (impedance values for lines). When using an OpenDSS model alinecodeis thenameinNew linecode.namelinelengthsvector of floats to scale impedance valuesbussesvector of bus namesphasesvector of vectors with ints for the line phases (e.g.[[1,2,3], [1,3], ...])Ploaddict withbussesfor keys and uncontrolled real power loads (positive is load) by phase and timeQloaddict withbussesfor keys and uncontrolled reactive power loads (positive is load) by phase and timeSbasebase apparent power for network, typ. feeder capacity. Used to normalize all powers in modelVbasebase voltage for network, used to determineZbase = Vbase^2 / SbaseIbase=Sbase / (Vbase * sqrt(3))Zdictdict withlinecodesfor keys and subdicts with "xmatrix" and "zmatrix" keys with per unit length values. Values are divided byZbaseand multiplied by linelength in mathematical model.v0slack bus reference voltage
TODO Zdict example
TODO test against simple model to make sure scaling is done right
The edges, linecodes, phases, edge_keys, and linelengths are in mutual order (i.e. the i-th value in each list corresponds to the same line)
Building a Model
The build_ldf! function takes a JuMP.Model and Inputs struct as its two arguments and adds the variables and constraints:
LinDistFlow.build_ldf! — Functionbuild_ldf!(m::JuMP.AbstractModel, p::Inputs)Add variables and constraints to m using the values in p. Calls the following functions:
add_variables(m, p)
constrain_power_balance(m, p)
constrain_substation_voltage(m, p)
constrain_KVL(m, p)
constrain_loads(m, p)Variables
Let m be the JuMP.Model provided by the user, then the variables can be accessed via:
m[:vsqrd]voltage magnitude squared, indexed on busses, (phases), timem[:Pj], m[:Qj]net real, reactive power injection, indexed on busses, (phases), timem[:Pij], m[:Qij]net real, reactive line flow, indexed on edges, (phases), time
After a model has been solved using JuMP.optimize! variable values can be extracted with JuMP.value. For more see Getting started with JuMP.
Single phase models do not have a phase index
Accessing and Modifying Constraints
Let the JuMP.Model provided by the user be called m. All constraints are stored in m[:cons] as anonymous constraints.
Power Injections
LinDistFlow.jl uses the convention that power injections are positive (and loads are negative). If no load is provided for a given bus (and phase) then the real and reactive power injections at that bus (and phase) are set to zero with an equality constraint.
All power injection constraints are stored in m[:cons][:injection_equalities]. The constraints are indexed in the following order:
- by bus name (string), as provided in
Inputs.busses; - by
:Por:Qfor real and reactive power respectively; - by phase number (integer); and
- by time (integer).
For example, m[:cons][:injection_equalities]["680"][:P][2][1] contains the constraint reference for the power injection equality constraint for bus "680", real power, on phase 2, in time step 1.
If one wished to replace any constraint one must first delete the constraint using the delete function. For example:
delete(m, m[:cons][:injection_equalities]["680"][:P][1])Note that the time index was not provided in the delete command in this example, which implies that the equality constraints for all time steps were deleted. One can also delete individual time step constraints by providing the time index.
The deleted constraints can then be replaced with a new set of constraints. For example:
m[:cons][:injection_equalities]["680"][:P][1] = @constraint(m, [t in 1:p.Ntimesteps],
m[:Pj]["680",1,t] == -1e3 / p.Sbase
)where p is short for "parameters" and is the Inputs struct for the problem of interest. Note that it is not necessary to store the new constraints in the m[:cons][:injection_equalities].
See the JuMP documentation for more on deleting constraints.
Results
Results for the power flow variables can be retrieved via the Results methods described below. The fields of the Results struct are dictionaries that have the same indices as the variables described above.
LinDistFlow.Results — MethodResults(m::AbstractModel, p::Inputs{SinglePhase}; digits=8)return a Results struct with fieldnames:
voltage_magnitudes
real_power_injections
reactive_power_injections
current_magnitudes
real_sending_end_powers
reactive_sending_end_powers
pricesLinDistFlow.Results — MethodResults(m::AbstractModel, p::Inputs{MultiPhase}; digits=8)return a Results struct with fieldnames:
voltage_magnitudes
real_power_injections
reactive_power_injections
current_magnitudes
real_sending_end_powers
reactive_sending_end_powersApproximate line amperage values can be obtained via get_line_amps and get_peak_line_amps_percent for multi-phase models.
LinDistFlow.get_line_amps — Functionget_line_amps(m::JuMP.AbstractModel, p::Inputs{MultiPhase})Estimating the line amps as $|(V_i - V_j) / Z|$ where we use the approximation: $|V_i - V_j| \approx r_{ij} P_{ij} + x_{ij} Q_{ij}$
LinDistFlow.get_peak_line_amps_percent — Functionget_peak_line_amps_percent(m::JuMP.AbstractModel, p::Inputs{MultiPhase})A Dict{String, Float64} with edge keys and peak percent line amps (peak over all time steps)
Estimating the line amps as $|(V_i - V_j) / Z|$ where we use the approximation: $|V_i - V_j| \approx r_{ij} P_{ij} + x_{ij} Q_{ij}$