Public API#

The public surface of the package is the handful of names below; everything else is implementation. The full generated reference for every module is in hermess.

Running#

hermess.simulate(system, system_root=None, progress_callback=None, init_callback=None, quiet=False, **overrides)[source]

Run one simulation and return the finished DaeSim.

The short form of config.updated(...) followed by hermess.run.run(), for the common case of “simulate this system with a few settings changed”:

import hermess
dae = hermess.simulate("3bus_loadstep", T_end=5.0)
dae = hermess.simulate("ieee39", line_dyn=False, T_end=10.0, plot=True)
Parameters:
  • system (str) – A system name from list_systems(), or a folder name under system_root.

  • system_root (str | Path | None) – Where to look for the system folder (default: the systems shipped with the package). Point it at your own directory to run a system you wrote or edited.

  • progress_callback (Any) – Optional hook called during the time stepping with the completed fraction in [0, 1]; a falsy return other than None cancels the run by raising SimulationCancelled.

  • init_callback (Any) – Optional hook called once with the initialized model at the operating point, before the time stepping; a falsy return other than None cancels the run (e.g. after inspecting dae.eigenvalues).

  • quiet (bool) – Silence the run: no progress bar, warnings-only logging (show_progress=False, log_level="WARNING"). An explicit override of either field still wins.

  • overrides (Any) – Any field of Config (T_end, ts, line_dyn, omega_mode, small_signal_analysis, plot, …). Plotting is off by default here, unlike the shipped configuration, so the call returns quietly and you plot what you want from the returned object.

Return type:

DaeSim

Returns:

The DaeSim holding the symbolic model and the trajectories.

hermess.list_systems(root=None)[source]

Names of the ready-made systems, usable as the first argument of simulate() (or as testsystemfile in a Config).

Return type:

list

Parameters:

root (str | Path | None)

The returned model is a DaeSim; see Reading the results for the reading guide. hermess.SYSTEMS_DIR points at the folder of shipped systems.

Results#

extract_results and SimulationResults are importable from hermess directly (loaded lazily, so pandas stays off the import path until used).

hermess.results.extract_results(dae, config=None)[source]

Build a SimulationResults from a finished DaeSim.

Expects the per-bus and per-device result dictionaries to be filled, i.e. save_data has run (as hermess.run.run() does after the simulation).

Parameters:
  • dae – The finished model, e.g. the return value of hermess.simulate().

  • config – The Config of the run, recorded for provenance (optional).

Return type:

SimulationResults

class hermess.results.SimulationResults(system, t, voltage, power, devices=<factory>, small_signal=None, power_flow_bus=None, power_flow_branch=None, config=<factory>, hermess_version='', created='')[source]

All plottable data of one finished run, detached from the model.

Parameters:
  • system (str)

  • t (ndarray)

  • voltage (dict[str, ndarray])

  • power (dict[str, ndarray])

  • devices (list[DeviceTrajectories])

  • small_signal (SmallSignalResults | None)

  • power_flow_bus (DataFrame | None)

  • power_flow_branch (DataFrame | None)

  • config (dict[str, Any])

  • hermess_version (str)

  • created (str)

Analysis#

Everything in hermess.analysis (see the guide); one star-import gives a notebook the whole workflow, including hermess.simulate() and the model registry.

hermess.analysis.signals(dae, what=None, kind=None)[source]

The address book of the run as a table: every signal, its unit and meaning.

signals(dae) lists everything; signals(dae, "SG1") or signals(dae, "*:f") filters with the same selector the plots take; kind filters by 'state', 'algebraic', 'derived', 'bus' or 'branch'.

Parameters:
  • what (object)

  • kind (str | None)

hermess.analysis.signal_names(dae, what)[source]

Resolve a selector into canonical signal names, in catalog order, deduplicated.

Accepts a string ("SG1:omega", "f", "bus*:v", "SG1"), a list of them, a {owner: quantity-or-list} dict, a device object, or a (device, state) tuple. Matching is case-insensitive and understands * and ?.

Return type:

list[str]

Parameters:

what (object)

hermess.analysis.get(dae, what)[source]

Resolve a selector and return {signal name: values} (each of length nts).

Return type:

dict[str, ndarray]

Parameters:

what (object)

hermess.analysis.to_dataframe(dae, what='*:*', every=1)[source]

Selected signals as a pandas DataFrame indexed by time [s] (every: keep every n-th sample, for exporting a manageable file).

Parameters:
  • what (object)

  • every (int)

hermess.analysis.to_csv(dae, path, what, every=1, float_format='%.6g')[source]

Write selected signals to CSV as t plus one named column per signal.

Return type:

Path

Parameters:
  • path (str | Path)

  • what (object)

  • every (int)

  • float_format (str)

hermess.analysis.metrics(dae, what='*:f', settle_from=None)[source]

Excursion metrics per signal: pre-event value, nadir/peak and when, final value and the largest rate of change. Defaults to every device frequency.

Parameters:
  • what (object)

  • settle_from (float | None)

hermess.analysis.summary(dae)[source]

One-paragraph description of what was simulated: size, devices, disturbances.

Return type:

str

hermess.analysis.plot(dae, what, ax=None, events=True, label_prefix='', figsize=None, title=None, **kw)[source]

Plot any selection of signals against time.

Signals are grouped by unit: everything sharing a unit lands on one axes, and a mixed selection produces one panel per unit. Pass ax to force a single axes (e.g. to overlay two runs yourself). Style keywords (color=, lw=, …) pass through to matplotlib:

plot(dae, "SG1:omega")
plot(dae, "f")                       # every device frequency
plot(dae, ["*:f", "bus*:v"])         # two panels, Hz and p.u.
plot(dae, {"GFMI2": ["Pc_tilde", "delta_c"]})
Parameters:
  • what (object)

  • events (bool)

  • label_prefix (str)

  • title (str | None)

hermess.analysis.plot_states(dae, who='*', states='*', ncols=4, figsize=None, events=True, **kw)[source]

One small panel per state, the way you look at a device you are debugging.

plot_states(dae, “SG1”) every state of SG1 plot_states(dae, “SG1”, [“omega”, “delta”]) two of them plot_states(dae, “*”, “omega”) that state on every device plot_states(dae, “GFM*”) every state of every GFM

Parameters:
  • who (object)

  • states (object)

  • ncols (int)

  • events (bool)

hermess.analysis.compare(runs, what, ncols=2, figsize=None, events=True, colors=None, **kw)[source]

The same signals across several runs: one panel per signal, one line per run.

colors gives one color per run; the default is the active matplotlib color cycle.

compare({“droop”: dae1, “VSM”: dae2}, [“GFMI2:f”, “GFMI2:P”])

Parameters:
  • runs (Mapping[str, object])

  • what (object)

  • ncols (int)

  • events (bool)

  • colors (Sequence[str] | None)

hermess.analysis.plot_frequency(dae, who=None, ax=None, title='Frequency', **kw)[source]

Frequency [Hz] of every machine and converter, or of a selection (who="SG*", who=["SG1", "GFMI2"]).

Parameters:

who (object)

hermess.analysis.plot_voltages(dae, buses=None, ax=None, title='Bus voltages', **kw)[source]

Voltage magnitude [p.u.] at all buses, or at a selection (buses=["1", "3"], buses="bus3*").

Parameters:

buses (object)

hermess.analysis.plot_active_power(dae, who=None, ax=None, title='Active power', **kw)[source]

Active power [MW] injected by every machine and converter, or by a selection.

Parameters:

who (object)

hermess.analysis.plot_modes(dae, ax=None, label=None, color=None, damping_ref=0.05, fmax=None, xlim=None, annotate=False, **kw)[source]

s-plane scatter of the eigenvalues (imaginary axis in Hz).

fmax and xlim zoom into the slow part of the spectrum, e.g. plot_modes(dae, fmax=5, xlim=(-6, 0.5)) for the electromechanical modes. Call twice with different label/color on the same ax to compare two operating points or two designs. annotate=True writes the mode ids.

Parameters:
  • label (str | None)

  • color (str | None)

  • damping_ref (float)

  • fmax (float | None)

  • xlim (tuple[float, float] | None)

  • annotate (bool)

hermess.analysis.plot_system(dae, ax=None, pos=None, figsize=None, bus_labels=True, device_labels='auto', color_by=None, at=None, legend=True, title=None, device_scale=1.0, annotate_branches=False, colors=None)[source]

Draw the system: the network, and every device with its model type.

Buses are dots joined by their branches (transformer branches carry the usual two-circle glyph, and a branch opened by a disturbance is dashed). Each device hangs off its bus with the classic symbol – a circle with ∼ for a synchronous machine, a split square for a converter, an arrow for a load, a diamond for an SVC – colored by category and labeled with the model type.

Parameters:
  • pos (Optional[Mapping]) – {bus: (x, y)} to impose your own layout (e.g. the published geographic one). Omitted, the layout is computed from the topology and is deterministic, so figures are reproducible.

  • device_labels (str | None) – "idx" (names only), "type" (model types), "full" (both, plus the rating), None, or "auto" – full on small systems, names only once there are many devices.

  • color_by (str | None) – a signal selector resolving to one signal per bus (e.g. "bus*:v"); the buses are then colored by its value at time at (default: the end of the run), with a colorbar. Turns the diagram into a map of the result.

  • annotate_branches (bool) – write the sending-end active power on each branch.

  • colors (Optional[Mapping]) –

    override the per-category colors, e.g. {"machine": "k", "load": "0.4"} (categories as in the legend: machine, gfm, gsp, gfl, svc, infinite, load).

    plot_system(dae) # the system as drawn from the files plot_system(dae, color_by=”bus*:v”, at=1.02) # the voltage dip during a fault

  • bus_labels (bool)

  • at (float | None)

  • legend (bool)

  • title (str | None)

  • device_scale (float)

hermess.analysis.mark_events(ax, dae, label=True, color='0.5')[source]

Dashed vertical lines at the disturbance times stored on dae.events.

Return type:

None

Parameters:
  • label (bool)

  • color (str)

hermess.analysis.get_device(dae, key)[source]

Find a device by its idx (e.g. "SG1") or by class name (e.g. "GridForming").

Parameters:

key (str)

hermess.analysis.device_label(dev, unit=0)[source]
Return type:

str

Parameters:

unit (int)

hermess.analysis.frequency_hz(dae, dev, unit=0)[source]

Frequency of one device unit in Hz along the trajectory.

Synchronous machines: the rotor speed state omega (absolute p.u.). Converters: the converter frequency (omega_c) or the PLL frequency (omega_pll) the model itself published, evaluated over the stored run with the segment-aware snapshot machinery of hermess.results.extract_results(), so it stays correct across a SETPOINT disturbance and for user-defined strategies.

Return type:

ndarray

Parameters:

unit (int)

hermess.analysis.bus_voltage(dae, bus)[source]

Voltage magnitude [p.u.] at a bus along the trajectory.

Return type:

ndarray

Parameters:

bus (str)

hermess.analysis.state_index(dev, state, unit=0)[source]

Global index of a device state in the stacked state vector dae.x.

Return type:

int

Parameters:
  • state (str)

  • unit (int)

hermess.analysis.small_signal(dae, report=False, top_k=4)[source]

Run the eigenvalue / participation analysis at the operating point of the run (a single CasADi Jacobian plus numpy.linalg.eig) and return the list of mode dicts, least damped first. Set report=True to also print the built-in modal report.

Parameters:
  • report (bool)

  • top_k (int)

hermess.analysis.modal_table(dae, n=8, min_freq=None, max_freq=None, oscillatory_only=False)[source]

A pandas table of the modes: eigenvalue, frequency, damping ratio, dominant states.

Parameters:
  • n (int | None)

  • min_freq (float | None)

  • max_freq (float | None)

  • oscillatory_only (bool)

hermess.analysis.participation_table(dae, mode=1, top=10)[source]

Participation factors of one mode as a DataFrame (thin wrapper around dae.participation_table). mode is the id from modal_table().

Parameters:
hermess.analysis.state_matrix(dae, as_frame=False)[source]

The reduced state matrix A at the operating point (the linearization the eigenvalues come from), running the analysis first if needed.

Returns the raw (n, n) array, or a labeled DataFrame with as_frame=True.

Parameters:

as_frame (bool)

hermess.analysis.power_flow_table(dae, which='bus')[source]

The initial power flow as a DataFrame: which="bus" or "branch".

Thin wrapper around dae.grid.power_flow_tables(dae); the operating point the whole simulation starts from.

Parameters:

which (str)

hermess.analysis.copy_system(name, dest='systems', overwrite=False)[source]

Copy hermess/systems/<name> into <dest>/<name> and return dest.

Edit the copy, never the installed package. Pass the returned path as system_root to hermess.simulate(). Re-running without overwrite keeps your edits. A dest inside the shipped systems folder (hermess.SYSTEMS_DIR) is refused with a PermissionError before anything is touched.

Return type:

Path

Parameters:
  • name (str)

  • dest (str | Path)

  • overwrite (bool)

hermess.analysis.show_system(root, name, which=('sim_param.txt', 'sim_dist.txt'), strip_license=True)[source]

Print the system files (without the license header) for reading.

Return type:

None

Parameters:
  • root (str | Path)

  • name (str)

  • which (Sequence[str])

  • strip_license (bool)

hermess.analysis.set_param(root, name, idx, **values)[source]

Change parameters of one device record in <root>/<name>/sim_param.txt.

A text-level edit of the system file, keeping everything else in the record untouched. Example: set_param(root, "3bus_loadstep", "GFMI2", Kp=0.05) or set_param(root, "3bus_loadstep", "GFMI2", angle='"VSM"', H_v=2.0). A parameter that does not exist yet is appended to the record. Numbers are written with repr; pass strings with their quotes (angle='"VSM"'). The systems shipped with the package are read-only: a root inside hermess.SYSTEMS_DIR raises a PermissionError (make a copy with copy_system() first).

Return type:

None

Parameters:
  • root (str | Path)

  • name (str)

  • idx (str)

hermess.analysis.set_disturbances(root, name, rows)[source]

Overwrite sim_dist.txt of a local system copy with the given Disturbance rows.

rows is a list of strings (or one multi-line string), e.g.:

set_disturbances(root, "3bus_loadstep", [
    'Disturbance, time = 1.0, type = "FAULT_BUS", bus = "2", y = 20',
    'Disturbance, time = 1.1, type = "CLEAR_FAULT_BUS", bus = "2"',
])

The systems shipped with the package are read-only: a root inside hermess.SYSTEMS_DIR raises a PermissionError (make a copy with copy_system() first).

Return type:

None

Parameters:
  • root (str | Path)

  • name (str)

  • rows (Iterable[str] | str)

hermess.analysis.read_events(root, name)[source]

Parse sim_dist.txt into (time, type, where) tuples, in time order.

A finished run already carries the same list as dae.events; this reads it from the files, e.g. before simulating.

Return type:

list[tuple[float, str, str]]

Parameters:
  • root (str | Path)

  • name (str)

User models#

hermess.register(cls, name=None, kind=None)[source]

Make a user-defined model selectable from a system file.

Parameters:
  • cls (type) – The class to register: a device (subclass of Element) or a strategy (subclass of AVR, Governor, PSS, Shaft, Filter, AngleSource, VoltageControl, InnerControl or PLL).

  • name (str | None) – The keyword to select it by. Defaults to the class name; for a device the class name is the only accepted spelling, so name is ignored there.

  • kind (str | None) – Normally inferred from the base class. Pass it only to force a registry (e.g. for a class that subclasses several bases).

Return type:

type

Returns:

cls, so register() can be used as a decorator.

Registering the same name twice replaces the earlier entry, which is what you want while iterating on a model in a notebook.

Example:

@hermess.register            # a device: selected as  MyLoad, bus = "2"
class MyLoad(DeviceRect):
    ...

hermess.register(VSMAngle, "VSM")   # a strategy: selected as  angle = "VSM"
hermess.registered(kind=None)[source]

What can be selected from a system file today, shipped and user-defined.

registered() returns {kind: [names]} for every strategy axis plus the user-registered devices; registered("angle") narrows it to one axis.

Return type:

dict[str, list[str]]

Parameters:

kind (str | None)

hermess.unregister(name, kind)[source]

Remove a previously registered model (mostly useful in tests).

Return type:

None

Parameters:
  • name (str)

  • kind (str)

Errors#

class hermess.SimulationCancelled[source]

Raised inside hermess.system.DaeSim.simulate() when the progress callback requests cancellation (by returning False).

Interactive help#

hermess.help()[source]

Prints an overview of the hermess package and usage instructions.

Return type:

None