Merge branch 'feature-splitMeshRegions_autoPatch' into 'develop'

Draft: Extend splitMeshRegions to automatically create AMI inter-region patches

See merge request Development/openfoam!525
This commit is contained in:
Mattijs Janssens
2025-09-26 09:51:31 +00:00

View File

@ -43,8 +43,9 @@ Description
- volScalarField with regions as different scalars (-detectOnly)
or
- mesh with multiple regions and mapped patches. These patches
either cover the whole interface between two region (default) or
only part according to faceZones (-useFaceZones)
either cover the whole interface between two region (default),
only part according to faceZones (-useFaceZones) or be auto-generated
according to an AMI method (see below)
or
- mesh with cells put into cellZones (-makeCellZones)
@ -98,6 +99,14 @@ Description
- boundaryRegionAddressing : for every patch in this region the
patch in the original mesh (or -1 if added patch)
- auto-generate patches using AMI area-overlap detection. This requires a
patchSet to apply it to and an optional AMIMethod (default is
faceAreaWeightAMI2D).
-autoPatch '("solid*")'
-AMIMethod faceAreaWeightAMI2D
Any mapped patch thus generated should probably use the
nearestPatchFaceAMI sampling method.
\*---------------------------------------------------------------------------*/
#include "argList.H"
@ -115,6 +124,7 @@ Description
#include "mappedWallPolyPatch.H"
#include "fvMeshTools.H"
#include "processorMeshes.H"
#include "faceAreaWeightAMI2D.H"
using namespace Foam;
@ -296,12 +306,115 @@ void addToInterface
}
labelList getMinBoundaryValue
(
const polyMesh& mesh,
const word& AMIMethod,
const labelList& matchPatchIDs,
const labelList& cellRegion
)
{
// Neighbour cellRegion.
labelList coupledRegion(mesh.nBoundaryFaces());
forAll(coupledRegion, i)
{
label celli = mesh.faceOwner()[i+mesh.nInternalFaces()];
coupledRegion[i] = cellRegion[celli];
}
syncTools::swapBoundaryFaceList(mesh, coupledRegion);
// Add approximate matches
forAll(matchPatchIDs, i)
{
const label patchi = matchPatchIDs[i];
const auto& pp = mesh.boundaryMesh()[patchi];
for (label j = i+1; j < matchPatchIDs.size(); ++j)
{
const label nbrPatchi = matchPatchIDs[j];
const auto& nbrPp = mesh.boundaryMesh()[nbrPatchi];
// Use AMI to try and find matches
auto AMPtr(AMIInterpolation::New(AMIMethod));
AMPtr->calculate(pp, nbrPp, nullptr);
if
(
gAverage(AMPtr->tgtWeightsSum()) > SMALL
|| gAverage(AMPtr->srcWeightsSum()) > SMALL
)
{
// Pull remote data local
labelList thisDecomp(pp.size(), labelMax);
AMPtr->interpolateToSource
(
labelList(cellRegion, nbrPp.faceCells()),
[]
(
label& res,
const label facei,
const label& fld,
const scalar& w
)
{
res = min(res, fld);
},
thisDecomp,
thisDecomp // used in case of low-weight-corr
);
// Put thisDecomp into coupledRegion. Check for unmatched faces.
forAll(thisDecomp, i)
{
if (thisDecomp[i] != labelMax)
{
coupledRegion[pp.offset()+i] = thisDecomp[i];
}
}
labelList nbrDecomp(nbrPp.size(), labelMax);
AMPtr->interpolateToTarget
(
labelList(cellRegion, pp.faceCells()), //thisDecomp,
[]
(
label& res,
const label facei,
const label& fld,
const scalar& w
)
{
res = min(res, fld);
},
nbrDecomp,
nbrDecomp // used in case of low-weight-corr
);
// Put nbrDecomp into coupledRegion. Check for unmatched faces/
forAll(nbrDecomp, i)
{
if (nbrDecomp[i] != labelMax)
{
coupledRegion[nbrPp.offset()+i] = nbrDecomp[i];
}
}
}
}
}
return coupledRegion;
}
// Get region-region interface name and sizes.
// Returns interfaces as straight list for looping in identical order.
void getInterfaceSizes
(
const polyMesh& mesh,
const bool useFaceZones,
const word& AMIMethod,
const labelList& matchPatchIDs,
const labelList& cellRegion,
const wordList& regionNames,
@ -340,15 +453,16 @@ void getInterfaceSizes
// Boundary faces
// ~~~~~~~~~~~~~~
// Neighbour cellRegion.
labelList coupledRegion(mesh.nBoundaryFaces());
forAll(coupledRegion, i)
{
label celli = mesh.faceOwner()[i+mesh.nInternalFaces()];
coupledRegion[i] = cellRegion[celli];
}
syncTools::swapBoundaryFaceList(mesh, coupledRegion);
const labelList coupledRegion
(
getMinBoundaryValue
(
mesh,
AMIMethod,
matchPatchIDs,
cellRegion
)
);
forAll(coupledRegion, i)
{
@ -540,6 +654,8 @@ autoPtr<mapPolyMesh> createRegionMesh
const labelList& cellRegion,
const label regionI,
const word& regionName,
const word& AMIMethod,
const labelList& matchPatchIDs,
// Interface info
const labelList& interfacePatches,
const labelList& faceToInterface,
@ -551,15 +667,16 @@ autoPtr<mapPolyMesh> createRegionMesh
fvMeshTools::createDummyFvMeshFiles(mesh, regionName, true);
// Neighbour cellRegion.
labelList coupledRegion(mesh.nBoundaryFaces());
forAll(coupledRegion, i)
{
label celli = mesh.faceOwner()[i+mesh.nInternalFaces()];
coupledRegion[i] = cellRegion[celli];
}
syncTools::swapBoundaryFaceList(mesh, coupledRegion);
const labelList coupledRegion
(
getMinBoundaryValue
(
mesh,
AMIMethod,
matchPatchIDs,
cellRegion
)
);
// Topology change container. Start off from existing mesh.
polyTopoChange meshMod(mesh);
@ -578,20 +695,16 @@ autoPtr<mapPolyMesh> createRegionMesh
labelList exposedPatchIDs(exposedFaces.size());
forAll(exposedFaces, i)
{
label facei = exposedFaces[i];
label interfacei = faceToInterface[facei];
const label facei = exposedFaces[i];
const label interfacei = faceToInterface[facei];
label ownRegion = cellRegion[mesh.faceOwner()[facei]];
label neiRegion = -1;
if (mesh.isInternalFace(facei))
{
neiRegion = cellRegion[mesh.faceNeighbour()[facei]];
}
else
{
neiRegion = coupledRegion[facei-mesh.nInternalFaces()];
}
const label ownRegion = cellRegion[mesh.faceOwner()[facei]];
const label neiRegion
(
mesh.isInternalFace(facei)
? cellRegion[mesh.faceNeighbour()[facei]]
: coupledRegion[facei-mesh.nInternalFaces()]
);
// Check which side is being kept - determines which of the two
@ -638,6 +751,62 @@ autoPtr<mapPolyMesh> createRegionMesh
meshMod
);
// Do re-patching on non-removed cells ourselves. These are not exposed
// faces but are boundary faces
for (label bFacei = 0; bFacei < mesh.nBoundaryFaces(); bFacei++)
{
const label facei = mesh.nInternalFaces()+bFacei;
if (!meshMod.faceRemoved(facei))
{
const label interfacei = faceToInterface[facei];
const label ownRegion = cellRegion[mesh.faceOwner()[facei]];
const label neiRegion = coupledRegion[bFacei];
label exposedPatchID = -1;
if (ownRegion == regionI)
{
if (regionI < neiRegion)
{
exposedPatchID = interfacePatches[interfacei];
}
else if (regionI > neiRegion)
{
exposedPatchID = interfacePatches[interfacei]+1;
}
}
else if (neiRegion == regionI)
{
if (regionI < ownRegion)
{
exposedPatchID = interfacePatches[interfacei];
}
else if (regionI > ownRegion)
{
exposedPatchID = interfacePatches[interfacei]+1;
}
}
if (exposedPatchID != -1)
{
// In-place modify the patch
DynamicList<label>& patchID =
const_cast<DynamicList<label>&>(meshMod.region());
//Pout<< "For face:" << facei
// << " on interface:" << interfacei
// << " modifying from own:" << meshMod.faceOwner()[facei]
// << " nei:" << meshMod.faceNeighbour()[facei]
// << " verts:" << meshMod.faces()[facei]
// << " patch " << patchID[facei]
// << " to " << exposedPatchID << endl;
patchID[facei] = exposedPatchID;
}
}
}
autoPtr<mapPolyMesh> map = meshMod.makeMesh
(
newMesh,
@ -662,6 +831,8 @@ void createAndWriteRegion
const labelList& cellRegion,
const wordList& regionNames,
const bool prefixRegion,
const word& AMIMethod,
const labelList& matchPatchIDs,
const labelList& faceToInterface,
const labelList& interfacePatches,
const label regionI,
@ -678,6 +849,8 @@ void createAndWriteRegion
cellRegion,
regionI,
regionNames[regionI],
AMIMethod,
matchPatchIDs,
interfacePatches,
faceToInterface,
newMesh
@ -976,6 +1149,7 @@ labelList addRegionPatches
const wordList& regionNames,
const edgeList& interfaces,
const List<Pair<word>>& interfaceNames
//const List<mappedPatchBase::sampleMode>& interfaceModes
)
{
Info<< nl << "Adding patches" << nl << endl;
@ -999,7 +1173,7 @@ labelList addRegionPatches
0, // overridden
0, // overridden
regionNames[e[1]], // sampleRegion
mappedPatchBase::NEARESTPATCHFACE,
mappedPatchBase::NEARESTPATCHFACE, //interfaceModes[interI]
names[1], // samplePatch
point::zero, // offset
mesh.boundaryMesh()
@ -1021,7 +1195,7 @@ labelList addRegionPatches
0,
0,
regionNames[e[0]], // sampleRegion
mappedPatchBase::NEARESTPATCHFACE,
mappedPatchBase::NEARESTPATCHFACE, //interfaceModes[interI]
names[0],
point::zero, // offset
mesh.boundaryMesh()
@ -1411,6 +1585,7 @@ int main(int argc, char *argv[])
"Split mesh into multiple regions (detected by walking across faces)"
);
#include "addRegionOption.H"
#include "addOverwriteOption.H"
argList::addBoolOption
(
@ -1477,6 +1652,18 @@ int main(int argc, char *argv[])
"useFaceZones",
"Use faceZones to patch inter-region faces instead of single patch"
);
argList::addOption
(
"autoPatch",
"lists of patches",
"Find overlapping faces to auto-generate interface patches"
);
argList::addOption
(
"AMIMethod",
"word",
"type of AMI matching method"
);
argList::addBoolOption
(
"prefixRegion",
@ -1489,6 +1676,12 @@ int main(int argc, char *argv[])
#include "createTime.H"
#include "createNamedMesh.H"
// Note: could try to read multiple meshes and merge into one before
// operation but this would give problems with unique prefixes:
// - patches get renamed. So patchFields would need to be renamed.
// - what about e.g. 'samplePatch' in mapped patches?
const word oldInstance = mesh.pointsInstance();
word blockedFacesName;
@ -1512,6 +1705,14 @@ int main(int argc, char *argv[])
const bool useFaceZones = args.found("useFaceZones");
const bool prefixRegion = args.found("prefixRegion");
labelList matchPatchIDs;
word AMIMethod(faceAreaWeightAMI2D::typeName);
if (args.found("autoPatch"))
{
const wordRes patchNames(args.getList<wordRe>("autoPatch"));
matchPatchIDs = mesh.boundaryMesh().indices(patchNames);
args.readIfPresent("AMIMethod", AMIMethod);
}
if
(
@ -1541,6 +1742,13 @@ int main(int argc, char *argv[])
}
if (matchPatchIDs.size())
{
Info<< "Auto-detecting matching faces out of patches "
<< UIndirectList<word>(mesh.boundaryMesh().names(), matchPatchIDs)
<< nl << endl;
}
if (insidePoint && largestOnly)
{
@ -1608,8 +1816,8 @@ int main(int argc, char *argv[])
<< " This requires all"
<< " cells to be in one and only one cellZone." << nl << endl;
// Collect sets of zones into clusters. If no cluster is just an identity
// list (cluster 0 is cellZone 0 etc.)
// Collect sets of zones into clusters. If no cluster is just an
// identity list (cluster 0 is cellZone 0 etc.)
wordList clusterNames;
labelListList clusterToZones;
labelList zoneToCluster;
@ -1924,6 +2132,8 @@ int main(int argc, char *argv[])
(
mesh,
useFaceZones,
AMIMethod,
matchPatchIDs,
cellRegion,
regionNames,
@ -2103,6 +2313,7 @@ int main(int argc, char *argv[])
regionNames,
interfaces,
interfaceNames
//interfaceModes
)
);
@ -2155,6 +2366,8 @@ int main(int argc, char *argv[])
cellRegion,
regionNames,
prefixRegion,
AMIMethod,
matchPatchIDs,
faceToInterface,
interfacePatches,
regionI,
@ -2176,6 +2389,8 @@ int main(int argc, char *argv[])
cellRegion,
regionNames,
prefixRegion,
AMIMethod,
matchPatchIDs,
faceToInterface,
interfacePatches,
regionI,
@ -2197,6 +2412,8 @@ int main(int argc, char *argv[])
cellRegion,
regionNames,
prefixRegion,
AMIMethod,
matchPatchIDs,
faceToInterface,
interfacePatches,
regionI,