# 2D Flat-Plate Boundary Layer — RANS k-ε with Neural Network damping functions ## Overview The NN is trained on DNS channel flow data (Re_τ = 10 000) using `NN_SR.py` and then loaded into the CFD solver via `calceps_NN_SR.py`. --- ## Directory structure ``` 2d-example-boundary-layer/ ├── pyCALC-RANS.py # main solver (do not edit) ├── global # global variable declarations (do not edit) ├── calc_earsm.py # empty EARSM placeholder (not used here) │ └── boundary-layer-RANS-keps-NN/ ├── run-python # bash script to assemble and run the solver ├── setup_case.py # case settings (turbulence model, BCs, solvers) ├── modify_case.py # hooks: modify_init, fix_eps, modify_eps, etc. ├── calceps_NN_SR.py # NN-augmented calceps (replaces calceps in solver) ├── generate-bound-layer-grid.py # grid generation script ├── plot_inlet_bound.py # post-processing and plotting ├── create-inlet-rans-profiles.py # saves inlet profiles for restarts ├── vel_2540_dns.prof # DNS reference data (Re_θ ≈ 2540) ├── x2d.dat, y2d.dat # grid files (generated by grid script) ├── z.dat # spanwise extent └── nn/ # trained NN model files ├── model-f_2-f_mu-Re10000.pth ├── scaler-input0-f_2-f_mu-Re10000.bin ├── scaler-input1-f_2-f_mu-Re10000.bin └── min-max-f_2-f_mu-Re10000.txt ``` --- ## Changes from the original k-ω boundary layer case ### `setup_case.py` | Parameter | Original (k-ω) | New (k-ε NN) | |-----------|---------------|--------------| | `kom` | `True` | `False` | | `keps` | not set | `True` | | `c_omega_1/2` | `5/9`, `3/40` | kept as dummies (required by solver init) | | `prand_omega` | `2.0` | kept as dummy | | `c_eps_1` | — | `1.5` | | `c_eps_2` | — | `1.9` | | `prand_eps` | — | `1.4` | | `prand_k` | `2.0` | `1.4` | | `urf_omega` | `0.5` | kept as dummy | | `urf_eps` | — | `0.5` | | `convergence_limit_om` | `-1e-6` | kept as dummy | | `convergence_limit_eps` | — | `-1e-6` | | `restart` | `True` | `False` (no eps restart file from k-ω run) | | omega BCs | full block | replaced with eps BCs | **Epsilon wall BC** (`eps_bc_south`) is set as Dirichlet but the value is overridden every iteration by `fix_eps()` using ε = 2ν k/y². ### `modify_case.py` - `modify_om` → replaced with `modify_eps` (same inlet injection logic) - `fix_omega` → replaced with `fix_eps` (enforces ε = 2ν k/y² at south wall each iteration) - `modify_init` → initialises `eps2d = Cμ k²/(100ν)` instead of `om2d`; **return order** is `u2d, v2d, k2d, om2d, eps2d, vis2d, dist` ### `calceps_NN_SR.py` (new file, injected by `run-python`) Replaces `calceps` in `pyCALC-RANS.py`. Key differences from the standard solver: - Loads `NuNet` model (tanh activations, dynamic layer list via `nn.ModuleList`) - Predicts **both** f₂ and f_μ simultaneously (2-output network) - Inputs: y⁺ (scaled with local u_τ from south wall) and y* (from ε) - Outputs clipped to training min/max range - Set `NN_bool = False` to fall back to standard analytic AKN expressions ### `run-python` The original k-ω script used `cat` directly. The new script: 1. Renames `calceps` → `calceps_standard` in the solver (via `sed`) 2. Renames `fix_eps` → `fix_eps_standard` in the solver (via `sed`) 3. Injects `calceps_NN_SR.py` with the NN version before the solver 4. Uses `echo ""` between files to prevent concatenation syntax errors ```bash #!/bin/bash sed '/setup_case()/d' setup_case.py > temp_file sed 's/def calceps/def calceps_standard/' ../pyCALC-RANS.py | \ sed 's/def fix_eps/def fix_eps_standard/' > temp_file1 { cat ../global echo "" cat temp_file echo "" cat modify_case.py echo "" cat calceps_NN_SR.py echo "" cat temp_file1 } > exec-pyCALC-RANS.py /chalmers/groups/lada_8/anaconda3/bin/python -u exec-pyCALC-RANS.py > out ``` --- ## How to run ### Step 1 — Generate the grid (first time only) ```bash cd boundary-layer-RANS-keps-NN/ python generate-bound-layer-grid.py ``` This produces `x2d.dat` and `y2d.dat`. ### Step 2 — Train the NN (first time only, or if re-training) The NN files are already in `nn/`. If you need to retrain: ```bash cd ../ # go to 2d-example-boundary-layer/ python NN_SR.py ``` This writes the model, scalers and min-max file into `nn/`. ### Step 3 — Link the nn/ folder into the case directory ```bash cd boundary-layer-RANS-keps-NN/ ln -s ../nn nn # only needed if nn/ symlink does not already exist ``` ### Step 4 — Run the CFD solver ```bash chmod +x run-python # only needed once ./run-python ``` Output is written to `out`. Monitor convergence in a second terminal: ```bash tail -f out | grep 'max res' ``` The run is converged when `max res` drops below `sormax = 5e-5` (set in `setup_case.py`). ### Step 5 — Plot results ```bash python plot_inlet_bound.py ``` Produces the following figures: | File | Contents | |------|----------| | `ustar-vs-x.png` | Friction velocity u_τ along the plate | | `u_log_python.png` | U⁺ vs y⁺ (log), compared to DNS at Re_θ ≈ 2540 | | `vis_python.png` | ν_t/ν vs y⁺ at three x stations | | `vis_vs_y_python.png` | ν_t/ν vs y at three x stations | | `eps_vs_y_python.png` | ε vs y at three x stations | | `k_model_python.png` | k/u_τ² vs y⁺ at three x stations | | `k_vs_y_python.png` | k/u_τ² vs y at three x stations | | `cf_vs_re_mom.png` | Skin friction C_f vs Re_θ, compared to empirical correlation | ---