test of integrating neural network in pyCALC-RANS solver k epsilon model
Go to file
2026-04-24 13:14:31 +02:00
boundary-layer-RANS-keps-NN initial commit 2026-04-24 13:14:31 +02:00
.gitignore initial commit 2026-04-24 13:14:31 +02:00
calc_earsm.py initial commit 2026-04-24 13:14:31 +02:00
pyCALC-RANS.py initial commit 2026-04-24 13:14:31 +02:00
pyCALC-readme initial commit 2026-04-24 13:14:31 +02:00
README.md initial commit 2026-04-24 13:14:31 +02:00
README~ initial commit 2026-04-24 13:14:31 +02:00

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 calcepscalceps_standard in the solver (via sed)
  2. Renames fix_epsfix_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
#!/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)

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:

cd ../          # go to 2d-example-boundary-layer/
python NN_SR.py

This writes the model, scalers and min-max file into nn/.

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

chmod +x run-python   # only needed once
./run-python

Output is written to out. Monitor convergence in a second terminal:

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

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