| # ezflow_v2/app β interactive web app (turbulent-RANS GNN predictor) |
| |
| A self-contained FastAPI + Three.js app that predicts steady turbulent-RANS flow |
| fields around a body **in milliseconds** with a choice of surrogate models, an |
| interactive 3D viewer, slice planes, and a point-probe tool. |
| |
| > Self-contained under `ezflow_v2/`; **separate from the v1 app** (`app/`, port 8000) |
| > per the v1/v2 separation rule. The v2 app runs on **port 8001**. |
|
|
| ```bash |
| python ezflow_v2/app/run_app.py # -> http://127.0.0.1:8001 (opens a browser) |
| ``` |
| First prediction lazily loads the model(s) (the ensemble loads all three, ~10β20 s); |
| every prediction after that runs in milliseconds. Requires the trained runs under |
| `ezflow_v2/gnn/_runs/` (rans_v5, hybrid, transolver) and the v2 env (torch |
| 2.11+cu128, gmsh, trimesh, pyvista, fastapi, uvicorn). |
| |
| --- |
| |
| ## What it does (pipeline) |
| |
| ``` |
| geometry (procedural shape OR uploaded STL) |
| β |
| ββ premesh: normalize to L=1 β surface (true geometry if watertight, else |
| β voxel envelope) β graded tet mesh (fine near body) |
| β |
| ββ graph: nodes = mesh points; features = geometry + (Re, yaw) ONLY |
| β (same features.py as training β no solved field is ever an input) |
| β |
| ββ model: GeoReNet / HybridFlow / Transolver / mean(G,T,H) ensemble |
| β |
| ββ outputs: per-node u, v, w, p, k, Ο, Ξ½_t β 3D viewer + slice planes + probe |
| ``` |
| **No CFD is run for a prediction.** Inputs are the shape and the operating condition |
| (`Re`, yaw) only; OpenFOAM is used solely offline to train the models. |
| |
| --- |
| |
| ## Controls & features |
| |
| ### Surrogate Model (dropdown) |
| | Option | Backed by run | Params | |
| |---|---|--:| |
| | **mean(G,T,H) ensemble** (default) | average of the three below | ~10.2M | |
| | HybridFlow | `_runs/hybrid` | 3.04M | |
| | GeoReNet | `_runs/rans_v5` | 3.28M | |
| | Transolver | `_runs/transolver` | 3.88M | |
| |
| The ensemble averages the three models per node (in the transformed target space). |
| To add GINO: append `"GINO": ["gino"]` to `MODELS` in `infer_app.py`. |
| |
| ### Mesh Resolution (dropdown) β the premesher |
| | Preset | Surface | Near-body size | Typical nodes | Use | |
| |---|---|--:|--:|---| |
| | Coarse | voxel envelope (vox 64) | hβ0.12 | ~3k | fastest, smoothed | |
| | **Medium** (default) | **true geometry** (watertight) | hβ0.045 | ~10k | detail + training density | |
| | Fine | true geometry | hβ0.022 | ~25k+ | most detail (slower) | |
| |
| - **Watertight, genus-0 uploads keep their true surface** (mirrors/edges survive) and |
| are meshed with curvature-aware near-body refinement. |
| - **Non-watertight / complex bodies** (e.g. DrivAer: open underbody, wheel wells) can't |
| be parametrized into a clean external-flow solid, so they **auto-fall-back** to a |
| robust voxel **envelope** (smooth shell) at the requested volume density. This is |
| automatic β a complex STL will predict (as an envelope) rather than error. |
| - Medium β the ~10k-node density the models were trained on (the sweet spot). |
| |
| ### Geometry input |
| - **Simple Shape** tab: cylinder / sphere / box / capsule / cone / torus (sliders). |
| - **Upload STL** tab: drag-and-drop or browse any `.stl` (auto-normalized to L=1). |
| Test geometries live in `C:\dev\ezflow_eval\test_stls\`. |
| |
| ### Physics |
| Inlet speed, viscosity, density sliders β **Reynolds number** (the only physical knob, |
| `Re = Ο Uβ L / ΞΌ`), plus a **yaw** slider. Models were trained on **Re β 5Γ10Β²β1Γ10β΅**; |
| predictions far outside that range are extrapolation. |
| |
| ### Visualize Field (slice viewer) |
| All seven outputs are selectable: **|U|, u, v, w, p, k, Ο, Ξ½_t**. A slice plane (X/Y/Z |
| axis + position slider) is colored by the chosen field; optional flow vectors on the slice. |
| Color map is **green (low) β gold β red (high)**; the legend shows the range and a |
| non-dimensional unit label. |
| |
| ### Mouse controls |
| - **Left-drag** β rotate |
| - **Mouse-wheel scroll** β zoom |
| - **Wheel-press (middle) drag** β pan |
| - **Single left-click** β set the rotation pivot at that point **and** probe values there |
| |
| ### Point Query (probe) |
| Click any point in the 3D view β the sidebar shows the predicted **x,y,z and all field |
| values** (|U|, u, v, w, p, k, Ο, Ξ½_t) at the nearest node (`/api/probe`). |
| |
| ### Display & View (right sidebar) |
| Body style (glass / wireframe / hidden), wind-tunnel box, slice grid lines, flow vectors, |
| and view presets (3D / Side / Top / Front). |
| |
| --- |
| |
| ## HTTP API |
| |
| | Endpoint | Method | Purpose | |
| |---|---|---| |
| | `/` | GET | the app page | |
| | `/api/models` | GET | `{models, default}` | |
| | `/api/predict` | POST (form) | `shape_type, model_name, resolution, Re, yaw_deg`, shape params or `file`; returns coords, wall_tris, per-field `ranges`, `geom_meta` (incl. surface `method`/`n_faces`), `mesh_meta` (incl. `resolution`, `n_nodes_approx`), `model {name, params_m, members}`, bounds | |
| | `/api/slice` | POST (json) | `{normal_axis, position, field_type}` β slice `points/faces/scalars/vectors` | |
| | `/api/probe` | POST (json) | `{x, y, z}` β nearest-node `{point, values{...}, velocity_vec}` | |
| |
| All responses are sent with `Cache-Control: no-store` so the browser always loads the |
| latest build (static assets are also versioned, `?v=N`). |
| |
| --- |
| |
| ## Files |
| - `run_app.py` β launcher (port 8001). |
| - `serve.py` β FastAPI: endpoints above, no-cache middleware, caches the current |
| `pyvista` grid for slicing/probing. |
| - `infer_app.py` β model loading (architecture rebuilt per checkpoint) + uniform |
| ensemble; `mesh_stl()` (premesh + read) with the auto-fallback; `predict_fields()` |
| (predicts on the **full mesh** β the models are mesh-density-robust β and returns |
| physical fields via `features.inverse_targets`). |
| - `premesh.py` β detail-preserving premesher: `prepare_surface()` (true geometry vs |
| voxel envelope) + `build_volume()` (graded, curvature-aware gmsh mesh); `PRESETS`. |
| - `shape_generator.py` β procedural watertight shapes (trimesh). |
| - `frontend/` β `index.html`, `app.js` (Three.js viewer, slicing, probe, colormap), |
| `style.css`. |
| |
| Geometry meshing reuses the shared, geometry-neutral gmsh tooling in |
| `datasets/gen/shapenet/` (same as `infer_v5`); it carries no v1 physics. |
| |
| --- |
| |
| ## Caveats / limitations |
| - **Training fidelity:** the models were trained on the *smoothed envelope* geometries |
| (ModelNet shapes passed through the same voxel remesh). A high-detail mesh improves the |
| geometry/visualization and lets the model run on a faithful body, but the model **cannot |
| fully exploit fine features it never saw** (e.g. mirror wakes). True fine-detail |
| prediction on complex cars would need **retraining on high-fidelity geometry + CFD**. |
| - **Complex/dirty STLs** (open underbody, non-watertight) are served as a smooth envelope |
| (see Mesh Resolution). In-app gmsh can't make them a clean external-flow solid; the |
| CFD-grade route (snappyHexMesh) is out of scope for the interactive app. |
| - **Outputs are non-dimensional** (Uβ = 1, Ο = 1, L = 1); labels reflect this |
| (`p/ΟUβΒ²`, `Ξ½_t/Ξ½`, β¦). |
| - **Out-of-distribution** geometry (e.g. ModelNet car/airplane, sharp finned bodies) and |
| Reynolds numbers outside ~5Γ10Β²β1Γ10β΅ are extrapolation β interpret with care. |
| |
| ## Troubleshooting |
| - **Page looks stale / controls unresponsive:** hard-refresh once (Ctrl+Shift+R) or open |
| an Incognito window. The server sends no-cache headers, so it stays fresh afterward. |
| - **An STL errors:** it should auto-fall-back to the envelope; if not, try Coarse |
| resolution (goes straight to the robust envelope). |
| |