diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp
index bf5bbd0684cb..afbcbad0876f 100644
--- a/Common/include/CConfig.hpp
+++ b/Common/include/CConfig.hpp
@@ -747,6 +747,7 @@ class CConfig {
su2double Cauchy_Eps; /*!< \brief Epsilon used for the convergence. */
bool Restart, /*!< \brief Restart solution (for direct, adjoint, and linearized problems).*/
Wrt_Restart_Compact, /*!< \brief Write compact restart files with minimum nr. of variables. */
+ Wrt_Output_Double_Precision, /*!< \brief Write the fields of the volume and surface files in double. */
Read_Binary_Restart, /*!< \brief Read binary SU2 native restart files.*/
Wrt_Restart_Overwrite, /*!< \brief Overwrite restart files or append iteration number.*/
Wrt_Surface_Overwrite, /*!< \brief Overwrite surface output files or append iteration number.*/
@@ -5682,6 +5683,12 @@ class CConfig {
*/
bool GetWrt_Restart_Compact(void) const { return Wrt_Restart_Compact; }
+ /*!
+ * \brief Flag for whether the fields of the volume and surface files are written in double precision.
+ * \return TRUE means that double precision is used.
+ */
+ bool GetWrt_Output_Double_Precision(void) const { return Wrt_Output_Double_Precision; }
+
/*!
* \brief Flag for whether restart solution files are overwritten.
* \return Flag for overwriting. If Flag=false, iteration nr is appended to filename
diff --git a/Common/include/parallelization/mpi_structure.hpp b/Common/include/parallelization/mpi_structure.hpp
index 242aa00d7b0f..5768eab9a1a8 100644
--- a/Common/include/parallelization/mpi_structure.hpp
+++ b/Common/include/parallelization/mpi_structure.hpp
@@ -347,6 +347,8 @@ class CMediMPIWrapper : public CBaseMPIWrapper {
return medi::AMPI_MIN;
} else if (MPI_MAX == op) {
return medi::AMPI_MAX;
+ } else if (MPI_BOR == op) {
+ return medi::AMPI_BOR;
} else {
Error("Conversion not implemented", CURRENT_FUNCTION);
return medi::AMPI_SUM;
@@ -488,6 +490,7 @@ class CMediMPIWrapper : public CBaseMPIWrapper {
#define MPI_MAX 10
#define MPI_INT 11
#define MPI_PROD 12
+#define MPI_BOR 13
#define MPI_STATUS_IGNORE nullptr
/*!
diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp
index fd4207b0c5fe..8608b15b995a 100644
--- a/Common/src/CConfig.cpp
+++ b/Common/src/CConfig.cpp
@@ -1263,6 +1263,10 @@ void CConfig::SetConfig_Options() {
addBoolOption("RESTART_SOL", Restart, false);
/*!\brief WRT_RESTART_COMPACT \n DESCRIPTION: Minimize the size of restart files \n Options: NO, YES \ingroup Config */
addBoolOption("WRT_RESTART_COMPACT", Wrt_Restart_Compact, true);
+ /*!\brief WRT_OUTPUT_DOUBLE_PRECISION \n DESCRIPTION: Write the fields of the volume and surface files (CGNS and
+ Paraview XML) in double precision instead of single, the coordinates are always written in double precision by the
+ CGNS writer. \n Options: NO, YES \ingroup Config */
+ addBoolOption("WRT_OUTPUT_DOUBLE_PRECISION", Wrt_Output_Double_Precision, false);
/*!\brief BINARY_RESTART \n DESCRIPTION: Read binary SU2 native restart files. \n Options: YES, NO \ingroup Config */
addBoolOption("READ_BINARY_RESTART", Read_Binary_Restart, true);
/*!\brief WRT_RESTART_OVERWRITE \n DESCRIPTION: overwrite restart files or append iteration number. \n Options: YES, NO \ingroup Config */
diff --git a/SU2_CFD/include/output/filewriter/CCGNSFileWriter.hpp b/SU2_CFD/include/output/filewriter/CCGNSFileWriter.hpp
index 29dede06f626..c9baa088285c 100644
--- a/SU2_CFD/include/output/filewriter/CCGNSFileWriter.hpp
+++ b/SU2_CFD/include/output/filewriter/CCGNSFileWriter.hpp
@@ -32,13 +32,38 @@
#include
#endif
#include "cgnslib.h"
+#ifdef HAVE_MPI
+#include "pcgnslib.h"
#endif
+#endif
+
+#include
+#include
#include "CFileWriter.hpp"
+class CConfig;
+class CGeometry;
+class CFVMDataSorter;
+
class CCGNSFileWriter final : public CFileWriter {
private:
- const bool isSurface; /*!< \brief True if surface file. */
+ const bool isSurface; /*!< \brief True if surface file. */
+ const bool doublePrecisionFields; /*!< \brief True to write the fields in double precision instead of single. */
+
+ /*!
+ * \brief Boundary elements of one marker owned by this rank, written as a boundary section of a volume file.
+ */
+ struct BoundaryMarker {
+ string name; /*!< \brief Marker tag. */
+ unsigned short kindBC; /*!< \brief SU2 boundary condition kind. */
+ vector conn; /*!< \brief VTK type followed by the (1-based) output ids of the nodes, per element. */
+ };
+ vector boundaryMarkers; /*!< \brief Markers written as boundaries of a volume file. */
+
+ vector surfaceMarkers; /*!< \brief Markers written as one zone each in a surface file. */
+ CConfig* config = nullptr; /*!< \brief Config, to sort the surface data of each marker. */
+ CGeometry* geometry = nullptr; /*!< \brief Geometry, to sort the surface data of each marker. */
#ifdef HAVE_CGNS
int cgnsFileID; /*!< \brief CGNS file identifier. */
@@ -54,15 +79,13 @@ class CCGNSFileWriter final : public CFileWriter {
cgsize_t GlobalPoint; /*!< \brief Total number of points. */
cgsize_t GlobalElem; /*!< \brief Total number of elements. */
- typedef float dataPrecision; /*!< \brief Define data precision of output (float or double). */
- const DataType_t dataType = RealSingle; /*!< \brief Datatype of fields can be RealSingle or RealDouble. */
+ cgsize_t cumulative; /*!< \brief Cumulative number of elements written. */
- vector sendBufferConnectivity; /*!< \brief Send buffer for connectivity data. */
- vector recvBufferConnectivity; /*!< \brief Receive buffer for connectivity data. */
- vector recvBufferField; /*!< \brief Send buffer for field data. */
- vector sendBufferField; /*!< \brief Receive buffer for field data. */
+ /*--- Max connectivity entries per section, so that readers using 32-bit sizes can read it. ---*/
+ static constexpr cgsize_t maxSectionEntries = std::numeric_limits::max();
- cgsize_t cumulative; /*!< \brief Cumulative number of elements written. */
+ /*--- Max length of the names of the CGNS nodes (zones, sections, BCs, families). ---*/
+ static constexpr size_t maxNameLength = 32;
#endif
public:
/*!
@@ -75,7 +98,7 @@ class CCGNSFileWriter final : public CFileWriter {
* \param[in] valDataSorter - The parallel sorted data to write.
* \param[in] isSurf - True if it is a surface file.
*/
- CCGNSFileWriter(CParallelDataSorter* valDataSorter, bool isSurf = false);
+ CCGNSFileWriter(CParallelDataSorter* valDataSorter, bool isSurf = false, bool doublePrecision = false);
/*!
* \brief Write sorted data to file in CGNS file format.
@@ -83,20 +106,72 @@ class CCGNSFileWriter final : public CFileWriter {
*/
void WriteData(string val_filename) override ;
+ /*!
+ * \brief Add the boundaries to a volume file: one boundary section, BC and family per marker, named as the marker.
+ * \param[in] valConfig - Definition of the problem.
+ * \param[in] valGeometry - Geometrical definition of the problem.
+ * \param[in] volumeSorter - The volume data sorter, to find the boundary elements owned by this rank.
+ */
+ void SetBoundaryMarkers(const CConfig* valConfig, const CGeometry* valGeometry, const CFVMDataSorter* volumeSorter);
+
+ /*!
+ * \brief Write a surface file with one zone per plotted marker, named as the marker. The data of the surface
+ * sorter is sorted again for each marker when the file is written.
+ * \param[in] valConfig - Definition of the problem.
+ * \param[in] valGeometry - Geometrical definition of the problem.
+ */
+ void SetSurfaceMarkers(CConfig* valConfig, CGeometry* valGeometry);
+
private:
#ifdef HAVE_CGNS
/*!
- * \brief Initialize CGNS mesh file.
+ * \brief Create the CGNS file and its base.
+ * \param[in] val_filename - The name of the file.
*/
void InitializeMeshFile(const string& val_filename);
/*!
- * \brief Write i-th coordinate to file in CGNS file format.
+ * \brief Write a zone with the data currently held by the data sorter.
+ * \param[in] zoneName - Name of the zone.
+ */
+ void WriteZone(const string& zoneName);
+
+ /*!
+ * \brief Create a zone for the data currently held by the data sorter.
+ * \param[in] zoneName - Name of the zone, at most maxNameLength characters.
+ */
+ void InitializeZone(const string& zoneName);
+
+ /*!
+ * \brief Get the names of CGNS nodes named as the markers. Tags longer than maxNameLength characters are truncated,
+ * and a number is appended to a truncated tag equal to a previous name, so that the names are unique.
+ * \param[in] tags - Marker tags.
+ * \return The names, in the order of the tags.
+ */
+ vector GetUniqueNames(const vector& tags) const;
+
+ /*!
+ * \brief Write the boundary sections, BCs and families of the markers set with SetBoundaryMarkers.
+ */
+ void WriteBoundaries();
+
+ /*!
+ * \brief Write i-th coordinate to file in CGNS file format. Coordinates are always written in double precision,
+ * the fields follow the precision requested by the user.
* \param[in] iField - the output field ID.
* \param[in] FieldName - Field name in the CGNS.
*/
void WriteField(int iField, const string& FieldName);
+ /*!
+ * \brief Write i-th coordinate or field to file with the given data type.
+ * \param[in] iField - the output field ID.
+ * \param[in] FieldName - Field name in the CGNS.
+ * \param[in] dataType - CGNS data type matching T, i.e. RealSingle for float and RealDouble for double.
+ */
+ template
+ void WriteFieldOfType(int iField, const string& FieldName, DataType_t dataType);
+
/*!
* \brief Write connectivity to file for GEO_TYPE in CGNS file format.
* \param[in] type - GEO_TYPE.
@@ -109,6 +184,27 @@ class CCGNSFileWriter final : public CFileWriter {
*/
void InitializeFields();
+ /*!
+ * \brief Create an element section, collectively when built with MPI.
+ * \param[in] name - Name of the section.
+ * \param[in] type - CGNS element type of the section.
+ * \param[in] start - First element of the section.
+ * \param[in] end - Last element of the section.
+ * \param[out] section - Index of the section.
+ * \returns CGNS error code.
+ */
+ int SectionWrite(const string& name, ElementType_t type, cgsize_t start, cgsize_t end, int* section);
+
+ /*!
+ * \brief Write the connectivity of the elements [start, end] of a section, the elements of this rank.
+ * \param[in] section - Index of the section.
+ * \param[in] start - First element written by this rank.
+ * \param[in] end - Last element written by this rank.
+ * \param[in] elements - Connectivity, nullptr if this rank writes no element of the section.
+ * \returns CGNS error code.
+ */
+ int ElementsWriteData(int section, cgsize_t start, cgsize_t end, const cgsize_t* elements);
+
/*!
* \brief Call a generic CGNS function.
* \param[in] ier - error value.
@@ -117,6 +213,12 @@ class CCGNSFileWriter final : public CFileWriter {
if (ier) cg_error_exit();
}
+ /*!
+ * \brief Return the CGNS boundary condition type of an SU2 boundary condition kind.
+ * \param[in] kindBC - SU2 boundary condition kind.
+ */
+ static BCType_t GetCGNSBCType(unsigned short kindBC);
+
/*!
* \brief Return the CGNS element type (ElementType_t).
* \param[in] elementType - GEO_TYPE.
diff --git a/SU2_CFD/include/output/filewriter/CFileWriter.hpp b/SU2_CFD/include/output/filewriter/CFileWriter.hpp
index 655ad787ffe3..68daec23ffa5 100644
--- a/SU2_CFD/include/output/filewriter/CFileWriter.hpp
+++ b/SU2_CFD/include/output/filewriter/CFileWriter.hpp
@@ -33,6 +33,7 @@
#include
#include
#include
+#include
#include "../../output/filewriter/CParallelDataSorter.hpp"
@@ -168,12 +169,27 @@ class CFileWriter{
*/
bool WriteMPIString(const std::string& str, unsigned short processor);
+ /*!
+ * \brief Get the position of the data of this rank in an array that holds the data of all ranks in rank order.
+ * \note Collective call, all ranks must call it.
+ * \param[in] localCount - The size of the data of this rank.
+ * \return The offset of this rank (the size of the data of the ranks before it) and the total size over all ranks.
+ */
+ std::pair GetRankOffset(unsigned long localCount) const;
+
+ /*!
+ * \brief Write a string of each rank to the file, one after the other in rank order.
+ * \param[in] str - The string of this rank.
+ * \return TRUE if the writing was successful.
+ */
+ bool WriteMPIStringAll(const std::string& str);
+
/*!
* \brief Open a file to write using MPI I/O. Already existing file is deleted.
* \param[in] val_filename - The name of the file
* \return Boolean indicating whether the opening was successful.
*/
- bool OpenMPIFile(string val_filename);
+ bool OpenMPIFile(string val_filename, bool append = false);
/*!
* \brief Close a file using MPI I/O.
diff --git a/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp b/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp
index 9f2b0e52031f..78c716f06b2c 100644
--- a/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp
+++ b/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp
@@ -99,8 +99,8 @@ class CParallelDataSorter{
int *nPoint_Recv; //!< Number of points this processor receives from other processors
int *nElem_Send; //!< Number of elements this processor has to send to other processors
int *nElem_Cum; //!< Cumulative number of elements
- int *nElemConn_Send; //!< Number of element connectivity this processor has to send to other processors
- int *nElemConn_Cum; //!< Cumulative number of element connectivity entries
+ unsigned long *nElemConn_Send; //!< Number of element connectivity this processor has to send to other processors
+ unsigned long *nElemConn_Cum; //!< Cumulative number of element connectivity entries
unsigned long *Index; //!< Index each point has in the send buffer
passivedouble *connSend; //!< Send buffer holding the data that will be send to other processors
passivedouble *dataBuffer; //!< Buffer holding the sorted, partitioned data as passivedouble types
diff --git a/SU2_CFD/include/output/filewriter/CParaviewVTMFileWriter.hpp b/SU2_CFD/include/output/filewriter/CParaviewVTMFileWriter.hpp
index ee3b7c601a9d..851c7b1fdeee 100644
--- a/SU2_CFD/include/output/filewriter/CParaviewVTMFileWriter.hpp
+++ b/SU2_CFD/include/output/filewriter/CParaviewVTMFileWriter.hpp
@@ -124,7 +124,8 @@ class CParaviewVTMFileWriter final: public CFileWriter{
* \param[in] dataSorter - Datasorter object containing the actual data. Note, data must be sorted.
*/
//void AddDataset(string name, string file, CParallelDataSorter* dataSorter);
- void AddDataset(const string& foldername, string name, const string& file, CParallelDataSorter* dataSorter);
+ void AddDataset(const string& foldername, string name, const string& file, CParallelDataSorter* dataSorter,
+ bool doublePrecision);
/*!
* \brief Start a new block
diff --git a/SU2_CFD/include/output/filewriter/CParaviewXMLFileWriter.hpp b/SU2_CFD/include/output/filewriter/CParaviewXMLFileWriter.hpp
index 7862c26f89cc..f486857439ba 100644
--- a/SU2_CFD/include/output/filewriter/CParaviewXMLFileWriter.hpp
+++ b/SU2_CFD/include/output/filewriter/CParaviewXMLFileWriter.hpp
@@ -38,7 +38,9 @@ class CParaviewXMLFileWriter final: public CFileWriter{
*/
enum class VTKDatatype {
FLOAT32,
+ FLOAT64,
INT32,
+ INT64,
UINT8
};
@@ -47,6 +49,11 @@ class CParaviewXMLFileWriter final: public CFileWriter{
*/
bool bigEndian;
+ /*!
+ * \brief True to write the coordinates and fields in double precision instead of single.
+ */
+ bool doublePrecision = false;
+
/*!
* \brief The current data offset that is used to find data in the binary blob at the end of the file
*/
@@ -70,7 +77,7 @@ class CParaviewXMLFileWriter final: public CFileWriter{
* \brief Construct a file writer using field names and the data sorter.
* \param[in] valDataSorter - The parallel sorted data to write
*/
- CParaviewXMLFileWriter(CParallelDataSorter* valDataSorter);
+ CParaviewXMLFileWriter(CParallelDataSorter* valDataSorter, bool valDoublePrecision = false);
/*!
* \brief Destructor
@@ -102,7 +109,21 @@ class CParaviewXMLFileWriter final: public CFileWriter{
* \param[in] globalSize - The global size of the array over all processors
* \param[in] offset - The displacement in the file view for the current processor
*/
- void WriteDataArray(void *data, VTKDatatype type, unsigned long size, unsigned long globalSize, unsigned long offset);
+ void WriteDataArray(const void* data, VTKDatatype type, unsigned long size, unsigned long globalSize,
+ unsigned long offset);
+
+ /*!
+ * \brief Write the first values of a buffer as an array of type T with ::WriteDataArray, the values are converted
+ * if the buffer holds another type (e.g. double written as float, int64_t written as int32_t).
+ * \param[in] buffer - The data of this processor.
+ * \param[in] type - The vtk datatype, matching T.
+ * \param[in] size - The number of values of this processor to write.
+ * \param[in] globalSize - The global size of the array over all processors
+ * \param[in] offset - The displacement in the file view for the current processor
+ */
+ template
+ void WriteDataArrayOfType(const vector& buffer, VTKDatatype type, unsigned long size, unsigned long globalSize,
+ unsigned long offset);
/*!
* \brief Get the type string and size of a VTK datatype
@@ -112,13 +133,21 @@ class CParaviewXMLFileWriter final: public CFileWriter{
*/
inline void GetTypeInfo(const VTKDatatype type, string &typeStr, unsigned long &typeSize) const {
switch (type) {
+ case VTKDatatype::FLOAT64:
+ typeStr = "\"Float64\"";
+ typeSize = sizeof(double);
+ break;
case VTKDatatype::FLOAT32:
typeStr = "\"Float32\"";
typeSize = sizeof(float);
break;
case VTKDatatype::INT32:
typeStr = "\"Int32\"";
- typeSize = sizeof(int);
+ typeSize = sizeof(int32_t);
+ break;
+ case VTKDatatype::INT64:
+ typeStr = "\"Int64\"";
+ typeSize = sizeof(int64_t);
break;
case VTKDatatype::UINT8:
typeStr = "\"UInt8\"";
diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp
index a376483cee59..f503128a6727 100644
--- a/SU2_CFD/src/output/COutput.cpp
+++ b/SU2_CFD/src/output/COutput.cpp
@@ -556,7 +556,7 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form
volumeDataSorter->SortConnectivity(config, geometry, true);
LogOutputFiles("Paraview");
- fileWriter = new CParaviewXMLFileWriter(volumeDataSorter);
+ fileWriter = new CParaviewXMLFileWriter(volumeDataSorter, config->GetWrt_Output_Double_Precision());
break;
@@ -594,10 +594,9 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form
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);
+ auto* vtmWriter =
+ new CParaviewVTMFileWriter(GetHistoryFieldValue("CUR_TIME"), config->GetiZone(), config->GetnZone());
+ fileWriter = vtmWriter;
/*--- then we write the data into the folder---*/
vtmWriter->WriteFolderData(fileName, config, multiZoneHeaderString, volumeDataSorter, surfaceDataSorter, geometry);
@@ -683,7 +682,7 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form
surfaceDataSorter->SortOutputData();
LogOutputFiles("Paraview surface");
- fileWriter = new CParaviewXMLFileWriter(surfaceDataSorter);
+ fileWriter = new CParaviewXMLFileWriter(surfaceDataSorter, config->GetWrt_Output_Double_Precision());
break;
@@ -760,8 +759,15 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form
volumeDataSorter->SortConnectivity(config, geometry, true);
LogOutputFiles("CGNS");
- fileWriter = new CCGNSFileWriter(volumeDataSorter);
+ {
+ auto* cgnsWriter = new CCGNSFileWriter(volumeDataSorter, false, config->GetWrt_Output_Double_Precision());
+ /*--- Add the boundaries, named as the markers (the finite volume sorter knows which elements are halos). ---*/
+ if (const auto* fvmSorter = dynamic_cast(volumeDataSorter))
+ cgnsWriter->SetBoundaryMarkers(config, geometry, fvmSorter);
+
+ fileWriter = cgnsWriter;
+ }
break;
case OUTPUT_TYPE::SURFACE_CGNS:
@@ -779,8 +785,14 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form
surfaceDataSorter->SortOutputData();
LogOutputFiles("CGNS surface");
- fileWriter = new CCGNSFileWriter(surfaceDataSorter, true);
+ {
+ auto* cgnsWriter = new CCGNSFileWriter(surfaceDataSorter, true, config->GetWrt_Output_Double_Precision());
+
+ /*--- One zone per plotted marker, named as the marker. ---*/
+ cgnsWriter->SetSurfaceMarkers(config, geometry);
+ fileWriter = cgnsWriter;
+ }
break;
default:
diff --git a/SU2_CFD/src/output/filewriter/CCGNSFileWriter.cpp b/SU2_CFD/src/output/filewriter/CCGNSFileWriter.cpp
index 749abc74d201..f77f932d471e 100644
--- a/SU2_CFD/src/output/filewriter/CCGNSFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CCGNSFileWriter.cpp
@@ -26,11 +26,17 @@
*/
#include "../../../include/output/filewriter/CCGNSFileWriter.hpp"
+#include "../../../include/output/filewriter/CFVMDataSorter.hpp"
+#include "../../../../Common/include/CConfig.hpp"
+#include "../../../../Common/include/geometry/CGeometry.hpp"
+
+#include
+#include
const string CCGNSFileWriter::fileExt = ".cgns";
-CCGNSFileWriter::CCGNSFileWriter(CParallelDataSorter* valDataSorter, bool isSurf)
- : CFileWriter(valDataSorter, fileExt), isSurface(isSurf) {}
+CCGNSFileWriter::CCGNSFileWriter(CParallelDataSorter* valDataSorter, bool isSurf, bool doublePrecision)
+ : CFileWriter(valDataSorter, fileExt), isSurface(isSurf), doublePrecisionFields(doublePrecision) {}
void CCGNSFileWriter::WriteData(string val_filename) {
@@ -38,9 +44,102 @@ void CCGNSFileWriter::WriteData(string val_filename) {
/*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/
val_filename.append(fileExt);
+
+ /*--- Set a timer for the file writing. ---*/
+ startTime = SU2_MPI::Wtime();
+
/*--- Open the CGNS file for writing. ---*/
InitializeMeshFile(val_filename);
+ if (surfaceMarkers.empty()) {
+ WriteZone("Zone");
+ } else {
+ /*--- One zone per marker, the surface data is sorted again for each of them. The zones are named as the
+ markers, with names made unique within the 32 characters of CGNS. ---*/
+ const auto zoneNames = GetUniqueNames(surfaceMarkers);
+ for (size_t iMarker = 0; iMarker < surfaceMarkers.size(); ++iMarker) {
+ dataSorter->SortConnectivity(config, geometry, vector{surfaceMarkers[iMarker]});
+ dataSorter->SortOutputData();
+ WriteZone(zoneNames[iMarker]);
+ }
+ }
+
+ /*--- Close the CGNS file. ---*/
+#ifdef HAVE_MPI
+ CallCGNS(cgp_close(cgnsFileID));
+#else
+ CallCGNS(cg_close(cgnsFileID));
+#endif
+
+ /*--- Compute and store the write time and the bandwidth. ---*/
+ stopTime = SU2_MPI::Wtime();
+ usedTime = stopTime - startTime;
+ fileSize = DetermineFilesize(val_filename);
+ bandwidth = fileSize / (1.0e6) / usedTime;
+
+#endif
+}
+
+void CCGNSFileWriter::SetBoundaryMarkers(const CConfig* valConfig, const CGeometry* valGeometry,
+ const CFVMDataSorter* volumeSorter) {
+ boundaryMarkers.clear();
+
+ for (unsigned short iMarkerCfg = 0; iMarkerCfg < valConfig->GetnMarker_CfgFile(); iMarkerCfg++) {
+ const string tag = valConfig->GetMarker_CfgFile_TagBound(iMarkerCfg);
+ const auto kindBC = valConfig->GetMarker_CfgFile_KindBC(tag);
+ if (kindBC == SEND_RECEIVE) continue;
+
+ BoundaryMarker marker{tag, kindBC, {}};
+
+ for (unsigned short iMarker = 0; iMarker < valConfig->GetnMarker_All(); iMarker++) {
+ if (valConfig->GetMarker_All_TagBound(iMarker) != tag) continue;
+
+ for (unsigned long iElem = 0; iElem < valGeometry->GetnElem_Bound(iMarker); iElem++) {
+ const auto* elem = valGeometry->bound[iMarker][iElem];
+
+ /*--- Same rule as for the volume elements: keep the element on the rank where none of its nodes is a halo.
+ Also require a node owned by this rank, so that an element whose nodes are all owned by lower ranks is not
+ kept again by a higher rank that holds it as a halo element. ---*/
+ bool halo = false, owned = false;
+ for (unsigned short iNode = 0; iNode < elem->GetnNodes(); iNode++) {
+ halo |= volumeSorter->GetHalo(elem->GetNode(iNode));
+ owned |= valGeometry->nodes->GetDomain(elem->GetNode(iNode));
+ }
+ if (halo || !owned) continue;
+
+ marker.conn.push_back(elem->GetVTK_Type());
+ for (unsigned short iNode = 0; iNode < elem->GetnNodes(); iNode++)
+ marker.conn.push_back(valGeometry->nodes->GetGlobalIndex(elem->GetNode(iNode)) + 1);
+ }
+ }
+ boundaryMarkers.push_back(std::move(marker));
+ }
+}
+
+void CCGNSFileWriter::SetSurfaceMarkers(CConfig* valConfig, CGeometry* valGeometry) {
+ config = valConfig;
+ geometry = valGeometry;
+ surfaceMarkers.clear();
+
+ for (unsigned short iMarkerCfg = 0; iMarkerCfg < valConfig->GetnMarker_CfgFile(); iMarkerCfg++) {
+ const string tag = valConfig->GetMarker_CfgFile_TagBound(iMarkerCfg);
+ if (valConfig->GetMarker_CfgFile_Plotting(tag) != YES) continue;
+
+ /*--- Only keep the markers present on at least one rank. ---*/
+ int localFound = 0, globalFound = 0;
+ for (unsigned short iMarker = 0; iMarker < valConfig->GetnMarker_All(); iMarker++) {
+ if (valConfig->GetMarker_All_TagBound(iMarker) == tag && valConfig->GetMarker_All_KindBC(iMarker) != SEND_RECEIVE)
+ localFound = 1;
+ }
+ SU2_MPI::Allreduce(&localFound, &globalFound, 1, MPI_INT, MPI_SUM, SU2_MPI::GetComm());
+ if (globalFound > 0) surfaceMarkers.push_back(tag);
+ }
+}
+
+#ifdef HAVE_CGNS
+void CCGNSFileWriter::WriteZone(const string& zoneName) {
+ InitializeZone(zoneName);
+
/*--- Write point coordinates. ---*/
WriteField(0, "CoordinateX");
WriteField(1, "CoordinateY");
@@ -61,6 +160,9 @@ void CCGNSFileWriter::WriteData(string val_filename) {
WriteConnectivity(HEXAHEDRON, "Hexahedra");
}
+ /*--- Write the boundaries of a volume file. ---*/
+ if (!isSurface) WriteBoundaries();
+
/*--- Initialize and write fields. ---*/
InitializeFields();
@@ -68,102 +170,257 @@ void CCGNSFileWriter::WriteData(string val_filename) {
for (unsigned long i = nDim; i < fieldNames.size(); ++i) {
WriteField(i, fieldNames[i]);
}
+}
- /*--- Close the CGNS file. ---*/
- if (rank == MASTER_NODE) CallCGNS(cg_close(cgnsFileID));
+void CCGNSFileWriter::InitializeMeshFile(const string& val_filename) {
+ nDim = dataSorter->GetnDim();
+ /*--- If surface file cell dimension is decreased. ---*/
+ const auto nCell = static_cast(nDim - isSurface);
+
+ /*--- Remove the previous file if present, before any rank opens it. ---*/
+ if (rank == MASTER_NODE) remove(val_filename.c_str());
+
+#ifdef HAVE_MPI
+ /*--- All ranks open the file and write their own part of the data with the parallel CGNS API. The nodes of the
+ file (base, zone, sections, solution, fields, boundary conditions) are metadata and must be created by all
+ ranks with the same arguments, only the data itself is written per rank. ---*/
+
+ SU2_MPI::Barrier(SU2_MPI::GetComm());
+ CallCGNS(cgp_mpi_comm(SU2_MPI::GetComm()));
+ CallCGNS(cgp_pio_mode(CGP_COLLECTIVE));
+ CallCGNS(cgp_open(val_filename.c_str(), CG_MODE_WRITE, &cgnsFileID));
+#else
+ CallCGNS(cg_open(val_filename.c_str(), CG_MODE_WRITE, &cgnsFileID));
#endif
+
+ /*--- Create Base. ---*/
+ CallCGNS(cg_base_write(cgnsFileID, "Base", nCell, nDim, &cgnsBase));
}
-#ifdef HAVE_CGNS
-void CCGNSFileWriter::InitializeMeshFile(const string& val_filename) {
+void CCGNSFileWriter::InitializeZone(const string& zoneName) {
if (!dataSorter->GetConnectivitySorted()) {
SU2_MPI::Error("Connectivity must be sorted.", CURRENT_FUNCTION);
}
nLocalPoints = dataSorter->GetnPoints();
- nDim = dataSorter->GetnDim();
GlobalElem = static_cast(dataSorter->GetnElemGlobal());
GlobalPoint = static_cast(dataSorter->GetnPointsGlobal());
cumulative = 0;
- /*--- If surface file cell dimension is decreased. ---*/
- const auto nCell = static_cast(nDim - isSurface);
+ /*--- Create Zone. The number of cells does not include the boundary elements. ---*/
+ array zoneData;
+
+ zoneData[0] = GlobalPoint;
+ zoneData[1] = GlobalElem;
+ zoneData[2] = 0;
+
+ CallCGNS(cg_zone_write(cgnsFileID, cgnsBase, zoneName.c_str(), zoneData.data(), Unstructured, &cgnsZone));
+}
+
+vector CCGNSFileWriter::GetUniqueNames(const vector& tags) const {
+ /*--- CGNS names have at most 32 characters, longer tags are truncated. If two truncated tags are equal, a number is
+ appended to make the names unique. ---*/
+
+ vector names;
+ for (const auto& tag : tags) {
+ string name = tag.substr(0, maxNameLength);
+ for (unsigned long n = 1; std::find(names.begin(), names.end(), name) != names.end(); n++) {
+ const string suffix = "_" + to_string(n);
+ name = tag.substr(0, maxNameLength - suffix.size()) + suffix;
+ }
+ if (rank == MASTER_NODE && name != tag.substr(0, maxNameLength)) {
+ cout << "CGNS output: the marker " << tag << " is written as " << name << " (names have at most " << maxNameLength
+ << " characters)." << endl;
+ }
+ names.push_back(name);
+ }
+ return names;
+}
+
+void CCGNSFileWriter::WriteBoundaries() {
+ /*--- The sections, BCs and families are named as the markers. ---*/
+
+ vector tags;
+ for (const auto& marker : boundaryMarkers) tags.push_back(marker.name);
+ const auto names = GetUniqueNames(tags);
+
+ for (size_t iMarker = 0; iMarker < boundaryMarkers.size(); ++iMarker) {
+ const auto& marker = boundaryMarkers[iMarker];
+ /*--- Count the elements of this rank and collect the element types it holds. The local connectivity holds the
+ VTK type of each element followed by the ids of its nodes. ---*/
+
+ unsigned long nLocalElem = 0, typesMask = 0;
+ for (size_t pos = 0; pos < marker.conn.size();) {
+ const auto type = static_cast(marker.conn[pos]);
+ typesMask |= 1ul << type;
+ nLocalElem++;
+ pos += nPointsOfElementType(type) + 1;
+ }
+ const unsigned long nLocalEntries = marker.conn.size() - nLocalElem;
+
+ /*--- Offsets and totals of the elements and node ids of each rank, which are written as a contiguous range. ---*/
+
+ unsigned long elemOffset, nTotElem, entryOffset, nTotNodeEntries;
+ std::tie(elemOffset, nTotElem) = GetRankOffset(nLocalElem);
+ std::tie(entryOffset, nTotNodeEntries) = GetRankOffset(nLocalEntries);
+
+ if (nTotElem == 0) continue;
+
+ /*--- A marker with a single element type is written as a section of that type, one with several types
+ (e.g. triangles and quadrilaterals) as a MIXED section. ---*/
+
+ unsigned long globalTypesMask = 0;
+ SU2_MPI::Allreduce(&typesMask, &globalTypesMask, 1, MPI_UNSIGNED_LONG, MPI_BOR, SU2_MPI::GetComm());
+ const bool singleType = (globalTypesMask & (globalTypesMask - 1)) == 0;
- if (rank == MASTER_NODE) {
- /*--- Remove the previous file if present. ---*/
- remove(val_filename.c_str());
+ const string& name = names[iMarker];
+ const cgsize_t range[2] = {cumulative + 1, cumulative + static_cast(nTotElem)};
+ const cgsize_t first = range[0] + static_cast(elemOffset);
+ const cgsize_t last = first + static_cast(nLocalElem) - 1;
+ int section;
- /*--- Create CGNS file and open in write mode. ---*/
- CallCGNS(cg_open(val_filename.c_str(), CG_MODE_WRITE, &cgnsFileID));
+ if (singleType) {
+ /*--- Only the ids of the nodes are stored, the type is that of the section. ---*/
- /*--- Create Base. ---*/
- CallCGNS(cg_base_write(cgnsFileID, "Base", nCell, nDim, &cgnsBase));
+ vector elems;
+ elems.reserve(nLocalEntries);
+ for (size_t pos = 0; pos < marker.conn.size();) {
+ const auto nNodes = nPointsOfElementType(static_cast(marker.conn[pos]));
+ for (unsigned short iNode = 1; iNode <= nNodes; ++iNode)
+ elems.push_back(static_cast(marker.conn[pos + iNode]));
+ pos += nNodes + 1;
+ }
- /*--- Create Zone. ---*/
- array zoneData;
+ unsigned short type = 0;
+ while ((globalTypesMask >> type) != 1) type++;
- zoneData[0] = GlobalPoint;
- zoneData[1] = GlobalElem;
- zoneData[2] = 0;
+ CallCGNS(SectionWrite(name, GetCGNSType(type), range[0], range[1], §ion));
+ CallCGNS(ElementsWriteData(section, first, last, nLocalElem > 0 ? elems.data() : nullptr));
- CallCGNS(cg_zone_write(cgnsFileID, cgnsBase, "Zone", zoneData.data(), Unstructured, &cgnsZone));
+ } else {
+ /*--- The CGNS element type of each element is stored before the ids of its nodes, and the start offset of
+ each element in the connectivity array is stored in a second array. ---*/
+
+ const auto nTotEntries = nTotNodeEntries + nTotElem;
+
+ vector elems, offsets{static_cast(entryOffset + elemOffset)};
+ elems.reserve(marker.conn.size());
+ for (size_t pos = 0; pos < marker.conn.size();) {
+ const auto type = static_cast(marker.conn[pos]);
+ const auto nNodes = nPointsOfElementType(type);
+ elems.push_back(GetCGNSType(type));
+ for (unsigned short iNode = 1; iNode <= nNodes; ++iNode)
+ elems.push_back(static_cast(marker.conn[pos + iNode]));
+ offsets.push_back(offsets.back() + nNodes + 1);
+ pos += nNodes + 1;
+ }
+
+#ifdef HAVE_MPI
+ CallCGNS(cgp_poly_section_write(cgnsFileID, cgnsBase, cgnsZone, name.c_str(), MIXED, range[0], range[1],
+ static_cast(nTotEntries), 0, §ion));
+ CallCGNS(cgp_poly_elements_write_data(cgnsFileID, cgnsBase, cgnsZone, section, first, last,
+ nLocalElem > 0 ? elems.data() : nullptr,
+ nLocalElem > 0 ? offsets.data() : nullptr));
+#else
+ CallCGNS(cg_poly_section_write(cgnsFileID, cgnsBase, cgnsZone, name.c_str(), MIXED, range[0], range[1], 0,
+ elems.data(), offsets.data(), §ion));
+#endif
+ }
+ cumulative += static_cast(nTotElem);
+
+ /*--- The BC points to the boundary elements and takes its type from a family with the name of the marker.
+ These are metadata nodes, written by all ranks. ---*/
+
+ int bc, family, familyBC;
+ CallCGNS(cg_boco_write(cgnsFileID, cgnsBase, cgnsZone, name.c_str(), FamilySpecified, PointRange, 2, range, &bc));
+ CallCGNS(cg_boco_gridlocation_write(cgnsFileID, cgnsBase, cgnsZone, bc, nDim == 3 ? FaceCenter : EdgeCenter));
+ CallCGNS(cg_goto(cgnsFileID, cgnsBase, "Zone_t", cgnsZone, "ZoneBC_t", 1, "BC_t", bc, "end"));
+ CallCGNS(cg_famname_write(name.c_str()));
+
+ CallCGNS(cg_family_write(cgnsFileID, cgnsBase, name.c_str(), &family));
+ CallCGNS(cg_fambc_write(cgnsFileID, cgnsBase, family, "FamBC", GetCGNSBCType(marker.kindBC), &familyBC));
+ }
+}
+
+BCType_t CCGNSFileWriter::GetCGNSBCType(unsigned short kindBC) {
+ switch (kindBC) {
+ case EULER_WALL:
+ return BCWallInviscid;
+ case HEAT_FLUX:
+ return BCWallViscousHeatFlux;
+ case ISOTHERMAL:
+ return BCWallViscousIsothermal;
+ case HEAT_TRANSFER:
+ case CHT_WALL_INTERFACE:
+ case SMOLUCHOWSKI_MAXWELL:
+ return BCWallViscous;
+ case FAR_FIELD:
+ return BCFarfield;
+ case SYMMETRY_PLANE:
+ return BCSymmetryPlane;
+ case INLET_FLOW:
+ return BCInflow;
+ case OUTLET_FLOW:
+ return BCOutflow;
+ case SUPERSONIC_INLET:
+ return BCInflowSupersonic;
+ case SUPERSONIC_OUTLET:
+ return BCOutflowSupersonic;
+ default:
+ return BCTypeUserDefined;
}
}
void CCGNSFileWriter::WriteField(int iField, const string& FieldName) {
+ /*--- The coordinates define the mesh, so they are always written in double precision. Single precision would
+ move the points by up to ~1e-7 of the size of the domain, which can be larger than the smallest cells. ---*/
+
+ const bool isCoord = iField < nDim;
+
+ if (isCoord || doublePrecisionFields)
+ WriteFieldOfType(iField, FieldName, RealDouble);
+ else
+ WriteFieldOfType(iField, FieldName, RealSingle);
+}
+
+template
+void CCGNSFileWriter::WriteFieldOfType(int iField, const string& FieldName, DataType_t dataType) {
/*--- Check if field is coordinate. ---*/
const bool isCoord = iField < nDim;
- /*--- Create send buffer. ---*/
- sendBufferField.resize(nLocalPoints);
+ /*--- Each rank writes the points it holds, which are a contiguous range of the points of the file. ---*/
+ vector buffer(nLocalPoints);
for (unsigned long iPoint = 0; iPoint < nLocalPoints; iPoint++) {
- sendBufferField[iPoint] = static_cast(dataSorter->GetData(iField, iPoint));
+ buffer[iPoint] = static_cast(dataSorter->GetData(iField, iPoint));
}
- if (rank != MASTER_NODE) {
- SU2_MPI::Send(sendBufferField.data(), nLocalPoints * sizeof(dataPrecision), MPI_CHAR, MASTER_NODE, 0,
- SU2_MPI::GetComm());
- return;
- }
+ cgsize_t nodeBegin = static_cast(dataSorter->GetnPointCumulative(rank) + 1);
+ cgsize_t nodeEnd = static_cast(dataSorter->GetnPointCumulative(rank + 1));
- /*--- Coordinate vector is written in blocks, one for each process. ---*/
- cgsize_t nodeBegin = 1;
- auto nodeEnd = static_cast(nLocalPoints);
- if (nLocalPoints > 0) {
- if (isCoord) {
- int CoordinateNumber;
- CallCGNS(cg_coord_partial_write(cgnsFileID, cgnsBase, cgnsZone, dataType, FieldName.c_str(), &nodeBegin, &nodeEnd,
- sendBufferField.data(), &CoordinateNumber));
- } else {
- int fieldNumber;
- CallCGNS(cg_field_partial_write(cgnsFileID, cgnsBase, cgnsZone, cgnsFields, dataType, FieldName.c_str(), &nodeBegin,
- &nodeEnd, sendBufferField.data(), &fieldNumber));
- }
- }
+ /*--- A rank without points takes part in the collective call but writes nothing. ---*/
+ const T* data = nLocalPoints > 0 ? buffer.data() : nullptr;
- for (int i = 0; i < size; ++i) {
- if (i == MASTER_NODE) continue;
- /*--- In CGNS numbering starts form 1 and ranges are inclusive ---*/
- nodeBegin = static_cast(dataSorter->GetnPointCumulative(i) + 1);
- nodeEnd = static_cast(dataSorter->GetnPointCumulative(i + 1));
-
- const auto recvSize = static_cast(nodeEnd - nodeBegin + 1);
- recvBufferField.resize(recvSize);
-
- SU2_MPI::Recv(recvBufferField.data(), recvSize * sizeof(dataPrecision), MPI_CHAR, i, 0, SU2_MPI::GetComm(),
- MPI_STATUS_IGNORE);
- if (recvSize <= 0) continue;
- if (isCoord) {
- int CoordinateNumber;
- CallCGNS(cg_coord_partial_write(cgnsFileID, cgnsBase, cgnsZone, dataType, FieldName.c_str(), &nodeBegin, &nodeEnd,
- recvBufferField.data(), &CoordinateNumber));
- } else {
- int fieldNumber;
- CallCGNS(cg_field_partial_write(cgnsFileID, cgnsBase, cgnsZone, cgnsFields, dataType, FieldName.c_str(),
- &nodeBegin, &nodeEnd, recvBufferField.data(), &fieldNumber));
- }
+ if (isCoord) {
+ int coordinateNumber;
+#ifdef HAVE_MPI
+ CallCGNS(cgp_coord_write(cgnsFileID, cgnsBase, cgnsZone, dataType, FieldName.c_str(), &coordinateNumber));
+ CallCGNS(cgp_coord_write_data(cgnsFileID, cgnsBase, cgnsZone, coordinateNumber, &nodeBegin, &nodeEnd, data));
+#else
+ CallCGNS(cg_coord_partial_write(cgnsFileID, cgnsBase, cgnsZone, dataType, FieldName.c_str(), &nodeBegin, &nodeEnd,
+ data, &coordinateNumber));
+#endif
+ } else {
+ int fieldNumber;
+#ifdef HAVE_MPI
+ CallCGNS(cgp_field_write(cgnsFileID, cgnsBase, cgnsZone, cgnsFields, dataType, FieldName.c_str(), &fieldNumber));
+ CallCGNS(
+ cgp_field_write_data(cgnsFileID, cgnsBase, cgnsZone, cgnsFields, fieldNumber, &nodeBegin, &nodeEnd, data));
+#else
+ CallCGNS(cg_field_partial_write(cgnsFileID, cgnsBase, cgnsZone, cgnsFields, dataType, FieldName.c_str(),
+ &nodeBegin, &nodeEnd, data, &fieldNumber));
+#endif
}
}
@@ -171,69 +428,73 @@ void CCGNSFileWriter::WriteConnectivity(GEO_TYPE type, const string& SectionName
const auto nTotElem = dataSorter->GetnElemGlobal(type);
if (nTotElem == 0) return;
- /*--- Create a new CGNS node to store connectivity. ---*/
+ /*--- Create new CGNS nodes to store connectivity. Some readers (e.g. the VTK/ParaView CGNS reader)
+ store the connectivity size of a section in a 32-bit int, so an element type with more than
+ maxSectionEntries connectivity entries is split into several sections with consecutive ranges. ---*/
const auto elementType = GetCGNSType(type);
+ const auto nPointsElem = nPointsOfElementType(type);
+ const auto nTotElemCG = static_cast(nTotElem);
+ const auto maxElemSection = static_cast(maxSectionEntries / nPointsElem);
+ const auto nSections = (nTotElemCG + maxElemSection - 1) / maxElemSection;
+
+ /*--- First and last element (CGNS numbering starts from 1 and ranges are inclusive) of a section. ---*/
+ auto sectionBegin = [&](cgsize_t iSec) { return cumulative + 1 + iSec * maxElemSection; };
+ auto sectionEnd = [&](cgsize_t iSec) { return cumulative + std::min(nTotElemCG, (iSec + 1) * maxElemSection); };
+
+ /*--- The sections are metadata, all ranks create them with the same arguments. ---*/
+ vector cgnsSections(nSections);
+ for (cgsize_t iSec = 0; iSec < nSections; ++iSec) {
+ const string name = nSections == 1 ? SectionName : SectionName + "_" + std::to_string(iSec + 1);
+ CallCGNS(SectionWrite(name, elementType, sectionBegin(iSec), sectionEnd(iSec), &cgnsSections[iSec]));
+ }
- cgsize_t firstElem = cumulative + 1;
- cgsize_t endElem = cumulative + static_cast(nTotElem);
-
- int cgnsSection;
- if (rank == MASTER_NODE)
- CallCGNS(cg_section_partial_write(cgnsFileID, cgnsBase, cgnsZone, SectionName.c_str(), elementType, firstElem,
- endElem, 0, &cgnsSection));
-
- /*--- Retrieve element distribution among processes. ---*/
+ /*--- Retrieve element distribution among processes, the elements of a rank are a contiguous range. ---*/
const auto nLocalElem = dataSorter->GetnElem(type);
- vector distElem(size);
-
- SU2_MPI::Allgather(&nLocalElem, 1, MPI_UNSIGNED_LONG, distElem.data(), 1, MPI_UNSIGNED_LONG, SU2_MPI::GetComm());
+ const cgsize_t firstElem = cumulative + 1 + static_cast(GetRankOffset(nLocalElem).first);
+ const cgsize_t endElem = firstElem + static_cast(nLocalElem) - 1;
- firstElem = cumulative + 1;
- endElem = cumulative + static_cast(distElem[rank]);
-
- /*--- Connectivity is stored in send buffer. ---*/
- const auto nPointsElem = nPointsOfElementType(type);
- sendBufferConnectivity.resize(nLocalElem * nPointsElem);
+ /*--- Store the connectivity of this rank. ---*/
+ vector connectivity(nLocalElem * nPointsElem);
for (unsigned long iElem = 0; iElem < nLocalElem; iElem++) {
for (unsigned long iPoint = 0; iPoint < nPointsElem; iPoint++) {
- sendBufferConnectivity[iPoint + nPointsElem * iElem] =
+ connectivity[iPoint + nPointsElem * iElem] =
static_cast(dataSorter->GetElemConnectivity(type, iElem, iPoint));
}
}
- const auto bufferSize = static_cast(nLocalElem * nPointsElem * sizeof(cgsize_t));
- if (rank != MASTER_NODE) {
- SU2_MPI::Send(sendBufferConnectivity.data(), bufferSize, MPI_CHAR, MASTER_NODE, 1, SU2_MPI::GetComm());
- return;
+ /*--- Write the elements of this rank, which may span more than one section. A rank without elements takes
+ part in the collective calls but writes nothing. ---*/
+ for (cgsize_t iSec = 0; iSec < nSections; ++iSec) {
+ const auto lo = std::max(firstElem, sectionBegin(iSec));
+ const auto hi = std::min(endElem, sectionEnd(iSec));
+ const bool empty = (nLocalElem == 0) || (lo > hi);
+ CallCGNS(ElementsWriteData(cgnsSections[iSec], lo, hi, empty ? nullptr : &connectivity[(lo - firstElem) * nPointsElem]));
}
- /*--- Connectivity vector is written in blocks, one for each process. ---*/
- if (nLocalElem > 0)
- CallCGNS(cg_elements_partial_write(cgnsFileID, cgnsBase, cgnsZone, cgnsSection, firstElem, endElem,
- sendBufferConnectivity.data()));
-
- for (int i = 0; i < size; ++i) {
- if (i == MASTER_NODE) continue;
- /*--- In CGNS numbering starts form 1 and ranges are inclusive ---*/
- firstElem = endElem + 1;
- endElem += static_cast(distElem[i]);
- const auto recvSize = static_cast((endElem - firstElem + 1) * nPointsElem);
- recvBufferConnectivity.resize(recvSize);
-
- const auto recvByte = static_cast(recvBufferConnectivity.size() * sizeof(cgsize_t));
- SU2_MPI::Recv(recvBufferConnectivity.data(), recvByte, MPI_CHAR, i, 1, SU2_MPI::GetComm(), MPI_STATUS_IGNORE);
-
- if (!recvBufferConnectivity.empty())
- CallCGNS(cg_elements_partial_write(cgnsFileID, cgnsBase, cgnsZone, cgnsSection, firstElem, endElem,
- recvBufferConnectivity.data()));
- }
- cumulative += static_cast(nTotElem);
+ cumulative += nTotElemCG;
+}
+
+int CCGNSFileWriter::SectionWrite(const string& name, ElementType_t type, cgsize_t start, cgsize_t end, int* section) {
+#ifdef HAVE_MPI
+ return cgp_section_write(cgnsFileID, cgnsBase, cgnsZone, name.c_str(), type, start, end, 0, section);
+#else
+ return cg_section_partial_write(cgnsFileID, cgnsBase, cgnsZone, name.c_str(), type, start, end, 0, section);
+#endif
+}
+
+int CCGNSFileWriter::ElementsWriteData(int section, cgsize_t start, cgsize_t end, const cgsize_t* elements) {
+#ifdef HAVE_MPI
+ return cgp_elements_write_data(cgnsFileID, cgnsBase, cgnsZone, section, start, end, elements);
+#else
+ if (elements == nullptr) return CG_OK;
+ return cg_elements_partial_write(cgnsFileID, cgnsBase, cgnsZone, section, start, end, elements);
+#endif
}
void CCGNSFileWriter::InitializeFields() {
/*--- Create "Fields" node to store solution. ---*/
- if (rank == MASTER_NODE) CallCGNS(cg_sol_write(cgnsFileID, cgnsBase, cgnsZone, "Fields", Vertex, &cgnsFields));
+ CallCGNS(cg_sol_write(cgnsFileID, cgnsBase, cgnsZone, "Fields", Vertex, &cgnsFields));
}
#endif // HAVE_CGNS
diff --git a/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp b/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp
index 915e33d5c33f..3aaa35ff4635 100644
--- a/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp
+++ b/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp
@@ -59,8 +59,8 @@ CParallelDataSorter::CParallelDataSorter(CConfig *config, const vector &
nPoint_Recv = new int[size+1]();
nElem_Send = new int[size+1]();
nElem_Cum = new int[size+1]();
- nElemConn_Send = new int[size+1]();
- nElemConn_Cum = new int[size+1]();
+ nElemConn_Send = new unsigned long[size+1]();
+ nElemConn_Cum = new unsigned long[size+1]();
nElemPerType.fill(0);
nElemPerTypeGlobal.fill(0);
@@ -95,7 +95,7 @@ CParallelDataSorter::~CParallelDataSorter(){
void CParallelDataSorter::SortOutputData() {
- const int VARS_PER_POINT = GlobalField_Counter;
+ const size_t VARS_PER_POINT = GlobalField_Counter;
/*--- Allocate the memory that we need for receiving the conn
values and then cue up the non-blocking receives. Note that
@@ -109,6 +109,13 @@ void CParallelDataSorter::SortOutputData() {
* because it communicates passivedoubles and not AD types. This avoids some
* creative C++ to communicate AD types and then convert to passive. ---*/
+ /*--- The data of each point is sent as one element of a contiguous datatype, so that the MPI counts are numbers
+ of points and do not overflow when a rank sends or receives more than INT_MAX values. ---*/
+
+ MPI_Datatype pointType;
+ MPI_Type_contiguous(GlobalField_Counter, MPI_DOUBLE, &pointType);
+ MPI_Type_commit(&pointType);
+
/*--- We need double the number of messages to send both the conn. and the global IDs. ---*/
auto send_req = new MPI_Request[2*nSends];
@@ -117,12 +124,11 @@ void CParallelDataSorter::SortOutputData() {
unsigned long iMessage = 0;
for (int ii=0; ii nPoint_Recv[ii])) {
- int ll = VARS_PER_POINT*nPoint_Recv[ii];
- int kk = nPoint_Recv[ii+1] - nPoint_Recv[ii];
- int count = VARS_PER_POINT*kk;
+ size_t ll = VARS_PER_POINT*nPoint_Recv[ii];
+ int count = nPoint_Recv[ii+1] - nPoint_Recv[ii];
int source = ii;
int tag = ii + 1;
- MPI_Irecv(&(dataBuffer[ll]), count, MPI_DOUBLE, source, tag,
+ MPI_Irecv(&(dataBuffer[ll]), count, pointType, source, tag,
SU2_MPI::GetComm(), &(recv_req[iMessage]));
iMessage++;
}
@@ -133,12 +139,11 @@ void CParallelDataSorter::SortOutputData() {
iMessage = 0;
for (int ii=0; ii nPoint_Send[ii])) {
- int ll = VARS_PER_POINT*nPoint_Send[ii];
- int kk = nPoint_Send[ii+1] - nPoint_Send[ii];
- int count = VARS_PER_POINT*kk;
+ size_t ll = VARS_PER_POINT*nPoint_Send[ii];
+ int count = nPoint_Send[ii+1] - nPoint_Send[ii];
int dest = ii;
int tag = rank + 1;
- MPI_Isend(&(connSend[ll]), count, MPI_DOUBLE, dest, tag,
+ MPI_Isend(&(connSend[ll]), count, pointType, dest, tag,
SU2_MPI::GetComm(), &(send_req[iMessage]));
iMessage++;
}
@@ -179,17 +184,17 @@ void CParallelDataSorter::SortOutputData() {
/*--- Copy my own rank's data into the recv buffer directly. ---*/
- int mm = VARS_PER_POINT*nPoint_Recv[rank];
- int ll = VARS_PER_POINT*nPoint_Send[rank];
- int kk = VARS_PER_POINT*nPoint_Send[rank+1];
+ size_t mm = VARS_PER_POINT*nPoint_Recv[rank];
+ size_t ll = VARS_PER_POINT*nPoint_Send[rank];
+ size_t kk = VARS_PER_POINT*nPoint_Send[rank+1];
- for (int nn=ll; nn tmpBuffer(nPoint_Recv[size]);
- for (int jj = 0; jj < VARS_PER_POINT; jj++){
- for (int ii = 0; ii < nPoint_Recv[size]; ii++){
+ for (size_t jj = 0; jj < VARS_PER_POINT; jj++){
+ for (size_t ii = 0; ii < static_cast(nPoint_Recv[size]); ii++){
tmpBuffer[idRecv[ii]] = dataBuffer[ii*VARS_PER_POINT+jj];
}
- for (int ii = 0; ii < nPoint_Recv[size]; ii++){
+ for (size_t ii = 0; ii < static_cast(nPoint_Recv[size]); ii++){
dataBuffer[ii*VARS_PER_POINT+jj] = tmpBuffer[ii];
}
}
@@ -238,7 +244,7 @@ void CParallelDataSorter::PrepareSendBuffers(std::vector& globalI
unsigned long iPoint;
unsigned short iProcessor;
- int VARS_PER_POINT = GlobalField_Counter;
+ const size_t VARS_PER_POINT = GlobalField_Counter;
/*--- We start with the grid nodes distributed across all procs with
no particular ordering assumed. We need to loop through our local partition
@@ -370,8 +376,8 @@ void CParallelDataSorter::SetTotalElements(){
SU2_MPI::Allreduce(nElemPerType.data(), nElemPerTypeGlobal.data(), N_ELEM_TYPES, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm());
- nElemGlobal = std::accumulate(nElemPerTypeGlobal.begin(), nElemPerTypeGlobal.end(), 0);
- nElem = std::accumulate(nElemPerType.begin(), nElemPerType.end(), 0);
+ nElemGlobal = std::accumulate(nElemPerTypeGlobal.begin(), nElemPerTypeGlobal.end(), 0ul);
+ nElem = std::accumulate(nElemPerType.begin(), nElemPerType.end(), 0ul);
nConn = 0;
nConnGlobal = 0;
@@ -397,7 +403,7 @@ void CParallelDataSorter::SetTotalElements(){
nElem_Cum[0] = 0; nElemConn_Cum[0] = 0;
for (int ii=1; ii <= size; ii++) {
nElem_Send[ii] = int(nElem);
- nElemConn_Send[ii] = int(nConn);
+ nElemConn_Send[ii] = nConn;
nElem_Cum[ii] = 0;
nElemConn_Cum[ii] = 0;
}
@@ -407,8 +413,8 @@ void CParallelDataSorter::SetTotalElements(){
SU2_MPI::Alltoall(&(nElem_Send[1]), 1, MPI_INT,
&(nElem_Cum[1]), 1, MPI_INT, SU2_MPI::GetComm());
- SU2_MPI::Alltoall(&(nElemConn_Send[1]), 1, MPI_INT,
- &(nElemConn_Cum[1]), 1, MPI_INT, SU2_MPI::GetComm());
+ SU2_MPI::Alltoall(&(nElemConn_Send[1]), 1, MPI_UNSIGNED_LONG,
+ &(nElemConn_Cum[1]), 1, MPI_UNSIGNED_LONG, SU2_MPI::GetComm());
/*--- Put the counters into cumulative storage format. ---*/
diff --git a/SU2_CFD/src/output/filewriter/CParallelFileWriter.cpp b/SU2_CFD/src/output/filewriter/CParallelFileWriter.cpp
index bd115607b629..b3730941ee33 100644
--- a/SU2_CFD/src/output/filewriter/CParallelFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CParallelFileWriter.cpp
@@ -25,7 +25,9 @@
* License along with SU2. If not, see .
*/
+#include
#include
+#include
#include "../../../include/output/filewriter/CFileWriter.hpp"
@@ -61,25 +63,33 @@ bool CFileWriter::WriteMPIBinaryDataAll(const void *data, unsigned long sizeInBy
startTime = SU2_MPI::Wtime();
- MPI_Datatype filetype;
+ /*--- Prepare to write the actual data. MPI counts are int, so the local data
+ (which can exceed INT_MAX bytes) is described as a number of fixed-size blocks
+ plus a remainder, combined in a single datatype that is written with count 1. ---*/
- /*--- Prepare to write the actual data ---*/
+ constexpr unsigned long blockBytes = 1ul << 20;
+ int blockLengths[2] = {static_cast(sizeInBytes / blockBytes), static_cast(sizeInBytes % blockBytes)};
+ MPI_Aint displacements[2] = {0, static_cast(sizeInBytes - sizeInBytes % blockBytes)};
- MPI_Type_contiguous(int(sizeInBytes), MPI_BYTE, &filetype);
- MPI_Type_commit(&filetype);
+ MPI_Datatype blocktype, datatype;
+ MPI_Type_contiguous(static_cast(blockBytes), MPI_BYTE, &blocktype);
+ MPI_Datatype types[2] = {blocktype, MPI_BYTE};
+ MPI_Type_create_struct(2, blockLengths, displacements, types, &datatype);
+ MPI_Type_commit(&datatype);
/*--- Set the view for the MPI file write, i.e., describe the
location in the file that this rank "sees" for writing its
- piece of the file. ---*/
+ piece of the file. The data is contiguous so a byte view is enough. ---*/
- MPI_File_set_view(fhw, disp + offsetInBytes, MPI_BYTE, filetype,
+ MPI_File_set_view(fhw, disp + offsetInBytes, MPI_BYTE, MPI_BYTE,
(char*)"native", MPI_INFO_NULL);
/*--- Collective call for all ranks to write simultaneously. ---*/
- int ierr = MPI_File_write_all(fhw, data, int(sizeInBytes), MPI_BYTE, MPI_STATUS_IGNORE);
+ int ierr = MPI_File_write_all(fhw, data, 1, datatype, MPI_STATUS_IGNORE);
- MPI_Type_free(&filetype);
+ MPI_Type_free(&datatype);
+ MPI_Type_free(&blocktype);
disp += totalSizeInBytes;
fileSize += sizeInBytes;
@@ -198,7 +208,7 @@ bool CFileWriter::WriteMPIString(const string &str, unsigned short processor){
}
-bool CFileWriter::OpenMPIFile(string val_filename){
+bool CFileWriter::OpenMPIFile(string val_filename, bool append){
/*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/
val_filename.append(fileExt);
@@ -207,6 +217,26 @@ bool CFileWriter::OpenMPIFile(string val_filename){
int ierr;
disp = 0.0;
+ /*--- Continue writing at the end of a file that exists, e.g. to add a zone to a multizone mesh file. ---*/
+
+ if (append) {
+ /*--- The file is created if it does not exist yet, for example for the first zone written to a file of its
+ own, and the writing starts at its end. ---*/
+ ierr = MPI_File_open(SU2_MPI::GetComm(), val_filename.c_str(), MPI_MODE_CREATE|MPI_MODE_WRONLY, MPI_INFO_NULL,
+ &fhw);
+ if (ierr != MPI_SUCCESS) {
+ SU2_MPI::Error(string("Unable to open file ") + val_filename, CURRENT_FUNCTION);
+ }
+ MPI_Offset fileEnd;
+ MPI_File_get_size(fhw, &fileEnd);
+ disp = fileEnd;
+
+ fileSize = 0.0;
+ usedTime = 0;
+
+ return true;
+ }
+
/*--- All ranks open the file using MPI. Here, we try to open the file with
exclusive so that an error is generated if the file exists. We always want
to write a fresh output file, so we delete any existing files and create
@@ -216,7 +246,7 @@ bool CFileWriter::OpenMPIFile(string val_filename){
MPI_MODE_CREATE|MPI_MODE_EXCL|MPI_MODE_WRONLY,
MPI_INFO_NULL, &fhw);
if (ierr != MPI_SUCCESS) {
- MPI_File_close(&fhw);
+ /*--- The file exists: the failed open gives no valid handle to close, the file is deleted and created again. ---*/
if (rank == 0)
MPI_File_delete(val_filename.c_str(), MPI_INFO_NULL);
ierr = MPI_File_open(SU2_MPI::GetComm(), val_filename.c_str(),
@@ -231,7 +261,7 @@ bool CFileWriter::OpenMPIFile(string val_filename){
val_filename, CURRENT_FUNCTION);
}
#else
- fhw = fopen(val_filename.c_str(), "wb");
+ fhw = fopen(val_filename.c_str(), append ? "ab" : "wb");
/*--- Error check for opening the file. ---*/
if (!fhw) {
@@ -246,6 +276,25 @@ bool CFileWriter::OpenMPIFile(string val_filename){
return true;
}
+std::pair CFileWriter::GetRankOffset(unsigned long localCount) const {
+ vector counts(size, localCount);
+
+ SU2_MPI::Allgather(&localCount, 1, MPI_UNSIGNED_LONG, counts.data(), 1, MPI_UNSIGNED_LONG, SU2_MPI::GetComm());
+
+ return std::make_pair(std::accumulate(counts.begin(), counts.begin() + rank, 0ul),
+ std::accumulate(counts.begin(), counts.end(), 0ul));
+}
+
+bool CFileWriter::WriteMPIStringAll(const string &str){
+
+ /*--- Each rank writes its own text at the position that follows the text of the ranks before it. ---*/
+
+ const unsigned long sizeInBytes = str.size();
+ const auto offsetAndTotal = GetRankOffset(sizeInBytes);
+
+ return WriteMPIBinaryDataAll(str.data(), sizeInBytes, offsetAndTotal.second, offsetAndTotal.first);
+}
+
bool CFileWriter::CloseMPIFile(){
#ifdef HAVE_MPI
diff --git a/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp
index bde27d26bc65..e065a421ae24 100644
--- a/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp
@@ -27,6 +27,8 @@
#include "../../../include/output/filewriter/CParaviewBinaryFileWriter.hpp"
#include "../../../../Common/include/toolboxes/SwapBytes.hpp"
+#include
+#include
const string CParaviewBinaryFileWriter::fileExt = ".vtk";
@@ -56,7 +58,7 @@ void CParaviewBinaryFileWriter::WriteData(string val_filename){
unsigned short iDim = 0, nDim = dataSorter->GetnDim();
- unsigned long iPoint, iElem;
+ unsigned long iPoint;
const int MAX_STRING_LENGTH = 255;
char str_buf[MAX_STRING_LENGTH];
@@ -65,7 +67,13 @@ void CParaviewBinaryFileWriter::WriteData(string val_filename){
OpenMPIFile(val_filename);
- string header = "# vtk DataFile Version 3.0\n"
+ /*--- The classic (3.0) cell layout stores the cell sizes and node ids of all cells in one Int32 array. When that
+ array does not fit in Int32, use the 5.1 layout (VTK >= 9.0) with separate Int64 offsets and connectivity. ---*/
+
+ const unsigned long GlobalCellStorage = dataSorter->GetnConnGlobal() + dataSorter->GetnElemGlobal();
+ const bool cellsInt64 = GlobalCellStorage > static_cast(std::numeric_limits::max());
+
+ string header = string("# vtk DataFile Version ") + (cellsInt64 ? "5.1" : "3.0") + "\n"
"vtk output\n"
"BINARY\n"
"DATASET UNSTRUCTURED_GRID\n";
@@ -82,9 +90,7 @@ void CParaviewBinaryFileWriter::WriteData(string val_filename){
GlobalPoint = dataSorter->GetnPointsGlobal();
myPoint = dataSorter->GetnPoints();
- SPRINTF(str_buf, "POINTS %i float\n", SU2_TYPE::Int(GlobalPoint));
-
- WriteMPIString(string(str_buf), MASTER_NODE);
+ WriteMPIString("POINTS " + std::to_string(GlobalPoint) + " float\n", MASTER_NODE);
/*--- Load/write the 1D buffer of point coordinates. Note that we
always have 3 coordinate dimensions, even for 2D problems. ---*/
@@ -130,48 +136,76 @@ void CParaviewBinaryFileWriter::WriteData(string val_filename){
GlobalElem = dataSorter->GetnElemGlobal();
GlobalElemStorage = dataSorter->GetnConnGlobal();
- SPRINTF(str_buf, "\nCELLS %i %i\n", SU2_TYPE::Int(GlobalElem),
- SU2_TYPE::Int(GlobalElemStorage+GlobalElem));
- WriteMPIString(str_buf, MASTER_NODE);
-
- /*--- Load/write 1D buffers for the connectivity of each element type. ---*/
-
- vector connBuf(myElemStorage + myElem);
- unsigned long iStorage = 0;
- unsigned short iNode = 0;
+ /*--- Loop over the local elements of each type, calling f(type, iElem, nPoints). ---*/
- auto copyToBuffer = [&](GEO_TYPE type, unsigned long nElem, unsigned short nPoints){
- for (iElem = 0; iElem < nElem; iElem++) {
- connBuf[iStorage+0] = nPoints;
- for (iNode = 0; iNode < nPoints; iNode++){
- connBuf[iStorage+iNode+1] = int(dataSorter->GetElemConnectivity(type, iElem, iNode)-1);
- }
- iStorage += nPoints + 1;
+ auto forEachElem = [&](auto f) {
+ for (auto type : {LINE, TRIANGLE, QUADRILATERAL, TETRAHEDRON, HEXAHEDRON, PRISM, PYRAMID}) {
+ const auto nPoints = nPointsOfElementType(type);
+ for (unsigned long iElem = 0; iElem < dataSorter->GetnElem(type); iElem++) f(type, iElem, nPoints);
}
};
+ unsigned long iStorage = 0;
- copyToBuffer(LINE, nParallel_Line, N_POINTS_LINE);
- copyToBuffer(TRIANGLE, nParallel_Tria, N_POINTS_TRIANGLE);
- copyToBuffer(QUADRILATERAL, nParallel_Quad, N_POINTS_QUADRILATERAL);
- copyToBuffer(TETRAHEDRON, nParallel_Tetr, N_POINTS_TETRAHEDRON);
- copyToBuffer(HEXAHEDRON, nParallel_Hexa, N_POINTS_HEXAHEDRON);
- copyToBuffer(PRISM, nParallel_Pris, N_POINTS_PRISM);
- copyToBuffer(PYRAMID, nParallel_Pyra, N_POINTS_PYRAMID);
+ if (!cellsInt64) {
- if (!bigEndian) SwapBytes((char *)connBuf.data(), sizeof(int), myElemStorage+myElem);
+ WriteMPIString("\nCELLS " + std::to_string(GlobalElem) + " " + std::to_string(GlobalCellStorage) + "\n",
+ MASTER_NODE);
- /*--- Compute various data sizes --- */
+ /*--- Load/write the 1D buffer of the number of nodes followed by the node ids of each cell. ---*/
- sizeInBytesPerPoint = sizeof(int);
- sizeInBytesLocal = sizeInBytesPerPoint*(myElemStorage + myElem);
- sizeInBytesGlobal = sizeInBytesPerPoint*(GlobalElemStorage + GlobalElem);
- offsetInBytes = sizeInBytesPerPoint*
- (dataSorter->GetnElemConnCumulative(rank) + dataSorter->GetnElemCumulative(rank));
+ vector connBuf(myElemStorage + myElem);
+
+ forEachElem([&](GEO_TYPE type, unsigned long iElem, unsigned short nPoints) {
+ connBuf[iStorage++] = nPoints;
+ for (unsigned short iNode = 0; iNode < nPoints; iNode++)
+ connBuf[iStorage++] = static_cast(dataSorter->GetElemConnectivity(type, iElem, iNode) - 1);
+ });
+
+ if (!bigEndian) SwapBytes((char *)connBuf.data(), sizeof(int32_t), myElemStorage+myElem);
- WriteMPIBinaryDataAll(connBuf.data(), sizeInBytesLocal, sizeInBytesGlobal, offsetInBytes);
+ sizeInBytesPerPoint = sizeof(int32_t);
+ sizeInBytesLocal = sizeInBytesPerPoint*(myElemStorage + myElem);
+ sizeInBytesGlobal = sizeInBytesPerPoint*GlobalCellStorage;
+ offsetInBytes = sizeInBytesPerPoint*
+ (dataSorter->GetnElemConnCumulative(rank) + dataSorter->GetnElemCumulative(rank));
+
+ WriteMPIBinaryDataAll(connBuf.data(), sizeInBytesLocal, sizeInBytesGlobal, offsetInBytes);
+
+ } else {
+
+ WriteMPIString("\nCELLS " + std::to_string(GlobalElem + 1) + " " + std::to_string(GlobalElemStorage) + "\n",
+ MASTER_NODE);
+
+ /*--- Load the offsets (where each cell ends in the connectivity) and the connectivity. ---*/
+
+ vector offsetBuf(myElem), connBuf(myElemStorage);
+ unsigned long iCell = 0;
+
+ forEachElem([&](GEO_TYPE type, unsigned long iElem, unsigned short nPoints) {
+ for (unsigned short iNode = 0; iNode < nPoints; iNode++)
+ connBuf[iStorage++] = static_cast(dataSorter->GetElemConnectivity(type, iElem, iNode)) - 1;
+ offsetBuf[iCell++] = static_cast(iStorage + dataSorter->GetnElemConnCumulative(rank));
+ });
+
+ if (!bigEndian) {
+ SwapBytes((char *)offsetBuf.data(), sizeof(int64_t), myElem);
+ SwapBytes((char *)connBuf.data(), sizeof(int64_t), myElemStorage);
+ }
+
+ /*--- The offsets start with a 0, written by the master node. ---*/
+
+ WriteMPIString("OFFSETS vtktypeint64\n", MASTER_NODE);
+ const int64_t firstOffset = 0;
+ WriteMPIBinaryData(&firstOffset, sizeof(int64_t), MASTER_NODE);
+ WriteMPIBinaryDataAll(offsetBuf.data(), sizeof(int64_t)*myElem, sizeof(int64_t)*GlobalElem,
+ sizeof(int64_t)*dataSorter->GetnElemCumulative(rank));
+
+ WriteMPIString("\nCONNECTIVITY vtktypeint64\n", MASTER_NODE);
+ WriteMPIBinaryDataAll(connBuf.data(), sizeof(int64_t)*myElemStorage, sizeof(int64_t)*GlobalElemStorage,
+ sizeof(int64_t)*dataSorter->GetnElemConnCumulative(rank));
+ }
- SPRINTF (str_buf, "\nCELL_TYPES %i\n", SU2_TYPE::Int(GlobalElem));
- WriteMPIString(str_buf, MASTER_NODE);
+ WriteMPIString("\nCELL_TYPES " + std::to_string(GlobalElem) + "\n", MASTER_NODE);
/*--- Load/write the cell type for all elements in the file. ---*/
@@ -197,8 +231,7 @@ void CParaviewBinaryFileWriter::WriteData(string val_filename){
WriteMPIBinaryDataAll(typeBuf.data(), sizeInBytesLocal, sizeInBytesGlobal, offsetInBytes);
- SPRINTF (str_buf, "\nPOINT_DATA %i\n", SU2_TYPE::Int(GlobalPoint));
- WriteMPIString(str_buf, MASTER_NODE);
+ WriteMPIString("\nPOINT_DATA " + std::to_string(GlobalPoint) + "\n", MASTER_NODE);
/*--- Adjust container start location to avoid point coords. ---*/
diff --git a/SU2_CFD/src/output/filewriter/CParaviewFileWriter.cpp b/SU2_CFD/src/output/filewriter/CParaviewFileWriter.cpp
index 48f67b631637..64c3ab455541 100644
--- a/SU2_CFD/src/output/filewriter/CParaviewFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CParaviewFileWriter.cpp
@@ -37,211 +37,73 @@ CParaviewFileWriter::~CParaviewFileWriter()= default;
void CParaviewFileWriter::WriteData(string val_filename){
- /*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/
- val_filename.append(fileExt);
-
if (!dataSorter->GetConnectivitySorted()){
SU2_MPI::Error("Connectivity must be sorted.", CURRENT_FUNCTION);
}
- unsigned short iDim = 0, nDim = dataSorter->GetnDim();
-
- unsigned long iPoint, iElem;
-
- unsigned long nGlobal_Elem_Storage;
-
- ofstream Paraview_File;
-
- int iProcessor;
-
+ const unsigned short nDim = dataSorter->GetnDim();
const vector fieldNames = dataSorter->GetFieldNames();
- /*--- Set a timer for the file writing. ---*/
-
- startTime = SU2_MPI::Wtime();
-
- /*--- Open Paraview ASCII file and write the header. ---*/
-
- if (rank == MASTER_NODE) {
+ /*--- Each rank formats the data of its own points and elements into a string, and all ranks then write
+ their strings to the file at the same time, one after the other in rank order. ---*/
- Paraview_File.open(val_filename.c_str(), ios::out);
- Paraview_File.precision(6);
- Paraview_File << "# vtk DataFile Version 3.0\n";
- Paraview_File << "vtk output\n";
- Paraview_File << "ASCII\n";
- Paraview_File << "DATASET UNSTRUCTURED_GRID\n";
+ ostringstream data;
+ data << scientific;
- /*--- Write the header ---*/
- Paraview_File << "POINTS "<< dataSorter->GetnPointsGlobal() <<" double\n";
+ auto resetData = [&]() {
+ data.str("");
+ data.clear();
+ data << scientific;
+ };
- }
-
- Paraview_File.close();
-
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ OpenMPIFile(val_filename);
- /*--- Each processor opens the file. ---*/
+ /*--- Write the header. ---*/
- Paraview_File.open(val_filename.c_str(), ios::out | ios::app);
+ WriteMPIString("# vtk DataFile Version 3.0\nvtk output\nASCII\nDATASET UNSTRUCTURED_GRID\n", MASTER_NODE);
+ WriteMPIString("POINTS " + to_string(dataSorter->GetnPointsGlobal()) + " double\n", MASTER_NODE);
/*--- Write surface and volumetric point coordinates. ---*/
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
-
- /*--- Write the node data from this proc ---*/
-
-
- for (iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
- for (iDim = 0; iDim < nDim; iDim++)
- Paraview_File << scientific << dataSorter->GetData(iDim, iPoint) << "\t";
- if (nDim == 2) Paraview_File << scientific << "0.0" << "\t";
- }
- }
-
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ for (unsigned long iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
+ for (unsigned short iDim = 0; iDim < nDim; iDim++) data << dataSorter->GetData(iDim, iPoint) << "\t";
+ if (nDim == 2) data << "0.0" << "\t";
}
+ WriteMPIStringAll(data.str());
- /*--- Reduce the total number of each element. ---*/
-
- unsigned long nParallel_Line = dataSorter->GetnElem(LINE),
- nParallel_Tria = dataSorter->GetnElem(TRIANGLE),
- nParallel_Quad = dataSorter->GetnElem(QUADRILATERAL),
- nParallel_Tetr = dataSorter->GetnElem(TETRAHEDRON),
- nParallel_Hexa = dataSorter->GetnElem(HEXAHEDRON),
- nParallel_Pris = dataSorter->GetnElem(PRISM),
- nParallel_Pyra = dataSorter->GetnElem(PYRAMID);
-
- if (rank == MASTER_NODE) {
-
- /*--- Write the header ---*/
- nGlobal_Elem_Storage = dataSorter->GetnElemGlobal() + dataSorter->GetnConnGlobal();
-
- Paraview_File << "\nCELLS " << dataSorter->GetnElemGlobal() << "\t" << nGlobal_Elem_Storage << "\n";
-
- }
-
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
-
- /*--- Write connectivity data. ---*/
-
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
-
-
- for (iElem = 0; iElem < nParallel_Line; iElem++) {
- Paraview_File << N_POINTS_LINE << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(LINE, iElem, 0)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(LINE, iElem, 1)-1 << "\t";
- }
-
- for (iElem = 0; iElem < nParallel_Tria; iElem++) {
- Paraview_File << N_POINTS_TRIANGLE << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 0)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 1)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 2)-1 << "\t";
- }
-
- for (iElem = 0; iElem < nParallel_Quad; iElem++) {
- Paraview_File << N_POINTS_QUADRILATERAL << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 0)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 1)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 2)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 3)-1 << "\t";
- }
-
+ /*--- Write the connectivity, the number of nodes of each element is written before its nodes. ---*/
- for (iElem = 0; iElem < nParallel_Tetr; iElem++) {
- Paraview_File << N_POINTS_TETRAHEDRON << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 0)-1 << "\t"
- << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 1)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 2)-1 << "\t"
- << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3)-1 << "\t";
- }
+ const unsigned long nGlobal_Elem_Storage = dataSorter->GetnElemGlobal() + dataSorter->GetnConnGlobal();
- for (iElem = 0; iElem < nParallel_Hexa; iElem++) {
- Paraview_File << N_POINTS_HEXAHEDRON << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 0)-1 << "\t"
- << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 1)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 2)-1 << "\t"
- << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 3)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 4)-1 << "\t"
- << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 5)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 6)-1 << "\t"
- << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 7)-1 << "\t";
- }
+ WriteMPIString("\nCELLS " + to_string(dataSorter->GetnElemGlobal()) + "\t" + to_string(nGlobal_Elem_Storage) + "\n",
+ MASTER_NODE);
- for (iElem = 0; iElem < nParallel_Pris; iElem++) {
- Paraview_File << N_POINTS_PRISM << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(PRISM, iElem, 0)-1 << "\t"
- << dataSorter->GetElemConnectivity(PRISM, iElem, 1)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(PRISM, iElem, 2)-1 << "\t"
- << dataSorter->GetElemConnectivity(PRISM, iElem, 3)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(PRISM, iElem, 4)-1 << "\t"
- << dataSorter->GetElemConnectivity(PRISM, iElem, 5)-1 << "\t";
- }
-
- for (iElem = 0; iElem < nParallel_Pyra; iElem++) {
- Paraview_File << N_POINTS_PYRAMID << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 0)-1 << "\t"
- << dataSorter->GetElemConnectivity(PYRAMID, iElem, 1)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 2)-1 << "\t"
- << dataSorter->GetElemConnectivity(PYRAMID, iElem, 3)-1 << "\t";
- Paraview_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4)-1 << "\t";
- }
+ resetData();
- } Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ for (auto type : {LINE, TRIANGLE, QUADRILATERAL, TETRAHEDRON, HEXAHEDRON, PRISM, PYRAMID}) {
+ const auto nPoints = nPointsOfElementType(type);
+ for (unsigned long iElem = 0; iElem < dataSorter->GetnElem(type); iElem++) {
+ data << nPoints << "\t";
+ for (unsigned short iNode = 0; iNode < nPoints; iNode++)
+ data << dataSorter->GetElemConnectivity(type, iElem, iNode) - 1 << "\t";
+ }
}
+ WriteMPIStringAll(data.str());
- if (rank == MASTER_NODE) {
+ /*--- Write the type of each element. ---*/
- /*--- Write the header ---*/
- Paraview_File << "\nCELL_TYPES " << dataSorter->GetnElemGlobal() << "\n";
+ WriteMPIString("\nCELL_TYPES " + to_string(dataSorter->GetnElemGlobal()) + "\n", MASTER_NODE);
- }
+ resetData();
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
-
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- for (iElem = 0; iElem < nParallel_Line; iElem++) Paraview_File << "3\t";
- for (iElem = 0; iElem < nParallel_Tria; iElem++) Paraview_File << "5\t";
- for (iElem = 0; iElem < nParallel_Quad; iElem++) Paraview_File << "9\t";
- for (iElem = 0; iElem < nParallel_Tetr; iElem++) Paraview_File << "10\t";
- for (iElem = 0; iElem < nParallel_Hexa; iElem++) Paraview_File << "12\t";
- for (iElem = 0; iElem < nParallel_Pris; iElem++) Paraview_File << "13\t";
- for (iElem = 0; iElem < nParallel_Pyra; iElem++) Paraview_File << "14\t";
- }
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ for (auto type : {LINE, TRIANGLE, QUADRILATERAL, TETRAHEDRON, HEXAHEDRON, PRISM, PYRAMID}) {
+ for (unsigned long iElem = 0; iElem < dataSorter->GetnElem(type); iElem++) data << type << "\t";
}
+ WriteMPIStringAll(data.str());
- if (rank == MASTER_NODE) {
- /*--- Write the header ---*/
- Paraview_File << "\nPOINT_DATA "<< dataSorter->GetnPointsGlobal() <<"\n";
-
- }
+ /*--- Write the fields. ---*/
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ WriteMPIString("\nPOINT_DATA " + to_string(dataSorter->GetnPointsGlobal()) + "\n", MASTER_NODE);
unsigned short varStart = 2;
if (nDim == 3) varStart++;
@@ -263,109 +125,45 @@ void CParaviewFileWriter::WriteData(string val_filename){
}
found = fieldNames[iField].find("_y");
if (found!=string::npos) {
+ /*--- We have found a vector, so skip the Y component. ---*/
output_variable = false;
- //skip
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
VarCounter++;
}
found = fieldNames[iField].find("_z");
if (found!=string::npos) {
+ /*--- We have found a vector, so skip the Z component. ---*/
output_variable = false;
- //skip
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
VarCounter++;
}
- if (output_variable && isVector) {
-
- fieldname.erase(fieldname.end()-2,fieldname.end());
-
- if (rank == MASTER_NODE) {
- Paraview_File << "\nVECTORS " << fieldname << " double\n";
- }
+ if (!output_variable) continue;
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ resetData();
- /*--- Write surface and volumetric point coordinates. ---*/
+ if (isVector) {
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
-
- /*--- Write the node data from this proc ---*/
-
- for (iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
- Paraview_File << scientific << dataSorter->GetData(VarCounter+0, iPoint) << "\t" << dataSorter->GetData(VarCounter+1, iPoint) << "\t";
- if (nDim == 3) Paraview_File << scientific << dataSorter->GetData(VarCounter+2, iPoint) << "\t";
- if (nDim == 2) Paraview_File << scientific << "0.0" << "\t";
- }
- }
-
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
- }
-
- VarCounter++;
-
- } else if (output_variable) {
+ fieldname.erase(fieldname.end()-2,fieldname.end());
- if (rank == MASTER_NODE) {
+ WriteMPIString("\nVECTORS " + fieldname + " double\n", MASTER_NODE);
- Paraview_File << "\nSCALARS " << fieldname << " double 1\n";
- Paraview_File << "LOOKUP_TABLE default\n";
+ for (unsigned long iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
+ data << dataSorter->GetData(VarCounter+0, iPoint) << "\t" << dataSorter->GetData(VarCounter+1, iPoint) << "\t";
+ if (nDim == 3) data << dataSorter->GetData(VarCounter+2, iPoint) << "\t";
+ if (nDim == 2) data << "0.0" << "\t";
}
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
-
- /*--- Write surface and volumetric point coordinates. ---*/
+ } else {
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
+ WriteMPIString("\nSCALARS " + fieldname + " double 1\nLOOKUP_TABLE default\n", MASTER_NODE);
- /*--- Write the node data from this proc ---*/
-
- for (iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
- Paraview_File << scientific << dataSorter->GetData(VarCounter, iPoint) << "\t";
- }
-
- }
- Paraview_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ for (unsigned long iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
+ data << dataSorter->GetData(VarCounter, iPoint) << "\t";
}
-
- VarCounter++;
}
+ WriteMPIStringAll(data.str());
+ VarCounter++;
}
- Paraview_File.close();
-
-
- /*--- Compute and store the write time. ---*/
-
- stopTime = SU2_MPI::Wtime();
-
- usedTime = stopTime-startTime;
-
- fileSize = DetermineFilesize(val_filename);
-
- /*--- Compute and store the bandwidth ---*/
-
- bandwidth = fileSize/(1.0e6)/usedTime;
+ CloseMPIFile();
}
-
diff --git a/SU2_CFD/src/output/filewriter/CParaviewVTMFileWriter.cpp b/SU2_CFD/src/output/filewriter/CParaviewVTMFileWriter.cpp
index da812ee4d9d3..48d400df48e9 100644
--- a/SU2_CFD/src/output/filewriter/CParaviewVTMFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CParaviewVTMFileWriter.cpp
@@ -87,7 +87,8 @@ void CParaviewVTMFileWriter::WriteData(string val_filename){
}
-void CParaviewVTMFileWriter::AddDataset(const string& foldername, string name, const string& file, CParallelDataSorter* dataSorter){
+void CParaviewVTMFileWriter::AddDataset(const string& foldername, string name, const string& file,
+ CParallelDataSorter* dataSorter, bool doublePrecision){
/*--- Construct the full file name incl. folder ---*/
/*--- Note that the folder name is simply the filename ---*/
@@ -96,7 +97,7 @@ void CParaviewVTMFileWriter::AddDataset(const string& foldername, string name, c
/*--- Create an XML writer and dump data into file ---*/
- CParaviewXMLFileWriter XMLWriter(dataSorter);
+ CParaviewXMLFileWriter XMLWriter(dataSorter, doublePrecision);
XMLWriter.WriteData(fullFilename);
/*--- Add the dataset to the vtm file ---*/
@@ -136,7 +137,7 @@ void CParaviewVTMFileWriter::WriteFolderData(const string& foldername, CConfig *
StartBlock(std::move(multiZoneHeaderString));
StartBlock("Internal");
- AddDataset(foldername,"Internal", "Internal", volumeDataSorter);
+ AddDataset(foldername, "Internal", "Internal", volumeDataSorter, config->GetWrt_Output_Double_Precision());
EndBlock();
/*--- Open a block for the boundary ---*/
@@ -180,7 +181,7 @@ void CParaviewVTMFileWriter::WriteFolderData(const string& foldername, CConfig *
/*--- Add the dataset ---*/
- AddDataset(foldername, markerTag, markerTag, surfaceDataSorter);
+ AddDataset(foldername, markerTag, markerTag, surfaceDataSorter, config->GetWrt_Output_Double_Precision());
}
}
diff --git a/SU2_CFD/src/output/filewriter/CParaviewXMLFileWriter.cpp b/SU2_CFD/src/output/filewriter/CParaviewXMLFileWriter.cpp
index f8bc5b0448d4..3d521acd1ffd 100644
--- a/SU2_CFD/src/output/filewriter/CParaviewXMLFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CParaviewXMLFileWriter.cpp
@@ -27,11 +27,14 @@
#include "../../../include/output/filewriter/CParaviewXMLFileWriter.hpp"
#include "../../../../Common/include/toolboxes/printing_toolbox.hpp"
+#include
+#include
+#include
const string CParaviewXMLFileWriter::fileExt = ".vtu";
-CParaviewXMLFileWriter::CParaviewXMLFileWriter(CParallelDataSorter *valDataSorter) :
- CFileWriter(valDataSorter, fileExt){
+CParaviewXMLFileWriter::CParaviewXMLFileWriter(CParallelDataSorter *valDataSorter, bool valDoublePrecision) :
+ CFileWriter(valDataSorter, fileExt), doublePrecision(valDoublePrecision){
/* Check for big endian. We have to swap bytes otherwise.
* Since size of character is 1 byte when the character pointer
@@ -46,6 +49,17 @@ CParaviewXMLFileWriter::CParaviewXMLFileWriter(CParallelDataSorter *valDataSorte
CParaviewXMLFileWriter::~CParaviewXMLFileWriter()= default;
+template
+void CParaviewXMLFileWriter::WriteDataArrayOfType(const vector& buffer, VTKDatatype type, unsigned long size,
+ unsigned long globalSize, unsigned long offset) {
+ if constexpr (std::is_same::value) {
+ WriteDataArray(buffer.data(), type, size, globalSize, offset);
+ } else {
+ const vector converted(buffer.begin(), buffer.begin() + size);
+ WriteDataArray(converted.data(), type, size, globalSize, offset);
+ }
+}
+
void CParaviewXMLFileWriter::WriteData(string val_filename){
if (!dataSorter->GetConnectivitySorted()){
@@ -64,7 +78,6 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
unsigned long iPoint, iElem;
- char str_buf[255];
OpenMPIFile(val_filename);
@@ -98,6 +111,16 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
GlobalElem = dataSorter->GetnElemGlobal();
GlobalElemStorage = dataSorter->GetnConnGlobal();
+ /*--- The offsets into the connectivity array go up to GlobalElemStorage, use 64-bit
+ integers for connectivity and offsets only when that does not fit in Int32. ---*/
+
+ const bool connInt64 = GlobalElemStorage > static_cast(std::numeric_limits::max());
+ const auto connType = connInt64 ? VTKDatatype::INT64 : VTKDatatype::INT32;
+
+ /*--- Precision of the coordinates and fields. ---*/
+
+ const auto realType = doublePrecision ? VTKDatatype::FLOAT64 : VTKDatatype::FLOAT32;
+
/* Write the ASCII XML header. Note that we use the appended format for the data,
* which means that all data is appended at the end of the file in one binary blob.
*/
@@ -110,16 +133,14 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
WriteMPIString("\n", MASTER_NODE);
- SPRINTF(str_buf, "\n",
- SU2_TYPE::Int(GlobalPoint), SU2_TYPE::Int(GlobalElem));
-
- WriteMPIString(std::string(str_buf), MASTER_NODE);
+ WriteMPIString("\n", MASTER_NODE);
WriteMPIString("\n", MASTER_NODE);
- AddDataArray(VTKDatatype::FLOAT32, "", NCOORDS, myPoint*NCOORDS, GlobalPoint*NCOORDS);
+ AddDataArray(realType, "", NCOORDS, myPoint*NCOORDS, GlobalPoint*NCOORDS);
WriteMPIString("\n", MASTER_NODE);
WriteMPIString("\n", MASTER_NODE);
- AddDataArray(VTKDatatype::INT32, "connectivity", 1, myElemStorage, GlobalElemStorage);
- AddDataArray(VTKDatatype::INT32, "offsets", 1, myElem, GlobalElem);
+ AddDataArray(connType, "connectivity", 1, myElemStorage, GlobalElemStorage);
+ AddDataArray(connType, "offsets", 1, myElem, GlobalElem);
AddDataArray(VTKDatatype::UINT8, "types", 1, myElem, GlobalElem);
WriteMPIString("\n", MASTER_NODE);
@@ -168,11 +189,11 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
fieldname.erase(fieldname.end()-2,fieldname.end());
- AddDataArray(VTKDatatype::FLOAT32, fieldname, NCOORDS, myPoint*NCOORDS, GlobalPoint*NCOORDS);
+ AddDataArray(realType, fieldname, NCOORDS, myPoint*NCOORDS, GlobalPoint*NCOORDS);
} else if (output_variable) {
- AddDataArray(VTKDatatype::FLOAT32, fieldname, 1, myPoint, GlobalPoint);
+ AddDataArray(realType, fieldname, 1, myPoint, GlobalPoint);
}
@@ -188,35 +209,44 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
/*--- Load/write the 1D buffer of point coordinates. Note that we
always have 3 coordinate dimensions, even for 2D problems. ---*/
- vector dataBufferFloat(myPoint*NCOORDS);
+ vector dataBuffer(myPoint*NCOORDS);
+
+ /*--- Write the staged point data with the precision requested by the user. ---*/
+
+ auto writeRealArray = [&](unsigned long size, unsigned long globalSize, unsigned long offset) {
+ if (realType == VTKDatatype::FLOAT64) {
+ WriteDataArrayOfType(dataBuffer, realType, size, globalSize, offset);
+ } else {
+ WriteDataArrayOfType(dataBuffer, realType, size, globalSize, offset);
+ }
+ };
+
for (iPoint = 0; iPoint < myPoint; iPoint++) {
for (iDim = 0; iDim < NCOORDS; iDim++) {
if (nDim == 2 && iDim == 2) {
- dataBufferFloat[iPoint*NCOORDS + iDim] = 0.0;
+ dataBuffer[iPoint*NCOORDS + iDim] = 0.0;
} else {
- auto val = (float)dataSorter->GetData(iDim, iPoint);
- dataBufferFloat[iPoint*NCOORDS + iDim] = val;
+ dataBuffer[iPoint*NCOORDS + iDim] = SU2_TYPE::GetValue(dataSorter->GetData(iDim, iPoint));
}
}
}
- WriteDataArray(dataBufferFloat.data(), VTKDatatype::FLOAT32, NCOORDS*myPoint, GlobalPoint*NCOORDS,
- dataSorter->GetnPointCumulative(rank)*NCOORDS);
+ writeRealArray(NCOORDS*myPoint, GlobalPoint*NCOORDS, dataSorter->GetnPointCumulative(rank)*NCOORDS);
/*--- Load/write 1D buffers for the connectivity of each element type. ---*/
- vector connBuf(myElemStorage);
- vector offsetBuf(myElem);
+ vector connBuf(myElemStorage);
+ vector offsetBuf(myElem);
unsigned long iStorage = 0, iElemID = 0;
unsigned short iNode = 0;
auto copyToBuffer = [&](GEO_TYPE type, unsigned long nElem, unsigned short nPoints){
for (iElem = 0; iElem < nElem; iElem++) {
for (iNode = 0; iNode < nPoints; iNode++){
- connBuf[iStorage+iNode] = int(dataSorter->GetElemConnectivity(type, iElem, iNode)-1);
+ connBuf[iStorage+iNode] = static_cast(dataSorter->GetElemConnectivity(type, iElem, iNode)) - 1;
}
iStorage += nPoints;
- offsetBuf[iElemID++] = int(iStorage + dataSorter->GetnElemConnCumulative(rank));
+ offsetBuf[iElemID++] = static_cast(iStorage + dataSorter->GetnElemConnCumulative(rank));
}
};
@@ -228,9 +258,15 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
copyToBuffer(PRISM, nParallel_Pris, N_POINTS_PRISM);
copyToBuffer(PYRAMID, nParallel_Pyra, N_POINTS_PYRAMID);
- WriteDataArray(connBuf.data(), VTKDatatype::INT32, myElemStorage, GlobalElemStorage,
- dataSorter->GetnElemConnCumulative(rank));
- WriteDataArray(offsetBuf.data(), VTKDatatype::INT32, myElem, GlobalElem, dataSorter->GetnElemCumulative(rank));
+ if (connInt64) {
+ WriteDataArrayOfType(connBuf, connType, myElemStorage, GlobalElemStorage,
+ dataSorter->GetnElemConnCumulative(rank));
+ WriteDataArrayOfType(offsetBuf, connType, myElem, GlobalElem, dataSorter->GetnElemCumulative(rank));
+ } else {
+ WriteDataArrayOfType(connBuf, connType, myElemStorage, GlobalElemStorage,
+ dataSorter->GetnElemConnCumulative(rank));
+ WriteDataArrayOfType(offsetBuf, connType, myElem, GlobalElem, dataSorter->GetnElemCumulative(rank));
+ }
/*--- Load/write the cell type for all elements in the file. ---*/
@@ -279,20 +315,17 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
/*--- Load up the buffer for writing this rank's vector data. ---*/
- float val = 0.0;
for (iPoint = 0; iPoint < myPoint; iPoint++) {
for (iDim = 0; iDim < NCOORDS; iDim++) {
if (nDim == 2 && iDim == 2) {
- dataBufferFloat[iPoint*NCOORDS + iDim] = 0.0;
+ dataBuffer[iPoint*NCOORDS + iDim] = 0.0;
} else {
- val = (float)dataSorter->GetData(VarCounter+iDim,iPoint);
- dataBufferFloat[iPoint*NCOORDS + iDim] = val;
+ dataBuffer[iPoint*NCOORDS + iDim] = SU2_TYPE::GetValue(dataSorter->GetData(VarCounter+iDim,iPoint));
}
}
}
- WriteDataArray(dataBufferFloat.data(), VTKDatatype::FLOAT32, myPoint*NCOORDS, GlobalPoint*NCOORDS,
- dataSorter->GetnPointCumulative(rank)*NCOORDS);
+ writeRealArray(myPoint*NCOORDS, GlobalPoint*NCOORDS, dataSorter->GetnPointCumulative(rank)*NCOORDS);
VarCounter++;
@@ -303,12 +336,10 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
This will be replaced with a derived data type most likely. ---*/
for (iPoint = 0; iPoint < myPoint; iPoint++) {
- auto val = (float)dataSorter->GetData(VarCounter,iPoint);
- dataBufferFloat[iPoint] = val;
+ dataBuffer[iPoint] = SU2_TYPE::GetValue(dataSorter->GetData(VarCounter,iPoint));
}
- WriteDataArray(dataBufferFloat.data(), VTKDatatype::FLOAT32, myPoint, GlobalPoint,
- dataSorter->GetnPointCumulative(rank));
+ writeRealArray(myPoint, GlobalPoint, dataSorter->GetnPointCumulative(rank));
VarCounter++;
}
@@ -322,7 +353,7 @@ void CParaviewXMLFileWriter::WriteData(string val_filename){
}
-void CParaviewXMLFileWriter::WriteDataArray(void* data, VTKDatatype type, unsigned long arraySize,
+void CParaviewXMLFileWriter::WriteDataArray(const void* data, VTKDatatype type, unsigned long arraySize,
unsigned long globalSize, unsigned long offset){
std::string typeStr;
diff --git a/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp
index e0e940b83284..035220db9368 100644
--- a/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp
@@ -34,65 +34,38 @@ CSU2FileWriter::CSU2FileWriter(CParallelDataSorter *valDataSorter) :
void CSU2FileWriter::WriteData(string val_filename){
- ofstream restart_file;
const vector fieldNames = dataSorter->GetRequiredFieldNames();
- /*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/
- val_filename.append(fileExt);
+ OpenMPIFile(val_filename);
- /*--- Set a timer for the file writing. ---*/
+ /*--- Write the header. ---*/
- startTime = SU2_MPI::Wtime();
+ string header = "\"PointID\"";
+ for (auto& field : fieldNames) header += ",\"" + field + "\"";
+ header += "\n";
- /*--- Only the FIRST node writes the header (it does not matter if that is the master). ---*/
+ WriteMPIString(header, MASTER_NODE);
- if (rank == 0) {
- restart_file.open(val_filename);
- restart_file << "\"PointID\"";
- for (auto& field : fieldNames) restart_file << ",\"" << field << "\"";
- restart_file << "\n";
- restart_file.close();
- }
-
- /*--- Serialize the writes to the restart file. ---*/
-
- for (int iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- restart_file.open(val_filename, ios::app);
- restart_file.precision(15);
-
- for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) {
-
- /*--- Write global index of the current point. ---*/
+ /*--- Each rank formats the data of its own points into a string, and all ranks then write their strings
+ to the file at the same time, one after the other in rank order. ---*/
- restart_file << dataSorter->GetGlobalIndex(iPoint);
+ ostringstream data;
+ data.precision(15);
- /*--- Loop over the variables and write the values to file. ---*/
+ for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) {
- for (size_t iVar = 0; iVar < fieldNames.size(); iVar++)
- restart_file << ", " << scientific << dataSorter->GetData(iVar, iPoint);
- restart_file << "\n";
- }
+ /*--- Write global index of the current point. ---*/
- restart_file.close();
- }
+ data << dataSorter->GetGlobalIndex(iPoint);
- /*--- Wait for iProcessor to finish and close the file. ---*/
+ /*--- Loop over the variables and write the values to file. ---*/
- SU2_MPI::Barrier(SU2_MPI::GetComm());
+ for (size_t iVar = 0; iVar < fieldNames.size(); iVar++)
+ data << ", " << scientific << dataSorter->GetData(iVar, iPoint);
+ data << "\n";
}
- /*--- Compute and store the write time. ---*/
-
- stopTime = SU2_MPI::Wtime();
-
- usedTime = stopTime-startTime;
-
- /*--- Determine the file size ---*/
-
- fileSize = DetermineFilesize(val_filename);
-
- /*--- Compute and store the bandwidth ---*/
+ WriteMPIStringAll(data.str());
- bandwidth = fileSize/(1.0e6)/usedTime;
+ CloseMPIFile();
}
diff --git a/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp
index 525eb1e23c9e..c826eb09d238 100644
--- a/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp
@@ -25,6 +25,8 @@
* License along with SU2. If not, see .
*/
#include "../../../include/output/filewriter/CSU2MeshBinaryFileWriter.hpp"
+
+#include
#include "../../../../Common/include/toolboxes/printing_toolbox.hpp"
#include
@@ -65,144 +67,110 @@ void RestrictPermissions(const string& filename) {
#endif
}
-FILE* OpenAppend(const string& filename) {
- FILE* f = fopen(filename.c_str(), "ab");
- if (!f) SU2_MPI::Error(string("Unable to open file ") + filename, CURRENT_FUNCTION);
- RestrictPermissions(filename);
- return f;
+/*--- Append the bytes of a value to a buffer, the buffers of all ranks are written to the file together. ---*/
+template
+void AppendBytes(string& buffer, const T& value) {
+ buffer.append(reinterpret_cast(&value), sizeof(value));
}
} // namespace
void CSU2MeshBinaryFileWriter::WriteData(string val_filename) {
- val_filename.append(fileExt);
+ /*--- For multizone cases all zones are written into one file, the zones after the first are appended. ---*/
- /*--- Write the file-level header (only once, before the first zone) followed
- by the per-zone header (zone_id, n_dim, n_elem). Only rank 0 touches the
- file for this; the implicit synchronization at the first Allreduce below
- (also present in CSU2MeshFileWriter) keeps the other ranks from writing
- before this is done. ---*/
+ OpenMPIFile(val_filename, iZone != 0);
- if (rank == 0) {
- FILE* f = fopen(val_filename.c_str(), (iZone == 0) ? "wb" : "ab");
- if (!f) SU2_MPI::Error(string("Unable to open file ") + val_filename, CURRENT_FUNCTION);
- RestrictPermissions(val_filename);
+ if (rank == MASTER_NODE) RestrictPermissions(val_filename + fileExt);
- if (iZone == 0) {
- int32_t size_conn_type = SU2B_CONN_TYPE_SIZE;
- int32_t n_zone = nZone;
- fwrite(&size_conn_type, sizeof(size_conn_type), 1, f);
- fwrite(&n_zone, sizeof(n_zone), 1, f);
- }
+ /*--- Write the file-level header (only once, before the first zone) followed by the per-zone header
+ (zone_id, n_dim, n_elem). Only the master node writes it. ---*/
- /*--- Zone IDs are 1-based, matching the "IZONE=" convention of the ASCII
- format (CSU2MeshFileWriter writes iZone+1 as well). ---*/
- int32_t zone_id = iZone + 1;
- int32_t n_dim = dataSorter->GetnDim();
- conn_t n_elem = dataSorter->GetnElemGlobal();
- fwrite(&zone_id, sizeof(zone_id), 1, f);
- fwrite(&n_dim, sizeof(n_dim), 1, f);
- fwrite(&n_elem, sizeof(n_elem), 1, f);
+ string buffer;
- fclose(f);
+ if (iZone == 0) {
+ AppendBytes(buffer, SU2B_CONN_TYPE_SIZE);
+ AppendBytes(buffer, static_cast(nZone));
}
- /*--- Section 1: element offsets. Every rank streams, in turn, the starting
- connectivity-array position of each of its local elements (visited in
- the fixed type order above), starting from the cumulative total left
- behind by the previous ranks. Each rank's accumulator holds only its
- own local contribution (like CSU2MeshFileWriter's nElem/myPoint), so
- summing it across ranks via Allreduce yields the new cumulative total.
- Once all ranks are done, the final total is appended once more as the
- closing sentinel offset[n_elem]. ---*/
-
- unsigned long connOffset = 0, localConnOffset = 0;
-
- for (int iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- FILE* f = OpenAppend(val_filename);
- unsigned long running = connOffset;
- for (auto type : ElemTypes) {
- const conn_t nPointsElem = nPointsOfElementType(type);
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) {
- conn_t value = running;
- fwrite(&value, sizeof(value), 1, f);
- running += nPointsElem + 2;
- }
- }
- fclose(f);
- localConnOffset = running - connOffset;
- }
- SU2_MPI::Allreduce(&localConnOffset, &connOffset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm());
- }
+ /*--- Zone IDs are 1-based, matching the "IZONE=" convention of the ASCII format. ---*/
+ AppendBytes(buffer, static_cast(iZone + 1));
+ AppendBytes(buffer, static_cast(dataSorter->GetnDim()));
+ AppendBytes(buffer, static_cast(dataSorter->GetnElemGlobal()));
+
+ WriteMPIString(buffer, MASTER_NODE);
- if (rank == 0) {
- FILE* f = OpenAppend(val_filename);
- conn_t sentinel = connOffset;
- fwrite(&sentinel, sizeof(sentinel), 1, f);
- fclose(f);
+ /*--- Each rank writes the data of its own elements and points, at the position that follows the data of the
+ ranks before it. The global offsets and indices of a rank are those of the ranks before it. ---*/
+
+ /*--- Section 1: element offsets, the starting connectivity-array position of each element, closed by the
+ total size as sentinel offset[n_elem]. ---*/
+
+ unsigned long localConnSize = 0, localElemCount = 0;
+ for (auto type : ElemTypes) {
+ localConnSize += dataSorter->GetnElem(type) * (nPointsOfElementType(type) + 2);
+ localElemCount += dataSorter->GetnElem(type);
}
- /*--- Section 2: element connectivity, [VTK_Type, node_0..node_n-1, GlobalIndex]
- per element, in the same fixed type order and the same rank-by-rank
- streaming pattern as CSU2MeshFileWriter uses for the ASCII format
- (including the same "-1" to convert 1-based dataSorter indices to the
- 0-based indices used throughout the SU2 mesh formats). ---*/
-
- unsigned long elemIndexOffset = 0, localElemCount = 0;
-
- for (int iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- FILE* f = OpenAppend(val_filename);
- conn_t globalIndex = elemIndexOffset;
- for (auto type : ElemTypes) {
- const auto nPointsElem = nPointsOfElementType(type);
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) {
- conn_t vtkType = type;
- fwrite(&vtkType, sizeof(vtkType), 1, f);
- for (auto iNode = 0u; iNode < nPointsElem; iNode++) {
- conn_t node = dataSorter->GetElemConnectivity(type, iElem, iNode) - 1;
- fwrite(&node, sizeof(node), 1, f);
- }
- fwrite(&globalIndex, sizeof(globalIndex), 1, f);
- globalIndex++;
- }
- }
- fclose(f);
- localElemCount = static_cast(globalIndex - elemIndexOffset);
+ unsigned long connOffset, totalConnSize;
+ std::tie(connOffset, totalConnSize) = GetRankOffset(localConnSize);
+
+ unsigned long elemOffset, nElemGlobal;
+ std::tie(elemOffset, nElemGlobal) = GetRankOffset(localElemCount);
+
+ buffer.clear();
+ buffer.reserve(localElemCount * sizeof(conn_t));
+
+ unsigned long running = connOffset;
+ for (auto type : ElemTypes) {
+ const conn_t nPointsElem = nPointsOfElementType(type);
+ for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) {
+ AppendBytes(buffer, static_cast(running));
+ running += nPointsElem + 2;
+ }
+ }
+ WriteMPIStringAll(buffer);
+
+ buffer.clear();
+ AppendBytes(buffer, static_cast(totalConnSize));
+ WriteMPIString(buffer, MASTER_NODE);
+
+ /*--- Section 2: element connectivity, [VTK_Type, node_0..node_n-1, GlobalIndex] per element. ---*/
+
+ buffer.clear();
+ buffer.reserve(localConnSize * sizeof(conn_t));
+
+ conn_t globalIndex = elemOffset;
+ for (auto type : ElemTypes) {
+ const auto nPointsElem = nPointsOfElementType(type);
+ for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) {
+ AppendBytes(buffer, static_cast(type));
+ for (auto iNode = 0u; iNode < nPointsElem; iNode++)
+ AppendBytes(buffer, static_cast(dataSorter->GetElemConnectivity(type, iElem, iNode) - 1));
+ AppendBytes(buffer, globalIndex);
+ globalIndex++;
}
- SU2_MPI::Allreduce(&localElemCount, &elemIndexOffset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm());
}
+ WriteMPIStringAll(buffer);
- /*--- Section 3: point coordinates and IDs, interleaved. Same rank-by-rank
- streaming pattern as CSU2MeshFileWriter's point section. ---*/
+ /*--- Section 3: point coordinates and IDs, interleaved. ---*/
- if (rank == 0) {
- FILE* f = OpenAppend(val_filename);
- conn_t nPointsGlobal = dataSorter->GetnPointsGlobal();
- fwrite(&nPointsGlobal, sizeof(nPointsGlobal), 1, f);
- fclose(f);
- }
+ buffer.clear();
+ AppendBytes(buffer, static_cast(dataSorter->GetnPointsGlobal()));
+ WriteMPIString(buffer, MASTER_NODE);
- unsigned long myPoint = 0, pointOffset = 0;
+ unsigned long pointOffset, nPointsTotal;
+ std::tie(pointOffset, nPointsTotal) = GetRankOffset(dataSorter->GetnPoints());
- for (int iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- FILE* f = OpenAppend(val_filename);
- for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) {
- for (auto iDim = 0u; iDim < dataSorter->GetnDim(); iDim++) {
- double coord = dataSorter->GetData(iDim, iPoint);
- fwrite(&coord, sizeof(coord), 1, f);
- }
- conn_t pointID = iPoint + pointOffset;
- fwrite(&pointID, sizeof(pointID), 1, f);
- }
- fclose(f);
- myPoint = dataSorter->GetnPoints();
- }
- SU2_MPI::Allreduce(&myPoint, &pointOffset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm());
+ buffer.clear();
+ buffer.reserve(dataSorter->GetnPoints() * (dataSorter->GetnDim() * sizeof(double) + sizeof(conn_t)));
+
+ for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) {
+ for (auto iDim = 0u; iDim < dataSorter->GetnDim(); iDim++)
+ AppendBytes(buffer, static_cast(dataSorter->GetData(iDim, iPoint)));
+ AppendBytes(buffer, static_cast(iPoint + pointOffset));
}
+ WriteMPIStringAll(buffer);
/*--- Section 4: markers. Mirrors CSU2MeshFileWriter: the marker connectivity
is not available from the data sorter, so it is read back from the
@@ -210,8 +178,9 @@ void CSU2MeshBinaryFileWriter::WriteData(string val_filename) {
SU2_COMPONENT::SU2_DEF) right after reading the original mesh. Only the
master rank does this work, exactly as for the ASCII format. ---*/
+ buffer.clear();
+
if (rank == MASTER_NODE) {
- FILE* f = OpenAppend(val_filename);
string str = "boundary";
if (nZone > 1) str += "_" + PrintingToolbox::to_string(iZone);
@@ -228,7 +197,7 @@ void CSU2MeshBinaryFileWriter::WriteData(string val_filename) {
text_line.erase(0, 6);
const int32_t nMarker_ = atoi(text_line.c_str());
- fwrite(&nMarker_, sizeof(nMarker_), 1, f);
+ AppendBytes(buffer, nMarker_);
for (int iMarker = 0; iMarker < nMarker_; iMarker++) {
@@ -250,7 +219,7 @@ void CSU2MeshBinaryFileWriter::WriteData(string val_filename) {
are independent and must not silently drift apart. ---*/
char name_buf[SU2_BINARY_STRING_SIZE] = {};
strncpy(name_buf, Marker_Tag.c_str(), SU2_BINARY_STRING_SIZE - 1);
- fwrite(name_buf, sizeof(char), SU2_BINARY_STRING_SIZE, f);
+ buffer.append(name_buf, SU2_BINARY_STRING_SIZE);
getline(input_file, text_line);
text_line.erase(0, 13);
@@ -277,32 +246,35 @@ void CSU2MeshBinaryFileWriter::WriteData(string val_filename) {
}
auto nElemBoundConn = static_cast(nElem_Bound_);
- fwrite(&nElemBoundConn, sizeof(nElemBoundConn), 1, f);
+ AppendBytes(buffer, nElemBoundConn);
- unsigned long running = 0;
+ unsigned long boundOffset = 0;
for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound_; iElem_Bound++) {
- conn_t value = running;
- fwrite(&value, sizeof(value), 1, f);
- running += nPointsOfElementType(vtkTypes[iElem_Bound]) + 1;
+ conn_t value = boundOffset;
+ AppendBytes(buffer, value);
+ boundOffset += nPointsOfElementType(vtkTypes[iElem_Bound]) + 1;
}
- conn_t sentinel = running;
- fwrite(&sentinel, sizeof(sentinel), 1, f);
+ conn_t sentinel = boundOffset;
+ AppendBytes(buffer, sentinel);
for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound_; iElem_Bound++) {
conn_t vtkType = vtkTypes[iElem_Bound];
- fwrite(&vtkType, sizeof(vtkType), 1, f);
+ AppendBytes(buffer, vtkType);
const auto nPointsElem = nPointsOfElementType(vtkTypes[iElem_Bound]);
for (unsigned short iNode = 0; iNode < nPointsElem; iNode++) {
conn_t node = nodes[iElem_Bound][iNode];
- fwrite(&node, sizeof(node), 1, f);
+ AppendBytes(buffer, node);
}
}
}
}
input_file.close();
- fclose(f);
}
- SU2_MPI::Barrier(SU2_MPI::GetComm());
+ /*--- Only the master node has the markers, the other ranks write nothing. ---*/
+
+ WriteMPIString(buffer, MASTER_NODE);
+
+ CloseMPIFile();
}
diff --git a/SU2_CFD/src/output/filewriter/CSU2MeshFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2MeshFileWriter.cpp
index c471110cc9ce..d33dc89d7355 100644
--- a/SU2_CFD/src/output/filewriter/CSU2MeshFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CSU2MeshFileWriter.cpp
@@ -26,6 +26,7 @@
*/
#include "../../../include/output/filewriter/CSU2MeshFileWriter.hpp"
+
#include "../../../../Common/include/toolboxes/printing_toolbox.hpp"
const string CSU2MeshFileWriter::fileExt = ".su2";
@@ -36,129 +37,81 @@ CSU2MeshFileWriter::CSU2MeshFileWriter(CParallelDataSorter *valDataSorter,
void CSU2MeshFileWriter::WriteData(string val_filename) {
- ofstream output_file;
+ /*--- For multizone cases all zones are written into one file, the zones after the first are appended. ---*/
- /*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/
- val_filename.append(fileExt);
+ OpenMPIFile(val_filename, iZone != 0);
- /*--- Only the FIRST node writes the header (it does not matter if that is the master). ---*/
+ /*--- Write the header. ---*/
- if (rank == 0) {
- /*--- For multizone-cases this only works if the all zonal meshes are in one file.
- If the meshes are separate for each zone another solution has to be found. ---*/
- if (iZone==0) {
- output_file.open(val_filename);
- } else {
- output_file.open(val_filename, ios::app);
- }
+ ostringstream header;
- if (iZone==0 && nZone>1) {
- output_file << "NZONE= " << nZone << endl;
- }
+ if (iZone == 0 && nZone > 1) header << "NZONE= " << nZone << endl;
+ if (nZone > 1) header << "IZONE= " << iZone + 1 << endl;
- if (nZone > 1){
- output_file << "IZONE= " << iZone+1 << endl;
- }
+ header << "NDIME= " << dataSorter->GetnDim() << endl;
+ header << "NELEM= " << dataSorter->GetnElemGlobal() << endl;
- /*--- Write dimensions data. ---*/
+ WriteMPIString(header.str(), MASTER_NODE);
- output_file << "NDIME= " << dataSorter->GetnDim() << endl;
+ /*--- Each rank formats the data of its own elements and points into a string, and all ranks then write their
+ strings to the file at the same time, one after the other in rank order. The global index of an element or
+ point is its local index plus the number of elements or points of the ranks before this one. ---*/
- output_file << "NELEM= " << dataSorter->GetnElemGlobal() << endl;
+ ostringstream data;
- output_file.close();
- }
-
- unsigned long nElem = 0, offset = 0;
+ /*--- Write the connectivity, the type of each element is written before its nodes. ---*/
- for (int iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- output_file.open(val_filename, ios::app);
+ unsigned long nElem = 0;
+ for (auto type : {TRIANGLE, QUADRILATERAL, TETRAHEDRON, HEXAHEDRON, PRISM, PYRAMID})
+ nElem += dataSorter->GetnElem(type);
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(TRIANGLE); iElem++) {
- output_file << "5\t";
- for (auto iNode = 0u; iNode < N_POINTS_TRIANGLE; ++iNode)
- output_file << dataSorter->GetElemConnectivity(TRIANGLE, iElem, iNode) - 1 << "\t";
- output_file << nElem + offset << "\n"; nElem++;
- }
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(QUADRILATERAL); iElem++) {
- output_file << "9\t";
- for (auto iNode = 0u; iNode < N_POINTS_QUADRILATERAL; ++iNode)
- output_file << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, iNode) - 1 << "\t";
- output_file << nElem + offset << "\n"; nElem++;
- }
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(TETRAHEDRON); iElem++) {
- output_file << "10\t";
- for (auto iNode = 0u; iNode < N_POINTS_TETRAHEDRON; ++iNode)
- output_file << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, iNode) - 1 << "\t";
- output_file << nElem + offset << "\n"; nElem++;
- }
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(HEXAHEDRON); iElem++) {
- output_file << "12\t";
- for (auto iNode = 0u; iNode < N_POINTS_HEXAHEDRON; ++iNode)
- output_file << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, iNode) - 1 << "\t";
- output_file << nElem + offset << "\n"; nElem++;
- }
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(PRISM); iElem++) {
- output_file << "13\t";
- for (auto iNode = 0u; iNode < N_POINTS_PRISM; ++iNode)
- output_file << dataSorter->GetElemConnectivity(PRISM, iElem, iNode) - 1 << "\t";
- output_file << nElem + offset << "\n"; nElem++;
- }
-
- for (auto iElem = 0ul; iElem < dataSorter->GetnElem(PYRAMID); iElem++) {
- output_file << "14\t";
- for (auto iNode = 0u; iNode < N_POINTS_PYRAMID; ++iNode)
- output_file << dataSorter->GetElemConnectivity(PYRAMID, iElem, iNode) - 1 << "\t";
- output_file << nElem + offset << "\n"; nElem++;
- }
+ unsigned long offset = GetRankOffset(nElem).first;
- output_file.close();
+ nElem = 0;
+ for (auto type : {TRIANGLE, QUADRILATERAL, TETRAHEDRON, HEXAHEDRON, PRISM, PYRAMID}) {
+ const auto nPoints = nPointsOfElementType(type);
+ for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) {
+ data << type << "\t";
+ for (auto iNode = 0u; iNode < nPoints; ++iNode)
+ data << dataSorter->GetElemConnectivity(type, iElem, iNode) - 1 << "\t";
+ data << nElem + offset << "\n"; nElem++;
}
-
- /*--- Communicate offset, implies a barrier. ---*/
- SU2_MPI::Allreduce(&nElem, &offset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm());
}
+ WriteMPIStringAll(data.str());
+
/*--- Write the node coordinates. ---*/
- if (rank == 0) {
- output_file.open(val_filename, ios::app);
- output_file << "NPOIN= " << dataSorter->GetnPointsGlobal() << "\n";
- output_file.close();
- }
+ WriteMPIString("NPOIN= " + to_string(dataSorter->GetnPointsGlobal()) + "\n", MASTER_NODE);
- unsigned long myPoint = 0; offset = 0;
+ offset = GetRankOffset(dataSorter->GetnPoints()).first;
- for (int iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
- output_file.open(val_filename, ios::app);
- output_file.precision(15);
+ data.str("");
+ data.clear();
+ data.precision(15);
+ data << scientific;
- for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) {
+ for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) {
- /*--- Loop over the coordinates and write the values to file. ---*/
+ /*--- Loop over the coordinates and write the values to file. ---*/
- for (auto iDim = 0u; iDim < dataSorter->GetnDim(); iDim++) {
- output_file << scientific << dataSorter->GetData(iDim, iPoint) << "\t";
- }
+ for (auto iDim = 0u; iDim < dataSorter->GetnDim(); iDim++) {
+ data << dataSorter->GetData(iDim, iPoint) << "\t";
+ }
- /*--- Write global index. ---*/
+ /*--- Write global index. ---*/
- output_file << iPoint + offset << "\n";
- myPoint++;
- }
+ data << iPoint + offset << "\n";
+ }
- output_file.close();
- }
+ WriteMPIStringAll(data.str());
- /*--- Communicate offset, implies a barrier. ---*/
- SU2_MPI::Allreduce(&myPoint, &offset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm());
- }
+ /*--- The boundaries are copied from the file written by the mesh deformation, only the master node has them.
+ This is the last thing written to the file. ---*/
- if (rank == MASTER_NODE) {
+ ostringstream boundaries;
- output_file.open(val_filename, ios::app);
+ if (rank == MASTER_NODE) {
/*--- Read the boundary information ---*/
@@ -186,7 +139,7 @@ void CSU2MeshFileWriter::WriteData(string val_filename) {
text_line.erase(0,6);
const auto nMarker_ = atoi(text_line.c_str());
- output_file << "NMARK= " << nMarker_ << endl;
+ boundaries << "NMARK= " << nMarker_ << endl;
for (auto iMarker = 0; iMarker < nMarker_; iMarker++) {
@@ -208,15 +161,15 @@ void CSU2MeshFileWriter::WriteData(string val_filename) {
text_line.erase(0,13);
const auto nElem_Bound_ = atoi(text_line.c_str());
- output_file << "MARKER_TAG= " << Marker_Tag << endl;
- output_file << "MARKER_ELEMS= " << nElem_Bound_<< endl;
+ boundaries << "MARKER_TAG= " << Marker_Tag << endl;
+ boundaries << "MARKER_ELEMS= " << nElem_Bound_<< endl;
getline (input_file, text_line);
text_line.erase(0,8);
const auto SendTo = atoi(text_line.c_str());
if (Marker_Tag == "SEND_RECEIVE") {
- output_file << "SEND_TO= " << SendTo << endl;
+ boundaries << "SEND_TO= " << SendTo << endl;
}
for (auto iElem_Bound = 0; iElem_Bound < nElem_Bound_; iElem_Bound++) {
@@ -226,30 +179,33 @@ void CSU2MeshFileWriter::WriteData(string val_filename) {
unsigned short VTK_Type;
bound_line >> VTK_Type;
- output_file << VTK_Type;
+ boundaries << VTK_Type;
unsigned long vnodes[4] = {0};
switch (VTK_Type) {
case LINE:
case VERTEX:
bound_line >> vnodes[0]; bound_line >> vnodes[1];
- output_file << "\t" << vnodes[0] << "\t" << vnodes[1] << "\n";
+ boundaries << "\t" << vnodes[0] << "\t" << vnodes[1] << "\n";
break;
case TRIANGLE:
bound_line >> vnodes[0]; bound_line >> vnodes[1]; bound_line >> vnodes[2];
- output_file << "\t" << vnodes[0] << "\t" << vnodes[1] << "\t" << vnodes[2] << "\n";
+ boundaries << "\t" << vnodes[0] << "\t" << vnodes[1] << "\t" << vnodes[2] << "\n";
break;
case QUADRILATERAL:
bound_line >> vnodes[0]; bound_line >> vnodes[1]; bound_line >> vnodes[2]; bound_line >> vnodes[3];
- output_file << "\t" << vnodes[0] << "\t" << vnodes[1] << "\t" << vnodes[2] << "\t" << vnodes[3] << "\n";
+ boundaries << "\t" << vnodes[0] << "\t" << vnodes[1] << "\t" << vnodes[2] << "\t" << vnodes[3] << "\n";
break;
}
}
}
}
- output_file.close();
}
- SU2_MPI::Barrier(SU2_MPI::GetComm());
+ /*--- Only the master node has this text, the other ranks write nothing. ---*/
+
+ WriteMPIString(boundaries.str(), MASTER_NODE);
+
+ CloseMPIFile();
}
diff --git a/SU2_CFD/src/output/filewriter/CTecplotFileWriter.cpp b/SU2_CFD/src/output/filewriter/CTecplotFileWriter.cpp
index 4cfe9c8f1e41..9077b049134f 100644
--- a/SU2_CFD/src/output/filewriter/CTecplotFileWriter.cpp
+++ b/SU2_CFD/src/output/filewriter/CTecplotFileWriter.cpp
@@ -37,29 +37,16 @@ CTecplotFileWriter::~CTecplotFileWriter()= default;
void CTecplotFileWriter::WriteData(string val_filename){
- /*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/
- val_filename.append(fileExt);
-
if (!dataSorter->GetConnectivitySorted()){
SU2_MPI::Error("Connectivity must be sorted.", CURRENT_FUNCTION);
}
const vector fieldNames = dataSorter->GetFieldNames();
- unsigned short iVar;
+ unsigned long iVar;
unsigned long iPoint, iElem;
- int iProcessor;
-
- ofstream Tecplot_File;
-
- fileSize = 0.0;
-
- /*--- Set a timer for the file writing. ---*/
-
- startTime = SU2_MPI::Wtime();
-
/*--- Reduce the total number of each element. ---*/
unsigned long nParallel_Line = dataSorter->GetnElem(LINE),
@@ -80,150 +67,126 @@ void CTecplotFileWriter::WriteData(string val_filename){
/*--- Open Tecplot ASCII file and write the header. ---*/
- if (rank == MASTER_NODE) {
- Tecplot_File.open(val_filename.c_str(), ios::out);
- Tecplot_File.precision(6);
- Tecplot_File << "TITLE = \"Visualization of the solution\"" << endl;
+ OpenMPIFile(val_filename);
- Tecplot_File << "VARIABLES = ";
- for (iVar = 0; iVar < fieldNames.size()-1; iVar++) {
- Tecplot_File << "\"" << fieldNames[iVar] << "\",";
- }
- Tecplot_File << "\"" << fieldNames[fieldNames.size()-1] << "\"" << endl;
+ ostringstream header;
+ header.precision(6);
+ header << "TITLE = \"Visualization of the solution\"" << endl;
- /*--- Write the header ---*/
+ header << "VARIABLES = ";
+ for (iVar = 0; iVar < fieldNames.size()-1; iVar++) {
+ header << "\"" << fieldNames[iVar] << "\",";
+ }
+ header << "\"" << fieldNames[fieldNames.size()-1] << "\"" << endl;
- Tecplot_File << "ZONE ";
+ header << "ZONE ";
- if (timeStep > 0.0){
- Tecplot_File << "STRANDID="< 0.0){
+ header << "STRANDID="<GetnPointsGlobal() <<", ELEMENTS= "<< dataSorter->GetnElemGlobal();
+ header << "NODES= "<< dataSorter->GetnPointsGlobal() <<", ELEMENTS= "<< dataSorter->GetnElemGlobal();
- if (dataSorter->GetnDim() == 3){
- if ((nTot_Quad > 0 || nTot_Tria > 0) && (nTot_Hexa + nTot_Pris + nTot_Pyra + nTot_Tetr == 0)){
- Tecplot_File << ", DATAPACKING=POINT, ZONETYPE=FEQUADRILATERAL" << endl;
- }
- else {
- Tecplot_File <<", DATAPACKING=POINT, ZONETYPE=FEBRICK"<< endl;
- }
+ if (dataSorter->GetnDim() == 3){
+ if ((nTot_Quad > 0 || nTot_Tria > 0) && (nTot_Hexa + nTot_Pris + nTot_Pyra + nTot_Tetr == 0)){
+ header << ", DATAPACKING=POINT, ZONETYPE=FEQUADRILATERAL" << endl;
}
else {
- if (nTot_Line > 0 && (nTot_Tria + nTot_Quad == 0)){
- Tecplot_File << ", DATAPACKING=POINT, ZONETYPE=FELINESEG"<< endl;
- }
- else{
- Tecplot_File << ", DATAPACKING=POINT, ZONETYPE=FEQUADRILATERAL"<< endl;
- }
+ header <<", DATAPACKING=POINT, ZONETYPE=FEBRICK"<< endl;
+ }
+ }
+ else {
+ if (nTot_Line > 0 && (nTot_Tria + nTot_Quad == 0)){
+ header << ", DATAPACKING=POINT, ZONETYPE=FELINESEG"<< endl;
+ }
+ else{
+ header << ", DATAPACKING=POINT, ZONETYPE=FEQUADRILATERAL"<< endl;
}
- Tecplot_File.close();
}
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
+ WriteMPIString(header.str(), MASTER_NODE);
- /*--- Each processor opens the file. ---*/
+ /*--- Each rank formats the data of its own points and elements into a string, and all ranks then write
+ their strings to the file at the same time, one after the other in rank order. ---*/
- Tecplot_File.open(val_filename.c_str(), ios::out | ios::app);
+ ostringstream data;
+ data.precision(6);
+ data << scientific;
/*--- Write surface and volumetric solution data. ---*/
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
-
- /*--- Write the node data from this proc ---*/
-
+ for (iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
+ for (iVar = 0; iVar < fieldNames.size(); iVar++)
+ data << dataSorter->GetData(iVar, iPoint) << "\t";
+ data << endl;
+ }
- for (iPoint = 0; iPoint < dataSorter->GetnPoints(); iPoint++) {
- for (iVar = 0; iVar < fieldNames.size(); iVar++)
- Tecplot_File << scientific << dataSorter->GetData(iVar, iPoint) << "\t";
- Tecplot_File << endl;
- }
- }
+ WriteMPIStringAll(data.str());
- Tecplot_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
- }
+ data.str("");
+ data.clear();
/*--- Write connectivity data. ---*/
- for (iProcessor = 0; iProcessor < size; iProcessor++) {
- if (rank == iProcessor) {
+ {
+ {
for (iElem = 0; iElem < nParallel_Line; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(LINE, iElem, 0) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(LINE, iElem, 1)<< "\n";
+ data << dataSorter->GetElemConnectivity(LINE, iElem, 0) << "\t";
+ data << dataSorter->GetElemConnectivity(LINE, iElem, 1)<< "\n";
}
for (iElem = 0; iElem < nParallel_Tria; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 0) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 1) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 2) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 2) << "\n";
+ data << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 0) << "\t";
+ data << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 1) << "\t";
+ data << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 2) << "\t";
+ data << dataSorter->GetElemConnectivity(TRIANGLE, iElem, 2) << "\n";
}
for (iElem = 0; iElem < nParallel_Quad; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 0) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 1) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 2) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 3) << "\n";
+ data << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 0) << "\t";
+ data << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 1) << "\t";
+ data << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 2) << "\t";
+ data << dataSorter->GetElemConnectivity(QUADRILATERAL, iElem, 3) << "\n";
}
for (iElem = 0; iElem < nParallel_Tetr; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 1) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 2) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 2) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\n";
+ data << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 1) << "\t";
+ data << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 2) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 2) << "\t";
+ data << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\t";
+ data << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\t" << dataSorter->GetElemConnectivity(TETRAHEDRON, iElem, 3) << "\n";
}
for (iElem = 0; iElem < nParallel_Hexa; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 1) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 2) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 3) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 5) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 6) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 7) << "\n";
+ data << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 1) << "\t";
+ data << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 2) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 3) << "\t";
+ data << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 5) << "\t";
+ data << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 6) << "\t" << dataSorter->GetElemConnectivity(HEXAHEDRON, iElem, 7) << "\n";
}
for (iElem = 0; iElem < nParallel_Pris; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(PRISM, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 1) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(PRISM, iElem, 1) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 2) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(PRISM, iElem, 3) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 4) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(PRISM, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 5) << "\n";
+ data << dataSorter->GetElemConnectivity(PRISM, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 1) << "\t";
+ data << dataSorter->GetElemConnectivity(PRISM, iElem, 1) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 2) << "\t";
+ data << dataSorter->GetElemConnectivity(PRISM, iElem, 3) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 4) << "\t";
+ data << dataSorter->GetElemConnectivity(PRISM, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(PRISM, iElem, 5) << "\n";
}
for (iElem = 0; iElem < nParallel_Pyra; iElem++) {
- Tecplot_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 1) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 2) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 3) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\t";
- Tecplot_File << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\n";
+ data << dataSorter->GetElemConnectivity(PYRAMID, iElem, 0) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 1) << "\t";
+ data << dataSorter->GetElemConnectivity(PYRAMID, iElem, 2) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 3) << "\t";
+ data << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\t";
+ data << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\t" << dataSorter->GetElemConnectivity(PYRAMID, iElem, 4) << "\n";
}
}
- Tecplot_File.flush();
-#ifdef HAVE_MPI
- SU2_MPI::Barrier(SU2_MPI::GetComm());
-#endif
}
- Tecplot_File.close();
-
- /*--- Compute and store the write time. ---*/
-
- stopTime = SU2_MPI::Wtime();
-
- usedTime = stopTime-startTime;
-
- fileSize = DetermineFilesize(val_filename);
-
- /*--- Compute and store the bandwidth ---*/
+ WriteMPIStringAll(data.str());
- bandwidth = fileSize/(1.0e6)/usedTime;
+ CloseMPIFile();
}
diff --git a/TestCases/disc_adj_euler/cylinder3D/inv_cylinder3D.cfg b/TestCases/disc_adj_euler/cylinder3D/inv_cylinder3D.cfg
index f575e1a10f18..ef0f8ab93d86 100644
--- a/TestCases/disc_adj_euler/cylinder3D/inv_cylinder3D.cfg
+++ b/TestCases/disc_adj_euler/cylinder3D/inv_cylinder3D.cfg
@@ -99,6 +99,9 @@ CONV_CAUCHY_EPS= 1E-10
%
MESH_FILENAME= cylinder3D.cgns
MESH_FORMAT= CGNS
+%
+% Also write a CGNS file, to test the CGNS writer of the AD builds
+OUTPUT_FILES= (RESTART, CGNS)
MESH_OUT_FILENAME= mesh_out
SOLUTION_FILENAME= solution_flow
SOLUTION_ADJ_FILENAME= solution_adj
diff --git a/TestCases/output_writers/ascii_output.cfg b/TestCases/output_writers/ascii_output.cfg
new file mode 100644
index 000000000000..d14519b58f55
--- /dev/null
+++ b/TestCases/output_writers/ascii_output.cfg
@@ -0,0 +1,42 @@
+% Test of the ASCII output writers, which write in parallel with MPI-IO.
+% The solution of vandv/rans/flatplate is written without iterating (ITER= 0):
+% - SU2_CFD writes RESTART_ASCII,
+% - SU2_SOL writes PARAVIEW_ASCII and TECPLOT_ASCII (volume and surface),
+% - SU2_DEF (no deformation) writes the SU2 mesh file.
+% The files are compared with reference files written on 2 ranks by the previous writers,
+% in which the ranks wrote one after the other.
+%
+% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------%
+SOLVER= RANS
+KIND_TURB_MODEL= SST
+SST_OPTIONS= V1994m
+RESTART_SOL= YES
+% ----------- COMPRESSIBLE AND INCOMPRESSIBLE FREE-STREAM DEFINITION ----------%
+MACH_NUMBER= 0.2
+AOA= 0.0
+FREESTREAM_TEMPERATURE= 300.0
+REYNOLDS_NUMBER= 5000000.0
+REYNOLDS_LENGTH= 1.0
+FREESTREAM_TURBULENCEINTENSITY= 0.00038729
+FREESTREAM_TURB2LAMVISCRATIO= 0.009
+% -------------------- BOUNDARY CONDITION DEFINITION --------------------------%
+MARKER_HEATFLUX= ( wall, 0.0 )
+MARKER_FAR= ( farfield )
+MARKER_INLET= ( inlet, 302.4, 117691.7874, 1.0, 0.0, 0.0 )
+MARKER_OUTLET= ( outlet, 114455.0 )
+MARKER_SYM= ( symmetry )
+MARKER_PLOTTING= ( wall )
+% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------%
+NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES
+CONV_NUM_METHOD_FLOW= ROE
+CONV_NUM_METHOD_TURB= SCALAR_UPWIND
+ITER= 0
+% ------------------------- INPUT/OUTPUT INFORMATION --------------------------%
+MESH_FILENAME= ../vandv/rans/flatplate/mesh_flatplate_turb_035x025.su2
+MESH_FORMAT= SU2
+MESH_OUT_FILENAME= mesh_out
+SOLUTION_FILENAME= ../vandv/rans/flatplate/solution_flow
+RESTART_FILENAME= restart_flow
+VOLUME_FILENAME= flow
+SURFACE_FILENAME= surface_flow
+OUTPUT_FILES= (RESTART_ASCII, PARAVIEW_ASCII, TECPLOT_ASCII, SURFACE_PARAVIEW_ASCII, SURFACE_TECPLOT_ASCII)
diff --git a/TestCases/output_writers/cgns_mesh_readback.cfg b/TestCases/output_writers/cgns_mesh_readback.cfg
new file mode 100644
index 000000000000..9b3cf5d0af30
--- /dev/null
+++ b/TestCases/output_writers/cgns_mesh_readback.cfg
@@ -0,0 +1,53 @@
+% Read back the volume CGNS file written by cgns_output.cfg (run that test first) as mesh, and restart
+% from the restart file written by the same run. The boundaries of the file are named as the markers,
+% the points are in the order of the restart file. The residuals are those of the same restart on
+% the original SU2 mesh.
+%
+% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------%
+SOLVER= RANS
+KIND_TURB_MODEL= SST
+SST_OPTIONS= V1994m
+RESTART_SOL= YES
+% ----------- COMPRESSIBLE AND INCOMPRESSIBLE FREE-STREAM DEFINITION ----------%
+MACH_NUMBER= 0.2
+AOA= 0.0
+FREESTREAM_TEMPERATURE= 300.0
+REYNOLDS_NUMBER= 5000000.0
+REYNOLDS_LENGTH= 1.0
+FREESTREAM_TURBULENCEINTENSITY= 0.00038729
+FREESTREAM_TURB2LAMVISCRATIO= 0.009
+% -------------------- BOUNDARY CONDITION DEFINITION --------------------------%
+MARKER_HEATFLUX= ( wall, 0.0 )
+MARKER_FAR= ( farfield )
+MARKER_INLET= ( inlet, 302.4, 117691.7874, 1.0, 0.0, 0.0 )
+MARKER_OUTLET= ( outlet, 114455.0 )
+MARKER_SYM= ( symmetry )
+MARKER_PLOTTING= ( wall )
+MARKER_MONITORING= ( wall )
+% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------%
+NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES
+CFL_NUMBER= 10.0
+CFL_ADAPT= NO
+ITER= 3
+% ------------------------ LINEAR SOLVER DEFINITION ---------------------------%
+LINEAR_SOLVER= FGMRES
+LINEAR_SOLVER_PREC= ILU
+LINEAR_SOLVER_ERROR= 1E-6
+LINEAR_SOLVER_ITER= 10
+% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------%
+CONV_NUM_METHOD_FLOW= ROE
+MUSCL_FLOW= YES
+SLOPE_LIMITER_FLOW= NONE
+TIME_DISCRE_FLOW= EULER_IMPLICIT
+% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------%
+CONV_NUM_METHOD_TURB= SCALAR_UPWIND
+MUSCL_TURB= NO
+TIME_DISCRE_TURB= EULER_IMPLICIT
+% ------------------------- INPUT/OUTPUT INFORMATION --------------------------%
+MESH_FILENAME= cgns_flow.cgns
+MESH_FORMAT= CGNS
+SOLUTION_FILENAME= cgns_restart_flow
+CONV_FILENAME= cgns_readback_history
+SCREEN_OUTPUT= INNER_ITER WALL_TIME RMS_DENSITY RMS_MOMENTUM-X RMS_ENERGY RMS_TKE RMS_DISSIPATION DRAG
+HISTORY_OUTPUT= ITER RMS_RES AERO_COEFF
+OUTPUT_FILES= NONE
diff --git a/TestCases/output_writers/cgns_mesh_readback_bend.cfg b/TestCases/output_writers/cgns_mesh_readback_bend.cfg
new file mode 100644
index 000000000000..c9ebaeb49b5f
--- /dev/null
+++ b/TestCases/output_writers/cgns_mesh_readback_bend.cfg
@@ -0,0 +1,50 @@
+% Read back the volume CGNS file written by cgns_output_bend.cfg (run that test first) as mesh, and
+% restart from the restart file written by the same run. The boundaries of the file are named as the
+% markers, the points are in the order of the restart file. The residuals are those of the same
+% restart on the original mesh.
+%
+% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------%
+SOLVER= INC_NAVIER_STOKES
+KIND_TURB_MODEL= NONE
+RESTART_SOL= YES
+% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------%
+INC_DENSITY_MODEL= CONSTANT
+INC_ENERGY_EQUATION= NO
+INC_DENSITY_INIT= 1.2886
+INC_VELOCITY_INIT= ( 0.1, 0.0, 0.0 )
+INC_TEMPERATURE_INIT= 288.15
+INC_NONDIM= INITIAL_VALUES
+INC_INLET_TYPE= VELOCITY_INLET
+INC_OUTLET_TYPE= PRESSURE_OUTLET
+% --------------------------- VISCOSITY MODEL ---------------------------------%
+VISCOSITY_MODEL= CONSTANT_VISCOSITY
+MU_CONSTANT= 1.716E-5
+% -------------------- BOUNDARY CONDITION DEFINITION --------------------------%
+MARKER_HEATFLUX= ( WALL1, 0.0, WALL2, 0.0 )
+MARKER_INLET= ( INLET, 288.15, 0.1, 1.0, 0.0, 0.0 )
+MARKER_OUTLET= ( OUTLET, 0.0 )
+MARKER_SYM= ( SYMMETRY )
+MARKER_PLOTTING= ( WALL1 )
+MARKER_MONITORING= ( WALL1, WALL2 )
+% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------%
+NUM_METHOD_GRAD= GREEN_GAUSS
+CFL_NUMBER= 100.0
+ITER= 3
+% ------------------------ LINEAR SOLVER DEFINITION ---------------------------%
+LINEAR_SOLVER= FGMRES
+LINEAR_SOLVER_PREC= ILU
+LINEAR_SOLVER_ERROR= 1E-6
+LINEAR_SOLVER_ITER= 10
+% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------%
+CONV_NUM_METHOD_FLOW= FDS
+MUSCL_FLOW= YES
+SLOPE_LIMITER_FLOW= NONE
+TIME_DISCRE_FLOW= EULER_IMPLICIT
+% ------------------------- INPUT/OUTPUT INFORMATION --------------------------%
+MESH_FILENAME= cgns_flow_bend.cgns
+MESH_FORMAT= CGNS
+SOLUTION_FILENAME= cgns_restart_flow_bend
+CONV_FILENAME= cgns_readback_history_bend
+SCREEN_OUTPUT= INNER_ITER WALL_TIME RMS_PRESSURE RMS_VELOCITY-X RMS_VELOCITY-Y RMS_VELOCITY-Z DRAG
+HISTORY_OUTPUT= ITER RMS_RES AERO_COEFF
+OUTPUT_FILES= NONE
diff --git a/TestCases/output_writers/cgns_output.cfg b/TestCases/output_writers/cgns_output.cfg
new file mode 100644
index 000000000000..d57b9552d278
--- /dev/null
+++ b/TestCases/output_writers/cgns_output.cfg
@@ -0,0 +1,58 @@
+% Test of the CGNS output writer, which writes in parallel with the cgp_* API.
+% The flat plate of vandv/rans/flatplate is run for a few iterations from the free stream. At the end,
+% SU2_CFD writes the volume CGNS file (boundaries named as the markers), the surface CGNS file
+% (one zone per plotted marker) and the restart file.
+% The test cgns_mesh_readback.cfg then reads the volume CGNS file as mesh and restarts from this
+% restart file, which checks the coordinates, the connectivity, the order of the points and the
+% boundary markers written in the CGNS file.
+%
+% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------%
+SOLVER= RANS
+KIND_TURB_MODEL= SST
+SST_OPTIONS= V1994m
+RESTART_SOL= NO
+% ----------- COMPRESSIBLE AND INCOMPRESSIBLE FREE-STREAM DEFINITION ----------%
+MACH_NUMBER= 0.2
+AOA= 0.0
+FREESTREAM_TEMPERATURE= 300.0
+REYNOLDS_NUMBER= 5000000.0
+REYNOLDS_LENGTH= 1.0
+FREESTREAM_TURBULENCEINTENSITY= 0.00038729
+FREESTREAM_TURB2LAMVISCRATIO= 0.009
+% -------------------- BOUNDARY CONDITION DEFINITION --------------------------%
+MARKER_HEATFLUX= ( wall, 0.0 )
+MARKER_FAR= ( farfield )
+MARKER_INLET= ( inlet, 302.4, 117691.7874, 1.0, 0.0, 0.0 )
+MARKER_OUTLET= ( outlet, 114455.0 )
+MARKER_SYM= ( symmetry )
+MARKER_PLOTTING= ( wall, symmetry )
+MARKER_MONITORING= ( wall )
+% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------%
+NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES
+CFL_NUMBER= 10.0
+CFL_ADAPT= NO
+ITER= 3
+% ------------------------ LINEAR SOLVER DEFINITION ---------------------------%
+LINEAR_SOLVER= FGMRES
+LINEAR_SOLVER_PREC= ILU
+LINEAR_SOLVER_ERROR= 1E-6
+LINEAR_SOLVER_ITER= 10
+% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------%
+CONV_NUM_METHOD_FLOW= ROE
+MUSCL_FLOW= YES
+SLOPE_LIMITER_FLOW= NONE
+TIME_DISCRE_FLOW= EULER_IMPLICIT
+% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------%
+CONV_NUM_METHOD_TURB= SCALAR_UPWIND
+MUSCL_TURB= NO
+TIME_DISCRE_TURB= EULER_IMPLICIT
+% ------------------------- INPUT/OUTPUT INFORMATION --------------------------%
+MESH_FILENAME= ../vandv/rans/flatplate/mesh_flatplate_turb_035x025.su2
+MESH_FORMAT= SU2
+RESTART_FILENAME= cgns_restart_flow
+VOLUME_FILENAME= cgns_flow
+SURFACE_FILENAME= cgns_surface_flow
+CONV_FILENAME= cgns_history
+SCREEN_OUTPUT= INNER_ITER WALL_TIME RMS_DENSITY RMS_MOMENTUM-X RMS_ENERGY RMS_TKE RMS_DISSIPATION DRAG
+HISTORY_OUTPUT= ITER RMS_RES AERO_COEFF
+OUTPUT_FILES= (RESTART, CGNS, SURFACE_CGNS)
diff --git a/TestCases/output_writers/cgns_output_bend.cfg b/TestCases/output_writers/cgns_output_bend.cfg
new file mode 100644
index 000000000000..d3d021661bb1
--- /dev/null
+++ b/TestCases/output_writers/cgns_output_bend.cfg
@@ -0,0 +1,56 @@
+% Test of the CGNS output writer on a 3D mesh with mixed elements (tetrahedra, pyramids, prisms and
+% hexahedra; some markers have both triangles and quadrilaterals, which are written as MIXED sections).
+% The laminar bend of incomp_navierstokes/bend is run for a few iterations. At the end, SU2_CFD writes
+% the volume CGNS file (boundaries named as the markers), the surface CGNS file (one zone per plotted
+% marker) and the restart file.
+% The test cgns_mesh_readback_bend.cfg then reads the volume CGNS file as mesh and restarts from this
+% restart file, which checks the coordinates, the connectivity, the order of the points and the
+% boundary markers written in the CGNS file.
+%
+% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------%
+SOLVER= INC_NAVIER_STOKES
+KIND_TURB_MODEL= NONE
+RESTART_SOL= NO
+% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------%
+INC_DENSITY_MODEL= CONSTANT
+INC_ENERGY_EQUATION= NO
+INC_DENSITY_INIT= 1.2886
+INC_VELOCITY_INIT= ( 0.1, 0.0, 0.0 )
+INC_TEMPERATURE_INIT= 288.15
+INC_NONDIM= INITIAL_VALUES
+INC_INLET_TYPE= VELOCITY_INLET
+INC_OUTLET_TYPE= PRESSURE_OUTLET
+% --------------------------- VISCOSITY MODEL ---------------------------------%
+VISCOSITY_MODEL= CONSTANT_VISCOSITY
+MU_CONSTANT= 1.716E-5
+% -------------------- BOUNDARY CONDITION DEFINITION --------------------------%
+MARKER_HEATFLUX= ( WALL1, 0.0, WALL2, 0.0 )
+MARKER_INLET= ( INLET, 288.15, 0.1, 1.0, 0.0, 0.0 )
+MARKER_OUTLET= ( OUTLET, 0.0 )
+MARKER_SYM= ( SYMMETRY )
+MARKER_PLOTTING= ( WALL1, WALL2, SYMMETRY )
+MARKER_MONITORING= ( WALL1, WALL2 )
+% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------%
+NUM_METHOD_GRAD= GREEN_GAUSS
+CFL_NUMBER= 100.0
+ITER= 3
+% ------------------------ LINEAR SOLVER DEFINITION ---------------------------%
+LINEAR_SOLVER= FGMRES
+LINEAR_SOLVER_PREC= ILU
+LINEAR_SOLVER_ERROR= 1E-6
+LINEAR_SOLVER_ITER= 10
+% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------%
+CONV_NUM_METHOD_FLOW= FDS
+MUSCL_FLOW= YES
+SLOPE_LIMITER_FLOW= NONE
+TIME_DISCRE_FLOW= EULER_IMPLICIT
+% ------------------------- INPUT/OUTPUT INFORMATION --------------------------%
+MESH_FILENAME= ../incomp_navierstokes/bend/mesh_bend_coarse.cgns
+MESH_FORMAT= CGNS
+RESTART_FILENAME= cgns_restart_flow_bend
+VOLUME_FILENAME= cgns_flow_bend
+SURFACE_FILENAME= cgns_surface_flow_bend
+CONV_FILENAME= cgns_history_bend
+SCREEN_OUTPUT= INNER_ITER WALL_TIME RMS_PRESSURE RMS_VELOCITY-X RMS_VELOCITY-Y RMS_VELOCITY-Z DRAG
+HISTORY_OUTPUT= ITER RMS_RES AERO_COEFF
+OUTPUT_FILES= (RESTART, CGNS, SURFACE_CGNS)
diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py
index e85b5b7be660..b2244e0f433c 100755
--- a/TestCases/parallel_regression.py
+++ b/TestCases/parallel_regression.py
@@ -1885,6 +1885,37 @@ def main():
cgns_writer.new_output = True
test_list.append(cgns_writer)
+ # The CGNS output of a 2D mesh is read back as mesh, with the restart written by the same run.
+ # The read-back test must run after the test that writes the files.
+ cgns_output = TestCase('cgns_output')
+ cgns_output.cfg_dir = "output_writers"
+ cgns_output.cfg_file = "cgns_output.cfg"
+ cgns_output.test_iter = 2
+ cgns_output.test_vals = [-1.684071, 0.454658, 3.802834, -2.624573, 6.956844, 0.091480]
+ test_list.append(cgns_output)
+
+ cgns_mesh_readback = TestCase('cgns_mesh_readback')
+ cgns_mesh_readback.cfg_dir = "output_writers"
+ cgns_mesh_readback.cfg_file = "cgns_mesh_readback.cfg"
+ cgns_mesh_readback.test_iter = 2
+ cgns_mesh_readback.test_vals = [-2.480775, 0.352445, 2.998807, -3.103293, 6.892515, 0.085107]
+ test_list.append(cgns_mesh_readback)
+
+ # Same for a 3D mesh with mixed elements, whose boundaries are written as MIXED sections.
+ cgns_output_bend = TestCase('cgns_output_bend')
+ cgns_output_bend.cfg_dir = "output_writers"
+ cgns_output_bend.cfg_file = "cgns_output_bend.cfg"
+ cgns_output_bend.test_iter = 2
+ cgns_output_bend.test_vals = [-2.413023, -2.210141, -2.545448, -3.057828, 2.475696]
+ test_list.append(cgns_output_bend)
+
+ cgns_mesh_readback_bend = TestCase('cgns_mesh_readback_bend')
+ cgns_mesh_readback_bend.cfg_dir = "output_writers"
+ cgns_mesh_readback_bend.cfg_file = "cgns_mesh_readback_bend.cfg"
+ cgns_mesh_readback_bend.test_iter = 2
+ cgns_mesh_readback_bend.test_vals = [-3.256728, -2.769844, -3.007840, -3.567315, 4.318459]
+ test_list.append(cgns_mesh_readback_bend)
+
######################################
### RUN CHT TEST WITH FILEDIFF ###
######################################
@@ -1933,6 +1964,45 @@ def main():
pass_list.append(stl_writer_test.run_filediff())
test_list.append(stl_writer_test)
+ # The ASCII writers write in parallel, compare their files with those of the previous writers.
+ # ITER= 0 (test_iter = -1): the restart solution is written without iterating.
+ ascii_restart_writer = TestCase('ascii_restart_writer')
+ ascii_restart_writer.cfg_dir = "output_writers"
+ ascii_restart_writer.cfg_file = "ascii_output.cfg"
+ ascii_restart_writer.test_iter = -1
+ ascii_restart_writer.command = TestCase.Command("mpirun -n 2", "SU2_CFD")
+ ascii_restart_writer.timeout = 1600
+ ascii_restart_writer.reference_file = "restart_flow.csv.ref"
+ ascii_restart_writer.test_file = "restart_flow.csv"
+ pass_list.append(ascii_restart_writer.run_filediff())
+ test_list.append(ascii_restart_writer)
+
+ for tag, test_file in [("ascii_paraview_writer", "flow.vtk"), ("ascii_tecplot_writer", "flow.dat"),
+ ("ascii_surface_paraview_writer", "surface_flow.vtk"),
+ ("ascii_surface_tecplot_writer", "surface_flow.dat")]:
+ ascii_writer = TestCase(tag)
+ ascii_writer.cfg_dir = "output_writers"
+ ascii_writer.cfg_file = "ascii_output.cfg"
+ ascii_writer.test_iter = -1
+ ascii_writer.command = TestCase.Command("mpirun -n 2", "SU2_SOL")
+ ascii_writer.timeout = 1600
+ ascii_writer.reference_file = test_file + ".ref"
+ ascii_writer.test_file = test_file
+ pass_list.append(ascii_writer.run_filediff())
+ test_list.append(ascii_writer)
+
+ # SU2 mesh file written by SU2_DEF without deformation
+ ascii_mesh_writer = TestCase('ascii_mesh_writer')
+ ascii_mesh_writer.cfg_dir = "output_writers"
+ ascii_mesh_writer.cfg_file = "ascii_output.cfg"
+ ascii_mesh_writer.test_iter = -1
+ ascii_mesh_writer.command = TestCase.Command("mpirun -n 2", "SU2_DEF")
+ ascii_mesh_writer.timeout = 1600
+ ascii_mesh_writer.reference_file = "mesh_out.su2.ref"
+ ascii_mesh_writer.test_file = "mesh_out.su2"
+ pass_list.append(ascii_mesh_writer.run_filediff())
+ test_list.append(ascii_mesh_writer)
+
######################################
### RUN SU2_DEF TESTS ###
######################################
diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py
index c34fe6f47225..cd7473d0c30e 100755
--- a/TestCases/serial_regression.py
+++ b/TestCases/serial_regression.py
@@ -1288,6 +1288,41 @@ def main():
mms_dg_ns_3d.tol = 0.0001
test_list.append(mms_dg_ns_3d)
+ ######################################
+ ### CGNS writer ###
+ ######################################
+
+ # The CGNS output of a 2D mesh is read back as mesh, with the restart written by the same run.
+ # The read-back test must run after the test that writes the files.
+ cgns_output = TestCase('cgns_output')
+ cgns_output.cfg_dir = "output_writers"
+ cgns_output.cfg_file = "cgns_output.cfg"
+ cgns_output.test_iter = 2
+ cgns_output.test_vals = [-1.684071, 0.454658, 3.802835, -2.624573, 6.956844, 0.091480]
+ test_list.append(cgns_output)
+
+ cgns_mesh_readback = TestCase('cgns_mesh_readback')
+ cgns_mesh_readback.cfg_dir = "output_writers"
+ cgns_mesh_readback.cfg_file = "cgns_mesh_readback.cfg"
+ cgns_mesh_readback.test_iter = 2
+ cgns_mesh_readback.test_vals = [-2.480775, 0.352445, 2.998807, -3.103293, 6.892515, 0.085107]
+ test_list.append(cgns_mesh_readback)
+
+ # Same for a 3D mesh with mixed elements, whose boundaries are written as MIXED sections.
+ cgns_output_bend = TestCase('cgns_output_bend')
+ cgns_output_bend.cfg_dir = "output_writers"
+ cgns_output_bend.cfg_file = "cgns_output_bend.cfg"
+ cgns_output_bend.test_iter = 2
+ cgns_output_bend.test_vals = [-2.420818, -2.225454, -2.551501, -3.067128, 2.673612]
+ test_list.append(cgns_output_bend)
+
+ cgns_mesh_readback_bend = TestCase('cgns_mesh_readback_bend')
+ cgns_mesh_readback_bend.cfg_dir = "output_writers"
+ cgns_mesh_readback_bend.cfg_file = "cgns_mesh_readback_bend.cfg"
+ cgns_mesh_readback_bend.test_iter = 2
+ cgns_mesh_readback_bend.test_vals = [-3.254168, -2.756564, -3.006605, -3.572686, 4.308287]
+ test_list.append(cgns_mesh_readback_bend)
+
######################################
### RUN TESTS ###
######################################
diff --git a/config_template.cfg b/config_template.cfg
index 88533d33c689..c89470ac5994 100644
--- a/config_template.cfg
+++ b/config_template.cfg
@@ -2658,6 +2658,10 @@ TABULAR_FORMAT= CSV
% Set .precision(value) to specified value for SU2_DOT and HISTORY output. Useful for exact gradient validation.
OUTPUT_PRECISION= 10
%
+% Write the fields of the volume and surface files (CGNS and Paraview XML) in double
+% precision instead of single. The CGNS coordinates are always in double precision.
+WRT_OUTPUT_DOUBLE_PRECISION= NO
+%
% For multizone problems, extend solution and restart filenames automatically by zone number
MULTIZONE_ADAPT_FILENAME= YES
%