This tutorial gives an overview of the interaction with the PowerFactory database using powfacpy. The methods introduced in this tutorial serve as a basis do do more complex things and to write more readable code using less lines of code.
For a complete list of classes and methods, please have a look at the API Chapter of the documentation or at the source code.
Similar to using the Python API of PowerFactory directly, we first need to import the powerfactory module from PowerFactory's installation directory and get the application.
%load_ext autoreload
%autoreload 2
import sys
sys.path.append(r'C:\Program Files\DIgSILENT\PowerFactory 2022 SP1\Python\3.10') # you may use a different directory
import powerfactory as powerfactory
app = powerfactory.GetApplication()
Then import powfacpy and create an instance of class PFBaseInterface
(with argument app
). This interface class is used mainly to interact with the PowerFactory database as we will see below.
sys.path.insert(0,r'.\src')
sys.path.append(r'd:\User\seberlein\Code\powerfactorypy\src')
import powerfactorypy
pfbi = powerfactorypy.PFBaseInterface(app)
Note that pfbi
has an attribute app
that can be used similar to the app
variable we loaded from the powerfactory
module before. Here are two ways to 1. show the PowerFactory application and 2. activate a project:
app.Show()
app.ActivateProject(r"\seberlein\powerfactorypy_base") # You may change the project path
pfbi.app.Show()
pfbi.app.ActivateProject(r"\seberlein\powerfactorypy_base") # You may change the project path
0
So where does pfbi
differ from the Python interface that app
provides? Let's see.
Let's access an object from the PowerFactory database. When using app
, we would have to write several lines of code using the methods app.GetProjectFolder
, app.GetChildren
or app.GetContents
. In contrast, it is easier to use pfbi.get_obj
with the path of the object (relative to the folder of the active project) as an argument:
mv_terminal = pfbi.get_obj(r"Network Model\Network Data\Grid\Terminal MV")[0]
The [0]
at the end is necessary because the method always returns a list (with one element in this case).
Note that you can easily copy and paste the path from the data manager while selecting the object in the data tree:
pfbi.get_obj
also differs in other ways from app.GetContents
. Most importantly, you can set a condition for the objects that you want to access, which is best described by an example:
hv_terminals = pfbi.get_obj(r"Network Model\Network Data\Grid\*.ElmTerm",
condition=lambda x: getattr(x,"uknom") > 50)
First of all, we use a wildcard (*.ElmTerm
) to access all terminals in the Grid
folder. The condition
argument is a function that defines a certain condition the objects have to fulfill. In this case, the input argument x
is an ElmTerm
and getattr(x,"uknom") > 50
gets its attribute uknom
(nominal voltage) and checks whether it is larger than 50 (kV). You can also define more complex functions, such as lambda x: getattr(x,"uknom") > 50 and getattr(x,"uknom") < 200
to access terminals between 50 and 200 kV.
It is also possible to include subfolders in the search for objects:
terminals = pfbi.get_obj(r"Network Model\Network Data\*.ElmTerm",include_subfolders=True)
This will search in Network Model\Network Data
and all its subfolders.
What's also helpful is that pfbi.get_obj
throws an error if the path is incorrect and shows exactly where it fails (see PFPathError
description at the end of the message). So
terminals = pfbi.get_obj(r"Network Model\wrong_folder_name\*.ElmTerm")
will throw an error:
PFPathError: 'wrong_folder_name' does not exist in '\user\powerfactorypy_base\Network Model'
By default, an exception is also raised if no objects are found. For example:
terminals = pfbi.get_obj(r"Network Model\Network Data\wrong_object_name*",include_subfolders=True)
returns
PFPathError: 'wrong_object_name*' does not exist in '\user\powerfactorypy_base\Network Model\Network Data'
This can be turned off using error_if_non_existent=False
, then an empty list will be returned instead.
If you want to access objects in a folder many times and don't want to use every time the whole path relative to the active project, you can also specifiy a parent folder where the path
starts (this can also be more performant):
grid_folder = pfbi.get_obj(r"Network Model\Network Data\Grid")[0]
mv_terminal = pfbi.get_obj("Terminal MV", parent_folder=grid_folder)[0]
hv_terminal = pfbi.get_obj("Terminal HV 1", parent_folder=grid_folder)[0]
An alternative to pfbi.get_obj
is pfbi.get_single_obj
. Use this function if you want to access a single unique object from the database and want to avoid the [0]
(which is easily forgotten):
terminal_1=pfbi.get_single_obj(r"Network Model\Network Data\Grid\Terminal HV 1")
How about setting data in the database? You can set attributes of an object as follows:
pfbi.set_attr(r"Network Model\Network Data\Grid\Terminal MV",
{"uknom":33,"outserv":0})
So with only one command we set the attributes "uknom" and "outserv" of the terminal. This saves time and is also very readable code!
Note that the method set_attr
accepts the path (string) but also a PowerFactory object. For example, we could also use the object mv_terminal
that we loaded above:
pfbi.set_attr(mv_terminal,
{"uknom":33,"outserv":0})
This applies to many other methods in powfacpy
. Loading the object only once and then using the object can be more efficient than using the path string very many times.
If you want to get an attribute of an object, write:
nominal_voltage = pfbi.get_attr(r"Network Model\Network Data\Grid\Terminal MV","uknom")
# Equivalent:
nominal_voltage = pfbi.get_attr(mv_terminal,"uknom")
If you want to create a new object, use:
new_obj = pfbi.create_by_path(r"Library\Dynamic Models\dummy.BlkDef")
This will create an object of class "BlkDef" with the name "dummy" in the folder "Library\Dynamic Models". You can also use
new_obj = pfbi.create_in_folder(r"Library\Dynamic Models","dummy.BlkDef",overwrite=True)
obj_to_be_copied = r"Library\Dynamic Models\Linear_interpolation"
folder_copy_to = r"Library\Dynamic Models\TestCopy"
new_copy = pfbi.copy_obj(obj_to_be_copied,folder_copy_to)
As mentioned above, instead of using objects as method arguments, you can also use the path string:
new_copy = pfbi.copy_obj(r"Library\Dynamic Models\Linear_interpolation",
r"Library\Dynamic Models\TestCopy")
To copy a whole folder, write:
folder_copy_from = r"Library\Dynamic Models\TestDummyFolder"
folder_copy_to = r"Library\Dynamic Models\TestCopyMultiple"
new_copy_list = pfbi.copy_multiple_objects(folder_copy_from,folder_copy_to)
<l3>\seberlein.IntUser\powerfactorypy_base.IntPrj\Library.IntPrjfolder\Dynamic Models.IntPrjfolder\TestCopyMultiple</l3> <l3>\seberlein.IntUser\powerfactorypy_base.IntPrj\Library.IntPrjfolder\Dynamic Models.IntPrjfolder\TestCopyMultiple</l3>
You can also copy a list of objects. To show this, we get the contents of a folder first and then copy them to the new folder:
list_of_objects = pfbi.get_obj(r"Library\Dynamic Models\TestDummyFolder\*")
folder_copy_to = r"Library\Dynamic Models\TestCopyMultiple"
list_of_copied = pfbi.copy_multiple_objects(list_of_objects,folder_copy_to)
<l3>\seberlein.IntUser\powerfactorypy_base.IntPrj\Library.IntPrjfolder\Dynamic Models.IntPrjfolder\TestCopyMultiple</l3> <l3>\seberlein.IntUser\powerfactorypy_base.IntPrj\Library.IntPrjfolder\Dynamic Models.IntPrjfolder\TestCopyMultiple</l3>
If you want to delete an object, you can simply do:
pfbi.delete_obj(r"Library\Dynamic Models\TestCopyMultiple\dummy")
Alternatively, to delete multiple objects (use placeholder "*") in a folder:
folder = r"Library\Dynamic Models\TestDelete"
# Create object first
new_obj = pfbi.create_in_folder(folder,"dummy_to_be_deleted.BlkDef",overwrite=True)
# Then delete it
pfbi.delete_obj_from_folder(folder,"dummy_to_be_deleted*")