179 lines
5.6 KiB
Markdown
179 lines
5.6 KiB
Markdown
|
|
# 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 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|