DEV Community

Cover image for How to set up an external aerodynamics case in OpenFOAM.
Spondon Saha
Spondon Saha

Posted on

How to set up an external aerodynamics case in OpenFOAM.

Why is OpenFOAM comparatively more challenging?

While OpenFOAM directory files are primarily written in a plain-text configuration, it's design closely resembles C++. Interestingly, OpenFOAM source code is written entirely in C++. Additionally, the software must be operated using Linux or WSL (if in Windows). This are some of the reasons why it is extremely difficult for beginners to get a grasp of this software.

Reality & pre-requisites

To genuinely use OpenFOAM for running fluid simulations, it is mandatory to get an idea of fluid dynamics from a theoretical perspective. Without it, you will run into a heap of errors and eventually quit out of frustration. You must also get yourself more or less comfortable with basic Linux commands (such as mkdir, cd, cp, rm and their respective extensions) & the nano text editor. Personally, I used Ubuntu through WSL (Windows Sub-System for Linux). This post particularly focuses on external fluid steady-state simulations. The directory will be notably different from internal flow or transient flow simulations.

What to simulate?

For the purpose of my project, which inspired this post, I have simulated three types of wings. This post will contain the baseline configuration rectangular wing case files (To learn more about the project itself, visit the public GitHub repository here). The files are accommodated accordingly, but with a few changes, any solid body can be meshed & simulated. Ensure the solid is exported as a .stl file, as that is best supported by snappyHexMesh.

Folders in an OpenFOAM directory

An external flow simulation directory would look something like this:

caseFiles
.
├── 0/
│   ├── U
│   ├── p
│   ├── k
│   ├── nut
│   └── omega
├── constant/
│   ├── triSurface/
│   │   └── *.stl
│   ├── transportProperties
│   └── turbulenceProperties
└── system/
    ├── blockMeshDict
    ├── controlDict
    ├── snappyHexMeshDict
    ├── fvSchemes
    ├── fvSolution
    └── surfaceFeatureExtract
Enter fullscreen mode Exit fullscreen mode

For a lot of beginners, this may seem insanely confusing. The complexity most definitely dawned on me when I was first getting started. This may be a good time to say that I myself am not particularly very experienced, but I have ran a few simulations & majorly focused them on external aerodynamics, hence this post. OpenFOAM has built in tutorials which have pre-configured cases & can be of good help. Scroll down to learn how to access those.

The "0" folder

This is the folder which includes the case boundary conditions. Every single file in this folder needs mentioning of every single part of the mesh along with their properties to define them.

In our case, U refers to the velocity conditions. We have a box with a wing placed inside it. The "box" acts as a wind tunnel, and the wing is snapped in so fluid can flow over it. Here's the file contents:

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       volVectorField;
    object      U;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

dimensions      [0 1 -1 0 0 0 0];

internalField   uniform (29.886 0 2.616);

boundaryField
{
    inlet
    {
        type            fixedValue;
        value           uniform (29.886 0 2.616);
    }

    outlet
    {
        type            inletOutlet;
        inletValue      uniform (0 0 0);
        value           uniform (29.886 0 2.616);
    }

    top
    {
        type            slip;
    }
    bottom
    {
        type            slip;
    }
    sideMinusY
    {
        type            slip;
    }
    sidePlusY
    {
        type            symmetryPlane;
    }

    wing
    {
        type            noSlip;
    }
}

// ************************************************************************* //

Enter fullscreen mode Exit fullscreen mode

For further context, here I have simulated a 5 degree angle of attack, and so I rotated the velocity by using vectorization. In most cases here, all vector notations will be in the form (x, y, z). As for the boundary conditions, slip & noSlip indicates whether a surface has friction or not. fixedValue forces the specific velocity vector at the inlet, and inletOutlet is a hybrid condition which acts as a zeroGradient condition if fluid leaves the domain, but switches to fixedValue if fluid attempts to flow back, preventing backflow & unphysical value results from the simulation. There are many more boundary conditions that is not within the scope of this post, but explanations are widely available for each along with their use cases.

The other files have a similar structure, but omega, nut & k requires a little bit of calculation. omega refers to turbulent specific dissipation rate, nut refers to turbulent kinematic viscosity, & k refers to turbulent kinetic energy. Each of these has wall functions that must be decided upon to place on the mesh. There are plenty information available for each wall functions, and their specific use cases. For this case, the following was used:

  • omegaWallFunction
  • nutUSpaldingWallFunction
  • kqRWallFunction

Some conditions must be decided upon based on your simulation case. For this, we will use the following:

Air Density = 1.225kgm31.225\, kgm^{-3}

Dynamic Viscosity = 1.7894×105 m2s11.7894 \times 10^{-5}\ m^2s^{-1}

Freestream Velocity = 30ms130\,ms^{-1}

Now with these conditions decided, we can use the following formulas for each of conditions:

ν=μρ=1.7894×1051.225=1.4607×105 m2s1 \nu = \frac{\mu}{\rho} = \frac{1.7894 \times 10^{-5}}{1.225} = 1.4607 \times 10^{-5} \ m^2s^{-1}
kinlet=32(30×0.03)2=1.215 m2/s2 k_{inlet} = \frac{3}{2} (30 \times 0.03)^2 = 1.215 \ m^2/s^2

3 % turbulence was simulated
ωinlet=1.215(0.09)1/4(0.07(0.2))143.75 s1 \omega_{inlet} = \frac{\sqrt{1.215}}{(0.09)^{1/4} (0.07(0.2))} \approx 143.75 \ s^{-1}

0.09 is the CμC_\mu (Turbulent viscosity constant). 0.07 x Chord length is calculating LturbL_{turb} (turbulent length scale)

The "constant" folder

In most OpenFOAM case directories, the constant/ folder contains properties that remain constant (unchanged) throughout the entire simulation. The polyMesh/ subfolder contains the *.stl file, which is then used by snappyHexMesh to snap the shape into the domain. There are two more important configuration files in this directory:

1. turbulenceProperties

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    location    "constant";
    object      turbulenceProperties;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

simulationType  RAS;

RAS
{
    RASModel        kOmegaSST;
    turbulence      on;
    printCoeffs     on;
}

// ************************************************************************* //
Enter fullscreen mode Exit fullscreen mode

Under simulationType, you must enter the turbulence model to be used. Some popular models are laminar, RAS & LES. For this case, RAS was the most appropriate, and so RASModel was later specified with a model-specific coefficient (kOmegaSST in this case).

2. transportProperties

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    location    "constant";
    object      transportProperties;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

transportModel  Newtonian;

nu              nu [0 2 -1 0 0 0 0] 1.4607e-05;

// ************************************************************************* //

Enter fullscreen mode Exit fullscreen mode

This file defines the fluid material properties. In this case, it defines our nu( ν\nu , Kinematic viscosity) as per the previous calculation. It also includes the option to specify the transport model to be used, Newtonian or Non-Newtonian.

The "system" folder

The system/ folder practically defines how a mesh would be made and how the solver will solve the physics defined in 0/ & constant/. It includes a number of important files.

1. controlDict (Simulation Control Directory)

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      controlDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

application     simpleFoam;

startFrom       startTime;
startTime       0;

stopAt          endTime;
endTime         500;

deltaT          1;

writeControl    timeStep;
writeInterval   50;

purgeWrite      2;

writeFormat     ascii;
writePrecision  6;
writeCompression off;

timeFormat      general;
timePrecision   6;

runTimeModifiable true;

functions
{
    forceCoeffs1
    {
        type            forceCoeffs;
        libs            ("libforces.so");

        writeControl    timeStep;
        writeInterval   1;

        patches         (wing);

        // Force coefficients are reported relative to the FREESTREAM
        // direction (5 deg), not the body/chord axes. Our geometry is aligned with the body axes (0 deg), so we need
        // to rotate the freestream vector.
        // dragDir  = freestream unit vector  = (cos5, 0, sin5)
        // liftDir  = perpendicular, rotated +90 deg = (-sin5, 0, cos5)
        dragDir         (0.9962 0 0.0872);
        liftDir         (-0.0872 0 0.9962);

        CofR            (0.05 -0.3 0);
        pitchAxis       (0 1 0);

        rho             rhoInf;
        rhoInf          1.225;

        magUInf         30;
        lRef            0.2;        // chord length
        Aref            0.12;       // planform area = chord x span = 0.2 x 0.6 [m^2]

        binData //Lift/drag distributed along the span
        {
          nBin  20;
          direction (0 1 0);
          cumulative no;
        }
}

{
    type            yPlus;
    libs            ("libfieldFunctionObjects.so");
    writeControl    writeTime;
    patches         (wing);
}

CpField
{
    type            pressure;
    libs            ("libfieldFunctionObjects.so");
    Cp              yes;
    Cref            0;
    rho             rhoInf;
    rhoInf          1.225;
    URef            30;
    writeControl    writeTime;
}

residuals1
{
    type            residuals;
    libs            ("libutilityFunctionObjects.so");
    writeControl    timeStep;
    writeInterval   1;
    fields          (p U k omega);
}

}

// ************************************************************************* //
Enter fullscreen mode Exit fullscreen mode

This directory includes control variables that governs the loop of the solver execution. It also controls at which iterations data is to be saved, the residual limit at which the solver can converge, and a forceCoeffs section which calculates the lift/drag forces and a few other forces for each iteration. This gives a numerical database of the solver run, and is very helpful when analyzing KPIs.

2. fvSchemes (Finite Volume Schemes)

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      fvSchemes;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

ddtSchemes
{
    default         steadyState;
}

gradSchemes
{
    default         Gauss linear;
    grad(U)         cellLimited Gauss linear 1;
}

divSchemes
{
    default                         none;
    div(phi,U)                      bounded Gauss linearUpwindV grad(U);
    div(phi,k)                      bounded Gauss upwind;
    div(phi,omega)                  bounded Gauss upwind;
    div((nuEff*dev2(T(grad(U)))))   Gauss linear;
}

laplacianSchemes
{
    default         Gauss linear corrected;
}

interpolationSchemes
{
    default         linear;
}

snGradSchemes
{
    default         corrected;
}

wallDist
{
    method          meshWave;
}

// ************************************************************************* //
Enter fullscreen mode Exit fullscreen mode

This file defines the discretization schemes used in the governing equations. Since this case is a RANS (Reynold's Averaged Navier Stokes) simulation, and Navier Stokes equations are partial differentiation equations, this directory converts these equations into algebraic forms for the solver to interpret it and solve the equations.

3. fvSolution (Finite Volume Equation Solvers)

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      fvSolution;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

solvers
{
    p
    {
        solver          GAMG;
        tolerance       1e-06;
        relTol          0.1;
        smoother        GaussSeidel;
    }

    "(U|k|omega)"
    {
        solver          smoothSolver;
        smoother        GaussSeidel;
        tolerance       1e-08;
        relTol          0.1;
    }
}

SIMPLE
{
    nNonOrthogonalCorrectors 2;

    residualControl
    {
        p               1e-4;
        U               1e-4;
        "(k|omega|nut)"     1e-4;
    }
}

relaxationFactors
{
    fields
    {
        p               0.3;
    }
    equations
    {
        U               0.7;
        k               0.7;
        omega           0.7;
    }
}

// ************************************************************************* //
Enter fullscreen mode Exit fullscreen mode

This directory controls which linear equation solver needs to be used for the simulation, the tolerances for the solver & the algorithms that are used to calculate the matrix equations. For this case, the GAMG solver is selected as the matrix solver. Since it is a steady-state simulation, the SIMPLE algorithm sub-directory is used, which essentially controls the stability of the run. For transient simulations, PISO or PIMPLE can be used.

4. blockMeshDict ("Wind Tunnel")

/*--------------------------------*- C++ -*----------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM
   \\    /   O peration     |
    \\  /    A nd           |
     \\/     M anipulation  |
\*---------------------------------------------------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      blockMeshDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

// All coordinates below are in millimetres. This factor converts them to
// metres, which is the unit OpenFOAM solvers assume (kg-m-s SI units).
convertToMeters 0.001;

// Domain corner coordinates (mm), derived from chord=200mm, span=600mm:
//   x: -1000 (5c upstream)   to  2200 (10c downstream after 200mm chord)
//   y: -1200 (tip + 3c)      to  0 (Root chord at symmetryPlane)
//   z: -1000 (5c below)      to  1000 (5c above)
vertices
(
    (-1000 -1200 -1000)   // 0
    ( 2200 -1200 -1000)   // 1
    ( 2200   0 -1000)   // 2
    (-1000   0 -1000)   // 3
    (-1000 -1200  1000)   // 4
    ( 2200 -1200  1000)   // 5
    ( 2200   0  1000)   // 6
    (-1000   0  1000)   // 7
);

// Background cell size ~50 mm in every direction.
// 3200/50=64, 1200/50=24, 2000/50=40
// snappyHexMesh refines locally near the wing, this is just the coarse base.
blocks
(
    hex (0 1 2 3 4 5 6 7) (64 24 40) simpleGrading (1 1 1)
);

edges
(
);

// Patch names used later in 0/ boundary conditions and snappyHexMeshDict.
boundary
(
    inlet
    {
        type patch;
        faces
        (
            (0 4 7 3)
        );
    }
    outlet
    {
        type patch;
        faces
        (
            (1 2 6 5)
        );
    }
    bottom
    {
        type patch;
        faces
        (
            (0 3 2 1)
        );
    }
    top
    {
        type patch;
        faces
        (
            (4 5 6 7)
        );
    }
    sideMinusY
    {
        type patch;
        faces
        (
            (0 1 5 4)
        );
    }
    sidePlusY
    {
        type symmetryPlane;
        faces
        (
            (3 7 6 2)
        );
    }
);

mergePatchPairs
(
);

// ************************************************************************* //

Enter fullscreen mode Exit fullscreen mode

This is OpenFOAM's native block-structured mesh generator. It generates a square mesh, and you can configure it to act as the "Wind Tunnel" for an external flow simulation. It is also possible to CAD model it and use a *.stl file, but since OpenFOAM has a native built-in feature, you can choose which path to take based on your preference.

For aerodynamics cases, a general rule for spacing of the domain is to take the length of the object under simulation, then place 5 lengths upstream, 10 lengths downstream, 3 lengths on each side, & 5 lengths on top/bottom. The blockMeshDict text above is marked with notations to help understand the script, feel free to go through the script & try to gain a cognitive understanding of what each line in the script could mean. Do this with all the configuration files, and any doubts will likely be cleared away.

5. snappyHexMeshDict (Adding Solid to Domain)

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      snappyHexMeshDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

castellatedMesh true;
snap            true;
addLayers       true;
// Geometry — surfaces used for castellation and as refinement volumes
geometry
{
    rectangular_600-200.stl
    {
        type        triSurfaceMesh;
        name        wing;
        scale       0.001;
    }

    // A box around the wing where we want extra background refinement
    refinementBox
    {
        type        searchableBox;
        min         (-0.10 -0.70 -0.20);
        max         ( 0.60  0.00  0.20);
    }
    teRefinementBox
    {
       type         searchableBox;
       min          (0.20 -0.7  -0.05);
       max          (0.60 0.00  0.05);
    }
};
// Castellated Mesh Controls

castellatedMeshControls
{
    maxLocalCells           2000000;
    maxGlobalCells          8000000;
    minRefinementCells      10;
    nCellsBetweenLevels     4;

    features
    (
        {
            file    "rectangular_600-200.eMesh";
            level   7;
        }
    );

    refinementSurfaces
    {
        wing
        {
            level   (6 7);
            patchInfo
            {
                type    wall;
            }
        }
    }

    resolveFeatureAngle     60;

    refinementRegions
    {
        refinementBox
        {
            mode    inside;
            levels  ((1e15 5));
        }
        teRefinementBox
        {
            mode    inside;
            levels  ((1e15 7));
         }
    }

    locationInMesh          (1.5 -0.3 0.5);

    allowFreeStandingZoneFaces  true;
}

// Snap Controls

snapControls
{
    nSmoothPatch            3;
    tolerance               4.0;
    nSolveIter              50;
    nRelaxIter              8;

    nFeatureSnapIter        10;
    implicitFeatureSnap     false;
    explicitFeatureSnap     true;
    multiRegionFeatureSnap  false;
}

// Boundary Layer Controls

addLayersControls
{
    relativeSizes    true;

    layers
    {
        wing
        {
            nSurfaceLayers    5;   // 5 layers per spec
        }
    }
    expansionRatio          1.2; // 5 layers @ 1.20 growth rate
    minThickness            5e-06;
    firstLayerThickness    0.01041; // Used a y1 sizing script for a y+ target of 1

    nGrow                    1;
    featureAngle             60;
    nRelaxIter                20;
    nSmoothSurfaceNormals     1;
    nSmoothNormals            3;
    nSmoothThickness         10;
    nMedialAxisIter            10;
    maxFaceThicknessRatio     0.5;
    maxThicknessToMedialRatio 0.5;
    minMedialAxisAngle       80;
    nBufferCellsNoExtrude     4;
    nLayerIter                40;
}

meshQualityControls
{
    maxNonOrtho             65;
    maxBoundarySkewness     20;
    maxInternalSkewness     4;
    maxConcave              80;
    minFlatness             0.5;
    minVol                  1e-13;
    minTetQuality           1e-30;
    minArea                 -1;
    minTwist                0.02;
    minDeterminant          0.001;
    minFaceWeight           0.05;
    minVolRatio             0.01;
    minTriangleTwist        -1;

    nSmoothScale            4;
    errorReduction          0.75;

    relaxed
    {
        maxNonOrtho         75;
    }
}

mergeTolerance              1e-6;

//************************************************************************* //
Enter fullscreen mode Exit fullscreen mode

This is perhaps the most important mesh configuration file for a CFD case in OpenFOAM. It defines background mesh snapping, refinement regions where the mesh needs to be denser to calculate accurate fluid flow, boundary layer addition settings & mesh quality controls to make sure enough meshing iterations are passed before meshing completes so that the mesh is not broken or give out inaccurate solver results. It is heavily recommended to go through this file on a line-by-line basis, as it is quite dense with information about the case & will give out a solid idea about how the meshing is being done by snappyHexMesh.

6. surfaceFeatureExtractDict (Sharp Edges Control)

/*--------------------------------*- C++ -*----------------------------------*/
FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      surfaceFeatureExtractDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

rectangular_600-200.stl
{
    extractionMethod        extractFromSurface;

    extractFromSurfaceCoeffs
    {
        includedAngle       150;
    }
{
    writeObj                yes;
}

// ************************************************************************* //

Enter fullscreen mode Exit fullscreen mode

This is a simple yet essential configuration file that extracts sharp geometric edges & curves from the *.stl and produces an *.eMesh file at the constant/triSurface directory. Here in this case, the angle threshold for a sharp edge is 150 degrees.

That being all the files, here is an image of what a domain looks like after meshing is complete:

domain_mesh

The image is a 2D slice of the mesh to show the refinement. Actual mesh being simulated is in 3D.

How to run this case yourself?

First things first, install and set up Linux using the Ubuntu distro (or any other distros) and install OpenFOAM. Make sure to install ParaView as well. Then, visit the repository here and clone this to your local OpenFOAM directory. Enter the case directory and run the following commands in this order:
blockMesh
surfaceFeatureExtract
snappyHexMesh -overwrite
checkMesh

If checkMesh returns "MESH OK", run touch *.foam to add a .foam file to the case directory. Open the *.foam file using ParaView and visually inspect the mesh using "Surface with Edges". Verify the domain mesh using the Slice filter. It should look something similar to the image above. Then proceed and run:

simpleFoam

After the solver converges (you don't have to wait, you can manually stop the solver at ~100 iterations using Ctrl+C and inspect- for a rough idea of the run), open the previously created *.foam file in ParaView and create some visualizations. Here are some examples:

visual1

visual2

The built-in case

OpenFOAM has a built in case with pre-configured files which follows a structure that is very similar to the one explained in this post. It is a steady-state incompressible flow simulation over a motorbike. Bring the case into your local directory by using the command cp -r $FOAM_TUTORIALS/incompressible/simpleFoam/motorBike, then paste it in your local directory. Go through all the files in the three folders line-by-line and compare it with this case to gain a much greater depth of understanding about steady-state external aerodynamics simulations in OpenFOAM.

Conclusion

Learning CFD is already challenging enough, and adding OpenFOAM to it only makes it more complex. Regardless of that, starting with this more complex form will be frustrating yet rewarding. It will also help genuinely knowing the physics when switching to a GUI-based CFD software.

Top comments (0)