Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "FormattedTable.h"
11 : #include "MooseError.h"
12 : #include "MooseUtils.h"
13 :
14 : #include "libmesh/exodusII_io.h"
15 :
16 : #include <iomanip>
17 : #include <iterator>
18 :
19 : const unsigned short FormattedTable::_column_width = 15;
20 : const unsigned short FormattedTable::_min_pps_width = 40;
21 :
22 : const unsigned short DEFAULT_CSV_PRECISION = 14;
23 : const std::string DEFAULT_CSV_DELIMITER = ",";
24 :
25 : template <>
26 : void
27 232897 : dataStore(std::ostream & stream, FormattedTable & table, void * context)
28 : {
29 232897 : table.fillEmptyValues();
30 232897 : storeHelper(stream, table._data, context);
31 232897 : storeHelper(stream, table._align_widths, context);
32 232897 : storeHelper(stream, table._column_names, context);
33 232897 : storeHelper(stream, table._output_row_index, context);
34 232897 : storeHelper(stream, table._headers_output, context);
35 232897 : }
36 :
37 : template <>
38 : void
39 59073 : dataLoad(std::istream & stream, FormattedTable & table, void * context)
40 : {
41 59073 : loadHelper(stream, table._data, context);
42 59073 : loadHelper(stream, table._align_widths, context);
43 59073 : loadHelper(stream, table._column_names, context);
44 59073 : loadHelper(stream, table._output_row_index, context);
45 59073 : loadHelper(stream, table._headers_output, context);
46 59073 : }
47 :
48 : template <>
49 : void
50 1614656 : dataStore(std::ostream & stream, std::shared_ptr<TableValueBase> & value_base, void * context)
51 : {
52 1614656 : value_base->store(stream, context);
53 1614656 : }
54 :
55 : template <>
56 : void
57 64904 : dataLoad(std::istream & stream, std::shared_ptr<TableValueBase> & value_base, void * context)
58 : {
59 64904 : std::string type;
60 64904 : dataLoad(stream, type, context);
61 64904 : if (type == typeid(bool).name())
62 0 : TableValue<bool>::load(stream, value_base, context);
63 :
64 64904 : else if (type == typeid(unsigned short int).name())
65 0 : TableValue<unsigned short int>::load(stream, value_base, context);
66 :
67 64904 : else if (type == typeid(unsigned int).name())
68 620 : TableValue<unsigned int>::load(stream, value_base, context);
69 :
70 64284 : else if (type == typeid(unsigned long int).name())
71 90 : TableValue<unsigned long int>::load(stream, value_base, context);
72 :
73 64194 : else if (type == typeid(unsigned long long int).name())
74 0 : TableValue<unsigned long long int>::load(stream, value_base, context);
75 :
76 64194 : else if (type == typeid(short int).name())
77 0 : TableValue<short int>::load(stream, value_base, context);
78 :
79 64194 : else if (type == typeid(int).name())
80 224 : TableValue<int>::load(stream, value_base, context);
81 :
82 63970 : else if (type == typeid(long int).name())
83 0 : TableValue<long int>::load(stream, value_base, context);
84 :
85 63970 : else if (type == typeid(long long int).name())
86 0 : TableValue<long long int>::load(stream, value_base, context);
87 :
88 63970 : else if (type == typeid(float).name())
89 0 : TableValue<float>::load(stream, value_base, context);
90 :
91 63970 : else if (type == typeid(double).name())
92 63748 : TableValue<double>::load(stream, value_base, context);
93 :
94 222 : else if (type == typeid(long double).name())
95 0 : TableValue<long double>::load(stream, value_base, context);
96 :
97 222 : else if (type == typeid(char).name())
98 0 : TableValue<char>::load(stream, value_base, context);
99 :
100 222 : else if (type == typeid(char *).name())
101 0 : TableValue<char *>::load(stream, value_base, context);
102 :
103 222 : else if (type == typeid(std::string).name())
104 222 : TableValue<std::string>::load(stream, value_base, context);
105 :
106 : else
107 0 : mooseError("Unsupported table value type ", demangle(type.c_str()));
108 64904 : }
109 :
110 : void
111 460343 : FormattedTable::close()
112 : {
113 460343 : if (!_output_file.is_open())
114 402790 : return;
115 57553 : _output_file.flush();
116 57553 : _output_file.close();
117 57553 : _output_file_name = "";
118 : }
119 :
120 : void
121 57553 : FormattedTable::open(const std::string & file_name)
122 : {
123 57553 : if (_output_file.is_open() && _output_file_name == file_name)
124 0 : return;
125 57553 : close();
126 57553 : _output_file_name = file_name;
127 :
128 57553 : std::ios_base::openmode open_flags = std::ios::out;
129 57553 : if (_append)
130 2401 : open_flags |= std::ios::app;
131 : else
132 : {
133 55152 : open_flags |= std::ios::trunc;
134 55152 : _output_row_index = 0;
135 55152 : _headers_output = false;
136 : }
137 :
138 57553 : _output_file.open(file_name.c_str(), open_flags);
139 57553 : if (_output_file.fail())
140 0 : mooseError("Unable to open file ", file_name);
141 : }
142 :
143 350616 : FormattedTable::FormattedTable()
144 350616 : : _output_row_index(0),
145 350616 : _headers_output(false),
146 350616 : _append(false),
147 350616 : _output_time(true),
148 350616 : _csv_delimiter(DEFAULT_CSV_DELIMITER),
149 350616 : _csv_precision(DEFAULT_CSV_PRECISION),
150 350616 : _csv_use_scientific_notation(false)
151 : {
152 350616 : }
153 :
154 4753 : FormattedTable::FormattedTable(const FormattedTable & o)
155 4753 : : _column_names(o._column_names),
156 9506 : _output_file_name(""),
157 4753 : _output_row_index(o._output_row_index),
158 4753 : _headers_output(o._headers_output),
159 4753 : _append(o._append),
160 4753 : _output_time(o._output_time),
161 4753 : _csv_delimiter(o._csv_delimiter),
162 4753 : _csv_precision(o._csv_precision),
163 4753 : _csv_use_scientific_notation(o._csv_use_scientific_notation),
164 9506 : _column_names_unsorted(o._column_names_unsorted)
165 : {
166 4753 : if (_output_file.is_open())
167 0 : mooseError("Copying a FormattedTable with an open stream is not supported");
168 :
169 4753 : for (const auto & it : o._data)
170 0 : _data.emplace_back(it.first, it.second);
171 4753 : }
172 :
173 345237 : FormattedTable::~FormattedTable() { close(); }
174 :
175 : bool
176 1316084 : FormattedTable::empty() const
177 : {
178 1316084 : return _data.empty();
179 : }
180 :
181 : void
182 977 : FormattedTable::append(bool append_existing_file)
183 : {
184 977 : _append = append_existing_file;
185 977 : }
186 :
187 : void
188 352663 : FormattedTable::addRow(Real time)
189 : {
190 352663 : _data.emplace_back(time, std::map<std::string, std::shared_ptr<TableValueBase>>());
191 352663 : }
192 :
193 : Real
194 305151 : FormattedTable::getLastTime() const
195 : {
196 : mooseAssert(!empty(), "No Data stored in the FormattedTable");
197 305151 : return _data.rbegin()->first;
198 : }
199 :
200 : void
201 29360 : FormattedTable::printOmittedRow(std::ostream & out,
202 : std::map<std::string, unsigned short> & col_widths,
203 : std::vector<std::string>::iterator & col_begin,
204 : std::vector<std::string>::iterator & col_end) const
205 : {
206 29360 : printNoDataRow(':', ' ', out, col_widths, col_begin, col_end);
207 29360 : }
208 :
209 : void
210 384540 : FormattedTable::printRowDivider(std::ostream & out,
211 : std::map<std::string, unsigned short> & col_widths,
212 : std::vector<std::string>::iterator & col_begin,
213 : std::vector<std::string>::iterator & col_end) const
214 : {
215 384540 : printNoDataRow('+', '-', out, col_widths, col_begin, col_end);
216 384540 : }
217 :
218 : void
219 413900 : FormattedTable::printNoDataRow(char intersect_char,
220 : char fill_char,
221 : std::ostream & out,
222 : std::map<std::string, unsigned short> & col_widths,
223 : std::vector<std::string>::iterator & col_begin,
224 : std::vector<std::string>::iterator & col_end) const
225 : {
226 413900 : out.fill(fill_char);
227 413900 : out << std::right << intersect_char;
228 413900 : if (_output_time)
229 413900 : out << std::setw(_column_width + 2) << intersect_char;
230 1182853 : for (auto header_it = col_begin; header_it != col_end; ++header_it)
231 768953 : out << std::setw(col_widths[*header_it] + 2) << intersect_char;
232 413900 : out << "\n";
233 :
234 : // Clear the fill character
235 413900 : out.fill(' ');
236 413900 : }
237 :
238 : void
239 0 : FormattedTable::printTable(const std::string & file_name)
240 : {
241 0 : open(file_name);
242 0 : printTable(_output_file);
243 0 : }
244 :
245 : void
246 41 : FormattedTable::printTable(std::ostream & out, unsigned int last_n_entries)
247 : {
248 123 : printTable(out, last_n_entries, MooseEnum("ENVIRONMENT=-1", "ENVIRONMENT"));
249 41 : }
250 :
251 : void
252 124835 : FormattedTable::printTable(std::ostream & out,
253 : unsigned int last_n_entries,
254 : const MooseEnum & suggested_term_width)
255 : {
256 : unsigned short term_width;
257 :
258 124835 : if (suggested_term_width == "ENVIRONMENT")
259 124647 : term_width = MooseUtils::getTermWidth(true);
260 188 : else if (suggested_term_width == "AUTO")
261 0 : term_width = MooseUtils::getTermWidth(false);
262 : else
263 192 : term_width = MooseUtils::stringToInteger(suggested_term_width);
264 :
265 124828 : if (term_width < _min_pps_width)
266 0 : term_width = _min_pps_width;
267 :
268 124828 : std::vector<std::string>::iterator col_it = _column_names.begin();
269 124828 : std::vector<std::string>::iterator col_end = _column_names.end();
270 :
271 124828 : std::vector<std::string>::iterator curr_begin = col_it;
272 124828 : std::vector<std::string>::iterator curr_end;
273 253008 : while (col_it != col_end)
274 : {
275 128180 : std::map<std::string, unsigned short> col_widths;
276 128180 : unsigned int curr_width = _column_width + 4;
277 128180 : unsigned int cols_in_group = 0;
278 376244 : while (curr_width < term_width && col_it != col_end)
279 : {
280 248064 : curr_end = col_it;
281 248064 : col_widths[*col_it] = col_it->length() > _column_width ? col_it->length() + 1 : _column_width;
282 :
283 248064 : curr_width += col_widths[*col_it] + 3;
284 248064 : ++col_it;
285 248064 : ++cols_in_group;
286 : }
287 128180 : if (col_it != col_end && cols_in_group >= 2)
288 : {
289 : // curr_width -= col_widths[*curr_end];
290 3352 : col_widths.erase(*curr_end);
291 3352 : col_it = curr_end;
292 : }
293 : else
294 124828 : curr_end = col_it;
295 :
296 128180 : printTablePiece(out, last_n_entries, col_widths, curr_begin, curr_end);
297 128180 : curr_begin = curr_end;
298 128180 : }
299 124828 : }
300 :
301 : void
302 128180 : FormattedTable::printTablePiece(std::ostream & out,
303 : unsigned int last_n_entries,
304 : std::map<std::string, unsigned short> & col_widths,
305 : std::vector<std::string>::iterator & col_begin,
306 : std::vector<std::string>::iterator & col_end)
307 : {
308 128180 : fillEmptyValues(last_n_entries);
309 : /**
310 : * Print out the header row
311 : */
312 128180 : printRowDivider(out, col_widths, col_begin, col_end);
313 128180 : out << "|";
314 128180 : if (_output_time)
315 128180 : out << std::setw(_column_width) << std::left << " time" << " |";
316 372892 : for (auto header_it = col_begin; header_it != col_end; ++header_it)
317 244712 : out << " " << std::setw(col_widths[*header_it]) << *header_it << "|";
318 128180 : out << "\n";
319 128180 : printRowDivider(out, col_widths, col_begin, col_end);
320 :
321 128180 : auto data_it = _data.begin();
322 128180 : if (last_n_entries)
323 : {
324 128176 : if (_data.size() > last_n_entries)
325 : {
326 : // Print a blank row to indicate that values have been ommited
327 29360 : printOmittedRow(out, col_widths, col_begin, col_end);
328 :
329 : // Jump to the right place in the vector
330 29360 : data_it += _data.size() - last_n_entries;
331 : }
332 : }
333 : // Now print the remaining data rows
334 972893 : for (; data_it != _data.end(); ++data_it)
335 : {
336 844713 : out << "|";
337 844713 : if (_output_time)
338 844713 : out << std::right << std::setw(_column_width) << std::scientific << data_it->first << " |";
339 2214852 : for (auto header_it = col_begin; header_it != col_end; ++header_it)
340 : {
341 1370139 : auto & tmp = data_it->second;
342 1370139 : out << std::setw(col_widths[*header_it]) << *tmp[*header_it] << " |";
343 : }
344 844713 : out << "\n";
345 : }
346 :
347 128180 : printRowDivider(out, col_widths, col_begin, col_end);
348 128180 : }
349 :
350 : void
351 57553 : FormattedTable::printCSV(const std::string & file_name, int interval, bool align)
352 : {
353 57553 : fillEmptyValues();
354 :
355 57553 : open(file_name);
356 :
357 57553 : if (_output_row_index == 0)
358 : {
359 : /**
360 : * When the alignment option is set to true, the widths of the columns needs to be computed
361 : * based on longest of the column name of the data supplied. This is done here by creating a
362 : * map
363 : * of the widths for each of the columns, including time
364 : */
365 55209 : if (align)
366 : {
367 : // Set the initial width to the names of the columns
368 60 : _align_widths["time"] = 4;
369 :
370 240 : for (const auto & col_name : _column_names)
371 210 : _align_widths[col_name] = col_name.size();
372 :
373 : // Loop through the various times
374 103 : for (const auto & it : _data)
375 : {
376 : // Update the time _align_width
377 : {
378 73 : std::ostringstream oss;
379 73 : if (_csv_use_scientific_notation)
380 0 : oss << std::scientific;
381 73 : oss << std::setprecision(_csv_precision) << it.first;
382 73 : unsigned int w = oss.str().size();
383 292 : _align_widths["time"] = std::max(_align_widths["time"], w);
384 73 : }
385 :
386 : // Loop through the data for the current time and update the _align_widths
387 584 : for (const auto & jt : it.second)
388 : {
389 511 : std::ostringstream oss;
390 511 : if (_csv_use_scientific_notation)
391 0 : oss << std::scientific;
392 511 : oss << std::setprecision(_csv_precision) << *jt.second;
393 511 : unsigned int w = oss.str().size();
394 511 : _align_widths[jt.first] = std::max(_align_widths[jt.first], w);
395 511 : }
396 : }
397 : }
398 :
399 : // Output Header
400 55209 : if (!_headers_output)
401 : {
402 55209 : if (_output_time)
403 : {
404 47980 : if (align)
405 90 : _output_file << std::setw(_align_widths["time"]) << "time";
406 : else
407 47950 : _output_file << "time";
408 47980 : _headers_output = true;
409 : }
410 :
411 200033 : for (const auto & col_name : _column_names)
412 : {
413 144824 : if (_headers_output)
414 137595 : _output_file << _csv_delimiter;
415 :
416 144824 : if (align)
417 210 : _output_file << std::right << std::setw(_align_widths[col_name]) << col_name;
418 : else
419 144614 : _output_file << col_name;
420 144824 : _headers_output = true;
421 : }
422 55209 : _output_file << "\n";
423 : }
424 : }
425 :
426 3589448 : for (; _output_row_index < _data.size(); ++_output_row_index)
427 : {
428 3531895 : if (_output_row_index % interval == 0)
429 3531895 : printRow(_data[_output_row_index], align);
430 : }
431 :
432 57553 : close();
433 57553 : }
434 :
435 : void
436 3531895 : FormattedTable::printRow(
437 : std::pair<Real, std::map<std::string, std::shared_ptr<TableValueBase>>> & row_data, bool align)
438 : {
439 3531895 : bool first = true;
440 :
441 3531895 : if (_csv_use_scientific_notation)
442 8 : _output_file << std::scientific;
443 : else
444 3531887 : _output_file << std::defaultfloat;
445 :
446 3531895 : if (_output_time)
447 : {
448 3342897 : if (align)
449 75 : _output_file << std::setprecision(_csv_precision) << std::right
450 225 : << std::setw(_align_widths["time"]) << row_data.first;
451 : else
452 3342822 : _output_file << std::setprecision(_csv_precision) << row_data.first;
453 3342897 : first = false;
454 : }
455 :
456 10997128 : for (const auto & col_name : _column_names)
457 : {
458 7465233 : std::map<std::string, std::shared_ptr<TableValueBase>> & tmp = row_data.second;
459 :
460 7465233 : if (!first)
461 7276235 : _output_file << _csv_delimiter;
462 : else
463 188998 : first = false;
464 :
465 7465233 : if (align)
466 525 : _output_file << std::setprecision(_csv_precision) << std::right
467 525 : << std::setw(_align_widths[col_name]) << *tmp[col_name];
468 : else
469 7464708 : _output_file << std::setprecision(_csv_precision) << *tmp[col_name];
470 : }
471 3531895 : _output_file << "\n";
472 3531895 : }
473 :
474 : // const strings that the gnuplot generator needs
475 : namespace gnuplot
476 : {
477 : const std::string before_terminal = "set terminal ";
478 : const std::string before_ext = "\nset output 'all";
479 : const std::string after_ext =
480 : "'\nset title 'All Postprocessors'\nset xlabel 'time'\nset ylabel 'values'\nplot";
481 : }
482 :
483 : void
484 66 : FormattedTable::makeGnuplot(const std::string & base_file, const std::string & format)
485 : {
486 66 : fillEmptyValues();
487 :
488 : // TODO: run this once at end of simulation, right now it runs every iteration
489 : // TODO: do I need to be more careful escaping column names?
490 : // Note: open and close the files each time, having open files may mess with gnuplot
491 :
492 : // supported filetypes: ps, png
493 66 : std::string extension, terminal;
494 66 : if (format == "png")
495 : {
496 22 : extension = ".png";
497 22 : terminal = "png";
498 : }
499 :
500 44 : else if (format == "ps")
501 : {
502 22 : extension = ".ps";
503 22 : terminal = "postscript";
504 : }
505 :
506 22 : else if (format == "gif")
507 : {
508 22 : extension = ".gif";
509 22 : terminal = "gif";
510 : }
511 :
512 : else
513 0 : mooseError("gnuplot format \"" + format + "\" is not supported.");
514 :
515 : // Write the data to disk
516 66 : std::string dat_name = base_file + ".dat";
517 66 : std::ofstream datfile;
518 66 : datfile.open(dat_name.c_str(), std::ios::trunc | std::ios::out);
519 66 : if (datfile.fail())
520 0 : mooseError("Unable to open file ", dat_name);
521 :
522 66 : datfile << "# time";
523 132 : for (const auto & col_name : _column_names)
524 66 : datfile << '\t' << col_name;
525 66 : datfile << '\n';
526 :
527 165 : for (auto & data_it : _data)
528 : {
529 99 : datfile << data_it.first;
530 198 : for (const auto & col_name : _column_names)
531 : {
532 99 : auto & tmp = data_it.second;
533 99 : datfile << '\t' << *tmp[col_name];
534 : }
535 99 : datfile << '\n';
536 : }
537 66 : datfile.flush();
538 66 : datfile.close();
539 :
540 : // Write the gnuplot script
541 66 : std::string gp_name = base_file + ".gp";
542 66 : std::ofstream gpfile;
543 66 : gpfile.open(gp_name.c_str(), std::ios::trunc | std::ios::out);
544 66 : if (gpfile.fail())
545 0 : mooseError("Unable to open file ", gp_name);
546 :
547 : gpfile << gnuplot::before_terminal << terminal << gnuplot::before_ext << extension
548 66 : << gnuplot::after_ext;
549 :
550 : // plot all postprocessors in one plot
551 66 : int column = 2;
552 132 : for (const auto & col_name : _column_names)
553 : {
554 66 : gpfile << " '" << dat_name << "' using 1:" << column << " title '" << col_name
555 66 : << "' with linespoints";
556 66 : column++;
557 66 : if (column - 2 < static_cast<int>(_column_names.size()))
558 0 : gpfile << ", \\\n";
559 : }
560 66 : gpfile << "\n\n";
561 :
562 : // plot the postprocessors individually
563 66 : column = 2;
564 132 : for (const auto & col_name : _column_names)
565 : {
566 66 : gpfile << "set output '" << col_name << extension << "'\n";
567 66 : gpfile << "set ylabel '" << col_name << "'\n";
568 66 : gpfile << "plot '" << dat_name << "' using 1:" << column << " title '" << col_name
569 66 : << "' with linespoints\n\n";
570 66 : column++;
571 : }
572 :
573 66 : gpfile.flush();
574 66 : gpfile.close();
575 66 : }
576 :
577 : void
578 22315 : FormattedTable::clear()
579 : {
580 22315 : _data.clear();
581 22315 : _output_file.close();
582 22315 : _output_row_index = 0;
583 22315 : }
584 :
585 : void
586 418696 : FormattedTable::fillEmptyValues(unsigned int last_n_entries)
587 : {
588 418696 : auto begin = _data.begin();
589 418696 : auto end = _data.end();
590 418696 : if (last_n_entries && (last_n_entries < _data.size()))
591 29360 : begin = end - last_n_entries;
592 :
593 6350883 : for (auto it = begin; it != end; ++it)
594 : {
595 5932187 : auto & datamap = it->second;
596 5932187 : if (datamap.size() != _column_names.size())
597 : {
598 1941 : for (const auto & col_name : _column_names)
599 1450 : if (!datamap[col_name])
600 907 : datamap[col_name] =
601 1814 : std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
602 : }
603 : else
604 : {
605 17288958 : for (auto & [key, val] : datamap)
606 11357262 : if (!val)
607 0 : val = std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
608 : }
609 : }
610 418696 : }
611 :
612 : MooseEnum
613 134011 : FormattedTable::getWidthModes()
614 : {
615 536044 : return MooseEnum("ENVIRONMENT=-1 AUTO=0 80=80 120=120 160=160", "ENVIRONMENT", true);
616 : }
617 :
618 : void
619 124798 : FormattedTable::sortColumns()
620 : {
621 124798 : if (_column_names_unsorted)
622 : {
623 22609 : std::sort(_column_names.begin(), _column_names.end());
624 22609 : _column_names_unsorted = false;
625 : }
626 124798 : }
627 :
628 : std::ostream &
629 8835982 : operator<<(std::ostream & os, const TableValueBase & value)
630 : {
631 8835982 : value.print(os);
632 8835982 : return os;
633 : }
|