API Reference
stepss.cfg: Test Case Configuration
Section titled “stepss.cfg: Test Case Configuration”The stepss.cfg class defines a simulation scenario: data files, disturbance file, output files, observables, and runtime options.
Initializing
Section titled “Initializing”Create an empty configuration or load one from a previously saved command file:
import stepss
case = stepss.cfg() # empty configurationcase = stepss.cfg("cmd.txt") # load from command fileLoad 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'))writeCmdFile(filename=None)
Section titled “writeCmdFile(filename=None)”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
Section titled “Data Files”Data files describe the network topology, dynamic models, and solver settings. At least one must be provided.
addData(filename)
Section titled “addData(filename)”Add a data file to the case.
case.addData('dyn_A.dat')case.addData('settings1.dat')delData(filename)
Section titled “delData(filename)”Remove a specific data file from the case.
case.delData('dyn_A.dat')getData()
Section titled “getData()”Return the list of currently registered data files.
files = case.getData()clearData()
Section titled “clearData()”Remove all data files from the case.
case.clearData()Initialization File
Section titled “Initialization File”Specifies where the simulator writes initialization procedure output.
addInit(filename)
Section titled “addInit(filename)”case.addInit('init.trace')getInit()
Section titled “getInit()”Return the currently registered initialization file path.
path = case.getInit()Disturbance File
Section titled “Disturbance File”Describes the disturbances to be simulated (generator trips, faults, parameter changes, etc.).
addDst(filename)
Section titled “addDst(filename)”case.addDst('events.dst')getDst()
Section titled “getDst()”Return the currently registered disturbance file path.
path = case.getDst()clearDst()
Section titled “clearDst()”Remove the disturbance file from the case.
case.clearDst()Trajectory File
Section titled “Trajectory File”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.
addTrj(filename)
Section titled “addTrj(filename)”case.addTrj('output.trj')getTrj()
Section titled “getTrj()”Return the currently registered trajectory file path.
path = case.getTrj()Observables File
Section titled “Observables File”Defines which components and quantities are recorded in the trajectory file.
addObs(filename)
Section titled “addObs(filename)”case.addObs('obs.dat')getObs()
Section titled “getObs()”Return the currently registered observables file path.
path = case.getObs()Output/Trace Files
Section titled “Output/Trace Files”addOut(filename)
Section titled “addOut(filename)”Set the main output trace file for simulation progress logging.
case.addOut('output.trace')getOut()
Section titled “getOut()”Return the currently registered output trace file path.
path = case.getOut()addCont(filename)
Section titled “addCont(filename)”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')getCont()
Section titled “getCont()”Return the currently registered continuous trace file path.
path = case.getCont()addDisc(filename)
Section titled “addDisc(filename)”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')getDisc()
Section titled “getDisc()”Return the currently registered discrete trace file path.
path = case.getDisc()clearDisc()
Section titled “clearDisc()”Remove the discrete trace file from the case.
case.clearDisc()Runtime Observables
Section titled “Runtime Observables”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.
addRunObs(obs_string)
Section titled “addRunObs(obs_string)”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-01case.addRunObs('BQO 1041-01') # reactive power at origincase.addRunObs('BPE 1041-01') # active power at extremitycase.addRunObs('BQE 1041-01') # reactive power at extremityON INJECTOR_NAME OBSERVABLE_NAME, Named observable from an injector model:
case.addRunObs('ON WT1a Pw') # observable Pw from injector WT1aTO TWOP_NAME OBSERVABLE_NAME, Named observable from a two-port model:
case.addRunObs('TO hvdc1 P1') # observable P1 from two-port hvdc1RT RT, Real-time versus simulated-time plot (useful to gauge simulation speed):
case.addRunObs('RT RT')clearRunObs()
Section titled “clearRunObs()”Remove all runtime observables.
case.clearRunObs()stepss.sim: Simulation Control
Section titled “stepss.sim: Simulation Control”The stepss.sim class runs simulations. It wraps the RAMSES dynamic library and supports start/pause/continue, runtime queries, and disturbance injection.
Initializing
Section titled “Initializing”import stepss
ram = stepss.sim() # use bundled RAMSES librariesram = stepss.sim(custLibDir='/path/to/') # use custom library directory| Parameter | Type | Description |
|---|---|---|
custLibDir | str or None | Custom path to the RAMSES library directory. Default: use bundled libraries. |
Running Simulations
Section titled “Running Simulations”A properly configured stepss.cfg test case is required before running a simulation.
execSim(case): run to completion
Section titled “execSim(case): run to completion”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 scontSim(t): continue to time t
Section titled “contSim(t): continue to time t”Resume a paused simulation until a specified time:
ram.contSim(20.0) # resume until t = 20 sram.contSim(ram.getSimTime() + 60.0) # advance by 60 s from current timeram.contSim(ram.getInfTime()) # run to the end of the time horizonendSim(): terminate early
Section titled “endSim(): terminate early”Terminate the simulation before reaching the time horizon:
ram.endSim()pauseSim(t_pause): schedule a pause
Section titled “pauseSim(t_pause): schedule a pause”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 sgetEndSim()
Section titled “getEndSim()”Return 0 while the simulation is still running, 1 once it has ended:
if ram.getEndSim(): print('simulation finished')getLastErr()
Section titled “getLastErr()”Return the last error message issued by RAMSES as a string. Useful after catching a RAMSESError:
msg = ram.getLastErr()Querying State
Section titled “Querying State”When the simulation is paused, the following methods query the current system state.
getSimTime()
Section titled “getSimTime()”Return the current simulation time in seconds.
t = ram.getSimTime()getInfTime()
Section titled “getInfTime()”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())getAllCompNames(type)
Section titled “getAllCompNames(type)”Return a list of all component names of the given type.
buses = ram.getAllCompNames('BUS') # list of all bus namesgens = ram.getAllCompNames('SYNC') # list of all generator namesinjs = ram.getAllCompNames('INJ') # list of all injector namesdctls = ram.getAllCompNames('DCTL') # list of all discrete controller namesbranches = ram.getAllCompNames('BRANCH') # list of all branch namestwops = ram.getAllCompNames('TWOP') # list of all two-port namesshunts = ram.getAllCompNames('SHUNT') # list of all shunt namesloads = ram.getAllCompNames('LOAD') # list of all load namesSupported component types: BUS, SYNC, INJ, DCTL, BRANCH, TWOP, SHUNT, LOAD.
getCompName(comp_type, num)
Section titled “getCompName(comp_type, num)”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'getBusVolt(names)
Section titled “getBusVolt(names)”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)getBusPha(names)
Section titled “getBusPha(names)”Return voltage phase angles (in degrees) for a list of bus names.
phases = ram.getBusPha(bus_names)getBranchPow(names)
Section titled “getBranchPow(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]getBranchCur(names)
Section titled “getBranchCur(names)”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'])getObs(comp_types, comp_names, obs_names)
Section titled “getObs(comp_types, comp_names, obs_names)”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).
getPrm(comp_types, comp_names, prm_names)
Section titled “getPrm(comp_types, comp_names, prm_names)”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)getPrmNames(comp_types, comp_names)
Section titled “getPrmNames(comp_types, comp_names)”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 exciterRuntime Observable Recording
Section titled “Runtime Observable Recording”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):
initObserv(traj_filenm)
Section titled “initObserv(traj_filenm)”Initialize the runtime observable recording system and set the output trajectory file.
addObserv(string)
Section titled “addObserv(string)”Register one observable selector in RAMSES format (e.g. 'BUS *', 'SYNC g1'). Call once per selector.
finalObserv()
Section titled “finalObserv()”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 voltagesram.addObserv('SYNC g1') # record machine g1ram.finalObserv()ram.contSim(ram.getInfTime())Subsystems
Section titled “Subsystems”Subsystems select a group of buses for aggregate queries.
defineSS(ssID, filter1, filter2, filter3)
Section titled “defineSS(ssID, filter1, filter2, filter3)”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 busesgetSS(ssID)
Section titled “getSS(ssID)”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')Runtime Disturbances
Section titled “Runtime Disturbances”Disturbances can be added dynamically while the simulation is paused, enabling interactive scenario analysis.
addDisturb(time, description)
Section titled “addDisturb(time, description)”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 sram.addDisturb(100.0, 'BREAKER SYNC_MACH g7 0')
# Apply a 3-phase fault at bus 4032 and clear it 100 ms laterram.addDisturb(100.0, 'FAULT BUS 4032 0. 0.')ram.addDisturb(100.1, 'CLEAR BUS 4032')
# Step change in an LTC setpointram.addDisturb(100.0, 'CHGPRM DCTL 1-1041 Vsetpt -0.05 0')
ram.contSim(ram.getInfTime())Jacobian Export
Section titled “Jacobian Export”getJac()
Section titled “getJac()”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:
| File | Contents |
|---|---|
py_val.dat | Jacobian values (parsed into A) |
py_eqs.dat | Structural 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.
stepss.extractor: Result Extraction
Section titled “stepss.extractor: Result Extraction”The stepss.extractor class extracts and visualises time-series results from a trajectory file produced during simulation.
Initializing
Section titled “Initializing”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')Curve Objects
Section titled “Curve Objects”All extraction methods return objects whose attributes are curve objects (stepss.cur named tuples). Every curve object has:
| Attribute | Type | Description |
|---|---|---|
time | numpy.ndarray | Time values in seconds |
value | numpy.ndarray | Observable values |
msg | str | Description string (used as plot legend label) |
.plot()
Section titled “.plot()”Display the curve using Matplotlib:
bus = ext.getBus('4044')bus.mag.plot()Extraction Methods
Section titled “Extraction Methods”getBus(name)
Section titled “getBus(name)”Retrieve voltage time series for a bus. Returns an object with:
| Attribute | Description |
|---|---|
.mag | Voltage magnitude (pu) |
.pha | Voltage phase angle (deg) |
bus = ext.getBus('4044')bus.mag.plot() # voltage magnitude (pu)bus.pha.plot() # voltage phase angle (deg)getSync(name)
Section titled “getSync(name)”Retrieve the full set of synchronous machine observables. Returns an object with:
| Attribute | Description |
|---|---|
.P | Active power (MW) |
.Q | Reactive power (Mvar) |
.S | Rotor speed (pu; 1 = nominal) |
.A | Rotor angle w.r.t. COI (deg) |
.FV | Field voltage (pu) |
.FC | Field current (pu) |
.T | Mechanical torque (pu) |
.ET | Electromagnetic torque (pu) |
.FW | Field winding flux |
.DD | d1 damper flux |
.QD | q1 damper flux |
.QW | q2 winding flux |
.SC | COI 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)getExc(name)
Section titled “getExc(name)”Retrieve exciter observables. Available observables depend on the exciter model.
| Attribute | Description |
|---|---|
.obsdict | dict 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 modelexc.vf.plot() # field voltage (model-dependent name)getTor(name)
Section titled “getTor(name)”Retrieve governor/torque model observables. Available observables depend on the governor model.
| Attribute | Description |
|---|---|
.obsdict | dict 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 modelgov.Pm.plot() # mechanical power (pu)getInj(name)
Section titled “getInj(name)”Retrieve injector observables. Injectors include renewable energy sources (wind, PV, BESS), loads, and other single-bus components.
| Attribute | Description |
|---|---|
.obsdict | dict mapping observable name → description |
| (model-dependent) | Access by observable name, e.g. .Pw |
inj = ext.getInj('WT1a')print(inj.obsdict) # list available observablesinj.Pw.plot() # wind power (model-dependent name)getTwop(name)
Section titled “getTwop(name)”Retrieve two-port model observables. Two-port models include HVDC links (LCC and VSC), SVCs, and DC systems.
| Attribute | Description |
|---|---|
.obsdict | dict mapping observable name → description |
| (model-dependent) | Access by observable name, e.g. .P1, .P2 |
twop = ext.getTwop('hvdc1')print(twop.obsdict) # list available observablestwop.P1.plot() # active power at terminal 1twop.P2.plot() # active power at terminal 2getDctl(name)
Section titled “getDctl(name)”Retrieve discrete controller observables. Discrete controllers include LTC transformers, under-voltage load shedding, phase shifters, etc.
| Attribute | Description |
|---|---|
.obsdict | dict mapping observable name → description |
dctl = ext.getDctl('1-1041')print(dctl.obsdict) # list available observablesgetBranch(name)
Section titled “getBranch(name)”Retrieve branch (line/transformer) power flow time series.
| Attribute | Description |
|---|---|
.PF | Active power at FROM end (MW) |
.QF | Reactive power at FROM end (Mvar) |
.PT | Active power at TO end (MW) |
.QT | Reactive power at TO end (Mvar) |
.RM | Transformer ratio magnitude |
.RA | Transformer 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 magnitudebranch.RA.plot() # transformer phase angle (deg)getShunt(name)
Section titled “getShunt(name)”Retrieve shunt compensation time series.
| Attribute | Description |
|---|---|
.Q | Reactive power produced (Mvar) |
ext.getShunt('sh1').Q.plot()getLoad(name)
Section titled “getLoad(name)”Retrieve load time series.
| Attribute | Description |
|---|---|
.P | Active power consumed (MW) |
.Q | Reactive power consumed (Mvar) |
ext.getLoad('L_1').P.plot()Multi-Curve Plotting
Section titled “Multi-Curve Plotting”stepss.curplot(curves)
Section titled “stepss.curplot(curves)”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)stepss.monitor: Live Plotting
Section titled “stepss.monitor: Live Plotting”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 scenarioThe 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)”| Argument | Description |
|---|---|
ram | The stepss.sim driving the run. |
observables | What to plot: descriptor strings, (label, callable) pairs, or bare callables. A single observable need not be wrapped in a list. |
title | Figure title. |
refresh | Minimum wall-clock seconds between redraws. Samples are never skipped, only draws; 0 redraws at every sample. |
show | False collects the samples and builds no figure. |
Observable descriptors
Section titled “Observable descriptors”The vocabulary is the one addRunObs uses, plus BA and the generic OBS:
| Descriptor | Quantity | Unit |
|---|---|---|
BV BUSNAME | Voltage magnitude of a bus | pu |
BA BUSNAME | Voltage phase of a bus | deg |
MS MACHINE_NAME | Rotor speed of a synchronous machine | pu |
BPO / BQO / BPE / BQE BRANCH_NAME | Active or reactive power at the origin or extremity of a branch | MW, Mvar |
ON INJECTOR_NAME OBSERVABLE_NAME | Named observable of an injector model | set by the model |
TO TWOP_NAME OBSERVABLE_NAME | Named observable of a two-port model | set by the model |
OBS TYPE NAME OBSERVABLE_NAME | Named observable of any component type getObs accepts | set by the model |
RT RT | Elapsed wall-clock time, to gauge simulation speed | s |
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]),])run(step=1.0, until=None)
Section titled “run(step=1.0, until=None)”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 sstep 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 failurecurves()
Section titled “curves()”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.
sample()
Section titled “sample()”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()refresh(force=False)
Section titled “refresh(force=False)”Push the samples into the figure and redraw it. A draw falling inside the
refresh interval is skipped unless force is set.
savefig(fname, **kwargs)
Section titled “savefig(fname, **kwargs)”Redraw and write the figure to a file, forwarding to
matplotlib.figure.Figure.savefig.
close()
Section titled “close()”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.
Running
Section titled “Running”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 stepssfrom 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')ssa.load_archive(path, into=None)
Section titled “ssa.load_archive(path, into=None)”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 methodabsent = ssa.save_archive(res, 'kundur.ssa.zip') # or as a functionThe 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.
Filtering
Section titled “Filtering”res.electromechanical(lo=0.1, hi=2.5)
Section titled “res.electromechanical(lo=0.1, hi=2.5)”The rotor band, one member of each conjugate pair, sorted by frequency.
res.dominant(real_limit=-1.0)
Section titled “res.dominant(real_limit=-1.0)”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()Reading one mode
Section titled “Reading one mode”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.
Plotting
Section titled “Plotting”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 state matrix
Section titled “The state matrix”res.state_matrix
Section titled “res.state_matrix”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.
Complete Example
Section titled “Complete Example”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 filecase.addRunObs('BV 1041')case.addRunObs('MS g1')case.addRunObs('RT RT')
# Save configurationcase.writeCmdFile('cmd.txt')
# --- Run simulation ---ram = stepss.sim()
# Start and pause at t = 80 sram.execSim(case, 80.0)
# Inject a disturbance dynamicallyram.addDisturb(100.0, 'FAULT BUS 4032 0. 0.')ram.addDisturb(100.1, 'CLEAR BUS 4032')
# Export Jacobian at this operating pointA, E = ram.getJac()
# Run to endram.contSim(ram.getInfTime())
# --- Extract results ---ext = stepss.extractor(case.getTrj())
# Bus voltagesext.getBus('4044').mag.plot()
# Generator observablesgen = ext.getSync('g1')gen.S.plot() # rotor speedgen.A.plot() # rotor angle
# Branch flowsext.getBranch('1041-01').PF.plot()
# Plot multiple rotor speeds togetherstepss.curplot([ ext.getSync('g1').S, ext.getSync('g2').S, ext.getSync('g3').S,])Next Steps
Section titled “Next Steps”- Examples, Practical simulation examples and workflows
- Test Systems, Ready-to-run benchmark systems