OFprimer-v2012 1
OFprimer-v2012 1
OFprimer-v2012 1
Technology Primer
Version OpenFOAM-v2012
Tomislav Marić
Jens Höpken
Kyle G. Mooney
License
Disclaimer
First of all, Tomislav and Kyle would like to thank their doctoral thesis
supervisors for giving them the opportunity to undertake this endeavor,
namely Prof. Dr. rer. nat. Dieter Bothe of the Technical University of
Darmstadt and Prof. David P. Schmidt of UMass Amherst.
Of course, this book would not have been possible without a group
of competent reviewers, whose insights and suggestions have been very
valuable. Thanks to Bernhard Gschaider, Michael Wild, Maija Benitz,
Fiorenzo Ambrosino and Thomas Zeiss. In addition to the main review-
ers, we have asked various friends and colleagues to proofread and pro-
vide comments and suggestions for different chapters. Thanks to Udo
Lantermann, Matthias Tenzer, Andreas Peters and Irenäus Wlokas for go-
ing through the process of reading the first drafts of various chapters and
proposing a great amount of changes.
Furthermore, we want to thank persons that have supported us in different
ways and kept us motivated, as we wrote this book. These people are
namely: Olly Connelly, Hrvoje Jasak, Iago Fernández, Manuel Lopez
Quiroga-Teixeiro, Rainer Kaiser and Franjo Juretić.
To Marija, Kathi, Olivia and our families.
Contents
Forward 1
Authors 3
Preface 5
Intended audience . . . . . . . . . . . . . . . . . . . . . . . . . . 6
What is covered in this book . . . . . . . . . . . . . . . . . . . 6
How to read this book . . . . . . . . . . . . . . . . . . . . . . . 7
I Using OpenFOAM 11
2.2.3 cfMesh . . . . . . . . . . . . . . . . . . . . . . . . 84
2.3 Mesh Conversion from other Sources . . . . . . . . . . . . 94
2.3.1 Conversion from Thirdparty Meshing Packages . . 95
2.3.2 Converting from 2D to Axisymmetric Meshes . . . 97
2.4 Mesh Utilities in OpenFOAM . . . . . . . . . . . . . . . . 100
2.4.1 Refining the Mesh by a Specified Criterion . . . . 100
2.4.2 transformPoints . . . . . . . . . . . . . . . . . . . . 102
2.4.3 mirrorMesh . . . . . . . . . . . . . . . . . . . . . . 103
2.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
ii
Contents
iii
Contents
iv
Contents
14 Conclusions 411
v
List of Acronyms
VoF Volume-of-Fluid
GPL General Public License
DNS Direct Numerical Simulations
CFD Computational Fluid Dynamics
CAD Computational Aided Design
FVM Finite Volume Method
FEM Finite Element Method
STL Stereolithography
VTK Visualization Toolkit
PDE Partial Differential Equation
RANSE Reynolds Averaged Navier Stokes Equations
RANS Reynolds Averaged Navier Stokes
RAS Reynolds Averaged Simulation
LES Large Eddy Simulation
DES Detached Eddy Simulation
DNS Direct Numerical Simulation
CDS Central Differencing Scheme
BDS2 second-order accurate Backward Differencing Scheme
DSL domain specific language
IPC interprocess communication
MPI message passing interface
RTS Runtime Selection
GUI Graphical User Interface
DSL Domain Specific Language
Contents
viii
Forward
From its public release over twenty years ago, OpenFOAM has funda-
mentally disrupted the world of computational fluid dynamics, spreading
through academia and research centers worldwide. There are several rea-
sons for its success, largely tied to the project’s open-source nature and
the revolutionary design of the software. Foremost, OpenFOAM brings
down the cost of the CFD software. For this reason, academia, with its
wealth of student labor, led much of early OpenFOAM adoption. The
flexibility of open source allowed researchers to innovate with unparal-
leled freedom. The industry was also intrigued by the ability to customize
OpenFOAM and to have solvers specifically designed for their in-house
problems. Now, OpenFOAM is a standard of CFD that is familiar to
everyone in the field. Yet, there was always a weakness for all of Open-
FOAM’s strengths: lack of documentation and a relatively steep learning
curve.
Theoretically, with open source, the code is the documentation. An expe-
rienced user could dig down through the layers of C++ and understand
the code’s basic functionality. However, this expectation represents a huge
barrier to getting started with the software for a new user. This difficulty
is compounded by the complexity of the C++ language and the templated
OpenFOAM code. Even tools such as Doxygen, which attempts to catalog
the OpenFOAM class structure, do not sufficiently reduce the obstacles.
On one user forum, a frustrated person lamented that digging through
the Doxygen output was, for a C++ beginner, like ”reading Chinese back-
ward.” The OpenFOAM community needed, very badly, a comprehensive
and accessible form of documentation that would provide an on-ramp
for the OpenFOAM newbies of the world. Because OpenFOAM is free,
the learning curve was the fundamental barrier to entry for OpenFOAM
users.
One could buy training from experts in OpenFOAM for the right price,
but there is no substitute for a first-rate reference book. And for many
years, no reference text offered depth and breadth for the new OpenFOAM
Forward
user. That all changed in 2014 when Tomislav Marić, Jens Höpken, and
Kyle Mooney published the OpenFOAM Technology Primer. This book
was a windfall to those who wanted a single source that could make
sense of OpenFOAM. The target audience was those who had a basic
background in CFD but wanted to dive more deeply into the workings and
usage of OpenFOAM. Even though I had nearly a decade of experience
with OpenFOAM, that text became the bible of my research lab and lived
on my desk, within easy reach. But after a few years of great success,
the book utterly disappeared. The community longed for the return of
this lovely reference–until 2021.
We are now fortunate that the authors have devoted yet more time to
update the text and reissue it. One fundamental challenge of document-
ing OpenFOAM is that it is evolving software, ceaselessly changing and
improving. To keep such a text current is a Sisyphean task, requiring
an unending commitment. For these reasons, I am thrilled to see that
the authors chose to release the text under the Creative Commons Li-
cense. Much like the decision to open-source the original OpenFOAM
code, this valuable text will spearhead a new era in OpenFOAM use and
documentation.
—–
Professor David P. Schmidt
Dept. of Mechanical and Industrial Engineering
University of Massachusetts Amherst
2
Authors
Tomislav Marić
Tomislav studied Mechanical Engineering at the University of Zagreb,
Croatia, and has obtained his Ph.D. degree in 2017 at the Mathematical
Modeling and Analysis institute (MMA), lead by Prof. Dr. rer. nat. Di-
eter Bothe at the Mathematics Department, at TU Darmstadt (Germany).
Tomislav is currently working at TU Darmstadt as an Athene Young
Investigator (October 2020). Tomislav has been developing unstructured
Lagrangian / Eulerian Interface Approximation (LEIA) methods for sim-
ulating two-phase flows in the OpenFOAM open-source software since
2008. He co-founded sourceflux with Jens, and as member of the Col-
laborative Research Center (CRC) 1194 at TU Darmstadt since 2016,
Tomislav supports CRC-1194 researchers in scientific software develop-
ment and research data management.
Jens Höpken
Jens studied Naval Architecture at University of Duisburg-Essen and grad-
uated at the Institute of Ship Technology, Ocean Engineering and Trans-
port Systems (ISMT) of the University Duisburg-Essen, Germany. Jens
is working with OpenFOAM since 2007, he is an OpenFOAM expert
with over a decade of experience in developing OpenFOAM for Naval
Hydrodynamics. Jens co-founded sourceflux with Tomislav.
Kyle G. Mooney
Kyle received his Ph.D. in Mechanical Engineering from The University
of Massachusetts Amherst in 2016. His research involved numerical sim-
ulation of viscoelastic fluids and spray droplet dynamics. Following grad-
uate school he joined ICON Technology Process & Consulting where he
helped innovate automotive aerodynamics simulation processes for Ford
Motor Company, Fiat Chrysler Automobiles, and General Motors. After
moving to San Francisco he shifted to leading fluid mechanical R&D
work along with hardware and software product development at several
Authors
4
Preface
The OpenFOAM open-source software for Computational Fluid Dynamics
(CFD) is widely used in industrial and academic institutions. Compared
to using proprietary CFD software, the advantage of using OpenFOAM
lies in the Open-Source General Public License (GPL), which allows
the user to freely use and freely modify a modern high-end CFD code.
An open-source license removes license costs in the product optimiza-
tion cycle, enables straightforward automation of parameter variations,
and accelerates the development of new numerical methods and models.
The development and implementation of novel methods are accelerated
because they start from existing functionality in OpenFOAM, instead of
starting from scratch.
This book covers two main aspects of working with OpenFOAM: using
the applications and developing and extending OpenFOAM applications
and libraries. The first part describes the OpenFOAM workflow using a
couple of OpenFOAM utilities and applications. The second part of the
book covers the sustainable development of new solvers and libraries in
OpenFOAM.
Intended audience
6
How to read this book
in OpenFOAM.
Chapter 2 covers the domain discretization (mesh generation and con-
version) and domain decomposition.
Chapter 3 describes the structure and the setup of a simulation case:
setting initial and boundary conditions, configuring the simulation
control parameters, and numerical settings.
Chapter 4 provides an overview of pre-and post-processing utilities and
data visualization.
Chapter 5 provides an in-depth overview of an OpenFOAM library com-
pared to chapter 1.
Chapter 6 describes how to program OpenFOAM in a productive and
sustainable way: developing and using libraries, using the git ver-
sion control system, debugging and profiling, and so on.
Chapter 7 provides a brief overview of turbulence modeling: introducing
turbulence into a simulation case and configuring the turbulence
model.
Chapter 8 covers programming OpenFOAM pre- and post-processing
applications.
Chapter 9 describes the background of solver design in OpenFOAM,
and shows how to extend an existing solver with new functionality.
Chapter 10 shows the numerical background and software design as-
pects of boundary conditions in OpenFOAM. An implementation
example of a custom boundary condition is provided that uses the
principles described in chapter 6.
Chapter 11 covers the numerical background, design, and implemen-
tation of transport models in OpenFOAM, using a temperature-
dependent viscosity model as an example.
Chapter 12 covers the design and implementation of function objects in
OpenFOAM, in comparison to C++ function objects.
Chapter 13 covers dynamic mesh handling in OpenFOAM. The design
and usage of the dynamic mesh engine in OpenFOAM is covered,
as well as the extending a solver with dynamic mesh handling.
Chapter 14 concludes the book.
7
Preface
8
How to read this book
ddtSchemes
{
default Euler;
}
Contributing
Bug reports and topic suggestions are handled using the GitLab Service
Desk, and they can be voted on. The topics with the most upvotes will
have a higher chance of being addressed in the next edition.
Before submitting a bug report or a feature request, search the existing
issues to make sure it has not already been reported.
9
Part I
Using OpenFOAM
1
Computational Fluid
Dynamics in OpenFOAM
In this chapter, an overview of the workflow used to solve a Computa-
tional Fluid Dynamics (CFD) problem in OpenFOAM is presented. Ad-
ditionally, this chapter contains background information about the Finite
Volume Method (FVM) on unstructured meshes used by OpenFOAM, as
well as the top-level structure of the OpenFOAM platform.
General considerations
Thermo-physics
Computational Resources
14
Stages of a CFD analysis
Once the relevant aspects of the physical process are isolated, the problem
requires mathematical description in the form of a mathematical model,
which is in CFD usually a set of Partial Differential Equation (PDE)s.
A CFD engineer must understand the models used to describe different
physical phenomena. Within the OpenFOAM framework, the user has
a choice between dozens of solvers. Each solver implements a specific
mathematical model, and choosing the correct one is often crucial to
obtaining a valid solution to the simulated problem. The incompress-
ibility assumption for a flow of air over an airfoil excludes the solution
of the energy equation. As another example, a potential flow is solely
governed by the Laplace equation - details can be found in the book by
Ferziger and Perić [1]. If more complex physical transport phenomena are
taken into account, the complexity of the mathematical model increases.
This typically leads to more sophisticated mathematical models, e.g., the
Reynolds Averaged Navier Stokes Equations (RANSE), used to model
the turbulent flow. The mathematical model describes the flow details,
which means that the numerical simulation, which only approximates the
solution of the model at best, cannot produce more information about the
flow than what is available in the model. More information on turbulence
15
Computational Fluid Dynamics in OpenFOAM
INFO
More information on the swak4Foam project can be found on the Open-
FOAM wiki on http://openfoamwiki.net/index.php/Contrib/
swak4Foam.
16
Stages of a CFD analysis
1.2.4 Solving
Alongside mesh generation, this usually is the most time consuming part
of the CFD analysis. The time required highly depends on the mathe-
matical model, the numerical scheme used to approximate its solution,
as well as the geometrical and topological nature of the computational
mesh. In this step, the differential mathematical model is replaced by a
system of (linearised) algebraic equations. In CFD, such algebraic linear
equation systems are often large, resulting with matrices with millions or
billions of coefficients. The algebraic equation systems are solved using
algorithms developed specially for this purpose - iterative linear solvers.
OpenFOAM framework supports a wide choice of linear solvers although
the solver applications generally have preset or ideal choices of linear
solvers and parameters. A skilled CFD engineer has the opportunity to
modify both the solver and the corresponding parameters. The choice
of the linear solver and discretization strategy is important because it
impacts both computation speed and stability.
1.2.5 Post-processing
After the simulation completes successfully, the user often ends up having
a large amount of data that must be analyzed and discussed. The data
must be extracted, plotted, and/or visualized appropriately in order to in-
spect the details of the flow. By using dedicated tools such as paraView,
such data can be discussed fairly easily. For analyzing the simulation re-
sults, OpenFOAM provides a wide choice of post-processing applications.
17
Computational Fluid Dynamics in OpenFOAM
INFO
The standard post-processing tool, that comes with OpenFOAM is
paraView. It is an open-ource tool and can be obtained from www.
paraview.org.
This is the point where the user must determine whether to trust the
results or not. Generally, CFD software is complex, and it relies on
configurable parameters, which leaves ample room for error. If a mistake
is made in one of the previous steps, it will most likely be discovered
during verification and validation.
Verification ensures that a numerical method properly solves the mathe-
matical model it approximates. In other words, verification checks if the
solution of a mathematical model is approximated appropriately.
Validation compares simulation results with experimental data: it intro-
duces a more strict safety margin when it comes to confidence in the
simulation results. When comparing against experiments, validation en-
sures that the right mathematical model is chosen and that its solution
adequately reflects reality. If the simulation results do not satisfy the
requirements, previous steps of CFD analysis must be revisited.
18
Introducing the Finite Volume Method in OpenFOAM
properties that define the fluid flow, such as pressure, velocity, or temper-
ature/enthalpy are dependant variables in a mathematical model: a formal
mathematical description of the fluid flow. A mathematical model that
describes a fluid flow is defined as a system of PDEs.
∂φ
+ ∇ · (Uφ) − ∇ · (D∇φ) = Sφ . (1.1)
∂t
Here, φ is some transported scalar property, U the prescribed velocity
and D the diffusion coefficient. The terms in equation (1.1) from left to
right are: temporal term, convective term, diffusive term and source term.
Each term describes a physical process that changes the property φ in a
different way.
19
Computational Fluid Dynamics in OpenFOAM
Ω Ω ≈ ΩD = ∪c∈C Ωc
y xc y
x x
Ωc
Figure 1.1: Continuous and discretized flow domains and the correspond-
ing continuous and discrete flow variables.
Ω ≈ ΩD = ∪c∈C Ωc , (1.2)
where the union ΩD is the finite volume mesh and C is the set that
contains indexes of all cells in the mesh ΩD . Continuous fields that are
defined in each point of the space filled by the fluid in figure 1.1a are ap-
proximated linearly inside the finite volumes Ωc , as shown in figure 1.1b.
20
Introducing the Finite Volume Method in OpenFOAM
Expressing the volume-averaged value of φ(x) inside the cell Ωc with the
Taylor series expansion in (1.3) introduces quantities that are defined at
the point xc , whih are constant over Ωc . This leads to
Z
1
φc = φ(xc ) + ∇φ(xc ) · (x − xc ) dV
|Ωc | Ωc
Z
+ ∇∇φ(xc ) : (x − xc ) ⊗ (x − xc ) + . . . dV. (1.4)
Ωc
Equation (1.6) shows that the average value of φ over the finite volume
Ωc is exactly equal to the value of φ at the centroid xc of Ωc for a
linear φ, because for a linear φ the higher-order derivatives are zero. In
other words, the cell-average (cell-centered) value at the centroid of finite
volumes recovers values of linear fields exactly. A method that exactly
recovers values of linear functions is at least second-order accurate.
INFO
The domain discretization of the unstructured FVM that assigns cell-
average values φc of φ at centroids {xc }c∈C of the cells {Ωc }c∈C is
second-order accurate.
21
Computational Fluid Dynamics in OpenFOAM
22
Introducing the Finite Volume Method in OpenFOAM
7 18 4
11 9 6
3 5 10
23
Computational Fluid Dynamics in OpenFOAM
This is often the case in a region of extreme changes, when the order of
interpolation available on the structured mesh is still insufficiently accu-
rate. For example, abrubpt changes in the values of physical properties
are present in shocks or two-phase flow simulations where two immisci-
ble fluids are simulated. An interface is formed between two fluids that
separates them, and the values of the physical properties may vary by
orders of magnitude, as can be seen in a schematic diagram in figure 1.4.
To resolve such steep changes in values, the mesh is locally refined. This
24
Introducing the Finite Volume Method in OpenFOAM
h h
Air Air
h=0 h=0
Water Water
ρ ρ
1 1000 1 1000
(a) Continuous space with sudden jump (b) Discretized space with gradual - but
of the density ρ still steep - jump of the density ρ
25
Computational Fluid Dynamics in OpenFOAM
the block refinement must be carefully crafted such that the points on
adjoining blocks of different densities match perfectly (patch-conforming
block-meshes). Constructing block structured meshes is a complex prob-
lem even for simple flow domains. This often makes block-structured
meshes a poor choice for many technical applications that involve com-
plex geometries or boundary shapes. Refining block-structured meshes
results in refinement regions spreading through the blocks. With stan-
dard solvers that rely on patch-conforming block-meshes the refinement
complicates the mesh generation further.
Dynamic adaptive local refinement in OpenFOAM is performed by intro-
ducing additional data structures that generate and store the information
related to the refinement process. An example of such method is an octree
based refinement, where an octree datastructure is used to split the cells
of the structured Cartesian mesh into octants. This requires the mesh (or
at least the subset of the mesh undergoing refinement) to consist solely
of hexahedral cells. Information stored by the octree data structure is then
used by the numerical interpolation procedures (discrete differential op-
erators) taking into account the topological changes resulting from local
mesh refinement. The possibility of dealing with more complex geomet-
rical domains can then be added to an octree-refined structured mesh
by using an cut-cell approach. In that case, the cells which hold the
curved domain boundary, are cut by a piecewise-linear approximation of
the boundary. Octree-based adaptive mesh refinement may have an advan-
tage in its efficiency depending on the way the topological operations are
performed on the underlying structured mesh. However, the logic of the
octree based refinement requires the refining domain to be box-shaped.
More information about local adaptive mesh refinement procedure can be
found in [8].
OpenFOAM implements a FVM of second-order of convergence with
support for arbitrary unstructured meshes. In addition to the unstructured
mesh connectivity, mesh cells of an arbitrary unstructured mesh can be
of any shape. This allows the user to discretize flow domains of very
high geometrical complexity. The unstructured mesh allows for a very
fast, sometimes even automatic mesh generation procedure. This is very
important for industrial applications, where the time needed to obtain
results is of great importance.
Figure 1.3a shows a two-dimensional schematic of a quadratic unstruc-
tured mesh. Since the mesh addressing is not structured, the cells have
26
Introducing the Finite Volume Method in OpenFOAM
been labelled solely for the purpose of explaining the mesh connectiv-
ity. The unordered cells complicate the possibility to perform operations
in a specific direction without executing costly additional searches and
re-creating the directional information locally. Another advantage of the
unstructured mesh is the ability for a cell to be refined locally and di-
rectly, which is sketched in figure 1.3b. The local refinement is more
efficient in terms of the increase of the overall mesh density, since it
only increases the mesh density where it is required.
INFO
Although the connectivity of the mesh is fully unstructured in Open-
FOAM, often the mesh will be generated block-wise for cases with
simple domain geometry (blockMesh utility): this does not mean that
a block structured mesh is generated.
5 7
1 15 6
26 19 18
12 7
25 13
8 23 22
The way the mesh elements are addressed by the algorithms of the numer-
ical method is determined by the mesh connectivity. OpenFOAM relies
on indirect addressing, owner-neighbor addressing and boundary mesh
addressing to address mesh elements.
Indirect addressing defines how the mesh is assembled from the mesh
points, which are given as a global list of points. Cell faces are
index the global mesh point list using integer labels. This way, the
27
Computational Fluid Dynamics in OpenFOAM
28
Introducing the Finite Volume Method in OpenFOAM
cells, but over all faces, only once. The contribution at the face
center is then added to the owner and subtracted from the neighbor
cell by the discretized differential operator, following a convention
of the outer pointing normal area vector.
Boundary addressing takes care of how the faces on a boundary are
addressed. By definition, all faces that only have a single owner cell
and no neighbor cell, are boundary faces. To increase efficiency,
the boundary faces are stored at the end of the global list of mesh
faces and are grouped in patches. Grouping of boundary faces
into boundary patches is related to applying boundary conditions,
that will be different for different groups of boundary faces. The
complete boundary mesh is therefore defined as a list of boundary
patches. This allows an efficient definition of the boundary patches
as subsets of the global mesh face list. Such definition of the
boundary mesh results in the automatic parallelization of all the top-
level code in OpenFOAM that relies on the face-based interpolation
practice. Because they have only one cell owner, all the normal
vectors of the boundary mesh are directed outwards of the solution
domain.
29
Computational Fluid Dynamics in OpenFOAM
Ng
2 1 Og 0
N f Of Nh Oh
Ob
INFO
Time is discretized as another dimension of the solution domain.
the time interval [t0 , tE ], where t0 , tE are the start and end of the sim-
ulation, is discretized as a partition [t0 , t1 , t2 , . . . , tn−1 , tn , tn+1 , . . . , tE ],
such that tn−1 < tn < tn+1 and the difference tn+1 − tn is called a time
step δt, tn−1 is the previous point in the time partition (previous time),
tn is the current time and tn+1 is the next time.
30
Introducing the Finite Volume Method in OpenFOAM
discretization.
Alternative descriptions of the unstructured FVM are available in [6] and
[2]. Publicly available descriptions of the unstructured FVM in Open-
FOAM can be found in [3, 5, 12, 9], among others.
All terms of the equation (1.1) need to be discretized in order to obtain
the algebraic equation. The numerical method must be consistent (see [1]):
as the sizes of the cells are reduced, the discrete (algebraic) mathematical
model must approach the exact mathematical model. Or in other words:
as described by Ferziger and Perić [1], refining the computational domain
infinitely and solving the discretized model on this spatial discretization
leads to the solution of the mathematical model consisting of PDEs. To
obtain the discrete model, equation (1.1) is integrated over the cell Ωc :
Z Z Z Z
∂φ
dV + ∇ · (Uφ) dV − ∇· Γ∇φ dV = S(φ) dV (1.8)
Ωc ∂t Ωc Ωc Ωc
31
Computational Fluid Dynamics in OpenFOAM
n+1
3φn+1 − 2φnc + φn−1
Z
∂φ c c
dV = |Ωc | +
Ωc ∂t 2δt
+ O(kxc − xk22 ) + O(δt2 ) (1.12)
where Fc is the index set of the faces Sf of the cell Ωc , and the integral
on the r.h.s. of equation (1.13) can be written as
Z XZ
∇ · (Uφ) dV = φU · n do. (1.15)
Ωc f ∈Fc Sf
32
Introducing the Finite Volume Method in OpenFOAM
33
Computational Fluid Dynamics in OpenFOAM
INFO
Face-centered averages in unstructured FVM are, equivalently to cell-
centered values, second-order accurate.
φ
φO
f φf
φN
xO df f
f
xf
Sf
xN
f
φNf − φOf
φf = φO f + kxf − xOf k2 (1.18)
kdf k2
= wf φNf + (1 − wf )φOf , (1.19)
34
Introducing the Finite Volume Method in OpenFOAM
Ff = Uf · Sf . (1.22)
Z X
∇c · (Uφ) := ∇· (Uφ) dv ≈ Ff [wf φNf + (1 − wf )φOf ]
Ωc f ∈Fc
(1.23)
where Fc is the index set of all faces that belong to the cell Ωc .
Up to this point, no assumption was made about the orientation of normal
vectors of the cell boundary ∂Ωc , or its discrete counterpart, namely the
orientation of the Sf normal area vectors of the faces Fc of the cell
Ωc . However, OpenFOAM does not discretize the divergence/diffusion
differential operator terms from equation (1.1) (equation (1.8)) using sums
over all faces of the cell (index set Fc ), nor are the normal vectors of
the cell boundary consistently oriented (only outwards, or only inwards)
in OpenFOAM for each cell. For each face center, a unique vector Sf is
defined. This prevents the consistency of the Sf normals, because for one
of the cells adjacent to the face f , Sf then must be oriented inward.
The discrete divergence operator ∇c · (·) is implemented in OpenFOAM
using owner-neighbor addressing. The Sf vector shown in figure 1.8 is
the surface area normal vector that always points from the owner cell
35
Computational Fluid Dynamics in OpenFOAM
Of to the neighbor cell Nf (see section 1.3.1, figure 1.6). This is a very
important implementation aspect of the unstructured FVM in OpenFOAM.
The divergence theorem assumes a consistent orientation of the normal
of the boundary ∂Ωc in equation (1.13): the normal n can either be
oriented outward or inward with respect to the cell Ωc , and the convention
on the outward normal vector orientation is used in OpenFOAM. The
discretization of the divergence term by equation (1.23) results in a sum
over all faces of the cell Ωc . If the discretization would be performed
for each cell, enforcing the consistency of the normal orientation would
be complicated. For example, for the cell 1 in figure 1.6, a single face
area normal vector is oriented inwards because of the owner-neighbor
addressing. Defining a unique normal vector Sf any other way would
still make this vector point from one cell and into another cell, effectively
pointing into one of the cells. The owner-neighbor addressing is used for
the domain discretization covered in section 1.3.2 because it uniquely
determines the normal orientation based on the indexes of face-adjacent
cells: owner cell has a smaller index than the neighbor cell.
So, the question is: How to perform the discretization of the divergence
term given by equation (1.23) if some faces of the cell (cell 1 in fig-
ure 1.6) are oriented outward and some inward?
Because the Sf vector always points from the cell with the lower index
Of (face-owner, owner-cell, owner) into the cell with the higher index
Nf (face-neighbor, neighbor-cell, neighbor), the contributions to the sum
on the r.h.s. of equation (1.23) are added to the owner of the face, and
subtracted from the neighbor of the face f , for each face f in the mesh..
To achieve this, two index-sets are introduced: the owner (owner-cell) and
neighbor (neighbor-cell) index sets, namely
O := {Of : Of < Nf for each face f in the mesh {Ωc }c∈C }, (1.24)
N := {Nf : Of < Nf for each face f in the mesh {Ωc }c∈C }. (1.25)
36
Introducing the Finite Volume Method in OpenFOAM
where F is the index set of all faces in the mesh, Of is the index of the
face-owner cell from O given by equation (1.24), Nf is the index of the
face-neighbor cell N given by equation (1.25), and ∇· · is the discrete
divergence operator given by equation (1.23).
INFO
The discretization of the divergence operator by equation (1.23) is help-
ful for understanding the unstructured Finite Volume Method, while the
actual computation in OpenFOAM is performed using equation (1.26).
INFO
Owner and neighbor index sets can be accessed from the mesh using
owner() and neighbour() member functions of the fvMesh class.
As described in section 1.3.1 and shown in figure 1.6 for cell 1, boundary
faces only have one owner-cell. Because this face has no neighbor-cell,
it will not have a contribution −Ff wf φNf in equation (1.26). To avoid
checking if the face belongs to the mesh boundary, boundary faces (face b
in figure 1.6) are stored separately from internal faces in OpenFOAM.
The Laplace (diffusion) term from equation (1.1) is discretized similarly
to the divergence (advection) term. The discretization starts with the
integral over Ωc and the application of the divergence theorem, using the
shorthand notation Γf := Γ(xf ), ∇φ(xf ) := (∇φ)f , which leads to
Z
Γf (∇φ)f · Sf + O(kx − xf k22 ), (1.27)
X
∇· Γ∇φ dV =
Ωc f ∈Fc
where the spatial second-order accuracy error term results from the face-
centered averaging given by equation (1.16).
The next step in the discretization of the Laplace term is the dis-
cretization of the gradient (∇φ)f . The face-centered gradient (∇φ)f
37
Computational Fluid Dynamics in OpenFOAM
an := a ⊗ a ⊗ a, n ∈ N+ .
´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹¸¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹¶
n
Using this notation, Taylor series from the face center, the face owner
and neighbor are respectively given as
1 1
φOf = φf + (∇φ)f · xf xOf + (∇2 φ)f : xf xOf 2 + (∇3 φ)f :: xf xOf 3 + . . . ,
2 6
(1.28)
1 1
φNf = φf + (∇φ)f · xf xNf + (∇2 φ)f : xf xNf 2 + (∇3 φ)f :: xf xNf 3 . . . .
2 6
(1.29)
1
(∇φ)f · (xf xNf − xf xOf ) = φNf − φOf − (∇2 φ)f : (xf xNf 2 − xf xOf 2 )
2
1 3
− (∇ φ)f :: (xf xNf 3 − xf xOf 3 ) + . . . . (1.30)
6
On equidistant meshes, similar to the mesh shown in figure 1.8, face
centers xf split the vector df = xNf − xOf into two equal parts, such
that
1
xf xNf = −xf xOf = df . (1.31)
2
Inserting equation (1.31) into equation (1.30), multiplying the result with
·df , and dividing by kdf k22 , cancels out the second-order term in equa-
tion (1.30), i.e.
φNf − φOf 1 df
(∇φ)f = d̂f − (∇2 φ)f : ((0.5df )2 − (0.5df )2 ) · 2
kdf k2 2 ´¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¸¹¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¹ ¶ kdf k2
0
1 df
− (∇3 φ)f :: (d3f ) · + ..., (1.32)
24 kdf k22
38
Introducing the Finite Volume Method in OpenFOAM
φ φ
� �
φNf − φOf 1
(∇φ)f ≈ d̂f − (∇3 φ)f :: (d̂3f ) · d̂f kdf k22 . (1.33)
kdf k2 24
INFO
kx x k2
The aspect ratio kxOf xNNf k2 should ideally be 0.5 in regions where strong
f f
changes in φ are expected, because then equation (1.31) holds and
the second-order accurate discretization by equation (1.33) is achieved.
Stronger mesh grading can be used where (∇2 φ)f is expected to be
insignificantly small and have virtually no impact on the second-order
convergence of the solution.
39
Computational Fluid Dynamics in OpenFOAM
φ
φO
f
φf
xO
Sf
f
xf
αf
φN
f
df
xf
0
xN
f
Figure 1.10: A 2D nonorthogonal mesh.
Z X Γf (d̂f · Sf )
∇· Γ∇φ dV ≈ (φNf − φOf ) (1.34)
Ωc kdf k2
f ∈Fc
Z X Γf kSf k2
∇· Γ∇φ dV ≈ (φNf − φOf ). (1.35)
Ωc kdf k2
f ∈Fc
40
Introducing the Finite Volume Method in OpenFOAM
41
Computational Fluid Dynamics in OpenFOAM
INFO
Face-centered gradient is not used by equation (1.37) because it can
cause checkerboarding.
i.e.
(∇φ)f · Sf = (∇φ)⊥ f · Sf + (∇φ)f · Sf ,
⊥ 6⊥ 6⊥
(1.38)
such that
Sf = S⊥
f + Sf .
6⊥
(1.39)
Of S⊥
f df Nf Of S⊥
f df Nf Of df Nf
kSf k2
S⊥
f = (Sf · d̂f )d̂f S⊥
f = kSf k2 d̂f S⊥
f = d̂f
cos(αf )
Figure 1.11: Nonorthogonality corrections.
42
Introducing the Finite Volume Method in OpenFOAM
which does not hold, because the orthogonal and nonorthogonal contribu-
tions evaluated at different time steps. To correct the discrepancy between
tn+1 and tn in equation (1.40), nonorthogonal corrections in OpenFOAM
are performed using an additional fixed number M of additional iterations
within each time step, namely
6⊥ m 6⊥
(∇φ)f (tn+1 )·Sf = (∇φ)⊥ f (t
n+1
)·S⊥
f +(∇φ)f (t )·Sf , m = 1, 2, 3, . . . M,
(1.41)
in the hope that after M iterations (∇φ)f (t ) = (∇φ)f (t
6⊥ M 6⊥ n+1
). Iterations
for nonorthogonal correction play a vital role in the pressure-velocity
coupling algorithm, where the Laplace operator is used in the Poisson
equation for the pressure. That is why configuration files for pressure-
velocity coupling algorithms in OpenFOAM have an appropriate entry.
INFO
In OpenFOAM, the number M of iterations for the nonorthogonality
correction used in the pressure Poisson equation can be set in sys-
tem/fvSolution configuration file.
INFO
Information about the nonorthogonality angle in an unstructured Open-
FOAM mesh is reported by the checkMesh utility.
INFO
In the general case if the mesh is nonorthogonal, it is also non-
equidistant, in which case equation (1.31) does not hold and the gradient
approximation at face centers becomes first-order accurate.
43
Computational Fluid Dynamics in OpenFOAM
INFO
If the face centroid xf used for averaging of φf over the face Sf in
equation (1.16) does not correspond to the intersection point xOf xNf ∩
Sf , mesh skewness error is introduced.
and
n+1
∂φ
φnc = φn+1
c − δt + O(δt2 ), (1.45)
∂t c
n+1
φn+1 − φnc
∂φ
= c
+ O(δt). (1.46)
∂t c δt
44
Introducing the Finite Volume Method in OpenFOAM
n+1
∂φ 1 hX
= Df (∇φ)n+1
f · Sf
∂t c |Ω c |
f
i (1.47)
Uf φf · Sf + S(φn+1
X
n+1
− c ) + O(δt) + O(h2 ),
f
where the source term S(φc )n+1 ) is either evaluated at the old time step
tn , or extrapolated in φ and time using linearization. Additional informa-
tion about source term linearization can be found in [7]. Alternatively,
inserting equation (1.44) into equation (1.42) and summing the resulting
equation with equation (1.47), results in the Crank-Nicolson scheme
n+1 "
∂φ 0.5 X
= Df (∇φ)n+1
f · Sf +
∂t c |Ω c |
f
Df (∇φ)nf · Sf − Uf φn+1 · Sf
X X
+ f
f f (1.48)
#
Uf φnf · Sf + S(φnc ) + S(φn+1
X
− c )
f
+ O(δt2 ) + O(h2 )
45
Computational Fluid Dynamics in OpenFOAM
46
Introducing the Finite Volume Method in OpenFOAM
generally written as
(1.50)
X
ac φn+1
c + aN φn+1
N = Sc .
N ∈Nc
47
Computational Fluid Dynamics in OpenFOAM
Wall
internalField
Outlet Inlet
boundaryField
Wall
Figure 1.12: Example of a simple 2D channel flow with the inlet on the
right side and the outlet on the left hand side. The other
remaining two boundaries are assumed to be walls.
value φb for the property φ as well Ub for the velocity U. This is suf-
ficient for a fixed-value boundary condition, however, it complicates the
implementation of boundary conditions significantly. The vast majority
of faces in the mesh are internal faces, not boundary faces. Therefore
it would make no sense to keep the internal and boundary faces some-
how mixed together, and classified, such that the discretization could ”ask
each face” if it belongs to the boundary or not, and additionally which
boundary condition is prescribed for this face. To avoid this complication,
the faces of the mesh ΩD are separated into internal faces, and bound-
ary faces. Boundary faces are further grouped together into boundary
patches in OpenFOAM: sub-sets of the boundary mesh where the same
boundary conditions are applied. This makes sense, because simulated
processes usually have surfaces that are exposed to different conditions.
Considering heat transfer as an example: some surfaces may be isolated,
others may be cooled by streaming air or liquid sprays, some surfaces
will be adjacent to bodies made out of different material. Consequently,
as shown in figure 1.12, fields in OpenFOAM are separated into inter-
nal (cell-centered) fields, and boundary face-centered fields, separated
into patch fields that correspond to specific boundary patches (boundary
conditions). The discretization in OpenFOAM is significantly simplified
by separation of faces and fieds respectively into internal and boundary
patches and fields, because the evaluation of sums in the discretized op-
erators can be applied separately for the internal part of the domain and
its boundary.
48
Introducing the Finite Volume Method in OpenFOAM
and this is the condition that is used to compute the value of the property
at the boundary face b, using the Taylor series approximation:
φn+1
c ≈ φn+1
b + ∇φ(x)n+1 · b, (1.52)
so the value at the boundary face takes on the value from the cell center
for the zero-gradien boundary condition, namely
φn+1
b ≈ φn+1
c . (1.53)
49
Computational Fluid Dynamics in OpenFOAM
50
Summary
vectors, lists, etc. The main CFD solvers within the applications
folder use the contents of these libraries to function.
tutorials Pre-configured cases for the various available solvers. The
tutorials are useful for seeing how cases are set up for each
solver. Some cases illustrate more complex pre-processing opera-
tions such as multi-region decomposition for solid-fluid heat transfer
or Arbitrary Mesh Interface (AMI) setup.
wmake The bash based script, wmake, is a utility which configures and
calls the C++ compiler. When compiling a solver or a library with
wmake, information from Make/files and Make/options is used
to include headers and link other supporting libraries. A Make folder
is required to use wmake, and thus to compile most OpenFOAM
code.
The OpenFOAM library is described in depth from a software design
perspective in chapter 5. Different paradigms of the C++ programming
language and how they are used to make OpenFOAM such a modular
and powerful CFD platform are explained there.
1.5 Summary
The OpenFOAM CFD framework can often seem daunting because it de-
mands a solid understanding of physics, numerics, and engineering from
the user. OpenFOAM is open-source, and becuase of that many aspects of
the solution process are openly exposed to the user (contrast that with the
opacity common in commercial simulation products). Access to the source
code gives the user the power to adapt things to her/his needs. However,
this power comes at the cost of learning how to use the command line
in the Linux Operating system, learning about configuration files that
define parameters used by the unstructured Finite Volume Method, etc.
Understanding this chapter is the first step towards the successful use
of OpenFOAM. Understanding the unstructured Finite Volume Method in
OpenFOAM is essential, not only for developing new methods but also for
fully understanding why things may have went wrong in certain simula-
tion and how to fix it. All elements of OpenFOAM described throughout
the rest of the book, such as boundary conditions, discretization schemes,
solver applications, etc., are based on the unstructured Finite Volume
Method.
51
Computational Fluid Dynamics in OpenFOAM
Further reading
[1] J. H. Ferziger and M. Perić. Computational Methods for Fluid
Dynamics. 3rd rev. ed. Berlin: Springer, 2002.
[2] Charles Hirsch. Numerical computation of internal and external
flows: The fundamentals of computational fluid dynamics. Elsevier,
2007.
[3] Jasak. “Error Analysis and Estimatino for the Finite Volume Method
with Applications to Fluid Flows”. PhD thesis. Imperial College of
Science, 1996.
[4] Hrvoje Jasak, Aleksandar Jemcov, and Željko Tuković. “Open-
FOAM: A C++ Library for Complex Physics Simulations”. In:
Proceedings of the International Workshop on Coupled Problems
in Numerical Dynamics (CMND 2007) (2007).
[5] F. Juretić. “Error Analysis in Finite Volume CFD”. PhD thesis.
Imperial College of Science, 2004.
[6] F Moukalled, L Mangani, M Darwish, et al. The finite volume
method in computational fluid dynamics. Springer, 2016.
[7] Suhas Patankar. Numerical heat transfer and fluid flow. CRC Press,
1980.
[8] Tomasz Plewa, Timur Linde, and V Gregory Weirs. Adaptive mesh
refinement-theory and applications. Vol. 41. Springer, 2005, pp. 3–
5.
[9] Henrik Rusche. “Computational Fluid Dynamics of Dispersed Two-
Phase Flows at High Phase Fractions”. PhD thesis. Imperial college
of Science, Technology and Medicine, London, 2002.
[10] Yousef Saad. Iterative Methods for Sparse Linear Systems, Second
Edition. 2nd ed. Society for Industrial and Applied Mathematics,
Apr. 2003. url: http://www-users.cs.umn.edu/~saad/PS/
all_pdf.zip.
[11] Jonathan Richard Shewchuk et al. An introduction to the conjugate
gradient method without the agonizing pain. 1994.
[12] O. Ubbink. “Numerical prediction of two fluid system with sharp
interfaces”. PhD thesis. Imperial College of Science, 1997.
52
Summary
53
2
Geometry Definition,
Meshing and Mesh
Conversion
Before going into the details of this chapter, some notions have to be
put into context. A geometry in the CFD context is essentially a three
dimensional representation of the flow region. A mesh on the other hand
can have multiple meanings, but the three dimensional volume mesh is
typically the one considered here. There are small variations to this,
for example, a surface mesh, which is the discretization of a surface.
As could be expected for surface discretization, planar elements are
used as opposed to volume elements for the volume mesh. For complex
geometries, proper definition of the surface mesh can be essential.
This chapter outlines how to create a mesh from scratch, how to convert
meshes between formats, and reviews various utilities for manipulating a
mesh after creation.
Geometry Definition, Meshing and Mesh Conversion
56
Geometry Definition
Users having worked with CFD codes before, especially with codes that
are based upon structured meshes, may be missing the per-cell addressing.
Rather than constructing the mesh on a per-cell basis, the unstructured
FVM method in OpenFOAM constructs the mesh on a per-face basis. For
more information on this, the reader is referred to the owner-neighbour
addressing section of chapter 1.3. The following list illustrates the pur-
poses of each file in constant/polyMesh.
points defines all points of the mesh in a vectorField, where their
position in space is given in meters. These points are not the cell
centres C, but rather the corners of the cells. To translate the
mesh by 1 m in positive x-direction, each point must be changed
accordingly. Altering any other structure in the polyMesh sub-
directory for this purpose is not required, but this is covered by
section 2.4.
A closer look at the points can be taken by opening the respective
file with a text editor. To keep the output limited, the header is
neglected and only the first few lines are shown:
?> head -25 constant/polyMesh/points | tail -7
25012 // Number of points
(
(-0.0206 0 -0.0005) // Point 0
(-0.01901716308 0 -0.0005) // Point 1
(-0.01749756573 0 -0.0005)
(-0.01603868134 0 -0.0005)
(-0.01463808421 0 -0.0005)
The points contain nothing more than a list of 25012 points. This
list does not require to be ordered in any way although it can be.
Additionally, all elements of the list are unique, meaning that point
coordinates cannot occur multiple times. Accessing and addressing
those points is performed via the list position in the vectorField,
starting with 0. The position is stored as a label.
faces composes the faces from the points by their position in the points
vectorField and stores them in a labelListList. This is a
nested list, containing one element per face. Each of these elements
is in turn a labelList of its own, storing the labels of the points
used to construct the face. Figure 2.1 shows a visualization of the
labelListList construct.
Each face must consist at least of three points and its size is fol-
lowed by a list of point labels. On a face, every point is connected
by a straight edge to its neighbours [4]. Using the points which
define the face, the surface area vector Sf can be calculated where
57
Geometry Definition, Meshing and Mesh Conversion
labelListList
labelList
label
As can be seen from the first line of the output, the mesh consists
of 49180 faces of which only a subset is shown above. Similar to
the face list’s length of 49180, the length of each labelList is
stated before the lists starts. Hence all faces shown here are built
from 4 points, referred to by their position in the points list.
owner is again a labelList with the same dimension as the list storing
the faces. Because the faces are already constructed and stored in
the faces list, their affiliation to the volume cells must be defined.
Per definition, a face can only be shared between two adjacent cells.
The owner list stores which face is owned by which cell, which in
turn is decided based on the cell label. The cell with the lower cell
label owns the face and the other remaining face is considered the
neighbor. It instructs the code that the first face (index 0 in the list)
is owned by the cell with the label that is stored at that position.
58
Geometry Definition
A closer look at the owner file reveals that the first two faces are
owned by cell 0 and the next two faces are owned by cell 1.
?> head -25 constant/polyMesh/owner | tail -7
49180
(
0
0
1
1
For the pitzDaily example used throughout this section, the bound-
ary file contains a list of 5 patches. Each patch is represented by
a dictionary, lead by the patch name. The information included
in the dictionary includes: the patch type, number of faces and
59
Geometry Definition, Meshing and Mesh Conversion
starting face. Due to the sorting of the faces list, faces belonging
to a certain patch can be addressed quickly and easily using this
convention.
The addressing method for the boundary faces is illustrated in Fig-
ure 2.2. By design, all faces which don’t have a neighbour are
collected at the end of the faces list where they are sorted accord-
ing to their owner patch. All faces that are boundary faces must be
covered by the boundary description.
From a user’s perspective, neither points nor faces, owner and
neighbour will need to be touched or manipulated manually. If
they are changed manually, this will most certainly destroy the
mesh. The boundary file, however, may need to be altered for
certain setups depending on your workflow. The most likely reason
for changing the boundary file is to alter a patch name or type.
It may be considerably easier to make this change here rather than
re-running the respective mesh generator.
60
Geometry Definition
p
U,
:
g.
e.
Boundary Conditions
Boundary
Computational Grid
CAD Geometry
61
Geometry Definition, Meshing and Mesh Conversion
are applied to the patches for each of the fields (U,p, etc...) separately.
The patch types are:
patch Most patches (boundaries) can be described by the type patch,
as it is the most general description. Any boundary condition of
Neumann, Dirichlet or Cauchy can be applied to boundary patches
of this type.
wall If a patch is defined as wall, it does not imply that there is no flow
through that patch. It solely enables the turbulence models to prop-
erly apply wall functions to that patch (see chapter 7). Preventing a
flow through the patch of type wall must still be explicitly defined
via the velocity boundary condition.
symmetryPlane Setting the patch type to symmetryPlane declares it to
act as a symmetry plane. No other boundary conditions can be
applied to it except symmetryPlane, and must be applied for all
fields. More information on the various available boundary condi-
tions in OpenFOAM is given in chapter 10.
empty In case of a two-dimensional simulation, this type should be
applied to the patches which are ”in-plane”. Similar to the symme-
tryPlane type, the boundary conditions of those patches have to
be set to empty for all fields as well. No other boundary conditions
will be applied to those patches. It is essential that all cell edges
between both empty patches are parallel, otherwise an accurate
two-dimensional simulation is not possible.
cyclic If a geometry consists of multiple components that are identical
(e.g. a propeller blade or a turbine blade), only one needs to be
discretized and treated as if it were located between identical com-
ponents. For a four bladed propeller this would mean that only one
blade is meshed (90◦ mesh) and by assigning a cyclic patch type
to the patches with normals in tangential direction. These patches
would then act as being physically coupled.
wedge This boundary type is similar to a cyclic patch, only specifically
designed for cyclic patches which form a small (e.g. ≤ 5◦ ) wedge.
From an execution and compatibility standpoint, it does not matter how
the polyMesh structure is created, as long as the mesh data in itself
is valid. While there are various mesh generation tools packaged with
OpenFOAM, external third-party meshers can be used so long as a valid
conversion or output can be made.
In addition to the above mentioned essential core components of an
62
Geometry Definition
OpenFOAM mesh, there are various optional mesh constructs which may
only be used for particular applications. Since they are optional, they can
be present, regardless of the case setup. An OpenFOAM application reads
them as needed and will report back to the user if they are missing.
As a user it is fairly easy to get confused by the fact that there are
zones and sets in OpenFOAM and both to fairly similar things, when a
user is concerned: they select mesh entities. The very short answer to the
question which one to use is: use zones, as explained briefly by Hrvoje
Jasak via Twitter (cp. Figure 2.4).
This is however only really relevant for solver applications. If the applica-
tion is centered around pre- or post-processing, either one is fine. A sets
are essentially labelHashSets, whereas zones inherit from labelLists.
Both can store any mesh entity (point, face or cell) in a datastructure that
is somewhat similar to a list. The major difference is in the internal han-
dling of the mesh entities, especially in the case of a parallel simulation
with topological mesh changes. In this case, the addressing in the list has
to be updated accordingly and only the zone provides such a method.
The selection is usually performed by the tools setSet or topoSet, both
of which can select subsets of the mesh and perform boolean operations
on them. Both utilities can - generally speaking - convert zones to sets
and the other way around. The set or zone may be created for any
mesh entity (cell, point or face), but the a cellSet and a cellZone are
the two ones, which are most commonly used. Zones are stored as
ordinary dictionaries within the constant/polyMesh, whereas sets are
stored in the sets subdirectory of constant/polyMesh. Zones and sets
are stored identically on the filesystem: as a long list of labels of the
respective mesh entity.
63
Geometry Definition, Meshing and Mesh Conversion
solid TRIANGLE
facet normal -8.55322e-19 -0.950743 0.30998
outer loop
vertex -0.439394 1.29391e-18 -0.0625
vertex -0.442762 0.00226415 -0.0555556
vertex -0.442762 1.29694e-18 -0.0625
endloop
endfacet
endsolid TRIANGLE
The drawback of using ASCII STL files is that their filesize tends to grow
rapidly with increasing resolution of the surface. Edges are not included
explicitly because only triangles are stored in the file. Because of this,
identifying and extracting feature edges from an STL is sometimes a
challenging task.
64
Mesh Generation
2.2.1 blockMesh
65
Geometry Definition, Meshing and Mesh Conversion
2
10 6
7
2
5
11 7 1
3
5
9
3
4
1
4 8
x3 0
x2
x1
0
Figure 2.5: A blockMesh base block with vertice and edge naming con-
vention.
66
Mesh Generation
Figure 2.6: The gray dashed arc represents edge of a block, the black line
denotes the resulting cell edges, and the gray dots indicate
the three nodes on the edge.
connect vertices with each other. Finally, the surface of the block is de-
fined by patches though those have only to be specified explicitly for
block boundaries that don’t have a neighbouring block. Boundaries be-
tween two blocks must not be listed in the patch definition, as they
are - by definition - not patches. The length and number of nodes on
the particular edges must match in order to stay topologically consistent.
Boundary conditions for the actual simulation will be applied later on
those patches.
Note that it is possible to generate blocks with less than 8 vertices and to
have non-matching nodes on patches (see [4]), however, this is not covered
by this guide. The edges of the block are straight lines by default, but
can be replaced by different line types such as an arc, a polyline, or a
spline. Choosing e.g. an arc does affect the shape of the block edge
topology, but the connection between the final mesh points on that edge
remain straight lines (see figure 2.6).
Coordinate Systems
67
Geometry Definition, Meshing and Mesh Conversion
3
4 5
Node Distribution
During the meshing process, each block is subdivided into cells. The cells
are defined by the nodes on the edges in each of the three coordinate
axes of the block’s coordinate system and follow the relationship given
by:
δe
er = (2.2)
δs
1
r = er1−n (2.3)
68
Mesh Generation
1 − ri
λ (r, i) = with λ ∈ [0, 1] (2.4)
1 − rn
Even though this might look too laborious to perform for all of the blocks
in a blockMeshDict, this comes in handy when a smooth transition in
the cell sizes between two adjoining blocks is required. In many cases,
simple try and error usually suffices.
vertices
(
(0 0 0)
(1 0 0)
(1 1 0)
(0 1 0)
(0 0 1)
(1 0 1)
(1 1 1)
(0 1 1)
);
69
Geometry Definition, Meshing and Mesh Conversion
From having a glance at the above definition, it is clear that the syntax
is a list and similar to the list of points in the polyMesh definition. This
is due to the round brackets that indicate a list in OpenFOAM, whereas
curly brackets would define a dictionary. The first four lines define all
four vertices in the x3 = 0 plane and the following do the same for
the x3 = 1 plane. Similar to the points in polyMesh, each element is
accessed by its position in the list and not by the coordinates. Please
note, that each vertex must be unique and hence only occur once in the
list.
As a next step, the blocks must be defined. Figure 2.5 can be used as
a reference. An example block definition for the unit cube could look
like
blocks
(
hex (0 1 2 3 4 5 6 7) (10 10 10) simpleGrading (1 1 1);
);
Again this is a list which contains blocks and is not a dictionary, due to
the round brackets. The definition might appear odd at first glance, but
is actually quite straight forward. The first word hex and the first set of
round brackets containing eight numbers tells blockMesh to generate a
hexahedron out of the vertices 0 to 7. These vertices are exactly those
specified in the vertices section above and are accessed by their labels.
Their order is not arbitrary, but defined by the local block coordinate
system as follows:
1. For the local x3 = 0 plane list all four vertex labels starting at the
origin and moving according to the right-handed coordinate system.
2. Do the same for the local x3 6= 0 plane
It is possible to obtain a valid block definition by messing up the order
of the vertex list in the particular block definition. The resulting block
will either look twisted or uses incorrect global coordinate orientation.
As soon as blockMesh and checkMesh are executed and the mesh is
analyzed in a post-processor (e.g. paraView), this is detected.
INFO
checkMesh is a nativ OpenFOAM tool to check the mesh integrity and
quality, based on various criteria. If the output of checkMesh states
that the mesh is not ok, it must get improved.
70
Mesh Generation
The second set of round brackets defines how many cells are distributed
in each particular direction of the block. In this case, the block possesses
10 cells for each direction. Changing the cell count to 2 cells in x1 , 20
cells in x2 and 1337 cells in x3 , the block definition would look like
this:
hex (0 1 2 3 4 5 6 7) (2 20 1337) simpleGrading (1 1 1);
71
Geometry Definition, Meshing and Mesh Conversion
Figure 2.8: Unit cube meshed with blockMesh and an edge resolution of
10 cells in each direction
Each item of the list containing the edge definitions starts with a keyword
which indicates the type of edge, followed by the labels of the start and
end vertex. In this example, the line is closed by the third point that is
required to construct an arc. For any other edge shape (e.g. polyLine or
spline), this point would be replaced by a list of supporting points.
72
Mesh Generation
Figure 2.9: Unit cube meshed with blockMesh and an arc as one edge
An example of how inserting the above listed code alters the shape of
the unit cube (see figure 2.8) is presented in figure 2.9.
2.2.2 snappyHexMesh
73
Geometry Definition, Meshing and Mesh Conversion
The execution flow of snappyHexMesh can be split into three major steps
which are executed successively. Each of these steps can be disabled
by setting the respective keywords to false at the beginning of the
snappyHexMeshDict. These three steps can be summarized as follows:
castellatedMesh This is the first stage and performs two main opera-
tions. First, it adds the geometry to the grid and removes the cells
which are not inside the flow domain. Second, the existing cells
are split and refined according to the user’s specifications. The re-
sult is a mesh which consists only of hexahedrons that more or
less resembles the geometry. However, the majority of mesh points
which are supposed to be placed on the geometry’s surface are not
aligned with it. A screenshot of a later example at this stage of the
meshing process is shown in figure 2.10.
snap By performing the snapping step, the mesh points in the vicinity
of the surface are moved onto the surface. This can be seen in
figure 2.11. During this process, the topology of those cells may get
74
Mesh Generation
Figure 2.11: The same sphere as above but after the snapping process.
All points are aligned with the body surface.
All of the above settings and many more are defined in system/s-
nappyHexMeshDict, the dictionary which contains all of the parame-
ters required by snappyHexMesh. Several helpful tutorials can be found
in the OpenFOAM tutorials directory under meshing/snappyHexMesh.
Compared to other OpenFOAM dictionaries, the snappyHexMeshDict is
very long and consists of many hierarchy levels which are represented
by nested subdictionaries. One time step is written to the case directory,
for each of the above mentioned steps (assuming you have a standard
configuration). Each of the three steps will be addressed individually in
the following section.
75
Geometry Definition, Meshing and Mesh Conversion
Figure 2.12: Prism layers are applied to the sphere surface, by extruding
the surface.
Cell levels
Before starting the meshing process, the geometry must be defined in the
geometry subdictionary in the snappyHexMeshDict. Without the need to
76
Mesh Generation
INFO
STL geometries can be generated using almost any CAD program.
Paraview may be used to generate an STL representation of basic shapes
such as cylinders, spheres, or cones. Under the sources menu, various
shapes are available which can be exported using the save data entry,
under file menu.
For real world geometries there are of course various methods to gener-
ate the surface mesh and store it as STL. However, bear in in mind that
the quality of the surface mesh is essential to obtain a good volume
mesh.
As a simple example, the unit cube mesh which was prepared in the
previous section is reused and a sphere is inserted into it. The sphere
is generated using a STL file, instead of the shapes listed in table 2.1.
Loading a STL geometry can be done in a straight forward manner, by
simply copying the geometry to constant/triSurface of the case and
adding the following geometry subdictionary in the snappyHexMeshDict.
An example of this looks like the following:
geometry
{
sphere.stl // Name of the STL file
{
type triSurfaceMesh; // Type that deals with STL import
name SPHERE; // Name access the geometry from now on
}
}
77
Geometry Definition, Meshing and Mesh Conversion
78
Mesh Generation
{
type searchableSurfaceCollection;
mergeSubRegions true;
SPHERE2
{
surface SPHERE
scale (1 1 1);
}
smallerBox2
{
surface smallerBox;
scale (2 2 2);
}
}
}
This is the first out of three steps during the execution of snappy-
HexMesh. It includes the following two main steps: splitting the cells
according to the user specifications and deleting cells that are outside the
meshed region. A schematic of this process is given in figure 2.13.
The existing background mesh (black in figure 2.13) is read from con-
stant/polyMesh. Based on the parameters in the castellatedMesh-
Controls subdictionary of the snappyHexMeshDict, the mesh is refined.
It is important to distinguish between refinements that are defined by ge-
ometry surfaces and volumetric refinement. The surface refinement ensures
79
Geometry Definition, Meshing and Mesh Conversion
that the boundary faces that represent the geometry are refined up the de-
fined level. It is important to note that this does not only affect the cells
owning the particular cells, but the adjecient cells as well. Therefore, sur-
face refinement may appear somewhat similar to a volumetric refinement,
however, it is distinctly different. Applying such a surface refinement to
the SPHERE is controlled by entries in the castellatedMeshControls,
which could look like the following:
castellatedMeshControls
{
...
refinementSurfaces
{
SPHERE // Name of the surface
{
level (1 1); // Min and max refinement level
}
}
...
}
This refines the surface of the SPHERE to level 1. The two numbers
between the round brackets define a minimum and maximum level of
refinement for this surface. snappyHexMesh chooses between both, de-
pending on the surface curvature: highly curved surface areas are refined
to the higher level, lesser curved ones to the lower level.
The mode has three options: inside, outside and distance. As the
names suggests, inside only affects cells inside the selected geometry
whereas outside does exactly the opposite. The third option, distance, is
a combination of both and is calculated in outward and inward normal
direction of the surface. In addition to modes, there is a levels option,
which is more complex than for refinementSurfaces. It can already
be guessed from the name, it does support an arbitrary number of levels.
Each level must be defined in conjunction with a distance. With increasing
position in the list, the levels must decrease and the distances must
80
Mesh Generation
Compared to the other two steps of snappyHexMesh, this does not require
extensive user input. This step is responsible for aligning the purely hex-
ahedral mesh faces with the geometry by introducing new points into the
mesh and displacing them (see figure 2.11). This is a highly iterative pro-
cess, which is the reason why there is not much user interaction required.
An example snapControls subdictionary of the snappyHexMeshDict
read:
snapControls
{
nSmoothPatch 3;
tolerance 2.0;
81
Geometry Definition, Meshing and Mesh Conversion
nSolveIter 30;
nRelaxIter 5;
// Feature snapping
nFeatureSnapIter 10;
implicitFeatureSnap false;
explicitFeatureSnap true;
multiRegionFeatureSnap false;
}
There are only iteration counters, tolerances and flags defined. Half of
the parameters deal with snapping to the edges of the geometry, which
is not part of this description. A description of this can be found on [3],
however. Depending on the specifics of the case, increasing the iteration
counters usually leads to a higher quality mesh, but also increases the
meshing time significantly.
INFO
All of the parameters are explained in further detail in the snappy-
HexMeshDicts, provided with OpenFOAM.
All settings for the addLayers step are defined in the addLayersCon-
trols subdictionary of the snappyHexMeshDict. Any surface can be
used to extrude prism layers from, regardless of its type. Firstly, the
number of cell layers to be extruded per boundary needs to be specified
via the layers subdictionary. An example entry looks like the follow-
ing:
addLayersControls
{
...
layers
{
"SPHERE_.*" // Patch name with regular expressions
{
nSurfaceLayers 3; // Number of cell layers
}
}
...
}
82
Mesh Generation
that get extruded and is thus followed by an integer, denoting the number
of cell layers to be extruded. In the above example, regular expressions
are employed to match any patch names which start with SPHERE_. In
this case it is only the sphere itself however the use of wildcard charac-
ters in this manner can greatly reduce setup time. A cross-section of the
final mesh is shown in figure 2.12.
relativeSizes true;
expansionRatio 1.0;
finalLayerThickness 0.5;
minThickness 0.25;
INFO
Another high quality mesh generator for OpenFOAM is enGrid and can
be obtained freely from http://engits.eu/en/engrid.
83
Geometry Definition, Meshing and Mesh Conversion
INFO
The application cfMesh is a distributed memory parallel OpenFOAM
meshing tool which, like snappyHexMesh, takes in STL surfaces as an
input. This package is developed by Creative Fields Ltd. and can be
downloaded along with documentation at www.c-fields.com.
2.2.3 cfMesh
84
Mesh Generation
All workflows are parallelised for shared memory machines and use all
available CPU cores while running. The number of utilised cores can be
controlled by the OMP_NUM_THREADS environment variable which can be
set to the desired number of cores.
85
Geometry Definition, Meshing and Mesh Conversion
figure 2.16a, which span in the x-y plane and is extruded in the z direc-
tion.
Input geometry
86
Mesh Generation
87
Geometry Definition, Meshing and Mesh Conversion
The file formats suggested for meshing are: fms, ftr, and stl. In ad-
dition, the geometry can be imported in all formats supported by the
surfaceConvert utility which comes with OpenFOAM. However, the
three suggested formats support definition of patches which are trans-
ferred onto the volume mesh by default. Other formats can also be used
88
Mesh Generation
for meshing but they do not support definition of patches in the input
geometry and all faces at the boundary of the resulting volume mesh end
up in a single patch.
The preferred format for cfMesh is fms, designed to hold all relevant
information for setting up a meshing job. It stores patches, subsets, and
feature edges in a single file. In addition, it is the only format which can
store all geometric entities into a single file, and the users are strongly
encouraged to use it.
The cfMesh library requires only two mandatory settings to start a mesh-
ing process:
Refinement settings
When a uniform cell size is not satisfactory, there are many options for
local refinement sources in cfMesh.
89
Geometry Definition, Meshing and Mesh Conversion
90
Mesh Generation
the geometry being filled by the mesh. On the contrary, the mesh in thin
parts of the geometry can be lost if the specified cell size is larger than
the local feature size.
91
Geometry Definition, Meshing and Mesh Conversion
(a) Selected regions at the sur- (b) Resolved gap in the volume
face mesh. mesh.
Boundary layers in cfMesh are extruded from the boundary faces of the
volume mesh towards the interior, and cannot be extruded prior to the
meshing process. In addition, their thickness is controlled by the cell
size specified at the boundary and the mesh generator tends to produce
layers of similar thickness to the cell size. Layers in cfMesh can span
over multiple patches if they share concave edges or corners with valence
greater than three. Furthermore, cfMesh never breask the topology of a
boundary layer, and its final geometry depends on the smoothing proce-
dure. All boundary layer settings are provided inside a boundaryLayers
dictionary. The options are:
92
Mesh Generation
option ensures that the number of layers required for a patch shall not
spread to other patches in the same layer.
The settings presented in this section are used for changing of patch
names and patch types during the meshing process. The settings are pro-
vided inside a renameBoundary dictionary with the following options:
93
Geometry Definition, Meshing and Mesh Conversion
94
Mesh Conversion from other Sources
Many advanced external meshing utilities offer the user additional levels
of control during mesh generation. This includes selectable element types,
fitted boundary layer meshes, and length scale control to name a few.
Some mesh generators can export directly to a functional OpenFOAM
mesh format. Listed below is a compilation of the mesh formats supported
for conversion in OpenFOAM-3.0:
INFO
Due to licencing issues, the mesh conversion tool used to import meshes
from StarCMM+ and libraries related to it need to be downloaded and
compiled manually, rather than via a Allrun script.
INFO
The source code for all of the above mentioned conversion utilities are
found here: $WM_PROJECT_DIR/applications/utilities/mesh/-
conversion/.
Users also have the option of converting OpenFOAM meshes into Flu-
ent or Star-CD mesh formats using the foamMeshToFluent and foam-
ToStarMesh utilities. This could be especially useful for exporting
meshes generated from the snappyHexMesh utility mentioned previously.
The mesh conversion process is typically very straightforward with very
little syntax changes between the different conversion utilities. For that
95
Geometry Definition, Meshing and Mesh Conversion
reason, only one example will be given and will be based on the flu-
entMeshToFoam conversion utility. To begin the process, copy a tutorial
case to a directory of choice, this tutorial is based upon the existing mesh
conversion tutorial for the icoFoam solver.
Scaling the mesh during import is as simple as adding the option and
scaling factor to the command. For the sake of this tutorial, the mesh
should be scaled down by one order of magnitude.
96
Mesh Conversion from other Sources
movingWall
frontAndBack
INFO
The sources of makeAxialMesh are available on the OpenFOAM wiki:
http://openfoamwiki.net/index.php/Contrib_MakeAxialMesh.
Follow the instructions there to download and compile the utility.
The next steps are to create a copy of the case folder to a working
directory of your choice, renaming the directory to avoid any future
confusion and creating the 2D base mesh.
?> cp -r $FOAM_TUTORIALS/utilities/incompressible/icoFoam/cavity .
?> mv cavity axiSymCavity
?> cd axiSymCavity
?> blockMesh
97
Geometry Definition, Meshing and Mesh Conversion
The utility creates a new time directory (in this case 0.005) to store the
transformed mesh. If the creation did not work as expected, only this
directory needs to be deleted and the basic mesh is restored again. The
case directory should now contain the folders shown below:
?> ls
0 0.005 constant system
At this point the mesh has been warped into a 5° wedge shape, as
shown in figure 2.25. However, the faces from the movingWall patch
are present, however, they are now crushed into faces of near zero face
area. makeAxialMesh transforms the point positions but does not al-
ter the mesh connectivity. Because of this, the symmetry patch has no
faces assigned to it (nFaces = 0) and must be removed. Using the
collapseEdges tool is advisable in this case. It takes two mandatory
command line arguments: edge length and merge angle:
98
Mesh Conversion from other Sources
Figure 2.25: The 2D cavity mesh before and after the makeAxialMesh
wedge transformation
3
(
fixedWalls
{
type wall;
nFaces 60;
startFace 760;
}
frontAndBack_pos
{
type wedge;
nFaces 400;
startFace 820;
}
frontAndBack_neg
{
type wedge;
nFaces 400;
startFace 1220;
}
)
At this point, the fixedWalls patch can be split into 3 separate patches
using the autoPatch utility. This will look at a contiguious patch and try
to identify appropriate places to split it based on a given feature angle.
99
Geometry Definition, Meshing and Mesh Conversion
In this case, any patch edges that form an angle greater than 30° can be
split to form a new patch. This provides more flexibility, when it comes
to the assignment of boundary conditions.
The patches will be renamed after the split. The -latestTime flag will
only read the latest time step available. Instead of overwriting the time
step, the split mesh is stored in yet another time step directory. Fi-
nally the mesh should be checked for errors, using the checkMesh tool,
which should be considered a general rule of best practices: always run
checkMesh when the mesh was changed.
The utility applications (or just short utilities) which deal with mesh oper-
ations can be found in the directory $WM_PROJECT_DIR/application-
s/utilities/mesh. The mesh utilities are grouped in the following
categories: generation, manipulation, advanced and conversion. This cate-
gorization has not changed over the most recent versions at all. Generating
the mesh and converting it from different formats into the OpenFOAM
format has been described in section 2.2 and section 2.3. This section
covers manipulating the mesh as well as advanced operations like mesh
refinement once a base mesh has already been generated.
100
Mesh Utilities in OpenFOAM
?> cp -r $FOAM_TUTORIALS/multiphase/interFoam/laminar/damBreak .
?> cd damBreak
?> blockMesh
?> setFields
The mesh is now generated with the blockMesh and the αwater field is
set using the setFields pre-processing utility. The setFields utility is
described section 3.2. The basic calculator utility foamCalc can be used
to compute and store the gradient of the αwater field.
?> foamCalc magGrad alpha.water
This will store the cell-centred scalar field of the gradient magnitude
in the initial time directory 0 named magGradalphaWater. To refine the
mesh based on the gradient magnitude using the refineMesh application,
the configuration dictionary file for this utility must be copied into the
system directory of the damBreak case.
?> cp $FOAM_APP/utilities/mesh/manipulation/refineMesh/refineMeshDict system/
?> ls system/
controlDict fvSchemes refineMeshDict
decomposeParDict fvSolution setFieldsDict
101
Geometry Definition, Meshing and Mesh Conversion
source fieldToCell;
sourceInfo
{
fieldName magGradalpha1;
min 20;
max 100;
}
}
);
?> topoSet
?> refineHexMesh c0
When the mesh is now viewed using Paraview, the area of the free
surface should now have additional resolution.
2.4.2 transformPoints
INFO
It is also possible to use double quotation marks instead of single
quotation marks around the brackets. The order in which the tasks are
performed is hard coded and cannot be changed by the user. If you
want to make sure that the scaling is performed before the translation,
run transformPoints twice.
scale scales the points of the mesh in any or all cardinal directions
by a specified scalar ammount. -scale '(1.0 1.0 1.0)' does
not change the point locations, while -scale '(2.0 2.0 2.0)'
102
Mesh Utilities in OpenFOAM
2.4.3 mirrorMesh
?> cp -r $FOAM_TUTORIALS/stressAnalysis/solidDisplacementFoam/plateHole .
?> mv plateHole mirrorMeshExample
?> cd mirrorMeshExample
?> cp -r $FOAM_APP/utilities/mesh/manipulation/mirrorMesh/mirrorMeshDict \
system/
The next step is to define the plane which will act the the mirror-
plane.Such a plane can be defined by an origin and a normal vector, in
the mirrorMeshDict as shown below. Patches about which the reflection
is taking place are automatically removed.
103
Geometry Definition, Meshing and Mesh Conversion
pointAndNormalDict
{
basePoint (0 0 0);
normalVector (0 -1 0);
}
After this is defined properly, mirrorMesh can be executed and the mesh
should be checked for errors:
?> mirrorMesh
?> checkMesh
This gives a half mesh. For the second mirroring, the dictionary must be
changed to account for the different mirror plane:
pointAndNormalDict
{
basePoint (0 0 0);
normalVector (-1 0 0);
}
Again, run mirrorMesh and use checkMesh to check for any errors in
the mesh itself:
?> mirrorMesh
?> checkMesh
104
Summary
2.5 Summary
Further reading
[1] Sept. 2014. url: http://www.sourceflux.de/blog/adding-
source-terms-equations-fvoptions/.
[2] May 2015. url: http://www.sourceflux.de/blog/a-changing-
cell-set-in-openfoam/.
[3] J. Höpken and T. Maric. Feature handling in snappyHexMesh.
2013. url: www.sourceflux.de/blog/snappyhexmesh-snapping-
edges.
[4] OpenFOAM User Guide. OpenCFD limited. 2016.
[5] E. de Villiers. 7th OpenFOAM Workshop: snappyHexMesh Train-
ing. url: http://www.openfoamworkshop.org/2012/downloads/
Training/EugenedeVilliers/EugenedeVilliers-TrainingSlides.
tgz (visited on 12/2013).
105
3
OpenFOAM Case setup
In this chapter, the structure of OpenFOAM simulations and the definition
of boundary and initial conditions are covered.
INFO
Simulation test cases from this chapter are available in the example
case repository, in the chapter3 sub-directory.
U
movingWall
fixedWall
fixedWall
fixedWall
are not required by icoFoam, they are used by the pre-processing utili-
ties. Generally, the number of required input dictionaries increases with
increasing complexity of the solver.
Listing the sub-directories of the cavity case shows the way the simu-
lation files are organized:
?> cd $FOAM_TUTORIALS/incompressible/icoFoam/cavity
?> ls *
0:
p U
constant:
polyMesh transportProperties
system:
controlDict fvSchemes fvSolution
As the simulation progresses, the solver application will write the result-
ing simulation data to new sub-directories of the case directory. Those
directories are the so-called time step directories, and they are named
based on the simulation time values. They not only contain those fields
108
The OpenFOAM simulation case structure
already defined in the 0/ directory, but also auxiliary fields of the solver,
such as the volumetric flux phi.
TIP
Although the icoFoam solver starts the simulation with the initial values
for the pressure p and the velocity U fields, the FVM utilises the
volumetric flux fields (phi) in the process of equation discretization (see
chapter 1 for more details). As a result, the time step directories will
hold the volumetric flux field phi computed by the solver application.
In addition to the time step directories, other simulation data may get
written:
• by the solver application (e.g, the volumetric flux phi),
• by a function object running alongside the solver (see chapter 12),
109
OpenFOAM Case setup
TIP
If a solver application uses the dynamic mesh feature, where point
positions or mesh topology is changing, a new polyMesh folder is
written to each time step directory as well. The dynamic mesh feature
of OpenFOAM is discussed in chapter 13.
TIP
Configuration files in OpenFOAM are usually referred to as dictionary
files or even shorter as dictionaries. This naming is due to their usage
in the source code, which is based on the IOdictionary class.
110
Boundary Conditions and Initial Conditions
implies, define the field values at mesh boundaries. Initial conditions refer
to the initial values of the internal field. A sketch for the differentiation
between internal and boundary values is shown in figure 1.12.
INFO
More information on the computational mesh can be found in chap-
ter 2.1. The basics of the numerical method have been discussed in
chapter 1. Both topics should be known to the reader, to a certain
extend.
Before having a closer look at how boundary condition files are defined,
the cavity case designed to be simulated with icoFoam has to be copied
to a location of choice. To set a basic boundary condition for the cavity
simulation case, the simulation case directory needs to be copied and
renamed:
?> cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity \
cavityOscillatingU
?> cd cavityOscillatingU
Listing the 0/ directory reveals that there are two different fields defined:
p and U. Both files can be edited using any text editor, straight from the
command line. The relevant lines of the pressure boundary condition file
can be printed to screen using the following command:
internalField uniform 0;
boundaryField
{
movingWall
{
type zeroGradient;
}
fixedWalls
{
type zeroGradient;
}
frontAndBack
{
type empty;
}
}
111
OpenFOAM Case setup
This reveals three top level entries of the boundary condition file: di-
mensions, internalField and boundaryField. The first one is a set of
scalar dimensions (dimensionSet), that is used to define the dimen-
sions of the field. Each scalar corresponds to the power of the particlar
SI unit, as defined in the declaration source file for the dimension set
(dimensionSet.H):
//- Define an enumeration for the names of the dimension exponents
enum dimensionType
{
MASS, // kilogram kg
LENGTH, // metre m
TIME, // second s
TEMPERATURE, // Kelvin K
MOLES, // mole mol
CURRENT, // Ampere A
LUMINOUS_INTENSITY // Candela Cd
};
The next entry is internalField, which defines the initial conditions for the
field. Note that this does not include the boundaries, which are defined
by the last subdictionary: boundaryField. In this example, all cell values
are set to 0. It is also possible to define initial values on per-cell basis,
which in turn requires the user to compose a list with the desired value
for each cell. This list must have as many elements as cells are present
in the mesh and its composition is explained in [5]. Lastly, the boundary
conditions are defined, inside the boundaryField subdicitonary. Boundary
conditions must be specified for each patch and each field. Thus the
boundary condition for each patch is defined in a subdictionary of the
boundaryField dictionary.
112
Boundary Conditions and Initial Conditions
The description section of the large comment section at the top of the
header file contains a description of usage for the boundary condition,
that we adapt here for the movingWall patch:
movingWall
{
type codedFixedValue;
value uniform (0 0 0);
name oscillatingFixedValue; // name of generated BC
code
#{
operator==(Foam::cos(this->db().time().value() * M_PI)*vector(
1,
0,
0
));
#};
}
113
OpenFOAM Case setup
In the above C++ code snippet of the coded boundary condition, we have
overloaded the OpenFOAM field assignment operator, to set the velocity
vector to cos(πt)(1, 0, 0), making it switch direction once per second. In
addition to the above changes to the U field, the simulation time has to be
extended so that several velocity oscillation cycles can be visualized. In
order to do this, the system/controlDict dictionary has to be changed:
the entry endTime is modified from 0.5 to 4.0. All required adjustments
have been done and the changed boundary condition can be tested using
the icoFoam solver. Before doing so, the mesh must be generated with
the blockMesh utility:
?> blockMesh
?> icoFoam
INFO
To visualize OpenFOAM cases with ParaView, create an empty file
named case.foam (actually, any name with *.foam extension works)
and open this file with ParaView.
INFO
Setting initial conditions (IC) is known as pre-processing.
114
Boundary Conditions and Initial Conditions
The initial conditions can be trivial, like in the cavity case: the initial
internal velocity and pressure are set to a uniform vector value of 0. After
the first timestep, the computed flow solution is usually quite different
than the one initially set. For a stationary single-phase incompressible
flow, the initial conditions can be considered as an initial guess that
speeds up convergence to the steady state. Should this guess be exces-
sively far from the appropriate solution, solver divergence may occur.
In other situations, the initial conditions are crucial, because they deter-
mine how the field evolves in time from the initial values. Compressible
flow simulations rely heavily on the initial pressure, temperature, and/or
density to properly compute an equation of state. Incompressible mul-
tiphase simulations require very accurately initial values for the phase
indicator field that separates the fluid phases. Large errors in the ini-
tial phase indicator cause even larger errors in curvature approximation,
leading to strong numerical instability and likely catastrophic failure.
As stated previously, the cavity example case is relatively tolerant to-
wards the definition of the initial conditions. A case that has special
requirements on the initial conditions is the damBreak case, simulated
with the interFoam solver. The solver interFoam is a two-phase flow
solver, using an algebraic Volume-of-Fluid method to distinguish between
the two immiscible and incompressible fluid phases. For this purpose a
new scalar field is introduced: alpha.water. The gas and the liquid
phase in this example will have alpha.water values of 0 and 1, re-
spectively. For the first example, a water droplet will be added to the
damBreak case, as shown in figure 3.2.
First, a copy of the damBreak tutorial case is made and the case is
renamed:
?> run
?> cp -r $FOAM_TUTORIALS/multiphase/interFoam/\
laminar/damBreak/damBreak damBreakWithDrop
115
OpenFOAM Case setup
INFO
Some tutorials will have field files such as phi available in the 0
directory. Storing field files in their original state enables the user to
quickly reset initial field values. Alternatively, a version control system
might be used for the simulation case directory - more information on
this can be found in chapter 6.
The contents of the setFieldsDict for the example test case are shown
below, together with the added sphereToCell sub-dictionary entry used
to initialize a small droplet, i.e. a circle with the alpha.water value of
1:
defaultFieldValues
(
volScalarFieldValue alpha.water 0
);
regions
(
boxToCell
{
box (0 0 -1) (0.1461 0.292 1);
fieldValues
(
volScalarFieldValue alpha.water 1
);
}
sphereToCell
{
origin (0.4 0.4 0);
radius 0.05;
fieldValues
(
volScalarFieldValue alpha.water 1
volVectorFieldValue U (-1 0 0)
);
}
);
116
Discretization Schemes and Solver Control
Figure 3.2: A side by side of the initial conditions before and after adding
the droplet.
The defaultFieldValues will set the internal field to the provided de-
fault value 0, before proceeding to process the regions sub-dictionary.
The syntax of setFieldsDict is mostly self-explanatory for simple
shape-volume selections like those shown above. For a chosen volume, all
cells whose cell center is within this volume will have the given fields set
accordingly. For example, the sphereToCell source selects cells whose
centers are within the sphere of a specified center and radius, and sets
the field values αwater = 1 and U = (−1, 0, 0).
117
OpenFOAM Case setup
fvVectorMatrix UEqn
(
fvm::ddt(U)
+ fvm::div(phi, U)
- fvm::laplacian(nu, U)
);
solve(UEqn == -fvc::grad(p));
INFO
A Domain Specific Language (DSL) is developed on purpose for some
applications, and for others, it is a natural consequence of software
development with a clear separation of abstraction layers. Thinking in
terms of discretized equations, matrices, and source terms, and not in
terms of e.g. iteration loops, variable pointers and functions, represents
the foundation of equation mimicking / DSL in OpenFOAM. Separating
levels of abstraction is a sign of good software development practices.
118
Discretization Schemes and Solver Control
TIP
Discretization and interpolation schemes are generic algorithms in
OpenFOAM. To distinguish between the implicit algorithms (matrix
assembly) and the explicit algorithms (source terms), the algorithms
are categorized into C++ namespaces. A namespace is a programming
language construct that is used to avoid name lookup clashes (more
details given by [6])
119
OpenFOAM Case setup
gradSchemes
{
default Gauss linear;
grad(p) Gauss linear;
}
divSchemes
{
default none;
div(phi,U) Gauss linear;
}
laplacianSchemes
{
default Gauss linear orthogonal;
}
interpolationSchemes
{
default linear;
}
snGradSchemes
{
default orthogonal;
}
INFO
If the following description of selected schemes is not clear after the
first read, it doesn’t impact the understanding of the rest of the book.
Those interested may return to this part of the book later and read it
again alongside chapter 1. Describing all of the available schemes in
OpenFOAM is outside of scope for this book, so only a few selected
schemes were addressed.
The schemes are sorted into categories that correlate to the terms of the
mathematical model as well as the entries in system/fvSchemes:
• ddtSchemes
120
Discretization Schemes and Solver Control
• gradSchemes
• divSchemes
• laplacianSchemes
• interpolationSchemes
• snGradSchemes
INFO
The so-called Run-Time Selection in OpenFOAM enables the selection
different schemes in system/fvSchemes. This module outputs a list
of all available schemes if the entry in system/fvSchemes is wrong.
This can be used to learn what schemes are available: entering a name
of a non-existing scheme prompts the RTS system to provide a list of
available schemes.
ddtSchemes
The ddtSchemes are schemes used for temporal discretization. The Euler
first-order temporal discretization scheme is set as a default value for
transient problems, and it happens to be the scheme used to show the
discretization practice of the FVM in chapter 1. Alternative choices of
the temporal discretization schemes are:
• CoEuler,
• CrankNicolson,
• Euler,
• SLTS,
• backward,
• bounded,
• localEuler, and
• steadyState.
121
OpenFOAM Case setup
field computed from the local Courant number field. The reciprocal
of the Courant number limited local time step is calculated as
δt−1 −1
f Co = max(Cof /maxCo, 1)δt , (3.1)
where the local face centered Courant number is computed from
the volumetric flux F , the face area normal vector Sf , and the
distance between the cell centers of the cells connected at the face
d:
kF k
Cof = δt. (3.2)
k Sf kk d k
In case the mass flux is provided, the density field needs to be
used to compute the local face-centered Courant number using
k Fρ k
Cof = δt, (3.3)
ρf k Sf kk d k
where Fρ is the mass flux stored at the face center. Once the face
centered Courant number is computed, the reciprocal of the time
step is computed using equation 3.1. For a face-centered field φf ,
the reciprocal of the face-centered time step is then used to compute
the first-order temporal derivative:
δt−1 n o
f Co (φf − φf ). (3.4)
Since the temporal schemes operate mostly on cell centered fields,
a cell-centered value for the reciprocal of the time step is computed
as a maximum of the face values, i.e.
122
Discretization Schemes and Solver Control
1 1
φoo = φ(t−2δt) = φ(t)−2φ0 (t)δt+ φ00 4δt2 − φ000 δ8t3 +... (3.8)
2 6
Multiplying equation 3.7 with 4 and subtracting equation 3.8 from
that, results in the BDS2 temporal discretization of a cell-centered
field φc :
3 o 1 oo
2 φc − 2φc + 2 φc
0
φc ≈ . (3.9)
δt
The calculation of the derivative using old values is done in Open-
FOAM without requiring the client code to store old field and mesh
values. All volumetric and face centered fields have the ability store
the values from the old (o) and the old-old (oo) time step values
automatically. So does the mesh itself store information needed
for the temporal discretization of higher order (e.g. old and old-old
mesh volume fields). The backward scheme computes a second or-
der temporal derivative which may be explicitly evaluated, since the
o and oo field values are known. The actual implementation takes
into account the possible adjustment of the time step, resulting in
the derivative of the cell-centered fields as
ct φc − 2cto + ctoo φoo
φ0c ≈ c
, (3.10)
δt
where c coefficients are defined in the following way:
δt
ct = 1 + , (3.11)
δt + δto
δt2
ctoo = o , (3.12)
δt (δt + δto )
cto = ct + ctoo , (3.13)
and in case of the two sub-sequent time steps being equal, the c
coefficients correspond to the discretization using a constant time
step, compare equation (3.9).
123
OpenFOAM Case setup
gradSchemes
• Gauss
• cellLimited
• cellMDLimited
• edgeCellsLeastSquares
• faceLimited
• faceMDLimited
• fourth
• leastSquares
• pointCellsLeastSquares
Gauss is the most often used discretization scheme for the gradient, the
divergence (convective), and laplacian (diffusive) terms. It is also
described in chapter 1. This scheme expects face centered values
in order to compute the cell-centered gradient.
cellLimited extends the functionality of the standard gradient scheme. It
computes a limiter for the cell centered gradient value, computed
in the standard way and then scales the gradient value with this
limiter (0 < l ≤ 1). The limiter is calculated using the maximal
and minimal values (φcmax , φcmin ) of a cell centered property φc ,
found by indirectly searching through the face adjacent cells as
124
Discretization Schemes and Solver Control
TIP
The equations in this section rely on the cell-to-cell connectivity and
they also use cell-based stencils. The actual implementation makes use
of the owner-neighbor addressing, for reasons described in chapter 1.
125
OpenFOAM Case setup
Determining the final gradient in the cell center, the gradient com-
puted by the standard scheme is scaled with the limiter
∇(φ)c = ∇(φ)c · l. (3.22)
where cc are the cells of the stencil. wcc is the weighting factor
relating the cell in question and the cell of the stencil. Usually an
inversed distance between two cells is taken as the weight and Ecc 2
is the squared error of the Taylor expansion from the cell for which
the gradient is computed to the cell of the stencil:
1
Ecc = ∇∇(φ)c : (∆x∆x). (3.25)
2
126
Discretization Schemes and Solver Control
INFO
The Gauss gradient calculation shown for the example field in figure 3.3
is a textbook example of mesh anisotropy - the calculation strongly
depends on the orientation of faces with respect to the coordinate axes.
INFO
As an exercise, try computing the gradient of steep explicit functions
on different meshes and compare the error convergence for different
gradient schemes.
divSchemes
127
OpenFOAM Case setup
laplacianSchemes
interpolationSchemes
128
Discretization Schemes and Solver Control
129
OpenFOAM Case setup
relTol 0;
}
U
{
solver PBiCG;
preconditioner DILU;
tolerance 1e-05;
relTol 0;
}
}
PISO
{
nCorrectors 2;
nNonOrthogonalCorrectors 0;
pRefCell 0;
pRefValue 0;
}
INFO
There are two types solvers in OpenFOAM: solver applications and
linear solvers. Solver applications are programs used directly by the
end-user for running simulations. Linear solvers are algorithms used
to solve linear algebraic equation systems as a part of the simulation
solution procedure. Usually, the solver applications are named ”solvers”
in short, so this term is used for solver applications from this point on.
Linear Solvers
The example shown above, uses PCG to solve the pressure equation and
the matrix is preconditioned using DIC, which can be used for symmetric
130
Discretization Schemes and Solver Control
matrices. For the momentum equation on the other hand, the asymmetric
PBiCG solver is used, in conjunction with a DILU preconditioner.
tolerance defines the exit criterion for the solver. This means that if
the change from one iteration to the next is below this threshold,
the solving process is assumed to be sufficiently converged and
stopped. When performing e.g. steady state simulations of a steady
problem, the tolerance should be quite small, in order to improve
the convergence and accuracy. For transient problems on the other
hand, no steady solution can be obtained and hence the tolerance
must not be chosen to be very small.
relTol defines the relative tolerance as the exit criterion for the solver.
If it is set to some other value than 0, it overrides the tolerance
setting. Whereas tolerance defines the absolute change between
two consecutive iterations, relTol defines the relative change. A
value of 0.01 forces the solver to iterate until a change of 100% is
reached, between two consecutive iterations. This comes in handy,
when a strongly unsteady system is simulated and the tolerance
setting leads to high iteration numbers.
maxIter is an optional parameter and has a default value of 1000. It
defines the maximal number of iterations, until the solver is stopped
anyway.
Pressure-Velocity Coupling
TIP
For more background information on the various algorithms, the reader
is referred to [2, 7]. There is plenty of information available on the
OpenFOAM wiki as well.
131
OpenFOAM Case setup
Depending on the mesh size, the time step and the total time that is to
be simulated this command does take significantly longer, as the usual ls
or cp command. Additionally the information printed to the terminal is
132
Solver Execution and Run Control
massive and gets lost as soon as the terminal is closed. Hence the syntax
to execute the solver should be extended:
The nohup command instructs the shell to keep the job running, even
when the shell window is closed or the user logged out. Rather than
printing everything to the screen, the output is piped into a file called
log and the job is moved into the background. As the job is running
in the background and all output is forwarded to the log file, the tail
command is used to basically print the end of the file to the screen. By
passing -f as a parameter, this is updated until the command is quitted
by the user. An other option to keep up to date with the running job is
to use the pyFoamPlotWatcher, which parses any log file and generates
gnuplot windows from that. These windows are updated automatically
and include residual plots, which is handy for monitoring a simulation.
Each case must posses a system/controlDict file that defines all the
runtime related data. Including the time when to stop the solver, the time
step width, the interval and method how time step data is written to
the case directory. The content of this dictionary is re-read automatically
during the run time of a solver and hence allows for changes, while the
simulation is running.
133
OpenFOAM Case setup
134
Solver Execution and Run Control
INFO
The execution of a solver on a single processor core is often referred
to as ”serial execution” - the solver was executed ”in serial”.
INFO
Interaction between the interprocess communication (IPC) and the FVM
on unstructured meshes in OpenFOAM is handled automatically for
discrete differential operators, its details are relatively complex and
outside of the scope for this book.
The run alias switches to $FOAM_RUN directory. In the next step, a choice
is made on the way the computational domain will be decomposed. Var-
ious methods exist in OpenFOAM for decomposing the domain. The
scotch domain decomposition is used for this tutorial. The domain de-
composition configuration is stored in the dictionary file system/decom-
poseParDict. You can find available decomposeParDict and similar
files within OpenFOAM tutorials using
135
OpenFOAM Case setup
The cavity tutorial case does not contain this file by default. Just any
decomposeParDict that is located somewhere in the tutorials of Open-
FOAM is fine to copy to the local cavity case.
?> cp cp $FOAM_TUTORIALS/multiphase/interFoam/\
laminar/damBreak/damBreak/system/decomposeParDict ./system
method simple;
The first line defines how many sub-domains (or MPI processes) will be
used, and the second selects the method that should be used to decompose
the domain. Using the simple method is generally a bad choice for real
world applications, because it splits the domain in spatially equal parts.
This happens without minimizing the size of the inter-process communi-
cation boundaries between the sub-domains. Considering a mesh that is
very dense on one side and very coarse on the other, the simple method
would slice it in half, producing sub-domains with very uneven distribu-
tion of finite volumes. The mesh with more finite volumes requires more
computational resources, making the simulation imbalanced. Increased
inter-process communication time caused by unnecessarily large inter-
process communication boundaries can severely decrease the efficiency
of the parallel execution.
Before decomposing the domain, the mesh must be generated. This ex-
ample uses a blockMesh based mesh, which must hence be executed.
Once the mesh is generated, the domain decompositioning using decom-
posePar must be executed:
?> blockMesh
?> decomposePar
136
Solver Execution and Run Control
137
OpenFOAM Case setup
3.5 Summary
In this chapter we took the next step of the CFD workflow into the case
setup and simulation running phase. This entails setting the initial and
boundary field values for the fields involved in the simulation, which can
be done with various utilities. Discretization and interpolation schemes
are a critical component of the finite volume method in OpenFOAM and
they can be selected by the user at the start, or during the simulation.
In OpenFOAM there are many interpolation and discretization schemes
available, so we mentioned only a few of them. A list of all available
schemes is reported by OpenFOAM if a wrong entry is provided in the
configuration file. We walked though the numerical foundations for some
schemes, and their clean software design allows the interested reader
to learn how scheme works without a deep understanding of the C++
programming language. Finally, we showed how a user can execute a
flow solver in both serial and parallel modes.
Further reading
[1] J. U. Brackbill, D. B. Kothe, and C. Zemach. “A continuum method
for modeling surface tension”. In: J. Comput. Phys. 100.2 (June
1992), pp. 335–354. issn: 0021-9991.
[2] J. H. Ferziger and M. Perić. Computational Methods for Fluid
Dynamics. 3rd rev. ed. Berlin: Springer, 2002.
[3] Jasak. “Error Analysis and Estimatino for the Finite Volume Method
with Applications to Fluid Flows”. PhD thesis. Imperial College of
Science, 1996.
[4] Dimitri J. Mavriplis. Revisiting the Least-squares Procedure for
Gradient Reconstruction on Unstructured Meshes. Tech. rep. Na-
tional Institute of Aerospace, Hampton, Virginia, 2003.
[5] OpenFOAM User Guide. OpenCFD limited. 2016.
138
Summary
139
4
Post-Processing,
Visualization and Data
Sampling
This chapter covers the basics of post-processing, visualizaiton and data
sampling. Not only OpenFOAM tools are discussed, but also paraView
is used for various tasks, as well as runtime data sampling.
4.1 Post-processing
?> ls $FOAM_SRC/functionObjects
field graphics lagrangian solvers
forces initialisation randomProcesses utilities
postProcess
where solverName is the name of the solver that was used to originally
used to compute the existing flow data.
142
Post-processing
?> $FOAM_SRC/functionObjects/field
Outlined below are some of the more commonly used arithmetic or field
calculus operations:
div computes the divergence of a vector or tensor field and writes the
result to a new scalar or vector field respectively. A numerical
scheme for the divergence operator (for example, div(U)) must
be present in the fvSchemes dictionary, in order to be executed.
The divergence of the velocity field U for the latest time can be
computed by
?> simpleFoam -postProcess -func 'div(U)'
yPlus
143
Post-Processing, Visualization and Data Sampling
?> ls $FOAM_SRC/functionObjects/field/yPlus
yPlus.C yPlus.H
In the above example simpleFoam was called as the base solver. In the
general case whatever flow solver was used to compute the flow field
can be subsituted, for example pisoFoam for transient flow. The y + field
is calculated only on boundary faces, which are of type wall, leaving
the remaining cell centered values zero. Besides the freshly written field,
both yPlus tools print some valuable information to the screen. This
information contains the coefficients used for the particular turbulence
model as well as the minimum, maximum and average y + value for all
wall boundary patches.
?> ls $FOAM_UTILITIES/postProcessing/patch
patchAverage patchIntegrate
144
Post-processing
In the above code snippet, <field> and <patch> represent the field and
patch to operate on, respectively. The results are not stored anywhere in
the case directory, but only printed to the terminal. Though they can be
stored, by piping the output of the particular utility to a log file. Shown
below is an example of making these calculations on the air foil example
case with existing fields and piping to a stored text file.
?> postProcess -func 'patchAverage(<fieldName>,name=<patch>)' > patchAveResults.txt
?> postProcess -func 'patchIntegrate(<fieldName>,name=<patch>)' > patchIntegrateResult
vorticity
The vorticity utility calculates the vorticity field ω, using the velocity
field U and writes the result to a volVectorField named vorticity.
The vorticity of a velocity field represents the local magnitude and direc-
tion of rotation in the flow and is defined in equation 4.2. Performing this
operation is also known as taking the curl of a field. The final output of
this utility is the computed vorticity field written to each time directoy.
The source code for this post-processing utility can be found in $FOAM_UTIL-
ITIES/postProcessing/velocityField/vorticity with an execu-
tion example shown below.
?> postProcess -func vorticity
145
Post-Processing, Visualization and Data Sampling
probeLocations
fields
(
p
U
);
probeLocations
(
(0 0 0)
(1 1 1)
);
If not stated otherwise, the fields are probed at all existing times with the
output files located in a nested subdirectory inside the case directory. The
first folder is named probes and the subfolder indicates the first time step
the data was sampled from. For example, all pressure data gathered by the
probing can be found in the file probes/0/p. The data is arranged in a
tabulated manner with the time being stored in the first column, followed
by the extracted field value. This format can then be processed using
plotting utilities such as gnuplot or python/matplotlib. An example
of the results of a probing of the pressure field is shown below.
# x 0 1
# y 0 1
# z 0 1
# Time
0 0 0
10 3.2323 2.2242
146
Post-processing
To generate the results, the Allrun script provided with the tutorial must
be executed. After the simulation is finished, the distribution of y + over
the surface of the NACA profile is inspected. In order to do so, the
yPlus post processing function object has to be executed in the case
directory and the resulting yPlus field must be visualized on the surface
using paraView.
Although yPlus prints the minimum, maximum and average y + values to
the screen, the average value can be recomputed differently, based upon
the yPlus field. Using patchAverage does exactly this, after the initial
yPlus field has been computed:
147
Post-Processing, Visualization and Data Sampling
?> ./Allrun
?> simpleFoam -postProcess -func yPlus
?> postProcess -func 'patchAverage(yPlus,name=FOIL)'
148
Data sampling
Opening the examples shown above from etc in a text editor shows
all available configuration options for the sample post-processing utility.
There is a large number of options available, and the provided sam-
pleDict is very well documented. The options are described in a clear
fashion. sample can handle a multitude of sampling parameters: field
names, output formats, mesh sets, interpolation schemes, and surfaces.
Regardless of the variety of sampling parameters, sample always handles
the data sampling process in the same way, regardless of the user’s choice
of a parameter sub-set. A field to be sampled is chosen by providing the
field name within the fields word list. A sub-set of the mesh (sets
sub-dictionary) or a geometrical entity (surfaces sub-dictionary) is used
to locate the data sampling points. In case when the data sampling points
do not coincide with the mesh points that hold the field data (e.g. cell
centers or face centers), the data is interpolated using different inter-
polation schemes (interpolationScheme parameter). The interpolated
data is then stored in the case, in a specified output format (setFormat
parameter).
An example of a 1D data extraction is to define a line which intersects
the flow domain and sample the velocity field along this line. This can be
used to sample e.g. the velocity profile, as usually done for the cavity
149
Post-Processing, Visualization and Data Sampling
case. This extracted profile can then be compared to other datasets, using
any preferred plotting utility.
Another example application of sample is to extract boundary field val-
ues on large simulation cases. Instead of trying to open up the entire
simulation case in paraView, sample can be used to extract only the
values on the boundary patch in question. This localized approach to
post-processing using sample can drastically reduce the required compu-
tational resources, depending on the size of the datasets.
Any simulation case can be used to show how to sample the simulation
data using the sample utility. For that purpose, the two-dimensional rising
bubble test case is selected, available in the chapter4/risingBubble2D
sub-directory of the example case repository. In order to use sample
sucessfully on that case, the simulation has to be executed:
?> blockMesh
?> setFields
?> interFoam
Sections that follow cover examples of using sample and they all involve
manipulating a functions subdictionary. While this subdictionary can be
defined in the controlDict it can also be defined in a separate, custom
dictionary file which is the approach we will use in this example.
WARNING
Before proceeding with the examples, open the sampleDict provided
with the sample source code in a text editor of your choice, in order
to see all the possible sampling configuration options.
150
Data sampling
p_rgh
U
);
sets
(
alphaWaterLine
{
type uniform;
axis distance;
start (-0.001 1.88 0.005);
end (2.001 1.88 0.005);
nPoints 250;
}
);
The setFormat option changes the format of the data written to file and
the interpolationScheme option dictates what type (if any) of value
interpolation occurs, before data is mapped to the sample line. All fields
to be sampled need to be listed in the fields list - alpha.water field in
this case. The sets subdictionary contains a listing of all of the sample
lines that are extracted.
WARNING
Note that the sample line position is slightly adjusted to be away from
the boundary. In general, the sample line is not to be co-planar with
mesh faces, as it prevents sample from determining which cells the
line is intersecting.
151
Post-Processing, Visualization and Data Sampling
Figure 4.2: A sample of the alpha.water field along our defined line
?> ls postProcessing/sets/7
alphaWaterLine_alphaWater_p.xy alphaWaterLine_U.xy
The scalar fields alpha.water and p are stored in the same file, while
the sampled vector velocity field is stored in a separate file. alpha.water
field values can be visualized from the stored data, using a plotting tool
and should lead to a figure similar to figure 4.2.
TIP
If the -time 7 option is omitted when executing sample, every
timestep is sampled resulting in the correspoding XY data being inter-
polated and saved.
Sampling along a plane is especially useful for large 3D cases which are
big enough to require a long time to transfer the simulation data across
a network connection. The process for setting up the sampleDict for a
plane is very similar to setting up line sampling. The surfaceFormat
entry needs to be configured and a list of sample planes needs to be
provided inside the surfaces sub-dictionary. Similar to line sampling,
having the sample plane co-planar with mesh faces should be avoided.
152
Data sampling
surfaces
(
constantPlane
(
type cuttingPlane;
planeType pointAndNormal;
pointAndNormalDict
{
point (1.0 1.0 0.005);
normal (0.0 0.0 1.0);
}
)
);
};
}
The VTK output format is chosen for the surface, and the surface type
is set to plane in the constantPlane sub-directory. The plane is de-
fined using a point position vector (point) and a plane normal vector
(normal).
In order to generate the Visualization Toolkit (VTK) planar surface and
extract the alpha.water data, -postProcess is executed on a specific
explicit time directory of the chapter4/risingBubble2D case:
?> interFoam -postProcess -dict ./system/sample.surface -time 7
153
Post-Processing, Visualization and Data Sampling
surfaces
(
fluidInterface
(
type isoSurface;
isoField alpha.water;
isoValue 0.5;
interpolate true;
pointAndNormalDict
154
Data sampling
{
point (1.0 1.0 0.005);
normal (0.0 0.0 1.0);
}
)
);
};
}
155
Post-Processing, Visualization and Data Sampling
functions
{
// Surfaces sample function object
surfaces
{
type surfaces;
surfaceFormat vtk;
writeControl writeTime;
interpolationScheme cellPoint;
fields (U p_rgh alpha.water);
surfaces
(
walls_constant
(
type patch;
patches ( top );
)
);
};
}
?> ls postProcessing/surfaces/7/
walls_constant.vtp
One patch VTK file is created with the set of three fields written at the
boundary elements.
The sample utility allows sampling of different sets and surfaces, thus
sampleDict stores lists of such elements. A working sampleDict con-
figuration file is prepared in the risingBubble2D/system simulation
case directory. It stores the configuration for all the examples described
in this section. When additional functionalites are required from sample,
the sampleDict dictionary provided with the application source code
should be the first place to look.
156
Visualization
4.3 Visualization
157
Post-Processing, Visualization and Data Sampling
Note that if the paraview binary is not on your computer’s PATH then you
may have to call it with the full directory path. An example is shown
below but will need to be adjusted to represent your paraview installation
location.
By default, the internal mesh and all the cell centered fields of an Open-
FOAM case are chosen for visualization. The fields and the mesh are
read when the selection is accepted by clicking on the Apply button
in the Properties window. The choice of the visualized field can be
158
Visualization
made using the panel above the Properties window and the Pipeline
browser, shown in figure 4.6.
Initially, the mesh is colored with a solid color. Clicking on the Solid
color tab in the panel shown in figure 4.6, produces a drop-down menu
with all available fields. The alpha.water field in the initial time step
is shown in figure 4.7.
One often used Filter is the Contour filter for visualizing the interface
between two fluids. The Contour filter computes an iso-surface of a
prescribed value, based on a scalar field. In this case the scalar field is
alpha.water. This operation is in fact fairly similar to the isoSurface
sampling utility, described in section 4.2. The filter can be selected from
159
Post-Processing, Visualization and Data Sampling
160
Visualization
Extracting values from the top patch can be done using paraView as
opposed to the sample utility which was discussed in Section 4.2. In
order to sample the patch, the boundary mesh and not just the internal
mesh needs to be read in paraView. To do so, select the top patch
in the Mesh Regions part of the Properties window as shown in
Figure 4.10. The top patch is then isolated in the pipeline from the
rest of the case mesh by selecting it within the Patches branch using
the Filter->Alphabetical->Extract Block filter. To plot the data of
161
Post-Processing, Visualization and Data Sampling
162
Visualization
this patch directly without interpolation, select the extracted block in the
pipeline browser and choose Filters->Data Analysis->Plot Data. In
the display window (View->Display), select the dynamic pressure prgh
and skip to the last time step of the simulation. The resulting diagram is
shown in Figure 4.11.
In this section, the absolute minimal aspects of the workflow with the
paraView visualization application are provided. For further details, please
consult the material available on the Internet, as well as the official
documentation.
163
Part II
Programming with
OpenFOAM
5
OpenFOAM design
overview
The FOAM part of the OpenFOAM name is given as an acronym for
Field Operation and Manipulation. As noted in the previous chapters,
FOAM can be quite complex, because they represent and manipulate
physical properties as tensor fields in geometrically complex solution
domains using unstructured finite volume discretization . Explicit and
implicit operations on discrete tensor fields can be performed. Field
changes are governed by a conservation law, modeled as a Partial Dif-
ferential Equation (PDE), and the numerical solution of the PDE usually
involves assembling and solving large sparse linear equation systems.
Sometimes the PDE are strongly coupled and the coupling needs to be
taken into account by a numerical algorithm, for example the coupled
solution of the momentum and pressure equations. Solving the linear al-
gebraic equation systems for new field values is performed using iterative
solution algorithms because of usually large matrix sizes[1, 3].
the field and the mesh concepts are on a higher level of abstraction, than a
dictionary data structure. Abstraction allows the programmer to effectively
build software elements which model the behavior of complex concepts
in the area of interest by allowing him/her to concentrate not exclusively,
but more strongly, on the currently implemented concept. This way, the
programmer does not have to hold an image of an entire software system
in his mind, but switch concentration from one relatively isolated concept
to another. Well-modeled concepts are those that are strongly cohesive
and loosely coupled with one another. This enables simplifies extensions
and makes the software more modular. For example, an important con-
cept in CFD is a finite volume mesh that is used to discretize the flow
domain.
The mesh will hold various geometrical and topological data, as already
described in sections 1.3 and 2.1. Additionally, different (often complex)
functions are needed to operate on that data. As an example of abstrac-
tion in OpenFOAM and the C++ programming language, both the data
and the related functions of the finite volume mesh are encapsulated
into a class fvMesh. This allows the programmer to think in terms of
a mesh, and not bother with all the details involving the data structures
and functions that build it. Such high-level of abstraction in thinking al-
lows the programmer to write algorithms that use the entire mesh as one
of their arguments, which makes the algorithm interface much easier to
understand. Otherwise, algorithms working with specific sub-elements of
the mesh would have dozens of arguments, as is often the case in proce-
dural programming. Without abstraction, a very large number of global
variables would also be present and operated on by various algorithms
(routines) which makes it very difficult to determine the program flow.
INFO
The first sections of this chapter cover an overview of the software
design of OpenFOAM. However, terms from software development are
not covered and should be learned independently. OpenFOAM is a
large software and learning software development is a requirement for
learning how to develop new methods in OpenFOAM.
168
Generating doxygen documentation
readable, yet the C++ language still maintains very high computational
efficiency and is cross-platform. These aspects make the C++ language a
common choice for scientific and computational software development.
The best way to understand how different parts of OpenFOAM are de-
signed and interact with each other is through browsing the source code.
In order make this easier for the user, both the official and extend
OpenFOAM release provide support for generating HyperText Markup
Language (HTML) documentation. Generating HTML documentation is
performed by via the Doxygen documentation system. The Doxygen doc-
umentation generated for the main development version is also available
online as the Extended Code Guide.
$WM_PROJECT_DIR/doc
$WM_PROJECT_DIR/doc/Doxygen/html/index.html
169
OpenFOAM design overview
file can be viewed with a web browser. This page is the starting page of
the local generated HTML help of the OpenFOAM installation. Alterna-
tively, a LATEX based documentation, is available. This documentation is
available in PDF format but must be converted manually.
EXERCISE
Using the Doxygen source code documentation find what is a require-
ment for a Type managed by the tmp<Type> smart pointer.
This section will address some parts of OpenFOAM that are encountered
by the user who is running simulations.
Setting the initial and boundary conditions in chapter 3 has already shown
that the user has considerable flexibility at his or her disposal. Without
compiling any additional code existing boundary conditions, interpola-
tion and discretization schemes, viscosity models, equations of state, and
similar parameters can be selected in input (field or configuration) files.
In terms of the underlying implementation, choosing a boundary condition
based on input data from files is relatively complex: an object of a specific
class is chosen and instantiated at runtime based on a read parameter
(boundary condition type) defined by a user. Running simulations puts
the user directly or indirectly into contact with the following parts of
OpenFOAM: the executable applications (solvers, pre-processing utilities,
post-processing utilities), the configuration system (dictionary files), the
boundary conditions and the numerical operations (choosing discretization
schemes).
5.2.1 Applications
The executable applications are programs that are run by the user in the
command line or via a GUI. They belong to what is usually called client
code, that uses (is a client) of various OpenFOAM libraries. Executable
170
Parts of OpenFOAM encountered in simulations
applications
solvers
test
utilities
171
OpenFOAM design overview
applications
solvers
basic
combustion
compressible
dicreteMethods
DNS
electromagnetics
financial
heatTransfer
incompressible
lagrangian
multiphase
stressAnalysis
test
...
dictionary
...
parallel
parallel-nonBlocking
...
utilities
finiteArea
mesh
miscellaneous
parallelProcessing
postProcessing
preProcessing
surface
thermoPhysical
172
Parts of OpenFOAM encountered in simulations
INFO
The test applications are a very useful resource of information regard-
ing specific classes and algorithms. They show how the classes and
algorithms are meant to be used.
The various dictionary files found in the simulation case directory con-
tain different configuration parameters: boundary conditions, interpolation
schemes, gradient schemes, numerical solvers, etc. Choosing different el-
ements as well as initializing them properly is performed at runtime
after starting an executable application. The process of choosing types at
runtime is called Runtime Selection (RTS) and it employs quite a few
software design patterns and C++ language idioms. The RTS process is
quite complex, and its description is outside the scope of this chapter.
At this point it is only important to remember that it makes OpenFOAM
very flexible easy to use.
INFO
The configuration system itself does not exist in a form of an ab-
straction, implemented as a single class. The system is supported
by the functionallity of the dictionary and IOdictionary classes,
Input/Output (IO) file streams, and the RTS mechanism used in the
runtime selectable classes.
For example, any user defined class in OpenFOAM can be made runtime
selectable. Additionally, the class attributes can be modified during the
simulation - as soon as the dictionary file is modified, the file can be
re-read and the class attributes are set to the new values.
INFO
173
The parameters defined in dictionaries are not changed in the executable
application immediately when they are modified. The change is applied
OpenFOAM design overview
The numerical operations are used by the solver and are responsible
for equation discretization component of the simulation. The user will
be in contact with them when he/she modifies the system/fvSchemes
dictionary file in the simulation case directory in order to choose a
different kind of discretization practice, interpolation scheme, or similar
parameter. Different numerical operations have different properties and
choosing them requires experience in CFD and also, ideally, in numerical
mathematics.
The part of OpenFOAM responsible for numerical operations of the FVM
can be found executing the alias foamfv in the console, or switching to
the appropriate directory:
?> cd $FOAM_SRC/finiteVolume
174
Parts of OpenFOAM encountered in simulations
The discrete operators are generic algorithms templated for a tensor pa-
rameter, using a class trait system to determine the resulting tensor rank.
Standard choice of discretization in OpenFOAM is based on the di-
vergence theorem, and the operator calculation is delegated to the dis-
cretization scheme. Those interested in developing their own discretiza-
tion schemes may want to examine the files fvmDiv.C and convec-
tionScheme.C. Both files contain implementations that show how the
divergence term approaches the equation discretization. The source code
listing below holds the implementation of the code that delegates the
divergence calculation from the operator to the convection scheme.
return fv::convectionScheme<Type>::New
(
vf.mesh(),
flux,
vf.mesh().divScheme(name)
)().fvmDiv(flux, vf);
The schemes can be selected via RTS, but they must comply to the
interface prescribed by convectionScheme.H. Such flexible design can
be summarized with the following way:
175
OpenFOAM design overview
The source code which implements other discrete operators can be found
in the directory $FOAM_SRC/finiteVolume/finiteVolume in the sub-
directories fvc and fvm. The directory fvc stores implementations of
explicit discrete operators, which result in computed fields. The directory
fvm stores the implementation of implicit discrete operators, which result
in coefficient matrix assembly of the algebraic system of equations.
INFO
Whenever there is an fvc:: (finite volume calculus) operator in the
code, the result will be a field. When the fvm:: operator is encountered,
the result will be a coefficient matrix.
The actions are performed by the solver application, the interaction with
the solver code is done when the user modifies the solver in order to
make it function in a different way. OpenFOAM supports runtime solver
modifications via fvOptions and function objects (see chapter 12), without
requiring the user to modify the solver code. More information on solver
applications and how to program new solvers is covered in chapter 9.
Generally, when standard solvers are used to obtain simulation results,
their code will typically not be modified by the user.
A high level of abstraction in OpenFOAM allows the user to write new
solvers and solution algorithms very quickly. The high abstraction level
of OpenFOAM can be nearly be used as a CFD programming language
(DSL) - also reffered to as equation mimicking ([2]). Take, for example,
a mathematical model for a scalar transport of a scalar property T :
∂T
+ ∇· (T U) + ∇· (k∇T ) = S (5.1)
∂t
Equation (5.1) describes the transport of a field T composed of a passive
advection with the velocity U, diffusion with the diffusion coefficient field
k together with a source term S. Discretization operators, being function
templates, can operate on fields of different tensors, and thus on a scalar
field they produce the following model equation in OpenFOAM:
ddt(phi) + fvm::div(phi, T) + fvm::laplacian(k, T) = fvc::Sp(T)
176
Parts of OpenFOAM encountered in simulations
177
OpenFOAM design overview
5.2.5 Post-processing
WARNING
Before programming a post-processing application, it is advisable to
check if one with the desired functionality already exists. OpenFOAM
provides a large number of utility applications to choose from.
178
Often encountered classes
utilities
postProcessing
dataConversion
lagrangian
lumped
miscellaneous
noise
optimisation
postProcess
parts of the flow domain. This must not be limited to this, though. You
can basically compute anything during the post-processing step, using a
post-processing application.
Function objects, unlike the post-processing applications, are called dur-
ing the simulation run. The term function object comes from the C++
language terminology, where it denotes a class which is callable, since
it implements a call operator: operator()(). The function object is ba-
sically a function encapsulated into a class, which is advantageous e.g.
when the function needs to store information about its state after execu-
tion. For example, a function object which computes an average maximum
pressure in a simulation can stop a simulation if the pressure value ex-
ceeds a prescribed value. Such a function needs access to the maximal
pressure value and needs to store data required to calculate a running
average. Those two attributes are therefore encapsulated into a function
object class. More information on function objects in OpenFOAM can be
found in chapter 12.
179
OpenFOAM design overview
5.3.1 Dictionary
Reading dictionary entry values is the most basic form of working with
the dictionary class. The dictionary class interface provides multiple
methods that can be used to read data. The getOrDefault method is
the commonly used example of this as it not only provides read access to
the specified dictionary entry, it also defines a default value if no entry
is found. This eliminates runtime errors caused by missing non-critical
input values.
const auto& solution(mesh.time().solutionDict());
const auto name(solution.getOrDefault<word>("parameter1"));
const auto vector1(solution.getOrDefault<vector>("vector1");
As shown in the above code, getOrDefault requires more than just the
name of the parameter to read. It also requires a template argument with
the name of the data type that is to be looked up in the dictionary.
Accessing sub-dictionaries
180
Often encountered classes
axis (1 0 0);
origin (5 10 15);
type "modelA";
modelA
{
name "uniform";
}
template<class Type>
void Foam::dimensioned<Type>::operator+=
(
const dimensioned<Type>& dt
)
{
dimensions_ += dt.dimensions_;
value_ += dt.value_;
}
It can be seen that there are two arithmetic operations being performed:
one for the dimension (units) and the other for the numerical value
of the tensor. The arithmetic operators of the dimensionSet class are
responsible for the dimension checking process. The source code of the +=
arithmetic operator of the dimensionSet class looks like the following:
181
OpenFOAM design overview
return true;
}
182
Often encountered classes
return true;
}
...
dictionary 0;
dimensionSet 1;
mappedBase 0;
...
The conclusions presented for the += operator are exactly the same for
other dimensioned tensor arithmetic operations. Dimension checking is
activated by default and should generally not be deactivated in Open-
FOAM. Even if a custom application implements equations in dimen-
sionless form, those equations will be scaled with what should be di-
mensionless numbers. The dimension checking system can check if for
example the Reynolds number
auto Re = Foam::mag(U) * L / mu;
Info << Re.dimensions() << endl;
183
OpenFOAM design overview
EXERCISE
Why doesn’t the arithmetic operator += of the dimensionSet class
perform an actual arithmetic operation when the if block is evaluated
as false?
INFO
For many class templates in OpenFOAM with a complex name, there
is a typedef provided. In C++ the typedef keyword allows the pro-
grammer to define shorter and more concise type names. Even though
this typedef might not be much shorter of the original, it does at least
save the typing of the <Type> part.
In the case of dimensioned<Type> the emphasis is not on name
length, but on code style - dimensionedVector is a name in camel
case which is used for types in OpenFOAM application level code.
is described so far. Rather than requiring the tensor value and the di-
mensioned set, the dimensioned types require an additional parameter: a
name. For example, if two dimensionedVector objects are constructed
in the following way:
dimensionedVector velocity
(
"velocity",
dimLength / dimTime,
vector(1,0,0)
);
dimensionedVector momentum
(
"velocity",
dimMass * (dimLength / dimTime),
vector(1,0,0)
);
184
Often encountered classes
INFO
Because the fundamental physical dimension units are used to construct
the complex ones (e.g. N = kgm/s2 ), use the global predefined dimen-
sionSet objects when defining your own dimension sets to improve
code readability.
FOAM aborting
EXERCISE
Turn off dimension checking using the aforementioned debug flag and
run the arithmetic addition for momentum and velocity.
Pointers are a special kind of variable which stores the memory address
of an object, that can be used to refer to that object. In C++ it is
possible to pass objects either by value or by reference, with the latter
being significantly faster for larger objects. This is not only faster but also
185
OpenFOAM design overview
The result of the sum of operators acting upon fields rho, U and rhoPhi
will be a coefficient matrix (fvVectorMatrix). As a consequence, the
following points must be fulfilled from the above code:
If the operators ddt and div had been implemented as functions that
take modifiable parameters, writing mathematical models easily (usually
referred to as equation mimicking) would not be possible. The matri-
ces that are returned by the functions are quite large, so returning them
186
Often encountered classes
forAll(vf.boundaryField(), patchI)
{
const fvPatchField<Type>& psf =
vf.boundaryField()[patchI];
const fvsPatchScalarField& patchFlux =
faceFlux.boundaryField()[patchI];
const fvsPatchScalarField& pw =
weights.boundaryField()[patchI];
fvm.internalCoeffs()[patchI] =
patchFlux*psf.valueInternalCoeffs(pw);
fvm.boundaryCoeffs()[patchI] =
-patchFlux*psf.valueBoundaryCoeffs(pw);
}
if (tinterpScheme_().corrected())
{
fvm += fvc::surfaceIntegrate
(
faceFlux*tinterpScheme_().correction(vf)
);
}
187
OpenFOAM design overview
return tfvm;
INFO
Avoiding unnecessary copy operations has a commonly used shorter
name: copy elision. Copy elision can be enforced in various ways in
the C++ programming language: compiler optimizations (Return Value
Optimization, Named RVO), Expression Templates (ET), or by using
rvalue references and move semantics provided by the C++11 language
standard.
In general, when pointers are used, they point to objects which are created
on the heap using the operator new:
someType* ptr = new someType(arguments...);
Since the C++ programming language does not, on purpose, support auto-
matic garbage collection1 , the programmer is left responsible for releasing
the resources.
Thus, each invocation of the new operator needs to be followed by a
corresponding call to an appropriate delete operator. Of course, the
call to the delete operator must be placed in an appropriate location,
such as a class destructor. This opens the possibility that the programmer
simply forgets to delete pointers, which results in memory leaks. As an
alternative source of errors, accessing a part of the memory which is
referred to by an already deleted pointer will lead to undefined behavior.
Both issues will occur at runtime and are usually sources of errors which
are notoriously difficult to find and debug. To circumvent both problems,
direct handling of raw pointers is to be avoided. In C++, handling of raw
pointers has been replaced by an idiom called Resource Acquisition Is
Initialization (RAII).
The RAII idiom states that raw pointers need to be encapsulated into a
class, whose destructor takes care of deleting the pointer and releasing
1
Automatic garbage collection referrs to the deletion of heap-allocated objects.
188
Often encountered classes
the resource at the appropriate place in the code. Adapting raw pointers
in such classes, and providing different functionalities to the adapted raw
pointers has lead to the development of so-called smart pointers. Differ-
ent smart pointers exist and provide different functionalities. OpenFOAM
implements two of such smart pointers: autoPtr and tmp.
Provided that the environmental variables are set by sourcing the etc/
bashrc configuration script of the example code repository, the example
application code is made available in the folder $PRIMER_EXAMPLES_SR-
C/applications/test/. Those readers that are interested in following
the tutorials presented in the next sections step-by-step, need to create
a new executable application - testSmartPointers. Creating new ap-
plications in OpenFOAM is simplified, since scripts are available that
generate skeleton directories for applications. To create a new applica-
tion, a directory is chosen where the application code will be placed, and
the following commands are executed:
mkdir testSmartPointers
cd testSmartPointers
foamNew source App testSmartPointers
sed -i 's/FOAM_APPBIN/FOAM_USER_APPBIN/g' Make/options
The final line replaces the placement directory for the application bi-
nary file from the platform directory, to the user application binaries
directory.
INFO
It is a good practice to build your own applications into the
$FOAM_USER_APPBIN, which needs to be specified in the Make/op-
tions build configuration file.
INFO
In this section, the gcc compiler is used. Be aware of this when reading
about specific compiler flags in the text.
189
OpenFOAM design overview
it inherits from the basic field class template Field<Type>, and is named
infoField. Any other class could be used, since the optimizations per-
formed by the compiler for copy operations do not depend on the size of
the objects.
To start, the infoField class template can be defined as shown below:
template<typename Type>
class infoField
:
public Field<Type>
{
public:
infoField()
:
Field<Type>()
{
Info << "empty constructor" << endl;
}
~infoField()
{
Info << "destructor" << endl;
}
The class template inherits from Field<Type> and uses the following:
• empty constructor
• copy constructor
190
Often encountered classes
• destructor
• assignment operator
Each time those functions are used, an Info statement signals the par-
ticular call to the standard output stream. This is solely done for the
purpose of obtaining information on which function has been executed.
return temp;
}
The name of the type used in the example is shortened in order to reduce
the amount of unnecessary typing:
// Shorten the type name.
typedef infoField<scalar> infoScalarField;
Compiling and executing the application with either Debug or Opt options
will produce exactly the same results, even though the Debug option turns
of compiler optimizations. As mentioned at the section beginning: the
compiler is very clever at recognizing the fact that a temporary object is
returned only to be discarded away after assignment. Another advantage
of not using the geometrical fields is that this small example application
does not need to be executed within an OpenFOAM simulation case
directory. It can be called directly from the directory where the code is
stored. Executing the application results in the following output:
191
OpenFOAM design overview
?> testSmartPointers
EXE_LIBS = \
-lfiniteVolume
INFO
Remember to call wclean before executing wmake. Modifications to the
Make/options file are not recognized by the wmake build system as a
source code modification that requires re-compilation.
Compiling and running the application with the options file as defined
above results in the following output2 :
?> testSmartPointers
Value construction : size, value constructor
Empty construction : empty constructor
Function call
2
Please note that the comments, starting with a # are added by the authors
192
Often encountered classes
The output shows the unnecessary creation and deletion of the temporary
object. Eliding copies of temporary objects by performing an in-place
construction of an object at the point where the function returns has
become a standard option for compilers. It happens regularly that this
feature cannot be disabled even when surpressing optimizations in Debug
mode with the compiler flags:
-O0 -DFULLDEBUG
TIP
The above example serves one major purpose: To show that it is not
necessary to use the autoPtr smart pointer in expressions where a
named temporary object is returned anyway. On modern compilers,
even when compiling in the debug mode (for OpenFOAM this means
setting $WM_COMPILE_OPTION to Debug), this optimization is turned on
by default.
Some of the most prominent examples for the usage of the autoPtr are
within models employing RTS, such as turbulenceModel and fvOp-
tions. The particular base-classes are instantiated based on user speci-
fied keywords in a dictionary. Using the turbulenceModel-class as an
example, the base class is used as template argument for autoPtr in
e.g. the solver application and the RTS can then instantiate the specific
turbulence model into the autoPtr. RTS allows class users to instantiate
objects of a specific class in a class hierarchy at runtime. Instantiating
objects in that way enables the ability of C++ to access the derived class
object via a base class pointer or reference. This is usually referred to as
dynamic polymorphism.
The turbulence models are typically instantiated in the particular solver’s
createFields.H, which is included before the beginning of the time
193
OpenFOAM design overview
INFO
It is impossible to cover all details of the C++ language standard used
by OpenFOAM in this book. Whenever a C++ construct is encountered
that does not sound familiar, it should be looked up elsewhere.
At the end of the solver code, the following lines would be required:
delete turbulence;
turbulence = NULL;
Of course, for a single pointer, adding the lines to release resources might
not be a problem. However, the RTS is used for: transport models, bound-
ary conditions, fvOptions, discretization schemes, interpolation schemes,
gradient schemes and so forth. All of those objects are small compared to
things like fields and the mesh, so could they be returned by value? From
the standpoint of efficiency - yes, especially considering the Return Value
Optimization (RVO) is implemented by all modern compilers. From the
standpoint of flexibility - there is no chance of doing that, since the dy-
namic polymorphism relies on access via pointers or references. Keeping
the runtime flexibility high, means relying on pointers or references.
TIP
Using autoPtr or tmp is necessary when RTS is used to select objects
at runtime.
194
Often encountered classes
The following example examines the autoPtr interface and its ownership-
based copy semantics. The autoPtr owns the object it points to. This
is expected, as RAII requires the smart pointer to handle the resource
release so the programmer doesn’t have to. As a consequence, creat-
ing copies of autoPtr is complicated - copying an autoPtr invalidates
the original autoPtr and transfers the object ownership to the copy. To
see how this works, consider the main function of the following code
snippet:
int main()
{
// Construct the infoField pointer
autoPtr<infoScalarField> ifPtr (new infoScalarField(1e06, 0));
return 0;
}
?> testSmartPointers
size, value constructor
1000000{0}
1000000{0}
destructor
195
OpenFOAM design overview
TIP
The definition of the tmp smart pointer can be found in $FOAM_SR-
C/OpenFOAM/memory/tmp.
There is a catch when using the tmp smart pointer: the pointer class
template is not made responsible for counting the references. Reference
counting is expected from the wrapped type. This can be easily checked
when examining the class destructor:
template<class T>
inline Foam::tmp<T>::~tmp()
{
if (isTmp_ && ptr_)
{
if (ptr_->okToDelete())
{
delete ptr_;
ptr_ = 0;
}
else
{
ptr_->operator--();
}
}
}
196
Often encountered classes
The ptr_ attribute is the wrapped raw pointer to an object of type T and
the destructor attempts to access two member functions:
• T::okToDelete()
• T::operator--()
As a result, the wrapped object must adhere to a specific class interface.
Another way to test this catch is to try and use the tmp with a trivial
class that can be define as follows:
class testClass {};
By means of this error, the compiler complains about the fact that the
aforementioned member functions are not implemented by testClass.
Since OpenFOAM makes the objects perform the reference counting, it
has been encapsulated into a class that such objects inherit from, named
refCount. The refCount class implements the reference counter and
the related member functions.
TIP
In order to use tmp<class T> in OpenFOAM, the type T of the
wrapped object should inherit from refCount.
It can then be wrapped with tmp. To see how reference counting works
and how unnecessary construction of objects is avoided, the tmp can
be used with the class infoScalarField, that was used in the exam-
ples describing autoPtr. The refCount class allows the user to get
information on the current reference count with the member function
refCount::count(), which is used in the following example. Artificial
scopes are used to decrease the life-span of tmp objects so that their
197
OpenFOAM design overview
destructors are called. This would occur in a regular program code when
nested function calls or loops are present. Here is the example code:
tmp<infoScalarField> t1(new infoScalarField(1e06, 0));
Info << "reference count = " << t1->count() << endl;
{
tmp<infoScalarField> t2 (t1);
Info << "reference count = " << t1->count() << endl;
{
tmp<infoScalarField> t3(t2);
Info << "reference count = " << t1->count() << endl;
} // t3 destructor called
198
Often encountered classes
similar other fields are then generated in the form of concrete classes by
instantiating the GeometricField class template with specific template
arguments. As noted in the section on the dimensioned types, the type
names used for fields in OpenFOAM are shortened for convenience using
the typedef keyword.
INFO
Compilation errors that involve fields such as volScalarField cause
C++ template errors, which are quite long and not intuitive to read.
Because the fields are instantiations of the GeometricField template,
the GeometricField class template source code can investigated to
get more information about the error.
199
OpenFOAM design overview
surfaceScalarField phi
(
IOobject
(
"phi",
runTime.timeName(),
mesh,
IOobject::READ_IF_PRESENT,
IOobject::AUTO_WRITE
),
linearInterpolate(rho*U) & mesh.Sf()
);
Here the phi field is constructed from the field data itself if the file is
present. Otherwise, the flux is calculated from the velocity field directly.
This shows the difference between the construction of the T field and
the phi field; both of which take an IOobject as a first argument
but the construction of the T field takes a polyMesh as a second ar-
gument, whereas phi takes a surfaceScalarField. If you have a
closer look at $FOAM_SRC/OpenFOAM/fields/GeometricFields/Geo-
metricField/GeometricField.H, you will discover that there is a huge
variety of overloaded constructors for the GeometricField class, which
is the base-class, of all fields in OpenFOAM. You can basically use all
of them, depending on the particular needs.
200
Often encountered classes
INFO
Using loops for fields in the application-level program code reduces
code readability and may cause a significant drop in computational
efficiency.
labelList cellIDs(3);
cellIDs[0] = 1;
cellIDs[1] = 42;
cellIDs[2] = 39220;
forAll(cellIDs, cI)
{
Info<< U[cellIDs[cI]] << tab << p[cellIDs[cI]] << endl;
}
As covered in chapter 1, the internal field values are separated from the
boundary field values. Such logical separation of cell centered and bound-
ary (face-centered) values is defined by the principles of the numerical
interpolations that support the FVM.
Separating boundary field values from internal field values has an impor-
tant impact onto the way the algorithms are parallelized in OpenFOAM.
Numerical operations are paralellized in OpenFOAM using data paral-
lelism where the domain is decomposed into sub-domains and the nu-
merical operations are executed on each separate sub-domain. As a result,
a number of parallel processes are executed which require communication
201
OpenFOAM design overview
Further reading
[1] J. H. Ferziger and M. Perić. Computational Methods for Fluid
Dynamics. 3rd rev. ed. Berlin: Springer, 2002.
202
Often encountered classes
203
6
Productive programming
with OpenFOAM
Any software project of a significant size demands some level of or-
ganization. From the directory organization to the Version Control Sys-
tem (VCS), there are many aspects of code development that call for
standard practices.
In this chapter, best practices are covered that increase productivity when
programming OpenFOAM and using OpenFOAM on an HPC cluster.
Productive programming with OpenFOAM
The application code on the other hand is using the library code to as-
semble a higher level functionality. A flow solver of OpenFOAM is a
good example of this, as it combines separate libraries to handle concep-
tually distinct tasks such as disk I/O, mesh handling, discretisation, etc.
The author of the solver does not have to take care of the logic behind
the respective libraries but can concentrate on developing the solver.
During development, the programmer has two options for code orga-
nization: programming within the OpenFOAM directory structure, or
programming within a separate directory structure. Programming within
the OpenFOAM structure makes sense at first glance as it is logi-
cal to keep similar code in close proximity within the directory trees.
This ’main structure’ approach to development can, unfortunately, quickly
cause problems when collaborating with others using a Version Control
206
Code organization
System (VCS). Version control systems are absolutely essential for col-
laborative programming. Even if if a developer is working alone, VCS
significantly speeds up development and safety of the development pro-
cess, because it enables straightforward investigation of alternative ideas.
Even if a version control system is used properly, sharing custom code
located within the main OpenFOAM repository with others might be
problematic for the following reasons:
• there is not a clear overview of the files that belong to the project,
• tutorials and test cases are placed in a separate directory structure,
• handling additional dependencies that are not distributed alongside
OpenFOAM might cause problems with change integration,
• collaborating with others requires having access to a full-fledged
OpenFOAM release.
The last point makes collaborative work difficult, as creating a clone of
the entire release repository makes it necessary for anyone to clone the
entire OpenFOAM platform in order to collaborate on a usually signif-
icantly smaller new project. There are additionally situations when the
projects are not shared with the general public: they are developed by
research departments in companies, or the functionality is not yet ma-
ture enough for a release. In such cases when the project is not to be
integrated with an OpenFOAM release from the start, bundling a library
and application code in a single separate repository, that can be compiled
directly, makes individual and collaborative development much easier. At
a latter point, when the project proved to be of higher quality, integration
into one of the OpenFOAM release projects can be performed. Nowadays
there are multiple VCS hosting services available. These services provide
advanced web interfaces allowing the user to use bug tracking, hosting
a wiki for each project, and other tools that make working on such a
project much easier. Two of the most popular ones are gitlab, github
and bitbucket.
207
Productive programming with OpenFOAM
primer-examples
applications
solvers
test
utilities
postProcessing
preProcessing
doxyfile
etc
doc
src
README
Allwmake
Allwclean
208
Code organization
The bash configuration script sets the path variable named $PRIMER_EX-
AMPLES, which is the path variable to the main folder of the code
repository. The PATH variable needs to be extended, since the exam-
ple code repository contains scripts in OpenFOAM, that are located in
src/scripts. Otherwise those scripts cannot be invoked from anywhere
in the filesystem. The structure and configuration outlined here can be
reused in another repository by using different variable for the project’s
path. The applications and libraries of the code repository rely on the
variable $PRIMER_EXAMPLES to find the directories that hold the header
files that are to be included before compilation.
The compilation scripts, Allwmake and Allwclean in the root direc-
tory are used to respectively compile and clean the project binaries.
Additionally, similar scripts are placed in the src and applications
sub-directories, making it possible to compile of only libraries or only
applications. An example content of the Allwmake script for building
libraries within the src directory is shown in in the script below:
#!/bin/sh
cd ${0%/*} || exit 1 # run from this directory
209
Productive programming with OpenFOAM
applicationName
applicationName.C
Make
files
options
wmakeLnInclude .
wmake exampleLibrary
EXE = $(FOAM_USER_APPBIN)/applicationName
The Make/files lists the *.C files that are to be compiled and the name
and location of the binary file that will contain the compiled code. The
application installation target directory is set to $FOAM_USER_APPBIN, in
order not to pollute the primary OpenFOAM system application direc-
tory $FOAM_APPBIN with custom applications. Using $FOAM_USER_APP-
210
Code organization
EXE_INC = \
-I$(LIB_SRC)/finiteVolume/lnInclude \
-I$(LIB_SRC)/meshTools/lnInclude \
-I$(PRIMER_EXAMPLES_SRC)/lnInclude
EXE_LIBS = \
-L$(FOAM_USER_LIBBIN) \
-lfiniteVolume \
-lmeshTools \
-lexampleLibrary
INFO
The key step in having a custom project directory structure is preparing
the bashrc configuration script. Relying on the variables set by that
script to locate header files and libraries of the custom project separates
the custom project from the OpenFOAM platform.
211
Productive programming with OpenFOAM
INFO
Learn how to use a debugger: if OpenFOAM is running in parallel,
there is no prescribed order of execution of the code in different MPI
processes, which makes Info statements and their parallel counterparts
Pout or Perr useless for debugging.
Compiler flags used for OpenFOAM are bundled up into groups of op-
tions which can be interchanged depending on the target configuration.
212
Debugging and profiling
INFO
Debugging with gdb is quite straightforward and there is plenty of
information on working with gdb available on the official website www.
gnu.org/software/gdb/.
Debugging the code can sometimes be simple for bugs such as a seg-
mentation fault (accessing the wrong memory address) or a floating point
exception (dividing by zero). Those errors in the execution of the code
trigger system signals, such as SIGFPE (floating point exception signal)
and SIGSEV (segmentation violation signal) and are trapped by the de-
bugger. The debugger then allows the user to browse through the code
in order to find the error, set break points, examine variable values and
much more.
In order to show gdb in action, an example for debugging with gdb is
covered in this section. This tutorial is provided in the sample code repos-
itory in the ofprimer/applications/test/testDebugging directory.
The tutorial consists of a testing application that contains a function tem-
plate used for computing a harmonic mean of a given field, over all time
steps of the simulation. Because the harmonic mean involves comput-
ing x1 , where x is the field value, a floating point exception (SIGFPE)
appears if the code is executed on a field which contains zero values.
The function template is defined in the fvc:: namespace and it behaves
similar to the rest of the OpenFOAM operations. It does, however, have
a slightly reduced functionality. For this reason, the function template
213
Productive programming with OpenFOAM
The tutorial simulation case that can be used with this example is the
case ofbook-cases/chapter4/rising-bubble-2D. Calling the test-
Debugging application with the -field alpha.water argument within
the risingBubble-2D case directory results with the following error:
?> testDebugging -field alpha1
(snipped header output)
Time = 0
#0 Foam::error::printStack(Foam::Ostream&) at ??:?
#1 Foam::sigFpe::sigHandler(int) at ??:?
#2 ? in /usr/lib/libpthread.so.0
#3 ? in $FOAM_USER_APPBIN/testDebugging
#4 ? in $FOAM_USER_APPBIN/testDebugging
#5 __libc_start_main in /usr/lib/libc.so.6
#6 ? in $FOAM_USER_APPBIN/testDebugging
Floating point exception (core dumped)
This leads to the console version of gdb, which which is used to execute
any programs which require debugging:
(gdb)
After the execution of the above command, the SIGFPE error appears
again, but with much more details:
214
Debugging and profiling
215
Productive programming with OpenFOAM
Choosing frame 0 and listing the source code with the list command,
narrows down the location of the error:
(gdb) frame 0
(gdb) list
75 volumeField& resultField = resultTmp();
76
77 forAll (resultField, I)
78 {
79 // SIGFPE.
80 resultField[I] = (1. / resultField[I]);
Hence the culprit for the SIGFPE signal is line 80. To put a break point
at line 80, the break command must be executed and testDebugging
must be re-run:
(gdb) break 80
Breakpoint 1 at 0x4146cd: file testDebugging.C, line 80.
(gdb) run
The program being debugged has been started already.
Breakpoint 1, Foam::fvc::harmonicMean<double> (inputField=...) at
testDebugging.C:80
80 resultField[I] = (1. / resultField[I]);
After having placed the break point in line 80, the variable I can be
evaluated at this position:
(gdb) print I
$1 = 0
Breakpoint 1,
Foam::fvc::harmonicMean<double> (inputField=...)
at testDebugging.C:80
80resultField[I] = (1. / resultField[I]);
(gdb) c
Continuing.
216
Debugging and profiling
$2 = 1
(gdb)
For this simple example, it is obvious that at least two values of I lead to
0 for resultField. As, by definition, the field resultField must never
be 0, the next step is to investigate if the field has been preprocessed
properly.
INFO
The solution to problem is already included in the sources for this
example - uncommenting the marked lines allow the application to run
correctly.
6.2.2 Profiling
217
Productive programming with OpenFOAM
218
Debugging and profiling
219
Productive programming with OpenFOAM
Git is a distributed VCS which is very popular in the open source commu-
nity. There are many benefits of using git: creating new versions is very
straightforward which simplifies trying out new ideas, handling conflicts
in files edited by multiple contributors is simplified by git web services
(GitLab, GitHub, Bitbucket), each contributor holds a complete copy of
the project and contributors can work without access to the internet be-
cause an open connection to a central repository is not required while
working, etc. There is ample of information on git available online1 . A
reasonable understanding of git is assumed for the remaining chapter. For
working with OpenFOAM, it is sufficient to learn how to: clone a remote
repository, create delete and merge versions (branches) locally and on the
remote repository, push and pull from a remote repository, and create git
tags (snapshots) of your project. A great branching model can be found
on envie.com2 . In the following we outline some use cases involving
git and OpenFOAM.
INFO
Although not covered here, learning the basic usage of the git version
control system is necessary for OpenFOAM development.
1
http://git-scm.com/book
2
http://nvie.com/posts/a-successful-git-branching-model
220
Using git to track an OpenFOAM project
221
Productive programming with OpenFOAM
remotes/origin/feature-liquidFilm
...
Should you decide to work directly within the main OpenFOAM structure,
create an account on develop.openfoam.com, request membership to
the project from one of the project maintainers and fork OpenFOAM.
Once your feature is thoroughly tested, you can submit a merge request,
so that your code is integrated into OpenFOAM.
The most common way of using git for custom developments is to track
each of your projects in a seperate repository that is unrelated to the
main release. This simplifies the sharing of your code as it can be shared
without the full OpenFOAM release. Assuming three projects should be
put under version control: projectA, projectB and projectC. Each
of these projects’ directories has to be entered individually and a git
repository has to be initialized in each of them:
?> cd projectA
?> git init
222
Installing OpenFOAM on an HPC cluster
and any upstream changes to master should get merged into the local
development branch, in order to stay up to date. The deployment itself
can get automated further using git hooks. Migrating developments from
one major release to the next may be more difficult in this configuration
than with seperate and independent repositories, however.
As git can track any text files, it is not only limited to source code but can
also deal with OpenFOAM cases. Some files and directories, such as the
time step and processor directories, should not be tracked by git and must
hence be excluded by an appropriate .gitignore file. This selection is
already done by the .gitignore in the ofbook-cases project, which
ignores the mesh and a lot of other files and directories that are not
directly related to the case setup. A good application for tracking a case
is when the influence of various parameters on the simulation is to be
investigated. This renders copying the same case many times obsolete.
Snapshots of versions of simulation cases used in a publication or a
technical report can be created using git tags.
INFO
Contact the system administrator of the HPC cluster to make sure you
build OpenFOAM properly.
223
Productive programming with OpenFOAM
# [WM_COMPILER] - Compiler:
# = Gcc | Gcc4[8-9] | Gcc5[1-5] | Gcc6[1-5] | Gcc7[1-4] | Gcc8[12] |
# Clang | Clang3[7-9] | Clang[4-6]0 | Icc | Cray | Arm | Pgi
export WM_COMPILER=Gcc
224
Installing OpenFOAM on an HPC cluster
HPMPI)
export FOAM_MPI=hpmpi
export MPI_HOME=/opt/hpmpi
export MPI_ARCH_PATH=$MPI_HOME
_foamAddPath $MPI_ARCH_PATH/bin
225
Productive programming with OpenFOAM
PFLAGS = -DOMPI_SKIP_MPICXX
PINC = -I$(MPI_ARCH_PATH)/include
PLIBS = -L$(MPI_ARCH_PATH)/lib$(WM_COMPILER_LIB_ARCH)
-L$(MPI_ARCH_PATH)/lib -lmpi
226
Installing OpenFOAM on an HPC cluster
Further reading
[1] Nicolai M. Josuttis. The C++ standard library: a tutorial and
reference. Boston, MA, USA: Addison-Wesley Longman Publishing
Co., Inc., 1999.
[2] Why is std::vector::insert complexity linear (instead of being con-
stant)? url: http://stackoverflow.com/questions/25218880/
why-is-stdvectorinsert-complexity-linear-instead-of-
being-constant (visited on 03/2016).
227
7
Turbulence modeling
This chapter covers the modeling of turbulence in OpenFOAM. The de-
tails on the physical and mathematical modeling are kept to a minimum.
The reason for this decision lies in the fact that tubulence modeling is a
large topic in itself, one beyond the scope of this book. For more in-depth
discussions of turbulence modelling, the reader is referred to respective
literature, such as Pope [11], Pozrikidis [12], Wilcox [16], and Lesieur
[8].
7.1 Introduction
TIP
The Reynolds stresses can be regarded as “averaged momentum flow
per unit area, and so comparable to a shear stress” (see [1]) and they
introduce additional unknowns to the flow equations. This leads to a
closure problem, due to the nonlinearity of the Navier-Stokes equations,
when they are averaged, which leads to the Reynolds equations (more
details in [8]). To be able to solve this problem, more information
must be provided. In this specific case, the Reynolds stresses must be
modeled somehow, which is the point where the turbulence models
come into play (see Pope [11]).
RANS
complexity
increasing
DES
LES
DNS
The next group of models is called large eddy simulation (Large Eddy
Simulation (LES)) and differ from RANS models therein that only small
scale eddies are modeled. Large scale eddies are resolved spatially by
the computational grid, which needs to be significantly finer to function
correctly compared to RANS models. In terms of computational costs
and efficiency, LES lies between RANS and Direct Numerical Simulation
(DNS), as shown in figure 7.1. Pope [11] states that “LES can be expected
to be more accurate and reliable than Reynolds-stress models, for flows
230
Introduction
yuτ
y+ = (7.1)
ν
With y representing the absolute distance from the wall and uτ and ν
denote the friction velocity and kinematic viscosity, respectively.
Pope [11] provides great insight into wall functions and why they are so
important. The turbulence model need to account for the steep velocity
profile at the wall and in relatively close proximity to the wall. This
231
Turbulence modeling
is where wall functions come into play, which were first suggested by
Launder and Spalding [7]. The idea is that additional boundary conditions
are applied at some distance to the wall, to fulfill the log-law. Hence the
additional equations introduced by the turbulence model are not solved
close to the wall. Depending on the particular turbulence model used,
different wall functions must be applied to the respective fields of the
turbulence model. Meaning that a k − model requires different wall
functions than a k − ω model.
WARNING
Especially when the flow suffers severe flow separation, RANS models
are often unable to capture this separation properly. Hence such flow
problems must be handled with care, if RANS models are used (see
Pope [11], Wilcox [15], and Ferziger and Perić [3]).
232
Pre- and post-processing and boundary conditions
3
k = (|Uin | I)2 (7.2)
2
k µt −1
ω = ρ (7.3)
µ µ
√
k
ω = 1 (7.4)
Cµ4 L
233
Turbulence modeling
For other fields introduced by other turbulence models, there are equiva-
lent boundary conditions contained in the OpenFOAM framework.
7.2.1 Pre-processing
This tool provides two custom commandline parameters, Cbl. Cbl calcu-
lates the boundary layer thickness as the product of its argument and the
mean distance to the wall. Optionally the turbulent viscosity field νt can
be stored to disk, which is not required to be present by the turbulence
models.
234
Pre- and post-processing and boundary conditions
7.2.2 Post-processing
Reading field U
As can be seen from the above listing, not only the minimum, maximum
and average y + values for the patches of type wall are printed to the
screen.
235
Turbulence modeling
Further reading
[1] H.D. Baehr and K. Stephan. Heat and Mass Transfer. Springer,
2011.
[2] Lawrence J. De Chant. “The venerable 1/7th power law turbu-
lent velocity profile: a classical nonlinear boundary value problem
solution and its relationship to stochastic processes”. In: Applied
Mathematics and Computation 161.2 (2005), pp. 463–474.
[3] J. H. Ferziger and M. Perić. Computational Methods for Fluid
Dynamics. 3rd rev. ed. Berlin: Springer, 2002.
[4] Fluent. Fluent 6.2 User Guide. Fluent Inc. Centerra Resource Park,
10 Cavendish Court, Lebanon, NH 03766, USA, 2005.
[5] W.P Jones and B.E Launder. “The prediction of laminarization with
a two-equation model of turbulence”. In: International Journal of
Heat and Mass Transfer 15.2 (1972), pp. 301–314.
[6] B.E. Launder and B.I. Sharma. “Application of the energy-dissipation
model of turbulence to the calculation of flow near a spinning
disc”. In: Letters in Heat and Mass Transfer 1.2 (1974), pp. 131–
137.
[7] B.E. Launder and D.B. Spalding. Mathematical Models of Turbu-
lence. Academic Press, 1972.
[8] M. Lesieur. Turbulence in Fluids. Fluid Mechanics and Its Appli-
cations. Springer, 2008.
[9] F. R. Menter. “Zonal two-equation k − ω turbulence models for
aerodynamic flows”. In: AIAA Journal (1993), p. 2906.
236
Class design
237
8
Pre- and post-processing
applications
There are many ways to pre- and post-processing simulations OpenFOAM,
programming new solver applications or numerical algorithms may require
the user to develop new pre- and post-processing applications
Before considering the development of a new pre- or post-processing
application, one should make sure the required algorithm is not available
in OpenFOAM.
For example, a user-friendly pre-processing application funkySetFields
was developed by Bernhard Gschaider as a part of the swak4foamproject.
This application can be used to initialize OpenFOAM fields using alge-
braic expressions. The expression parser in funkySetFields extracts
arithmetic and differential operations from user-defined expressions and
evaluates them using OpenFOAM numerical libraries. If a required func-
tionality is not available in OpenFOAM, any of its sub-modules, or related
projects, it is likely simpler to extend an existing application or library
with new features than to program and test the new application from
scratch.
In this case, a good practice is to find an existing application with similar
functionality and modify it to fit the required task. As covered in chapter
6, the Doxygen generated HTML documentation can help identify and
locate those parts of the OpenFOAM framework that can be used to build
the new application.
Pre- and post-processing applications
A set of Linux shell scripts are available that help with creating ”skele-
ton” source code files: application, class, class templates, and build files
(used by the wmake build system). When creating a simple pre- or post-
processing application, only a single source code file is required (e.g.
myApp.C). Following the OpenFOAM naming convention, it is stored in
the directory named in the same way as the application.
?> wmake
The Make/options file holds the list of directories where the declarations
are stored, and possibly definitions in cases when class/function templates
are used. The directories that are to be searched by the wmake build
system are set with the -I option:
-I$(LIB_SRC)/finiteVolume/lnInclude
240
Custom pre-processing applications
In cases when the -I option does not use a relative path to a folder,
environmental variables are used, such as $(LIB_SRC) in the example
above. Additionally, a list of directories that hold precompiled dynamic
libraries that are linked with the application is defined. The directories
that contain the libraries are appended to the build options with the option
-L:
-L$(LIBRARY_VARIABLE)/lib
241
Pre- and post-processing applications
source $WM_PROJECT_DIR/bin/tools/RunFunctions
application=`getApplication`
242
Custom pre-processing applications
The function calls the grep program to filter for the line in system/de-
composeParDict that contains numberOfSubdomains. In this line the
’numberOfSubdomains’ string is deleted, any whitespace between this
keyword and the actual numerical value is removed, and the tailing semi-
colon is deleted. The result of this chained command is stored in the
bash variable n. To return it, echo is used.
Running any OpenFOAM solver or utility in parallel is done using the
mpirun command. For this example, this means that two variables need
to be passed over to mpirun as option arguments: the solver name, and
the number of processors. The completed Allparrun script is shown in
the listing 10.
For the most simple case, where a job queuing and submission system
is not available, you’ll only have to provide a machine file to mpirun.
The machine file contains either the IP addresses or the host names of
the computing nodes on the HPC cluster network. Refer to the respective
243
Pre- and post-processing applications
source $WM_PROJECT_DIR/bin/tools/RunFunctions
function nProcs
{
n=`grep "numberOfSubdomains" system/decomposeParDict \
| sed "s/numberOfSubdomains\s//g" \
| sed "s/;//g"`
echo $n
}
PWD=`pwd`
runApplication decomposePar
# Jump back
cd $PWD
done
INFO
Keep in mind that in this case the call to mpirun is starting all jobs
as local jobs on the machine. In order to initiate it on an HPC cluster
and use its compute nodes, other steps are necessary that depend on
the specific cluster configuration and are therefore omitted here.
244
Custom pre-processing applications
The PyFoam project consists of different Python modules that can ma-
nipulate OpenFOAM data. In addition to this library part, PyFoam al-
ready offers many different ready-to-use applications that simplify differ-
ent tasks.
245
Pre- and post-processing applications
INFO
PyFoam can be installed using the pip package installer for Python
with sudo pip install PyFoam , or without root privileges, with pip
install --user PyFoam.
WARNING
It is essential that the template simulation case is set up properly, as
all parametrized cases are created from it.
246
Custom pre-processing applications
INFO
The script create_naca_study.py can be found in the ofprimer/
cases/chapter04 folder alongside the naca template simulation case.
247
Pre- and post-processing applications
import argparse
import sys
from subprocess import call
parser = argparse.ArgumentParser(description='Runs \
pyFoamRunParameterVariation.py to create simulation folders \
without generating the mesh.')
parser.add_argument('--study_name', dest="study_name", \
type=str, required=True, \
help='Name of the parameter study.')
parser.add_argument('--parameter_file', dest="parameter_file", \
type=str, required=True,
help='PyFoam .parameter file')
parser.add_argument('--template_case', dest="template_case", \
type=str, required=True,
help='Parameter study template case.')
args = parser.parse_args()
if __name__ == '__main__':
args = parser.parse_args(sys.argv[1:])
call_args = ["pyFoamRunParameterVariation.py",
"--every-variant-one-case-execution",
"--create-database",
"--no-mesh-create",
"--no-server-process",
"--no-execute-solver",
"--no-case-setup",
"--cloned-case-prefix=%s" % prefix,
args.template_case,
args.parameter_file]
call(call_args)
listing 12 shows the parameters for the naca case, where only the kine-
matic viscosity is varied. The values listed in listing 12 and stored in the
naca.parameter file should be replaced somehow in the constant/
transportProperties file of the naca case. To ensure PyFoam re-
places the values of ν in constant/transportProperties, the file is
copied into constant/transportProperties.parameter, and nu is
248
Custom pre-processing applications
NU
(
1e-06 2e-06 3e-06 4e-06
);
}
transportModel Newtonian;
nu nu [0 2 -1 0 0 0 0] @!NU!@;
...
Running
?> ./create_naca_study.py --study_name my_study \
--template_case naca --parameter_file naca.parameter
generates from the naca simulation case template four OpenFOAM new
simulation cases with four viscosity values listed in listing 13,
?> ls -d my_study*
my_study_naca.parameter_00000_naca
my_study_naca.parameter_00001_naca
my_study_naca.parameter_00002_naca
my_study_naca.parameter_00003_naca
249
Pre- and post-processing applications
==============================
4 variations with 1 parameters
==============================
==================
Listing variations
==================
Before going into details about writing a custom PyFoam script, the
necessary background information is provided about the simulation. A
symmetric mesh is generated using blockMesh, that consist of three
blocks as outlined in figure 8.1. The grading of the blocks is adjusted
so that there is no noticeable change in cell sizes from block 1 to 2 and
from 2 to 3. To resolve the viscous boundary layer along the airfoil fairly
small cells are generated near the NACA profile.
All patches are named according to labeling shown in figure 8.1. The
vertex numbers are indicated by the two numbers separated by a dash.
All edges on the FOIL patch that are not oriented in the normal direction
of the figure are shaped using the polyLine of blockMesh with the
station being calculated according to the function that defines any two
digit NACA profile. The same goes for the vertices of the blue INLET
patch - but rather than using the two digit NACA polymonial, a quarter
circle is used. This makes the mesh look more visually appealing and
removes any boundary condition issues between the INLET patch and the
SIDE patch.
250
Custom pre-processing applications
1.3 m
SIDE SIDE
5/6 10/11 14/15
OUTLET
T
LE
2m
IN
1 2 3
1/2
4/7 0/3 FOIL 8/9 12/13
SYM SYM
2m c=1m 3m
INFO
The cases/chapter04/naca case is set up only for mesh generation
and parameter variation, not for obtaining physical results.
251
Pre- and post-processing applications
# Parameter variation
import numpy as np
from numpy import linspace,sin,cos,array,pi
import yaml
The modules for parsing command-line arguments and file paths and per-
forming computations in the custom parameter variation (cf. listing 15)
directly follow the PyFoam modules from listing 14. The custom param-
eter study will vary the angle of attack of the NACA0012 airfoil, which
requires a rotation of a vector around the −y coordinate axis in this exam-
ple case, implemented by functions from listing 16. The parameter vari-
ation script controls the study via command-line options. Alternatively,
one could control the parameter variation similarly as pyFoamRunParam-
eterVariation.py: using a configuration file, in a standardized format
such as CSV, JSON, or YAML. The command-line arguments are parsed
by code shown in listing 17. The main function of the parameter variation
script is shown in listing 18 and it is outlined in detail with comments.
The core idea is to read a template simulation case, then clone the case
into multiple simulation cases, identified by unique identifiers (IDs) that
correspond to a parameter variation. Each parameter variation in this
example case consists of a single scalar value: the angle of attack α.
Generally, a parameter variation is identified by a n-dimensional param-
eter vector in a parameter space, where n corresponds to the number of
parameters varied in a simulation. In this case, varying the angle of attack
requires the rotation of the velocity field around the −y coordinate axis
direction. Velocity file is read using PyFoam, and from it access is gained
252
Custom pre-processing applications
parser.add_argument('--template_case',
dest="template_case", type=str,
help='OpenFOAM template case.', required=True)
args = parser.parse_args()
to the inlet boundary condition and the internal velocity field. Both the
inlet boundary condition and the initial internal values are then set such
that the specific angle of attack is achieved. Once the velocity field is
initialized like this, its file is written. The dictionary that relates the ID
of the unique parameter variation with the parameter vector is then saved
as a YAML file. This ID - parameter vector mapping is also saved by
pyFoamRunParameterVariation.py in a PyFoam-specific format. Here,
YAML is used as a standard format that is easily manipulated in Python.
Although a single parameter is varied in this small example script, the
example shows how the main sub-modules of PyFoam can be used to-
gether with standard Python modules to extend parameter variation in
OpenFOAM to having full control over the variation. A next step would
be, for example, to reduce the full Cartesian product of parameter vectors
by applying conditions that disable parameter vectors that do not make
sense.
253
Pre- and post-processing applications
args = parser.parse_args(sys.argv[1:])
254
Custom pre-processing applications
results in
?> ls
angle-of-attack-00000001
angle-of-attack-00000002
angle-of-attack-00000003
...
angle-of-attack.yml
0:
ALPHA: 0.0
1:
ALPHA: 1.1111111111111112
2:
ALPHA: 2.2222222222222223
...
255
Pre- and post-processing applications
$FOAM_TUTORIALS/compressible/sonicFoam/RAS/nacaAirfoil
In the controlDict file of this case, the force coefficients of the airfoil
are defined by the function object entry shown in listing 19. When the
simulation starts, the forceCoeffs function object will process the fields
required to calculate force coefficients on the wall_4 boundary patch. Re-
sulting data is written in postProcessing/forces/0/coefficient.
dat, and can be analyzed as the simulation runs, preferably using a
Jupyter notebook. Using Jupyter notebooks to view and analyze active
processing results in OpenFOAM simulations, generated by OpenFOAM
function objects brings two important benefits. A Jupyter notebook can
be refreshed in a web browser as the simulation runs, making it possible
to update the data analysis and examine simulations as they run. Addi-
tionally, Jupyter notebooks can be started securely on a remote machine
(an HPC cluster) and viewed in a web browser on a PC/laptop. Function
objects that support this, can be executed after the simulation has com-
pleted, by running the postProcess application in the simulation case
folder. Information about different post-processing function objects and
256
Custom post-processing applications
patches
(
wall_4
);
rhoInf 1;
CofR (0 0 0);
liftDir (-0.239733 0.970839 0);
dragDir (0.970839 0.239733 0);
pitchAxis (0 0 1);
magUInf 618.022;
lRef 1;
Aref 1;
}
}
their usage is available in the Extended Code Guide so this section only
provides this brief overview.
257
Pre- and post-processing applications
The {αc }c∈C volume fractions are associated to the cell centers, and they
are approximated as α̃c := α̃c (x) at a point x for reconstructing the iso-
surface. The iso-surface reconstruction algorithm in OpenFOAM imple-
ments the regularized marching tetrahedra algorithm [6], which uses linear
approximation for α̃c (x) along a mesh-edge intersected by Σ(t). Inversed-
Distance Weighted (IDW) interpolation is used to compute cell-corner
volume fraction values from cell-centered volume fractions {αc }c∈C .
INFO
This chapter relies on the cases/chapter04/risingBubble2D simu-
lation case from the ofprimer repository.
folder of the code repository. The name of the library used by the
isoSurfaceBubbleCalculator is bubbleCalc and its source code is
placed in the
src/bubbleCalc
folder.
258
Custom post-processing applications
259
Pre- and post-processing applications
public:
260
Custom post-processing applications
// Output format.
word outputFormat_;
Instead of having multiple write calls in the solvers (which would look
something like object.write()) the class Foam::Time serves as the
Observer or Object Registry, and dispatches the call to write to all sub-
jects (registered objects).
In order to write the isoBubble object using the write controls available
in OpenFOAM (e.g. every 5 time steps, or every 0.01 seconds), the
isoBubble class inherits publicly from the regIOobject class. The
padWithZeros private member function of the isoBubble expands the
file names so that they are made readable as a time-sequence by the
paraView visualization application. The actual output in the specified
VTK format is then delegated from the isoBubble to the isoSurface
261
Pre- and post-processing applications
class. The files are written in an instance directory under the name given
as a sub-argument of the IOobject constructor argument.
262
Custom post-processing applications
vectors:
(8.2)
X
Ab = kSf k.
f
The bubble center is calculated as the algebraic average of all the iso-
surface mesh points:
1 X
Cb = xN , (8.3)
N
N
forAll (faces, I)
{
area += mag(faces[I].normal(points));
}
return area;
}
forAll(points, I)
{
bubbleCenter += points[I];
}
263
Pre- and post-processing applications
LIB = $(FOAM_USER_LIBBIN)/libbubbleCalc
EXE_LIBS = \
-lmeshTools \
-lfiniteVolume \
-ltriSurface \
-lsampling
The library build specifications are given in the Make folder, in files
options and files. The file files lists the files that are compiled into
the library object code The linker later uses this to bind to the function
calls declared in the included header files and called in our application
code. This file also specifies where the compiled library will be installed,
in this case, and as recommended for user-defined libraries, the library is
installed in the folder $FOAM_USER_LIBBIN to avoid polluting the library
install directory of the OpenFOAM system.
264
Custom post-processing applications
The options file lists all the necessary paths to directories that hold the
included header files used in isoBubble, as well as paths to the direc-
tories where the library code is installed. The pre-compiled library code
allows us to extend existing code without always having to re-compile
everything. The library is compiled with the OpenFOAM build system
wmake, by issuing the command wmake libso within the bubbleCalc
directory.
With the compiled library, the isoBubbble class can be used in the
example post-processing utility isoSurfaceBubbleCalculator.
INFO
The example post-processing utility isoSurfaceBubbleCalculator
is avalable in the code repository, in the applications/utilities/
postProcessing folder.
First, the declaration of the isoBubble class must be included for the
library to be used in an application. Additionally, to process existing
simulation results, the time step selector is used. The corresponding code
snippet is shown in listing 27. Next, a command line option is assinged to
Inheriting from the regIOobject forces the client code to initialize the
isoBubble object with an IOobject. The parameters of the IOobject
constructor are defined as follows:
• "bubble" - name of the object (in our case, the name of the file
we are using to save the iso-surface data),
• "bubble" - instance, or the directory where the object is stored
(in this example same as the name of the file),
265
Pre- and post-processing applications
#include "setRootCase.H"
#include "createTime.H"
#include "createMesh.H"
if (!args.found("isoField"))
{
FatalErrorInFunction
<< "Provide the name of the iso-surface field."
<< "Use '-help' for more information."
<< abort(FatalError);
}
The timeSelector class is then used to find the time step (iteration)
directories among the files and folders in the simulation case directo-
ries. Within each iteration, the iso-field is read, the isoBubble object
is reconstructed, the area and the center of the bubble are computed,
and the bubble iso-surface mesh is written to the disk. In this sense, the
isoBubble class is behaving in the same manner of any other Open-
FOAM class.
Running the simulation by calling the Allrun script initializes the bubble,
runs the interIsoFoam solver and calls
to calculate the bubble iso surface. The bubble surface is stored as a series
of ”vtk” files in the bubble directory. The physical time, the bubble area
and the coordinates of the bubble centroid are stored in bubble.csv.
266
Custom post-processing applications
OFstream bubbleFile("bubble.csv");
bubbleFile << "TIME,AREA,CENTER_X,CENTER_Y,CENTER_Z\n";
forAll(timeDirs, timeI)
{
runTime.setTime(timeDirs[timeI], timeI);
volScalarField isoField
(
IOobject
(
args.get<word>("isoField"),
runTime.timeName(),
mesh,
IOobject::MUST_READ,
IOobject::NO_WRITE
),
mesh
);
isoBubble bubble
(
IOobject
(
"bubble",
"bubble",
runTime,
IOobject::NO_READ,
IOobject::AUTO_WRITE
),
isoField
);
bubble.write();
}
267
Pre- and post-processing applications
268
Custom post-processing applications
Listing 30 Python pandas code for processing the iso-surface bubble data.
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams["figure.dpi"] = 200
rcParams["text.usetex"] = True
bubble_csv = pd.read_csv("bubble.csv")
plt.plot(bubble_csv["TIME"], bubble_csv["AREA"])
plt.xlabel("Time in $s$")
plt.ylabel("Area in $m^2$")
plt.savefig("iso-bubble-area.png")
plt.plot(bubble_csv["TIME"], bubble_csv["CENTER_Y"])
plt.ylabel("Y-coordinate of the bubble center")
plt.xlabel("Time in $s$")
plt.savefig("iso-bubble-center-y.png")
269
Pre- and post-processing applications
270
Custom post-processing applications
EXERCISE
What happens if the rising velocity is computed with a more accurate
time finite differencing scheme? Extend the isoBubble to compute the
bubble velocity as the average velocity evaluated at centers of trian-
gles of the bubble iso-surface? Hint: use interpolationCellPoint
to interpolate velocities at triangle centers.
EXERCISE
Write another post-processing application that computes the bubble ve-
locity using only the alpha.air and U fields. What are the differences
in the calculated velocity, and where do these differences originate
from?
Further reading
[1] Erich Gamma et al. Design patterns: elements of reusable object-
oriented software. Addison-Wesley Longman Publishing Co., Inc.,
1995. isbn: 0-201-63361-2.
[2] Tomislav Marić, Douglas B Kothe, and Dieter Bothe. “Unstruc-
tured un-split geometrical Volume-of-Fluid methods–A review”. In:
Journal of Computational Physics 420 (2020), p. 109695.
[3] William J Rider and Douglas B Kothe. “Reconstructing volume
tracking”. In: Journal of computational physics 141.2 (1998), pp. 112–
152.
[4] Johan Roenby, Henrik Bredmose, and Hrvoje Jasak. “A Compu-
tational Method for Sharp Interface Advection”. In: R. Soc. Open
Sci. 3.11 (2016). doi: 10 . 1098 / rsos . 160405. url: http : / /
arxiv.org/abs/1601.05392.
[5] Henning Scheufler and Johan Roenby. “Accurate and efficient sur-
face reconstruction from volume fraction data on general meshes”.
In: J. Comput. Phys. 383 (Apr. 2019), pp. 1–23. issn: 10902716.
doi: 10.1016/j.jcp.2019.01.009. arXiv: 1801.05382. url:
https://www.sciencedirect.com/science/article/pii/
S0021999119300269.
[6] Graham M Treece, Richard W Prager, and Andrew H Gee. “Reg-
ularised marching tetrahedra: improved iso-surface extraction”. In:
Computers & Graphics 23.4 (1999), pp. 583–598.
271
9
Solver Customization
9.1 Solver Design
which switches to the parent directory of all the solvers available in Open-
FOAM, or by changing manually to the $FOAM_SOLVERS environmental
shell variable:
?> cd $FOAM_SOLVERS
denotes the filling level of the cell with the first phase and takes on the
values from the interval [0, 1]. More information on the VoF method can
be found in [8].
Every solver directory contains many different files that accompany the
main solver implementation file, in this case interFoam.C. When the
solver implements a system of PDEs that are coupled together by differ-
ent terms, it is necessary to achieve a solution that does not only satisfy
a single PDE, but all of them. The additional files implement parts of
a so-called solution algorithm: the algorithm that determines how cou-
pled PDEs are solved in OpenFOAM. Implementing equations in separate
files and including them in the main solver application increases signifi-
cantly the readability of the solution algorithm implemented by the solver
application. In addition to a better overview of the solver application, sep-
arating the algorithms into different files makes them easily re-usable for
other solvers, while avoiding copies of the source code. The supporting
files that contain implementations of important for the VoF solver family
can be found in
?> ls $FOAM_SOLVERS/multiphase/VoF/
aCourantNo.H alphaEqn.H alphaEqnSubCycle.H
teAlphaFluxes.H setDeltaT.H setRDeltaT.H
A shared file can easily be replaced by a file with the same name placed
in the solver directory: the wmake build system searches the solver di-
rectory first for the files to be included. The files containing the ”Eqn”
suffix contain source code that define the equations of a mathematical
model that the solver is implementing. This implementation is done on
a very high level of abstraction, making it more human readable. Obvi-
ously, interFoam uses the momentum equation (UEqn.H), the pressure
equation (pEqn.H), the volume fraction equation (alphaEqn.H) as well
as other supporting parts of the solution algorithm. The alphaEqn.H
is shared by different versions of interFoam and is therefore stored in
$FOAM_SOLVERS/multiphase/VoF.
274
Solver Design
INFO
Whenever a source file is encountered with an Eqn suffix, that file
contains an implementation of an equation of a mathematical model.
Source code that operates on global field variables and is not implemented
in a form of a function or a class, is distributed in files that are included
by the solver applications. Including the file CourantNo.H inside solver
code, includes the calculation of the current Courant number in the solver
application which is based on the field variables available at the global
scope. In order to prevent code duplication, the number of such files that
share common source code snippets is reduced to a minimum with the
help of the build system. All shared included files are stored in specific
folders such as:
?> ls $FOAM_SRC/finiteVolume/cfdTools/general/include
checkPatchFieldTypes.H fvCFD.H initContinuityErrs.H
readGravitationalAcceleration.H readPISOControls.H
readTimeControls.H setDeltaT.H setInitialDeltaT.H
volContinuity.H
and this directory contains the symbolic links to all header files in the
finiteVolume directory and its sub-directories. Therefore, the included
header files shared between different applications from cfdTools are by
default made available to any OpenFOAM application.
Other globally available functionalities that are implemented in terms of
header files are listed above, and they include the following:
• time step adjustment based on the CFL condition,
• correction for volume conservation,
• determination of continuity errors,
• reading the gravitational acceleration vector, . . .
One could argue that the calculations implemented in terms of files that
are included into the application (solver) code is not a pure object-oriented
275
Solver Customization
approach to software design. This is true, however, the very nature of the
CFD simulation is procedural: process simulation input to compute the
approximative solution and store the output. Therefore, some variables
have been made global and thus allways accessible to the solution process,
such as the fields and the mesh. In that case, encapsulating operations
that are defined in the included files into classes and imposing hierarchy
on them is still possible. For example, there is nothing preventing the user
to implement a hierarchy of classes that implement different strategies to
modify the time step based on the Courant number. Actually, calculations
that are performed using the global field variables as arguments can
be encapsulated very nicely into function objects, as covered in chapter
12. However, such encapsulation is not allways necessary, and in those
situations, the included source files are used.
The high level of abstraction of the OpenFOAM DSL developed for math-
ematical models is observed in construction of the momentum equation
as shown in listing 31. The discrete operators used by the unstructured
FVM (divergence div, gradient grad, curl curl and the temporal deriva-
tive ddt) are implemented as function templates in the C++ language.
Using the generic programming allows the programmer to retain the same
function names (that are human readible) for functions taking completely
different formal parameters. For this reason, there exists a single imple-
mentation of the gradient operator and not separate implementations for
tensors of different ranks: scalar, vector, symmetric tensor, and the like.
276
Solver Design
INFO
OpenFOAM uses generic programming to implement the discrete dif-
ferential operators. The operators dispatch the discretization and in-
terpolation operations to a generic hierarchy of discretization and in-
terpolation schemes, respectively. The interpolation/discretization class
templates are instantiated and have been made runtime selectable. As a
consequence, no change to the solver code is required when a different
scheme is chosen.
9.1.1 Fields
277
Solver Customization
pimpleControl pimple(mesh);
#include "initContinuityErrs.H"
#include "createFields.H"
#include "readTimeControls.H"
#include "correctPhi.H"
#include "CourantNo.H"
#include "setInitialDeltaT.H"
The momentum conservation equation contains the term on the right hand
side that models the volumetric force density of the pressure acting on the
fluid. The pressure field is not known at the beginning of the simulation.
The pressure acting upon the fluid enforces a change in the momentum,
however, the mass conservation (continuity) equation needs to be satisfied
at all times. For an incompressible flow, this condition causes a tight
coupling between the continuity equation and the momentum equation.
This equation coupling is usually referred to as pressure-velocity coupling
and there are a multitude of algorithms developed in CFD whose sole
purpose is handling the equation coupling while keeping the solution
process stable.
278
Customizing the Solver
279
Solver Customization
IOdictionary transportProperties
(
IOobject
(
// Name of the file
"transportProperties",
// File location in the case directory
runTime.constant(),
// Object registry to to which the dict is registered
mesh,
// Read strategy flag: read if the file is modified
IOobject::MUST_READ_IF_MODIFIED,
// Write strategy flag: do not re-write the file
IOobject::NO_WRITE
)
);
280
Customizing the Solver
volScalarField& alpha1(mixture.alpha1());
volScalarField& alpha2(mixture.alpha2());
ateFields.H file to contain the code that initializes the viscosity (nu)
with a value contained in the transportProperties the dictionary:
dimensionedScalar nu
(
"nu",
dimViscosity,
transportProperties
);
and the predefined dimension set dimViscosity is used to set the unit
dimensions of the kinematic viscosity nu. The example transportProp-
erties entry (listing 34) shows how the nu entry is defined. More in-
formation on how the dimension system in OpenFOAM works can be
found in chapter 5. The following example shows how transport proper-
ties are looked up by the interFoam solver, which is a bit more compli-
cated. The important source code is located at $FOAM_APP/solvers/-
multiphase/interFoam/ At the beginning of the createFields.H file,
the immiscibleIncompressibleTwoPhaseMixture class is instantiated
(see listing 35). There are two member functions (rho1(), rho2()), used
to lookup the material properties of the twoPhaseMixture class. Exam-
ining the class source code is necessary, in order to find the code that
performs the actual dictionary access.
281
Solver Customization
EXERCISE
Figure out how the mixture viscosity and interface curvature are cal-
culated starting from the immiscibleIncompressibleTwoPhaseMix-
ture class using the Extended Code Guide.
As the fields and the mesh are used in the form of global variables in
the OpenFOAM solver applications, tracking all of them and dispatching
calls to their member functions explicitly would involve a lot of unnec-
essary code repetition. An example of such a clustered call dispatch is a
request of the solver to write out all fields to the hard disk. In the case of
using objects directly, the names of the objects would be hardcoded and
changing the names would introduce a cascade of changes in the appli-
cation code. Also, the code that implements such calls would need to be
copied on multiple places. For example, the same code responsible for the
field output would then be copied to every application that relies on the
same set of fields with the same variable names. Using included header
files as covered in section 9.1 would be possible for the solver family
that relies on same field variables. But changing a single field variable
would render such a header file unusable for the entire solver family. This
approach hence represents a stiff software design, or a software design
which does not scale well. A stiff or not-scaling software design is a
design for which a single extension requires the modification of existing
code, and furthermore, the modification occurs often at multiple places
in the existing code base.
Because of this issue, the logic behind coordination of operations for
multiple objects has been encapsulated into a class, that can then be re-
used at many places in the OpenFOAM code. To this purpose, an object
registry has been implemented: an object that registers other objects to
itself and then dispatches (forwards) the call to its member functions to
the registered objects. The object registry is an implementation of the
Observer Pattern from the OOD and it is described in more detail in
section 8.4. Additionally, there is a great review of the object registry as
well as the registered object classes on the OpenFOAM Wiki page 1 .
An example of the use of an object registry is a boundary condition
1
http://openfoamwiki.net/index.php/Snip_objectRegistry
282
Customizing the Solver
EXERCISE
Find out what is the totalPressureFvPatchScalarField boundary
condition meant to be used for. Which member function performs the
actual calculations? How are the alternative calculations implemented?
Can you think of an alternative runtime selectable implementation for
the alternative calculations?
The total pressure boundary condition updates the field it has been as-
signed to as it is done by all other boundary conditions in OpenFOAM,
using the updateCoeffs member function as shown in listing 36. How-
ever, the updateCoeffs() dispatches the calculation to the overloaded
updateCoeffs. The second argument of the updateCoeffs call shown
in listing 36 accesses the fvPatch constant reference attribute of the
boundary field using the patch() member function. On the other hand,
the fvPatch class stores a constant reference to the finite volume mesh,
which inherits from objectRegistry and is therefore an object registry.
The lookupPatchField is defined as a template member function of the
fvPatch class template, in the file fvPatchFvMeshTemplates.H and it
is shown in listing 37. The return type declaration of the function is
dependant on the template parameter GeometricField and therefore re-
quires the typename keyword, in order to make the compiler aware that
the PatchFieldType is indeed a type. The more important part of the
member function template is the return statement that obviously makes
use of the object registry functionality, which is inherited to the fvMesh
283
Solver Customization
284
Implementing a new PDE
accounted for, due to the use of equations of state which couple pres-
sure, temperature and density. For the sake of simplicity, thermodynamic
equations of state are omitted and laminar flow is assumed.
∂ρT
+ ∇ · (ρUT ) − ∇ · (Def f ∇T ) = 0, (9.1)
∂t
where T denotes the temperature, ρ is the density, U is the velocity,
Def f is the thermal conductivity coefficient of the phase mixture.
V1
α= , (9.2)
V
where V1 is the volume occupied by phase 1 in the cell that has the
total volume V . The properties of the single continuum are then modeled
using the volume fraction field, forming mixture quantities, such as the
mixture viscosity
ν = ν1 α1 + (1 − α1 )ν2 (9.3)
or mixture density
ρ = ρ1 α1 + (1 − α1 )ρ2 , (9.4)
where ν1 , ν2 and ρ1 , ρ2 are the kinematic viscosities and densities of
respective two fluid phases. The effective heat conduction coefficient in
285
Solver Customization
equation 9.1 Def f is modeled a similar way in this example, using the
α1 field
αk1 (1 − α)k2
Def f = + . (9.5)
Cv1 Cv2
Here, k1 , k2 and Cv1 , Cv2 represent the conduction coefficients and heat
capacities of respective fluid phases.
INFO
Each OpenFOAM version exports the $FOAM_RUN variable for the user
working directory, but does not generate it during the installation of
OpenFOAM. In case this directory is not available, create it with mkdir
-p $FOAM_RUN.
286
Implementing a new PDE
In the next steps all the files that are not to be modified must be deleted.
This is done to prevent code duplication, that should always be avoided. If
code is duplicated, maintaining the solver becomes problematic, because,
as OpenFOAM evolves, the solver files need to be updated. That is
why the changes introduced to existing files when implementing a solver
should be kept to a minimum.
INFO
When developing a new solver, keep the changes introduced in shared
files to a minimum. Introducing additional files and including them
into your new solver application while re-using existing files makes
your new solver easier to maintain, because re-using existing files will
pick up changes in those files in new OpenFOAM versions.
The directory should be cleaned up from all the files that are not
changed
?> rm -rf interMixingFoam/ overInterDyMFoam/
?> rm alphaSuSp.H createFields.H correctPhi.H
initCorrectPhi.H rhofs.H pEqn.H UEqn.H
INFO
Alternatively if you know from the start which solver files must modi-
fied, only those files and the Make directory can be copied.
Since the solver source code files have been renamed, the Make/files
must be modified so it reflects the changes. The Make/files config file
should contain only the lines shown here:
heatTransferTwoPhaseSolver.C
EXE = $(FOAM_USER_APPBIN)/heatTransferTwoPhaseSolver
287
Solver Customization
In order to inform the new solver to re-use the files from interFoam and
shared files from $FOAM_SOLVERS/multiphase/VoF, Make/options must
be modified into
INC = \
-I$(FOAM_SOLVERS)/multiphase/interFoam \
-I$(FOAM_SOLVERS)/multiphase/VoF \
...
This way wmake is instructed to look into the folder of the interFoam
solver first for files shared with interFoam, then into $FOAM_SOLVERS/
multiphase/VoF for files shared with the VoF solver family.
Two new required material phase properties must be looked up for each
phase from the transport properties dictionary: the heat conduction coeffi-
cient k and the heat capacity Cv. Additionally, the new temperature T and
the effective heat transfer coefficient Def f fields need to be initialized.
The initialization is placed in a new initialization file createHeatTrans-
ferFields.H, together with the initialization of the temperature and heat
transfer coefficient fields, as shown in in listing 38. The new create-
HeatTransferFields.H shown in listing 38 should be inserted in new
solver after createFields.H file, after the immiscibleIncompress-
ibleTwoPhaseMixture has been initialized. The heat capacity of type
dimensionedScalar will be loaded from each phases’ subdictionary in
the transportProperties dictionary file. With all of the dictionary
lookup and declaration complete, the new model equation 9.1 is to be
added to the solution algorithm.
The temperature equation is placed into a separate file TEqn.H and this
file is then included in the solver application. The code defined in the
TEqn.H file implements two operations. It calculates the coefficient Def f
288
Implementing a new PDE
auto k1 (phase1dict.get<dimensionedScalar>("k"));
auto Cv1 (phase1dict.get<dimensionedScalar>("Cv"));
auto k2 (phase2dict.get<dimensionedScalar>("k"));
auto Cv2 (phase2dict.get<dimensionedScalar>("Cv"));
volScalarField T
(
IOobject
(
"T",
runTime.timeName(),
mesh,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh
);
volScalarField Deff
(
"Deff",
(alpha1*k1/Cv1 + (1.0 - alpha1)*k2/Cv2)
);
289
Solver Customization
if (pimple.frozenFlow())
{
continue;
}
#include "TEqn.H"
#include "UEqn.H"
...
}
and it solve the passive temperature transport PDE given by equation 9.1.
290
Implementing a new PDE
?> wclean
?> wmake
INFO
The final case setup can be found in the cases repository, in the chap-
ter09/2DheatXferTest folder.
With the new solver created, new initial conditions, boundary conditions,
material properties, and solver control input parameters are now required
to handle the heat transfer computation. In the 0 directory, the temperature
field is added in the form of the T file, with the appropriate dimensions
FoamFile
{
version 2.0;
format ascii;
class volScalarField;
location "0";
object T;
}
dimensions [0 0 0 1 0 0 0];
regions
(
sphereToCell
{
centre (1 1 0);
radius 0.2;
291
Solver Customization
fieldValues
(
volScalarFieldValue alpha1 1
volScalarFieldValue T 500
);
}
);
transportModel Newtonian;
nu nu [ 0 2 -1 0 0 0 0 ] 1e-02;
rho rho [ 1 -3 0 0 0 0 0 ] 100;
k k [ 1 1 -3 -1 0 0 0] 100;
Cv Cv [ 0 2 -2 -1 0 0 0] 100;
...
e2
transportModel Newtonian;
nu nu [ 0 2 -1 0 0 0 0 ] 1e-02;
rho rho [ 1 -3 0 0 0 0 0 ] 1000;
k k [ 1 1 -3 -1 0 0 0] 1000;
Cv Cv [ 0 2 -2 -1 0 0 0] 1000;
...
292
Implementing a new PDE
relTol 0;
}
These are the options that set the solver for the heat transfer equation,
as well as its parameters. That should be the final case configuration
change needed to run the new solver with an added new model equation.
The solver can be run within the case directory and the results of the
simulation can be analyzed.
In order to run the case, make sure that the library and the applica-
tion code of the example code repository is compiled automatically by
invoking
?> ./Allwmake
in the main directory of the example code repository. To start the solver,
simply execute the following command within the simulation case direc-
tory:
?> heatTrasferTwoPhaseSolver
Figure 9.1 shows the distribution of the volume fraction field α and
the temperature field T for the simulation for the two-dimensional rising
bubble. The bubble temperature is set to be higher than the temperature
of the surrounding fluid, which should cause the bubble to release heat
flux to its surroundings as it rises.
EXERCISE
Exact solutions exist for heat conduction in different geometries, for
example a solid cylinder or a plate. Create a simulation case for a
cylinder surrounded by hot still air, or two plates in contact and verify
the new solver. Does the solution converge to the exact solution?
293
Solver Customization
294
Implementing a new PDE
Further reading
[1] I Clifford and H Jasak. “The application of a multi-physics toolkit
to spatial reactor dynamics”. In: International Conference on Math-
ematics, Computational Methods and Reactor Physics. 2009.
[2] M Darwish, I Sraj, and F Moukalled. “A coupled finite volume
solver for the solution of incompressible flows on unstructured
grids”. In: Journal of Computational Physics 228.1 (2009), pp. 180–
201.
[3] J. H. Ferziger and M. Perić. Computational Methods for Fluid
Dynamics. 3rd rev. ed. Berlin: Springer, 2002.
[4] R. I. Issa. “Solution of the implicitly discretised fluid flow equa-
tions by operator-splitting”. In: Journal of Computational physics
62.1 (1986), pp. 40–65.
[5] Kathrin Kissling et al. “A coupled pressure based solution al-
gorithm based on the volume-of-fluid approach for two or more
immiscible fluids”. In: V European Conference on Computational
Fluid Dynamics, ECCOMAS CFD. 2010.
[6] Suhas Patankar. Numerical heat transfer and fluid flow. CRC Press,
1980.
[7] S.V. Patankar and D.B. Spalding. “A calculation procedure for heat,
mass and momentum transfer in three-dimensional parabolic flows”.
In: International Journal of Heat and Mass Transfer 15.10 (1972),
pp. 1787–1806.
[8] G. Tryggvason, R. Scardovelli, and S. Zaleski. Direct Numerical
Simulations of Gas-Liquid Multiphase Flows. Cambridge University
Press, 2011. isbn: 9780521782401.
295
10
Boundary conditions
For this chapter, a solid understanding of the unstructured Finite Volume
Method (FVM). Chapter 2 covers the details of the unstructured mesh
and section 1.3 summarizes the unstructured FVM. This chapter covers
the use of boundary conditions in OpenFOAM simulations as well as
their further development.
The boundary of the domain and the respective boundary patches can
be found in in the constant/polyMesh/boundary file. Each boundary-
patch is a set of cell (finite volume) faces. The boundary file is rarely
examined by the OpenFOAM user; it could be helpful to the program-
mer in understanding the data structures OpenFOAM uses to store the
connectivity of the unstructured mesh.
OpenFOAM stores the fields as files in the initial time step directory (0).
As an example, consider a part of the configuration file for the dynamic
pressure field p_rgh of the simulation case rising-bubble-2D:
internalField uniform 0;
boundaryField
{
bottom
{
type zeroGradient;
}
298
Boundary condition design
INFO
The order in which the boundary patches are stored in the boundary
mesh is the same for all fields and it is determined by the way the
patches are listed in the polyMesh/boundary file. This is, in turn,
determined by the used mesh generator.
By default any field returns values from its internalField, when ac-
cessed with the access operator operator[](const label&). To access
values of the boundary field the boundaryField() member function
must be called (boundaryFieldRef() for con-constant access). This re-
turns a list of boundary patches, one boundary patch for each mesh
299
Boundary conditions
INFO
It useful to look into the Extended Code Guide when working with
classes in OpenFOAM to find what member functions are available and
what are their interfaces.
300
Boundary condition design
internalField
fvPatchField
boundaryField
pointPatchField
GeometricField
the points on the boundary and not for the face centers. Looking
for point*Field in the source code will show all fields of this
category.
After this clarification, we can dive into the relationship between fields
and boundary conditions. From the design perspective, the boundary con-
ditions in OpenFOAM encapsulate field values mapped to the domain
boundaries together with member functions responsible for calculations
that determine those boundary values based on internal values - the
boundary condition. The GeometricField class template provides mem-
ber functions which simplify the formulation and update of boundary
conditions. Some functionalities of the member functions include comput-
ing values of the boundary fields and taking values stored in the adjacent
internal cells as arguments. Both are key requirements to an implemen-
tation of any boundary condition. More information on which member
functions are supposed to do which task is provided in the following
section 10.2.2. The boundary fields - and thus the boundary conditions -
are encapsulated together with the internalField to form Geometric-
Field (see figure 10.1). Any of the three geometric field models, is a
typedef for a GeometricField with appropriate template arguments.
301
Boundary conditions
erator==, the operations are extended to take the boundary fields into
account as well. Values on the boundary can be overwritten from outside
of the actual boundary condition, until the boundary conditions are evalu-
ated again by the GeometricField::correctBoundaryConditions()
member function.
INFO
Operator GeometricField::operator== is not a logical equality
comparison operator in OpenFOAM, it extends the assignment of the
GeometricalField to include boundary field values. It is added to
the GeometricField interface to include arithmetic operations on the
composited boundary fields.
302
Boundary condition design
At this point you should have an overview of the fields involved in the
simulation in OpenFOAM and why the GeometricBoundaryField is en-
capsulated in the GeometricField, with non-constant access prepared for
manipulation done by client code of the GeometricField class template.
Analyzing the class inheritance and collaboration diagrams, although cer-
tainly helpful, is not as efficient in gaining understanding as using the
classes themselves, which is addressed in the following sections.
303
Boundary conditions
// GeometricField.H
//- Return internal field
InternalField& internalField();
// GeometricField.C
template
<
class Type,
template<class> class PatchField,
class GeoMesh
>
typename
Foam::GeometricField<Type, PatchField, GeoMesh>::InternalField&
Foam::GeometricField<Type, PatchField, GeoMesh>::internalField()
{
this->setUpToDate();
storeOldTimes();
return *this;
}
304
Boundary condition design
305
Boundary conditions
INFO
To understand how boundary conditions are updated by the Geomet-
ricField the last line in listing 44 must be understood.
The forAll loop in listing 45 loops over all patches of the Geomet-
ricBoundaryField which is referred to as *this. The updateCo-
effs() member function of fvPatchField is called directly for each
element of the domain boundary, using the operator[].
306
Boundary condition design
forAll(*this, patchi)
{
this->operator[](patchi).updateCoeffs();
}
}
307
Boundary conditions
GeometricField
+correctBoundaryConditions() : void
1 PatchField, Type
FieldField
*
GeometricBoundaryField
+evaluate() : void 1
+updateCoeffs() : void
Type
*
PatchField
+evaluate() : void
+updateCoeffs() : void
308
Boundary condition design
Type
fvPatchField
+updateCoeffs() : void
Type
mixedFvPatchField Type
Type
inletOutletFvPatchField
-refGrad_ = 0 : Field<Type>
+updateCoeffs() : void
As these attributes are private, there are public member functions that
provide const and non-const access to them. This enables the derived
309
Boundary conditions
310
Boundary condition design
template<class Type>
void Foam::inletOutletFvPatchField<Type>::updateCoeffs()
{
if (this->updated())
{
return;
}
mixedFvPatchField<Type>::updateCoeffs();
}
311
Boundary conditions
312
Implementing a new boundary condition
INFO
The recirculation boundary condition example may not work in a paral-
lel execution, depending on the fact if the modified boundary is a part
of the processor domain. The goal of this example is to show how the
mesh, the object registry and the fields connect with each other, and
not how to parallelize boundary condition updates.
313
Boundary conditions
decorator
fvMesh
314
Implementing a new boundary condition
Type
fvPatchField
+updateCoeffs() : void
1
1 Type
decorator
- decorated_ : fvPatchField<Type>
+updateCoeffs() : void
Type
concrete decorator
+updateCoeffs() : void
315
Boundary conditions
The decorator must implement all the pure virtual methods prescribed by
the abstract class fvPatchField. As it composites an ordinary boundary
condition as well, the decorator willl delegate the function calls to the
decorated boundary condition, based on the conditions prescribed by the
programmer. The extensions provided by the decorator can be designed as
the programmer desires. By combining both inheritance and composition
as shown in figure 10.5, it is possible for a decorated boundary condition
to switch its own type during runtime, as it delegates the computation to
the decorator.
For the example simulation case, the boundary condition checks if the
flow of the extended boundary has recirculation and tries to control the
decorated boundary condition in order to reduce it. This type of boundary
condition decorator is most likely to be set for some of the boundary con-
ditions where outflow is expected. This control in this example is done
in a fairly straightforward manner: by modifying the decorated bound-
ary condition as such that its field is overwritten with increasing inflow
values.
316
Implementing a new boundary condition
from the example code repository opened in a text editor. In the follow-
ing discussion the same names are used for the files and classes as those
present in the example code repository.
INFO
The boundary conditions have to be compiled as shared libraries. They
should never be implemented directly into application code, as this lim-
its their usability significantly as well as sharing with other OpenFOAM
programmers.
The libraries that the boundary conditions are compiled into are dynam-
ically linked with solver applications during runtime. At the start of the
tutorial, the library directory needs to be created:
?> mkdir -p primerBoundaryConditions/recirculationControl
?> cd !$
Note that the script wmakeFilesAndOptions will insert all *.C files into
the Make/files file, which means that the line
recirculationControl/recirculationControlFvPatchField.C
317
Boundary conditions
recirculationControl/recirculationControlFvPatchFields.C
LIB = $(FOAM_USER_LIBBIN)/libofPrimerBoundaryConditions
EXE_INC = \
-I$(LIB_SRC)/finiteVolume/lnInclude \
-I$(LIB_SRC)/meshTools/lnInclude
EXE_LIBS = \
-lfiniteVolume \
-lmeshTools
318
Implementing a new boundary condition
libs ("libofPrimerBoundaryConditions.so")
This loads the boundary condition shared library and links it to the
OpenFOAM executable.
TIP
Performing such integration tests is a strongly recommended step in
the process of writing a custom library. Using a version control system
(Chapter 6) improves the workflow as well. As an example, try applying
the recirculation control boundary condition to the fixedWalls patch
of the velocity field in the cavity tutorial case using the icoFoam
solver.
319
Boundary conditions
scalar recirculationRate_;
320
Implementing a new boundary condition
template<class Type>
Foam::recirculationControlFvPatchField<Type>::
recirculationControlFvPatchField
(
const recirculationControlFvPatchField<Type>& ptf,
const fvPatch& p,
const DimensionedField<Type, volMesh>& iF,
const fvPatchFieldMapper& mapper
)
:
fvPatchField<Type>(ptf, p, iF, mapper),
baseTypeTmp_(),
applyControl_(ptf.applyControl_),
baseTypeName_(ptf.baseTypeName_),
fluxFieldName_(ptf.fluxFieldName_),
controlledPatchName_(ptf.controlledPatchName_),
maxValue_(ptf.maxValue_),
recirculationRate_(ptf.recirculationRate_)
{
// Instantiate the baseType based on the dictionary entries.
baseTypeTmp_ = fvPatchField<Type>::New
(
ptf.baseTypeTmp_,
p,
iF,
mapper
);
}
321
Boundary conditions
condition.
The positive and negative volumetric fluxes are computed by the extended
boundary condition as shown in listing 54. Once the positive and neg-
ative volumetric fluxes are computed, the recirculation rate (ratio of the
negative flux and the total flux) is calculated with the code shown in
listing 55.
322
Implementing a new boundary condition
if (this->updated())
{
return;
}
forAll (phip, I)
{
totalFlux += mag(phip[I]);
if (phip[I] < 0)
{
negativeFlux += mag(phip[I]);
}
}
323
Boundary conditions
// If there is no recirculation.
if (negativeFlux < SMALL)
{
// Update the decorated boundary condition.
baseTypeTmp_->updateCoeffs();
// Mark the BC updated.
fvPatchField<Type>::updateCoeffs();
return;
}
INFO
Casting away constness this way should be avoided wherever possible:
it invalidates the point of encapsulation - the object state that can
be modified only by the class member functions. This example uses
the const cast only to point to a possible collaboration between the
geometrical field and the object registry.
324
Implementing a new boundary condition
the inlet pressure or velocity. Still, note that this is only an example
that illustrates two major things: First it shows how the design of the
VolumetricField may be used to couple functionality between different
boundary conditions. Secondly, it illustrates how to program a new bound-
ary condition in OpenFOAM, that is derived from fvPatchField.
upperWall
inlet
|U| = 2 m s−1 outlet
lowerWall
y leftWall
x lowerWall
Figure 10.6: Geometrical setup and initial conditions for the recirculation
control test case
Figure 10.6 depicts the initial configuration of the flow, where the velocity
of the backward step is set to zero as it is an impermeable wall. The
recirculation control boundary condition will override the zero velocity of
the backward facing step the moment recirculation appears at the outlet.
Further on, it will switch the boundary condition applied to the leftWall
boundary into an inflow, in order to drive the recirculation out.
The comparison of the velocity field without and with the recirculation
control boundary condition is shown in figure 10.7.
325
Boundary conditions
INFO
The setup for this test case is available in the ofbook-
cases/chapter10/recirculationControlChannel folder of the ex-
ample case repository and is simulated using the icoFoam solver for
the transient laminar incompressible single-phase flow.
Figure 10.7: Velocity field of the recirculation control test case, with-
out and with recirculation control, respectively with zero-
gradient and recirculation outlet.
EXERCISE
An abstract Decorator was not implemented in this example: modify
the recirculationControlFvPatchField such that an abstract Dec-
orator is added to the class hierarchy. That decorator generalizes the
decoration of the boundary condition, resulting in the ability to add any
functionality to any boundary condition in OpenFOAM at runtime.
326
Implementing a new boundary condition
if (
(applyControl_ == "yes") &&
(newRecirculationRate > recirculationRate_)
)
{
Info << "Executing control..." << endl;
if (p.name() == controlledPatchName_)
{
if (! bf[patchI].updated())
{
// Envoke a standard update first to avoid the field
// being later overwritten.
bf[patchI].updateCoeffs();
}
// Compute new boundary field values.
Field<Type> newValues (bf[patchI]);
328
Implementing a new boundary condition
In this section, a mesh motion solver will use the dynamicFvMesh library
and in a simulation example, it will solve a Laplace’s equation for the
point displacement with the displacement at the boundary defined by the
new boundary condition. Since the Laplace’s equation is used to model
diffusive transport, the displacement prescribed at the mesh boundary
will be smoothly diffused to the surrounding mesh, which ensures higher
quality of the deformed cells. For this type of application, the field that
stores the deformation of the points is called pointDisplacement.
The boundary condition presented in this section reads the position and
orientation of the patch’s centre of gravity from an input file and applies
the displacement to the mesh boundary, with respect to its previous posi-
tion and it must be used in conjunction with dynamic meshes, otherwise
the pointDisplacement field will not be read. The boundary condition
functionality consists of two existing components of OpenFOAM:
329
Boundary conditions
A brief search in the existing code base of OpenFOAM reveals that there
is a mesh motion solver that reads the motion data from a tabulated
file, similar to what is planned for this boundary condition. However, in
that case, the same displacement is applied to all mesh points, resulting
in a mesh motion which moves the mesh as a rigid body: no mesh
deformation occurs and the relative position of the mesh points does not
change. For the purpose of this tutorial, the calculation of motion can
be used and later be applied to the patch points making the boundary
of the mesh move as a rigid body. This kind of mesh motion may be
beneficial when the relative motion of the body is small with respect to
the mesh (flow domain). In this case, the if the motion is propagated into
the flow domain in a diffusion-like manor, the motion of the mesh far
away from the boundary with prescribed displacement will be near zero.
How fast the displacement vanishes away from the body is determined
by the magnitude of the displacement diffusion coefficient.
The code implementing the rigid body mesh motion based on tabular
data is contained within the tabulated6DoFMotionFvMesh class which
is derived from solidBodyMotionFunction and can be found here:
330
Implementing a new boundary condition
$FOAM_SRC/dynamicMesh/motionSolvers/displacement/solidBody/\
solidBodyMotionFunctions/tabulated6DoFMotion/
There is an example case in the official release that employs the tabu-
lated6DoFMotion to prescribe the motion of a closed tank. This tutorial
can be found here:
$FOAM_TUTORIALS/multiphase/interFoam/laminar/sloshingTank3D6DoF
331
Boundary conditions
f 24
f 19 f 23
f 14
18
f 9 f 13 f f 22
17
f4 f 8 f 12 f f 21 f19 f2
4
16 f
f3 f 7 f 11 f f 20 14
f2 f 6 f 10 f 15 f4 f9 f18 f2
3
f13
f1 f5 f3 f 8
7 f2
2
f0 tn f7
f12 f1
6 f2
f2 1
f6
f11 f1
5 f2
f1 0
f5
f10 f1
f0
p (tn ) t0
z p (t0 )
y
332
Implementing a new boundary condition
fileName newTimeDataFileName
(
fileName(SBMFCoeffs_.lookup("timeDataFileName")).expand()
);
IFstream dataStream(timeDataFileName_);
List<Tuple2<scalar, translationRotationVectors> > timeValues
(
dataStream
);
times_.setSize(timeValues.size());
values_.setSize(timeValues.size());
forAll(timeValues, i)
{
times_[i] = timeValues[i].first();
values_[i] = timeValues[i].second();
}
scalar t = time_.value();
// -- Some lines were spared --
translationRotationVectors TRV = interpolateSplineXY
(
t,
times_,
values_
);
shown in listing 59. The second assignment interpolating the position and
orientation data for the current time t. All angles must be converted to
radians and finally a representation of the transformation is assembled,
using quaternions and septernions.
333
Boundary conditions
After having found the code responsible for the point motion, the next step
is to find a boundary condition that is derived from pointPatchField
and performs a similar task than the one we plan to implement: from
this we plan build our own boundary condition.
?> $FOAM_SRC/fvMotionSolver/pointPatchFields/\
derived/oscillatingDisplacement/
with both amplitude_ and omega_ being scalar values (omega is the an-
gular rotation) read from a dictionary. The implementation of this method
can be performed as shon in listing 60.
The line
Field<vector>::operator=(amplitude_*sin(omega_*t.value()));
334
Implementing a new boundary condition
void oscillatingDisplacementPointPatchVectorField::updateCoeffs()
{
if (this->updated())
{
Field<vector>::operator=(amplitude_*sin(omega_*t.value()));
fixedValuePointPatchField<vector>::updateCoeffs();
}
fixedValuePointPatchField<vector>::updateCoeffs();
}
?> mkdir -p \
$WM_PROJECT_USER_DIR/applications/tabulatedRigidBodyDisplacement
?> cd $WM_PROJECT_USER_DIR/applications/tabulatedRigidBodyDisplacement
335
Boundary conditions
LIB = $(FOAM_USER_LIBBIN)/libtabulatedRigidBodyDisplacement
336
Implementing a new boundary condition
LIB_LIBS = \
-lfiniteVolume \
-lmeshTools \
-lfileFormats
#include "fixedValuePointPatchField.H"
#include "solidBodyMotionFunction.H"
And the source file must include some more header files:
#include "tabulatedRigidBodyDisplacementPointPatchVectorField.H"
#include "pointPatchFields.H"
#include "addToRunTimeSelectionTable.H"
#include "Time.H"
#include "fvMesh.H"
#include "IFstream.H"
#include "transformField.H"
337
Boundary conditions
tabulatedRigidBodyDisplacementPointPatchVectorField::
tabulatedRigidBodyDisplacementPointPatchVectorField
(
const pointPatch& p,
const DimensionedField<vector, pointMesh>& iF
)
:
fixedValuePointPatchField<vector>(p, iF),
dict_()
{}
tabulatedRigidBodyDisplacementPointPatchVectorField::
tabulatedRigidBodyDisplacementPointPatchVectorField
(
const pointPatch& p,
const DimensionedField<vector, pointMesh>& iF,
const dictionary& dict
)
:
fixedValuePointPatchField<vector>(p, iF, dict),
dict_(dict)
{
updateCoeffs();
}
This constructor is the only one that calls updateCoeffs() during con-
struction. The updateCoeffs member function is shown in listing 62 The
most important line is the line that defines the SBMFPtr. It constructs
an autoPtr for a solidBodyMotionFunction, based on a dictionary
and an object of Time. As this code originates from the dynamicFvMesh
library, the dictionary passed to the constructor is a subdictionary of
the dynamicMeshDict. This subdicitonary contains all the parameters
required by the solidBodyMotionFunction selected by the user in the
dynamicMeshDict. As the definition of the motion parameters for this
boundary condition should be performed on a per-boundary basis and
not a global-basis, the dictionary that is passed to the constructor should
be read from the boundary condition in the 0 directory, rather than the
dynamicMeshDict. This is what the private member dict_ is for: It is
read only once and passed to the solidBodyMotionFunction in each
call to updateCoeffs().
The next lines are similar to the ones that can be found in the tab-
ulated6DoFMotion. The absolute positions of the patch points after
338
Implementing a new boundary condition
void tabulatedRigidBodyDisplacementPointPatchVectorField::updateCoeffs()
{
if (this->updated())
{
return;
}
autoPtr<solidBodyMotionFunction> SBMFPtr
(
solidBodyMotionFunction::New(dict_, t)
);
pointField vectorIO(mesh.points().size(),vector::zero);
vectorIO = transform
(
SBMFPtr().transformation(),
ptPatch.localPoints()
);
Field<vector>::operator=
(
vectorIO-ptPatch.localPoints()
);
fixedValuePointPatchField<vector>::updateCoeffs();
}
339
Boundary conditions
340
Implementing a new boundary condition
stationary walls
moving object
Figure 10.9: Illustration of the domain and moving patch of the tabulat-
edMotionObject example case.
movingObject
{
type tabulatedRigidBodyDisplacement;
value uniform (0 0 0);
solidBodyMotionFunction tabulated6DoFMotion;
tabulated6DoFMotionCoeffs
{
CofG ( 0 0 0 );
timeDataFileName "constant/6DoF.dat";
}
}
surrounding points did not move with the patch, proximal cell would be
quickly deformed and crushed. The contents of the dynamicMeshDict
are shown in listing 64.
The simulation can be run by executing the Allrun script within the
simulation case directory. This script performs all necessary steps, such as
mesh generation, to complete this example hassle-free. The utility solver
moveDynamicMesh only calls dynamic mesh routines and is devoid of
any flow related computations, which makes it relatively fast compared
to the usual flow solvers with dynamic mesh capabilities. In addition to
performing mesh motion operations it executes numerious mesh related
341
Boundary conditions
dynamicFvMesh dynamicMotionSolverFvMesh;
motionSolverLibs ("libfvMotionSolvers.so");
solver displacementLaplacian;
displacementLaplacianCoeffs
{
diffusivity inverseDistance (floatingObject);
}
}
quality checks.
Post-processing the case is straightforward and can be performed visually:
the case can be inspected in Paraview. Animating the case shows the
internal patch tumbling and swaying around within the domain, similar
to the data provided in the input file. Cutting the domain in half using
the Clip filter with the option Crickle Clip2 clarifies some interesting
things with regards to dynamic meshes: The way the point displacement
emposed by the boundary condition is dissipated by the mesh motion
solver. The Laplace equation dissipates the displacement to internal mesh
points, moves the points accordingly and maintains good cell quality
while the patch translates and rotates. Figure 10.10 holds the image of
the final patch position before and after transformation.
Summary
342
Implementing a new boundary condition
Further reading
[1] Erich Gamma et al. Design patterns: elements of reusable object-
oriented software. Addison-Wesley Longman Publishing Co., Inc.,
1995. isbn: 0-201-63361-2.
[2] OpenFOAM User Guide. OpenCFD limited. 2016.
343
11
Transport models
This chapter covers the implementation of viscosity models in Open-
FOAM, other transport models in OpenFOAM have a similar structure
and can be developed further based on the information presented in this
chapter.
y u (y) d
with µ denoting the dynamic viscosity that relates to the kinematic vis-
cosity ν in the following manner:
µ
ν= . (11.2)
ρ
The viscosity model is not the only model that affects the viscosity:
the turbulence model changes the so-called effective viscosity (cf. chap-
ter 7). If turbulence modeling is disabled, the viscosity model exclusively
determines ν.
346
Software design
transportModel
+nu(): tmp<volScalarField>
+correct(): void
+read(): void
singlePhaseTransportModel
-viscosityModelPtr_: autoPtr<viscosityModel>
+nu(): tmp<volScalarField>
+correct(): void
viscosityModel
Newtonian
-viscosityProperties_: dictionary
-nu0_: volVectorField
-U_: volVectorField
-nu_: volScalarField
-phi_: surfaceScalarField
+nu(): tmp<volScalarField>
+nu(): tmp<volScalarField>
+correct(): void
+correct(): void
+read(): void
+read(): void
347
Transport models
Note that the constructor of the turbulence model requires the lami-
narTransport object as an argument because a turbulence model will
require access to the laminar viscosity, that in turn is described by the
singlePhaseTransportModel.
348
Software design
349
Transport models
twoPhaseMixture
transportModel
-phase1Name_ : word
+nu(): tmp<volScalarField> -phase2Name_ : word
+correct(): void -alpha1_ : volScalarField
+read(): void -alpha2_ : volScalarField
incompressibleTwoPhaseMixture
-nuModel1_ : autoPtr<viscosityModel>
2 1 -nuModel2_ : autoPtr<viscosityModel>
+nu(): tmp<volScalarField>
+correct(): void
+read(): void
viscosityModel
+nu(): tmp<volScalarField>
+correct(): void
+read(): void
between both phases’ properties such as viscosity and density. Unlike the
singlePhaseTransportModel, where a call to the public member func-
tion nu is delegated directly to the chosen viscosityModel, the return
value of this public member function is composed differently. A copy of
the private member nu_ is returned, which is calculated based on both
phases’ viscosityModels and the current alpha1_ field. This calcula-
tion is performed by the private member calcNu and triggered when the
public member function correct is called as shown in listing 67.
350
Software design
// Protected data
autoPtr<viscosityModel> nuModel1_;
autoPtr<viscosityModel> nuModel2_;
dimensionedScalar rho1_;
dimensionedScalar rho2_;
volScalarField nu_;
351
Transport models
volScalarField nu_;
352
Implementation of a new Viscosity Model
INFO
Even though the transportModel is the abstract base class for all
transport models, specialistic solvers will select other model classes
in the hierarchy. An example is the incompressibleTwoPhaseMix-
ture, which is a transportModel, but is also a twoPhaseMixture,
specifically meant to be used by the two-phase solvers.
353
Transport models
INFO
The example focuses on implementing a new viscosity model in Open-
FOAM, not on physical correctnes of the model.
ure 11.4. This example covers implementing a new viscosity model class
that reads the kinematic viscosity and the local strain-rate and returns
the effective viscosity. This data table-based approach contrasts with the
other more common viscosity models that use analytical expressions of
the strain-rate vs. viscosity relationship. An example of a set of mea-
surements from a shear rheometer (effective viscosity vs. strain rate) is
shown in figure 11.4. Because there are only discrete data points in the
rheometry table, an interpolation scheme must be used to interpolate be-
tween them and assign a viscosity to any given strain rate. OpenFOAM
provide two-dimensional spline-based interpolation which can be used for
this purpose: interpolateSplineXY. The source code for this library is
available in the example repository, in
ofbook/ofprimer/src/ofBookTransportModels
354
Implementation of a new Viscosity Model
10−5
INFO
In OpenFOAM, as in every other large-scale object-oriented software,
the alternative implementation of virtual functions extend the function-
ality of a library. In other words, when extending a library in Open-
FOAM, find out what virtual functions do in other derived classes, as
those member functions are most likely to be modified in your new
class as well.
The viscosity model extension starts with the public inheritance from
viscosityModel and the definition of private data members, required
for the viscosity calculation, shown in listing 70. The private member
function loadViscosityTable in listing 70 is responsible for reading
the experimental data from the CSV file, which is converted to scalar
fields rheologyTableX and rheologyTableY, used as input for the
spline-based interpolation in OpenFOAM.
The declaration of the pure virtual member functions inherited from
viscosityModel is shown in listing 71. The kinematic viscosity field is
a private data member (wrapped in a tmp smart pointer), so the viscosity
field and the patch viscosity field can be returned easily. The main part
of the model implementation therefore lies in loadViscosityTable and
355
Transport models
tmp<volScalarField> nuPtr_;
356
Implementation of a new Viscosity Model
forAll(data, lineI)
{
const auto& dataTuple = data[lineI];
rheologyTableX_[lineI] = dataTuple.first();
rheologyTableY_[lineI] = dataTuple.second();
}
correct.
The csvTableReader class is used to read the CSV file that contains
the experimental data from figure 11.4. The loadViscosityTable is
called once in the body of the constructor and once in the read mem-
ber function. The correct member function obtains the strain rate from
viscosityModel in each cell, then interpolates the apparent kinematic
viscosity from the experimental data read by loadViscosityTable us-
ing a spline-based interpolation. The implementation is outlined in list-
ing 73.
transportModel interpolatedSplineViscosityModel;
interpolatedSplineViscosityModelCoeffs
{
dataFileName "constant/viscosityData/anand2004kinematic.csv";
hasHeaderLine false;
refColumn 0;
componentColumns (1);
}
rho 1060;
The input of the new viscosity model contains the path to the file with
the experimental data, as well as information that defines the structure
of the CSV file. The CSV file does not contain a header, the reference
357
Transport models
358
Implementation of a new Viscosity Model
non newtonian
droplet
g
solid walls
Figure 11.6: Comparison of the volume fraction and the local shear-
dependent viscosity of the droplet impacting a wall.
359
Transport models
Further reading
[1] M Anand and Kr R Rajagopal. “A shear-thinning viscoelastic fluid
model for describing the flow of blood”. In: Int. J. Cardiovasc.
Med. Sci. 4.2 (2004), pp. 59–68. url: http : / / www . cs . cmu .
edu / afs / cs . cmu . edu / project / taos - 10 / publications /
MAKRR2004.pdf.
[2] Henning Scheufler and Johan Roenby. “Accurate and efficient sur-
face reconstruction from volume fraction data on general meshes”.
In: J. Comput. Phys. 383 (Apr. 2019), pp. 1–23. issn: 10902716.
doi: 10.1016/j.jcp.2019.01.009. arXiv: 1801.05382. url:
https://www.sciencedirect.com/science/article/pii/
S0021999119300269.
[3] Hermann Schlichting and Klaus Gersten. Boundary-Layer Theory.
8rd rev. ed. Berlin: Springer, 2001.
[4] D. C. Wilcox. Basic Fluid Mechanics. 3rd rev. ed. DCW Industries
Inc., 2007.
360
12
Function Objects
A function object is solver-independent code compiled into a dynamic
library and executed either during the simulation time loop or by a post-
processing application after the simulation has been completed. Function
objects most often perform post-processing calculations as the simulation
runs, but they can also manipulate the fields or the simulation parameters.
An OpenFOAM function object library can contain an arbitrary number
of function objects that can be linked to the application during runtime:
OpenFOAM provides a mechanism for loading dynamic libraries in every
simulation case. The function objects’ encapsulated functionality is ac-
cessed from the solver in a predefined way, so the function objects must
adhere to a fixed class interface.
This section covers the software design of the C++ and OpenFOAM
function objects. There are differences between C++ and OpenFOAM
function objects that should be made clear because they may lead to
confusion.
362
Software design
void operator()() {}
};
c();
return 0;
}
363
Function Objects
364
Software design
be added to the cell set based on the value of the field. However, it is ir-
relevant how this comparison is implemented, as long as the operator()
of col is implemented and returns a boolean.
The function object used in this example is the equal_to STL function
object template. This generic function object simply checks whether two
values of type T are identical. Since it uses type lifting aspect of generic
programming, it may only be applied on instances of types that have the
equality operator== defined. However, the equal_to function object
requires two arguments for the comparison. The question that remains
is how it can be called for the field value within the collectCells
member function template. For this simple example, the answer is fairly
simple: The first argument of the equal_to function object has been
bound to the value of 1. Meaning that the testFielCellSet application
should create a cell set consisted of all mesh cells with the field values
equal to 1.
365
Function Objects
by calling
?> blockMesh
?> setAlphaField
?> testFieldCellSet -field alpha.water
The resulting cell set is stored in the 0 directory and to visualize it using
the paraView application, it must be converted to the VTK format using
foamToVTK. Before this can be done, the cell set must be copied from
the 0 directory to constant/polyMesh/sets:
?> mkdir -p constant/polyMesh/sets
?> cp 0/fieldCellSet !$
366
Software design
Figure 12.1: Field alpha1 of the field based cell set comprising the rising
bubble in the initial time step.
367
Function Objects
//- Name
virtual const word& name() const;
368
Software design
called from within the mesh class polyMesh, using the constant access
to simulation time.
As the result of those requirements, the Time class composits all function
objects loaded for each particular simulation in a list of function objects
(functionObjectList) as shown in the diagram in figure 12.2. The
functionObjectList implements the interface of functionObject and
delegates the member function invocation to the composited function
objects. As the simulation starts and the runTime object of the Time
class gets initialized, the simulation control dictionary controlDict is
read. The constructor of functionObjectList reads the controlDict
and parses the entries in the functions sub-dictionary. Each entry in
the functions sub-dictionary defines parameters of a single function
object, which are then passed to the function object selector. The selector
(functionObject::New) implements the ”Factory Pattern” known from
OOP and uses the dictionary parameter type to initialize a concrete
model of the functionObject abstract class during runtime. Finally, the
selected function object gets appended to the list of function objects. This
mechanism also relies on the RTS mechanism in OpenFOAM, allowing
the user to select and instantiate different function objects for different
simulation cases.
Since the function objects are initialized during runtime and rely on dy-
namic polymorphism (virtual functions), the member functions that im-
plement the main functionality have an overhead when resolving which
virtual member function is called (dynamic dispatch). However, the cal-
culations performed by OpenFOAM function objects take orders of mag-
nitude more computational time than dynamic dispatch, so the overhead
of using dynamic dispatch can be neglected safely.
The function objects in OpenFOAM are also objects that perform function-
like operations during the simulation, so that property justifies their name.
The class declaration of functionObject serves as the other example
to describe further differences:
369
Function Objects
functionObject
polyMesh
+start(): bool
+execute(const bool): bool
+end(): bool +updateMesh(const mapPolyMesh&): void
+timeSet(): bool +movePoints(const polyMesh&): void
+read(const dictionary&): bool
+updateMesh(const mapPolyMesh&): void
+movePoints(const polyMesh& mesh): void
functionObjects()
* Time
-functionObjects_
+run(): bool
+operator++()() : Time &
1 +functionObjects() : const functionObjectList&
functionObjectList 1
+start(): bool
1 functionObjects_
+execute(const bool): bool
+end(): bool
+timeSet(): bool
+read(const dictionary&): bool
+updateMesh(const mapPolyMesh&): void
+movePoints(const polyMesh& mesh): void
Contrary to the C++ function objects, the assignment and copy construc-
tion are both prohibited for function objects in OpenFOAM, making it
impossible to pass them as value arguments. The prohibited assignment
and copy-construction have no negative impact on OpenFOAM function
objects: the central point of use of function objects in OpenFOAM is
the private attribute functionObjectList in the Time class, so there is
no need to instantiate them manually in a list or pass them around by
value.
370
Using OpenFOAM Function Objects
371
Function Objects
courantNo
{
type CourantNo;
phiName phi;
rhoName rho;
writeControl outputTime;
libs ("libfieldFunctionObjects.so");
}
...
Figure 12.3: Courant number distribution for the droplet impacting a wall.
object, the the dynamically loaded library and the functions entries in
the system/controlDict file of the falling-droplet-2D simulation
case need to be defined as shown in listing 76. For the courantNo
function object, as well as for other function objects in OpenFOAM,
the output is independent from the simulation output. Because of this,
the additional entry writeControl is necessary to prescribe the output
control of the function object. Setting writeControl to outputTime
writes the Courant number field using the same output frequency as the
output frequency of other fields in the simulation. Figure 12.3 shows the
resulting Courant number distribution for the droplet simulated with the
interIsoFoam solver at 0.04 seconds.
372
Implementation of a custom Function Object
INFO
To use the function object generator, make sure that the OpenFOAM
environment is set.
Following commands create a new function object library with the foam-
NewFunctionObject script
?> foamNewFunctionObject myFuncObject
?> cd myFuncObject
373
Function Objects
Foam::functionObjects::myFuncObject::myFuncObject
(
const word& name,
const Time& runTime,
const dictionary& dict
)
:
fvMeshFunctionObject(name, runTime, dict),
// Bracket error fixed.
boolData_(dict.getOrDefault<bool>("boolData",true)),
After this small syntax correction, the new library can be compiled with
myFuncObject > wmake libso
LIB = $(FOAM_USER_LIBBIN)/libmyFuncObjectFunctionObject
374
Implementation of a custom Function Object
its Make folder now contains the build configuration of the myFunc-
tionObjects library, that needs to be modified. Specifially, the my-
FunctionObject/Make/files should look like this:
myFuncObjectA/myFuncObjectA.C
myFuncObjectB/myFuncObjectB.C
LIB = $(FOAM_USER_LIBBIN)/libmyFunctionObjects
which basically lists the two implementation (.C) files of the two func-
tion objects containes in the myFunctionObjects library, and specifies
the location ($FOAM_LIBBIN), and the name of the new function object
library.
INFO
If you are on the latest commit on the master branch, the syntax error
present on the OpenFOAM-v2012 tag has been fixed.
To compile the library, fix the small syntax erro in the constructor of the
both function objects as discussed above, and execute wmake libso. Once
the library is compiled successfully, function objects from the myFunc-
tionObjects library can be used like any other OpenFOAM function
object, with any solver and any simulation case, provided that the my-
FunctionObjects library is listed as a dynamically loaded library in the
controlDict file. For example, the new library is used in the cavity
tutorial case with the icoFoam solver as follows:
?> mkdir -p $FOAM_RUN && cd !$
?> mkdir myFunctionObjectsTest && cd myFunctionObjectsTest
?> cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity .
?> cd cavity
The new function object myFuncObjectA from the new library myFunc-
tionObjects is activated by the following entries in system/control-
Dict file:
libs ("libmyFunctionObjects.so");
functions
{
funcA
{
type myFuncObjectA;
}
}
375
Function Objects
The first line specifies the new library that should be dynamically loaded.
OpenFOAM automatically searches for $FOAM_LIBBIN and $FOAM_USER_
LIBBIN for available libraries. The functions entry is a dictionary may
contain arbitrary many dictionary entries, each specifying a different func-
tion object. In the above snippet, funcA sub-dictionary configures the
myFuncObjectA function object. The name funcA is arbitrary: any user-
defined name is acceptable.
Generating the mesh with blockMesh and starting the icoFoam solver,
leads to the error
--> FOAM FATAL IO ERROR: (openfoam-2012)
Entry 'labelData' not found in dictionary
"path/to/cavity/system/controlDict.functions.funcA"
Therefore, there are two ways in which using function objects may go
wrong: we misname the function object, or we do not provide the data
required for its initialization. If we misname a function object, the solver
will run without it. If a data entry required to initialize the function
object is missing, the solver does not run and the user is promted to
enter required data in system/controlDict. The complete definition of
the funcA sub-dict in system/controlDict is
functions
{
funcA
{
type myFuncObjectA;
boolData false;
labelData 0;
scalarData 0;
}
}
376
Implementation of a custom Function Object
//- bool
bool boolData_;
//- label
label labelData_;
//- word
word wordData_;
//- scalar
scalar scalarData_;
will be replaced by data members that support the computation of the new
function object. Member functions generated by foamNewFunctionOb-
ject do practically nothing:
bool Foam::functionObjects::myFuncObjectA::read(const dictionary& dict)
{
dict.readEntry("boolData", boolData_);
dict.readEntry("labelData", labelData_);
dict.readIfPresent("wordData", wordData_);
dict.readEntry("scalarData", scalarData_);
return true;
}
bool Foam::functionObjects::myFuncObjectA::execute()
{
return true;
}
bool Foam::functionObjects::myFuncObjectA::end()
{
return true;
}
bool Foam::functionObjects::myFuncObjectA::write()
{
return true;
}
377
Function Objects
In case when the calculation is not different at the beginning and the
end of the time loop, member functions start and end call the member
function execute. The function object described in this section keeps
track of all the mesh cells that have contained a specific phase during
a multiphase simulation. This example is not especially useful, its only
purpose is to demonstrate the calculations usually performed by function
objects, involving the mesh, the fields, and the simulation time control.
The code for the function object example is available in the example code
repository in
src/ofPrimerFunctionObjects/phaseCellsFunctionObject
Viewing the code in a text editor may help with understanding the below
described example.
378
Implementation of a custom Function Object
Edit Make/files so that the function object is compiled into the new
library
phaseCellsFunctionObject/phaseCellsFunctionObject.C
LIB = $(FOAM_USER_LIBBIN)/libofPrimerFunctionObjects
The Allrun script generates the mesh and starts the simulation, and the
function object generated from the template does nothing at first.
INFO
The implementation of the function object is described below and it as-
sumes some prior knowledge of the C++ programming language: classes
/ encapsulation, declaration vs definition, virtual functions, inheritance,
etc.
The name of the function object used for the Runtime Type Selec-
tion (RTS) in OpenFOAM is generated by foamNewFunctionObject
as ”name” + ”FunctionObject”, where ”name” is the argument given
to foamNewFunctionObject. The type name of the generated function
object can be changed in phaseCellsFunctionObject.H, from
379
Function Objects
//- Time.
const Time& time_;
to
//- Runtime type information
TypeName("phaseCells");
380
Implementation of a custom Function Object
381
Function Objects
The calcWettedCells member function marks the cells that are ”wet-
ted”,
void phaseCellsFunctionObject::calcWettedCells()
{
forAll (alpha1_, cellI)
{
if (isWetted(alpha1_[cellI]))
{
phaseCells_[cellI] = 1;
}
}
}
382
Implementation of a custom Function Object
bool isWetted(scalar s)
{
return (s > phaseTolerance_);
}
The percentage of cells that were ”wetted” by the specific phase is com-
puted as
void phaseCellsFunctionObject::calcWettedDomainPercent()
{
scalar phaseCellsSum = 0;
phaseDomainPercent_ =
100. * phaseCellsSum / alpha1_.size();
}
Since the function object contains data members that can be written down
during the simulation for later inspection, the write() member function
is implemented as
bool phaseCellsFunctionObject::write()
{
if (time_.writeTime())
{
phaseCells_.write();
}
return true;
}
This ensures that the phaseCells field is only written to the disk at the
write frequency defined by the user of the simulation and controlled by
Foam::Time.
383
Function Objects
markPhaseCells
{
type phaseCells;
libs ("libofPrimerFunctionObjects.so");
phaseIndicator alpha.water;
phaseTolerance 1e-06;
}
This defines the dictionary entries of the constructor (see listing 78), and
the dynamically linked library of the function object. Executing Allrun
in cases/chapter11/falling-droplet-2D results in the phaseCells
being written to disk and the following addition to the solver output
Phase alpha.water covers 5.4 % of the solution domain.
Phase alpha.water covers 5.4 % of the solution domain.
Phase alpha.water covers 5.4 % of the solution domain.
Phase alpha.water covers 5.48 % of the solution domain.
Phase alpha.water covers 5.48 % of the solution domain.
Further reading
[1] Andrei Alexandrescu. Modern C++ design: generic programming
and design patterns applied. Boston, MA, USA: Addison-Wesley
Longman Publishing Co., Inc., 2001.
[2] Nicolai M. Josuttis. The C++ standard library: a tutorial and
reference. Boston, MA, USA: Addison-Wesley Longman Publishing
Co., Inc., 1999.
[3] David Vandevoorde and Nicolai M. Josuttis. C++ Templates: The
Complete Guide. 1st ed. Addison-Wesley Professional, Nov. 2002.
384
13
Dynamic mesh handling
Generally there are two main dynamic mesh handling capabilities in
OpenFOAM: mesh motion and topological changes ([4], [5]). Mesh mo-
tion solely involves displacement of the mesh points without altering mesh
topology: the connectivity between mesh elements such as points, edges,
faces, and cells. Displacing mesh points might seem like a rather trivial
task, but depending on what motion is desired, the required operations
can be more complex than expected. Geometrical information used by the
unstructured FVM, such as face centers and face area normal vectors, is
built from the list of unique mesh points. Therefore, the displacement of
mesh points triggers the computation of the geometrical information re-
quired by the unstructured FVM. After the mesh has been deformed, the
fields whose values still relate to the initial mesh need to be mapped to
the new mesh. This mapping must be applied for cell-centered and face-
centered fields because the field values in FVM represent values averaged
over the volume of a cell or the area of a cell-face, and faces and cells
may experience modifications by mesh refinement and de-refinement.
inside the domain, and relative motion exists between mesh points, the
cells are likely to get either too distorted or compressed. An example
of this is a piston moving in a cylinder: layers of cells are added or
removed, parts of the mesh can be disconnected from each other and be
reconnected later on.
The second problem category requires the simulation to have higher ac-
curacy in those domain regions that are unknown a priori, e.g., at a point
when the mesh is generated. For simulations that involve shocks in the
simulation domain where the shock position is a part of the solution
process, topological changes are applied based on, e.g., pressure gradi-
ent to achieve local static refinement in the region the shock appears. A
different example is an interface between two immiscible liquid phases
in a two-phase simulation: physical properties change abruptly in values
across the interface, but the interface position is known at the start of
the simulation. However, to obtain a more accurate solution, the mesh is
locally and dynamically refined near the fluid interface. This refinement
follows the interface as it moves across the computational domain.
386
Software design
The design of the dynamic mesh classes varies depending on the function-
ality of each particular dynamic mesh. In this chapter, a brief overview
of the functionality and the design of dynamic meshes in OpenFOAM is
provided, involving mesh motion and topological changes of the mesh.
387
Dynamic mesh handling
Figure 13.1: The filled body is subjected to a solid body motion, so the
mesh points do not move relatively to each other.
The dynamic mesh class performs the actual solid body mesh motion
is named solidBodyMotionFvMesh and inherits from dynamicFvMesh.
The relationship between solidBodyMotionFvMesh and solidBodyMo-
tionFunction is shown on the class diagram in figure 13.2. The trans-
formation function is implemented using the Strategy Pattern ([1]) for
the solidBodyMotionFvMesh class. Consequently, the solid body mo-
tion function can be re-used by other library parts as a standalone entity.
To compute the solid body motion, the relationship between a class and
the solid body mesh motion must not be known. Additionally, the solid-
BodyMotionFunction is designed using the Strategy pattern and allows
for easy addition of other functions. Simply inheriting from solidBody-
388
Software design
solidBodyMotionFvMesh
solidBodyMotionFunction
INFO
Strategy pattern:
Encapsulating various algorithms in a class hierarchy, composing them
in the client (user) class, and making each of them selectable during
runtime.
389
Dynamic mesh handling
INFO
Template Method pattern:
A virtual function is used to implement the algorithm and the (base)
class may provide a default algorithm implementation. As a result, the
derived classes implement the variety of alternative algorithms, resulting
in a per-algorithm branching in the hierarchy.
puting the transformation and nothing else. Additionally that, the motion
functions could generally be combined: e.g., an oscillating linear motion
with constant rotation. In that case, the Template Method pattern breaks
down, as it relies on using multiple inheritances with the dynamic mesh
class.
The solidBodyMotion dynamic mesh transforms either all points of the
mesh, or a specific part (i.e. a cellZone), using the same septernion de-
fined by the user selected solidBodyMotionFunction. If no cellZone
is specified, all mesh points are moved using the same septernion; hence
a uniform motion is achieved. If a cellZone is specified in the dictio-
nary, only the points of the cells in the cellZone are moved accordingly.
This comes in handy when a body rotates in a rotor-stator configuration,
using sliding interfaces (AMI/GGI). In addition to the solidBodyMo-
tionFvMesh, there is the multiSolidBodyMotionFvMesh, which basi-
cally does the same as solidBodyMotionFvMesh, but allows for multiple
motions to be either superimposed or act upon different cellZones. The
latter is very important for applications where two rotors rotate, using dif-
ferent cellZones and AMI/GGI interfaces. The motion itself is applied
to all relevant mesh points directly, using fvMesh::movePoints, so no
extra equations are solved, which makes this a fairly fast approach.
INFO
The cellZone-based selection of mesh points can be used for a rotor-
stator mesh motion configuration that involves a sliding interface.
Mesh deformation
390
Software design
INFO
Mesh quality depends on the numerical method chosen for equation
discretization. For the FVM, the two most important mesh-related dis-
cretization errors are the non-orthogonality and the skewness error ([3],
[6]).
∇· (γ∇d) = 0, (13.1)
γ = γ(r). (13.2)
In equation (13.2), r is the distance between the mesh point and the
mesh boundary, and the coefficient function is prescribed in a way that
makes the coefficient decrease with the distance from the boundary. Equa-
tion (13.1) is approximated using the FVM in OpenFOAM. In that case,
the fields solved are cell-centered, with the boundary fields stored at face
centers. In order to calculate the displacements in the mesh points (cell
corner points) using the FVM, an interpolation from cell-centered values
to the mesh points needs to be performed. Alternatively, in foam-extend,
solution to the equation 13.1, can be approximated using the Finite Ele-
ment Method (FEM).
391
Dynamic mesh handling
dynamicFvMesh
+update(): bool
dynamicMotionSolverFvMesh motionSolver
-motionPtr_: autoPtr<motionSolver>
newPoints() : tmp<pointField>
+update(): bool solve() : void
392
Software design
if (foundObject<volVectorField>("U"))
{
volVectorField& U =
const_cast<volVectorField&>(lookupObject<volVectorField>("U"));
U.correctBoundaryConditions();
}
return true;
}
INFO
Although we have omitted some details of mesh motion in OpenFOAM
in this section, the available information should be sufficient for under-
standing and extending mesh motion.
393
Dynamic mesh handling
displacementMotionSolver
displacementLaplacianFvMotionSolver
Figure 13.4: Laplacian finite volume based motion solver class diagram.
Foam::solve
(
fvm::laplacian
(
diffusivityPtr_->operator()(),
cellDisplacement_,
"laplacian(diffusivity,cellDisplacement)"
)
);
and the point displacements are then interpolated using Inverse Distance
Weighted (IDW) interpolation from the cell centers, to the cell corner
points (mesh points) within the curPoints member function:
volPointInterpolation::New(fvMesh_).interpolate
(
cellDisplacement_,
394
Software design
pointDisplacement_
);
INFO
A new IDW interpolation object is allocated each time when the cur-
Points is executed - when the mesh is deformed using the FVM based
motion solver.
395
Dynamic mesh handling
396
Software design
fvMesh
dynamicFvMesh
+update(): bool
dynamicRefineFvMesh
INFO
The collaboration between the dynamicRefineFvMesh and fvMesh is
achieved by calling the fvMesh::updateMesh member function. The
mapping of fields may be a complex algorithm to implement. When
a new class for topological changes is implemented, it may be easier
to re-use this algorithm by adhering to the class relationship shown in
figure 13.5
397
Dynamic mesh handling
INFO
Adaptive refinement of hexahedral unstructured meshes in OpenFOAM
is not based on an octree data structure. The mesh topology is changed
directly and stored in the new mesh, enabling the application of exist-
ing discretization operators and schemes on the topologically modified
mesh.
Any solver that contains DyMFoam in its name can use any dynamic
mesh available in OpenFOAM. The prerequisite is the dynamicMeshDict,
which must be present in the constant folder in the simulation case,
and be configured properly.
Two examples are provided in this section and each relates to the mesh
motion types described in the previous section: global mesh motion using
solidBodyMotionFvMesh and mesh deformation based on dynamicMo-
tionSolverFvMesh. Both examples utilize the same base mesh, which
398
Using dynamic meshes
ZMAX
BOX
XMIN
XMAX
ZMIN
Setting up OpenFOAM cases that use the dynamic mesh can be tedious
work, especially if the flow solver takes up a lot of computational time
during a time step. The tool moveDynamicMesh avoids waiting for a
long time to check if the case is configured correctly: it performs all the
steps the solvers make when using dynamic meshes without the expen-
sive flow solution step. Hence, only mesh. update() is executed inside
the time loop, which triggers the mesh modifications performed by the
runtime selected dynamic mesh class. This approach works well, as long
as the motion is not dependent on any data that results from the flow
simulation.
INFO
Extending a simulation case with dynamic mesh operations is faster
with the moveDynamicMesh application because it requires shorter ex-
ecution times than the flow solver.
399
Dynamic mesh handling
motionSolverLibs ("libfvMotionSolvers.so");
solver solidBody;
solidBodyMotionFunction linearMotion;
velocity (1 0 0);
400
Using dynamic meshes
dimensions [0 1 0 0 0 0 0];
boundaryField
{
"(XMIN|XMAX|YMIN|YMAX|ZMIN|ZMAX)"
{
type fixedValue;
value uniform (0 0 0);
}
HULL
{
type solidBodyMotionDisplacement;
solidBodyMotionFunction linearMotion;
linearMotionCoeffs
{
velocity (1 0 0);
}
}
}
Rather than defining a motion only for the box itself, a velocity is as-
signed to the outer boundaries as well, in order to illustrate the capa-
bilities of the dynamicMotionSolverFvMesh. The box has the same
velocity as for the previous tutorial, but it is reduced to 0.75 for the
remaining patches. Of course, this setup has a practically limited time of
execution, as the box will compress the cells too much with its motion.
Still, this setup serves as an example of how various motions can be
assigned to different boundaries.
401
Dynamic mesh handling
motionSolverLibs ("libfvMotionSolvers.so");
solver displacementLaplacian;
displacementLaplacianCoeffs
{
diffusivity inverseDistance (HULL);
}
402
Developing with dynamic meshes
INFO
When extending OpenFOAM solvers, and in general when program-
ming, reduce the amount of copied source code as much as possible.
403
Dynamic mesh handling
algorithms for coupled PDEs. Suppose the entire solver folder is copied,
and the modification to the solver is minor and placed in a single file.
In that case, it becomes difficult to find out the difference between the
new and the original solver. Therefore, the ”common” practice of blindly
copying the entire solver folder when writing a new OpenFOAM solver
is, in the long run, inefficient.
The first step when programming a new OpenFOAM solver is, therefore,
to create the solver folder and copy only the necessary files to the solver
folder
?> mkdir scalarTransportDyMFoam && cd scalarTransportDyMFoam
?> cp $FOAM_SOLVERS/basic/scalarTransportFoam/scalarTransportFoam.C .
?> cp -r $FOAM_SOLVERS/basic/scalarTransportFoam/Make .
EXE_LIBS = \
-lfiniteVolume \
-lfvOptions \
-lmeshTools \
-ldynamicMesh \
-ldynamicFvMesh \
-lsampling
The first ”include” line leads to the folder of the OpenFOAM solver using
the $FOAM_SOLVER environment variable. If the folder of the original
solver contains sub-folders that contain other necessary files, those can
be included as well. The other lines listed in the above code snippet are
there for including dynamic mesh headers and loading dynamic mesh
libraries.
Since this example creates a new solver, the scalarTransportFoam.C
file should be renamed to scalarTransportDyMSolver.C, following the
OpenFOAM convention for solvers with dynamic mesh support. Then,
404
Developing with dynamic meshes
Figure 13.8: Test case for scalar transport with mesh refinement. Red
cells are initialized with the value 100, and blue cells with
value 0, the wireframe sphere is an iso-surface with the iso-
value of 50.
Figure 13.8 shows the initial conditions and the domain geometry for
the test case used with this example. The test case consists of a cubical
domain with an initial field preset as a sphere in the corner of the domain
and a constant velocity field used to transport the field in the direction
of the spatial diagonal.
405
Dynamic mesh handling
return 0;
The dynamic mesh header files and respective library binary code must
be made available during the build process for the new solver to work. To
include the headers and link the libraries, the Make/options file needs
to be modified by adding the following ”include” lines:
-I$(LIB_SRC)/dynamicMesh/lnInclude \
-I$(LIB_SRC)/dynamicFvMesh/lnInclude \
406
Developing with dynamic meshes
in the case folder. Figure 13.9 shows the transported spherical scalar
field T shown in figure 13.8 with dynamic adaptive mesh refinement. The
advection of T is diffusive, and the mesh refinement and de-refinement
resolve the diffusive region and follow the transported field.
INFO
By refining the mesh, new cell faces are created, which requires the
modification of the volumetric flux field. The volume conservation,
numerical boundedness as well as solution stability depend strongly on
the volumetric flux values.
EXERCISE
The refinement criterion used for this test case refines the interior of
the transported sphere uniformly. An interesting exercise would be to
apply a refinement criterion that would follow the jump in values of T.
This would decrease computational costs and increase accuracy.
407
Dynamic mesh handling
13.4 Summary
The overview of the design of different dynamic mesh classes from the
representative mesh handling categories, and the descriptions of the dy-
namic mesh usage, coupled with the extension of a solver for dynamic
mesh handling in OpenFOAM should provide a good starting point for
developing dynamic mesh handling in OpenFOAM. More complex tasks
such as developing lower-level topological changes and their agglomer-
ation into entirely new dynamic mesh classes in OpenFOAM are more
complex and are out of scope for this book.
Further reading
[1] Erich Gamma et al. Design patterns: elements of reusable object-
oriented software. Addison-Wesley Longman Publishing Co., Inc.,
1995. isbn: 0-201-63361-2.
[2] R. Goldman. Rethinking Quaternions: Theory and Computation.
Morgan & Claypool, 2010.
[3] Jasak. “Error Analysis and Estimatino for the Finite Volume Method
with Applications to Fluid Flows”. PhD thesis. Imperial College of
Science, 1996.
408
Summary
409
14
Conclusions
Although many core parts of OpenFOAM are covered in this book, there
are still many interesting topics left open, such as the implementation of
new FVM schemes and operators, parallel programming with Open MPI
in OpenFOAM, developing new Euler-Lagrange models and methods for
particle-laden and spray flows, development of new dynamic mesh classes,
developing new constitutive laws for compressible flows, and much more.
Nevertheless, the software design of those OpenFOAM elements resem-
bles the content covered in this book and is based on the repetition of
a handful of software design patterns in the C++ programming language
outlined throughout the text. A thorough understanding of the material
presented here and the basic understanding of software design patterns in
C++ - and not avoiding the hands-on work on the examples and exercises
- presents a solid basis for further developing OpenFOAM.