#include #include #include #ifdef HEAT_TRANSMISSION_GUI #include #endif #include "Sheet.h" Sheet::Sheet(QObject *parent) : QObject(parent) { } Sheet* Sheet::load(const QString& filePath, QObject* parent) { // Create a new Sheet object Sheet* sheet = new Sheet(parent); // Try to load the file, on success, return the sheet if ( sheet->loadCsv(filePath) ) return sheet; // On error, delete the sheet and return nothing delete sheet; return nullptr; } bool Sheet::loadCsv(const QString& filePath) { // Get information about the file. If it does not exist, stop QFileInfo fileInfo{filePath}; if ( ! fileInfo.exists() ) return false; // Get the field separator according to the file extension char separator = ','; if ( fileInfo.suffix().toLower() == "tsv" ) separator = '\t'; else if ( fileInfo.suffix().toLower() == "ssv" ) separator = ';'; // Load the file using the separator return this->loadCsv(filePath, separator); } bool Sheet::loadCsv(const QString& filePath, char separator) { // Open the file QFile file(filePath); if ( ! file.open(QIODevice::ReadOnly | QIODevice::Text) ) return false; // Manages text file encoding QTextStream textStream( &file ); // Read one line at time QString line; while ( textStream.readLineInto(&line) ) if ( ! this->loadLine(line, separator) ) return false; // Reserve memory for the second matrix this->temperatures2 = this->temperatures1; return true; } bool Sheet::loadLine(const QString& line, char separator) { // Separate the line into an array of tokens const QStringList& tokens = line.split(separator); // Create an array of doubles for this line QVector values( tokens.count() ); // Convert each token into a double value bool ok = true; for ( int index = 0; index < tokens.count(); ++index ) { // If the token is not a double, report error double value = tokens[index].toDouble(&ok); if ( ! ok ) return false; // Store the value into the matrix values[index] = value; // Update the minimum and maximum if ( value < this->minimum ) this->minimum = value; if ( value > this->maximum ) this->maximum = value; } // Add the line to the internal temperatures this->temperatures1.append( values ); // All lines must have the same size if ( this->temperatures1.count() > 1 ) return this->temperatures1[0].count() == values.count(); return true; } #ifdef HEAT_TRANSMISSION_GUI bool Sheet::fillImage(QImage& image) { // The image must know its size if ( image.width() <= 0 || image.height() <= 0 ) return false; // We must have temperatures if ( this->getRows() <= 0 || this->getColumns() <= 0 ) return false; // Calculate the sampling rates as proportions const double rowProportion = 1.0 * this->getRows() / image.height(); const double colProportion = 1.0 * this->getColumns() / image.width(); const double range = this->maximum - this->minimum; const double factor = 255.0 / range; // Fill up the image for ( int x = 0; x < image.width(); ++x ) { for ( int y = 0; y < image.height(); ++y ) { // Sample a value for this pixel const int row = static_cast( y * rowProportion ); const int col = static_cast( x * colProportion ); const double value = (*this->current)[row][col]; // Get a RGB between the minimum and maximum temperatures int red = static_cast( factor * (value - this->minimum) ); int green = 0; int blue = 255 - static_cast( factor * (value - this->minimum) ); image.setPixel( x, y, qRgb(red, green, blue) ); } } return true; } #endif // HEAT_TRANSMISSION_GUI bool Sheet::startSimulation(double epsilon) { // Epsilon must be positive if ( epsilon <= 0.0 ) { emit this->simulationDone( this->generation ); return false; } // Update the internal epsilon this->epsilon = epsilon; // Start simulation this->simulating = true; // Update the matrixes until no more significant changes are detected // or until user stops the simulation while ( this->simulating && this->updateGeneration() > epsilon ) ; // The simulation has finished this->simulating = false; emit this->simulationDone( this->generation ); return true; } void Sheet::stopSimulation() { this->simulating = false; } #ifdef HEAT_TRANSMISSION_GUI #include #endif double Sheet::updateGeneration() { // The maximum temperature change detected in this generation double maxChange = DBL_MIN; // Update all rows for ( int row = 1; row < this->current->count() - 1; ++row ) { // Upate all columns for ( int col = 1; col < (*this->current)[row].count() - 1; ++col ) { // Update cell from current matrix to the next matrix double change = this->updateCell(row, col); // If the cell change is greater than our maximum, update the maximum if ( change > maxChange ) maxChange = change; } } // The next matrix is enterely updated, it is now the current matrix // The current matrix will be used for the next generation. So swap them TemperatureMatrix* temp = this->current; this->current = this->next; this->next = temp; // A new generation was completed ++this->generation; #ifdef HEAT_TRANSMISSION_GUI // Ugly fix: allow GUI to process events QApplication::processEvents(); #endif // Return the max temperature change detected in this generation return maxChange; } double Sheet::updateCell(int row, int col) { Q_ASSERT(row >= 1 && row < this->getRows() - 1 ); Q_ASSERT(col >= 1 && col < this->getColumns() - 1 ); // The new value is the average of the neighbors in the previous generation (*this->next)[row][col] = ( (*this->current)[row - 1][col] + (*this->current)[row][col - 1] + (*this->current)[row][col + 1] + (*this->current)[row + 1][col] ) / 4.0; // Return the difference between the cell's new temperature and old one return qAbs( (*this->next)[row][col] - (*this->current)[row][col] ); }