Two-dimensional adaptive meshing in OpenFOAM

19
Dynamic meshing in OpenFOAM in 2-D. O. Y. Shonibare September 26, 2015 This is the first version of this draft delineating the automatic refinement/ unrefinement process of hexahedral meshes in OpenFOAM with major emphasis on the two-dimensional implementation. Two major libraries are utilized during this process - dynamicMesh and dynamicFvMesh. Also, some variables to be used in the program are initialized by reading the dynamicMeshDictFile (See Fig. 1). 1. The refinement process begins by reading the dynamicMeshDict file. Listing 1: dynamicRefineFvMesh.C //Note that the dynamicMeshDict can be //modified on-the-fly dictionary refineDict ( IOdictionary ( IOobject ( "dynamicMeshDict", time().constant(), * this , IOobject::MUST_READ_IF_MODIFIED , IOobject::NO_WRITE, false ) ).subDict(typeName + "Coeffs") ); 2. Next, all cells that satisfy the refinement criteria are selected (The refinement criteria is based on the volume fraction field, alpha, the maximum number of refinement specified in the ’dynam- icMeshDict’ file, and the number of anchor points of a cell). These cells are called ’candidate cells’. (range for alpha, max refinement).Lastly, the number of anchor points must be ’8’. Listing 2: dynamicRefineFvMesh.C const word fieldName(refineDict.lookup("field")); const volScalarField& vFld = lookupObject <volScalarField >(fieldName); const scalar lowerRefineLevel = 1

Transcript of Two-dimensional adaptive meshing in OpenFOAM

Dynamic meshing in OpenFOAM in 2-D.

O. Y. Shonibare

September 26, 2015

This is the first version of this draft delineating the automatic refinement/ unrefinement process ofhexahedral meshes in OpenFOAM with major emphasis on the two-dimensional implementation. Twomajor libraries are utilized during this process - dynamicMesh and dynamicFvMesh. Also, some variablesto be used in the program are initialized by reading the dynamicMeshDictFile (See Fig. 1).

1. The refinement process begins by reading the dynamicMeshDict file.

Listing 1: dynamicRefineFvMesh.C

//Note that the dynamicMeshDict can be

// modified on-the -fly

dictionary refineDict

(

IOdictionary

(

IOobject

(

"dynamicMeshDict",

time (). constant(),

*this ,

IOobject :: MUST_READ_IF_MODIFIED ,

IOobject ::NO_WRITE ,

false

)

). subDict(typeName + "Coeffs")

);

2. Next, all cells that satisfy the refinement criteria are selected (The refinement criteria is basedon the volume fraction field, alpha, the maximum number of refinement specified in the ’dynam-icMeshDict’ file, and the number of anchor points of a cell). These cells are called ’candidate cells’.(range for alpha, max refinement).Lastly, the number of anchor points must be ’8’.

Listing 2: dynamicRefineFvMesh.C

const word fieldName(refineDict.lookup("field"));

const volScalarField& vFld =

lookupObject <volScalarField >( fieldName );

const scalar lowerRefineLevel =

1

Figure 1: Case directory structure.

readScalar(refineDict.lookup("lowerRefineLevel"));

const scalar upperRefineLevel =

readScalar(refineDict.lookup("upperRefineLevel"));

const scalar unrefineLevel = refineDict.lookupOrDefault <scalar >

(

"unrefineLevel",

GREAT

);

// Determine candidates for refinement (looking at field only)

// First , cells are selected for refinement based on the volume

// fraction field , alpha , only

selectRefineCandidates

(

lowerRefineLevel ,

upperRefineLevel ,

vFld ,

refineCell

);

3. Next, depending on the maximum number of cells specified in the ’d-file’, a subset of the candidatecells are chosen for refinement.

Listing 3: dynamicRefineFvMesh.C

// Select subset of candidates. Take into account max allowable

// cells , refinement level , protected cells.

2

labelList cellsToRefine

(

selectRefineCells

(

maxCells ,

maxRefinement ,

refineCell

)

);

4. The refinement process then proceeds as follows:

(a) A point is added to the center of all boundary faces of each candidate cell that lies on anempty patch. In the figure below and subsequent ones, shaded faces represent boundary facesthat belongs to an empty patch.

Listing 4: dynamicRefineFvMesh.C

// Introduce face points

...

forAll(faceMidPoint , faceI)

{

if (faceMidPoint[faceI] >= 0 &&

isDivisibleFace[faceI])

{

// Face marked to be split. Replace faceMidPoint

//with actual point label.

const face& f = mesh_.faces ()[ faceI];

faceMidPoint[faceI] = meshMod.setAction

(

polyAddPoint

(

(

faceI < mesh_.nInternalFaces ()

? mesh_.faceCentres ()[ faceI]

: bFaceMids[faceI -mesh_.nInternalFaces ()]

), // pointcontent ...

f[0], // master point

-1, // zone for point

true // supports a cell

)

);

// Determine the level of the corner points and midpoint will

// be one higher.

newPointLevel(faceMidPoint[faceI]) = faceAnchorLevel[faceI ]+1;

}

}

}

3

(b) Next, a point is added to the center of each boundary edges of a candidate cell that is anelement of empty patch only.

Listing 5: dynamicRefineFvMesh.C

// If p0 and p1 are existing vertices check if

//edge is split and insert splitPoint.

void Foam:: hexRef4 :: insertEdgeSplit

(

const labelList& edgeMidPoint ,

const label p0 ,

const label p1 ,

DynamicList <label >& verts

) const

{

if (p0 < mesh_.nPoints () && p1 < mesh_.nPoints ())

{

label edgeI = meshTools :: findEdge(mesh_ , p0, p1);

if (edgeI != -1 && edgeMidPoint[edgeI] != -1)

{

verts.append(edgeMidPoint[edgeI ]);

}

}

}

(c) Boundary faces with a point at the center are divided into four new faces, each with its‘owner’ and ‘neighbor’ cell.

Listing 6: dynamicRefineFvMesh.C

forAll(faceMidPoint , faceI)

{

if (faceMidPoint[faceI] >= 0 &&

affectedFace.get(faceI ))

{

// Face needs to be split and hasn’t yet

//been done in some way (affectedFace - is

// impossible since this is first change but

// just for completeness)

const face& f = mesh_.faces ()[ faceI];

// Has original faceI been used (three faces added , original gets

// modified)

bool modifiedFace = false;

label anchorLevel = faceAnchorLevel[faceI ];

if (isDivisibleFace[faceI])

{

face newFace (4);

4

forAll(f, fp)

{

label pointI = f[fp];

if (pointLevel_[pointI] <= anchorLevel)

{

// point is anchor. Start collecting face.

DynamicList <label > faceVerts (4);

faceVerts.append(pointI );

// Walk forward to mid point.

// - if next is +2 midpoint is +1

// - if next is +1 it is midpoint

// - if next is +0 there has to be edgeMidPoint

walkFaceToMid

(

edgeMidPoint ,

anchorLevel ,

faceI ,

fp ,

faceVerts

);

faceVerts.append(faceMidPoint[faceI ]);

walkFaceFromMid

(

edgeMidPoint ,

anchorLevel ,

faceI ,

fp ,

faceVerts

);

// Convert dynamiclist to face.

newFace.transfer(faceVerts );

// Get new owner/neighbour

label own , nei;

getFaceNeighbours

(

cellAnchorPoints ,

cellAddedCells ,

faceI ,

pointI , // Anchor point

own ,

nei

5

);

if (debug)

{

if (mesh_.isInternalFace(faceI ))

{

label oldOwn = mesh_.faceOwner ()[ faceI];

label oldNei = mesh_.faceNeighbour ()[ faceI ];

checkInternalOrientation

(

meshMod ,

oldOwn ,

faceI ,

mesh_.cellCentres ()[ oldOwn],

mesh_.cellCentres ()[ oldNei],

newFace

);

}

else

{

label oldOwn = mesh_.faceOwner ()[ faceI];

checkBoundaryOrientation

(

meshMod ,

oldOwn ,

faceI ,

mesh_.cellCentres ()[ oldOwn],

mesh_.faceCentres ()[ faceI],

newFace

);

}

}

if (! modifiedFace)

{

modifiedFace = true;

modFace(meshMod , faceI , newFace , own , nei);

}

else

{

addFace(meshMod , faceI , newFace , own , nei);

}

}

}

}

6

(d) While boundary faces with no central point are divided into two faces, each with its ‘owner’and ‘neighbor’ cell.

Listing 7: dynamicRefineFvMesh.C

else // boundary faces with no central point

{

face newFace (2);

forAll(f,fp)

{

label pointI = f[fp];

label nextpointI = f[f.fcIndex(fp)];

label edgeI = meshTools :: findEdge

(mesh_ , pointI , nextpointI );

if (edgeMidPoint[edgeI] >=0)

{

DynamicList <label > faceVerts (4);

label pointJ = f[f.rcIndex(fp)];

faceVerts.append(pointI );

walkFaceToMid

(

edgeMidPoint ,

anchorLevel ,

faceI ,

fp ,

faceVerts

);

walkFaceFromMid

(

edgeMidPoint ,

anchorLevel ,

faceI ,

f.rcIndex(fp),

faceVerts

);

faceVerts.append(pointJ );

newFace.transfer(faceVerts );

label own , nei;

getFaceNeighbours

(

cellAnchorPoints ,

cellAddedCells ,

faceI ,

pointI ,

own ,

nei

);

if (debug)

7

{

if (mesh_.isInternalFace(faceI ))

{

label oldOwn = mesh_.faceOwner ()[ faceI];

label oldNei = mesh_.faceNeighbour ()[ faceI ];

checkInternalOrientation

(

meshMod ,

oldOwn ,

faceI ,

mesh_.cellCentres ()[ oldOwn],

mesh_.cellCentres ()[ oldNei],

newFace

);

}

else

{

label oldOwn = mesh_.faceOwner ()[ faceI];

checkBoundaryOrientation

(

meshMod ,

oldOwn ,

faceI ,

mesh_.cellCentres ()[ oldOwn],

mesh_.faceCentres ()[ faceI],

newFace

);

}

}

if (! modifiedFace)

{

modifiedFace = true;

modFace(meshMod , faceI , newFace , own , nei);

}

else

{

addFace(meshMod , faceI , newFace , own , nei);

}

}

}

}

// Mark face as having been handled

affectedFace.unset(faceI);

}

}

8

(e) Four new internal faces are created by joining the center of opposite faces and midpoint ofopposite edges on empty patch.

Listing 8: dynamicRefineFvMesh.C

bool haveTwoFaceMids = false;

Map <edge >:: iterator faceMidFnd =

midPointToFaceMids.find(edgeMidPointI );

if (faceMidFnd == midPointToFaceMids.end ())

{

midPointToFaceMids.insert

(edgeMidPointI , edge(faceMidPointI , -1));

}

else

{

edge& e = faceMidFnd ();

if (faceMidPointI != e[0])

{

if (e[1] == -1)

{

e[1] = faceMidPointI;

changed = true;

}

}

if (e[0] != -1 && e[1] != -1)

{

haveTwoFaceMids = true;

}

}

// Check if this call of storeMidPointInfo is the one

// that completed all the nessecary information.

if (changed && haveTwoAnchors)

{

const cell& cFaces = mesh_.cells ()[ cellI];

label face1 = -1;

forAll(cFaces , i)

{

label faceJ = cFaces[i];

if (cellMidPoint[faceJ] != faceMidPointI

&& cellMidPoint[faceJ] >= 0

&& cellMidPoint[faceJ] != 123456789 )

{

face1 = faceJ;

}

}

9

const edge& anchors = midPointToAnchors[edgeMidPointI ];

label index = findIndex(cellAnchorPoints[cellI], anchorPointJ );

if (findIndex(cellAnchorPoints[cellI], anchorPointJ) == 0)

{

index = 4;

}

if (findIndex(cellAnchorPoints[cellI], anchorPointJ) == 4)

{

index = 8;

}

label point1 = cellAnchorPoints[cellI ][8 - index];

label edgeMidPointJ = -1;

// Create face consistent with anchorI being the owner.

// Note that the edges between the edge mid point and the face

// mids might be marked for splitting. Note that these

// edge splits cannot be between cellMid and face mids.

const face& f = mesh_.faces ()[ face1];

const labelList& fEdges = mesh_.faceEdges(face1 );

DynamicList <label > newFaceVerts (4);

if (faceOrder == (mesh_.faceOwner ()[ faceI] == cellI))

{

label anch = findIndex(f, point1 );

if (pointLevel_[f[f.rcIndex(anch )]] <= cellLevel_[cellI ])

{

label edgeJ = fEdges[f.rcIndex(anch )];

edgeMidPointJ = edgeMidPoint[edgeJ];

}

else

{

label edgeMid = findLevel(face1 , f, f.rcIndex(anch),

false , cellLevel_[cellI] +1);

edgeMidPointJ = f[edgeMid ];

}

newFaceVerts.append(faceMidPointI );

// Check & insert edge split if any

insertEdgeSplit

(

edgeMidPoint ,

faceMidPointI , // edge between faceMid and

edgeMidPointI , // edgeMid

newFaceVerts

);

newFaceVerts.append(edgeMidPointI );

10

insertEdgeSplit

(

edgeMidPoint ,

edgeMidPointI ,

edgeMidPointJ ,

newFaceVerts

);

newFaceVerts.append(edgeMidPointJ );

newFaceVerts.append(cellMidPoint[face1 ]);

}

else

{

label anch = findIndex(f, point1 );

if (pointLevel_[f[f.fcIndex(anch )]] <= cellLevel_[cellI ])

{

label edgeJ = fEdges[anch];

edgeMidPointJ = edgeMidPoint[edgeJ];

}

else

{

label edgeMid = findLevel(face1 , f, f.fcIndex(anch),

true , cellLevel_[cellI] + 1);

edgeMidPointJ = f[edgeMid ];

}

newFaceVerts.append(edgeMidPointJ );

insertEdgeSplit

(

edgeMidPoint ,

edgeMidPointJ ,

edgeMidPointI ,

newFaceVerts

);

newFaceVerts.append(edgeMidPointI );

insertEdgeSplit

(

edgeMidPoint ,

edgeMidPointI ,

faceMidPointI ,

newFaceVerts

);

newFaceVerts.append(faceMidPointI );

newFaceVerts.append(cellMidPoint[face1 ]);

}

11

face newFace;

newFace.transfer(newFaceVerts );

(f) Lastly, field values are mapped from the base mesh to the refined mesh as initial conditions.

5. The unrefinement procedure goes thus:

(a) Points to be unrefined are selected depending on their pointLevel and the number of bufferlayers (specified in dynamicMeshDict file).

Listing 9: hexRef4.C

//- Return the points at the centre of top -level split cells

// that can be unsplit.

Foam:: labelList Foam:: hexRef4 :: getSplitPoints () const

{

if (debug)

{

checkRefinementLevels (-1, labelList (0));

}

if (debug)

{

Pout << "hexRef4 :: getSplitPoints :"

<< " Calculating unrefineable points" << endl;

}

if (! history_.active ())

{

FatalErrorIn("hexRef4 :: getSplitPoints ()")

<< "Only call if constructed with history capability"

<< abort(FatalError );

}

// Master cell

// -1 undetermined

// -2 certainly not split point

// >= label of master cell

labelList splitMaster(mesh_.nPoints(), -1);

labelList splitMasterLevel(mesh_.nPoints(), 0);

// Unmark all with not 8 cells

for (label pointI = 0; pointI < mesh_.nPoints (); pointI ++)

{

const labelList& pCells = mesh_.pointCells(pointI );

vector coord = mesh_.points ()[ pointI ];

if (pCells.size() != 4 || coord [2] > 0)

{

splitMaster[pointI] = -2;

12

}

}

// Unmark all with different master cells

const labelList& visibleCells = history_.visibleCells ();

forAll(visibleCells , cellI)

{

const labelList& cPoints = mesh_.cellPoints(cellI );

if (visibleCells[cellI] != -1 && history_.parentIndex(cellI) >= 0)

{

label parentIndex = history_.parentIndex(cellI);

// Check same master.

forAll(cPoints , i)

{

label pointI = cPoints[i];

label masterCellI = splitMaster[pointI ];

if (masterCellI == -1)

{

// First time visit of point. Store parent cell and

// level of the parent cell (with respect to cellI ). This

// is additional guarantee that we’re referring to the

// same master at the same refinement level.

splitMaster[pointI] = parentIndex;

splitMasterLevel[pointI] = cellLevel_[cellI] - 1;

}

else if (masterCellI == -2)

{

// Already decided that point is not splitPoint

}

else if

(

(masterCellI != parentIndex)

|| (splitMasterLevel[pointI] != cellLevel_[cellI] - 1)

)

{

// Different masters so point is on two refinement

// patterns

splitMaster[pointI] = -2;

}

}

}

else

{

13

// Either not visible or is unrefined cell

forAll(cPoints , i)

{

label pointI = cPoints[i];

splitMaster[pointI] = -2;

}

}

}

// Collect into labelList

label nSplitPoints = 0;

forAll(splitMaster , pointI)

{

if (splitMaster[pointI] >= 0)

{

nSplitPoints ++;

}

}

labelList splitPoints(nSplitPoints );

nSplitPoints = 0;

forAll(splitMaster , pointI)

{

if (splitMaster[pointI] >= 0)

{

splitPoints[nSplitPoints ++] = pointI;

}

}

return splitPoints;

}

(b) The unrefinement begins by deleting points selected in the previous step, and any face orpoints they are linked to. Before deleting points, a consistency check is done by the functionconsistentUnrefinement to be sure that the mesh configuration satisfies the 2:1 rule. e.g.consistentUnrefinement(pointsToUnrefine, maxSet) returns ok list of points to unrefine.

Listing 10: hexRef4.C

void Foam:: hexRef4 :: setUnrefinement

(

const labelList& splitPointLabels ,

polyTopoChange& meshMod

)

{

if (! history_.active ())

{

14

FatalErrorIn

(

"hexRef4 :: setUnrefinement(const labelList&, polyTopoChange &)"

) << "Only call if constructed with history capability"

<< abort(FatalError );

}

if (debug)

{

Pout << "hexRef4 :: setUnrefinement :"

<< " Checking initial mesh just to make sure" << endl;

checkMesh ();

forAll(cellLevel_ , cellI)

{

if (cellLevel_[cellI] < 0)

{

FatalErrorIn

(

"hexRef4 :: setUnrefinement"

"("

"const labelList&, "

"polyTopoChange&"

")"

) << "Illegal cell level " << cellLevel_[cellI]

<< " for cell " << cellI

<< abort(FatalError );

}

}

// Write to sets.

pointSet pSet(mesh_ , "splitPoints", splitPointLabels );

pSet.write ();

cellSet cSet(mesh_ , "splitPointCells", splitPointLabels.size ());

forAll(splitPointLabels , i)

{

const labelList& pCells = mesh_.pointCells(splitPointLabels[i]);

forAll(pCells , j)

{

cSet.insert(pCells[j]);

}

}

cSet.write ();

15

Pout << "hexRef4 :: setRefinement : Dumping " << pSet.size()

<< " points and "

<< cSet.size() << " cells for unrefinement to" << nl

<< " pointSet " << pSet.objectPath () << nl

<< " cellSet " << cSet.objectPath ()

<< endl;

}

labelList cellRegion;

labelList cellRegionMaster;

labelList facesToRemove;

{

labelHashSet splitFaces (6* splitPointLabels.size ());

forAll(splitPointLabels , i)

{

const labelList& pFaces = mesh_.pointFaces ()[ splitPointLabels[i]];

forAll(pFaces , j)

{

if (mesh_.isInternalFace(pFaces[j]))

{

splitFaces.insert(pFaces[j]);

}

}

}

// Check with faceRemover what faces will get removed.

// Note that this can be more (but never less) than

// splitFaces provided.

faceRemover_.compatibleRemoves

(

splitFaces.toc(), // pierced faces

cellRegion , // per cell -1 or region it is merged into

cellRegionMaster , // per region the master cell

facesToRemove // new faces to be removed.

);

if (facesToRemove.size() != splitFaces.size ())

{

FatalErrorIn

(

"hexRef4 :: setUnrefinement(const labelList&, polyTopoChange &)"

) << "Ininitial set of split points to unrefine does not"

<< " seem to be consistent or not mid points of refined cells"

<< abort(FatalError );

}

16

}

// Redo the region master so it is consistent with our master.

// This will guarantee that the new cell (for which

// faceRemover uses the region master) is already compatible

// with our refinement structure.

forAll(splitPointLabels , i)

{

label pointI = splitPointLabels[i];

// Get original cell label

const labelList& pCells = mesh_.pointCells(pointI );

// Check

if (pCells.size() != 4)

{

FatalErrorIn

(

"hexRef4 :: setUnrefinement(const labelList&, polyTopoChange &)"

) << "splitPoint " << pointI

<< " should have 8 cells using it. It has " << pCells

<< abort(FatalError );

}

// Check that the lowest numbered pCells is the master of the region

// (should be guaranteed by directRemoveFaces)

//if (debug)

{

label masterCellI = min(pCells );

forAll(pCells , j)

{

label cellI = pCells[j];

label region = cellRegion[cellI ];

if (region == -1)

{

FatalErrorIn("hexRef4 :: setUnrefinement (..)")

<< "Ininitial set of split points to unrefine does not"

<< " seem to be consistent or not mid points"

<< " of refined cells" << nl

<< "cell:" << cellI << " on splitPoint " << pointI

<< " has no region to be merged into"

<< abort(FatalError );

}

17

if (masterCellI != cellRegionMaster[region ])

{

FatalErrorIn("hexRef4 :: setUnrefinement (..)")

<< "cell:" << cellI << " on splitPoint:" << pointI

<< " in region " << region

<< " has master:" << cellRegionMaster[region]

<< " which is not the lowest numbered cell"

<< " among the pointCells:" << pCells

<< abort(FatalError );

}

}

}

}

// Insert all commands to combine cells. Never fails so

// don’t have to test for success.

faceRemover_.setRefinement

(

facesToRemove ,

cellRegion ,

cellRegionMaster ,

meshMod

);

// Remove the 8 cells that originated from merging around

// the split point and adapt cell levels (note that

// pointLevels stay the same since points

// either get removed or stay at the same position .)

forAll(splitPointLabels , i)

{

label pointI = splitPointLabels[i];

const labelList& pCells = mesh_.pointCells(pointI );

label masterCellI = min(pCells );

forAll(pCells , j)

{

cellLevel_[pCells[j]]--;

}

history_.combineCells(masterCellI , pCells );

}

// Mark files as changed

setInstance(mesh_.facesInstance ());

// history_.updateMesh will take care of truncating.

18

}

(c) Lastly, field values are mapped from the refined mesh to the new mesh. The centroid valueof the new cell is obtained by taking the average of centroid values of daughter cells.

19