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