ENH: add dictionary-driven multi-pass stitchMesh facility

- the dictionary-driven variant of stitchMesh allows sequential
  application of 'stitch' operation with requiring intermediate
  writing to disk.

- Without arguments:
  * stitchMesh uses a system/stitchMeshDict or -dict dict

- With arguments:
  * master/slave patches specified on the command-line as in previous
    versions.
This commit is contained in:
Mark Olesen 2017-11-10 01:53:30 +01:00
parent d4b7fbe9a1
commit 3cafdccb4c
9 changed files with 569 additions and 233 deletions

View File

@ -66,34 +66,35 @@ Description
#include "fvCFD.H"
#include "polyTopoChanger.H"
#include "mapPolyMesh.H"
#include "ListOps.H"
#include "slidingInterface.H"
#include "perfectInterface.H"
#include "IOobjectList.H"
#include "ReadFields.H"
#include <numeric>
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Checks whether patch present
void checkPatch(const polyBoundaryMesh& bMesh, const word& name)
// Checks whether patch present and non-zero
bool checkPatch(const polyBoundaryMesh& bMesh, const word& name)
{
const label patchi = bMesh.findPatchID(name);
if (patchi == -1)
{
FatalErrorInFunction
<< "Cannot find patch " << name << endl
<< "It should be present and of non-zero size" << endl
<< "Valid patches are " << bMesh.names()
<< exit(FatalError);
Info<< "No patch " << name << " in mesh" << nl
<< "Known patches: " << bMesh.names() << endl;
return false;
}
if (bMesh[patchi].empty())
{
FatalErrorInFunction
<< "Patch " << name << " is present but zero size"
<< exit(FatalError);
Info<< "Patch " << name << " has zero size" << nl;
return false;
}
return true;
}
@ -101,28 +102,38 @@ int main(int argc, char *argv[])
{
argList::addNote
(
"Merge the faces on the specified patches (if geometrically possible)\n"
"so the faces become internal.\n"
"Integral matching is used when the options -partial and -perfect are "
"omitted.\n"
"Merge the faces on specified patches (if geometrically possible)"
" so that the\n"
"faces become internal.\n"
"This utility can be called without arguments (uses stitchMeshDict)"
" or with\n"
"two arguments (master/slave patch names)."
);
argList::noParallel();
#include "addOverwriteOption.H"
#include "addRegionOption.H"
argList::validArgs.append("masterPatch");
argList::validArgs.append("slavePatch");
#include "addDictOption.H"
argList::addBoolOption
(
"integral",
"couple integral master/slave patches (2 argument mode: default)"
);
argList::addBoolOption
(
"partial",
"couple partially overlapping patches (optional)"
"couple partially overlapping master/slave patches (2 argument mode)"
);
argList::addBoolOption
(
"perfect",
"couple perfectly aligned patches (optional)"
"couple perfectly aligned master/slave patches (2 argument mode)"
);
argList::addBoolOption
(
"intermediate",
"write intermediate stages, not just the final result"
);
argList::addOption
(
@ -131,69 +142,239 @@ int main(int argc, char *argv[])
"dictionary file with tolerances"
);
#include "setRootCase.H"
// The arguments are non-mandatory when using dictionary mode
argList::validArgs.append("masterPatch");
argList::validArgs.append("slavePatch");
#include "setRootCaseNonMandatoryArgs.H"
// We now handle checking args and general sanity etc.
const bool useCommandArgs = (args.size() > 1);
if (useCommandArgs)
{
if (args.optionFound("dict"))
{
FatalErrorInFunction
<< "Cannot specify both dictionary and command-line arguments"
<< nl
<< endl;
}
// If we have arguments - we require all arguments!
if (!args.check(true, false))
{
FatalError.exit();
}
}
else
{
// Carp about inapplicable options
if (args.optionFound("integral"))
{
FatalErrorInFunction
<< "Only specify -integral with command-line arguments"
<< endl;
}
if (args.optionFound("partial"))
{
FatalErrorInFunction
<< "Only specify -partial with command-line arguments"
<< endl;
}
if (args.optionFound("perfect"))
{
FatalErrorInFunction
<< "Only specify -perfect with command-line arguments"
<< endl;
}
}
#include "createTime.H"
runTime.functionObjects().off();
#include "createNamedMesh.H"
const word oldInstance = mesh.pointsInstance();
const word masterPatchName = args[1];
const word slavePatchName = args[2];
const bool partialCover = args.optionFound("partial");
const bool perfectCover = args.optionFound("perfect");
const bool intermediate = args.optionFound("intermediate");
const bool overwrite = args.optionFound("overwrite");
if (partialCover && perfectCover)
const word dictName("stitchMeshDict");
// A validated input dictionary
dictionary validatedDict;
if (useCommandArgs)
{
// Command argument driven:
const int integralCover = args.optionFound("integral");
const int partialCover = args.optionFound("partial");
const int perfectCover = args.optionFound("perfect");
if ((integralCover + partialCover + perfectCover) > 1)
{
FatalErrorInFunction
<< "Cannot supply both partial and perfect." << endl
<< "Can only specify one of -integral | -partial | -perfect."
<< nl
<< "Use perfect match option if the patches perfectly align"
<< " (both vertex positions and face centres)" << endl
<< exit(FatalError);
}
// Patch names
const word masterPatchName(args[1]);
const word slavePatchName(args[2]);
const word mergePatchName(masterPatchName + slavePatchName);
const word cutZoneName(mergePatchName + "CutFaceZone");
// Patch names
Info<< " " << masterPatchName
<< " / " << slavePatchName << nl;
slidingInterface::typeOfMatch tom = slidingInterface::INTEGRAL;
if (partialCover)
// Bail out if either patch has problems
if
(
!checkPatch(mesh.boundaryMesh(), masterPatchName)
|| !checkPatch(mesh.boundaryMesh(), slavePatchName)
)
{
Info<< "Coupling partially overlapping patches "
<< masterPatchName << " and " << slavePatchName << nl
<< "Resulting internal faces will be in faceZone " << cutZoneName
<< nl
<< "Any uncovered faces will remain in their patch"
<< endl;
FatalErrorInFunction
<< "Cannot continue"
<< exit(FatalError);
tom = slidingInterface::PARTIAL;
return 1;
}
else if (perfectCover)
// Input was validated
dictionary dict;
if (perfectCover)
{
Info<< "Coupling perfectly aligned patches "
<< masterPatchName << " and " << slavePatchName << nl
<< "Resulting (internal) faces will be in faceZone " << cutZoneName
<< nl << nl
<< "Note: both patches need to align perfectly." << nl
<< "Both the vertex"
<< " positions and the face centres need to align to within" << nl
<< "a tolerance given by the minimum edge length on the patch"
<< endl;
dict.add("match", word("perfect"));
}
else if (partialCover)
{
dict.add
(
"match",
slidingInterface::typeOfMatchNames[slidingInterface::PARTIAL]
);
}
else
{
Info<< "Coupling patches " << masterPatchName << " and "
<< slavePatchName << nl
<< "Resulting (internal) faces will be in faceZone " << cutZoneName
<< nl << nl
<< "Note: the overall area covered by both patches should be"
<< " identical (\"integral\" interface)." << endl
<< "If this is not the case use the -partial option" << nl << endl;
dict.add
(
"match",
slidingInterface::typeOfMatchNames[slidingInterface::INTEGRAL]
);
}
// Patch names
dict.add("master", masterPatchName);
dict.add("slave", slavePatchName);
validatedDict.add("stitchMesh", dict);
}
else
{
// dictionary-driven:
#include "setSystemRunTimeDictionaryIO.H"
Info<< "Reading " << dictName;
IOdictionary stitchDict(dictIO);
Info<< " with " << stitchDict.size() << " entries" << nl;
// Suppress duplicate names
wordHashSet requestedPatches;
forAllConstIters(stitchDict, iter)
{
if (!iter().isDict())
{
Info<< "Ignoring non-dictionary entry: "
<< iter().keyword() << nl;
continue;
}
const dictionary& dict = iter().dict();
// Match type
word matchName;
if (dict.readIfPresent("match", matchName))
{
if
(
matchName != "perfect"
&& !slidingInterface::typeOfMatchNames.hasEnum(matchName)
)
{
Info<< "Error: unknown match type - " << matchName
<< " should be one of "
<< slidingInterface::typeOfMatchNames.toc() << nl;
continue;
}
}
// Patch names
const word masterPatchName(dict["master"]);
const word slavePatchName(dict["slave"]);
// Patch names
Info<< " " << masterPatchName
<< " / " << slavePatchName << nl;
if (!requestedPatches.insert(masterPatchName))
{
Info<< "Error: patch specified multiple times - "
<< masterPatchName << nl;
continue;
}
if (!requestedPatches.insert(slavePatchName))
{
Info<< "Error: patch specified multiple times - "
<< slavePatchName << nl;
requestedPatches.erase(masterPatchName);
continue;
}
// Bail out if either patch has problems
if
(
!checkPatch(mesh.boundaryMesh(), masterPatchName)
|| !checkPatch(mesh.boundaryMesh(), slavePatchName)
)
{
requestedPatches.erase(masterPatchName);
requestedPatches.erase(slavePatchName);
continue;
}
// Input was validated
validatedDict.add(iter().keyword(), iter().dict());
}
}
const label nActions = validatedDict.size();
Info<< nl << nActions << " validated actions" << endl;
if (!nActions)
{
Info<<"\nStopping" << nl << endl;
return 1;
}
// ------------------------------------------
// This is where the real work begins
// set up the tolerances for the sliding mesh
dictionary slidingTolerances;
if (args.optionFound("toleranceDict"))
@ -212,120 +393,6 @@ int main(int argc, char *argv[])
slidingTolerances += toleranceFile;
}
// Check for non-empty master and slave patches
checkPatch(mesh.boundaryMesh(), masterPatchName);
checkPatch(mesh.boundaryMesh(), slavePatchName);
// Create and add face zones and mesh modifiers
// Master patch
const polyPatch& masterPatch = mesh.boundaryMesh()[masterPatchName];
// Make list of masterPatch faces
labelList isf(masterPatch.size());
forAll(isf, i)
{
isf[i] = masterPatch.start() + i;
}
polyTopoChanger stitcher(mesh, IOobject::NO_READ);
stitcher.clear();
stitcher.setSize(1);
mesh.pointZones().clearAddressing();
mesh.faceZones().clearAddressing();
mesh.cellZones().clearAddressing();
if (perfectCover)
{
// Starts as master zone, but receives the resulting internal faces
mesh.faceZones()
(
cutZoneName,
true // verbose
).resetAddressing(isf.xfer(), false);
// Add the perfect interface mesh modifier
stitcher.set
(
0,
new perfectInterface
(
"couple",
0,
stitcher,
cutZoneName,
masterPatchName,
slavePatchName
)
);
}
else
{
// An empty point zone
mesh.pointZones()
(
mergePatchName + "CutPointZone",
true // verbose
) = labelList();
// The master zone
mesh.faceZones()
(
mergePatchName + "MasterZone",
true // verbose
).resetAddressing(isf.xfer(), false);
// Slave patch
const polyPatch& slavePatch = mesh.boundaryMesh()[slavePatchName];
labelList osf(slavePatch.size());
forAll(osf, i)
{
osf[i] = slavePatch.start() + i;
}
mesh.faceZones()
(
mergePatchName + "SlaveZone",
true // verbose
).resetAddressing(osf.xfer(), false);
// An empty zone for cut faces
mesh.faceZones()
(
cutZoneName,
true // verbose
).resetAddressing(labelList(), false);
// Add the sliding interface mesh modifier
stitcher.set
(
0,
new slidingInterface
(
"couple",
0,
stitcher,
mergePatchName + "MasterZone",
mergePatchName + "SlaveZone",
mergePatchName + "CutPointZone",
cutZoneName,
masterPatchName,
slavePatchName,
tom, // integral or partial
true // couple/decouple mode
)
);
static_cast<slidingInterface&>(stitcher[0]).setTolerances
(
slidingTolerances,
true
);
}
// Search for list of objects for this time
IOobjectList objects(mesh, runTime.timeName());
@ -358,7 +425,183 @@ int main(int argc, char *argv[])
//PtrList<surfaceTensorField> surfaceTensorFields;
//ReadFields(mesh, objects, surfaceTensorFields);
if (!overwrite)
// Increase precision for output mesh points
IOstream::defaultPrecision(max(10u, IOstream::defaultPrecision()));
polyTopoChanger stitcher(mesh, IOobject::NO_READ);
// Step through the topology changes
label actioni = 0;
forAllConstIters(validatedDict, iter)
{
const dictionary& dict = iter().dict();
// Match type
bool perfect = false;
slidingInterface::typeOfMatch matchType = slidingInterface::PARTIAL;
word matchName;
if (dict.readIfPresent("match", matchName))
{
if (matchName == "perfect")
{
perfect = true;
}
else
{
matchType = slidingInterface::typeOfMatchNames[matchName];
}
}
// Patch names
const word masterPatchName(dict["master"]);
const word slavePatchName(dict["slave"]);
// Zone names
const word mergePatchName(masterPatchName + slavePatchName);
const word cutZoneName(mergePatchName + "CutFaceZone");
Info<< nl << "========================================" << nl;
// Information messages
if (perfect)
{
Info<< "Coupling PERFECTLY aligned patches "
<< masterPatchName << " / " << slavePatchName << nl << nl
<< "Resulting (internal) faces in faceZone "
<< cutZoneName << nl << nl
<< "The patch vertices and face centres must align within a"
<< " tolerance relative to the minimum edge length on the patch"
<< nl << endl;
}
else if (matchType == slidingInterface::INTEGRAL)
{
Info<< "Coupling INTEGRALLY matching of patches "
<< masterPatchName << " / " << slavePatchName << nl << nl
<< "Resulting (internal) faces in faceZone "
<< cutZoneName << nl << nl
<< "The overall area covered by both patches should be"
<< " identical!" << endl
<< "If this is not the case use partial"
<< nl << endl;
}
else
{
Info<< "Coupling PARTIALLY overlapping patches "
<< masterPatchName << " / " << slavePatchName << nl
<< "Resulting internal faces in faceZone "
<< cutZoneName << nl
<< "Uncovered faces remain in their patch"
<< nl << endl;
}
// Master/slave patches
const polyPatch& masterPatch = mesh.boundaryMesh()[masterPatchName];
const polyPatch& slavePatch = mesh.boundaryMesh()[slavePatchName];
mesh.pointZones().clearAddressing();
mesh.faceZones().clearAddressing();
mesh.cellZones().clearAddressing();
// Lists of master and slave faces:
labelList faceIds;
// Markup master face ids
faceIds.setSize(masterPatch.size());
std::iota(faceIds.begin(), faceIds.end(), masterPatch.start());
stitcher.clear();
stitcher.setSize(1);
if (perfect)
{
// Add new (empty) zone for resulting internal faces
mesh.faceZones()
(
cutZoneName,
true // verbose
).resetAddressing(faceIds.xfer(), false);
// Add the perfect interface mesh modifier
stitcher.set
(
0,
new perfectInterface
(
"couple" + Foam::name(actioni),
0,
stitcher,
cutZoneName,
masterPatchName,
slavePatchName
)
);
}
else
{
mesh.pointZones()
(
mergePatchName + "CutPointZone",
true // verbose
) = labelList();
mesh.faceZones()
(
mergePatchName + "MasterZone",
true // verbose
).resetAddressing(faceIds.xfer(), false);
// Markup slave face ids
faceIds.setSize(slavePatch.size());
std::iota(faceIds.begin(), faceIds.end(), slavePatch.start());
mesh.faceZones()
(
mergePatchName + "SlaveZone",
true // verbose
).resetAddressing(faceIds.xfer(), false);
// Add empty zone for cut faces
mesh.faceZones()
(
cutZoneName,
true // verbose
).resetAddressing(labelList(), false);
// Add the sliding interface mesh modifier
stitcher.set
(
0,
new slidingInterface
(
"couple" + Foam::name(actioni),
0,
stitcher,
mergePatchName + "MasterZone",
mergePatchName + "SlaveZone",
mergePatchName + "CutPointZone",
cutZoneName,
masterPatchName,
slavePatchName,
matchType, // integral or partial
true // couple/decouple mode
)
);
static_cast<slidingInterface&>(stitcher[0]).setTolerances
(
slidingTolerances,
true
);
}
++actioni;
// Advance time for intermediate results or only on final
if (!overwrite && (intermediate || actioni == nActions))
{
runTime++;
}
@ -374,9 +617,11 @@ int main(int argc, char *argv[])
mesh.setInstance(oldInstance);
stitcher.instance() = oldInstance;
}
Info<< nl << "Writing polyMesh to time " << runTime.timeName() << endl;
IOstream::defaultPrecision(max(10u, IOstream::defaultPrecision()));
if (intermediate || actioni == nActions)
{
Info<< nl << "Writing polyMesh to time "
<< runTime.timeName() << endl;
// Bypass runTime write (since only writes at writeTime)
if
@ -401,8 +646,10 @@ int main(int argc, char *argv[])
// Write fields
runTime.write();
}
}
Info<< nl << "End" << nl << endl;
Info<< "\nEnd\n" << endl;
return 0;
}

View File

@ -0,0 +1,38 @@
/*--------------------------------*- C++ -*----------------------------------*\
| ========= | |
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
| \\ / O peration | Version: plus |
| \\ / A nd | Web: www.OpenFOAM.com |
| \\/ M anipulation | |
\*---------------------------------------------------------------------------*/
FoamFile
{
version 2.0;
format ascii;
class dictionary;
object stitchMeshDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
outerx
{
match partial; // partial | integral | perfect
master outerx;
slave innerx;
}
outery
{
match partial;
master outery;
slave innery;
}
outerz
{
match partial;
master outerz;
slave innerz;
}
// ************************************************************************* //

View File

@ -79,7 +79,6 @@ Foam::pointField Foam::perfectInterface::calcFaceCentres
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
// Construct from components
Foam::perfectInterface::perfectInterface
(
const word& name,
@ -97,7 +96,6 @@ Foam::perfectInterface::perfectInterface
{}
// Construct from dictionary
Foam::perfectInterface::perfectInterface
(
const word& name,

View File

@ -45,6 +45,7 @@ namespace Foam
);
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::setUpdater::setUpdater

View File

@ -52,7 +52,7 @@ const Foam::Enum
<
Foam::slidingInterface::typeOfMatch
>
Foam::slidingInterface::typeOfMatchNames_
Foam::slidingInterface::typeOfMatchNames
{
{ typeOfMatch::INTEGRAL, "integral" },
{ typeOfMatch::PARTIAL, "partial" },
@ -112,8 +112,6 @@ void Foam::slidingInterface::clearOut() const
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
// Construct from components
Foam::slidingInterface::slidingInterface
(
const word& name,
@ -204,7 +202,6 @@ Foam::slidingInterface::slidingInterface
}
// Construct from components
Foam::slidingInterface::slidingInterface
(
const word& name,
@ -244,7 +241,7 @@ Foam::slidingInterface::slidingInterface
dict.lookup("slavePatchName"),
mme.mesh().boundaryMesh()
),
matchType_(typeOfMatchNames_.lookup("typeOfMatch", dict)),
matchType_(typeOfMatchNames.lookup("typeOfMatch", dict)),
coupleDecouple_(dict.lookup("coupleDecouple")),
attached_(dict.lookup("attached")),
projectionAlgo_
@ -749,7 +746,7 @@ void Foam::slidingInterface::write(Ostream& os) const
<< cutFaceZoneID_.name() << nl
<< masterPatchID_.name() << nl
<< slavePatchID_.name() << nl
<< typeOfMatchNames_[matchType_] << nl
<< typeOfMatchNames[matchType_] << nl
<< coupleDecouple_ << nl
<< attached_ << endl;
}
@ -776,7 +773,7 @@ void Foam::slidingInterface::writeDict(Ostream& os) const
os.writeEntry("cutFaceZoneName", cutFaceZoneID_.name());
os.writeEntry("masterPatchName", masterPatchID_.name());
os.writeEntry("slavePatchName", slavePatchID_.name());
os.writeEntry("typeOfMatch", typeOfMatchNames_[matchType_]);
os.writeEntry("typeOfMatch", typeOfMatchNames[matchType_]);
os.writeEntry("coupleDecouple", coupleDecouple_);
os.writeEntry("projection", intersection::algorithmNames_[projectionAlgo_]);
os.writeEntry("attached", attached_);

View File

@ -84,8 +84,8 @@ public:
PARTIAL
};
//- Direction names
static const Enum<typeOfMatch> typeOfMatchNames_;
//- Names for the types of matches
static const Enum<typeOfMatch> typeOfMatchNames;
private:

View File

@ -4,9 +4,11 @@ cd ${0%/*} || exit 1 # Run from this directory
runApplication ./Allmesh
for dir in x y z
do
runApplication -s dir-$dir stitchMesh -partial outer$dir inner$dir
done
# Use stitchMesh with dictionary
# runApplication stitchMesh -intermediate
runApplication stitchMesh -overwrite
runApplication checkMesh
# -----------------------------------------------------------------------------

View File

@ -0,0 +1,15 @@
#!/bin/sh
cd ${0%/*} || exit 1 # Run from this directory
. $WM_PROJECT_DIR/bin/tools/RunFunctions # Tutorial run functions
runApplication ./Allmesh
# Use stitchMesh with command arguments (no dictionary)
for dir in x y z
do
runApplication -s dir-$dir stitchMesh -partial outer$dir inner$dir
done
runApplication checkMesh
# -----------------------------------------------------------------------------

View File

@ -0,0 +1,38 @@
/*--------------------------------*- C++ -*----------------------------------*\
| ========= | |
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
| \\ / O peration | Version: plus |
| \\ / A nd | Web: www.OpenFOAM.com |
| \\/ M anipulation | |
\*---------------------------------------------------------------------------*/
FoamFile
{
version 2.0;
format ascii;
class dictionary;
object stitchMeshDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
outerx
{
match partial; // partial | integral | perfect
master outerx;
slave innerx;
}
outery
{
match partial;
master outery;
slave innery;
}
outerz
{
match partial;
master outerz;
slave innerz;
}
// ************************************************************************* //