scotch.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2011-2024 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "scotch.H"
28 #include "floatScalar.H"
29 #include "Time.H"
30 #include "OFstream.H"
31 #include "globalIndex.H"
32 #include "SubField.H"
33 
34 extern "C"
35 {
36 #include "scotch.h"
37 }
38 
39 
40 // Hack: scotch generates floating point errors so need to switch of error
41 // trapping!
42 #ifdef __GLIBC__
43  #ifndef _GNU_SOURCE
44  #define _GNU_SOURCE
45  #endif
46  #include <fenv.h>
47 #endif
48 
49 
50 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
51 
52 namespace Foam
53 {
54 namespace decompositionMethods
55 {
56  defineTypeNameAndDebug(scotch, 0);
57 
59  (
60  decompositionMethod,
61  scotch,
62  decomposer
63  );
64 
66  (
67  decompositionMethod,
68  scotch,
69  distributor
70  );
71 }
72 }
73 
74 
75 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
76 
77 void Foam::decompositionMethods::scotch::check
78 (
79  const int retVal,
80  const char* str
81 )
82 {
83  if (retVal)
84  {
86  << "Call to scotch routine " << str << " failed."
87  << exit(FatalError);
88  }
89 }
90 
91 
93 (
94  const fileName& meshPath,
95  const labelList& adjncy,
96  const labelList& xadj,
97  const scalarField& cellWeights,
98  labelList& decomp
99 )
100 {
101  if (!Pstream::parRun())
102  {
103  decomposeOneProc
104  (
105  meshPath,
106  adjncy,
107  xadj,
108  cellWeights,
109  decomp
110  );
111  }
112  else
113  {
114  if (debug)
115  {
116  Info<< "scotch : running in parallel."
117  << " Decomposing all of graph on master processor." << endl;
118  }
119  globalIndex globalCells(xadj.size()-1);
120  label nTotalConnections = returnReduce(adjncy.size(), sumOp<label>());
121 
122  // Send all to master. Use scheduled to save some storage.
123  if (Pstream::master())
124  {
125  Field<label> allAdjncy(nTotalConnections);
126  Field<label> allXadj(globalCells.size()+1);
127  scalarField allWeights(globalCells.size());
128 
129  // Insert my own
130  label nTotalCells = 0;
131  forAll(cellWeights, celli)
132  {
133  allXadj[nTotalCells] = xadj[celli];
134  allWeights[nTotalCells++] = cellWeights[celli];
135  }
136  nTotalConnections = 0;
137  forAll(adjncy, i)
138  {
139  allAdjncy[nTotalConnections++] = adjncy[i];
140  }
141 
142  for (int slave=1; slave<Pstream::nProcs(); slave++)
143  {
144  IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
145  Field<label> nbrAdjncy(fromSlave);
146  Field<label> nbrXadj(fromSlave);
147  scalarField nbrWeights(fromSlave);
148 
149  // Append.
150  // label procStart = nTotalCells;
151  forAll(nbrXadj, celli)
152  {
153  allXadj[nTotalCells] = nTotalConnections+nbrXadj[celli];
154  allWeights[nTotalCells++] = nbrWeights[celli];
155  }
156  // No need to renumber xadj since already global.
157  forAll(nbrAdjncy, i)
158  {
159  allAdjncy[nTotalConnections++] = nbrAdjncy[i];
160  }
161  }
162  allXadj[nTotalCells] = nTotalConnections;
163 
164 
165  Field<label> allFinalDecomp;
166  decomposeOneProc
167  (
168  meshPath,
169  allAdjncy,
170  allXadj,
171  allWeights,
172  allFinalDecomp
173  );
174 
175 
176  // Send allFinalDecomp back
177  for (int slave=1; slave<Pstream::nProcs(); slave++)
178  {
179  OPstream toSlave(Pstream::commsTypes::scheduled, slave);
180  toSlave << SubField<label>
181  (
182  allFinalDecomp,
183  globalCells.localSize(slave),
184  globalCells.offset(slave)
185  );
186  }
187  // Get my own part (always first)
188  decomp = SubField<label>
189  (
190  allFinalDecomp,
191  globalCells.localSize()
192  );
193  }
194  else
195  {
196  // Send my part of the graph (already in global numbering)
197  {
198  OPstream toMaster
199  (
202  );
203  toMaster<< adjncy << SubField<label>(xadj, xadj.size()-1)
204  << cellWeights;
205  }
206 
207  // Receive back decomposition
208  IPstream fromMaster
209  (
212  );
213  fromMaster >> decomp;
214  }
215  }
216  return 0;
217 }
218 
219 
220 Foam::label Foam::decompositionMethods::scotch::decomposeOneProc
221 (
222  const fileName& meshPath,
223  const labelList& adjncy,
224  const labelList& xadj,
225  const scalarField& cellWeights,
226 
227  labelList& decomp
228 )
229 {
230  // Dump graph
231  if (!methodDict_.empty())
232  {
233  if (methodDict_.lookupOrDefault("writeGraph", false))
234  {
235  OFstream str(meshPath + ".grf");
236 
237  Info<< "Dumping Scotch graph file to " << str.name() << endl
238  << "Use this in combination with gpart." << endl;
239 
240  label version = 0;
241  str << version << nl;
242  // Number of vertices
243  str << xadj.size()-1 << ' ' << adjncy.size() << nl;
244  // Numbering starts from 0
245  label baseval = 0;
246  // Has weights?
247  label hasEdgeWeights = 0;
248  label hasVertexWeights = 0;
249  label numericflag = 10*hasEdgeWeights+hasVertexWeights;
250  str << baseval << ' ' << numericflag << nl;
251  for (label celli = 0; celli < xadj.size()-1; celli++)
252  {
253  label start = xadj[celli];
254  label end = xadj[celli+1];
255  str << end-start;
256 
257  for (label i = start; i < end; i++)
258  {
259  str << ' ' << adjncy[i];
260  }
261  str << nl;
262  }
263  }
264  }
265 
266  // Reset the seed of the pseudo-random generator used by the graph
267  // partitioning routines of the libScotch library. Two consecutive calls to
268  // the same libScotch partitioning routines, and separated by a call to
269  // SCOTCH randomReset, will always yield the same results, as if the
270  // equivalent standalone Scotch programs were used twice, independently,
271  SCOTCH_randomReset();
272 
273  // Strategy
274  // ~~~~~~~~
275 
276  // Default.
277  SCOTCH_Strat stradat;
278  check(SCOTCH_stratInit(&stradat), "SCOTCH_stratInit");
279 
280  if (!methodDict_.empty())
281  {
282  string strategy;
283  if (methodDict_.readIfPresent("strategy", strategy))
284  {
285  if (debug)
286  {
287  Info<< "scotch : Using strategy " << strategy << endl;
288  }
289  SCOTCH_stratGraphMap(&stradat, strategy.c_str());
290  }
291  }
292 
293 
294  // Graph
295  // ~~~~~
296 
297  labelList velotab;
298 
299  // Check for externally provided cellweights and if so initialise weights
300  if (!cellWeights.empty())
301  {
302  if (cellWeights.size() != xadj.size()-1)
303  {
305  << "Number of cell weights " << cellWeights.size()
306  << " does not equal number of cells " << xadj.size()-1
307  << exit(FatalError);
308  }
309 
310  label nWeights = 1;
311  velotab = scaleWeights(cellWeights, nWeights, false);
312  }
313 
314 
315  SCOTCH_Graph grafdat;
316  check(SCOTCH_graphInit(&grafdat), "SCOTCH_graphInit");
317  check
318  (
319  SCOTCH_graphBuild
320  (
321  &grafdat,
322  0, // baseval, c-style numbering
323  xadj.size()-1, // vertnbr, nCells
324  xadj.begin(), // verttab, start index per cell into adjncy
325  &xadj[1], // vendtab, end index ,,
326  velotab.begin(), // velotab, vertex weights
327  nullptr, // vlbltab
328  adjncy.size(), // edgenbr, number of arcs
329  adjncy.begin(), // edgetab
330  nullptr // edlotab, edge weights
331  ),
332  "SCOTCH_graphBuild"
333  );
334  check(SCOTCH_graphCheck(&grafdat), "SCOTCH_graphCheck");
335 
336 
337  // Architecture
338  // ~~~~~~~~~~~~
339  // (fully connected network topology since using switch)
340 
341  SCOTCH_Arch archdat;
342  check(SCOTCH_archInit(&archdat), "SCOTCH_archInit");
343 
344  labelList processorWeights;
345  if (!methodDict_.empty())
346  {
347  methodDict_.readIfPresent("processorWeights", processorWeights);
348  }
349  if (processorWeights.size())
350  {
351  if (debug)
352  {
353  Info<< "scotch : Using processor weights " << processorWeights
354  << endl;
355  }
356  check
357  (
358  SCOTCH_archCmpltw(&archdat, nProcessors_, processorWeights.begin()),
359  "SCOTCH_archCmpltw"
360  );
361  }
362  else
363  {
364  check
365  (
366  SCOTCH_archCmplt(&archdat, nProcessors_),
367  "SCOTCH_archCmplt"
368  );
369  }
370 
371  // Hack:switch off fpu error trapping
372  #ifdef FE_NOMASK_ENV
373  int oldExcepts = fedisableexcept
374  (
375  FE_DIVBYZERO
376  | FE_INVALID
377  | FE_OVERFLOW
378  );
379  #endif
380 
381  decomp.setSize(xadj.size()-1);
382  decomp = 0;
383  check
384  (
385  SCOTCH_graphMap
386  (
387  &grafdat,
388  &archdat,
389  &stradat, // const SCOTCH_Strat *
390  decomp.begin() // parttab
391  ),
392  "SCOTCH_graphMap"
393  );
394 
395  #ifdef FE_NOMASK_ENV
396  feenableexcept(oldExcepts);
397  #endif
398 
399  // Release storage for graph
400  SCOTCH_graphExit(&grafdat);
401 
402  // Release storage for strategy
403  SCOTCH_stratExit(&stradat);
404 
405  // Release storage for network topology
406  SCOTCH_archExit(&archdat);
407 
408  return 0;
409 }
410 
411 
412 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
413 
415 (
416  const dictionary& decompositionDict,
417  const dictionary& methodDict
418 )
419 :
420  decompositionMethod(decompositionDict),
421  methodDict_(methodDict)
422 {}
423 
424 
425 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
426 
428 (
429  const polyMesh& mesh,
430  const pointField& points,
431  const scalarField& pointWeights
432 )
433 {
434  if (points.size() != mesh.nCells())
435  {
437  << "Can use this decomposition method only for the whole mesh"
438  << endl
439  << "and supply one coordinate (cellCentre) for every cell." << endl
440  << "The number of coordinates " << points.size() << endl
441  << "The number of cells in the mesh " << mesh.nCells()
442  << exit(FatalError);
443  }
444 
445  checkWeights(points, pointWeights);
446 
447  // Calculate local or global (if Pstream::parRun()) connectivity
448  CompactListList<label> cellCells;
450  (
451  mesh,
453  mesh.nCells(),
454  true,
455  cellCells
456  );
457 
458  // Decompose using default weights
459  labelList decomp;
460  decompose
461  (
462  mesh.time().path()/mesh.name(),
463  cellCells.m(),
464  cellCells.offsets(),
465  pointWeights,
466  decomp
467  );
468 
469  return decomp;
470 }
471 
472 
474 (
475  const polyMesh& mesh,
476  const labelList& agglom,
477  const pointField& agglomPoints,
478  const scalarField& pointWeights
479 )
480 {
481  if (agglom.size() != mesh.nCells())
482  {
484  << "Size of cell-to-coarse map " << agglom.size()
485  << " differs from number of cells in mesh " << mesh.nCells()
486  << exit(FatalError);
487  }
488 
489  checkWeights(agglomPoints, pointWeights);
490 
491  // Calculate local or global (if Pstream::parRun()) connectivity
492  CompactListList<label> cellCells;
493  calcCellCells
494  (
495  mesh,
496  agglom,
497  agglomPoints.size(),
498  true,
499  cellCells
500  );
501 
502  // Decompose using weights
503  labelList decomp;
504  decompose
505  (
506  mesh.time().path()/mesh.name(),
507  cellCells.m(),
508  cellCells.offsets(),
509  pointWeights,
510  decomp
511  );
512 
513  // Rework back into decomposition for original mesh_
514  labelList fineDistribution(agglom.size());
515 
516  forAll(fineDistribution, i)
517  {
518  fineDistribution[i] = decomp[agglom[i]];
519  }
520 
521  return fineDistribution;
522 }
523 
524 
526 (
527  const labelListList& globalCellCells,
528  const pointField& cellCentres,
529  const scalarField& cellWeights
530 )
531 {
532  if (cellCentres.size() != globalCellCells.size())
533  {
535  << "Inconsistent number of cells (" << globalCellCells.size()
536  << ") and number of cell centres (" << cellCentres.size()
537  << ")." << exit(FatalError);
538  }
539 
540  checkWeights(cellCentres, cellWeights);
541 
542  // Make Metis CSR (Compressed Storage Format) storage
543  // adjncy : contains neighbours (= edges in graph)
544  // xadj(celli) : start of information in adjncy for celli
545 
546  CompactListList<label> cellCells(globalCellCells);
547 
548  // Decompose using weights
549  labelList decomp;
550  decompose
551  (
552  "scotch",
553  cellCells.m(),
554  cellCells.offsets(),
555  cellWeights,
556  decomp
557  );
558 
559  return decomp;
560 }
561 
562 
563 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
Macros for easy insertion into run-time selection tables.
label size() const
Return number of elements in table.
Definition: HashTableI.H:65
fileName path() const
Explicitly inherit path from TimePaths to disambiguate from.
Definition: TimePaths.H:138
static int masterNo()
Process index of the master.
Definition: UPstream.H:417
static bool master(const label communicator=0)
Am I the master process.
Definition: UPstream.H:423
static label nProcs(const label communicator=0)
Number of processes in parallel run.
Definition: UPstream.H:411
static bool & parRun()
Is this a parallel run?
Definition: UPstream.H:399
label checkWeights(const pointField &points, const scalarField &pointWeights) const
Check the weights against the points.
static void calcCellCells(const polyMesh &mesh, const labelList &agglom, const label nLocalCoarse, const bool global, CompactListList< label > &cellCells)
Helper: determine (local or global) cellCells from mesh.
virtual labelList decompose(const pointField &points, const scalarField &pointWeights)
Inherit decompose from decompositionMethod.
scotch(const dictionary &decompositionDict, const dictionary &methodDict)
Construct given the decomposition dictionary and mesh.
Definition: dummyScotch.C:95
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:420
const word & name() const
Return reference to name.
Definition: fvMesh.H:434
label nCells() const
Foam::fvMesh mesh(Foam::IOobject(regionName, runTime.name(), runTime, Foam::IOobject::MUST_READ), false)
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
volScalarField scalarField(fieldObject, mesh)
const pointField & points
addToRunTimeSelectionTable(decompositionMethod, metis, decomposer)
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
List< label > labelList
A List of labels.
Definition: labelList.H:56
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:258
messageStream Info
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:42
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
List< labelList > labelListList
A List of labelList.
Definition: labelList.H:57
T returnReduce(const T &Value, const BinaryOp &bop, const int tag=Pstream::msgType(), const label comm=UPstream::worldComm)
error FatalError
labelList identityMap(const label len)
Create identity map (map[i] == i) of given length.
Definition: ListOps.C:104
static const char nl
Definition: Ostream.H:267
word meshPath
Definition: setMeshPath.H:1