Skip to content

API Reference

The stepss.cfg class defines a simulation scenario: data files, disturbance file, output files, observables, and runtime options.


Create an empty configuration or load one from a previously saved command file:

import stepss
case = stepss.cfg() # empty configuration
case = stepss.cfg("cmd.txt") # load from command file

Load multiple cases in a loop:

import stepss
list_of_cases = []
for i in range(12):
list_of_cases.append(stepss.cfg('cmd' + str(i) + '.txt'))

Save the current configuration to a command file. Useful for reproducing a run later. When called without an argument, it returns the command-file content as a string instead of writing a file.

case.writeCmdFile('cmd.txt')

Save multiple cases in a loop:

for i in range(12):
list_of_cases[i].writeCmdFile('cmd' + str(i) + '.txt')

Data files describe the network topology, dynamic models, and solver settings. At least one must be provided.

Add a data file to the case.

case.addData('dyn_A.dat')
case.addData('settings1.dat')

Remove a specific data file from the case.

case.delData('dyn_A.dat')

Return the list of currently registered data files.

files = case.getData()

Remove all data files from the case.

case.clearData()

Specifies where the simulator writes initialization procedure output.

case.addInit('init.trace')

Return the currently registered initialization file path.

path = case.getInit()

Describes the disturbances to be simulated (generator trips, faults, parameter changes, etc.).

case.addDst('events.dst')

Return the currently registered disturbance file path.

path = case.getDst()

Remove the disturbance file from the case.

case.clearDst()

Specifies the file where time-series simulation results (trajectories) are saved for post-processing. This file is used by stepss.extractor to access results after the simulation completes.

case.addTrj('output.trj')

Return the currently registered trajectory file path.

path = case.getTrj()

Defines which components and quantities are recorded in the trajectory file.

case.addObs('obs.dat')

Return the currently registered observables file path.

path = case.getObs()

Set the main output trace file for simulation progress logging.

case.addOut('output.trace')

Return the currently registered output trace file path.

path = case.getOut()

Set the continuous trace file. Records Newton solver convergence information at each step. Useful for debugging but can slow down the simulation.

case.addCont('cont.trace')

Return the currently registered continuous trace file path.

path = case.getCont()

Set the discrete trace file. Records discrete events: switching actions from disturbance files, discrete controllers, or discrete variables in injector/torque/exciter/two-port models.

case.addDisc('disc.trace')

Return the currently registered discrete trace file path.

path = case.getDisc()

Remove the discrete trace file from the case.

case.clearDisc()

Runtime observables are recorded by the engine while a simulation runs, into a curve file you can read with cur and plot with curplot. Nothing has to be installed for this.

Add a runtime observable. The following observable types are supported:

BV BUSNAME, Voltage magnitude of a bus:

case.addRunObs('BV 1041')

MS MACHINE_NAME, Rotor speed of a synchronous machine:

case.addRunObs('MS g1')

BPE / BQE / BPO / BQO BRANCH_NAME, Active (P) or reactive (Q) power at the origin (O) or extremity (E) of a branch:

case.addRunObs('BPO 1041-01') # active power at origin of branch 1041-01
case.addRunObs('BQO 1041-01') # reactive power at origin
case.addRunObs('BPE 1041-01') # active power at extremity
case.addRunObs('BQE 1041-01') # reactive power at extremity

ON INJECTOR_NAME OBSERVABLE_NAME, Named observable from an injector model:

case.addRunObs('ON WT1a Pw') # observable Pw from injector WT1a

TO TWOP_NAME OBSERVABLE_NAME, Named observable from a two-port model:

case.addRunObs('TO hvdc1 P1') # observable P1 from two-port hvdc1

RT RT, Real-time versus simulated-time plot (useful to gauge simulation speed):

case.addRunObs('RT RT')

Remove all runtime observables.

case.clearRunObs()

The stepss.sim class runs simulations. It wraps the RAMSES dynamic library and supports start/pause/continue, runtime queries, and disturbance injection.


import stepss
ram = stepss.sim() # use bundled RAMSES libraries
ram = stepss.sim(custLibDir='/path/to/') # use custom library directory
ParameterTypeDescription
custLibDirstr or NoneCustom path to the RAMSES library directory. Default: use bundled libraries.

A properly configured stepss.cfg test case is required before running a simulation.

ram.execSim(case)

execSim(case, t): start and pause at time t

Section titled “execSim(case, t): start and pause at time t”

Start the simulation and pause at a specific time (in seconds):

ram.execSim(case, 10.0) # start and pause at t = 10 s

Resume a paused simulation until a specified time:

ram.contSim(20.0) # resume until t = 20 s
ram.contSim(ram.getSimTime() + 60.0) # advance by 60 s from current time
ram.contSim(ram.getInfTime()) # run to the end of the time horizon

Terminate the simulation before reaching the time horizon:

ram.endSim()

Schedule a pause at a given simulated time; it takes effect on the next execSim() or contSim() call:

ram.pauseSim(10.0)
ram.execSim(case) # will pause at t = 10 s

Return 0 while the simulation is still running, 1 once it has ended:

if ram.getEndSim():
print('simulation finished')

Return the last error message issued by RAMSES as a string. Useful after catching a RAMSESError:

msg = ram.getLastErr()

When the simulation is paused, the following methods query the current system state.

Return the current simulation time in seconds.

t = ram.getSimTime()

Return the value used as “infinity” (i.e., the end of the simulation time horizon). Pass this to contSim() to run to completion.

t_inf = ram.getInfTime()
ram.contSim(ram.getInfTime())

Return a list of all component names of the given type.

buses = ram.getAllCompNames('BUS') # list of all bus names
gens = ram.getAllCompNames('SYNC') # list of all generator names
injs = ram.getAllCompNames('INJ') # list of all injector names
dctls = ram.getAllCompNames('DCTL') # list of all discrete controller names
branches = ram.getAllCompNames('BRANCH') # list of all branch names
twops = ram.getAllCompNames('TWOP') # list of all two-port names
shunts = ram.getAllCompNames('SHUNT') # list of all shunt names
loads = ram.getAllCompNames('LOAD') # list of all load names

Supported component types: BUS, SYNC, INJ, DCTL, BRANCH, TWOP, SHUNT, LOAD.

Return the name of the num-th component (1-based) of the given type. Accepts the same component types as getAllCompNames().

first_bus = ram.getCompName('BUS', 1) # e.g. 'B1'

Return voltage magnitudes (in pu) for a list of bus names.

ram.execSim(case, 10.0)
bus_names = ['g1', 'g2', '4032']
voltages = ram.getBusVolt(bus_names)

Return voltage phase angles (in degrees) for a list of bus names.

phases = ram.getBusPha(bus_names)

Return power flows for a list of branch names. Each entry is [P_from, Q_from, P_to, Q_to] in MW and Mvar.

powers = ram.getBranchPow(['1041-01'])
# powers[0] == [P_from, Q_from, P_to, Q_to]

Return branch currents for a list of branch names. Each entry contains the x–y current components at the origin and extremity: [ix_orig, iy_orig, ix_extr, iy_extr].

currents = ram.getBranchCur(['1011-1013', '1012-1014'])

Get the current value of named observables for a list of components. Lists must be the same length.

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)

Supported model types: EXC (exciter), TOR (governor), INJ (injector), TWOP (two-port), DCTL (discrete controller), SYN (synchronous generator).

Get parameter values for a list of components. Lists must be the same length.

comp_type = ['EXC', 'EXC']
comp_name = ['g1', 'g2']
prm_name = ['V0', 'KPSS']
prms = ram.getPrm(comp_type, comp_name, prm_name)

List the parameter names of one or more model instances. Accepts single strings or equal-length lists; component types are EXC, TOR, INJ, DCTL, TWOP.

names = ram.getPrmNames('EXC', 'g1') # list of parameter names of g1's exciter

Observables can also be selected programmatically after the simulation starts, instead of via an observables file. Call the three methods in order, after execSim(case, 0.0):

Initialize the runtime observable recording system and set the output trajectory file.

Register one observable selector in RAMSES format (e.g. 'BUS *', 'SYNC g1'). Call once per selector.

Finalize the selection, allocate the recording buffers, and write the trajectory file header.

ram.execSim(case, 0.0)
ram.initObserv('obs.trj')
ram.addObserv('BUS *') # record all bus voltages
ram.addObserv('SYNC g1') # record machine g1
ram.finalObserv()
ram.contSim(ram.getInfTime())

Subsystems select a group of buses for aggregate queries.

Define subsystem ssID as the intersection of three filters: voltage levels, zones, and bus names. Each filter is a list of strings; an empty list deactivates that filter.

ram.defineSS(1, ['735'], [], []) # subsystem 1 = all 735 kV buses

Return the list of buses belonging to a subsystem.

buses = ram.getSS(1)

getTrfoSS(ssID, location, in_service, rettype)

Section titled “getTrfoSS(ssID, location, in_service, rettype)”

Return transformer information for a subsystem. location: 1 = both ends inside the subsystem, 2 = tie transformers, 3 = both. in_service: 1 = in-service only, 2 = all. rettype selects the returned quantity: NAME, From, To, Status, Tap, Currentf, Currentt, Pf, Qf, Pt, Qt.

status = ram.getTrfoSS(1, 3, 2, 'Status')

Disturbances can be added dynamically while the simulation is paused, enabling interactive scenario analysis.

Schedule a disturbance to occur at a given simulation time.

The disturbance description string follows the same syntax as the disturbance file format. See Disturbances for the complete reference.

ram.execSim(case, 80.0)
# Trip generator g7 at t = 100 s
ram.addDisturb(100.0, 'BREAKER SYNC_MACH g7 0')
# Apply a 3-phase fault at bus 4032 and clear it 100 ms later
ram.addDisturb(100.0, 'FAULT BUS 4032 0. 0.')
ram.addDisturb(100.1, 'CLEAR BUS 4032')
# Step change in an LTC setpoint
ram.addDisturb(100.0, 'CHGPRM DCTL 1-1041 Vsetpt -0.05 0')
ram.contSim(ram.getInfTime())

Export the system Jacobian in descriptor form at the current pause point. Returns a tuple (A, E) of scipy.sparse.csc_matrix objects, where A holds the numerical Jacobian values and E is the structural incidence matrix of the descriptor system. Two intermediate text files are also written to the working directory:

FileContents
py_val.datJacobian values (parsed into A)
py_eqs.datStructural incidence matrix (parsed into E)
ram.execSim(case, 10.0)
A, E = ram.getJac()

Use this when you want to drive your own solver, for instance the sparse shift-invert methods in scipy.sparse.linalg that large systems need. To have RAMSES do the analysis instead, schedule an EIG disturbance; see Eigenanalysis.


The stepss.extractor class extracts and visualises time-series results from a trajectory file produced during simulation.


Pass the trajectory file path to the extractor:

import stepss
case = stepss.cfg('cmd.txt')
# ... run simulation ...
ext = stepss.extractor(case.getTrj())

Or provide the file path directly:

ext = stepss.extractor('output.trj')

All extraction methods return objects whose attributes are curve objects (stepss.cur named tuples). Every curve object has:

AttributeTypeDescription
timenumpy.ndarrayTime values in seconds
valuenumpy.ndarrayObservable values
msgstrDescription string (used as plot legend label)

Display the curve using Matplotlib:

bus = ext.getBus('4044')
bus.mag.plot()

Retrieve voltage time series for a bus. Returns an object with:

AttributeDescription
.magVoltage magnitude (pu)
.phaVoltage phase angle (deg)
bus = ext.getBus('4044')
bus.mag.plot() # voltage magnitude (pu)
bus.pha.plot() # voltage phase angle (deg)

Retrieve the full set of synchronous machine observables. Returns an object with:

AttributeDescription
.PActive power (MW)
.QReactive power (Mvar)
.SRotor speed (pu; 1 = nominal)
.ARotor angle w.r.t. COI (deg)
.FVField voltage (pu)
.FCField current (pu)
.TMechanical torque (pu)
.ETElectromagnetic torque (pu)
.FWField winding flux
.DDd1 damper flux
.QDq1 damper flux
.QWq2 winding flux
.SCCOI speed deviation (pu; 0 = nominal)
gen = ext.getSync('g1')
gen.P.plot() # active power (MW)
gen.Q.plot() # reactive power (Mvar)
gen.S.plot() # rotor speed (pu)
gen.A.plot() # rotor angle w.r.t. COI (deg)
gen.FV.plot() # field voltage (pu)
gen.FC.plot() # field current (pu)
gen.T.plot() # mechanical torque (pu)
gen.ET.plot() # electromagnetic torque (pu)

Retrieve exciter observables. Available observables depend on the exciter model.

AttributeDescription
.obsdictdict mapping observable name → description
(model-dependent)Access by observable name, e.g. .vf
exc = ext.getExc('g1')
print(exc.obsdict) # list available observables for this model
exc.vf.plot() # field voltage (model-dependent name)

Retrieve governor/torque model observables. Available observables depend on the governor model.

AttributeDescription
.obsdictdict mapping observable name → description
(model-dependent)Access by observable name, e.g. .Pm
gov = ext.getTor('g1')
print(gov.obsdict) # list available observables for this model
gov.Pm.plot() # mechanical power (pu)

Retrieve injector observables. Injectors include renewable energy sources (wind, PV, BESS), loads, and other single-bus components.

AttributeDescription
.obsdictdict mapping observable name → description
(model-dependent)Access by observable name, e.g. .Pw
inj = ext.getInj('WT1a')
print(inj.obsdict) # list available observables
inj.Pw.plot() # wind power (model-dependent name)

Retrieve two-port model observables. Two-port models include HVDC links (LCC and VSC), SVCs, and DC systems.

AttributeDescription
.obsdictdict mapping observable name → description
(model-dependent)Access by observable name, e.g. .P1, .P2
twop = ext.getTwop('hvdc1')
print(twop.obsdict) # list available observables
twop.P1.plot() # active power at terminal 1
twop.P2.plot() # active power at terminal 2

Retrieve discrete controller observables. Discrete controllers include LTC transformers, under-voltage load shedding, phase shifters, etc.

AttributeDescription
.obsdictdict mapping observable name → description
dctl = ext.getDctl('1-1041')
print(dctl.obsdict) # list available observables

Retrieve branch (line/transformer) power flow time series.

AttributeDescription
.PFActive power at FROM end (MW)
.QFReactive power at FROM end (Mvar)
.PTActive power at TO end (MW)
.QTReactive power at TO end (Mvar)
.RMTransformer ratio magnitude
.RATransformer phase angle (deg)
branch = ext.getBranch('1041-01')
branch.PF.plot() # active power at FROM end (MW)
branch.QF.plot() # reactive power at FROM end (Mvar)
branch.PT.plot() # active power at TO end (MW)
branch.QT.plot() # reactive power at TO end (Mvar)
branch.RM.plot() # transformer ratio magnitude
branch.RA.plot() # transformer phase angle (deg)

Retrieve shunt compensation time series.

AttributeDescription
.QReactive power produced (Mvar)
ext.getShunt('sh1').Q.plot()

Retrieve load time series.

AttributeDescription
.PActive power consumed (MW)
.QReactive power consumed (Mvar)
ext.getLoad('L_1').P.plot()

Display multiple curve objects on the same axes. Each curve’s msg field is used as the legend label.

import stepss
ext = stepss.extractor(case.getTrj())
curves = [
ext.getSync('g1').S,
ext.getSync('g2').S,
ext.getSync('g3').S,
]
stepss.curplot(curves)

monitor plots chosen quantities while a simulation runs. It steps the engine forward in slices, reads those quantities at every pause, and redraws a stacked figure: one panel per observable, all sharing the time axis.

The engine is polled in process, so nothing is read from disk and nothing has to be installed beyond matplotlib, which pip installs with the package. Runtime observables, above, are a separate mechanism in which the engine writes a curve file of its own; the two are independent and can be used together.

import stepss
ram = stepss.sim()
case = stepss.cfg('cmd.txt')
ram.execSim(case, 0.0) # initialise, paused at t = 0
mon = stepss.monitor(ram, ['BV 4044', 'MS g6', 'RT RT'], title='Nordic')
curves = mon.run(step=0.5) # run to the end of the scenario

The simulation must already be initialised and paused, which execSim does when given a pause time.

monitor(ram, observables, title=None, refresh=0.2, show=True)

Section titled “monitor(ram, observables, title=None, refresh=0.2, show=True)”
ArgumentDescription
ramThe stepss.sim driving the run.
observablesWhat to plot: descriptor strings, (label, callable) pairs, or bare callables. A single observable need not be wrapped in a list.
titleFigure title.
refreshMinimum wall-clock seconds between redraws. Samples are never skipped, only draws; 0 redraws at every sample.
showFalse collects the samples and builds no figure.

The vocabulary is the one addRunObs uses, plus BA and the generic OBS:

DescriptorQuantityUnit
BV BUSNAMEVoltage magnitude of a buspu
BA BUSNAMEVoltage phase of a busdeg
MS MACHINE_NAMERotor speed of a synchronous machinepu
BPO / BQO / BPE / BQE BRANCH_NAMEActive or reactive power at the origin or extremity of a branchMW, Mvar
ON INJECTOR_NAME OBSERVABLE_NAMENamed observable of an injector modelset by the model
TO TWOP_NAME OBSERVABLE_NAMENamed observable of a two-port modelset by the model
OBS TYPE NAME OBSERVABLE_NAMENamed observable of any component type getObs acceptsset by the model
RT RTElapsed wall-clock time, to gauge simulation speeds

Anything the descriptors do not cover is a callable, taking the simulator and returning one number:

mon = stepss.monitor(ram, [
'BV 4044',
('4044 reactive reserve (Mvar)', lambda r: r.getObs('SYN', 'g6', 'Q')[0]),
])

Advance the simulation, sampling and redrawing at each pause, and return one cur per observable. Returns when the engine reports the end of the scenario, when until is reached, or when a slice fails to advance the simulated time.

curves = mon.run(step=0.1, until=30.0) # 30 s of simulated time, sampled every 0.1 s

step is simulated seconds per slice, so it sets both the sampling resolution and the redraw cadence. The engine pauses at the first internal step at or after the requested time, so the samples land on or just after the multiples of step.

If the engine raises, the samples taken up to that point stay available from curves():

from stepss.globals import RAMSESError
try:
mon.run(step=1.0)
except RAMSESError:
stepss.curplot(mon.curves()) # everything up to the failure

Return everything sampled so far, one cur per observable, in the order given to the constructor. These are the same objects extractor produces, so curplot and the rest of the post-processing accept them unchanged.

Read every observable once, at the current simulated time. Call it directly when driving the simulation yourself:

mon = stepss.monitor(ram, ['BV 4044'])
for target in [10.0, 20.0, 30.0]:
ram.contSim(target)
ram.addDisturb(target + 1.0, 'CHGPRM DCTL 1-1041 Vsetpt -0.01 0')
mon.sample()
mon.refresh()

Push the samples into the figure and redraw it. A draw falling inside the refresh interval is skipped unless force is set.

Redraw and write the figure to a file, forwarding to matplotlib.figure.Figure.savefig.

Close the figure. The samples are unaffected: curves() keeps working.


stepss.ssa: Small-Signal Stability Analysis

Section titled “stepss.ssa: Small-Signal Stability Analysis”

RAMSES performs the analysis itself and writes three files named from a basename. stepss.ssa drives that run and reads them back, so no parsing is needed.

Needs RAMSES 3.79 or newer, which is what writes the v2 files this module reads.


ssa.run(case, basename='ssa', t=None, workdir=None, jacobian=False, ram=None, keep_open=False)

Section titled “ssa.run(case, basename='ssa', t=None, workdir=None, jacobian=False, ram=None, keep_open=False)”

Run one analysis and return its results. t is when to linearise, in seconds; left at None it defaults to the earliest instant the engine accepts, 0.001 s. The case is copied rather than modified, and the copy is given $SCHEME DE and $OMEGA_REF SYN in a generated file read last, because the engine refuses the analysis under either of the values a case that says nothing about them lands on. Anything already on disk under basename is cleared first, since the only evidence a run produced results is that its modes file is there afterwards.

import stepss
from stepss import ssa
case = stepss.cfg('cmd.txt')
res = ssa.run(case, basename='ssa', workdir='run1')
res.summary()

Pass jacobian=True to write the four Jacobian tables at the instant of the reduction as well.

ssa.load(directory, basename) and ssa.basenames(directory)

Section titled “ssa.load(directory, basename) and ssa.basenames(directory)”

Read a run produced anywhere, and list the runs in a directory.

print(ssa.basenames('run1')) # ['ssa']
res = ssa.load('run1', 'ssa')

Open a .ssa archive, in .zip or .tar.gz, whichever interface wrote it. Returns (results, manifest). The manifest records the run’s basename, the engine version and the analysis time, and an archive with none, one written by something other than STEPSS, is refused rather than read.

into names where to unpack; a temporary directory is used when it is omitted. The results are read from there and the returned Results goes on naming it, so that directory has to outlive the object.

ssa.save_archive(results, path, saved_by=None)

Section titled “ssa.save_archive(results, path, saved_by=None)”

Write one run to a .ssa archive the graphical interface opens with Load dynamic Jacobian. res.save(path) is the same thing as a method.

absent = res.save('kundur.ssa.zip') # as a method
absent = ssa.save_archive(res, 'kundur.ssa.zip') # or as a function

The format comes from the file name: .tar.gz or .tgz for a gzipped tar, anything else for a zip. Everything goes under one directory named for the run, manifest first, so unpacking by hand gives a folder rather than eight loose files.

The return value is the member names that were not on disk, which for a run made without jacobian=True is the four Jacobian tables. An empty list means every one of the seven was archived.

Two refusals worth knowing. A run whose modes file is gone is refused, because there is no analysis to archive. And a basename of 45 characters or more is refused on the tar path alone: member names would pass the 100-byte limit that format allows, and the resulting archive is one the Java reader mangles without saying so. Save as .zip, or shorten the basename.

The archive is written through a .part file and moved into place, so a failure partway through leaves no truncated file that looks like an archive until someone opens it.

ssa.save is kept as an alias for the same function, because releases 3.81.1 and 3.81.2 shipped under that name.


The rotor band, one member of each conjugate pair, sorted by frequency.

The modes whose real part is above the limit, strictly greater than. This is the filter that used to be a parameter of the run, and it is now a question asked of results already in hand: widening it costs nothing.

Both return a ModeView, which composes and carries .rows, .lam, .table() and .to_frame():

res.electromechanical().dominant(-1.0).table()

res.participation(mode, floor=0.05, allow_degenerate=False)

Section titled “res.participation(mode, floor=0.05, allow_degenerate=False)”

Participation factors, largest first. floor is applied here, not by the engine: the file carries every entry above the run’s own pf_floor, so lowering it shows more without re-running anything.

res.mode_shape(mode, allow_degenerate=False)

Section titled “res.mode_shape(mode, allow_degenerate=False)”

Each machine’s rotor-speed phasor: magnitude normalised so the largest is 1, angle relative to that entry.

Both refuse a mode whose simple flag is false. In a degenerate eigenspace the eigenvectors are not unique, so both quantities are basis-dependent and would come out differently on another machine.


view.splane(ax=None, zeta=0.05, annotate=True, interactive=None)

Section titled “view.splane(ax=None, zeta=0.05, annotate=True, interactive=None)”

The s-plane, fitted to the modes on screen. Under an interactive backend (%matplotlib widget) clicking a pole prints it, dragging a rectangle zooms, and a double click restores the fitted window.

res.mode_shape_plot(mode, ax=None, allow_degenerate=False) and res.participation_plot(mode, floor=0.05, ax=None, allow_degenerate=False)

Section titled “res.mode_shape_plot(mode, ax=None, allow_degenerate=False) and res.participation_plot(mode, floor=0.05, ax=None, allow_degenerate=False)”

The polar dial and a horizontal bar chart. Each takes and returns an Axes, so two runs go side by side in one figure.


The matrix the engine reduced, as an (nstates, nstates) array. It lives in the engine rather than in any file, so it is available on a live run only, and a later analysis replaces it: read it before starting the next one.

sim.runSsa(basename, t=None) and sim.getStateMatrix()

Section titled “sim.runSsa(basename, t=None) and sim.getStateMatrix()”

The low-level entries ssa.run is built on, for a caller driving the run themselves.


import stepss
# --- Build test case ---
case = stepss.cfg()
case.addData('dyn_A.dat')
case.addData('settings1.dat')
case.addInit('init.trace')
case.addDst('events.dst')
case.addTrj('output.trj')
case.addObs('obs.dat')
case.addOut('output.trace')
case.addCont('cont.trace')
case.addDisc('disc.trace')
# Runtime observables, recorded by the engine into a curve file
case.addRunObs('BV 1041')
case.addRunObs('MS g1')
case.addRunObs('RT RT')
# Save configuration
case.writeCmdFile('cmd.txt')
# --- Run simulation ---
ram = stepss.sim()
# Start and pause at t = 80 s
ram.execSim(case, 80.0)
# Inject a disturbance dynamically
ram.addDisturb(100.0, 'FAULT BUS 4032 0. 0.')
ram.addDisturb(100.1, 'CLEAR BUS 4032')
# Export Jacobian at this operating point
A, E = ram.getJac()
# Run to end
ram.contSim(ram.getInfTime())
# --- Extract results ---
ext = stepss.extractor(case.getTrj())
# Bus voltages
ext.getBus('4044').mag.plot()
# Generator observables
gen = ext.getSync('g1')
gen.S.plot() # rotor speed
gen.A.plot() # rotor angle
# Branch flows
ext.getBranch('1041-01').PF.plot()
# Plot multiple rotor speeds together
stepss.curplot([
ext.getSync('g1').S,
ext.getSync('g2').S,
ext.getSync('g3').S,
])
  • Examples, Practical simulation examples and workflows
  • Test Systems, Ready-to-run benchmark systems