vtkCloud.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) 2018-2024 OpenCFD Ltd.
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 "vtkCloud.H"
29 #include "Cloud.H"
30 #include "dictionary.H"
31 #include "fvMesh.H"
32 #include "foamVtkOutputOptions.H"
34 #include "pointList.H"
35 #include "stringOps.H"
36 #include <fstream>
37 
38 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
39 
40 namespace Foam
41 {
42 namespace functionObjects
43 {
44  defineTypeNameAndDebug(vtkCloud, 0);
45  addToRunTimeSelectionTable(functionObject, vtkCloud, dictionary);
46 }
47 }
48 
49 
50 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
51 
52 void Foam::functionObjects::vtkCloud::writeVerts
53 (
54  autoPtr<vtk::formatter>& format,
55  const label nTotParcels
56 ) const
57 {
58  // No collectives - can skip on slave processors
59  if (!format) return;
60 
61  // Same payload for connectivity and offsets
62  const uint64_t payLoad = vtk::sizeofData<label>(nTotParcels);
63 
65 
66  //
67  // 'connectivity'
68  // = linear mapping onto points
69  //
70  {
71  format().beginDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY);
72  format().writeSize(payLoad);
73 
74  vtk::writeIdentity(format(), nTotParcels);
75 
76  format().flush();
77  format().endDataArray();
78  }
79 
80  //
81  // 'offsets' (connectivity offsets)
82  // = linear mapping onto points (with 1 offset)
83  //
84  {
85  format().beginDataArray<label>(vtk::dataArrayAttr::OFFSETS);
86  format().writeSize(payLoad);
87 
88  vtk::writeIdentity(format(), nTotParcels, 1);
89 
90  format().flush();
91  format().endDataArray();
92  }
93 
94  format().endTag(vtk::fileTag::VERTS);
95 }
96 
97 
98 bool Foam::functionObjects::vtkCloud::writeCloud
99 (
100  const fileName& file,
101  const word& cloudName
102 )
103 {
104  applyFilter_ = false;
105 
106  const auto* cloudPtr = mesh_.cfindObject<cloud>(cloudName);
107  if (!cloudPtr)
108  {
109  return false;
110  }
111 
112  const auto& currCloud = *cloudPtr;
113 
114  objectRegistry obrTmp
115  (
116  IOobject
117  (
118  "vtk::vtkCloud::" + cloudName,
119  mesh_.time().constant(),
120  mesh_,
124  )
125  );
126 
127  currCloud.writeObjects(obrTmp);
128 
129  const auto* pointsPtr = cloud::findIOPosition(obrTmp);
130 
131  if (!pointsPtr)
132  {
133  // This should be impossible
134  return false;
135  }
136 
137  applyFilter_ = calculateFilter(obrTmp, log);
138  Pstream::reduceOr(applyFilter_);
139 
140 
141  // Number of parcels (locally)
142  const label nParcels
143  (
144  applyFilter_ ? parcelAddr_.count() : pointsPtr->size()
145  );
146 
147  // Total number of parcels on all processes
148  const label nTotParcels = returnReduce(nParcels, sumOp<label>());
149 
150  if (applyFilter_)
151  {
152  // Report filtered/unfiltered count
153  Log << "After filtering using " << nTotParcels << '/'
154  << (returnReduce(pointsPtr->size(), sumOp<label>()))
155  << " parcels" << nl;
156  }
157 
158  if (pruneEmpty_ && !nTotParcels)
159  {
160  return false;
161  }
162 
163  std::ofstream os;
164  autoPtr<vtk::formatter> format;
165 
166  if (!file.has_ext("vtp"))
167  {
169  << type() << " File missing .vtp extension!" << nl << endl
170  << exit(FatalError);
171  }
172 
173  if (UPstream::master())
174  {
175  Foam::mkDir(file.path());
176  os.open(file);
177 
178  format = writeOpts_.newFormatter(os);
179 
180  // beginFile()
181 
182  // XML (inline)
183  format()
184  .xmlHeader()
185  .xmlComment
186  (
187  "case='" + time_.globalCaseName()
188  + "' cloud='" + cloudName
189  + "' time='" + time_.timeName()
190  + "' index='" + Foam::name(time_.timeIndex())
191  + "'"
192  )
193  .beginVTKFile<vtk::fileTag::POLY_DATA>();
194 
195 
196  // FieldData with TimeValue
197  format()
198  .beginFieldData()
199  .writeTimeValue(time_.value())
200  .endFieldData();
201 
202 
203  // writeGeometry()
204 
205  // beginPiece()
206  if (useVerts_)
207  {
208  format()
209  .tag
210  (
212  vtk::fileAttr::NUMBER_OF_POINTS, nTotParcels,
213  vtk::fileAttr::NUMBER_OF_VERTS, nTotParcels
214  );
215  }
216  else
217  {
218  format()
219  .tag
220  (
223  );
224  }
225 
226  // writePoints()
227  {
228  const uint64_t payLoad = vtk::sizeofData<float,3>(nTotParcels);
229 
231  .beginDataArray<float,3>(vtk::dataArrayAttr::POINTS);
232 
233  format().writeSize(payLoad);
234  }
235  }
236 
237 
238  if (applyFilter_)
239  {
240  vtk::writeListParallel(format.ref(), *pointsPtr, parcelAddr_);
241  }
242  else
243  {
244  vtk::writeListParallel(format.ref(), *pointsPtr);
245  }
246 
247 
248  if (UPstream::master())
249  {
250  format().flush();
251  format().endDataArray();
252  format().endTag(vtk::fileTag::POINTS);
253 
254  if (useVerts_)
255  {
256  writeVerts(format, nTotParcels);
257  }
258  }
259 
260 
261  // Prevent any possible conversion of positions as a field
262  obrTmp.filterKeys
263  (
264  [](const word& k)
265  {
266  return k.starts_with("position") || k.starts_with("coordinate");
267  },
268  true // prune
269  );
270 
271  // Restrict to specified fields
272  if (selectFields_.size())
273  {
274  obrTmp.filterKeys(selectFields_);
275  }
276 
277 
278  // Write fields
279 
280  if (UPstream::master())
281  {
282  if (useVerts_)
283  {
284  format().beginCellData();
285  }
286  else
287  {
288  format().beginPointData();
289  }
290  }
291 
292  DynamicList<word> written(obrTmp.size() + currCloud.objectRegistry::size());
293 
294  written.push_back
295  (
296  writeFields<label>(format, obrTmp, nTotParcels)
297  );
298  written.push_back
299  (
300  writeFields<scalar>(format, obrTmp, nTotParcels)
301  );
302  written.push_back
303  (
304  writeFields<vector>(format, obrTmp, nTotParcels)
305  );
306 
307  // Any cloudFunctions results
308  written.push_back
309  (
310  writeFields<scalar>(format, currCloud, nTotParcels)
311  );
312 
313  if (UPstream::master())
314  {
315  if (useVerts_)
316  {
317  format().endCellData();
318  }
319  else
320  {
321  format().endPointData();
322  }
323 
324  format().endPiece();
326  .endVTKFile();
327  }
328 
329 
330  // Record information into the state (all processors)
331  //
332  // foName
333  // {
334  // cloudName
335  // {
336  // file "<case>/postProcessing/name/cloud1_0001.vtp";
337  // fields (U T rho);
338  // }
339  // }
340 
341  // Case-local file name with "<case>" to make relocatable
343  propsDict.add
344  (
345  "file",
346  time_.relativePath(file, true)
347  );
348  propsDict.add("fields", written);
349 
350  setObjectProperty(name(), cloudName, propsDict);
351 
352  return true;
353 }
354 
355 
356 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
357 
358 Foam::functionObjects::vtkCloud::vtkCloud
359 (
360  const word& name,
361  const Time& runTime,
362  const dictionary& dict
363 )
364 :
366  writeOpts_(vtk::formatType::INLINE_BASE64),
367  printf_(),
368  useVerts_(false),
369  pruneEmpty_(false),
370  applyFilter_(false),
371  selectClouds_(),
372  selectFields_(),
373  directory_(),
374  series_()
375 {
376  // May still want this? (OCT-2018)
377  // if (postProcess)
378  // {
379  // // Disable for post-process mode.
380  // // Emit as FatalError for the try/catch in the caller.
381  // FatalError
382  // << type() << " disabled in post-process mode"
383  // << exit(FatalError);
384  // }
386  read(dict);
387 }
388 
389 
390 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
391 
393 {
395 
396  // We probably cannot trust old information after a reread
397  series_.clear();
398 
399  //
400  // Default format is xml base64. Legacy is not desired.
401  //
402  writeOpts_ = vtk::formatType::INLINE_BASE64;
403 
404  writeOpts_.ascii
405  (
408  );
409 
410  writeOpts_.append(false); // No append supported
411  writeOpts_.legacy(false); // No legacy supported
412 
413  writeOpts_.precision
414  (
415  dict.getOrDefault("precision", IOstream::defaultPrecision())
416  );
417 
418  // Info<< type() << " " << name() << " output-format: "
419  // << writeOpts_.description() << nl;
420 
421  const int padWidth = dict.getOrDefault<int>("width", 8);
422 
423  // Appropriate printf format - Enforce min/max sanity limits
424  if (padWidth < 1 || padWidth > 31)
425  {
426  printf_.clear();
427  }
428  else
429  {
430  printf_ = "%0" + std::to_string(padWidth) + "d";
431  }
432 
433  // useTimeName_ = dict.getOrDefault("useTimeName", false);
434 
435  useVerts_ = dict.getOrDefault("cellData", false);
436  pruneEmpty_ = dict.getOrDefault("prune", false);
437 
438  selectClouds_.clear();
439  dict.readIfPresent("clouds", selectClouds_);
440  selectClouds_.uniq();
441 
442  if (selectClouds_.empty())
443  {
444  word cloudName;
445  if (dict.readIfPresent("cloud", cloudName))
446  {
447  selectClouds_.push_back(std::move(cloudName));
448  }
449  }
450 
451  selectFields_.clear();
452  dict.readIfPresent("fields", selectFields_);
453  selectFields_.uniq();
454 
455  // Actions to define selection
456  parcelSelect_ = dict.subOrEmptyDict("selection");
457 
458  // Output directory
459 
460  directory_.clear();
461  dict.readIfPresent("directory", directory_);
462 
463  if (directory_.size())
464  {
465  // User-defined output directory
466  directory_.expand();
467  if (!directory_.isAbsolute())
468  {
469  directory_ = time_.globalPath()/directory_;
470  }
471  }
472  else
473  {
474  // Standard postProcessing/ naming
475  directory_ = time_.globalPath()/functionObject::outputPrefix/name();
476  }
477  directory_.clean(); // Remove unneeded ".."
478 
479  return true;
480 }
481 
484 {
485  return true;
486 }
487 
488 
490 {
491  const wordList cloudNames
492  (
493  selectClouds_.empty()
494  ? mesh_.sortedNames<cloud>()
495  : mesh_.sortedNames<cloud>(selectClouds_)
496  );
497 
498  if (cloudNames.empty())
499  {
500  return true; // skip - not available
501  }
502 
503  const scalar timeValue = time_.value();
504 
505  const word timeDesc = "_" +
506  (
507  printf_.empty()
508  ? Foam::name(time_.timeIndex())
509  : word::printf(printf_, time_.timeIndex())
510  );
511 
512  Log << name() << " output Time: " << time_.timeName() << nl;
513 
514  // Each cloud separately
515  for (const word& cloudName : cloudNames)
516  {
517  // Legacy is not to be supported
518 
519  const fileName outputName
520  (
521  directory_/cloudName + timeDesc + ".vtp"
522  );
523 
524  // writeCloud() includes mkDir (on master)
525 
526  if (writeCloud(outputName, cloudName))
527  {
528  Log << " cloud : "
529  << time_.relativePath(outputName) << endl;
530 
531  if (UPstream::master())
532  {
533  // Add to file-series and emit as JSON
535 
536  vtk::seriesWriter& series = series_(seriesName);
537 
538  // First time?
539  // Load from file, verify against filesystem,
540  // prune time >= currentTime
541  if (series.empty())
542  {
543  series.load(seriesName, true, timeValue);
544  }
545 
546  series.append(timeValue, outputName);
547  series.write(seriesName);
548  }
549  }
550  }
551 
552  return true;
553 }
554 
555 
556 // ************************************************************************* //
static const IOField< point > * findIOPosition(const objectRegistry &obr)
Locate the "position" IOField within object registry.
Definition: cloud.H:188
dictionary dict
defineTypeNameAndDebug(ObukhovLength, 0)
A class for handling file names.
Definition: fileName.H:72
static fileName base(const fileName &outputName, char sep='_')
Extract the base name for a file series.
dimensionedScalar log(const dimensionedScalar &ds)
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:129
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:608
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
"ascii" (normal default)
engineTime & runTime
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
static unsigned int defaultPrecision() noexcept
Return the default precision.
Definition: IOstream.H:418
static void reduceOr(bool &value, const label communicator=worldComm)
Logical (or) reduction (MPI_AllReduce)
label k
Boltzmann constant.
Ignore writing from objectRegistry::writeObject()
T returnReduce(const T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Perform reduction on a copy, using specified binary operation.
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:69
Macros for easy insertion into run-time selection tables.
IOdictionary propsDict(dictIO)
fileName::Type type(const fileName &name, const bool followLink=true)
Return the file type: DIRECTORY or FILE, normally following symbolic links.
Definition: POSIX.C:799
virtual bool execute()
Execute, currently does nothing.
Definition: vtkCloud.C:476
word outputName("finiteArea-edges.obj")
bool mkDir(const fileName &pathName, mode_t mode=0777)
Make a directory and return an error if it could not be created.
Definition: POSIX.C:614
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
virtual bool read(const dictionary &dict)
Read the vtkCloud specification.
Definition: vtkCloud.C:385
Provides a means of accumulating and generating VTK file series.
const word cloudName(propsDict.get< word >("cloud"))
A class for handling words, derived from Foam::string.
Definition: word.H:63
void writeIdentity(vtk::formatter &fmt, const label len, label start=0)
Write an identity list of labels.
Definition: foamVtkOutput.C:89
A cloud is a registry collection of lagrangian particles.
Definition: cloud.H:53
wordList sortedNames() const
The sorted names of all objects.
formatType
The output format type for file contents.
Definition: foamVtkCore.H:66
virtual bool write()
Write fields.
Definition: vtkCloud.C:482
static word printf(const char *fmt, const PrimitiveType &val)
Use a printf-style formatter for a primitive.
OBJstream os(runTime.globalPath()/outputName)
bool empty() const noexcept
True if there are no data sets.
addToRunTimeSelectionTable(functionObject, ObukhovLength, dictionary)
word format(conversionProperties.get< word >("format"))
static streamFormat formatEnum(const word &fmtName, const streamFormat deflt=streamFormat::ASCII)
Lookup streamFormat enum corresponding to the string (ascii | binary).
static void write(const fileName &base, const UList< instant > &series, const char sep='_')
Write file series (JSON format) to disk, for specified instances.
static bool master(const label communicator=worldComm)
True if process corresponds to the master rank in the communicator.
Definition: UPstream.H:1094
Nothing to be read.
#define Log
Definition: PDRblock.C:28
static word outputPrefix
Directory prefix.
virtual bool read(const dictionary &dict)
Read optional controls.
void writeListParallel(vtk::formatter &fmt, const UList< Type > &values)
Write a list of values.
label load(const fileName &seriesName, const bool checkFiles=false, const scalar restartTime=ROOTVGREAT)
Clear contents and reload by parsing the specified file.
XML inline base64, base64Formatter.
Specialization of Foam::functionObject for an Foam::fvMesh, providing a reference to the Foam::fvMesh...
Do not request registration (bool: false)
Namespace for OpenFOAM.
bool append(const fileNameInstant &inst)
Append the specified file instant.