Terminal UI

pyfds-evac-tui is an optional front end that runs in a terminal. You pick a scenario and an FDS folder, set the options, check the effective configuration, run, and read the results, all in six steps. It works in an SSH session on a remote machine, with no port forwarding.

The terminal UI runs the same model, with the same defaults, as the pyfds-evac command. It sets the same options and calls the same run path: stream_run → build_run_kwargs → run_scenario → apply_outputs. For a form in the browser, with charts and a trajectory replay, see the Web GUI.

⚠️
The run stops when the terminal closes. A closed window or a dropped SSH session ends the run, and it leaves no trajectory and no CSV files. For long runs, start the terminal UI inside tmux or screen, or use pyfds-evac from the command line. See When the terminal closes.

Install and start

Install the tui extra. It adds Textual (textual>=8.2). The terminal UI needs Python 3.12 to 3.14, like the package.

pip install "pyfds-evac[tui]"

In a source checkout, use uv:

uv sync --extra tui
uv run pyfds-evac-tui

Without the extra, the command stops with the line below. pyfds-evac-tui --help works without the extra.

pyfds-evac-tui needs the TUI extra, which is not installed (missing module '…'). Install it with: pip install 'pyfds-evac[tui]'

Start it in a folder with assets/

The terminal UI reads its examples from ./assets and writes runs under ./results, where ./ is the folder you start it in. The package ships no scenarios. Unpack the example zip of the Quickstart and start inside the unpacked folder, as for the Web GUI:

cd pyfds-evac-quickstart
pyfds-evac-tui
  • Results folder. Runs go to ./results, or to $PYFDS_EVAC_RESULTS_DIR when it is set. The terminal UI has no upload folder: it opens scenario files where they are.
  • Terminal size. The minimum is 80 × 24 characters. A smaller terminal shows “Terminal is W×H; the TUI needs 80×24. Resize, or zoom out. A run continues meanwhile.” At 120 × 35 or larger, the Run and Results steps show the plan view beside the progress bars. In a smaller terminal, v opens the plan full screen.
Themes

Two themes ship: evac-dark and solarized-light. The only command-line option is --theme {evac-dark,solarized-light}. The theme is chosen in this order:

  1. --theme;
  2. the environment variable PYFDS_EVAC_TUI_THEME;
  3. the last theme used, stored in recent.json (see Reproduce a run);
  4. evac-dark.

An invalid PYFDS_EVAC_TUI_THEME stops the start with PYFDS_EVAC_TUI_THEME='x' is not a theme; choose from evac-dark, solarized-light. To switch while the terminal UI runs, open the command palette (ctrl+k) and choose “Theme: evac dark” or “Theme: Solarized Light”. The choice is remembered.

Run a scenario

The first run uses ISO-table21 from the Quickstart zip, in clear air: one agent walks a corridor 100 m long to the exit.

  1. Scenario. On a first start the Examples tab is open, with ISO-table21 first. Press Enter. The terminal UI moves to the FDS step. After earlier runs the Recent tab opens instead: press shift+tab, → and tab to reach Examples, then Enter.
  2. FDS. ISO-table21 has no fire. Press Enter on No FDS (clear air). The terminal UI moves to Configure.
  3. Configure. Leave the defaults. Press ctrl+n to go to Review.
  4. Review. It reads “✓ Ready to run”. Press ctrl+r.
  5. Run. The bars fill while the run goes on. The terminal UI moves to Results when the run ends.

The sections below describe each step. The screenshots of the Scenario and Configure steps are the terminal UI’s snapshot tests, so they change when the screens change. The others come from a real run of assets/t_junction with the fire_2MW_PVC FDS output and seed 42; that FDS output was produced separately and is not in the repository.

The step bar

The top line marks each step with a glyph and a word, so it reads without colour:

MarkMeaning
✓done
!errors
⟳running
✎settings changed since the run
◐ (Results)incomplete
■ (Results)cancelled
✗ (Results)failed, or the run stopped without a result

1 Scenario

pyFDS-EvacpyFDS-Evac 1  2  3  4  5  6 RecentExamplesOpen file━━━━━━━━╸━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊type to filter▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊ISO-table21  deck only▎▊ISO 20414 Table 21 — reduced visibility vs walking speed▎▊iso_table21_coupled  FDS output found▎▊ISO 20414 Table 21, coupled to real FDS output▎▊t_junction  deck only▎▊T-junction test: smoke-blocked T-corridor▎▊  ├ config_discovery  deck only▎▊  ├ config_full  deck only▎▊  ├ config_initial_pre0  deck only▎▊  ├ config_initial_pre30  deck only▎▊  ├ config_initial_pre60  deck only▎▊▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎ISO-table21directory  agents 1   exits 1   max time 300 s   seed 420 ^n next ▏ ^q quit  ? keys  ^k palette 
The Scenario step (snapshot test test_snapshot_scenario, 80 × 24).

Three tabs choose the scenario:

  • Recent lists the last 10 pairs of scenario and FDS folder, each with its last status and date. Entries whose scenario or FDS folder is gone come last, dimmed, and say which: “scenario missing”, “FDS folder missing”, or both. When two rows would read alike, a short part of the scenario’s folder tells them apart. Enter restores the scenario and the FDS folder and jumps to Review. It restores no other setting (#534). If the scenario is gone, Enter stays on the Scenario step and says how to recover. If only the FDS folder is gone, Enter loads the scenario and opens the FDS step to choose another folder or none. Delete removes the highlighted entry from the list; nothing is removed automatically. Recent opens at start only when one of its entries can be used.
  • Examples lists every folder in ./assets that has a config.json, with its config_*.json variants, and a filter box. Each row says “FDS output found” or “deck only”.
  • Open file takes a .json, a .zip, or a folder with config.json. It browses as the FDS step does, starting in the folder you started the terminal UI in: the list shows the folders and the .json and .zip files, marks scenarios as scenario (a .json or .zip file, or a folder with a JSON and a WKT file), and offers .. to go up. Enter or a click on a scenario opens it; on any other folder it goes into it. Typing a path filters the list; Tab completes and ↓ goes to the list.

The info line shows the number of agents and exits, the maximum time and the seed.

2 FDS

pyFDS-EvacpyFDS-Evac 1 Scenario✓ › 2 FDS✓ › 3 Configure › 4 Review › 5 Run › 6 Results FDS output folder▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊/Users/Shared/pyfds-evac-demo/assets/t_junction/fire_2MW_PVC▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊Found FDS output  assets/t_junction/fire_2MW_PVC▎▊No FDS (clear air)  no fire input▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎/Users/Shared/pyfds-evac-demo/assets/t_junction/fire_2MW_PVC✓ extinction  ✓ co  ✓ co2  ✓ o2  ✗ temperature✗ integrated_intensityFDS output ends at 300.0 s; scenario runs to 300.0 s ✓L2  smoke FDS  gas FED  heat off  FIC off  reroute 1 s  vis smoky  ✓ valid ^n next  ^p scenario ▏ ^q quit  ? keys  ^k palette 
The FDS step with the fire_2MW_PVC output of assets/t_junction.
  • The step suggests the folders with a .smv file up to 2 levels below the scenario and below the start folder. You can also type a path, or choose No FDS (clear air).
  • Typing a path browses folders, as Emacs dired does. The list shows the subfolders of the typed folder that match the typed name as fzf does (fvs finds fic_vs_fed_speed; names that start with it come first), marks those with a .smv file as FDS output, and offers .. to go up. Enter (or a click) on an FDS output folder chooses it; on any other folder it goes into it. Tab completes the name as far as it is unique, as a shell does; ↓ goes to the list; ~ is the home folder. Hidden folders show when the typed name starts with a dot. Typing while the list has the focus goes to the path box, so the list narrows and ↑/↓/Enter still pick.
  • Choosing a folder reads its inventory in a separate process. The step shows ✓ or ✗ for extinction, CO, CO2, O2, temperature and integrated intensity, and the end time of the FDS output against the scenario’s time limit.
  • For a “deck only” example, the step says to run FDS first or to continue without fire. What your FDS case must provide lists the slices a run reads.

3 Configure

pyFDS-EvacpyFDS-Evac 1✓  2✓  3✓  4  5  6 ▼ Scenario & runOutput folderderived (results/<scenario>/…)…/ISO-table21/deterministic/seed420/20261003T120000Z  derived; fixed untilyou change a settingSeednot setdefaultRandom seed. Leave blank to use the scenario's own baseSeed, as run.pydoes. The same seed reproduces the same run; change it to get a differentrandom spawn layout and variation.  --seed · F1 more▶ Advanced (1)▼ FDS input & smokeFDS folder  none, no fire input  (change: Esc to step 2)Constant extinction [1/m]not setdefaultSmoke update interval [s]1.0                    – inactiveSmoke slice height [m]1.6                    – inactiveAllow fds horizon holdoff– inactiveRequire fds coverageoff– inactive▶ Toxic gas (FED/FIC)▶ Heat  off▶ Routing▶ Visibility & signs▶ ASET/RSET tools▇▇L2  smoke off  gas off  heat off  FIC off  reroute 1 s  vis off  ✓ valid ^n review  ^p fds  ^f find ▏ ^q quit  ? keys  ^k palette 
The Configure step (snapshot test test_snapshot_configure, 80 × 24).

The options are grouped in the sections of pyfds-evac --help: Scenario & run, FDS input & smoke, Toxic gas (FED/FIC), Heat, Routing, Visibility & signs, ASET/RSET tools, and Outputs.

  • The step offers 40 options: every run option of pyfds-evac except --cleanup, --export-only, --inspect-fds and --print-summary. --show-config is not a run option; the Review step shows its report.
  • Each section shows its common options, then the others under “Advanced (n)”.
  • The output paths are not fields. They follow from the Output folder at the top: the Outputs section lists them, see Outputs and folders.
  • An option that has no effect with the current settings is greyed out, with the reason. Enter on it goes to the setting that enables it.
  • ? or F1 shows the option’s help, its flag, its default, its Python API default, its FDS+Evac counterpart, its unit and a link to its docs page, followed by the list of keys.
  • ctrl+f finds a setting. The palette (ctrl+k) has “Reset all settings”, which asks first, and “Toggle advanced in all sections”.
  • The summary line at the bottom shows the review level, the active models, and “✓ valid” or the number of errors.

4 Review

pyFDS-EvacpyFDS-Evac 1 Scenario✓ › 2 FDS✓ › 3 Configure › 4 Review✓ › 5 Run › 6 Results ✓ Ready to runLevel 2Inputs  scenario   /Users/Shared/pyfds-evac-demo/assets/t_junction (directory)  FDS        …/pyfds-evac-demo/assets/t_junction/fire_2MW_PVC   ends 300.0 s  seed       42, scenario     time limit 300 s, scenario  output     …/t_junction/deterministic/seed42/20261009T075807ZModels  smoke_speed            ✓ on   FDS extinction at 1.6 m, every 1.0 s  gas_fed                ✓ on   CO, CO2, O2; O2 threshold 20.0 vol %  heat_fed               – off  needs --enable-heat-fed  tenability             ✓ on   FIC off, gas deterministic  rerouting              ✓ on   every 1.0 s  visibility             ✓ on   smoke-aware, 3 signs, time step 1.0 s  smoke_blind            – off  replay_exits           – off  fds_horizon_hold       – off  fds_coverage_required  – offChanged settings  noneresolved vismap_time_step_s 1.0No effect  noneWarnings  noneScenario routing* w_smokeCommand▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊pyfds-evac --scenario /Users/Shared/pyfds-evac-demo/assets/t_junction --output-sqlite ▎▊/Users/Shared/pyfds-evac-demo/results/t_junction/deterministic/seed42/20261009T075807Z/t_junction.sqlite ▎▊--export-app-bundle ▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎L2  smoke FDS  gas FED  heat off  FIC off  reroute 1 s  vis smoky  ✓ valid ^r run  ^p configure  c copy  s save  p python ▏ ^q quit  ? keys  ^k palette 
The Review step for the fire_2MW_PVC run. The paths are those of the folder the screenshot was taken in.

Review shows the effective configuration: the same report as pyfds-evac --show-config, described in Checking a configuration before the run. At the bottom is the pyfds-evac command for these settings.

  • Errors are listed first. Enter on an error goes to its field. A run with errors cannot start.
  • Level 2 means the FDS folder was inspected. Level 1 means it was not, because the inspection failed or is still running. Then the checks that need the FDS slices are not shown yet. Level 1 does not mean the configuration is wrong. The palette entry “Inspect FDS folder” tries again, and a failed inspection still allows the run.
  • c copies the command, s writes command.sh and run.py into the planned run folder, and p shows the equivalent Python. See Reproduce a run.

5 Run

pyFDS-EvacpyFDS-Evac 1 Scenario✓ › 2 FDS✓ › 3 Configure › 4 Review✓ › 5 Run⟳ › 6 Results ⟳ Runninginitialising › FDS inspection › visibility › running › writing outputs › donesim 37.8 / 300 s (limit)   wall 0:03   evacuated 8 of 200 planned (4 %)   incapacitated 0   not spawned 181! 1 warningw list┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓Evacuated█•▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██░░░░░░░░░░░░░░░  8 of 200 planned█◆•▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀◆█Simulated time█•••▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀◆▀▀▀▀▀▀•▀▀•▀•▀•▀▀▀▀▀▀▀▀▀▀▀▀███░░░░░░░░░░░░░░  38 of 300 s██━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓▀▀▀▀•▀▀▀▀▀•▀▀▀┏━━━━━━━━━━━━━━██▁▁▃▃▃▃▃▅█A_left7┃▀▀▀▀▀▀▀┃B_right0▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃▃▃▃▅▅█████████┃▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀┃┃▀▀▀▀▀▀•┃┃▀▀▀▀▀▀┃┃▀▀▀▀▀▀┃┃▀▀▀▀▀┃┃▀▀┃┃▀┃┃┃├───────────┤5my↑x→┗━━━━━━━━━━━━━━┛ plan at  37.0 s ━━━━━━╋───────────────────────────────────────────── 300 s limit smoke K [1/m]  0.1  0.5  1  3  10   FDS slice z = 2.0 m, frame t = 37 s • agent   ● 2+ agents   x incapacitated   ◆ sign   █ exit + evacuated  The run stops if this terminal closes; use tmux or screen for long runs.────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────Configuring visibility model (3 signs).Configuring tenability (FIC slowdown=off, FIC alpha=0.7, min=0.3, FED median=1.0, incapacitation=deterministic, heat FED median=1.0, heat incapacitation=deterministic).▁▁Added DirectSteeringStage for checkpoint jps-checkpoints_0: time=0sFlow spawning: jps-distributions_0 - 200 agents over 400s (freq: 2.00s, rate: 0.50 agents/s) x cancel  ^p review  v fullscreen  w warnings  l log ▏ ^q quit  ? keys  ^k palette 
The Run step about 38 s into the fire_2MW_PVC run (seed 42), wide layout.

The run happens in a separate process. You can move between steps; the run goes on. Only one run happens at a time: ctrl+r during a run says “A run is in progress (run #N)”.

  • The phase line shows initialising → FDS inspection → visibility → running → writing outputs → done.
  • The status line shows the simulated time against the limit, the wall time, “evacuated e of t planned”, the incapacitated agents (only when the run models incapacitation), the agents not yet spawned, and the number of warnings (w lists them).
  • During the run, t counts the planned agents, including flow agents that have not spawned yet. Results counts the agents that entered. In the screenshots, the run shows “of 200 planned” and Results “of 150 that entered”, because 50 agents had not spawned when the time limit was reached (#279).
  • x or ctrl+c cancels, after a confirmation. See Cancel a run.
  • v, w and l open the plan, the warnings and the log full screen.

6 Results

pyFDS-EvacpyFDS-Evac 1 Scenario✓ › 2 FDS✓ › 3 Configure › 4 Review✓ › 5 Run✓ › 6 Results◐ ◐ Incomplete: time limit reached, 51 agents inside, 50 not spawnedexit 2  Simulated time (limit reached) 300.0 s   Evacuated 99 of 150 that entered   Incapacitated 0██████████████████████████░░░░░░░░░░░░░░  99 of 150 that enteredSimulation incomplete: time limit reached after 300.00 s (99/150 evacuated, 51 remaining, 50 not spawned).run #1 · t_junction · seed 42 · wall 0:22┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓! 1 warningw list█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀•▀•▀▀▀▀▀┃Per exit (end of run)█◆▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀◆▀▀▀•▀▀•••▀▀•▀●◆▀┃█ exit_A_left          12┗━━━━━━━━━━━━━━━━━━━━━━━┓▀▀▀•▀▀▀▀┏━━━━━━━━━┛█ exit_B_right         87A_left12┃•▀▀▀▀▀▀▀┃B_right6Evacuated over sim time, 0–300 s ↓┃▀▀▀•▀▀▀▀┃▁▁▂▂▃▃▃▄▅▆▆▇█┃▀▀▀▀▀▀▀▀┃▁▁▁▁▂▃▃▄▄▅▅▆▆▆▆▇▇▇▇█████████████████┃▀▀▀▀▀•▀▀┃┃▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀┃├──────┤5my↑x→┗━━━━━━━━┛ plan at  60.0 s ━━━━━━━━━━╋───────────────────────────────────────── 300 s limit smoke K [1/m]  0.1  0.5  1  3  10   FDS slice z = 2.0 m, frame t = 60 s • agent   ● 2+ agents   x incapacitated   ◆ sign   █ exit + evacuated   ←/→ 1 s · shift+←/→ 10 s: replay the framesOutput filesin …/results/t_junction/deterministic/seed42/20261010T174536Z  · Enter preview · y copy path▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎▊bundle  4.6 KB▎▊t_junction_smoke_history.csv  828.7 KB▎▊t_junction_fed_history.csv  2.7 MB▎▊t_junction_route_history.csv  6.9 KB▎▊t_junction_route_cost_history.csv  77.7 KB▎▊t_junction_exit_history.csv  7.6 KB▎▊t_junction.sqlite  6.1 MB▎▊t_junction.manifest.json  23.4 KB▎▊child.log  680 B▎▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎ ←/→ replay  ^r run again  ^p configure  e change  n new  c command  s save  v fullscreen▏ ^q quit  ? keys  ^k palette 
Results of the fire_2MW_PVC run, with the plan replayed at 60 s.

The outcome comes first, in the same words as the Web GUI:

Outcome lineExit status
“Complete: all agents evacuated”0
“Incomplete: time limit reached, k agents inside[, m not spawned]”2
“Run failed: …”; “(during setup; the run was not started)” when the run failed before it started1
“Cancelled at sim t s”, or “Cancelled before the first progress sample”none
“The run stopped without a result (process exit N)”none

The exit statuses are those of pyfds-evac; see Exit status. Then follow the time, “Evacuated e of n agents” ("… that entered" when flow agents had not entered: cut off by the time limit, or without a free position before their flow window closed), and “Incapacitated j” when the run models incapacitation or “Incapacitated: not modelled in this run” when it does not. Below 100 columns the incapacitated part goes on a line of its own. Results on the Web GUI page says when a run models incapacitation. After that come the run line (run number, scenario, seed used, wall time), the warnings, the per-exit counts at the end of the run, the evacuated-over-time sparkline (wide layout only), and the output files with their sizes.

  • ← and → replay the stored plan frames 1 s at a time, shift+← and shift+→ 10 s at a time. Replay works only after the run has ended.
  • y copies the path of the highlighted file. t shows the traceback or the end of child.log.
  • c and s give the command and the script of this run; see Reproduce a run.
  • e goes back to Configure, n starts a new scenario.

Settings changed. If you edit the settings after a run so that they differ from the run’s, Results reads “✎ Previous settings. Settings changed since run #N. These results show that run’s settings, not the current ones. Run again …”. The output paths are left out of this comparison; a typed output folder is included. The idea is the same as in the Web GUI; see Settings changed.

Keys

WhereKeyAction
everywherectrl+kcommand palette: themes, go to step, Inspect FDS folder, Copy command, Save command and script, Show Python, Reset all settings, Toggle advanced, Open docs page
everywherectrl+qquit; during a run it asks “A run is in progress. Quit and cancel it?”
FDS to Resultsctrl+pprevious step, keeping all values; the footer names it. From Run it goes to Review and the run continues; from Results it goes to Configure. Esc does the same, but tmux’s escape-time can delay it
everywhere? or F1the list of keys; on a Configure field, the field’s help first. ? is never typed into a text box
Scenario, FDS, Configurectrl+nnext step (from Configure: to Review)
Configure, Review, Resultsctrl+rrun. With warnings, outside Review, it asks “r Run anyway / Esc Review”; on Review it runs at once
Configure↑ / ↓previous / next option, also out of a text box or a closed dropdown; Enter flips a switch, opens a dropdown or a section, and on an inactive option goes to the setting that enables it
Configurectrl+ffind a setting
Reviewccopy the command to the clipboard (OSC 52)
Reviewswrite command.sh and run.py into the planned run folder, before any run
Reviewpshow the equivalent Python of the current settings
Review, during a runctrl+nback to the Run step
Runx or ctrl+ccancel, after a confirmation
Runv / w / lfull-screen plan / warnings / log
Resultscopen “Command and Python for run #N”, built from the run’s settings; c in the dialog copies
Resultsswrite command.sh and run.py of the run into the run folder, replacing a save from Review
ResultsEnterpreview the highlighted output file: the first 40 lines of a text file, the tables and row counts of a SQLite file, the files of a folder. In the dialog y copies the path and o opens the file in the system app (macOS open, Linux xdg-open; not over SSH)
Resultsycopy the path of the highlighted output file
Resultse / n / t / wchange settings (Configure) / new scenario / traceback or end of child.log / warnings
Results, full plan← / →, shift+← / shift+→replay the plan frames by 1 s / 10 s, after the run has ended
Run, Resultsvfull-screen plan (Esc back)

The keys avoid common terminal conflicts: there is no ctrl+s (XOFF) and no ctrl+a or ctrl+b (the screen and tmux prefixes). ctrl+n and ctrl+p are next and previous, as in Emacs and tmux. The palette is on ctrl+k, not Textual’s default ctrl+p, so in a text box ctrl+k does not delete to the end of the line.

The footer shows the keys of the current step on the left and, on every step and in the full-screen plan, a fixed group on the right: ^q quit ? keys ^k palette. On a narrow terminal the step keys are cut first; the fixed group stays visible at 80 columns. The labels are words, so they read the same with NO_COLOR or TERM=dumb.

Outputs and folders

Each run gets its own folder, under the same rules as in the Web GUI: <results folder>/<scenario>/<mode>/seed<seed>/<UTC start>, or <typed folder>/<UTC start>, with -2, -3, … added when the folder exists. Output folders on the Web GUI page gives the rules in full. Configure shows the planned folder under Output folder; it stays the same until a setting changes.

The files are named after the scenario:

FileWritten
<name>.sqlitealways: the trajectory
<name>.manifest.jsonalways: the run manifest, beside the trajectory
<name>_smoke_history.csvwhen a smoke model ran
<name>_fed_history.csvwhen the gas FED or the heat FED ran
<name>_route_history.csvwhen rerouting is on (the default)
<name>_route_cost_history.csvwhen rerouting is on
<name>_exit_history.csvalways
bundle/always: the scenario for the app
child.logalways; listed in Results only when it is not empty
command.sh, run.pywhen you press s

Outputs describes each model output column by column. Results lists the files as the run reports them, with their names and sizes; the labels of the Web GUI are not shared yet (#550).

Reproduce a run

Three records describe a run:

  • The run manifest <name>.manifest.json holds the effective configuration under configuration. configuration.command is the full pyfds-evac … command. See Run manifest.
  • command.sh is one line: the shortest pyfds-evac command, with absolute paths. It includes the --output-* and --export-app-bundle flags, which point at the run’s own folder. Running it again overwrites that run’s files. Edit the paths first, or use run.py.
  • run.py is the same standalone script as the Web GUI’s Show the run as Python, with the header “Generated by pyfds-evac-tui.” It writes into <run folder>/python_output, so it never overwrites the run. Saved from Results, it carries the seed the run used, also when that seed came from the scenario.

s on Review saves the scripts of the current settings before a run. c and s on Results use the settings the run had.

The terminal UI cannot reopen a run’s settings yet (#534). Recent restores only the scenario and the FDS folder.

Where the Recent list is stored
The Recent list is $XDG_CONFIG_HOME/pyfds-evac/recent.json, or ~/.config/pyfds-evac/recent.json when XDG_CONFIG_HOME is not set, on every operating system. It holds the last 10 runs and the theme. If the file cannot be read or written, the terminal UI ignores it.

The plan view

pyFDS-EvacpyFDS-Evac  run #1  t_junction  seed 42  Esc back┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██▀◆▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀•▀▀▀•▀▀▀▀▀▀◆██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀◆▀▀▀▀▀▀•▀▀▀▀▀••▀▀▀▀▀▀▀▀▀•▀▀███━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓▀▀▀▀▀▀•▀▀▀▀▀▀▀┏•━━━━━━━•━━•━━██A_left12┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃B_right6┃▀▀•▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀•▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀•▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃┃▀▀▀▀▀▀▀▀▀▀▀▀▀▀┃├───────────┤5my↑x→┗━━━━━━━━━━━━━━┛ plan at  60.0 s ━━━━━━━━━━╋─────────────────────────────────────── 300 s limit smoke K [1/m]  0.1  0.5  1  3  10   FDS slice z = 2.0 m, frame t = 60 s • agent   ● 2+ agents   x incapacitated   ◆ sign   █ exit + evacuated   ←/→ 1 s · shift+←/→ 10 s: replay the frames esc back  ← −1 s  → +1 s ▏ ^q quit  ? keys  ^k palette 
The full-screen plan (v) at 80 × 24 in the Solarized Light theme, replaying the fire_2MW_PVC run at 60 s.

The plan view is a coarse preview of the run. The trajectory SQLite and the CSV files are the results. It draws:

  • walls, and the exits with their evacuated counts;
  • signs (◆), agents (•; ● for two or more in a cell; x incapacitated);
  • the smoke as the extinction coefficient K in fixed bins at 0.1, 0.5, 1, 3 and 10 1/m, the same for every run. The Web GUI replay uses the same bins and the same slice rule, so the two front ends draw smoke by the same rule.

The legend states the FDS slice height and the FDS frame time the run read. That height is the slice actually read, the one nearest to the smoke slice height: in the screenshots the slice is at 2.0 m, while the setting is the default 1.6 m. See Selecting a slice height. The run manifest does not record the slice height yet (#592); see Run manifest.

Without plan data, or with TERM=dumb, the region reads “plan not available”.

How often the plan is updated

The run sends a plan frame when 1/f wall seconds have passed or when 1 simulated second has passed, whichever comes first. A smoke grid is sent whenever the FDS frame changes. f is 5 per second, 2 when SSH_CONNECTION is set, and 1 when PYFDS_EVAC_TUI_REDUCED_MOTION is set.

On a fast run the simulated-second rule decides: on t_junction about 90 frames come per wall second. The Run screen redraws at most 10 times per wall second. So the two variables only lower the wall-time rule; they do not limit the plan to 1 or 2 updates per second.

All frames stay in memory for the replay, so a very long run uses more memory. Tests show that recording the frames does not change the results.

How a run is executed

Each run is a separate spawn child process that runs the same code as pyfds-evac. A test runs the same scenario through the terminal UI and through pyfds-evac with the command the terminal UI shows. The two give identical trajectory_data rows, the same set of CSV files with byte-identical contents, and the same exit status. The test uses ISO-table21 with seed 7, without fire, so it compares the route, route-cost and exit histories; it does not cover a fire scenario.

  • child.log. The child’s standard output and error go to <run folder>/child.log: Python warnings, output of the native libraries, and the --debug lines. Nothing prints over the screen.
  • Temporary files. The child gets a private temporary folder, removed when the child ends, whatever the outcome. If the terminal UI itself was killed, its folder (pyfds-evac-run-<pid>-…) stays behind until the next start of the terminal UI removes it.
  • A crashed run. When the child ends without a result, Results reads “The run stopped without a result (process exit N)”, and t shows the last 20 lines of child.log.

Cancel a run

  1. x or ctrl+c asks “Cancel run? Files already written are kept.”
  2. The run stops at its next progress sample, or between two phases. The status reads “Cancelling… (waiting for the current phase: …)”.
  3. If no result comes within 10 s, the terminal UI terminates the process, and kills it 3 s later.

This automatic stop never happens while the outputs are being written, because those writes are not atomic. A cancel pressed during “writing outputs” is not acted on: the run finishes, and Results shows Complete or Incomplete, not Cancelled.

Pressing cancel again asks “Stop the process now?”. y terminates the process at once, also during “writing outputs”, and so does ctrl+q with “Quit and cancel”. Both can leave a partial file.

A cancelled run has no exit code. Results reads “■ Cancelled at sim t s”, or “■ Cancelled before the first progress sample” when no progress sample had arrived, with “; the process was stopped” when it was terminated. A run cancelled before the writing phase writes no trajectory and no CSV files: only child.log remains, and command.sh and run.py if you saved them.

When the terminal closes

The run stops. On a hang-up (SIGHUP), or when its parent process is gone, the child stops at its next progress sample, as on a cancel. Outputs are written only at the end of a run, so a closed terminal or a dropped SSH session leaves no trajectory and no CSV files: only child.log, and command.sh and run.py if you saved them. Start the run again.

  • The Run step says “The run stops if this terminal closes; use tmux or screen for long runs.”
  • For long runs, start the terminal UI inside tmux or screen. Or use pyfds-evac from the command line, with Slurm on a cluster.
  • A run cannot be detached or reattached (#530).
  • A child that is still starting when the terminal UI is killed can run to its end (#555).

SSH, terminals and accessibility

  • Copy (c, y) uses the OSC 52 escape sequence only, so the terminal must support it. It does not work in macOS Terminal (see App.copy_to_clipboard). Under tmux it needs set -g set-clipboard on. When it fails, nothing is copied and no error appears. The message reads “Sent to clipboard (OSC 52). If nothing was copied, select the command above or press s.” Over SSH, the reliable ways are the command shown on Review and the files that s writes.
  • Over SSH (SSH_CONNECTION set), the wall-time rule for plan frames drops to 2 per second. The screen still redraws up to 10 times per second.
  • Colours. The themes need a truecolor terminal. With 256 or 16 colours, each theme colour is rounded to the nearest one the terminal has, so solarized-light turns yellow, grey and pink. The terminal UI then says at start “This terminal shows 256 colours, so the theme colours are approximate.” It decides from COLORTERM and TERM, as Rich does. To get truecolor:
    • set COLORTERM=truecolor when the terminal supports it. SSH does not forward COLORTERM, so set it on the server, for example in ~/.bashrc;
    • in tmux 3.2 or later, add set -g default-terminal "tmux-256color" and set -as terminal-features ",*:RGB" to ~/.tmux.conf, then restart the tmux server (tmux kill-server).
  • NO_COLOR (any non-empty value) gives Textual’s monochrome rendering. The plan then draws the smoke bins with the shade glyphs ░ ▒ ▓ █, and agents and states keep their glyphs. NO_COLOR is the only way to get the shade glyphs.
  • PYFDS_EVAC_TUI_REDUCED_MOTION only lowers the wall-time rule for plan frames, to 1 per second. It does not stop the motion; see How often the plan is updated.
  • Glyphs. • ● ◆ ◐ ✓ ✗ have an ambiguous East Asian width. In a CJK locale a terminal may draw them two cells wide. There is no ASCII fallback.
  • Platforms. The terminal UI tests, including the snapshots, run in CI on Ubuntu with Python 3.12, 3.13 and 3.14 (3.12 and 3.14 on pull requests). It was developed on macOS. Windows is not tested. Windows has no SIGHUP, so there the run stops on a closed terminal only through the check of the parent process.

Web GUI and terminal UI

The output folders, the outcome wording, “Settings changed” and the Python script are shared and described on the Web GUI page. The differences:

Web GUITerminal UI
Runs ina background thread of the servera child process; its output goes to child.log
Closing the window or the terminalthe server keeps the run goingthe run stops
Scenario input./assets picker; uploads saved in ./uploads./assets examples, Recent, or a file, zip or folder opened where it is
Options offeredthe GUI form (several heat and visibility options under “Other”, #311, #270)40 options: every run option except --cleanup, --export-only, --inspect-fds, --print-summary
Effective configuration before the run—Review (the --show-config report)
Resultscharts, trajectory replay, FED paneloutcome, per-exit counts, sparkline, plan replay in 1 s steps, file list
Results-only modeyesno
Over SSHneeds port forwarding (--host, --port)runs in the SSH session

Limits

  • No reopening of a run’s settings, and no --config file (#534).
  • No stored run records, no reattaching, and no Slurm submission from the terminal UI (#530).
  • A child that is still starting when the terminal UI is killed can run to its end; its temporary folder is removed at the next start (#555).
  • The output files have no labels shared with the Web GUI and the command line; Results shows the file names (#550).
  • The run counts planned agents; Results counts the agents that entered (#279).
  • All replay frames are held in memory.
  • No results-only mode, no charts and no trajectory replay. Open the SQLite in the Web GUI or in fds-viewer.

Error messages of the terminal UI, with their fixes, are on Troubleshooting.

Where this is in the code
  • pyfds_evac/tui/runner.py, ProcessRunner, child_entry, frame_rate, sweep_temp_dirs: the child process, the frame rate and the temporary folders.
  • pyfds_evac/tui/app.py, EvacTui, results_text, result_files: the steps, the outcome lines and the file list.
  • pyfds_evac/tui/model.py, Recent, save_command, list_examples: Recent, command.sh and run.py, and the Examples tab.
  • pyfds_evac/config/frontend.py, output_paths, results_root, run_outcome: the files, the results folder and the outcome wording, shared with the Web GUI.
  • pyfds_evac/config/effective.py, cli_command: the command in command.sh.
  • pyfds_evac/core/run_stream.py, stream_run: the run, as pyfds-evac makes it.
  • tests/test_tui.py, test_a20_tui_run_equals_cli: the equivalence test with pyfds-evac.
Last updated on