Inlets, Outlets, and Post-Processing For Modelling Open-Channel Flow With The Volume of Fluid Method
Inlets, Outlets, and Post-Processing For Modelling Open-Channel Flow With The Volume of Fluid Method
: 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
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.
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:
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
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
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.
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,
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
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
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
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 ∂Ω
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);
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.
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.
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
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.
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.
15
3.3. SWAKEXPRESSIONAVERAGEDISTRIBUTION CHAPTER 3. EXISTING TOOLS
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.
19
4.2. MODIFICATION CHAPTER 4. DEPTH AT OUTLET
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_;
// - 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_)
// 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_);
// 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 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
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
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.
// - Length axis
26
5.1. CREATING A FUNCTION OBJECT CHAPTER 5. DEPTH-AVERAGED VELOCITY
label lengthAxis_;
// - Number of bins
label noBins_;
// - 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;
// geometry
const boundBox& meshBounds = mesh_.bounds();
scalar longMin = meshBounds.min().component(lengthAxis_);
scalar longMax = meshBounds.max().component(lengthAxis_);
// initialise sums
scalar sum = 0;
scalar totVol = 0;
28
5.2. APPLICATION CHAPTER 5. DEPTH-AVERAGED VELOCITY
sum += UAxis*vol;
totVol += vol;
}
}
// depth-averaged velocity
scalar avg;
// 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
31
Bibliography
32