/*! * \file COutput.cpp * \brief Main subroutines for output solver information * \author F. Palacios, T. Economon * \version 8.4.0 "Harrier" * * SU2 Project Website: https://su2code.github.io * * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * * Copyright 2012-2026, SU2 Contributors (cf. AUTHORS.md) * * SU2 is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * SU2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with SU2. If not, see . */ #include #include #include "../../../Common/include/geometry/CGeometry.hpp" #include "../../include/solvers/CSolver.hpp" #include "../../include/output/COutput.hpp" #include "../../include/output/CTurboOutput.hpp" #include "../../include/output/filewriter/CFVMDataSorter.hpp" #include "../../include/output/filewriter/CFEMDataSorter.hpp" #include "../../include/output/filewriter/CCGNSFileWriter.hpp" #include "../../include/output/filewriter/CSurfaceFVMDataSorter.hpp" #include "../../include/output/filewriter/CSurfaceFEMDataSorter.hpp" #include "../../include/output/filewriter/CParaviewFileWriter.hpp" #include "../../include/output/filewriter/CSTLFileWriter.hpp" #include "../../include/output/filewriter/CParaviewBinaryFileWriter.hpp" #include "../../include/output/filewriter/CParaviewXMLFileWriter.hpp" #include "../../include/output/filewriter/CParaviewVTMFileWriter.hpp" #include "../../include/output/filewriter/CTecplotFileWriter.hpp" #include "../../include/output/filewriter/CTecplotBinaryFileWriter.hpp" #include "../../include/output/filewriter/CCSVFileWriter.hpp" #include "../../include/output/filewriter/CSU2FileWriter.hpp" #include "../../include/output/filewriter/CSU2BinaryFileWriter.hpp" #include "../../include/output/filewriter/CSU2MeshFileWriter.hpp" namespace { volatile sig_atomic_t STOP; void signalHandler(int signum) { std::cout << "Interrupt signal (" << signum << ") received, saving files and exiting.\n"; STOP = 1; } } COutput::COutput(const CConfig *config, unsigned short ndim, bool fem_output): rank(SU2_MPI::GetRank()), size(SU2_MPI::GetSize()), nDim(ndim), multiZone(config->GetMultizone_Problem()), gridMovement(config->GetDynamic_Grid()), femOutput(fem_output), si_units(config->GetSystemMeasurements() == SI), us_units(config->GetSystemMeasurements() == US) { cauchyTimeConverged = false; maxTimeDelayActive = false; convergenceTable = new PrintingToolbox::CTablePrinter(&std::cout); multiZoneHeaderTable = new PrintingToolbox::CTablePrinter(&std::cout); fileWritingTable = new PrintingToolbox::CTablePrinter(&std::cout); historyFileTable = new PrintingToolbox::CTablePrinter(&histFile, ""); /*--- Set default filenames ---*/ surfaceFilename = "surface"; volumeFilename = "volume"; restartFilename = "restart"; /*--- Retrieve the history filename, including extension ---*/ historyFilename = config->GetHistory_FileName(); historySep = ","; /*--- Initialize residual ---*/ rhoResNew = EPS; rhoResOld = EPS; nRequestedHistoryFields = config->GetnHistoryOutput(); for (unsigned short iField = 0; iField < nRequestedHistoryFields; iField++){ requestedHistoryFields.push_back(config->GetHistoryOutput_Field(iField)); } nRequestedScreenFields = config->GetnScreenOutput(); for (unsigned short iField = 0; iField < nRequestedScreenFields; iField++){ requestedScreenFields.push_back(config->GetScreenOutput_Field(iField)); } nRequestedVolumeFields = config->GetnVolumeOutput(); for (unsigned short iField = 0; iField < nRequestedVolumeFields; iField++){ requestedVolumeFields.push_back(config->GetVolumeOutput_Field(iField)); } /*--- Default is to write history to file and screen --- */ noWriting = false; /*--- Initialize convergence monitoring structure ---*/ nCauchy_Elems = config->GetCauchy_Elems(); cauchyEps = config->GetCauchy_Eps(); minLogResidual = config->GetMinLogResidual(); for (unsigned short iField = 0; iField < config->GetnConv_Field(); iField++){ convFields.emplace_back(config->GetConv_Field(iField)); } newFunc = vector(convFields.size()); oldFunc = vector(convFields.size()); cauchySerie = vector>(convFields.size(), vector(nCauchy_Elems, 0.0)); cauchyValue = 0.0; convergence = false; /*--- Initialize time convergence monitoring structure ---*/ nWndCauchy_Elems = config->GetWnd_Cauchy_Elems(); wndCauchyEps = config->GetWnd_Cauchy_Eps(); wndConvFields.reserve(config->GetnWndConv_Field()); for (unsigned short iField = 0; iField < config->GetnWndConv_Field(); iField++){ wndConvFields.emplace_back(config->GetWndConv_Field(iField)); } WndOld_Func = vector(wndConvFields.size()); WndNew_Func = vector(wndConvFields.size()); WndCauchy_Serie = vector>(wndConvFields.size(), vector(nWndCauchy_Elems, 0.0)); WndCauchy_Value = 0.0; TimeConvergence = false; /*--- Check that the number of cauchy elems is not too large ---*/ if (nCauchy_Elems > 1000){ SU2_MPI::Error("Number of Cauchy Elems must be smaller than 1000", CURRENT_FUNCTION); } if (nWndCauchy_Elems > 1000){ SU2_MPI::Error("Number of Time Cauchy Elems must be smaller than 1000", CURRENT_FUNCTION); } /*--- Initialize all convergence flags to false. ---*/ convergence = false; buildFieldIndexCache = false; curInnerIter = 0; curOuterIter = 0; curTimeIter = 0; volumeDataSorter = nullptr; volumeDataSorterCompact = nullptr; surfaceDataSorter = nullptr; headerNeeded = false; /*--- Setup a signal handler for SIGTERM. ---*/ signal(SIGTERM, signalHandler); } COutput::~COutput() { delete convergenceTable; delete multiZoneHeaderTable; delete fileWritingTable; delete historyFileTable; delete volumeDataSorter; delete volumeDataSorterCompact; delete surfaceDataSorter; } void COutput::SetHistoryOutput(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter) { curTimeIter = TimeIter; curAbsTimeIter = max(TimeIter, config->GetStartWindowIteration()) - config->GetStartWindowIteration(); curOuterIter = OuterIter; curInnerIter = InnerIter; /*--- Retrieve residual and extra data -----------------------------------------------------------------*/ LoadCommonHistoryData(config); LoadHistoryData(config, geometry, solver_container); ConvergenceMonitoring(config, curInnerIter); PostprocessHistoryData(config); MonitorTimeConvergence(config, curTimeIter); OutputScreenAndHistory(config); } void COutput::SetHistoryOutput(CGeometry *geometry, CSolver **solver_container, CConfig *config) { /*--- Retrieve residual and extra data -----------------------------------------------------------------*/ LoadCommonHistoryData(config); LoadHistoryData(config, geometry, solver_container); ConvergenceMonitoring(config, curInnerIter); PostprocessHistoryData(config); } void COutput::SetHistoryOutput(CGeometry ****geometry, CSolver *****solver, CConfig **config, std::shared_ptr(TurboStagePerf), std::shared_ptr TurboPerf, unsigned short val_iZone, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter, unsigned short val_iInst){ unsigned long Iter= InnerIter; if (config[ZONE_0]->GetMultizone_Problem()) Iter = OuterIter; /*--- Turbomachinery Performance Screen summary output---*/ if (Iter%100 == 0 && rank == MASTER_NODE) { SetTurboPerformance_Output(TurboPerf, config[val_iZone], TimeIter, OuterIter, InnerIter); SetTurboMultiZonePerformance_Output(TurboStagePerf, TurboPerf, config[val_iZone]); } for (int iZone = 0; iZone < config[ZONE_0]->GetnZone(); iZone ++){ if (rank == MASTER_NODE) { WriteTurboSpanwisePerformance(TurboPerf, geometry[iZone][val_iInst][MESH_0], config, iZone); } } /*--- Update turboperformance history file*/ if (rank == MASTER_NODE){ LoadTurboHistoryData(TurboStagePerf, TurboPerf, config[val_iZone]); } } void COutput::SetMultizoneHistoryOutput(COutput **output, CConfig **config, CConfig *driver_config, unsigned long TimeIter, unsigned long OuterIter){ curTimeIter = TimeIter; curAbsTimeIter = max(TimeIter, driver_config->GetStartWindowIteration()) - driver_config->GetStartWindowIteration(); curOuterIter = OuterIter; /*--- Retrieve residual and extra data -----------------------------------------------------------------*/ LoadCommonHistoryData(driver_config); LoadMultizoneHistoryData(output, config); ConvergenceMonitoring(driver_config, curOuterIter); PostprocessHistoryData(driver_config); MonitorTimeConvergence(driver_config, curTimeIter); OutputScreenAndHistory(driver_config); } void COutput::OutputScreenAndHistory(CConfig *config) { if (rank == MASTER_NODE && !noWriting) { if (WriteHistoryFileOutput(config)) SetHistoryFileOutput(config); if (WriteScreenHeader(config)) SetScreenHeader(config); if (WriteScreenOutput(config)) SetScreenOutput(config); } } void COutput::SetupCustomHistoryOutput(const std::string& expression, CustomHistoryOutput& output) const { std::vector symbols; output.expression = mel::Parse(expression, symbols); output.symbolValues.reserve(symbols.size()); for (const auto& symbol : symbols) { const auto* ptr = GetPtrToHistoryOutput(symbol); if (ptr == nullptr) { SU2_MPI::Error(std::string("Invalid history output (") + symbol + std::string(") used in expression:\n") + expression, CURRENT_FUNCTION); } output.symbolValues.push_back(ptr); } output.ready = true; } void COutput::SetCustomAndComboObjectives(int idxSol, const CConfig *config, CSolver **solver) { if (config->GetKind_ObjFunc() == CUSTOM_OBJFUNC && !config->GetCustomObjFunc().empty()) { if (!customObjFunc.ready) { SetupCustomHistoryOutput(config->GetCustomObjFunc(), customObjFunc); } solver[idxSol]->SetTotal_Custom_ObjFunc(customObjFunc.Eval()); } solver[idxSol]->Evaluate_ObjFunc(config, solver); SetHistoryOutputValue("COMBO", solver[idxSol]->GetTotal_ComboObj()); } void COutput::AllocateDataSorters(CConfig *config, CGeometry *geometry){ /*---- Construct a data sorter object to partition and distribute * the local data into linear chunks across the processors ---*/ if (femOutput){ if (volumeDataSorter == nullptr) volumeDataSorter = new CFEMDataSorter(config, geometry, volumeFieldNames); if (config->GetWrt_Restart_Compact() && volumeDataSorterCompact == nullptr) volumeDataSorterCompact = new CFEMDataSorter(config, geometry, requiredVolumeFieldNames); if (surfaceDataSorter == nullptr) surfaceDataSorter = new CSurfaceFEMDataSorter(config, geometry, dynamic_cast(volumeDataSorter)); } else { if (volumeDataSorter == nullptr) volumeDataSorter = new CFVMDataSorter(config, geometry, volumeFieldNames); if (config->GetWrt_Restart_Compact() && volumeDataSorterCompact == nullptr) volumeDataSorterCompact = new CFVMDataSorter(config, geometry, requiredVolumeFieldNames); if (surfaceDataSorter == nullptr) surfaceDataSorter = new CSurfaceFVMDataSorter(config, geometry, dynamic_cast(volumeDataSorter)); } } void COutput::LoadData(CGeometry *geometry, CConfig *config, CSolver** solver_container){ /*--- Check if the data sorters are allocated, if not, allocate them. --- */ AllocateDataSorters(config, geometry); /*--- Loop over all points and store the requested volume output data into the data sorter objects ---*/ LoadDataIntoSorter(config, geometry, solver_container); /*--- Partition and sort the volume output data -- */ volumeDataSorter->SortOutputData(); if (volumeDataSorterCompact != nullptr) volumeDataSorterCompact->SortOutputData(); } void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE format, string fileName) { /*--- File writer that will later be used to write the file to disk. Created below in the "switch" ---*/ CFileWriter *fileWriter = nullptr; /*--- Set current time iter even if history file is not written ---*/ curTimeIter = config->GetTimeIter(); /*--- If the filename with appended iteration is set (depending on the WRT_*_OVERWRITE options) * two files are writen, the normal one and a copy to avoid overwriting previous outputs. ---*/ string filename_iter, extension; /*--- Write output information to screen ---*/ auto LogOutputFiles = [&](const std::string& message) { if (rank == MASTER_NODE) { (*fileWritingTable) << message << fileName + extension; if (!filename_iter.empty()) (*fileWritingTable) << message + " + iter" << filename_iter + extension; } }; /*--- Write files depending on the format --- */ switch (format) { case OUTPUT_TYPE::SURFACE_CSV: extension = CSU2FileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- If we have compact restarts, we use only the required fields. ---*/ if (config->GetWrt_Restart_Compact()) surfaceDataSorter->SetRequiredFieldNames(requiredVolumeFieldNames); surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("CSV file"); fileWriter = new CSU2FileWriter(surfaceDataSorter); break; case OUTPUT_TYPE::RESTART_ASCII: case OUTPUT_TYPE::CSV: extension = CSU2FileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(restartFilename, "", curTimeIter); if (!config->GetWrt_Restart_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- If we have compact restarts, we use only the required fields. ---*/ if (config->GetWrt_Restart_Compact()) volumeDataSorter->SetRequiredFieldNames(requiredVolumeFieldNames); LogOutputFiles("SU2 ASCII restart"); fileWriter = new CSU2FileWriter(volumeDataSorter); break; case OUTPUT_TYPE::RESTART_BINARY: extension = CSU2BinaryFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(restartFilename, "", curTimeIter); if (!config->GetWrt_Restart_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); LogOutputFiles("SU2 binary restart"); if (config->GetWrt_Restart_Compact()) { /*--- If we have compact restarts, we use only the required fields. ---*/ volumeDataSorterCompact->SetRequiredFieldNames(requiredVolumeFieldNames); fileWriter = new CSU2BinaryFileWriter(volumeDataSorterCompact); } else { fileWriter = new CSU2BinaryFileWriter(volumeDataSorter); } break; case OUTPUT_TYPE::MESH: extension = CSU2MeshFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("SU2 mesh"); fileWriter = new CSU2MeshFileWriter(volumeDataSorter, config->GetiZone(), config->GetnZone()); break; case OUTPUT_TYPE::TECPLOT_BINARY: extension = CTecplotBinaryFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, false); LogOutputFiles("Tecplot binary"); fileWriter = new CTecplotBinaryFileWriter(volumeDataSorter, curTimeIter, GetHistoryFieldValue("TIME_STEP")); break; case OUTPUT_TYPE::TECPLOT_ASCII: extension = CTecplotFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("Tecplot ASCII"); fileWriter = new CTecplotFileWriter(volumeDataSorter, curTimeIter, GetHistoryFieldValue("TIME_STEP")); break; case OUTPUT_TYPE::PARAVIEW_XML: extension = CParaviewXMLFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("Paraview"); fileWriter = new CParaviewXMLFileWriter(volumeDataSorter); break; case OUTPUT_TYPE::PARAVIEW_LEGACY_BINARY: extension = CParaviewBinaryFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("Paraview binary (legacy)"); fileWriter = new CParaviewBinaryFileWriter(volumeDataSorter); break; case OUTPUT_TYPE::PARAVIEW_MULTIBLOCK: { extension = CParaviewVTMFileWriter::fileExt; if (fileName.empty()) fileName = config->GetUnsteady_FileName(volumeFilename, curTimeIter, ""); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Sort volume connectivity ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("Paraview Multiblock"); fileWriter = new CParaviewVTMFileWriter(GetHistoryFieldValue("CUR_TIME"), config->GetiZone(), config->GetnZone()); /*--- We cast the pointer to its true type, to avoid virtual functions ---*/ auto* vtmWriter = dynamic_cast(fileWriter); /*--- then we write the data into the folder---*/ vtmWriter->WriteFolderData(fileName, config, multiZoneHeaderString, volumeDataSorter, surfaceDataSorter, geometry); /*--- and we write the data into the folder with the iteration number ---*/ if (!config->GetWrt_Volume_Overwrite()) vtmWriter->WriteFolderData(filename_iter, config, multiZoneHeaderString, volumeDataSorter, surfaceDataSorter, geometry); } break; case OUTPUT_TYPE::PARAVIEW_ASCII: extension = CParaviewFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("Paraview ASCII"); fileWriter = new CParaviewFileWriter(volumeDataSorter); break; case OUTPUT_TYPE::SURFACE_PARAVIEW_ASCII: extension = CParaviewFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("Paraview ASCII surface"); fileWriter = new CParaviewFileWriter(surfaceDataSorter); break; case OUTPUT_TYPE::SURFACE_PARAVIEW_LEGACY_BINARY: extension = CParaviewBinaryFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("Paraview binary surface (legacy)"); fileWriter = new CParaviewBinaryFileWriter(surfaceDataSorter); break; case OUTPUT_TYPE::SURFACE_PARAVIEW_XML: extension = CParaviewXMLFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("Paraview surface"); fileWriter = new CParaviewXMLFileWriter(surfaceDataSorter); break; case OUTPUT_TYPE::SURFACE_TECPLOT_ASCII: extension = CTecplotFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("Tecplot ASCII surface"); fileWriter = new CTecplotFileWriter(surfaceDataSorter, curTimeIter, GetHistoryFieldValue("TIME_STEP")); break; case OUTPUT_TYPE::SURFACE_TECPLOT_BINARY: extension = CTecplotBinaryFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("Tecplot binary surface"); fileWriter = new CTecplotBinaryFileWriter(surfaceDataSorter, curTimeIter, GetHistoryFieldValue("TIME_STEP")); break; case OUTPUT_TYPE::STL_ASCII: extension = CSTLFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("STL ASCII"); fileWriter = new CSTLFileWriter(surfaceDataSorter); break; case OUTPUT_TYPE::CGNS: extension = CCGNSFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(volumeFilename, "", curTimeIter); if (!config->GetWrt_Volume_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ volumeDataSorter->SortConnectivity(config, geometry, true); LogOutputFiles("CGNS"); fileWriter = new CCGNSFileWriter(volumeDataSorter); break; case OUTPUT_TYPE::SURFACE_CGNS: extension = CCGNSFileWriter::fileExt; if (fileName.empty()) fileName = config->GetFilename(surfaceFilename, "", curTimeIter); if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); /*--- Load and sort the output data and connectivity. ---*/ surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); LogOutputFiles("CGNS surface"); fileWriter = new CCGNSFileWriter(surfaceDataSorter, true); break; default: break; } if (fileWriter != nullptr) { /*--- Write data to file ---*/ fileWriter->WriteData(fileName); su2double BandWidth = fileWriter->GetBandwidth(); /*--- Write data with iteration number to file if required ---*/ if (!filename_iter.empty()) { fileWriter->WriteData(filename_iter); /*--- Average bandwidth ---*/ BandWidth = (BandWidth + fileWriter->GetBandwidth()) / 2; } /*--- Compute and store the bandwidth ---*/ if (format == OUTPUT_TYPE::RESTART_BINARY) { config->SetRestart_Bandwidth_Agg(config->GetRestart_Bandwidth_Agg() + BandWidth); } if (config->GetWrt_Performance() && (rank == MASTER_NODE)){ fileWritingTable->SetAlign(PrintingToolbox::CTablePrinter::RIGHT); (*fileWritingTable) << " " << "(" + PrintingToolbox::to_string(BandWidth) + " MB/s)"; fileWritingTable->SetAlign(PrintingToolbox::CTablePrinter::LEFT); } delete fileWriter; } } bool COutput::GetCauchyCorrectedTimeConvergence(const CConfig *config){ // Handle Cauchy convergence delay for 2nd order time stepping if(!cauchyTimeConverged && TimeConvergence && config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND){ // Change flags for 2nd order Time stepping: In case of convergence, this iter and next iter gets written out. then solver stops cauchyTimeConverged = TimeConvergence; TimeConvergence = false; } else if(cauchyTimeConverged){ TimeConvergence = cauchyTimeConverged; } // Handle max time delay for 2nd order time stepping // Delay stopping at max_time to ensure both timestep N and N-1 are written for proper restart if(config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND){ const su2double cur_time = GetHistoryFieldValue("CUR_TIME"); const su2double max_time = config->GetMax_Time(); const bool final_time_reached = (cur_time >= max_time); // If max_time is reached on first detection, delay the stop if(final_time_reached && !maxTimeDelayActive){ maxTimeDelayActive = true; TimeConvergence = false; // Delay stop to run one more iteration } else if(maxTimeDelayActive){ TimeConvergence = true; // Now allow stop maxTimeDelayActive = false; // Reset for next run } } return TimeConvergence; } bool COutput::SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** solver_container, unsigned long iter, bool force_writing) { bool isFileWrite = false, dataIsLoaded = false; const auto nVolumeFiles = config->GetnVolumeOutputFiles(); const auto* VolumeFiles = config->GetVolumeOutputFiles(); /*--- Check if the data sorters are allocated, if not, allocate them. --- */ AllocateDataSorters(config, geometry); for (unsigned short iFile = 0; iFile < nVolumeFiles; iFile++) { /*--- Collect the volume data from the solvers. * If time-domain is enabled, we also load the data although we don't output it, * since we might want to do time-averaging. ---*/ const bool write_file = WriteVolumeOutput(config, iter, force_writing || cauchyTimeConverged, iFile); if ((write_file || config->GetTime_Domain()) && !dataIsLoaded) { LoadDataIntoSorter(config, geometry, solver_container); dataIsLoaded = true; } if (!write_file) continue; /*--- Partition and sort the data --- */ volumeDataSorter->SortOutputData(); if (volumeDataSorterCompact != nullptr) volumeDataSorterCompact->SortOutputData(); if (rank == MASTER_NODE && !isFileWrite) { fileWritingTable->SetAlign(PrintingToolbox::CTablePrinter::CENTER); fileWritingTable->PrintHeader(); fileWritingTable->SetAlign(PrintingToolbox::CTablePrinter::LEFT); } /*--- Loop through all requested output files and write * the partitioned and sorted data stored in the data sorters. ---*/ WriteToFile(config, geometry, VolumeFiles[iFile]); /*--- Write any additonal files defined in the child class ----*/ WriteAdditionalFiles(config, geometry, solver_container); isFileWrite = true; } if (rank == MASTER_NODE && isFileWrite) { fileWritingTable->PrintFooter(); headerNeeded = true; } return isFileWrite; } void COutput::PrintConvergenceSummary(){ PrintingToolbox::CTablePrinter ConvSummary(&cout); ConvSummary.AddColumn("Convergence Field", 28); ConvSummary.AddColumn("Value", 14); ConvSummary.AddColumn("Criterion", 14); ConvSummary.AddColumn("Converged",12); ConvSummary.SetAlign(PrintingToolbox::CTablePrinter::CENTER); ConvSummary.PrintHeader(); for (unsigned short iField_Conv = 0; iField_Conv < convFields.size(); iField_Conv++){ const string &convField = convFields[iField_Conv]; if (historyOutput_Map.at(convField).fieldType == HistoryFieldType::COEFFICIENT) { string convMark = "No"; if ( historyOutput_Map.at("CAUCHY_" + convField).value < cauchyEps) convMark = "Yes"; ConvSummary << historyOutput_Map.at("CAUCHY_" + convField).fieldName << historyOutput_Map.at("CAUCHY_" + convField).value << " < " + PrintingToolbox::to_string(cauchyEps) << convMark; } else if (historyOutput_Map.at(convField).fieldType == HistoryFieldType::RESIDUAL || historyOutput_Map.at(convField).fieldType == HistoryFieldType::AUTO_RESIDUAL) { string convMark = "No"; if (historyOutput_Map.at(convField).value < minLogResidual) convMark = "Yes"; ConvSummary << historyOutput_Map.at(convField).fieldName << historyOutput_Map.at(convField).value << " < " + PrintingToolbox::to_string(minLogResidual) << convMark; } } ConvSummary.PrintFooter(); } bool COutput::ConvergenceMonitoring(CConfig *config, unsigned long Iteration) { convergence = true; for (auto iField_Conv = 0ul; iField_Conv < convFields.size(); iField_Conv++) { const auto& convField = convFields[iField_Conv]; const auto it = historyOutput_Map.find(convField); if (it == historyOutput_Map.end()) continue; const auto& field = it->second; const su2double monitor = field.value; /*--- Stop the simulation in case a nan appears, do not save the solution. ---*/ if (std::isnan(SU2_TYPE::GetValue(monitor))) { SU2_MPI::Error("SU2 has diverged (NaN detected).", CURRENT_FUNCTION); } bool fieldConverged = false; switch (field.fieldType) { /*--- Cauchy based convergence criteria ---*/ case HistoryFieldType::COEFFICIENT: { if (Iteration == 0) { for (auto iCounter = 0ul; iCounter < nCauchy_Elems; iCounter++) { cauchySerie[iField_Conv][iCounter] = 0.0; } newFunc[iField_Conv] = monitor; } oldFunc[iField_Conv] = newFunc[iField_Conv]; newFunc[iField_Conv] = monitor; /*--- Automatically modify the scaling factor of relative Cauchy convergence for * coefficients that are close to zero. Example: For the clean aircraft, the rolling * moment coefficient MOMENT_X is close to zero and thus will never reach a relative * cauchy convergence ->> dividing tiny numbers is not a good idea. Using absolute * cauchy convergence is more robust in this case. ---*/ cauchyFunc = fabs(newFunc[iField_Conv] - oldFunc[iField_Conv]) / fmax(fabs(monitor), 0.1); cauchySerie[iField_Conv][Iteration % nCauchy_Elems] = cauchyFunc; cauchyValue = 0.0; for (auto iCounter = 0ul; iCounter < nCauchy_Elems; iCounter++) cauchyValue += cauchySerie[iField_Conv][iCounter]; cauchyValue /= nCauchy_Elems; /*--- Start monitoring only if the current iteration * is larger than the number of cauchy elements. --- */ fieldConverged = (cauchyValue < cauchyEps) && (Iteration >= nCauchy_Elems); if (Iteration == 0) cauchyValue = 1.0; SetHistoryOutputValue("CAUCHY_" + convField, cauchyValue); } break; /*--- Residual based convergence criteria ---*/ case HistoryFieldType::RESIDUAL: case HistoryFieldType::AUTO_RESIDUAL: fieldConverged = (Iteration != 0) && (monitor <= minLogResidual); break; default: break; } convergence = fieldConverged && convergence; } /*--- Do not apply any convergence criteria if the number * of iterations is less than a particular value. ---*/ if (convFields.empty() || Iteration < config->GetStartConv_Iter()) convergence = false; /*--- If a SIGTERM signal is sent to one of the processes, we set convergence to true. ---*/ if (STOP) convergence = true; /*--- Apply the same convergence criteria to all processors. ---*/ unsigned short local = convergence, global = 0; SU2_MPI::Allreduce(&local, &global, 1, MPI_UNSIGNED_SHORT, MPI_MAX, SU2_MPI::GetComm()); convergence = global > 0; return convergence; } bool COutput::MonitorTimeConvergence(CConfig *config, unsigned long TimeIteration) { bool Inner_IterConv = GetConvergence() || config->GetnInner_Iter()-1 <= curInnerIter; //Check, if Inner_Iter is converged if(TimeIteration == 0){ for (unsigned short iField_Conv = 0; iField_Conv < wndConvFields.size(); iField_Conv++){ const string WndConv_Field= wndConvFields[iField_Conv]; if (historyOutput_Map.count(WndConv_Field) > 0){ SetHistoryOutputValue("CAUCHY_"+ WndConv_Field, 1.0); } } } if(Inner_IterConv && TimeIteration >= config->GetStartWindowIteration()){ TimeConvergence = true; unsigned short iCounter; for (unsigned short iField_Conv = 0; iField_Conv < wndConvFields.size(); iField_Conv++){ bool fieldConverged = false; const string WndConv_Field= wndConvFields[iField_Conv]; if (historyOutput_Map.count(WndConv_Field) > 0){ su2double monitor = historyOutput_Map[WndConv_Field].value; /*--- Stop the simulation in case a nan appears, do not save the solution ---*/ if (std::isnan(SU2_TYPE::GetValue(monitor))) { SU2_MPI::Error("SU2 has diverged (NaN detected).", CURRENT_FUNCTION); } /*--- Cauchy based convergence criteria ---*/ if (historyOutput_Map[WndConv_Field].fieldType == HistoryFieldType::AUTO_COEFFICIENT) { //TAVG values are AUTO_COEFF if (TimeIteration == config->GetStartWindowIteration()){ for (iCounter = 0; iCounter < nWndCauchy_Elems; iCounter++){ WndCauchy_Serie[iField_Conv][iCounter] = 0.0; } WndNew_Func[iField_Conv] = monitor; } WndOld_Func[iField_Conv] = WndNew_Func[iField_Conv]; WndNew_Func[iField_Conv] = monitor; WndCauchy_Func = fabs(WndNew_Func[iField_Conv] - WndOld_Func[iField_Conv]); WndCauchy_Serie[iField_Conv][TimeIteration % nWndCauchy_Elems] = WndCauchy_Func; WndCauchy_Value = 1.0; if (TimeIteration >= nWndCauchy_Elems+config->GetStartWindowIteration()){ WndCauchy_Value = 0.0; for (iCounter = 0; iCounter < nWndCauchy_Elems; iCounter++){ WndCauchy_Value += WndCauchy_Serie[iField_Conv][iCounter]; } WndCauchy_Value /= nWndCauchy_Elems; } fieldConverged = WndCauchy_Value < wndCauchyEps; /*--- Start monitoring only if the current iteration is larger than the * number of cauchy elements and the number of start-up iterations ---*/ if (TimeIteration < config->GetStartWindowIteration() + max(config->GetWnd_StartConv_Iter(), nWndCauchy_Elems)){ fieldConverged = false; } SetHistoryOutputValue("CAUCHY_" + WndConv_Field, WndCauchy_Value); } TimeConvergence = fieldConverged && TimeConvergence; } } /*--- Do not apply any convergence criterion if the option is disabled. */ if(!config->GetWnd_Cauchy_Crit()){TimeConvergence = false;} if(wndConvFields.empty()){TimeConvergence = false;} } return TimeConvergence; } void COutput::SetHistoryFileHeader(const CConfig *config) { unsigned short iField_Output = 0, iReqField = 0, iMarker = 0; stringstream out; int width = 20; for (iField_Output = 0; iField_Output < historyOutput_List.size(); iField_Output++){ const string &fieldIdentifier = historyOutput_List[iField_Output]; const HistoryOutputField &field = historyOutput_Map.at(fieldIdentifier); for (iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++){ const string & requestedField = requestedHistoryFields[iReqField]; if (requestedField == field.outputGroup || (requestedField == fieldIdentifier)){ if (field.screenFormat == ScreenOutputFormat::INTEGER) width = std::max((int)field.fieldName.size()+2, 10); else{ width = std::max((int)field.fieldName.size()+2, 18);} historyFileTable->AddColumn("\"" + field.fieldName + "\"", width); } } } for (iField_Output = 0; iField_Output < historyOutputPerSurface_List.size(); iField_Output++){ const string &fieldIdentifier = historyOutputPerSurface_List[iField_Output]; for (iMarker = 0; iMarker < historyOutputPerSurface_Map[fieldIdentifier].size(); iMarker++){ const HistoryOutputField &field = historyOutputPerSurface_Map.at(fieldIdentifier)[iMarker]; for (iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++){ const string &requestedField = requestedHistoryFields[iReqField]; if (requestedField == field.outputGroup || (requestedField == fieldIdentifier)){ if (field.screenFormat == ScreenOutputFormat::INTEGER) width = std::max((int)field.fieldName.size()+2, 10); else{ width = std::max((int)field.fieldName.size()+2, 18);} historyFileTable->AddColumn("\"" + field.fieldName + "\"", width); } } } } if (config->GetTabular_FileFormat() == TAB_OUTPUT::TAB_TECPLOT) { histFile << "VARIABLES = \\" << endl; } historyFileTable->PrintHeader(); histFile.flush(); } void COutput::SetHistoryFileOutput(const CConfig *config) { if (requestedHistoryFieldCache.empty()) { for (const auto& fieldIdentifier : historyOutput_List){ const auto& field = historyOutput_Map.at(fieldIdentifier); for (const auto& requestedField : requestedHistoryFields) { if ((requestedField == field.outputGroup) || (requestedField == fieldIdentifier)) { requestedHistoryFieldCache.push_back(&field.value); } } } for (const auto& fieldIdentifier : historyOutputPerSurface_List) { for (const auto& field : historyOutputPerSurface_Map.at(fieldIdentifier)) { for (const auto& requestedField : requestedHistoryFields){ if ((requestedField == field.outputGroup) || (requestedField == fieldIdentifier)) { requestedHistoryFieldCache.push_back(&field.value); } } } } } for (const auto* valPtr : requestedHistoryFieldCache) { (*historyFileTable) << *valPtr; } /*--- Print the string to file and remove the last two characters (a separator and a space) ---*/ histFile.flush(); } void COutput::SetScreenHeader(const CConfig *config) { if (config->GetMultizone_Problem()) multiZoneHeaderTable->PrintHeader(); convergenceTable->PrintHeader(); } void COutput::SetScreenOutput(const CConfig *config) { if (requestedScreenFieldCache.empty()) { for (const auto& RequestedField : requestedScreenFields) { const auto it1 = historyOutput_Map.find(RequestedField); if (it1 != historyOutput_Map.end()) { requestedScreenFieldCache.push_back(&it1->second); } const auto it2 = historyOutputPerSurface_Map.find(RequestedField); if (it2 != historyOutputPerSurface_Map.end()) { for (size_t i = 0; i < it2->second.size(); ++i) { requestedScreenFieldCache.push_back(&it2->second[i]); } } } } for (const auto* fieldPtr : requestedScreenFieldCache) { const auto& field = *fieldPtr; stringstream out; switch (field.screenFormat) { case ScreenOutputFormat::INTEGER: PrintingToolbox::PrintScreenInteger(out, SU2_TYPE::Int(field.value), fieldWidth); break; case ScreenOutputFormat::FIXED: PrintingToolbox::PrintScreenFixed(out, field.value, fieldWidth); break; case ScreenOutputFormat::SCIENTIFIC: PrintingToolbox::PrintScreenScientific(out, field.value, fieldWidth); break; case ScreenOutputFormat::PERCENT: PrintingToolbox::PrintScreenPercent(out, field.value, fieldWidth); break; } (*convergenceTable) << out.str(); } SetAdditionalScreenOutput(config); } void COutput::PreprocessHistoryOutput(CConfig *config, bool wrt){ noWriting = !wrt; /*--- Set the common output fields ---*/ SetCommonHistoryFields(); /*--- Set the History output fields using a virtual function call to the child implementation ---*/ SetHistoryOutputFields(config); /*--- Detect user-defined outputs ---*/ SetCustomOutputs(config); /*--- Postprocess the history fields. Creates new fields based on the ones set in the child classes ---*/ PostprocessHistoryFields(config); /*--- We use a fixed size of the file output summary table ---*/ int total_width = 72; fileWritingTable->AddColumn("File Writing Summary", (total_width)/2-1); fileWritingTable->AddColumn("Filename", total_width/2-1); fileWritingTable->SetAlign(PrintingToolbox::CTablePrinter::LEFT); /*--- Check for consistency and remove fields that are requested but not available --- */ CheckHistoryOutput(config->GetnZone()); if (rank == MASTER_NODE && !noWriting){ /*--- Open history file and print the header ---*/ if (!config->GetMultizone_Problem() || config->GetWrt_ZoneHist()) PrepareHistoryFile(config); total_width = nRequestedScreenFields*fieldWidth + (nRequestedScreenFields-1); /*--- Set the multizone screen header ---*/ if (config->GetMultizone_Problem()){ multiZoneHeaderTable->AddColumn(multiZoneHeaderString, total_width); multiZoneHeaderTable->SetAlign(PrintingToolbox::CTablePrinter::CENTER); multiZoneHeaderTable->SetPrintHeaderBottomLine(false); } } } void COutput::PreprocessMultizoneHistoryOutput(COutput **output, CConfig **config, CConfig* driver_config, bool wrt){ noWriting = !wrt; /*--- Set the common history fields for all solvers ---*/ SetCommonHistoryFields(); /*--- Set the History output fields using a virtual function call to the child implementation ---*/ SetMultizoneHistoryOutputFields(output, config); /*--- Postprocess the history fields. Creates new fields based on the ones set in the child classes ---*/ PostprocessHistoryFields(driver_config); /*--- We use a fixed size of the file output summary table ---*/ int total_width = 72; fileWritingTable->AddColumn("File Writing Summary", (total_width-1)/2); fileWritingTable->AddColumn("Filename", total_width/2); fileWritingTable->SetAlign(PrintingToolbox::CTablePrinter::LEFT); /*--- Check for consistency and remove fields that are requested but not available --- */ CheckHistoryOutput(config[ZONE_0]->GetnZone()); if (rank == MASTER_NODE && !noWriting){ /*--- Open history file and print the header ---*/ PrepareHistoryFile(driver_config); total_width = nRequestedScreenFields*fieldWidth + (nRequestedScreenFields-1); /*--- Set the multizone screen header ---*/ if (config[ZONE_0]->GetMultizone_Problem()){ multiZoneHeaderTable->AddColumn(multiZoneHeaderString, nRequestedScreenFields*fieldWidth + (nRequestedScreenFields-1)); multiZoneHeaderTable->SetAlign(PrintingToolbox::CTablePrinter::CENTER); multiZoneHeaderTable->SetPrintHeaderBottomLine(false); } } } void COutput::PrepareHistoryFile(CConfig *config){ /*--- Open the history file ---*/ histFile.open(historyFilename, ios::out); /*--- Create and format the history file table ---*/ historyFileTable->SetInnerSeparator(historySep); historyFileTable->SetAlign(PrintingToolbox::CTablePrinter::CENTER); historyFileTable->SetPrintHeaderTopLine(false); historyFileTable->SetPrintHeaderBottomLine(false); historyFileTable->SetPrecision(config->GetOutput_Precision()); /*--- Add the header to the history file. ---*/ SetHistoryFileHeader(config); } void COutput::CheckHistoryOutput(unsigned short nZone) { /*--- Set screen convergence output header and remove unavailable fields ---*/ vector requestWithExpandedGroups; for (const auto& requestedField : requestedScreenFields) { bool isGroup = false; for (const auto& name : historyOutput_List) { if (requestedField == historyOutput_Map.at(name).outputGroup) { isGroup = true; requestWithExpandedGroups.push_back(name); } } for (const auto& name : historyOutputPerSurface_List) { if (requestedField == historyOutputPerSurface_Map.at(name).front().outputGroup) { isGroup = true; requestWithExpandedGroups.push_back(name); } } if (!isGroup) { requestWithExpandedGroups.push_back(requestedField); } } requestedScreenFields = std::move(requestWithExpandedGroups); vector FieldsToRemove; for (const auto& requestedField : requestedScreenFields) { const auto it1 = historyOutput_Map.find(requestedField); if (it1 != historyOutput_Map.end()) { convergenceTable->AddColumn(it1->second.fieldName, fieldWidth); } const auto it2 = historyOutputPerSurface_Map.find(requestedField); if (it2 != historyOutputPerSurface_Map.end()) { for (const auto& field : it2->second) { convergenceTable->AddColumn(field.fieldName, fieldWidth); } } if (it1 == historyOutput_Map.end() && it2 == historyOutputPerSurface_Map.end()) { FieldsToRemove.push_back(requestedField); } } /*--- Remove fields which are not defined --- */ for (unsigned short iReqField = 0; iReqField < FieldsToRemove.size(); iReqField++) { if (rank == MASTER_NODE) { if (iReqField == 0) { cout << " Info: Ignoring the following screen output fields:\n "; } cout << FieldsToRemove[iReqField]; if (iReqField != FieldsToRemove.size()-1) { cout << ", "; } else { cout << endl; } } requestedScreenFields.erase(std::find(requestedScreenFields.begin(), requestedScreenFields.end(), FieldsToRemove[iReqField])); } nRequestedScreenFields = requestedScreenFields.size(); if (rank == MASTER_NODE){ cout <<"Screen output fields: "; for (unsigned short iReqField = 0; iReqField < nRequestedScreenFields; iReqField++){ cout << requestedScreenFields[iReqField]; if (iReqField != nRequestedScreenFields - 1) cout << ", "; } cout << endl; } /*--- Remove unavailable fields from the history file output ---*/ FieldsToRemove.clear(); vector FoundField(nRequestedHistoryFields, false); /*--- Checks if TURBO_PERF is enabled in config and sets the final zone calculations to be output ---*/ for (unsigned short iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++){ if (requestedHistoryFields[iReqField] == "TURBO_PERF" && nZone > 1){ std::stringstream reqField; std::string strZones = std::to_string(nZone-1); reqField << "TURBO_PERF[" << strZones << "]"; reqField >> requestedHistoryFields[iReqField]; } } for (const auto& fieldReference : historyOutput_List) { const auto &field = historyOutput_Map.at(fieldReference); for (unsigned short iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++) { const auto& requestedField = requestedHistoryFields[iReqField]; if ((requestedField == field.outputGroup) || (requestedField == fieldReference)) { FoundField[iReqField] = true; } } } for (const auto& fieldReference : historyOutputPerSurface_List) { for (const auto& field : historyOutputPerSurface_Map.at(fieldReference)) { for (unsigned short iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++){ const auto& requestedField = requestedHistoryFields[iReqField]; if ((requestedField == field.outputGroup) || (requestedField == fieldReference)) { FoundField[iReqField] = true; } } } } for (unsigned short iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++){ if (!FoundField[iReqField]){ FieldsToRemove.push_back(requestedHistoryFields[iReqField]); } } /*--- Remove fields which are not defined --- */ for (unsigned short iReqField = 0; iReqField < FieldsToRemove.size(); iReqField++){ if (rank == MASTER_NODE) { if (iReqField == 0){ cout << " Info: Ignoring the following history output groups:\n "; } cout << FieldsToRemove[iReqField]; if (iReqField != FieldsToRemove.size()-1){ cout << ", "; } else { cout << endl; } } requestedHistoryFields.erase(std::find(requestedHistoryFields.begin(), requestedHistoryFields.end(), FieldsToRemove[iReqField])); } nRequestedHistoryFields = requestedHistoryFields.size(); if (rank == MASTER_NODE){ cout <<"History output group(s): "; for (unsigned short iReqField = 0; iReqField < nRequestedHistoryFields; iReqField++){ cout << requestedHistoryFields[iReqField]; if (iReqField != nRequestedHistoryFields - 1) cout << ", "; } cout << endl; } /*--- Check that the requested convergence monitoring field is available ---*/ bool removedField = false; FieldsToRemove.clear(); for (unsigned short iField_Conv = 0; iField_Conv < convFields.size(); iField_Conv++){ if (historyOutput_Map.count(convFields[iField_Conv]) == 0){ if (!removedField) { if(rank == MASTER_NODE) cout << "Ignoring Convergence Field(s): "; removedField = true; } if(rank == MASTER_NODE) cout << convFields[iField_Conv] << " "; FieldsToRemove.push_back(convFields[iField_Conv]); } } if (removedField && (rank == MASTER_NODE)) cout << endl; for (unsigned short iField_Conv = 0; iField_Conv < FieldsToRemove.size(); iField_Conv++){ convFields.erase(std::find(convFields.begin(), convFields.end(), FieldsToRemove[iField_Conv])); } if (rank == MASTER_NODE){ if(convFields.empty()){ cout << "Warning: No (valid) fields chosen for convergence monitoring. Convergence monitoring inactive."<< endl; } else{ cout <<"Convergence field(s): "; for (unsigned short iField_Conv = 0; iField_Conv < convFields.size(); iField_Conv++){ cout << convFields[iField_Conv]; if (iField_Conv != convFields.size() - 1) cout << ", "; } cout << endl; } } /*--- Check that the requested time convergene monitoring field(s) are available*/ removedField = false; FieldsToRemove.clear(); for (unsigned short iField_Conv = 0; iField_Conv < wndConvFields.size(); iField_Conv++){ if (historyOutput_Map.count(wndConvFields[iField_Conv]) == 0){ if (!removedField) { if(rank == MASTER_NODE) cout << "Ignoring Time Convergence Field(s): "; removedField = true; } if(rank == MASTER_NODE)cout << wndConvFields[iField_Conv] << " "; FieldsToRemove.push_back(wndConvFields[iField_Conv]); } } if (removedField && rank ==MASTER_NODE) cout << endl; for (unsigned short iField_Conv = 0; iField_Conv < FieldsToRemove.size(); iField_Conv++){ wndConvFields.erase(std::find(wndConvFields.begin(), wndConvFields.end(), FieldsToRemove[iField_Conv])); } if (rank == MASTER_NODE){ if(wndConvFields.empty()){ cout << "Warning: No (valid) fields chosen for time convergence monitoring. Time convergence monitoring inactive."<< endl; } else{ cout <<"Time Convergence field(s): "; for (unsigned short iField_Conv = 0; iField_Conv < wndConvFields.size(); iField_Conv++){ cout << wndConvFields[iField_Conv]; if (iField_Conv != wndConvFields.size() - 1) cout << ", "; } cout << endl; } } } void COutput::PreprocessVolumeOutput(CConfig *config){ /*--- Set the volume output fields using a virtual function call to the child implementation ---*/ SetVolumeOutputFields(config); /*--- Coordinates must be always in the output. If they are not requested, add them here. ---*/ auto itCoord = std::find(requestedVolumeFields.begin(), requestedVolumeFields.end(), "COORDINATES"); if (itCoord == requestedVolumeFields.end()) { requestedVolumeFields.emplace_back("COORDINATES"); nRequestedVolumeFields++; } /*--- Add the solution if it was not requested for backwards compatibility, unless the COMPACT keyword was used to request exclusively the specified fields. ---*/ auto itSol = std::find(requestedVolumeFields.begin(), requestedVolumeFields.end(), "SOLUTION"); if (itSol == requestedVolumeFields.end()) { auto itCompact = std::find(requestedVolumeFields.begin(), requestedVolumeFields.end(), "COMPACT"); if (itCompact == requestedVolumeFields.end()) { requestedVolumeFields.emplace_back("SOLUTION"); nRequestedVolumeFields++; } } std::vector FoundField(nRequestedVolumeFields, false); vector FieldsToRemove; /*--- Loop through all fields defined in the corresponding SetVolumeOutputFields(). * If it is also defined in the config (either as part of a group or a single field), the field * object gets an offset so that we know where to find the data in the Local_Data() array. * Note that the default offset is -1. An index !=-1 defines this field as part of the output. ---*/ unsigned short nVolumeFields = 0, nVolumeFieldsCompact = 0; for (size_t iField_Output = 0; iField_Output < volumeOutput_List.size(); iField_Output++) { const string &fieldReference = volumeOutput_List[iField_Output]; const auto it = volumeOutput_Map.find(fieldReference); if (it != volumeOutput_Map.end()) { VolumeOutputField &Field = it->second; /*--- Loop through the minimum required fields for restarts. ---*/ for (const auto& RequiredField : restartVolumeFields) { if ((RequiredField == Field.outputGroup || RequiredField == fieldReference) && Field.offsetCompact == -1) { Field.offsetCompact = nVolumeFieldsCompact++; requiredVolumeFieldNames.push_back(Field.fieldName); } } /*--- Loop through all fields specified in the config. ---*/ for (size_t iReqField = 0; iReqField < nRequestedVolumeFields; iReqField++) { const auto &RequestedField = requestedVolumeFields[iReqField]; if ((RequestedField == Field.outputGroup || RequestedField == fieldReference) && Field.offset == -1) { Field.offset = nVolumeFields++; volumeFieldNames.push_back(Field.fieldName); FoundField[iReqField] = true; } } } } for (size_t iReqField = 0; iReqField < nRequestedVolumeFields; iReqField++){ if (!FoundField[iReqField]){ FieldsToRemove.push_back(requestedVolumeFields[iReqField]); } } /*--- Remove fields which are not defined --- */ for (size_t iReqField = 0; iReqField < FieldsToRemove.size(); iReqField++){ if (rank == MASTER_NODE) { if (iReqField == 0){ cout << " Info: Ignoring the following volume output fields/groups:" << endl; cout << " "; } cout << FieldsToRemove[iReqField]; if (iReqField != FieldsToRemove.size()-1){ cout << ", "; } else { cout << endl; } } requestedVolumeFields.erase(std::find(requestedVolumeFields.begin(), requestedVolumeFields.end(), FieldsToRemove[iReqField])); } nRequestedVolumeFields = requestedVolumeFields.size(); if (rank == MASTER_NODE){ cout <<"Volume output fields: "; for (unsigned short iReqField = 0; iReqField < nRequestedVolumeFields; iReqField++){ cout << requestedVolumeFields[iReqField]; if (iReqField != nRequestedVolumeFields - 1) cout << ", "; } cout << endl; } } void COutput::LoadDataIntoSorter(CConfig* config, CGeometry* geometry, CSolver** solver){ unsigned short iMarker = 0; unsigned long iPoint = 0, jPoint = 0; unsigned long iVertex = 0; /*--- Reset the offset cache and index --- */ cachePosition = 0; fieldIndexCache.clear(); fieldIndexCacheCompact.clear(); curGetFieldIndex = 0; fieldGetIndexCache.clear(); if (femOutput) { /*--- Create an object of the class CMeshFEM_DG and retrieve the necessary geometrical information for the FEM DG solver. ---*/ auto *DGGeometry = dynamic_cast(geometry); unsigned long nVolElemOwned = DGGeometry->GetNVolElemOwned(); CVolumeElementFEM *volElem = DGGeometry->GetVolElem(); /*--- Access the solution by looping over the owned volume elements. ---*/ for(unsigned long l=0; lGetnPointDomain(); iPoint++) { buildFieldIndexCache = fieldIndexCache.empty(); LoadVolumeData(config, geometry, solver, iPoint); } /*--- Reset the offset cache and index --- */ cachePosition = 0; fieldIndexCache.clear(); fieldIndexCacheCompact.clear(); curGetFieldIndex = 0; fieldGetIndexCache.clear(); for (iMarker = 0; iMarker < config->GetnMarker_All(); iMarker++) { /*--- We only want to have surface values on solid walls ---*/ if (config->GetSolid_Wall(iMarker)) { for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); /*--- Load the surface data into the data sorter. --- */ if (geometry->nodes->GetDomain(iPoint)) { buildFieldIndexCache = fieldIndexCache.empty(); LoadSurfaceData(config, geometry, solver, iPoint, iMarker, iVertex); } } } } } } void COutput::SetVolumeOutputValue(const string& name, unsigned long iPoint, su2double value){ if (buildFieldIndexCache) { /*--- Build up the offset cache to speed up subsequent * calls of this routine since the order of calls is * the same for every value of iPoint. ---*/ const auto it = volumeOutput_Map.find(name); if (it != volumeOutput_Map.end()) { const short Offset = it->second.offset; fieldIndexCache.push_back(Offset); if (Offset != -1) { volumeDataSorter->SetUnsortedData(iPoint, Offset, value); } /*--- Note that the compact fields are a subset of the full fields. ---*/ const short OffsetCompact = it->second.offsetCompact; fieldIndexCacheCompact.push_back(OffsetCompact); if (volumeDataSorterCompact != nullptr && OffsetCompact != -1) { volumeDataSorterCompact->SetUnsortedData(iPoint, OffsetCompact, value); } } else { SU2_MPI::Error("Cannot find output field with name " + name, CURRENT_FUNCTION); } } else { /*--- Use the offset caches for the access. ---*/ const short Offset = fieldIndexCache[cachePosition]; const short OffsetCompact = fieldIndexCacheCompact[cachePosition++]; if (cachePosition == fieldIndexCache.size()) { cachePosition = 0; } if (Offset != -1) { volumeDataSorter->SetUnsortedData(iPoint, Offset, value); } if (volumeDataSorterCompact != nullptr && OffsetCompact != -1) { volumeDataSorterCompact->SetUnsortedData(iPoint, OffsetCompact, value); } } } su2double COutput::GetVolumeOutputValue(const string& name, unsigned long iPoint) { if (buildFieldIndexCache) { /*--- Build up the offset cache to speed up subsequent * calls of this routine since the order of calls is * the same for every value of iPoint. ---*/ const auto it = volumeOutput_Map.find(name); if (it != volumeOutput_Map.end()) { const short Offset = it->second.offset; fieldGetIndexCache.push_back(Offset); if (Offset != -1) { return volumeDataSorter->GetUnsortedData(iPoint, Offset); } } else { SU2_MPI::Error("Cannot find output field with name " + name, CURRENT_FUNCTION); } } else { /*--- Use the offset cache for the access, ---*/ const short Offset = fieldGetIndexCache[curGetFieldIndex++]; if (curGetFieldIndex == fieldGetIndexCache.size()) { curGetFieldIndex = 0; } if (Offset != -1) { return volumeDataSorter->GetUnsortedData(iPoint, Offset); } } return 0.0; } void COutput::SetAvgVolumeOutputValue(const string& name, unsigned long iPoint, su2double value){ const su2double scaling = 1.0 / su2double(curAbsTimeIter + 1); if (buildFieldIndexCache) { /*--- Build up the offset cache to speed up subsequent * calls of this routine since the order of calls is * the same for every value of iPoint. ---*/ const auto it = volumeOutput_Map.find(name); if (it != volumeOutput_Map.end()) { const short Offset = it->second.offset; fieldIndexCache.push_back(Offset); /*--- This function is used for time-averaged fields and we know * those are not part of the compact restart fields. ---*/ fieldIndexCacheCompact.push_back(-1); if (Offset != -1) { const su2double old_value = volumeDataSorter->GetUnsortedData(iPoint, Offset); const su2double new_value = value * scaling + old_value * (1.0 - scaling); volumeDataSorter->SetUnsortedData(iPoint, Offset, new_value); } } else { SU2_MPI::Error("Cannot find output field with name " + name, CURRENT_FUNCTION); } } else { /*--- Use the offset cache for the access ---*/ const short Offset = fieldIndexCache[cachePosition++]; if (Offset != -1) { const su2double old_value = volumeDataSorter->GetUnsortedData(iPoint, Offset); const su2double new_value = value * scaling + old_value * (1.0 - scaling); volumeDataSorter->SetUnsortedData(iPoint, Offset, new_value); } if (cachePosition == fieldIndexCache.size()) { cachePosition = 0; } } } void COutput::PostprocessHistoryData(CConfig *config){ map > Average; map Count; for (unsigned short iField = 0; iField < historyOutput_List.size(); iField++){ const string &fieldIdentifier = historyOutput_List[iField]; const HistoryOutputField ¤tField = historyOutput_Map.at(fieldIdentifier); if (currentField.fieldType == HistoryFieldType::RESIDUAL){ if ( SetInitResiduals(config) || (currentField.value > initialResiduals[fieldIdentifier]) ) { initialResiduals[fieldIdentifier] = currentField.value; } SetHistoryOutputValue("REL_" + fieldIdentifier, currentField.value - initialResiduals[fieldIdentifier]); Average[currentField.outputGroup].first += currentField.value; Average[currentField.outputGroup].second++; } if (currentField.fieldType == HistoryFieldType::COEFFICIENT){ if (config->GetTime_Domain()){ auto it = windowedTimeAverages.find(fieldIdentifier); if (it == windowedTimeAverages.end()) { it = windowedTimeAverages.insert({fieldIdentifier, CWindowedAverage(config->GetKindWindow())}).first; } auto& timeAverage = it->second; timeAverage.AddValue(currentField.value,config->GetTimeIter(), config->GetStartWindowIteration()); //Collecting Values for Windowing SetHistoryOutputValue("TAVG_" + fieldIdentifier, timeAverage.GetVal()); if (config->GetDirectDiff() != NO_DERIVATIVE) { SetHistoryOutputValue("D_TAVG_" + fieldIdentifier, SU2_TYPE::GetDerivative(timeAverage.GetVal())); } } if (config->GetDirectDiff() != NO_DERIVATIVE){ SetHistoryOutputValue("D_" + fieldIdentifier, SU2_TYPE::GetDerivative(currentField.value)); } } } auto it = Average.begin(); for (it = Average.begin(); it != Average.end(); it++){ const su2double& value = it->second.first; const int& count = it->second.second; const su2double average = value/count; if (historyOutput_Map.count("AVG_" + it->first) > 0 ) SetHistoryOutputValue("AVG_" + it->first, average); } } void COutput::PostprocessHistoryFields(CConfig *config){ map Average; map AverageGroupName = {{"BGS_RES", "bgs"},{"RMS_RES","rms"},{"MAX_RES", "max"}}; for (unsigned short iField = 0; iField < historyOutput_List.size(); iField++){ const string &fieldIdentifier = historyOutput_List[iField]; const HistoryOutputField ¤tField = historyOutput_Map.at(fieldIdentifier); if (currentField.fieldType == HistoryFieldType::RESIDUAL){ AddHistoryOutput("REL_" + fieldIdentifier, "rel" + currentField.fieldName, currentField.screenFormat, "REL_" + currentField.outputGroup, "Relative residual.", HistoryFieldType::AUTO_RESIDUAL); Average[currentField.outputGroup] = true; } } auto it = Average.begin(); for (it = Average.begin(); it != Average.end(); it++){ if (AverageGroupName.count(it->first) > 0) { AddHistoryOutput("AVG_" + it->first, "avg[" + AverageGroupName[it->first] + "]", ScreenOutputFormat::FIXED, "AVG_" + it->first , "Average residual over all solution variables.", HistoryFieldType::AUTO_RESIDUAL); } } if (config->GetTime_Domain()){ for (unsigned short iField = 0; iField < historyOutput_List.size(); iField++){ const string &fieldIdentifier = historyOutput_List[iField]; const HistoryOutputField ¤tField = historyOutput_Map.at(fieldIdentifier); if (currentField.fieldType == HistoryFieldType::COEFFICIENT){ AddHistoryOutput("TAVG_" + fieldIdentifier, "tavg[" + currentField.fieldName + "]", currentField.screenFormat, "TAVG_" + currentField.outputGroup, "Time averaged values.", HistoryFieldType::AUTO_COEFFICIENT); } } } if (config->GetDirectDiff()){ for (unsigned short iField = 0; iField < historyOutput_List.size(); iField++){ const string &fieldIdentifier = historyOutput_List[iField]; const HistoryOutputField ¤tField = historyOutput_Map.at(fieldIdentifier); if (currentField.fieldType == HistoryFieldType::COEFFICIENT){ AddHistoryOutput("D_" + fieldIdentifier, "d[" + currentField.fieldName + "]", currentField.screenFormat, "D_" + currentField.outputGroup, "Derivative value (DIRECT_DIFF=YES)", HistoryFieldType::AUTO_COEFFICIENT); } } } if (config->GetTime_Domain() && config->GetDirectDiff()){ for (unsigned short iField = 0; iField < historyOutput_List.size(); iField++){ const string &fieldIdentifier = historyOutput_List[iField]; const HistoryOutputField ¤tField = historyOutput_Map.at(fieldIdentifier); if (currentField.fieldType == HistoryFieldType::COEFFICIENT){ AddHistoryOutput("D_TAVG_" + fieldIdentifier, "dtavg[" + currentField.fieldName + "]", currentField.screenFormat, "D_TAVG_" + currentField.outputGroup, "Derivative of the time averaged value (DIRECT_DIFF=YES)", HistoryFieldType::AUTO_COEFFICIENT); } } } for (unsigned short iFieldConv = 0; iFieldConv < convFields.size(); iFieldConv++){ const string &convField = convFields[iFieldConv]; if (historyOutput_Map.count(convField) > 0){ if (historyOutput_Map[convField].fieldType == HistoryFieldType::COEFFICIENT){ AddHistoryOutput("CAUCHY_" + convField, "Cauchy[" + historyOutput_Map.at(convField).fieldName + "]", ScreenOutputFormat::SCIENTIFIC, "CAUCHY", "Cauchy residual value of field set with CONV_FIELD.", HistoryFieldType::AUTO_COEFFICIENT); } } } for (unsigned short iFieldConv = 0; iFieldConv < wndConvFields.size(); iFieldConv++){ const string &wndConvField = wndConvFields[iFieldConv]; if (historyOutput_Map.count(wndConvField) > 0){ AddHistoryOutput("CAUCHY_" + wndConvField, "Cauchy[" + historyOutput_Map[wndConvField].fieldName + "]", ScreenOutputFormat::SCIENTIFIC, "CAUCHY", "Cauchy residual value of field set with WND_CONV_FIELD.", HistoryFieldType::AUTO_COEFFICIENT); } } } bool COutput::WriteScreenHeader(const CConfig *config) { unsigned long RestartIter = 0; if (config->GetRestart() && config->GetTime_Domain()){ RestartIter = config->GetRestart_Iter(); } unsigned long ScreenWrt_Freq_Inner = config->GetScreen_Wrt_Freq(2); unsigned long ScreenWrt_Freq_Outer = config->GetScreen_Wrt_Freq(1); unsigned long ScreenWrt_Freq_Time = config->GetScreen_Wrt_Freq(0); /*--- Header is always disabled for multizone problems unless explicitely requested --- */ if (config->GetMultizone_Problem() && !config->GetWrt_ZoneConv()){ return false; } /*--- Always print header if it is forced ---*/ if (headerNeeded){ headerNeeded = false; return true; } /* --- Always print header in the first iteration --- */ if ((curInnerIter == 0) && (curOuterIter == 0) && (curTimeIter == RestartIter)){ return true; } if (!PrintOutput(curTimeIter, ScreenWrt_Freq_Time)&& !(curTimeIter == config->GetnTime_Iter() - 1)){ return false; } /*--- If there is no inner or outer iteration, don't print header ---*/ if (ScreenWrt_Freq_Outer == 0 && ScreenWrt_Freq_Inner == 0){ return false; } /*--- Print header if we are at the first inner iteration ---*/ if (curInnerIter == 0){ return true; } return false; } bool COutput::WriteScreenOutput(const CConfig *config) { unsigned long ScreenWrt_Freq_Inner = config->GetScreen_Wrt_Freq(2); unsigned long ScreenWrt_Freq_Outer = config->GetScreen_Wrt_Freq(1); unsigned long ScreenWrt_Freq_Time = config->GetScreen_Wrt_Freq(0); if (config->GetMultizone_Problem() && !config->GetWrt_ZoneConv()){ return false; } /*--- Check if screen output should be written --- */ if (!PrintOutput(curTimeIter, ScreenWrt_Freq_Time)&& !(curTimeIter == config->GetnTime_Iter() - 1)){ return false; } if (convergence) {return true;} if (!PrintOutput(curOuterIter, ScreenWrt_Freq_Outer) && !(curOuterIter == config->GetnOuter_Iter() - 1)){ return false; } if (!PrintOutput(curInnerIter, ScreenWrt_Freq_Inner) && !(curInnerIter == config->GetnInner_Iter() - 1)){ return false; } return true; } bool COutput::WriteHistoryFileOutput(const CConfig *config) { unsigned long HistoryWrt_Freq_Inner = config->GetHistory_Wrt_Freq(2); unsigned long HistoryWrt_Freq_Outer = config->GetHistory_Wrt_Freq(1); unsigned long HistoryWrt_Freq_Time = config->GetHistory_Wrt_Freq(0); if (config->GetMultizone_Problem() && !config->GetWrt_ZoneHist()){ return false; } /*--- Check if screen output should be written --- */ if (!PrintOutput(curTimeIter, HistoryWrt_Freq_Time)&& !(curTimeIter == config->GetnTime_Iter() - 1)){ return false; } if (convergence) {return true;} if (!PrintOutput(curOuterIter,HistoryWrt_Freq_Outer) && !(curOuterIter == config->GetnOuter_Iter() - 1)){ return false; } if (!PrintOutput(curInnerIter, HistoryWrt_Freq_Inner) && !(curInnerIter == config->GetnInner_Iter() - 1)){ return false; } return true; } bool COutput::WriteVolumeOutput(CConfig *config, unsigned long Iter, bool force_writing, unsigned short iFile){ if (config->GetTime_Domain()){ return ((Iter % config->GetVolumeOutputFrequency(iFile) == 0)) || force_writing; } return ((Iter > 0) && (Iter % config->GetVolumeOutputFrequency(iFile) == 0)) || force_writing; } void COutput::SetCommonHistoryFields() { /// BEGIN_GROUP: ITERATION, DESCRIPTION: Iteration identifier. /// DESCRIPTION: The time iteration index. AddHistoryOutput("TIME_ITER", "Time_Iter", ScreenOutputFormat::INTEGER, "ITER", "Time iteration index"); /// DESCRIPTION: The outer iteration index. AddHistoryOutput("OUTER_ITER", "Outer_Iter", ScreenOutputFormat::INTEGER, "ITER", "Outer iteration index"); /// DESCRIPTION: The inner iteration index. AddHistoryOutput("INNER_ITER", "Inner_Iter", ScreenOutputFormat::INTEGER, "ITER", "Inner iteration index"); /// END_GROUP /// BEGIN_GROUP: TIME_DOMAIN, DESCRIPTION: Time integration information /// Description: The current time AddHistoryOutput("CUR_TIME", "Cur_Time", ScreenOutputFormat::SCIENTIFIC, "TIME_DOMAIN", "Current physical time (s)"); /// Description: The current time step AddHistoryOutput("TIME_STEP", "Time_Step", ScreenOutputFormat::SCIENTIFIC, "TIME_DOMAIN", "Current time step (s)"); /// DESCRIPTION: Currently used wall-clock time. AddHistoryOutput("WALL_TIME", "Time(sec)", ScreenOutputFormat::SCIENTIFIC, "WALL_TIME", "Average wall-clock time since the start of inner iterations."); AddHistoryOutput("NONPHYSICAL_POINTS", "Nonphysical_Points", ScreenOutputFormat::INTEGER, "NONPHYSICAL_POINTS", "The number of non-physical points in the solution"); } void COutput::RequestCommonHistory(bool dynamic) { requestedHistoryFields.emplace_back("ITER"); if (dynamic) requestedHistoryFields.emplace_back("CUR_TIME"); requestedHistoryFields.emplace_back("RMS_RES"); } void COutput::SetCustomOutputs(const CConfig* config) { const auto& inputString = config->GetCustomOutputs(); if (inputString.empty()) return; /*--- Split the different functions. ---*/ auto DebugPrint = [](const std::string& str) { #ifndef NDEBUG std::cout << str << std::endl; #endif }; DebugPrint(inputString); const std::map opMap = { {"Macro", OperationType::MACRO}, {"Function", OperationType::FUNCTION}, {"AreaAvg", OperationType::AREA_AVG}, {"AreaInt", OperationType::AREA_INT}, {"MassFlowAvg", OperationType::MASSFLOW_AVG}, {"MassFlowInt", OperationType::MASSFLOW_INT}, {"Probe", OperationType::PROBE}, }; std::stringstream knownOps; for (const auto& item : opMap) knownOps << item.first << ", "; /*--- Split the input string into functions delimited by ";". ---*/ std::vector functions; const auto last = inputString.end(); for (auto it = inputString.begin(); it != last;) { /*--- Find the start of the function name. ---*/ while (it != last && (*it == ' ' || *it == ';')) ++it; if (it == last) break; /*--- Find the end of the function. ---*/ const auto start = it; while (it != last && *it != ';') ++it; functions.emplace_back(start, it); } /*--- Process each function. ---*/ size_t iFunc = 0; for (const auto& functionString : functions) { ++iFunc; DebugPrint(functionString); const auto last = functionString.end(); for (auto it = functionString.begin(); it != last;) { /*--- Find the end of the function name. ---*/ auto start = it; while (it != last && *it != ' ' && *it != ':') ++it; auto name = std::string(start, it); DebugPrint(name); /*--- Find the start and end of the operation type. ---*/ while (it != last && (*it == ' ' || *it == ':')) ++it; start = it; while (it != last && *it != ' ' && *it != '{') ++it; const auto opType = std::string(start, it); DebugPrint(opType); auto item = opMap.find(opType); if (item == opMap.end()) { SU2_MPI::Error("Invalid operation type '" + opType + "', must be one of: " + knownOps.str(), CURRENT_FUNCTION); } const auto type = item->second; /*--- Find the user expression. ---*/ while (it != last && (*it == ' ' || *it == '{')) ++it; start = it; while (it != last && *it != '}') ++it; auto func = std::string(start, it); DebugPrint(func); if (type == OperationType::MACRO) { /*--- Replace the expression in downstream functions, do not create a custom output for it. ---*/ const auto key = '$' + name; for (auto i = iFunc; i < functions.size(); ++i) { size_t pos = 0; while ((pos = functions[i].find(key)) != std::string::npos) { functions[i].replace(pos, key.length(), func); DebugPrint(functions[i]); } } break; } customOutputs.emplace_back(); auto& output = customOutputs.back(); output.name = std::move(name); output.type = type; output.func = std::move(func); output.expression = mel::Parse(output.func, output.varSymbols); #ifndef NDEBUG mel::Print(output.expression, output.varSymbols, std::cout); #endif if (type == OperationType::FUNCTION) { AddHistoryOutput(output.name, output.name, ScreenOutputFormat::SCIENTIFIC, "CUSTOM", "Custom output", HistoryFieldType::COEFFICIENT); break; } /*--- Find the marker names. ---*/ while (it != last && (*it == ' ' || *it == '}' || *it == '[')) ++it; while (it != last && *it != ']') { start = it; while (it != last && *it != ' ' && *it != ',' && *it != ']') ++it; output.markers.emplace_back(start, it); DebugPrint(output.markers.back()); while (it != last && (*it == ' ' || *it == ',')) ++it; } /*--- Skip the terminating "]". ---*/ if (it != last) ++it; AddHistoryOutput(output.name, output.name, ScreenOutputFormat::SCIENTIFIC, "CUSTOM", "Custom output", HistoryFieldType::COEFFICIENT); } } } void COutput::ComputeSimpleCustomOutputs(const CConfig *config) { const bool adjoint = config->GetDiscrete_Adjoint(); for (auto& output : customOutputs) { if (output.type != OperationType::FUNCTION) { if (adjoint) continue; SU2_MPI::Error("The current solver can only use 'Function' custom outputs.", CURRENT_FUNCTION); } if (output.varIndices.empty()) { output.varIndices.reserve(output.varSymbols.size()); output.otherOutputs.reserve(output.varSymbols.size()); for (const auto& var : output.varSymbols) { output.varIndices.push_back(output.varIndices.size()); output.otherOutputs.push_back(GetPtrToHistoryOutput(var)); if (output.otherOutputs.back() == nullptr) { if (!adjoint) { // In primal mode all functions must be valid. SU2_MPI::Error("Invalid history output (" + var + ") used in function " + output.name, CURRENT_FUNCTION); } else { if (rank == MASTER_NODE) { std::cout << "Info: Ignoring function " + output.name + " because it may be used by the primal/adjoint " "solver.\n If the function is ignored twice it is invalid." << std::endl; } output.skip = true; break; } } } } if (output.skip) continue; auto Functor = [&](unsigned long i) { return *output.otherOutputs[i]; }; SetHistoryOutputValue(output.name, output.Eval(Functor)); } } void COutput::LoadCommonHistoryData(const CConfig *config) { SetHistoryOutputValue("TIME_STEP", config->GetDelta_UnstTimeND()*config->GetTime_Ref()); /*--- Update the current time only if the time iteration has changed ---*/ if (SU2_TYPE::Int(GetHistoryFieldValue("TIME_ITER")) != static_cast(curTimeIter)) { SetHistoryOutputValue("CUR_TIME", GetHistoryFieldValue("CUR_TIME") + GetHistoryFieldValue("TIME_STEP")); } SetHistoryOutputValue("TIME_ITER", curTimeIter); SetHistoryOutputValue("INNER_ITER", curInnerIter); SetHistoryOutputValue("OUTER_ITER", curOuterIter); su2double StopTime, UsedTime; StopTime = SU2_MPI::Wtime(); UsedTime = (StopTime - config->Get_StartTime())/(curInnerIter+1); SetHistoryOutputValue("WALL_TIME", UsedTime); SetHistoryOutputValue("NONPHYSICAL_POINTS", config->GetNonphysical_Points()); } void COutput::PrintHistoryFields() const { if (rank != MASTER_NODE) return; PrintingToolbox::CTablePrinter HistoryFieldTable(&std::cout); size_t NameSize = 0, GroupSize = 0, DescrSize = 0; for (int perSurf = 0; perSurf < 2; ++perSurf) { const auto& outputList = perSurf ? historyOutputPerSurface_List : historyOutput_List; for (const auto& outputName : outputList) { const HistoryOutputField* Field = nullptr; if (!perSurf) { Field = &historyOutput_Map.at(outputName); } else { Field = historyOutputPerSurface_Map.at(outputName).data(); } if (perSurf || !Field->description.empty()) { NameSize = std::max(NameSize, outputName.size()); GroupSize = std::max(GroupSize, Field->outputGroup.size()); DescrSize = std::max(DescrSize, Field->description.size()); } } } cout << "Available screen/history output fields for the current configuration in " << multiZoneHeaderString << ":\n"; HistoryFieldTable.AddColumn("Name", NameSize); HistoryFieldTable.AddColumn("Group Name", GroupSize); HistoryFieldTable.AddColumn("Type",5); HistoryFieldTable.AddColumn("Description", DescrSize); HistoryFieldTable.SetAlign(PrintingToolbox::CTablePrinter::LEFT); HistoryFieldTable.PrintHeader(); string type; for (int perSurf = 0; perSurf < 2; ++perSurf) { const auto& outputList = perSurf ? historyOutputPerSurface_List : historyOutput_List; for (const auto& outputName : outputList) { const HistoryOutputField* Field = nullptr; if (!perSurf) { Field = &historyOutput_Map.at(outputName); } else { Field = historyOutputPerSurface_Map.at(outputName).data(); } if (!perSurf && Field->description.empty()) continue; if (Field->fieldType == HistoryFieldType::DEFAULT || Field->fieldType == HistoryFieldType::COEFFICIENT || Field->fieldType == HistoryFieldType::RESIDUAL) { switch (Field->fieldType) { case HistoryFieldType::COEFFICIENT: type = "C"; break; case HistoryFieldType::RESIDUAL: type = "R"; break; default: type = "D"; break; } HistoryFieldTable << outputName << Field->outputGroup << type << Field->description; } } } HistoryFieldTable.PrintFooter(); cout << "Type legend: Default (D), Residual (R), Coefficient (C)\n"; cout << "Generated screen/history fields (only first field of every group is shown):\n"; PrintingToolbox::CTablePrinter ModifierTable(&std::cout); ModifierTable.AddColumn("Name", NameSize); ModifierTable.AddColumn("Group Name", GroupSize); ModifierTable.AddColumn("Type",5); ModifierTable.AddColumn("Description", DescrSize); ModifierTable.SetAlign(PrintingToolbox::CTablePrinter::LEFT); ModifierTable.PrintHeader(); std::map GroupVisited; for (unsigned short iField = 0; iField < historyOutput_List.size(); iField++){ const auto& Field = historyOutput_Map.at(historyOutput_List[iField]); if ((Field.fieldType == HistoryFieldType::AUTO_COEFFICIENT || Field.fieldType == HistoryFieldType::AUTO_RESIDUAL) && (GroupVisited.count(Field.outputGroup) == 0)){ switch (Field.fieldType) { case HistoryFieldType::AUTO_COEFFICIENT: type = "AC"; break; case HistoryFieldType::AUTO_RESIDUAL: type = "AR"; break; default: type = "AD"; break; } if (!Field.description.empty()) ModifierTable << historyOutput_List[iField] << Field.outputGroup << type << Field.description; GroupVisited[Field.outputGroup] = true; } } ModifierTable.PrintFooter(); } void COutput::PrintVolumeFields(){ if (rank == MASTER_NODE){ PrintingToolbox::CTablePrinter VolumeFieldTable(&std::cout); unsigned short NameSize = 0, GroupSize = 0, DescrSize = 0; for (unsigned short iField = 0; iField < volumeOutput_List.size(); iField++){ VolumeOutputField &Field = volumeOutput_Map.at(volumeOutput_List[iField]); if (!Field.description.empty()){ if (volumeOutput_List[iField].size() > NameSize){ NameSize = volumeOutput_List[iField].size(); } if (Field.outputGroup.size() > GroupSize){ GroupSize = Field.outputGroup.size(); } if (Field.description.size() > DescrSize){ DescrSize = Field.description.size(); } } } cout << "Available volume output fields for the current configuration in " << multiZoneHeaderString << ":" << endl; cout << "Note: COORDINATES are always included, and so is SOLUTION unless you add the keyword COMPACT to the list of fields." << endl; VolumeFieldTable.AddColumn("Name", NameSize); VolumeFieldTable.AddColumn("Group Name", GroupSize); VolumeFieldTable.AddColumn("Description", DescrSize); VolumeFieldTable.SetAlign(PrintingToolbox::CTablePrinter::LEFT); VolumeFieldTable.PrintHeader(); for (unsigned short iField = 0; iField < volumeOutput_List.size(); iField++){ VolumeOutputField &Field = volumeOutput_Map.at(volumeOutput_List[iField]); if (!Field.description.empty()) VolumeFieldTable << volumeOutput_List[iField] << Field.outputGroup << Field.description; } VolumeFieldTable.PrintFooter(); } }