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
end
Inputs
edges
Vector{Tuple} e.g.[("0", "1"), ("1", "2")]
linecodes
vector of string keys for the Zdict (impedance values for lines). When using an OpenDSS model alinecode
is thename
inNew linecode.name
linelengths
vector of floats to scale impedance valuesbusses
vector of bus namesphases
vector of vectors with ints for the line phases (e.g.[[1,2,3], [1,3], ...]
)Pload
dict withbusses
for keys and uncontrolled real power loads (positive is load) by phase and timeQload
dict withbusses
for keys and uncontrolled reactive power loads (positive is load) by phase and timeSbase
base apparent power for network, typ. feeder capacity. Used to normalize all powers in modelVbase
base voltage for network, used to determineZbase = Vbase^2 / Sbase
Ibase
=Sbase / (Vbase * sqrt(3))
Zdict
dict withlinecodes
for keys and subdicts with "xmatrix" and "zmatrix" keys with per unit length values. Values are divided byZbase
and multiplied by linelength in mathematical model.v0
slack 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
:P
or:Q
for 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
prices
LinDistFlow.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_powers
Approximate 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}$