parMetis.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) 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 "parMetis.H"
27 #include "Time.H"
28 #include "globalIndex.H"
29 #include "labelIOField.H"
30 #include "PstreamGlobals.H"
32 
33 
34 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
35 
36 namespace Foam
37 {
38 namespace decompositionMethods
39 {
41 
43  (
45  parMetis,
46  distributor
47  );
48 }
49 }
50 
51 
52 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
53 
55 (
56  const labelList& xadj,
57  const labelList& adjncy,
58  const pointField& cellCentres,
59  const label nWeights,
60  const labelList& cellWeights,
61  const labelList& faceWeights,
62  labelList& decomp
63 )
64 {
65  // C style numbering
66  label numFlag = 0;
67 
68  List<real_t> processorWeights;
69 
70  if (processorWeights_.size())
71  {
72  if (processorWeights_.size() != nWeights*nProcessors_)
73  {
75  << "Number of processor weights specified in parMetisCoeffs "
76  << processorWeights_.size()
77  << " does not equal number of constraints * number of domains "
79  << exit(FatalError);
80  }
81 
82  processorWeights = processorWeights_;
83  }
84  else
85  {
86  processorWeights.setSize(nWeights*nProcessors_, 1.0/nProcessors_);
87  }
88 
89  // Imbalance tolerance
90  List<real_t> ubvec(nWeights, 1.02);
91 
92  // If only one processor there is no imbalance
93  if (nProcessors_ == 1)
94  {
95  ubvec[0] = 1;
96  }
97 
98  // Distribute to all processors the number of cells on each processor
99  globalIndex globalMap(cellCentres.size());
100 
101  // Get the processor-cell offset table
102  labelList cellOffsets(globalMap.offsets());
103 
104  // Weight info
105  label wgtFlag = 0;
106  const label* vwgtPtr = nullptr;
107  const label* adjwgtPtr = nullptr;
108 
109  // Weights on vertices of graph (cells)
110  if (cellWeights.size())
111  {
112  vwgtPtr = cellWeights.begin();
113  wgtFlag += 2;
114  }
115 
116  // Weights on edges of graph (faces)
117  if (faceWeights.size())
118  {
119  adjwgtPtr = faceWeights.begin();
120  wgtFlag += 1;
121  }
122 
123  // Set the list of valid processors,
124  // i.e. processors with non-zero number of cells
125  labelList validProcs(Pstream::nProcs());
126  labelList validCellOffsets(cellOffsets);
127  label nValidProcs = 0;
128 
129  for(label proci=0; proci<Pstream::nProcs(); proci++)
130  {
131  if (cellOffsets[proci + 1] - cellOffsets[proci] > 0)
132  {
133  validProcs[nValidProcs] = proci;
134  validCellOffsets[nValidProcs] = validCellOffsets[proci];
135  nValidProcs++;
136  }
137  }
138  validProcs.setSize(nValidProcs);
139  validCellOffsets[nValidProcs] = validCellOffsets[Pstream::nProcs()];
140 
141  // Initialise the communicator index to worldComm
142  label commi = UPstream::worldComm;
143 
144  // If there any processors with zero cells create a communicator
145  // for the sub-set of processors containing cells
146  if (nValidProcs != Pstream::nProcs())
147  {
149  }
150 
151  // Lookup the MPI communicator for the current communicator index
152  MPI_Comm comm = PstreamGlobals::MPICommunicators_[commi];
153 
154  // Output: cell -> processor addressing
155  decomp.setSize(cellCentres.size());
156 
157  // Output: the number of edges that are cut by the partitioning
158  label edgeCut = 0;
159 
160  // Call ParMETIS only for processors containing cells
161  if (cellCentres.size())
162  {
163  if (method_ == "kWay")
164  {
165  ParMETIS_V3_PartKway
166  (
167  validCellOffsets.begin(),
168  const_cast<label*>(xadj.begin()),
169  const_cast<label*>(adjncy.begin()),
170  const_cast<label*>(vwgtPtr),
171  const_cast<label*>(adjwgtPtr),
172  &wgtFlag,
173  &numFlag,
174  const_cast<label*>(&nWeights),
175  &nProcessors_,
176  processorWeights.begin(),
177  ubvec.begin(),
178  const_cast<labelList&>(options_).begin(),
179  &edgeCut,
180  decomp.begin(),
181  &comm
182  );
183  }
184  else if (method_ == "geomKway")
185  {
186  // Number of dimensions
187  label nDims = 3;
188 
189  // Convert pointField into List<real_t>
190  List<real_t> xyz(nDims*cellCentres.size());
191  label i = 0;
192  forAll(cellCentres, celli)
193  {
194  const point& cc = cellCentres[celli];
195  xyz[i++] = cc.x();
196  xyz[i++] = cc.y();
197  xyz[i++] = cc.z();
198  }
199 
200  ParMETIS_V3_PartGeomKway
201  (
202  validCellOffsets.begin(),
203  const_cast<label*>(xadj.begin()),
204  const_cast<label*>(adjncy.begin()),
205  const_cast<label*>(vwgtPtr),
206  const_cast<label*>(adjwgtPtr),
207  &wgtFlag,
208  &numFlag,
209  &nDims,
210  xyz.begin(),
211  const_cast<label*>(&nWeights),
212  &nProcessors_,
213  processorWeights.begin(),
214  ubvec.begin(),
215  const_cast<labelList&>(options_).begin(),
216  &edgeCut,
217  decomp.begin(),
218  &comm
219  );
220  }
221  else if (method_ == "adaptiveRepart")
222  {
223  // Size of the vertices with respect to redistribution cost
224  labelList vsize(cellCentres.size(), 1);
225 
226  ParMETIS_V3_AdaptiveRepart
227  (
228  validCellOffsets.begin(),
229  const_cast<label*>(xadj.begin()),
230  const_cast<label*>(adjncy.begin()),
231  const_cast<label*>(vwgtPtr),
232  const_cast<label*>(vsize.begin()),
233  const_cast<label*>(adjwgtPtr),
234  &wgtFlag,
235  &numFlag,
236  const_cast<label*>(&nWeights),
237  &nProcessors_,
238  processorWeights.begin(),
239  ubvec.begin(),
240  &itr_,
241  const_cast<labelList&>(options_).begin(),
242  &edgeCut,
243  decomp.begin(),
244  &comm
245  );
246  }
247  }
248 
249  // Delete the sub-set communicator if created
250  if (commi != UPstream::worldComm)
251  {
253  }
254 
255  // Sum the number of cells allocated to each processor
256  labelList nProcCells(Pstream::nProcs(), 0);
257 
258  forAll(decomp, i)
259  {
260  nProcCells[decomp[i]]++;
261  }
262 
263  reduce(nProcCells, ListOp<sumOp<label>>());
264 
265  // If there are no cells allocated to this processor keep the first one
266  // to ensure that all processors have at least one cell
267  if (nProcCells[Pstream::myProcNo()] == 0)
268  {
269  Pout<< " No cells allocated to this processor"
270  ", keeping first cell"
271  << endl;
272  decomp[0] = Pstream::myProcNo();
273  }
274 
275  return edgeCut;
276 }
277 
278 
279 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
280 
282 (
283  const dictionary& decompositionDict
284 )
285 :
286  decompositionMethod(decompositionDict),
287  method_("geomKway"),
288  options_(4, label(0)),
289  itr_(1000)
290 {
291  // Check for user supplied weights and decomp options
292  if (decompositionDict.found("parMetisCoeffs"))
293  {
294  const dictionary& parMetisCoeffs =
295  decompositionDict.subDict("parMetisCoeffs");
296 
297  Info<< type() << ": reading coefficients:" << endl;
298 
299  if (parMetisCoeffs.readIfPresent("method", method_))
300  {
301  if
302  (
303  method_ != "kWay"
304  && method_ != "geomKWay"
305  && method_ != "adaptiveRepart"
306  )
307  {
308  FatalIOErrorInFunction(parMetisCoeffs)
309  << "Method " << method_
310  << " in parMetisCoeffs in dictionary : "
311  << decompositionDict.name()
312  << " should be kWay, geomKWay or adaptiveRepart"
313  << exit(FatalIOError);
314  }
315 
316  Info<< " method: " << method_ << endl;
317  }
318 
319  if
320  (
321  method_ == "adaptiveRepart"
322  && parMetisCoeffs.readIfPresent("itr", itr_)
323  )
324  {
325  Info<< " itr: " << itr_ << endl;
326  }
327 
328  if (parMetisCoeffs.readIfPresent("options", options_))
329  {
330  if (options_.size() != 4)
331  {
332  FatalIOErrorInFunction(parMetisCoeffs)
333  << "Number of options in parMetisCoeffs dictionary : "
334  << decompositionDict.name()
335  << " should be 4, found " << options_
336  << exit(FatalIOError);
337  }
338 
339  Info<< " options: " << options_ << endl;
340  }
341 
342  parMetisCoeffs.readIfPresent("processorWeights_", processorWeights_);
343 
344  Info << endl;
345  }
346 }
347 
348 
349 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
350 
352 (
353  const polyMesh& mesh,
354  const pointField& points,
355  const scalarField& pointWeights
356 )
357 {
358  if (points.size() != mesh.nCells())
359  {
361  << "Can use this decomposition method only for the whole mesh"
362  << endl
363  << "and supply one coordinate (cellCentre) for every cell." << endl
364  << "The number of coordinates " << points.size() << endl
365  << "The number of cells in the mesh " << mesh.nCells()
366  << exit(FatalError);
367  }
368 
369  // Make Metis CSR (Compressed Storage Format) storage
370  // adjncy : contains neighbours (= edges in graph)
371  // xadj(celli) : start of information in adjncy for celli
372  CompactListList<label> cellCells;
373  calcCellCells
374  (
375  mesh,
376  identityMap(mesh.nCells()),
377  mesh.nCells(),
378  true,
379  cellCells
380  );
381 
382  label nWeights = this->nWeights(points, pointWeights);
383 
384  const labelList intWeights(scaleWeights(pointWeights, nWeights));
385 
386  labelList decomp;
387  decompose
388  (
389  cellCells.offsets(),
390  cellCells.m(),
391  points,
392  nWeights,
393  intWeights,
394  labelList(),
395  decomp
396  );
397 
398  return decomp;
399 }
400 
401 
403 (
404  const polyMesh& mesh,
405  const labelList& cellToRegion,
406  const pointField& regionPoints,
407  const scalarField& pointWeights
408 )
409 {
410  if (cellToRegion.size() != mesh.nCells())
411  {
413  << "Size of cell-to-coarse map " << cellToRegion.size()
414  << " differs from number of cells in mesh " << mesh.nCells()
415  << exit(FatalError);
416  }
417 
418  // Make Metis CSR (Compressed Storage Format) storage
419  // adjncy : contains neighbours (= edges in graph)
420  // xadj(celli) : start of information in adjncy for celli
421  CompactListList<label> cellCells;
422  calcCellCells
423  (
424  mesh,
425  cellToRegion,
426  regionPoints.size(),
427  true,
428  cellCells
429  );
430 
431  label nWeights = this->nWeights(regionPoints, pointWeights);
432  const labelList intWeights(scaleWeights(pointWeights, nWeights));
433 
434  // Decompose using weights
435  labelList decomp;
436  decompose
437  (
438  cellCells.m(),
439  cellCells.offsets(),
440  regionPoints,
441  nWeights,
442  intWeights,
443  labelList(),
444  decomp
445  );
446 
447  // Rework back into decomposition for original mesh
448  labelList fineDistribution(cellToRegion.size());
449 
450  forAll(fineDistribution, i)
451  {
452  fineDistribution[i] = decomp[cellToRegion[i]];
453  }
454 
455  return fineDistribution;
456 }
457 
458 
460 (
461  const labelListList& globalCellCells,
462  const pointField& cellCentres,
463  const scalarField& cellWeights
464 )
465 {
466  if (cellCentres.size() != globalCellCells.size())
467  {
469  << "Inconsistent number of cells (" << globalCellCells.size()
470  << ") and number of cell centres (" << cellCentres.size()
471  << ")." << exit(FatalError);
472  }
473 
474  // Make Metis Distributed CSR (Compressed Storage Format) storage
475  // adjncy : contains neighbours (= edges in graph)
476  // xadj(celli) : start of information in adjncy for celli
477  CompactListList<label> cellCells(globalCellCells);
478 
479  label nWeights = this->nWeights(cellCentres, cellWeights);
480  const labelList intWeights(scaleWeights(cellWeights, nWeights));
481 
482  labelList decomp;
483  decompose
484  (
485  cellCells.offsets(),
486  cellCells.m(),
487  cellCentres,
488  nWeights,
489  intWeights,
490  labelList(),
491  decomp
492  );
493 
494  return decomp;
495 }
496 
497 
498 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
Macros for easy insertion into run-time selection tables.
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
void setSize(const label)
Reset size of List.
Definition: List.C:281
const UList< label > & offsets() const
Return the offset table (= size()+1)
const UList< T > & m() const
Return the packed matrix of data.
iterator begin()
Return an iterator to begin traversing the UList.
Definition: UListI.H:216
static void freeCommunicator(const label communicator, const bool doPstream=true)
Free a previously allocated communicator.
Definition: UPstream.C:314
static label worldComm
Default communicator (all processors)
Definition: UPstream.H:278
static label nProcs(const label communicator=0)
Number of processes in parallel run.
Definition: UPstream.H:411
static label allocateCommunicator(const label parent, const labelList &subRanks, const bool doPstream=true)
Allocate a new communicator.
Definition: UPstream.C:248
static int myProcNo(const label communicator=0)
Number of this process (starting from masterNo() = 0)
Definition: UPstream.H:429
const Cmpt & x() const
Definition: VectorI.H:75
Abstract base class for decomposition.
label nWeights(const pointField &points, const scalarField &pointWeights) const
Return the number of weights per point.
ParMetis redistribution in parallel.
Definition: parMetis.H:93
virtual labelList decompose(const pointField &points, const scalarField &pointWeights)
Inherit decompose from decompositionMethod.
parMetis(const dictionary &decompositionDict)
Construct given the decomposition dictionary.
Definition: parMetis.C:282
const fileName & name() const
Return the dictionary name.
Definition: dictionary.H:111
A list of keyword definitions, which are a keyword followed by any number of values (e....
Definition: dictionary.H:162
const dictionary & subDict(const word &) const
Find and return a sub-dictionary.
Definition: dictionary.C:843
bool readIfPresent(const word &, T &, bool recursive=false, bool patternMatch=true) const
Find an entry if present, and assign to T.
bool found(const word &, bool recursive=false, bool patternMatch=true) const
Search dictionary for given keyword.
Definition: dictionary.C:509
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:80
label nCells() const
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:346
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
const pointField & points
DynamicList< MPI_Comm > MPICommunicators_
addToRunTimeSelectionTable(decompositionMethod, metis, decomposer)
tmp< scalarField > faceWeights(const polyMesh &mesh, const vectorField &fCtrs, const vectorField &fAreas, const vectorField &cellCtrs)
Generate interpolation factors field.
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:257
messageStream Info
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:42
vector point
Point is a vector.
Definition: point.H:41
void reduce(const List< UPstream::commsStruct > &comms, T &Value, const BinaryOp &bop, const int tag, const label comm)
IOerror FatalIOError
prefixOSstream Pout(cout, "Pout")
Definition: IOstreams.H:53
error FatalError
labelList identityMap(const label len)
Create identity map (map[i] == i) of given length.
Definition: ListOps.C:104
fileType type(const fileName &, const bool checkVariants=true, const bool followLink=true)
Return the file type: directory or file.
Definition: POSIX.C:488