/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2016-2025 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 .
\*---------------------------------------------------------------------------*/
#include "ZoneMesh.H"
#include "entry.H"
#include "DynamicList.H"
#include "Pstream.H"
#include "PtrListOps.H"
#include "globalIndex.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
template
int Foam::ZoneMesh::disallowGenericZones
(
debug::debugSwitch("disallowGenericZones", 0)
);
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
template
Foam::label Foam::ZoneMesh::totalSize() const
{
// Count number of objects in all zones
const PtrList& zones = *this;
label total = 0;
for (const ZoneType& zn : zones)
{
total += zn.size();
}
return total;
}
template
void Foam::ZoneMesh::calcZoneMap() const
{
if (zoneMapPtr_)
{
FatalErrorInFunction
<< "zone map already calculated"
<< abort(FatalError);
}
else
{
zoneMapPtr_.reset(new Map());
auto& map = *zoneMapPtr_;
// Fill in objects of all zones into the map.
// The key is the global object index, value is the zone index
map.reserve(this->totalSize());
const PtrList& zones = *this;
label zonei = 0;
for (const ZoneType& zn : zones)
{
for (const label id : static_cast(zn))
{
//map.insert(id, zonei);
const auto fnd = map.cfind(id);
if (!fnd)
{
map.insert(id, zonei);
}
else if (fnd.val() != zonei)
{
// Multiple zones for same id
if (!additionalMapPtr_)
{
// First occurrence
label maxIndex = -1;
for (const ZoneType& zn : zones)
{
for
(
const label id
: static_cast(zn)
)
{
maxIndex = Foam::max(maxIndex, id);
}
}
additionalMapPtr_.reset(new labelListList(maxIndex+1));
}
auto& additionalMap = *additionalMapPtr_;
additionalMap[id].push_uniq(zonei);
}
}
++zonei;
}
// Sort such that map contains lowest zoneID
if (additionalMapPtr_)
{
auto& additionalMap = *additionalMapPtr_;
forAll(additionalMap, id)
{
labelList& zones = additionalMap[id];
if (zones.size())
{
Foam::stableSort(zones);
const label zonei = map[id];
const label index = findLower(zones, zonei);
if (index == -1)
{
// Already first
}
else
{
map.set(id, zones[0]);
for (label i = 0; i < zones.size() && i <= index; i++)
{
zones[i] = zones[i+1];
}
zones[index+1] = zonei;
}
}
}
}
}
}
template
bool Foam::ZoneMesh::hasGroupIDs() const
{
if (groupIDsPtr_)
{
// Use existing cache
return !groupIDsPtr_->empty();
}
const PtrList& zones = *this;
for (const ZoneType& zn : zones)
{
if (!zn.inGroups().empty())
{
return true;
}
}
return false;
}
template
void Foam::ZoneMesh::calcGroupIDs() const
{
if (groupIDsPtr_)
{
return; // Or FatalError
}
groupIDsPtr_.reset(new HashTable(16));
auto& groupLookup = *groupIDsPtr_;
const PtrList& zones = *this;
forAll(zones, zonei)
{
for (const word& groupName : zones[zonei].inGroups())
{
groupLookup(groupName).push_back(zonei);
}
}
// Remove groups that clash with zone names
forAll(zones, zonei)
{
if (groupLookup.empty())
{
break; // Early termination
}
else if (groupLookup.erase(zones[zonei].name()))
{
WarningInFunction
<< "Removed group '" << zones[zonei].name()
<< "' which clashes with zone " << zonei
<< " of the same name."
<< endl;
}
}
}
template
void Foam::ZoneMesh::populate
(
PtrList&& entries
)
{
clearLocalAddressing();
PtrList& zones = *this;
zones.resize_null(entries.size());
// Transcribe
// Does not handle nullptr at all
forAll(zones, zonei)
{
// Possible handling for nullptr:
// zones.emplace_set
// (
// zonei,
// "missing_" + ::Foam::name(zonei), zonei, *this
// );
zones.set
(
zonei,
ZoneType::New
(
entries[zonei].keyword(),
entries[zonei].dict(),
zonei,
*this
)
);
}
entries.clear();
}
template
bool Foam::ZoneMesh::readIOcontents
(
const bool allowOptionalRead
)
{
bool updated = false;
PtrList entries;
if
(
isReadRequired()
|| (allowOptionalRead && isReadOptional() && headerOk())
)
{
// Warn for MUST_READ_IF_MODIFIED
warnNoRereading>();
// Read entries
Istream& is = readStream(typeName);
is >> entries;
is.check(FUNCTION_NAME);
close();
updated = true;
}
// Future: support master-only and broadcast?
if (updated)
{
populate(std::move(entries));
}
return updated;
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template
Foam::ZoneMesh::ZoneMesh
(
const IOobject& io,
const MeshType& mesh
)
:
PtrList(),
regIOobject(io),
mesh_(mesh)
{
// Note: this is inconsistent with polyBoundaryMesh
// which does not permit optional reading
readIOcontents(true); // allowOptionalRead = true
}
template
Foam::ZoneMesh::ZoneMesh
(
const IOobject& io,
const MeshType& mesh,
Foam::zero
)
:
PtrList(),
regIOobject(io),
mesh_(mesh)
{}
template
Foam::ZoneMesh::ZoneMesh
(
const IOobject& io,
const MeshType& mesh,
const label size
)
:
PtrList(size),
regIOobject(io),
mesh_(mesh)
{
// Note: this is inconsistent with polyBoundaryMesh
// which does not read all
readIOcontents(true); // allowOptionalRead = true
}
template
Foam::ZoneMesh::ZoneMesh
(
const IOobject& io,
const MeshType& mesh,
const PtrList& list
)
:
PtrList(),
regIOobject(io),
mesh_(mesh)
{
if (!readIOcontents(true)) // allowOptionalRead = true
{
// Nothing read. Use supplied zones
PtrList& zones = *this;
zones.resize(list.size());
forAll(zones, zonei)
{
zones.set(zonei, list[zonei].clone(*this));
}
}
}
template
Foam::ZoneMesh::ZoneMesh
(
const IOobject& io,
const MeshType& mesh,
PtrList&& entries
)
:
PtrList(),
regIOobject(io),
mesh_(mesh)
{
if (!readIOcontents(true)) // allowOptionalRead = true
{
populate(std::move(entries));
}
entries.clear();
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
template
const Foam::Map&
Foam::ZoneMesh::zoneMap() const
{
if (!zoneMapPtr_)
{
calcZoneMap();
}
return *zoneMapPtr_;
}
template
Foam::label Foam::ZoneMesh::whichZone
(
const label objectIndex
) const
{
return zoneMap().lookup(objectIndex, -1);
}
template
Foam::label Foam::ZoneMesh::whichZones
(
const label objectIndex,
DynamicList& zones
) const
{
zones.clear();
const auto fnd = zoneMap().cfind(objectIndex);
if (fnd)
{
// Add main element
zones.push_back(fnd.val());
if (additionalMapPtr_)
{
const auto& additionalMap = *additionalMapPtr_;
if (objectIndex < additionalMap.size())
{
for (const label zonei : additionalMap[objectIndex])
{
zones.push_uniq(zonei);
}
}
}
}
return zones.size();
}
template
Foam::wordList Foam::ZoneMesh::types() const
{
return PtrListOps::get(*this, typeOp());
}
template
Foam::wordList Foam::ZoneMesh::names() const
{
return PtrListOps::get(*this, nameOp());
}
template
Foam::wordList Foam::ZoneMesh::groupNames() const
{
return this->groupZoneIDs().sortedToc();
}
template
Foam::wordList Foam::ZoneMesh::names
(
const wordRe& matcher
) const
{
return PtrListOps::names(*this, matcher);
}
template
Foam::wordList Foam::ZoneMesh::names
(
const wordRes& matcher
)
const
{
return PtrListOps::names(*this, matcher);
}
template
Foam::wordList Foam::ZoneMesh::sortedNames() const
{
wordList sorted(this->names());
Foam::sort(sorted);
return sorted;
}
template
Foam::wordList Foam::ZoneMesh::sortedNames
(
const wordRe& matcher
) const
{
wordList sorted(this->names(matcher));
Foam::sort(sorted);
return sorted;
}
template
Foam::wordList Foam::ZoneMesh::sortedNames
(
const wordRes& matcher
)
const
{
wordList sorted(this->names(matcher));
Foam::sort(sorted);
return sorted;
}
template
Foam::labelList Foam::ZoneMesh::indices
(
const wordRe& matcher,
const bool useGroups
) const
{
if (matcher.empty())
{
return labelList();
}
// Only check groups if requested and they exist
const bool checkGroups = (useGroups && this->hasGroupIDs());
labelHashSet ids;
if (checkGroups)
{
ids.reserve(this->size());
}
if (matcher.isPattern())
{
if (checkGroups)
{
const auto& groupLookup = groupZoneIDs();
forAllConstIters(groupLookup, iter)
{
if (matcher(iter.key()))
{
// Add ids associated with the group
ids.insert(iter.val());
}
}
}
if (ids.empty())
{
return PtrListOps::findMatching(*this, matcher);
}
else
{
ids.insert(PtrListOps::findMatching(*this, matcher));
}
}
else
{
// Literal string.
// Special version of above for reduced memory footprint
const label zoneId = PtrListOps::firstMatching(*this, matcher);
if (zoneId >= 0)
{
return labelList(one{}, zoneId);
}
else if (checkGroups)
{
const auto iter = groupZoneIDs().cfind(matcher);
if (iter.good())
{
// Add ids associated with the group
ids.insert(iter.val());
}
}
}
return ids.sortedToc();
}
template
Foam::labelList Foam::ZoneMesh::indices
(
const wordRes& matcher,
const bool useGroups
) const
{
if (matcher.empty())
{
return labelList();
}
else if (matcher.size() == 1)
{
return this->indices(matcher.front(), useGroups);
}
labelHashSet ids;
// Only check groups if requested and they exist
if (useGroups && this->hasGroupIDs())
{
ids.reserve(this->size());
const auto& groupLookup = groupZoneIDs();
forAllConstIters(groupLookup, iter)
{
if (matcher(iter.key()))
{
// Add ids associated with the group
ids.insert(iter.val());
}
}
}
if (ids.empty())
{
return PtrListOps::findMatching(*this, matcher);
}
else
{
ids.insert(PtrListOps::findMatching(*this, matcher));
}
return ids.sortedToc();
}
template
Foam::labelList Foam::ZoneMesh::indices
(
const wordRes& allow,
const wordRes& deny,
const bool useGroups
) const
{
if (allow.empty() && deny.empty())
{
// Fast-path: select all
return identity(this->size());
}
const wordRes::filter matcher(allow, deny);
labelHashSet ids;
// Only check groups if requested and they exist
if (useGroups && this->hasGroupIDs())
{
ids.reserve(this->size());
const auto& groupLookup = groupZoneIDs();
forAllConstIters(groupLookup, iter)
{
if (matcher(iter.key()))
{
// Add ids associated with the group
ids.insert(iter.val());
}
}
}
if (ids.empty())
{
return PtrListOps::findMatching(*this, matcher);
}
else
{
ids.insert(PtrListOps::findMatching(*this, matcher));
}
return ids.sortedToc();
}
template
Foam::label Foam::ZoneMesh::findIndex
(
const wordRe& key
) const
{
if (key.empty())
{
return -1;
}
return PtrListOps::firstMatching(*this, key);
}
template
Foam::label Foam::ZoneMesh::findIndex
(
const wordRes& matcher
) const
{
if (matcher.empty())
{
return -1;
}
return PtrListOps::firstMatching(*this, matcher);
}
template
Foam::label Foam::ZoneMesh::findZoneID
(
const word& zoneName
) const
{
if (zoneName.empty())
{
return -1;
}
label zoneId = PtrListOps::firstMatching(*this, zoneName);
if (zoneId < 0)
{
DebugInFunction
<< "Zone named " << zoneName << " not found. "
<< "List of available zone names: " << names() << endl;
// Used for -dry-run, for example
if (disallowGenericZones != 0)
{
Info<< "Creating dummy zone " << zoneName << endl;
auto& zm = const_cast&>(*this);
zm.emplace_back(zoneName, zm.size(), zm);
}
}
return zoneId;
}
template
const ZoneType* Foam::ZoneMesh::cfindZone
(
const word& zoneName
) const
{
if (zoneName.empty())
{
return nullptr;
}
const PtrList& zones = *this;
for (auto iter = zones.begin(); iter != zones.end(); ++iter)
{
const ZoneType* ptr = iter.get();
if (ptr && zoneName == ptr->name())
{
return ptr;
}
}
// Used for -dry-run, for example
if (disallowGenericZones != 0)
{
Info<< "Creating dummy zone " << zoneName << endl;
auto& zm = const_cast&>(*this);
zm.emplace_back(zoneName, zm.size(), zm);
}
return nullptr;
}
template
ZoneType* Foam::ZoneMesh::findZone
(
const word& zoneName
)
{
return const_cast(this->cfindZone(zoneName));
}
template
Foam::bitSet Foam::ZoneMesh::selection
(
const labelUList& zoneIds
) const
{
bitSet bitset;
for (const label zonei : zoneIds)
{
#ifdef FULLDEBUG
if (zonei < 0 || zonei >= this->size())
{
FatalErrorInFunction
<< ZoneType::typeName << " "
<< zonei << " out of range [0," << this->size() << ")"
<< abort(FatalError);
}
#endif
bitset.set
(
static_cast(this->operator[](zonei))
);
}
return bitset;
}
template
Foam::bitSet Foam::ZoneMesh::selection
(
const wordRe& matcher,
const bool useGroups
) const
{
// matcher.empty() is handled by indices()
return this->selection(this->indices(matcher, useGroups));
}
template
Foam::bitSet Foam::ZoneMesh::selection
(
const wordRes& matcher,
const bool useGroups
) const
{
// matcher.empty() is handled by indices()
return this->selection(this->indices(matcher, useGroups));
}
template
const Foam::HashTable&
Foam::ZoneMesh::groupZoneIDs() const
{
if (!groupIDsPtr_)
{
calcGroupIDs();
}
return *groupIDsPtr_;
}
template
void Foam::ZoneMesh::setGroup
(
const word& groupName,
const labelUList& zoneIDs
)
{
groupIDsPtr_.reset(nullptr);
PtrList& zones = *this;
boolList pending(zones.size(), true);
// Add to specified zones
for (const label zonei : zoneIDs)
{
if (pending.test(zonei))
{
pending.unset(zonei);
zones[zonei].addGroup(groupName);
}
}
// Remove from other zones
forAll(zones, zonei)
{
if (pending.test(zonei))
{
zones[zonei].removeGroup(groupName);
}
}
}
// Private until it is more generally required (and gets a better name?)
template
void Foam::ZoneMesh::clearLocalAddressing()
{
zoneMapPtr_.reset(nullptr);
additionalMapPtr_.reset(nullptr);
groupIDsPtr_.reset(nullptr);
}
template
void Foam::ZoneMesh::clearAddressing()
{
clearLocalAddressing();
PtrList& zones = *this;
for (ZoneType& zn : zones)
{
zn.clearAddressing();
}
}
template
void Foam::ZoneMesh::clearPrimitives()
{
PtrList& zones = *this;
for (ZoneType& zn : zones)
{
zn.clearPrimitives();
}
}
template
void Foam::ZoneMesh::clear()
{
clearAddressing();
PtrList::clear();
}
template
bool Foam::ZoneMesh::checkDefinition
(
const bool report
) const
{
bool hasError = false;
const PtrList& zones = *this;
for (const ZoneType& zn : zones)
{
hasError |= zn.checkDefinition(report);
}
return hasError;
}
template
bool Foam::ZoneMesh::checkParallelSync
(
const bool report
) const
{
if (!UPstream::parRun())
{
return false;
}
const PtrList& zones = *this;
bool hasError = false;
const wordList localNames(this->names());
const wordList localTypes(this->types());
// Check and report error(s) on master
// - don't need indexing on master itself
const globalIndex procAddr
(
globalIndex::gatherNonLocal{},
localNames.size()
);
const wordList allNames(procAddr.gather(localNames));
const wordList allTypes(procAddr.gather(localTypes));
// Automatically restricted to master
for (const int proci : procAddr.subProcs())
{
const auto procNames(allNames.slice(procAddr.range(proci)));
const auto procTypes(allTypes.slice(procAddr.range(proci)));
if (procNames != localNames || procTypes != localTypes)
{
hasError = true;
if (debug || report)
{
Info<< " ***Inconsistent zones across processors, "
"processor 0 has zone names:" << localNames
<< " zone types:" << localTypes
<< " processor " << proci
<< " has zone names:" << procNames
<< " zone types:" << procTypes
<< endl;
}
}
}
Pstream::broadcast(hasError);
// Check local contents
if (!hasError)
{
for (const ZoneType& zn : zones)
{
if (zn.checkParallelSync(false))
{
hasError = true;
if (debug || (report && UPstream::master()))
{
Info<< " ***Zone " << zn.name()
<< " of type " << zn.type()
<< " is not correctly synchronised"
<< " across coupled boundaries."
<< " (coupled faces are either not both"
<< " present in set or have same flipmap)" << endl;
}
}
}
}
return hasError;
}
template
void Foam::ZoneMesh::movePoints(const pointField& pts)
{
PtrList& zones = *this;
for (ZoneType& zn : zones)
{
zn.movePoints(pts);
}
}
template
void Foam::ZoneMesh::updateMetaData()
{
wordList zoneNames(this->names());
if (zoneNames.empty())
{
this->removeMetaData();
}
else
{
dictionary& meta = this->getMetaData();
meta.set("names", zoneNames);
}
}
template
bool Foam::ZoneMesh::writeData(Ostream& os) const
{
os << *this;
return os.good();
}
// * * * * * * * * * * * * * * Member Operators * * * * * * * * * * * * * * //
template
const ZoneType& Foam::ZoneMesh::operator[]
(
const word& zoneName
) const
{
const label zonei = findZoneID(zoneName);
if (zonei < 0)
{
FatalErrorInFunction
<< "Zone named " << zoneName << " not found." << nl
<< "Available zone names: " << names() << endl
<< abort(FatalError);
}
return operator[](zonei);
}
template
ZoneType& Foam::ZoneMesh::operator[]
(
const word& zoneName
)
{
const label zonei = findZoneID(zoneName);
if (zonei < 0)
{
FatalErrorInFunction
<< "Zone named " << zoneName << " not found." << nl
<< "Available zone names: " << names() << endl
<< abort(FatalError);
}
return operator[](zonei);
}
template
ZoneType& Foam::ZoneMesh::operator()
(
const word& zoneName,
const bool verbose
)
{
ZoneType* ptr = findZone(zoneName);
const bool existing = bool(ptr);
if (!ptr)
{
ptr = new ZoneType(zoneName, this->size(), *this);
this->push_back(ptr);
}
if (verbose)
{
Info<< ZoneType::typeName << ' ' << zoneName
<< " (" << (existing ? "existing" : "new")
<< " at index " << ptr->index() << ')'
<< endl;
}
return *ptr;
}
// * * * * * * * * * * * * * * * IOstream Operators * * * * * * * * * * * * //
template
Foam::Ostream& Foam::operator<<
(
Ostream& os,
const ZoneMesh& zones
)
{
const label sz = zones.size();
if (sz)
{
os << sz << nl << token::BEGIN_LIST;
for (label i=0; i < sz; ++i)
{
zones[i].writeDict(os);
}
os << token::END_LIST;
}
else
{
os << sz << token::BEGIN_LIST << token::END_LIST;
}
return os;
}
// ************************************************************************* //