Update -6/25/2026 - PSS/E UDMs for injecting forced oscillations available now in the UDM folder. Can be used outside the RATLLE framework.
A Python-based script suite for evaluating bulk power system reliability risk from oscillations introduced by large dynamic digital loads (LDDLs), using PSS/E as the simulation engine. Tested with PSS/E v35 and WECC planning cases in v34. Revised - 5/6/2026.
The risk assessment script suite is divided into three modules, designed for pre-screening vulnerable locations, running time-series simulations, and analyzing simulation outputs (Figure 1). The scripts are written in a simplified modular function-style in Python, so that they can be easily understood and modified to suit a utility’s needs.
**To cite this work:**
Biswas, Shuchismita, Antos Varghese, Kaustav Chatterjee, Brett Ross, and Jim Follum. "pnnl/LL-risk-assessment (33525-E)." US Department of Energy (DOE) Software (2026): 68.
S. Biswas, A. C. Varghese, K. Chatterjee, S. Nekkalapu, B. Ross and J. Follum, "Evaluating the Risk to Bulk Power System Reliability from Large Load Induced Oscillations," 2026 IEEE/PES Transmission and Distribution Conference and Exposition (T&D), Chicago, IL, USA, 2026, pp. 1-5, doi: 10.1109/TD48022.2026.11562229.
Figure 1. Script architecture
Module 1: Pre-screening
Planners might consider the risk assessment question from two perspectives:
- How can one quickly identify the most vulnerable locations in their system?
- How would they go about conducting more detailed risk analysis for an individual location?
Step 1: Identify locations with high voltage and angle sensitivity.
- Add fictitious load to user-selected buses (specified voltages in a specified area) without switched shunts and note change in voltage due to change in MW/MVar injection.
- Add fictitious load to user-selected generator buses (>specified generation threshold in a specified area) and note change in bus angle due to change in MW injection.
Step 2: Identify excitable frequencies from a selected location
- Simulate a load impulse at user selected location.
- Analyze ringdown response using an automated FFT+VARPRO pipeline to identify risky mode frequencies excitable from the selected location.
Module 2: Simulation
- Modify existing HV load to represent a data-center load with an oscillation injection block.
- Run PSS/E simulations to inject oscillation from user-selected location and specifiable (shape, frequency, amplitude) combinations.
- Two shapes can be selected - square wave, biperiodic square wave.
Module 3: Analysis
- Analyze simulation outputs to compute risk metrics and identify most-impacted elements.
- Risk is visualized through interactive HTML dashboards as well as summary spreadsheets.
- Thresholds can be set by an utility according to their risk budget.
- Users can also visualize impedance trajectories and RAS activation likelihood for selected elements without having to explicitly configure them in simulation models.
---
Root/
│
├── psse_config.py ← Edit once: set your PSS/E install path and version
│
├── Pre_Screening_config.csv ← Configuration for Steps 1, 2a, 2b, 2c
├── modal_analysis_config.csv ← Configuration for Steps 2b and 2c
├── simulation_config.csv ← Configuration for Steps 3a through 8
│
├── PSSE_Cases/ ← Place your .sav, .dyr, and .raw, .idv files here
│
├── Processing/ ← Intermediate outputs
│
├── results/ ← Simulation outputs and plots
│
├── Step1_extract_case_info.py
├── Step2a_locational_sensitivity.py
├── Step2b_load_impulse.py
├── Step2c_mode_estimates.py
├── Step3a_simsetup_loadadd.py
├── Step3b_simsetup_monitoredqty.py
├── Step4_runsim.py
├── Step5_analyze_sim.py
├── Step6_metrics_visualization.py
├── Step7a_distance_z3_reach.py
└── Step7b_RAS_check.py
---
Open psse_config.py and set your PSS/E installation details:
PSSE_INSTALL_DIR = r"C:\\Program Files\\PTI\\PSSE35\\PSSBIN"
PSSE_VERSION = 35This file is imported by every script that calls PSS/E — you only need to edit it once.
---
All user inputs are driven by three CSV configuration files. Edit these before running any step.
Used by Steps 1, 2a, 2b, and 2c. One row per case.
| Column | Description | Example |
|---|---|---|
case_name |
PSS/E .sav file name (without extension) | WECC_2031_HW |
dyr_name |
Dynamics .dyr file name (without extension) | WECC_2031_HW_dyn |
voltage_sensitivity_minKV |
Minimum bus voltage (kV) for sensitivity screening | 69 |
voltage_sensitivity_maxKV |
Maximum bus voltage (kV) for sensitivity screening | 138 |
area |
PSS/E area number to screen (leave blank for all areas) | 3 |
angle_sensitivity_minMW |
Minimum MW injection for angle sensitivity calculation | 25 |
Used by Steps 2b and 2c. One row per bus to analyse.
| Column | Description | Example |
|---|---|---|
case_name |
PSS/E .sav file name (without extension) | WECC_2031_HW |
dyr_name |
Dynamics .dyr file name (without extension) | WECC_2031_HW_dyn |
bus_number |
Bus where the load impulse is injected | 5003 |
load_step_MW |
Impulse magnitude in MW | 50 |
Used by Steps 3a through 8. One row per simulation scenario.
| Column | Description | Example |
|---|---|---|
case_name |
PSS/E .sav file name (without extension) | WECC_2031_HW |
dyr_name |
Dynamics .dyr file name (without extension) | WECC_2031_HW_dyn |
bus_number |
Bus where the LDDL oscillation is injected | 5003 |
load_id |
Load ID at that bus | 1 |
oscillation_shape |
Waveform type: square, biperiodic |
square |
oscillation_frequency |
Oscillation frequency in Hz | 0.4 |
oscillation_amplitude |
Peak oscillation amplitude in MW | 100 |
| 'oscillation_frequency_fast' | Faster frequency (Hz) for biperiodic load variation | 4 |
---
The steps are designed to run in sequence. You can stop after any step if only partial results are needed — for example, stopping after Step 2c gives you mode estimates without running full simulations.
python Step1_extract_case_info.pyReads the PSS/E case and writes bus, branch, generator, load, and area summary CSVs to Processing/. Run this first for any new case.
---
python Step2a_locational_sensitivity.pyApplies small fictitious injections at each bus in the specified voltage range and computes dV/dP, dV/dQ, and dθ/dP. Use this to identify vulnerable locations in the network.
---
python Step2b_load_impulse.pyApplies a short load impulse at the bus specified in modal_analysis_config.csv and records the ringdown response.
---
python Step2c_mode_estimates.pyAnalyses the ringdown signal from Step 2b to identify excitable oscillatory modes. If a prominent mode is found near a particular frequency, that frequency is a priority candidate for detailed simulation in Steps 3–8.
---
python Step3a_simsetup_loadadd.pyModifies the PSS/E case to represent the LDDL. Moves the existing load to an MV bus behind a step-down transformer, replaces its dynamic model with a CMLD model (NERC LMWG data center parameters), and adds a separate oscillation injection block. Outputs LLmod.sav, LLmod.snp, and a modified .dyr file.
---
python Step3b_simsetup_monitoredqty.pyUses the case summary from Step 1 to compile the list of buses, generators, loads, and lines to be logged during simulation. Outputs four CSVs to Processing/. Adjust selection criteria in the script if the default channel count is too large for your system.
---
python Step4_runsim.pyRuns the PSS/E dynamic simulation with the oscillation waveform defined in simulation_config.csv. Outputs results/<bus>_sim.out and results/<bus>_sim.csv. Simulation outputs are tagged with a run identifier of the form bus<N>_<freq>Hz_<amp>MW (e.g. bus5003_0.4Hz_100MW) so multiple scenarios can coexist in the results/ folder.
---
python Step5_analyze_sim.pyReads the simulation CSV and computes swing amplitude, envelope, and thermal loading metrics for generators, lines, buses, and loads. Flags elements that exceed configurable risk thresholds and writes summary and detail violation reports.
To adjust the risk thresholds, edit the RISK_THRESHOLDS dictionary near the top of the script.
---
python Step6_metrics_visualization.pyGenerates a self-contained HTML dashboard (results/risk_visualization_<run\_tag>.html) from the metrics produced by Step 5. The dashboard includes summary risk statistics and time-series plots for the worst elements in each category. Users can interactively change violation thresholds.
Example results

---
python Step7a_distance_z3_reach # interactive
python Step7a_distance_z3_reach --line 5001-5003-1 # directFor a selected line, computes the Zone 3 mho relay reach (if not already present in PSSE model) and plots the apparent impedance trajectory from the simulation against the Zone 3 boundary. If RATE_B (or any other branch parameter) is missing from the case data, the script prompts you to enter it manually.
Reach setting philosophy is simple - zone 3 reach is set in a way that the relay doesn't trip for 150% of rate B flow if voltage falls below 0.85 p.u.
python Step7b_RAS_check.py # interactive
python Step8_RAS_check.py --bus 5003 --signal volt --threshold 1.05 --duration 0.5 --direction above
python Step8_RAS_check.py --line 5001-5003-1 --signal P --threshold 300 --duration 0.3 --direction aboveChecks whether a user-defined Remedial Action Scheme trigger condition was sustained during the simulation. Only rudimentary rules can be implemented at present. You specify an element, a signal, a threshold, a direction (above/below), and a minimum duration. The script identifies all windows where the condition is met and produces an annotated time-series plot and a per-timestep CSV with a condition flag.
Supported signals:
| Element type | Available signals |
|---|---|
| Bus | Voltage magnitude (pu) |
| Line | Active power P (MW), Reactive power Q (MVar), Angle difference Δθ from–to (degrees) |
---
The scripts have been tested with the following publicly available PSS/E cases:
| Case | Source | Default scenario |
|---|---|---|
| WECC 240-bus | NREL Test Case Repository | 1.2 Hz oscillation from bus 6508 |
---
Please report bugs, unexpected behaviour, or suggestions to shuchismita.biswas@pnnl.gov.
Simulation script fails if no load present at bus selected to be LDDL oscillation source in the base case.
Number of oscillation cycles to run hard coded as 8 in Step 4.
LDDL MV bus number must be specified if the HV bus number has seven digits.
Visualization improvement opportunities in the HTML dashboard.
Number of step-down transformers hard coded in Step 3a. Can be edited by users.