OpenFOAM 14 Case 01: Laminar Flow Between Parallel Plates, Validated Against Poiseuille

by giux CFD and OpenFOAM 8 min read

This is the first case of the Learning OpenFOAM 14 path: steady, incompressible laminar flow between two parallel plates. I chose it on purpose because it has an exact solution, the Poiseuille profile, so the numerical result can be checked with numbers and not just by eye. In the end the computed profile matches the theoretical one to within 0.2%.

What follows is the complete reproducible procedure for the case, with analytical validation and ParaView visualization, in the same order as the original handout: OpenFOAM 14 (Foundation / CFD Direct) and ParaView 5.13.2.

1. Purpose and expected result

This handout describes the complete procedure required to build, run, validate and visualize a steady two-dimensional incompressible laminar flow between two parallel no-slip plates using OpenFOAM 14.

The case is deliberately simple because it has a closed-form Poiseuille solution. The numerical result can therefore be checked quantitatively rather than only visually.

Main target results:

QuantityValue
OpenFOAM version14
Solver moduleincompressibleFluid via foamRun
Mesh200 × 40 × 1 = 8,000 hexahedral cells
Mean inlet velocity0.010 m/s
Kinematic viscosity1.0e-6 m²/s
Hydraulic-diameter Reynolds numberRe = 200
Final steady iteration label2000
CFD sampled maximum Ux0.014962547 m/s
Analytical maximum Ux0.015000000 m/s
Normalized RMSE0.150343 %
Maximum absolute error / Umax0.187193 %
VisualizationParaView 5.13.2 client-server using pvserver

The accepted numerical solution reproduces the analytical Poiseuille profile to substantially better than 0.2% over the sampled cross-section.

2. Prerequisites

The following commands should be available:

foamVersion
command -v blockMesh
command -v checkMesh
command -v foamRun
command -v foamPostProcess
command -v pvserver

Expected OpenFOAM version:

14

The environment should define $FOAM_RUN. The case in this handout is created under:

$FOAM_RUN/01_laminar_channel

3. Physical model

Channel dimensions:

ParameterValue
Channel length L0.500 m
Plate spacing H0.010 m
Numerical depth0.001 m
Front/back conditionempty

Fluid and inlet conditions:

ParameterValue
Kinematic viscosity ν1.0e-6 m²/s
Mean inlet velocity Umean0.010 m/s

The hydraulic diameter for two wide parallel plates is approximately

Dh = 2H = 0.020 m

and therefore

Re = Umean·Dh / ν = (0.01 · 0.02) / 10⁻⁶ = 200.

The flow is intentionally well inside the laminar regime.

2D parallel-plate channel for Case 01 with noSlip walls, inlet and outlet
Channel geometry and structured mesh for Case 01.

4. Create the case directory

mkdir -p $FOAM_RUN/01_laminar_channel/{0,constant,system}
cd $FOAM_RUN/01_laminar_channel

5. Mesh definition

Create system/blockMeshDict:

FoamFile
{
    format      ascii;
    class       dictionary;
    object      blockMeshDict;
}

scale 1;

vertices
(
    (0.0  -0.005  0.0)
    (0.5  -0.005  0.0)
    (0.5   0.005  0.0)
    (0.0   0.005  0.0)

    (0.0  -0.005  0.001)
    (0.5  -0.005  0.001)
    (0.5   0.005  0.001)
    (0.0   0.005  0.001)
);

blocks
(
    hex (0 1 2 3 4 5 6 7) (200 40 1) simpleGrading (1 1 1)
);

edges
(
);

boundary
(
    inlet
    {
        type patch;
        faces
        (
            (0 4 7 3)
        );
    }

    outlet
    {
        type patch;
        faces
        (
            (1 2 6 5)
        );
    }

    walls
    {
        type wall;
        faces
        (
            (0 1 5 4)
            (3 7 6 2)
        );
    }

    frontAndBack
    {
        type empty;
        faces
        (
            (0 3 2 1)
            (4 5 6 7)
        );
    }
);

mergePatchPairs
(
);

The structured mesh contains

200 × 40 × 1 = 8000

hexahedral cells.

6. Run control

Create system/controlDict before running the mesh utilities:

FoamFile
{
    format      ascii;
    class       dictionary;
    location    "system";
    object      controlDict;
}

solver          incompressibleFluid;

startFrom       startTime;
startTime       0;

stopAt          endTime;
endTime         2000;

deltaT          1;

writeControl    runTime;
writeInterval   200;

purgeWrite      0;

writeFormat     ascii;
writePrecision  8;
writeCompression off;

timeFormat      general;
timePrecision   6;

runTimeModifiable true;

Because the case uses steadyState in fvSchemes, the values 1…2000 are iteration/pseudo-time labels, not a 2000-second physical transient.

7. Generate and verify the mesh

blockMesh | tee log.blockMesh
checkMesh | tee log.checkMesh

Useful check:

grep -E "points:|faces:|cells:|hexahedra:|Mesh OK" log.checkMesh

Expected result:

cells:          8000
hexahedra:      8000
Mesh OK.

8. Initial and boundary fields

8.1 Velocity field: 0/U

FoamFile
{
    format      ascii;
    class       volVectorField;
    location    "0";
    object      U;
}

dimensions      [velocity];

internalField   uniform (0 0 0);

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

    outlet
    {
        type            zeroGradient;
    }

    walls
    {
        type            noSlip;
    }

    frontAndBack
    {
        type            empty;
    }
}

8.2 Pressure field: 0/p

FoamFile
{
    format      ascii;
    class       volScalarField;
    location    "0";
    object      p;
}

dimensions      [kinematicPressure];

internalField   uniform 0;

boundaryField
{
    inlet
    {
        type            zeroGradient;
    }

    outlet
    {
        type            fixedValue;
        value           uniform 0;
    }

    walls
    {
        type            zeroGradient;
    }

    frontAndBack
    {
        type            empty;
    }
}

9. Fluid properties and laminar model

9.1 constant/physicalProperties

FoamFile
{
    format      ascii;
    class       dictionary;
    location    "constant";
    object      physicalProperties;
}

viscosityModel  constant;

nu              1e-06;

9.2 constant/momentumTransport

FoamFile
{
    format      ascii;
    class       dictionary;
    location    "constant";
    object      momentumTransport;
}

simulationType  laminar;

10. Numerical schemes

Create system/fvSchemes:

FoamFile
{
    format      ascii;
    class       dictionary;
    location    "system";
    object      fvSchemes;
}

ddtSchemes
{
    default         steadyState;
}

gradSchemes
{
    default         Gauss linear;
}

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

laplacianSchemes
{
    default         Gauss linear corrected;
}

interpolationSchemes
{
    default         linear;
}

snGradSchemes
{
    default         corrected;
}

11. Linear solvers and pressure-velocity coupling

Create system/fvSolution:

FoamFile
{
    format      ascii;
    class       dictionary;
    location    "system";
    object      fvSolution;
}

solvers
{
    p
    {
        solver          PCG;
        preconditioner  DIC;
        tolerance       1e-10;
        relTol          0.05;
    }

    pFinal
    {
        $p;
        relTol          0;
    }

    U
    {
        solver          smoothSolver;
        smoother        symGaussSeidel;
        tolerance       1e-10;
        relTol          0.05;
    }

    UFinal
    {
        $U;
        relTol          0;
    }
}

PIMPLE
{
    momentumPredictor        no;
    nOuterCorrectors         1;
    nNonOrthogonalCorrectors 0;
}

relaxationFactors
{
    fields
    {
        p   0.3;
    }

    equations
    {
        U   0.7;
    }
}

12. Execute the CFD calculation

Run a final mesh check and start the solver:

checkMesh
foamRun | tee log.foamRun

Inspect the final iterations:

tail -40 log.foamRun

The validated run reached the final iteration label 2000 and terminated normally with End.

Typical final behavior:

QuantityObserved behavior
Execution timeapproximately 27.7 s
Pressure initial residualorder 1e-10
Pressure iterationsoften 0–1, occasionally a few
Local continuity errororder 1e-12
Terminationnormal End

13. Analytical Poiseuille solution

For fully developed laminar flow between parallel plates, the analytical velocity profile is

u(y) = (3/2)·Umean·[1 − (2y/H)²].

With Umean = 0.010 m/s and H = 0.010 m:

Umax,theory = 1.5·Umean = 0.015 m/s.

14. Velocity-profile post-processing

Create system/graphUniform:

FoamFile
{
    format      ascii;
    class       dictionary;
    object      graphUniform;
}

start           (0.45 -0.004875 0.0005);
end             (0.45  0.004875 0.0005);
nPoints         40;

fields          (U);

axis            y;

#includeEtc "caseDicts/postProcessing/graphs/graphUniform.cfg"

Run the function object explicitly:

foamPostProcess -solver incompressibleFluid -func graphUniform -latestTime

Locate the result:

find postProcessing/graphUniform -type f

Expected file:

postProcessing/graphUniform/2000/line.xy

Inspect it:

head -10 postProcessing/graphUniform/2000/line.xy
tail -10 postProcessing/graphUniform/2000/line.xy
wc -l postProcessing/graphUniform/2000/line.xy

The file contains one header plus 40 sampled points. Its columns are:

y   U_x   U_y    U_z

Representative values:

y [m]CFD Ux [m/s]Uy [m/s]
-0.0048750.00073033707-5.38e-17
-0.0041250.0047752809-2.79e-16
-0.0031250.0091198502-4.20e-16
-0.0028750.010018726-5.09e-16
+0.0028750.010018726-2.82e-15
+0.0038750.0059737828-4.36e-15
+0.0048750.00073033707-7.37e-16

The transverse velocity is effectively zero and Uz is zero, as expected for this 2D channel.

15. Quantitative validation script

The following command compares every sampled point with the analytical Poiseuille profile:

awk '
BEGIN {
    H=0.01
    Umean=0.01
    UmaxTheory=1.5*Umean
    umax=-1
}
$1 !~ /^#/ && NF >= 4 {
    y=$1
    u=$2

    uth=UmaxTheory*(1-(2*y/H)^2)

    e=u-uth
    ae=(e<0 ? -e : e)

    sumsq += e*e
    n++

    if (ae > maxerr) {
        maxerr=ae
        maxerr_y=y
        maxerr_cfd=u
        maxerr_theory=uth
    }

    if (u > umax) {
        umax=u
        umax_y=y
    }
}
END {
    rmse=sqrt(sumsq/n)

    printf "Points              : %d\n", n
    printf "CFD maximum Ux      : %.10f m/s\n", umax
    printf "Location of max     : %.7f m\n", umax_y
    printf "Theoretical Umax    : %.10f m/s\n", UmaxTheory
    printf "RMSE                : %.10e m/s\n", rmse
    printf "Normalized RMSE     : %.6f %%\n", 100*rmse/UmaxTheory
    printf "Maximum abs error   : %.10e m/s\n", maxerr
    printf "Max error / Umax    : %.6f %%\n", 100*maxerr/UmaxTheory
    printf "Max-error y         : %.7f m\n", maxerr_y
    printf "CFD there           : %.10f m/s\n", maxerr_cfd
    printf "Theory there        : %.10f m/s\n", maxerr_theory
}' postProcessing/graphUniform/2000/line.xy

Validated result:

MetricValue
Points compared40
CFD maximum Ux0.0149625470 m/s
Location of CFD maximumy = -0.0001250 m
Theoretical Umax0.0150000000 m/s
RMSE2.2551518927e-05 m/s
Normalized RMSE0.150343 %
Maximum absolute error2.8079000000e-05 m/s
Maximum error / Umax0.187193 %
Location of maximum errory = +0.0001250 m
CFD value at max-error point0.0149625460 m/s
Theory at same point0.0149906250 m/s

The sampling uses 40 points, therefore the centerline y = 0 is not sampled exactly. The closest samples are approximately at y = ±0.000125 m.

OpenFOAM velocity profile compared with the analytical Poiseuille solution
Numerical and analytical velocity profiles.

16. Pressure verification

For plane Poiseuille flow, the expected kinematic-pressure gradient is

dp/dx = −12·ν·Umean / H² = −1.2 × 10⁻³ m/s².

Across 0.5 m, the corresponding kinematic pressure drop is approximately

6 × 10⁻⁴ m²/s²,

or approximately 0.6 Pa for water with ρ ≈ 1000 kg/m³.

To sample pressure along the developed region, temporarily use the following system/graphUniform:

FoamFile
{
    format      ascii;
    class       dictionary;
    object      graphUniform;
}

start           (0.30 0 0.0005);
end             (0.49 0 0.0005);
nPoints         40;

fields          (p);

axis            x;

#includeEtc "caseDicts/postProcessing/graphs/graphUniform.cfg"

Then run:

foamPostProcess -solver incompressibleFluid -func graphUniform -latestTime

The sampled pressure should decrease approximately linearly in the fully developed region.

17. ParaView client-server visualization

For interactive visualization, use ParaView in client-server mode.

17.1 Start the server

Check the installed version:

command -v pvserver
pvserver --version

Validated version:

paraview version 5.13.2

Create the OpenFOAM marker file:

cd $FOAM_RUN/01_laminar_channel
touch 01_laminar_channel.foam

Start pvserver:

pvserver --bind-address=0.0.0.0 --server-port=11111

17.2 Connect from ParaView on the workstation

Use the same ParaView version as the server: 5.13.2.

In ParaView:

File -> Connect... -> Add Server

Configure:

Name: CFD Server
Server Type: Client / Server
Host: <server IP address>
Port: 11111

After connecting, File → Open browses the remote Linux filesystem. Open:

$FOAM_RUN/01_laminar_channel/01_laminar_channel.foam

Select internalMesh, enable U and p, then press Apply.

17.3 Visual checks

At the final iteration label 2000:

  1. Coloring → U → Magnitude
  2. Rescale to Data Range
  3. verify a symmetric velocity distribution with maximum velocity at the channel center and zero velocity at the no-slip walls
  4. Coloring → p
  5. verify a regular pressure decrease from inlet to outlet

For a clean streamline view, the channel may be displayed as Outline, or as a low-opacity surface.

18. Streamlines

Select the OpenFOAM source and create:

Filters -> Stream Tracer

Recommended settings:

PropertyValue
VectorsU
Integration DirectionFORWARD
Seed TypeLine
Point1(0.001, -0.0045, 0.0005)
Point2(0.001, +0.0045, 0.0005)
Resolution20
Maximum Streamline Length0.5 m

Resolution here controls the number of seed intervals along the seed line; it is not the CFD mesh resolution.

For this steady case the streamlines coincide with particle trajectories. They should be straight and parallel to the x direction.

19. Generic troubleshooting

SymptomCheck / correction
blockMesh or checkMesh cannot start the caseensure system/controlDict exists
foamPostProcess creates no postProcessing directorycall the function explicitly with -func graphUniform
expected line_U.xy is not presentOpenFOAM 14 writes line.xy for this configuration
graphUniform include is not founduse caseDicts/postProcessing/graphs/graphUniform.cfg
only a small number of streamlines appearsverify that the entire seed line lies inside the CFD domain
streamline view is visually cluttereddisplay the CFD source as Outline or reduce surface opacity

20. Acceptance criteria

Acceptance criterionStatus
OpenFOAM environment operationalPASS
Mesh generated and checkMesh successfulPASS
Steady calculation reaches normal EndPASS
Velocity profile symmetricPASS
Uy negligible and Uz = 0PASS
Normalized RMSE < 0.2%PASS – 0.150343%
Pressure decreases along the channelPASS
ParaView client-server visualization operationalPASS
Streamlines span the channel and remain parallel to xPASS

Final status: CASE 01 COMPLETE AND VALIDATED.

21. Reproducibility checklist

  • Source the OpenFOAM 14 environment.
  • Create $FOAM_RUN/01_laminar_channel.
  • Create blockMeshDict and controlDict.
  • Run blockMesh and checkMesh.
  • Create 0/U and 0/p.
  • Create constant/physicalProperties and constant/momentumTransport.
  • Create fvSchemes and fvSolution.
  • Run foamRun | tee log.foamRun.
  • Sample U at x = 0.45 m with graphUniform.
  • Compare the sampled profile against Poiseuille theory.
  • Optionally sample p along x and compare with the analytical pressure gradient.
  • Create the .foam marker file.
  • Start pvserver and connect from ParaView 5.13.2.
  • Inspect U, p and streamlines at the final iteration label.

This case is the validated baseline for the subsequent CFD training simulations: in Case 02 the same channel is used for a mesh convergence study.

#CFD #CFD validation #computational fluid dynamics #mesh #OpenFOAM #ParaView #Poiseuille flow

Leave a Reply

Your email address will not be published. Required fields are marked *