www.mooseframework.org
FormattedTable.C
Go to the documentation of this file.
1 /****************************************************************/
2 /* DO NOT MODIFY THIS HEADER */
3 /* MOOSE - Multiphysics Object Oriented Simulation Environment */
4 /* */
5 /* (c) 2010 Battelle Energy Alliance, LLC */
6 /* ALL RIGHTS RESERVED */
7 /* */
8 /* Prepared by Battelle Energy Alliance, LLC */
9 /* Under Contract No. DE-AC07-05ID14517 */
10 /* With the U. S. Department of Energy */
11 /* */
12 /* See COPYRIGHT for full restrictions */
13 /****************************************************************/
14 
15 #include "FormattedTable.h"
16 #include "MooseError.h"
17 #include "InfixIterator.h"
18 #include "MooseUtils.h"
19 
20 #include "libmesh/exodusII_io.h"
21 
22 #include <iomanip>
23 #include <iterator>
24 
25 // Used for terminal width
26 #include <sys/ioctl.h>
27 #include <cstdlib>
28 
29 const unsigned short FormattedTable::_column_width = 15;
30 const unsigned short FormattedTable::_min_pps_width = 40;
31 
32 const unsigned short DEFAULT_CSV_PRECISION = 14;
33 const std::string DEFAULT_CSV_DELIMITER = ",";
34 
35 template <>
36 void
37 dataStore(std::ostream & stream, FormattedTable & table, void * context)
38 {
39  storeHelper(stream, table._data, context);
40  storeHelper(stream, table._align_widths, context);
41  storeHelper(stream, table._column_names, context);
42  storeHelper(stream, table._output_row_index, context);
43 }
44 
45 template <>
46 void
47 dataLoad(std::istream & stream, FormattedTable & table, void * context)
48 {
49  loadHelper(stream, table._data, context);
50  loadHelper(stream, table._align_widths, context);
51  loadHelper(stream, table._column_names, context);
52  loadHelper(stream, table._output_row_index, context);
53 
54  // Don't assume that the stream is open if we've restored.
55  table._stream_open = false;
56 }
57 
58 void
60 {
61  if (!_stream_open)
62  return;
63  _output_file.flush();
64  _output_file.close();
65  _stream_open = false;
66  _output_file_name = "";
67 }
68 
69 void
70 FormattedTable::open(const std::string & file_name)
71 {
72  if (_stream_open && _output_file_name == file_name)
73  return;
74  close();
75  _output_file_name = file_name;
76 
77  std::ios_base::openmode open_flags = std::ios::out;
78  if (_append)
79  open_flags |= std::ios::app;
80  else
81  {
82  open_flags |= std::ios::trunc;
84  }
85 
86  _output_file.open(file_name.c_str(), open_flags);
87  _stream_open = true;
88 }
89 
91  : _output_row_index(0),
92  _stream_open(false),
93  _append(false),
94  _output_time(true),
97 {
98 }
99 
102  _output_file_name(""),
105  _append(o._append),
110 {
111  if (_stream_open)
112  mooseError("Copying a FormattedTable with an open stream is not supported");
113 
114  for (const auto & it : o._data)
115  _data.emplace_back(it.first, it.second);
116 }
117 
119 
120 bool
122 {
123  return _data.empty();
124 }
125 
126 void
127 FormattedTable::append(bool append_existing_file)
128 {
129  _append = append_existing_file;
130 }
131 
132 void
133 FormattedTable::addData(const std::string & name, Real value, Real time)
134 {
135  auto back_it = _data.rbegin();
136 
137  mooseAssert(back_it == _data.rend() || !MooseUtils::absoluteFuzzyLessThan(time, back_it->first),
138  "Attempting to add data to FormattedTable with the dependent variable in a "
139  "non-increasing order.\nDid you mean to use addData(std::string &, const "
140  "std::vector<Real> &)?");
141 
142  // See if the current "row" is already in the table
143  if (back_it == _data.rend() || !MooseUtils::absoluteFuzzyEqual(time, back_it->first))
144  {
145  _data.emplace_back(time, std::map<std::string, Real>());
146  back_it = _data.rbegin();
147  }
148  // Insert or update value
149  back_it->second[name] = value;
150 
151  if (std::find(_column_names.begin(), _column_names.end(), name) == _column_names.end())
152  _column_names.push_back(name);
153  _column_names_unsorted = true;
154 }
155 
156 void
157 FormattedTable::addData(const std::string & name, const std::vector<Real> & vector)
158 {
159  for (auto i = beginIndex(vector); i < vector.size(); ++i)
160  {
161  if (i == _data.size())
162  _data.emplace_back(i, std::map<std::string, Real>());
163 
164  mooseAssert(MooseUtils::absoluteFuzzyEqual(_data[i].first, i),
165  "Inconsistent indexing in VPP vector");
166 
167  auto & curr_entry = _data[i];
168  curr_entry.second[name] = vector[i];
169  }
170 
171  if (std::find(_column_names.begin(), _column_names.end(), name) == _column_names.end())
172  _column_names.push_back(name);
173  _column_names_unsorted = true;
174 }
175 
176 Real &
177 FormattedTable::getLastData(const std::string & name)
178 {
179  mooseAssert(!empty(), "No Data stored in the FormattedTable");
180 
181  auto & last_data_map = _data.rbegin()->second;
182  auto it = last_data_map.find(name);
183  if (it == last_data_map.end())
184  mooseError("No Data found for name: " + name);
185 
186  return it->second;
187 }
188 
189 void
191  std::map<std::string, unsigned short> & col_widths,
192  std::vector<std::string>::iterator & col_begin,
193  std::vector<std::string>::iterator & col_end) const
194 {
195  printNoDataRow(':', ' ', out, col_widths, col_begin, col_end);
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
208 FormattedTable::printNoDataRow(char intersect_char,
209  char fill_char,
210  std::ostream & out,
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  out.fill(fill_char);
216  out << std::right << intersect_char << std::setw(_column_width + 2) << intersect_char;
217  for (auto header_it = col_begin; header_it != col_end; ++header_it)
218  out << std::setw(col_widths[*header_it] + 2) << intersect_char;
219  out << "\n";
220 
221  // Clear the fill character
222  out.fill(' ');
223 }
224 
225 void
226 FormattedTable::printTable(const std::string & file_name)
227 {
228  open(file_name);
230 }
231 
232 void
233 FormattedTable::printTable(std::ostream & out, unsigned int last_n_entries)
234 {
235  printTable(out, last_n_entries, MooseEnum("ENVIRONMENT=-1", "ENVIRONMENT"));
236 }
237 
238 void
239 FormattedTable::printTable(std::ostream & out,
240  unsigned int last_n_entries,
241  const MooseEnum & suggested_term_width)
242 {
243  unsigned short term_width;
244 
245  if (suggested_term_width == "ENVIRONMENT")
246  term_width = getTermWidth(true);
247  else if (suggested_term_width == "AUTO")
248  term_width = getTermWidth(false);
249  else
250  term_width = suggested_term_width;
251 
252  if (term_width < _min_pps_width)
253  term_width = _min_pps_width;
254 
255  std::vector<std::string>::iterator col_it = _column_names.begin();
256  std::vector<std::string>::iterator col_end = _column_names.end();
257 
258  std::vector<std::string>::iterator curr_begin = col_it;
259  std::vector<std::string>::iterator curr_end;
260  while (col_it != col_end)
261  {
262  std::map<std::string, unsigned short> col_widths;
263  unsigned int curr_width = _column_width + 4;
264  unsigned int cols_in_group = 0;
265  while (curr_width < term_width && col_it != col_end)
266  {
267  curr_end = col_it;
268  col_widths[*col_it] = col_it->length() > _column_width ? col_it->length() + 1 : _column_width;
269 
270  curr_width += col_widths[*col_it] + 3;
271  ++col_it;
272  ++cols_in_group;
273  }
274  if (col_it != col_end && cols_in_group >= 2)
275  {
276  // curr_width -= col_widths[*curr_end];
277  col_widths.erase(*curr_end);
278  col_it = curr_end;
279  }
280  else
281  curr_end = col_it;
282 
283  printTablePiece(out, last_n_entries, col_widths, curr_begin, curr_end);
284  curr_begin = curr_end;
285  }
286 }
287 
288 void
290  unsigned int last_n_entries,
291  std::map<std::string, unsigned short> & col_widths,
292  std::vector<std::string>::iterator & col_begin,
293  std::vector<std::string>::iterator & col_end)
294 {
298  printRowDivider(out, col_widths, col_begin, col_end);
299  out << "|" << std::setw(_column_width) << std::left << " time"
300  << " |";
301  for (auto header_it = col_begin; header_it != col_end; ++header_it)
302  out << " " << std::setw(col_widths[*header_it]) << *header_it << "|";
303  out << "\n";
304  printRowDivider(out, col_widths, col_begin, col_end);
305 
306  auto data_it = _data.begin();
307  if (last_n_entries)
308  {
309  if (_data.size() > last_n_entries)
310  {
311  // Print a blank row to indicate that values have been ommited
312  printOmittedRow(out, col_widths, col_begin, col_end);
313 
314  // Jump to the right place in the vector
315  data_it += _data.size() - last_n_entries;
316  }
317  }
318  // Now print the remaining data rows
319  for (; data_it != _data.end(); ++data_it)
320  {
321  out << "|" << std::right << std::setw(_column_width) << std::scientific << data_it->first
322  << " |";
323  for (auto header_it = col_begin; header_it != col_end; ++header_it)
324  {
325  auto & tmp = data_it->second;
326  out << std::setw(col_widths[*header_it]) << tmp[*header_it] << " |";
327  }
328  out << "\n";
329  }
330 
331  printRowDivider(out, col_widths, col_begin, col_end);
332 }
333 
334 void
335 FormattedTable::printCSV(const std::string & file_name, int interval, bool align)
336 {
337  open(file_name);
338 
339  if (_output_row_index == 0)
340  {
347  if (align)
348  {
349  // Set the initial width to the names of the columns
350  _align_widths["time"] = 4;
351 
352  for (const auto & col_name : _column_names)
353  _align_widths[col_name] = col_name.size();
354 
355  // Loop through the various times
356  for (const auto & it : _data)
357  {
358  // Update the time _align_width
359  {
360  std::ostringstream oss;
361  oss << std::setprecision(_csv_precision) << it.first;
362  unsigned int w = oss.str().size();
363  _align_widths["time"] = std::max(_align_widths["time"], w);
364  }
365 
366  // Loop through the data for the current time and update the _align_widths
367  for (const auto & jt : it.second)
368  {
369  std::ostringstream oss;
370  oss << std::setprecision(_csv_precision) << jt.second;
371  unsigned int w = oss.str().size();
372  _align_widths[jt.first] = std::max(_align_widths[jt.first], w);
373  }
374  }
375  }
376 
377  // Output Header
378  {
379  bool first = true;
380 
381  if (_output_time)
382  {
383  if (align)
384  _output_file << std::setw(_align_widths["time"]) << "time";
385  else
386  _output_file << "time";
387  first = false;
388  }
389 
390  for (const auto & col_name : _column_names)
391  {
392  if (!first)
394 
395  if (align)
396  _output_file << std::right << std::setw(_align_widths[col_name]) << col_name;
397  else
398  _output_file << col_name;
399  first = false;
400  }
401  _output_file << "\n";
402  }
403  }
404 
405  for (; _output_row_index < _data.size(); ++_output_row_index)
406  {
407  if (_output_row_index % interval == 0)
409  }
410 
411  _output_file.flush();
412 }
413 
414 void
415 FormattedTable::printRow(std::pair<Real, std::map<std::string, Real>> & row_data, bool align)
416 {
417  bool first = true;
418 
419  if (_output_time)
420  {
421  if (align)
422  _output_file << std::setprecision(_csv_precision) << std::right
423  << std::setw(_align_widths["time"]) << row_data.first;
424  else
425  _output_file << std::setprecision(_csv_precision) << row_data.first;
426  first = false;
427  }
428 
429  for (const auto & col_name : _column_names)
430  {
431  std::map<std::string, Real> & tmp = row_data.second;
432 
433  if (!first)
435  else
436  first = false;
437 
438  if (align)
439  _output_file << std::setprecision(_csv_precision) << std::right
440  << std::setw(_align_widths[col_name]) << tmp[col_name];
441  else
442  _output_file << std::setprecision(_csv_precision) << tmp[col_name];
443  }
444  _output_file << "\n";
445 }
446 
447 // const strings that the gnuplot generator needs
448 namespace gnuplot
449 {
450 const std::string before_terminal = "set terminal ";
451 const std::string before_ext = "\nset output 'all";
452 const std::string after_ext =
453  "'\nset title 'All Postprocessors'\nset xlabel 'time'\nset ylabel 'values'\nplot";
454 }
455 
456 void
457 FormattedTable::makeGnuplot(const std::string & base_file, const std::string & format)
458 {
459  // TODO: run this once at end of simulation, right now it runs every iteration
460  // TODO: do I need to be more careful escaping column names?
461  // Note: open and close the files each time, having open files may mess with gnuplot
462 
463  // supported filetypes: ps, png
464  std::string extension, terminal;
465  if (format == "png")
466  {
467  extension = ".png";
468  terminal = "png";
469  }
470 
471  else if (format == "ps")
472  {
473  extension = ".ps";
474  terminal = "postscript";
475  }
476 
477  else if (format == "gif")
478  {
479  extension = ".gif";
480  terminal = "gif";
481  }
482 
483  else
484  mooseError("gnuplot format \"" + format + "\" is not supported.");
485 
486  // Write the data to disk
487  std::string dat_name = base_file + ".dat";
488  std::ofstream datfile;
489  datfile.open(dat_name.c_str(), std::ios::trunc | std::ios::out);
490 
491  datfile << "# time";
492  for (const auto & col_name : _column_names)
493  datfile << '\t' << col_name;
494  datfile << '\n';
495 
496  for (auto & data_it : _data)
497  {
498  datfile << data_it.first;
499  for (const auto & col_name : _column_names)
500  {
501  auto & tmp = data_it.second;
502  datfile << '\t' << tmp[col_name];
503  }
504  datfile << '\n';
505  }
506  datfile.flush();
507  datfile.close();
508 
509  // Write the gnuplot script
510  std::string gp_name = base_file + ".gp";
511  std::ofstream gpfile;
512  gpfile.open(gp_name.c_str(), std::ios::trunc | std::ios::out);
513 
514  gpfile << gnuplot::before_terminal << terminal << gnuplot::before_ext << extension
516 
517  // plot all postprocessors in one plot
518  int column = 2;
519  for (const auto & col_name : _column_names)
520  {
521  gpfile << " '" << dat_name << "' using 1:" << column << " title '" << col_name
522  << "' with linespoints";
523  column++;
524  if (column - 2 < static_cast<int>(_column_names.size()))
525  gpfile << ", \\\n";
526  }
527  gpfile << "\n\n";
528 
529  // plot the postprocessors individually
530  column = 2;
531  for (const auto & col_name : _column_names)
532  {
533  gpfile << "set output '" << col_name << extension << "'\n";
534  gpfile << "set ylabel '" << col_name << "'\n";
535  gpfile << "plot '" << dat_name << "' using 1:" << column << " title '" << col_name
536  << "' with linespoints\n\n";
537  column++;
538  }
539 
540  gpfile.flush();
541  gpfile.close();
542 
543  // Run the gnuplot script
544  /* We aren't going to run gnuplot automatically
545 
546  if (!system(NULL))
547  mooseError("No way to run gnuplot on this computer");
548 
549  std::string command = "gnuplot " + gp_name;
550  if (system(command.c_str()))
551  mooseError("gnuplot command failed");
552  */
553 }
554 
555 void
557 {
558  _data.clear();
559 }
560 
561 unsigned short
562 FormattedTable::getTermWidth(bool use_environment) const
563 {
564  struct winsize w;
569  w.ws_col = std::numeric_limits<unsigned short>::max();
570 
571  if (use_environment)
572  {
573  char * pps_width = std::getenv("MOOSE_PPS_WIDTH");
574  if (pps_width != NULL)
575  {
576  std::stringstream ss(pps_width);
577  ss >> w.ws_col;
578  }
579  }
580  else
581  {
582  try
583  {
584  ioctl(0, TIOCGWINSZ, &w);
585  }
586  catch (...)
587  {
588  // Something bad happened, make sure we have a sane value
589  w.ws_col = std::numeric_limits<unsigned short>::max();
590  }
591  }
592 
593  return w.ws_col;
594 }
595 
596 MooseEnum
598 {
599  return MooseEnum("ENVIRONMENT=-1 AUTO=0 80=80 120=120 160=160", "ENVIRONMENT", true);
600 }
601 
602 void
604 {
606  {
607  std::sort(_column_names.begin(), _column_names.end());
608  _column_names_unsorted = false;
609  }
610 }
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...
~FormattedTable()
The destructor is used to close the file handle.
bool empty() const
Returns a boolean value based on whether the FormattedTable contains data or not. ...
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)
bool _stream_open
Keeps track of whether the current stream is open or not.
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 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 mooseError(Args &&...args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:182
bool _output_time
Whether or not to output the Time column.
void close()
Close the underlying output file stream if any. This is idempotent.
unsigned short getTermWidth(bool use_environment) const
Returns the width of the terminal using sys/ioctl.
bool _column_names_unsorted
Flag indicating that sorting is necessary (used by sortColumns method).
void addData(const std::string &name, Real value, Real time)
Method for adding data to the output table.
bool absoluteFuzzyLessThan(const libMesh::Real &var1, const libMesh::Real &var2, const libMesh::Real &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is less than another variable within an absolute tolerance...
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:528
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)
void printRow(std::pair< Real, std::map< std::string, Real >> &row_data, bool align)
const unsigned short DEFAULT_CSV_PRECISION
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:37
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
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 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...
const std::string before_ext
bool absoluteFuzzyEqual(const libMesh::Real &var1, const libMesh::Real &var2, const libMesh::Real &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
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:592
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
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