Two-dimensional adaptive meshing in OpenFOAM
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