UPstreamCommsStruct.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) 2011-2016 OpenFOAM Foundation
9  Copyright (C) 2021-2025 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "UPstream.H"
30 
31 #include <algorithm>
32 #include <numeric>
33 
34 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
35 
36 namespace Foam
37 {
38 
39 // This outputs as depth-first, but graphviz sorts that for us
40 static void printGraph_impl
41 (
42  Ostream& os,
43  const UPstream::commsStructList& comms,
44  const int proci,
45  int depth,
46  const int maxDepth = 1024
47 )
48 {
49  if (proci >= comms.size())
50  {
51  // Corner case when only a single rank involved
52  // (eg, for node-local communicator)
53  return;
54  }
55 
56  const auto& below = comms[proci].below();
57 
58  if (proci == 0)
59  {
60  os << nl << "// communication graph:" << nl;
61  os.beginBlock("graph");
62 
63  // Prefer left-to-right layout for large graphs
64  os << indent << "rankdir=LR" << nl;
65  }
66 
67 
68  // Output the immediate neighbours below
69 
70  if (below.empty())
71  {
72  if (proci == 0)
73  {
74  // A graph with a single-node (eg, self-comm)
75  os << indent << proci << nl;
76  }
77  }
78  else
79  {
80  os << indent << proci << " -- " << token::BEGIN_BLOCK;
81 
82  // Accumulate into ranges whenever possible
84 
85  // Print accumulated range and reset
86  auto emit_range = [&]()
87  {
88  if (!range.empty())
89  {
90  os << ' ';
91  if (range.min() < range.max())
92  {
93  os << '"' << range.min() << ".." << range.max() << '"';
94  }
95  else
96  {
97  os << range.min();
98  }
99  range.reset();
100  }
101  };
102 
103  for (const auto nbrProci : below)
104  {
105  const bool terminal = comms[nbrProci].below().empty();
106 
107  if
108  (
109  terminal
110  && (!range.empty() && (range.max()+1 == nbrProci))
111  )
112  {
113  // Accumulate
114  ++range;
115  continue;
116  }
117 
118  // Emit accumulated range
119  emit_range();
120 
121  if (terminal)
122  {
123  range.reset(nbrProci, 1);
124  }
125  else
126  {
127  os << token::SPACE << nbrProci;
128  }
129  }
130 
131  // Emit accumulated range
132  emit_range();
133 
135  }
136 
137 
138  // Recurse into below neighbours, but limit the maximum depth
139  ++depth;
140  if (depth >= maxDepth && (proci != 0))
141  {
142  return;
143  }
144 
145  for (const auto nbrProci : below)
146  {
147  // if (proci == nbrProci) continue; // Extreme safety!
148  printGraph_impl(os, comms, nbrProci, depth, maxDepth);
149  }
150 
151  if (proci == 0)
152  {
153  os.endBlock();
154  os << "// end graph" << nl;
155  }
156 }
157 
158 } // End namespace Foam
159 
160 
161 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
162 
163 // Create a tree-like schedule. For 8 procs:
164 // (level 0)
165 // 0 receives from 1
166 // 2 receives from 3
167 // 4 receives from 5
168 // 6 receives from 7
169 // (level 1)
170 // 0 receives from 2
171 // 4 receives from 6
172 // (level 2)
173 // 0 receives from 4
174 //
175 // The sends/receives for all levels are collected per processor
176 // (one send per processor; multiple receives possible) creating
177 // a table:
178 //
179 // So per processor:
180 // proc receives from sends to
181 // ---- ------------- --------
182 // 0 1,2,4 -
183 // 1 - 0
184 // 2 3 0
185 // 3 - 2
186 // 4 5 0
187 // 5 - 4
188 // 6 7 4
189 // 7 - 6
190 
191 namespace Foam
192 {
193 
194 static int simpleTree
195 (
196  const int myProci,
197  const int numProcs,
198 
199  DynamicList<int>& below,
200  DynamicList<int>& allBelow
201 )
202 {
203  int above(-1);
204 
205  for (int mod = 2, step = 1; step < numProcs; step = mod)
206  {
207  mod = step * 2;
208 
209  if (myProci % mod)
210  {
211  // The rank above
212  above = myProci - (myProci % mod);
213  break;
214  }
215  else
216  {
217  for
218  (
219  int i = myProci + step;
220  i < numProcs && i < myProci + mod;
221  i += step
222  )
223  {
224  below.push_back(i);
225  }
226  for
227  (
228  int i = myProci + step;
229  i < numProcs && i < myProci + mod;
230  ++i
231  )
232  {
233  allBelow.push_back(i);
234  }
235  }
236  }
237 
238  return above;
239 }
241 } // End namespace Foam
242 
243 
244 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
245 
247 (
248  const int above,
249  List<int>&& below,
250  List<int>&& allBelow,
251  List<int>&& allNotBelow
252 )
253 :
254  above_(above),
255  below_(std::move(below)),
256  allBelow_(std::move(allBelow)),
257  allNotBelow_(std::move(allNotBelow))
258 {}
259 
260 
262 (
263  const int numProcs,
264  const int myProcID,
265  const int above,
266  const UList<int>& below,
267  const UList<int>& allBelow
268 )
269 :
270  above_(above),
271  below_(below),
272  allBelow_(allBelow),
273  allNotBelow_(numProcs - allBelow.size() - 1)
274 {
275  List<bool> isNotBelow(numProcs, true);
276 
277  // Exclude self
278  isNotBelow[myProcID] = false;
279 
280  // Exclude allBelow
281  for (const auto proci : allBelow)
282  {
283  isNotBelow[proci] = false;
284  }
285 
286  // Compacting to obtain allNotBelow_
287  int nNotBelow = 0;
288  for (int proci = 0; proci < numProcs; ++proci)
289  {
290  if (isNotBelow[proci])
291  {
292  allNotBelow_[nNotBelow++] = proci;
293  }
294  }
295 
296  if (nNotBelow != allNotBelow_.size())
297  {
299  << "Problem: " << nNotBelow << " != " << allNotBelow_.size() << nl
301  }
302 }
303 
304 
305 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
306 
308 (
309  Ostream& os,
310  const int proci
311 ) const
312 {
313  // Print graph - starting at depth 0
314  // Avoid corner case when only a single rank involved
315  // (eg, for node-local communicator)
316 
317  if (proci < size())
318  {
319  printGraph_impl(os, *this, proci, 0);
320  }
321 }
322 
323 
324 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
327 {
328  return (1 + int(allBelow_.size() + allNotBelow_.size()));
329 }
330 
331 
333 {
334  above_ = -1;
335  below_.clear();
336  allBelow_.clear();
337  allNotBelow_.clear();
338 }
339 
340 
342 (
343  const int myProci,
344  const int numProcs
345 )
346 {
347  reset();
348 
349  // Linear (flat) communication pattern
350  int above(-1);
351  List<int> below;
352 
353  if (myProci == 0)
354  {
355  below.resize(numProcs-1);
356  std::iota(below.begin(), below.end(), 1);
357  }
358  else
359  {
360  above = 0;
361  }
362 
363  *this = UPstream::commsStruct(numProcs, myProci, above, below, below);
364 }
365 
366 
368 (
369  const int myProci,
370  const int numProcs,
371  const int communicator
372 )
373 {
374  // Trivially small domains
375  if (numProcs <= 2)
376  {
377  reset_linear(myProci, numProcs);
378  return;
379  }
380 
381 
382  reset();
383 
384  int above(-1);
385  DynamicList<int> below;
386  DynamicList<int> allBelow;
387 
388  if (UPstream::usingNodeComms(communicator))
389  {
390  // Additional treatment...
391  }
392 
393 
394  // Simple tree communication pattern
395  above = simpleTree
396  (
397  myProci,
398  numProcs,
399  below,
400  allBelow
401  );
402 
403  *this = UPstream::commsStruct(numProcs, myProci, above, below, allBelow);
404 }
405 
406 
407 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
408 
411 {
412  static std::unique_ptr<commsStructList> singleton;
413 
414  if (!singleton)
415  {
416  singleton = std::make_unique<commsStructList>();
417  }
419  return *singleton;
420 }
421 
422 
423 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
424 
426 {
427  if (flat_ != on)
428  {
429  // Current size
430  const auto len = tree_.size();
431 
432  flat_ = on;
433  tree_.clear();
434  if (len > 0)
435  {
436  tree_.resize(len);
437  }
438  }
439 }
440 
441 
443 {
444  comm_ = communicator;
445  tree_.clear();
446 }
447 
448 
450 {
451  comm_ = communicator;
452  tree_.clear();
453  flat_ = flat;
454 }
455 
456 
458 {
459  comm_ = communicator;
460  tree_.clear();
461  if (comm_ >= 0)
462  {
463  tree_.resize(UPstream::nProcs(comm_));
464  }
465 }
466 
467 
468 void Foam::UPstream::commsStructList::init(int communicator, bool flat)
469 {
471  flat_ = flat;
472 }
473 
474 
477 {
478  const auto numProcs = UPstream::nProcs(comm_);
479 
480  // Only if reset(comm) instead of init(comm) was used
481  if (tree_.size() < numProcs)
482  {
483  const_cast<List<commsStruct>&>(tree_).resize(numProcs);
484  }
485 
486  const UPstream::commsStruct& entry = tree_[proci];
487 
488  if (entry.nProcs() != numProcs)
489  {
490  // Create/update
491 
492  if (flat_)
493  {
494  const_cast<UPstream::commsStruct&>(entry)
495  .reset_linear(proci, numProcs);
496  }
497  else
498  {
499  const_cast<UPstream::commsStruct&>(entry)
500  .reset(proci, numProcs, comm_);
501  }
502  }
504  return entry;
505 }
506 
507 
508 // * * * * * * * * * * * * * * * Member Operators * * * * * * * * * * * * * //
509 
511 {
512  return
513  (
514  (above() == comm.above())
515  && (below() == comm.below())
516  );
517 }
518 
519 
521 {
522  return !operator==(comm);
523 }
524 
525 
526 // * * * * * * * * * * * * * * * Ostream Operator * * * * * * * * * * * * * //
527 
528 Foam::Ostream& Foam::operator<<(Ostream& os, const UPstream::commsStruct& comm)
529 {
530  os << comm.above() << nl;
531  os << " "; comm.below().writeList(os) << nl;
532  os << " "; comm.allBelow().writeList(os) << nl;
533  os << " "; comm.allNotBelow().writeList(os);
534 
535  os.check(FUNCTION_NAME);
536  return os;
537 }
538 
539 
540 // ************************************************************************* //
Begin block [isseparator].
Definition: token.H:165
commsStruct() noexcept
Default construct with above == -1.
Definition: UPstream.H:254
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
int above() const noexcept
The procID of the processor directly above.
Definition: UPstream.H:287
void printGraph(Ostream &os, int proci=0) const
Print un-directed graph in graphviz dot format.
Ostream & indent(Ostream &os)
Indent stream.
Definition: Ostream.H:491
static bool usingNodeComms(const int communicator)
True if node topology-aware routines have been enabled, it is running in parallel, the starting point is the world-communicator and it is not an odd corner case (ie, all processes on one node, all processes on different nodes)
Definition: UPstream.C:751
patchWriters resize(patchIds.size())
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
const List< int > & allBelow() const noexcept
The procIDs of all processors below (so not just directly below)
Definition: UPstream.H:298
label size() const noexcept
The number of entries.
Definition: UPstream.H:440
An interval of (signed) integers defined by a start and a size.
Definition: IntRange.H:59
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
bool empty() const noexcept
True if List is empty (ie, size() is zero)
Definition: UList.H:697
static int simpleTree(const int myProci, const int numProcs, DynamicList< int > &below, DynamicList< int > &allBelow)
void init(int communicator)
Reset communicator index, fill tree with empty entries.
void reset()
Reset to default constructed state.
void reset(int communicator)
Reset communicator index, clear tree entries.
scalar range
static label nProcs(const label communicator=worldComm)
Number of ranks in parallel run (for given communicator). It is 1 for serial run. ...
Definition: UPstream.H:1602
static bool init(int &argc, char **&argv, const bool needsThread)
Initialisation function called from main.
Definition: UPstream.C:40
virtual Ostream & endBlock()
Write end block group.
Definition: Ostream.C:108
Space [isspace].
Definition: token.H:131
bool linear() const noexcept
Linear (flat) communication instead of tree communication.
Definition: UPstream.H:419
static const commsStructList & null()
An empty structure. Used for placeholders etc.
errorManip< error > abort(error &err)
Definition: errorManip.H:139
Structure for communicating between processors.
Definition: UPstream.H:220
iterator begin() noexcept
Return an iterator to begin traversing the UList.
Definition: UListI.H:385
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: HashTable.H:105
An Ostream is an abstract base class for all output systems (streams, files, token lists...
Definition: Ostream.H:56
const direction noexcept
Definition: scalarImpl.H:255
bool operator==(const commsStruct &) const
const List< int > & below() const noexcept
The procIDs of the processors directly below.
Definition: UPstream.H:292
OBJstream os(runTime.globalPath()/outputName)
Collection of communication structures.
Definition: UPstream.H:354
#define FUNCTION_NAME
bool operator!=(const commsStruct &) const
void push_back(const T &val)
Copy append an element to the end of this list.
Definition: DynamicListI.H:558
void reset_linear(const int myProci, const int numProcs)
Reset to linear (flat) communication.
Wrapper for internally indexed communicator label. Always invokes UPstream::allocateCommunicatorCompo...
Definition: UPstream.H:2237
Ostream & operator<<(Ostream &, const boundaryPatch &p)
Write boundaryPatch as dictionary entries (without surrounding braces)
Definition: boundaryPatch.C:77
limits reset(1/(limits.max()+VSMALL), 1/(limits.min()+VSMALL))
static void printGraph_impl(Ostream &os, const UPstream::commsStructList &comms, const int proci, int depth, const int maxDepth=1024)
virtual Ostream & beginBlock(const keyType &kw)
Write begin block group with the given name.
Definition: Ostream.C:90
iterator end() noexcept
Return an iterator to end traversing the UList.
Definition: UListI.H:429
int nProcs() const noexcept
The number of processors addressed by the structure.
bool empty() const noexcept
True if the list is empty.
Definition: UPstream.H:435
tmp< faMatrix< Type > > operator==(const faMatrix< Type > &, const faMatrix< Type > &)
End block [isseparator].
Definition: token.H:166
const UPstream::commsStruct & get(int proci) const
Get existing or create (demand-driven) entry.
Namespace for OpenFOAM.
A keyword and a list of tokens is an &#39;entry&#39;.
Definition: entry.H:63