https://mooseframework.inl.gov
FormattedTable.C
Go to the documentation of this file.
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 dataStore(std::ostream & stream, FormattedTable & table, void * context)
28 {
29  table.fillEmptyValues();
30  storeHelper(stream, table._data, context);
31  storeHelper(stream, table._align_widths, context);
32  storeHelper(stream, table._column_names, context);
33  storeHelper(stream, table._output_row_index, context);
34  storeHelper(stream, table._headers_output, context);
35 }
36 
37 template <>
38 void
39 dataLoad(std::istream & stream, FormattedTable & table, void * context)
40 {
41  loadHelper(stream, table._data, context);
42  loadHelper(stream, table._align_widths, context);
43  loadHelper(stream, table._column_names, context);
44  loadHelper(stream, table._output_row_index, context);
45  loadHelper(stream, table._headers_output, context);
46 }
47 
48 template <>
49 void
50 dataStore(std::ostream & stream, std::shared_ptr<TableValueBase> & value_base, void * context)
51 {
52  value_base->store(stream, context);
53 }
54 
55 template <>
56 void
57 dataLoad(std::istream & stream, std::shared_ptr<TableValueBase> & value_base, void * context)
58 {
59  std::string type;
60  dataLoad(stream, type, context);
61  if (type == typeid(bool).name())
62  TableValue<bool>::load(stream, value_base, context);
63 
64  else if (type == typeid(unsigned short int).name())
65  TableValue<unsigned short int>::load(stream, value_base, context);
66 
67  else if (type == typeid(unsigned int).name())
68  TableValue<unsigned int>::load(stream, value_base, context);
69 
70  else if (type == typeid(unsigned long int).name())
71  TableValue<unsigned long int>::load(stream, value_base, context);
72 
73  else if (type == typeid(unsigned long long int).name())
74  TableValue<unsigned long long int>::load(stream, value_base, context);
75 
76  else if (type == typeid(short int).name())
77  TableValue<short int>::load(stream, value_base, context);
78 
79  else if (type == typeid(int).name())
80  TableValue<int>::load(stream, value_base, context);
81 
82  else if (type == typeid(long int).name())
83  TableValue<long int>::load(stream, value_base, context);
84 
85  else if (type == typeid(long long int).name())
86  TableValue<long long int>::load(stream, value_base, context);
87 
88  else if (type == typeid(float).name())
89  TableValue<float>::load(stream, value_base, context);
90 
91  else if (type == typeid(double).name())
92  TableValue<double>::load(stream, value_base, context);
93 
94  else if (type == typeid(long double).name())
95  TableValue<long double>::load(stream, value_base, context);
96 
97  else if (type == typeid(char).name())
98  TableValue<char>::load(stream, value_base, context);
99 
100  else if (type == typeid(char *).name())
101  TableValue<char *>::load(stream, value_base, context);
102 
103  else if (type == typeid(std::string).name())
104  TableValue<std::string>::load(stream, value_base, context);
105 
106  else
107  mooseError("Unsupported table value type ", demangle(type.c_str()));
108 }
109 
110 void
112 {
113  if (!_output_file.is_open())
114  return;
115  _output_file.flush();
116  _output_file.close();
117  _output_file_name = "";
118 }
119 
120 void
121 FormattedTable::open(const std::string & file_name)
122 {
123  if (_output_file.is_open() && _output_file_name == file_name)
124  return;
125  close();
126  _output_file_name = file_name;
127 
128  std::ios_base::openmode open_flags = std::ios::out;
129  if (_append)
130  open_flags |= std::ios::app;
131  else
132  {
133  open_flags |= std::ios::trunc;
134  _output_row_index = 0;
135  _headers_output = false;
136  }
137 
138  _output_file.open(file_name.c_str(), open_flags);
139  if (_output_file.fail())
140  mooseError("Unable to open file ", file_name);
141 }
142 
144  : _output_row_index(0),
145  _headers_output(false),
146  _append(false),
147  _output_time(true),
148  _csv_delimiter(DEFAULT_CSV_DELIMITER),
149  _csv_precision(DEFAULT_CSV_PRECISION),
150  _csv_use_scientific_notation(false)
151 {
152 }
153 
155  : _column_names(o._column_names),
156  _output_file_name(""),
157  _output_row_index(o._output_row_index),
158  _headers_output(o._headers_output),
159  _append(o._append),
160  _output_time(o._output_time),
161  _csv_delimiter(o._csv_delimiter),
162  _csv_precision(o._csv_precision),
163  _csv_use_scientific_notation(o._csv_use_scientific_notation),
164  _column_names_unsorted(o._column_names_unsorted)
165 {
166  if (_output_file.is_open())
167  mooseError("Copying a FormattedTable with an open stream is not supported");
168 
169  for (const auto & it : o._data)
170  _data.emplace_back(it.first, it.second);
171 }
172 
174 
175 bool
177 {
178  return _data.empty();
179 }
180 
181 void
182 FormattedTable::append(bool append_existing_file)
183 {
184  _append = append_existing_file;
185 }
186 
187 void
189 {
190  _data.emplace_back(time, std::map<std::string, std::shared_ptr<TableValueBase>>());
191 }
192 
193 Real
195 {
196  mooseAssert(!empty(), "No Data stored in the FormattedTable");
197  return _data.rbegin()->first;
198 }
199 
200 void
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  printNoDataRow(':', ' ', out, col_widths, col_begin, col_end);
207 }
208 
209 void
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  printNoDataRow('+', '-', out, col_widths, col_begin, col_end);
216 }
217 
218 void
219 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  out.fill(fill_char);
227  out << std::right << intersect_char;
228  if (_output_time)
229  out << std::setw(_column_width + 2) << intersect_char;
230  for (auto header_it = col_begin; header_it != col_end; ++header_it)
231  out << std::setw(col_widths[*header_it] + 2) << intersect_char;
232  out << "\n";
233 
234  // Clear the fill character
235  out.fill(' ');
236 }
237 
238 void
239 FormattedTable::printTable(const std::string & file_name)
240 {
241  open(file_name);
243 }
244 
245 void
246 FormattedTable::printTable(std::ostream & out, unsigned int last_n_entries)
247 {
248  printTable(out, last_n_entries, MooseEnum("ENVIRONMENT=-1", "ENVIRONMENT"));
249 }
250 
251 void
252 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  if (suggested_term_width == "ENVIRONMENT")
259  term_width = MooseUtils::getTermWidth(true);
260  else if (suggested_term_width == "AUTO")
261  term_width = MooseUtils::getTermWidth(false);
262  else
263  term_width = MooseUtils::stringToInteger(suggested_term_width);
264 
265  if (term_width < _min_pps_width)
266  term_width = _min_pps_width;
267 
268  std::vector<std::string>::iterator col_it = _column_names.begin();
269  std::vector<std::string>::iterator col_end = _column_names.end();
270 
271  std::vector<std::string>::iterator curr_begin = col_it;
272  std::vector<std::string>::iterator curr_end;
273  while (col_it != col_end)
274  {
275  std::map<std::string, unsigned short> col_widths;
276  unsigned int curr_width = _column_width + 4;
277  unsigned int cols_in_group = 0;
278  while (curr_width < term_width && col_it != col_end)
279  {
280  curr_end = col_it;
281  col_widths[*col_it] = col_it->length() > _column_width ? col_it->length() + 1 : _column_width;
282 
283  curr_width += col_widths[*col_it] + 3;
284  ++col_it;
285  ++cols_in_group;
286  }
287  if (col_it != col_end && cols_in_group >= 2)
288  {
289  // curr_width -= col_widths[*curr_end];
290  col_widths.erase(*curr_end);
291  col_it = curr_end;
292  }
293  else
294  curr_end = col_it;
295 
296  printTablePiece(out, last_n_entries, col_widths, curr_begin, curr_end);
297  curr_begin = curr_end;
298  }
299 }
300 
301 void
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  fillEmptyValues(last_n_entries);
312  printRowDivider(out, col_widths, col_begin, col_end);
313  out << "|";
314  if (_output_time)
315  out << std::setw(_column_width) << std::left << " time" << " |";
316  for (auto header_it = col_begin; header_it != col_end; ++header_it)
317  out << " " << std::setw(col_widths[*header_it]) << *header_it << "|";
318  out << "\n";
319  printRowDivider(out, col_widths, col_begin, col_end);
320 
321  auto data_it = _data.begin();
322  if (last_n_entries)
323  {
324  if (_data.size() > last_n_entries)
325  {
326  // Print a blank row to indicate that values have been ommited
327  printOmittedRow(out, col_widths, col_begin, col_end);
328 
329  // Jump to the right place in the vector
330  data_it += _data.size() - last_n_entries;
331  }
332  }
333  // Now print the remaining data rows
334  for (; data_it != _data.end(); ++data_it)
335  {
336  out << "|";
337  if (_output_time)
338  out << std::right << std::setw(_column_width) << std::scientific << data_it->first << " |";
339  for (auto header_it = col_begin; header_it != col_end; ++header_it)
340  {
341  auto & tmp = data_it->second;
342  out << std::setw(col_widths[*header_it]) << *tmp[*header_it] << " |";
343  }
344  out << "\n";
345  }
346 
347  printRowDivider(out, col_widths, col_begin, col_end);
348 }
349 
350 void
351 FormattedTable::printCSV(const std::string & file_name, int interval, bool align)
352 {
353  fillEmptyValues();
354 
355  open(file_name);
356 
357  if (_output_row_index == 0)
358  {
365  if (align)
366  {
367  // Set the initial width to the names of the columns
368  _align_widths["time"] = 4;
369 
370  for (const auto & col_name : _column_names)
371  _align_widths[col_name] = col_name.size();
372 
373  // Loop through the various times
374  for (const auto & it : _data)
375  {
376  // Update the time _align_width
377  {
378  std::ostringstream oss;
380  oss << std::scientific;
381  oss << std::setprecision(_csv_precision) << it.first;
382  unsigned int w = oss.str().size();
383  _align_widths["time"] = std::max(_align_widths["time"], w);
384  }
385 
386  // Loop through the data for the current time and update the _align_widths
387  for (const auto & jt : it.second)
388  {
389  std::ostringstream oss;
391  oss << std::scientific;
392  oss << std::setprecision(_csv_precision) << *jt.second;
393  unsigned int w = oss.str().size();
394  _align_widths[jt.first] = std::max(_align_widths[jt.first], w);
395  }
396  }
397  }
398 
399  // Output Header
400  if (!_headers_output)
401  {
402  if (_output_time)
403  {
404  if (align)
405  _output_file << std::setw(_align_widths["time"]) << "time";
406  else
407  _output_file << "time";
408  _headers_output = true;
409  }
410 
411  for (const auto & col_name : _column_names)
412  {
413  if (_headers_output)
415 
416  if (align)
417  _output_file << std::right << std::setw(_align_widths[col_name]) << col_name;
418  else
419  _output_file << col_name;
420  _headers_output = true;
421  }
422  _output_file << "\n";
423  }
424  }
425 
426  for (; _output_row_index < _data.size(); ++_output_row_index)
427  {
428  if (_output_row_index % interval == 0)
430  }
431 
432  close();
433 }
434 
435 void
437  std::pair<Real, std::map<std::string, std::shared_ptr<TableValueBase>>> & row_data, bool align)
438 {
439  bool first = true;
440 
442  _output_file << std::scientific;
443  else
444  _output_file << std::defaultfloat;
445 
446  if (_output_time)
447  {
448  if (align)
449  _output_file << std::setprecision(_csv_precision) << std::right
450  << std::setw(_align_widths["time"]) << row_data.first;
451  else
452  _output_file << std::setprecision(_csv_precision) << row_data.first;
453  first = false;
454  }
455 
456  for (const auto & col_name : _column_names)
457  {
458  std::map<std::string, std::shared_ptr<TableValueBase>> & tmp = row_data.second;
459 
460  if (!first)
462  else
463  first = false;
464 
465  if (align)
466  _output_file << std::setprecision(_csv_precision) << std::right
467  << std::setw(_align_widths[col_name]) << *tmp[col_name];
468  else
469  _output_file << std::setprecision(_csv_precision) << *tmp[col_name];
470  }
471  _output_file << "\n";
472 }
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 FormattedTable::makeGnuplot(const std::string & base_file, const std::string & format)
485 {
486  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  std::string extension, terminal;
494  if (format == "png")
495  {
496  extension = ".png";
497  terminal = "png";
498  }
499 
500  else if (format == "ps")
501  {
502  extension = ".ps";
503  terminal = "postscript";
504  }
505 
506  else if (format == "gif")
507  {
508  extension = ".gif";
509  terminal = "gif";
510  }
511 
512  else
513  mooseError("gnuplot format \"" + format + "\" is not supported.");
514 
515  // Write the data to disk
516  std::string dat_name = base_file + ".dat";
517  std::ofstream datfile;
518  datfile.open(dat_name.c_str(), std::ios::trunc | std::ios::out);
519  if (datfile.fail())
520  mooseError("Unable to open file ", dat_name);
521 
522  datfile << "# time";
523  for (const auto & col_name : _column_names)
524  datfile << '\t' << col_name;
525  datfile << '\n';
526 
527  for (auto & data_it : _data)
528  {
529  datfile << data_it.first;
530  for (const auto & col_name : _column_names)
531  {
532  auto & tmp = data_it.second;
533  datfile << '\t' << *tmp[col_name];
534  }
535  datfile << '\n';
536  }
537  datfile.flush();
538  datfile.close();
539 
540  // Write the gnuplot script
541  std::string gp_name = base_file + ".gp";
542  std::ofstream gpfile;
543  gpfile.open(gp_name.c_str(), std::ios::trunc | std::ios::out);
544  if (gpfile.fail())
545  mooseError("Unable to open file ", gp_name);
546 
547  gpfile << gnuplot::before_terminal << terminal << gnuplot::before_ext << extension
549 
550  // plot all postprocessors in one plot
551  int column = 2;
552  for (const auto & col_name : _column_names)
553  {
554  gpfile << " '" << dat_name << "' using 1:" << column << " title '" << col_name
555  << "' with linespoints";
556  column++;
557  if (column - 2 < static_cast<int>(_column_names.size()))
558  gpfile << ", \\\n";
559  }
560  gpfile << "\n\n";
561 
562  // plot the postprocessors individually
563  column = 2;
564  for (const auto & col_name : _column_names)
565  {
566  gpfile << "set output '" << col_name << extension << "'\n";
567  gpfile << "set ylabel '" << col_name << "'\n";
568  gpfile << "plot '" << dat_name << "' using 1:" << column << " title '" << col_name
569  << "' with linespoints\n\n";
570  column++;
571  }
572 
573  gpfile.flush();
574  gpfile.close();
575 }
576 
577 void
579 {
580  _data.clear();
581  _output_file.close();
582  _output_row_index = 0;
583 }
584 
585 void
586 FormattedTable::fillEmptyValues(unsigned int last_n_entries)
587 {
588  auto begin = _data.begin();
589  auto end = _data.end();
590  if (last_n_entries && (last_n_entries < _data.size()))
591  begin = end - last_n_entries;
592 
593  for (auto it = begin; it != end; ++it)
594  {
595  auto & datamap = it->second;
596  if (datamap.size() != _column_names.size())
597  {
598  for (const auto & col_name : _column_names)
599  if (!datamap[col_name])
600  datamap[col_name] =
601  std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
602  }
603  else
604  {
605  for (auto & [key, val] : datamap)
606  if (!val)
607  val = std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
608  }
609  }
610 }
611 
612 MooseEnum
614 {
615  return MooseEnum("ENVIRONMENT=-1 AUTO=0 80=80 120=120 160=160", "ENVIRONMENT", true);
616 }
617 
618 void
620 {
622  {
623  std::sort(_column_names.begin(), _column_names.end());
624  _column_names_unsorted = false;
625  }
626 }
627 
628 std::ostream &
629 operator<<(std::ostream & os, const TableValueBase & value)
630 {
631  value.print(os);
632  return os;
633 }
std::string name(const ElemQuality q)
~FormattedTable()
The destructor is used to close the file handle.
void printTablePiece(std::ostream &out, unsigned int last_n_entries, std::map< std::string, unsigned short > &col_widths, std::vector< std::string >::iterator &col_begin, std::vector< std::string >::iterator &col_end)
std::string _output_file_name
The optional output file stream.
std::size_t _output_row_index
Keeps track of the index indicating which vector elements have been output.
bool _csv_use_scientific_notation
Whether to print floating point CSV values in scientific notation.
void makeGnuplot(const std::string &base_file, const std::string &format)
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
void printCSV(const std::string &file_name, int interval=1, bool align=false)
Method for dumping the table to a csv file - opening and closing the file handle is handled...
void open(const std::string &file_name)
Open or switch the underlying file stream to point to file_name. This is idempotent.
void printOmittedRow(std::ostream &out, std::map< std::string, unsigned short > &col_widths, std::vector< std::string >::iterator &col_begin, std::vector< std::string >::iterator &col_end) const
bool _output_time
Whether or not to output the Time column.
void close()
Close the underlying output file stream if any. This is idempotent.
void printRow(std::pair< Real, std::map< std::string, std::shared_ptr< TableValueBase >>> &row_data, bool align)
bool _column_names_unsorted
Flag indicating that sorting is necessary (used by sortColumns method).
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
bool empty() const
Returns a boolean value based on whether the FormattedTable contains data or not. ...
std::basic_ostream< charT, traits > * os
Definition: InfixIterator.h:34
static void load(std::istream &stream, std::shared_ptr< TableValueBase > &value_base, void *context)
std::string _csv_delimiter
*.csv file delimiter, defaults to ","
void fillEmptyValues(unsigned int last_n_entries=0)
Fill any values that are not defined (usually when there are mismatched column lengths) ...
static const unsigned short _min_pps_width
The absolute minimum PPS table width.
void dataLoad(std::istream &stream, FormattedTable &table, void *context)
auto max(const L &left, const R &right)
void storeHelper(std::ostream &stream, P &data, void *context)
Scalar helper routine.
Definition: DataIO.h:938
static MooseEnum getWidthModes()
std::ofstream _output_file
The stream handle (corresponds to _output_file_name)
void append(bool append_existing_file)
Sets append mode which means an existing file is not truncated on opening.
void dataStore(std::ostream &stream, FormattedTable &table, void *context)
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
const unsigned short DEFAULT_CSV_PRECISION
void printRowDivider(std::ostream &out, std::map< std::string, unsigned short > &col_widths, std::vector< std::string >::iterator &col_begin, std::vector< std::string >::iterator &col_end) const
std::vector< std::pair< Real, std::map< std::string, std::shared_ptr< TableValueBase > > > > _data
Data structure for the console table: The first part of the pair tracks the independent variable (nor...
std::map< std::string, unsigned int > _align_widths
Alignment widths (only used if asked to print aligned to CSV output)
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
This class is used for building, formatting, and outputting tables of numbers.
std::string demangle(const char *name)
Real getLastTime() const
Retrieve the last time (or independent variable) value.
unsigned int _csv_precision
*.csv file precision, defaults to 14
static const unsigned short _column_width
The single cell width used for all columns in the table.
void printNoDataRow(char intersect_char, char fill_char, std::ostream &out, std::map< std::string, unsigned short > &col_widths, std::vector< std::string >::iterator &col_begin, std::vector< std::string >::iterator &col_end) const
void printTable(std::ostream &out, unsigned int last_n_entries=0)
Methods for dumping the table to the stream - either by filename or by stream handle.
const std::string after_ext
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< std::string > _column_names
The set of column names updated when data is inserted through the setter methods. ...
OStreamProxy out
FormattedTable()
Default constructor - The default constructor takes an optional parameter to turn off stateful printi...
bool _headers_output
Keeps track of whether the header has been output.
const std::string before_ext
std::ostream & operator<<(std::ostream &os, const TableValueBase &value)
void addRow(Real time)
Force a new row in the table with the passed in time.
void sortColumns()
Sorts columns alphabetically.
bool _append
Keeps track of whether we want to open an existing file for appending or overwriting.
const std::string DEFAULT_CSV_DELIMITER
const std::string before_terminal
void loadHelper(std::istream &stream, P &data, void *context)
Scalar helper routine.
Definition: DataIO.h:1030
int stringToInteger(const std::string &input, bool throw_on_failure)
Definition: MooseUtils.C:967
unsigned short getTermWidth(bool use_environment)
Definition: MooseUtils.C:640