2.2.4. Examples

All examples below assume the working directory contains the relevant data and disturbance files. The Nordic test system (available in data/test_systems/Nordic/) is used throughout.

2.2.4.1. Running a basic simulation

Define the test case, run the simulation, and extract results:

import pyramses

case = pyramses.cfg()
case.addData('dyn_A.dat')        # dynamic models
case.addData('volt_rat_A.dat')   # power-flow initialisation
case.addData('settings1.dat')    # solver settings
case.addDst('short_trip_branch.dst')
case.addInit('init.trace')
case.addTrj('output.trj')        # save trajectories for post-processing
case.addObs('obs.dat')           # define which observables to record
case.addCont('cont.trace')
case.addDisc('disc.trace')
case.addRunObs('BV 4044')        # live voltage display (requires Gnuplot)
case.addRunObs('BV 1041')
case.writeCmdFile('cmd.txt')     # save for future reuse

ram = pyramses.sim()
ram.execSim(case)                # run to completion

ext = pyramses.extractor(case.getTrj())
ext.getBus('1041').mag.plot()    # voltage magnitude at bus 1041

Note

Set $NB_THREADS 0 ; in the solver settings file to use all available CPU cores for parallel simulation.

2.2.4.2. Pause and continue

Pause the simulation at intermediate time points to inspect state or add disturbances:

ram = pyramses.sim()
ram.execSim(case, 0.0)                        # initialise, paused at t=0
ram.contSim(10.0)                             # simulate to t=10 s
ram.contSim(ram.getSimTime() + 60.0)          # advance 60 s from current time
ram.contSim(ram.getInfTime())                 # run to end of time horizon

2.2.4.3. Get measurements from the system

Query bus voltages, branch flows, observables, and parameters while paused:

ram.execSim(case, 10.0)

# Bus voltages
busNames = ['g1', 'g2', 'g3']
voltages = ram.getBusVolt(busNames)   # list of voltage magnitudes (pu)
phases   = ram.getBusPha(busNames)    # list of phase angles (rad)

# Branch power flows
branch_pq = ram.getBranchPow(['1041-01'])  # [[P_from, Q_from, P_to, Q_to]]

# Component observables: P of injector L_11, vf of exciter g2, Pm of governor g3
comp_type = ['INJ',  'EXC', 'TOR']
comp_name = ['L_11', 'g2',  'g3']
obs_name  = ['P',    'vf',  'Pm']
obs = ram.getObs(comp_type, comp_name, obs_name)

# Component parameters: V0 of exciter g1, KPSS of exciter g2
comp_type = ['EXC', 'EXC']
comp_name = ['g1',  'g2']
prm_name  = ['V0',  'KPSS']
prms = ram.getPrm(comp_type, comp_name, prm_name)

# All component names of a type
all_buses = ram.getAllCompNames('BUS')
all_gens  = ram.getAllCompNames('SYNC')

2.2.4.4. Modify parameters and add disturbances dynamically

Inject disturbances and parameter changes while the simulation is running:

ram.execSim(case, 80.0)

# LTC voltage setpoint changes at t=100 s (ramp)
for i in range(1, 6):
    ram.addDisturb(100.0, f'CHGPRM DCTL {i}-104{i}  Vsetpt -0.05 0')

# Fault and clearance
ram.addDisturb(100.0, 'FAULT BUS 4032 0. 0.')         # 3-phase short circuit
ram.addDisturb(100.1, 'CLEAR BUS 4032')                # clear fault
ram.addDisturb(100.1, 'BREAKER BRANCH 4032-4044 0 0')  # trip line

# Generator trip
ram.addDisturb(100.0, 'BREAKER SYNC_MACH g7 0')

ram.contSim(ram.getInfTime())

For full disturbance syntax, see Disturbance description.

2.2.4.5. Plotting extracted results

Extract and plot time-series results after the simulation:

import pyramses
ext = pyramses.extractor('output.trj')

# Single curve
ext.getSync('g5').S.plot()       # rotor speed of generator g5
ext.getBus('4044').mag.plot()    # voltage magnitude at bus 4044

# Multiple curves on the same plot
curves = [ext.getSync(f'g{i}').S for i in range(1, 5)]
pyramses.curplot(curves)

# Exciter and governor outputs
ext.getExc('g1').vf.plot()       # field voltage
ext.getTor('g1').Pm.plot()       # mechanical power

# Branch power flows
ext.getBranch('1041-01').PF.plot()

# Injector (wind/PV/BESS)
ext.getInj('WT1a').Pw.plot()

# Two-port (HVDC)
ext.getTwop('hvdc1').P1.plot()

2.2.4.6. Parameter sweep

Run multiple simulations with varying parameters and collect results:

import pyramses
import numpy as np

results = {}
for disturbance_time in [5.0, 10.0, 20.0]:
    case = pyramses.cfg('cmd.txt')
    trj_file = f'output_{disturbance_time:.0f}.trj'
    case.addTrj(trj_file)
    case.addObs('obs.dat')

    ram = pyramses.sim()
    ram.execSim(case, 0.0)
    ram.addDisturb(disturbance_time, 'BREAKER SYNC_MACH g7 0')
    ram.contSim(ram.getInfTime())
    ram.endSim()

    ext = pyramses.extractor(trj_file)
    min_freq = np.min(ext.getSync('g5').S.value)
    results[disturbance_time] = min_freq
    print(f't_dist={disturbance_time:5.1f}s  min_speed={min_freq:.5f} pu')

2.2.4.7. Eigenanalysis workflow

Export the system Jacobian for small-signal stability analysis:

import pyramses

case = pyramses.cfg('cmd.txt')
ram = pyramses.sim()

# Pause at steady-state operating point
ram.execSim(case, 0.0)

# Export Jacobian matrices (writes jac_val.dat, jac_eqs.dat, jac_var.dat, jac_struc.dat)
ram.getJac()
ram.endSim()

The generated files can then be analysed in MATLAB using the RAMSES Eigenanalysis tool:

% In MATLAB:
ssa('jac_val.dat', 'jac_eqs.dat', 'jac_var.dat', 'jac_struc.dat')

Note

Set $OMEGA_REF SYN ; in the solver settings file when exporting Jacobians for eigenanalysis.

2.2.4.8. Nordic test system: generator trip

The following example is based on the Nordic JHub starter notebook. It simulates a generator trip at t = 10 s and plots the rotor speed of the tripped machine.

import pyramses
import os

# --- Configure the test case ---
case = pyramses.cfg()
case.addData('dyn_B.dat')         # dynamic model data
case.addData('volt_rat_B.dat')    # power-flow initialisation
case.addData('settings1.dat')     # solver settings
case.addDst('nothing.dst')        # no pre-defined disturbances
case.addObs('obs.dat')            # observables to record
case.addTrj('output.trj')         # trajectory output file

# --- Remove stale output files from previous runs ---
for f in os.listdir('.'):
    if f.endswith('.trj') or f.endswith('.trace'):
        os.remove(f)

# --- Run simulation ---
ram = pyramses.sim()
ram.execSim(case, 0.0)                        # initialise, paused at t = 0
ram.addDisturb(10.0, 'BREAKER SYNC_MACH g7 0')  # trip generator g7 at t = 10 s
ram.contSim(150.0)                            # simulate to t = 150 s
ram.endSim()

# --- Extract and plot results ---
ext = pyramses.extractor(case.getTrj())
ext.getSync('g7').S.plot()    # rotor speed of generator g7

Note

The Nordic test system files (dyn_B.dat, volt_rat_B.dat, etc.) are available in the Nordic_JhubStart repository.

2.2.4.9. 5-bus system: exciter parameter change

The following example is based on the 5-bus test system Case 2 notebook. It applies a step change to the exciter voltage setpoint at t = 1 s and plots the generator active power.

import pyramses
import os

# --- Configure the test case ---
case = pyramses.cfg()
case.addData('dyn.dat')           # dynamic model data
case.addData('lf1solv.dat')       # power-flow solution
case.addData('solveroptions.dat') # solver settings
case.addDst('nothing.dst')        # no pre-defined disturbances
case.addObs('obs.dat')            # observables to record
case.addTrj('output.trj')         # trajectory output file

# --- Remove stale output files from previous runs ---
for f in os.listdir('.'):
    if f.endswith('.trj') or f.endswith('.trace'):
        os.remove(f)

# --- Run simulation ---
ram = pyramses.sim()
ram.execSim(case, 0.0)                              # initialise, paused at t = 0
ram.addDisturb(1.0, 'CHGPRM EXC G Vo 0.05 2')      # step +0.05 pu on Vo of exciter G at t = 1 s
ram.contSim(60.0)                                   # simulate to t = 60 s
ram.endSim()

# --- Extract and plot results ---
ext = pyramses.extractor(case.getTrj())
ext.getSync('G').P.plot()     # active power of generator G
ext.getSync('G').Q.plot()     # reactive power of generator G

Note

The 5-bus test system files are available in the 5_bus_test_system repository.