a tutorial to urban wind flow using openfoam for the course cfd with opensource...

46
A tutorial to urban wind flow using OpenFOAM for the course CFD with OpenSource Software David Segersson Swedish Meteorological and Hydrological Institute & Stockholm University 2017-11-23 David Segersson 2017-11-23 1 / 46

Upload: others

Post on 29-Mar-2020

66 views

Category:

Documents


0 download

TRANSCRIPT

A tutorial to urban wind flow using OpenFOAMfor the course

CFD with OpenSource Software

David Segersson

Swedish Meteorological and Hydrological Institute & Stockholm University

2017-11-23

David Segersson 2017-11-23 1 / 46

Introduction

Urban wind flow is affected by:

A rough ground surface

buildings

vegetation

varying topography

moving vehicles

Most of the time the ABL (Atmospheric Boundary Layer) is stratified.In this tutorial we will only consider the neutral ABL.The tutorial will focus on the description of flow through tree canopy andhow to modell flow over the rough ground surface.

David Segersson 2017-11-23 2 / 46

Learning outcomes

How to use it:

How to set-up boundary conditions for simulations of wind in anurban environment.

How to apply and evaluate rough wall-functions for the ABL.

The theory of it:

How to model ground roughness consistently with inlet boundaryconditions.

How to represent tree canopy using a porosity model.

How it is implemented:

How run-time selectable source-terms can be added using thefvOptions framework.

How to implement a custom run-time selectable option for addingsource-terms to the momentum and turbulence equations.

How to set varying roughness length within the same wall patch.

David Segersson 2017-11-23 3 / 46

The test case

Open moorland with a forest edge, Northumberland, England.Forest is a uniform plantation of Sitka spruce.Measurements published by Irvine & Gardiner (1997)

David Segersson 2017-11-23 4 / 46

Pre-processing

Copy the turbineSiting tutorial to the run directory and rename it asirvineForestEdge.

cp -r $FOAM_TUTORIALS/incompressible/simpleFoam/turbineSiting \

$FOAM_RUN/irvineForestEdge

cd $FOAM_RUN/irvineForestEdge

David Segersson 2017-11-23 5 / 46

blockMeshDict

The mesh for the test domain is generated with blockMesh.Vertical grading, first cell height at ground around 0.2 m, last cell around8 mEmpty patches are created in north and south direction.

David Segersson 2017-11-23 6 / 46

Adding a patch

A new patch is added using the tool createPatch. This tools can create anew patch from a cellSet. The cellSet is first generated using the utilitytopoSet.

actions

(

{

name forest;

type faceSet;

action new;

source boxToFace;

sourceInfo

{

box (0 -100 -100) (1000 100 0.01);

}

}

);

Running

topoSet

David Segersson 2017-11-23 7 / 46

Adding a patch

and the patch is then created using the createPatch utility.

pointSync false;

patches

(

{

name forest;

patchInfo

{

type wall;

}

constructFrom set;

set forest;

}

);

Running

createPatch -overwrite

David Segersson 2017-11-23 8 / 46

Setting boundary conditions

The boundary conditions from the original turbineSiting case are manuallymodified by:

1 Copying the bc configuration for the terrain patch to the groundpatch.

2 Renaming the terrain patch to forest.

David Segersson 2017-11-23 9 / 46

Inlet boundary conditions

The approaching wind profile for a neutral ABL is often modelled usingboundary conditions suggested by Richards and Hoxey (1993), assuming:

1 zero vertical velocity.

2 pressure is constant in vertical and streamwise directions.

3 constant shear stress in the boundary layer.

4 the turbulent kinetic energy, k and dissipation rate, ε, satisfy theirtransport equations.

David Segersson 2017-11-23 10 / 46

Inlet boundary conditions

The velocity and turbulence profiles are given by:

u =u∗κln

(z + z0z0

)

k =u2∗√Cµ

ε =u3∗

κ (z + z0)

where κ is von Karmans constant and z0 is the roughness length [m]. Thek and ε conservation equations are satisfied when

σε =κ2

(Cε2 − Cε1)√Cµ

where σε, Cε2, Cε1 and Cµ are coefficients of the k − ε model.David Segersson 2017-11-23 11 / 46

Ground boundary conditions

The ground surface can be very rough!The often used Nikuradse sand-grain roughness is made for smallroughness elements. For atmospheric flows, the roughness length, z0 isused.The relation between ks and z0 is z0 = 20ks

The ks wall functions do not accept the first cell center from the wall at adistance > ks. For low crops, z0 ≈ 0.1 would give us ks ≈ 2m, forcing usto use a very coarse mesh.

For wallfunctions using z0, the domain starts at z0 and there are no limitsregarding the first cell height.

David Segersson 2017-11-23 12 / 46

How about y+?

The usually applied requirement 30 < y+ < 300 to apply wallfunctions does not hold.

Best practice for flow at pedestrian level is to use around 5 cells below2 meters above ground.

David Segersson 2017-11-23 13 / 46

The influence of tree canopy

the branches and leafs of trees are not resolved by the mesh.

the influence of the canopy is modelled using source-terms formomentum and turbulence

there are several formulations published for the source-terms, we willfollow Dalp and Masson (2007)

David Segersson 2017-11-23 14 / 46

Dalp and Masson canopy model

Su = −ρCdα|U |U

Sk = ρCdα(βp|U |3 − βdk|U |

)Sε = ρCdα

ε

k

(Cε4βp|U |3 − Cε5βdk|U |

)where Su, Sk and Sε are source-terms for momentum, k and ε respectively,α is the leaf area density and Cd is the tree canopy drag coefficient. Theβp (1.0), βd (5.03), Cε4 (0.78) and Cε5 (0.78) are model constants.

David Segersson 2017-11-23 15 / 46

Leaf Area Density & Leaf Area Index

The Leaf Area Density (LAD) measures leaf area per m3

LAD varies within the tree canopy

Leaf Area Index is the integral of LAD over tree height.

LAI is easier to measure.

David Segersson 2017-11-23 16 / 46

The fvOptions framework

source-terms, constraints and corrections can be added at run-time toany solver

Introduced in OpenFOAM 2.2, before there were multiple versions ofeach solver to allow for Moving Reference Frames and porous flow.

Examples of options inlcuded with OpenFOAM are:

semiImplicitSource

actuationDiscSource

meanVelocitySource

explicitPorositySource

buoyancyForce

David Segersson 2017-11-23 17 / 46

How are options added to the solvers?

Each solver using the fvOptions framework includes the filecreateFvOptions.H where an object named fvOptions is created as:

fv::options& fvOptions(fv::options::New(mesh));

The New function is found in options base class defined in$FOAM SRC/finiteVolume/cfdTools/general/fvOptions.C

David Segersson 2017-11-23 18 / 46

Initialisations of options

The options base class inherits the optionList class.

options::New(...) → options::options(...) → optionList::optionList(...) →optionList::reset(...)

In optionList::reset(), option::New() is called for each option found in theconstant/fvOptions dictionary.

option::New identifies and calls the constructor of each option type definedin constant/fvOptions.

David Segersson 2017-11-23 19 / 46

How are source-terms added?

Example from simpleFoam:

tmp<fvVectorMatrix> tUEqn

(

fvm::div(phi, U)

+ MRF.DDt(U)

+ turbulence->divDevReff(U)

==

fvOptions(U)

);

David Segersson 2017-11-23 20 / 46

How are source-terms added?

The options class has overloaded the operator ():

template<class Type>

tmp<fvMatrix<Type>> operator()

(

GeometricField<Type, fvPatchField, volMesh>\& field

);

which redirects to:

tmp<Foam::fvMatrix<Type>> Foam::fv::optionList::operator()

(

GeometricField<Type, fvPatchField, volMesh>\& field,

const word\& fieldName

)

David Segersson 2017-11-23 21 / 46

Finally applying the source-terms...template<class Type>

Foam::tmp<Foam::fvMatrix<Type>> Foam::fv::optionList::operator()

(

GeometricField<Type, fvPatchField, volMesh>\& field,

const word\& fieldName

)

{

checkApplied();

const dimensionSet ds = field.dimensions()/dimTime*dimVolume;

tmp<fvMatrix<Type>> tmtx(new fvMatrix<Type>(field, ds));

fvMatrix<Type>\& mtx = tmtx.ref();

forAll(*this, i)

{

option\& source = this->operator[](i);

label fieldi = source.applyToField(fieldName);

if (fieldi != -1)

{

addProfiling(fvopt, "fvOption()." + source.name());

source.setApplied(fieldi);

if (source.isActive())

{

if (debug)

{

Info<< "Applying source " << source.name() << " to field "

<< fieldName << endl;

}

source.addSup(mtx, fieldi);

}

}

}

return tmtx;

}

David Segersson 2017-11-23 22 / 46

Creating a customized option

The desired features of the new option are:

allow a spatially varying landuse

a landuse class should represent roughnes length and verticalLAD-profile

a landuse class should be applicable for varying tree canopy height

allow specifying landuse patch-wise or via a separate GIS raster file

allow specifying tree canopy height per landuse class or via a separateGIS raster file

allow writing fields to disk for visualization

allow parallel calculations

David Segersson 2017-11-23 23 / 46

The landuseClass

A separate class named landuseClass is developed to represent a specifictype of landuse.

Attributes:

code

z0 (roughness length [m])

LAI (Leaf Area Index [-])

LADmax (maximum Leaf Area Density in a vertical profile [m−1])

height (canopy height above ground [m])

LADProfile (normalized LAD values describing the verticaldistribution within the canopy)

David Segersson 2017-11-23 24 / 46

How to read landuse classes from fvOptions dictionaryWe create a constructor that instatiates a landuseClass from a dictionary.

landuseClass::landuseClass(const dictionary& dict, word name) {

dictionary landuseClassDict(dict.subDict(name));

landuseClassDict.lookup("code") >> code_;

name_ = name;

Cd_ = landuseClassDict.lookupOrDefault("Cd", 0.2, false, false);

height_ = landuseClassDict.lookupOrDefault("height", 0.0, false, false);

z0_ = landuseClassDict.lookupOrDefault("z0", 0.001, false, false);

LAI_ = landuseClassDict.lookupOrDefault("LAI", 0.0, false, false);

LADmax_ = landuseClassDict.lookupOrDefault("LADmax", -1.0, false, false);

if (landuseClassDict.found("LADProfile"))

landuseClassDict.lookup("LADProfile", false, false) >> LADProfile_;

if (LADmax_ == -1.0) {

LADmaxFromLAI();

}

}

David Segersson 2017-11-23 25 / 46

Some additional classes

Raster.C and Raster.HWe need to read and handle geo-referenced rasters (ESRI Ascii rasterformat). The class Raster is provided.

groundDist.C and groundDist.HWe also need to calculate distances from a specified patch. There issupport for this in the OpenFOAM core libraries. A modified class fromOpenFOAM 2.2 is provided for this.

David Segersson 2017-11-23 26 / 46

The canopySource base classSince there are different implementations of canopy source-terms, theinitialization and general functionality is placed in a base class. The

member variables are:

wordList sourcePatches_;

autoPtr<volScalarField> canopy_;

HashTable<landuseClass, label> landuseTable_;

HashTable<landuseClass, label> patchLanduseTable_;

Switch readLanduseFromRaster_;

Raster landuseRaster_;

Switch readCanopyHeightFromRaster_;

Raster canopyHeightRaster_;

vector translateRaster;

Switch writeFields_;

bool _LAD_from_disk;

bool _landuse_from_disk;

bool _z0_from_disk;

David Segersson 2017-11-23 27 / 46

The canopySource member functions

void addSup(const volScalarField& rho, fvMatrix<vector>\& eqn, const label fieldi);

void addSup(fvMatrix<vector>\& eqn, const label fieldi);

bool read(const dictionary& dict);

void checkData() const;

Raster readRaster(fileName rasterPath);

void readLanduseClasses();

void calculatePatchDistance(label patch, volScalarField &d);

void calculateCanopy();

void setPatchLanduse(

label patch, volScalarField &landuse,

volScalarField &LAD, volScalarField &z0,

volScalarField &nut, volScalarField &d

);

David Segersson 2017-11-23 28 / 46

The fvOptions dictcanopy

{

type canopySource;

active on;

writeFields on;

readLanduseFromRaster on;

readCanopyHeightFromRaster on;

sourcePatches (ground forest);

patchLanduse (0 1);

translateRaster (0 0 0);

landuse

{

low\_birch

{

code 1;

Cd 0.2;

LAI 2.15;

LADmax 1.2;

z0 0.06;

height 7.5;

LADProfile ( 0.05 0.1 0.15 0.35 1.1 0.9 0.5 0.2 0.15 0.05 0.01 );

};

grass

{

code 0;

Cd 0.2;

LAI 0;

z0 0.06;

height 0;

};

};

}David Segersson 2017-11-23 29 / 46

Implementing the Dalpe and Masson canopy model

dalpeMassonCanopySource inherits the canopySource base class.

member variables are added for the empirical coeffients.

addSup is implemented for momentum and turbulence equations.

David Segersson 2017-11-23 30 / 46

addSup for momentum

Versions with and without density included.

Su = −ρCdα|U |U

void Foam::fv::dalpeMassonCanopySource::addSup

(

fvMatrix<vector>\& eqn,

const label fieldi

)

{

const volVectorField\& U = eqn.psi();

const volScalarField\& canopy = canopy_;

fvMatrix<vector> S_canopy

(

fvm::Sp(canopy * mag(U), U)

);

eqn -= S_canopy;

}

David Segersson 2017-11-23 31 / 46

addSup for k

Sk = ρCdα(βp|U |3 − C5βdk|U |

)if (eqn.psi().name() == word("k")) {

const volScalarField& k = eqn.psi();

const volVectorField& U = mesh_.lookupObject<volVectorField>("U");

fvMatrix<scalar> Sk

(

betaP_*canopy*pow(mag(U),3)

+ fvm::Sp(betaD_*canopy*mag(U), k)

);

eqn += Sk;

}

David Segersson 2017-11-23 32 / 46

addSup for epsilon

Sε = ρCdαε

k

(C4βp|U |3 − C5βdk|U |

)else if (eqn.psi().name() == word("epsilon")) {

const volScalarField& epsilon = eqn.psi();

const volScalarField& k = mesh_.lookupObject<volScalarField>("k");

const volVectorField& U = mesh_.lookupObject<volVectorField>("U");

fvMatrix<scalar> Sepsilon

(

fvm::Sp(

canopy/k*(C4_*betaP_*pow(mag(U),3)

- C5_*betaD_*k*mag(U)),

epsilon

)

);

eqn += Sepsilon;

}

}

David Segersson 2017-11-23 33 / 46

Linearization of source-terms

If the source term for field TP is linearized as S = SC + SPTPwhere SC is the constant part and SP enters into the coeffiecient matrix.The value of SP should never be positive, or stability will be reduced(Patankar, 1980).In OpenFOAM the function for adding implicitly is fvm::Sp(...) The

functions fvm::SuSp(...) will try to determine if the source-term should beadded implicitly or explicitly.

David Segersson 2017-11-23 34 / 46

Implementation of non-uniform roughness

In the function setPatchLanduse, the z0 scalarField of the wall is accessedby:

Foam::nutkAtmRoughWallFunctionFvPatchScalarField& wallNut =

refCast<Foam::nutkAtmRoughWallFunctionFvPatchScalarField>(

nut.boundaryFieldRef()[patch]

);

scalarField& z0 = wallNut.z0();

The nu.boundaryFieldRef()[patch] returns the base classfvPatchScalarField. Therefore, to be able to access the specific attributesof the nutkAtmRoughWallFunctionFvPatchScalarField, a cast is necessary.

David Segersson 2017-11-23 35 / 46

Evaluating ability to maintain atmospheric inlet profiles along thedomain

Many commercial CFD codes (CFD, Fluent) have been shown inable tomaintain standard atmospheric wind speed and turbulence profiles for alonger distance using the standard k-Epsilon model and wall-functions. Wewill make a simple test to see how OpenFOAM performs.

David Segersson 2017-11-23 36 / 46

Run calculations, monitorin and post-processing

decomposePar

foamJob -parallel simpleFoam >& log &

foamMonitor -l -r 2 postProcessing/residuals/0/residuals.dat

reconstructPar -latestTime

simpleFoam -postProcess -func verticalProfiles -latestTime

./plot_horizontal_homogenity.py

David Segersson 2017-11-23 37 / 46

Evaluating ability to maintain atmospheric inlet profiles along thedomain

U and ε profiles are well preserved

k is offset from the expected and changes significantly over thedomain

more investigation required...

David Segersson 2017-11-23 38 / 46

Compile library and solver

src/fvOptionssrc/fvOptions/sources/canopySourcelanduseClass.C, landuseClass.HgroundDistance.C, groundDistance.HRaster.C, Raster.HcanopySource.C, canopySource.H

src/fvOptions/sources/dalpeMassonCanopySourcedalpeMassonCanopySource.C, dalpeMassonCanopySource.H

To compile the library:

cd \$WM_PROJECT_USER_DIR/applications/src/fvOptions

wmake

David Segersson 2017-11-23 39 / 46

Add constant/fvOptionscanopy

{

type dalpeMassonCanopySource;

active off;

writeFields on;

readLanduseFromRaster off;

readCanopyHeightFromRaster off;

canopyHeightRasterFileName "constant/canopy_height.asc";

translateRaster (0 0 0);

sourcePatches (ground forest);

patchLanduse (0 1);

landuse

{

low_birch

{

code 1;

Cd 0.2;

LAI 2.15;

z0 0.06;

height 7.5;

LADProfile ( 0.05 0.1 0.15 0.35 1.1 0.9 0.5 0.2 0.15 0.05 0.01 );

};

grass

{

code 0;

Cd 0.2;

LAI 0;

z0 0.06;

height 0;

};

};

}

David Segersson 2017-11-23 40 / 46

Running...

The case is rerun as before, but with fvOptions.After extracting vertical profiles. A plotting script is provided to comparewith measurements:

./plot_vertical_profiles.py

David Segersson 2017-11-23 41 / 46

Results

Fairly good performance for velocitysome deviation for turbulence intensity, maybe due to error inapproaching k profile...

David Segersson 2017-11-23 42 / 46

LAD at the forest edge

David Segersson 2017-11-23 43 / 46

k along the domain

David Segersson 2017-11-23 44 / 46

U along the domain

David Segersson 2017-11-23 45 / 46

Thank you!

David Segersson 2017-11-23 46 / 46