www.mooseframework.org
FormattedTable.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://www.mooseframework.org
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 << std::setw(_column_width + 2) << intersect_char;
226  for (auto header_it = col_begin; header_it != col_end; ++header_it)
227  out << std::setw(col_widths[*header_it] + 2) << intersect_char;
228  out << "\n";
229 
230  // Clear the fill character
231  out.fill(' ');
232 }
233 
234 void
235 FormattedTable::printTable(const std::string & file_name)
236 {
237  open(file_name);
239 }
240 
241 void
242 FormattedTable::printTable(std::ostream & out, unsigned int last_n_entries)
243 {
244  printTable(out, last_n_entries, MooseEnum("ENVIRONMENT=-1", "ENVIRONMENT"));
245 }
246 
247 void
248 FormattedTable::printTable(std::ostream & out,
249  unsigned int last_n_entries,
250  const MooseEnum & suggested_term_width)
251 {
252  unsigned short term_width;
253 
254  if (suggested_term_width == "ENVIRONMENT")
255  term_width = MooseUtils::getTermWidth(true);
256  else if (suggested_term_width == "AUTO")
257  term_width = MooseUtils::getTermWidth(false);
258  else
259  term_width = MooseUtils::stringToInteger(suggested_term_width);
260 
261  if (term_width < _min_pps_width)
262  term_width = _min_pps_width;
263 
264  std::vector<std::string>::iterator col_it = _column_names.begin();
265  std::vector<std::string>::iterator col_end = _column_names.end();
266 
267  std::vector<std::string>::iterator curr_begin = col_it;
268  std::vector<std::string>::iterator curr_end;
269  while (col_it != col_end)
270  {
271  std::map<std::string, unsigned short> col_widths;
272  unsigned int curr_width = _column_width + 4;
273  unsigned int cols_in_group = 0;
274  while (curr_width < term_width && col_it != col_end)
275  {
276  curr_end = col_it;
277  col_widths[*col_it] = col_it->length() > _column_width ? col_it->length() + 1 : _column_width;
278 
279  curr_width += col_widths[*col_it] + 3;
280  ++col_it;
281  ++cols_in_group;
282  }
283  if (col_it != col_end && cols_in_group >= 2)
284  {
285  // curr_width -= col_widths[*curr_end];
286  col_widths.erase(*curr_end);
287  col_it = curr_end;
288  }
289  else
290  curr_end = col_it;
291 
292  printTablePiece(out, last_n_entries, col_widths, curr_begin, curr_end);
293  curr_begin = curr_end;
294  }
295 }
296 
297 void
299  unsigned int last_n_entries,
300  std::map<std::string, unsigned short> & col_widths,
301  std::vector<std::string>::iterator & col_begin,
302  std::vector<std::string>::iterator & col_end)
303 {
304  fillEmptyValues();
308  printRowDivider(out, col_widths, col_begin, col_end);
309  out << "|" << std::setw(_column_width) << std::left << " time"
310  << " |";
311  for (auto header_it = col_begin; header_it != col_end; ++header_it)
312  out << " " << std::setw(col_widths[*header_it]) << *header_it << "|";
313  out << "\n";
314  printRowDivider(out, col_widths, col_begin, col_end);
315 
316  auto data_it = _data.begin();
317  if (last_n_entries)
318  {
319  if (_data.size() > last_n_entries)
320  {
321  // Print a blank row to indicate that values have been ommited
322  printOmittedRow(out, col_widths, col_begin, col_end);
323 
324  // Jump to the right place in the vector
325  data_it += _data.size() - last_n_entries;
326  }
327  }
328  // Now print the remaining data rows
329  for (; data_it != _data.end(); ++data_it)
330  {
331  out << "|" << std::right << std::setw(_column_width) << std::scientific << data_it->first
332  << " |";
333  for (auto header_it = col_begin; header_it != col_end; ++header_it)
334  {
335  auto & tmp = data_it->second;
336  out << std::setw(col_widths[*header_it]) << *tmp[*header_it] << " |";
337  }
338  out << "\n";
339  }
340 
341  printRowDivider(out, col_widths, col_begin, col_end);
342 }
343 
344 void
345 FormattedTable::printCSV(const std::string & file_name, int interval, bool align)
346 {
347  fillEmptyValues();
348 
349  open(file_name);
350 
351  if (_output_row_index == 0)
352  {
359  if (align)
360  {
361  // Set the initial width to the names of the columns
362  _align_widths["time"] = 4;
363 
364  for (const auto & col_name : _column_names)
365  _align_widths[col_name] = col_name.size();
366 
367  // Loop through the various times
368  for (const auto & it : _data)
369  {
370  // Update the time _align_width
371  {
372  std::ostringstream oss;
373  oss << std::setprecision(_csv_precision) << it.first;
374  unsigned int w = oss.str().size();
375  _align_widths["time"] = std::max(_align_widths["time"], w);
376  }
377 
378  // Loop through the data for the current time and update the _align_widths
379  for (const auto & jt : it.second)
380  {
381  std::ostringstream oss;
382  oss << std::setprecision(_csv_precision) << *jt.second;
383  unsigned int w = oss.str().size();
384  _align_widths[jt.first] = std::max(_align_widths[jt.first], w);
385  }
386  }
387  }
388 
389  // Output Header
390  if (!_headers_output)
391  {
392  if (_output_time)
393  {
394  if (align)
395  _output_file << std::setw(_align_widths["time"]) << "time";
396  else
397  _output_file << "time";
398  _headers_output = true;
399  }
400 
401  for (const auto & col_name : _column_names)
402  {
403  if (_headers_output)
405 
406  if (align)
407  _output_file << std::right << std::setw(_align_widths[col_name]) << col_name;
408  else
409  _output_file << col_name;
410  _headers_output = true;
411  }
412  _output_file << "\n";
413  }
414  }
415 
416  for (; _output_row_index < _data.size(); ++_output_row_index)
417  {
418  if (_output_row_index % interval == 0)
420  }
421 
422  _output_file.flush();
423 }
424 
425 void
427  std::pair<Real, std::map<std::string, std::shared_ptr<TableValueBase>>> & row_data, bool align)
428 {
429  bool first = true;
430 
431  if (_output_time)
432  {
433  if (align)
434  _output_file << std::setprecision(_csv_precision) << std::right
435  << std::setw(_align_widths["time"]) << row_data.first;
436  else
437  _output_file << std::setprecision(_csv_precision) << row_data.first;
438  first = false;
439  }
440 
441  for (const auto & col_name : _column_names)
442  {
443  std::map<std::string, std::shared_ptr<TableValueBase>> & tmp = row_data.second;
444 
445  if (!first)
447  else
448  first = false;
449 
450  if (align)
451  _output_file << std::setprecision(_csv_precision) << std::right
452  << std::setw(_align_widths[col_name]) << *tmp[col_name];
453  else
454  _output_file << std::setprecision(_csv_precision) << *tmp[col_name];
455  }
456  _output_file << "\n";
457 }
458 
459 // const strings that the gnuplot generator needs
460 namespace gnuplot
461 {
462 const std::string before_terminal = "set terminal ";
463 const std::string before_ext = "\nset output 'all";
464 const std::string after_ext =
465  "'\nset title 'All Postprocessors'\nset xlabel 'time'\nset ylabel 'values'\nplot";
466 }
467 
468 void
469 FormattedTable::makeGnuplot(const std::string & base_file, const std::string & format)
470 {
471  fillEmptyValues();
472 
473  // TODO: run this once at end of simulation, right now it runs every iteration
474  // TODO: do I need to be more careful escaping column names?
475  // Note: open and close the files each time, having open files may mess with gnuplot
476 
477  // supported filetypes: ps, png
478  std::string extension, terminal;
479  if (format == "png")
480  {
481  extension = ".png";
482  terminal = "png";
483  }
484 
485  else if (format == "ps")
486  {
487  extension = ".ps";
488  terminal = "postscript";
489  }
490 
491  else if (format == "gif")
492  {
493  extension = ".gif";
494  terminal = "gif";
495  }
496 
497  else
498  mooseError("gnuplot format \"" + format + "\" is not supported.");
499 
500  // Write the data to disk
501  std::string dat_name = base_file + ".dat";
502  std::ofstream datfile;
503  datfile.open(dat_name.c_str(), std::ios::trunc | std::ios::out);
504  if (datfile.fail())
505  mooseError("Unable to open file ", dat_name);
506 
507  datfile << "# time";
508  for (const auto & col_name : _column_names)
509  datfile << '\t' << col_name;
510  datfile << '\n';
511 
512  for (auto & data_it : _data)
513  {
514  datfile << data_it.first;
515  for (const auto & col_name : _column_names)
516  {
517  auto & tmp = data_it.second;
518  datfile << '\t' << *tmp[col_name];
519  }
520  datfile << '\n';
521  }
522  datfile.flush();
523  datfile.close();
524 
525  // Write the gnuplot script
526  std::string gp_name = base_file + ".gp";
527  std::ofstream gpfile;
528  gpfile.open(gp_name.c_str(), std::ios::trunc | std::ios::out);
529  if (gpfile.fail())
530  mooseError("Unable to open file ", gp_name);
531 
532  gpfile << gnuplot::before_terminal << terminal << gnuplot::before_ext << extension
534 
535  // plot all postprocessors in one plot
536  int column = 2;
537  for (const auto & col_name : _column_names)
538  {
539  gpfile << " '" << dat_name << "' using 1:" << column << " title '" << col_name
540  << "' with linespoints";
541  column++;
542  if (column - 2 < static_cast<int>(_column_names.size()))
543  gpfile << ", \\\n";
544  }
545  gpfile << "\n\n";
546 
547  // plot the postprocessors individually
548  column = 2;
549  for (const auto & col_name : _column_names)
550  {
551  gpfile << "set output '" << col_name << extension << "'\n";
552  gpfile << "set ylabel '" << col_name << "'\n";
553  gpfile << "plot '" << dat_name << "' using 1:" << column << " title '" << col_name
554  << "' with linespoints\n\n";
555  column++;
556  }
557 
558  gpfile.flush();
559  gpfile.close();
560 }
561 
562 void
564 {
565  _data.clear();
566  _output_file.close();
567  _output_row_index = 0;
568 }
569 
570 void
572 {
573  for (auto & it : _data)
574  for (const auto & col_name : _column_names)
575  if (!it.second[col_name])
576  it.second[col_name] =
577  std::dynamic_pointer_cast<TableValueBase>(std::make_shared<TableValue<char>>('0'));
578 }
579 
580 MooseEnum
582 {
583  return MooseEnum("ENVIRONMENT=-1 AUTO=0 80=80 120=120 160=160", "ENVIRONMENT", true);
584 }
585 
586 void
588 {
590  {
591  std::sort(_column_names.begin(), _column_names.end());
592  _column_names_unsorted = false;
593  }
594 }
595 
596 std::ostream &
597 operator<<(std::ostream & os, const TableValueBase & value)
598 {
599  value.print(os);
600  return os;
601 }
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:1062
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:284
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 ","
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:809
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:31
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:881
Real getLastTime()
Retrieve the last time (or independent variable) value.
void fillEmptyValues()
Fill any values that are not defined (usually when there are mismatched column lengths) ...
unsigned short getTermWidth(bool use_environment)
Returns the width of the terminal using sys/ioctl.
Definition: MooseUtils.C:626