PowerFactory Python API: getting started

PowerFactory comes with a Python module, powerfactory. Inside PowerFactory, powerfactory.GetApplication() gives you the running program; from your own Python, powerfactory.GetApplicationExt() starts it in the background and tells you why when it cannot. Everything after that is objects: activate a project, run a command such as ComLdf, and read any value with GetAttribute.

What the PowerFactory Python API is

The API is a compiled module, powerfactory.pyd, installed with PowerFactory in one folder per Python version, for example C:\Program Files\DIgSILENT\PowerFactory 2024\Python\3.12. The Python that imports it must be that version. There are two ways to run a script:

  • Inside PowerFactory, as a Python command (ComPython) in the project: powerfactory.GetApplication() returns the program you are looking at, with its active project and study case.
  • From your own Python (a terminal, VS Code, Jupyter): add the folder to sys.path, import the module and call powerfactory.GetApplicationExt(). It starts PowerFactory without a window, the engine mode, which is what batch studies use. GetApplication() starts it too, but only returns None when something is wrong, where GetApplicationExt() raises powerfactory.ExitError with a code that says what.

Everything PowerFactory holds is an object with a class name: elements such as ElmLne (a line) and ElmTerm (a busbar), types such as TypLne, commands such as ComLdf (load flow), and study cases (IntCase). Each object reads and writes its values with GetAttribute and SetAttribute; the attribute reference lists every name.

The method

  1. Find the module folder for your PowerFactory, and run Python of the same version (3.12 for this folder).
  2. Connect: GetApplicationExt() outside PowerFactory, GetApplication() inside it.
  3. Activate the project with app.ActivateProject(name), then the study case you want. The study case decides the grids, the operation scenario and the variations every calculation uses.
  4. Find objects with GetCalcRelevantObjects("*.ElmTerm"), or by name with GetCalcRelevantObjects("Load A.ElmLod").
  5. Run a command: app.GetFromStudyCase("ComLdf").Execute(). It returns 0 when it worked.
  6. Read the results with GetAttribute, by the attribute's name and prefix (m:u for a busbar voltage).

A first script

getting_started.py

"""A first PowerFactory Python script: connect, open a project, run a load flow, read results.

Runs on the "Nine-bus System" example project that comes with PowerFactory 2024.
"""
import sys

# The powerfactory module ships with PowerFactory, one folder per Python version. Run this
# file with the Python whose version has a folder there (3.12 here).
sys.path.append(r"C:\Program Files\DIgSILENT\PowerFactory 2024\Python\3.12")
import powerfactory

PROJECT = "Nine-bus System"

# From your own Python: start PowerFactory in the background (engine mode).
# Inside PowerFactory (a Python command, ComPython): app = powerfactory.GetApplication()
app = powerfactory.GetApplicationExt()

if app.ActivateProject(PROJECT) != 0:                    # 0 means it worked
    raise SystemExit(f"There is no project called {PROJECT}")
case = app.GetActiveStudyCase()
print("Study case:", case.loc_name)

ldf = app.GetFromStudyCase("ComLdf")                     # the load flow command of the case
if ldf.Execute() != 0:                                   # 0 means it converged
    raise SystemExit("The load flow did not converge")

for bus in app.GetCalcRelevantObjects("*.ElmTerm"):      # every terminal of the active grids
    print(f"{bus.loc_name:6} {bus.GetAttribute('m:u'):.4f} p.u. {bus.GetAttribute('m:Ul'):8.2f} kV")

for line in app.GetCalcRelevantObjects("*.ElmLne"):
    print(f"{line.loc_name:9} loading {line.GetAttribute('c:loading'):5.1f} %")

load = app.GetCalcRelevantObjects("Load A.ElmLod")[0]    # one element, found by name
print("Load A:", load.plini, "MW")                       # input data also reads as a property

What it prints on the Nine-bus example

Study case: 01- Load Flow
Bus 1  1.0400 p.u.    17.16 kV
Bus 2  1.0250 p.u.    18.45 kV
Bus 3  1.0250 p.u.    14.14 kV
Bus 4  1.0258 p.u.   235.93 kV
Bus 5  0.9956 p.u.   229.00 kV
Bus 6  1.0127 p.u.   232.91 kV
Bus 7  1.0258 p.u.   235.93 kV
Bus 8  1.0159 p.u.   233.65 kV
Bus 9  1.0324 p.u.   237.44 kV
Line 4-5  loading  14.2 %
Line 4-6  loading   8.6 %
Line 5-7  loading  21.4 %
Line 6-9  loading  15.4 %
Line 7-8  loading  18.9 %
Line 8-9  loading   8.5 %
Load A: 125.0 MW

The calls you use most

CallWhat it does
powerfactory.GetApplication()Inside PowerFactory, the program the Python command runs in. From your own Python it starts PowerFactory in engine mode; None when it cannot.
powerfactory.GetApplicationExt()Starts PowerFactory in engine mode from your own Python, and raises powerfactory.ExitError with a code when it cannot. Once per Python process.
app.ActivateProject(name)Activates a project of the current user by its name; returns 0 when it worked.
app.GetActiveStudyCase()The active study case (IntCase); its Activate() switches to another.
app.GetProjectFolder("study")A folder of the active project: "study" for study cases, "equip" for the type library, "scen" for operation scenarios.
app.GetFromStudyCase("ComLdf")A command or object of the active study case by class: ComLdf, ComShc, ComInc, ComSim, ElmRes, IntEvt.
app.GetCalcRelevantObjects("*.ElmLne")Every object of a class in the active grids; the name can be a pattern, as in "Line 4*.ElmLne".
obj.GetContents("*.IntCase", 1)The objects stored inside obj that match; 1 also searches the folders below.
obj.GetAttribute("m:u")Reads an input parameter or a result. obj.loc_name and obj.plini read the same way.
obj.SetAttribute("plini", 140.0)Writes an input parameter. It changes the project.
cmd.Execute()Runs a command (load flow, short circuit, simulation); 0 means success.
obj.CreateObject("ElmGenstat", name)Creates an object inside obj; obj.Delete() removes one.
obj.GetClassName(), obj.GetParent()The class of an object and the object it is stored in.
app.ResetCalculation()Drops the results in memory, as the reset button does; needed before deleting what a calculation used.
app.SetAttributeModeInternal(1)Reads and writes values in internal units (seconds for times) until it is set back to 0.

Every call in this table was used in the scripts on these pages, run on PowerFactory 2024's example projects.

Open this in the Builder. The Load flow on every study case Quick Start loads in the PowerFactory Workflow Builder, ready for your project. Change any block and download the Python file.

Open in the Builder

The full Python script from the Builder

A script the Builder writes is the same API with the chores done: it finds the module folder for the Python that runs it, connects inside PowerFactory or in engine mode, stops with a plain message when the licence or the database is not free, and puts back every value it changed. This is its load flow on every study case, filled in for the Nine-bus example:

load_flow_on_every_study_case.py as the Builder writes it: the settings and the workflow⬇ Download the full file (.py, 1,628 lines)

#!/usr/bin/env python3
# Load flow on every study case
"""Made with the PowerFactory Workflow Builder, https://tool.adjiebrotots.com/powerfactory-scripter/builder/

BEFORE YOU RUN
  1. Check the SETTINGS below (lines marked REQUIRED).
  2. Close PowerFactory, or run this file from inside PowerFactory (see RUN_MODE).
  3. Run it with the Python that matches your PowerFactory, e.g.  py -3.12 load_flow_on_every_study_case.py
Results and run.log go to a new folder next to this file (or in OUTPUT_DIR).
"""
# ==== IMPORTS ====
import csv
import difflib
import fnmatch
import glob
import importlib.util
import math
import os
import re
import subprocess
import sys
import time
import traceback

# ==== SETTINGS (edit here) ====
# The name of this workflow: used for the run folder and in the log.
WORKFLOW_NAME = "Load flow on every study case"
# REQUIRED. The PowerFactory project to open, exactly as the Data Manager shows it (letter case
# counts).
PROJECT = "Nine-bus System"
# OPTIONAL. The study case to activate first ("" = keep the active one).
STUDY_CASE = ""
# OPTIONAL. Where each run writes its folder of results and run.log ("" = the folder of this
# script).
OUTPUT_DIR = ""
# OPTIONAL. True: put every value the workflow changed back when it ends.
UNDO_CHANGES = True
# OPTIONAL. How the results tables are saved in the run folder when the run ends: "csv", "xlsx"
# (Excel, needs pandas and openpyxl) or "both".
TABLE_FORMAT = "csv"
# OPTIONAL. Nominal frequency in Hz for the frequency checks: 50, 60 or "auto" (read from the
# grid).
F_NOMINAL = "auto"
# OPTIONAL. "auto", "engine" (start PowerFactory in the background) or "inside" (run from inside
# PowerFactory).
RUN_MODE = "auto"
# OPTIONAL. True: show the PowerFactory window while running (engine mode only).
SHOW_PF = False
# OPTIONAL. True: no screen updates and fewer output window messages while running.
FAST_MODE = True
# OPTIONAL. Seconds to wait for PowerFactory to start.
CONNECT_TIMEOUT = 120
# OPTIONAL. The folder with the powerfactory Python module ("" = find it).
PF_API_PATH = ""
# OPTIONAL. Log in to PowerFactory as this user ("" = your own).
PF_USER = ""

# OPTIONAL. Study case: Study cases named.
STUDY_CASE_PATTERN = "*"

# True: connect, find every element named in this workflow, say what was found, then stop
# without running anything.
CHECK_ONLY = False


# ==== WORKFLOW ====
# Note: This runs the load flow of every study case of the project (or of the cases named on For each study case, for example 0*) and reads the voltage of every busbar in service. Each case gets a row with...
def main():
    preflight([])
    app = pf_connect()
    try:
        if CHECK_ONLY:
            check_elements(app, [("*.ElmTerm", "all")])
            return
        # -- Step 1: Start (Project: Nine-bus System) --
        # -- Step 2: Study case (Every study case) --
        study_case_cases = case_list(app, STUDY_CASE_PATTERN)
        if not study_case_cases:
            log("Study case: no study case to loop over.", "warn")
        for study_case_pass_no, study_case in enumerate(each_case(app, study_case_cases), 1):
            ctx_push("Study case", study_case, study_case_pass_no, len(study_case_cases))
            try:
                # -- Step 3: Run load flow (Settings of the study case) --
                ldf = run_load_flow(app, "case", "case", "case")
                # Did not converge: skips to the next pass of Study case
                if not ldf.ok:
                    case_skipped("Load flow did not converge")
                    continue
                # -- Step 4: Busbar voltage (m:u of all * (ElmTerm) (load flow results)) --
                busbar_voltage = pf_get_all(pf_find(app, "*.ElmTerm", "all"), "m:u", None, False, True)
                # -- Step 5: Add row to results table (Into "Load flow", saved at the end automatically) --
                table("Load flow").add({
                    "Lowest voltage (p.u.)": busbar_voltage.min,
                    "Lowest at": busbar_voltage.at_min,
                    "Highest voltage (p.u.)": busbar_voltage.max,
                    "Highest at": busbar_voltage.at_max,
                }, status=True)
            except StopWorkflow:
                raise
            except Exception as err:
                case_failed(err)
            finally:
                ctx_pop(["Load flow"])
    except StopWorkflow as stop:
        log(f"Stopped: {stop}")
    finally:
        finish(app)

The file goes on with 1,519 lines of helper functions copied from the Builder's runtime (connecting, logging, tables, graphs, putting values back), only those this workflow needs. The download has them all.

How the script works

  • RUN_MODE = "auto" runs inside PowerFactory when the file is started there, and in engine mode otherwise. PF_API_PATH stays empty: the script looks for Python\<version> under Program Files\DIgSILENT itself.
  • pf_connect() calls GetApplicationExt() under a watchdog (CONNECT_TIMEOUT), turns ExitError codes into plain words, then activates PROJECT and STUDY_CASE.
  • CHECK_ONLY = True connects, finds every element the workflow names, says what it found and stops without running anything: a safe first run on a new project.
  • The workflow itself is main(), one commented step per block; the helpers below it come from the Builder's runtime and are copied in only when a block needs them.
  • Every run writes a new folder with its results tables and a run.log.

Pitfalls

  • The Python version must match the module folder. Importing powerfactory from the 3.12 folder into another Python fails with an import error. Use the matching interpreter, for example py -3.12 script.py.
  • One engine per Python process. Start it once and keep app: a second call in the same process hands back the session already running. In Jupyter, restart the kernel for a fresh one.
  • The database can be held by another PowerFactory. With a local database, a PowerFactory window that is open (or an engine that has not exited) makes GetApplicationExt() fail with ExitError 4002. Close it and wait about 45 s.
  • Licence errors are ExitError too: 3000 no licence found, 3018 no free seat, 3010 a licence without scripting.
  • Changes are saved. There is no "do not save" for an engine session: SetAttribute, CreateObject and Delete change the project. Put values back in a finally block, or work on a copy of the project.
  • A project name is exact and letter case counts. ActivateProject returns non-zero when no project of that name belongs to the current user.

Questions

What is the difference between GetApplication and GetApplicationExt?

Both return the application object, and from your own Python both start PowerFactory in engine mode. When PowerFactory cannot start, GetApplication() returns None, while GetApplicationExt() raises powerfactory.ExitError with an error code (4002 database in use, 3000 no licence). Inside PowerFactory, use GetApplication().

Where is the PowerFactory Python module?

In the PowerFactory installation, one folder per Python version, such as C:\Program Files\DIgSILENT\PowerFactory 2024\Python\3.12. Add that folder to sys.path before import powerfactory.

Which Python version does PowerFactory 2024 use?

PowerFactory 2024 installs the module for Python 3.8, 3.9, 3.10, 3.11 and 3.12, one folder each. Use any of them; the scripts on these pages were run with Python 3.12.

Can I run PowerFactory from Jupyter or VS Code?

Yes: in engine mode with GetApplicationExt(), once per Python process, with the matching Python version.

Related

Attributes used on this page: ElmTerm, ElmLne, ElmLod. All attributes.