Dynamics Simulation
UQGrid models power-system dynamics as differential-algebraic equations (DAEs). This guide covers the core APIs for preparing a system, configuring disturbances, executing time integration, and interpreting the results.
Load a system
from uqgrid.io.parse import load_psse, add_dyr
psys = load_psse(raw_filename="data/IEEE39_v33.raw")
add_dyr(psys, "data/IEEE39.dyr")
load_psse
parses the steady-state network description while add_dyr
attaches
dynamic device models (generators, exciters, governors, loads, and monitors).
Configure disturbances
Add shunt faults or other events before integration. Fault timing is controlled externally via the integration settings.
psys.add_busfault(bus=1, rfault=0.01)
The first fault registered is toggled automatically using IntegrationConfig.ton
(fault-on time) and IntegrationConfig.toff
(fault clearing time). Multiple
faults can be registered when more elaborate sequencing is needed.
Initialize with a power flow
from uqgrid.simulation.pflow import runpf
psys.createYbusComplex()
v_init, s_inj = runpf(psys, verbose=False)
The solved voltages seed the dynamic state vector and ensure algebraic constraints are satisfied at the start of the simulation.
Build an integration configuration
IntegrationConfig
encapsulates solver and timing options. Validation is handled
by Pydantic.
from uqgrid.simulation.config import IntegrationConfig
config = IntegrationConfig(
tend=10.0,
dt=1.0 / 120.0,
ton=0.25,
toff=0.40,
steps=-1,
verbose=False,
comp_sens=False,
fsolve=False,
petsc=True,
)
Key fields:
- tend: Final simulation time (seconds).
- dt: Integration step size (seconds).
- steps: Optional fixed number of steps (overrides
tend
when positive). - ton/toff: Fault activation and removal times.
- comp_sens: Enable adjoint-based sensitivities (requires PETSc).
- petsc: Switch to PETSc-backed integrators for improved robustness and adjoint capabilities.
Run the integrator
from uqgrid.simulation.dynamics import integrate_system
results = integrate_system(psys, config)
The solver returns a dictionary with time stamps (tvec
), state trajectory
(history
), and optional adjoint outputs when sensitivities are enabled.
When PETSc is unavailable, UQGrid falls back to the pure-Python integrator with the same interface.
Interpret the trajectory
Generator speed deviations, bus voltages, and other state variables can be
plotted directly from the result arrays. Fault transitions occur immediately
after the step in which the simulation time first exceeds ton
or toff
; a
zero-step Newton solve re-establishes the algebraic manifold at each transition.
import matplotlib.pyplot as plt
speed_idx = psys.genspeed_idx_set()
plt.plot(results["tvec"], results["history"][speed_idx, :].T)
plt.xlabel("Time [s]")
plt.ylabel("Speed deviation [p.u.]")
plt.title("Generator speed response after Bus 1 fault")
plt.show()
Sensitivities and post-processing
Setting comp_sens=True
and petsc=True
activates the adjoint solve and adds:
adjoint_cost
: Scalar performance index over the simulated interval.adjoint_gradient_trajectory
: Contribution from the trajectory (μᵢ
).adjoint_gradient_initial
: Contribution from the initial condition (λᵢ ∂y₀/∂p
).adjoint_gradient_complete
: Sum of trajectory and initial-condition terms.
Example: rank loads by their influence on the monitored quantity.
import numpy as np
grad = results["adjoint_gradient_complete"]
per_load = grad.reshape(psys.nloads, 2)
magnitudes = np.linalg.norm(per_load, axis=1)
for load, value in zip(psys.loads, magnitudes):
print(f"{load.name:>12}: {value: .3e}")
Batch studies
Use bin/generate_scenarios.py
for Monte Carlo sweeps that sample load
perturbations, execute dynamics simulations in parallel, and store summary
statistics. Customize the script or import its helpers when building larger
workflows.
Data management
Large sweeps can create multi-gigabyte traces. Use the script's output
options (e.g., --outdir
) to segment runs by study.