Skip to content

GUI

aeolus1d_gui is a PySide6 + PyVista visual editor + post-processor for aeolus1d cases. It reuses the same aeolus1d.io.schema dataclasses as the solver so the editor never holds a separate model — what you see on the canvas IS the spec the CLI runs.

Launch

./aeolus1d_gui/run_gui.sh
# or
python -m aeolus1d_gui
# or, if installed with [gui] extra:
aeolus1d-gui

On first launch a Welcome dialog walks through the four panels. Help → Quick Start (F1) reopens it later.

The four panels

Palette (left dock) — every component aeolus1d supports, grouped by category (aeolus1d_gui/shell/palette.py):

  • Geometry: Pipe, Plenum Junction, Corberán Junction, EHR Junction
  • Boundaries: a single generic Boundary Condition button. Drop it on (or near) a pipe end and pick the kind in the Inspector — the schema accepts ~13 kinds (wall / transmissive / stagnation / mass- flow / velocity inlets, pressure outlet, transient variants, non-reflecting LODI, and the two valve kinds); the drop defaults to wall and every kind is editable to any other after drop, so a button-per-kind was collapsed into one.
  • Diagnostics: Probe — a time-series probe at a fractional position along a pipe. Drag it onto a pipe block and the drop's x-fraction is captured automatically; the Plot dock turns it into a var(t) curve once the run is traced (--trace-dt).
  • Engine: Cylinder, Compressor, Turbine, Shaft

Click a tile to add the component at the canvas centre, or drag it onto the canvas to drop at the cursor.

Canvas (centre) — every component renders as an extruded 2D silhouette specific to its kind (pipe = horizontal capsule, junction = diamond, cylinder = vertical pill, BC = chevron, shaft = thin bar, compressor / turbine = bowtie), coloured by family and centre-stamped with a single-letter glyph (P/J/C/B/S/K/T) so the kind reads at a glance even when zoomed out. Lighter-tinted port spheres on each block edge are the connect points; a soft drop shadow sits below every block for depth, and a dark outline traces the silhouette boundary for crisp definition against the canvas background.

Several decorations encode spec values directly on the block:

  • Pipe n_cells → vertical tick marks across the top of the capsule (capped at 10).
  • Pipe with a [catalyst] block → green corner badge (top-right).
  • Pipe with a [filter] / [dpf] block → grey corner badge (top-left).

Connection lines are smooth cubic beziers in the source node's family colour, with a cone arrowhead at the midpoint pointing along physical flow direction (aeolus1d convention: pipe left = upstream, right = downstream). An opt-in flow animation (Preferences → Canvas → "Animate flow on edges", default off) walks a small cream / amber dot along each connection at ~30 fps when you want a live indication of which way gas is going.

Hover any node to pop a small tooltip with the component id + 2–3 of its key spec values (pipe length + n_cells + catalyst / filter presence, junction kind + volume, cylinder bore × stroke + ω, BC kind + end, turbo kind + vane).

The view is locked to top-down 2D with a subtle gradient background (theme-tinted), a minor/major XY grid overlay, and an aeolus1d · 2D editor watermark in the lower-left corner; middle-drag pans, right-drag and the mouse wheel zoom, rotation is disabled.

Inspector (right dock) — when a component is selected, a Qt form auto-generates from its dataclass schema (Literal → combo, nested dataclass → group, list[float] → comma-separated text). Edits write back into the live spec; the case is marked dirty and connections re-render to track field changes (e.g. swapping a pipe's length). The right column also tabs in a stack of docks: a Component-list dock for select-by-name, a Network View, a 2D Plot dock with Fix-Y lock + probe overlay + PNG/CSV export, a Modes dock (FEM eigenfrequencies), a Phase-44 Optimize dock (described below), a Sensitivity dock (parameter ranking that pairs with Optimize), a CFL analyzer dock, a Case Settings dock (the full run-settings form, mirrored with the runner-dock quick-strip; raise it with Ctrl+E), a Turbo-map dock (renders the [[turbo]] compressor / turbine performance maps), a read-only TOML preview dock, and a Solver Internals dock (Phase B17-gui / B17-gui-live) showing a per-pipe table of closure-Jacobian routing (analytic ✓ vs FD) across friction / heat / porous. The dock has a "Refresh with built network" button for opt-in live mode that scans pipe.py_friction / py_heat / py_porous overrides post- build_network and forces those cells to FD; structural edits reset the dock to spec mode automatically.

Basic vs Advanced settings

Several schema dataclasses carry dozens of knobs the typical case never touches (chemistry tolerances, implicit-solver inner loops, QS-driver tuning, adaptive-Δt deadbands, …). Rendering every field flat overwhelms the Inspector on a fresh case, so the schema declares a BASIC_FIELDS class-var on each adopter and the form factory routes anything outside that set into a collapsed "▶ Advanced settings…" expander below the basic rows. Clicking the expander reveals the rest; the arrow flips to ▼ so the state reads at a glance. The split is purely a presentation layer — see aeolus1d_gui/shell/form_factory.py (lines 486–532, 671–768) for the implementation and src/aeolus1d/io/schema.py for the per-dataclass BASIC_FIELDS frozensets.

Important: the TOML on disk is untouched. Every CaseHeader field still round-trips through the strict TOML loader and aeolus1d expand writes every field at the top level regardless of whether it's marked basic or advanced. The split only changes which rows the GUI shows up-front; loading a case written by hand, by the CLI, or by an older GUI build works exactly as before.

Adopters as of MR !132 / !135 / !136:

Dataclass Basic fields Advanced (hidden)
CaseHeader 10 ~56
PipeSpec 5 11
JunctionSpec (kind + core) rest
BCSpec (kind + core) rest
FrictionSpec (kind + core) rest
HeatSpec (kind + core) rest
PorousSpec (kind + core) rest
CatalystSpec (kind + core) rest
FilterSpec (kind + core) rest
UreaSpec (kind + core) rest
SpeciesSpec (kind + core) rest
EOSSpec (kind + core) rest
ThermalWallSpec (kind + core) rest

CaseHeader's 10 basic knobs are the ones that change run to run on a typical case: name, t_end, cfl, scheme, limiter, rk, solver, adaptive_dt, dt_fixed, chem_solver (see CaseHeader.BASIC_FIELDS in src/aeolus1d/io/schema.py lines 35–41). PipeSpec's 5 are the ones that define a pipe at all: id, length, n_cells, diameter, init. The 9 nested closure specs each expose their kind discriminator + the handful of routine knobs that go with that kind; everything else (model-specific correlation coefficients, alternative formulations, debug toggles) lives under Advanced. The form factory's kind dispatch — re-rendering when you change friction.kind from darcy to colebrook, for instance — replays the BASIC/Advanced split for the newly selected kind on every swap.

For the actual semantics of each field — defaults, units, which combinations are legal, when to reach for an advanced knob — see the run-settings reference (the case_settings chapter in this doc tree, plus implicit-solver-reference.md for the Newton / Picard knobs and cli.md for the chemistry / QS-driver flags). The Inspector and the reference docs share src/aeolus1d/io/schema.py as the single source of truth.

Runner (bottom dock) — Run / Cancel plus Pause / Resume buttons, a "Resume from latest saved time step" checkbox (passes --resume <last_h5> to the next run when a prior HDF5 exists), a run-settings strip (t_end / cfl / trace-dt / scheme / limiter / RK integrator combos, plus adaptive-Δt / CFL-ramp / HLLC low-Mach / chem-interval knobs that write back into case.case on edit), and a dark console. Pressing F5 (or Run) saves the case to a temp directory, launches aeolus1d run as a subprocess, and streams stdout (grey) / stderr (red) into the console. A pre-flight check (_scheme_compatibility_error → validate_case_pre_run) rejects known incompatible combinations up front instead of letting the subprocess crash — e.g. an explicit RK scheme with cfl > 1, which exceeds the ~1.0 acoustic-CFL ceiling and would produce a vacuum cell after a few hundred steps. (MP5 now covers every closure family and the network timeloop, so it is no longer flagged.) On a clean exit (code 0), the post-processing tab automatically loads the output HDF5 and renders each pipe as a coloured tube, sharing a single colour scale across all pipes.

View → Panels — the togglable right-pane docks (Inspector, Network View, 2D Plot, Modes, Optimize, Sensitivity, CFL, Solver Internals, Case Settings, Turbo-map, TOML — see _right_pane_panels in aeolus1d_gui/app.py) can each be shown or hidden individually from the View → Panels submenu, one checkable entry per dock. Each entry is the dock's own toggleViewAction(). Closing a dock with its window × unchecks it here, and re-checking brings it back. The hidden set persists across sessions, so a layout you pare down to just Inspector + Plot reopens the same way next launch.

View → Show canvas — unchecked, it hides the editor canvas (the central view) and the docks take the whole window, which gives a plot or the network view the full screen. The case, the selection and the camera are kept while the canvas is hidden; re-check the entry to bring it back. The choice persists across sessions (canvas_visible in the GUI's QSettings), like Snap to grid. The shortcut is Ctrl+Shift+C, and the toolbar's Canvas check box does the same. While the canvas is hidden, Fit view, Reset view, Probe mode and Organize are greyed out.

Id fields are drop-downs. A field that names another component is picked from a list, not typed: the pipe of a BC, port, cylinder port or probe; a turbo's inlet and outlet pipe and its shaft; and a controller's pipe fields, its turbine (turbines only) and its cylinder. An optional reference (a turbo's shaft, the controller fields) has a blank entry that unsets it. A reference to a component that no longer exists is shown and kept, so an old case loads unchanged.

The three docks this submenu does not list — the Palette (left), the Component list, and the Runner (bottom) — have no × on their title bar. A panel is closable only if something can re-open it; those had no menu entry, so closing one was a one-way door that saveState then carried into every later session. They are still movable and floatable, just not closable. _lock_unrecoverable_panels derives that set from _right_pane_panels, so a dock added later stays un-closable until it is registered in the submenu.

Interactions

Action Shortcut
New case Ctrl+N
Open / Save / Save As Ctrl+O / Ctrl+S / Ctrl+Shift+S
Open Recent → submenu (lazy)
Add Pipe Ctrl+P
Delete selected Del
Undo / Redo Ctrl+Z / Ctrl+Shift+Z
Run / Cancel F5 / Esc
Fit view F
Quick Start F1
Preferences Ctrl+,
Quit Ctrl+Q

Drag a yellow port sphere to a compatible port on another component to wire them; the rubber-band line turns red on illegal targets. The connection rules:

  • Pipe ends connect to BC target, junction in, turbo inlet/outlet, cylinder ports.
  • Shaft port connects to compressor / turbine shaft.
  • A pipe end can host at most one consumer (single-consumer rule).

Importers

File → Import wraps the third-party case importers as menu actions, so you can bring a foreign model onto the canvas without touching the CLI:

  • AVL Cruise M (.proj) — aftertreatment-only models map cleanly; vehicle / hybrid models report what they dropped.
  • AVL Boost (.bws) — best-effort structural import.
  • GT-Power SIF (.xml) — GT-Power Standard Input File.
  • GT-Suite (.gtm) — the native project file (distinct from the legacy SIF export); resolves sibling .gtsub sub-assemblies recursively so engine templates (cylinder / compressor / turbine / shaft / injector) materialise as aeolus1d components.
  • Ricardo WAVE (.wvm).

Opening a [vehicle] drive-cycle TOML (e.g. 85_vehicle_drive_cycle) is a special case: instead of loading it onto the pipe-network canvas the GUI detects the vehicle schema (aeolus1d.io.vehicle_case) and pops a dedicated Vehicle case dialog for editing the [vehicle] / [driver] / driveline blocks.

Schedule fields and CSV / Parquet import

Several boundary conditions carry piecewise-linear schedules — (t, value) pairs the solver interpolates at runtime. Examples: mdot_t / mdot_v on a transient mass-flow inlet, p_back_t / p_back_v on a transient pressure outlet, and Y_t / Y_v for a time-varying inlet species concentration.

The Inspector renders each detected pair as a single two-column table (t, value) with row +/− buttons — no need to type comma-separated lists. Two import options live above the table:

  • Load CSV / Parquet… on each schedule widget — pick one file and one column for the time + value. The picker dialog shows a preview of the first 5 rows so you can sanity-check the parse. Bad rows (lines that don't parse as numbers) are dropped with a warning that lists the offending line numbers.
  • Load all schedules from CSV / Parquet… appears at the top of the inspector panel when a BC has 2+ schedule pairs (e.g. the transient mass-flow inlet with NH3 + NO species schedules). One click maps every schedule to a column in the same multi-column file — useful for fitting against measured experimental data where ṁ, T₀, Y_NH3, Y_NO arrive as a single CSV / Parquet log.

Parquet (.parquet / .pq) is read via pyarrow — gas- analyser exports often use Parquet for efficient compression of long time-series. Install pyarrow (or the [gui] extras group) to enable it.

Optimize dock (Phase 44)

The right column has an Optimize tab that drives the aeolus1d optimize subprocess. Workflow:

  1. Load TOML — pick a case file with an [optimize] block (see the CLI reference for the schema). The dock populates two tables: parameters (path / theta0 / lo / hi) and probes (pipe / x_frac / var / metric / reference_csv). Both are inline-editable.
  2. Edit — type in the cells, + Add / − Remove rows, Browse CSV… to pick a reference file (stored relative to the loaded TOML when possible). Save edits rewrites the [optimize] block in place; everything else in the file is left byte-for-byte unchanged.
  3. Run — auto-saves the edits, launches aeolus1d optimize in a subprocess, streams progress into the convergence plot (log-y when J spans more than two decades) and a status line showing the current (theta, J). On success, the optimum is written to a sibling *.tuned.toml next to the input.
  4. Cancel — terminates the subprocess; the dock returns to idle without modifying the file.

The dock pairs with the 2D Plot dock's CSV export — drop probes on a pipe, run, "CSV" to export the time-series, then point the optimizer at that file as a reference. That's the closed-loop calibration path: measure → tune → re-measure.

Theme

Edit → Preferences (Ctrl+,) opens a multi-page dialog whose Appearance page has a Theme dropdown with seven modes (see ThemeMode / _THEME_REGISTRY in aeolus1d_gui/shell/theme.py): System default, Light, Dark, Neumorphism, Neumorphism Dark, Glassmorphism, and Glassmorphism Dark. System default restores Qt's platform palette; Light / Dark force a Fusion-styled palette; the neumorphism modes are soft, low-contrast, extruded- control themes and the glassmorphism modes are frosted / translucent (both approximated with QSS qlineargradient fills and bevel borders, since Qt widget-QSS has no true blur / box-shadow). The choice persists in $AEOLUS_GUI_CONFIG_DIR/theme.json (default ~/.config/aeolus1d_gui/theme.json) and is applied on startup. The canvas, console, labels, grid lines, drop shadows, and flow-dot accents all pick contrast-appropriate colours from the selected palette. The canvas background is a subtle vertical gradient biased toward indigo at the top on dark mode (light at the top on light mode) — picked so the silhouettes pop without the bg competing for attention.

The Appearance group also has a Canvas: Animate flow on edges checkbox (default off). When enabled, a small cream yellow (dark theme) or deep amber (light theme) dot walks every connection at ~30 fps in the direction of physical flow.

Layout sidecar

When you Save the case, the GUI writes the TOML AND a sibling <case>.layout.json describing component positions, camera state, and probe points (layout_version 2 — see aeolus1d_gui/io/layout_io.py). The theme is not stored here; it lives in the separate theme.json GUI-prefs file. The CLI ignores the sidecar — it's GUI-only metadata so the visual layout you crafted survives across sessions.

Recent files

File → Open Recent shows the last 10 cases you opened or saved. Persisted in ~/.config/aeolus1d_gui/recent.json. Missing files prompt a clean dialog instead of silently failing.

Documentation browser

Help → Documentation (Ctrl+H) opens the bundled docs inside the GUI — the same docs/*.md pages as the published site, rendered Qt-native (LaTeX math included) so you can read the tutorials, theory, and CLI reference without leaving the app or running mkdocs serve. A file tree on the left mirrors the docs/ layout; "Open in browser…" hands the current page to your system browser when you'd rather use the styled site.

A search box sits above the tree (Ctrl+F focuses it). Type two or more characters and it runs a full-text search across every page, showing ranked results with a one-line context snippet under each title. The query is tokenised the same way the published site indexes it — splitting on whitespace, -, ., _, / — so a symbol-heavy identifier like flow_nu or gas_property_correction is found by its parts, and multiple terms are AND-matched (every term must appear on the page). Click a result to jump to that page with all matches highlighted; clearing the box restores the tree.

Implementation notes (for contributors)

The editor canvas is split across a small set of modules that together own everything you see on the canvas:

Module What it owns
aeolus1d_gui/editor/canvas.py CanvasController — the PyVista widget, the 2D-lock interactor style, drag / drop / pick / select / connect interactions, the gradient background + grid + watermark, the connection-line bezier + arrowhead build, the hover-tooltip pick path, the flow-animation QTimer. The integration layer; doesn't own visual constants.
aeolus1d_gui/editor/nodes.py PvNode — one node's actor + outline + shadow + symbol + label + port markers + decoration actors. Per-node lifecycle (add_to_plotter / remove_from_plotter / move_to / refresh_*).
aeolus1d_gui/editor/visual_style.py Every visual constant — kind palette, halo geometry, shadow + flow-dot accents, label typography, edge colours, the orient_polyline_for_flow direction-from-endpoint-strings helper. Single source of truth; the other editor modules import from here.
aeolus1d_gui/editor/silhouettes.py Per-kind 2D outline factories + extruded PolyData (silhouette_for_kind), outline polylines (outline_polyline_for_kind), shadow planes (shadow_for_kind). Pure-Python outline → triangulated → extruded; no Qt dependency.
aeolus1d_gui/editor/roles.py What a pipe contains, from its spec (classify(kind, spec, ctx)). Pure — no Qt, no pyvista. The single owner of that question, shared by the canvas decorations, the hover tooltip and the docs generator, so they cannot disagree. Pipe-only: non-pipe kinds return no roles.
aeolus1d_gui/editor/decorations.py Spec-driven annotation glyphs (decorations_for_spec(kind, spec)), drawn by the active icon style in icon_styles.py — the default schematic style: role tint, family pattern, rims, urea band, n_cells tick marks (the [catalyst] / [filter] corner badges are the legacy style). Returns (suffix, polydata, colour, mode) tuples; PvNode owns the actor lifecycle.
aeolus1d_gui/post/schematic_style.py Separate post-run schematic canvas (SchematicCanvas, QPainter-based) plus the Qt-side reference iconography that inspired the editor silhouettes. Not on the editor canvas; opt-in via its own menu item.

If you're adding a new component kind, the touch points are: register the silhouette outline in silhouettes._OUTLINES, add a family colour + glyph symbol to visual_style._GLYPH_COLOURS / _GLYPH_SYMBOLS, add a hover-tooltip branch to canvas._format_node_hover, and (optionally) add spec-driven decorations to decorations.decorations_for_spec.

If instead you are adding a new closure kind that a pipe can carry (a reaction, catalyst, filter or porous kind), the touch point is editor/roles.py: map it to a role, or add it to roles.UNMAPPED with a reason. test_roles_cover_registry fails otherwise — deliberately, because an unmapped kind renders as a blank pipe and nothing else would notice.

If the new kind should also get its own canvas pattern or tint, add it to editor/icon_styles.py, then re-run python scripts/generate_icon_reference.py --write — docs/icon-reference.md is generated and tests/unit/test_icon_reference_current.py fails when it is stale. The PvNode lifecycle picks up the new kind automatically — it dispatches via self.kind strings, no per-kind branching.

If you're tuning visuals, almost everything ships as a constant in visual_style.py (block half-extent / thickness lives on nodes.py to avoid the circular import). Move there first; the modules that need a value re-export it for backwards compat.

Troubleshooting

The canvas is sensitive to the underlying X / OpenGL stack. If you're on WSL and components don't render or the canvas crashes, see the Installation → WSL / Windows section for the standard fixes.