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-2021 OpenCFD Ltd.
9-------------------------------------------------------------------------------
10License
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
36namespace 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)
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.nameLessExt();
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.hasExt("series")
244 ? autoPtr<OFstream>::New(seriesName)
245 : autoPtr<OFstream>::New(seriesName + ".series")
246 );
247
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.hasExt("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
274bool 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.found())
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
303bool 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.found())
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.hasExt("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)
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.hasExt("series"))
601 {
602 seriesFile.removeExt();
603 }
604
605 const word stem = seriesFile.nameLessExt();
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.hasExt(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
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 header.resize(is.gcount());
649
650 // DebugInfo
651 // << "got header:\n=====\n" << header << "\n=====\n" << nl;
652
653
654 // Look for time="...", time='...', or even time=... attribute
655
656 auto begAttr = header.find("time=");
657
658 if (string::npos == begAttr)
659 {
660 if (!warnings)
661 {
662 Info<< "No 'time=' comment attribute found:\n(" << nl;
663 warnings = true;
664 }
665 Info<< " " << file << nl;
666 continue;
667 }
668
669 // Skip past the 'time='
670 begAttr += 5;
671 const char quote = header[begAttr];
672
673 // Info<< "have time=" << int(begAttr) << nl;
674
675 auto endAttr =
676 (
677 (quote == '"' || quote == '\'')
678 ?
679 // Quoted
680 header.find(quote, ++begAttr)
681 :
682 // Unquoted
683 header.find_first_of("\t\n\v\f\r ", begAttr)
684 );
685
686
687 if
688 (
689 string::npos != endAttr && begAttr < endAttr
690 && readScalar
691 (
692 header.substr(begAttr, endAttr-begAttr),
693 timeValue
694 )
695 && lessThan(timeValue, restartTime)
696 )
697 {
698 // Success
699 append(timeValue, file);
700 }
701 }
702
703 if (warnings)
704 {
705 Info<< ")" << nl << nl;
706 }
707
708 // Don't trust the order. Sort by time and name instead.
709 this->sort();
710
711 return size();
712}
713
714
715bool Foam::vtk::seriesWriter::removeNewer(const scalar timeValue)
716{
717 // Rebuild hash as side-effect
718 existing_.clear();
719
720 label dsti = 0;
721
722 const label nElem = entries_.size();
723
724 for (label elemi=0; elemi < nElem; ++elemi)
725 {
726 fileNameInstant& src = entries_[elemi];
727
728 if (!src.name().empty() && lessThan(src.value(), timeValue))
729 {
730 if (dsti != elemi)
731 {
732 entries_[dsti] = std::move(src);
733 existing_.insert(entries_[dsti].name());
734 }
735 ++dsti;
736 }
737 }
738
739 entries_.resize(dsti);
740
741 return (nElem != entries_.size());
742}
743
744
746{
747 Foam::sort(entries_, seriesLess());
748}
749
750
751// ************************************************************************* //
Various functions to operate on Lists.
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
A HashTable with keys but without contents that is similar to std::unordered_set.
Definition: HashSet.H:96
bool insert(const Key &key)
Insert a new entry, not overwriting existing entries.
Definition: HashSet.H:191
bool found(const Key &key) const
Return true if hashed entry is found in table.
Definition: HashTableI.H:100
Input from file stream, using an ISstream.
Definition: IFstream.H:57
const word & name() const noexcept
Return the object name.
Definition: IOobjectI.H:65
bool bad() const noexcept
True if stream is corrupted.
Definition: IOstream.H:251
Generic input stream using a standard (STL) stream.
Definition: ISstream.H:58
virtual Istream & read(token &t)
Return next token from stream.
Definition: ISstream.C:530
A tuple of scalar value and key. The value often corresponds to a time value, thus the naming of the ...
Definition: Instant.H:52
scalar value() const noexcept
The value (const access)
Definition: Instant.H:118
const T & name() const noexcept
The name/key (const access)
Definition: Instant.H:130
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:62
static autoPtr< Time > New()
Construct (dummy) Time - no functionObjects or libraries.
Definition: Time.C:717
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: UList.H:94
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
Pointer management similar to std::unique_ptr, with some additional methods and type checking.
Definition: autoPtr.H:66
virtual tmp< triSurfacePointScalarField > load()
Load the cell size field.
Base functionality common to reader and writer classes.
Definition: ccmBase.H:63
A class for handling file names.
Definition: fileName.H:76
static std::string nameLessExt(const std::string &str)
Return basename, without extension.
Definition: fileName.C:396
bool removeExt()
Remove extension, returning true if string changed.
Definition: stringI.H:96
word ext() const
Return file name extension (part after last .)
Definition: fileNameI.H:218
static std::string path(const std::string &str)
Return directory path name (part before last /)
Definition: fileNameI.H:176
bool hasExt() const
Various checks for extensions.
Definition: stringI.H:56
virtual bool write()
Write the output fields.
An instant of time. Contains the time value and name. Uses Foam::Time when formatting the name.
Definition: instant.H:56
scalar print()
Print to screen.
A token holds an item read from Istream.
Definition: token.H:69
bool isNumber() const noexcept
Token is LABEL, FLOAT or DOUBLE.
Definition: tokenI.H:587
bool isPunctuation() const noexcept
Token is PUNCTUATION.
Definition: tokenI.H:459
@ BEGIN_BLOCK
Begin block [isseparator].
Definition: token.H:159
@ BEGIN_SQR
Begin dimensions [isseparator].
Definition: token.H:157
@ COLON
Colon [isseparator].
Definition: token.H:127
@ END_BLOCK
End block [isseparator].
Definition: token.H:160
@ END_SQR
End dimensions [isseparator].
Definition: token.H:158
@ COMMA
Comma [isseparator].
Definition: token.H:129
const string & stringToken() const
Return const reference to the string contents.
Definition: tokenI.H:689
punctuationToken pToken() const
Return punctuation character.
Definition: tokenI.H:485
bool good() const noexcept
True if token is not UNDEFINED or ERROR.
Definition: tokenI.H:405
bool isString() const noexcept
Token is string-variant (STRING, EXPRESSION, VARIABLE, VERBATIM)
Definition: tokenI.H:653
scalar number() const
Return label, float or double value.
Definition: tokenI.H:593
static constexpr uint64_t npos
Out of range position or size.
static word suffix(const fileName &file, char sep='_')
Extract the time-varying ending of files.
void sort()
Sort by time value and by file name.
bool removeNewer(const scalar timeValue)
Remove entries that are greater_equal the time value.
label scan(const fileName &seriesName, const scalar restartTime=ROOTVGREAT)
Clear contents and scan directory for files.
A class for handling words, derived from Foam::string.
Definition: word.H:68
fileName path(UMean.rootPath()/UMean.caseName()/"graphs"/UMean.instance())
patchWriters clear()
word outputName("finiteArea-edges.obj")
OBJstream os(runTime.globalPath()/outputName)
rAUs append(new volScalarField(IOobject::groupName("rAU", phase1.name()), 1.0/(U1Eqn.A()+byDt(max(phase1.residualAlpha() - alpha1, scalar(0)) *rho1))))
Namespace for OpenFOAM.
bool mkDir(const fileName &pathName, mode_t mode=0777)
Make a directory and return an error if it could not be created.
Definition: MSwindows.C:515
static bool getValueToken(ISstream &is, token &tok)
static bool getToken(ISstream &is, token &tok)
messageStream Info
Information stream (stdout output on master, null elsewhere)
Instant< fileName > fileNameInstant
A tuple of value and fileName.
void dot(FieldField< Field1, typename innerProduct< Type1, Type2 >::type > &f, const FieldField< Field1, Type1 > &f1, const FieldField< Field2, Type2 > &f2)
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.
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
void sort(UList< T > &list)
Sort the list.
Definition: UList.C:342
fileNameList readDir(const fileName &directory, const fileName::Type type=fileName::FILE, const bool filtergz=true, const bool followLink=true)
Read a directory and return the entries as a fileName List.
Definition: MSwindows.C:715
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: MSwindows.C:666
static bool lessThan(const scalar &val, const scalar &upper)
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
bool isDir(const fileName &name, const bool followLink=true)
Does the name exist as a DIRECTORY in the file system?
Definition: MSwindows.C:651
constexpr char nl
The newline '\n' character (0x0a)
Definition: Ostream.H:53
volScalarField & b
Definition: createFields.H:27
Specialized string sorting.
Three-way comparison operation of two parameters,.
Definition: ops.H:269
bool operator()(const fileNameInstant a, const fileNameInstant b) const
Encapsulation of natural order sorting for algorithms.
Definition: stringOpsSort.H:63
static int compare(const std::string &s1, const std::string &s2)
Natural compare for std::string.
Definition: stringOpsSort.H:67