Module Guide

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 24

DownloadModule Guide
Open PDF In BrowserView PDF
flowPsi Module Guide

May 31, 2019

Contents
1 Introduction

1

1.1

Loci FVM Module Facilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.2

flowPsi facilities and variables . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3

2 Custom Output

6

3 Custom Boundary Condition Input

8

4 Custom Initial Conditions

10

5 Surface Integration

11

6 Volume Integration

13

7 Adding a Volume Source

14

8 Scalar Transport

16

8.1

Tracer Variable Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

8.2

The Menter one equation turbulence model . . . . . . . . . . . . . . . . . . . .

17

8.2.1

18

Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

Introduction

This module guide provides a set of tutorials for creating add on modules for the flowPsi
solver. It is assumed that the reader has some basic understanding of the Loci programming
model. To gain that understanding please work through the tutorial provided with the Loci
framework. This document and examples will extend that tutorial to describe how to create
common types of extensions to the flowPsi solver. In general modules are loaded into flowPsi by
first making sure that the directory that contains the module is either in the LD LIBRARY PATH
or the LOCI MODULE PATH environment variable. Then the modules can be loaded into your
flowPsi case by adding a loadModule: followed by the module name at the top of the vars file.
The Makefile provided in these example directories are easy to use and can be copied for your
module development. The Makefile is designed such that all files that end in with the .loci
postfix will be compiled into a module. The file only needs to be edited to provide the module
name and the FLOWPSI BASE directory that points to where the flowPsi solver is installed.

1.1

Loci FVM Module Facilities

Before we begin discussing building modules for flowPsi lets review some of the basic facilities
that Loci provides for finite volume solvers that flowPsi utilizes. First, flowPsi uses the face
based data-structure that includes the face2node map that provides an ordered listing of the
nodes that form each face in the mesh as well as the cl and cr maps that provide the maps
from the face to the left and right cells. Faces on the boundary always have normals pointing
out of the domain and therefore the cl map points to the cell next to the boundary while the
cr cell may point to a ghost cell (in flowPsi, ghost cells are only used to implement periodic
boundary conditions). The ci and ref maps are only defined for boundary faces where ci
refers to the adjacent cell inside the mesh and ref refers to the reference surface that forms the
facets of that boundary. The pmap map associates the periodic boundary face with its periodic
pair. The cell to face maps upper, lower, and boundary map is used to form matrices that are
based on the face connectivity where upper refers to the faces that form the upper and lower
triangular matrix entries and the boundary map identifies boundary faces of the cell.
In addition to these data structure elements, the Loci FVM module provides some generic
rules for computing grid metrics such as face areas and cell volumes. Additionally the module
provides some basic facilities for computing gradients and interpolating solutions from cells
to nodes for plot output. These facilities generally will need the cell center values and the
boundary face values. The boundary face values will have the naming convention of having a
f postfix annotation denoting that it is a face value. A table that summarizes that facilities
provided by the FVM module are provided in table 1.

1

Table 1: Table of Loci FVM provided utilities
Supplied by Loci FVM facilities
Variable
Type
Description
Mesh Data Structures
ci
map
cell next to boundary face
cl
map
cell on left side of face
cr
map
cell on right side of face
ref
map
map from boundary faces to surface
face2node
multiMap
map from face to nodes
pmap
map
periodic matching face
upper
multiMap
map from cell to upper faces
lower
multiMap
map from cell to lower faces
boundary map
multiMap
map from cell to boundary faces
Mesh Metrics
pos
vector
node positions
cellcenter
vector
cell centroid location
facecenter
vector
face centroid location
area
composite
face area and normal
vol
scalar
cell volume
gridvol
parameter
total grid volume
Parametric Rules
grads(X)
vector
gradient of scalar variable X
gradv3d(X)
tensor
gradient of vector variable X
gradv(X)
generic vector
gradient of mixture vector X
grads f(X)
vector
face gradient of scalar X
gradv3d f(X)
tensor
face gradient of vector X
gradv f(X)
generic vector
gradient of mixture vector X
cell2node(X)
scalar
cell to node interpolation for scalar X
cell2node v3d(X)
vector
cell to node interpolation for vector X
cell2nodeMax(X)
scalar
nodal maximum of X for connected cells
cell2nodeMin(X)
scalar
nodal minimum of X for connected cells
L1Norm(X)
parameter
volume integrated L1 norm of cell variable X
L2Norm(X)
parameter
volume integrated L2 norm of cell variable X
LinfNorm(X)
parameter
volume integrated L∞ norm of cell variable X
fileNumber(X)
integer
file numbering for variable X

2

Table 2: Table of flowPsi Fluid Properties

Variable
rho
rho f
temperature
temperature f
gagePressure
gagePressure f
Pambient
pressure
u
uf
divu
vort
vortMag
strainRate
tau
mu
mu f
kconduct
kconduct f
us n
us
Cp
gamma
Rtilde
tmu
soundSpeed

1.2

flowPsi fluid properties variables
Type
Description
scalar
fluid density
scalar
fluid density at face
scalar
fluid temperature
scalar
fluid temperature at face
scalar
fluid pressure reference to Pambient
scalar
fluid gage pressure at face
parameter
reference pressure
scalar
fluid pressure
vector
fluid velocity in cell
vector
fluid velocity at face
scalar
divergence of u
vector
fluid vorticity
scalar
fluid vorticity magnitude
scalar
strain rate magnitude, S
symmetric tensor
fluid stress
scalar
fluid laminar viscosity
scalar
fluid laminar viscosity at face
scalar
fluid laminar conductivity
scalar
fluid laminar conductivity at face
scalar
face grid velocity dot normal
vector
face grid velocity
parameter
fluid Cp
parameter
fluid isentropic index, γ
parameter
fluid gas constant
scalar
turbulent viscosity
scalar
local speed of sound

flowPsi facilities and variables

When flowPsi runs the only the conservative variables (and gage pressure) are carried from
time-step to time-step. All other variables that are derived from these conservative variables
are only computed during the Newton iteration. Thus most of the computations in flowPsi
happen as part of the Newton iteration step. This section outlines some of the facilities that
are available during that step. Table 2 gives that generic fluid properties that are available.
Solver specific variables that can be replaced through priority rules or used in control are shown
in table 3 while the parametric utilities are described in table 4. These tables are provided to
give a brief survey of the kinds of data that is available for interfacing to flowPsi modules.

3

Table 3: Table of flowPsi solver specific variables

Variable
p0Ref
T0Ref
massFluxRef
temperatureRef
gagePressureRef
uRef
Twall
qwall
wallVelocity
src
iflux
vflux
scalar mdot
srcJ
fjp
fjm
min cell2noslip
dist noslip
dt
stime
ncycle
newtonFinished
do restart
do output
do plot
do boundary plot

flowPsi solver specific variables
Type
Description
scalar
total pressure BC input
scalar
total temperature BC input
scalar
mass flux BC input
scalar
temperature BC input
scalar
gagePressure BC input
vector
fluid velocity BC input
scalar
viscous wall BC temperature
scalar
viscous wall BC heat flux
vector
viscous wall tangential velocity
general vector source array for mass, momentum, and energy
Array(5)
inviscid flux
Array(4)
viscous flux
scalar
mass flux through face
matrix block
diagonal jacobian term of src
matrix block
jacobian of flux from left
matrix block
jacobian of flux from right
map
Map from cell to closest viscous wall
scalar
distance from cell to nonslip wall
parameter
timestep value in seconds
parameter
simulation time in seconds
parameter
simulation step
conditional
Newton iteration complete
conditional
output restart files
conditional
text output
conditional
plot file output
conditional
boundary plot file output

4

Table 4: Table of flowPsi parametric variables

Variable
scalarTransport(X,C)
scalarTransportP(X,C)
scalarMean(X)
scalarMean f(X)
scalarVariance(X)
vect3dMean(X)
vect3dMean f(X)
vect3dVariance(X)
vect3dVariance f(X)
vect3dCoVariance(X)
vect3dCoVariance f(X)
scalarFavreMean(X)
scalarFavreMean f(X)
scalarFavreVar(X)
vect3dFavreMean(X)
vect3dFavreMean f(X)
vect3dFavreVar(X)
vect3dFavreVar f(X)
vect3dFavreCoVar(X)
vect3dFavreCoVar f(X)

flowPsi parametric variables
Type
Description
generic
build transport of scalar X, enabled by C
generic
like above for positive scalar X
scalar
time averaged cell value X
scalar
time averaged boundary face value X f
scalar
time variance of variable X
vector
time averaged vector mean
vector
time averaged boundary vector mean
vector
time variance of vector X
vector
time variance of boundary vector
vector
co-variance of vector X
vector
co-variance of boundary vector x
scalar
Favre average of variable X
scalar
Favre average of boundary variable
scalar
Favre weighted variance
vector
vector Favre weighted mean
vector
vector Favre weighted boundary mean
vector
vector Favre weighted variance
vector
vector Favre weighted variance boundary
vector
vector Favre weighted co-variance
vector vector Favre weighted co-variance boundary

5

2

Custom Output

One of the more common needs for add-on modules is the development of derived variable computations. While it is possible to compute derived variables directly in post-processing software,
these computations can sometimes be misleading due to the fact that they are performed on
interpolated data which may introduce errors in the derived computations. So it is sometimes
useful to perform computations directly on the cell values before interpolation in order to get
more satisfactory results. For this example we will consider the output of a variable that can be
used for numerical schlieren imaging. This is done by computing the logarithm of the density
gradient. So in this example we will show how one goes about computing and outputting such
a variable in a module.
The first step to this is to copy the Makefile provided in the examples which will automatically
compile all .loci files in the directory. The only change that is needed is to set the MODULE NAME
variable in the Makefile to the name that you wish to use in the loadModule line of the vars
file. In this case we will call the module schlieren, or
# What is the name of the module given to loadModule
MODULE_NAME = schlieren
At this point we can begin editing our Loci program which we will conveniently name schlieren.loci.
At the beginning of the file we need to include some files that will be needed to access the facilities we will be using. This is accomplished with the following includes:
// Include Loci system
#include 
// defines types used by flowPsi
#include "flowTypes.h"
// imports flowPsi IO functionality
#include "flowPsiIO.h"
// defines Loci types for flowPsi variables
$include "flowPsi.lh"
It is convenient but not necessary to add the code in the C++ namespace called flowPsi.
Alternatively, we can add the code in our own namespace but add code to tell C++ to use
variables from the flowPsi namespace such as:
namespace schlieren {
using namespace flowPsi ;
We now need to compute the variable that we are going to plot. Since flowPsi computes
unknowns at cell centers this will be where we perform the computations. However, to improve
nodal interpolation accuracy we also need to compute the target variables for all boundary
faces as well. Since these variables are not part of the flowPsi infrastructure we will need to
define their types first:
// define types for cell and boundary face values
$type loggradrho store ;
$type loggradrho_f store ;
6

Then write rules to define how to compute these variables as well. These computations will use
the scalar gradient parametric rules provided by Loci’s FVM module. The rules will be defined
as:
// To interpolate to the nodal values we need both cell values and boundary
// face values. Thus we have two rules to compute the value of interest
$rule pointwise(loggradrho<-grads(rho)) {
$loggradrho = log10(max(norm($grads(rho)),1e-4)) ;
}
$rule pointwise(loggradrho_f<-grads_f(rho)) {
$loggradrho_f = log10(max(norm($grads_f(rho)),1e-4)) ;
}
At this point we have defined the variable that we wish to compute but as they are defined
now they will not be computed because flowPsi will never request these variables. Instead we
will want to tell flowPsi that these variables are ones that we need plot files. For this, flowPsi
provides a macro called OUTPUT SCALAR that interfaces with the flowPsi plotting facilities. It
will allow this variable to be written out when requested by placing the given variable name in
the plot output variable in the vars file. This is accomplished with the line:
OUTPUT_SCALAR("cell2node(loggradrho)", schlieren) ;
This line tells the code to write out the variable cell2node(loggradrho) as a variable named
schlieren. Note that the variable that we wrote out is not the variable that we computed.
This is because volume data is plotted from nodal values and since flowPsi computes its degrees
of freedom at the cells, an interpolation function is needed to compute the nodal values. The
cell2node parametric rules provided by the Loci FVM module performs this function. This
parametric rule will access the variables that we defined earlier. Also this macro will enable
requesting this variable on cutting plane and isosurface output from flowPsi as well.
In addition to volume data, we can also output boundary facet data. This is provided with the
macro OUTPUT BNDRY SCALAR. So if we wish to output the schlieren variable for the boundary
facets we can add the following line to or loci file:
OUTPUT_BNDRY_SCALAR("loggradrho_f",schlierenb,"ci") ;
The first argument of this macro is the variable that we wish to plot, while the second is the
name of the variable (hear with a b appended to differentiate boundary facet data from the
nodal data). Finally, the last argument defines the set of boundaries that we wish to write this
data. Since all boundary faces have the attribute ci this will write out for all boundary faces.
Also not, for outputting 3D vector data replace the SCALAR with VECTOR in the above macros.

7

3

Custom Boundary Condition Input

Most of the boundary conditions provided by flowPsi can be customized. Typically the inputs
for each boundary face are assigned to a Ref variables that can be overridden by modules to
change the basic behavior. For this example we will demonstrate this in a simple boundary
condition where the pressure is perturbed by a sinusoidal signal. In this particular case we will
override the definition of the gageP ressureRef variable so that instead of being the constant
defined in the boundary condition, it will instead be a time varying value. Note, it is possible
to also change the boundary condition to be reactive to the solution itself. However, in these
cases the stability of the resulting boundary condition cannot be guaranteed.
To add this capability to an inflow boundary condition we want to be able to add a specification
to each boundary where we want to add the perturbation that will give the amplitude and
frequency of the disturbance. By default the flowPsi boundary condition checking code will
not allow these parameters to be added without generating error messages. Therefore it is
necessary to register these new boundary condition arguments with flowPsi. This checker for
the boundary conditions are defined in the read grid.h header file which will need to be
included. The checker for these two new variables is added through the following code:
// Here we create a checking class that inherits from BC_Check that is defined
// in readGrid.h
class pwave_check : public BC_Check {
string error_message ;
public:
// List which boundary conditions this checker is for
std::string boundaryConditions() { return "inflow,supersonicInflow,farfield" ; }
// List which variables this checker will verify
std::string variablesChecked() { return "amplitude,frequency" ; }
// Check for the existence of amplitude and frequency
// in our implementation these are optional, but if amplitude is
// given then frequency must also be given
bool checkOptions(const options_list& bc_options) {
error_message = "" ;
bool check = true ;
if(bc_options.optionExists("amplitude")) {
if(!check_scalar_units(bc_options,"amplitude","Pa")) {
error_message += "Pressure units needed for ’amplitude’ " ;
check = false ;
}
if(!bc_options.optionExists("frequency")) {
error_message += "’amplitude’ must also specify ’frequency’ " ;
check = false ;
}
}
return check ;
}
// Print out error messsage. Used if checker fails to generate error
// message to the user. Usually the error message is remembered from
// when the checker was run.
8

std::ostream &ErrorMessage(std::ostream &s) {
}
} ;
// The register_BC template registers this object in the boundary checker
// database. The names are not important only that we create the object
// so that it is registered.
register_BC register_BC_pwave_check ;
Next we will need to extract the values for amplitude and pressure from the BC options variable
that is defined for each boundary surface. For this we will use the constraint that is created
by the FVM grid reader that indicates what parameter are defined in the BC options variable.
This will ensure that the variables will only be extracted for the boundary conditions where
these arguments are specified. The rules that extract these variables are as follows:
$type amplitude_BC store ;
$rule pointwise(amplitude_BC<-BC_options),constraint(amplitude_BCoption) {
$BC_options.getOptionUnits("amplitude","Pa",$amplitude_BC) ;
}
$type frequency_BC store ;
$rule pointwise(frequency_BC<-BC_options),constraint(frequency_BCoption) {
$BC_options.getOption("frequency",$frequency_BC) ;
}
We can now compute the time varying prescribed pressure and override the gagePressureRef
variable that will be used by the inflow boundary condition.
$type pgDelta_BC store ;
$rule pointwise(pgDelta_BC<-stime,amplitude_BC,frequency_BC) {
$pgDelta_BC = $amplitude_BC*sin($stime*$frequency_BC*2.*M_PI) ;
}
// Override the reference condtions for the faces that adds in the
// perturbation. This will be used instead of the default settings
$rule pointwise(distrbance::gagePressureRef(gagePressureRef_BC,pgDelta_BC)) {
$gagePressureRef = $ref->$gagePressureRef_BC + $ref->$pgDelta_BC ;
}
Other variables can also be overridden using a similar process. For a complete list of the Ref
variables that can be overridden see table 3.

9

4

Custom Initial Conditions
// For the initial conditons we define temperature_ic, gagePressure_ic, and
// initial velocity (u_ic)
$rule pointwise(temperature_ic,gagePressure_ic,u_icintegrate BCoption
to determine the set. In this example we use the parametric facility to integrate over each
boundary surface separately. To do this we use the fact that for each boundary a parametric
variable will be created in by the grid reader of the form boundaryName(X) where X is replaced
by each boundary surface name. This is a string parameter which is defined over the set of
faces that form that boundary surface. For our mean temperature we will use an area weighted
average, so at first we will compute the total area for each boundary with the rule:
// This sets up the initial value of the boundary area before summing over
// faces. This needs to be set to zero as this is the indentity of the
// summation operator
$rule unit(boundaryArea_X<-boundaryName(X)),parametric(boundaryName(X)) {
$boundaryArea_X = 0 ;
}
// Add the areas from each face. In some cases, for example when
// running in axisymmetric mode the area might be zero, so the max
// function is there to keep that case from generating a divide by zero
// condition.
$rule apply(boundaryArea_X<-area,boundaryName(X))[Loci::Summation],
parametric(boundaryName(X)) {
// note, the area is the sada component of the area input, the n component
// is the normal vector
join($boundaryArea_X,max($area.sada,1e-13)) ;
}
In this set of parametric rules the boundaryArea X variable will be created for each boundary
condition with the X replaced by each boundary name. Now we can compute the weighted
temperature with the following rules:
// Now we will compute the area weighted average temperature
$type boundaryMeanTemp_X param ;
// set the initial value for the mean temp
$rule unit(boundaryMeanTemp_X<-boundaryName(X)),parametric(boundaryName(X))
$boundaryMeanTemp_X = 0 ;
}
11

{

// Now compute the weighted sum of the of the temperature. The temperature
// at the boundary face is given by temperature_f
$rule apply(boundaryMeanTemp_X<-area,temperature_f,boundaryArea_X,boundaryName(X))[Loci::Summation],
parametric(boundaryName(X)) {
double weight = max($area.sada,1e-13)/$boundaryArea_X ;
join($boundaryMeanTemp_X,weight*$temperature_f) ;
}
Now we can output the integrated value. Note, that although this appears to be one rule, one
will be instantiated for each boundary surface. In the end this will generate output for each
boundary.
// Now output to stdout the average temperature. Do this output only
// when the code is outputting boundary plot files.
// Note, since this is only output once, we use the prelude and
// because we are in the prelude we must use the dereference operatpr
// ’*’ to access the computed parameters. The $[Once] tells Loci that
// you only want to output this once even when running in parallel where
// there would be multiple instances of execution
$rule pointwise(OUTPUT<-boundaryMeanTemp_X,boundaryName(X)),
conditional(do_boundary_plot),
parametric(boundaryName(X)), prelude {
$[Once] {
cout << "Boundary " << *$boundaryName(X) << " has mean temperature of "
<< *$boundaryMeanTemp_X << " Kelvin" << endl ;
}
} ;
//^ This semicolon is needed because we are not following the prelude
// section with a compute section.

12

6

Volume Integration

In this example we will show how to perform a volume integration over each named component
of the mesh. This will work similarly to the surface integration except that we will use the
volumeTag(X) parametric variable instead. For this example we will compute the integrated
fluid kinetic energy by simply adding up the kinetic energy integrated over each cell. The basic
approach is as follows:
// KEComponent_X is the integrated kinetic energy for component X. This
// rule is parametric so one of these variables will be created for each
// component.
$type KEComponent_X param ;
$rule unit(KEComponent_X),constraint(volumeTag(X)),
parametric(volumeTag(X)) {
$KEComponent_X = 0 ;
}
// Here we integrate the kinetic energy over each cell which by use
// of the midpoint rule is simply vol*rho*0.5*dot(u,u), then the sum
// is the total kinetic energy
$rule apply(KEComponent_X<-vol,rho,u)[Loci::Summation],
constraint(volumeTag(X)),parametric(volumeTag(X)) {
double kecell = 0.5*$vol*$rho*dot($u,$u) ;
join($KEComponent_X,kecell) ;
}
Then KEComponent X can be written out to the screen in a similar fashion as the previous
example.

13

7

Adding a Volume Source

Another common type of addition to the solver is to add a new source term to the equations.
In this example we add a body force to return momentum that is lost to viscous walls for
the purpose of computing channel flow type of simulations. When using the implicit solver a
jacobian may need to also be computed depending on the stiffness of the source term. In flowPsi
there are two different possible jacobian types that will need to be supported to support all
implicit formulations. By default the code uses jacobians with respect to the primitive variables
density and gage pressure, but in modes using the local preconditioner it will use temperature
and gage pressure instead. If you want to support both modes of operation you will need to
define both types of jacobians.
For the channel flow case we first need to find out how much momentum was lost through the
fluid interaction with the viscous walls. For this we utilize a convenience variable (AllViscousBCs)
that contains all viscous boundary conditions including viscousWall and wallLaw. For the
integration of the momentum flux we consider both the inviscid flux, iflux, and the viscous
flux vflux. Since these variables contain mass, momentum, and energy fluxes, we need to
extract just the part that we are going to use. For the inviscid flux we skip the first item as
this is the mass flux. For the viscous flux the first item is the momentum flux as there is no
mass flux in the variable vflux
$type viscousWallMomentumFlux param ;
$rule unit(viscousWallMomentumFlux),constraint(UNIVERSE) {
$viscousWallMomentumFlux = vect3d(0,0,0) ;
}
// Note viscous and inviscid flux have already been integrated over the
// boundary facet area. We just need to sum up their contributions
$rule apply(viscousWallMomentumFlux<-iflux)[Loci::Summation],
constraint(AllViscousBCs) {
const int mi = 1
// extract inviscid momentum flux
vect3d mflux($iflux[mi+0],$iflux[mi+1],$iflux[mi+2]) ;
join($viscousWallMomentumFlux,mflux) ;
}
$rule apply(viscousWallMomentumFlux<-vflux,qvi)[Loci::Summation],
constraint(AllViscousBCs) {
const int mi = 0 ;
// extract viscous momentum flux
vect3d mflux($vflux[mi+0],$vflux[mi+1],$vflux[mi+2]) ;
join($viscousWallMomentumFlux,mflux) ;
}
Now we can compute the total force that we need to add back into the system to restore the lost
momentum. To do this we also integrate the total mass in the volume using a similar unit/apply
rule combination to compute the variable massSum. The body force is then computed for each
cell based on the local density. We also add a term to the energy equation to account for
the energy added to the system due to this addition (since we do not wish this energy to be
deducted from the internal energy of the fluid). Adding the resulting body force to the solver
then is implemented as:
14

//add friction loss to the source term
$rule apply(src<-dragAccel,rho,vol,cellcenter,u)[Loci::Summation] {
const int mi = 1 ;
const int mj = mi + 1 ;
const int mk = mi + 2 ;
const int ei = 4 ;
$src[mi] += $dragAccel.x*$rho*$vol ;
$src[mj] += $dragAccel.y*$rho*$vol ;
$src[mk] += $dragAccel.z*$rho*$vol ;
$src[ei] += $dragAccel.x*$u.x*$rho*$vol ;
}
The jacobians in flowPsi are computed with respect to the primitive variables gage pressure,
temperature, and velocity. The jacobians for the diagonal blocks are stored in the variable
srcJ. For this simple source term the jacobians are straightforward to implement which is
shown below:
$rule apply(srcJ<-rho,gagePressure,temperature,Pambient,u,dragAccel,vol)[Loci::Summation],
constraint(vol) {
const int mi = 1 ;
const int mj = mi + 1 ;
const int mk = mi + 2 ;
const int ei = 4 ;
const real drdt = -$rho/$temperature ;
const real drdp = $rho/($gagePressure+$Pambient) ;
real coefP = drdp*$vol ;
real coefT = drdt*$vol ;
real rho_vol = $rho*$vol ;
$srcJ[mi][4] += $dragAccel.x*coefP ;
$srcJ[mj][4] += $dragAccel.y*coefP ;
$srcJ[mk][4] += $dragAccel.z*coefP ;
$srcJ[ei][4] += dot($dragAccel,$u)*coefP ;
$srcJ[mi][0] += $dragAccel.x*coefT ;
$srcJ[mj][0] += $dragAccel.y*coefT ;
$srcJ[mk][0] += $dragAccel.z*coefT ;
$srcJ[ei][0] += dot($dragAccel,$u)*coefT ;
$srcJ[ei][mi] += $dragAccel.x*rho_vol ;
$srcJ[ei][mj] += $dragAccel.y*rho_vol ;
$srcJ[ei][mk] += $dragAccel.z*rho_vol ;
}

15

8

Scalar Transport

The flowPsi code provides an infrastructure that makes it simple to add new variables that
will be transported with the fluid flow. These variables makes it simple to add new models
to accompany the basic fluid flow. In this section we will discuss two examples. First the
development of a simple tracer variable that can inject spatially distributed sine waves that can
be convected with the flow and a second which goes through the implementation of a simple
one equation turbulence model. One advantage of using the scalar transport facilities is that
these facilities include interfaces with the restart, overset, and time integration facilities of the
flowPsi solver. Thus the scalar transport facilities gives a comprehensive way of adding new
variables to the flow solver.
Before we begin, we will have a quick review of the implementation of the scalar transport
equations. The scalar transport equation in conservative form is written for a convected scalar,
φ as
∂ρφ
+ ∇ · (ρuφ) = ∇ · (λ∇φ) + Sφ ,
(1)
∂t
where ρ is the fluid density, u is the fluid velocity vector, λ is a diffusion coefficient, and Sφ
is a prescribed source term. We note that by substituting φ = 1 into the above equation one
recovers the global continuity equation. We discretize the above equation in time and space to
arrive at the expression

V 
(1 + ψ)(φn+1 ρn+1 − φn ρn ) + ψ(φn ρn − φn−1 ρn−1 ) =
∆t
X
X
−
φn+1
ṁf +
Dφ f + Sφ ,
u

(2)

where φu is an upwinded extrapolation of φ based on the sign of the face mass flux ṁ and Dφ f
is the numerical diffusion flux for scalar φ. Note that the discrete continuity equation is given
by
X

V 
(1 + ψ)(ρn+1 − ρn ) + ψ(ρn − ρn−1 ) = −
ṁf .
(3)
∆t
To improve the diagonal dominance of the scheme we employ a trick where we recognize that
Eq. (3) is zero when the system of equation is solved, thus we multiply Eq. (3) by φn+1 and
subtract it from Eq. (3) to arrive at the following expression:




V 
(1 + ψ) φn+1 − φn ρn + φn ρn − φn−1 ρn−1 ψ − ρn − ρn−1 φn+1 ψ −
∆t
h
i
X
X
X
φn+1
ṁf −
φun+1 ṁf +
Dφ f + Sφ = Lφ (φn+1 ) = 0.

(4)

The above operator, Lφ , is solved using a Newton method concurrently with the fluid equations.
These facilities can be enable to track the convection and diffusion of any arbitrary scalar. The
facility is provided by the parametric variables scalarTransport(X,C) or scalarTransportP(X,C)
where the first version is for variables that can be of any sign while the second is for variables
that are always positive. The first X argument to the parameter is the name of the scalar to be
transported, while the second C argument is a constraint that can be used to activate/deactivate
the scalar integration. Accessing this variable causes an entire iterative infrastructure to be
constructed that solves the scalar transport equations which can then be augmented with additional source terms to create a wide variety of models. An arbitrary number of scalar transport
equations can be constructed.
16

8.1

Tracer Variable Example

The first example that we have is simply the convection of a tracer variable that will simply
be convected with the flow. This will allow us to show how to set up the boundary and initial
conditions are setup. For this we will create the scalar transport for the variable tracer.
To setup flowPsi to transport the variable we first need to instantiate the scalar transport
equations. This is accomplished with the line:
$rule pointwise(OUTPUT<-scalarTransport(tracer,UNIVERSE)) { }
Note that this rule will never actually execute. Instead, the existence of this rule will cause the
parametric scalarTransport variable to be instantiated for the variable tracer. Setting the
second argument to UNIVERSE ensures that this scalar transport model will be always active.
To complete the scalar transport equations we need to provide the state of the tracer variable
at the boundaries. For this we define the boundary variable tracer bc. In general we will
define the variable for whatever our scalar name is followed by the bc postfix. In this case we
will use a spatially sinusoidal function for boundaries that we mark as having the tracer and set
the function to zero otherwise. By default the boundary condition will use simple upwinding
to determine the value of tracer f. To define this we simply use the rules:
$type tracer_bc store ;
$rule pointwise(tracer_bc),constraint(ref->BC_options) {
$tracer_bc = 0 ;
}

$rule pointwise(marker::tracer_bc<-facecenter,tracerNormal,tracerPt,tracerInterval),constra
real r = 2.*M_PI*dot($facecenter-$tracerPt,$tracerNormal)/$tracerInterval ;
$tracer_bc = cos(r) ;
}
Finally we need to output the tracer so that we can see where the tracer advected in the
simulation. This is accomplished with the line:
OUTPUT_SCALAR("cell2node(tracer)",tracer) ;
That is all that is needed to add the most basic type of scalar transport equation. Next we will
show how to develop a model with more comprehensive set of parts: a turbulence model.

8.2

The Menter one equation turbulence model

The Menter one equation turbulence model[1] is formally derived from the k −  model after
eliminating k and  from the equations by using the definition of the eddy viscosity and by
assuming that the turbulent shear stress is proportional to the turbulent kinetic energy. The
model solves the following single equation for the undamped eddy viscosity ν̃t :
17

 


∂ρν̃t
ν̃t
+ ∇ · (ρ~uν̃t ) = ∇ · ρ ν +
∇ν̃t + ρc1 D1 ν̃t S − ρc2 E1e
∂t
σ

(5)

where S is the strain rate defined by:

S=

q

(∇~u) · (∇~u + ∇~uT )

(6)

The modified destruction term E1e is defined as:


Ek−
c3 EBB

E1e = c3 EBB tanh
where
Ek− = ν̃t2



1

2

LV K

= ν̃t2




,

∇S · ∇S
S2

(7)


(8)

involves the inverse of the von Karman length scale, and EBB is the Baldwin-Barth destruction
term:
EBB = ∇ν̃t · ∇ν̃t .
(9)
The damping terms D1 and D2 are defined as:
D1 =

νt + ν
ν̃t + ν
+

D2 = 1 − e−(ν̃t /A

(10)

κν)2

,

(11)

and the model constants are:
c1 = 0.144, c2 = 1.86, c3 = 7, κ = 0.41, σ = 1, A+ = 13 .

(12)

The damped eddy viscosity is obtained from:
νt = D2 ν̃t .

(13)

The boundary conditions for ν̃t at a solid wall are ν̃t = 0. The initial and free-stream conditions
are ν̃t∞ ≤ ν∞ . The default initial and free-stream value for ν̃t is 5 x 10−9 m2 /s. Note that the
boundary conditions are given in terms of the undamped eddy viscosity (ν̃t ) rather than νt .
8.2.1

Implementation

Examining equation (5) we note that the scalar transport will implement the first three terms.
We will need to define the diffusion coefficient and the last two terms to implement the turbulence model. First we invoke the scalar transport of the variable nuTM which represents the ν̃t
in the equations. This is accomplished with the following code:
18

$rule pointwise(OUTPUT<-scalarTransportP(nuTM,menter)) { }
Here we constraint the model by the variable menter which will be defined as part of the model
as the set of constants that are used in the model. For the initial conditions we will extract
nu t from the initial conditions. This is extracted with the following rules:
$type nuTM_ic store ;
$type icRegionInfo blackbox ;
$rule pointwise(nuTM_ic<-icRegionInfo,cellcenter),
constraint(geom_cells,menter) {
double nu_tm ;
$icRegionInfo.defaultState.get_nu_t(nu_tm) ;
$nuTM_ic = nu_tm ;
}
To complete the basic transport of nuTM we need to setup the boundary conditions for the
model For this we define:
$type nuTM_bcVal store ;
// extract the boundary condition from BC_options
$rule pointwise(nuTM_bcVal<-BC_options) {
real nuTM = 5e-9 ;
if($BC_options.optionExists("nu_t"))
$BC_options.getOption("nu_t",nuTM) ;
$nuTM_bcVal = nuTM ;
}
// set boundary condition
$rule pointwise(nuTM_bc<- ref->nuTM_bcVal) {
$nuTM_bc = $ref->$nuTM_bcVal ;
}
This does not complete the boundary condition however, as the default is to use upwinding to
define the boundary face values. However for a boundary such as a viscous wall the boundary
value for nuTM f must be set explicitly. This is accomplished with the priority override rule
that will replace the upwind definition with this Dirichlet one:
$rule pointwise(noslip::nuTM_f),constraint(viscousWall_BC) {
$nuTM_f = 0.0 ;
}
Now we need to define the scalar diffusion term which is the third term in equation (5). This
will be defined by a face value for the diffusion coefficient which will have a name derived from
the scalar variable name by appending nu f. The diffusion term is thus implemented with the
following rules:
$type nuTM_nu_f store ;
19

$rule pointwise(nuTM_nu_f<-rho_f,nuTM_f,mu_f,menter) {
const real sigma = $menter.sigma ;
$nuTM_nu_f = ($mu_f+$rho_f*$nuTM_f/sigma) ;
}
// The diffusion coefficient needs nuTM_f at all faces, but the
// infrastructure only defines them at the boundary.
// For interior faces we set the face value to be simply the
// reciprocal volume weighted average of the cell values
$rule pointwise(nuTM_f<-(cr,cl)->(vol,nuTM)) {
real vrvl = 1.0/($cr->$vol+$cl->$vol) ;
$nuTM_f = ($cr->$vol*$cl->$nuTM+$cl->$vol*$cr->$nuTM)*vrvl ;
}
Now we have completely defined the first three terms of equation (5). Now what remains is
to define the destruction and production terms. To add these terms we will use a unit rule
to combine them with the previous terms in the variable nuTM src. First lets consider the
production term which which can be added using the rules:
$type nuTM_prod store ;
$rule pointwise(nuTM_prod<-nuTM,D1,rho,strainRate,vol,menter) {
const real c1 = $menter.c1 ;
const real P_k = $rho*c1*$D1*$nuTM*$strainRate ;
$nuTM_prod = P_k ;
}
//=======================================
// add integrated production term to rhs
//=======================================
$rule apply(nuTM_src<-nuTM_prod,vol)[Loci::Summation] {
$nuTM_src += $nuTM_prod*$vol ;
}
where strainRate is provide by the solver and D1 is the production damping function which
is defined by the rule:
$type D1 store ;
$rule pointwise(D1<-nuTM,tmu,mu,rho) {
const real nut = $tmu/$rho ;
const real nu = $mu/$rho ;
$D1 = (nut+nu)/($nuTM+nu) ;
}
Note, the turbulent viscosity (tmu) has not yet been defined but will be shortly. The order
that rules appear in the file do not matter. Note, we have not considered the jacobian of the
production term. In general the sign of the production jacobian is destabilizing and as a general
rule it is desirable to leave this term out of an implicit implementation.
Next we will consider the last destruction term. This can be implemented using the following
rules:
20

$rule pointwise(nuTM_dest<-nuTM,strainRate,grads(nuTM),grads(strainRate),rho,menter) {
const real c3 = $menter.c3 ;
const real Ebb = dot($grads(nuTM),$grads(nuTM)) ;
const real Sdot = dot($grads(strainRate),$grads(strainRate)) ;
const real S2 = $strainRate*$strainRate ;
const real Eke = pow($nuTM,2)*Sdot/(S2+EPSILON) ;
real E1e = c3*Ebb*tanh(Eke/(c3*Ebb+EPSILON)) ;
const real c2 = $menter.c2 ;
$nuTM_dest = $rho*c2*E1e ;
}
//======================================
// Add integrated destruction term rhs
//======================================
$rule apply(nuTM_src<-nuTM_dest,vol)[Loci::Summation] {
join($nuTM_src, -$nuTM_dest*$vol) ;
}
One point to note on this is that we need the gradient of strainRate to implement this
term. For this implementation we are simply using the least squares gradient provided by the
FVM module to compute this. However, we will run into a problem here because the gradient
computation will require a value for strainRate at the face which is not provided by the solver.
For expediency we will assume that strainRate is relatively constant at the boundaries and
just copy from the cell to the face. This is accomplished with the rule
$rule pointwise(strainRate_f<-ci->strainRate) {
$strainRate_f = $ci->$strainRate ;
}
This implementation is OK in a pinch, but more sophisticated methods for computing this would
be suggested for a production level implementation. In particular the recursive application of
least squares gradients can be sensitive to numerical noise and the treatment of the boundary
values may lead to spurious results. For our limited tests this implementation seems to produce
satisfactory results.
For the destruction term it can be helpful to include a jacobian term. However, due to the
gradients of nuTM used in the destruction term, the exact jacobian can be expensive and tedious
to derive. For this reason we use a diagonalized approximation that can stabilize the solver
with a reasonable cost. The diagonal term of the jacobian is in the variable nuTM srcJ. Our
approximation is implemented using the following rule
//=================================================
// Apply an approximate destruction term jacobian
//=================================================
$type nuTM_srcJ store ;
$rule apply(nuTM_srcJ<-nuTM_dest,vol,nuTM)[Loci::Summation] {
join($nuTM_srcJ, -$vol*2.*$nuTM*($nuTM_dest/pow($nuTM,2))) ;
}
21

Now all that remains is defining the turbulent viscosity which is computed from the scalar
variable we are solving combined with a the damping term D2 . This is defined using the rules:
// nuTM damping function
$type D2 store ;
$rule pointwise(D2<-nuTM,rho,mu,menter) {
const real aplus = $menter.aplus ;
const real kappa = $menter.kappa ;
const real nu = $mu/$rho ;
const real a1 = pow($nuTM/(aplus*kappa*nu),2) ;
$D2 = 1.-exp(-a1) ;
}
//=========================================================
// Define turbulent viscosity for mean flow solver to use
//=========================================================
$rule pointwise(menter::tmu<-nuTM,rho,D2) {
$tmu = $rho*$D2*$nuTM ;
}
And with this we complete the implementation of the turbulence model.

References
[1] F R. Menter. Eddy viscosity transport and equations and their relation to the k- model.
J. of Fluids Engineering, 119:876–884, 1997.

22



Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.5
Linearized                      : No
Page Count                      : 24
Producer                        : pdfTeX-1.40.14
Creator                         : TeX
Create Date                     : 2019:05:31 20:54:58-05:00
Modify Date                     : 2019:05:31 20:54:58-05:00
Trapped                         : False
PTEX Fullbanner                 : This is pdfTeX, Version 3.1415926-2.5-1.40.14 (TeX Live 2013) kpathsea version 6.1.1
EXIF Metadata provided by EXIF.tools

Navigation menu