diff --git a/Printers/include/gambit/Printers/printers/asciiprinter.hpp b/Printers/include/gambit/Printers/printers/asciiprinter.hpp index 11b4c720a3..d99acd74ac 100644 --- a/Printers/include/gambit/Printers/printers/asciiprinter.hpp +++ b/Printers/include/gambit/Printers/printers/asciiprinter.hpp @@ -135,9 +135,13 @@ namespace Gambit // write the printer buffer to file void dump_buffer(bool force=false); - // retrieve the name of the main output file (used by auxilliary printers to match the names) + // actual on-disk path of the main output file (includes any per-rank suffix) std::string get_output_filename(); + // output filename before the per-rank MPI suffix; queried by + // auxiliary printers to avoid double-appending the rank. + std::string get_base_output_filename(); + // retrieve the bufferlength (used by auxilliary printers to match the primary printer) int get_bufferlength(); @@ -170,9 +174,12 @@ namespace Gambit void template_print_vec(std::vector const&, const std::string&, const int, const uint, const ulong); private: - /// Output file + /// Output file (on-disk path; includes the per-rank suffix under MPI) std::string output_file; + /// Output filename before the per-rank MPI suffix + std::string base_output_file; + /// Info file (describes contents of output file, i.e. contents of columns) std::string info_file; @@ -214,8 +221,8 @@ namespace Gambit uint mpiSize; #endif - /// Number of digits of precision to use in output columns - int precision = 10; + /// Number of digits of precision to use in output columns (YAML 'precision', default 10) + int precision; /// Full buffer of output to be printed // Key is ; value is a Record (for a single model point) @@ -234,6 +241,10 @@ namespace Gambit std::map> label_record; //the 'int' here is the vertex ID. Could make a typedef to make this safer. bool info_file_written = false; // Flag to let us know that the info file has been written + /// Write a '#'-prefixed shorthand-label header line at the top of + /// the data file (YAML 'write_header', default false) + bool write_header = false; + bool header_written = false; }; // Register printer so it can be constructed via inifile instructions diff --git a/Printers/src/printers/asciiprinter/asciiprinter.cpp b/Printers/src/printers/asciiprinter/asciiprinter.cpp index 0c11ce5b4d..08e217d9b6 100644 --- a/Printers/src/printers/asciiprinter/asciiprinter.cpp +++ b/Printers/src/printers/asciiprinter/asciiprinter.cpp @@ -84,6 +84,11 @@ namespace Gambit // Common constructor tasks void asciiPrinter::common_constructor(const Options& options) { + // Pick up the resume flag set by PrinterManager. BasePrinter does not + // initialise BaseBasePrinter::resume, so without this any later call + // to get_resume() would read uninitialised memory. + set_resume(options.getValue("resume")); + if( this->is_auxilliary_printer() ) // check if this is an auxilliary printer { @@ -93,9 +98,10 @@ namespace Gambit // Get primary printer (need to cast from BasePrinter type to asciiPrinter) asciiPrinter* primary = dynamic_cast(this->get_primary_printer()); - // Name files based on the primary printer filenames + // Use the primary's pre-rank filename, otherwise the MPI block + // below would append the rank a second time. std::ostringstream f; - f << primary->get_output_filename() << "_" << printer_name; + f << primary->get_base_output_filename() << "_" << printer_name; output_file = Utils::ensure_path_exists(options.getValueOrDef(f.str(),"output_file")); // Match the buffer length to the primary printer, or use a user-supplied option @@ -120,6 +126,9 @@ namespace Gambit bufferlength = options.getValueOrDef(100,"buffer_length"); } + // Snapshot the pre-rank-suffix name for auxiliary printers to query. + base_output_file = output_file; + // Name "info" file to match "output" file std::ostringstream finfo; finfo<< output_file <<"_info"; @@ -144,6 +153,22 @@ namespace Gambit info_file = finfo2.str(); #endif + // Refuse to overwrite a pre-existing output or info file unless the + // user has set 'delete_file_on_restart: true', or we are resuming. + bool overwrite_file = options.getValueOrDef(false,"delete_file_on_restart"); + for(const std::string& f : {output_file, info_file}) + { + if(Utils::file_exists(f) and not overwrite_file and not get_resume()) + { + std::ostringstream errmsg; + errmsg << "Refusing to overwrite pre-existing asciiPrinter output file '"<(false,"auxilliary")) , output_file("") + , base_output_file("") , info_file("") , bufferlength(100) , global(false) @@ -166,6 +192,7 @@ namespace Gambit , myComm() // attaches to MPI_COMM_WORLD, beware collisions with e.g. scanning algorithms. , mpiSize(1) #endif + , precision(10) , lastPointID(nullpoint) { common_constructor(options); @@ -173,6 +200,19 @@ namespace Gambit // Choose whether or not to print invalid and suspicious point codes print_suspicious_point_code = options.getValueOrDef(true,"print_suspicious_point_code"); print_invalidation_code = options.getValueOrDef(true,"print_invalidation_code"); + + // Number of digits of precision to use in output columns + precision = options.getValueOrDef(10,"precision"); + if(precision < 0) + { + std::ostringstream errmsg; + errmsg << "Invalid value for asciiPrinter option 'precision': " << precision + << ". Must be a non-negative integer (number of digits to use for std::setprecision)."; + printer_error().raise(LOCAL_INFO, errmsg.str()); + } + + // Optional '#'-prefixed shorthand-label header line at top of data file. + write_header = options.getValueOrDef(false,"write_header"); } @@ -243,8 +283,9 @@ namespace Gambit } // getters for internal variables - std::string asciiPrinter::get_output_filename() { return output_file; } - int asciiPrinter::get_bufferlength() { return bufferlength; } + std::string asciiPrinter::get_output_filename() { return output_file; } + std::string asciiPrinter::get_base_output_filename() { return base_output_file; } + int asciiPrinter::get_bufferlength() { return bufferlength; } // add results to printer buffer void asciiPrinter::addtobuffer(const std::vector& functor_data, const std::vector& functor_labels, const int vID, const int rank, const int pointID) @@ -453,6 +494,24 @@ namespace Gambit printer_error().raise(LOCAL_INFO,errmsg.str()); } + // Single column width, shared by the header line and data rows. + const int colwidth = precision + 13; + + // Shorthand column name: substring after the last "::" (or the whole + // label), with whitespace replaced by '_' and prefixed with the + // column number to disambiguate collisions. + auto shorthand = [](const std::string& full, int col_index_1based) + { + std::string suffix; + std::size_t pos = full.rfind("::"); + if(pos != std::string::npos) suffix = full.substr(pos + 2); + else suffix = full; + for(char& c : suffix) if(c == ' ' || c == '\t') c = '_'; + std::ostringstream out; + out << col_index_1based << "_" << suffix; + return out.str(); + }; + // Write the file explaining what is in each column of the output file if (info_file_written==false) { @@ -467,7 +526,7 @@ namespace Gambit { int vID = it->first; int length = it->second; // slots reserved in output file for these results - + for (int i=0; i::iterator + it = lineindexrecord.begin(); it != lineindexrecord.end(); it++) + { + int vID = it->first; + int length = it->second; + for (int i = 0; i < length; i++) + { + if (column_index > 1) my_fstream << " "; + my_fstream << std::setw(colwidth - 1) + << shorthand(label_record.at(vID)[i], column_index); + column_index++; + } + } + my_fstream << std::endl; + header_written = true; + } + // Actual dump of buffer to file for (Buffer::iterator bufentry = buffer.begin(); bufentry != buffer.end(); /* Will increment in loop */ ) @@ -520,8 +605,8 @@ namespace Gambit default_value = stream.str(); } - // Print to the fstream! - int colwidth = precision + 8; // Just kind of guessing here; tweak as needed + // Print to the fstream. Same field width for numeric values and + // 'none'/default placeholders so columns align across rows. for (uint j=0;j=results->size()) @@ -532,7 +617,7 @@ namespace Gambit else { // print an entry from the results vector - my_fstream<