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.

In [1]:
%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.

In [2]:
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:

In [3]:
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
Out[3]:
0

So where does pfbi differ from the Python interface that app provides? Let's see.

Accessing objects¶

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:

In [4]:
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:

object path

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:

In [5]:
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:

In [6]:
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):

In [20]:
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):

In [7]:
terminal_1=pfbi.get_single_obj(r"Network Model\Network Data\Grid\Terminal HV 1") 

Setting and getting object attributes¶

How about setting data in the database? You can set attributes of an object as follows:

In [8]:
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:

In [9]:
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:

In [21]:
nominal_voltage = pfbi.get_attr(r"Network Model\Network Data\Grid\Terminal MV","uknom")
# Equivalent:
nominal_voltage = pfbi.get_attr(mv_terminal,"uknom")

Creating objects¶

If you want to create a new object, use:

In [11]:
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

In [12]:
new_obj = pfbi.create_in_folder(r"Library\Dynamic Models","dummy.BlkDef",overwrite=True)

which will overwrite the former object.

Copying objects¶

To copy an object to a folder use

In [13]:
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:

In [14]:
new_copy = pfbi.copy_obj(r"Library\Dynamic Models\Linear_interpolation",
    r"Library\Dynamic Models\TestCopy")

To copy a whole folder, write:

In [22]:
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:

In [16]:
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>

Deleting objects¶

If you want to delete an object, you can simply do:

In [17]:
pfbi.delete_obj(r"Library\Dynamic Models\TestCopyMultiple\dummy")

Alternatively, to delete multiple objects (use placeholder "*") in a folder:

In [18]:
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*")