diff --git a/src/meshTools/Make/files b/src/meshTools/Make/files index d92cd130ad11bd503cb2289ca69461f7b9353ea4..09b7191723513790075d096bd98171e42c3ca822 100644 --- a/src/meshTools/Make/files +++ b/src/meshTools/Make/files @@ -298,6 +298,8 @@ $(ACMICycPatches)/cyclicACMIPointPatchField/cyclicACMIPointPatchFields.C PeriodicAMICycPatches=$(AMI)/patches/cyclicPeriodicAMI $(PeriodicAMICycPatches)/cyclicPeriodicAMIPolyPatch/cyclicPeriodicAMIPolyPatch.C +multiWorld/multiWorldConnectionsObject.C + mappedPatches/mappedPolyPatch/mappedPatchBase.C mappedPatches/mappedPolyPatch/mappedPolyPatch.C mappedPatches/mappedPolyPatch/mappedWallPolyPatch.C diff --git a/src/meshTools/multiWorld/multiWorldConnectionsObject.C b/src/meshTools/multiWorld/multiWorldConnectionsObject.C new file mode 100644 index 0000000000000000000000000000000000000000..f8d2c438ed99c225117d6a6e5d7a5437e1003483 --- /dev/null +++ b/src/meshTools/multiWorld/multiWorldConnectionsObject.C @@ -0,0 +1,462 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2021 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/>. + +\*---------------------------------------------------------------------------*/ + +#include "multiWorldConnectionsObject.H" +#include "Pstream.H" + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +namespace Foam +{ + defineTypeNameAndDebug(multiWorldConnections, 0); +} + + +// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // + +namespace Foam +{ + +// Combine world-to-world connections. +// Forward connection = 1, Backward connection = 2, Both = 3 +struct worldConnectBitOrEq +{ + void operator()(EdgeMap<unsigned>& a, const EdgeMap<unsigned>& b) const + { + forAllConstIters(b, iter) + { + a(iter.key()) |= iter.val(); + } + } +}; + + +static void printDOT(Ostream& os, const EdgeMap<unsigned>& connections) +{ + os << nl << "// Multiworld communication graph:" << nl; + os.beginBlock("graph"); + + // Graph Nodes == worlds + label worldi = 0; + for (const word& worldName : UPstream::allWorlds()) + { + os.indent(); + os << worldi << " [xlabel=" << worldi + << ",label=\"" << worldName << "\"]" << nl; + + ++worldi; + } + os << nl; + + // Graph Edges == connections + for (const edge& connect : connections.sortedToc()) + { + os.indent(); + os << connect.first() << " -- " << connect.second(); + + // Mismatched forward/backward connections? + if (connections.lookup(connect, 0u) != 3u) + { + os << " [style=dashed] // mismatched?"; + } + os << nl; + } + + os.endBlock(); + + os << "// end graph" << nl; +} + +} // End namespace Foam + + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +Foam::edge Foam::multiWorldConnections::worldPair(const label otherWorld) +{ + if (otherWorld < 0 || !Pstream::parRun()) + { + Perr<< "ignore: no world or non-parallel" << endl; + return edge(-1, -1); + } + else if (UPstream::allWorlds().size() <= otherWorld) + { + Perr<< "ignore: invalid world: " << otherWorld << endl; + return edge(-1, -1); + } + + const label thisWorldID = UPstream::myWorldID(); + + // The worlds (sorted) + return edge(thisWorldID, otherWorld, true); +} + + +Foam::edge Foam::multiWorldConnections::worldPair(const word& otherWorld) +{ + if (otherWorld.empty() || !Pstream::parRun()) + { + Perr<< "ignore: no world or non-parallel" << endl; + return edge(-1, -1); + } + + const label thisWorldID = UPstream::myWorldID(); + const label otherWorldID = UPstream::allWorlds().find(otherWorld); + + if (otherWorldID < 0) + { + FatalErrorInFunction + << "Cannot find world " << otherWorld + << " in set of worlds " << flatOutput(UPstream::allWorlds()) + << exit(FatalError); + } + + // The worlds (sorted) + return edge(thisWorldID, otherWorldID, true); +} + + +Foam::label Foam::multiWorldConnections::createCommunicator(const edge& worlds) +{ + // Fallback: do not create, just use local world + label comm = UPstream::worldComm; + + if (!worlds.valid()) + { + return comm; + } + + const labelList& worldIDs = UPstream::worldIDs(); + + DynamicList<label> subRanks(worldIDs.size()); + forAll(worldIDs, proci) + { + if (worlds.found(worldIDs[proci])) + { + subRanks.append(proci); + } + } + + // Allocate new communicator with parent 0 (= world) + comm = UPstream::allocateCommunicator(0, subRanks, true); + + if (debug & 2) + { + Pout<< "multiWorld::communicator :" + << " between " << UPstream::allWorlds()[worlds.first()] + << " and " << UPstream::allWorlds()[worlds.second()] + << " sub-ranks: " << subRanks + << " comm:" << comm << endl; + } + + return comm; +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::multiWorldConnections::multiWorldConnections(const Time& runTime) +: + MeshObjectType(runTime) +{} + + +// * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * * // + +const Foam::multiWorldConnections& +Foam::multiWorldConnections::New(const Time& runTime) +{ + return MeshObjectType::New(runTime); +} + + +// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // + +Foam::multiWorldConnections::~multiWorldConnections() +{} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +bool Foam::multiWorldConnections::empty() const noexcept +{ + return table_.empty(); +} + + +Foam::label Foam::multiWorldConnections::size() const noexcept +{ + return table_.size(); +} + + +void Foam::multiWorldConnections::createComms() +{ + // Need new communicator(s) + + const label thisWorldID = UPstream::myWorldID(); + + EdgeMap<unsigned> allConnections; + forAllConstIters(table_, iter) + { + const edge& connect = iter.key(); + + allConnections.insert + ( + connect, + (connect.first() == thisWorldID ? 1u : 2u) + ); + } + + + // Use MPI_COMM_WORLD + const label oldWorldComm(Pstream::worldComm); + const label oldWarnComm(Pstream::warnComm); + Pstream::worldComm = 0; + Pstream::warnComm = Pstream::worldComm; + + if (Pstream::parRun()) + { + Pstream::combineGather + ( + allConnections, + worldConnectBitOrEq() + ); + Pstream::scatter(allConnections); + } + + // Check for mismatched connections + label brokenConnections = 0; + + forAllConstIters(allConnections, iter) + { + // Mismatched forward/backward connections? + if (iter.val() != 3u) + { + ++brokenConnections; + } + } + + if (brokenConnections) + { + Pstream::warnComm = oldWarnComm; + Pstream::worldComm = oldWorldComm; + + FatalErrorInFunction + << "Has " << brokenConnections + << " broken world-world connections"; + + printDOT(FatalError, allConnections); + + FatalError << exit(FatalError); + } + else + { + // NOTE: process in sorted order to ensure proper + // synchronization on all worlds and all processors + + for (const edge& connect : allConnections.sortedToc()) + { + // Process known connections without communicators. + // - create a communicator and cache its value + + auto iter = table_.find(connect); + if (iter.found() && iter.val() == -1) + { + iter.val() = createCommunicator(connect); + } + } + + Pstream::warnComm = oldWarnComm; + Pstream::worldComm = oldWorldComm; + } + + if (debug) + { + printDOT(Info, allConnections); + } +} + + +bool Foam::multiWorldConnections::addConnectionById(const label otherWorld) +{ + // The worlds (sorted) + edge worlds(worldPair(otherWorld)); + + if (!worlds.valid()) + { + return false; + } + + const bool added = table_.insert(worlds, -1); + + Pout<< (added ? "Add" : "Existing") << " connection from " + << UPstream::myWorld() << " to " << otherWorld << nl; + + return added; +} + + +bool Foam::multiWorldConnections::addConnectionByName(const word& otherWorld) +{ + // The worlds (sorted) + edge worlds(worldPair(otherWorld)); + + if (!worlds.valid()) + { + return false; + } + + const bool added = table_.insert(worlds, -1); + + Pout<< (added ? "Add" : "Existing") << " connection from " + << UPstream::myWorld() << " to " << otherWorld << nl; + + return added; +} + + +Foam::label Foam::multiWorldConnections::getCommById +( + const label otherWorldID +) const +{ + // Default: use local world + label comm = UPstream::worldComm; + + // The communication worlds (sorted) + edge worlds(worldPair(otherWorldID)); + + if (!worlds.valid()) + { + return comm; + } + + const auto iter = table_.cfind(worlds); + + if (!iter.found()) + { + FatalErrorInFunction + << "No connection registered for worlds " << worlds + << exit(FatalError); + } + + // Get cached value, or allocate ALL known communicators + comm = iter.val(); + + if (comm == -1) + { + // Need new communicator(s) + const_cast<multiWorldConnections&>(*this).createComms(); + + // Retrieve from table cache + comm = table_.lookup(worlds, UPstream::worldComm); + } + + return comm; +} + + +Foam::label Foam::multiWorldConnections::getCommByName +( + const word& otherWorld +) const +{ + // Default: use local world + label comm = UPstream::worldComm; + + // The communication worlds (sorted) + edge worlds(worldPair(otherWorld)); + + if (!worlds.valid()) + { + return comm; + } + + const auto iter = table_.cfind(worlds); + + if (!iter.found()) + { + FatalErrorInFunction + << "No connection registered for worlds " << worlds + << exit(FatalError); + } + + // Get cached value, or allocate ALL known communicators + comm = iter.val(); + + if (comm == -1) + { + // Need new communicator(s) + const_cast<multiWorldConnections&>(*this).createComms(); + + // Retrieve from table cache + comm = table_.lookup(worlds, UPstream::worldComm); + } + + return comm; +} + + +Foam::labelList Foam::multiWorldConnections::comms() const +{ + labelList list(table_.size()); + + if (list.empty()) + { + // Default: use local world + list.resize(1, UPstream::worldComm); + } + else + { + forAllConstIters(table_, iter) + { + if (iter.val() == -1) + { + // Need new communicator(s) + const_cast<multiWorldConnections&>(*this).createComms(); + break; + } + } + + // Retrieve values from table cache + label i = 0; + + forAllConstIters(table_, iter) + { + list[i] = iter.val(); + ++i; + } + + Foam::sort(list); // Consistent order! + } + + return list; +} + + +// ************************************************************************* // diff --git a/src/meshTools/multiWorld/multiWorldConnectionsObject.H b/src/meshTools/multiWorld/multiWorldConnectionsObject.H new file mode 100644 index 0000000000000000000000000000000000000000..76cf3f505a9962782cc3b7539631e80a8aa89f3f --- /dev/null +++ b/src/meshTools/multiWorld/multiWorldConnectionsObject.H @@ -0,0 +1,157 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2021 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/>. + +Class + Foam::multiWorldConnections + +Description + Centralized handling of multi-world MPI connections. + +Note + This class may move to static only or a singleton in the future. + +SourceFiles + multiWorldConnectionsObject.C + +\*---------------------------------------------------------------------------*/ + +#ifndef meshObjects_multiWorldConnections_H +#define meshObjects_multiWorldConnections_H + +#include "MeshObject.H" +#include "EdgeMap.H" +#include "Time.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +/*---------------------------------------------------------------------------*\ + Class multiWorldConnections Declaration +\*---------------------------------------------------------------------------*/ + +class multiWorldConnections +: + public MeshObject + < + Time, + TopologicalMeshObject, + multiWorldConnections + > +{ + // Private Typedefs + + typedef MeshObject + < + Time, + TopologicalMeshObject, + multiWorldConnections + > MeshObjectType; + + + // Private Data + + //- Table of world/world connections to communicator label + EdgeMap<label> table_; + + + // Private Member Functions + + //- Pairing from myWorld to other world by ID + // \return invalid on any error or if non-parallel + static edge worldPair(const label otherWorld); + + //- Pairing from myWorld to other world by NAME. + // \return invalid on any error or if non-parallel + static edge worldPair(const word& otherWorld); + + //- Allocate a communicator between myWorld and other world + static label createCommunicator(const edge& worlds); + + +public: + + //- Run-time type information + TypeName("multiWorld"); + + + // Constructors + + //- Construct + explicit multiWorldConnections(const Time& runTime); + + + // Selectors + + //- Access mesh object + static const multiWorldConnections& New(const Time& runTime); + + + //- Destructor + ~multiWorldConnections(); + + + // Member Functions + + //- True if no world-to-world connections are defined + bool empty() const noexcept; + + //- Number of world-to-world connections defined. + label size() const noexcept; + + //- Create all communicators. + //- Low-level, not normally called directly + void createComms(); + + //- Define a connection from myWorld to other world by ID + bool addConnectionById(const label otherWorld); + + //- Define a connection from myWorld to other world by NAME + bool addConnectionByName(const word& otherWorld); + + //- Get communicator for myWorld to other world connection by ID. + // Uses cached value, or creates a new communicator + label getCommById(const label otherWorld) const; + + //- Get communicator for myWorld to other world connection by NAME. + // Uses cached value, or creates a new communicator + label getCommByName(const word& otherWorld) const; + + //- Get communicators used for myWorld to other worlds in sorted order. + // \return worldComm if no world-to-world communicators in use. + labelList comms() const; +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* //