foamVtkSeriesWriter.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-2023 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 "foamVtkSeriesWriter.H"
29 #include "Fstream.H"
30 #include "ListOps.H"
31 #include "stringOpsSort.H"
32 #include "OSspecific.H"
33 
34 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
35 
36 namespace Foam
37 {
38  // Get any single token.
39  static inline bool getToken(ISstream& is, token& tok)
40  {
41  return (!is.read(tok).bad() && tok.good());
42  }
43 
44  // Get two tokens.
45  // The first one must be a ':' token, the second one is any value
46  //
47  // This corrsponds to the JSON "key" : value syntax,
48  // we trigger after reading the "key".
49  static inline bool getValueToken(ISstream& is, token& tok)
50  {
51  return
52  (
53  // Token 1 = ':' separator
54  (getToken(is, tok) && tok.isPunctuation(token::COLON))
55 
56  // Token 2 is the value
57  && getToken(is, tok)
58  );
59  }
60 
61 
62  // Sorting for fileNameInstant
63  // 1. sort by value (time)
64  // 2. natural sort (name)
65  struct seriesLess
66  {
67  bool operator()(const fileNameInstant a, const fileNameInstant b) const
68  {
69  scalar val = compareOp<scalar>()(a.value(), b.value());
70  if (val == 0)
71  {
72  return
74  }
75  return val < 0;
76  }
77  };
78 
79 
80  // Check if value is less than upper, with some tolerance.
81  static inline bool lessThan(const scalar& val, const scalar& upper)
82  {
83  return (val < upper && Foam::mag(val - upper) > ROOTVSMALL);
84  }
85 }
86 
87 
88 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
89 
91 (
92  const fileName& outputName,
93  char sep
94 )
95 {
96  const auto dash = outputName.rfind(sep);
97 
98  // No separator? Or separator in path() instead of name()?
99  if
100  (
101  std::string::npos == dash
102  || std::string::npos != outputName.find('/', dash)
103  )
104  {
105  // Warn?
106  return outputName;
107  }
108 
109  const auto dot = outputName.find('.', dash);
110 
111  if (std::string::npos == dot)
112  {
113  return outputName.substr(0, dash);
114  }
115 
116  return outputName.substr(0, dash) + outputName.substr(dot);
117 }
118 
119 
121 (
122  const fileName& file,
123  char sep
124 )
125 {
126  const auto dash = file.rfind(sep);
127 
128  // No separator? Or separator in path() instead of name()?
129  if
130  (
131  std::string::npos == dash
132  || std::string::npos != file.find('/', dash)
133  )
134  {
135  // Warn?
136  return "";
137  }
138 
139  const auto dot = file.find('.', dash);
140 
141  if (std::string::npos == dot)
142  {
143  return file.substr(dash);
144  }
145 
146  return file.substr(dash, (dot-dash));
147 }
148 
149 
151 (
152  Ostream& os,
153  const fileName& base,
154  const UList<instant>& series,
155  const char sep
156 )
157 {
158  // Split the base into (stem, ext) components
159  //
160  // base = "path/file.vtm"
161  //
162  // stem = "file"
163  // ext = ".vtm"
164 
165  const word stem = base.stem();
166  const word ext = "." + base.ext();
167 
168  // Begin file-series (JSON)
169  os << "{\n \"file-series-version\" : \"1.0\",\n \"files\" : [\n";
170 
171  // Track how many entries are remaining
172  // - trailing commas on all but the final entry (JSON requirement)
173  label nremain = series.size();
174 
175  // Each entry
176  // { "name" : "<stem><sep>name<ext>", "time" : value }
177 
178  for (const instant& inst : series)
179  {
180  os << " { \"name\" : \""
181  << stem << sep << inst.name() << ext
182  << "\", \"time\" : " << inst.value() << " }";
183 
184  if (--nremain)
185  {
186  os << ',';
187  }
188  os << nl;
189  }
190 
191  os << " ]\n}\n";
192 
193  return os;
194 }
195 
196 
198 (
199  Ostream& os,
200  const UList<fileNameInstant>& series
201 )
202 {
203  // Begin file-series (JSON)
204  os << "{\n \"file-series-version\" : \"1.0\",\n \"files\" : [\n";
205 
206  // Track how many entries are remaining
207  // - trailing commas on all but the final entry (JSON requirement)
208  label nremain = series.size();
209 
210  // Each entry
211  // { "name" : "<file>", "time" : <value> }
212 
213  for (const fileNameInstant& inst : series)
214  {
215  os << " { \"name\" : \""
216  << inst.name().name()
217  << "\", \"time\" : " << inst.value() << " }";
218 
219  if (--nremain)
220  {
221  os << ',';
222  }
223  os << nl;
224  }
225 
226  os << " ]\n}\n";
227 
228  return os;
229 }
230 
231 
233 (
234  const fileName& seriesName,
235  const UList<instant>& series,
236  const char sep
237 )
238 {
239  mkDir(seriesName.path());
240 
241  autoPtr<OFstream> osPtr =
242  (
243  seriesName.has_ext("series")
244  ? autoPtr<OFstream>::New(seriesName)
245  : autoPtr<OFstream>::New(seriesName + ".series")
246  );
248  print(*osPtr, seriesName, series, sep);
249 }
250 
251 
252 
254 (
255  const fileName& seriesName,
256  const UList<fileNameInstant>& series
257 )
258 {
259  mkDir(seriesName.path());
260 
261  autoPtr<OFstream> osPtr =
262  (
263  seriesName.has_ext("series")
264  ? autoPtr<OFstream>::New(seriesName)
265  : autoPtr<OFstream>::New(seriesName + ".series")
266  );
267 
268  print(*osPtr, series);
269 }
270 
271 
272 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
273 
274 bool Foam::vtk::seriesWriter::appendCheck(fileNameInstant inst)
275 {
276  if (inst.name().empty())
277  {
278  return false;
279  }
280 
281  const auto iter = existing_.find(inst.name());
282 
283  if (iter.good())
284  {
285  for (fileNameInstant& dst : entries_)
286  {
287  if (dst.name() == inst.name())
288  {
289  // Replace value
290  dst.value() = inst.value();
291  return true;
292  }
293  }
294  }
295 
296  entries_.append(inst);
297  existing_.insert(inst.name());
298 
299  return true;
300 }
301 
302 
303 bool Foam::vtk::seriesWriter::removeDuplicates()
304 {
305  const label nElem = entries_.size();
306 
307  HashTable<label, fileName> filesSeen(2*nElem);
308 
309  bool changed = false;
310 
311  for (label elemi=0; elemi < nElem; ++elemi)
312  {
313  fileNameInstant& inst = entries_[elemi];
314 
315  if (inst.name().empty())
316  {
317  changed = true;
318  }
319  else
320  {
321  auto iter = filesSeen.find(inst.name());
322 
323  if (iter.good())
324  {
325  // Mark previous location as being superseded
326  entries_[*iter].name().clear();
327  changed = true;
328 
329  *iter = elemi; // The latest with this name
330  }
331  else
332  {
333  filesSeen.insert(inst.name(), elemi);
334  }
335  }
336  }
337 
338 
339  if (changed)
340  {
341  label dsti = 0;
342  for (label elemi=0; elemi < nElem; ++elemi)
343  {
344  fileNameInstant& src = entries_[elemi];
345 
346  if (!src.name().empty())
347  {
348  if (dsti != elemi)
349  {
350  entries_[dsti] = std::move(src);
351  }
352  ++dsti;
353  }
354  }
355 
356  entries_.resize(dsti);
357  }
358 
359  return (nElem != entries_.size());
360 }
361 
362 
363 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
364 
366 (
367  const fileName& seriesName,
368  const bool checkFiles,
369  const scalar restartTime
370 )
371 {
372  clear();
373 
374  fileName seriesFile(seriesName);
375  if (!seriesFile.has_ext("series"))
376  {
377  seriesFile.ext("series");
378  }
379 
380  if (!isFile(seriesFile))
381  {
382  return size();
383  }
384 
385  HashSet<fileName> filesOnDisk;
386 
387  if (checkFiles)
388  {
389  filesOnDisk.insert(Foam::readDir(seriesFile.path()));
390  }
391 
392 
393  // Parse JSON content:
394  //
395  // {
396  // "file-series-version" : "1.0",
397  // "files" : [
398  // { "name" : "abc", "time" : 123 },
399  // { "name" : "def", "time" : 345 }
400  // ]
401  // }
402 
403  // Parsing states
404  enum parse
405  {
406  NONE, // Looking for "files"
407  FILES_ARRAY, // Saw "file" : '['
408  ENTRY, // Parsing in { "name" : ... }
409  DONE, // Saw a ']' while in FILES_ARRAY
410  FAIL // Something bad happened
411  };
412 
413  // Track if "file" and "time" keys have been located
414  unsigned instStatus = 0;
415  fileNameInstant inst;
416 
417  token tok;
418 
419  IFstream is(seriesFile);
420 
421  for
422  (
423  parse state = parse::NONE;
424  (state != parse::DONE && state != parse::FAIL)
425  && getToken(is, tok);
426  /*nil*/
427  )
428  {
429  switch (state)
430  {
431  // Still scanning for initial "files" entry
432  case parse::NONE :
433  {
434  if (tok.isString() && tok.stringToken() == "files")
435  {
436  // Expect "files" : [ ...
437 
438  if
439  (
440  getValueToken(is, tok)
441  && tok.isPunctuation(token::BEGIN_SQR)
442  )
443  {
444  state = parse::FILES_ARRAY;
445  }
446  else
447  {
448  state = parse::FAIL;
449  }
450  }
451  }
452  break;
453 
454  // Parsing entries within "files" array
455  case parse::FILES_ARRAY :
456  {
457  if (tok.isPunctuation())
458  {
459  switch (tok.pToken())
460  {
461  // ',' - keep going (another entry)
462  case token::COMMA :
463  break;
464 
465  // '{' - begin entry
466  case token::BEGIN_BLOCK :
467  state = parse::ENTRY;
468  instStatus = 0;
469  break;
470 
471  // ']' - done array
472  case token::END_SQR :
473  state = parse::DONE;
474  break;
475 
476  default:
477  state = parse::FAIL;
478  break;
479  }
480  }
481  else
482  {
483  state = parse::FAIL;
484  }
485  }
486  break;
487 
488  // Parsing an individual entry within "files"
489  case parse::ENTRY :
490  {
491  if (tok.isPunctuation())
492  {
493  switch (tok.pToken())
494  {
495  // ',' - keep going (another key/value pair)
496  case token::COMMA :
497  break;
498 
499  // '}'
500  case token::END_BLOCK :
501  {
502  // Verify instant was properly parsed and
503  // is also valid
504  if
505  (
506  instStatus == 0x03
507  && lessThan(inst.value(), restartTime)
508  &&
509  (
510  checkFiles
511  ? filesOnDisk.found(inst.name())
512  : true
513  )
514  )
515  {
516  appendCheck(inst);
517  }
518 
519  state = parse::FILES_ARRAY;
520  instStatus = 0;
521  }
522  break;
523 
524  default:
525  state = parse::FAIL;
526  break;
527  }
528  }
529  else if (tok.isString())
530  {
531  // Expect "key" : value
532 
533  const string key(tok.stringToken());
534 
535  if (getValueToken(is, tok))
536  {
537  if ("name" == key)
538  {
539  if (tok.isString())
540  {
541  inst.name() = tok.stringToken();
542  instStatus |= 0x01;
543  }
544  else
545  {
546  state = parse::FAIL;
547  }
548  }
549  else if ("time" == key)
550  {
551  if (tok.isNumber())
552  {
553  inst.value() = tok.number();
554  instStatus |= 0x02;
555  }
556  else
557  {
558  state = parse::FAIL;
559  }
560  }
561  }
562  else
563  {
564  state = parse::FAIL;
565  }
566  }
567  else
568  {
569  state = parse::FAIL;
570  }
571  }
572  break;
573 
574  default:
575  break;
576  }
577  }
578 
579  return size();
580 }
581 
582 
584 (
585  const fileName& seriesName,
586  const scalar restartTime
587 )
588 {
589  clear();
590 
591  const fileName path = seriesName.path();
592 
593  if (!isDir(path))
594  {
595  return size();
596  }
597 
598  fileName seriesFile(seriesName);
599 
600  if (seriesName.has_ext("series"))
601  {
602  seriesFile.remove_ext();
603  }
604 
605  const word stem = seriesFile.stem();
606  const word ext = seriesFile.ext();
607 
608  // Accept "fileN.ext", "fileNN.ext", but reject "file.ext"
609  const auto minLen = stem.length() + ext.length() + 1;
610 
611  const auto acceptName =
612  [=](const fileName& file) -> bool
613  {
614  return
615  (
616  minLen < file.length()
617  && file.has_ext(ext) && file.starts_with(stem)
618  );
619  };
620 
621 
622  fileNameList files = subsetList(Foam::readDir(path), acceptName);
623 
624  // Names sorted so warnings appear less random
625  Foam::sort(files, stringOps::natural_sort());
626 
627  // Scratch space for reading some of the file
628  std::string header;
629 
630  scalar timeValue;
631 
632  bool warnings = false;
633 
634  for (const fileName& file : files)
635  {
636  std::ifstream is(path/file);
637 
638  if (!is)
639  {
640  continue;
641  }
642 
643  // Read directly into the string
644  // 1024 (12 lines of 80 chars) is plenty for all comments
645 
646  header.resize(1024);
647  is.read(&(header.front()), header.size());
648  const std::streamsize gcount = is.gcount();
649  header.erase(gcount <= 0 ? 0 : gcount);
650 
651  // DebugInfo
652  // << "got header:\n=====\n" << header << "\n=====\n" << nl;
653 
654 
655  // Look for time="...", time='...', or even time=... attribute
656 
657  auto begAttr = header.find("time=");
658 
659  if (string::npos == begAttr)
660  {
661  if (!warnings)
662  {
663  Info<< "No 'time=' comment attribute found:\n(" << nl;
664  warnings = true;
665  }
666  Info<< " " << file << nl;
667  continue;
668  }
669 
670  // Skip past the 'time='
671  begAttr += 5;
672  const char quote = header[begAttr];
673 
674  // Info<< "have time=" << int(begAttr) << nl;
675 
676  auto endAttr =
677  (
678  (quote == '"' || quote == '\'')
679  ?
680  // Quoted
681  header.find(quote, ++begAttr)
682  :
683  // Unquoted
684  header.find_first_of("\t\n\v\f\r ", begAttr)
685  );
686 
687 
688  if
689  (
690  string::npos != endAttr && begAttr < endAttr
691  && readScalar
692  (
693  header.substr(begAttr, endAttr-begAttr),
694  timeValue
695  )
696  && lessThan(timeValue, restartTime)
697  )
698  {
699  // Success
700  append(timeValue, file);
701  }
702  }
703 
704  if (warnings)
705  {
706  Info<< ")" << nl << nl;
707  }
708 
709  // Don't trust the order. Sort by time and name instead.
710  this->sort();
711 
712  return size();
713 }
714 
715 
716 bool Foam::vtk::seriesWriter::removeNewer(const scalar timeValue)
717 {
718  // Rebuild hash as side-effect
719  existing_.clear();
720 
721  label dsti = 0;
722 
723  const label nElem = entries_.size();
724 
725  for (label elemi=0; elemi < nElem; ++elemi)
726  {
727  fileNameInstant& src = entries_[elemi];
728 
729  if (!src.name().empty() && lessThan(src.value(), timeValue))
730  {
731  if (dsti != elemi)
732  {
733  entries_[dsti] = std::move(src);
734  existing_.insert(entries_[dsti].name());
735  }
736  ++dsti;
737  }
738  }
740  entries_.resize(dsti);
741 
742  return (nElem != entries_.size());
743 }
744 
745 
747 {
748  Foam::sort(entries_, seriesLess());
749 }
750 
751 
752 // ************************************************************************* //
Begin block [isseparator].
Definition: token.H:165
bool good() const noexcept
True if token is not UNDEFINED or ERROR.
Definition: tokenI.H:507
const Type & value() const noexcept
Return const reference to value.
static word suffix(const fileName &file, char sep='_')
Extract the time-varying ending of files.
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
static std::string::size_type length(const char *s)
Length of the character sequence (with nullptr protection)
Definition: string.H:258
A class for handling file names.
Definition: fileName.H:72
bool bad() const noexcept
True if stream is corrupted.
Definition: IOstream.H:305
static fileName base(const fileName &outputName, char sep='_')
Extract the base name for a file series.
Specialized string sorting.
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
Ostream & print(Ostream &os, UIntType value, char off='0', char on='1')
Print 0/1 bits in the (unsigned) integral type.
Definition: BitOps.H:323
Begin dimensions [isseparator].
Definition: token.H:163
Output to file stream, using an OSstream.
Definition: OFstream.H:49
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
A token holds an item read from Istream.
Definition: token.H:65
Three-way comparison operation of two parameters,.
Definition: ops.H:260
static bool getValueToken(ISstream &is, token &tok)
No type, or default initialized type.
tmp< DimensionedField< TypeR, GeoMesh > > New(const tmp< DimensionedField< TypeR, GeoMesh >> &tf1, const word &name, const dimensionSet &dimensions, const bool initCopy=false)
Global function forwards to reuseTmpDimensionedField::New.
string upper(const std::string &s)
Return string copy transformed with std::toupper on each character.
Definition: stringOps.C:1187
static bool lessThan(const scalar &val, const scalar &upper)
static std::string path(const std::string &str)
Return directory path name (part before last /)
Definition: fileNameI.H:169
word ext() const
Return file name extension (part after last .)
Definition: fileNameI.H:211
static std::string stem(const std::string &str)
Return the basename, without extension.
Definition: fileName.C:391
End dimensions [isseparator].
Definition: token.H:164
void dot(FieldField< Field1, typename innerProduct< Type1, Type2 >::type > &f, const FieldField< Field1, Type1 > &f1, const FieldField< Field2, Type2 > &f2)
Various functions to operate on Lists.
scalar value() const noexcept
The value (const access)
Definition: Instant.H:134
bool isDir(const fileName &name, const bool followLink=true)
Does the name exist as a DIRECTORY in the file system?
Definition: POSIX.C:860
static bool getToken(ISstream &is, token &tok)
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
Comma [isseparator].
Definition: token.H:135
word outputName("finiteArea-edges.obj")
const T & name() const noexcept
The name/key (const access)
Definition: Instant.H:144
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
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
A class for handling words, derived from Foam::string.
Definition: word.H:63
List< T > subsetList(const UList< T > &input, const UnaryPredicate &pred, const bool invert=false)
Copy a subset of the input list when predicate is true.
void sort(UList< T > &list)
Sort the list.
Definition: UList.C:296
bool operator()(const fileNameInstant a, const fileNameInstant b) const
static int compare(const std::string &s1, const std::string &s2)
Natural compare for std::string.
Definition: stringOpsSort.H:71
patchWriters clear()
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: HashTable.H:105
An Ostream is an abstract base class for all output systems (streams, files, token lists...
Definition: Ostream.H:56
OBJstream os(runTime.globalPath()/outputName)
fileName path(UMean.rootPath()/UMean.caseName()/"graphs"/UMean.instance())
bool removeNewer(const scalar timeValue)
Remove entries that are greater_equal the time value.
bool remove_ext()
Remove extension, returning true if string changed.
Definition: stringI.H:93
virtual Istream & read(token &t) override
Return next token from stream.
Definition: ISstream.C:535
label scan(const fileName &seriesName, const scalar restartTime=ROOTVGREAT)
Clear contents and scan directory for files.
const word & name() const noexcept
Return const reference to name.
bool isFile(const fileName &name, const bool checkGzip=true, const bool followLink=true)
Does the name exist as a FILE in the file system?
Definition: POSIX.C:877
An instant of time. Contains the time value and name. Uses Foam::Time when formatting the name...
Definition: instant.H:53
bool starts_with(char c) const
True if string starts with given character (cf. C++20)
Definition: string.H:435
rAUs append(new volScalarField(IOobject::groupName("rAU", phase1.name()), 1.0/(U1Eqn.A()+byDt(max(phase1.residualAlpha() - alpha1, scalar(0)) *rho1))))
static Ostream & print(Ostream &os, const fileName &seriesName, const UList< instant > &series, const char sep='_')
Print file series (JSON format) for specified time instances.
Generic input stream using a standard (STL) stream.
Definition: ISstream.H:51
auto key(const Type &t) -> typename std::enable_if< std::is_enum< Type >::value, typename std::underlying_type< Type >::type >::type
Definition: foamGltfBase.H:103
static void write(const fileName &base, const UList< instant > &series, const char sep='_')
Write file series (JSON format) to disk, for specified instances.
A tuple of scalar value and key. The value often corresponds to a time value, thus the naming of the ...
Definition: Instant.H:45
messageStream Info
Information stream (stdout output on master, null elsewhere)
void sort()
Sort by time value and by file name.
Instant< fileName > fileNameInstant
A tuple of value and fileName.
bool has_ext() const
Various checks for extensions.
Definition: stringI.H:43
label load(const fileName &seriesName, const bool checkFiles=false, const scalar restartTime=ROOTVGREAT)
Clear contents and reload by parsing the specified file.
Pointer management similar to std::unique_ptr, with some additional methods and type checking...
Definition: HashPtrTable.H:48
static autoPtr< T > New(Args &&... args)
Construct autoPtr with forwarding arguments.
Definition: autoPtr.H:178
List< fileName > fileNameList
List of fileName.
Definition: fileNameList.H:32
End block [isseparator].
Definition: token.H:166
fileNameList readDir(const fileName &directory, const fileName::Type type=fileName::Type::FILE, const bool filtergz=true, const bool followLink=true)
Read a directory and return the entries as a fileName List.
Definition: POSIX.C:963
Namespace for OpenFOAM.
Colon [isseparator].
Definition: token.H:133