Short-circuit automation in PowerFactory with Python

In Python, a short-circuit calculation is the study case's ComShc command: set the method (iopt_mde = 1 for IEC 60909), the fault type (iopt_shc = "3psc") and the location (iopt_allbus = 2 for every busbar), call Execute(), then read m:Ikss, m:ip and m:Skss on each busbar.

What a short-circuit calculation gives you

A short-circuit calculation gives the current that flows into a fault, and the currents and voltages around it. The maximum currents size equipment: the breaking capacity of breakers, the peak and thermal withstand of busbars and cables, and the fault level a new connection adds to. The minimum currents check that protection still sees a fault.

PowerFactory has several methods in one command, ComShc: IEC 60909 and its German twin VDE 0102 (an equivalent voltage source at the fault with a voltage factor c, no load flow needed), ANSI (IEEE C37), and the complete method, which superimposes the fault on a load flow. The results on a faulted busbar include the initial symmetrical current m:Ikss (Ik"), the peak current m:ip and the initial short-circuit power m:Skss (Sk" = √3 Un Ik").

The method

  1. Activate the study case whose grids and operating state you want faulted.
  2. Get the command with app.GetFromStudyCase("ComShc") and keep its settings to put back.
  3. Choose the method and the fault: iopt_mde 1 for IEC 60909; iopt_shc "3psc" three-phase, "2psc" two-phase, "spgf" single-phase to ground, "2pgf" two-phase to ground; iopt_cur 0 for maximum or 1 for minimum currents.
  4. Choose where: iopt_allbus = 2 faults every busbar in turn in one run, or iopt_allbus = 0 with shcobj set to one busbar or line.
  5. Run with Execute() (0 means it worked) and read m:Ikss, m:ip and m:Skss on each faulted ElmTerm.
  6. Save the rows to CSV, and put the command's settings back.

The script

short_circuit.py

"""Short circuit in PowerFactory with Python: an IEC 60909 three-phase fault at every busbar, with
the initial, peak and power values of each saved to CSV.

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

sys.path.append(r"C:\Program Files\DIgSILENT\PowerFactory 2024\Python\3.12")
import powerfactory

PROJECT = "Nine-bus System"
STUDY_CASE = "01- Load Flow"

app = powerfactory.GetApplicationExt()          # inside PowerFactory: powerfactory.GetApplication()
app.ActivateProject(PROJECT)
app.GetProjectFolder("study").GetContents(STUDY_CASE + ".IntCase")[0].Activate()

shc = app.GetFromStudyCase("ComShc")
saved = {a: shc.GetAttribute(a) for a in ("iopt_mde", "iopt_shc", "iopt_cur", "iopt_allbus")}
shc.SetAttribute("iopt_mde", 1)         # method: 0 VDE 0102, 1 IEC 60909, 2 ANSI, 3 complete
shc.SetAttribute("iopt_shc", "3psc")    # fault: 3psc three-phase, 2psc two-phase, spgf single-phase to ground
shc.SetAttribute("iopt_cur", 0)         # 0 maximum, 1 minimum short-circuit currents
shc.SetAttribute("iopt_allbus", 2)      # 2 a fault at every busbar, one at a time
try:
    if shc.Execute() != 0:
        raise SystemExit("The short-circuit calculation failed")
    rows = [{"Busbar": b.loc_name, "Nominal (kV)": round(b.uknom, 2),
             "Ikss (kA)": b.GetAttribute("m:Ikss"),      # initial symmetrical short-circuit current
             "ip (kA)": b.GetAttribute("m:ip"),          # peak short-circuit current
             "Skss (MVA)": b.GetAttribute("m:Skss")}     # initial short-circuit power
            for b in app.GetCalcRelevantObjects("*.ElmTerm")]
finally:
    for a, v in saved.items():                  # leave the command as it was
        shc.SetAttribute(a, v)

for r in sorted(rows, key=lambda r: -r["Ikss (kA)"]):
    print(f"{r['Busbar']:6} {r['Nominal (kV)']:6.1f} kV  Ikss {r['Ikss (kA)']:6.2f} kA  ip {r['ip (kA)']:6.2f} kA")

with open("short_circuit_results.csv", "w", newline="") as f:
    out = csv.DictWriter(f, fieldnames=list(rows[0]))
    out.writeheader()
    out.writerows(rows)

Result on the Nine-bus example

The highest fault level on the 230 kV network is at Bus 7: 3.08 kA, 1227 MVA. The generator terminals (13.8 to 18 kV) see around 50 kA, because the same power at a lower voltage is a larger current. As a check, Bus 5 gives Sk" = √3 Γ— 230 kV Γ— 2.256 kA = 898.7 MVA, the 898.7 MVA PowerFactory reports.

Initial three-phase short-circuit current at each 230 kV busbar of the Nine-bus example, IEC 60909 maximum01234Bus 4: 3.05 kABus 4Bus 5: 2.26 kABus 5Bus 6: 2.15 kABus 6Bus 7: 3.08 kABus 7Bus 8: 2.46 kABus 8Bus 9: 2.77 kABus 9Initial short-circuit current Ikss (kA)
IEC 60909, maximum currents, a three-phase fault at each busbar in turn. Read with m:Ikss.
BusbarNominal kVIkss kAip kASkss MVA
Bus 116.557.01147.981629
Bus 218.047.75124.861489
Bus 313.848.36125.581156
Bus 4230.03.057.881216
Bus 5230.02.265.61899
Bus 6230.02.155.18855
Bus 7230.03.088.021227
Bus 8230.02.466.26978
Bus 9230.02.777.101104

Open this in the Builder. The Short circuit at every busbar 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

The Builder's Quick Start does the same with a loop: a fault at one busbar per pass, the results into a table, and a bar chart of the fault currents at the end. It gives the same 2.256 kA at Bus 5 of the Nine-bus example. This is its file, filled in for that example:

short_circuit_at_every_busbar.py as the Builder writes it: the settings and the workflow⬇ Download the full file (.py, 2,604 lines)

#!/usr/bin/env python3
# Short circuit at every busbar
"""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 short_circuit_at_every_busbar.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
import warnings

# ==== SETTINGS (edit here) ====
# The name of this workflow: used for the run folder and in the log.
WORKFLOW_NAME = "Short circuit at every busbar"
# 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 = ""

# How every graph of this run looks: set in the Builder (select nothing, then Graph style).
# Colours are hex codes. A value that cannot be used falls back to the Adjie Brotools style.
GRAPH_STYLE = {
    "template": "brotools",                         # where this style started (a name only)
    "colors": ["#5A91E8", "#B45309", "#4A9E87", "#C46A7E", "#9B59B6"],  # in order; more lines repeat them, paler
    "ramp": "blue",                                 # blue, night, viridis, cividis, magma, coolwarm, greys, classic
    "background": "#FFFFFF",
    "plot_background": "#FFFFFF",
    "text": "#14203A",
    "muted": "#4C5B76",
    "grid": "#C8D4E8",
    "axis": "#C8D4E8",
    "font": "DM Sans",
    "font_size": 9.5,                               # points, for the axis numbers (the rest follows)
    "title_size": 13,                               # points
    "title_bold": True,                             # True or False
    "title_align": "center",                        # center, left
    "grid_axes": "both",                            # both, y, x, none
    "grid_style": "solid",                          # solid, dashed, dotted
    "frame": "left-bottom",                         # left-bottom, box, bottom, none
    "ticks": "out",                                 # out, in, none
    "thousands": True,                              # True: 1,200 rather than 1200
    "line_width": 2,                                # points
    "markers": "auto",                              # auto, always, never
    "line_styles": "solid",                         # solid, vary
    "legend": "bottom",                             # bottom, top, right, inside, none
    "legend_frame": False,                          # True or False
    "width": 10,                                    # inches
    "height": 5.6,                                  # inches
    "dpi": 150,                                     # the PNG is width x dpi pixels wide
    "transparent": False,                           # True: no background (for slides)
    "also_save": "",                                # "", svg, pdf
}

# REQUIRED. Busbar: Elements. A name (ElmTerm), exactly as in the Data Manager, or a pattern
# with * and ?: every element that matches is used.
BUSBAR_EL = "*"

# 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 an IEC 60909 maximum three-phase short circuit at each busbar in service in turn (or at the busbars named on For each element) and reads the initial short-circuit current and power there....
def main():
    preflight(["matplotlib"])
    app = pf_connect()
    try:
        if CHECK_ONLY:
            check_elements(app, [(BUSBAR_EL + ".ElmTerm", "all")])
            return
        # -- Step 1: Start (Project: Nine-bus System) --
        # -- Step 2: Busbar (Each of all * (ElmTerm)) --
        busbar_elements = as_items(pf_find(app, BUSBAR_EL + ".ElmTerm", "all", in_service=True))
        if not busbar_elements:
            log("Busbar: no element to loop over.", "warn")
        for busbar_pass_no, busbar in enumerate(busbar_elements, 1):
            ctx_push("Busbar", busbar, busbar_pass_no, len(busbar_elements))
            try:
                # -- Step 3: Run short circuit (IEC 60909, three-phase, max, at the element) --
                shc = run_short_circuit(app, "iec", "3ph", "max", busbar, 0, 0)
                # Failed: skips to the next pass of Busbar
                if not shc.ok:
                    case_skipped("Short circuit failed")
                    continue
                # -- Step 4: Ikss (m:Ikss of the element (short-circuit results)) --
                ikss = pf_get_all(busbar, "m:Ikss", "kA", False, True)
                # -- Step 5: Skss (m:Skss of the element (short-circuit results)) --
                skss = pf_get_all(busbar, "m:Skss", "MVA", False, True)
                # -- Step 6: Add row to results table (Into "Short circuit", saved at the end automatically) --
                table("Short circuit").add({
                    "Ikss (kA)": ikss.value,
                    "Skss (MVA)": skss.value,
                }, status=True)
            except StopWorkflow:
                raise
            except Exception as err:
                case_failed(err)
            finally:
                ctx_pop(["Short circuit"])
        # -- Step 7: Fault current at every busbar (Bars) --
        fault_currents = table_pick("Short circuit", "Ikss (kA)", "all", None, "num")  # Fault currents
        busbars = table_pick("Short circuit", "Busbar", "all", None, "text")  # Busbars
        plot_xy(busbars, [fault_currents], labels=["Ikss (kA)"], kind="bar", title="Fault current at every busbar", xlabel="Busbar", xunit="", ylabel="Initial short-circuit current", yunit="kA", limits=[], stem=None)
    except StopWorkflow as stop:
        log(f"Stopped: {stop}")
    finally:
        finish(app)

The file goes on with 2,455 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

  • Step 2 finds every busbar in service that matches BUSBAR_EL (* for all) and loops over them.
  • Step 3, run_short_circuit, sets ComShc for this run only (IEC 60909, three-phase, maximum, the fault at this pass's busbar through shcobj) and puts the settings back.
  • Steps 4 and 5 read m:Ikss in kA and m:Skss in MVA at the faulted busbar; step 6 adds the row.
  • After the loop, two "Table column" blocks pull the busbar names and the currents out of the table, and "Draw 2D graph" saves the bar chart as a PNG in the Adjie Brotools style.

Pitfalls

  • Set the method every time. The Nine-bus example's short-circuit command is set to VDE 0102 (iopt_mde 0). A script that does not set iopt_mde gets whatever the study case holds.
  • iopt_shc is text, not a number: "3psc", "2psc", "spgf", "2pgf".
  • With one fault location, another busbar's m:Ikss is not its fault level. Only the faulted busbar carries its own fault current. Use iopt_allbus = 2, or loop and fault each busbar, to get a fault level everywhere.
  • Maximum and minimum currents answer different questions. Maximum (iopt_cur 0) rates equipment; minimum (1) checks protection reaches. Run both when you need both.
  • ANSI gives other result names than IEC 60909, so a script written for one method may raise AttributeError with the other. Check the busbar's short-circuit results in the ElmTerm reference.
  • Put the command's settings back (the script does it in finally), or the next person to open the study case gets your method and fault type.

Questions

How do I run a short circuit in PowerFactory with Python?

Get the command with app.GetFromStudyCase("ComShc"), set iopt_mde, iopt_shc, iopt_cur and the location (iopt_allbus or shcobj), then call Execute(). It returns 0 when it worked.

How do I get the fault level at every busbar?

Set iopt_allbus to 2 before Execute(), then read m:Ikss (kA) and m:Skss (MVA) on every ElmTerm.

What does iopt_mde mean?

The calculation method of ComShc: 0 VDE 0102, 1 IEC 60909, 2 ANSI, 3 the complete method, 4 IEC 61363.

What are Ikss, ip and Skss?

Ikss (Ik") is the initial symmetrical short-circuit current, ip the peak short-circuit current, and Skss (Sk") the initial short-circuit power, √3 Un Ik".

Related

Attributes used on this page: ElmTerm, ElmLne, ElmSym, ElmXnet, EvtShc. All attributes.