2. Streamlit fundamentals for CAE
Streamlit's execution model is the single most important concept to internalize before building any CAE analytics application. Unlike a traditional web framework where you write request handlers, a Streamlit script runs top to bottom on every user interaction. When a user drags a slider, clicks a button, or uploads a file, the entire script re-executes from line one. Understanding this rerun model explains why widgets behave the way they do, why session state exists, and why callbacks matter for simulation workflows where expensive computations must not be triggered accidentally.
The Rerun Model and Widget Identity
Every widget you create returns a value. That value is available only during the current run. On the next rerun, the widget re-creates itself and returns its current value again. Streamlit identifies widgets by a combination of their type, label, and an optional key. If you generate widgets inside a loop without stable keys, Streamlit may confuse them across reruns, producing duplicated widget errors or lost values.
import streamlit as st
st.title("Modal Frequency Explorer")
# Stable key ensures the widget keeps its identity across reruns
f_min = st.slider("Minimum frequency (Hz)", 0.0, 500.0, 10.0, key="f_min")
f_max = st.slider("Maximum frequency (Hz)", 0.0, 500.0, 200.0, key="f_max")
st.write(f"Analyzing modes between {f_min:.1f} Hz and {f_max:.1f} Hz")
For CAE data, this pattern is common: you load a result file, expose sliders for frequency bands, load cases, or time steps, and filter the dataset on each rerun. The filtering itself is cheap, so rerunning is fine. The problem arises when the rerun triggers a solver call, a large file parse, or a database query.
Session State: Persisting Simulation Context
st.session_state is a dictionary-like object that survives reruns. It is the correct place to store loaded result sets, solver handles, user selections that must persist, and computed caches. Without it, every rerun would reload a 200 MB result file.
if "results" not in st.session_state:
st.session_state.results = load_result_set("modal_analysis.h5")
results = st.session_state.results
modes = results.query(frequencies=(f_min, f_max))
st.dataframe(modes.summary())
A practical rule for CAE apps: keep inputs in widgets, keep expensive artifacts in session state, and keep derived views as plain local variables recomputed each run. This separation prevents stale data and keeps memory usage predictable.
Callbacks: Controlling When Work Happens
Callbacks run before the script reruns, which makes them ideal for validating input, updating dependent widgets, or triggering a computation only when a button is pressed. The on_click and on_change parameters accept a function.
def run_solver():
st.session_state.solver_output = solve(
st.session_state.mesh_id,
st.session_state.material,
)
st.button("Run solve", on_click=run_solver, key="solve_btn")
Without a callback, a button click would still rerun the script, but the solve would execute on every subsequent rerun too if you placed it unconditionally. Callbacks scope the work to the interaction itself. For long-running CAE jobs, combine a callback with a status placeholder and a progress bar updated from a worker thread or a polling loop.
Layout Primitives for Engineering Dashboards
Streamlit offers several layout containers. Choosing the right one keeps a CAE dashboard readable when it mixes controls, tables, plots, and 3D views.
| Container | Typical CAE use |
|---|---|
st.sidebar | Global controls: load case, units, solver tolerance |
st.columns | Side-by-side comparison of two result sets or two plots |
st.tabs | Separating geometry, mesh, boundary conditions, and results |
st.expander | Collapsing raw metadata or solver logs |
st.container | Grouping a plot with its caption and export button |
A common pattern is a sidebar for global simulation settings, a main area split into two columns for pre-processing and post-processing, and tabs for different result types. Avoid nesting more than two levels of columns; deep nesting breaks on narrow screens and makes the layout fragile.
Wiring It Together: A Minimal CAE Analytics Flow
The diagram below shows how a single user interaction propagates through a Streamlit CAE app. The key insight is that the rerun is the central mechanism, and session state is what makes the rerun cheap and consistent.
Practical Guidance for Simulation Data
- Cache aggressively, invalidate deliberately. Use
@st.cache_datafor pure functions that parse files or compute derived quantities, and@st.cache_resourcefor solver sessions or database connections. Key the cache on the file path and a modification timestamp so a re-exported result set is picked up. - Never mutate cached objects. Cached data is shared across sessions. Copy before modifying, or store mutable state in
st.session_stateinstead. - Guard expensive operations with a button. A slider that triggers a 30-second solve on every drag is unusable. Bind the solve to a button callback and let sliders only update the preview.
- Use
st.formfor batch input. When a user must set several parameters before a single solve, wrap them in a form so the script reruns only on submit, not on every field change. - Show progress for long jobs.
st.progressandst.statusgive users feedback during mesh generation or transient solves, which is essential when a run takes minutes.
st.write(st.session_state) at the top of your script. Watching which keys change between interactions quickly reveals whether a widget is losing its identity or a callback is firing more often than intended.
With these fundamentals in place, you can structure a CAE analytics app so that controls, data loading, computation, and visualization each live in the right place. The next step is applying this structure to real result files and building the first end-to-end post-processing view.