This major development provides coupling of patches which are
non-conformal, i.e. where the faces of one patch do not match the faces
of the other. The coupling is fully conservative and second order
accurate in space, unlike the Arbitrary Mesh Interface (AMI) and
associated ACMI and Repeat AMI methods which NCC replaces.
Description:
A non-conformal couple is a connection between a pair of boundary
patches formed by projecting one patch onto the other in a way that
fills the space between them. The intersection between the projected
surface and patch forms new faces that are incorporated into the finite
volume mesh. These new faces are created identically on both sides of
the couple, and therefore become equivalent to internal faces within the
mesh. The affected cells remain closed, meaning that the area vectors
sum to zero for all the faces of each cell. Consequently, the main
benefits of the finite volume method, i.e. conservation and accuracy,
are not undermined by the coupling.
A couple connects parts of mesh that are otherwise disconnected and can
be used in the following ways:
+ to simulate rotating geometries, e.g. a propeller or stirrer, in which
a part of the mesh rotates with the geometry and connects to a
surrounding mesh which is not moving;
+ to connect meshes that are generated separately, which do not conform
at their boundaries;
+ to connect patches which only partially overlap, in which the
non-overlapped section forms another boundary, e.g. a wall;
+ to simulate a case with a geometry which is periodically repeating by
creating multiple couples with different transformations between
patches.
The capability for simulating partial overlaps replaces the ACMI
functionality, currently provided by the 'cyclicACMI' patch type, and
which is unreliable unless the couple is perfectly flat. The capability
for simulating periodically repeating geometry replaces the Repeat AMI
functionality currently provided by the 'cyclicRepeatAMI' patch type.
Usage:
The process of meshing for NCC is very similar to existing processes for
meshing for AMI. Typically, a mesh is generated with an identifiable set
of internal faces which coincide with the surface through which the mesh
will be coupled. These faces are then duplicated by running the
'createBaffles' utility to create two boundary patches. The points are
then split using 'splitBaffles' in order to permit independent motion of
the patches.
In AMI, these patches are assigned the 'cyclicAMI' patch type, which
couples them using AMI interpolation methods.
With NCC, the patches remain non-coupled, e.g. a 'wall' type. Coupling
is instead achieved by running the new 'createNonConformalCouples'
utility, which creates additional coupled patches of type
'nonConformalCyclic'. These appear in the 'constant/polyMesh/boundary'
file with zero faces; they are populated with faces in the finite volume
mesh during the connection process in NCC.
For a single couple, such as that which separates the rotating and
stationary sections of a mesh, the utility can be called using the
non-coupled patch names as arguments, e.g.
createNonConformalCouples -overwrite rotatingZoneInner rotatingZoneOuter
where 'rotatingZoneInner' and 'rotatingZoneOuter' are the names of the
patches.
For multiple couples, and/or couples with transformations,
'createNonConformalCouples' should be run without arguments. Settings
will then be read from a configuration file named
'system/createNonConformalCouplesDict'. See
'$FOAM_ETC/caseDicts/annotated/createNonConformalCouplesDict' for
examples.
Boundary conditions must be specified for the non-coupled patches. For a
couple where the patches fully overlap, boundary conditions
corresponding to a slip wall are typically applied to fields, i.e
'movingWallSlipVelocity' (or 'slip' if the mesh is stationary) for
velocity U, 'zeroGradient' or 'fixedFluxPressure' for pressure p, and
'zeroGradient' for other fields. For a couple with
partially-overlapping patches, boundary conditions are applied which
physically represent the non-overlapped region, e.g. a no-slip wall.
Boundary conditions also need to be specified for the
'nonConformalCyclic' patches created by 'createNonConformalCouples'. It
is generally recommended that this is done by including the
'$FOAM_ETC/caseDicts/setConstraintTypes' file in the 'boundaryField'
section of each of the field files, e.g.
boundaryField
{
#includeEtc "caseDicts/setConstraintTypes"
inlet
{
...
}
...
}
For moving mesh cases, it may be necessary to correct the mesh fluxes
that are changed as a result of the connection procedure. If the
connected patches do not conform perfectly to the mesh motion, then
failure to correct the fluxes can result in noise in the pressure
solution.
Correction for the mesh fluxes is enabled by the 'correctMeshPhi' switch
in the 'PIMPLE' (or equivalent) section of 'system/fvSolution'. When it
is enabled, solver settings are required for 'MeshPhi'. The solution
just needs to distribute the error enough to dissipate the noise. A
smooth solver with a loose tolerance is typically sufficient, e.g. the
settings in 'system/fvSolution' shown below:
solvers
{
MeshPhi
{
solver smoothSolver;
smoother symGaussSeidel;
tolerance 1e-2;
relTol 0;
}
...
}
PIMPLE
{
correctMeshPhi yes;
...
}
The solution of 'MeshPhi' is an inexpensive computation since it is
applied only to a small subset of the mesh adjacent to the
couple. Conservation is maintained whether or not the mesh flux
correction is enabled, and regardless of the solution tolerance for
'MeshPhi'.
Advantages of NCC:
+ NCC maintains conservation which is required for many numerical
schemes and algorithms to operate effectively, in particular those
designed to maintain boundedness of a solution.
+ Closed-volume systems no longer suffer from accumulation or loss of
mass, poor convergence of the pressure equation, and/or concentration
of error in the reference cell.
+ Partially overlapped simulations are now possible on surfaces that are
not perfectly flat. The projection fills space so no overlaps or
spaces are generated inside contiguously overlapping sections, even if
those sections have sharp angles.
+ The finite volume faces created by NCC have geometrically accurate
centres. This makes the method second order accurate in space.
+ The polyhedral mesh no longer requires duplicate boundary faces to be
generated in order to run a partially overlapped simulation.
+ Lagrangian elements can now transfer across non-conformal couplings in
parallel.
+ Once the intersection has been computed and applied to the finite
volume mesh, it can use standard cyclic or processor cyclic finite
volume boundary conditions, with no need for additional patch types or
matrix interfaces.
+ Parallel communication is done using the standard
processor-patch-field system. This is more efficient than alternative
systems since it has been carefully optimised for use within the
linear solvers.
+ Coupled patches are disconnected prior to mesh motion and topology
change and reconnected afterwards. This simplifies the boundary
condition specification for mesh motion fields.
Resolved Bug Reports:
+ https://bugs.openfoam.org/view.php?id=663
+ https://bugs.openfoam.org/view.php?id=883
+ https://bugs.openfoam.org/view.php?id=887
+ https://bugs.openfoam.org/view.php?id=1337
+ https://bugs.openfoam.org/view.php?id=1388
+ https://bugs.openfoam.org/view.php?id=1422
+ https://bugs.openfoam.org/view.php?id=1829
+ https://bugs.openfoam.org/view.php?id=1841
+ https://bugs.openfoam.org/view.php?id=2274
+ https://bugs.openfoam.org/view.php?id=2561
+ https://bugs.openfoam.org/view.php?id=3817
Deprecation:
NCC replaces the functionality provided by AMI, ACMI and Repeat AMI.
ACMI and Repeat AMI are insufficiently reliable to warrant further
maintenance so are removed in an accompanying commit to OpenFOAM-dev.
AMI is more widely used so will be retained alongside NCC for the next
version release of OpenFOAM and then subsequently removed from
OpenFOAM-dev.
378 lines
12 KiB
C++
378 lines
12 KiB
C++
/*---------------------------------------------------------------------------*\
|
|
========= |
|
|
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
|
|
\\ / O peration | Website: https://openfoam.org
|
|
\\ / A nd | Copyright (C) 2011-2022 OpenFOAM Foundation
|
|
\\/ M anipulation |
|
|
-------------------------------------------------------------------------------
|
|
License
|
|
This file is part of OpenFOAM.
|
|
|
|
OpenFOAM is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Application
|
|
createNonConformalCouples
|
|
|
|
Description
|
|
Utility to create non-conformal couples between non-coupled patches.
|
|
|
|
Usage
|
|
\b createNonConformalCouples <patch1> <patch2>
|
|
|
|
Note
|
|
If run with two arguments, these arguments specify the patches between
|
|
which a single couple is to be created. The resulting couple will not have
|
|
a transformation.
|
|
|
|
Usage
|
|
\b createNonConformalCouples
|
|
|
|
Note
|
|
If run without arguments then settings are read from a \b
|
|
system/createNonConformalCouplesDict dictionary (or from a different
|
|
dictionary specified by the \b -dict option). This dictionary can specify
|
|
the creation of multiple couples and/or couples with transformations.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
#include "argList.H"
|
|
#include "fvMeshStitchersStationary.H"
|
|
#include "nonConformalCyclicPolyPatch.H"
|
|
#include "nonConformalErrorPolyPatch.H"
|
|
#include "nonConformalProcessorCyclicPolyPatch.H"
|
|
#include "polyMesh.H"
|
|
#include "processorPolyPatch.H"
|
|
#include "systemDict.H"
|
|
#include "Time.H"
|
|
|
|
using namespace Foam;
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
|
|
void createNonConformalCouples
|
|
(
|
|
fvMesh& mesh,
|
|
const List<Pair<word>>& patchNames,
|
|
const wordList& cyclicNames,
|
|
const List<cyclicTransform>& transforms
|
|
)
|
|
{
|
|
const polyBoundaryMesh& patches = mesh.boundaryMesh();
|
|
|
|
List<polyPatch*> newPatches;
|
|
|
|
// Find the first processor patch and face
|
|
label firstProcPatchi = patches.size(), firstProcFacei = mesh.nFaces();
|
|
forAll(patches, patchi)
|
|
{
|
|
const polyPatch& pp = patches[patchi];
|
|
|
|
if (isA<processorPolyPatch>(pp) && firstProcPatchi == patches.size())
|
|
{
|
|
firstProcPatchi = patchi;
|
|
firstProcFacei = pp.start();
|
|
}
|
|
|
|
if (!isA<processorPolyPatch>(pp) && firstProcPatchi != patches.size())
|
|
{
|
|
FatalErrorInFunction
|
|
<< "Processor patches do not follow boundary patches"
|
|
<< exit(FatalError);
|
|
}
|
|
}
|
|
|
|
// Clone the non-processor patches
|
|
for (label patchi = 0; patchi < firstProcPatchi; ++ patchi)
|
|
{
|
|
const polyPatch& pp = patches[patchi];
|
|
|
|
newPatches.append
|
|
(
|
|
pp.clone(patches, patchi, pp.size(), pp.start()).ptr()
|
|
);
|
|
}
|
|
|
|
// Convenience function to generate patch names for the owner or neighbour
|
|
auto nccPatchNames = [&](const label i)
|
|
{
|
|
return
|
|
Pair<word>
|
|
(
|
|
cyclicNames[i] + "_on_" + patchNames[i][0],
|
|
cyclicNames[i] + "_on_" + patchNames[i][1]
|
|
);
|
|
};
|
|
|
|
// Add the cyclic patches
|
|
forAll(patchNames, i)
|
|
{
|
|
Info<< indent << "Adding "
|
|
<< nonConformalCyclicPolyPatch::typeName
|
|
<< " interfaces between patches: " << incrIndent << nl
|
|
<< indent << patchNames[i] << decrIndent << nl
|
|
<< indent << "Named:" << incrIndent << nl
|
|
<< indent << Pair<word>(nccPatchNames(i)) << decrIndent << nl
|
|
<< indent << "With transform: " << incrIndent << nl;
|
|
transforms[i].write(Info);
|
|
Info<< decrIndent << nl;
|
|
|
|
newPatches.append
|
|
(
|
|
new nonConformalCyclicPolyPatch
|
|
(
|
|
nccPatchNames(i)[0],
|
|
0,
|
|
firstProcFacei,
|
|
newPatches.size(),
|
|
patches,
|
|
nonConformalCyclicPolyPatch::typeName,
|
|
nccPatchNames(i)[1],
|
|
patchNames[i][0],
|
|
transforms[i]
|
|
)
|
|
);
|
|
newPatches.append
|
|
(
|
|
new nonConformalCyclicPolyPatch
|
|
(
|
|
nccPatchNames(i)[1],
|
|
0,
|
|
firstProcFacei,
|
|
newPatches.size(),
|
|
patches,
|
|
nonConformalCyclicPolyPatch::typeName,
|
|
nccPatchNames(i)[0],
|
|
patchNames[i][1],
|
|
inv(transforms[i])
|
|
)
|
|
);
|
|
}
|
|
|
|
// Add the error patches. Note there is only one for each source patch,
|
|
// regardless of how many interfaces are attached to that patch.
|
|
auto appendErrorPatches = [&](const bool owner)
|
|
{
|
|
wordHashSet patchANames;
|
|
forAll(patchNames, i)
|
|
{
|
|
patchANames.insert(patchNames[i][!owner]);
|
|
}
|
|
forAllConstIter(wordHashSet, patchANames, iter)
|
|
{
|
|
newPatches.append
|
|
(
|
|
new nonConformalErrorPolyPatch
|
|
(
|
|
nonConformalErrorPolyPatch::typeName + "_on_" + iter.key(),
|
|
0,
|
|
firstProcFacei,
|
|
newPatches.size(),
|
|
patches,
|
|
nonConformalErrorPolyPatch::typeName,
|
|
iter.key()
|
|
)
|
|
);
|
|
}
|
|
};
|
|
appendErrorPatches(true);
|
|
appendErrorPatches(false);
|
|
|
|
// Clone the processor patches
|
|
for (label patchi = firstProcPatchi; patchi < patches.size(); ++ patchi)
|
|
{
|
|
const polyPatch& pp = patches[patchi];
|
|
|
|
newPatches.append
|
|
(
|
|
pp.clone(patches, newPatches.size(), pp.size(), pp.start()).ptr()
|
|
);
|
|
}
|
|
|
|
// Add the processor cyclic patches
|
|
if (Pstream::parRun())
|
|
{
|
|
forAll(patchNames, i)
|
|
{
|
|
const polyPatch& patch1 = patches[patchNames[i][0]];
|
|
const polyPatch& patch2 = patches[patchNames[i][1]];
|
|
|
|
boolList procHasPatch1(Pstream::nProcs(), false);
|
|
procHasPatch1[Pstream::myProcNo()] = !patch1.empty();
|
|
Pstream::gatherList(procHasPatch1);
|
|
Pstream::scatterList(procHasPatch1);
|
|
|
|
boolList procHasPatch2(Pstream::nProcs(), false);
|
|
procHasPatch2[Pstream::myProcNo()] = !patch2.empty();
|
|
Pstream::gatherList(procHasPatch2);
|
|
Pstream::scatterList(procHasPatch2);
|
|
|
|
// Multiple cyclic interfaces must be ordered in a specific way for
|
|
// processor communication to function correctly.
|
|
//
|
|
// A communication that is sent from the cyclic owner is received
|
|
// on the cyclic neighbour and vice versa. Therefore, in a coupled
|
|
// pair of processors if one sends the owner first the other must
|
|
// receive the neighbour first.
|
|
//
|
|
// We ensure the above by ordering the patches so that for the
|
|
// lower indexed processor the owner interface comes first, and for
|
|
// the higher indexed processor the neighbour comes first.
|
|
|
|
auto appendProcPatches = [&](const bool owner, const bool first)
|
|
{
|
|
const boolList& procHasPatchA =
|
|
owner ? procHasPatch1 : procHasPatch2;
|
|
const boolList& procHasPatchB =
|
|
owner ? procHasPatch2 : procHasPatch1;
|
|
|
|
if (procHasPatchA[Pstream::myProcNo()])
|
|
{
|
|
forAll(procHasPatchB, proci)
|
|
{
|
|
if
|
|
(
|
|
(
|
|
(first && proci < Pstream::myProcNo())
|
|
|| (!first && proci > Pstream::myProcNo())
|
|
)
|
|
&& procHasPatchB[proci]
|
|
)
|
|
{
|
|
newPatches.append
|
|
(
|
|
new nonConformalProcessorCyclicPolyPatch
|
|
(
|
|
0,
|
|
mesh.nFaces(),
|
|
newPatches.size(),
|
|
patches,
|
|
Pstream::myProcNo(),
|
|
proci,
|
|
nccPatchNames(i)[!owner],
|
|
patchNames[i][!owner]
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
appendProcPatches(true, true);
|
|
appendProcPatches(false, true);
|
|
appendProcPatches(false, false);
|
|
appendProcPatches(true, false);
|
|
}
|
|
}
|
|
|
|
// Re-patch the mesh
|
|
mesh.removeFvBoundary();
|
|
mesh.addFvPatches(newPatches);
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
#include "addOverwriteOption.H"
|
|
#include "addRegionOption.H"
|
|
#include "addDictOption.H"
|
|
|
|
const bool haveArgs = argList::hasArgs(argc, argv);
|
|
if (haveArgs)
|
|
{
|
|
argList::validArgs.append("patch1");
|
|
argList::validArgs.append("patch2");
|
|
}
|
|
|
|
#include "setRootCase.H"
|
|
#include "createTime.H"
|
|
runTime.functionObjects().off();
|
|
|
|
// Get the patches between which to create interfaces and the associated
|
|
// transformations. If there are arguments then read the patch names from
|
|
// the arguments and assume an identity transformation. If not, then load
|
|
// the system dictionary and read potentially multiple pairs of patches and
|
|
// associated transformations.
|
|
List<Pair<word>> patchNames;
|
|
wordList cyclicNames;
|
|
List<cyclicTransform> transforms;
|
|
if (haveArgs)
|
|
{
|
|
patchNames.append(Pair<word>(args[1], args[2]));
|
|
cyclicNames.append(nonConformalCyclicPolyPatch::typeName);
|
|
transforms.append(cyclicTransform(true));
|
|
}
|
|
else
|
|
{
|
|
static const word dictName("createNonConformalCouplesDict");
|
|
|
|
IOdictionary dict(systemDict(dictName, args, runTime));
|
|
|
|
forAllConstIter(dictionary, dict, iter)
|
|
{
|
|
patchNames.append(iter().dict().lookup<Pair<word>>("patches"));
|
|
cyclicNames.append(iter().dict().dictName());
|
|
transforms.append(cyclicTransform(iter().dict(), true));
|
|
}
|
|
}
|
|
|
|
Foam::word meshRegionName = polyMesh::defaultRegion;
|
|
args.optionReadIfPresent("region", meshRegionName);
|
|
|
|
const bool overwrite = args.optionFound("overwrite");
|
|
|
|
#include "createNamedMesh.H"
|
|
|
|
const word oldInstance = mesh.pointsInstance();
|
|
|
|
// Make sure the mesh is not connected before couples are added
|
|
fvMeshStitchers::stationary stitcher(mesh);
|
|
stitcher.disconnect(false, false);
|
|
|
|
createNonConformalCouples
|
|
(
|
|
mesh,
|
|
patchNames,
|
|
cyclicNames,
|
|
transforms
|
|
);
|
|
|
|
// Connect the mesh so that the new stitching topology gets written out
|
|
stitcher.connect(false, false);
|
|
|
|
mesh.setInstance(runTime.timeName());
|
|
|
|
// Set the precision of the points data to 10
|
|
IOstream::defaultPrecision(max(10u, IOstream::defaultPrecision()));
|
|
|
|
if (!overwrite)
|
|
{
|
|
runTime++;
|
|
}
|
|
else
|
|
{
|
|
mesh.setInstance(oldInstance);
|
|
}
|
|
|
|
// Write resulting mesh
|
|
Info<< "Writing mesh to " << runTime.timeName() << nl << endl;
|
|
mesh.write();
|
|
|
|
Info<< "End\n" << endl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|