Introduction to the PowerModelsDistribution Data Models
In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give several examples of how to use this new data model directly, including new transformations that have become easier with its introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model.
Imports
All commands in this document with no package namespace specified are directly exported by PowerModelsDistribution or already available in Julia base. Any commands that are only avaiable via an external package will be specified by including by using import
, which will require specifying the originating package before the command, e.g. Ipopt.Optimizer
as you will see below.
using PowerModelsDistribution
In these examples we will use the following optimization solvers, specified using optimizer_with_attributes
from JuMP v0.21
import Ipopt
ipopt_solver = optimizer_with_attributes(Ipopt.Optimizer, "tol"=>1e-6, "print_level"=>0)
MathOptInterface.OptimizerWithAttributes(Ipopt.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute, Any}[MathOpt
Interface.RawParameter("tol") => 1.0e-6, MathOptInterface.RawParameter("print_level") => 0])
Parsing Data
Here we give the first example of how to parse data into the ENGINEERING
data model structure, which is the default data structure type that the user will see without passing additional arguments, as we demonstrate later.
We start with a 3 bus unbalanced load case provided as a dss file in the test
folder of the PowerModelsDistribution.jl repository
eng = parse_file("../test/data/opendss/case3_unbalanced.dss")
Error: SystemError: opening file "../test/data/opendss/case3_unbalanced.dss": No such file or directory
Different information and warning messages will be given depending on the input file. In the case above, these messages all related to various parse notifications that arise during a parse of a dss file, and can be safely ignored
The resulting data structure is a Julia dictionary. The first notable field is "data_model"
which specifies which data model this data structure corresponds to, in this case ENGINEERING
. This value is expected to be an Enum
of type DataModel
The next notable field is "settings"
, which contains some important default/starting values for the distribution network
eng["settings"]
Error: UndefVarError: eng not defined
"sbase_default"
is the starting value for the power base,"vbases_default"
is the starting voltage base for the case, and multiple voltage bases can be specified, which would be useful in cases where there are multiple isolated islands with their own generation,"voltage_scale_factor"
is a scaling factor for all voltage values, which in the case of OpenDSS is in kV by default"power_scale_factor"
is a scaling factor for all power values"base_frequency"
is the base frequency of the network in Hz, which is useful to know for mixed frequency networks
Next we look at the "bus"
components
eng["bus"]
Error: UndefVarError: eng not defined
We can see there are three buses in this system, identified by ids "primary"
, "sourcebus"
, and "loadbus"
.
NOTE: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like e.g. Vectors
.
Identifying components by non-integer names is a new feature of the ENGINEERING
model, and makes network debugging more straightforward.
NOTE: all names are converted to lowercase on parse from the originating dss file.
Each bus component has the following properties in the ENGINEERING
model
eng["bus"]["sourcebus"]
Error: UndefVarError: eng not defined
"terminals"
indicates which terminals on the bus have active connections"grounded"
indicates which terminals are grounded"rg"
and"xg"
indicate the grounding resistance and reactance of the ground"status"
indicates whether a bus isENABLED
orDISABLED
, and is specified for every component in the engineering model
Next, we look at the "line"
components, which is a generic name for both overhead lines and underground cables, which we do not differentiate between in the nomenclature
eng["line"]
Error: UndefVarError: eng not defined
eng["line"]["quad"]
Error: UndefVarError: eng not defined
Again, we see components identified by their OpenDSS names. A "line"
is an edge object, which will always have the following properties:
"f_bus"
"t_bus"
"f_connections"
- list of terminals to which the line is connected on the from-side"t_connections"
- list of terminals to which the line is connected on the to-side
Here we are also introduced to two important concepts, the "source_id"
, which is an easy way to identify from where an object originates in the dss file, and a data type element, pointed to by "linecode"
in this case.
A data type element is an element that does not represent a real engineering object, but only contains data that one of those real objects can refer to, in this case a linecode, which contains information like line resistance/reactance and conductance/susceptance.
eng["linecode"]["4/0quad"]
Error: UndefVarError: eng not defined
Next, we introduce a node element, the "load"
object, where we also see the first example of a specification of less than three phases at a time
eng["load"]["l1"]
Error: UndefVarError: eng not defined
We can see that the length of the Vectors for "pd_nom"
and "qd_nom"
are only one, although the number of terminals listed in "connections"
is two. This is because the connection is WYE, and therefore the final connection is a grounded neutral
Here we are also introduced to two new Enums, WYE
, which gives the connection configuration, and NO
under dispatchable, which indicates that if this case were used in an MLD problem, i.e. with run_mc_mld
that this load would not be sheddable.
Finally, we show the generation source for this case, which in opendss is a voltage source named "source"
eng["voltage_source"]["source"]
Error: UndefVarError: eng not defined
"vm"
- specifies the fixed voltage magnitudes per phase at the bus"va"
- specifies the fixed reference angles per phases at the bus"rs"
and"xs"
specifies internal impedances of the voltage source
Importing raw dss properties
In case there are additional properties that you want to use from dss, it is possible to import those directly into the ENGINEERING
(and MATHEMATICAL
) data structure with the import_all
keyword argument
eng_all = parse_file("../test/data/opendss/case3_unbalanced.dss"; import_all=true)
eng_all["line"]
Error: SystemError: opening file "../test/data/opendss/case3_unbalanced.dss": No such file or directory
You will note the presence of "dss"
dictionaries under components, and "dss_options"
at the root level
Time Series Parsing Example
In the ENGINEERING
model, we have included the time_series
data type, which holds all time series data and can be referred to similar to "linecode"
as demonstrated above.
Below we can see an example of a parse that includes some time_series components
eng_ts = parse_file("../test/data/opendss/case3_balanced.dss"; time_series="daily")
Error: SystemError: opening file "../test/data/opendss/case3_balanced.dss": No such file or directory
eng_ts["load"]["l1"]["time_series"]
Error: UndefVarError: eng_ts not defined
You can see that under the actual component, in this case a "load"
, that there is a "time_series"
dictionary that contains ENGINEERING
model variable names and references to the identifiers of a root-level time_series
object,
eng_ts["time_series"]["ls1"]
Error: UndefVarError: eng_ts not defined
This feature is useful for building multinetwork data structures, which will be described below in the section on the MATHEMATICAL
model
Running Optimal Power Flow
In this section we introduce how to run an optimal power flow (opf) in PowerModelsDistribution on an engineering data model
In order to run an OPF problem you will need
- a data model
- a formulation
- a solver
In these examples we will use the eng
model we worked with above, the ACPPowerModel
, which is a AC power flow formulation in polar coordinates, and the ipopt_solver
we already defined above
result = solve_mc_opf(eng, ACPPowerModel, ipopt_solver)
Error: UndefVarError: eng not defined
The result of solve_mc_opf
will be very familiar to those who are already familiar with PowerModels and PowerModelsDistribution. The notable difference will be in the "solution"
dictionary
result["solution"]
Error: UndefVarError: result not defined
Here you can see that the solution comes back out by default into the same data model as is provided by the user to the run_
command, as well as being in SI units, as opposed to per unit, which is used during the solve. For example,
result["solution"]["bus"]["loadbus"]
Error: UndefVarError: result not defined
If for some reason you want to return the result in per-unit rather than SI, you can specify this in the run_
command by
result_pu = solve_mc_opf(eng, ACPPowerModel, ipopt_solver; make_si=false)
result_pu["solution"]["bus"]["loadbus"]
Error: UndefVarError: eng not defined
Branch Flow formulations
Previously, to use a branch flow formulation, such as SOCNLPUBFPowerModel
, it was required to use a different run_
command, but now, by using multiple dispatch we have simplified this for the user
result_bf = solve_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver)
Error: UndefVarError: eng not defined
Running Time Series Models
By default, time_series
object will be ignored when running a model. To use the time series information you will need to have a multinetwork problem specification
In the example below we use a test case, which is not exported by default, and therefore requires the specification of the PowerModelsDistribution namespace
result_mn = PowerModelsDistribution._solve_mn_mc_opb(eng_ts, NFAPowerModel, ipopt_solver)
Error: UndefVarError: eng_ts not defined
Engineering Model Transformations
One of the power things about the engineering model is that data transformations are much more simple. Here we illustrate two examples that are currently included in PowerModelsDistribution, but writing your own data transformation functions will be trivial, as we will show.
Note: In v0.9, apply_kron_reduction!
and apply_phase_projection!
are applied by default, but can be disabled with the keyword arguments kron_reduced=false
and project_phases=false
, respectively in parse_file
or transform_data_model
.
First, there are several objects that have loss models by default when parsing from dss files, such as voltage sources, transformers, and switches. To remove these loss models, therefore making these components lossless, we can use the included make_lossess!
function. Here we use a basic 2-winding wye-wye connected transformer case from test
to illustrate this
eng_ut = parse_file("../test/data/opendss/ut_trans_2w_yy.dss")
eng_ut["transformer"]["tx1"]
Error: SystemError: opening file "../test/data/opendss/ut_trans_2w_yy.dss": No such file or directory
We can see that "noloadloss"
, "rw"
, and "imag"
are non-zero, but if we apply the make_lossless!
function we can see these parameters are set to zero, effectively eliminating the losses
make_lossless!(eng_ut)
eng_ut["transformer"]["tx1"]
Error: UndefVarError: eng_ut not defined
Alternatively, we can apply this function at parse
eng_ut = parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; transformations=[make_lossless!])
eng_ut["transformer"]["tx1"]
Error: SystemError: opening file "../test/data/opendss/ut_trans_2w_yy.dss": No such file or directory
Another transformation function included in PowerModelsDistribution is the apply_voltage_bounds!
function, which will apply some voltage bounds in SI units, given some percent value, e.g. if we want the lower bound on voltage to be 0.9
and upper bound 1.1
after per-unit conversion
apply_voltage_bounds!(eng_ut; vm_lb=0.9, vm_ub=1.1)
eng_ut["bus"]["2"]
Error: UndefVarError: eng_ut not defined
Alternatively, this can be specified at parse by
eng_ut = parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; transformations=[make_lossless!, (apply_voltage_bounds!, "vm_lb"=>0.9, "vm_ub"=>1.1)])
eng_ut["bus"]["2"]
Error: SystemError: opening file "../test/data/opendss/ut_trans_2w_yy.dss": No such file or directory
Transformations on Multinetworks
Transformations on Multinetworks should happen before the network is converted into a MATHEMATICAL
data model, so that they can generally follow the same pattern as shown above and can be seen in the make_lossless!
and apply_voltage_bounds!
functions already in PowerModelsDistribution
Mathematical Model
In this section we introduce the mathematical model, which was the previous user-facing model in PowerModelsDistribution, explain how conversions between the model happen in practice, and give an example of how to do this conversion manually
In practice, unless the user is interested, the conversion between the ENGINEERING
and MATHEMATICAL
models should be seemless and invisible to the user. By providing an ENGINEERING
model to a run_
command the run_mc_model
command will know to convert the model to MATHEMATICAL
, which will be used to the generate the JuMP model that will actually be optimized. Similarly, the solution generated by this optimization will be automatically converted back to the format of the ENGINEERING
model.
Let's first take a look at how to convert to the MATHEMATICAL
model
math = transform_data_model(eng)
Error: UndefVarError: eng not defined
There are a couple of things to notice right away. First, the data model transform automatically converts the model to per-unit. Second, there are a lot of empty component sets, whereas in the ENGINEERING
model, only component types that had components in them were listed. In the MATHEMATICAL
model certain component dictionaries are always expected to exist, and the eng2math
conversion functions will automatically populate these.
Next, there are a few unusal fields, such as "settings"
, which previously didn't exist in the MATHEMATICAL
model. This is used for the per-unit conversion specifically in PowerModelsDistribution. Also, is the "map"
field, which is a Vector
of Dictionaries that enable the conversion back to ENGINEERING
from MATHEMATICAL
. Without this it would be impossible to convert back, and in fact only the solution can be converted, because some properties are combined destructively during the conversion to the MATHEMATICAL
model, and therefore cannot be reverse engineered. However, since the conversion to MATHEMATICAL
is not in-place, you will always have a copy of eng
alongside math
.
Here is an example of one of the "map"
entries
math["map"][end]
Error: UndefVarError: math not defined
Alternatively, the MATHEMATICAL
model can be returned directly from the parse_file
command with the data_model
keyword argument
math = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEMATICAL)
Error: SystemError: opening file "../test/data/opendss/case3_unbalanced.dss": No such file or directory
Multinetworks
In this subsection we cover parsing into a multinetwork data structure, which is a structure that only exists in the MATHEMATICAL
model
For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems.
Multinetwork data structures are formatted like so
mn = Dict{String,Any}( "multinetwork" => true, "nw" => Dict{String,Any}( "1" => Dict{String,Any}( "bus" => Dict{String,Any}(), ... ), ... ), ... )
To automatically create a multinetwork structure from an engineering model that contains time_series
elements, we can use the build_multinetwork
keyword argument in transform_data_model
math_mn = transform_data_model(eng_ts; build_multinetwork=true)
Error: UndefVarError: eng_ts not defined
Alternatively, we can use parse_file
with the build_multinetwork
keyword argument combined with data_model=MATHEMATICAL
math_mn = parse_file("../test/data/opendss/case3_balanced.dss"; build_multinetwork=true, data_model=MATHEMATICAL)
Error: SystemError: opening file "../test/data/opendss/case3_balanced.dss": No such file or directory
Running MATHEMATICAL
models
There is very little difference from the user point-of-view in running MATHEMATICAL
models other than the results will not be automatically converted back to the the format of the ENGINEERING
model
result_math = solve_mc_opf(math, ACPPowerModel, ipopt_solver)
result_math["solution"]
Error: UndefVarError: math not defined
It is also possible to manually convert the solution back to the ENGINEERING
format, provided you have the map
sol_eng = transform_solution(result_math["solution"], math)
Error: UndefVarError: result_math not defined
Running MATHEMATICAL
Multinetworks
As with the ENGINEERING
example of running a multinetwork problem, you will need a multinetwork problem specification, and as with the previous single MATHEMATICAL
network example above, we only obtain the MATHEMATICAL
solution, and can transform the solution in the same manner as before
result_math_mn = PowerModelsDistribution._solve_mn_mc_opb(math_mn, NFAPowerModel, ipopt_solver)
result_math_mn["solution"]["nw"]["1"]
Error: UndefVarError: math_mn not defined
sol_eng_mn = transform_solution(result_math_mn["solution"], math_mn)
sol_eng_mn["nw"]["1"]
Error: UndefVarError: result_math_mn not defined
Building the JuMP Model
In some cases the user will want to directly build the JuMP model, which would traditionally be done with instantiate_model
from PowerModels. In order to facilitate using the ENGINEERING
model we have introduced instantiate_mc_model
to aid in the generation of the JuMP model. instantiate_mc_model
will automatically convert the data model to MATHEMATICAL if necessary (notifying the user of the conversion), and pass the MATHEMATICAL model off to PowerModels' instantiate_model
with ref_add_arcs_transformer!
in ref_extensions
, which is a required ref extension for PowerModelsDistribution.
pm_eng = instantiate_mc_model(eng, NFAPowerModel, build_mc_opf)
print(pm_eng.model)
Error: UndefVarError: eng not defined
This is equivalent to
import PowerModels
pm_math = PowerModels.instantiate_model(math, NFAPowerModel, build_mc_opf; ref_extensions=[ref_add_arcs_transformer!])
print(pm_math.model)
Error: UndefVarError: math not defined
Conclusion
This concludes the introduction to the ENGINEERING
data model and conversion to the MATHEMATICAL
model. We hope that you will find this new data model abstraction easy to use and simple to understand