0% found this document useful (0 votes)
97 views33 pages

Inlets, Outlets, and Post-Processing For Modelling Open-Channel Flow With The Volume of Fluid Method

This document provides a tutorial on modeling open-channel flow using the volume of fluid (VOF) method in OpenFOAM. It discusses (1) the theory of the VOF method and how it is implemented in OpenFOAM solvers like interFoam, (2) existing tools in OpenFOAM for modeling open-channel flow, and (3) developing new boundary conditions and functions to extract the depth and depth-averaged velocity, which are important for comparing VOF results to 1D hydraulic models. The tutorial uses a weir overflow case to demonstrate the functionality and test the new implementations.

Uploaded by

Feng Zhao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
97 views33 pages

Inlets, Outlets, and Post-Processing For Modelling Open-Channel Flow With The Volume of Fluid Method

This document provides a tutorial on modeling open-channel flow using the volume of fluid (VOF) method in OpenFOAM. It discusses (1) the theory of the VOF method and how it is implemented in OpenFOAM solvers like interFoam, (2) existing tools in OpenFOAM for modeling open-channel flow, and (3) developing new boundary conditions and functions to extract the depth and depth-averaged velocity, which are important for comparing VOF results to 1D hydraulic models. The tutorial uses a weir overflow case to demonstrate the functionality and test the new implementations.

Uploaded by

Feng Zhao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 33

Cite as: Leakey, S.

: Inlets, outlets, and post-processing for modelling open-channel flow with the volume of
fluid method. In Proceedings of CFD with OpenSource Software, 2019, Edited by Nilsson. H.,
http://dx.doi.org/10.17196/OS_CFD#YEAR_2019

CFD with OpenSource software


A course at Chalmers University of Technology
Taught by Håkan Nilsson

Inlets, outlets, and post-processing for


modelling open-channel flow with the
volume of fluid method

Developed for OpenFOAM-v1906


Optional: swak4Foam

Author: Peer reviewed by:


Shannon Leakey Anonymous
Newcastle University Muye Ge

Licensed under CC-BY-NC-SA, https://creativecommons.org/licenses/

Disclaimer: This is a student project work, done as part of a course where OpenFOAM and some
other OpenSource software are introduced to the students. Any reader should be aware that it
might not be free of errors. Still, it might be useful for someone who would like learn some details
similar to the ones presented in the report and in the accompanying files. The material has gone
through a review process. The role of the reviewer is to go through the tutorial and make sure that
it works, that it is possible to follow, and to some extent correct the writing. The reviewer has no
responsibility for the contents.

December 17, 2019


Learning outcomes

The reader will learn:

How to use it:

• how to copy and run an open-channel flow tutorial


• how to visualise the results with the ParaView filters clip, extract block, glyph, and contour
• how to extract the free surface with a standard surface function object

• how to extract the depth-averaged velocity with a swak4Foam function object


The theory of it:
• the basics of 1D hydraulic modelling
• the governing equations of the volume of fluid method

How it is implemented:
• the surface-capturing approach taken in the solvers interFoam and interIsoFoam
• the approach taken in the boundary condition variableHeightFlowRateInletVelocity
How to modify it:

• how to create a depth boundary condition for the velocity at outlets


• how to create a depth-averaged velocity function object without using swak4Foam

1
Prerequisites

The reader is expected to know the following in order to get maximum benefit out of this report:
• Linux commands
• OpenFOAM workflow
• Vector calculus

• C++ syntax

2
Contents

1 Introduction 4

2 Volume of fluid method 6


2.1 Integral averages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Governing equations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 interFoam . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 interIsoFoam . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3 Existing tools 11
3.1 variableHeightFlowRateInletVelocity . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 isoSurface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3 swakExpressionAverageDistribution . . . . . . . . . . . . . . . . . . . . . . . . . 16

4 Depth at outlet 19
4.1 Copying a boundary condition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.2 Modification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.3 Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

5 Depth-averaged velocity 26
5.1 Creating a function object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.2 Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

3
Chapter 1

Introduction

Open-channel flows are usually simulated with 1D hydraulic models. These models are based on the
1D shallow water equations, which Toro [10] writes in conservative form as
   
h hu
+ =0 (1.1)
hu t hu2 + 21 gh2 x

where h is the depth of the water across the y-direction (m), u the depth-averaged velocity in the
x-direction (m/s), that is,
1 y0 +h
Z
u= ux dy, (1.2)
h y0
and g the gravitational acceleration (m/s2 ). This simplification of the fluid dynamics means that
not all open-channel flows can be modelled by the 1D shallow water equations. What if we want
to model the impact of a hydraulic structure—say a sluice gate or weir—on the open-channel flow?
Place a weir in the middle of the channel and the movement of the water up and over the weir will
violate the hydrostatic pressure assumption, meaning the 1D shallow water equations are no longer
valid. To solve such a problem, we might turn to OpenFOAM’s volume of fluid (VOF) solvers,
which would allow us to resolve the geometry of the hydraulic structure and its effects in 2D or 3D.
However, if we want to compare or couple the VOF results with those from a 1D hydraulic model,
we will encounter two major problems.
The first problem lies in the boundary conditions. Generally, in hydraulic models, the discharge
q = hu is set at the inlet and the depth h at the outlet. This allows backwater from any obstacles
beyond the computational outlet to influence the solution within the domain. For example, this
backwater could come from tides or a bridge blockage downstream. In OpenFOAM, while there is
an inlet boundary condition that allows the discharge to be set, there is currently no outlet boundary
condition that allows the depth to be set. Flora [5] provided a workaround by applying an average
velocity across the whole outlet patch, but a programmed solution would be more convenient and
generalisable to transient cases.
The second problem lies in post-processing. It is useful to be able to extract the values of
the depth h and depth-averaged velocity u from the VOF results as these are the quantities of
interest in 1D hydraulic models. While the depth is simple to extract with either a ParaView
filter or standard surface function object, the depth-averaged velocity is more complicated. This is
because the approaches of 1D hydraulic models and VOF are fundamentally different. Gschaider
[6, 7] provided a workaround with swak4Foam but, again, a programmed solution would be more
convenient.
In this tutorial, we will explore the theory and implementation of VOF in Chapter 2 and the
usage and implementation of the existing functionality for modelling open-channel flow in Chapter
3. We will then move on to program our own depth outlet boundary condition in Chapter 4 and
depth-averaged velocity function object in Chapter 5. Chapters 2 and 3 are not essential for making
the implementations in Chapters 4 and 5; however, they contain useful background knowledge. The

4
CHAPTER 1. INTRODUCTION

weirOverflow case is used throughout the tutorial to demonstrate functionality and test our new
programmed solutions. To start, source the OpenFOAM environment and then enter the commands
> run
> cp -r $FOAM_TUTORIALS/multiphase/interFoam/RAS/weirOverflow .
> cd weirOverflow
> ./Allrun
This will copy and run the weirOverflow case for the interFoam solver. Enter the command
> paraFoam

to view the simulation results in ParaView. Click the green apply button in the side panel
and view the alpha.water field with in the top toolbar. Scroll down the side panel
and check and then parallel projection , which is a good idea for 2D
cases such as this. Press the play button in the top toolbar. At the end of the simulation, the
alpha.water field should resemble Figure 1.1.

Figure 1.1: The alpha.water field at t = 60s for the weirOverflow tutorial. Flow is from left to right.

5
Chapter 2

Volume of fluid method

The alpha.water field displayed in Figure 1.1 is transported by OpenFOAM’s VOF solver interFoam.
Another option is the solver interIsoFoam of Roenby et al. [8]. These two solvers have different
approaches to capturing the air-water interface, but both approaches are derived from the same
governing equations. The theory and implementation is outlined briefly below.

2.1 Integral averages


The idea of VOF is to use a characteristic (or indicator, or colour) function χ, defined as
(
1 if there is water at point x at time t
χ(x, t) = (2.1)
0 otherwise.

If a computational cell Ω contains both air and water, then it will have an average value of χ
somewhere between 0 and 1. Formally, define the phase fraction α to be the integral average of χ,
that is, Z
1
α(Ω, t) = χ = χ dV (2.2)
|Ω| Ω
where dV means that we are integrating over a volume. Then α = 0 for cells filled with air, α = 1
for cells filled with water, and 0 < α < 1 for cells containing a free surface.
The velocity vector field u is defined over the whole domain, not just in the parts that are water.
If we want the velocity of the water in a particular computational cell, we have to consider the phasic
average uwater , defined by Drew [4] to be
Z
uχ 1
uwater = = uχ dV (2.3)
α |Ω|α Ω
and similarly for air:
u(1 − χ)
Z
1
uair = = u(1 − χ) dV. (2.4)
1−α |Ω|(1 − α) Ω
Then, for a particular computational cell,

u = αuwater + (1 − α)uair . (2.5)

2.2 Governing equations


We will model the water and air as incompressible fluids, which gives us the transport equation
∂χ
+ ∇ · (uχ) = 0. (2.6)
∂t

6
2.3. INTERFOAM CHAPTER 2. VOLUME OF FLUID METHOD

See Drew [4] for details on how to derive Equation 2.6 from first principles using test functions. To
make Equation 2.6 in terms of α, we take the integral average over the computational cell Ω,
Z Z
1 ∂χ 1
dV + ∇ · (uχ) dV = 0, (2.7)
|Ω| Ω ∂t |Ω| Ω

and then swap the order of averaging and differentiating as in Drew [4] to get
 Z   Z 
∂ 1 1
χ dV + ∇ · uχ dV = 0. (2.8)
∂t |Ω| Ω |Ω| Ω

Substitute Equation 2.2 into the first term on the left-hand side and Equation 2.3 and into the
second to get what Rusche [9, p.100] calls the conditionally averaged continuity equation

∂α
+ ∇ · (uwater α) = 0. (2.9)
∂t
As noted by Cifani et al. [2], OpenFOAM needs an equation in terms of u, not uwater . To get such
an equation, add ∇ · (uα) to both sides of Equation 2.9,

∂α
+ ∇ · (uwater α) + ∇ · (uα) = ∇ · (uα), (2.10)
∂t
and then rearrange to get
∂α
+ ∇ · (uα) = ∇ · (uα − uwater α). (2.11)
∂t
Multiply both sides of Equation 2.5 by α and substitute this into the right-hand side of Equation
2.11,
∂α
+ ∇ · (uα) = ∇ · (α2 uwater + α(1 − α)uair − uwater α), (2.12)
∂t
and finally rearrange to get the equation that Rusche [9, p.117] notes is in conservative form,

∂α
+ ∇ · (uα) + ∇ · ((uwater − uair )α(1 − α)) = 0. (2.13)
∂t

2.3 interFoam
Algebraic VOF is based on Equation 2.13. Deshpande et al. [3] and Cifani et al. [2] say that the
OpenFOAM solver interFoam approximates the relative velocity ur = uwater − uair at the cell
interface f by !
(ur )f = n̂f min cα |uf |, max
S |uf | . (2.14)
f∈ Ω

The right-hand side of Equation 2.14 has two parts: the vector n̂f , multiplied by the scalar min(...).
The vector part n̂f = (∇α)f /|(∇α)f | is the normalised gradient of α at the cell interface, where
the gradient (∇α)f is approximated using the central difference between the owner and neighbour
cells. As the vector (∇α)f approximates the direction of greatest change of α, the vector n̂f is
perpendicular to the air-water interface and pointing towards the water.
The scalar part min(...) determines how great the magnitude of (ur )f should be and does not
affect its direction. It contains the factor cα , which describes how much compression we want.
Generally cα is taken to be equal to 1, which means that the relative velocity (ur )f has exactly the
same magnitude as the cell interface velocity uf .
An examination of the source code suggests that Equation 2.14 is at least partially implemented
in the solver. Before looking at the source code, first define the flux at face f to be

ϕf = uf · Sf . (2.15)

7
2.4. INTERISOFOAM CHAPTER 2. VOLUME OF FLUID METHOD

This means that Equation 2.14 is equivalent to


!
|ϕf | |ϕf |
(ϕr )f = (n̂f · Sf ) min cα , max . (2.16)
|Sf | f ∈ Ω |Sf |
S

Now looking at the source code, the command


> sed -n -e 568,569p -e 597p
,→ $FOAM_SOLVERS/multiphase/multiphaseInterFoam/multiphaseMixture/multiphaseMixture.C
prints out the lines that implement Equation 2.16 for multiphaseInterFoam, that is,

surfaceScalarField phic(mag(phi_/mesh_.magSf()));
phic = min(cAlpha*phic, max(phic));
surfaceScalarField phir(phic*nHatf(alpha, alpha2));
where the definition for nHatf can be found with

> sed -n -e 121,122p -e 131p


,→ $FOAM_SRC/transportModels/interfaceProperties/interfaceProperties.C
which prints
// Face unit interface normal
surfaceVectorField nHatfv(gradAlphaf/(mag(gradAlphaf) + deltaN_));
nHatf_ = nHatfv & Sf;

However, it is not as simple for interFoam. The command


> sed -n -e 59p -e 162p $FOAM_SOLVERS/multiphase/VoF/alphaEqn.H
prints out the lines that implement Equation 2.16 for interFoam, that is,
surfaceScalarField phic(mixture.cAlpha()*mag(phi/mesh.magSf()));
surfaceScalarField phir(phic*mixture.nHatf());
Unless there is something else buried in the depths of lines 60-161 of VoF/alphaEqn.H, it seems that
interFoam calculates
|ϕf |
(ϕr )f = (nf · Sf )cα . (2.17)
|Sf |
not Equation 2.16.

2.4 interIsoFoam
Geometric VOF is based on a different manipulation of Equation 2.7. Using Gauss’s theorem, we
can manipulate Equation 2.7 into
Z
∂α 1
+ (u · n)χ dS = 0 (2.18)
∂t |Ω| ∂Ω
Z
∂α 1
=− (u · n)χ dS. (2.19)
∂t |Ω| ∂Ω

where ∂Ω is the boundary of the computational cell Ω and dS means that we are integrating over a
surface. Integrate Equation 2.19 over the time interval [t0 , t1 ] to get
Z t1 Z t1 Z 
∂α 1
dt = − (u · n)χ dS dt. (2.20)
t0 ∂t |Ω| t0 ∂Ω

8
2.4. INTERISOFOAM CHAPTER 2. VOLUME OF FLUID METHOD

The Fundamental Theorem of Calculus says that


Z t1
∂α
dt = α(Ω, t1 ) − α(Ω, t0 ), (2.21)
t0 ∂t

which can be substituted into the left-hand side of Equation 2.20 to get
Z t1 Z 
1
α(Ω, t1 ) − α(Ω, t0 ) = − (u · n)χ dS dt (2.22)
|Ω| t0 ∂Ω
Z t1 Z 
1
α(Ω, t1 ) = α(Ω, t0 ) − (u · n)χ dS dt. (2.23)
|Ω| t0 ∂Ω

Finally, split up the cell boundary ∂Ω into faces f ,

1 X t1
Z Z 
α(Ω, t1 ) = α(Ω, t0 ) − (u · n)χ dS dt. (2.24)
|Ω| t0 f
f ∈∂Ω

Roenby et al. [8] note that Equation 2.24 is still exact. However, approximations must be introduced
for practical purposes. Indeed, to update the volume fraction α in the computational cell Ω from
time t0 to time t1 using Equation 2.24, we need:
1. The value of α for the computational cell Ω at time t0
2. The geometry of the computational cell Ω and its boundary faces f
3. The normal vector field n of the boundary faces f
4. The characteristic function χ, a scalar field
5. The velocity vector field u
While requirements 1-3 are known, requirements 4-5 must be approximated.
For requirement 4, approximating the characteristic function χ is equivalent to guessing the
location of the water-air interface. Roenby et al. [8] note that, generally, we assume that the local
curvature of the fluid is larger than the mesh size, and so approximate each cell’s water-air interface
by a plane. In the OpenFOAM solver interIsoFoam, this is done using isosurfaces.
For requirement 5, if the velocity vector field u is assumed to be constant within each cell and
time step, it can be brought out of any time or space integral. This is done in interIsoFoam to give
Z t1 Z  Z t1 Z 
ϕf
(u · n)χ dS dt ≈ χ dS dt (2.25)
t0 f |Sf | t0 f

for the second term in the right-hand side of Equation R2.24. The interaction between u and χ
is included by considering how the submerged face area f χ dS changes over time as the upwind
isosurface moves with u.
Equation 2.25 can be found in the source code. Indeed, interIsoFoam uses functions in the file
> gedit $FOAM_SRC/finiteVolume/fvMatrices/solvers/isoAdvection/isoAdvection/isoAdvection.C
Note in particular line 853, which calls the function timeIntegratedFlux, found in lines 146-383
of the same file. For each face f , this function calculates dVf_, which we will see is simply the
right-hand side of Equation 2.25. It has to be calculated for all internal and boundary faces. The
loop over internal faces calls the function timeIntegratedFaceFlux in line 284,
dVfIn[facei] = isoCutFace_.timeIntegratedFaceFlux
and the loop over boundary faces calls the function timeIntegratedFaceFlux in line 357,
dVfb[patchi][patchFacei] = isoCutFace_.timeIntegratedFaceFlux

9
2.4. INTERISOFOAM CHAPTER 2. VOLUME OF FLUID METHOD

Close this file and then open the file containing the function timeIntegratedFaceFlux with
> gedit $FOAM_SRC/finiteVolume/fvMatrices/solvers/isoAdvection/isoCutFace/isoCutFace.C
Now timeIntegratedFaceFlux, defined in lines 340-483 of this file, has in lines 417-452,
if (nShifts == 2)
{
dVf = phi/magSf*timeIntegratedArea(fPts, pTimes, dt, magSf, Un0);
}
else if (nShifts > 2)
{
...
for (label pi = 0; pi < nPoints; pi++)
{
...
dVf += phi_tri/magSf_tri
*timeIntegratedArea
(
fPts_tri,
pTimes_tri,
dt,
magSf_tri,
Un0
);
}
}
which calls timeIntegratedArea. We will see that timeIntegratedArea gives the value
Z t1 Z 
χ dS dt, (2.26)
t0 f

meaning that dVf_ is indeed given by the right-hand side of Equation 2.25. The function timeIntegratedArea
is found in lines 486-622 of the current file. In particular, lines 579-597,
forAll(sortedTimes, ti)
{
const scalar newTime = sortedTimes[ti];
// New face-interface intersection line
DynamicList<point> newFIIL(3);
cutPoints(fPts, pTimes, newTime, newFIIL);

// quadrilateral area coefficients


scalar alpha = 0, beta = 0;
quadAreaCoeffs(FIIL, newFIIL, alpha, beta);
// Integration of area(t) = A*t^2+B*t from t = 0 to 1
tIntArea += (newTime - time)*
(initialArea + sign(Un0)*(alpha/3.0 + 0.5*beta));
// Adding quad area to submerged area
initialArea += sign(Un0)*(alpha + beta);

FIIL = newFIIL;
time = newTime;
}
show how the interval [t0 , t1 ] is split into sub-intervals such that the face-interface intersection line
sweeps along a quadrilateral during each sub-interval. Full details can be found in Roenby et al. [8].
Close the file.

10
Chapter 3

Existing tools

This chapter will cover the existing functionality for modelling open-channel flow in OpenFOAM,
focusing on usage but exploring theory and implementation along the way.

3.1 variableHeightFlowRateInletVelocity
To compare VOF results with 1D hydraulic modelling results, the boundary conditions used in the
two simulations need to be comparable. In 1D hydraulic models, the discharge q = hu is usually set
at the upstream boundary. However, as outlined in the previous section, VOF takes a fundamentally
different approach to defining the surface of the water than 1D hydraulic models. Nonetheless, there
is an analogous inlet boundary condition used in the weirOverflow tutorial. This section will
describe its usage and implementation.
First, we will apply some ParaView filters so that the boundary conditions can be viewed better.
We want to focus on the water, so click on the clip filter in the top toolbar. Then apply the
settings in Figure 3.1 in the side panel and click the green apply button. This shows us the cells
that contain more than 50% water. View the clip by the velocity field with in the
top toolbar.

Figure 3.1: Settings for clipping the alpha.water field.

Now we want to extract only the boundary patches. Click back onto weirOverflow.foam in the
side panel so that it is highlighted like . In the side panel, check all the mesh regions
with , and then press the green apply button again. Click on the filters menu , then
alphabetical, and extract block. Choose the settings in Figure 3.2 and click the green apply button.
Now the ExtractBlock1 filter is like weirOverflow.foam but only with the boundary patches we
have chosen.

Figure 3.2: Settings for extracting the boundary patches.

11
3.1. VARIABLEHEIGHTFLOWRATEINLETVELOCITY CHAPTER 3. EXISTING TOOLS

We want to view the velocities as arrows along these patches. In the filters alphabetical menu,
choose cell centres, and then click the green apply button. Then click on the glyph filter . In the
side panel, choose the scale mode to be vector with a scale factor of 1. Press
the green apply button and arrows should be visible. Play with the settings on the various filters
until it looks helpful.
For example, to obtain the view in Figure 3.3, stay in the side panel for the glyph filter. Change
the glyph type to a 2D arrow with , ensure that glyphs are shown for each
patch face with , and increase the arrow width to 2 with .
Then change the colouring to solid colour in the top toolbar. Choose the colour to
be black with the edit colour map button . Click the green apply button. Now click back onto
weirOverflow.foam in the side panel so that it is highlighted like and click the eye
to make this layer visible. In the top toolbar, change the colouring to solid colour with
and view the wireframe with . In the file menu, save the state so that it can be used
again later.

Figure 3.3: Velocities on the water phase and boundary patches at t = 60s. Flow is from left to right.

Figure 3.3 shows the boundary conditions of interest. Print out the file that defines the boundary
conditions for velocity with
> cat 0.orig/U

We see that outlet uses the boundary condition zeroGradient. Meanwhile, inlet uses the
boundary condition variableHeightFlowRateInletVelocity, where flowRate comes from the
include/initialConditions file. Run the command
> cat 0.orig/include/initialConditions
and see that the value of inletFlowRate is 75. The question is: seventy-five what? We will have
to look at the documentation built into the source code. Find the location of the relevant directory
with
> find $FOAM_SRC -name variableHeightFlowRateInletVelocity
The output of this command should be $FOAM_SRC/finiteVolume/fields/fvPatchFields/derived,
so run

12
3.1. VARIABLEHEIGHTFLOWRATEINLETVELOCITY CHAPTER 3. EXISTING TOOLS

> cd $FOAM_SRC/finiteVolume/fields/fvPatchFields/derived
> ls
> cd variableHeightFlowRateInletVelocity
> ls

which shows how each boundary condition has its own folder which contains two files. The header
file contains the documentation. Run
> gedit variableHeightFlowRateInletVelocityFvPatchVectorField.H
and look in the Usage section of the commented part. It says that flowRate is the volumetric flow
rate in m3 /s. This is slightly different to the quantity q = hu which is measured in m2 /s. However,
in 2D cases such as weirOverflow the difference is only that q needs to be multiplied by the channel
width to get the total flow rate Q. In fact, as weirOverflow/system/blockMeshDict tells us that
the channel width is 1, the difference is only in dimensions. Close this file and then open the other
one with
> gedit variableHeightFlowRateInletVelocityFvPatchVectorField.C

Scroll down to the member function


void Foam::variableHeightFlowRateInletVelocityFvPatchVectorField
::updateCoeffs()
{

This is where the boundary condition is implemented. First, it checks whether the boundaries have
already been updated with
if (updated())
{
return;
}
If they have, then the function stops here. Next,
scalarField alphap =
patch().lookupPatchField<volScalarField, scalar>(alphaName_);

looks up the values of the boundary faces of the volScalarField called alphaName_ on the current
patch. The value of alphaName_ is looked up from a dictionary—have another look at the inlet
dictionary in the weirOverflow/0.orig/U file. The alpha entry is what is being looked up and, in
this case, it is alpha.water. The lines
alphap = max(alphap, scalar(0));
alphap = min(alphap, scalar(1));
make sure that the alphap field is bounded within physical values. Next, the flowRate_ is a function
of time. In the case of weirOverflow, it is a constant function. The lines
const scalar t = db().time().timeOutputValue();
scalar flowRate = flowRate_->value(t);

find the value of flowRate_ for the current time. There is a lot going on in the next line,
// a simpler way of doing this would be nice
scalar avgU = -flowRate/gSum(patch().magSf()*alphap);
First note that the flow rate Q is equal to product of the average velocity and the cross-sectional
area of the alphap phase,
Q = Au (3.1)

13
3.2. ISOSURFACE CHAPTER 3. EXISTING TOOLS

So the average velocity we want to set is equal to flowRate divided by the cross-sectional area.
The minus sign is because we want the velocity to be positive into the domain and the face normal
vectors are pointing out of the domain, away from the cells that own the faces. The quantity
patch().magSf() is the area of the boundary faces and, multiplied by alphap, it will give the cross-
sectional area of the alphap phase, A. The function gSum() means that this is summed over all the
faces in each processor, and so it works even in parallel cases. So Q/A gives u.
Finally, note that, unlike the vector velocity, this average velocity is a scalar. This means that
avgU needs to be turned into a vector before it can be set at the boundary field. It is multiplied by
the normal vector with
vectorField n(patch().nf());

operator==(n*avgU*alphap);
which also multiplies by alphap to pick out the water phase. The member function ends with
fixedValueFvPatchField<vector>::updateCoeffs();
}
To summarise, the boundary condition variableHeightFlowRateInletVelocity calculates an
average velocity by dividing the desired flow rate by the current cross-sectional area of the water
phase. This average velocity is then applied across the water phase on the boundary patch. Indeed,
Figure 3.3 shows a uniform velocity across boundaries of cells with α = 1, zero velocity across
boundaries of cells with α = 0, and an intermediate velocity for cells containing the air-water
interface. Consequently, our requirement for an upstream boundary condition that sets the discharge
is met.

3.2 isoSurface
As hydraulic models work with the depth h rather than the volume fraction α, it would be useful
to be able to post-process the results so that the depth h can be extracted. This can be done
with ParaView, approximating the location of the air-water interface by the α = 0.5 contour. With
weirOverflow.foam selected so that it is highlighted like , select the contour filter .
Use the settings in Figure 3.4 and click the green apply button. The results should be as in Figure
3.5.

Figure 3.4: Settings for extracting the air-water interface.

However, it would be better to have these results in a file that could then be plotted with other
software. To do get such a file with a function object, first open up the controlDict with
> run
> cd weirOverflow
> gedit system/controlDict

14
3.2. ISOSURFACE CHAPTER 3. EXISTING TOOLS

Figure 3.5: Air-water interface from the ParaView filter contour. Flow is from left to right.

and add to the end of the file the entry


functions
{
surfaces
{
libs ("libsampling.so");
type surfaces;
writeControl outputTime;
writeInterval 1;
interpolationScheme cellPoint;
surfaceFormat raw;
surfaces
(
freeSurface
{
type isoSurface;
isoField alpha.water;
isoValue 0.5;
interpolate true;
}
);
fields ( alpha.water );
}
};
Then use the command
> interFoam -postProcess
to apply this function object without running the solver again. The results are in the postProcessing/surfaces
directory. Open gnuplot with the command
> gnuplot

15
3.3. SWAKEXPRESSIONAVERAGEDISTRIBUTION CHAPTER 3. EXISTING TOOLS

then, when in gnuplot, run the commands


> set xlabel "X Axis"
> set ylabel "Y Axis"
> unset key
> plot "postProcessing/surfaces/60/alpha.water_freeSurface.raw"
to view the free surface at t = 60s. It should look like Figure 3.6. To quit gnuplot, type
> q

Figure 3.6: Air-water interface from the surface function object. Flow is from left to right.

3.3 swakExpressionAverageDistribution
Recall that the depth-averaged velocity at the point x0 is given by
Z y0 +h
1
u(x0 ) = ux (x0 , y) dy. (3.2)
h y0

where h is the depth. As the approaches of 1D hydraulic models and VOF are fundamentally
different, the depth-averaged velocity is not straightforward to extract with a function object. To
see this, consider the domain of integration of Equation 3.2, the interval [y0 , y0 + h]. When post-
processing a VOF simulation, this interval will intersect many computational cells Ωi . Partition the
set {x0 } × [y0 , y0 + h] into subsets {x0 } × Ii where each set {x0 } × Ii is contained in a computational
cell Ωi , that is,
{x0 } × Ii = Ωi ∩ ({x0 } × [y0 , y0 + h]) . (3.3)
Here the operator × is the Cartesian product. The domain of integration in Equation 3.2 can then
be partitioned to get Z 
1X
u(x0 ) = ux (x0 , y) dy . (3.4)
h i Ii

As the value of ux is constant in each cell Ωi , it is also constant in each interval Ii . Therefore
Equation 3.4 becomes
1X
u(x0 ) = (ux (Ωi ) · |Ii |) . (3.5)
h i

16
3.3. SWAKEXPRESSIONAVERAGEDISTRIBUTION CHAPTER 3. EXISTING TOOLS

There are two practical problems here: defining the depth h and defining the intervals Ii . First note
that Equation 3.5 is equivalent to
P
(ux (Ωi ) · |Ii |)
u(x0 ) = i P . (3.6)
i |Ii |

One practical solution is to divide the x-axes into bins and switch to
P 
i ux (Ω̃i ) · |Ω̃i |
u(x0 ) ≈ P , (3.7)
i |Ω̃i |

where now the computational cells Ω̃i are those whose x-components lie close to x0 and whose α
values are greater than 0.5.
One way to calculate Equation 3.7 is by using a function object from the community contribution
swak4Foam. This is an optional section as we will be making our own function object that does
the same job in Chapter 5. The only difference is how the function object defines which cells have
x-components lying close enough to x0 ; it is unclear exactly how swak4Foam does it but the custom
function object in Chapter 5 picks those cells with centres lying in a defined interval around x0 . If
you want to try out the swak4Foam option, first install mercurial with
> sudo apt-get install mercurial
and then download swak4Foam by running
> ufoam
> hg clone http://hg.code.sf.net/p/openfoam-extend/swak4Foam
> cd swak4Foam
Switch to the development branch (necessary at the time of writing for v1906 compatibility) with
> hg update develop
and then compile with
> ./AllwmakeAll
After compilation, the binaries for the swak4Foam applications are in $FOAM_USER_APPBIN and
libraries in $FOAM_USER_LIBBIN. Open up the controlDict of the case with
> run
> cd weirOverflow
> gedit system/controlDict
and change the functions entry to
functions
{
depthAveragedVelocity
{
libs ("libsimpleSwakFunctionObjects.so");
type swakExpressionAverageDistribution;
outputControlMode outputTime;
writeStartTime true;
valueType internalField;

aliases
{
alpha alpha.water;
}

17
3.3. SWAKEXPRESSIONAVERAGEDISTRIBUTION CHAPTER 3. EXISTING TOOLS

variables ("threshold=0.5;");

expression "U.x";

mask "alpha>threshold";
abscissa "pos().x";
dynamicExtremesAbscissa true;
binNumber 100;
weight "vol()";
valueIfZero -999;
}
};
This function object is based on the forum posts of Gschaider [6, 7]. It divides the x-axis up onto
100 bins. For each bin, every cell whose x coordinate fits into a given bin and has α > 0.5 is
selected. The x-direction velocities are averaged for each bin, weighted by the volume of each cell
as in Equation 3.7. If there is no cell with alpha.water > 0.5 then −999 is given instead. An alias
has to be used as swak4Foam cannot deal with the character . in alpha.water. It seems that the
solver cannot be run in post-processing mode to apply this function object, so use the commands
> ./Allclean
> ./Allrun
Open up gnuplot again in the required directory
> cd postProcessing/swakExpressionAverageDistribution_depthAveragedVelocity
> gnuplot
and type in
> set xlabel "X Axis"
> set ylabel "Depth-averaged velocity (m/s)"
> unset key
> plot "60/expression_averageDistribution_"
to graph the results quickly. The results should be as in Figure 3.7. Again, exit gnuplot with q.

Figure 3.7: Depth-averaged velocity from swak4Foam function object. Flow is from left to right.

18
Chapter 4

Depth at outlet

In 1D hydraulic models, the discharge hu is usually set upstream and the depth h downstream. In
Section 3.1, we saw how the velocity boundary condition variableHeightFlowRateInletVelocity
can be used to set the discharge at the inlet of a VOF simulation. However, there is no boundary
condition for setting the depth at the outlet. We will create our own velocity boundary condition
that does this job and test it on weirOverflow.

4.1 Copying a boundary condition


The boundary condition variableHeightFlowRateInletVelocity already reads in the fields we
care about, alpha.water and U, so we will copy and modify it. First, copy the boundary condition
to the user directory with
> foam
> cp -r --parents
,→ src/finiteVolume/fields/fvPatchFields/derived/variableHeightFlowRateInletVelocity
,→ $WM_PROJECT_USER_DIR
> cd $WM_PROJECT_USER_DIR/src/finiteVolume/fields/fvPatchFields/derived
Then rename the file names with

> mv variableHeightFlowRateInletVelocity depthOutletVelocity


> cd depthOutletVelocity
> mv variableHeightFlowRateInletVelocityFvPatchVectorField.C
,→ depthOutletVelocityFvPatchVectorField.C
> mv variableHeightFlowRateInletVelocityFvPatchVectorField.H
,→ depthOutletVelocityFvPatchVectorField.H
and rename the class in these files with
> sed -i s/variableHeightFlowRateInletVelocity/depthOutletVelocity/g
,→ depthOutletVelocityFvPatchVectorField.*
Now add in compilation instructions with
> cd $WM_PROJECT_USER_DIR/src/finiteVolume
> mkdir Make
> touch Make/files Make/options
ensuring that the contents of Make/files are
fields/fvPatchFields/derived/depthOutletVelocity/depthOutletVelocityFvPatchVectorField.C
LIB = $(FOAM_USER_LIBBIN)/libdepthOutletVelocity

19
4.2. MODIFICATION CHAPTER 4. DEPTH AT OUTLET

and the contents of Make/options are


EXE_INC = \
-I$(LIB_SRC)/fileFormats/lnInclude \
-I$(LIB_SRC)/surfMesh/lnInclude \
-I$(LIB_SRC)/meshTools/lnInclude \
-I$(LIB_SRC)/finiteVolume/lnInclude

LIB_LIBS = \
-lOpenFOAM \
-lfileFormats \
-lsurfMesh \
-lmeshTools
It is good practise to compile now and check that the boundary condition still works as its old
functionality. Compile with
> wmake
Make a fresh copy of weirOverflow with
> run
> cp -r $FOAM_TUTORIALS/multiphase/interFoam/RAS/weirOverflow weirOverflowTest
> cd weirOverflowTest
rename the velocity boundary condition for inlet with
> sed -i s/variableHeightFlowRateInletVelocity/depthOutletVelocity/g 0.orig/U
add the library to the controlDict with
> echo "libs (\"libdepthOutletVelocity.so\");" >> system/controlDict
and then run the case with
> ./Allrun
Now we have verified that we have not inadvertently destroyed the boundary condition, we can alter
it to suit our new requirements.

4.2 Modification
The boundary condition in Section 3.1 read in Q from a dictionary, calculated A by reading in fields,
and used the equation Q = Au to calculate u. The idea here is the opposite: calculate Q by reading
in fields, read in h from a dictionary (giving A), and then use the equation Q = Au to calculate
u. This is like the approach of Bayon-Barrachina and Lopez-Jiminez [1] and Flora [5]. However,
we will not apply the velocity to the whole patch but only to cells beneath the depth marker and
a small buffer above it. Otherwise the increased velocity of the air at the outlet patch means air
has to be drawn in from the atmosphere patch, which can result in high velocities and problems
with stability. Another problem is that the sudden slowing down at the boundary condition could
result in rollers that temporarily provide a negative Q at the boundary. This results in a negative u,
and a feedback loop causes the simulation to blow up. Thus we will make the boundary condition
default to a fixed value of (0 0 0) if the total flow rate out of the domain is negative, allowing such
problems to settle out.
To read in the depth from a dictionary, we need to define a variable called depth_. We will
also need to read in both U and alpha.water to calculate the current outlet flow rate but, as this
boundary condition is applied to U, we only need to define a variable for the name of the other field
alphaName_. To generalise the boundary condition to any axis orientation, we also need to define
the depthAxis_. Finally, we also need a parameter depthBuffer_ to determine how large the buffer
should be for applying the average velocity.
So, open up the header file with

20
4.2. MODIFICATION CHAPTER 4. DEPTH AT OUTLET

> cd $WM_PROJECT_USER_DIR/src/finiteVolume/fields/fvPatchFields/derived
> gedit depthOutletVelocity/depthOutletVelocityFvPatchVectorField.H
and change the private data to
// - Depth axis
label depthAxis_;

// - Required depth
autoPtr<Function1<scalar>> depth_;

// - Name of alpha field


word alphaName_;

// - Depth buffer
scalar depthBuffer_;
The depth is declared as a Function1, which means that it can be a function of time. This function
can be a constant, a trigonometric function, or read from a table or csv file. Close the file. To
discover all of the options, run
> ls $FOAM_SRC/OpenFOAM/primitives/functions/Function1
Now open up the file with the class definitions
> gedit depthOutletVelocity/depthOutletVelocityFvPatchVectorField.C
and change the initialisation of the first constructor to
fixedValueFvPatchField<vector>(p, iF),
depthAxis_(),
depth_(),
alphaName_("none"),
depthBuffer_()
the second constructor to
fixedValueFvPatchField<vector>(p, iF, dict),
depthAxis_(dict.get<label>("depthAxis")),
depth_(Function1<scalar>::New("depth", dict)),
alphaName_(dict.lookup("alpha")),
depthBuffer_(dict.get<scalar>("depthBuffer"))
the third constructor to
fixedValueFvPatchField<vector>(ptf, p, iF, mapper),
depthAxis_(ptf.depthAxis_),
depth_(ptf.depth_.clone()),
alphaName_(ptf.alphaName_),
depthBuffer_(ptf.depthBuffer_)
the fourth constructor to
fixedValueFvPatchField<vector>(ptf),
depthAxis_(ptf.depthAxis_),
depth_(ptf.depth_.clone()),
alphaName_(ptf.alphaName_),
depthBuffer_(ptf.depthBuffer_)
and the fifth constructor to

21
4.2. MODIFICATION CHAPTER 4. DEPTH AT OUTLET

fixedValueFvPatchField<vector>(ptf, iF),
depthAxis_(ptf.depthAxis_),
depth_(ptf.depth_.clone()),
alphaName_(ptf.alphaName_),
depthBuffer_(ptf.depthBuffer_)

Now change the contents of the updateCoeffs() member function to


if (updated())
{
return;
}

Info << "depthOutletVelocity" << endl;

// current alpha
scalarField alphaOld =
patch().lookupPatchField<volScalarField, scalar>(alphaName_);
alphaOld = max(alphaOld, scalar(0)); // bounded to physical values
alphaOld = min(alphaOld, scalar(1));

// required depth
const scalar t = db().time().timeOutputValue();
scalar depth = depth_->value(t);

// required alpha
scalarField alphaNew(patch().size(),0);
scalarField alphaBuffer(patch().size(),0);

// current velocity
vectorField velocityOld(this->patchInternalField());

// mesh geometry
const scalarField& areas = patch().magSf();
const vectorField& centres = patch().Cf();
const vectorField normals = patch().nf();
const scalarField centresAxis = centres.component(depthAxis_);

boundBox bb(patch().patch().localPoints(), true);


scalar datum = bb.min().component(depthAxis_);

// required alpha
forAll(centresAxis, i)
{
if ((centresAxis[i] - datum) < depth)
{
// below required depth
alphaNew[i] = 1.0;
alphaBuffer[i] = 1.0;
}
else if ((centresAxis[i] - datum) < depthBuffer_*depth)
{
// below depth buffer
alphaNew[i] = 0.0;
alphaBuffer[i] = 1.0;

22
4.3. APPLICATION CHAPTER 4. DEPTH AT OUTLET

}
else
{
// above depth buffer
alphaNew[i] = 0.0;
alphaBuffer[i] = 0.0;
}
}

// required cross-sectional area


scalar areaNew = gSum(alphaNew*areas);

// required discharge
scalar discharge = gSum(alphaOld*areas*(velocityOld & normals[0]));

// required velocity
scalar avgVel = discharge/areaNew;
avgVel = max(0, avgVel); // in case flow rate temporarily negative
vector velocityNew = avgVel * normals[0];

operator==
(
velocityNew*alphaBuffer + // fixed value below buffer
velocityOld*(1-alphaBuffer) // zero gradient above buffer
);

fixedValueFvPatchField<vector>::updateCoeffs();
and the contents of the write(stream& os) function to
fvPatchField<vector>::write(os);
os.writeEntry("depthAxis", depthAxis_);
depth_->writeData(os);
os.writeEntry("alpha", alphaName_);
os.writeEntry("depthBuffer", depthBuffer_);
writeEntry("value", os);
Compile with
> cd $WM_PROJECT_USER_DIR/src/finiteVolume
> wmake

4.3 Application
Now we will test our new boundary condition on the weirOverflow tutorial. Clean the case and
change the relevant files with
> run
> cd weirOverflow
> ./Allclean
> echo "libs (\"libdepthOutletVelocity.so\");" >> system/controlDict
> gedit 0.orig/U
to change the outlet boundary condition to
outlet
{

23
4.3. APPLICATION CHAPTER 4. DEPTH AT OUTLET

type depthOutletVelocity;
depth 25.0;
depthAxis 1;
depthBuffer 1.5;
alpha alpha.water;
value uniform (0 0 0);
}
Also extend the temporal and spatial domains with
> sed -i s/60/120/g system/controlDict
> sed -i s/90/150/g system/blockMeshDict
> sed -i s/60/120/g system/blockMeshDict
Then run the case
> ./Allrun

As we have saved the ParaView state file from earlier, we can open it up without applying all the
filters again. The results should be as in Figure 4.1, where the fixed outlet depth forces a hydraulic
jump to occur from supercritical to subcritical flow. To check that the implementation is sufficiently
general, we can also add some extra pre-processing steps. Open up the Allrun file with
> gedit Allrun

and add an extra line after the line with setFields. For example,
runApplication transformPoints -translate "(0 100 0)"
will shift the mesh 100m in the y-direction while

runApplication transformPoints -rollPitchYaw "(0 90 0)"


will switch the x and z axes, keeping the depthAxis as y. The results should be the same. This
demonstrates that the boundary condition is effective and robust, as well as capable of fixing the
depth at the outlet for highly transient flows. The boundary condition could be used for investigating
the effect of the downstream depth on weir performance, as in Villemonte et al. [11]. It could also
be used for studying hydraulic jumps in isolation, as in Bayon-Barrachina and Lopez-Jimenez [1].

24
4.3. APPLICATION CHAPTER 4. DEPTH AT OUTLET

Figure 4.1: Results for depthOutletVelocity at t = 28 and 120s. Flow is from left to right, with
depthOutletVelocity applied on the outlet at the right-hand side.

25
Chapter 5

Depth-averaged velocity

The depth-averaged velocity at a point x0 is given by


Z y0 +h
1
u(x0 ) = ux (x0 , y) dy. (5.1)
h y0

It is a primitive variable in 1D hydraulic modelling, and so a useful quantity to extract from VOF
simulations. Recall from Section 3.3 that it can be approximated by
P 
i ux (Ω̃i ) · |Ω̃i |
u(x0 ) ≈ P , (5.2)
i |Ω̃i |

where Ω̃i are computational cells whose x-components lie close to x0 and whose α values are greater
than 0.5. In Section 3.3, we calculated this using a swak4Foam function object, but it is unclear how
the cells Ω̃i are chosen in swak4Foam. In this chapter, we will create our own function object that
also calculates Equation 5.2 but selecting the cells Ω̃i to be those lying in a bin around x0 . Creating
our own function object requires less compilation, and makes it easier to see what the function object
is doing.

5.1 Creating a function object


In Chapter 4, we modified an existing boundary condition. However, OpenFOAM also has some
useful utilities that will set up code for us to modify. Type
> foamNew [TAB] [TAB]
to see the list. We will use the utility foamNewFunctionObject. Run the commands
> cd $WM_PROJECT_USER_DIR/src
> foamNewFunctionObject depthAveragedVelocity
> cd depthAveragedVelocity
> gedit depthAveragedVelocity.H
We need to declare our data in this header file. As we are going to use the same tactic as the
swak4Foam function object, we want noBins_, valueIfZero_ and alphaName_. To generalise the
function object to any axis orientation, we also want depthAxis_ and lengthAxis_. Finally, we
want to know the name of the alpha.water field with alphaName_. Change the private data to
// - Depth axis
label depthAxis_;

// - Length axis

26
5.1. CREATING A FUNCTION OBJECT CHAPTER 5. DEPTH-AVERAGED VELOCITY

label lengthAxis_;

// - Number of bins
label noBins_;

// - Value if nothing in the bin


label valueIfZero_;

// - Name of alpha field


word alphaName_;

// - Averaging threshold
scalar threshold_;
Then open up the other file with
> gedit depthAveragedVelocity.C
and, before the static data members, add
#include "volFields.H"
#include "fileName.H"
#include "OFstream.H"
then change the initialisation of the constructor to
fvMeshFunctionObject(name, runTime, dict),
depthAxis_(dict.get<label>("depthAxis")),
lengthAxis_(dict.get<label>("lengthAxis")),
noBins_(dict.get<label>("noBins")),
valueIfZero_(dict.get<label>("valueIfZero")),
alphaName_(dict.lookupOrDefault<word>("alpha", "alpha.water")),
threshold_(0.5)
the contents of the read member function to
dict.readIfPresent("alpha", alphaName_);

dict.readEntry("depthAxis", depthAxis_);
dict.readEntry("lengthAxis", lengthAxis_);
dict.readEntry("noBins", noBins_);
dict.readEntry("valueIfZero", valueIfZero_);
return true;
and the contents of the write member function to
if (obr_.time().write())
{
Info << "depthAveragedVelocity" << endl;

// e.g. $FOAM_RUN/weirOverflow/postProcessing/depthAveragedVelocity/0
fileName myDir =
obr_.time().rootPath() /
obr_.time().globalCaseName() /
"postProcessing" /
this->name() /
obr_.time().timeName();

27
5.1. CREATING A FUNCTION OBJECT CHAPTER 5. DEPTH-AVERAGED VELOCITY

mkDir(myDir);

// e.g. $FOAM_RUN/weirOverflow/postProcessing/depthAveragedVelocity/0/
// depthAveragedVelocity.dat
fileName myFile = myDir / this->name() + ".dat";
OFstream myStream(myFile);

// file header
myStream << "# location, average" << endl;

// current alpha and U


const volScalarField& alpha =
obr_.lookupObject<volScalarField>(alphaName_);
const volVectorField& U =
obr_.lookupObject<volVectorField>("U");

// geometry
const boundBox& meshBounds = mesh_.bounds();
scalar longMin = meshBounds.min().component(lengthAxis_);
scalar longMax = meshBounds.max().component(lengthAxis_);

scalar length = longMax - longMin;


scalar dx = length/noBins_;

// loop over bins


for (int n = 0; n < noBins_; n++)
{
// bin geometry
scalar lowerBound = longMin + dx*n;
scalar upperBound = longMin + dx*(n+1);
scalar midpoint = lowerBound + dx/2;

const vectorField& centres = mesh_.cellCentres();

// initialise sums
scalar sum = 0;
scalar totVol = 0;

// loop over cells


forAll (centres, i)
{
scalar centresAxis = centres[i].component(lengthAxis_);

// check if cell lies in bin


bool centreInInterval =
centresAxis >= lowerBound &&
centresAxis < upperBound;

if (centreInInterval && alpha[i] > threshold_)


{
// if it lies in bin and is water then add to sum
scalar UAxis = U[i].component(lengthAxis_);
scalar vol = mesh_.V()[i];

28
5.2. APPLICATION CHAPTER 5. DEPTH-AVERAGED VELOCITY

sum += UAxis*vol;
totVol += vol;
}
}

// collect sums from all processors


reduce(sum, sumOp<scalar>());
reduce(totVol, sumOp<scalar>());

// depth-averaged velocity
scalar avg;

if (totVol < 1e-12)


{
// nothing in the bin
avg = valueIfZero_;
}
else
{
// something in the bin
avg = sum/totVol;
}

// result in file
myStream<< midpoint << tab << avg << endl;
}
}

return true;
Compile with wmake.

5.2 Application
We want to compare our new function object with the swak4Foam one, so make a fresh copy of
weirOverflow and open up the controlDict with
> run
> rm -rf weirOverflowTest
> cp -r $FOAM_TUTORIALS/multiphase/interFoam/RAS/weirOverflow weirOverflowTest
> cd weirOverflowTest
> gedit system/controlDict
and add to the end
functions
{
depthAveragedVelocity
{
libs ("libdepthAveragedVelocityFunctionObject");
type depthAveragedVelocity;
depthAxis 1;
lengthAxis 0;
noBins 100;
valueIfZero -999;

29
5.2. APPLICATION CHAPTER 5. DEPTH-AVERAGED VELOCITY

alpha alpha.water;
}
}
Run the case then plot with

> ./Allrun
> gnuplot
> set xlabel "X Axis"
> set ylabel "Depth-averaged velocity (m/s)"
> unset key
> plot "postProcessing/depthAveragedVelocity/60/depthAveragedVelocity.dat"
The results should be as in Figure 5.1, which is very similar to Figure 3.7, as expected. As when
testing the boundary condition, the axis can be moved with transformPoints to check whether the
function object is sufficiently general. This demonstrates that the function object is effective and
robust, as well as capable of replicating results from an established swak4Foam function object.

Figure 5.1: Depth-averaged velocity from custom function object. Flow is from left to right.

30
Study questions

How to use it:


1. What directory in $FOAM_TUTORIALS contains open-channel flow cases?
2. What ParaView filter is used to view the free surface?
3. What library contains the surface function object?

4. Why does an alias have to be used for alpha.water in swak4Foam?


The theory of it:
1. What boundary conditions are usually set upstream and downstream in 1D hydraulic models?

2. What is the difference between χ and α?


How it is implemented:
1. What is the difference between timeIntegratedFlux and timeIntegratedFaceFlux?
2. What does the “g” in gSum mean?

How to modify it:


1. What must $(FOAM_LIBBIN) be changed to when copying a Make/files file from the Open-
FOAM installation to the user directory?
2. What utility can be used to create a new function object?

31
Bibliography

[1] A. Bayon-Barrachina and P. A. Lopez-Jimenez. “Numerical analysis of hydraulic jumps using


OpenFOAM”. In: Journal of Hydroinformatics 17.4 (2015), pp. 662–678.
[2] P. Cifani et al. “A comparison between the surface compression method and an interface
reconstruction method for the VOF approach”. In: Computers and Fluids 136 (2016), pp. 421–
435.
[3] S. S. Deshpande, L. Anumolu, and M. F. Trujillo. “Evaluating the performance of the two-
phase flow solver interFoam”. In: Computational Science & Discovery 5.1 (2012).
[4] D. A. Drew. “Mathematical Modeling of Two-Phase Flow”. In: Annual Review of Fluid Me-
chanics 15.1 (1983), pp. 261–291.
[5] K. Flora. Setting BCs for Riverine Flows using Interfoam [Post 17]. CFD-Online, Nov. 2012.
url: https://www.cfd- online.com/Forums/openfoam- solving/97987- setting- bcs-
riverine-flows-using-interfoam.html.
[6] B. Gschaider. [swak4Foam] Averaging along an axis.. swak4foam? [Post 3]. CFD-Online, Oct.
2014. url: https://www.cfd-online.com/Forums/openfoam-community-contributions/
142505-averaging-along-axis-swak4foam.html.
[7] B. Gschaider. interFoam Average velocity of water only! [Post 2]. CFD-Online, Sept. 2011. url:
https://www.cfd- online.com/Forums/openfoam- post- processing/92180- interfoam-
average-velocity-water-only.html.
[8] J. Roenby, H. Bredmose, and H. Jasak. “A computational method for sharp interface advec-
tion”. In: Royal Society Open Science 3.11 (2016).
[9] H. Rusche. “Computational Fluid Dynamics of Dispersed Two-Phase Flows at High Phase
Fractions”. PhD thesis. Imperial College London, 2002.
[10] E. F. Toro. Shock-capturing methods for free-surface shallow flows. West Sussex: John Wiley,
2001.
[11] J. R. Villemonte. “Submerged weir discharge studies”. In: Engineering News-Record 139.26
(1947), pp. 54–56.

32

You might also like