Analysis and plotting#
hermess.analysis turns a finished run into tables and figures with one
import. It is built around a single idea: every quantity in a run has an
address, owner:quantity, and everything that takes a “what to plot”
argument accepts the same selector.
from hermess.analysis import *
root = copy_system("3bus_loadstep") # editable local copy of a shipped system
dae = simulate("3bus_loadstep", system_root=root, T_end=5.0, quiet=True)
plot(dae, ["*:f", "bus*:v"])
Signal addresses#
SG1:omega a machine state GFMI2:Pc_tilde a converter state
SG1:f frequency [Hz] GFMI2:f frequency [Hz]
SG1:P power injection [MW] bus3:v voltage magnitude
bus3:theta voltage angle [deg] line1-2:P branch flow [MW]
signals() lists every address of a run with its unit
and meaning. Selectors are case-insensitive, understand * and ? globs,
and come in several shapes:
plot(dae, "SG1:omega") # one signal
plot(dae, "*:f") # that quantity on every device
plot(dae, "f") # same, the "*:" is implied
plot(dae, ["bus1:v", "bus3:v"]) # a list
plot(dae, "bus*:v") # a glob
plot(dae, {"SG1": ["omega", "delta"]}) # a dict
plot_states(dae, "GFMI2") # every state of one device
compare({"droop": d1, "VSM": d2}, "GFMI2:f") # across runs
An unknown name raises with suggestions. Buses and branches may be addressed
with or without their prefix ("3:v" is "bus3:v").
Signals come in five kinds: device state trajectories, private
algebraic variables, derived quantities evaluated from the model’s own
symbolic expressions (frequency, electrical power), and bus and branch
network quantities. The derived frequencies use the same segment-aware
machinery as hermess.results.extract_results(), so they stay correct
across a SETPOINT disturbance.
Tables and exports#
signals(dae) # the address book, as a DataFrame
get(dae, "*:f") # {name: numpy array}
to_dataframe(dae, "*:f") # DataFrame indexed by time
to_csv(dae, "f.csv", "*:f") # t plus one named column per signal
metrics(dae) # pre-event value, nadir/peak, final, max rate
summary(dae) # one paragraph: size, devices, disturbances
For the operating point and the small-signal analysis:
power_flow_table(dae) # the initial power flow ("bus" or "branch")
small_signal(dae) # run (if needed) and return the mode list
modal_table(dae) # eigenvalue, frequency, damping, dominant states
participation_table(dae, mode=1) # participation factors of one mode
state_matrix(dae, as_frame=True) # the linearization, labeled
The modes are those of the initial operating point, i.e. the system as it
was built and initialized at T_start, before any disturbance. A run that
contains a load step, a fault or a setpoint change reports the same modes
as the disturbance-free run of the same system, whether the analysis is
requested before the stepping (small_signal_analysis=True) or afterwards
from these helpers. The post-disturbance system is not analyzed.
Plotting#
plot() groups signals by unit into panels;
plot_states() shows one small panel per state;
compare() overlays several runs;
plot_frequency(),
plot_voltages() and
plot_active_power() are one-line shortcuts;
plot_modes() draws the s-plane; and
mark_events() adds the disturbance times to any axes.
The plots follow the active matplotlib style; pass color= (or colors=
on compare() and
plot_system()) to override.
plot_system() draws the single-line diagram of the
run: buses joined by their branches, each device with the classic symbol,
colored by category and labeled with its model type. color_by="bus*:v"
colors the buses by a result and turns the diagram into a map.
System files#
Text-level conveniences for working on a local copy of a system:
root = copy_system("3bus_loadstep") # copy out of the installed package
show_system(root, "3bus_loadstep") # print the files, license header stripped
set_param(root, "3bus_loadstep", "GFMI2", Kp=0.05, angle='"VSM"')
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"',
])
read_events(root, "3bus_loadstep") # [(time, type, where), ...]
A finished run carries the same event list as dae.events, and the resolved
configuration as dae.cfg. To run without terminal output, pass
quiet=True to hermess.simulate() (no progress bar, warnings-only
logging), or set the show_progress configuration field directly.
The full reference is in the API section.