GUI post-processing: a result file to the plot, the schematic and the live plot¶
What happens to a result HDF5 between the solver writing it and a curve or a
coloured pipe appearing: h5_reader turns the file into a RunResult, the
run model hands it to the main window, and the main window gives it to the
plot dock and the network view. Each step has its own answer to a missing
group, an empty file and a run that is still writing. The contracts of every
function below are in their docstrings (the standard is
Function contracts and flow charts).
Units are the file's, nothing is converted on the way: Pa, K, kg/m^3, m/s,
kg/kg, W/m^3, metres, seconds. The plot dock converts only the x axis of a
time series, seconds to crank angle in degrees (theta0 + omega t in rad,
shown in degrees, minus the case's TDC offset). Temperature is the solver's
own Pipe.temperature_cells() where the file stores it (written since
2026-09-28). A traced row with no stored T is shown as p / (rho R) with
each cell's R taken from the stored final temperature (traced_gas_constant,
since 2026-10-11): exact for a constant R and at the final frame, approximate
at earlier times on a mixture or real-gas pipe. An older file has no stored
T at all; both views then use the file's single R, which is wrong for a
real-gas or mixture pipe. A cell with rho <= 0 shows NaN.
From the file to a RunResult¶
What enters: a path (the run's output, or Load Results). What leaves: a
RunResult, or an exception the caller turns into a message. The live view
re-reads the same file while the solver appends to it: write_hdf5 resizes
the datasets and writes time_series/t last, so t is the commit marker.
flowchart TD
F["result HDF5, written by write_hdf5"] --> L{"load_now or _poll_tick"}
L -- "Load Results, Reload, auto-load after a run" --> LN["load_now: set path, load_run"]
L -- "timer, file size or mtime moved" --> PT["_poll_tick: load_run_incremental"]
LN --> LR{"load_run opens the file"}
LR -- "missing, folder, empty, truncated, locked" --> X1["FileNotFoundError or OSError"]
LR --> FS["_read_final_state: pipes and junctions"]
FS -- "pipe without x, rho, u or p" --> X2["KeyError naming the dataset"]
FS --> TS{"time_series group with a t dataset?"}
TS -- "no group, or no t: still being created" --> OUT0["RunResult, time_series None: final state only"]
TS -- yes --> PS["_pipe_series and _junction_series, partitioned by _junction_partition"]
PS -- "traced pipe without rho, u or p" --> X2
PS --> OUT["RunResult with a trace"]
X1 --> SHOW["Load Results and Reload: a box naming the file; auto-load: a console line"]
X2 --> SHOW
PT --> INC{"load_run_incremental: previous run, length of t, _cached_tail_matches"}
INC -- "no previous run, shorter t, or a different tail value: a new run overwrote the file" --> LR
INC -- "file locked or half written" --> SWALLOW["swallowed: the next tick tries again"]
INC --> TAIL["cached arrays plus rows n_read onward"]
TAIL -- "a pipe, junction or field appeared or vanished, or a length disagrees with t" --> LR
TAIL --> OUT
OUT0 --> H["RunModel emits runLoaded"]
OUT --> H
H --> W["_on_run_loaded, one guard per dock"]
A trace caught between the dataset resize and the t resize comes back with
pipe arrays longer than t. Nothing clips them: the profile still draws (it
indexes by the slider), the time-series plot raises, and the next tick
repairs it. A trace with no samples is treated as untraced by both views.
What the plot dock draws¶
What enters: the run, the case, the layout (probes), the selection and the mode buttons. What leaves: one matplotlib figure and the data behind it for the CSV, PNG and audio exports. Every branch that cannot draw says why on the canvas instead of raising.
flowchart TD
S["set_run, select_component, set_case, a toolbar signal"] --> R{"_refresh"}
R -- "a BC is selected" --> BC["_render_bc: schedules before any run, end cell after"]
R -- "no run or nothing selected" --> M1["message: Run a case + select a component to plot"]
R -- "pipe" --> P{"_render_pipe"}
R -- "junction" --> J["_render_junction: trace, else the final value as text"]
R -- "cylinder" --> C["_render_cylinder: trace, else the final value as text"]
R -- "another kind" --> M2["message: No plot for kind"]
P -- "pipe not in the run" --> M3["message: Pipe not in run output"]
P -- "profile mode" --> PR["_render_pipe_profile: var(x) at the slider time"]
P -- "time series, untraced" --> M4["message: Time series requires --trace-dt"]
P -- "time series" --> TSR["_render_pipe_timeseries: var(t) or var(theta) per probe"]
PR --> Y{"_pipe_profile_y"}
Y -- "variable absent" --> M5["message: Variable missing for this pipe"]
Y --> AX["axes: pad a constant range, species at least 0, Fix Y lock, no offset notation"]
TSR --> TA{"_timeseries_at_frac"}
TA -- "no cells or variable not traced" --> M5
TA --> XK{"_x_axis_kind"}
XK -- "crank angle and a crank" --> CAD["_t_to_cad, then _apply_cad_range: a reversed range, or one bound past the data, is dropped: axis back to auto, log warning"]
XK --> AX
CAD --> AX
AX --> EX["_stash_payload: PNG, CSV and audio exports on"]
M1 --> D["_draw_message: no ticks, exports off"]
M2 --> D
M3 --> D
M4 --> D
M5 --> D
An exception from a renderer is not caught in the dock. From a Qt signal Qt
prints it and the plot keeps its last picture; from set_run it reaches
_on_run_loaded, which logs it as a warning and goes on to the network view.
What the network view does with a run¶
What enters: a run and the case Layout, on every live poll (about every
half second during a run). What leaves: actors in a PyVista plotter. The
actors are rebuilt only when the topology key changes; a poll with new
samples only rewrites the colour scalars.
flowchart TD
A["show_run: run, layout"] --> G{"plotter torn down and dock hidden?"}
G -- yes --> KEEP["keep the run, build nothing: showEvent replays it"]
G -- no --> K{"_topology_key_for changed, or no actors?"}
K -- yes --> B["_clear_actors, _compute_chain_packed_positions, _build_pipe_actors, _build_cylinder_actors"]
K -- "no: same pipes, cells, cylinders, layout revision" --> V["_refresh_var_combo"]
B --> V
V --> SL{"_sync_slider_and_colours"}
SL -- "run is None" --> BL["slider off, readout blank, legend removed"]
SL -- "trace of two or more samples" --> ON["_on_slider: readout, sample_changed, refresh_colours, pistons"]
SL -- "no trace, one sample, or a trace with no samples" --> UT["readout t_final, refresh_colours from the final state"]
ON --> RC["refresh_colours"]
UT --> RC
RC --> SV["_sample_variable per pipe: trace row at the slider, else the final state"]
SV -- "species the pipe lacks" --> NAN["NaN, drawn in the no-data colour"]
SV --> RNG{"colour range"}
RNG -- "Fix Y" --> FR["_fixed_range_for: finite values over the whole trace"]
RNG -- "per frame" --> PF["finite min and max, a constant widened by 1"]
FR --> W["_write_scalars into the kept meshes, one legend for all pipes"]
PF --> W
The chain packer (_pack_rows_horizontally, _propagate_x_topologically,
_rank_rows_vertically, _place_cylinders) positions pipes by length, not by
the editor's slots: a pipe with no length in the file takes no width, a pipe
missing from the layout falls back to a row stack, and an empty case places
nothing and draws nothing.
The live-plot dock (LivePlotDock) is not part of this flow. It does not
read a file: a caller registers a probe function and passes on_step to the
solver loop, and nothing in the main window does that today.
Functions¶
aeolus1d_gui/model.py::RunModel.load_nowaeolus1d_gui/model.py::RunModel._poll_tickaeolus1d_gui/app.py::MainWindow._on_run_loadedaeolus1d_gui/app.py::MainWindow._auto_load_last_runaeolus1d_gui/app.py::MainWindow._reload_last_runaeolus1d_gui/post/h5_reader.py::load_runaeolus1d_gui/post/h5_reader.py::load_run_incrementalaeolus1d_gui/post/h5_reader.py::_read_final_stateaeolus1d_gui/post/h5_reader.py::_pipe_seriesaeolus1d_gui/post/h5_reader.py::_junction_seriesaeolus1d_gui/post/h5_reader.py::_junction_partitionaeolus1d_gui/post/h5_reader.py::_cached_tail_matchesaeolus1d_gui/post/h5_reader.py::variable_namesaeolus1d_gui/post/h5_reader.py::derived_Taeolus1d_gui/post/h5_reader.py::pipe_Taeolus1d_gui/post/plot_dock.py::Plot2DDock.set_runaeolus1d_gui/post/plot_dock.py::Plot2DDock._refreshaeolus1d_gui/post/plot_dock.py::Plot2DDock._render_bcaeolus1d_gui/post/plot_dock.py::Plot2DDock._render_pipeaeolus1d_gui/post/plot_dock.py::Plot2DDock._render_pipe_profileaeolus1d_gui/post/plot_dock.py::Plot2DDock._render_pipe_timeseriesaeolus1d_gui/post/plot_dock.py::Plot2DDock._render_junctionaeolus1d_gui/post/plot_dock.py::Plot2DDock._render_cylinderaeolus1d_gui/post/plot_dock.py::Plot2DDock._pipe_profile_yaeolus1d_gui/post/plot_dock.py::Plot2DDock._timeseries_at_fracaeolus1d_gui/post/plot_dock.py::Plot2DDock._x_axis_kindaeolus1d_gui/post/plot_dock.py::Plot2DDock._t_to_cadaeolus1d_gui/post/plot_dock.py::Plot2DDock._apply_cad_rangeaeolus1d_gui/post/plot_dock.py::Plot2DDock._stash_payloadaeolus1d_gui/post/plot_dock.py::Plot2DDock._draw_messageaeolus1d_gui/post/network_view.py::NetworkViewDock.show_runaeolus1d_gui/post/network_view.py::NetworkViewDock.showEventaeolus1d_gui/post/network_view.py::NetworkViewDock._topology_key_foraeolus1d_gui/post/network_view.py::NetworkViewDock._clear_actorsaeolus1d_gui/post/network_view.py::NetworkViewDock._compute_chain_packed_positionsaeolus1d_gui/post/network_view.py::NetworkViewDock._pack_rows_horizontallyaeolus1d_gui/post/network_view.py::NetworkViewDock._propagate_x_topologicallyaeolus1d_gui/post/network_view.py::NetworkViewDock._rank_rows_verticallyaeolus1d_gui/post/network_view.py::NetworkViewDock._place_cylindersaeolus1d_gui/post/network_view.py::NetworkViewDock._build_pipe_actorsaeolus1d_gui/post/network_view.py::NetworkViewDock._build_cylinder_actorsaeolus1d_gui/post/network_view.py::NetworkViewDock._refresh_var_comboaeolus1d_gui/post/network_view.py::NetworkViewDock._sync_slider_and_coloursaeolus1d_gui/post/network_view.py::NetworkViewDock._on_slideraeolus1d_gui/post/network_view.py::NetworkViewDock.refresh_coloursaeolus1d_gui/post/network_view.py::NetworkViewDock._sample_variableaeolus1d_gui/post/network_view.py::NetworkViewDock._fixed_range_foraeolus1d_gui/post/network_view.py::NetworkViewDock._write_scalarsaeolus1d_gui/post/live_plot_dock.py::LivePlotDock.on_step