Python tools for 2D airfoil Openfoam Simulation
Project description
AirfoilRANSSolver
Python tooling that automates the full pipeline for running 2D airfoil RANS simulations in OpenFOAM — from airfoil geometry and mesh generation, through case setup and parallel solving, to post-processing of force coefficients and surface distributions.
Given a Reynolds number (and, for compressible cases, a Mach number) plus a set of geometry and mesh parameters, a single function call will:
- Generate the airfoil geometry and mesh (via the companion
airfoilgmesherpackage). - Copy a solver-specific OpenFOAM case template.
- Compute and patch the freestream and turbulence initial/boundary values from
Re(andMach). - Run OpenFOAM pre-processing (
gmshToFoam, boundary-patch typing,checkMesh,potentialFoam,decomposePar). - Launch the solver in parallel with MPI.
- Monitor the aerodynamic force coefficients and stop the solver
automatically once
Cl/Cdare statistically stationary (the coefficient stopping criterion). - Reconstruct the latest time and export
Cl/Cd, the surfaceCp/Cfdistributions, the coefficient history, and convergence plots.
pip install airfoilranssolver
Solvers & validation status
The package ships several launchers, one per OpenFOAM solver. Each is a thin,
self-contained module under airfoilranssolver/.
| Launcher (module → function) | Solver | Regime | Turbulence | Stopping | Status |
|---|---|---|---|---|---|
launchers_simplefoam → launch_simplefoam |
simpleFoam |
Incompressible, steady | Spalart–Allmaras | Coefficient criterion | ✅ Validated |
launchers_simplefoam_urans → launch_simplefoam_urans |
simpleFoam → pimpleFoam |
Steady → URANS (incompressible) | Spalart–Allmaras | Steady: fixed-point or 3× limit cycle; URANS: mean-stationary + cap | 🧪 Experimental |
launchers_no_coefficient_control → launch_pimplefoam |
pimpleFoam |
Incompressible, (pseudo-)transient | k‑ω SST | Fixed iteration count (Allrun) |
✅ Validated |
launchers_pimplefoam → launch_pimplefoam |
pimpleFoam |
Incompressible, (pseudo-)transient | k‑ω SST | Coefficient criterion | 🧪 Experimental |
launchers_rhosimplefoam → launch_rhosimplefoam |
rhoSimpleFoam |
Compressible / transonic, steady | k‑ω SST | Coefficient criterion | 🧪 Experimental |
launchers_rhopimplefoam → launch_rho_pimplefoam |
rhoPimpleFoam |
Compressible / transonic, transient | k‑ω SST | Coefficient criterion | 🧪 Experimental |
The incompressible pipelines are validated:
launch_simplefoam(including the automatic coefficient stopping criterion) and thelaunch_pimplefoamfromlaunchers_no_coefficient_control(which runs a fixed number of iterations to completion). The compressible/transonic launchers are under active development.
The package has no top-level exports — import directly from the launcher module
you need, e.g. from airfoilranssolver.launchers_simplefoam import launch_simplefoam.
Requirements
- OpenFOAM (ESI / openfoam.com; templates target v2312, dictionaries are
v2006-compatible) sourced in your shell, so that
simpleFoam,pimpleFoam,gmshToFoam,checkMesh,potentialFoam,decomposePar,reconstructPar,foamDictionaryand$WM_PROJECT_DIRare available. - MPI (
mpirun) for parallel runs. - Python ≥ 3.9 with the dependencies below (installed automatically by pip):
numpy,scipy,pandas,matplotlib,gmsh,fluidfoam, andairfoilgmesher(geometry + mesh).
How the pipeline works
The coefficient-controlled launchers (launch_simplefoam, launch_pimplefoam,
launch_rhosimplefoam, launch_rho_pimplefoam) split execution into two phases
so that Python — not OpenFOAM — owns the solver loop:
Phase 1 — Allrun_pre (pre-processing only). Converts the Gmsh mesh
(gmshToFoam), rewrites the boundary patch types (e.g. airfoil → wall,
inlet/outlet → patch, front/back → empty), runs checkMesh and
potentialFoam, then decomposePar into n_subdomains. It does not run the
solver or reconstructPar.
Phase 2 — monitored solve. Python launches
mpirun -np <n_subdomains> <solver> -parallel, and every check_every_seconds
reads postProcessing/forces/0/coefficient.dat. Once the convergence test
passes (or max_wall_seconds is hit), it cleanly terminates the whole MPI
process group, runs reconstructPar -latestTime, and post-processes.
The launchers_no_coefficient_control variant instead runs the template's full
Allrun script (pre-processing + solve + reconstruct) to a fixed
endTime, then post-processes the result.
The coefficient stopping criterion
Implemented in airfoilranssolver/utils.py::force_coefficients_converged. Over
the last window samples of the force history, the run is declared converged
when all of the following hold:
- at least
min_samplessamples are available; - the peak-to-peak ranges satisfy
Cl_range < cl_range_tolandCd_range < cd_range_tol; - the relative standard deviations satisfy
Cl_rel_std < cl_rel_std_tolandCd_rel_std < cd_rel_std_tol.
Defaults: min_samples=5000, window=5000, cl_range_tol=2e-4,
cd_range_tol=2e-5, cl_rel_std_tol=2e-4, cd_rel_std_tol=5e-4. Looser
tolerances are appropriate for noisier multi-element cases (see the 30P30N
benchmark).
Quick start
Incompressible steady — simpleFoam (validated, coefficient-controlled)
from airfoilranssolver.launchers_simplefoam import launch_simplefoam
geometry_parameters = {
"airfoil_type": "NACA",
"m": 0.0, # max camber
"p": 0.0, # max-camber location
"t": 0.12, # max thickness (NACA 0012)
"N": 400, # surface points
}
mesh_parameters = {
"type": "Structured",
"xmax": 20, "xmin": -10, "ymax": 10,
"alpha": 10, # angle of attack [deg]
"Inflation-y": 2, "Inflation-x": 2,
"airfoil_divisions": 200, "wake_divisions": 200,
"vertical_divisions": 150, "front_divisions": 150,
"horizontal_boundary_divisions": 75, "vertical_boundary_divisions": 50,
"airfoil_progression": 0.01, "front_displacement": -0.35,
}
result = launch_simplefoam(
"cases/naca0012_re6e6_aoa10",
Re=6e6,
geometry_parameters=geometry_parameters,
mesh_parameters=mesh_parameters,
refinement_parameters=["LE", "TE"], # refine leading/trailing edges
n_subdomains=20, # MPI ranks
)
print(result["Cl"], result["Cd"])
Incompressible — pimpleFoam, fixed iterations (validated)
from airfoilranssolver.launchers_no_coefficient_control import launch_pimplefoam
geometry_parameters = {"airfoil_type": "NACA", "m": 0.0, "p": 0.0, "t": 0.12, "N": 400}
mesh_parameters = {
"xmax": 10, "xmin": -5, "ymax": 7,
"alpha": 10,
"BL_t": 0.02, "Inflation-y": 1.3, "Inflation-x": 1.5, "r_BL": 1.2,
"AR_target": 0.08, "boundary_ratio": 0.2, "infl_ratio": 2.5,
}
launch_pimplefoam(
"cases/naca0012_re6e6_aoa10/",
Re=6e6,
geometry_parameters=geometry_parameters,
mesh_parameters=mesh_parameters,
refinement_parameters=["LE", "TE"],
n_subdomains=48,
)
Steady → URANS restart (launch_simplefoam_urans)
For cases where the steady solver never reaches a true fixed point (Cl/Cd settle into a periodic limit cycle), this launcher runs a two-phase scheme:
- Steady
simpleFoamuntil either the Cl/Cd stabilization criterion is met or a repeating limit cycle is detected (the same cycle repeatedn_cyclestimes, default 3) — whichever comes first. - URANS — the same case is continued in time-accurate transient mode
(
pimpleFoam, still Spalart–Allmaras, adaptivemaxCotime step) starting from the last steady iteration. It stops once the running time-averaged Cl/Cd become stationary, bounded byurans_max_time.
from airfoilranssolver.launchers_simplefoam_urans import launch_simplefoam_urans
result = launch_simplefoam_urans(
"cases/naca0012_urans",
Re=6e6,
geometry_parameters=geometry_parameters,
mesh_parameters=mesh_parameters,
refinement_parameters=["LE", "TE"],
n_subdomains=20,
n_cycles=3, # limit cycle repeated 3x => steady "converged"
urans_max_co=5.0, # adaptive time-step target Courant number
urans_max_time=200.0, # physical-time cap for the URANS phase
)
print(result["steady_stop_reason"], result["Cl"], result["Cd"])
result["steady_stop_reason"] is fixed_point, limit_cycle, or max_wall. Set
run_urans=False to stop after the steady phase. Reported Cl/Cd are the time-average
over the converged URANS window (or the steady tail when URANS is skipped). The transient
overrides live in Template_simplefoam/system/urans/.
A runnable end-to-end example lives in test.py; the
test/ directory contains a completed incompressible case for reference.
API at a glance
All launchers share the same core arguments:
| Argument | Meaning |
|---|---|
output_path |
Case directory (created if missing; the template is copied here). |
Re |
Chord-based Reynolds number. Incompressible cases use c = U_inf = rho = 1, so nu = 1/Re. |
Mach |
Freestream Mach number (compressible launchers only). |
geometry_parameters |
Airfoil definition passed to airfoilgmesher (airfoil_type: NACA, RAE2822, 30P30N, …). |
mesh_parameters |
Mesh controls passed to airfoilgmesher (domain extent, angle of attack alpha, inflation, divisions, …). |
splitting_parameters, permutation_parameters, refinement_parameters |
Optional airfoilgmesher controls; e.g. refinement_parameters=["LE", "TE"]. |
n_subdomains |
Number of MPI ranks / mesh subdomains (default 48). |
max_iterations |
Hard cap on solver iterations. Patches endTime in the copied controlDict (endTime = max_iterations × deltaT; for the deltaT = 1 solvers this is simply the iteration count). The coefficient criterion can still stop the run earlier. |
min_samples, window, cl_range_tol, cd_range_tol, cl_rel_std_tol, cd_rel_std_tol |
Convergence criterion controls (coefficient-controlled launchers). |
check_every_seconds, min_wall_seconds, max_wall_seconds |
Polling cadence and wall-clock guards for the monitored solve. |
Compressible launchers additionally accept c, T_inf, I (turbulence
intensity), nut_over_nu, and iteration/time-stepping controls. For the
transient launch_rho_pimplefoam the acoustic CFL time step is derived
automatically from the minimum mesh edge.
The coefficient-controlled launchers return a dict with Cl, Cd, Re
(and Mach), the case directory, the freestream/turbulence properties, and the
convergence_info.
Freestream & turbulence initialization
Initial/inlet turbulence quantities are derived from a turbulence intensity I
and a target eddy-viscosity ratio nut/nu (see utils.py):
- Spalart–Allmaras (
simpleFoam): setsnu,nuTilda,nut(compute_sa_initial_properties_from_nut_ratio). - k‑ω SST (
pimpleFoam): setsnu,k,omega,nut(compute_sst_initial_properties_from_nut_ratio). - Compressible (
rho*Foam): uses Sutherland viscosity and setsU_inf,p_inf,T_inf,rho_inf,k,omega,nut,alphat, plusrhoInf/magUInfinforceCoeffs(compute_compressible_freestream).
Outputs
Each run writes into output_path:
| File | Contents |
|---|---|
mesh.png |
Rendered Gmsh mesh. |
airfoil.geo, airfoil.msh |
Generated geometry and mesh. |
Cp.csv / Cp_<patch>.csv |
Surface x, y, Cp, Cf (per airfoil patch for multi-element cases). |
outputs_processed.csv |
Full Cl/Cd (and components) time history. |
coefficients.npz |
Converged Cd, Cl, Re/Mach, freestream props, convergence diagnostics. |
Cl_Cd_history.png |
Cl/Cd convergence plot. |
log.* |
OpenFOAM logs (log.Allrun_pre, log.<solver>, log.reconstructPar, …). |
Cl/Cd are reported as the mean over the final samples of the history.
Steady (simpleFoam/rhoSimpleFoam) cases read instantaneous p and
wallShearStress; transient (pimpleFoam/rhoPimpleFoam) cases use the
time-averaged pMean/wallShearStressMean fields produced by the
fieldAverage function object. Cp = (p − p∞)/q∞, Cf = |τ_w|/q∞.
Benchmarks & validation
Reference cases and analysis scripts live in airfoilranssolver/benchmarking/
(benchmark_*.py to run, analyze_benchmark_*.py to compare against reference
data in benchmarking/data/):
| Case | Conditions | Solver | Reference |
|---|---|---|---|
| NACA 0012 | Re = 6×10⁶, AoA = 10° | pimpleFoam (fixed iterations) |
Cl ≈ 1.08075, Cd ≈ 0.012505 |
| 30P30N (three-element) | Re = 5×10⁶, AoA = 8.12° | simpleFoam (coefficient-controlled) |
Cl ≈ 3.1; also used for a 5–56 core scaling study |
| RAE2822 (transonic) | Mach = 0.729, Re = 6.5×10⁶ | rhoPimpleFoam |
NASA RAE2822 reference Cp (experimental) |
Repository layout
airfoilranssolver/
├── launchers_simplefoam.py # simpleFoam (incompressible, SA) ✅
├── launchers_no_coefficient_control.py# pimpleFoam (fixed iterations) ✅
├── launchers_pimplefoam.py # pimpleFoam (coefficient-controlled)
├── launchers_rhosimplefoam.py # rhoSimpleFoam (compressible, steady)
├── launchers_rhopimplefoam.py # rhoPimpleFoam (compressible, transient)
├── utils.py # convergence test, FOAM dict patching, freestream calcs
├── Templates/ # one OpenFOAM case template per solver
│ ├── Template_simplefoam/
│ ├── Template_pimplefoam/
│ ├── Template_rhoSimpleFoam/
│ └── Template_rhoPimpleFoam/
└── benchmarking/ # validation cases + reference data
test.py # end-to-end usage example
test/ # a completed reference case
Each template provides the OpenFOAM dictionaries (0_org/, constant/,
system/) plus an Allrun_pre (and, where relevant, Allrun) script. The
launchers patch the freestream/turbulence fields and forceCoeffs
(rhoInf, magUInf) before solving.
Publishing
See update_pypi.md for the build-and-upload workflow
(increment the version in pyproject.toml, python -m build,
twine check, twine upload).
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file airfoilranssolver-0.2.0.tar.gz.
File metadata
- Download URL: airfoilranssolver-0.2.0.tar.gz
- Upload date:
- Size: 140.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3b93dd5448ffbe153fe886c3b067a182299237c4bb8d2e0d190b38214128f92
|
|
| MD5 |
ccea60eb94e13ecb48d6e4f396ecc319
|
|
| BLAKE2b-256 |
fd3b3aa188ff151bec6c7a8eb0f1ae8102f448fb46bd524f8d7e8adb80a9a917
|
File details
Details for the file airfoilranssolver-0.2.0-py3-none-any.whl.
File metadata
- Download URL: airfoilranssolver-0.2.0-py3-none-any.whl
- Upload date:
- Size: 192.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff4ba60f6ac88f8e73255ce68cc65a3d249ede1e5cca7f8eb4e6fc5c8ed7c3d2
|
|
| MD5 |
666396576c1adad15d24a2a8f13def9f
|
|
| BLAKE2b-256 |
2f2d96d74c43d59a4d00024d2e3b2b5cfd5ed467fbfb308f546482511eafa958
|