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  const dictionary& methodDict
285 )
286 :
287  decompositionMethod(decompositionDict),
288  methodDict_(methodDict),
289  method_("geomKway"),
290  options_(4, label(0)),
291  itr_(1000)
292 {
293  // Check for user supplied weights and decomp options
294  if (decompositionDict.found("parMetisCoeffs"))
295  {
296  const dictionary& parMetisCoeffs =
297  decompositionDict.subDict("parMetisCoeffs");
298 
299  Info<< type() << ": reading coefficients:" << endl;
300 
301  if (parMetisCoeffs.readIfPresent("method", method_))
302  {
303  if
304  (
305  method_ != "kWay"
306  && method_ != "geomKWay"
307  && method_ != "adaptiveRepart"
308  )
309  {
310  FatalIOErrorInFunction(parMetisCoeffs)
311  << "Method " << method_
312  << " in parMetisCoeffs in dictionary : "
313  << decompositionDict.name()
314  << " should be kWay, geomKWay or adaptiveRepart"
315  << exit(FatalIOError);
316  }
317 
318  Info<< " method: " << method_ << endl;
319  }
320 
321  if
322  (
323  method_ == "adaptiveRepart"
324  && parMetisCoeffs.readIfPresent("itr", itr_)
325  )
326  {
327  Info<< " itr: " << itr_ << endl;
328  }
329 
330  if (parMetisCoeffs.readIfPresent("options", options_))
331  {
332  if (options_.size() != 4)
333  {
334  FatalIOErrorInFunction(parMetisCoeffs)
335  << "Number of options in parMetisCoeffs dictionary : "
336  << decompositionDict.name()
337  << " should be 4, found " << options_
338  << exit(FatalIOError);
339  }
340 
341  Info<< " options: " << options_ << endl;
342  }
343 
344  parMetisCoeffs.readIfPresent("processorWeights_", processorWeights_);
345 
346  Info << endl;
347  }
348 }
349 
350 
351 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
352 
354 (
355  const polyMesh& mesh,
356  const pointField& points,
357  const scalarField& pointWeights
358 )
359 {
360  if (points.size() != mesh.nCells())
361  {
363  << "Can use this decomposition method only for the whole mesh"
364  << endl
365  << "and supply one coordinate (cellCentre) for every cell." << endl
366  << "The number of coordinates " << points.size() << endl
367  << "The number of cells in the mesh " << mesh.nCells()
368  << exit(FatalError);
369  }
370 
371  // Make Metis CSR (Compressed Storage Format) storage
372  // adjncy : contains neighbours (= edges in graph)
373  // xadj(celli) : start of information in adjncy for celli
374  CompactListList<label> cellCells;
375  calcCellCells
376  (
377  mesh,
379  mesh.nCells(),
380  true,
381  cellCells
382  );
383 
384  label nWeights = this->nWeights(points, pointWeights);
385 
386  const labelList intWeights(scaleWeights(pointWeights, nWeights));
387 
388  labelList decomp;
389  decompose
390  (
391  cellCells.offsets(),
392  cellCells.m(),
393  points,
394  nWeights,
395  intWeights,
396  labelList(),
397  decomp
398  );
399 
400  return decomp;
401 }
402 
403 
405 (
406  const polyMesh& mesh,
407  const labelList& cellToRegion,
408  const pointField& regionPoints,
409  const scalarField& pointWeights
410 )
411 {
412  if (cellToRegion.size() != mesh.nCells())
413  {
415  << "Size of cell-to-coarse map " << cellToRegion.size()
416  << " differs from number of cells in mesh " << mesh.nCells()
417  << exit(FatalError);
418  }
419 
420  // Make Metis CSR (Compressed Storage Format) storage
421  // adjncy : contains neighbours (= edges in graph)
422  // xadj(celli) : start of information in adjncy for celli
423  CompactListList<label> cellCells;
424  calcCellCells
425  (
426  mesh,
427  cellToRegion,
428  regionPoints.size(),
429  true,
430  cellCells
431  );
432 
433  label nWeights = this->nWeights(regionPoints, pointWeights);
434  const labelList intWeights(scaleWeights(pointWeights, nWeights));
435 
436  // Decompose using weights
437  labelList decomp;
438  decompose
439  (
440  cellCells.m(),
441  cellCells.offsets(),
442  regionPoints,
443  nWeights,
444  intWeights,
445  labelList(),
446  decomp
447  );
448 
449  // Rework back into decomposition for original mesh
450  labelList fineDistribution(cellToRegion.size());
451 
452  forAll(fineDistribution, i)
453  {
454  fineDistribution[i] = decomp[cellToRegion[i]];
455  }
456 
457  return fineDistribution;
458 }
459 
460 
462 (
463  const labelListList& globalCellCells,
464  const pointField& cellCentres,
465  const scalarField& cellWeights
466 )
467 {
468  if (cellCentres.size() != globalCellCells.size())
469  {
471  << "Inconsistent number of cells (" << globalCellCells.size()
472  << ") and number of cell centres (" << cellCentres.size()
473  << ")." << exit(FatalError);
474  }
475 
476  // Make Metis Distributed CSR (Compressed Storage Format) storage
477  // adjncy : contains neighbours (= edges in graph)
478  // xadj(celli) : start of information in adjncy for celli
479  CompactListList<label> cellCells(globalCellCells);
480 
481  label nWeights = this->nWeights(cellCentres, cellWeights);
482  const labelList intWeights(scaleWeights(cellWeights, nWeights));
483 
484  labelList decomp;
485  decompose
486  (
487  cellCells.offsets(),
488  cellCells.m(),
489  cellCentres,
490  nWeights,
491  intWeights,
492  labelList(),
493  decomp
494  );
495 
496  return decomp;
497 }
498 
499 
500 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
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:311
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:245
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, const dictionary &methodDict)
Construct given the decomposition dictionary.
Definition: parMetis.C:282
const fileName & name() const
Return the dictionary name.
Definition: dictionary.H:111
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
const dictionary & subDict(const word &) const
Find and return a sub-dictionary.
Definition: dictionary.C:849
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:539
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:80
label nCells() const
Foam::fvMesh mesh(Foam::IOobject(regionName, runTime.name(), runTime, Foam::IOobject::MUST_READ), false)
#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:258
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