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 byhermess.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 fromlist_systems(), or a folder name undersystem_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 thanNonecancels the run by raisingSimulationCancelled.init_callback (
Any) – Optional hook called once with the initialized model at the operating point, before the time stepping; a falsy return other thanNonecancels the run (e.g. after inspectingdae.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 ofConfig(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:
- Returns:
The
DaeSimholding 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 astestsystemfilein aConfig).- 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
SimulationResultsfrom a finishedDaeSim.Expects the per-bus and per-device result dictionaries to be filled, i.e.
save_datahas run (ashermess.run.run()does after the simulation).- Parameters:
dae – The finished model, e.g. the return value of
hermess.simulate().config – The
Configof the run, recorded for provenance (optional).
- Return type:
- 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")orsignals(dae, "*:f")filters with the same selector the plots take;kindfilters 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
tplus 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
axto 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.
colorsgives 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).
fmaxandxlimzoom 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 differentlabel/coloron the sameaxto compare two operating points or two designs.annotate=Truewrites 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 timeat(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.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 ofhermess.results.extract_results(), so it stays correct across aSETPOINTdisturbance 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. Setreport=Trueto 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).modeis the id frommodal_table().
- hermess.analysis.state_matrix(dae, as_frame=False)[source]
The reduced state matrix
Aat 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 withas_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 returndest.Edit the copy, never the installed package. Pass the returned path as
system_roottohermess.simulate(). Re-running withoutoverwritekeeps your edits. Adestinside the shipped systems folder (hermess.SYSTEMS_DIR) is refused with aPermissionErrorbefore 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)orset_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 withrepr; pass strings with their quotes (angle='"VSM"'). The systems shipped with the package are read-only: arootinsidehermess.SYSTEMS_DIRraises aPermissionError(make a copy withcopy_system()first).- Return type:
None- Parameters:
root (str | Path)
name (str)
idx (str)
- hermess.analysis.set_disturbances(root, name, rows)[source]
Overwrite
sim_dist.txtof a local system copy with the givenDisturbancerows.rowsis 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
rootinsidehermess.SYSTEMS_DIRraises aPermissionError(make a copy withcopy_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.txtinto(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 ofElement) or a strategy (subclass ofAVR,Governor,PSS,Shaft,Filter,AngleSource,VoltageControl,InnerControlorPLL).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, sonameis 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, soregister()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 returningFalse).
Interactive help#
- hermess.help()[source]
Prints an overview of the hermess package and usage instructions.
- Return type:
None