#include #include #include #include #include "HeatTransmissionTester.h" #include "Sheet.h" HeatTransmissionTester::HeatTransmissionTester(int& argc, char** argv) : QCoreApplication(argc, argv) { } int HeatTransmissionTester::run() { if ( this->arguments().count() <= 1 ) return printHelp(); for ( int index = 1; index < this->arguments().count(); ++index ) this->testDirectory( this->arguments()[index] ); return /*this->sheets.count() > 0 ? this->exec() :*/ EXIT_SUCCESS; } int HeatTransmissionTester::testDirectory(const QString& dirPath) { QDir dir(dirPath); if ( not dir.exists() ) { std::cerr << "Could not open directory " << qPrintable(dirPath) << std::endl; return EXIT_FAILURE; } dir.setFilter(QDir::Files); //dir.setSorting(QDir::Size | QDir::Reversed); QFileInfoList list = dir.entryInfoList(); std::cout << qPrintable(dirPath) << ":" << std::endl; for (int fileIndex = 0; fileIndex < list.size(); ++fileIndex) this->testFile( list[fileIndex] ); return EXIT_SUCCESS; } int HeatTransmissionTester::printHelp() { std::cout << "Usage: HeatTransmissionTester \n"; std::cout << std::endl; std::cout << "Directories should contain pairs of input and output CSV files\n"; std::cout << "each one contains the epsilon preceded by a dash, e.g:\n"; std::cout << "\t input013-0.005.csv\n"; std::cout << "\toutput013-0.005.csv\n"; return EXIT_FAILURE; } int HeatTransmissionTester::testFile(const QFileInfo& inputFileInfo) { // Ignore files in the director that does not start with "inputXXX.csv" if ( ! inputFileInfo.baseName().toLower().startsWith("input") ) return EXIT_SUCCESS; // Get the epsilon from the filename "inputXXX-epsilon.csv" int pos = inputFileInfo.baseName().lastIndexOf('-'); if ( pos != -1 ) { // Convert the epsilon to a double bool isValid = true; double epsilon = inputFileInfo.completeBaseName().mid(pos + 1).toDouble(&isValid); if ( isValid && epsilon >= 0.0 ) { // The outputXXX-epsilon.csv file must exist const QString& outputFilename = inputFileInfo.fileName().mid(5); // 5 == len("input") QFileInfo outputFileInfo(inputFileInfo.absolutePath() + "/output" + outputFilename); if ( ! outputFileInfo.exists() ) std::cerr << "Error: missing output csv file: " << qPrintable(outputFileInfo.fileName()) << std::endl; return this->testContents(epsilon, inputFileInfo, outputFileInfo); } else std::cerr << "Error: invalid epsilon in: " << qPrintable(inputFileInfo.fileName()) << std::endl; } else std::cerr << "Error: missing epsilon in: " << qPrintable(inputFileInfo.fileName()) << std::endl; return EXIT_FAILURE; } int HeatTransmissionTester::testContents(double epsilon, const QFileInfo& inputFileInfo, const QFileInfo& outputFileInfo) { std::cout << " " << qPrintable(inputFileInfo.fileName()) << " (" << epsilon << "): "; std::flush(std::cout); Sheet* sheet = Sheet::load(inputFileInfo.absoluteFilePath(), this); if ( sheet == nullptr ) return (void)(std::cerr << "Error: invalid input CSV: " << qPrintable(inputFileInfo.fileName()) << std::endl), EXIT_FAILURE; this->connect( sheet, &Sheet::simulationDone, this, &HeatTransmissionTester::sheetFinished ); this->sheets.insert(sheet, outputFileInfo); sheet->startSimulation(epsilon); return EXIT_SUCCESS; } void HeatTransmissionTester::sheetFinished(unsigned long long generations) { Q_UNUSED(generations); std::cout << generations << " generations"; Sheet* sheet = dynamic_cast( sender() ); Q_ASSERT(sheet); const QFileInfo& outputFileInfo = this->sheets.value( sheet, QFileInfo() ); // Q_ASSERT( outputFileInfo.exists() ); this->compareContents(sheet, outputFileInfo); if ( ++this->finishedSheetCount >= this->sheets.count() ) qApp->quit(); } bool compareTemperatures(const TemperatureMatrix& generated, const TemperatureMatrix& expected, double difference); void writeTemperatures(const TemperatureMatrix& temperatures, const QFileInfo& outputFileInfo); bool HeatTransmissionTester::compareContents(Sheet* inputSheet, const QFileInfo& outputFileInfo) { // Get simulation temperatures const TemperatureMatrix& sheetTemperatures = inputSheet->getCurrentMatrix(); // Load output exected temperatures from CSV file if ( outputFileInfo.exists() ) { Sheet* outputSheet = Sheet::load(outputFileInfo.absoluteFilePath(), this); if ( outputSheet == nullptr ) return (void)(std::cerr << "Error: invalid output CSV: " << qPrintable(outputFileInfo.fileName()) << std::endl), EXIT_FAILURE; const TemperatureMatrix& fileTemperatures = outputSheet->getCurrentMatrix(); if ( compareTemperatures(sheetTemperatures, fileTemperatures, detectPrecisionDifference(outputFileInfo.absoluteFilePath())) ) return (void)(std::cout << " ok" << std::endl), true; } // std::cerr << " fail!\nTest case failed: " << qPrintable(outputFileInfo.fileName()) << std::endl; std::cerr << " (generated!)\n"; writeTemperatures(sheetTemperatures, outputFileInfo); return false; } double HeatTransmissionTester::detectPrecisionDifference(const QString& outputCsvFilename) { // Open the file QFile file(outputCsvFilename); if ( ! file.open(QIODevice::ReadOnly | QIODevice::Text) ) return -1.0; // Manages text file encoding QTextStream textStream( &file ); // The minimum difference, calculated counting the number of decimal digits double diff = 1.0; // Read a char at time QChar ch = 0; bool dotFound = false; while ( ! (textStream >> ch).atEnd() ) { // Check only first value, if a new one starts, stop here if ( ch == ',' ) break; // If we found the decimal separator if ( ch == '.' ) dotFound = true; // If we found a digit and we are in the decimal part, adjust our difference if ( dotFound && ch.isDigit() ) diff /= 10.0; } // QFile destructor will close the file return diff; } #define FLOAT_EQUALS(x,y) ( qAbs(x - y) < 0.000001 ) bool compareTemperatures(const TemperatureMatrix& generated, const TemperatureMatrix& expected, double difference) { std::cout << " [" << difference << "] "; if ( generated.count() != expected.count() ) return (void)(std::cerr << generated.count() << " generated rows, expected " << expected.count() << std::endl), false; for ( int row = 0; row < expected.count(); ++row ) { if ( generated[row].count() != expected[row].count() ) return (void)(std::cerr << "Row " << row << ": " << generated[row].count() << " generated columns, expected " << expected[row].count() << std::endl), false; for ( int col = 0; col < expected[row].count(); ++col ) if ( qAbs(generated[row][col] - expected[row][col]) >= difference ) return (void)(std::cerr << std::endl << row + 1 << ":" << col + 1 << ": generated: " << generated[row][col] << " expected: " << expected[row][col] << std::endl), false; } return true; } void writeTemperatures(const TemperatureMatrix& temperatures, const QFileInfo& outputFileInfo) { QFileInfo outputFilename(outputFileInfo.absolutePath() + "/" + outputFileInfo.completeBaseName() + "-jhc." + outputFileInfo.suffix()); QFile file(outputFilename.absoluteFilePath()); if ( file.open(QIODevice::WriteOnly | QIODevice::Text) ) { QTextStream out(&file); for ( int row = 0; row < temperatures.count(); ++row ) for ( int col = 0; col < temperatures[row].count(); ++col ) out << QString("%1").arg(temperatures[row][col], 0, 'f', 2).replace(QRegularExpression("\\.?0+$"), "") << (col == temperatures[row].count() - 1 ? '\n' : ','); } }