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 {
151 }
152 
154  : _column_names(o._column_names),
155  _output_file_name(""),
156  _output_row_index(o._output_row_index),
157  _headers_output(o._headers_output),
158  _append(o._append),
159  _output_time(o._output_time),
160  _csv_delimiter(o._csv_delimiter),
161  _csv_precision(o._csv_precision),
162  _column_names_unsorted(o._column_names_unsorted)
163 {
164  if (_output_file.is_open())
165  mooseError("Copying a FormattedTable with an open stream is not supported");
166 
167  for (const auto & it : o._data)
168  _data.emplace_back(it.first, it.second);
169 }
170 
172 
173 bool
175 {
176  return _data.empty();
177 }
178 
179 void
180 FormattedTable::append(bool append_existing_file)
181 {
182  _append = append_existing_file;
183 }
184 
185 void
187 {
188  _data.emplace_back(time, std::map<std::string, std::shared_ptr<TableValueBase>>());
189 }
190 
191 Real
193 {
194  mooseAssert(!empty(), "No Data stored in the FormattedTable");
195  return _data.rbegin()->first;
196 }
197 
198 void
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  printNoDataRow(':', ' ', out, col_widths, col_begin, col_end);
205 }
206 
207 void
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  printNoDataRow('+', '-', out, col_widths, col_begin, col_end);
214 }
215 
216 void
217 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  out.fill(fill_char);
225  out << std::right << intersect_char;
226  if (_output_time)
227  out << std::setw(_column_width + 2) << intersect_char;
228  for (auto header_it = col_begin; header_it != col_end; ++header_it)
229  out << std::setw(col_widths[*header_it] + 2) << intersect_char;
230  out << "\n";
231 
232  // Clear the fill character
233  out.fill(' ');
234 }
235 
236 void
237 FormattedTable::printTable(const std::string & file_name)
238 {
239  open(file_name);
241 }
242 
243 void
244 FormattedTable::printTable(std::ostream & out, unsigned int last_n_entries)
245 {
246  printTable(out, last_n_entries, MooseEnum("ENVIRONMENT=-1", "ENVIRONMENT"));
247 }
248 
249 void
250 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  if (suggested_term_width == "ENVIRONMENT")
257  term_width = MooseUtils::getTermWidth(true);
258  else if (suggested_term_width == "AUTO")
259  term_width = MooseUtils::getTermWidth(false);
260  else
261  term_width = MooseUtils::stringToInteger(suggested_term_width);
262 
263  if (term_width < _min_pps_width)
264  term_width = _min_pps_width;
265 
266  std::vector<std::string>::iterator col_it = _column_names.begin();
267  std::vector<std::string>::iterator col_end = _column_names.end();
268 
269  std::vector<std::string>::iterator curr_begin = col_it;
270  std::vector<std::string>::iterator curr_end;
271  while (col_it != col_end)
272  {
273  std::map<std::string, unsigned short> col_widths;
274  unsigned int curr_width = _column_width + 4;
275  unsigned int cols_in_group = 0;
276  while (curr_width < term_width && col_it != col_end)
277  {
278  curr_end = col_it;
279  col_widths[*col_it] = col_it->length() > _column_width ? col_it->length() + 1 : _column_width;
280 
281  curr_width += col_widths[*col_it] + 3;
282  ++col_it;
283  ++cols_in_group;
284  }
285  if (col_it != col_end && cols_in_group >= 2)
286  {
287  // curr_width -= col_widths[*curr_end];
288  col_widths.erase(*curr_end);
289  col_it = curr_end;
290  }
291  else
292  curr_end = col_it;
293 
294  printTablePiece(out, last_n_entries, col_widths, curr_begin, curr_end);
295  curr_begin = curr_end;
296  }
297 }
298 
299 void
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  fillEmptyValues(last_n_entries);
310  printRowDivider(out, col_widths, col_begin, col_end);
311  out << "|";
312  if (_output_time)
313  out << std::setw(_column_width) << std::left << " time" << " |";
314  for (auto header_it = col_begin; header_it != col_end; ++header_it)
315  out << " " << std::setw(col_widths[*header_it]) << *header_it << "|";
316  out << "\n";
317  printRowDivider(out, col_widths, col_begin, col_end);
318 
319  auto data_it = _data.begin();
320  if (last_n_entries)
321  {
322  if (_data.size() > last_n_entries)
323  {
324  // Print a blank row to indicate that values have been ommited
325  printOmittedRow(out, col_widths, col_begin, col_end);
326 
327  // Jump to the right place in the vector
328  data_it += _data.size() - last_n_entries;
329  }
330  }
331  // Now print the remaining data rows
332  for (; data_it != _data.end(); ++data_it)
333  {
334  out << "|";
335  if (_output_time)
336  out << std::right << std::setw(_column_width) << std::scientific << data_it->first << " |";
337  for (auto header_it = col_begin; header_it != col_end; ++header_it)
338  {
339  auto & tmp = data_it->second;
340  out << std::setw(col_widths[*header_it]) << *tmp[*header_it] << " |";
341  }
342  out << "\n";
343  }
344 
345  printRowDivider(out, col_widths, col_begin, col_end);
346 }
347 
348 void
349 FormattedTable::printCSV(const std::string & file_name, int interval, bool align)
350 {
351  fillEmptyValues();
352 
353  open(file_name);
354 
355  if (_output_row_index == 0)
356  {
363  if (align)
364  {
365  // Set the initial width to the names of the columns
366  _align_widths["time"] = 4;
367 
368  for (const auto & col_name : _column_names)
369  _align_widths[col_name] = col_name.size();
370 
371  // Loop through the various times
372  for (const auto & it : _data)
373  {
374  // Update the time _align_width
375  {
376  std::ostringstream oss;
377  oss << std::setprecision(_csv_precision) << it.first;
378  unsigned int w = oss.str().size();
379  _align_widths["time"] = std::max(_align_widths["time"], w);
380  }
381 
382  // Loop through the data for the current time and update the _align_widths
383  for (const auto & jt : it.second)
384  {
385  std::ostringstream oss;
386  oss << std::setprecision(_csv_precision) << *jt.second;
387  unsigned int w = oss.str().size();
388  _align_widths[jt.first] = std::max(_align_widths[jt.first], w);
389  }
390  }
391  }
392 
393  // Output Header
394  if (!_headers_output)
395  {
396  if (_output_time)
397  {
398  if (align)
399  _output_file << std::setw(_align_widths["time"]) << "time";
400  else
401  _output_file << "time";
402  _headers_output = true;
403  }
404 
405  for (const auto & col_name : _column_names)
406  {
407  if (_headers_output)
409 
410  if (align)
411  _output_file << std::right << std::setw(_align_widths[col_name]) << col_name;
412  else
413  _output_file << col_name;
414  _headers_output = true;
415  }
416  _output_file << "\n";
417  }
418  }
419 
420  for (; _output_row_index < _data.size(); ++_output_row_index)
421  {
422  if (_output_row_index % interval == 0)
424  }
425 
426  _output_file.flush();
427 }
428 
429 void
431  std::pair<Real, std::map<std::string, std::shared_ptr<TableValueBase>>> & row_data, bool align)
432 {
433  bool first = true;
434 
435  if (_output_time)
436  {
437  if (align)
438  _output_file << std::setprecision(_csv_precision) << std::right
439  << std::setw(_align_widths["time"]) << row_data.first;
440  else
441  _output_file << std::setprecision(_csv_precision) << row_data.first;
442  first = false;
443  }
444 
445  for (const auto & col_name : _column_names)
446  {
447  std::map<std::string, std::shared_ptr<TableValueBase>> & tmp = row_data.second;
448 
449  if (!first)
451  else
452  first = false;
453 
454  if (align)
455  _output_file << std::setprecision(_csv_precision) << std::right
456  << std::setw(_align_widths[col_name]) << *tmp[col_name];
457  else
458  _output_file << std::setprecision(_csv_precision) << *tmp[col_name];
459  }
460  _output_file << "\n";
461 }
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 FormattedTable::makeGnuplot(const std::string & base_file, const std::string & format)
474 {
475  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  std::string extension, terminal;
483  if (format == "png")
484  {
485  extension = ".png";
486  terminal = "png";
487  }
488 
489  else if (format == "ps")
490  {
491  extension = ".ps";
492  terminal = "postscript";
493  }
494 
495  else if (format == "gif")
496  {
497  extension = ".gif";
498  terminal = "gif";
499  }
500 
501  else
502  mooseError("gnuplot format \"" + format + "\" is not supported.");
503 
504  // Write the data to disk
505  std::string dat_name = base_file + ".dat";
506  std::ofstream datfile;
507  datfile.open(dat_name.c_str(), std::ios::trunc | std::ios::out);
508  if (datfile.fail())
509  mooseError("Unable to open file ", dat_name);
510 
511  datfile << "# time";
512  for (const auto & col_name : _column_names)
513  datfile << '\t' << col_name;
514  datfile << '\n';
515 
516  for (auto & data_it : _data)
517  {
518  datfile << data_it.first;
519  for (const auto & col_name : _column_names)
520  {
521  auto & tmp = data_it.second;
522  datfile << '\t' << *tmp[col_name];
523  }
524  datfile << '\n';
525  }
526  datfile.flush();
527  datfile.close();
528 
529  // Write the gnuplot script
530  std::string gp_name = base_file + ".gp";
531  std::ofstream gpfile;
532  gpfile.open(gp_name.c_str(), std::ios::trunc | std::ios::out);
533  if (gpfile.fail())
534  mooseError("Unable to open file ", gp_name);
535 
536  gpfile << gnuplot::before_terminal << terminal << gnuplot::before_ext << extension
538 
539  // plot all postprocessors in one plot
540  int column = 2;
541  for (const auto & col_name : _column_names)
542  {
543  gpfile << " '" << dat_name << "' using 1:" << column << " title '" << col_name
544  << "' with linespoints";
545  column++;
546  if (column - 2 < static_cast<int>(_column_names.size()))
547  gpfile << ", \\\n";
548  }
549  gpfile << "\n\n";
550 
551  // plot the postprocessors individually
552  column = 2;
553  for (const auto & col_name : _column_names)
554  {
555  gpfile << "set output '" << col_name << extension << "'\n";
556  gpfile << "set ylabel '" << col_name << "'\n";
557  gpfile << "plot '" << dat_name << "' using 1:" << column << " title '" << col_name
558  << "' with linespoints\n\n";
559  column++;
560  }
561 
562  gpfile.flush();
563  gpfile.close();
564 }
565 
566 void
568 {
569  _data.clear();
570  _output_file.close();
571  _output_row_index = 0;
572 }
573 
574 void
575 FormattedTable::fillEmptyValues(unsigned int last_n_entries)
576 {
577  auto begin = _data.begin();
578  auto end = _data.end();
579  if (last_n_entries && (last_n_entries < _data.size()))
580  begin = end - last_n_entries;
581 
582  for (auto it = begin; it != end; ++it)
583  {
584  auto & datamap = it->second;
585  if (datamap.size() != _column_names.size())
586  {
587  for (const auto & col_name : _column_names)
588  if (!datamap[col_name])
589  datamap[col_name] =
590  std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
591  }
592  else
593  {
594  for (auto & [key, val] : datamap)
595  if (!val)
596  val = std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
597  }
598  }
599 }
600 
601 MooseEnum
603 {
604  return MooseEnum("ENVIRONMENT=-1 AUTO=0 80=80 120=120 160=160", "ENVIRONMENT", true);
605 }
606 
607 void
609 {
611  {
612  std::sort(_column_names.begin(), _column_names.end());
613  _column_names_unsorted = false;
614  }
615 }
616 
617 std::ostream &
618 operator<<(std::ostream & os, const TableValueBase & value)
619 {
620  value.print(os);
621  return os;
622 }
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)
int stringToInteger(const std::string &input, bool throw_on_failure=false)
Robust string to integer conversion that fails for cases such at "1foo".
Definition: MooseUtils.C:1073
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.
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:302
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:33
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:893
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:33
This class is used for building, formatting, and outputting tables of numbers.
std::string demangle(const char *name)
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:985
Real getLastTime()
Retrieve the last time (or independent variable) value.
unsigned short getTermWidth(bool use_environment)
Returns the width of the terminal using sys/ioctl.
Definition: MooseUtils.C:636