gambit is hosted by Hepforge, IPPP Durham
GAMBIT  v1.5.0-2191-ga4742ac
a Global And Modular Bsm Inference Tool
asciiprinter.cpp
Go to the documentation of this file.
1 // GAMBIT: Global and Modular BSM Inference Tool
2 // *********************************************
20 
21 
22 // Standard libraries
23 #include <map>
24 #include <vector>
25 #include <algorithm>
26 #include <ios>
27 #include <sstream>
28 #include <fstream>
29 #include <iomanip>
30 
31 // Gambit
36 
37 // MPI bindings
39 
40 // Switch for debugging output (manual at the moment)
41 
42 //#define AP_DEBUG_MODE
43 
44 #ifdef AP_DEBUG_MODE
45  #define AP_DBUG(x) x
46 #else
47  #define AP_DBUG(x)
48 #endif
49 
50 
51 // Code!
52 namespace Gambit
53 {
54 
55  namespace Printers
56  {
57 
59  //TODO: It would be good to add something like this to the Gambit Utils to use as a standard I think.
60 
61  void open_output_file(std::ofstream& output, std::string filename, std::ios_base::openmode mode)
62  {
63  // Pass in reference to externally created ofstream "output"
64  output.open(filename, std::ofstream::out | mode);
65 
66  if( output.fail() | output.bad() )
67  {
68  std::ostringstream ss;
69  ss << "IO error while opening file for writing! Tried to open ofstream to file \""<<filename<<"\", but encountered error bit in the created ostream.";
70  throw std::runtime_error( ss.str() );
71  }
72  }
73 
74  Record::Record() : readyToPrint(false) {};
75 
77  {
78  data.clear();
79  readyToPrint = false;
80  }
81 
82  // Printer to ascii file (i.e. table of doubles)
83 
84  // Common constructor tasks
86  {
87  if( this->is_auxilliary_printer() ) // check if this is an auxilliary printer
88  {
89 
90  // Get stream name from printermanager
91  printer_name = options.getValue<std::string>("name");
92 
93  // Get primary printer (need to cast from BasePrinter type to asciiPrinter)
94  asciiPrinter* primary = dynamic_cast<asciiPrinter*>(this->get_primary_printer());
95 
96  // Name files based on the primary printer filenames
97  std::ostringstream f;
98  f << primary->get_output_filename() << "_" << printer_name;
99  output_file = Utils::ensure_path_exists(options.getValueOrDef<std::string>(f.str(),"output_file"));
100 
101  // Match the buffer length to the primary printer, or use a user-supplied option
102  bufferlength = options.getValueOrDef<uint>(primary->get_bufferlength(),"buffer_length");
103  }
104  else
105  {
106  printer_name = "Primary";
107 
108  std::ostringstream f;
109  if(options.hasKey("output_path"))
110  {
111  f << options.getValue<std::string>("output_path") << "/";
112  }
113  else
114  {
115  f << options.getValue<std::string>("default_output_path") << "/";
116  }
117  f << options.getValue<std::string>("output_file");
118  output_file = Utils::ensure_path_exists(f.str());
119 
120  bufferlength = options.getValueOrDef<uint>(100,"buffer_length");
121  }
122 
123  // Name "info" file to match "output" file
124  std::ostringstream finfo;
125  finfo<< output_file <<"_info";
126  info_file = finfo.str();
127 
128  #ifdef WITH_MPI
129  myRealRank = myComm.Get_rank();
130  this->setRank(myRealRank);
131  mpiSize = myComm.Get_size();
132 
133  // Append mpi rank to file names to avoid collisions between processes
134  std::ostringstream fout;
135  std::ostringstream finfo2;
136  fout << output_file <<"_"<<myRealRank;
137  finfo2<< info_file <<"_"<<myRealRank;
138  output_file = fout.str();
139  info_file = finfo2.str();
140  #endif
141 
142  // Erase contents of output_file and info_file if they already exist
143  std::ofstream output;
144  open_output_file(output, output_file, std::ofstream::trunc);
145  output.close();
146 
147  std::ofstream info;
148  open_output_file(info, info_file, std::ofstream::trunc);
149  info.close();
150  }
151 
152  // Constructor
153  asciiPrinter::asciiPrinter(const Options& options, BasePrinter* const primary)
154  : BasePrinter(primary,options.getValueOrDef<bool>(false,"auxilliary"))
155  , output_file("")
156  , info_file("")
157  , bufferlength(100)
158  , global(false)
159  , printer_name("")
160  #ifdef WITH_MPI
161  , myComm() // attaches to MPI_COMM_WORLD, beware collisions with e.g. scanning algorithms.
162  , mpiSize(1)
163  #endif
164  , lastPointID(nullpoint)
165  {
166  common_constructor(options);
167  }
168 
169 
171  // Overload the base class virtual destructor
173  {
174  // Make sure buffer is completely written to disk (MOVED TO FINALISE)
175  AP_DBUG( std::cout << "Destructing asciiPrinter object (with name=\""<<printer_name<<"\")..." << std::endl; )
176  }
177 
179  // Run by dependency resolver, which supplies the functors with a vector of VertexIDs whose requiresPrinting flags are set to true.
180  void asciiPrinter::initialise(const std::vector<int>& /*printmevec*/)
181  {
182  // Currently don't seem to need this... could use it to check if all VertexID's have submitted print requests.
183  }
184 
185  // Get options required to construct a reader object that can read
186  // the previous output of this printer.
187  // TODO: Currently unavailable
189  {
190  std::ostringstream err;
191  err << "Sorry, the asciiPrinter is currently in a state of neglect, and lacks features necessary for constructing reader objects for resume data. If you really want these features then please file a bug to make your desires known :)." << std::endl;
192  printer_error().raise(LOCAL_INFO, err.str());
193  return Options();
194  }
195 
197  void asciiPrinter::finalise(bool /*abnormal*/)
198  {
199  dump_buffer(true);
200  AP_DBUG( std::cout << "Buffer (of asciiPrinter with name=\""<<printer_name<<"\") successfully dumped..." << std::endl; )
201  }
202 
204  {
205  dump_buffer(true);
206  }
207 
210  {
211  std::ofstream my_fstream;
212  open_output_file(my_fstream, output_file, std::ofstream::trunc);
213  my_fstream.close();
214  erase_buffer();
216  }
217 
220  {
221  // Used to just erase the records, but preserve vertex IDs. Not sure this is necessary, so for now just
222  // emptying the map.
223  buffer.clear();
224  }
225 
226  // getters for internal variables
229 
230  // add results to printer buffer
231  void asciiPrinter::addtobuffer(const std::vector<double>& functor_data, const std::vector<std::string>& functor_labels, const int vID, const int rank, const int pointID)
232  {
233  //TODO: If a functor gets called twice without the printer advancing the data will currently just be overwritten. Should generate an error or something.
234 
235  // Key for accessing buffer
236  std::pair<int,int> bkey = std::make_pair(rank,pointID);
237  PPIDpair ppid(pointID,rank); // This is a bit clunky because I added PPIDpairs later, so not all asciiprinter internals have been updated to use these instead of simple pairs.
238 
239  // Register <pointID> as coming from process <rank>.
240  AP_DBUG( std::cout << "Rank "<<myRealRank<<": adding data from (ptID,rank) "<<ppid<<"; labels="<<functor_labels<<std::endl; )
241  AP_DBUG( std::cout << "Rank "<<myRealRank<<": last point was from (ptID,rank) "<<lastPointID<<std::endl; )
242  //AP_DBUG( std::cout << "Rank "<<this->getRank()<<": Note: nullpoint is (ptID,rank) "<<nullpoint<<std::endl; )
243 
244  if(lastPointID == nullpoint)
245  {
246  // No previous point; add current point
247  lastPointID = ppid;
248  }
249  else if(lastPointID == ppid)
250  {
251  // Don't need to do anything; staying on same point
252  }
253  else
254  {
255  // Moving to new point; set previous point data as "ready to print".
256  std::pair<int,int> prevbkey = std::make_pair(lastPointID.rank,lastPointID.pointID);
257  if(buffer.find(prevbkey)==buffer.end())
258  {
259  std::ostringstream err;
260  err << "Tried to move asciiPrinter buffer to new point '" << ppid << "', however the *previous* point '" << endl
261  << lastPointID << "' could not be found in the buffer (we need to set it as 'finished'). This " << endl
262  << "probably means that the old point was never actually entered into the buffer, which must " << endl
263  << "mean there is a bug in the asciiPrinter. Please report this." << endl
264  << "Debug data:" << endl
265  << " functor label: "<< functor_labels << endl
266  << " slot (rank,pointID): "<< rank <<", "<< pointID << endl;
267  printer_error().raise(LOCAL_INFO, err.str());
268  }
269 
270  buffer.at(prevbkey).readyToPrint = true;
271  lastPointID = ppid;
272 
273  // Check whether it is time to dump the (completed) buffer points to disk
274  if(buffer.size()>=bufferlength) {
275  AP_DBUG( std::cout << "asciiPrinter: Buffer full ("<< buffer.size() <<" records), running buffer dump"<<std::endl; )
276  dump_buffer();
277  }
278  }
279 
280  if( buffer.find(bkey)!=buffer.end() and buffer.at(bkey).readyToPrint==true )
281  {
282  std::ostringstream err;
283  err << "Error! Attempted to write to \"old\" model point " << endl
284  << "buffer! Bug in asciiprinter.cpp somewhere. Buffer " << endl
285  << "records are initialised with readyToPrint=false, and " << endl
286  << "should not be written to again after this flag is set " << endl
287  << "to true. The records are destroyed upon writing their " << endl
288  << "contents to disk, and there is a unique record for " << endl
289  << "every rank/pointID pair." << endl
290  << "Debug info:" << endl
291  << " functor label: "<< functor_labels << endl
292  << " slot (rank,pointID): "<< rank <<", "<< pointID << endl;
293  printer_error().raise(LOCAL_INFO, err.str());
294  }
295 
296  // Do not write suspicious points to buffer as this will require extending the dataset, which is not possible in ascii
297  if (functor_labels[0] == "Suspicious Point Code")
298  return;
299 
300  // Assign to buffer, adding keys if needed
301  buffer[bkey].data[vID] = functor_data;
302 
303  //if ( info_file_written == false )
304  //{
305  if ( label_record.find(vID)==label_record.end() or functor_labels.size()>label_record.at(vID).size() )
306  {
307  // Assume the new, longer label list is better to use. This variation of functor_data length from point to point is kind of dangerous for an ascii output file though and we might want to forbid it. There is some probability that my method of allocating the columns according to the longest used by each functor in the first buffer dump will fail.
308  label_record[vID] = functor_labels;
309  }
310  //}
311  }
312 
313  // write the printer buffer to file
314  void asciiPrinter::dump_buffer(bool force)
315  {
316  // Write record of what is in each column if we haven't done so yet
317  // Note the downside of using a map as the buffer; the order of stuff in the output file is going
318  // to be kind of haphazard due to the sorted order used by map. Will have to do more work to achieve
319  // an ordering that reflects the order of stuff in, say, the inifile.
320  // force=true -- dumps all records regardless if they are "readyToPrint"
321  AP_DBUG( std::cout << "dumping asciiprinter buffer" << std::endl; )
322  AP_DBUG( std::cout << "lfpvfc 1" << std::endl; )
323 
324  // Open output file in append mode
325  std::ofstream my_fstream;
326  open_output_file(my_fstream, output_file, std::ofstream::app);
327  my_fstream.precision(precision);
328 
329  std::map<int,int> newlineindexrecord(lineindexrecord);
330  // Work out how to organise the output file
331  // To do this we need to go through the buffer and find the maximum length of vector associated with each VertexID.
332 
333  for (Buffer::iterator
334  bufentry = buffer.begin(); bufentry != buffer.end(); ++bufentry)
335  {
336  Record& record = bufentry->second;
337  for (LineBuf::iterator
338  item = record.data.begin(); item != record.data.end(); ++item)
339  {
340  //item->first - VertexID
341  //item->second - std::vector<double> (result values)
342  int oldlen = newlineindexrecord[item->first];
343  int newlen = (item->second).size();
344  newlineindexrecord[item->first] = std::max(oldlen, newlen);
345  }
346  }
347  AP_DBUG( std::cout << "lfpvfc 2" << std::endl; )
348 
349  // Check if the output format has changed, and raise an error if so
350  if (lineindexrecord.size()==0)
351  {
352  // initialise if empty
353  lineindexrecord = newlineindexrecord;
354  }
355  else if (lineindexrecord!=newlineindexrecord)
356  {
357  std::ostringstream errmsg;
358  errmsg << "Error! Output format has changed since last buffer dump! The asciiPrinter cannot handle this!"
359  << "Details:" << std::endl;
360  // First check if a new vertexID has appeared
361  std::vector<int> new_vIDs;
362  std::vector<int> increased_lengths;
363  for (std::map<int,int>::iterator
364  it = newlineindexrecord.begin(); it != newlineindexrecord.end(); ++it)
365  {
366  // try to find each key in the old lineindexrecord
367  if(lineindexrecord.find(it->first)==lineindexrecord.end())
368  {
369  new_vIDs.push_back(it->first);
370  } // otherwise see if its data increased in length
371  else if(it->second > lineindexrecord.at(it->first))
372  {
373  increased_lengths.push_back(it->first);
374  }
375  }
376 
377  if(new_vIDs.size()!=0)
378  {
379  errmsg << " The following vertexIDs are new since the last buffer dump " << endl
380  << " (i.e. they did not try to print themselves during filling " << endl
381  << " of any previous buffer):" << endl;
382  for(std::vector<int>::iterator it = new_vIDs.begin(); it!=new_vIDs.end(); ++it)
383  {
384  errmsg<<" - vID="<<(*it)<<", label="<<label_record.at(*it)<<std::endl;
385  }
386  }
387 
388  if(increased_lengths.size()!=0)
389  {
390  errmsg << " The following vertexIDs tried to print longer data vectors " << endl
391  << " than were seen during filling of the first (and any other) previous buffer:" <<std::endl;
392  for(std::vector<int>::iterator it = increased_lengths.begin(); it!=increased_lengths.end(); ++it)
393  {
394  errmsg<<" - vID="<<(*it)<<", label="<<label_record.at(*it)<<std::endl;
395  errmsg<<" orig length="<<lineindexrecord.at(*it)<<", new length="<<newlineindexrecord.at(*it)<<std::endl;
396  }
397  }
398  printer_error().raise(LOCAL_INFO,errmsg.str());
399  }
400  AP_DBUG( std::cout << "lfpvfc 3" << std::endl; )
401 
402  // Write the file explaining what is in each column of the output file
403  if (info_file_written==false)
404  {
405  AP_DBUG( std::cout << "asciiPrinter: Writing info file..." << std::endl; )
406 
407  std::ofstream info_fstream;
408  open_output_file(info_fstream, info_file, std::ofstream::trunc); // trunc mode overwrites old content
409 
410  int column_index = 1;
411  for (std::map<int,int>::iterator
412  it = lineindexrecord.begin(); it != lineindexrecord.end(); it++)
413  {
414  int vID = it->first;
415  int length = it->second; // slots reserved in output file for these results
416  for (int i=0; i<length; i++)
417  {
418  AP_DBUG( std::cout<<"Column "<<column_index<<": "<<label_record.at(vID)[i]<<std::endl; )
419  info_fstream<<"Column "<<column_index<<": "<<label_record.at(vID)[i]<<std::endl;
420  column_index++;
421  }
422  }
423  AP_DBUG( std::cout << "lfpvfc 3.1" << std::endl; )
424  info_fstream.close();
425  info_file_written=true;
426  }
427 
428  AP_DBUG( std::cout << "lfpvfc 4" << std::endl; )
429 
430  // Actual dump of buffer to file
431  for (Buffer::iterator
432  bufentry = buffer.begin(); bufentry != buffer.end(); /* Will increment in loop */ )
433  {
434  Record& record = bufentry->second;
435  AP_DBUG( std::cout << "asciiPrinter: Examining record with key <rank="<<bufentry->first.first<<", pointID="<<bufentry->first.second<<">"<< std::endl; )
436  if(force or record.readyToPrint)
437  {
438  AP_DBUG( std::cout << "asciiPrinter: readyToPrint -- writing output..." << std::endl; )
439  for (std::map<int,int>::iterator
440  it = lineindexrecord.begin(); it != lineindexrecord.end(); ++it)
441  {
442  // it->first - int VertexID
443  // it->second - int length
444 
445  std::vector<double> empty; // Empty vector
446  std::vector<double>* results = &empty; // Pointer to results vector
447 
448  LineBuf::iterator itdata = record.data.find(it->first);
449  if( itdata != record.data.end())
450  {
451  results = &(itdata->second);
452  }
453  else
454  {
455  // Not an error. This can happen if evaluation of a point is abandoned midway for some reason.
456  AP_DBUG( std::cout << "asciiPrinter: No data for vertex ID \"" << it->first.
457  << "\" found in record <rank=" << bkey.first
458  << ", pointID=" << bkey.second << ">, printer will output 'null'" << std::endl; )
459  }
460  uint length = it->second; // slots reserved in output file for these results
461 
462  // Print to the fstream!
463  int colwidth = precision + 8; // Just kind of guessing here; tweak as needed
464  for (uint j=0;j<length;j++)
465  {
466  if(j>=results->size())
467  {
468  // Finished parsing results vector; fill remaining empty slots with 'none'
469  my_fstream<<std::setw(colwidth)<<"none";
470  }
471  else
472  {
473  // print an entry from the results vector
474  my_fstream<<std::setw(colwidth+5)<<std::scientific<<(*results)[j];
475  }
476  }
477  }
478  // Delete the record from the buffer and move to next one
479  // Post-increment: Increment the iterator first, THEN delete old one.
480  AP_DBUG( std::cout << "asciiPrinter: Erasing record <rank="<<bkey.first<<", pointID="<<bkey.second<<">"<< std::endl; )
481  buffer.erase(bufentry++);
482  }
483  else
484  {
485  AP_DBUG( std::cout << "asciiPrinter: Not readyToPrint -- leaving in buffer" << std::endl; )
486  ++bufentry;
487  }
488  // line printed, print endline character and go to next line
489  my_fstream<<std::endl;
490  }
491  AP_DBUG( std::cout << "lfpvfc 5" << std::endl; )
492 
493  // buffer dump complete! Flush the fstream to ensure write to file happens.
494  //my_fstream.flush();
495  my_fstream.close();
496 
497  AP_DBUG( std::cout << "lfpvfc 6" << std::endl; )
498  }
499 
500  } // end namespace printers
501 } // end namespace Gambit
Define overloadings of the stream operator for various containers.
LineBuf data
The data; each functor outputs a vector of doubles. We index these by vertexID.
std::map< int, int > lineindexrecord
unsigned long long int pointID
EXPORT_SYMBOLS error & printer_error()
Printer errors.
void reset(bool force=false)
Delete contents of output file (to be replaced/updated) and erase everything in the buffer...
unsigned int bufferlength
Number of lines to store in buffer before printing.
#define LOCAL_INFO
Definition: local_info.hpp:34
Buffer buffer
Full buffer of output to be printed.
bool hasKey(const args &... keys) const
Getters for key/value pairs (which is all the options node should contain)
General small utility functions.
TYPE getValue(const args &... keys) const
void erase_buffer()
Ask the printer for the highest ID number known for a given rank process (needed for resuming...
void common_constructor(const Options &)
Tasks common to the various constructors.
void finalise(bool abnormal=false)
Do final buffer dumps.
Ascii printer class declaration.
std::ofstream my_fstream
Main output file stream.
dictionary item
asciiPrinter(const Options &, BasePrinter *const primary=NULL)
TODO: proper gambit error.
std::string output_file
Output file.
std::string printer_name
Label for printer, mostly for more helpful error messages.
#define AP_DBUG(x)
EXPORT_SYMBOLS const str & ensure_path_exists(const str &)
Ensure that a path exists (and then return the path, for chaining purposes)
std::map< int, std::vector< std::string > > label_record
Record a set of labels for each printer item: used to write "info" file explain what is in each colum...
TYPE getValueOrDef(TYPE def, const args &... keys) const
A simple C++ wrapper for the MPI C bindings.
void addtobuffer(const std::vector< double > &, const std::vector< std::string > &, const int, const int, const int)
void initialise(const std::vector< int > &)
Virtual function overloads:
PPIDpair lastPointID
Recording of which model point each process is working on.
Exception objects required for standalone compilation.
void open_output_file(std::ofstream &output, std::string filename, std::ios_base::openmode mode)
Open file stream with error checking.
std::ofstream info_fstream
"Info file" output stream
Structure to hold data for a single model point.
void dump_buffer(bool force=false)
EXPORT_SYMBOLS const PPIDpair nullpoint
Define &#39;nullpoint&#39; const.
pointID / process number pair Used to identify a single parameter space point
TODO: see if we can use this one:
Definition: Analysis.hpp:33
A small wrapper object for &#39;options&#39; nodes.
std::string info_file
Info file (describes contents of output file, i.e. contents of columns)
bool readyToPrint
Flag to indicate if record is available to send for output.