scotchDecomp.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-2017 OpenFOAM Foundation
9  Copyright (C) 2015-2023 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 "scotchDecomp.H"
31 #include "floatScalar.H"
32 #include "Time.H"
34 #include "OFstream.H"
35 #include <cstdio>
36 #include <limits>
37 
38 // Probably not needed, but in case we pickup a ptscotch.h ...
39 #ifndef MPICH_SKIP_MPICXX
40 #define MPICH_SKIP_MPICXX
41 #endif
42 #ifndef OMPI_SKIP_MPICXX
43 #define OMPI_SKIP_MPICXX
44 #endif
45 
46 #include "scotch.h"
47 
48 // Hack: scotch generates floating point errors so need to switch off error
49 // trapping!
50 #ifdef __GLIBC__
51  #ifndef _GNU_SOURCE
52  #define _GNU_SOURCE
53  #endif
54  #include <fenv.h>
55 #endif
56 
57 // Error if we attempt narrowing
58 static_assert
59 (
60  sizeof(Foam::label) <= sizeof(SCOTCH_Num),
61  "SCOTCH_Num is too small for Foam::label, check your scotch headers"
62 );
63 
64 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
65 
66 namespace Foam
67 {
68  defineTypeNameAndDebug(scotchDecomp, 0);
70  (
71  decompositionMethod,
72  scotchDecomp,
73  dictionary
74  );
75 }
76 
77 
78 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
79 
80 namespace Foam
81 {
82 
83 // Check and print error message
84 static inline void check(const int retVal, const char* what)
85 {
86  if (retVal)
87  {
89  << "Call to scotch routine " << what
90  << " failed (" << retVal << ")\n"
91  << exit(FatalError);
92  }
93 }
94 
95 
96 // The mesh-relative graph path/name (without extension)
97 static inline Foam::fileName getGraphPathBase(const polyMesh& mesh)
98 {
99  return mesh.time().path()/mesh.name();
100 }
101 
103 } // End namespace Foam
104 
105 
106 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
107 
109 (
110  const labelList& adjncy,
111  const labelList& xadj,
112  const List<scalar>& cWeights,
113  labelList& decomp
114 ) const
115 {
116  const SCOTCH_Num numCells = max(0, (xadj.size()-1));
117 
118  // Addressing
121 
122  // Output: cell -> processor addressing
123  decomp.resize_nocopy(numCells);
124  decomp = 0;
125  PrecisionAdaptor<SCOTCH_Num, label, List> decomp_param(decomp, false);
126 
127  // Avoid potential nullptr issues with zero-sized arrays
128  labelList adjncy_dummy, xadj_dummy, decomp_dummy;
129  if (!numCells)
130  {
131  adjncy_dummy.resize(1, 0);
132  adjncy_param.set(adjncy_dummy);
133 
134  xadj_dummy.resize(2, 0);
135  xadj_param.set(xadj_dummy);
136 
137  decomp_dummy.resize(1, 0);
138  decomp_param.clear(); // Avoid propagating spurious values
139  decomp_param.set(decomp_dummy);
140  }
141 
142 
143  // Dump graph
144  if (coeffsDict_.getOrDefault("writeGraph", false))
145  {
146  OFstream str(graphPath_ + ".grf");
147 
148  Info<< "Dumping Scotch graph file to " << str.name() << nl
149  << "Use this in combination with gpart." << endl;
150 
151  const label numConnect = adjncy.size();
152 
153  // Version 0 = Graph file (.grf)
154  str << "0" << nl;
155 
156  // Number of vertices,
157  // number of edges (connections)
158  str << numCells << ' ' << numConnect << nl;
159 
160  // Numbering starts from 0
161  // 100*hasVertlabels+10*hasEdgeWeights+1*hasVertWeights
162  str << "0 000" << nl;
163 
164  for (label celli = 0; celli < numCells; ++celli)
165  {
166  const label beg = xadj[celli];
167  const label end = xadj[celli+1];
168 
169  str << (end-beg); // size
170 
171  for (label i = beg; i < end; ++i)
172  {
173  str << ' ' << adjncy[i];
174  }
175  str << nl;
176  }
177  }
178 
179 
180  // Make repeatable
181  SCOTCH_randomReset();
182 
183  // Strategy
184  // ~~~~~~~~
185 
186  // Default.
187  SCOTCH_Strat stradat;
188  check
189  (
190  SCOTCH_stratInit(&stradat),
191  "SCOTCH_stratInit"
192  );
193 
194  string strategy;
195  if (coeffsDict_.readIfPresent("strategy", strategy))
196  {
197  DebugInfo << "scotchDecomp : Using strategy " << strategy << endl;
198 
199  SCOTCH_stratGraphMap(&stradat, strategy.c_str());
200  //fprintf(stdout, "S\tStrat=");
201  //SCOTCH_stratSave(&stradat, stdout);
202  //fprintf(stdout, "\n");
203  }
204 
205 
206  // Graph
207  // ~~~~~
208 
209  // Check for externally provided cellweights and if so initialise weights
210 
211  bool hasWeights = !cWeights.empty();
212 
213  // Note: min, not gMin since routine runs on master only.
214  const scalar minWeights = hasWeights ? min(cWeights) : scalar(1);
215 
216  if (minWeights <= 0)
217  {
218  hasWeights = false;
220  << "Illegal minimum weight " << minWeights
221  << " ... ignoring"
222  << endl;
223  }
224  else if (hasWeights && (cWeights.size() != numCells))
225  {
227  << "Number of cell weights " << cWeights.size()
228  << " does not equal number of cells " << numCells
229  << exit(FatalError);
230  }
231 
232 
233  List<SCOTCH_Num> velotab;
234 
235  if (hasWeights)
236  {
237  scalar rangeScale(1);
238 
239  const scalar velotabSum = sum(cWeights)/minWeights;
240 
241  const scalar upperRange = static_cast<scalar>
242  (
244  );
245 
246  if (velotabSum > upperRange)
247  {
248  // 0.9 factor of safety to avoid floating point round-off in
249  // rangeScale tipping the subsequent sum over the integer limit.
250  rangeScale = 0.9*upperRange/velotabSum;
251 
253  << "Sum of weights overflows SCOTCH_Num: " << velotabSum
254  << ", compressing by factor " << rangeScale << endl;
255  }
256 
257  {
258  // Convert to integers.
259  velotab.resize(cWeights.size());
260 
261  forAll(velotab, i)
262  {
263  velotab[i] = static_cast<SCOTCH_Num>
264  (
265  ((cWeights[i]/minWeights - 1)*rangeScale) + 1
266  );
267  }
268  }
269  }
270  else
271  {
272  // HACK: specify uniform weights
273  // - seems that scotch takes different code paths internally
274  // if weights are not specified (issue #3063)
275 
276  velotab.resize(numCells);
277  velotab = static_cast<SCOTCH_Num>(1);
278  }
279 
280 
281  //
282  // Decomposition graph
283  //
284 
285  SCOTCH_Graph grafdat;
286  check
287  (
288  SCOTCH_graphInit(&grafdat),
289  "SCOTCH_graphInit"
290  );
291  check
292  (
293  SCOTCH_graphBuild
294  (
295  &grafdat, // Graph to build
296  0, // Base for indexing (C-style)
297 
298  numCells, // Number of vertices [== nCells]
299  xadj_param().cdata(), // verttab, start index per cell into adjncy
300  nullptr, // vendtab, end index (nullptr == automatic)
301 
302  velotab.cdata(), // velotab, vertex weights
303  nullptr, // Vertex labels (nullptr == ignore)
304 
305  adjncy.size(), // Number of graph edges
306  adjncy_param().cdata(), // Edge array
307  nullptr // Edge weights (nullptr == ignore)
308  ),
309  "SCOTCH_graphBuild"
310  );
311  check
312  (
313  SCOTCH_graphCheck(&grafdat),
314  "SCOTCH_graphCheck"
315  );
316 
317 
318  // Architecture
319  // ~~~~~~~~~~~~
320  // (fully connected network topology since using switch)
321 
322  SCOTCH_Arch archdat;
323  check
324  (
325  SCOTCH_archInit(&archdat),
326  "SCOTCH_archInit"
327  );
328 
329  List<SCOTCH_Num> procWeights;
330 
331  // Do optional tree-like/multi-level decomposition by specifying
332  // domains/weights
333  List<SCOTCH_Num> domains;
334  List<scalar> dWeights;
335 
336  if
337  (
338  coeffsDict_.readIfPresent("processorWeights", procWeights)
339  && !procWeights.empty()
340  )
341  {
342  if (procWeights.size() != nDomains_)
343  {
345  << "processorWeights (" << procWeights.size()
346  << ") != number of domains (" << nDomains_ << ")" << nl
347  << exit(FatalIOError);
348  }
349 
350  DebugInfo
351  << "scotchDecomp : Using procesor weights "
352  << procWeights << endl;
353 
354  check
355  (
356  SCOTCH_archCmpltw(&archdat, nDomains_, procWeights.cdata()),
357  "SCOTCH_archCmpltw"
358  );
359  }
360  else if
361  (
362  coeffsDict_.readIfPresent("domains", domains, keyType::LITERAL)
363  && coeffsDict_.readIfPresent("domainWeights", dWeights, keyType::LITERAL)
364  )
365  {
366  // multi-level
367 
368  label nTotal = 1;
369  for (const label n : domains)
370  {
371  nTotal *= n;
372  }
373 
374  if (nTotal < nDomains())
375  {
376  const label sz = domains.size();
377  domains.setSize(sz+1);
378  dWeights.setSize(sz+1);
379  for (label i = sz-1; i >= 0; i--)
380  {
381  domains[i+1] = domains[i];
382  dWeights[i+1] = dWeights[i];
383  }
384 
385  if (nDomains() % nTotal)
386  {
388  << "Top level decomposition specifies " << nDomains()
389  << " domains which is not equal to the product of"
390  << " all sub domains " << nTotal
391  << exit(FatalError);
392  }
393 
394  domains[0] = nDomains() / nTotal;
395  dWeights[0] = scalar(1);
396  }
397 
398 
399  // Note: min, not gMin since routine runs on master only.
400  const scalar minWeights = min(dWeights);
401 
402  // Convert to integers.
403  List<SCOTCH_Num> weights(dWeights.size());
404 
405  forAll(weights, i)
406  {
407  weights[i] = static_cast<SCOTCH_Num>
408  (
409  (dWeights[i]/minWeights - 1) + 1
410  );
411  }
412 
413  check
414  (
415  SCOTCH_archTleaf
416  (
417  &archdat,
418  SCOTCH_Num(domains.size()),
419  domains.cdata(),
420  weights.cdata()
421  ),
422  "SCOTCH_archTleaf"
423  );
424  }
425  else
426  {
427  check
428  (
429  SCOTCH_archCmplt(&archdat, nDomains_),
430  "SCOTCH_archCmplt"
431  );
432 
433 
434  //- Hack to test clustering. Note that decomp is non-compact
435  // numbers!
436  //
438  //check
439  //(
440  // SCOTCH_archVcmplt(&archdat),
441  // "SCOTCH_archVcmplt"
442  //);
443  //
445  //SCOTCH_Num straval = 0;
448  //
451  //SCOTCH_Num agglomSize = 3;
452  //
454  //check
455  //(
456  // SCOTCH_stratGraphClusterBuild
457  // (
458  // &stradat, // strategy to build
459  // straval, // strategy flags
460  // agglomSize, // cells per cluster
461  // 1.0, // weight?
462  // 0.01 // max load imbalance
463  // ),
464  // "SCOTCH_stratGraphClusterBuild"
465  //);
466  }
467 
468 
469  //SCOTCH_Mapping mapdat;
470  //SCOTCH_graphMapInit(&grafdat, &mapdat, &archdat, nullptr);
471  //SCOTCH_graphMapCompute(&grafdat, &mapdat, &stradat); /* Perform mapping */
472  //SCOTCH_graphMapExit(&grafdat, &mapdat);
473 
474 
475  // Hack:switch off fpu error trapping
476  #ifdef FE_NOMASK_ENV
477  int oldExcepts = fedisableexcept
478  (
479  FE_DIVBYZERO
480  | FE_INVALID
481  | FE_OVERFLOW
482  );
483  #endif
484 
485  check
486  (
487  SCOTCH_graphMap
488  (
489  &grafdat,
490  &archdat,
491  &stradat, // const SCOTCH_Strat *
492  decomp_param.ref().data() // parttab
493  ),
494  "SCOTCH_graphMap"
495  );
496 
497  #ifdef FE_NOMASK_ENV
498  feenableexcept(oldExcepts);
499  #endif
500 
501  //decomp.resize(numCells);
502  //check
503  //(
504  // SCOTCH_graphPart
505  // (
506  // &grafdat,
507  // nDomains_, // partnbr
508  // &stradat, // const SCOTCH_Strat *
509  // decomp_param.ref().data() // parttab
510  // ),
511  // "SCOTCH_graphPart"
512  //);
513 
514  SCOTCH_graphExit(&grafdat); // Release storage for graph
515  SCOTCH_stratExit(&stradat); // Release storage for strategy
516  SCOTCH_archExit(&archdat); // Release storage for network topology
517 
518  return 0;
519 }
521 
522 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
523 
524 Foam::scotchDecomp::scotchDecomp(const label numDomains)
525 :
526  metisLikeDecomp(numDomains)
527 {}
528 
529 
531 (
532  const dictionary& decompDict,
533  const word& regionName
534 )
535 :
536  metisLikeDecomp(typeName, decompDict, regionName, selectionType::NULL_DICT)
537 {}
538 
540 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
541 
543 (
544  const polyMesh& mesh,
545  const pointField& points,
546  const scalarField& pointWeights
547 ) const
548 {
549  // Where to write graph
550  graphPath_ = getGraphPathBase(mesh);
551 
553  (
554  mesh,
555  points,
556  pointWeights
557  );
558 }
559 
560 
562 (
563  const polyMesh& mesh,
564  const labelList& agglom,
565  const pointField& agglomPoints,
566  const scalarField& agglomWeights
567 ) const
568 {
569  // Where to write graph
570  graphPath_ = getGraphPathBase(mesh);
571 
573  (
574  mesh,
575  agglom,
576  agglomPoints,
577  agglomWeights
578  );
579 }
580 
581 
583 (
584  const CompactListList<label>& globalCellCells,
585  const pointField& cellCentres,
586  const scalarField& cWeights
587 ) const
588 {
589  // Where to write graph
590  graphPath_ = "scotch.grf";
591 
593  (
594  globalCellCells,
595  cellCentres,
596  cWeights
597  );
598 }
599 
600 
602 (
603  const labelListList& globalCellCells,
604  const pointField& cellCentres,
605  const scalarField& cWeights
606 ) const
607 {
608  // Where to write graph
609  graphPath_ = "scotch.grf";
610 
612  (
613  globalCellCells,
614  cellCentres,
615  cWeights
616  );
617 }
618 
619 
620 // ************************************************************************* //
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &f1)
fileName path() const
Return path = rootPath/caseName. Same as TimePaths::path()
Definition: Time.H:503
A class for handling file names.
Definition: fileName.H:72
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:160
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
label nDomains_
Number of domains for the decomposition.
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:598
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
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:666
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
static Foam::fileName getGraphPathBase(const polyMesh &mesh)
void resize_nocopy(const label len)
Adjust allocated size of list without necessarily.
Definition: ListI.H:175
A non-const Field/List wrapper with possible data conversion.
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:360
const dictionary & coeffsDict_
Coefficients for all derived methods.
Macros for easy insertion into run-time selection tables.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
virtual labelList decompose(const polyMesh &mesh, const pointField &points=pointField::null(), const scalarField &pointWeights=scalarField::null()) const
Return for every coordinate the wanted processor number.
Definition: scotchDecomp.C:539
dynamicFvMesh & mesh
const pointField & points
A class for handling words, derived from Foam::string.
Definition: word.H:63
scotchDecomp(const scotchDecomp &)=delete
No copy construct.
A const Field/List wrapper with possible data conversion.
Domain decomposition using METIS-like data structures.
String literal.
Definition: keyType.H:82
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
virtual labelList decompose(const polyMesh &mesh, const pointField &points=pointField::null(), const scalarField &pointWeights=scalarField::null()) const
Return for every coordinate the wanted processor number.
label nDomains() const noexcept
Number of domains.
#define DebugInfo
Report an information message using Foam::Info.
A packed storage of objects of type <T> using an offset table for access.
void set(Container< InputType > &input, const bool doCopy=true)
Set adaptor for different input, copying input as required.
bool readIfPresent(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry if present, and assign to T val. FatalIOError if it is found and the number of tokens i...
Container< Type > & ref() const
Return non-const reference to the contents of a non-null managed pointer.
Definition: refPtrI.H:230
constexpr auto end(C &c) -> decltype(c.end())
Return iterator to the end of the container c.
Definition: stdFoam.H:201
defineTypeNameAndDebug(combustionModel, 0)
static void check(const int retVal, const char *what)
void clear() const noexcept
If object pointer points to valid object: delete object and set pointer to nullptr.
Definition: refPtrI.H:303
#define WarningInFunction
Report a warning using Foam::Warning.
const word & name() const
Return reference to name.
Definition: fvMesh.H:387
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:627
Foam::word regionName(args.getOrDefault< word >("region", Foam::polyMesh::defaultRegion))
messageStream Info
Information stream (stdout output on master, null elsewhere)
label n
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:74
T getOrDefault(const word &keyword, const T &deflt, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a T, or return the given default value. FatalIOError if it is found and the number of...
void set(const Container< InputType > &input)
Set adaptor for different input, copying input if required.
Namespace for OpenFOAM.
virtual label decomposeSerial(const labelList &adjncy, const labelList &xadj, const List< scalar > &cWeights, labelList &decomp) const
Decompose non-parallel.
Definition: scotchDecomp.C:102
addToRunTimeSelectionTable(functionObject, pointHistory, dictionary)
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...