agglomerationInfo.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) 2026 Keysight Technologies
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "agglomerationInfo.H"
30 #include "Pstream.H"
31 #include "fvMesh.H"
32 #include "volFields.H"
33 #include "GAMGSolver.H"
34 
35 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
36 
37 namespace Foam
38 {
39 namespace functionObjects
40 {
41  defineTypeNameAndDebug(agglomerationInfo, 0);
42 
44  (
45  functionObject,
46  agglomerationInfo,
47  dictionary
48  );
49 }
50 }
51 
52 
53 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
54 
56 Foam::functionObjects::agglomerationInfo::agglomeration() const
57 {
58  const auto* agglomPtr = mesh_.cfindObject<GAMGAgglomeration>(agglomName_);
59 
60  if (!agglomPtr)
61  {
63  << "GAMGAgglomeration '" << agglomName_ << "' not found. "
64  << "Set cacheAgglomeration true for field " << fieldName_
65  << " in fvSolution"
66  << exit(FatalError);
67  }
68 
69  return *agglomPtr;
70 }
71 
72 
73 void Foam::functionObjects::agglomerationInfo::writeLevel0()
74 {
75  // Level 0: original fine (un-coarsened) mesh
76  // Note: each cell maps to its own globally-unique index
77  // Values are just the per-proc cell counts
78  const label statsComm = mesh_.comm();
79  labelList allNCells0(UPstream::nProcs(statsComm), Zero);
80  allNCells0[UPstream::myProcNo(statsComm)] = mesh_.nCells();
81  Pstream::gatherList(allNCells0, UPstream::msgType(), statsComm);
82  Pstream::broadcast(allNCells0, statsComm);
83 
84  label total0 = 0;
85  label min0 = labelMax;
86  label max0 = 0;
87  label nActive0 = 0;
88  for (label n : allNCells0)
89  {
90  if (n >= 0)
91  {
92  total0 += n;
93  min0 = min(min0, n);
94  max0 = max(max0, n);
95  ++nActive0;
96  }
97  }
98  const scalar avg0 = nActive0 > 0 ? scalar(total0)/nActive0 : 0;
99 
100  // Globally-unique cell ID = proc prefix-sum offset + local index.
101  label myOffseti0 = 0;
102  for (label p = 0; p < UPstream::myProcNo(statsComm); ++p)
103  {
104  myOffseti0 += allNCells0[p];
105  }
106 
107  const word writtenName0 = "GAMGAgglom_" + fieldName_ + "_level0_mesh";
108 
109  volScalarField fineMesh
110  (
111  IOobject
112  (
113  writtenName0,
114  mesh_.time().timeName(),
115  mesh_,
119  ),
120  mesh_,
122  );
123 
124  forAll(fineMesh, celli)
125  {
126  fineMesh[celli] = scalar(myOffseti0 + celli);
127  }
128 
129  const label nP0 = label(allNCells0.size());
130  const word assignedStr0 = Foam::name(nP0) + " proc" + (nP0 != 1 ? "s" : "");
131 
132  auto& os = file();
133 
134  os << "0" << tab
135  << total0 << tab
136  << min0 << tab
137  << label(avg0) << tab
138  << max0 << tab
139  << assignedStr0 << tab
140  << writtenName0 << tab
141  << nl;
142 
143  fineMesh.write();
144 }
145 
146 
147 void Foam::functionObjects::agglomerationInfo::setCellsPerLevel
148 (
149  const GAMGAgglomeration& agglom,
150  const label leveli,
151  const label& procAgglomLeveli,
152  labelField& cellsPerLevel
153 ) const
154 {
155  if (leveli == 0) return;
156 
157  if (agglom.hasProcMesh(leveli))
158  {
159  // Processor agglomeration
160  // Note: master holds the combined restrict addressing for all
161  // processors' cells. Non-master processors had their level data
162  // cleared
163  const label comm = agglom.agglomCommunicator(leveli);
164  const label nPart = UPstream::nProcs(comm);
165  const label myRank = UPstream::myProcNo(comm);
166 
167  // Step 1: gather per-proc fine-cell counts within group
168  labelList fineSizes(nPart, Zero);
169  fineSizes[myRank] = agglom.nCells(leveli - 1);
170  Pstream::gatherList(fineSizes, UPstream::msgType(), comm);
171  Pstream::broadcast(fineSizes, comm);
172 
173  // Compute this proc offset into combined addr
174  label myOffseti = 0;
175  for (label p = 0; p < myRank; ++p)
176  {
177  myOffseti += fineSizes[p];
178  }
179 
180  // Step 2: broadcast combined addr from master
181  labelField combinedAddr;
182 
183  if (UPstream::myProcNo(comm) == 0) // master in agglomComm
184  {
185  combinedAddr = agglom.restrictAddressing(leveli);
186  }
187  Pstream::broadcast(combinedAddr, comm);
188 
189  // Local fine coarse index -> group-local combined coarse index
190  labelField next(cellsPerLevel.size());
191  forAll(next, celli)
192  {
193  next[celli] = combinedAddr[myOffseti + cellsPerLevel[celli]];
194  }
195  cellsPerLevel.transfer(next);
196  }
197  else if (procAgglomLeveli >= 0 && leveli > procAgglomLeveli)
198  {
199  // Post-proc-agglomeration level
200  // These levels were further coarsened on master only after
201  // proc agglomeration; restrict addressing is null on
202  // non-masters
203  const label comm = agglom.agglomCommunicator(procAgglomLeveli);
204  labelField addr;
205  if (UPstream::myProcNo(comm) == 0) // master in agglomComm
206  {
207  addr = agglom.restrictAddressing(leveli);
208  }
209  Pstream::broadcast(addr, comm);
210 
211  for (auto& addri : cellsPerLevel)
212  {
213  addri = addr[addri];
214  }
215  }
216  else
217  {
218  // Normal level, no proc agglom crossed yet
219  const labelField& addr = agglom.restrictAddressing(leveli);
220 
221  for (auto& addri : cellsPerLevel)
222  {
223  addri = addr[addri];
224  }
225  }
226 }
227 
228 
230 Foam::functionObjects::agglomerationInfo::initAgglomField
231 (
232  const GAMGAgglomeration& agglom,
233  const label leveli,
234  const label procAgglomLeveli
235 ) const
236 {
237  // Note:
238  // - Coarsest level gets "_coarsest" suffix
239  // - Append "_procAgglom" is added if proc-agglom was active
240  word levelSuffix = "";
241 
242  if (leveli == agglom.size() - 1)
243  {
244  levelSuffix = "_coarsest";
245  if (procAgglomLeveli >= 0)
246  {
247  levelSuffix += "_procAgglom";
248  }
249  }
250 
251  const word writtenName =
252  "GAMGAgglom_" + fieldName_
253  + "_level" + Foam::name(leveli + 1)
254  + levelSuffix;
255 
256  // Actual agglomeration field per level
258  (
259  IOobject
260  (
261  writtenName,
262  mesh_.time().timeName(),
263  mesh_,
267  ),
268  mesh_,
270  );
271 }
272 
273 
274 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
275 
277 (
278  const word& name,
279  const Time& runTime,
280  const dictionary& dict
281 )
282 :
284  writeFile(runTime, name, typeName, dict),
285  fieldName_(),
286  agglomName_()
287 {
288  read(dict);
289 }
290 
291 
292 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
293 
295 {
297  {
298  fieldName_ = dict.get<word>("field");
299 
300  const auto& solverDict = mesh_.solution().solver(fieldName_);
301 
302  const word solverType = solverDict.get<word>("solver");
303 
304  if (solverType != GAMGSolver::typeName)
305  {
307  << "Solver for field '" << fieldName_ << "' is not a "
308  << GAMGSolver::typeName << " solver. "
309  << "Agglomeration info is only available for GAMG."
310  << exit(FatalIOError);
311  }
312 
313  agglomName_ =
314  solverDict.getOrDefault<word>("name", GAMGAgglomeration::typeName);
315 
316  return true;
317  }
318 
319  return false;
320 }
321 
322 
324 {
325  Log << type() << " " << name() << ":" << nl;
326 
327  auto& os = file();
328 
329  const GAMGAgglomeration& agglom = this->agglomeration();
330 
331  // Number of agglomeration levels
332  const label nLevels = agglom.size();
333 
334  if (nLevels == 0)
335  {
337  << "GAMGAgglomeration '" << agglomName_ << "' has no levels. "
338  << "Nothing to write." << nl;
339 
340  return true;
341  }
342 
343  // Find the proc-agglomeration level (if it exists)
344  label procAgglomLeveli = -1;
345  for (label leveli = 0; leveli < nLevels; ++leveli)
346  {
347  if (agglom.hasProcMesh(leveli))
348  {
349  procAgglomLeveli = leveli;
350  break;
351  }
352  }
353 
354  writeHeaderValue(os, "name", name());
355  writeHeaderValue(os, "time", mesh_.time().timeName());
356  writeHeaderValue(os, "field", fieldName_);
357  writeHeaderValue(os, "levels", nLevels+1);
358 
359  if (procAgglomLeveli >= 0)
360  {
361  writeHeaderValue(os, "proc-agglom@level", procAgglomLeveli+1);
362  }
363 
364  writeCommented(os, "level");
365  writeTabbed(os, "total");
366  writeTabbed(os, "min");
367  writeTabbed(os, "avg");
368  writeTabbed(os, "max");
369  writeTabbed(os, "assigned");
370  writeTabbed(os, "field");
371  os << nl;
372 
373  writeLevel0();
374 
375  // Initialise 'cellsPerLevel' with cell labels for agglomeration level 0
376  // Note: always has size == mesh_.nCells()
377  labelField cellsPerLevel(agglom.restrictAddressing(0));
378 
379  // Unified stats gather over the full mesh communicator
380  // - Pre-agglom: all procs have valid nCells.
381  // - Proc-agglom / post-agglom: masters have valid nCells, non-masters
382  // have n < 0
383  const label statsComm = mesh_.comm();
384 
385  for (label leveli = 0; leveli < nLevels; ++leveli)
386  {
387  setCellsPerLevel(agglom, leveli, procAgglomLeveli, cellsPerLevel);
388 
389  labelList allNCells(UPstream::nProcs(statsComm), Zero);
390  allNCells[UPstream::myProcNo(statsComm)] = agglom.nCells(leveli);
391 
392  Pstream::gatherList(allNCells, UPstream::msgType(), statsComm);
393  Pstream::broadcast(allNCells, statsComm);
394  label displayNCells = 0;
395  label minNCells = labelMax;
396  label maxNCells = 0;
397  label nActive = 0;
398 
399  for (label n : allNCells)
400  {
401  if (n >= 0)
402  {
403  displayNCells += n;
404  minNCells = min(minNCells, n);
405  maxNCells = max(maxNCells, n);
406 
407  ++nActive;
408  }
409  }
410 
411  const scalar avgNCells =
412  nActive > 0 ? scalar(displayNCells)/nActive : 0;
413 
414  // For pre-proc-agglom levels, cellsPerLevel holds proc-local
415  // 0-based cluster indices. Add per-proc rank offset to produce
416  // globally unique indices for visualisation after reconstruction
417 
418  labelField writeField(cellsPerLevel);
419 
420  if (procAgglomLeveli < 0)
421  {
422  // Pre-agglom: offset by count of cells on lower-ranked procs.
423  label myOffseti = 0;
424  for (label p = 0; p < UPstream::myProcNo(statsComm); ++p)
425  {
426  myOffseti += allNCells[p];
427  }
428 
429  if (myOffseti > 0)
430  {
431  for (auto& wf : writeField)
432  {
433  wf += myOffseti;
434  }
435  }
436  }
437  else
438  {
439  // Post-proc-agglom: coarse IDs are group-local; add a per-
440  // group prefix-sum offset so that IDs are globally unique.
441  // agglomProcIDs(procAgglomLeveli)[0] is the world rank of this
442  // group's master - used to index into allNCells (rank-ordered).
443  const label myMasterRank =
444  agglom.agglomProcIDs(procAgglomLeveli)[0];
445  label groupOffseti = 0;
446  for (label p = 0; p < myMasterRank; ++p)
447  {
448  if (allNCells[p] >= 0) // valid master entry
449  {
450  groupOffseti += allNCells[p];
451  }
452  }
453  if (groupOffseti > 0)
454  {
455  for (auto& wf : writeField)
456  {
457  wf += groupOffseti;
458  }
459  }
460  }
461 
462  auto tagglomField = initAgglomField(agglom, leveli, procAgglomLeveli);
463  auto& agglomField = tagglomField.ref();
464 
465  // Fill agglomeration field with globally-unique coarse cluster ids
466  forAll(agglomField, celli)
467  {
468  agglomField[celli] = scalar(writeField[celli]);
469  }
470 
471  // "assigned" column: which procs/masters are active at this level
472  // - pre-agglom : "N_procs"
473  // - proc-agglom : "N_procs-M_masters"
474  // - post-agglom : "M_masters"
475 
476  const word levelStr = Foam::name(leveli + 1);
477  word assignedStr;
478 
479  if (procAgglomLeveli < 0 || leveli < procAgglomLeveli)
480  {
481  const label nP = label(allNCells.size());
482  assignedStr = Foam::name(nP) + "_procs";
483  }
484  else if (leveli == procAgglomLeveli)
485  {
486  assignedStr =
487  Foam::name(allNCells.size()) + "_procs-"
488  + Foam::name(nActive) + "_masters";
489  }
490  else
491  {
492  assignedStr = Foam::name(nActive) + "_masters";
493  }
494 
495  os << levelStr << tab
496  << displayNCells << tab
497  << minNCells << tab
498  << label(avgNCells) << tab
499  << maxNCells << tab
500  << assignedStr << tab
501  << agglomField.name() << tab
502  << (leveli == nLevels - 1 ? "\n\n" : "\n");
503 
504  Info<< " Writing agglomeration field for level " << leveli + 1
505  << " : " << agglomField.name() << endl;
506 
507  agglomField.write();
508  }
509 
510  Info<< " Written file: " << os.name() << nl << endl;
511 
512  return true;
513 }
514 
515 
516 // ************************************************************************* //
bool hasProcMesh(const label fineLeveli) const
Check that level has combined mesh.
dictionary dict
Field< label > labelField
Specialisation of Field<T> for label.
Definition: labelField.H:48
defineTypeNameAndDebug(ObukhovLength, 0)
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
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:130
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:652
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
engineTime & runTime
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:518
static int myProcNo(label communicator=worldComm)
Rank of this process in the communicator (starting from masterNo()). Negative if the process is not a...
Definition: UPstream.H:1799
virtual bool read(const dictionary &dict)
Read the function-object dictionary.
constexpr char tab
The tab &#39;\t&#39; character(0x09)
Definition: Ostream.H:49
static int & msgType() noexcept
Message tag of standard messages.
Definition: UPstream.H:2019
static void broadcast(Type &value, const int communicator=UPstream::worldComm)
Broadcast content (contiguous or non-contiguous) to all communicator ranks. Does nothing in non-paral...
Ignore writing from objectRegistry::writeObject()
const dimensionSet dimless
Dimensionless.
bool writeField(ensightOutput::floatBufferType &scratch, ensightFile &os, const Field< Type > &fld, const ensightCells &part, bool parallel)
Write a field of cell values as an indirect list, using the cell ids from ensightCells.
const Type * cfindObject(const word &name, const bool recursive=false) const
Return const pointer to the object of the given Type.
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:68
Macros for easy insertion into run-time selection tables.
static void gatherList(UList< T > &values, const int tag=UPstream::msgType(), const int communicator=UPstream::worldComm)
Gather data, but keep individual values separate.
bool read(const char *buf, int32_t &val)
Same as readInt32.
Definition: int32.H:127
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:400
GeometricField< scalar, fvPatchField, volMesh > volScalarField
Definition: volFieldsFwd.H:72
fileName::Type type(const fileName &name, const bool followLink=true)
Return the file type: DIRECTORY or FILE, normally following symbolic links.
Definition: POSIX.C:805
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
A class for handling words, derived from Foam::string.
Definition: word.H:63
virtual bool write()
Write the function-object results.
const labelList & agglomProcIDs(const label fineLeveli) const
Set of processors to agglomerate. Element 0 is the master processor. (local, same only on those proce...
static tmp< T > New(Args &&... args)
Construct tmp with forwarding arguments.
Definition: tmp.H:217
virtual bool write(const token &tok)=0
Write token to stream or otherwise handle it.
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
addToRunTimeSelectionTable(functionObject, ObukhovLength, dictionary)
virtual bool read(const dictionary &dict)
Read.
Definition: writeFile.C:239
agglomerationInfo(const agglomerationInfo &)=delete
No copy construct.
static label nProcs(label communicator=worldComm)
Number of ranks in parallel run (for given communicator). It is 1 for serial run. ...
Definition: UPstream.H:1790
#define WarningInFunction
Report a warning using Foam::Warning.
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:681
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
Nothing to be read.
#define Log
Definition: PDRblock.C:28
const labelField & restrictAddressing(const label leveli) const
Return cell restrict addressing of given level.
messageStream Info
Information stream (stdout output on master, null elsewhere)
constexpr label labelMax
Definition: label.H:55
virtual bool read(const dictionary &dict)
Read optional controls.
label n
Specialization of Foam::functionObject for an Foam::fvMesh, providing a reference to the Foam::fvMesh...
List< label > labelList
A List of labels.
Definition: List.H:61
volScalarField & p
A class for managing temporary objects.
Definition: HashPtrTable.H:50
Base class for writing single files from the function objects.
Definition: writeFile.H:112
Geometric agglomerated algebraic multigrid agglomeration class.
const fvMesh & mesh_
Reference to the fvMesh.
Do not request registration (bool: false)
label nCells(const label leveli) const
Return number of coarse cells (before processor agglomeration)
Namespace for OpenFOAM.
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127