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 // Used for terminal width
20 #include <sys/ioctl.h>
21 #include <cstdlib>
22 
23 const unsigned short FormattedTable::_column_width = 15;
24 const unsigned short FormattedTable::_min_pps_width = 40;
25 
26 const unsigned short DEFAULT_CSV_PRECISION = 14;
27 const std::string DEFAULT_CSV_DELIMITER = ",";
28 
29 template <>
30 void
31 dataStore(std::ostream & stream, FormattedTable & table, void * context)
32 {
33  storeHelper(stream, table._data, context);
34  storeHelper(stream, table._align_widths, context);
35  storeHelper(stream, table._column_names, context);
36  storeHelper(stream, table._output_row_index, context);
37  storeHelper(stream, table._headers_output, context);
38 }
39 
40 template <>
41 void
42 dataLoad(std::istream & stream, FormattedTable & table, void * context)
43 {
44  loadHelper(stream, table._data, context);
45  loadHelper(stream, table._align_widths, context);
46  loadHelper(stream, table._column_names, context);
47  loadHelper(stream, table._output_row_index, context);
48  loadHelper(stream, table._headers_output, context);
49 }
50 
51 void
53 {
54  if (!_output_file.is_open())
55  return;
56  _output_file.flush();
57  _output_file.close();
58  _output_file_name = "";
59 }
60 
61 void
62 FormattedTable::open(const std::string & file_name)
63 {
64  if (_output_file.is_open() && _output_file_name == file_name)
65  return;
66  close();
67  _output_file_name = file_name;
68 
69  std::ios_base::openmode open_flags = std::ios::out;
70  if (_append)
71  open_flags |= std::ios::app;
72  else
73  {
74  open_flags |= std::ios::trunc;
76  _headers_output = false;
77  }
78 
79  _output_file.open(file_name.c_str(), open_flags);
80  if (_output_file.fail())
81  mooseError("Unable to open file ", file_name);
82 }
83 
85  : _output_row_index(0),
86  _headers_output(false),
87  _append(false),
88  _output_time(true),
89  _csv_delimiter(DEFAULT_CSV_DELIMITER),
90  _csv_precision(DEFAULT_CSV_PRECISION)
91 {
92 }
93 
95  : _column_names(o._column_names),
96  _output_file_name(""),
97  _output_row_index(o._output_row_index),
98  _headers_output(o._headers_output),
99  _append(o._append),
100  _output_time(o._output_time),
101  _csv_delimiter(o._csv_delimiter),
102  _csv_precision(o._csv_precision),
103  _column_names_unsorted(o._column_names_unsorted)
104 {
105  if (_output_file.is_open())
106  mooseError("Copying a FormattedTable with an open stream is not supported");
107 
108  for (const auto & it : o._data)
109  _data.emplace_back(it.first, it.second);
110 }
111 
113 
114 bool
116 {
117  return _data.empty();
118 }
119 
120 void
121 FormattedTable::append(bool append_existing_file)
122 {
123  _append = append_existing_file;
124 }
125 
126 void
128 {
129  _data.emplace_back(time, std::map<std::string, Real>());
130 }
131 
132 void
133 FormattedTable::addData(const std::string & name, Real value)
134 {
135  if (empty())
136  mooseError("No Data stored in the the FormattedTable");
137 
138  auto back_it = _data.rbegin();
139  back_it->second[name] = value;
140 
141  if (std::find(_column_names.begin(), _column_names.end(), name) == _column_names.end())
142  {
143  _column_names.push_back(name);
144  _column_names_unsorted = true;
145  }
146 }
147 
148 void
149 FormattedTable::addData(const std::string & name, Real value, Real time)
150 {
151  auto back_it = _data.rbegin();
152 
153  mooseAssert(back_it == _data.rend() || !MooseUtils::absoluteFuzzyLessThan(time, back_it->first),
154  "Attempting to add data to FormattedTable with the dependent variable in a "
155  "non-increasing order.\nDid you mean to use addData(std::string &, const "
156  "std::vector<Real> &)?");
157 
158  // See if the current "row" is already in the table
159  if (back_it == _data.rend() || !MooseUtils::absoluteFuzzyEqual(time, back_it->first))
160  {
161  _data.emplace_back(time, std::map<std::string, Real>());
162  back_it = _data.rbegin();
163  }
164  // Insert or update value
165  back_it->second[name] = value;
166 
167  if (std::find(_column_names.begin(), _column_names.end(), name) == _column_names.end())
168  {
169  _column_names.push_back(name);
170  _column_names_unsorted = true;
171  }
172 }
173 
174 void
175 FormattedTable::addData(const std::string & name, const std::vector<Real> & vector)
176 {
177  for (MooseIndex(vector) i = 0; i < vector.size(); ++i)
178  {
179  if (i == _data.size())
180  _data.emplace_back(i, std::map<std::string, Real>());
181 
182  mooseAssert(MooseUtils::absoluteFuzzyEqual(_data[i].first, i),
183  "Inconsistent indexing in VPP vector");
184 
185  auto & curr_entry = _data[i];
186  curr_entry.second[name] = vector[i];
187  }
188 
189  if (std::find(_column_names.begin(), _column_names.end(), name) == _column_names.end())
190  {
191  _column_names.push_back(name);
192  _column_names_unsorted = true;
193  }
194 }
195 
196 Real
198 {
199  mooseAssert(!empty(), "No Data stored in the FormattedTable");
200  return _data.rbegin()->first;
201 }
202 
203 Real &
204 FormattedTable::getLastData(const std::string & name)
205 {
206  mooseAssert(!empty(), "No Data stored in the FormattedTable");
207 
208  auto & last_data_map = _data.rbegin()->second;
209  auto it = last_data_map.find(name);
210  if (it == last_data_map.end())
211  mooseError("No Data found for name: " + name);
212 
213  return it->second;
214 }
215 
216 void
218  std::map<std::string, unsigned short> & col_widths,
219  std::vector<std::string>::iterator & col_begin,
220  std::vector<std::string>::iterator & col_end) const
221 {
222  printNoDataRow(':', ' ', out, col_widths, col_begin, col_end);
223 }
224 
225 void
227  std::map<std::string, unsigned short> & col_widths,
228  std::vector<std::string>::iterator & col_begin,
229  std::vector<std::string>::iterator & col_end) const
230 {
231  printNoDataRow('+', '-', out, col_widths, col_begin, col_end);
232 }
233 
234 void
235 FormattedTable::printNoDataRow(char intersect_char,
236  char fill_char,
237  std::ostream & out,
238  std::map<std::string, unsigned short> & col_widths,
239  std::vector<std::string>::iterator & col_begin,
240  std::vector<std::string>::iterator & col_end) const
241 {
242  out.fill(fill_char);
243  out << std::right << intersect_char << std::setw(_column_width + 2) << intersect_char;
244  for (auto header_it = col_begin; header_it != col_end; ++header_it)
245  out << std::setw(col_widths[*header_it] + 2) << intersect_char;
246  out << "\n";
247 
248  // Clear the fill character
249  out.fill(' ');
250 }
251 
252 void
253 FormattedTable::printTable(const std::string & file_name)
254 {
255  open(file_name);
257 }
258 
259 void
260 FormattedTable::printTable(std::ostream & out, unsigned int last_n_entries)
261 {
262  printTable(out, last_n_entries, MooseEnum("ENVIRONMENT=-1", "ENVIRONMENT"));
263 }
264 
265 void
266 FormattedTable::printTable(std::ostream & out,
267  unsigned int last_n_entries,
268  const MooseEnum & suggested_term_width)
269 {
270  unsigned short term_width;
271 
272  if (suggested_term_width == "ENVIRONMENT")
273  term_width = getTermWidth(true);
274  else if (suggested_term_width == "AUTO")
275  term_width = getTermWidth(false);
276  else
277  term_width = MooseUtils::stringToInteger(suggested_term_width);
278 
279  if (term_width < _min_pps_width)
280  term_width = _min_pps_width;
281 
282  std::vector<std::string>::iterator col_it = _column_names.begin();
283  std::vector<std::string>::iterator col_end = _column_names.end();
284 
285  std::vector<std::string>::iterator curr_begin = col_it;
286  std::vector<std::string>::iterator curr_end;
287  while (col_it != col_end)
288  {
289  std::map<std::string, unsigned short> col_widths;
290  unsigned int curr_width = _column_width + 4;
291  unsigned int cols_in_group = 0;
292  while (curr_width < term_width && col_it != col_end)
293  {
294  curr_end = col_it;
295  col_widths[*col_it] = col_it->length() > _column_width ? col_it->length() + 1 : _column_width;
296 
297  curr_width += col_widths[*col_it] + 3;
298  ++col_it;
299  ++cols_in_group;
300  }
301  if (col_it != col_end && cols_in_group >= 2)
302  {
303  // curr_width -= col_widths[*curr_end];
304  col_widths.erase(*curr_end);
305  col_it = curr_end;
306  }
307  else
308  curr_end = col_it;
309 
310  printTablePiece(out, last_n_entries, col_widths, curr_begin, curr_end);
311  curr_begin = curr_end;
312  }
313 }
314 
315 void
317  unsigned int last_n_entries,
318  std::map<std::string, unsigned short> & col_widths,
319  std::vector<std::string>::iterator & col_begin,
320  std::vector<std::string>::iterator & col_end)
321 {
325  printRowDivider(out, col_widths, col_begin, col_end);
326  out << "|" << std::setw(_column_width) << std::left << " time"
327  << " |";
328  for (auto header_it = col_begin; header_it != col_end; ++header_it)
329  out << " " << std::setw(col_widths[*header_it]) << *header_it << "|";
330  out << "\n";
331  printRowDivider(out, col_widths, col_begin, col_end);
332 
333  auto data_it = _data.begin();
334  if (last_n_entries)
335  {
336  if (_data.size() > last_n_entries)
337  {
338  // Print a blank row to indicate that values have been ommited
339  printOmittedRow(out, col_widths, col_begin, col_end);
340 
341  // Jump to the right place in the vector
342  data_it += _data.size() - last_n_entries;
343  }
344  }
345  // Now print the remaining data rows
346  for (; data_it != _data.end(); ++data_it)
347  {
348  out << "|" << std::right << std::setw(_column_width) << std::scientific << data_it->first
349  << " |";
350  for (auto header_it = col_begin; header_it != col_end; ++header_it)
351  {
352  auto & tmp = data_it->second;
353  out << std::setw(col_widths[*header_it]) << tmp[*header_it] << " |";
354  }
355  out << "\n";
356  }
357 
358  printRowDivider(out, col_widths, col_begin, col_end);
359 }
360 
361 void
362 FormattedTable::printCSV(const std::string & file_name, int interval, bool align)
363 {
364  open(file_name);
365 
366  if (_output_row_index == 0)
367  {
374  if (align)
375  {
376  // Set the initial width to the names of the columns
377  _align_widths["time"] = 4;
378 
379  for (const auto & col_name : _column_names)
380  _align_widths[col_name] = col_name.size();
381 
382  // Loop through the various times
383  for (const auto & it : _data)
384  {
385  // Update the time _align_width
386  {
387  std::ostringstream oss;
388  oss << std::setprecision(_csv_precision) << it.first;
389  unsigned int w = oss.str().size();
390  _align_widths["time"] = std::max(_align_widths["time"], w);
391  }
392 
393  // Loop through the data for the current time and update the _align_widths
394  for (const auto & jt : it.second)
395  {
396  std::ostringstream oss;
397  oss << std::setprecision(_csv_precision) << jt.second;
398  unsigned int w = oss.str().size();
399  _align_widths[jt.first] = std::max(_align_widths[jt.first], w);
400  }
401  }
402  }
403 
404  // Output Header
405  if (!_headers_output)
406  {
407  if (_output_time)
408  {
409  if (align)
410  _output_file << std::setw(_align_widths["time"]) << "time";
411  else
412  _output_file << "time";
413  _headers_output = true;
414  }
415 
416  for (const auto & col_name : _column_names)
417  {
418  if (_headers_output)
420 
421  if (align)
422  _output_file << std::right << std::setw(_align_widths[col_name]) << col_name;
423  else
424  _output_file << col_name;
425  _headers_output = true;
426  }
427  _output_file << "\n";
428  }
429  }
430 
431  for (; _output_row_index < _data.size(); ++_output_row_index)
432  {
433  if (_output_row_index % interval == 0)
435  }
436 
437  _output_file.flush();
438 }
439 
440 void
441 FormattedTable::printRow(std::pair<Real, std::map<std::string, Real>> & row_data, bool align)
442 {
443  bool first = true;
444 
445  if (_output_time)
446  {
447  if (align)
448  _output_file << std::setprecision(_csv_precision) << std::right
449  << std::setw(_align_widths["time"]) << row_data.first;
450  else
451  _output_file << std::setprecision(_csv_precision) << row_data.first;
452  first = false;
453  }
454 
455  for (const auto & col_name : _column_names)
456  {
457  std::map<std::string, Real> & tmp = row_data.second;
458 
459  if (!first)
461  else
462  first = false;
463 
464  if (align)
465  _output_file << std::setprecision(_csv_precision) << std::right
466  << std::setw(_align_widths[col_name]) << tmp[col_name];
467  else
468  _output_file << std::setprecision(_csv_precision) << tmp[col_name];
469  }
470  _output_file << "\n";
471 }
472 
473 // const strings that the gnuplot generator needs
474 namespace gnuplot
475 {
476 const std::string before_terminal = "set terminal ";
477 const std::string before_ext = "\nset output 'all";
478 const std::string after_ext =
479  "'\nset title 'All Postprocessors'\nset xlabel 'time'\nset ylabel 'values'\nplot";
480 }
481 
482 void
483 FormattedTable::makeGnuplot(const std::string & base_file, const std::string & format)
484 {
485  // TODO: run this once at end of simulation, right now it runs every iteration
486  // TODO: do I need to be more careful escaping column names?
487  // Note: open and close the files each time, having open files may mess with gnuplot
488 
489  // supported filetypes: ps, png
490  std::string extension, terminal;
491  if (format == "png")
492  {
493  extension = ".png";
494  terminal = "png";
495  }
496 
497  else if (format == "ps")
498  {
499  extension = ".ps";
500  terminal = "postscript";
501  }
502 
503  else if (format == "gif")
504  {
505  extension = ".gif";
506  terminal = "gif";
507  }
508 
509  else
510  mooseError("gnuplot format \"" + format + "\" is not supported.");
511 
512  // Write the data to disk
513  std::string dat_name = base_file + ".dat";
514  std::ofstream datfile;
515  datfile.open(dat_name.c_str(), std::ios::trunc | std::ios::out);
516  if (datfile.fail())
517  mooseError("Unable to open file ", dat_name);
518 
519  datfile << "# time";
520  for (const auto & col_name : _column_names)
521  datfile << '\t' << col_name;
522  datfile << '\n';
523 
524  for (auto & data_it : _data)
525  {
526  datfile << data_it.first;
527  for (const auto & col_name : _column_names)
528  {
529  auto & tmp = data_it.second;
530  datfile << '\t' << tmp[col_name];
531  }
532  datfile << '\n';
533  }
534  datfile.flush();
535  datfile.close();
536 
537  // Write the gnuplot script
538  std::string gp_name = base_file + ".gp";
539  std::ofstream gpfile;
540  gpfile.open(gp_name.c_str(), std::ios::trunc | std::ios::out);
541  if (gpfile.fail())
542  mooseError("Unable to open file ", gp_name);
543 
544  gpfile << gnuplot::before_terminal << terminal << gnuplot::before_ext << extension
546 
547  // plot all postprocessors in one plot
548  int column = 2;
549  for (const auto & col_name : _column_names)
550  {
551  gpfile << " '" << dat_name << "' using 1:" << column << " title '" << col_name
552  << "' with linespoints";
553  column++;
554  if (column - 2 < static_cast<int>(_column_names.size()))
555  gpfile << ", \\\n";
556  }
557  gpfile << "\n\n";
558 
559  // plot the postprocessors individually
560  column = 2;
561  for (const auto & col_name : _column_names)
562  {
563  gpfile << "set output '" << col_name << extension << "'\n";
564  gpfile << "set ylabel '" << col_name << "'\n";
565  gpfile << "plot '" << dat_name << "' using 1:" << column << " title '" << col_name
566  << "' with linespoints\n\n";
567  column++;
568  }
569 
570  gpfile.flush();
571  gpfile.close();
572 }
573 
574 void
576 {
577  _data.clear();
578 }
579 
580 unsigned short
581 FormattedTable::getTermWidth(bool use_environment) const
582 {
583  struct winsize w;
588  w.ws_col = std::numeric_limits<unsigned short>::max();
589 
590  if (use_environment)
591  {
592  char * pps_width = std::getenv("MOOSE_PPS_WIDTH");
593  if (pps_width != NULL)
594  {
595  std::stringstream ss(pps_width);
596  ss >> w.ws_col;
597  }
598  }
599  else
600  {
601  try
602  {
603  ioctl(0, TIOCGWINSZ, &w);
604  }
605  catch (...)
606  {
607  // Something bad happened, make sure we have a sane value
608  w.ws_col = std::numeric_limits<unsigned short>::max();
609  }
610  }
611 
612  return w.ws_col;
613 }
614 
615 MooseEnum
617 {
618  return MooseEnum("ENVIRONMENT=-1 AUTO=0 80=80 120=120 160=160", "ENVIRONMENT", true);
619 }
620 
621 void
623 {
625  {
626  std::sort(_column_names.begin(), _column_names.end());
627  _column_names_unsorted = false;
628  }
629 }
std::vector< std::pair< Real, std::map< std::string, Real > > > _data
Data structure for the console table: The first part of the pair tracks the independent variable (nor...
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: MooseUtils.h:231
~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:682
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:207
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.
bool _column_names_unsorted
Flag indicating that sorting is necessary (used by sortColumns method).
bool empty() const
Returns a boolean value based on whether the FormattedTable contains data or not. ...
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)
void storeHelper(std::ostream &stream, P &data, void *context)
Scalar helper routine.
Definition: DataIO.h:677
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)
unsigned short getTermWidth(bool use_environment) const
Returns the width of the terminal using sys/ioctl.
void printRow(std::pair< Real, std::map< std::string, Real >> &row_data, bool align)
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
bool absoluteFuzzyLessThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is less than another variable within an absolute tolerance...
Definition: MooseUtils.h:322
std::map< std::string, unsigned int > _align_widths
Alignment widths (only used if asked to print aligned to CSV output)
Real & getLastData(const std::string &name)
Retrieve Data for last value of given name.
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.
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 addData(const std::string &name, Real value)
Method for adding data to the output table.
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
std::vector< std::string > _column_names
The set of column names updated when data is inserted through the setter methods. ...
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
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:741
Real getLastTime()
Retrieve the last time (or independent variable) value.