file asciiprinter/asciiprinter.cpp
[No description available] More…
Namespaces
Name |
---|
Gambit TODO: see if we can use this one: |
Gambit::Printers Forward declaration. |
Defines
Name | |
---|---|
AP_DBUG(x) |
Detailed Description
Author:
- Ben Farmer (benjamin.farmer@monash.edu.au)
- Pat Scott (patscott@physics.mcgill.ca)
Date:
- 2013 Jul, Sep, 2014 Jan
- 2014 Jan
ASCII printer class member function definitions
Authors (add name and date if you modify):
Macros Documentation
define AP_DBUG
#define AP_DBUG(
x
)
Source code
// GAMBIT: Global and Modular BSM Inference Tool
// *********************************************
/// \file
///
/// ASCII printer class member function definitions
///
/// *********************************************
///
/// Authors (add name and date if you modify):
///
/// \author Ben Farmer
/// (benjamin.farmer@monash.edu.au)
/// \date 2013 Jul, Sep, 2014 Jan
///
/// \author Pat Scott
/// (patscott@physics.mcgill.ca)
/// \date 2014 Jan
///
/// *********************************************
// Standard libraries
#include <map>
#include <vector>
#include <algorithm>
#include <ios>
#include <sstream>
#include <fstream>
#include <iomanip>
// Gambit
#include "gambit/Printers/printers/asciiprinter.hpp"
#include "gambit/Utils/standalone_error_handlers.hpp"
#include "gambit/Utils/stream_overloads.hpp"
#include "gambit/Utils/util_functions.hpp"
// MPI bindings
#include "gambit/Utils/mpiwrapper.hpp"
// Switch for debugging output (manual at the moment)
//#define AP_DEBUG_MODE
#ifdef AP_DEBUG_MODE
#define AP_DBUG(x) x
#else
#define AP_DBUG(x)
#endif
// Code!
namespace Gambit
{
namespace Printers
{
/// Open file stream with error checking
//TODO: It would be good to add something like this to the Gambit Utils to use as a standard I think.
void open_output_file(std::ofstream& output, std::string filename, std::ios_base::openmode mode)
{
// Pass in reference to externally created ofstream "output"
output.open(filename, std::ofstream::out | mode);
if( output.fail() || output.bad() )
{
std::ostringstream ss;
ss << "IO error while opening file for writing! Tried to open ofstream to file \""<<filename<<"\", but encountered error bit in the created ostream.";
throw std::runtime_error( ss.str() );
}
}
Record::Record() : readyToPrint(false) {}
void Record::reset()
{
data.clear();
readyToPrint = false;
}
// Printer to ascii file (i.e. table of doubles)
// Common constructor tasks
void asciiPrinter::common_constructor(const Options& options)
{
if( this->is_auxilliary_printer() ) // check if this is an auxilliary printer
{
// Get stream name from printermanager
printer_name = options.getValue<std::string>("name");
// Get primary printer (need to cast from BasePrinter type to asciiPrinter)
asciiPrinter* primary = dynamic_cast<asciiPrinter*>(this->get_primary_printer());
// Name files based on the primary printer filenames
std::ostringstream f;
f << primary->get_output_filename() << "_" << printer_name;
output_file = Utils::ensure_path_exists(options.getValueOrDef<std::string>(f.str(),"output_file"));
// Match the buffer length to the primary printer, or use a user-supplied option
bufferlength = options.getValueOrDef<uint>(primary->get_bufferlength(),"buffer_length");
}
else
{
printer_name = "Primary";
std::ostringstream f;
if(options.hasKey("output_path"))
{
f << options.getValue<std::string>("output_path") << "/";
}
else
{
f << options.getValue<std::string>("default_output_path") << "/";
}
f << options.getValue<std::string>("output_file");
output_file = Utils::ensure_path_exists(f.str());
bufferlength = options.getValueOrDef<uint>(100,"buffer_length");
}
// Name "info" file to match "output" file
std::ostringstream finfo;
finfo<< output_file <<"_info";
info_file = finfo.str();
// Name "metadata" file to match "output" file
std::ostringstream fmetadata;
fmetadata << output_file << "_metadata";
metadata_file = fmetadata.str();
#ifdef WITH_MPI
myRealRank = myComm.Get_rank();
this->setRank(myRealRank);
mpiSize = myComm.Get_size();
// Append mpi rank to file names to avoid collisions between processes
std::ostringstream fout;
std::ostringstream finfo2;
fout << output_file <<"_"<<myRealRank;
finfo2<< info_file <<"_"<<myRealRank;
output_file = fout.str();
info_file = finfo2.str();
#endif
// Erase contents of output_file and info_file if they already exist
std::ofstream output;
open_output_file(output, output_file, std::ofstream::trunc);
output.close();
std::ofstream info;
open_output_file(info, info_file, std::ofstream::trunc);
info.close();
}
// Constructor
asciiPrinter::asciiPrinter(const Options& options, BasePrinter* const primary)
: BasePrinter(primary,options.getValueOrDef<bool>(false,"auxilliary"))
, output_file("")
, info_file("")
, bufferlength(100)
, global(false)
, printer_name("")
#ifdef WITH_MPI
, myComm() // attaches to MPI_COMM_WORLD, beware collisions with e.g. scanning algorithms.
, mpiSize(1)
#endif
, lastPointID(nullpoint)
{
common_constructor(options);
// Choose whether or not to print invalid and suspicious point codes
print_suspicious_point_code = options.getValueOrDef<bool>(true,"print_suspicious_point_code");
print_invalidation_code = options.getValueOrDef<bool>(true,"print_invalidation_code");
}
/// Destructor
// Overload the base class virtual destructor
asciiPrinter::~asciiPrinter()
{
// Make sure buffer is completely written to disk (MOVED TO FINALISE)
AP_DBUG( std::cout << "Destructing asciiPrinter object (with name=\""<<printer_name<<"\")..." << std::endl; )
}
/// Initialisation function
// Run by dependency resolver, which supplies the functors with a vector of VertexIDs whose requiresPrinting flags are set to true.
void asciiPrinter::initialise(const std::vector<int>& /*printmevec*/)
{
// Currently don't seem to need this... could use it to check if all VertexID's have submitted print requests.
}
// Get options required to construct a reader object that can read
// the previous output of this printer.
// TODO: Currently unavailable
Options asciiPrinter::resume_reader_options()
{
std::ostringstream err;
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;
printer_error().raise(LOCAL_INFO, err.str());
return Options();
}
/// Do final buffer dumps
void asciiPrinter::finalise(bool /*abnormal*/)
{
dump_buffer(true);
AP_DBUG( std::cout << "Buffer (of asciiPrinter with name=\""<<printer_name<<"\") successfully dumped..." << std::endl; )
// Add last point ID to metadata
if (get_output_metadata())
{
std::stringstream ssPPID;
ssPPID << lastPointID;
map_str_str lastpoint;
lastpoint["lastPointID"] = ssPPID.str();
_print_metadata(lastpoint);
}
}
void asciiPrinter::flush()
{
dump_buffer(true);
}
/// Delete contents of output file (to be replaced/updated) and erase everything in the buffer
void asciiPrinter::reset(bool)
{
std::ofstream my_fstream;
open_output_file(my_fstream, output_file, std::ofstream::trunc);
my_fstream.close();
erase_buffer();
lastPointID = nullpoint;
}
/// Clear buffer
void asciiPrinter::erase_buffer()
{
// Used to just erase the records, but preserve vertex IDs. Not sure this is necessary, so for now just
// emptying the map.
buffer.clear();
}
// getters for internal variables
std::string asciiPrinter::get_output_filename() { return output_file; }
int asciiPrinter::get_bufferlength() { return bufferlength; }
// add results to printer buffer
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)
{
//TODO: If a functor gets called twice without the printer advancing the data will currently just be overwritten. Should generate an error or something.
// Do not write invalid or suspicious points to buffer if they are not requested to be printed
if (!print_suspicious_point_code && (functor_labels[0] == "Suspicious Point Code"))
{
return;
}
if (!print_invalidation_code && (functor_labels[0] == "Invalidation Code"))
{
return;
}
// Key for accessing buffer
std::pair<int,int> bkey = std::make_pair(rank,pointID);
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.
// Register <pointID> as coming from process <rank>.
AP_DBUG( std::cout << "Rank "<<myRealRank<<": adding data from (ptID,rank) "<<ppid<<"; labels="<<functor_labels<<std::endl; )
AP_DBUG( std::cout << "Rank "<<myRealRank<<": last point was from (ptID,rank) "<<lastPointID<<std::endl; )
//AP_DBUG( std::cout << "Rank "<<this->getRank()<<": Note: nullpoint is (ptID,rank) "<<nullpoint<<std::endl; )
if(lastPointID == nullpoint)
{
// No previous point; add current point
lastPointID = ppid;
}
else if(lastPointID == ppid)
{
// Don't need to do anything; staying on same point
}
else
{
// Moving to new point; set previous point data as "ready to print".
std::pair<int,int> prevbkey = std::make_pair(lastPointID.rank,lastPointID.pointID);
if(buffer.find(prevbkey)==buffer.end())
{
std::ostringstream err;
err << "Tried to move asciiPrinter buffer to new point '" << ppid << "', however the *previous* point '" << endl
<< lastPointID << "' could not be found in the buffer (we need to set it as 'finished'). This " << endl
<< "probably means that the old point was never actually entered into the buffer, which must " << endl
<< "mean there is a bug in the asciiPrinter. Please report this." << endl
<< "Debug data:" << endl
<< " functor label: "<< functor_labels << endl
<< " slot (rank,pointID): "<< rank <<", "<< pointID << endl;
printer_error().raise(LOCAL_INFO, err.str());
}
buffer.at(prevbkey).readyToPrint = true;
lastPointID = ppid;
// Check whether it is time to dump the (completed) buffer points to disk
if(buffer.size()>=bufferlength) {
AP_DBUG( std::cout << "asciiPrinter: Buffer full ("<< buffer.size() <<" records), running buffer dump"<<std::endl; )
dump_buffer();
}
}
if( buffer.find(bkey)!=buffer.end() and buffer.at(bkey).readyToPrint==true )
{
std::ostringstream err;
err << "Error! Attempted to write to \"old\" model point " << endl
<< "buffer! Bug in asciiprinter.cpp somewhere. Buffer " << endl
<< "records are initialised with readyToPrint=false, and " << endl
<< "should not be written to again after this flag is set " << endl
<< "to true. The records are destroyed upon writing their " << endl
<< "contents to disk, and there is a unique record for " << endl
<< "every rank/pointID pair." << endl
<< "Debug info:" << endl
<< " functor label: "<< functor_labels << endl
<< " slot (rank,pointID): "<< rank <<", "<< pointID << endl;
printer_error().raise(LOCAL_INFO, err.str());
}
// Assign to buffer, adding keys if needed
buffer[bkey].data[vID] = functor_data;
//if ( info_file_written == false )
//{
if ( label_record.find(vID)==label_record.end() or functor_labels.size()>label_record.at(vID).size() )
{
// 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.
label_record[vID] = functor_labels;
}
//}
// Check if the Suspicious Point/Invalidation Code labels are in the buffer, and if not, add them
if (print_suspicious_point_code && !Found_sus)
{
// Check which required entries are not present
for (auto entry: label_record)
{
if (entry.second[0] == "Suspicious Point Code") {Found_sus = true;}
}
if (!Found_sus)
{
// Add Suspicious Point Code to the buffer
std::vector<double> default_data = {0};
std::vector<std::string> default_labels = {"Suspicious Point Code"};
addtobuffer(default_data, default_labels, get_main_param_id("Suspicious Point Code"), rank, pointID);
}
}
if (print_invalidation_code && !Found_inv)
{
// Check which required entries are not present
for (auto entry: label_record)
{
if (entry.second[0] == "Invalidation Code") {Found_inv = true;}
}
if (!Found_inv)
{
// Add Suspicious Point Code to the buffer
std::vector<double> default_data = {0};
std::vector<std::string> default_labels = {"Invalidation Code"};
addtobuffer(default_data, default_labels, get_main_param_id("Invalidation Code"), rank, pointID);
}
}
}
// write the printer buffer to file
void asciiPrinter::dump_buffer(bool force)
{
// Write record of what is in each column if we haven't done so yet
// Note the downside of using a map as the buffer; the order of stuff in the output file is going
// to be kind of haphazard due to the sorted order used by map. Will have to do more work to achieve
// an ordering that reflects the order of stuff in, say, the inifile.
// force=true -- dumps all records regardless if they are "readyToPrint"
AP_DBUG( std::cout << "dumping asciiprinter buffer" << std::endl; )
// Open output file in append mode
std::ofstream my_fstream;
open_output_file(my_fstream, output_file, std::ofstream::app);
my_fstream.precision(precision);
std::map<int,int> newlineindexrecord(lineindexrecord);
// Work out how to organise the output file
// To do this we need to go through the buffer and find the maximum length of vector associated with each VertexID.
for (Buffer::iterator
bufentry = buffer.begin(); bufentry != buffer.end(); ++bufentry)
{
Record& record = bufentry->second;
for (LineBuf::iterator
item = record.data.begin(); item != record.data.end(); ++item)
{
//item->first - VertexID
//item->second - std::vector<double> (result values)
int oldlen = newlineindexrecord[item->first];
int newlen = (item->second).size();
newlineindexrecord[item->first] = std::max(oldlen, newlen);
}
}
// Check if the output format has changed, and raise an error if so
if (lineindexrecord.size()==0)
{
// initialise if empty
lineindexrecord = newlineindexrecord;
}
else if (lineindexrecord!=newlineindexrecord)
{
std::ostringstream errmsg;
errmsg << "Error! Output format has changed since last buffer dump! The asciiPrinter cannot handle this!"
<< "Details:" << std::endl;
// First check if a new vertexID has appeared
std::vector<int> new_vIDs;
std::vector<int> increased_lengths;
for (std::map<int,int>::iterator
it = newlineindexrecord.begin(); it != newlineindexrecord.end(); ++it)
{
// try to find each key in the old lineindexrecord
if(lineindexrecord.find(it->first)==lineindexrecord.end())
{
new_vIDs.push_back(it->first);
} // otherwise see if its data increased in length
else if(it->second > lineindexrecord.at(it->first))
{
increased_lengths.push_back(it->first);
}
}
if(new_vIDs.size()!=0)
{
errmsg << " The following vertexIDs are new since the last buffer dump " << endl
<< " (i.e. they did not try to print themselves during filling " << endl
<< " of any previous buffer):" << endl;
for(std::vector<int>::iterator it = new_vIDs.begin(); it!=new_vIDs.end(); ++it)
{
errmsg<<" - vID="<<(*it)<<", label="<<label_record.at(*it)<<std::endl;
}
}
if(increased_lengths.size()!=0)
{
errmsg << " The following vertexIDs tried to print longer data vectors " << endl
<< " than were seen during filling of the first (and any other) previous buffer:" <<std::endl;
for(std::vector<int>::iterator it = increased_lengths.begin(); it!=increased_lengths.end(); ++it)
{
errmsg<<" - vID="<<(*it)<<", label="<<label_record.at(*it)<<std::endl;
errmsg<<" orig length="<<lineindexrecord.at(*it)<<", new length="<<newlineindexrecord.at(*it)<<std::endl;
}
}
printer_error().raise(LOCAL_INFO,errmsg.str());
}
// Write the file explaining what is in each column of the output file
if (info_file_written==false)
{
AP_DBUG( std::cout << "asciiPrinter: Writing info file..." << std::endl; )
std::ofstream info_fstream;
open_output_file(info_fstream, info_file, std::ofstream::trunc); // trunc mode overwrites old content
int column_index = 1;
for (std::map<int,int>::iterator
it = lineindexrecord.begin(); it != lineindexrecord.end(); it++)
{
int vID = it->first;
int length = it->second; // slots reserved in output file for these results
for (int i=0; i<length; i++)
{
AP_DBUG( std::cout<<"Column "<<column_index<<": "<<label_record.at(vID)[i]<<std::endl; )
info_fstream<<"Column "<<column_index<<": "<<label_record.at(vID)[i]<<std::endl;
column_index++;
}
}
info_fstream.close();
info_file_written=true;
}
// Actual dump of buffer to file
for (Buffer::iterator
bufentry = buffer.begin(); bufentry != buffer.end(); /* Will increment in loop */ )
{
Record& record = bufentry->second;
AP_DBUG( std::cout << "asciiPrinter: Examining record with key <rank="<<bufentry->first.first<<", pointID="<<bufentry->first.second<<">"<< std::endl; )
if(force or record.readyToPrint)
{
AP_DBUG( std::cout << "asciiPrinter: readyToPrint -- writing output..." << std::endl; )
for (std::map<int,int>::iterator
it = lineindexrecord.begin(); it != lineindexrecord.end(); ++it)
{
// it->first - int VertexID
// it->second - int length
std::vector<double> empty; // Empty vector
std::vector<double>* results = ∅ // Pointer to results vector
LineBuf::iterator itdata = record.data.find(it->first);
if( itdata != record.data.end())
{
results = &(itdata->second);
}
else
{
// Not an error. This can happen if evaluation of a point is abandoned midway for some reason.
AP_DBUG( std::cout << "asciiPrinter: No data for vertex ID \"" << it->first.
<< "\" found in record <rank=" << bkey.first
<< ", pointID=" << bkey.second << ">, printer will output 'null'" << std::endl; )
}
uint length = it->second; // slots reserved in output file for these results
// Setting some custom default values for when not printing a suspicious or invalid point code.
std::string default_value = "none";
if (label_record.at(it->first).at(0) == "Suspicious Point Code" || label_record.at(it->first).at(0) == "Invalidation Code")
{
std::stringstream stream;
stream << std::scientific << std::setprecision(precision) << 0.0;
default_value = stream.str();
}
// Print to the fstream!
int colwidth = precision + 8; // Just kind of guessing here; tweak as needed
for (uint j=0;j<length;j++)
{
if(j>=results->size())
{
// Finished parsing results vector; fill remaining empty slots with 'none'
my_fstream<<std::setw(colwidth)<<default_value;
}
else
{
// print an entry from the results vector
my_fstream<<std::setw(colwidth+5)<<std::scientific<<(*results)[j];
}
}
}
// Delete the record from the buffer and move to next one
// Post-increment: Increment the iterator first, THEN delete old one.
AP_DBUG( std::cout << "asciiPrinter: Erasing record <rank="<<bkey.first<<", pointID="<<bkey.second<<">"<< std::endl; )
buffer.erase(bufentry++);
}
else
{
AP_DBUG( std::cout << "asciiPrinter: Not readyToPrint -- leaving in buffer" << std::endl; )
++bufentry;
}
// line printed, print endline character and go to next line
my_fstream<<std::endl;
}
// buffer dump complete! Flush the fstream to ensure write to file happens.
//my_fstream.flush();
my_fstream.close();
}
// Print metadata info to file
void asciiPrinter::_print_metadata(map_str_str metadata)
{
// Open metadata file in append mode
std::ofstream metadata_fstream;
open_output_file(metadata_fstream, metadata_file, std::ofstream::app);
// Print metadata
for(auto data: metadata)
metadata_fstream << data.first << "\t" << data.second << std::endl;;
// Close metadata file
metadata_fstream.close();
}
} // end namespace printers
} // end namespace Gambit
Updated on 2024-07-18 at 13:53:33 +0000