multiWorldConnectionsObject.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2021-2025 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
29 #include "Pstream.H"
30 
31 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
32 
33 namespace Foam
34 {
35  defineTypeNameAndDebug(multiWorldConnections, 0);
36 }
37 
38 
39 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
40 
41 namespace Foam
42 {
43 
44 // Combine world-to-world connections.
45 // Forward connection = 1, Backward connection = 2, Both = 3
47 {
48  void operator()(EdgeMap<unsigned>& a, const EdgeMap<unsigned>& b) const
49  {
50  forAllConstIters(b, iter)
51  {
52  a(iter.key()) |= iter.val();
53  }
54  }
55 };
56 
57 
58 static void printDOT(Ostream& os, const EdgeMap<unsigned>& connections)
59 {
60  os << nl << "// Multiworld communication graph:" << nl;
61  os.beginBlock("graph");
62 
63  // Graph Nodes == worlds
64  label worldi = 0;
65  for (const word& worldName : UPstream::allWorlds())
66  {
67  os.indent();
68  os << worldi << " [xlabel=" << worldi
69  << ",label=\"" << worldName << "\"]" << nl;
70 
71  ++worldi;
72  }
73  os << nl;
74 
75  // Graph Edges == connections
76  for (const edge& connect : connections.sortedToc())
77  {
78  os.indent();
79  os << connect.first() << " -- " << connect.second();
80 
81  // Mismatched forward/backward connections?
82  if (connections.lookup(connect, 0u) != 3u)
83  {
84  os << " [style=dashed] // mismatched?";
85  }
86  os << nl;
87  }
88 
89  os.endBlock();
90 
91  os << "// end graph" << nl;
92 }
93 
94 } // End namespace Foam
95 
96 
97 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
98 
99 Foam::edge Foam::multiWorldConnections::worldPair(const label otherWorld)
100 {
101  if (otherWorld < 0 || !Pstream::parRun())
102  {
103  Perr<< "ignore: no world or non-parallel" << endl;
104  return edge(-1, -1);
105  }
106  else if (UPstream::allWorlds().size() <= otherWorld)
107  {
108  Perr<< "ignore: invalid world: " << otherWorld << endl;
109  return edge(-1, -1);
110  }
111 
112  const label thisWorldID = UPstream::myWorldID();
113 
114  // The worlds (sorted)
115  return edge::sorted(thisWorldID, otherWorld);
116 }
117 
118 
119 Foam::edge Foam::multiWorldConnections::worldPair(const word& otherWorld)
120 {
121  if (otherWorld.empty() || !Pstream::parRun())
122  {
123  Perr<< "ignore: no world or non-parallel" << endl;
124  return edge(-1, -1);
125  }
126 
127  const label thisWorldID = UPstream::myWorldID();
128  const label otherWorldID = UPstream::allWorlds().find(otherWorld);
129 
130  if (otherWorldID < 0)
131  {
133  << "Cannot find world " << otherWorld
134  << " in set of worlds " << flatOutput(UPstream::allWorlds())
135  << exit(FatalError);
136  }
137 
138  // The worlds (sorted)
139  return edge::sorted(thisWorldID, otherWorldID);
140 }
141 
142 
143 Foam::label Foam::multiWorldConnections::createCommunicator(const edge& worlds)
144 {
145  // Fallback: do not create, just use local world
146  label comm = UPstream::worldComm;
147 
148  if (!worlds.good())
149  {
150  return comm;
151  }
152 
153  const labelList& worldIDs = UPstream::worldIDs();
154 
155  DynamicList<label> subRanks(worldIDs.size());
156  forAll(worldIDs, proci)
157  {
158  if (worlds.found(worldIDs[proci]))
159  {
160  subRanks.push_back(proci);
161  }
162  }
163 
164  // Allocate new communicator with global world
166 
167  if (debug & 2)
168  {
169  Pout<< "multiWorld::communicator :"
170  << " between " << UPstream::allWorlds()[worlds.first()]
171  << " and " << UPstream::allWorlds()[worlds.second()]
172  << " sub-ranks: " << subRanks
173  << " comm:" << comm << endl;
174  }
176  return comm;
177 }
178 
179 
180 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
181 
183 :
185 {}
186 
187 
188 // * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * * //
189 
192 {
194 }
195 
196 
197 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
200 {
201  return table_.empty();
202 }
203 
206 {
207  return table_.size();
208 }
209 
210 
212 {
213  // Need new communicator(s)
214 
215  const label thisWorldID = UPstream::myWorldID();
216 
217  EdgeMap<unsigned> allConnections;
218  forAllConstIters(table_, iter)
219  {
220  const edge& connect = iter.key();
221 
222  allConnections.insert
223  (
224  connect,
225  (connect.first() == thisWorldID ? 1u : 2u)
226  );
227  }
228 
229 
230  // Use MPI_COMM_WORLD
231  const label oldWarnComm = UPstream::commWarn(UPstream::commGlobal());
232  const label oldWorldComm = UPstream::commWorld(UPstream::commGlobal());
233 
234  Pstream::combineReduce(allConnections, worldConnectBitOrEq());
235 
236  // Check for mismatched connections
237  label brokenConnections = 0;
238 
239  forAllConstIters(allConnections, iter)
240  {
241  // Mismatched forward/backward connections?
242  if (iter.val() != 3u)
243  {
244  ++brokenConnections;
245  }
246  }
247 
248  if (brokenConnections)
249  {
250  // Restore communicator settings
251  UPstream::commWarn(oldWarnComm);
252  UPstream::commWorld(oldWorldComm);
253 
255  << "Has " << brokenConnections
256  << " broken world-world connections";
257 
258  printDOT(FatalError, allConnections);
259 
261  }
262  else
263  {
264  // NOTE: process in sorted order to ensure proper
265  // synchronization on all worlds and all processors
266 
267  for (const edge& connect : allConnections.sortedToc())
268  {
269  // Process known connections without communicators.
270  // - create a communicator and cache its value
271 
272  auto iter = table_.find(connect);
273  if (iter.good() && iter.val() == -1)
274  {
275  iter.val() = createCommunicator(connect);
276  }
277  }
278 
279  // Restore communicator settings
280  UPstream::commWarn(oldWarnComm);
281  UPstream::commWorld(oldWorldComm);
282  }
283 
284  if (debug)
285  {
286  printDOT(Info, allConnections);
287  }
288 }
289 
290 
291 bool Foam::multiWorldConnections::addConnectionById(const label otherWorld)
292 {
293  // The worlds (sorted)
294  edge worlds(worldPair(otherWorld));
295 
296  if (!worlds.good())
297  {
298  return false;
299  }
300 
301  const bool added = table_.insert(worlds, -1);
302 
303  Pout<< (added ? "Add" : "Existing") << " connection from "
304  << UPstream::myWorld() << " to " << otherWorld << nl;
305 
306  return added;
307 }
308 
309 
311 {
312  // The worlds (sorted)
313  edge worlds(worldPair(otherWorld));
314 
315  if (!worlds.good())
316  {
317  return false;
318  }
319 
320  const bool added = table_.insert(worlds, -1);
321 
322  Pout<< (added ? "Add" : "Existing") << " connection from "
323  << UPstream::myWorld() << " to " << otherWorld << nl;
324 
325  return added;
326 }
327 
328 
330 (
331  const label otherWorldID
332 ) const
333 {
334  // Default: use local world
335  label comm = UPstream::worldComm;
336 
337  // The communication worlds (sorted)
338  edge worlds(worldPair(otherWorldID));
339 
340  if (!worlds.good())
341  {
342  return comm;
343  }
344 
345  const auto iter = table_.cfind(worlds);
346 
347  if (!iter.good())
348  {
350  << "No connection registered for worlds " << worlds
351  << exit(FatalError);
352  }
353 
354  // Get cached value, or allocate ALL known communicators
355  comm = iter.val();
356 
357  if (comm == -1)
358  {
359  // Need new communicator(s)
360  const_cast<multiWorldConnections&>(*this).createComms();
361 
362  // Retrieve from table cache
363  comm = table_.lookup(worlds, UPstream::worldComm);
364  }
365 
366  return comm;
367 }
368 
369 
371 (
372  const word& otherWorld
373 ) const
374 {
375  // Default: use local world
376  label comm = UPstream::worldComm;
377 
378  // The communication worlds (sorted)
379  edge worlds(worldPair(otherWorld));
380 
381  if (!worlds.good())
382  {
383  return comm;
384  }
385 
386  const auto iter = table_.cfind(worlds);
387 
388  if (!iter.good())
389  {
391  << "No connection registered for worlds " << worlds
392  << exit(FatalError);
393  }
394 
395  // Get cached value, or allocate ALL known communicators
396  comm = iter.val();
397 
398  if (comm == -1)
399  {
400  // Need new communicator(s)
401  const_cast<multiWorldConnections&>(*this).createComms();
402 
403  // Retrieve from table cache
404  comm = table_.lookup(worlds, UPstream::worldComm);
405  }
406 
407  return comm;
408 }
409 
410 
412 {
413  labelList list(table_.size());
414 
415  if (list.empty())
416  {
417  // Default: use local world
418  list.resize(1, UPstream::worldComm);
419  }
420  else
421  {
422  forAllConstIters(table_, iter)
423  {
424  if (iter.val() == -1)
425  {
426  // Need new communicator(s)
427  const_cast<multiWorldConnections&>(*this).createComms();
428  break;
429  }
430  }
431 
432  // Retrieve values from table cache
433  label i = 0;
434 
435  forAllConstIters(table_, iter)
436  {
437  list[i] = iter.val();
438  ++i;
439  }
440 
441  Foam::sort(list); // Consistent order!
442  }
443 
444  return list;
445 }
446 
447 
448 // ************************************************************************* //
static const word & myWorld()
My world.
Definition: UPstream.H:1707
label getCommById(const label otherWorld) const
Get communicator for myWorld to other world connection by ID.
prefixOSstream Perr
OSstream wrapped stderr (std::cerr) with parallel prefix.
const T & first() const noexcept
Access the first element.
Definition: Pair.H:137
void operator()(EdgeMap< unsigned > &a, const EdgeMap< unsigned > &b) const
static const labelList & worldIDs() noexcept
The indices into allWorlds for all processes.
Definition: UPstream.H:1691
static void printDOT(Ostream &os, const EdgeMap< unsigned > &connections)
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
void resize(const label len)
Adjust allocated size of list.
Definition: ListI.H:150
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:600
Centralized handling of multi-world MPI connections.
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
static label newCommunicator(const label parent, const labelRange &subRanks, const bool withComponents=true)
Create new communicator with sub-ranks on the parent communicator.
Definition: UPstream.C:272
static const multiWorldConnections & New(const Time &runTime)
Access mesh object.
engineTime & runTime
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:529
static bool & parRun() noexcept
Test if this a parallel run.
Definition: UPstream.H:1586
tmp< DimensionedField< TypeR, GeoMesh > > New(const tmp< DimensionedField< TypeR, GeoMesh >> &tf1, const word &name, const dimensionSet &dimensions, const bool initCopy=false)
Global function forwards to reuseTmpDimensionedField::New.
labelList comms() const
Get communicators used for myWorld to other worlds in sorted order.
static constexpr int commGlobal() noexcept
Communicator for all ranks, irrespective of any local worlds.
Definition: UPstream.H:999
static label worldComm
Communicator for all ranks. May differ from commGlobal() if local worlds are in use.
Definition: UPstream.H:987
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:69
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:286
bool insert(const edge &key, const T &obj)
Copy insert a new entry, not overwriting existing entries.
Definition: HashTableI.H:152
bool empty() const noexcept
True if no world-to-world connections are defined.
static label commWarn(const label communicator) noexcept
Alter communicator debugging setting. Warns for use of any communicator differing from specified...
Definition: UPstream.H:1040
An edge is a list of two vertex labels. This can correspond to a directed graph edge or an edge on a ...
Definition: edge.H:59
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
A class for handling words, derived from Foam::string.
Definition: word.H:63
void sort(UList< T > &list)
Sort the list.
Definition: UList.C:295
virtual Ostream & endBlock()
Write end block group.
Definition: Ostream.C:108
multiWorldConnections(const Time &runTime)
Construct.
label find(const T &val) const
Find index of the first occurrence of the value.
Definition: UList.C:172
const direction noexcept
Definition: scalarImpl.H:255
bool good() const noexcept
True if the vertices are unique and non-negative.
Definition: edgeI.H:116
label getCommByName(const word &otherWorld) const
Get communicator for myWorld to other world connection by NAME.
int debug
Static debugging option.
Map from edge (expressed as its endpoints) to value. Hashing (and ==) on an edge is symmetric...
Definition: edgeHashes.H:56
OBJstream os(runTime.globalPath()/outputName)
defineTypeNameAndDebug(combustionModel, 0)
static label commWorld() noexcept
Communicator for all ranks (respecting any local worlds)
Definition: UPstream.H:1019
static void combineReduce(T &value, CombineOp cop, const int tag=UPstream::msgType(), const int communicator=UPstream::worldComm)
Reduce inplace (cf. MPI Allreduce) applying cop to inplace combine value from different processors...
label size() const noexcept
Number of world-to-world connections defined.
bool addConnectionByName(const word &otherWorld)
Define a connection from myWorld to other world by NAME.
virtual void indent() override
Add indentation characters.
Definition: OSstream.C:278
List< edge > sortedToc() const
The table of contents (the keys) in sorted order.
Definition: HashTable.C:156
messageStream Info
Information stream (stdout output on master, null elsewhere)
virtual Ostream & beginBlock(const keyType &kw)
Write begin block group with the given name.
Definition: Ostream.C:90
void createComms()
Create all communicators. Low-level, not normally called directly.
static edge sorted(label from, label to)
Create (in ascending order) from two vertex labels.
Definition: edgeI.H:32
List< label > labelList
A List of labels.
Definition: List.H:61
static const wordList & allWorlds() noexcept
All worlds.
Definition: UPstream.H:1683
bool addConnectionById(const label otherWorld)
Define a connection from myWorld to other world by ID.
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
Namespace for OpenFOAM.
forAllConstIters(mixture.phases(), phase)
Definition: pEqn.H:28
static label myWorldID()
My worldID.
Definition: UPstream.H:1699
FlatOutput::OutputAdaptor< Container, Delimiters > flatOutput(const Container &obj, Delimiters delim)
Global flatOutput() function with specified output delimiters.
Definition: FlatOutput.H:225