# Instructional workshop on OpenFOAM programming...

### Transcript of Instructional workshop on OpenFOAM programming...

Outline

Recap of Week 1

1d Heat Equation

Finite difference

lduMatrix and fvMatrix

FOAM Finite volume - Operators (laplacian)

Recap of Week 1

I Code development compile/running

I Basic FOAM data-structures

I FOAM polyMesh and fvMesh

I FOAM fields

1d Heat equation

∂φ

∂t− κ∂

2φ

∂x2= f (x , t) 0 ≤ x ≤ L, t ≥ 0 (1)

with initial conditions,

φ(x , 0) = g0(x)

and boundary conditions,

I Dirichlet type

φ(0, t) = φ0 and φ(L, t) = φL

I Neumann type

∂φ

∂x(0, t) = φ′0 and

∂φ

∂x(L, t) = φ′L

Robins BC discussed on Day 2 (user defined BCs)

Finite difference (FD) basics 12 FINITE DIFFERENCE METHOD 3

ContinuousPDE forφ(x, t)

Finitedifference−−−−−−−−→

DiscreteDifferenceEquation

Solutionmethod−−−−−−−−→ φm

i approxi-mation to φ(x, t)

Figure 1: Relationship between continuous and discrete problems.

uniformly spaced in the interval 0 ≤ x ≤ L such that

xi = (i − 1)∆x, i = 1, 2, . . . N

where N is the total number of spatial nodes, including those on the boundary.Given L and N , the spacing between the xi is computed with

∆x =L

N − 1.

Similarly, the discrete t are uniformly spaced in 0 ≤ t ≤ tmax:

tm = (m − 1)∆t, m = 1, 2, . . . M

where M is the number of time steps and ∆t is the size of a time step2

∆t =tmax

M − 1.

The solution domain is depicted in Figure 2. Table 1 summarizes the notationused to obtain the approximate solution to Equation (1) and to analyze theresult.

2.2 Finite Difference Approximations

The finite difference method involves using discrete approximations like

∂φ

∂x≈ φi+1 − φi

∆x(3)

2The first mesh lines in space and time are at i = 1 and m = 1 to be consistent with theMatlab requirement that the first row or column index in a vector or matrix is one.

Table 1: Notation

Symbol Meaning

φ(x, t) Continuous solution (true solution).

φ(xi, tm) Continuous solution evaluated at the mesh points.

φmi Approximate numerical solution obtained by solving

the finite-difference equations.

I x discretized as uniformly spaced interval 0 ≤ x ≤ L such that

xi = (i − 1)∆x i = 1, 2, ...N where, ∆x =L

N − 1

I Similarly, discretize t uniformly in 0 ≤ t ≤ T

tm = (m − 1)∆t i = 1, 2, ...M where, ∆t =T

M − 1

φ(x , t)→ φ(xi , tm)→ φmi

1Source: http://www.nada.kth.se/˜jjalap/numme/FDheat.pdf

2nd order central differenceTaylor series expansion

I Forward

φi+1 = φi + ∆x∂φ

∂x

∣∣∣∣xi

+∆x2

2

∂2φ

∂x2

∣∣∣∣xi

+ ... (2)

I Backward

φi−1 = φi −∆x∂φ

∂x

∣∣∣∣xi

+∆x2

2

∂2φ

∂x2

∣∣∣∣xi

− ... (3)

Adding (2) and (3) we get

φi+1 + φi−1 = 2φi + ∆x2 ∂2φ

∂x2

∣∣∣∣xi

+ O(∆x)4 (4)

∂2φ

∂x2

∣∣∣∣xi

=φi+1 − 2φi + φi−1

∆x2+ O(∆x)2 (5)

2nd order central difference

Subtracting (2) and (3) we get

φi+1 − φi−1 = 2∆x∂φ

∂x

∣∣∣∣xi

+ (∆x)3∂3φ

∂x3+ ... (6)

∂φ

∂x

∣∣∣∣xi

=φi+1 − φi−1

2∆x+ O(∆x)2 (7)

Alternatively, using equation (2),

∂φ

∂x

∣∣∣∣xi

=φi+1 − φi

∆x+ O(∆x) (8)

and equation (3),

∂φ

∂x

∣∣∣∣xi

=φi − φi−1

∆x+ O(∆x) (9)

Boundary condition

I Dirichlet typeφi=1 = φ0 φi=N = φL

I Neumann type (1st order)

φ1 = φ2 − φ′0∆x and φN = φN−1 + φ′L∆x

I Neumann type (2nd order)

φi=0 = φ2 − 2φ′0∆x and φN+1 = φN − 2φ′L∆x

I Halo nodes i = 0 and i = N + 1

Make FD look like FV - owner/neighbour

I Need cell-to-cell connectivity for FOAM FD

I Avoid this by modifying FD implementation

I Original domain

i=Ni=1

i=Ni=1

RLL R L R L R+1/2-1/2

I Modified domain (half points - faces)

i=Ni=1

i=Ni=1

R L

Make FD look like FV - owner/neighbour

I Modified domain

i=Ni=1

i=Ni=1

R L

I Split into face contribution (interior faces)

∂2φ

∂x2

∣∣∣∣xi

=φi+1 − 2φi + φi−1

∆x2=

1

∆x

φi+1 − φi

∆x︸ ︷︷ ︸L

− φi − φi−1∆x︸ ︷︷ ︸R

(10)

φi+1 − φi

∆x=

1

∆x

φi+1︸︷︷︸L

− φi︸︷︷︸R

(11)

φi − φi−1∆x

=1

∆x

φi︸︷︷︸L

−φi−1︸︷︷︸R

(12)

Make FD look like FV - owner/neighbour

Sign consistency examples (ignore ∆x for clarity)

I Case (i)

i=Ni=1

i=Ni=1

R L

i

RL

f1 f2

R L

i-1 i+1

RL

f1 f2

L R

ii-1 i+1

f1 = φL − φR = φi−1 − φi (13)

f2 = φL − φR = φi − φi+1 (14)

−f1︸︷︷︸R

+ f2︸︷︷︸L

= −(φi+1 − 2φi + φi−1) = − ∂2φ

∂x2

∣∣∣∣xi

(15)

Make FD look like FV - owner/neighbour

I Case (ii)

i=Ni=1

i=Ni=1

R L

i

RL

f1 f2

R L

i-1 i+1

RL

f1 f2

L R

ii-1 i+1

f1 = φL − φR = φi−1 − φi (16)

f2 = φL − φR = φi+1 − φi (17)

−f1︸︷︷︸R

− f2︸︷︷︸R

= −(φi+1 − 2φi + φi−1) = − ∂2φ

∂x2

∣∣∣∣xi

(18)

I As long as L/R is consistently defined we get the correctexpression

Make FD look like FV - owner/neighbour

Neumann boundary conditions

I Remember the fact that

φ′0 =φ1 − φ0

∆x= φf

1 and φ′L =φN+1 − φN

∆x= φf

N (19)

where, f denotes the face value

I Simply enforce values φ′0 and φ′L for the Neumann patch face

Make FD look like FV - owner/neighbour

Dirichlet boundary conditions

I Patch face value set to enforces correct values (weak)I Introduce halo nodes at both ends

I At i = NφN+1 − φN

∆x=

1

∆x(φL − φN ) (20)

I At i = 1φi=1 − φi=0

∆x=

1

∆x(φ1 − φ0) (21)

I Remember that if dy and dz is = 1 then ∆x = ∆V , where∆V is cell volume

Make FD look like FV - owner/neighbour

Sign consistency for boundary (ignore ∆x for clarity)

I Case (i) At x = 0

i = 1LR

fx=0

R L

i = 0 i = 2

I Dirichlet type

fx=0 =φL − φR

∆x=φ1 − φx=0

∆x(22)

I Neumann typefx=0 = φ′0 (23)

Make FD look like FV - owner/neighbour

Sign consistency for boundary (ignore ∆x for clarity)

I Case (i) At x = 0

i = NRL

fx=L

L R

i = N+1

I Dirichlet type

fx=L =φL − φR

∆x=φN − φN+1

∆x(24)

I Neumann typefx=L = φ′L (25)

How to access field boundary values ?

I fixedValue patch type

const tmp<scalarField> sf;scalarField some( x.boundaryField()[ipatch].

valueBoundaryCoeffs(sf) );

I fixedGradient patch type

scalarField some( x.boundaryField()[ipatch].gradientBoundaryCoeffs() );

Hands on - FD solver I

I For convenience set dx = dy = dz = 1 in the 1d grid

I Ignore time term for now and solve for steady-state

I Create a volScalarField phi and read from input file

I Create a surfaceScalarField named flux

I Loop over all internal faces of flux and set face value

φf = φowner − φneighbour (26)

I For Neumann patches set face value to the gradient

φf = φ′(x = 0 or x = L) (27)

I For Dirichlet patches set face value using

φf = φowner − φ(x = 0 or x = L) (28)

Hands on - FD solver I

I Create a volScalarField named residue and initialize to zero

I Loop over all faces of flux

I Add the face value to the owner cell and subtract fromneighbour cell of residue

I Do the same for boundary faces (no neighbour cell)

I Now residue contains the laplacian

Matrix forms

I Possible to construct matrix version of the discrete operator

Matrix form of discrete system of Poisson Equations

1 2 3 4 5 6 7 8 9 10

1 −1 1 × × × × × × × ×2 1 −2 1 × × × × × × ×3 × 1 −2 1 × × × × × ×4 × × 1 −2 1 × × × × ×5 × × × 1 −2 1 × × × ×6 × × × × 1 −2 1 × × ×7 × × × × × 1 −2 1 × ×8 × × × × × × 1 −2 1 ×9 × × × × × × × 1 −2 110 × × × × × × × × 1 −1

x1x2x3x4x5x6x7x8x9x10

=

f1f2f3f4f5f6f7f8f9f10

I BC enforced separately using boundaryCoeff and internalCoeff

I For zeroGradient BC this matrix becomes the full system

I Most of the entries of A are zeros =⇒ A is sparse

Sparse Matrix Storage in FOAM

I FOAM uses the LDU sparse matrix storage

I Uses the owner/neighbour data for addressing non-zero L/Uentries

I Optimized for symmetric matrices also handles asymmetricmatrix

I Key limitation “cannot store entries beyond first level of cellneighbours”

I Big issue for hybrid and highly skewed meshesI Higher order FVM (> 2nd order)

lduMatrix

I Abstract base class implementing the ludMatix and solvers

I Uses the lduMatrix to construct the owner/neighbouraddressing scheme

/// Construct given an LDU addressed mesh.lduMatrix (const lduMesh &)/// Construct given an LDU addressed mesh and an

Istream.lduMatrix (const lduMesh &, Istream &)

I lduMatrix is not usable, need non-abstract class fvMatrix

I FOAM operators like grad , div , etc, return fvMatrix

fvMatrix

fvMesh

fvMatrix

field source solver

Matrix CoeffslduAddressing

I Solves the system of equations

Ax = b (29)

where, x is the field , b is the source and sparse matrix Aentries are Matrix Coeffs.

I Subject to BC specified on the field using a specified solver

fvm and fvc namespace

I Two possible ways to construct discrete operatorsI Matrix-free scoped in fvc namespaceI Matrix-based scoped in fvm namespace

I Explicit matrix construction impossible for non-linearoperations like limiting

I Operators defined in fvc return field types

I Operators defined in fvm return fvMatrix types

Krylov solvers 2

I For large sparse systems it only possible to performmatrix-vector products (Ax)

I Even explicit construction of matrix A is impossible

I But it is possible to construct a Krylov sequence{x,Ax,A2x,A3x, ...

}I Krylov sub-space methods build up on the sequence and look

forI Eigenvectors andI Invariant sub-spaces

within the Krylov sub-space

I A large class of LA algorithms like CG, BICG, GMRES arebased on Krylov sub-spaces

I Preconditioning is normally performed to speed upconvergence

2Source:‘‘The Matrix Eigenvalue Problem” by David S. Watkins

Krylov sub-space solvers in fvMatrix

I Krylov sub-space solvers available for fvMatrixI Symmetric matrix

I GAMG - Geometric/Algebraic Multi-Grid solverI ICCG - Incomplete Cholesky Conjugate GradientI PCG - Preconditioned Conjugate Gradient solver

I Asymmetric matrixI BICCG - Bi-Conjugate GradientI GAMG - Geometric/Algebraic Multi-Grid solverI PBiCG - Preconditioned Bi-Conjugate Gradient

I Matrix preconditionersI DIC - Diagonal Incomplete CholeskyI DILU - Diagonal Incomplete LUI GAMG - Geometric/Algebraic Multi-Grid solver

Multigrid and Krylov solver beyond the scope of the workshop

How to use fvMatrix ?

I Will look at the specific constructor using field variable

fvScalarMatrix A( x, x.dimensions() );

I Access non-zero upper diagonal entries using A.upper()

I Access diagonal entries using A.diag()

I Access non-zero lower diagonal entries using A.lower()I First access to A.upper() after construction makes A

symmetricI lowerPtr = upperPtr ;

I Since fvMatrix entries are based onmesh.owner()/neighbour() following holds true

I Size of diag().size() = number of cellsI Size of upper()/lower().size() = number of internal faces

negDiagSum() function

I Negated row-wise sum of non-zero off diagonal coeffs

void Foam::lduMatrix::negSumDiag(){const scalarField& Lower

= const_cast<const lduMatrix &>(*this).lower();const scalarField& Upper

= const_cast<const lduMatrix &>(*this).upper();scalarField& Diag = diag();

const labelUList& l = lduAddr().lowerAddr();const labelUList& u = lduAddr().upperAddr();

for (register label face=0; face<l.size(); face++){

Diag[l[face]] -= Lower[face];Diag[u[face]] -= Upper[face];

}}

Hands on - Create FD matrix A from example

I Ignore BC patches for now

I Access A.upper() and assign that to 1

I Calculate the diagonal term using the negSumDiag() function

I Now check if the matrix is symmetric using A.symmetric()

I It should return true or false ?

Hints

I A.upper()/diag() is a scalarField so forAll works

I Lazy yet efficient option is to use assignment operator =

Specifying the BC

The BC is specified using two fields

A.boundaryCoeffs()

I Source term that goes to the RHS

I For Neumann condition the −fixedGradient value is specified

I For Dirichlet condition the −fixedValue divided by ∆x

A.internalCoeffs()

I Term that goes to the LHS matrix coefficient

I For Neumann condition it is zero

I For Dirichlet condition it is −1 divided by ∆x

Show on black-board how this calculated

Access boundary values (fixedValue)

I Access field connected to A using A.psi()

forAll( A.psi().boundaryField(), ipatch ) {if(A.psi().boundaryField()[ipatch].type()== "fixedValue"

){const tmp<scalarField> sf;scalarField value(

A.psi().boundaryField()[ipatch].valueBoundaryCoeffs(sf)

);A.boundaryCoeffs()[ipatch] = -1.0 * value;A.internalCoeffs()[ipatch] = -1.0;

}

Access boundary values (fixedGradient)

if(A.psi().boundaryField()[ipatch].type()== "fixedGradient"

){Info << ipatch << " fixedGradient ";scalarField gradient(

A.psi().boundaryField()[ipatch].gradientBoundaryCoeffs()

);A.boundaryCoeffs()[ipatch] = -1.0 * gradient;Info << " gradient = " << gradient << "\n";

}

Access boundary values (zeroGradient)

if(A.psi().boundaryField()[ipatch].type()== "zeroGradient"

){Info << "Patch " << ipatch << ": is zeroGradient\n

";}

How to access field boundary values ?

I Number of coefficients equal the number of faces in the patch

I Consequence of the fact that it is possible to have differentvalues for each face in the patch

I Achieved using the nonuniform key word in the field dictionary

I Inlet velocity profiles for pipe flows

Hands on: Print boundary field type and value

I Loop over all patches of the field x

I Print the type of boundary and boundary value

I Set correct values for A.boundaryCoeffs()/internalCoeffs()

Solving the LA problem

I So far we have only discussed setting up Ax

I What about b ?

I It is accessed using A.source()

I If everything is set then we are ready to solve the LA problem

Ax = b (30)

Solving the LA problem

I So far we have only discussed setting up Ax

I What about b ?

I It is accessed using A.source()

I If everything is set then we are ready to solve the LA problem

Ax = b (30)

The solution dictionary

system/fvSolution dictionary

solvers{x // The field variable name{

solver PCG;preconditioner none;tolerance 1e-06;relTol 0;

}}

Hands on - Post-process 1d data

I 1d case is actually 3d with once cell thickness

I Print the result to a file profile.dat

#include <fstream>....std::ofstream fout("initial_sol.dat");forAll( mesh.C(), i )fout << mesh.C()[i][0] << " " << x[i] << "\n";

I Visualize using gnuplot

gnuplot> plot "initial_sol.dat" using 1:2 with lines

Hands on - Using fvMatrix for solution

I Solve the 1d FD problem without time derivative usingfvMatrix

I Proper boundary condition and value should be set

I Choose appropriate solver (write solver dictionary)

I Set the A.source() to zero

I Essentially solving the equation

∂2φ

∂x2= 0 (31)

I Should give a straight line profile if Dirichlet BC is specifiedon both ends (use the previous hand-on to visualize results ingnuplot)

Finite Volume (FV) discretization of 1d heat equation

I FV domain

i=Ni=1

i=Ni=1

R L

I Split into face contribution (interior faces)

∂2φ

∂x2

∣∣∣∣xi

=φi+1 − 2φi + φi−1

∆x2=

1

∆x

φi+1 − φi

∆x︸ ︷︷ ︸L

− φi − φi−1∆x︸ ︷︷ ︸R

(32)

φi+1 − φi

∆x=

1

∆x

φi+1︸︷︷︸L

− φi︸︷︷︸R

(33)

φi − φi−1∆x

=1

∆x

φi︸︷︷︸L

−φi−1︸︷︷︸R

(34)

Finite Volume (FV) discretization of 1d heat equation

I It is not a surprise that we end up with the same set ofequations

I FV and FD give the same discretization for equally spacedgrids in this case

I But this time we make use of FOAM operators to make thejob simple

FOAM operators - laplacianSchemeI fvm :: laplacian is an in-built operator which yields correct

fvMatrixI Need to define the laplacianScheme in the fvSchemes

dictionary

laplacianSchemes{/// Green-gauss type with/// non-orthogonality correctiondefault Gauss linear corrected;

}

I Constructor requires only the field as argument

fvScalarMatrix A( fvm::laplacian(x) );A.source() = 0.0;A.solve();

Hands on - FOAM’ish solver

I Implement the FV solver with similar inputs and equations asthe FD hands on

I Plot 1d solution using gnuplot