openfoam/applications/utilities/parallelProcessing/reconstructPar/reconstructPar.C
Mark Olesen b5435cc83e ENH: separate registry and revised file locations for finite-area
- The internal storage location of finite-area changes from being
  piggybacked on the polyMesh registry to a having its own dedicated
  registry:

  * allows a clearer separation of field types without name clashes.
  * prerequisite for supporting multiple finite-area regions (future)

Old Locations:
```
   0/Us
   constant/faMesh
   system/faMeshDefinition
   system/faSchemes
   system/faSolution
```

New Locations:
```
   0/finite-area/Us
   constant/finite-area/faMesh
   system/finite-area/faMeshDefinition  (or system/faMeshDefinition)
   system/finite-area/faSchemes
   system/finite-area/faSolution
```

NOTES:
    The new locations represent a hard change (breaking change) that
    is normally to be avoided, but seamless compatibility handling
    within the code was found to be unworkable.

    The `foamUpgradeFiniteArea` script provides assistance with migration.

    As a convenience, the system/faMeshDefinition location continues
    to be supported (may be deprecated in the future).
2024-04-19 17:20:09 +02:00

966 lines
30 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation
Copyright (C) 2015-2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
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
reconstructPar
Group
grpParallelUtilities
Description
Reconstructs fields of a case that is decomposed for parallel
execution of OpenFOAM.
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "timeSelector.H"
#include "fvCFD.H"
#include "IOobjectList.H"
#include "processorMeshes.H"
#include "regionProperties.H"
#include "fvFieldReconstructor.H"
#include "pointFieldReconstructor.H"
#include "lagrangianReconstructor.H"
#include "faCFD.H"
#include "faMesh.H"
#include "processorFaMeshes.H"
#include "faFieldReconstructor.H"
#include "cellSet.H"
#include "faceSet.H"
#include "pointSet.H"
#include "hexRef8Data.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
bool haveAllTimes
(
const wordHashSet& masterTimeDirSet,
const instantList& timeDirs
)
{
// Loop over all times
for (const instant& t : timeDirs)
{
if (!masterTimeDirSet.found(t.name()))
{
return false;
}
}
return true;
}
int main(int argc, char *argv[])
{
argList::addNote
(
"Reconstruct fields of a parallel case"
);
// Enable -constant ... if someone really wants it
// Enable -withZero to prevent accidentally trashing the initial fields
timeSelector::addOptions(true, true); // constant(true), zero(true)
argList::noParallel();
#include "addAllRegionOptions.H"
argList::addVerboseOption();
argList::addOption
(
"fields",
"wordRes",
"Specify single or multiple fields to reconstruct (all by default)."
" Eg, 'T' or '(p T U \"alpha.*\")'"
);
argList::addBoolOption
(
"no-fields", // noFields
"Skip reconstructing fields"
);
argList::addOptionCompat("no-fields", {"noFields", 2106});
argList::addOption
(
"lagrangianFields",
"wordRes",
"Specify single or multiple lagrangian fields to reconstruct"
" (all by default)."
" Eg, '(U d)'"
" - Positions are always included."
);
argList::addBoolOption
(
"no-lagrangian", // noLagrangian
"Skip reconstructing lagrangian positions and fields"
);
argList::addOptionCompat("no-lagrangian", {"noLagrangian", 2106});
argList::addBoolOption
(
"no-sets",
"Skip reconstructing cellSets, faceSets, pointSets"
);
argList::addOptionCompat("no-sets", {"noSets", 2106});
argList::addBoolOption
(
"newTimes",
"Only reconstruct new times (i.e. that do not exist already)"
);
#include "setRootCase.H"
#include "createTime.H"
const bool doFields = !args.found("no-fields");
wordRes selectedFields;
if (doFields)
{
args.readListIfPresent<wordRe>("fields", selectedFields);
}
else
{
Info<< "Skipping reconstructing fields";
if (args.found("fields"))
{
Info<< ". Ignore -fields option";
}
Info<< nl << endl;
}
const bool doFiniteArea = !args.found("no-finite-area");
if (!doFiniteArea)
{
Info<< "Skipping reconstructing finiteArea mesh/fields"
<< nl << endl;
}
const bool doLagrangian = !args.found("no-lagrangian");
wordRes selectedLagrangianFields;
if (doLagrangian)
{
args.readListIfPresent<wordRe>
(
"lagrangianFields", selectedLagrangianFields
);
}
else
{
Info<< "Skipping reconstructing lagrangian positions/fields";
if (args.found("lagrangianFields"))
{
Info<< ". Ignore -lagrangianFields option";
}
Info<< nl << endl;
}
const bool doReconstructSets = !args.found("no-sets");
if (!doReconstructSets)
{
Info<< "Skipping reconstructing cellSets, faceSets and pointSets"
<< nl << endl;
}
const bool newTimes = args.found("newTimes");
// Get region names
#include "getAllRegionOptions.H"
// Determine the processor count
label nProcs{0};
if (regionNames.empty())
{
FatalErrorInFunction
<< "No regions specified or detected."
<< exit(FatalError);
}
else if (regionNames[0] == polyMesh::defaultRegion)
{
nProcs = fileHandler().nProcs(args.path());
}
else
{
nProcs = fileHandler().nProcs(args.path(), regionNames[0]);
if (regionNames.size() == 1)
{
Info<< "Using region: " << regionNames[0] << nl << endl;
}
}
if (!nProcs)
{
FatalErrorInFunction
<< "No processor* directories found"
<< exit(FatalError);
}
// Warn fileHandler of number of processors
const_cast<fileOperation&>(fileHandler()).nProcs(nProcs);
// Create the processor databases
PtrList<Time> databases(nProcs);
forAll(databases, proci)
{
databases.set
(
proci,
new Time
(
Time::controlDictName,
args.rootPath(),
args.caseName()/("processor" + Foam::name(proci)),
args.allowFunctionObjects(),
args.allowLibs()
)
);
}
// Use the times list from the master processor
// and select a subset based on the command-line options
instantList timeDirs = timeSelector::select
(
databases[0].times(),
args
);
// Note that we do not set the runTime time so it is still the
// one set through the controlDict. The -time option
// only affects the selected set of times from processor0.
// - can be illogical
// + any point motion handled through mesh.readUpdate
if (timeDirs.empty())
{
WarningInFunction << "No times selected";
exit(1);
}
// Get current times if -newTimes
instantList masterTimeDirs;
if (newTimes)
{
masterTimeDirs = runTime.times();
}
wordHashSet masterTimeDirSet(2*masterTimeDirs.size());
for (const instant& t : masterTimeDirs)
{
masterTimeDirSet.insert(t.name());
}
// Set all times on processor meshes equal to reconstructed mesh
forAll(databases, proci)
{
databases[proci].setTime(runTime);
}
forAll(regionNames, regioni)
{
const word& regionName = regionNames[regioni];
const word& regionDir = polyMesh::regionName(regionName);
Info<< "\n\nReconstructing fields" << nl
<< "region=" << regionName << nl << endl;
if
(
newTimes
&& regionNames.size() == 1
&& regionDir.empty()
&& haveAllTimes(masterTimeDirSet, timeDirs)
)
{
Info<< "Skipping region " << regionName
<< " since already have all times"
<< endl << endl;
continue;
}
fvMesh mesh
(
IOobject
(
regionName,
runTime.timeName(),
runTime,
Foam::IOobject::MUST_READ
)
);
// Read all meshes and addressing to reconstructed mesh
processorMeshes procMeshes(databases, regionName);
// Loop over all times
forAll(timeDirs, timei)
{
if (newTimes && masterTimeDirSet.found(timeDirs[timei].name()))
{
Info<< "Skipping time " << timeDirs[timei].name()
<< endl << endl;
continue;
}
// Set time for global database
runTime.setTime(timeDirs[timei], timei);
Info<< "Time = " << runTime.timeName() << endl << endl;
// Set time for all databases
forAll(databases, proci)
{
databases[proci].setTime(timeDirs[timei], timei);
}
// Check if any new meshes need to be read.
polyMesh::readUpdateState meshStat = mesh.readUpdate();
polyMesh::readUpdateState procStat = procMeshes.readUpdate();
if (procStat == polyMesh::POINTS_MOVED)
{
// Reconstruct the points for moving mesh cases and write
// them out
procMeshes.reconstructPoints(mesh);
}
else if (meshStat != procStat)
{
WarningInFunction
<< "readUpdate for the reconstructed mesh:"
<< meshStat << nl
<< "readUpdate for the processor meshes :"
<< procStat << nl
<< "These should be equal or your addressing"
<< " might be incorrect."
<< " Please check your time directories for any "
<< "mesh directories." << endl;
}
// Get list of objects from processor0 database
IOobjectList objects
(
procMeshes.meshes()[0],
databases[0].timeName(),
IOobjectOption::NO_REGISTER
);
IOobjectList faObjects;
if (doFiniteArea && doFields)
{
// List of area mesh objects (assuming single region)
// - scan on processor0
faObjects = IOobjectList
(
procMeshes.meshes()[0],
databases[0].timeName(),
faMesh::dbDir(word::null), // local relative to mesh
IOobjectOption::NO_REGISTER
);
}
if (doFields)
{
// If there are any FV fields, reconstruct them
Info<< "Reconstructing FV fields" << nl << endl;
fvFieldReconstructor reconstructor
(
mesh,
procMeshes.meshes(),
procMeshes.faceProcAddressing(),
procMeshes.cellProcAddressing(),
procMeshes.boundaryProcAddressing()
);
reconstructor.reconstructAllFields(objects, selectedFields);
if (reconstructor.nReconstructed() == 0)
{
Info<< "No FV fields" << nl << endl;
}
}
if (doFields)
{
Info<< "Reconstructing point fields" << nl << endl;
const pointMesh& pMesh = pointMesh::New(mesh);
PtrList<pointMesh> pMeshes(procMeshes.meshes().size());
forAll(pMeshes, proci)
{
pMeshes.set
(
proci,
new pointMesh(procMeshes.meshes()[proci])
);
}
pointFieldReconstructor reconstructor
(
pMesh,
pMeshes,
procMeshes.pointProcAddressing(),
procMeshes.boundaryProcAddressing()
);
reconstructor.reconstructAllFields(objects, selectedFields);
if (reconstructor.nReconstructed() == 0)
{
Info<< "No point fields" << nl << endl;
}
}
// If there are any clouds, reconstruct them.
// The problem is that a cloud of size zero will not get written so
// in pass 1 we determine the cloud names and per cloud name the
// fields. Note that the fields are stored as IOobjectList from
// the first processor that has them. They are in pass2 only used
// for name and type (scalar, vector etc).
if (doLagrangian)
{
HashTable<IOobjectList> allCloudObjects;
forAll(databases, proci)
{
fileName lagrangianDir
(
fileHandler().filePath
(
databases[proci].timePath()
/ regionDir
/ cloud::prefix
)
);
fileNameList cloudDirs;
if (!lagrangianDir.empty())
{
cloudDirs = fileHandler().readDir
(
lagrangianDir,
fileName::DIRECTORY
);
}
for (const fileName& cloudDir : cloudDirs)
{
// Check if we already have cloud objects for this
// cloudname
if (!allCloudObjects.found(cloudDir))
{
// Do local scan for valid cloud objects
IOobjectList localObjs
(
procMeshes.meshes()[proci],
databases[proci].timeName(),
cloud::prefix/cloudDir
);
if
(
localObjs.found("coordinates")
|| localObjs.found("positions")
)
{
allCloudObjects.insert(cloudDir, localObjs);
}
}
}
}
if (allCloudObjects.size())
{
lagrangianReconstructor reconstructor
(
mesh,
procMeshes.meshes(),
procMeshes.faceProcAddressing(),
procMeshes.cellProcAddressing()
);
// Pass2: reconstruct the cloud
forAllConstIters(allCloudObjects, iter)
{
const word cloudName = word::validate(iter.key());
// Objects (on arbitrary processor)
const IOobjectList& cloudObjs = iter.val();
Info<< "Reconstructing lagrangian fields for cloud "
<< cloudName << nl << endl;
reconstructor.reconstructPositions(cloudName);
reconstructor.reconstructAllFields
(
cloudName,
cloudObjs,
selectedLagrangianFields
);
}
}
else
{
Info<< "No lagrangian fields" << nl << endl;
}
}
// If there are any FA fields, reconstruct them
if (!doFiniteArea)
{
}
else if
(
faObjects.count<areaScalarField>()
|| faObjects.count<areaVectorField>()
|| faObjects.count<areaSphericalTensorField>()
|| faObjects.count<areaSymmTensorField>()
|| faObjects.count<areaTensorField>()
|| faObjects.count<edgeScalarField>()
)
{
Info << "Reconstructing FA fields" << nl << endl;
faMesh aMesh(mesh);
processorFaMeshes procFaMeshes(procMeshes.meshes());
faFieldReconstructor reconstructor
(
aMesh,
procFaMeshes.meshes(),
procFaMeshes.edgeProcAddressing(),
procFaMeshes.faceProcAddressing(),
procFaMeshes.boundaryProcAddressing()
);
reconstructor.reconstructAllFields(faObjects);
}
else
{
Info << "No FA fields" << nl << endl;
}
if (doReconstructSets)
{
// Scan to find all sets
HashTable<label> cSetNames;
HashTable<label> fSetNames;
HashTable<label> pSetNames;
forAll(procMeshes.meshes(), proci)
{
const fvMesh& procMesh = procMeshes.meshes()[proci];
// Note: look at sets in current time only or between
// mesh and current time?. For now current time. This will
// miss out on sets in intermediate times that have not
// been reconstructed.
IOobjectList objects
(
procMesh,
databases[0].timeName(), //procMesh.facesInstance()
polyMesh::meshSubDir/"sets"
);
for (const IOobject& io : objects.csorted<cellSet>())
{
cSetNames.insert(io.name(), cSetNames.size());
}
for (const IOobject& io : objects.csorted<faceSet>())
{
fSetNames.insert(io.name(), fSetNames.size());
}
for (const IOobject& io : objects.csorted<pointSet>())
{
pSetNames.insert(io.name(), pSetNames.size());
}
}
if (cSetNames.size() || fSetNames.size() || pSetNames.size())
{
// Construct all sets
PtrList<cellSet> cellSets(cSetNames.size());
PtrList<faceSet> faceSets(fSetNames.size());
PtrList<pointSet> pointSets(pSetNames.size());
Info<< "Reconstructing sets:" << endl;
if (cSetNames.size())
{
Info<< " cellSets "
<< cSetNames.sortedToc() << endl;
}
if (fSetNames.size())
{
Info<< " faceSets "
<< fSetNames.sortedToc() << endl;
}
if (pSetNames.size())
{
Info<< " pointSets "
<< pSetNames.sortedToc() << endl;
}
// Load sets
forAll(procMeshes.meshes(), proci)
{
const fvMesh& procMesh = procMeshes.meshes()[proci];
IOobjectList objects
(
procMesh,
databases[0].timeName(),
polyMesh::meshSubDir/"sets"
);
// cellSets
const labelList& cellMap =
procMeshes.cellProcAddressing()[proci];
for (const IOobject& io : objects.csorted<cellSet>())
{
// Load cellSet
const cellSet procSet(io);
const label seti = cSetNames[io.name()];
if (!cellSets.set(seti))
{
cellSets.set
(
seti,
new cellSet
(
mesh,
io.name(),
procSet.size()
)
);
}
cellSet& cSet = cellSets[seti];
cSet.instance() = runTime.timeName();
for (const label celli : procSet)
{
cSet.insert(cellMap[celli]);
}
}
// faceSets
const labelList& faceMap =
procMeshes.faceProcAddressing()[proci];
for (const IOobject& io : objects.csorted<faceSet>())
{
// Load faceSet
const faceSet procSet(io);
const label seti = fSetNames[io.name()];
if (!faceSets.set(seti))
{
faceSets.set
(
seti,
new faceSet
(
mesh,
io.name(),
procSet.size()
)
);
}
faceSet& fSet = faceSets[seti];
fSet.instance() = runTime.timeName();
for (const label facei : procSet)
{
fSet.insert(mag(faceMap[facei])-1);
}
}
// pointSets
const labelList& pointMap =
procMeshes.pointProcAddressing()[proci];
for (const IOobject& io : objects.csorted<pointSet>())
{
// Load pointSet
const pointSet procSet(io);
const label seti = pSetNames[io.name()];
if (!pointSets.set(seti))
{
pointSets.set
(
seti,
new pointSet
(
mesh,
io.name(),
procSet.size()
)
);
}
pointSet& pSet = pointSets[seti];
pSet.instance() = runTime.timeName();
for (const label pointi : procSet)
{
pSet.insert(pointMap[pointi]);
}
}
}
// Write sets
for (const auto& set : cellSets)
{
set.write();
}
for (const auto& set : faceSets)
{
set.write();
}
for (const auto& set : pointSets)
{
set.write();
}
}
// Reconstruct refinement data
{
PtrList<hexRef8Data> procData(procMeshes.meshes().size());
forAll(procMeshes.meshes(), procI)
{
const fvMesh& procMesh = procMeshes.meshes()[procI];
procData.set
(
procI,
new hexRef8Data
(
IOobject
(
"dummy",
procMesh.time().timeName(),
polyMesh::meshSubDir,
procMesh,
IOobject::READ_IF_PRESENT,
IOobject::NO_WRITE,
IOobject::NO_REGISTER
)
)
);
}
// Combine individual parts
const PtrList<labelIOList>& cellAddr =
procMeshes.cellProcAddressing();
UPtrList<const labelList> cellMaps(cellAddr.size());
forAll(cellAddr, i)
{
cellMaps.set(i, &cellAddr[i]);
}
const PtrList<labelIOList>& pointAddr =
procMeshes.pointProcAddressing();
UPtrList<const labelList> pointMaps(pointAddr.size());
forAll(pointAddr, i)
{
pointMaps.set(i, &pointAddr[i]);
}
UPtrList<const hexRef8Data> procRefs(procData.size());
forAll(procData, i)
{
procRefs.set(i, &procData[i]);
}
hexRef8Data
(
IOobject
(
"dummy",
mesh.time().timeName(),
polyMesh::meshSubDir,
mesh,
IOobject::NO_READ,
IOobject::NO_WRITE,
IOobject::NO_REGISTER
),
cellMaps,
pointMaps,
procRefs
).write();
}
}
// Reconstruct refinement data
{
PtrList<hexRef8Data> procData(procMeshes.meshes().size());
forAll(procMeshes.meshes(), procI)
{
const fvMesh& procMesh = procMeshes.meshes()[procI];
procData.set
(
procI,
new hexRef8Data
(
IOobject
(
"dummy",
procMesh.time().timeName(),
polyMesh::meshSubDir,
procMesh,
IOobject::READ_IF_PRESENT,
IOobject::NO_WRITE,
IOobject::NO_REGISTER
)
)
);
}
// Combine individual parts
const PtrList<labelIOList>& cellAddr =
procMeshes.cellProcAddressing();
UPtrList<const labelList> cellMaps(cellAddr.size());
forAll(cellAddr, i)
{
cellMaps.set(i, &cellAddr[i]);
}
const PtrList<labelIOList>& pointAddr =
procMeshes.pointProcAddressing();
UPtrList<const labelList> pointMaps(pointAddr.size());
forAll(pointAddr, i)
{
pointMaps.set(i, &pointAddr[i]);
}
UPtrList<const hexRef8Data> procRefs(procData.size());
forAll(procData, i)
{
procRefs.set(i, &procData[i]);
}
hexRef8Data
(
IOobject
(
"dummy",
mesh.time().timeName(),
polyMesh::meshSubDir,
mesh,
IOobject::NO_READ,
IOobject::NO_WRITE,
IOobject::NO_REGISTER
),
cellMaps,
pointMaps,
procRefs
).write();
}
// If there is a "uniform" directory in the time region
// directory copy from the master processor
{
fileName uniformDir0
(
fileHandler().filePath
(
databases[0].timePath()/regionDir/"uniform"
)
);
if (!uniformDir0.empty() && fileHandler().isDir(uniformDir0))
{
fileHandler().cp(uniformDir0, runTime.timePath()/regionDir);
}
}
// For the first region of a multi-region case additionally
// copy the "uniform" directory in the time directory
if (regioni == 0 && !regionDir.empty())
{
fileName uniformDir0
(
fileHandler().filePath
(
databases[0].timePath()/"uniform"
)
);
if (!uniformDir0.empty() && fileHandler().isDir(uniformDir0))
{
fileHandler().cp(uniformDir0, runTime.timePath());
}
}
}
}
Info<< "\nEnd\n" << endl;
return 0;
}
// ************************************************************************* //