Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GridOPTICS/GridPACK/llms.txt

Use this file to discover all available pages before exploring further.

GridPACK exposes a subset of its C++ framework through a pybind11-based Python extension module. The primary capability currently accessible from Python is the HADREC application — a combined power flow and dynamic simulation driver — together with the full dynamic simulation (DSFullApp) and state estimation (SEApp) modules. This page covers installation, environment setup, and practical usage examples drawn directly from the scripts in python/src/.

Requirements

Before installing the Python wrapper, verify that these prerequisites are satisfied:
  • GridPACK ≥ 3.4, built and installed as shared libraries
  • All GridPACK dependencies (PETSc, Global Arrays, Boost) also built as shared libraries
  • Python ≥ 3.x (Python 2.7 is no longer recommended)
  • pybind11 ≥ 2.4 (source included as a git submodule)
  • Python packages: setuptools, mpi4py, numpy
GridPACK must be installed — not just built in-tree — before the Python wrapper can be built. Pass -DCMAKE_INSTALL_PREFIX=/your/install/path to CMake and run cmake --install build/ after a successful build.

Initialise the pybind11 submodule

The pybind11 source is bundled as a git submodule inside python/. Fetch it before building:
git submodule update --init

Installation

1

Set GRIDPACK_DIR

Point the build system to your GridPACK installation:
export GRIDPACK_DIR=/usr/local/gridpack
2

Install with pip

From the python/ directory at the top of the GridPACK source tree, run:
cd .../GridPACK/python
pip install --no-deps --upgrade --prefix=$GRIDPACK_DIR .
This installs the gridpack Python module into $GRIDPACK_DIR, alongside the rest of the GridPACK installation.
3

Set PYTHONPATH

Add the site-packages directory to PYTHONPATH. The exact path depends on your Python version:
export PYTHONPATH=$GRIDPACK_DIR/lib/python3.12/site-packages
Adjust python3.12 to match the Python version used during installation.
4

Verify the installation

python -c 'import gridpack'
If no error is printed, the module is installed correctly.

RHEL / CentOS systems

On Red Hat Enterprise Linux and CentOS, the stock OpenMPI packages can cause import failures. Set the RHEL_OPENMPI_HACK environment variable to work around this:
export RHEL_OPENMPI_HACK=yes
This is not needed with MPICH or OpenMPI on other Linux distributions.

Basic usage

import gridpack

# Every script must create an Environment before any other GridPACK objects.
# It initialises MPI, Global Arrays, and the math libraries.
env = gridpack.Environment()

comm = gridpack.Communicator()
print("Rank:", comm.rank(), "of", comm.size())

# Clean up in reverse order to avoid MPI teardown issues
del comm
del env
Always delete GridPACK objects in reverse creation order before the script exits. pybind11 calls C++ destructors at Python garbage-collection time, and MPI must still be active when those destructors run.

HADREC application

HADREC (Hybrid Application for Dynamic Reliability Evaluation and Control) is the main capability exposed through the Python interface. It combines power flow initialisation with full dynamic simulation and supports real-time control actions.

Minimal HADREC script

import gridpack
import gridpack.hadrec

env  = gridpack.Environment()
comm = gridpack.Communicator()

hadapp = gridpack.hadrec.Module()

# Solve power flow using the first RAW file listed in the XML input
hadapp.solvePowerFlowBeforeDynSimu("input_39bus_step005_v33.xml", 0)
hadapp.transferPFtoDS()

# Define a bus fault event
fault          = gridpack.dynamic_simulation.Event()
fault.start    = 1.0    # fault starts at t = 1.0 s
fault.end      = 1.1    # fault clears at t = 1.1 s
fault.step     = 0.005  # internal time step during fault
fault.isBus    = True
fault.bus_idx  = 22     # bus number

faultlist = gridpack.dynamic_simulation.EventVector([fault])

# Initialise the dynamic simulation with the fault list
hadapp.initializeDynSimu(faultlist, 0)

# Step through the simulation
while not hadapp.isDynSimuDone():
    hadapp.executeDynSimuOneStep()

del hadapp
del comm
del env

Getting observations

After each time step you can retrieve network observations for monitoring or machine-learning applications:
# Get the list of observed quantities defined in the XML input
(obs_genBus, obs_genIDs,
 obs_loadBuses, obs_loadIDs,
 obs_busIDs) = hadapp.getObservationLists()

while not hadapp.isDynSimuDone():
    hadapp.executeDynSimuOneStep()
    ob_vals = hadapp.getObservations()
    # ob_vals is a flat list: generator speeds, angles, P, Q, bus voltages, angles

Applying control actions

HADREC supports three action types that can be injected at any simulation step:
# Line tripping (actiontype = 1)
line_trip            = gridpack.hadrec.Action()
line_trip.actiontype = 1
line_trip.brch_from_bus_number = 4
line_trip.brch_to_bus_number   = 5
line_trip.componentID          = "1"

# Generator tripping (actiontype = 2)
gen_trip            = gridpack.hadrec.Action()
gen_trip.actiontype = 2
gen_trip.bus_number  = 35
gen_trip.componentID = "1"

# Load shedding (actiontype = 3)
load_shed             = gridpack.hadrec.Action()
load_shed.actiontype  = 3
load_shed.bus_number  = 504
load_shed.componentID = "1"
load_shed.percentage  = -300  # shed 300 MW

# Apply during the simulation loop
while not hadapp.isDynSimuDone():
    if current_step == 3000:
        hadapp.applyAction(line_trip)
    hadapp.executeDynSimuOneStep()

Full dynamic simulation (DSFullApp)

DSFullApp is an alternative to the HADREC module that provides finer-grained control, including the ability to query bus and generator data by field name after each step.
import gridpack
from gridpack.dynamic_simulation import DSFullApp, Event, EventVector

env  = gridpack.Environment()
comm = gridpack.Communicator()

ds_app = DSFullApp()
ds_app.solvePowerFlowBeforeDynSimu("input_39bus_step005_v33.xml", 0)
ds_app.readGenerators(0)
ds_app.readSequenceData()
ds_app.initialize()
ds_app.setGeneratorWatch()

fault         = Event()
fault.start   = 1.0
fault.end     = 1.1
fault.step    = 0.005
fault.isBus   = True
fault.bus_idx = 22

ds_app.solvePreInitialize(EventVector([fault])[0])

dt = ds_app.getTimeStep()
step = 0

while not ds_app.isDynSimuDone():
    ds_app.executeOneSimuStep()
    step += 1

ds_app = None
env    = None

Inspecting network state

DSFullApp exposes typed accessors to read bus and generator data by keyword:
for bus in range(ds_app.totalBuses()):
    bus_num  = ds_app.getBusInfoInt(bus, "BUS_NUMBER")
    bus_name = ds_app.getBusInfoString(bus, "BUS_NAME")
    vmag     = ds_app.getBusInfoReal(bus, "BUS_VMAG_CURRENT")

    for g in range(ds_app.numGenerators(bus)):
        model = ds_app.getBusInfoString(bus, "GENERATOR_MODEL", g)
        pg    = ds_app.getBusInfoReal(bus, "GENERATOR_PG_CURRENT", g)

State estimation (SEApp)

The gridpack.state_estimation submodule wraps the state estimation application module. Measurements are passed in as a MeasurementVector:
import gridpack
import gridpack.state_estimation as gse

env  = gridpack.Environment()
conf = gridpack.Configuration()
conf.open("se_input.xml", gridpack.Communicator())

app = gse.SEApp()
app.readNetwork(conf)
app.initialize()
app.readMeasurements()

# Retrieve measurements from the configuration, modify if needed,
# then push them back in
measurements = app.getMeasurements(conf)
app.setMeasurements(measurements)

app.solve()
if app.hasConverged():
    app.write()
    app.saveData()
Measurement objects carry bus/branch identifiers, a value, a standard deviation, and a type string ("VA", "VM", "PIJ", "QIJ", etc.):
m            = gse.Measurement()
m.busid      = 5
m.value      = 1.02
m.deviation  = 0.001
m.type("VM")          # voltage magnitude at bus 5

Running example scripts

The python/src/example/ directory contains ready-to-run scripts for the 39-bus IEEE test case. After installing the module and setting PYTHONPATH:
cd python/src/example

# Serial runs
python 39bus_test_example.py
python 39bus_test_example_dsf.py
python 39bus_scatterload_steptest_new_itr.py
python 39bus_scatterload_steptest_new_itr_dsf.py
python 39bus_scatterload_steptest_new_itr_compensateY.py
python 39bus_test_pfdata.py

# Parallel runs with MPI
mpiexec -np 2 python 39bus_scatterload_steptest_new_itr.py
mpiexec -np 2 python 39bus_scatterload_steptest_new_itr_dsf.py
The 39bus_scatterload_steptest_new_itr.py script demonstrates how to change bus loads dynamically during a simulation — a common pattern in reinforcement-learning and control studies.

The dsf2.py script

After installation, a dsf2.py script is placed in $GRIDPACK_DIR/bin/. It is a Python drop-in replacement for the compiled dsf2.x binary and can run any dynamic simulation XML input file:
cd src/build/applications/dynamic_simulation_full_y

# Single-process runs
$GRIDPACK_DIR/bin/dsf2.py input_9b3g.xml
$GRIDPACK_DIR/bin/dsf2.py input_240bus.xml

# Parallel run
mpiexec -np 4 $GRIDPACK_DIR/bin/dsf2.py input_240bus.xml
The script itself is short and illustrates the canonical usage pattern:
#!/usr/bin/env python
import sys, os
import gridpack
from gridpack.dynamic_simulation import DSFullApp

env  = gridpack.Environment()
comm = gridpack.Communicator()

ds_app = DSFullApp()
ds_app.solvePowerFlowBeforeDynSimu(sys.argv[1], -1)
ds_app.readGenerators()
ds_app.readSequenceData()
ds_app.initialize()
ds_app.setGeneratorWatch()
ds_app.setup()
ds_app.run()

del ds_app
del comm
del env

Running in parallel with MPI

The gridpack Python module is MPI-aware. Pass an mpiexec command with the desired process count and Python will pick up the MPI environment automatically:
mpiexec -np 4 python my_gridpack_script.py
gridpack.Communicator() returns the world communicator by default. Sub-communicators can be created for task-parallel simulations in exactly the same way as in C++.
mpi4py is required as a build dependency (pyproject.toml) but the gridpack module handles MPI initialisation internally through the Environment object. Do not call MPI_Init manually in scripts that use gridpack.Environment().

Build docs developers (and LLMs) love