diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 58918bfdc2c..d55416ed681 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -804,6 +804,7 @@ class CConfig { nRefOriginMoment_Z; /*!< \brief Number of Z-coordinate moment computation origins. */ unsigned short nMesh_Box_Size; short *Mesh_Box_Size; /*!< \brief Array containing the number of grid points in the x-, y-, and z-directions for the analytic RECTANGLE and BOX grid formats. */ + unsigned short Mesh_Box_PSolFEM; /*!< \brief FEM polynomial degree of the solution for the RECTANGLE and BOX grid formats. */ string Mesh_FileName, /*!< \brief Mesh input file. */ Mesh_Out_FileName, /*!< \brief Mesh output file. */ Solution_FileName, /*!< \brief Flow solution input file. */ @@ -9606,6 +9607,12 @@ class CConfig { */ su2double GetMeshBoxOffset(unsigned short val_iDim) const { return mesh_box_offset[val_iDim]; } + /*! + * \brief Get the polynomial degree of the FEM solution for the analytic RECTANGLE or BOX. + * \return The polynomial degree of the FEM solution. + */ + unsigned short GetMeshBoxPSolFEM(void) const { return Mesh_Box_PSolFEM; } + /*! * \brief Get the number of screen output variables requested (maximum 6) */ diff --git a/Common/include/fem/fem_cgns_elements.hpp b/Common/include/fem/fem_cgns_elements.hpp deleted file mode 100644 index f3debaeb0ea..00000000000 --- a/Common/include/fem/fem_cgns_elements.hpp +++ /dev/null @@ -1,198 +0,0 @@ -/*! - * \file fem_cgns_elements.hpp - * \brief Headers of the classes and functions for reading CGNS files - * with high order elements. - * The functions are in the cgns_elements.cpp file. - * \author E. van der Weide - * \version 8.1.0 "Harrier" - * - * SU2 Project Website: https://su2code.github.io - * - * The SU2 Project is maintained by the SU2 Foundation - * (http://su2foundation.org) - * - * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) - * - * SU2 is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * SU2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with SU2. If not, see . - */ - -#pragma once - -#include "../parallelization/mpi_structure.hpp" - -#ifdef HAVE_CGNS -#include "cgnslib.h" -#endif - -#include "../geometry/primal_grid/CPrimalGridFEM.hpp" - -/* Forward declaration of CBoundaryFace to avoid problems. */ -class CBoundaryFace; - -#ifdef HAVE_CGNS -#if CGNS_VERSION >= 3300 - -/*! - * \class CCGNSElementType - * \brief Class which stores the CGNS element type info for a connectivity section. - */ - -class CCGNSElementType { - public: - int connID; /*!< \brief CGNS connectivity ID of this connectivity. */ - ElementType_t elemType; /*!< \brief Element type according to the CGNS convention, - possibly MIXED. */ - cgsize_t indBeg; /*!< \brief Index of the first element in the CGNS connectivity. */ - cgsize_t indEnd; /*!< \brief Index of the last element in the CGNS connectivity. */ - cgsize_t nElem; /*!< \brief Number of elements present for this element type. */ - - std::string connName; /*!< \brief Name of this connectivity. */ - - bool volumeConn; /*!< \brief Whether or not this is a volume connectivity. */ - bool surfaceConn; /*!< \brief Whether or not this is a surface connectivity. */ - - /* Standard constructor, nothing to be done. */ - CCGNSElementType() {} - - /* Destructor, nothing to be done. */ - ~CCGNSElementType() {} - - /*--- Member function, which determines the meta data for this element type. ---*/ - void DetermineMetaData(const unsigned short nDim, const int fn, const int iBase, const int iZone, const int iConn); - - /*--- Member function, which reads the required boundary connectivity range. ---*/ - void ReadBoundaryConnectivityRange(const int fn, const int iBase, const int iZone, const unsigned long offsetRank, - const unsigned long nBoundElemRank, const unsigned long startingBoundElemIDRank, - unsigned long& locBoundElemCount, std::vector& boundElems); - - /*--- Member function, which reads the required connectivity range. ---*/ - void ReadConnectivityRange(const int fn, const int iBase, const int iZone, const unsigned long offsetRank, - const unsigned long nElemRank, const unsigned long startingElemIDRank, CPrimalGrid**& elem, - unsigned long& locElemCount, unsigned long& nDOFsLoc); - - private: - /*--- Member function, which creates the required data for the given - element type. ---*/ - void CreateDataElementType(const ElementType_t typeElem, unsigned short& VTK_Type, unsigned short& nPoly, - unsigned short& nDOFs, std::vector& SU2ToCGNS); - - /*--- Member function, which determines the element dimension, i.e. the - number of parametric coordinates. ---*/ - unsigned short DetermineElementDimension(const int fn, const int iBase, const int iZone); - - /*--- Member function, which determines the element dimension when the - connectivity is mixed. ---*/ - unsigned short DetermineElementDimensionMixed(const int fn, const int iBase, const int iZone); - - /*--- Member function, which determines the corresponding index of the - given element in the stored types. If not present, a new index - is created. ---*/ - unsigned short IndexInStoredTypes(const ElementType_t typeElem, std::vector& CGNS_Type, - std::vector& VTK_Type, std::vector& nPoly, - std::vector& nDOFs, - std::vector >& SU2ToCGNS); - - /*--- Functions to create the conversion data from CGNS format to SU2 format - for all the supported CGNS elements. ---*/ - void CreateDataNODE(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataBAR_2(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataBAR_3(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataBAR_4(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataBAR_5(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTRI_3(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTRI_6(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTRI_10(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTRI_15(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataQUAD_4(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataQUAD_9(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataQUAD_16(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataQUAD_25(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTETRA_4(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTETRA_10(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTETRA_20(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataTETRA_35(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPYRA_5(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPYRA_14(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPYRA_30(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPYRA_55(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPENTA_6(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPENTA_18(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPENTA_40(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataPENTA_75(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataHEXA_8(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataHEXA_27(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataHEXA_64(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); - - void CreateDataHEXA_125(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, - std::vector& SU2ToCGNS); -}; -#endif -#endif - -using namespace std; diff --git a/Common/include/fem/fem_geometry_structure.hpp b/Common/include/fem/fem_geometry_structure.hpp index 4d3e9cc367d..ab4cc457f40 100644 --- a/Common/include/fem/fem_geometry_structure.hpp +++ b/Common/include/fem/fem_geometry_structure.hpp @@ -30,9 +30,6 @@ #include "../geometry/CGeometry.hpp" #include "fem_standard_element.hpp" -#ifdef HAVE_CGNS -#include "fem_cgns_elements.hpp" -#endif #include "../wall_model.hpp" #include "../linear_algebra/blas_structure.hpp" diff --git a/Common/include/geometry/CPhysicalGeometry.hpp b/Common/include/geometry/CPhysicalGeometry.hpp index 5671ceae247..e6a871bb977 100644 --- a/Common/include/geometry/CPhysicalGeometry.hpp +++ b/Common/include/geometry/CPhysicalGeometry.hpp @@ -28,7 +28,7 @@ #pragma once #include "CGeometry.hpp" -#include "meshreader/CMeshReaderFVM.hpp" +#include "meshreader/CMeshReaderBase.hpp" #include "../containers/C2DContainer.hpp" /*! @@ -285,51 +285,49 @@ class CPhysicalGeometry final : public CGeometry { * \param[in] val_iZone - Domain to be read from the grid file. * \param[in] val_nZone - Total number of domains in the grid file. */ - void Read_Mesh_FVM(CConfig* config, const string& val_mesh_filename, unsigned short val_iZone, - unsigned short val_nZone); + void Read_Mesh(CConfig* config, const string& val_mesh_filename, unsigned short val_iZone, unsigned short val_nZone); /*! - * \brief Reads for the FEM solver the geometry of the grid and adjust the boundary - * conditions with the configuration file in parallel (for parmetis). - * \param[in] config - Definition of the particular problem. - * \param[in] val_mesh_filename - Name of the file with the grid information. - * \param[in] val_iZone - Domain to be read from the grid file. - * \param[in] val_nZone - Total number of domains in the grid file. - */ - void Read_SU2_Format_Parallel_FEM(CConfig* config, const string& val_mesh_filename, unsigned short val_iZone, - unsigned short val_nZone); - - /*! - * \brief Reads for the FEM solver the geometry of the grid and adjust the boundary - * conditions with the configuration file in parallel (for parmetis). - * \param[in] config - Definition of the particular problem. - * \param[in] val_mesh_filename - Name of the file with the grid information. - * \param[in] val_iZone - Domain to be read from the grid file. - * \param[in] val_nZone - Total number of domains in the grid file. + * \brief Routine to load the CGNS grid points from a single zone into the proper SU2 data structures. + * \param[in] config - definition of the particular problem. + * \param[in] mesh - mesh reader object containing the current zone data. */ - void Read_CGNS_Format_Parallel_FEM(CConfig* config, const string& val_mesh_filename, unsigned short val_iZone, - unsigned short val_nZone); + void LoadLinearlyPartitionedPoints(CConfig* config, CMeshReaderBase* mesh); /*! - * \brief Routine to load the CGNS grid points from a single zone into the proper SU2 data structures. + * \brief Routine to load the grid points from a single zone into the proper SU2 data structures for the FEM solver. * \param[in] config - definition of the particular problem. * \param[in] mesh - mesh reader object containing the current zone data. */ - void LoadLinearlyPartitionedPoints(CConfig* config, CMeshReaderFVM* mesh); + void LoadLinearlyPartitionedPointsFEM(CConfig* config, CMeshReaderBase* mesh); /*! * \brief Loads the interior volume elements from the mesh reader object into the primal element data structures. * \param[in] config - definition of the particular problem. * \param[in] mesh - mesh reader object containing the current zone data. */ - void LoadLinearlyPartitionedVolumeElements(CConfig* config, CMeshReaderFVM* mesh); + void LoadLinearlyPartitionedVolumeElements(CConfig* config, CMeshReaderBase* mesh); + + /*! + * \brief Loads the interior volume elements from the mesh reader object into the primal element data structures for + * the FEM solver. \param[in] config - definition of the particular problem. \param[in] mesh - mesh reader object + * containing the current zone data. + */ + void LoadLinearlyPartitionedVolumeElementsFEM(CConfig* config, CMeshReaderBase* mesh); /*! * \brief Loads the boundary elements (markers) from the mesh reader object into the primal element data structures. * \param[in] config - definition of the particular problem. * \param[in] mesh - mesh reader object containing the current zone data. */ - void LoadUnpartitionedSurfaceElements(CConfig* config, CMeshReaderFVM* mesh); + void LoadUnpartitionedSurfaceElements(CConfig* config, CMeshReaderBase* mesh); + + /*! + * \brief Loads the boundary elements (markers) from the mesh reader object into the primal element data structures + * for the FEM solver. \param[in] config - definition of the particular problem. \param[in] mesh - mesh reader + * object containing the current zone data. + */ + void LoadLinearlyPartitionedSurfaceElementsFEM(CConfig* config, CMeshReaderBase* mesh); /*! * \brief Prepares the grid point adjacency based on a linearly partitioned mesh object needed by ParMETIS for graph diff --git a/Common/include/geometry/meshreader/CBoxMeshReaderFEM.hpp b/Common/include/geometry/meshreader/CBoxMeshReaderFEM.hpp new file mode 100644 index 00000000000..f5a0e53934b --- /dev/null +++ b/Common/include/geometry/meshreader/CBoxMeshReaderFEM.hpp @@ -0,0 +1,82 @@ +/*! + * \file CBoxMeshReaderFEM.hpp + * \brief Header file for the class CBoxMeshReaderFEM. + * The implementations are in the CBoxMeshReaderFEM.cpp file. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#include "CMeshReaderBase.hpp" + +/*! + * \class CBoxMeshReaderFEM + * \brief Reads a 3D box grid into linear partitions for the finite element solver (FEM). + * \author: T. Economon, E. van der Weide + */ +class CBoxMeshReaderFEM final : public CMeshReaderBase { + private: + unsigned long nNode; /*!< \brief Number of grid nodes in the x-direction. */ + unsigned long mNode; /*!< \brief Number of grid nodes in the y-direction. */ + unsigned long pNode; /*!< \brief Number of grid nodes in the z-direction. */ + + su2double Lx; /*!< \brief Length of the domain in the x-direction. */ + su2double Ly; /*!< \brief Length of the domain in the y-direction. */ + su2double Lz; /*!< \brief Length of the domain in the z-direction. */ + + su2double Ox; /*!< \brief Offset of the domain from 0.0 in the x-direction. */ + su2double Oy; /*!< \brief Offset of the domain from 0.0 in the y-direction. */ + su2double Oz; /*!< \brief Offset of the domain from 0.0 in the z-direction. */ + + unsigned short KindElem; /*!< \brief VTK identifier of the interior elements. */ + unsigned short KindBound; /*!< \brief VTK identifier of the surface elements. */ + + unsigned short nPolySol; /*!< \brief Polynomial degree of the solution. */ + + /*! + * \brief Computes and stores the grid points based on an analytic definition of a box grid. + */ + void ComputeBoxPointCoordinates(); + + /*! + * \brief Computes and stores the volume element connectivity based on an analytic definition of a box grid. + */ + void ComputeBoxVolumeConnectivity(); + + /*! + * \brief Computes and stores the surface element connectivity based on an analytic definition of a box grid. + */ + void ComputeBoxSurfaceConnectivity(); + + public: + /*! + * \brief Constructor of the CBoxMeshReaderFEM class. + */ + CBoxMeshReaderFEM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CBoxMeshReaderFEM class. + */ + ~CBoxMeshReaderFEM(void) override; +}; diff --git a/Common/include/geometry/meshreader/CBoxMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CBoxMeshReaderFVM.hpp index 4c97369ec35..a557630e7c8 100644 --- a/Common/include/geometry/meshreader/CBoxMeshReaderFVM.hpp +++ b/Common/include/geometry/meshreader/CBoxMeshReaderFVM.hpp @@ -28,14 +28,14 @@ #pragma once -#include "CMeshReaderFVM.hpp" +#include "CMeshReaderBase.hpp" /*! * \class CBoxMeshReaderFVM * \brief Reads a 3D box grid into linear partitions for the finite volume solver (FVM). * \author: T. Economon */ -class CBoxMeshReaderFVM : public CMeshReaderFVM { +class CBoxMeshReaderFVM : public CMeshReaderBase { private: unsigned long nNode; /*!< \brief Number of grid nodes in the x-direction. */ unsigned long mNode; /*!< \brief Number of grid nodes in the y-direction. */ @@ -71,7 +71,7 @@ class CBoxMeshReaderFVM : public CMeshReaderFVM { /*! * \brief Constructor of the CBoxMeshReaderFVM class. */ - CBoxMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + CBoxMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); /*! * \brief Destructor of the CBoxMeshReaderFVM class. diff --git a/Common/include/geometry/meshreader/CCGNSElementType.hpp b/Common/include/geometry/meshreader/CCGNSElementType.hpp new file mode 100644 index 00000000000..fbe944f0114 --- /dev/null +++ b/Common/include/geometry/meshreader/CCGNSElementType.hpp @@ -0,0 +1,362 @@ +/*! + * \file CCGNSElementType.hpp + * \brief Header file for the class CCGNSElementType. + * The implementations are in the CCGNSElementType.cpp file. + * \author E. van der Weide + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#include + +#ifdef HAVE_CGNS +#include "cgnslib.h" +#endif + +#ifdef HAVE_CGNS + +using namespace std; + +/*! + * \class CCGNSElementType + * \brief Class used to convert the CGNS format to SU2 format for high order elements. + * \author: E. van der Weide + */ +class CCGNSElementType { + private: + vector CGNSTypeStored; /*!< \brief CGNS element types for which the data is stored. */ + vector VTKTypeStored; /*!< \brief VTK type of the element. */ + vector nPolyStored; /*!< \brief Polynomial degree of the element. */ + vector nDOFsStored; /*!< \brief Number of DOFs of the element. */ + vector > SU2ToCGNSStored; /*!< \brief Double vector, which stores the conversion + from SU2 to CGNS for the type in local numbering. */ + public: + /*--- Standard constructor, nothing to be done. ---*/ + CCGNSElementType() = default; + + /*--- Destructor, nothing to be done. ---*/ + ~CCGNSElementType() = default; + + /*! + * \brief Converts the connectivity information from CGNS to SU2 format. + * \param[in] val_elemType - CGNS elements type to be converted. + * \param[in] val_globalID - Global ID of this element. + * \param[in] connCGNS - Array with the connectivity of the element in CGNS format. + * \param[out] connSU2 - Vector with the connectivity and meta data in SU2 format. + */ + void CGNSToSU2(const ElementType_t val_elemType, const unsigned long val_globalID, const cgsize_t* connCGNS, + std::vector& connSU2); + + private: + /*! + * \brief Converts the connectivity from CGNS to SU2 for a node. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataNODE(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Bar2 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataBAR_2(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Bar3 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataBAR_3(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Bar4 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataBAR_4(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Bar5 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataBAR_5(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tri3 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTRI_3(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tri6 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTRI_6(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tri10 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTRI_10(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tri15 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTRI_15(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Quad4 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataQUAD_4(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Quad9 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataQUAD_9(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Quad16 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataQUAD_16(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Quad25 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataQUAD_25(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tetra4 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTETRA_4(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tetra10 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTETRA_10(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tetra20 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTETRA_20(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Tetra35 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataTETRA_35(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Pyra5 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPYRA_5(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Pyra14 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPYRA_14(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Pyra30 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPYRA_30(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Pyra55 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPYRA_55(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Penta6 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPENTA_6(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Penta18 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPENTA_18(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Penta40 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPENTA_40(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Penta75 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataPENTA_75(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Hexa8 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataHEXA_8(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Hexa27 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataHEXA_27(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Hexa64 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataHEXA_64(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); + + /*! + * \brief Converts the connectivity from CGNS to SU2 for a Hexa125 element. + * \param[out] VTK_Type - Corresponding VTK type + * \param[out] nPoly - Polynomial degree + * \param[out] nDOFs - Number of DOFs of the element. + * \param[out] SU2ToCGNS - Vector containing the mapping from SU2 to CGNS. + */ + void CreateDataHEXA_125(unsigned short& VTK_Type, unsigned short& nPoly, unsigned short& nDOFs, + vector& SU2ToCGNS); +}; +#endif diff --git a/Common/include/geometry/meshreader/CCGNSMeshReaderBase.hpp b/Common/include/geometry/meshreader/CCGNSMeshReaderBase.hpp new file mode 100644 index 00000000000..01574423fcc --- /dev/null +++ b/Common/include/geometry/meshreader/CCGNSMeshReaderBase.hpp @@ -0,0 +1,108 @@ +/*! + * \file CCGNSMeshReaderBase.hpp + * \brief Header file for the class CCGNSMeshReaderBase. + * The implementations are in the CCGNSMeshReaderBase.cpp file. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#ifdef HAVE_CGNS +#include "cgnslib.h" +#endif + +#include "CMeshReaderBase.hpp" + +/*! + * \class CCGNSMeshReaderBase + * \brief Base class for the reading of a CGNS zone. + * \author: T. Economon + */ +class CCGNSMeshReaderBase : public CMeshReaderBase { + protected: +#ifdef HAVE_CGNS + int cgnsFileID; /*!< \brief CGNS file identifier. */ + const int cgnsBase = 1; /*!< \brief CGNS database index (the CGNS reader currently assumes a single database). */ + const int cgnsZone = 1; /*!< \brief CGNS zone index (and 1 zone in that database). */ + + int nSections; /*!< \brief Total number of sections in the CGNS file. */ + + vector isInterior; /*!< \brief Vector of booleans to store whether each section in the CGNS file is an interior + or boundary section. */ + vector + nElems; /*!< \brief Vector containing the local number of elements found within each CGNS section. */ + vector elemOffset; /*!< \brief Global ID offset for each interior section (i.e., the total number of + global elements that came before it). */ + vector > connElems; /*!< \brief Vector containing the local element connectivity found within each + CGNS section. First index is the section, second contains the connectivity in + format [globalID VTK n1 n2 n3 n4 n5 n6 n7 n8] for each element. */ + vector > sectionNames; /*!< \brief Vector for storing the names of each boundary section (marker). */ + + /*! + * \brief Open the CGNS file and checks for errors. + * \param[in] val_filename - string name of the CGNS file to be read. + */ + void OpenCGNSFile(const string& val_filename); + + /*! + * \brief Reads all CGNS database metadata and checks for errors. + */ + void ReadCGNSDatabaseMetadata(); + + /*! + * \brief Reads all CGNS zone metadata and checks for errors. + */ + void ReadCGNSZoneMetadata(); + + /*! + * \brief Reads the grid points from a CGNS zone into linear partitions across all ranks. + */ + void ReadCGNSPointCoordinates(); + + /*! + * \brief Reads the metadata for each CGNS section in a zone and collect information, including the size and whether + * it is an interior or boundary section. + */ + void ReadCGNSSectionMetadata(); + + /*! + * \brief Get the VTK type and string name for a CGNS element type. + * \param[in] val_elem_type - CGNS element type. + * \param[out] val_vtk_type - VTK type identifier index. + * \returns String containing the name of the element type. + */ + string GetCGNSElementType(ElementType_t val_elem_type, int& val_vtk_type); +#endif + + public: + /*! + * \brief Constructor of the CCGNSMeshReaderBase class. + */ + CCGNSMeshReaderBase(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CCGNSMeshReaderBase class. + */ + virtual ~CCGNSMeshReaderBase(void) override; +}; diff --git a/Common/include/geometry/meshreader/CCGNSMeshReaderFEM.hpp b/Common/include/geometry/meshreader/CCGNSMeshReaderFEM.hpp new file mode 100644 index 00000000000..cb7fcd404e8 --- /dev/null +++ b/Common/include/geometry/meshreader/CCGNSMeshReaderFEM.hpp @@ -0,0 +1,92 @@ +/*! + * \file CCGNSMeshReaderFEM.hpp + * \brief Header file for the class CCGNSMeshReaderFEM. + * The implementations are in the CCGNSMeshReaderFEM.cpp file. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#include "CCGNSMeshReaderBase.hpp" + +/*! + * \class CCGNSMeshReaderFEM + * \brief Reads a CGNS zone into linear partitions for the finite element solver (FEM). + * \author: T. Economon + */ +class CCGNSMeshReaderFEM final : public CCGNSMeshReaderBase { + private: + /*! + * \brief Communicates the grid points to the MPI rank where they are needed. + */ + void CommPointCoordinates(); + +#ifdef HAVE_CGNS + + /*! + * \brief Reads the connectivity range from a CGNS section and convert it to the internal format. + * \param[in] val_section - CGNS section index. + * \param[in] val_firstIndex - Global index of the first element to be stored on this rank. + * \param[in] val_lastIndex - Global index of the last element (not included) to be stored on this rank. + * \param[inout] elemCount - Counter, which keeps track how many global elements are stored. + * \param[inout] localElemCount - Counter, which keeps track how many local elements are stored. + * \param[inout] localConn - Vector where the connectivity must be stored. + */ + void ReadCGNSConnectivityRangeSection(const int val_section, const unsigned long val_firstIndex, + const unsigned long val_lastIndex, unsigned long& elemCount, + unsigned long& localElemCount, vector& localConn); + + /*! + * \brief Reads the interior volume elements from one section of a CGNS zone into linear partitions across all ranks. + */ + void ReadCGNSVolumeElementConnectivity(); + + /*! + * \brief Reads the surface (boundary) elements from one section of a CGNS zone into linear partitions across all + * ranks. + */ + void ReadCGNSSurfaceElementConnectivity(); + + /*! + * \brief Reads the connectivity from a CGNS surface section and select the relevant faces. + * \param[in] val_section - CGNS section index. + * \param[in] localFaces - The faces of the locally stored volume elements. + * \param[out] nSurfElem - Number of local surface elements stored for this surface section. + * \param[out] surfConn - Vector to store the connectivity of the surface elements to be stored. + */ + void ReadCGNSSurfaceSection(const int val_section, const vector& localFaces, unsigned long& nSurfElem, + vector& surfConn); +#endif + + public: + /*! + * \brief Constructor of the CCGNSMeshReaderFEM class. + */ + CCGNSMeshReaderFEM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CCGNSMeshReaderFEM class. + */ + ~CCGNSMeshReaderFEM(void) override; +}; diff --git a/Common/include/geometry/meshreader/CCGNSMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CCGNSMeshReaderFVM.hpp index 6199cb540d5..fe8612b3bbb 100644 --- a/Common/include/geometry/meshreader/CCGNSMeshReaderFVM.hpp +++ b/Common/include/geometry/meshreader/CCGNSMeshReaderFVM.hpp @@ -28,64 +28,16 @@ #pragma once -#ifdef HAVE_CGNS -#include "cgnslib.h" -#endif - -#include "CMeshReaderFVM.hpp" +#include "CCGNSMeshReaderBase.hpp" /*! * \class CCGNSMeshReaderFVM * \brief Reads a CGNS zone into linear partitions for the finite volume solver (FVM). * \author: T. Economon */ -class CCGNSMeshReaderFVM : public CMeshReaderFVM { +class CCGNSMeshReaderFVM final : public CCGNSMeshReaderBase { private: #ifdef HAVE_CGNS - int cgnsFileID; /*!< \brief CGNS file identifier. */ - const int cgnsBase = 1; /*!< \brief CGNS database index (the CGNS reader currently assumes a single database). */ - const int cgnsZone = 1; /*!< \brief CGNS zone index (and 1 zone in that database). */ - - int nSections; /*!< \brief Total number of sections in the CGNS file. */ - - vector isInterior; /*!< \brief Vector of booleans to store whether each section in the CGNS file is an interior - or boundary section. */ - vector - nElems; /*!< \brief Vector containing the local number of elements found within each CGNS section. */ - vector elemOffset; /*!< \brief Global ID offset for each interior section (i.e., the total number of - global elements that came before it). */ - vector > connElems; /*!< \brief Vector containing the local element connectivity found within each - CGNS section. First index is the section, second contains the connectivity in - format [globalID VTK n1 n2 n3 n4 n5 n6 n7 n8] for each element. */ - vector > sectionNames; /*!< \brief Vector for storing the names of each boundary section (marker). */ - - /*! - * \brief Open the CGNS file and checks for errors. - * \param[in] val_filename - string name of the CGNS file to be read. - */ - void OpenCGNSFile(const string& val_filename); - - /*! - * \brief Reads all CGNS database metadata and checks for errors. - */ - void ReadCGNSDatabaseMetadata(); - - /*! - * \brief Reads all CGNS zone metadata and checks for errors. - */ - void ReadCGNSZoneMetadata(); - - /*! - * \brief Reads the grid points from a CGNS zone into linear partitions across all ranks. - */ - void ReadCGNSPointCoordinates(); - - /*! - * \brief Reads the metadata for each CGNS section in a zone and collect information, including the size and whether - * it is an interior or boundary section. - */ - void ReadCGNSSectionMetadata(); - /*! * \brief Reads the interior volume elements from one section of a CGNS zone into linear partitions across all ranks. * \param[in] val_section - CGNS section index. @@ -108,13 +60,6 @@ class CCGNSMeshReaderFVM : public CMeshReaderFVM { */ void ReformatCGNSSurfaceConnectivity(); - /*! - * \brief Get the VTK type and string name for a CGNS element type. - * \param[in] val_elem_type - CGNS element type. - * \param[out] val_vtk_type - VTK type identifier index. - * \returns String containing the name of the element type. - */ - string GetCGNSElementType(ElementType_t val_elem_type, int& val_vtk_type); #endif /*! @@ -146,7 +91,7 @@ class CCGNSMeshReaderFVM : public CMeshReaderFVM { /*! * \brief Constructor of the CCGNSMeshReaderFVM class. */ - CCGNSMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + CCGNSMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); /*! * \brief Destructor of the CCGNSMeshReaderFVM class. diff --git a/Common/include/geometry/meshreader/CMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CMeshReaderBase.hpp similarity index 71% rename from Common/include/geometry/meshreader/CMeshReaderFVM.hpp rename to Common/include/geometry/meshreader/CMeshReaderBase.hpp index 7980acf39fd..622b48db4c8 100644 --- a/Common/include/geometry/meshreader/CMeshReaderFVM.hpp +++ b/Common/include/geometry/meshreader/CMeshReaderBase.hpp @@ -1,8 +1,8 @@ /*! - * \file CMeshReaderFVM.hpp - * \brief Header file for the class CMeshReaderFVM. - * The implementations are in the CMeshReaderFVM.cpp file. + * \file CMeshReaderBase.hpp + * \brief Header file for the class CMeshReaderBase. + * The implementations are in the CMeshReaderBase.cpp file. * \author T. Economon * \version 8.1.0 "Harrier" * @@ -31,15 +31,17 @@ #include +#include "../primal_grid/CPrimalGridFEM.hpp" +#include "../../fem/geometry_structure_fem_part.hpp" #include "../../parallelization/mpi_structure.hpp" #include "../../CConfig.hpp" /*! - * \class CMeshReaderFVM - * \brief Base class for the mesh zone readers of the finite volume solver (FVM). + * \class CMeshReaderBase + * \brief Base class for the mesh zone readers. * \author T. Economon */ -class CMeshReaderFVM { +class CMeshReaderBase { protected: const int rank; /*!< \brief MPI Rank. */ const int size; /*!< \brief MPI Size. */ @@ -54,6 +56,7 @@ class CMeshReaderFVM { vector > localPointCoordinates; /*!< \brief Vector holding the coordinates from the mesh file for the local grid points. First index is dimension, second is point index. */ + vector globalPointIDs; /*!< \brief Vector holding the global IDs of the local grid points. */ unsigned long numberOfLocalElements = 0; /*!< \brief Number of local elements within the linear partition on this rank. */ @@ -63,20 +66,39 @@ class CMeshReaderFVM { unsigned long numberOfMarkers = 0; /*!< \brief Total number of markers contained within the mesh file. */ vector markerNames; /*!< \brief String names for all markers in the mesh file. */ + vector + numberOfLocalSurfaceElements; /*!< \brief Vector containing the number of local surface elements. */ vector > surfaceElementConnectivity; /*!< \brief Vector containing the surface element connectivity from the mesh file on a - per-marker basis. Only the master node reads and stores this connectivity. */ + per-marker basis. For FVM, only the master node reads and stores this connectivity. + */ + + /*! + * \brief Function, which determines the faces of the local volume elements. + * \param[out] localFaces - The faces of the locally stored volume elements. + */ + void DetermineFacesVolumeElements(vector& localFaces); + + /*! + * \brief Get all the corner points of all the faces of the given element. It must + * \param[in] elemInfo - Array, which contains the info of the given element. + * \param[out] nFaces - Number of faces of the element. + * \param[out] nPointsPerFace - Number of corner points for each of the faces. + * \param[out] faceConn - Global IDs of the corner points of the faces. + */ + void GetCornerPointsAllFaces(const unsigned long* elemInfo, unsigned short& numFaces, unsigned short nPointsPerFace[], + unsigned long faceConn[6][4]); public: /*! - * \brief Constructor of the CMeshReaderFVM class. + * \brief Constructor of the CMeshReaderBase class. * \param[in] val_config - config object for the current zone. * \param[in] val_iZone - Current zone index. * \param[in] val_nZone - Total number of zones. */ - CMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + CMeshReaderBase(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); - virtual ~CMeshReaderFVM() = default; + virtual ~CMeshReaderBase() = default; /*! * \brief Get the physical dimension of the problem (2 or 3). @@ -84,6 +106,12 @@ class CMeshReaderFVM { */ inline unsigned short GetDimension() const { return dimension; } + /*! + * \brief Get the global IDs of the local points. + * \returns Reference to the vector containing the global points IDs. + */ + inline const vector& GetGlobalPointIDs() const { return globalPointIDs; } + /*! * \brief Get the local point coordinates (linearly partitioned). * \returns Local point coordinates (linear partitioned). @@ -99,6 +127,14 @@ class CMeshReaderFVM { return surfaceElementConnectivity[val_iMarker]; } + /*! + * \brief Get the number surface elements for all markers. + * \returns Reference to the vector containing the number of surface elements for all markers. + */ + inline const vector& GetNumberOfSurfaceElementsAllMarkers() const { + return numberOfLocalSurfaceElements; + } + /*! * \brief Get the number surface elements for the specified marker. * \param[in] val_iMarker - current marker index. diff --git a/Common/include/geometry/meshreader/CRectangularMeshReaderFEM.hpp b/Common/include/geometry/meshreader/CRectangularMeshReaderFEM.hpp new file mode 100644 index 00000000000..cc4745659a9 --- /dev/null +++ b/Common/include/geometry/meshreader/CRectangularMeshReaderFEM.hpp @@ -0,0 +1,79 @@ +/*! + * \file CRectangularMeshReaderFEM.hpp + * \brief Header file for the class CRectangularMeshReaderFEM. + * The implementations are in the CRectangularMeshReaderFEM.cpp file. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#include "CMeshReaderBase.hpp" + +/*! + * \class CRectangularMeshReaderFEM + * \brief Reads a 2D rectangular grid into linear partitions for the finite element solver (FEM). + * \author: T. Economon, E. van der Weide + */ +class CRectangularMeshReaderFEM : public CMeshReaderBase { + private: + unsigned long nNode; /*!< \brief Number of grid nodes in the x-direction. */ + unsigned long mNode; /*!< \brief Number of grid nodes in the y-direction. */ + + su2double Lx; /*!< \brief Length of the domain in the x-direction. */ + su2double Ly; /*!< \brief Length of the domain in the y-direction. */ + + su2double Ox; /*!< \brief Offset of the domain from 0.0 in the x-direction. */ + su2double Oy; /*!< \brief Offset of the domain from 0.0 in the y-direction. */ + + unsigned short KindElem; /*!< \brief VTK identifier of the interior elements. */ + unsigned short KindBound; /*!< \brief VTK identifier of the surface elements. */ + + unsigned short nPolySol; /*!< \brief Polynomial degree of the solution. */ + + /*! + * \brief Computes and stores the grid points based on an analytic definition of a rectangular grid. + */ + void ComputeRectangularPointCoordinates(); + + /*! + * \brief Computes and stores the volume element connectivity based on an analytic definition of a rectangular grid. + */ + void ComputeRectangularVolumeConnectivity(); + + /*! + * \brief Computes and stores the surface element connectivity based on an analytic definition of a rectangular grid. + */ + void ComputeRectangularSurfaceConnectivity(); + + public: + /*! + * \brief Constructor of the CRectangularMeshReaderFEM class. + */ + CRectangularMeshReaderFEM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CRectangularMeshReaderFEM class. + */ + ~CRectangularMeshReaderFEM(void) override; +}; diff --git a/Common/include/geometry/meshreader/CRectangularMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CRectangularMeshReaderFVM.hpp index 54f393b88d8..30f28354d12 100644 --- a/Common/include/geometry/meshreader/CRectangularMeshReaderFVM.hpp +++ b/Common/include/geometry/meshreader/CRectangularMeshReaderFVM.hpp @@ -28,14 +28,14 @@ #pragma once -#include "CMeshReaderFVM.hpp" +#include "CMeshReaderBase.hpp" /*! * \class CRectangularMeshReaderFVM * \brief Reads a 2D rectangular grid into linear partitions for the finite volume solver (FVM). * \author: T. Economon */ -class CRectangularMeshReaderFVM : public CMeshReaderFVM { +class CRectangularMeshReaderFVM : public CMeshReaderBase { private: unsigned long nNode; /*!< \brief Number of grid nodes in the x-direction. */ unsigned long mNode; /*!< \brief Number of grid nodes in the y-direction. */ @@ -69,4 +69,9 @@ class CRectangularMeshReaderFVM : public CMeshReaderFVM { * \brief Constructor of the CRectangularMeshReaderFVM class. */ CRectangularMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CRectangularMeshReaderFVM class. + */ + ~CRectangularMeshReaderFVM(void) override; }; diff --git a/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp new file mode 100644 index 00000000000..66ecbf97095 --- /dev/null +++ b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp @@ -0,0 +1,116 @@ +/*! + * \file CSU2ASCIIMeshReaderBase.hpp + * \brief Header file for the class CSU2ASCIIMeshReaderBase. + * The implementations are in the CSU2ASCIIMeshReaderBase.cpp file. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#include + +#include "CMeshReaderBase.hpp" + +/*! + * \class CSU2ASCIIMeshReaderBase + * \brief Base class for the reading of a native SU2 ASCII grid. + * \author T. Economon + */ +class CSU2ASCIIMeshReaderBase : public CMeshReaderBase { + protected: + enum class FileSection { POINTS, ELEMENTS, MARKERS }; /*!< \brief Different sections of the file. */ + std::array SectionOrder{}; /*!< \brief Order of the sections in the file. */ + + const unsigned short myZone; /*!< \brief Current SU2 zone index. */ + const unsigned short nZones; /*!< \brief Total number of zones in the SU2 file. */ + + const string meshFilename; /*!< \brief Name of the SU2 ASCII mesh file being read. */ + ifstream mesh_file; /*!< \brief File object for the SU2 ASCII mesh file. */ + + bool actuator_disk; /*!< \brief Boolean for whether we have an actuator disk to split. */ + + unsigned long ActDiskNewPoints = + 0; /*!< \brief Total number of new grid points to add due to actuator disk splitting. */ + + su2double Xloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ + su2double Yloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ + su2double Zloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ + + vector ActDisk_Bool; /*!< \brief Flag to identify the grid points on the actuator disk. */ + + vector ActDiskPoint_Back; /*!< \brief Vector containing the global index for the new grid points added + to the back of the actuator disk. */ + vector VolumePoint_Inv; /*!< \brief Vector containing the inverse mapping from the global index to the + added point index for the actuator disk. */ + + vector CoordXActDisk; /*!< \brief X-coordinates of the new grid points added by splitting the actuator disk + (size = ActDiskNewPoints). */ + vector CoordYActDisk; /*!< \brief Y-coordinates of the new grid points added by splitting the actuator disk + (size = ActDiskNewPoints). */ + vector CoordZActDisk; /*!< \brief Z-coordinates of the new grid points added by splitting the actuator disk + (size = ActDiskNewPoints). */ + + vector CoordXVolumePoint; /*!< \brief X-coordinates of the volume elements touching the actuator disk. */ + vector CoordYVolumePoint; /*!< \brief Y-coordinates of the volume elements touching the actuator disk. */ + vector CoordZVolumePoint; /*!< \brief Z-coordinates of the volume elements touching the actuator disk. */ + + /*! + * \brief Reads all SU2 ASCII mesh metadata and checks for errors. + * \param[in] single_pass - Try to read the contents together with the metadata if the order allows (points before + * elements). \param[in,out] config - Problem configuration where some metadata is updated (e.g. AoA). \returns True + * if single_pass was successful. + */ + bool ReadMetadata(const bool single_pass, CConfig* config); + + /*! + * \brief Reads the grid points from an SU2 zone into linear partitions across all ranks. + */ + virtual void ReadPointCoordinates(const bool single_pass = false); + + /*! + * \brief Reads the interior volume elements from one section of an SU2 zone into linear partitions across all ranks. + */ + virtual void ReadVolumeElementConnectivity(const bool single_pass = false); + + /*! + * \brief Reads the surface (boundary) elements from the SU2 zone. + */ + virtual void ReadSurfaceElementConnectivity(const bool single_pass = false); + + /*! + * \brief Helper function to find the current zone in an SU2 ASCII mesh object. + */ + void FastForwardToMyZone(); + + public: + /*! + * \brief Constructor of the CSU2ASCIIMeshReaderBase class. + */ + CSU2ASCIIMeshReaderBase(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2ASCIIMeshReaderBase class. + */ + virtual ~CSU2ASCIIMeshReaderBase(void) override; +}; diff --git a/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFEM.hpp b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFEM.hpp new file mode 100644 index 00000000000..d92cf336103 --- /dev/null +++ b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFEM.hpp @@ -0,0 +1,68 @@ +/*! + * \file CSU2ASCIIMeshReaderFEM.hpp + * \brief Header file for the class CSU2ASCIIMeshReaderFEM. + * The implementations are in the CSU2ASCIIMeshReaderFEM.cpp file. + * \author T. Economon, E. van der Weide + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#pragma once + +#include + +#include "CSU2ASCIIMeshReaderBase.hpp" + +/*! + * \class CSU2ASCIIMeshReaderFEM + * \brief Reads a native SU2 ASCII grid into linear partitions for the finite element solver (FEM). + * \author T. Economon, E. van der Weide + */ +class CSU2ASCIIMeshReaderFEM : public CSU2ASCIIMeshReaderBase { + private: + /*! + * \brief Reads the grid points from an SU2 zone into linear partitions across all ranks. + */ + void ReadPointCoordinates(); + + /*! + * \brief Reads the interior volume elements from one section of an SU2 zone into linear partitions across all ranks. + */ + void ReadVolumeElementConnectivity(); + + /*! + * \brief Reads the surface (boundary) elements from one section of an SU2 zone into linear partitions across all + * ranks. + */ + void ReadSurfaceElementConnectivity(); + + public: + /*! + * \brief Constructor of the CSU2ASCIIMeshReaderFEM class. + */ + CSU2ASCIIMeshReaderFEM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2ASCIIMeshReaderFEM class. + */ + ~CSU2ASCIIMeshReaderFEM(void) override; +}; diff --git a/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp index 1be0177b435..20f88bc66bc 100644 --- a/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp +++ b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp @@ -30,87 +30,28 @@ #include -#include "CMeshReaderFVM.hpp" +#include "CSU2ASCIIMeshReaderBase.hpp" /*! * \class CSU2ASCIIMeshReaderFVM * \brief Reads a native SU2 ASCII grid into linear partitions for the finite volume solver (FVM). * \author T. Economon */ -class CSU2ASCIIMeshReaderFVM : public CMeshReaderFVM { +class CSU2ASCIIMeshReaderFVM : public CSU2ASCIIMeshReaderBase { private: - enum class FileSection { POINTS, ELEMENTS, MARKERS }; /*!< \brief Different sections of the file. */ - std::array SectionOrder{}; /*!< \brief Order of the sections in the file. */ - - const unsigned short myZone; /*!< \brief Current SU2 zone index. */ - const unsigned short nZones; /*!< \brief Total number of zones in the SU2 file. */ - - const string meshFilename; /*!< \brief Name of the SU2 ASCII mesh file being read. */ - ifstream mesh_file; /*!< \brief File object for the SU2 ASCII mesh file. */ - - bool actuator_disk; /*!< \brief Boolean for whether we have an actuator disk to split. */ - - unsigned long ActDiskNewPoints = - 0; /*!< \brief Total number of new grid points to add due to actuator disk splitting. */ - - su2double Xloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ - su2double Yloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ - su2double Zloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ - - vector ActDisk_Bool; /*!< \brief Flag to identify the grid points on the actuator disk. */ - - vector ActDiskPoint_Back; /*!< \brief Vector containing the global index for the new grid points added - to the back of the actuator disk. */ - vector VolumePoint_Inv; /*!< \brief Vector containing the inverse mapping from the global index to the - added point index for the actuator disk. */ - - vector CoordXActDisk; /*!< \brief X-coordinates of the new grid points added by splitting the actuator disk - (size = ActDiskNewPoints). */ - vector CoordYActDisk; /*!< \brief Y-coordinates of the new grid points added by splitting the actuator disk - (size = ActDiskNewPoints). */ - vector CoordZActDisk; /*!< \brief Z-coordinates of the new grid points added by splitting the actuator disk - (size = ActDiskNewPoints). */ - - vector CoordXVolumePoint; /*!< \brief X-coordinates of the volume elements touching the actuator disk. */ - vector CoordYVolumePoint; /*!< \brief Y-coordinates of the volume elements touching the actuator disk. */ - vector CoordZVolumePoint; /*!< \brief Z-coordinates of the volume elements touching the actuator disk. */ - - /*! - * \brief Reads all SU2 ASCII mesh metadata and checks for errors. - * \param[in] single_pass - Try to read the contents together with the metadata if the order allows (points before - * elements). \param[in,out] config - Problem configuration where some metadata is updated (e.g. AoA). \returns True - * if single_pass was successful. - */ - bool ReadMetadata(const bool single_pass, CConfig* config); - /*! * \brief Splits a single surface actuator disk boundary into two separate markers (repeated points). */ void SplitActuatorDiskSurface(); - /*! - * \brief Reads the grid points from an SU2 zone into linear partitions across all ranks. - */ - void ReadPointCoordinates(const bool single_pass = false); - - /*! - * \brief Reads the interior volume elements from one section of an SU2 zone into linear partitions across all ranks. - */ - void ReadVolumeElementConnectivity(const bool single_pass = false); - - /*! - * \brief Reads the surface (boundary) elements from the SU2 zone. - */ - void ReadSurfaceElementConnectivity(const bool single_pass = false); - - /*! - * \brief Helper function to find the current zone in an SU2 ASCII mesh object. - */ - void FastForwardToMyZone(); - public: /*! * \brief Constructor of the CSU2ASCIIMeshReaderFVM class. */ CSU2ASCIIMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2ASCIIMeshReaderFVM class. + */ + ~CSU2ASCIIMeshReaderFVM(void) override; }; diff --git a/Common/include/geometry/primal_grid/CPrimalGridBoundFEM.hpp b/Common/include/geometry/primal_grid/CPrimalGridBoundFEM.hpp index fd7db594f6f..3975d1debba 100644 --- a/Common/include/geometry/primal_grid/CPrimalGridBoundFEM.hpp +++ b/Common/include/geometry/primal_grid/CPrimalGridBoundFEM.hpp @@ -51,16 +51,9 @@ class CPrimalGridBoundFEM final : public CPrimalGrid { public: /*! * \brief Constructor using data to initialize the boundary element. - * \param[in] val_elemGlobalID - Global boundary element ID of this element. - * \param[in] val_domainElementID - Global ID of the corresponding domain element. - * \param[in] val_VTK_Type - VTK type to indicate the element type - * \param[in] val_nPolyGrid - Polynomial degree to describe the geometry of the element. - * \param[in] val_nDOFsGrid - Number of DOFs used to describe the geometry of the element. - * \param[in] val_nodes - Vector, which contains the global node IDs of the element. - */ - CPrimalGridBoundFEM(unsigned long val_elemGlobalID, unsigned long val_domainElementID, unsigned short val_VTK_Type, - unsigned short val_nPolyGrid, unsigned short val_nDOFsGrid, - std::vector& val_nodes); + * \param[in] dataElem - Meta and connectivity data for this element. + */ + CPrimalGridBoundFEM(const unsigned long* dataElem); /*! * \brief Get the number of nodes that composes a face of an element. diff --git a/Common/include/geometry/primal_grid/CPrimalGridFEM.hpp b/Common/include/geometry/primal_grid/CPrimalGridFEM.hpp index 81c214c78ff..77e1a5fc3b9 100644 --- a/Common/include/geometry/primal_grid/CPrimalGridFEM.hpp +++ b/Common/include/geometry/primal_grid/CPrimalGridFEM.hpp @@ -55,33 +55,10 @@ class CPrimalGridFEM final : public CPrimalGrid { public: /*! * \brief Constructor using data to initialize the element. - * \param[in] val_elemGlobalID - Global element ID of this element. - * \param[in] val_VTK_Type - VTK type to indicate the element type - * \param[in] val_nPolyGrid - Polynomial degree to describe the geometry of the element. - * \param[in] val_nPolySol - Polynomial degree to describe the solution of the element. - * \param[in] val_nDOFsGrid - Number of DOFs used to describe the geometry of the element. - * \param[in] val_nDOFsSol - Number of DOFs used to describe the solution of the element. - * \param[in] val_offDOfsSol - Global offset of the solution DOFs of the element. - * \param[in] elem_line - istringstream, which contains the grid node numbers of the element. + * \param[in] dataElem - Meta and connectivity data for this element. + * \param[in,out] offsetDOFs - The offset of the solution DOFs for this element. */ - CPrimalGridFEM(unsigned long val_elemGlobalID, unsigned short val_VTK_Type, unsigned short val_nPolyGrid, - unsigned short val_nPolySol, unsigned short val_nDOFsGrid, unsigned short val_nDOFsSol, - unsigned long val_offDOfsSol, std::istringstream& elem_line); - - /*! - * \brief Constructor using data to initialize the element. - * \param[in] val_elemGlobalID - Global element ID of this element. - * \param[in] val_VTK_Type - VTK type to indicate the element type - * \param[in] val_nPolyGrid - Polynomial degree to describe the geometry of the element. - * \param[in] val_nPolySol - Polynomial degree to describe the solution of the element. - * \param[in] val_nDOFsGrid - Number of DOFs used to describe the geometry of the element. - * \param[in] val_nDOFsSol - Number of DOFs used to describe the solution of the element. - * \param[in] val_offDOfsSol - Global offset of the solution DOFs of the element. - * \param[in] connGrid - Array, which contains the grid node numbers of the element. - */ - CPrimalGridFEM(unsigned long val_elemGlobalID, unsigned short val_VTK_Type, unsigned short val_nPolyGrid, - unsigned short val_nPolySol, unsigned short val_nDOFsGrid, unsigned short val_nDOFsSol, - unsigned long val_offDOfsSol, const unsigned long* connGrid); + CPrimalGridFEM(const unsigned long* dataElem, unsigned long& offsetSolDOFs); /*! * \brief Get the number of nodes that composes a face of an element. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index cc5bd3faca6..d54d1dc3a6c 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -2145,6 +2145,9 @@ void CConfig::SetConfig_Options() { mesh_box_offset[0] = 0.0; mesh_box_offset[1] = 0.0; mesh_box_offset[2] = 0.0; addDoubleArrayOption("MESH_BOX_OFFSET", 3, mesh_box_offset); + /* DESCRIPTION: Polynomial degree of the FEM solution for the RECTANGLE or BOX grid. (default: 1). */ + addUnsignedShortOption("MESH_BOX_POLY_SOL_FEM", Mesh_Box_PSolFEM, 1); + /* DESCRIPTION: Determine if the mesh file supports multizone. \n DEFAULT: true (temporarily) */ addBoolOption("MULTIZONE_MESH", Multizone_Mesh, true); /* DESCRIPTION: Determine if we need to allocate memory to store the multizone residual. \n DEFAULT: false (temporarily) */ diff --git a/Common/src/fem/fem_geometry_structure.cpp b/Common/src/fem/fem_geometry_structure.cpp index e671a2d0265..c6e52dbea81 100644 --- a/Common/src/fem/fem_geometry_structure.cpp +++ b/Common/src/fem/fem_geometry_structure.cpp @@ -25,6 +25,7 @@ * License along with SU2. If not, see . */ +#include "../../include/toolboxes/CLinearPartitioner.hpp" #include "../../include/fem/fem_geometry_structure.hpp" #include "../../include/geometry/primal_grid/CPrimalGridFEM.hpp" #include "../../include/geometry/primal_grid/CPrimalGridBoundFEM.hpp" @@ -221,6 +222,10 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /*--- Allocate the memory for blasFunctions. ---*/ blasFunctions = new CBlasStructure; + /*--- Define the linear partitioning of the elements. ---*/ + Global_nElem = geometry->GetGlobal_nElem(); + CLinearPartitioner elemPartitioner(Global_nElem, 0); + /*--- The new FEM mesh class has the same problem dimension/zone. ---*/ nDim = geometry->GetnDim(); nZone = geometry->GetnZone(); @@ -262,14 +267,14 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { map rankToIndCommBuf; for (int i = 0; i < size; ++i) { if (sendToRank[i]) { - int ind = (int)rankToIndCommBuf.size(); + int ind = static_cast(rankToIndCommBuf.size()); rankToIndCommBuf[i] = ind; } } /*--- Definition of the communication buffers, used to send the element data to the correct ranks. ---*/ - int nRankSend = (int)rankToIndCommBuf.size(); + int nRankSend = static_cast(rankToIndCommBuf.size()); vector > shortSendBuf(nRankSend, vector(0)); vector > longSendBuf(nRankSend, vector(0)); vector > doubleSendBuf(nRankSend, vector(0)); @@ -289,7 +294,7 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /*--- Loop over the local elements to fill the communication buffers with element data. ---*/ for (unsigned long i = 0; i < geometry->GetnElem(); ++i) { - int ind = (int)geometry->elem[i]->GetColor(); + int ind = static_cast(geometry->elem[i]->GetColor()); map::const_iterator MI = rankToIndCommBuf.find(ind); ind = MI->second; @@ -374,13 +379,14 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /* Loop over the local boundary elements in geometry for this marker. */ for (unsigned long i = 0; i < geometry->GetnElem_Bound(iMarker); ++i) { /* Determine the local ID of the corresponding domain element. */ - unsigned long elemID = geometry->bound[iMarker][i]->GetDomainElement() - geometry->beg_node[rank]; + unsigned long elemID = + geometry->bound[iMarker][i]->GetDomainElement() - elemPartitioner.GetFirstIndexOnRank(rank); /* Determine to which rank this boundary element must be sent. That is the same as its corresponding domain element. Update the corresponding index in longSendBuf. */ - int ind = (int)geometry->elem[elemID]->GetColor(); - map::const_iterator MI = rankToIndCommBuf.find(ind); + int ind = static_cast(geometry->elem[elemID]->GetColor()); + const auto MI = rankToIndCommBuf.find(ind); ind = MI->second; ++longSendBuf[ind][indLongBuf[ind]]; @@ -560,8 +566,10 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { the elements with constant and non-constant Jacobians are considered the same. */ if (JacConstant) { - const auto orderExactStraight = (unsigned short)ceil(nPolySol * config->GetQuadrature_Factor_Straight()); - const auto orderExactCurved = (unsigned short)ceil(nPolySol * config->GetQuadrature_Factor_Curved()); + const auto orderExactStraight = + static_cast(ceil(nPolySol * config->GetQuadrature_Factor_Straight())); + const auto orderExactCurved = + static_cast(ceil(nPolySol * config->GetQuadrature_Factor_Curved())); if (orderExactStraight == orderExactCurved) JacConstant = false; } @@ -666,9 +674,7 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /* Determine the number of elements per rank of the originally partitioned grid stored in cumulative storage format. */ vector nElemPerRankOr(size + 1); - - for (int i = 0; i < size; ++i) nElemPerRankOr[i] = geometry->beg_node[i]; - nElemPerRankOr[size] = geometry->end_node[size - 1]; + for (int i = 0; i <= size; ++i) nElemPerRankOr[i] = elemPartitioner.GetCumulativeSizeBeforeRank(i); /* Determine to which ranks I have to send messages to find out the information of the halos stored on this rank. */ @@ -687,14 +693,14 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { rankToIndCommBuf.clear(); for (int i = 0; i < size; ++i) { if (sendToRank[i]) { - int ind = (int)rankToIndCommBuf.size(); + int ind = static_cast(rankToIndCommBuf.size()); rankToIndCommBuf[i] = ind; } } /* Resize the first index of the long send buffers for the communication of the halo data. */ - nRankSend = (int)rankToIndCommBuf.size(); + nRankSend = static_cast(rankToIndCommBuf.size()); longSendBuf.resize(nRankSend); /* Determine the number of ranks, from which this rank will receive elements. */ @@ -713,7 +719,7 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { if (*low > haloElements[i].long0) --ind; /* Convert this rank to the index in the send buffer. */ - MI = rankToIndCommBuf.find((int)ind); + MI = rankToIndCommBuf.find(static_cast(ind)); ind = MI->second; /* Store the global element ID and the periodic index in the long buffer. @@ -803,11 +809,10 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /* Determine the local index of the element in the original partitioning. Check if the index is valid. */ - const long localID = globalID - geometry->beg_node[rank]; - if (localID < 0 || localID >= (long)geometry->nPointLinear[rank]) { + const long localID = globalID - elemPartitioner.GetFirstIndexOnRank(rank); + if (elemPartitioner.GetRankContainingIndex(globalID) != static_cast(rank)) { ostringstream message; - message << localID << " " << geometry->nPointLinear[rank] << endl; - message << "Invalid local element ID"; + message << "Invalid local element ID: " << localID; SU2_MPI::Error(message.str(), CURRENT_FUNCTION); } @@ -908,12 +913,12 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { for (int i = 0; i < size; ++i) { if (nHaloElemPerRank[i + 1] > nHaloElemPerRank[i]) { sendToRank[i] = 1; - int ind = (int)rankToIndCommBuf.size(); + int ind = static_cast(rankToIndCommBuf.size()); rankToIndCommBuf[i] = ind; } } - nRankSend = (int)rankToIndCommBuf.size(); + nRankSend = static_cast(rankToIndCommBuf.size()); /* Store the value of nRankSend for later use. */ const int nRankSendHaloInfo = nRankSend; @@ -1176,7 +1181,7 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /*--- Create the graph of local elements. The halo elements are ignored. ---*/ vector > neighElem(nVolElemOwned, vector(0)); - nRankRecv = (int)longRecvBuf.size(); + nRankRecv = static_cast(longRecvBuf.size()); for (int i = 0; i < nRankRecv; ++i) { unsigned long indL = 1, indS = 0; for (long j = 0; j < longRecvBuf[i][0]; ++j) { @@ -1449,7 +1454,7 @@ CMeshFEM::CMeshFEM(CGeometry* geometry, CConfig* config) { /*--- Resize the first index of the send buffers to nRankRecv, because this number of messages must be sent back to the sending ranks with halo information. ---*/ - nRankRecv = (int)longSecondRecvBuf.size(); + nRankRecv = static_cast(longSecondRecvBuf.size()); shortSendBuf.resize(nRankRecv); longSendBuf.resize(nRankRecv); doubleSendBuf.resize(nRankRecv); @@ -2452,12 +2457,16 @@ void CMeshFEM_DG::CreateFaces(CConfig* config) { is set to false. Hence it is only needed to carry out this check for faces with a constant Jacobian. This is done to reduce the number of standard elements. */ if (thisFace.JacFaceIsConsideredConstant) { - auto orderExactStraight = (unsigned short)ceil(thisFace.nPolyGrid0 * config->GetQuadrature_Factor_Straight()); - auto orderExactCurved = (unsigned short)ceil(thisFace.nPolyGrid0 * config->GetQuadrature_Factor_Curved()); + auto orderExactStraight = + static_cast(ceil(thisFace.nPolyGrid0 * config->GetQuadrature_Factor_Straight())); + auto orderExactCurved = + static_cast(ceil(thisFace.nPolyGrid0 * config->GetQuadrature_Factor_Curved())); if (orderExactStraight == orderExactCurved) { - orderExactStraight = (unsigned short)ceil(thisFace.nPolySol0 * config->GetQuadrature_Factor_Straight()); - orderExactCurved = (unsigned short)ceil(thisFace.nPolySol0 * config->GetQuadrature_Factor_Curved()); + orderExactStraight = + static_cast(ceil(thisFace.nPolySol0 * config->GetQuadrature_Factor_Straight())); + orderExactCurved = + static_cast(ceil(thisFace.nPolySol0 * config->GetQuadrature_Factor_Curved())); if (orderExactStraight == orderExactCurved) thisFace.JacFaceIsConsideredConstant = false; } } @@ -3156,7 +3165,7 @@ void CMeshFEM_DG::SetSendReceive(const CConfig* config) { map rankToIndRecvBuf; for (int i = 0; i < size; ++i) { if (recvFromRank[i]) { - int ind = (int)rankToIndRecvBuf.size(); + int ind = static_cast(rankToIndRecvBuf.size()); rankToIndRecvBuf[i] = ind; } } diff --git a/Common/src/fem/fem_standard_element.cpp b/Common/src/fem/fem_standard_element.cpp index b24c7cacfaf..ab8e537ad9c 100644 --- a/Common/src/fem/fem_standard_element.cpp +++ b/Common/src/fem/fem_standard_element.cpp @@ -186,9 +186,9 @@ CFEMStandardElementBase::CFEMStandardElementBase(unsigned short val_VTK_Type, un orderExact = val_orderExact; } else { if (constJacobian) - orderExact = (unsigned short)ceil(val_nPoly * config->GetQuadrature_Factor_Straight()); + orderExact = static_cast(ceil(val_nPoly * config->GetQuadrature_Factor_Straight())); else - orderExact = (unsigned short)ceil(val_nPoly * config->GetQuadrature_Factor_Curved()); + orderExact = static_cast(ceil(val_nPoly * config->GetQuadrature_Factor_Curved())); } /*--- Determine the integration points. This depends on the element type. ---*/ @@ -903,7 +903,7 @@ void CFEMStandardElementBase::SubConnForPlottingLine(const unsigned short nPoly, /*--- Determine the local subconnectivity of the line element used for plotting purposes. This is rather trivial, because the line element is subdivided into nPoly linear line elements. ---*/ - unsigned short nnPoly = max(nPoly, (unsigned short)1); + unsigned short nnPoly = max(nPoly, static_cast(1)); for (unsigned short i = 0; i < nnPoly; ++i) { subConn.push_back(i); subConn.push_back(i + 1); @@ -916,7 +916,7 @@ void CFEMStandardElementBase::SubConnForPlottingQuadrilateral(const unsigned sho plotting purposes. Note that the connectivity of the linear subelements obey the VTK connectivity rule of a quadrilateral, which is different from the connectivity for the high order quadrilateral. ---*/ - unsigned short nnPoly = max(nPoly, (unsigned short)1); + unsigned short nnPoly = max(nPoly, static_cast(1)); for (unsigned short j = 0; j < nnPoly; ++j) { unsigned short jj = j * (nnPoly + 1); for (unsigned short i = 0; i < nnPoly; ++i) { diff --git a/Common/src/fem/geometry_structure_fem_part.cpp b/Common/src/fem/geometry_structure_fem_part.cpp index d9536a67903..f4734eed85d 100644 --- a/Common/src/fem/geometry_structure_fem_part.cpp +++ b/Common/src/fem/geometry_structure_fem_part.cpp @@ -25,14 +25,12 @@ * License along with SU2. If not, see . */ +#include "../../include/toolboxes/CLinearPartitioner.hpp" #include "../../include/geometry/CPhysicalGeometry.hpp" #include "../../include/fem/fem_standard_element.hpp" #include "../../include/geometry/primal_grid/CPrimalGridFEM.hpp" #include "../../include/geometry/primal_grid/CPrimalGridBoundFEM.hpp" -#ifdef HAVE_CGNS -#include "../../include/fem/fem_cgns_elements.hpp" -#endif #include "../../include/adt/CADTElemClass.hpp" #include "../../include/linear_algebra/blas_structure.hpp" @@ -363,1473 +361,109 @@ void CMatchingFace::Copy(const CMatchingFace& other) { tolForMatching = other.tolForMatching; } -void CPhysicalGeometry::Read_SU2_Format_Parallel_FEM(CConfig* config, const string& val_mesh_filename, - unsigned short val_iZone, unsigned short val_nZone) { - string text_line, Marker_Tag; - ifstream mesh_file; - string::size_type position; - unsigned long nDOFsGrid_Local = 0, loc_element_count = 0; - bool domain_flag = false; - bool time_spectral = config->GetTime_Marching() == TIME_MARCHING::HARMONIC_BALANCE; - unsigned short nMarker_Max = config->GetnMarker_Max(); - nZone = val_nZone; - - /*--- Initialize counters for local/global points & elements ---*/ - Global_nPoint = 0; - Global_nPointDomain = 0; - Global_nElem = 0; - nelem_edge = 0; - Global_nelem_edge = 0; - nelem_triangle = 0; - Global_nelem_triangle = 0; - nelem_quad = 0; - Global_nelem_quad = 0; - nelem_tetra = 0; - Global_nelem_tetra = 0; - nelem_hexa = 0; - Global_nelem_hexa = 0; - nelem_prism = 0; - Global_nelem_prism = 0; - nelem_pyramid = 0; - Global_nelem_pyramid = 0; - - /*--- Allocate memory for the linear partition of the elements of the mesh. - These arrays are the size of the number of ranks. ---*/ - - beg_node = new unsigned long[size]; - end_node = new unsigned long[size]; - nPointLinear = new unsigned long[size]; - - /*--- Open grid file ---*/ - - mesh_file.open(val_mesh_filename.c_str(), ios::in); - - /*--- Check the grid ---*/ - - if (mesh_file.fail()) - SU2_MPI::Error(string("There is no mesh file (CPhysicalGeometry)!! ") + val_mesh_filename, CURRENT_FUNCTION); - - /*--- If more than one, find the zone in the mesh file ---*/ - - if (val_nZone > 1 || time_spectral) { - if (time_spectral) { - if (rank == MASTER_NODE) cout << "Reading time spectral instance " << val_iZone + 1 << ":" << endl; - } else { - while (getline(mesh_file, text_line)) { - /*--- Search for the current domain ---*/ - position = text_line.find("IZONE=", 0); - if (position != string::npos) { - text_line.erase(0, 6); - unsigned short jDomain = atoi(text_line.c_str()); - if (jDomain == val_iZone + 1) { - if (rank == MASTER_NODE) cout << "Reading zone " << val_iZone + 1 << " points:" << endl; - break; - } - } - } - } - } - - /*--- Read grid file with format SU2 ---*/ - - while (getline(mesh_file, text_line)) { - /*--- Read the dimension of the problem ---*/ - - position = text_line.find("NDIME=", 0); - if (position != string::npos) { - if (!domain_flag) { - text_line.erase(0, 6); - nDim = atoi(text_line.c_str()); - if (rank == MASTER_NODE) { - if (nDim == 2) cout << "Two dimensional problem." << endl; - if (nDim == 3) cout << "Three dimensional problem." << endl; - } - domain_flag = true; - } else { - break; - } - } - - /*--- Read the information about inner elements ---*/ - - position = text_line.find("NELEM=", 0); - if (position != string::npos) { - text_line.erase(0, 6); - stringstream stream_line(text_line); - stream_line >> Global_nElem; - - if ((rank == MASTER_NODE) && (size > SINGLE_NODE)) - cout << Global_nElem << " interior elements before parallel partitioning." << endl; - - /*--- Check if the number of cores used is larger than the number of - elements. Terminate if this is the case, because it does not make - sense to do this. ---*/ - unsigned long nCores = size; // Correct for the number of cores per rank. - if (nCores > Global_nElem) { - ostringstream message; - message << "The number of cores, " << nCores; - message << ", is larger than the number of elements, " << Global_nElem << "." << endl; - message << "This is not an efficient use of the resources and therefore " - << "SU2 will terminate."; - - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - /*--- Compute the number of elements that will be on each processor. - This is a linear partitioning with the addition of a simple load - balancing for any remainder elements. ---*/ - unsigned long total_elem_accounted = 0; - for (unsigned long i = 0; i < (unsigned long)size; i++) { - nPointLinear[i] = Global_nElem / size; - total_elem_accounted = total_elem_accounted + nPointLinear[i]; - } - - /*--- Get the number of remainder elements after the even division ---*/ - unsigned long rem_elem = Global_nElem - total_elem_accounted; - for (unsigned long i = 0; i < rem_elem; i++) { - ++nPointLinear[i]; - } - - /*--- Store the local number of elements and the beginning/end index ---*/ - nElem = nPointLinear[rank]; - beg_node[0] = 0; - end_node[0] = beg_node[0] + nPointLinear[0]; - for (unsigned long i = 1; i < (unsigned long)size; i++) { - beg_node[i] = end_node[i - 1]; - end_node[i] = beg_node[i] + nPointLinear[i]; - } - - /*--- Allocate space for elements ---*/ - elem = new CPrimalGrid*[nElem](); - - /*--- Loop over all the elements and store the elements to be stored on - this rank. Furthermore, determine the total amount of DOFs for - the solution (which may differ from the number of DOFS for the - grid). ---*/ - unsigned long nDOFs_tot = 0; - for (unsigned long i = 0; i < Global_nElem; i++) { - getline(mesh_file, text_line); - istringstream elem_line(text_line); - - /*--- Read the value that defines the element type, the polynomial - degree of the geometry and the polynomial degree of the - solution. Extract this info as well. ---*/ - unsigned long typeRead; - elem_line >> typeRead; - unsigned long typeReadErrorMessage = typeRead; - unsigned short nPolySol, nPolyGrid; - if (typeRead > 10000) { - nPolySol = typeRead / 10000 - 1; - typeRead = typeRead % 10000; - nPolyGrid = typeRead / 100 + 1; - } else { - nPolyGrid = typeRead / 100 + 1; - nPolySol = nPolyGrid; - } - - unsigned short VTK_Type = typeRead % 100; - - unsigned short nDOFsGrid = CFEMStandardElementBase::GetNDOFsStatic(VTK_Type, nPolyGrid, typeReadErrorMessage); - unsigned short nDOFsSol = CFEMStandardElementBase::GetNDOFsStatic(VTK_Type, nPolySol, typeReadErrorMessage); - - /*--- Allocate the memory for a new primary grid FEM element if - this element must be stored on this rank. ---*/ - if ((i >= beg_node[rank]) && (i < end_node[rank])) { - elem[loc_element_count] = - new CPrimalGridFEM(i, VTK_Type, nPolyGrid, nPolySol, nDOFsGrid, nDOFsSol, nDOFs_tot, elem_line); - nDOFsGrid_Local += nDOFsGrid; - loc_element_count++; - } - - /*--- Update the total value of the number of DOFs. ---*/ - nDOFs_tot += nDOFsSol; - } - - /*--- Break from the loop to find the element information. ---*/ - break; - } - } - - mesh_file.close(); - - /*--- Create a vector, which contains the global node IDs of the local elements. ---*/ - vector nodeIDsElemLoc; - nodeIDsElemLoc.reserve(nDOFsGrid_Local); - for (unsigned long i = 0; i < loc_element_count; i++) { - unsigned short nDOFsElem = elem[i]->GetnNodes(); - for (unsigned short j = 0; j < nDOFsElem; ++j) nodeIDsElemLoc.push_back(elem[i]->GetNode(j)); - } +void CPhysicalGeometry::LoadLinearlyPartitionedPointsFEM(CConfig* config, CMeshReaderBase* mesh) { + /*--- Get the partitioned coordinates and their global IDs from the mesh object. ---*/ + const auto& gridCoords = mesh->GetLocalPointCoordinates(); + const auto& globalPointIDs = mesh->GetGlobalPointIDs(); - sort(nodeIDsElemLoc.begin(), nodeIDsElemLoc.end()); - vector::iterator lastNode; - lastNode = unique(nodeIDsElemLoc.begin(), nodeIDsElemLoc.end()); - nodeIDsElemLoc.erase(lastNode, nodeIDsElemLoc.end()); + /*--- Initialize point counts and the grid node data structure. ---*/ - /*--- Allocate the memory for the coordinates to be stored on this rank. ---*/ - nPoint = nodeIDsElemLoc.size(); nodes = new CPoint(nPoint, nDim); - /*--- Open the grid file again and go to the position where - the correct zone is stored. ---*/ - mesh_file.open(val_mesh_filename.c_str(), ios::in); - - if (val_nZone > 1 && !time_spectral) { - while (getline(mesh_file, text_line)) { - position = text_line.find("IZONE=", 0); - if (position != string::npos) { - text_line.erase(0, 6); - unsigned short jDomain = atoi(text_line.c_str()); - if (jDomain == val_iZone + 1) break; - } - } - } - - /*--- While loop to read the point information. ---*/ - while (getline(mesh_file, text_line)) { - position = text_line.find("NPOIN=", 0); - if (position != string::npos) { - text_line.erase(0, 6); - stringstream stream_line(text_line); - stream_line >> Global_nPoint; - - /*--- Loop over the global number of points and store the - ones that are needed on this processor. ---*/ - unsigned long ii = 0; - for (unsigned long i = 0; i < Global_nPoint; ++i) { - getline(mesh_file, text_line); - - if (binary_search(nodeIDsElemLoc.begin(), nodeIDsElemLoc.end(), i)) { - istringstream point_line(text_line); - su2double Coord[3] = {0.0}; - point_line >> Coord[0]; - point_line >> Coord[1]; - if (nDim == 3) point_line >> Coord[2]; - nodes->SetCoord(ii, Coord); - nodes->SetGlobalIndex(ii, i); - ++ii; - } - } - - break; - } - } - - mesh_file.close(); - - /*--- Determine the faces of the local elements. --- */ - vector localFaces; - for (unsigned long k = 0; k < loc_element_count; k++) { - /*--- Get the global IDs of the corner points of all the faces of this elements. ---*/ - unsigned short nFaces; - unsigned short nPointsPerFace[6]; - unsigned long faceConn[6][4]; - - elem[k]->GetCornerPointsAllFaces(nFaces, nPointsPerFace, faceConn); - - /*--- Loop over the faces and add them to localFaces. ---*/ - for (unsigned short i = 0; i < nFaces; ++i) { - CFaceOfElement thisFace; - thisFace.nCornerPoints = nPointsPerFace[i]; - for (unsigned short j = 0; j < nPointsPerFace[i]; ++j) thisFace.cornerPoints[j] = faceConn[i][j]; - thisFace.elemID0 = k + beg_node[rank]; - - thisFace.CreateUniqueNumbering(); - localFaces.push_back(thisFace); - } - } - - /*--- Sort localFaces in increasing order and remove the double entities, - such that the binary search later on is a bit more efficient. ---*/ - sort(localFaces.begin(), localFaces.end()); - vector::iterator lastFace; - lastFace = unique(localFaces.begin(), localFaces.end()); - localFaces.erase(lastFace, localFaces.end()); - - /*--- Open the grid file again and go to the position where - the correct zone is stored. ---*/ - mesh_file.open(val_mesh_filename.c_str(), ios::in); - - if (val_nZone > 1 && !time_spectral) { - while (getline(mesh_file, text_line)) { - position = text_line.find("IZONE=", 0); - if (position != string::npos) { - text_line.erase(0, 6); - unsigned short jDomain = atoi(text_line.c_str()); - if (jDomain == val_iZone + 1) break; - } - } - } - - /*--- While loop to read the boundary information. ---*/ - while (getline(mesh_file, text_line)) { - /*--- Read number of markers ---*/ - position = text_line.find("NMARK=", 0); - if (position != string::npos) { - text_line.erase(0, 6); - istringstream stream_line(text_line); - stream_line >> nMarker; - - if (rank == MASTER_NODE) cout << nMarker << " surface markers." << endl; - config->SetnMarker_All(nMarker); - bound = new CPrimalGrid**[nMarker]; - nElem_Bound = new unsigned long[nMarker]; - Tag_to_Marker = new string[nMarker_Max]; - - for (unsigned short iMarker = 0; iMarker < nMarker; iMarker++) { - getline(mesh_file, text_line); - text_line.erase(0, 11); - text_line.erase(remove(text_line.begin(), text_line.end(), ' '), text_line.end()); - text_line.erase(remove(text_line.begin(), text_line.end(), '\r'), text_line.end()); - text_line.erase(remove(text_line.begin(), text_line.end(), '\n'), text_line.end()); - - Marker_Tag = text_line; - - /*--- Read the number of elements for this marker. ---*/ - getline(mesh_file, text_line); - text_line.erase(0, 13); - istringstream nmark_line(text_line); - unsigned long nElem_Bound_Global; - nmark_line >> nElem_Bound_Global; - if (rank == MASTER_NODE) - cout << nElem_Bound_Global << " boundary elements in index " << iMarker << " (Marker = " << Marker_Tag << ")." - << endl; - - /*--- Define a vector of FEM boundary elements to store the local boundary faces. ---*/ - vector boundElems; - - /*--- Loop over the global boundary faces. ---*/ - for (unsigned long i = 0; i < nElem_Bound_Global; ++i) { - getline(mesh_file, text_line); - istringstream bound_line(text_line); - - /*--- Determine the element type, its number of DOFs and read - its node IDs. ---*/ - unsigned long typeRead; - bound_line >> typeRead; - unsigned short nPolyGrid = typeRead / 100 + 1; - unsigned short VTK_Type = typeRead % 100; - unsigned short nDOFEdgeGrid = nPolyGrid + 1; - - unsigned short nDOFsGrid = 0; - CFaceOfElement thisFace; - thisFace.cornerPoints[0] = 0; - thisFace.cornerPoints[1] = nPolyGrid; - switch (VTK_Type) { - case LINE: - nDOFsGrid = nDOFEdgeGrid; - thisFace.nCornerPoints = 2; - break; - - case TRIANGLE: - nDOFsGrid = nDOFEdgeGrid * (nDOFEdgeGrid + 1) / 2; - thisFace.nCornerPoints = 3; - thisFace.cornerPoints[2] = nDOFsGrid - 1; - break; - - case QUADRILATERAL: - nDOFsGrid = nDOFEdgeGrid * nDOFEdgeGrid; - thisFace.nCornerPoints = 4; - thisFace.cornerPoints[2] = nPolyGrid * nDOFEdgeGrid; - thisFace.cornerPoints[3] = nDOFsGrid - 1; - break; - - default: - ostringstream message; - message << "Unknown FEM boundary element value, " << typeRead << ", in " << val_mesh_filename; - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - vector nodeIDs(nDOFsGrid); - for (unsigned short j = 0; j < nDOFsGrid; ++j) bound_line >> nodeIDs[j]; - - /*--- Convert the local numbering of thisFace to global numbering - and create a unique numbering of these nodes. ---*/ - for (unsigned short j = 0; j < thisFace.nCornerPoints; ++j) - thisFace.cornerPoints[j] = nodeIDs[thisFace.cornerPoints[j]]; - thisFace.CreateUniqueNumbering(); - - /*--- Check if this boundary face must be stored on this rank. - If so, create an object of CBoundaryFace and add it - to boundElems. ---*/ - vector::iterator low; - low = lower_bound(localFaces.begin(), localFaces.end(), thisFace); - if (low != localFaces.end()) { - if (!(thisFace < *low)) { - CBoundaryFace thisBoundFace; - thisBoundFace.VTK_Type = VTK_Type; - thisBoundFace.nPolyGrid = nPolyGrid; - thisBoundFace.nDOFsGrid = nDOFsGrid; - thisBoundFace.globalBoundElemID = i; - thisBoundFace.domainElementID = low->elemID0; - thisBoundFace.Nodes = nodeIDs; - - boundElems.push_back(thisBoundFace); - } - } - } - - /*--- Allocate space for the boundary elements and store the ones - whose parent element is stored on this rank. ---*/ - nElem_Bound[iMarker] = boundElems.size(); - bound[iMarker] = new CPrimalGrid*[nElem_Bound[iMarker]]; - - for (unsigned long i = 0; i < nElem_Bound[iMarker]; ++i) - bound[iMarker][i] = new CPrimalGridBoundFEM(boundElems[i].globalBoundElemID, boundElems[i].domainElementID, - boundElems[i].VTK_Type, boundElems[i].nPolyGrid, - boundElems[i].nDOFsGrid, boundElems[i].Nodes); - - /*--- Update config information storing the boundary information in the right place ---*/ - Tag_to_Marker[config->GetMarker_CfgFile_TagBound(Marker_Tag)] = Marker_Tag; - config->SetMarker_All_TagBound(iMarker, Marker_Tag); - config->SetMarker_All_KindBC(iMarker, config->GetMarker_CfgFile_KindBC(Marker_Tag)); - config->SetMarker_All_Monitoring(iMarker, config->GetMarker_CfgFile_Monitoring(Marker_Tag)); - config->SetMarker_All_GeoEval(iMarker, config->GetMarker_CfgFile_GeoEval(Marker_Tag)); - config->SetMarker_All_Designing(iMarker, config->GetMarker_CfgFile_Designing(Marker_Tag)); - config->SetMarker_All_Plotting(iMarker, config->GetMarker_CfgFile_Plotting(Marker_Tag)); - config->SetMarker_All_Analyze(iMarker, config->GetMarker_CfgFile_Analyze(Marker_Tag)); - config->SetMarker_All_ZoneInterface(iMarker, config->GetMarker_CfgFile_ZoneInterface(Marker_Tag)); - config->SetMarker_All_DV(iMarker, config->GetMarker_CfgFile_DV(Marker_Tag)); - config->SetMarker_All_Moving(iMarker, config->GetMarker_CfgFile_Moving(Marker_Tag)); - config->SetMarker_All_SobolevBC(iMarker, config->GetMarker_CfgFile_SobolevBC(Marker_Tag)); - config->SetMarker_All_PerBound(iMarker, config->GetMarker_CfgFile_PerBound(Marker_Tag)); - config->SetMarker_All_SendRecv(iMarker, NONE); - } - - break; - } + /*--- Loop over the points and set the coordinates and global index. ---*/ + for (unsigned long iPoint = 0; iPoint < nPoint; iPoint++) { + for (unsigned short iDim = 0; iDim < nDim; ++iDim) nodes->SetCoord(iPoint, iDim, gridCoords[iDim][iPoint]); + nodes->SetGlobalIndex(iPoint, globalPointIDs[iPoint]); } - - mesh_file.close(); } -void CPhysicalGeometry::Read_CGNS_Format_Parallel_FEM(CConfig* config, const string& val_mesh_filename, - unsigned short val_iZone, unsigned short val_nZone) { -#ifdef HAVE_CGNS - - /*--- For proper support of the high order elements, at least version 3.3 - of CGNS must be used. Check this. ---*/ -#if CGNS_VERSION >= 3300 - - /*--- Check whether the supplied file is truly a CGNS file. ---*/ - int file_type; - if (cg_is_cgns(val_mesh_filename.c_str(), &file_type) != CG_OK) - SU2_MPI::Error(val_mesh_filename + string(" is not a CGNS file that can be read."), CURRENT_FUNCTION); - - /*--- Initialize counters for local/global points & elements ---*/ - Global_nPoint = 0; - Global_nPointDomain = 0; - Global_nElem = 0; - nelem_edge = 0; - Global_nelem_edge = 0; - nelem_triangle = 0; - Global_nelem_triangle = 0; - nelem_quad = 0; - Global_nelem_quad = 0; - nelem_tetra = 0; - Global_nelem_tetra = 0; - nelem_hexa = 0; - Global_nelem_hexa = 0; - nelem_prism = 0; - Global_nelem_prism = 0; - nelem_pyramid = 0; - Global_nelem_pyramid = 0; - - /*--------------------------------------------------------------------------*/ - /*--- Checking of the file, determine the dimensions, etc. ---*/ - /*--------------------------------------------------------------------------*/ +void CPhysicalGeometry::LoadLinearlyPartitionedVolumeElementsFEM(CConfig* config, CMeshReaderBase* mesh) { + /*--- Reset the global to local element mapping. ---*/ + Global_to_Local_Elem.clear(); - /*--- Allocate memory for the linear partition of the elements of the mesh. - These arrays are the size of the number of ranks. ---*/ - beg_node = new unsigned long[size]; - end_node = new unsigned long[size]; - nPointLinear = new unsigned long[size]; - - /* Open the CGNS file for reading and check if it went OK. */ - int fn; - if (cg_open(val_mesh_filename.c_str(), CG_MODE_READ, &fn) != CG_OK) cg_error_exit(); - if (rank == MASTER_NODE) { - cout << "Reading the CGNS file: " << val_mesh_filename << "." << endl; - } - - /* Get the number of databases. This is the highest node in the CGNS heirarchy. - The current implementation assumes that there is only one database. */ - int nbases; - if (cg_nbases(fn, &nbases) != CG_OK) cg_error_exit(); - if (nbases > 1) { - ostringstream message; - message << "CGNS file contains " << nbases << " databases." << endl; - message << "CGNS reader can handle only 1 at the moment." << endl; + /*--- Get the volume connectivity from the mesh object. ---*/ + const auto& dataElems = mesh->GetLocalVolumeElementConnectivity(); - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - /* Read the information of the base, especially the number of dimensions. */ - char cgnsname[CGNS_STRING_SIZE]; - int cellDim, physDim; - const int iBase = 1; - if (cg_base_read(fn, iBase, cgnsname, &cellDim, &physDim)) cg_error_exit(); - nDim = physDim; - - if (cellDim != physDim) { - ostringstream message; - message << "The element dimension, " << cellDim << ", differs from the physical dimension, " << physDim << "." - << endl; - message << "These should be the same for the DG-FEM solver." << endl; - - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - /* Write the info about the number of dimensions. */ - if (rank == MASTER_NODE) { - if (nDim == 2) cout << "Two dimensional problem." << endl; - if (nDim == 3) cout << "Three dimensional problem." << endl; - } - - /* Set the zone ID for CGNS, CGNS indices starts at 1, and check - if the requested zone ID is a valid one. */ - const int iZone = val_iZone + 1; - - int nzones; - if (cg_nzones(fn, iBase, &nzones) != CG_OK) cg_error_exit(); - if (iZone < 1 || iZone > nzones) { - ostringstream message; - message << "Zone " << iZone << " requested for reading, but there are only " << nzones - << " zones present in the CGNS file." << endl; - - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - /* Determine the zone type for the requested zone and check if it is - unstructured. */ - ZoneType_t zoneType; - if (cg_zone_type(fn, iBase, iZone, &zoneType) != CG_OK) cg_error_exit(); - if (zoneType != Unstructured) - SU2_MPI::Error("Structured CGNS zone found while unstructured expected.", CURRENT_FUNCTION); - - /* Determine the number of sections for the connectivities in this zone. */ - int nsections; - if (cg_nsections(fn, iBase, iZone, &nsections) != CG_OK) cg_error_exit(); - - /*--------------------------------------------------------------------------*/ - /*--- Reading and distributing the volume elements. ---*/ - /*--------------------------------------------------------------------------*/ - - /* Loop over the sections to store the meta data in CGNSElemTypes - and to determine the number of volume elements. - Note that the indices start at 1 in CGNS. */ - vector CGNSElemTypes(nsections); - - for (int iConn = 1; iConn <= nsections; ++iConn) { - CGNSElemTypes[iConn - 1].DetermineMetaData(nDim, fn, iBase, iZone, iConn); - if (CGNSElemTypes[iConn - 1].volumeConn) Global_nElem += CGNSElemTypes[iConn - 1].nElem; - } - - if ((rank == MASTER_NODE) && (size > SINGLE_NODE)) - cout << Global_nElem << " interior elements before parallel partitioning." << endl; - - /*--- Check if the number of cores used is larger than the number of - elements. Terminate if this is the case, because it does not make - sense to do this. ---*/ - unsigned long nCores = size; // Correct for the number of cores per rank. - if (nCores > Global_nElem) { - ostringstream message; - message << "The number of cores, " << nCores; - message << ", is larger than the number of elements, " << Global_nElem << "." << endl; - message << "This is not an efficient use of the resources and therefore " - << "SU2 will terminate."; - - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - /*--- Compute the number of elements that will be stored on each rank. - This is a linear partitioning with the addition of a simple load - balancing for any remainder elements. ---*/ - unsigned long total_elem_accounted = 0; - for (unsigned long i = 0; i < (unsigned long)size; i++) { - nPointLinear[i] = Global_nElem / size; - total_elem_accounted = total_elem_accounted + nPointLinear[i]; - } - - /*--- Get the number of remainder elements after the even division ---*/ - const unsigned long rem_elem = Global_nElem - total_elem_accounted; - for (unsigned long i = 0; i < rem_elem; i++) { - ++nPointLinear[i]; - } + /*--- Allocate space for the interior elements in our SU2 data + structure. Note that we only instantiate our rank's local set. ---*/ + elem = new CPrimalGrid*[nElem](); - /*--- Store the local number of elements and the beginning/end index ---*/ - nElem = nPointLinear[rank]; - beg_node[0] = 0; - end_node[0] = beg_node[0] + nPointLinear[0]; - for (int i = 1; i < size; i++) { - beg_node[i] = end_node[i - 1]; - end_node[i] = beg_node[i] + nPointLinear[i]; - } + /*--- Loop over all of the internal, local volumetric elements. ---*/ + unsigned long ind = 0; + unsigned long offsetSolDOFs = 0; + for (unsigned long jElem = 0; jElem < nElem; ++jElem) { + /*--- Create a FEM element from the data dataElems. ---*/ + const auto* dataElem = dataElems.data() + ind; + elem[jElem] = new CPrimalGridFEM(dataElem, offsetSolDOFs); - /*--- Allocate space for elements ---*/ - elem = new CPrimalGrid*[nElem](); + /*--- Store the global to local mapping in Global_to_Local_Elem. ---*/ + Global_to_Local_Elem[dataElem[4]] = jElem; - /*--- Loop over over the connectivities and read the elements to be stored on - this rank. Furthermore, determine the local amount of DOFs for the - solution (which may differ from the number of DOFS for the grid). ---*/ - unsigned long nDOFsLoc = 0, elemCount = 0, locElemCount = 0; - - for (int iConn = 0; iConn < nsections; ++iConn) { - if (CGNSElemTypes[iConn].volumeConn) { - /* Determine the global volume element range for this connectivity. */ - const unsigned long elemCountOld = elemCount; - elemCount += CGNSElemTypes[iConn].nElem; - - /* Check for overlap with the element range this rank is responsible for. */ - const unsigned long indBegOverlap = max(elemCountOld, beg_node[rank]); - const unsigned long indEndOverlap = min(elemCount, end_node[rank]); - - if (indEndOverlap > indBegOverlap) { - /* This rank must read element data from this connectivity section. - Determine the offset relative to the start of this section and the - number of elements to be read by this rank. */ - const unsigned long offsetRank = indBegOverlap - elemCountOld; - const unsigned long nElemRank = indEndOverlap - indBegOverlap; - - /* Read the connectivity range determined above. */ - CGNSElemTypes[iConn].ReadConnectivityRange(fn, iBase, iZone, offsetRank, nElemRank, beg_node[rank], elem, - locElemCount, nDOFsLoc); - } - } + /*--- Update ind for the next element. ---*/ + ind += dataElem[3] + 5; } #ifdef HAVE_MPI - /* The global offset of the DOFs must be corrected when running in + /* The global offset of the solution DOFs must be corrected when running in parallel. Therefore gather the number of DOFs of all the ranks. */ - vector nDOFsPerRank(size); - SU2_MPI::Allgather(&nDOFsLoc, 1, MPI_UNSIGNED_LONG, nDOFsPerRank.data(), 1, MPI_UNSIGNED_LONG, SU2_MPI::GetComm()); + vector nSolDOFsPerRank(size); + SU2_MPI::Allgather(&offsetSolDOFs, 1, MPI_UNSIGNED_LONG, nSolDOFsPerRank.data(), 1, MPI_UNSIGNED_LONG, + SU2_MPI::GetComm()); /* Determine the offset for the DOFs on this rank. */ unsigned long offsetRank = 0; - for (int i = 0; i < rank; ++i) offsetRank += nDOFsPerRank[i]; + for (int i = 0; i < rank; ++i) offsetRank += nSolDOFsPerRank[i]; /* Loop over the local elements to correct the global offset of the DOFs. */ - for (unsigned long i = 0; i < nElem; ++i) elem[i]->AddOffsetGlobalDOFs(offsetRank); -#endif - - /*--------------------------------------------------------------------------*/ - /*--- Reading and distributing the coordinates. ---*/ - /*--------------------------------------------------------------------------*/ - - /* Determine the global number of vertices in the requested zone. - The other size information is not used. */ - cgsize_t sizes[3]; - if (cg_zone_read(fn, iBase, iZone, cgnsname, sizes) != CG_OK) cg_error_exit(); - Global_nPoint = sizes[0]; - - /*--- Determine the number of points per rank in cumulative storage format. - This is done to avoid that every rank reads all the coordinates. - The required coordinates for each rank are later obtained via - communication. ---*/ - unsigned long totalPointsAccounted = 0; - vector nPointsPerRank(size + 1); - for (int i = 1; i <= size; ++i) { - nPointsPerRank[i] = Global_nPoint / size; - totalPointsAccounted += nPointsPerRank[i]; - } - - const unsigned long nPointsRem = Global_nPoint - totalPointsAccounted; - for (unsigned long i = 1; i <= nPointsRem; ++i) ++nPointsPerRank[i]; - - nPointsPerRank[0] = 0; - for (int i = 0; i < size; ++i) nPointsPerRank[i + 1] += nPointsPerRank[i]; - - /* Determine the number of points that must be read by this rank and - allocate the memory for the coordinate buffers. */ - const cgsize_t nPointsRead = nPointsPerRank[rank + 1] - nPointsPerRank[rank]; - vector > coorBuf(nDim, vector(nPointsRead)); - - /* Loop over the number of dimensions to read the coordinates. Note that - the loop starts at 1 and ends at nDim because CGNS requires this. */ - for (unsigned short iDim = 1; iDim <= nDim; ++iDim) { - /* Determine the data type and name of the coordinate. Copy the name - of the coordinate in a string for easier comparison. */ - DataType_t datatype; - if (cg_coord_info(fn, iBase, iZone, iDim, &datatype, cgnsname) != CG_OK) cg_error_exit(); - string coorname = cgnsname; - - /* Check the name of the coordinate and determine the index in coorBuf - where to store this coordinate. Normally this should be iDim-1. */ - unsigned short indC = 0; - if (coorname == "CoordinateX") - indC = 0; - else if (coorname == "CoordinateY") - indC = 1; - else if (coorname == "CoordinateZ") - indC = 2; - else - SU2_MPI::Error(string("Unknown coordinate name, ") + coorname + string(", encountered in the CGNS file."), - CURRENT_FUNCTION); - - /* Easier storage of the range in the CGNS file. */ - cgsize_t range_min = nPointsPerRank[rank] + 1; - cgsize_t range_max = nPointsPerRank[rank + 1]; - - /*--- Read the coordinate with the required precision and copy - this data to the correct index in coorBuf. ---*/ - switch (datatype) { - case RealSingle: { - /* Single precision used. */ - vector buf(nPointsRead); - if (cg_coord_read(fn, iBase, iZone, cgnsname, datatype, &range_min, &range_max, buf.data()) != CG_OK) - cg_error_exit(); - - for (cgsize_t i = 0; i < nPointsRead; ++i) coorBuf[indC][i] = buf[i]; - break; - } - - case RealDouble: { - /* Double precision used. */ - vector buf(nPointsRead); - if (cg_coord_read(fn, iBase, iZone, cgnsname, datatype, &range_min, &range_max, buf.data()) != CG_OK) - cg_error_exit(); - - for (cgsize_t i = 0; i < nPointsRead; ++i) coorBuf[indC][i] = buf[i]; - break; - } - - default: { - ostringstream message; - message << "Datatype for coordinates must be RealSingle or RealDouble, " - << "not " << datatype << endl; - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - } - } - - /* Make a distinction between sequential and parallel mode. */ -#ifdef HAVE_MPI - /*--- Parallel mode. Create a vector, which contains the global - node IDs of the local elements. ---*/ - vector nodeIDsElemLoc; - nodeIDsElemLoc.reserve(nDOFsLoc); - for (unsigned long i = 0; i < locElemCount; ++i) { - unsigned short nDOFsElem = elem[i]->GetnNodes(); - for (unsigned short j = 0; j < nDOFsElem; ++j) nodeIDsElemLoc.push_back(elem[i]->GetNode(j)); - } - - sort(nodeIDsElemLoc.begin(), nodeIDsElemLoc.end()); - vector::iterator lastNode; - lastNode = unique(nodeIDsElemLoc.begin(), nodeIDsElemLoc.end()); - nodeIDsElemLoc.erase(lastNode, nodeIDsElemLoc.end()); - - /*--- Allocate the memory for the coordinates to be stored on this rank. ---*/ - nPoint = nodeIDsElemLoc.size(); - nodes = new CPoint(nPoint, nDim); - - /*--- Store the global ID's of the nodes in such a way that they can - be sent to the rank that actually stores the coordinates.. ---*/ - vector > nodeBuf(size, vector(0)); - for (unsigned long i = 0; i < nodeIDsElemLoc.size(); ++i) { - const cgsize_t nodeID = nodeIDsElemLoc[i]; - vector::iterator low; - low = lower_bound(nPointsPerRank.begin(), nPointsPerRank.end(), nodeID); - cgsize_t rankNode = low - nPointsPerRank.begin(); - if (*low > nodeID) --rankNode; - - nodeBuf[rankNode].push_back(nodeIDsElemLoc[i]); - } - - /*--- Determine the total number of ranks to which this rank will send - a message and also determine the number of ranks from which this - rank will receive a message. Furthermore, determine the starting - indices where data from the different ranks should be stored in - node. ---*/ - int nRankSend = 0; - vector sendToRank(size, 0); - vector startingIndRanksInNode(size + 1); - startingIndRanksInNode[0] = 0; - - for (int i = 0; i < size; ++i) { - startingIndRanksInNode[i + 1] = startingIndRanksInNode[i] + nodeBuf[i].size(); - - if (!nodeBuf[i].empty()) { - ++nRankSend; - sendToRank[i] = 1; - } - } - - int nRankRecv; - vector sizeRecv(size, 1); - SU2_MPI::Reduce_scatter(sendToRank.data(), &nRankRecv, sizeRecv.data(), MPI_INT, MPI_SUM, SU2_MPI::GetComm()); - - /*--- Send out the messages with the global node numbers. Use nonblocking - sends to avoid deadlock. ---*/ - vector sendReqs(nRankSend); - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!nodeBuf[i].empty()) { - SU2_MPI::Isend(nodeBuf[i].data(), nodeBuf[i].size(), MPI_UNSIGNED_LONG, i, i, SU2_MPI::GetComm(), - &sendReqs[nRankSend]); - ++nRankSend; - } - } - - /* Define the communication buffer for the coordinates and the vector - for the return communication requests. */ - vector returnReqs(nRankRecv); - vector > coorReturnBuf(nRankRecv, vector(0)); - - /*--- Loop over the number of ranks from which this rank receives global - point numbers that should be stored on this rank. ---*/ - for (int i = 0; i < nRankRecv; ++i) { - /* Block until a message arrives. Determine the source and size - of the message. */ - SU2_MPI::Status status; - SU2_MPI::Probe(MPI_ANY_SOURCE, rank, SU2_MPI::GetComm(), &status); - int source = status.MPI_SOURCE; - - int sizeMess; - SU2_MPI::Get_count(&status, MPI_UNSIGNED_LONG, &sizeMess); - - /* Allocate the memory for a buffer to receive this message and also - for the buffer to return to coordinates. */ - vector nodeRecvBuf(sizeMess); - coorReturnBuf[i].resize(nDim * sizeMess); - - /* Receive the message using a blocking receive. */ - SU2_MPI::Recv(nodeRecvBuf.data(), sizeMess, MPI_UNSIGNED_LONG, source, rank, SU2_MPI::GetComm(), &status); - - /*--- Loop over the nodes just received and fill the return communication - buffer with the coordinates of the requested nodes. ---*/ - for (int j = 0; j < sizeMess; ++j) { - const int jj = nDim * j; - const long kk = nodeRecvBuf[j] - nPointsPerRank[rank]; - if (kk < 0 || kk >= nPointsRead) - SU2_MPI::Error("Invalid point requested. This should not happen.", CURRENT_FUNCTION); - - for (unsigned short k = 0; k < nDim; ++k) coorReturnBuf[i][jj + k] = coorBuf[k][kk]; - } - - /* Send the buffer just filled back to the requesting rank. - Use a non-blocking send to avoid deadlock. */ - SU2_MPI::Isend(coorReturnBuf[i].data(), coorReturnBuf[i].size(), MPI_DOUBLE, source, source + 1, SU2_MPI::GetComm(), - &returnReqs[i]); - } - - /* Loop over the ranks from which this rank has requested coordinates. */ - for (int i = 0; i < nRankSend; ++i) { - /* Block until a message arrives. Determine the source of the message. */ - SU2_MPI::Status status; - SU2_MPI::Probe(MPI_ANY_SOURCE, rank + 1, SU2_MPI::GetComm(), &status); - int source = status.MPI_SOURCE; - - /* Allocate the memory for the coordinate receive buffer. */ - vector coorRecvBuf(nDim * nodeBuf[source].size()); - - /* Receive the message using a blocking receive. */ - SU2_MPI::Recv(coorRecvBuf.data(), coorRecvBuf.size(), MPI_DOUBLE, source, rank + 1, SU2_MPI::GetComm(), &status); - - /*--- Make a distinction between 2D and 3D to store the data of the nodes. - This data is created by taking the offset of the source rank into - account. In this way the nodes are numbered with increading - global node ID. ---*/ - for (unsigned long j = 0; j < nodeBuf[source].size(); ++j) { - const unsigned long jj = nDim * j; - const unsigned long kk = startingIndRanksInNode[source] + j; - - nodes->SetCoord(kk, &coorRecvBuf[jj]); - nodes->SetGlobalIndex(kk, nodeBuf[source][j]); - } - } - - /* Complete the non-blocking sends of both rounds. */ - SU2_MPI::Waitall(sendReqs.size(), sendReqs.data(), MPI_STATUSES_IGNORE); - SU2_MPI::Waitall(returnReqs.size(), returnReqs.data(), MPI_STATUSES_IGNORE); - - /* Wild cards have been used in the communication, - so synchronize the ranks to avoid problems. */ - SU2_MPI::Barrier(SU2_MPI::GetComm()); - -#else - /*--- Sequential mode. Create the data for the points. The global - number of points equals the local number of points. ---*/ - nPoint = Global_nPoint; - nodes = new CPoint(nPoint, nDim); - - for (unsigned long i = 0; i < nPoint; ++i) { - for (unsigned short iDim = 0; iDim < nDim; ++iDim) nodes->SetCoord(i, iDim, coorBuf[iDim][i]); - nodes->SetGlobalIndex(i, i); - } - -#endif - - /*--------------------------------------------------------------------------*/ - /*--- Determine and distribute the single faces of the elements. These ---*/ - /*--- faces are distributed over the ranks such that later the boundary ---*/ - /*--- can retrieve the info on which rank they must be stored without ---*/ - /*--- each rank having to read the entire connectivity data. ---*/ - /*--------------------------------------------------------------------------*/ - - /*--- Determine the faces of the local elements. --- */ - vector localFaces; - for (unsigned long k = 0; k < nElem; ++k) { - /*--- Get the global IDs of the corner points of all the faces of this elements. ---*/ - unsigned short nFaces; - unsigned short nPointsPerFace[6]; - unsigned long faceConn[6][4]; - - elem[k]->GetCornerPointsAllFaces(nFaces, nPointsPerFace, faceConn); - - /*--- Loop over the faces and add them to localFaces. For consistency - between sequential and parallel mode the rank is stored at the - position for the second element ID. ---*/ - for (unsigned short i = 0; i < nFaces; ++i) { - CFaceOfElement thisFace; - thisFace.nCornerPoints = nPointsPerFace[i]; - for (unsigned short j = 0; j < nPointsPerFace[i]; ++j) thisFace.cornerPoints[j] = faceConn[i][j]; - thisFace.elemID0 = k + beg_node[rank]; - thisFace.elemID1 = rank; - - thisFace.CreateUniqueNumbering(); - localFaces.push_back(thisFace); - } - } - - /*--- Sort localFaces in increasing order and remove the double entities, - such that unnecessary data is not communicated later on. ---*/ - sort(localFaces.begin(), localFaces.end()); - vector::iterator lastFace; - lastFace = unique(localFaces.begin(), localFaces.end()); - localFaces.erase(lastFace, localFaces.end()); - -#ifdef HAVE_MPI - - /*--- In parallel mode these faces must be distributed over the ranks. - A face is stored on the rank where its first node ID is located - based on nPointsPerRank. Define the communication buffers and - determine their contents. ---*/ - vector > faceBuf(size, vector(0)); - for (unsigned long i = 0; i < localFaces.size(); ++i) { - const cgsize_t nodeID = localFaces[i].cornerPoints[0]; - vector::iterator low; - low = lower_bound(nPointsPerRank.begin(), nPointsPerRank.end(), nodeID); - cgsize_t rankNode = low - nPointsPerRank.begin(); - if (*low > nodeID) --rankNode; - - faceBuf[rankNode].push_back(localFaces[i].nCornerPoints); - for (unsigned short j = 0; j < localFaces[i].nCornerPoints; ++j) - faceBuf[rankNode].push_back(localFaces[i].cornerPoints[j]); - faceBuf[rankNode].push_back(localFaces[i].elemID0); - } - - /* Delete the memory of localFaces again, because its contents will be - build from the messages that this rank will receive. */ - localFaces.clear(); - - /*--- Determine the number of messages this rank will send and receive. ---*/ - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!faceBuf[i].empty()) { - ++nRankSend; - sendToRank[i] = 1; - } else { - sendToRank[i] = 0; - } - } - - SU2_MPI::Reduce_scatter(sendToRank.data(), &nRankRecv, sizeRecv.data(), MPI_INT, MPI_SUM, SU2_MPI::GetComm()); - - /*--- Send the messages using non-blocking sends to avoid deadlock. ---*/ - sendReqs.resize(nRankSend); - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!faceBuf[i].empty()) { - SU2_MPI::Isend(faceBuf[i].data(), faceBuf[i].size(), MPI_UNSIGNED_LONG, i, i + 4, SU2_MPI::GetComm(), - &sendReqs[nRankSend]); - ++nRankSend; - } - } - - /* Loop over the number of ranks from which this rank will receive data. */ - for (int i = 0; i < nRankRecv; ++i) { - /* Block until a message arrives and determine the source and size - of the message. */ - SU2_MPI::Status status; - SU2_MPI::Probe(MPI_ANY_SOURCE, rank + 4, SU2_MPI::GetComm(), &status); - int source = status.MPI_SOURCE; - - int sizeMess; - SU2_MPI::Get_count(&status, MPI_UNSIGNED_LONG, &sizeMess); - - /* Allocate the memory for the receive buffer and receive the - message using a non-blocking receive. */ - vector faceRecvBuf(sizeMess); - SU2_MPI::Recv(faceRecvBuf.data(), faceRecvBuf.size(), MPI_UNSIGNED_LONG, source, rank + 4, SU2_MPI::GetComm(), - &status); - - /* Loop to extract the data from the receive buffer. */ - int ii = 0; - while (ii < sizeMess) { - /* Store the data for this face in localFaces. The rank where the - corresponding element is physically present is stored in the - second element ID. Note that it is not necessary to create a unique - numbering anymore, because this has already been done before the - communication buffer was created. */ - CFaceOfElement thisFace; - thisFace.nCornerPoints = (unsigned short)faceRecvBuf[ii++]; - for (unsigned short j = 0; j < thisFace.nCornerPoints; ++j, ++ii) thisFace.cornerPoints[j] = faceRecvBuf[ii]; - thisFace.elemID0 = faceRecvBuf[ii++]; - - thisFace.elemID1 = source; - localFaces.push_back(thisFace); - } - } - - /*--- Sort localFaces in increasing order and remove the double entities, - such that searching is a bit more efficient later on. ---*/ - sort(localFaces.begin(), localFaces.end()); - lastFace = unique(localFaces.begin(), localFaces.end()); - localFaces.erase(lastFace, localFaces.end()); - - /* Complete the non-blocking sends. Afterwards, synchronize the ranks, - because wild cards have been used. */ - SU2_MPI::Waitall(sendReqs.size(), sendReqs.data(), MPI_STATUSES_IGNORE); - SU2_MPI::Barrier(SU2_MPI::GetComm()); - + for (unsigned long jElem = 0; jElem < nElem; ++jElem) elem[jElem]->AddOffsetGlobalDOFs(offsetRank); #endif +} - /*--------------------------------------------------------------------------*/ - /*--- Reading and distributing the surface elements. ---*/ - /*--------------------------------------------------------------------------*/ - - /* Determine the number of families in this base and read their names. - Note that when multiple zones are present, this step is repeated for - every zone. */ - int nFamilies; - if (cg_nfamilies(fn, iBase, &nFamilies) != CG_OK) cg_error_exit(); - - vector familyNames(nFamilies); - for (int i = 1; i <= nFamilies; ++i) { - int nFamBC, nGeo; - if (cg_family_read(fn, iBase, i, cgnsname, &nFamBC, &nGeo) != CG_OK) cg_error_exit(); - familyNames[i - 1] = cgnsname; - } - - /* Determine the number of boundary conditions for this zone. */ - int nBCs; - if (cg_nbocos(fn, iBase, iZone, &nBCs) != CG_OK) cg_error_exit(); - - /* Read the names of the boundary conditions and determine their family names. - If not family name is specified for a boundary condition, the family name - is set to the name of the boundary condition. */ - vector BCNames(nBCs), BCFamilyNames(nBCs); - for (int i = 1; i <= nBCs; ++i) { - /* Read the info for this boundary condition. */ - BCType_t BCType; - PointSetType_t ptsetType; - cgsize_t npnts, NormalListSize; - int NormalIndex, nDataSet; - DataType_t NormalDataType; - if (cg_boco_info(fn, iBase, iZone, i, cgnsname, &BCType, &ptsetType, &npnts, &NormalIndex, &NormalListSize, - &NormalDataType, &nDataSet) != CG_OK) - cg_error_exit(); - BCNames[i - 1] = cgnsname; - - /* Read the possibly family name and set it. If not present, it is - equal to BCName. */ - if (cg_goto(fn, iBase, "Zone_t", iZone, "ZoneBC_t", 1, "BC_t", i, "end") != CG_OK) cg_error_exit(); - - int ierr = cg_famname_read(cgnsname); - if (ierr == CG_ERROR) - cg_error_exit(); - else if (ierr == CG_OK) - BCFamilyNames[i - 1] = cgnsname; - else - BCFamilyNames[i - 1] = BCNames[i - 1]; - } - - /*--- Determine the number of different surface connectivities. It is - possible to specify a family name for a surface connectivity. If that - family name is the same for multiple surface connectivities, these - connectivities are merged together for the boundary condition - treatment in the DG-FEM solver. ---*/ - vector surfaceNames; - vector > surfaceConnIDs; - - for (int i = 0; i < nsections; ++i) { - if (CGNSElemTypes[i].surfaceConn) { - /*--- Determine the surface name to use for this connectivity. - This name is determined as follows (in terms of importance). - 1) Family name specified for this connectivity. - 2) Family name of the corresponding boundary condition, - if present. - 3) Name of the connectivity. ---*/ - string thisSurfaceName; - const int connID = CGNSElemTypes[i].connID; - - /* First try to read the family name for the connectivity. */ - if (cg_goto(fn, iBase, "Zone_t", iZone, "Elements_t", connID, "end") != CG_OK) cg_error_exit(); - int ierr = cg_famname_read(cgnsname); - if (ierr == CG_ERROR) - cg_error_exit(); - else if (ierr == CG_OK) - thisSurfaceName = cgnsname; - else { - /* No family name. Check the boundary conditions. It is assumed that - the boundary conditions have the same name as the connectivities. */ - int j; - for (j = 0; j < nBCs; ++j) { - if (BCNames[j] == CGNSElemTypes[i].connName) { - thisSurfaceName = BCFamilyNames[j]; - break; - } - } - - /* If the name is not found in the boundary conditions, set the name - of this surface connectivity to the name of this connectivity. */ - if (j == nBCs) thisSurfaceName = CGNSElemTypes[i].connName; - } - - /* Loop over the previously stored surface names and check if this - surface name is already present. */ - unsigned long j; - for (j = 0; j < surfaceNames.size(); ++j) { - if (thisSurfaceName == surfaceNames[j]) { - surfaceConnIDs[j].push_back(i); - } - } - - /* If the surface name is not stored yet, create new entries in - surfaceNames and surfaceConnIDs. */ - if (j == surfaceNames.size()) { - surfaceNames.push_back(thisSurfaceName); - vector thisSurfaceConn(1, i); - surfaceConnIDs.push_back(thisSurfaceConn); - } - } - } - - /* Write a message about the number of surface markers and allocate the - memory for the data structures to store the required information. */ - nMarker = surfaceNames.size(); - if (rank == MASTER_NODE) cout << nMarker << " surface markers." << endl; +void CPhysicalGeometry::LoadLinearlyPartitionedSurfaceElementsFEM(CConfig* config, CMeshReaderBase* mesh) { + /*--- Store the number of markers and print to the screen. ---*/ + nMarker = mesh->GetNumberOfMarkers(); config->SetnMarker_All(nMarker); + if (rank == MASTER_NODE) cout << nMarker << " surface markers." << endl; - unsigned short nMarker_Max = config->GetnMarker_Max(); - + /*--- Create the data structure for boundary elements. ---*/ bound = new CPrimalGrid**[nMarker]; nElem_Bound = new unsigned long[nMarker]; - Tag_to_Marker = new string[nMarker_Max]; - - /* Loop over the number of markers to read and distribute the connectivities. */ - for (unsigned short iMarker = 0; iMarker < nMarker; ++iMarker) { - /* Easier storage of the entries in CGNSElemTypes that contribute - to this boundary marker. */ - const int nEntries = surfaceConnIDs[iMarker].size(); - const int* entries = surfaceConnIDs[iMarker].data(); - - /* Determine the global number of elements for this boundary marker. */ - cgsize_t nElem_Bound_Global = 0; - for (int iConn = 0; iConn < nEntries; ++iConn) nElem_Bound_Global += CGNSElemTypes[entries[iConn]].nElem; - - /* Write a message about the global number of surface elements - present in this marker. */ - string Marker_Tag = surfaceNames[iMarker]; - if (rank == MASTER_NODE) - cout << nElem_Bound_Global << " boundary elements in index " << iMarker << " (Marker = " << Marker_Tag << ")." - << endl; - - /* Determine the number of surface elements per rank in cumulative storage - format. This is done to avoid that every rank reads all the elements. - This is to avoid that every rank reads all the elements. The correct - rank for storage is determined later via communication. */ - unsigned long totalBoundElemAccounted = 0; - vector nBoundElemPerRank(size + 1); - for (int i = 1; i <= size; ++i) { - nBoundElemPerRank[i] = nElem_Bound_Global / size; - totalBoundElemAccounted += nBoundElemPerRank[i]; - } - - const unsigned long nBoundElemRem = nElem_Bound_Global - totalBoundElemAccounted; - for (unsigned long i = 1; i <= nBoundElemRem; ++i) ++nBoundElemPerRank[i]; - - nBoundElemPerRank[0] = 0; - for (int i = 0; i < size; ++i) nBoundElemPerRank[i + 1] += nBoundElemPerRank[i]; - - /* Define a vector of FEM boundary elements to store the local - boundary faces to be read. */ - vector boundElems; - - /* Loop over the connectivity sections that contribute. */ - elemCount = locElemCount = 0; - for (int iConn = 0; iConn < nEntries; ++iConn) { - /* Determine the global range for this connectivity. */ - const unsigned long elemCountOld = elemCount; - elemCount += CGNSElemTypes[entries[iConn]].nElem; - - /* Check for overlap with the element range this rank is responsible for. */ - const unsigned long indBegOverlap = max(elemCountOld, nBoundElemPerRank[rank]); - const unsigned long indEndOverlap = min(elemCount, nBoundElemPerRank[rank + 1]); - - if (indEndOverlap > indBegOverlap) { - /* This rank must read boundary element data from this connectivity - section. Determine the offset relative to the start of this section - and the number of elements to be read by this rank. */ - const unsigned long offsetRank = indBegOverlap - elemCountOld; - const unsigned long nElemRank = indEndOverlap - indBegOverlap; - - /* Read the connectivity range determined above. */ - CGNSElemTypes[entries[iConn]].ReadBoundaryConnectivityRange(fn, iBase, iZone, offsetRank, nElemRank, - nBoundElemPerRank[rank], locElemCount, boundElems); - } - } - - /* Make a distinction between sequential and parallel mode. */ -#ifdef HAVE_MPI - /*--- Parallel mode. The information stored in boundElems must be - communicated to find out where it must be stored. First clear the - contents of the faceBuf, such that it can be used again to send - data to the appropiate rank. ---*/ - for (int i = 0; i < size; ++i) faceBuf[i].clear(); - - /*-- Loop over the locally read boundary elements and store its contents - in the appropiate location in faceBuf. ---*/ - for (unsigned long i = 0; i < boundElems.size(); ++i) { - /* Create an object of the class CFaceOfElement to determine the - node number that determines on which rank the corresponding face - in localFaces is stored. */ - CFaceOfElement thisFace(boundElems[i].VTK_Type, boundElems[i].nPolyGrid, boundElems[i].Nodes.data()); - thisFace.CreateUniqueNumbering(); - - /* Determine the rank to which this face must be sent to. */ - const cgsize_t nodeID = thisFace.cornerPoints[0]; - vector::iterator low; - low = lower_bound(nPointsPerRank.begin(), nPointsPerRank.end(), nodeID); - cgsize_t rankNode = low - nPointsPerRank.begin(); - if (*low > nodeID) --rankNode; - - /*--- Copy the relevant data of this boundary element into faceBuf. ---*/ - faceBuf[rankNode].push_back(boundElems[i].VTK_Type); - faceBuf[rankNode].push_back(boundElems[i].nPolyGrid); - faceBuf[rankNode].push_back(boundElems[i].nDOFsGrid); - faceBuf[rankNode].push_back(boundElems[i].globalBoundElemID); - faceBuf[rankNode].insert(faceBuf[rankNode].end(), boundElems[i].Nodes.begin(), boundElems[i].Nodes.end()); - } - - /* The contents of boundElems is copied into faceBuf, so it can - be released. */ - boundElems.clear(); - - /*--- Determine the number of messages this rank will send and receive. ---*/ - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!faceBuf[i].empty()) { - ++nRankSend; - sendToRank[i] = 1; - } else { - sendToRank[i] = 0; - } - } - - SU2_MPI::Reduce_scatter(sendToRank.data(), &nRankRecv, sizeRecv.data(), MPI_INT, MPI_SUM, SU2_MPI::GetComm()); - - /*--- Send the messages using non-blocking sends to avoid deadlock. ---*/ - sendReqs.resize(nRankSend); - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!faceBuf[i].empty()) { - SU2_MPI::Isend(faceBuf[i].data(), faceBuf[i].size(), MPI_UNSIGNED_LONG, i, i + 5, SU2_MPI::GetComm(), - &sendReqs[nRankSend]); - ++nRankSend; - } - } - - /* Use nodeBuf as storage for the sending of the surface element data - to the correct rank. First clear its contents. */ - for (int i = 0; i < size; ++i) nodeBuf[i].clear(); - - /*--- Loop over the number of ranks from which this rank receives - surface elements to be processed. ---*/ - for (int i = 0; i < nRankRecv; ++i) { - /* Block until a message arrives. Determine the source and size - of the message. */ - SU2_MPI::Status status; - SU2_MPI::Probe(MPI_ANY_SOURCE, rank + 5, SU2_MPI::GetComm(), &status); - int source = status.MPI_SOURCE; - - int sizeMess; - SU2_MPI::Get_count(&status, MPI_UNSIGNED_LONG, &sizeMess); - - /* Allocate the memory for the receive buffer and receive the message - using a blocking send. */ - vector boundElemRecvBuf(sizeMess); - SU2_MPI::Recv(boundElemRecvBuf.data(), sizeMess, MPI_UNSIGNED_LONG, source, rank + 5, SU2_MPI::GetComm(), - &status); - - /* Loop to extract the data from the receive buffer. */ - int ii = 0; - while (ii < sizeMess) { - /* Store the data for this boundary element. */ - const auto VTK_Type = (unsigned short)boundElemRecvBuf[ii++]; - const auto nPolyGrid = (unsigned short)boundElemRecvBuf[ii++]; - const auto nDOFsGrid = (unsigned short)boundElemRecvBuf[ii++]; - - const unsigned long globalBoundElemID = boundElemRecvBuf[ii++]; - const unsigned long* Nodes = boundElemRecvBuf.data() + ii; - ii += nDOFsGrid; - - /* Determine the corner nodes and store them in an object of - the class CFaceOfElement to carry out the search in localFaces. */ - CFaceOfElement thisFace(VTK_Type, nPolyGrid, Nodes); - - /* Check if the face is actually present. If not, print an error message - and exit. */ - thisFace.CreateUniqueNumbering(); - vector::iterator low; - low = lower_bound(localFaces.begin(), localFaces.end(), thisFace); - - bool thisFaceFound = false; - if (low != localFaces.end()) { - if (!(thisFace < *low)) thisFaceFound = true; - } - - if (!thisFaceFound) - SU2_MPI::Error("Boundary element not found in list of faces. This is a bug.", CURRENT_FUNCTION); - - /* Determine the domain element and the rank where - this boundary element should be sent to.. */ - const unsigned long domainElementID = low->elemID0; - const int rankBoundElem = (int)low->elemID1; - - /*--- Store the data for this element in the communication buffer - for rankBoundElem. ---*/ - nodeBuf[rankBoundElem].push_back(VTK_Type); - nodeBuf[rankBoundElem].push_back(nPolyGrid); - nodeBuf[rankBoundElem].push_back(nDOFsGrid); - nodeBuf[rankBoundElem].push_back(globalBoundElemID); - nodeBuf[rankBoundElem].push_back(domainElementID); - - for (unsigned short j = 0; j < nDOFsGrid; ++j) nodeBuf[rankBoundElem].push_back(Nodes[j]); - } - } - - /* Complete the non-blocking sends. */ - SU2_MPI::Waitall(sendReqs.size(), sendReqs.data(), MPI_STATUSES_IGNORE); - - /*--- Determine the number of messages this rank will send and receive. ---*/ - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!nodeBuf[i].empty()) { - ++nRankSend; - sendToRank[i] = 1; - } else { - sendToRank[i] = 0; - } - } - - SU2_MPI::Reduce_scatter(sendToRank.data(), &nRankRecv, sizeRecv.data(), MPI_INT, MPI_SUM, SU2_MPI::GetComm()); - - /*--- Send the messages using non-blocking sends to avoid deadlock. ---*/ - sendReqs.resize(nRankSend); - nRankSend = 0; - for (int i = 0; i < size; ++i) { - if (!nodeBuf[i].empty()) { - SU2_MPI::Isend(nodeBuf[i].data(), nodeBuf[i].size(), MPI_UNSIGNED_LONG, i, i + 6, SU2_MPI::GetComm(), - &sendReqs[nRankSend]); - ++nRankSend; - } - } - - /*--- Loop over the number of ranks from which this rank receives - surface elements to be stored on this rank. ---*/ - for (int i = 0; i < nRankRecv; ++i) { - /* Block until a message arrives. Determine the source and size - of the message. */ - SU2_MPI::Status status; - SU2_MPI::Probe(MPI_ANY_SOURCE, rank + 6, SU2_MPI::GetComm(), &status); - int source = status.MPI_SOURCE; + Tag_to_Marker = new string[config->GetnMarker_Max()]; + + /*--- Retrieve the name of the surface markers as well as + the number of surface elements for every marker. ---*/ + const auto& sectionNames = mesh->GetMarkerNames(); + const auto& nSurfElemPerMarker = mesh->GetNumberOfSurfaceElementsAllMarkers(); + + /*--- Loop over all sections that we extracted from the grid file + that were identified as boundary element sections so that we can + store those elements into our SU2 data structures. ---*/ + for (int iMarker = 0; iMarker < nMarker; ++iMarker) { + /*--- Get the string name and set the number of surface elements + for this marker. ---*/ + string Marker_Tag = sectionNames[iMarker]; + nElem_Bound[iMarker] = nSurfElemPerMarker[iMarker]; + + /*--- Allocate the memory of the pointers for the surface + elements for this marker. ---*/ + bound[iMarker] = new CPrimalGrid*[nElem_Bound[iMarker]]; - int sizeMess; - SU2_MPI::Get_count(&status, MPI_UNSIGNED_LONG, &sizeMess); + /*--- Retrieve the boundary element data for this marker. ---*/ + const auto& dataElems = mesh->GetSurfaceElementConnectivityForMarker(iMarker); - /* Allocate the memory for the receive buffer and receive the message - using a blocking send. */ - vector boundElemRecvBuf(sizeMess); - SU2_MPI::Recv(boundElemRecvBuf.data(), sizeMess, MPI_UNSIGNED_LONG, source, rank + 6, SU2_MPI::GetComm(), - &status); + /*--- Loop over the number of boundary elements for this marker. ---*/ + unsigned long ind = 0; + for (unsigned long jElem = 0; jElem < nElem_Bound[iMarker]; ++jElem) { + /*--- Create a boundary FEM element from the data dataElems. ---*/ + const auto* dataElem = dataElems.data() + ind; + bound[iMarker][jElem] = new CPrimalGridBoundFEM(dataElem); - /* Loop to extract the data from the receive buffer. */ - int ii = 0; - while (ii < sizeMess) { - /* Store the data for this boundary element. */ - const auto VTK_Type = (unsigned short)boundElemRecvBuf[ii++]; - const auto nPolyGrid = (unsigned short)boundElemRecvBuf[ii++]; - const auto nDOFsGrid = (unsigned short)boundElemRecvBuf[ii++]; - - const unsigned long globalBoundElemID = boundElemRecvBuf[ii++]; - const unsigned long domainElementID = boundElemRecvBuf[ii++]; - const unsigned long* Nodes = boundElemRecvBuf.data() + ii; - ii += nDOFsGrid; - - /* Create an object of CBoundaryFace and store it in boundElems. */ - CBoundaryFace thisBoundFace; - thisBoundFace.VTK_Type = VTK_Type; - thisBoundFace.nPolyGrid = nPolyGrid; - thisBoundFace.nDOFsGrid = nDOFsGrid; - thisBoundFace.globalBoundElemID = globalBoundElemID; - thisBoundFace.domainElementID = domainElementID; - - thisBoundFace.Nodes.resize(nDOFsGrid); - for (unsigned short j = 0; j < nDOFsGrid; ++j) thisBoundFace.Nodes[j] = Nodes[j]; - - boundElems.push_back(thisBoundFace); - } + /*--- Update ind for the next element. ---*/ + ind += dataElem[2] + 5; } - /* Sort boundElems in increasing order, where the less than operator - is a comparison between the globalBoundElemID's. */ - sort(boundElems.begin(), boundElems.end()); - - /* Complete the non-blocking sends and synchronize the processors, - because wild cards have been used. */ - SU2_MPI::Waitall(sendReqs.size(), sendReqs.data(), MPI_STATUSES_IGNORE); - - SU2_MPI::Barrier(SU2_MPI::GetComm()); - -#else - /*--- Sequential mode. All boundary elements read must be stored on this - rank. The only information missing is the global ID of the domain - element of the boundary elements. This is created below. ---*/ - for (unsigned long i = 0; i < boundElems.size(); ++i) { - /* Determine the corner nodes and store them in an object of - the class CFaceOfElement to carry out the search in localFaces. */ - CFaceOfElement thisFace(boundElems[i].VTK_Type, boundElems[i].nPolyGrid, boundElems[i].Nodes.data()); - - /* Check if the face is actually present. If not, print an error message - and exit. */ - thisFace.CreateUniqueNumbering(); - vector::iterator low; - low = lower_bound(localFaces.begin(), localFaces.end(), thisFace); - - bool thisFaceFound = false; - if (low != localFaces.end()) { - if (!(thisFace < *low)) thisFaceFound = true; - } - - if (!thisFaceFound) - SU2_MPI::Error("Boundary element not found in list of faces. This is a bug.", CURRENT_FUNCTION); - - /* Set the domain element. */ - boundElems[i].domainElementID = low->elemID0; - } -#endif - - /*--- Allocate space for the local boundary elements and copy the data - from boundElems into bound. ---*/ - nElem_Bound[iMarker] = boundElems.size(); - bound[iMarker] = new CPrimalGrid*[nElem_Bound[iMarker]]; - - for (unsigned long i = 0; i < nElem_Bound[iMarker]; ++i) - bound[iMarker][i] = new CPrimalGridBoundFEM(boundElems[i].globalBoundElemID, boundElems[i].domainElementID, - boundElems[i].VTK_Type, boundElems[i].nPolyGrid, - boundElems[i].nDOFsGrid, boundElems[i].Nodes); - - /*--- Update config information storing the boundary information in the right place ---*/ + /*--- Update config file lists in order to store the boundary + information for this marker in the correct place. ---*/ Tag_to_Marker[config->GetMarker_CfgFile_TagBound(Marker_Tag)] = Marker_Tag; config->SetMarker_All_TagBound(iMarker, Marker_Tag); config->SetMarker_All_KindBC(iMarker, config->GetMarker_CfgFile_KindBC(Marker_Tag)); @@ -1841,26 +475,15 @@ void CPhysicalGeometry::Read_CGNS_Format_Parallel_FEM(CConfig* config, const str config->SetMarker_All_ZoneInterface(iMarker, config->GetMarker_CfgFile_ZoneInterface(Marker_Tag)); config->SetMarker_All_DV(iMarker, config->GetMarker_CfgFile_DV(Marker_Tag)); config->SetMarker_All_Moving(iMarker, config->GetMarker_CfgFile_Moving(Marker_Tag)); - config->SetMarker_All_SobolevBC(iMarker, config->GetMarker_CfgFile_SobolevBC(Marker_Tag)); + config->SetMarker_All_Deform_Mesh(iMarker, config->GetMarker_CfgFile_Deform_Mesh(Marker_Tag)); + config->SetMarker_All_Fluid_Load(iMarker, config->GetMarker_CfgFile_Fluid_Load(Marker_Tag)); + config->SetMarker_All_PyCustom(iMarker, config->GetMarker_CfgFile_PyCustom(Marker_Tag)); config->SetMarker_All_PerBound(iMarker, config->GetMarker_CfgFile_PerBound(Marker_Tag)); config->SetMarker_All_SendRecv(iMarker, NONE); + config->SetMarker_All_Turbomachinery(iMarker, config->GetMarker_CfgFile_Turbomachinery(Marker_Tag)); + config->SetMarker_All_TurbomachineryFlag(iMarker, config->GetMarker_CfgFile_TurbomachineryFlag(Marker_Tag)); + config->SetMarker_All_MixingPlaneInterface(iMarker, config->GetMarker_CfgFile_MixingPlaneInterface(Marker_Tag)); } - - /* Close the CGNS file again. */ - if (cg_close(fn) != CG_OK) cg_error_exit(); - if (rank == MASTER_NODE) cout << "Successfully closed the CGNS file." << endl; - -#else /* CGNS_VERSION >= 3300 */ - - SU2_MPI::Error("CGNS version 3.3 or higher is necessary for the DG FEM solver", CURRENT_FUNCTION); - -#endif /* CGNS_VERSION >= 3300 */ - -#else /* HAVE_CGNS. */ - - SU2_MPI::Error("SU2 built without CGNS support!!\nTo use CGNS, build SU2 accordingly.", CURRENT_FUNCTION); - -#endif /* HAVE_CGNS. */ } void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { @@ -1882,7 +505,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { CFaceOfElement thisFace; thisFace.nCornerPoints = nPointsPerFace[i]; for (unsigned short j = 0; j < nPointsPerFace[i]; ++j) thisFace.cornerPoints[j] = faceConn[i][j]; - thisFace.elemID0 = beg_node[rank] + k; + thisFace.elemID0 = elem[k]->GetGlobalElemID(); thisFace.nPolySol0 = elem[k]->GetNPolySol(); thisFace.nDOFsElem0 = elem[k]->GetNDOFsSol(); thisFace.elemType0 = elem[k]->GetVTK_Type(); @@ -1986,7 +609,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { the points that occur in the faces of localFacesComm. ---*/ vector facePointsProc(size + 1, 0); unsigned long total_point_accounted = 0; - for (unsigned long i = 1; i <= (unsigned long)size; ++i) { + for (unsigned long i = 1; i <= static_cast(size); ++i) { facePointsProc[i] = maxPointID / size; total_point_accounted += facePointsProc[i]; } @@ -1994,7 +617,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { unsigned long rem_point = maxPointID - total_point_accounted; for (unsigned long i = 1; i <= rem_point; ++i) ++facePointsProc[i]; - for (unsigned long i = 0; i < (unsigned long)size; ++i) facePointsProc[i + 1] += facePointsProc[i]; + for (unsigned long i = 0; i < static_cast(size); ++i) facePointsProc[i + 1] += facePointsProc[i]; /*--- Determine the number of faces that has to be sent to each rank. Note that the rank is stored in elemID1, such that the search @@ -2014,7 +637,8 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { vector sendBufFace(9 * nFacesLocComm); vector counter(size); counter[0] = 0; - for (unsigned long i = 1; i < (unsigned long)size; ++i) counter[i] = counter[i - 1] + 9 * nFacesComm[i - 1]; + for (unsigned long i = 1; i < static_cast(size); ++i) + counter[i] = counter[i - 1] + 9 * nFacesComm[i - 1]; for (unsigned long i = 0; i < nFacesLocComm; ++i) { unsigned long rankFace = localFacesComm[i].elemID1; @@ -2034,7 +658,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { /*--- Determine the number of ranks from which I receive a message. */ unsigned long nMessSend = 0; - for (unsigned long i = 0; i < (unsigned long)size; ++i) { + for (unsigned long i = 0; i < static_cast(size); ++i) { if (nFacesComm[i]) { counter[i] = 1; ++nMessSend; @@ -2051,7 +675,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { nMessSend = 0; unsigned long indSend = 0; - for (unsigned long i = 0; i < (unsigned long)size; ++i) { + for (unsigned long i = 0; i < static_cast(size); ++i) { if (nFacesComm[i]) { unsigned long count = 9 * nFacesComm[i]; SU2_MPI::Isend(&sendBufFace[indSend], count, MPI_UNSIGNED_LONG, i, i, SU2_MPI::GetComm(), &commReqs[nMessSend]); @@ -2169,7 +793,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { sizeMess /= 9; unsigned long jj = 0; - for (unsigned long j = 0; j < (unsigned long)sizeMess; ++j, jj += 9) { + for (unsigned long j = 0; j < static_cast(sizeMess); ++j, jj += 9) { CFaceOfElement thisFace; thisFace.nCornerPoints = recvBuf[jj]; thisFace.cornerPoints[0] = recvBuf[jj + 1]; @@ -2241,24 +865,26 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { map mapExternalElemIDToTimeLevel; DetermineTimeLevelElements(config, localFaces, mapExternalElemIDToTimeLevel); + /*--- Define the linear partitioning of the elements. ---*/ + CLinearPartitioner elemPartitioner(Global_nElem, 0); + /*--- Determine the ownership of the internal faces, i.e. which adjacent element is responsible for computing the fluxes through the face. ---*/ for (unsigned long i = 0; i < nFacesLoc; ++i) { /* Check for a matching face. */ if (localFaces[i].elemID1 < Global_nElem) { - /* Determine the time level for both elements. Elem0 is always owned, while - elem1 is either owned or external. The data for external elements is - stored in mapExternalElemIDToTimeLevel. */ - unsigned long elemID0 = localFaces[i].elemID0 - beg_node[rank]; - unsigned short timeLevel0 = elem[elemID0]->GetTimeLevel(); + /*--- Determine the time level of Elem0, which is always owned. ---*/ + const unsigned long elemID0 = localFaces[i].elemID0 - elemPartitioner.GetFirstIndexOnRank(rank); + const unsigned short timeLevel0 = elem[elemID0]->GetTimeLevel(); + /*--- Determine the time level of Elem1, which is either owned or + external. Hence a distinction must be made. ---*/ unsigned short timeLevel1; - if (localFaces[i].elemID1 >= beg_node[rank] && localFaces[i].elemID1 < beg_node[rank] + nElem) { - unsigned long elemID1 = localFaces[i].elemID1 - beg_node[rank]; + if (elemPartitioner.GetRankContainingIndex(localFaces[i].elemID1) == static_cast(rank)) { + const unsigned long elemID1 = localFaces[i].elemID1 - elemPartitioner.GetFirstIndexOnRank(rank); timeLevel1 = elem[elemID1]->GetTimeLevel(); } else { - map::const_iterator MI; - MI = mapExternalElemIDToTimeLevel.find(localFaces[i].elemID1); + const auto MI = mapExternalElemIDToTimeLevel.find(localFaces[i].elemID1); if (MI == mapExternalElemIDToTimeLevel.end()) SU2_MPI::Error("Entry not found in mapExternalElemIDToTimeLevel", CURRENT_FUNCTION); timeLevel1 = MI->second.short0; @@ -2308,7 +934,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { CFaceOfElement thisFace; thisFace.nCornerPoints = nPointsPerFace[i]; for (unsigned short j = 0; j < nPointsPerFace[i]; ++j) thisFace.cornerPoints[j] = faceConn[i][j]; - thisFace.elemID0 = beg_node[rank] + k; + thisFace.elemID0 = k + elemPartitioner.GetFirstIndexOnRank(rank); thisFace.nPolySol0 = elem[k]->GetNPolySol(); thisFace.nDOFsElem0 = elem[k]->GetNDOFsSol(); @@ -2337,12 +963,18 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { of the graph. First the faces. ---*/ vector > adjacency(nElem, vector(0)); for (unsigned long i = 0; i < nFacesLoc; ++i) { - long ii = localFaces[i].elemID0 - beg_node[rank]; - adjacency[ii].push_back(localFaces[i].elemID1); + /*--- Determine the local index of elem0, which is always stored locally, + and add elemID1 to the adjacency list. ---*/ + const unsigned long elem0 = localFaces[i].elemID0 - elemPartitioner.GetFirstIndexOnRank(rank); + adjacency[elem0].push_back(localFaces[i].elemID1); + /*--- Check if this is not a periodic face and if the second element is + also a local element. If so, add elemID0 to the adjacency list ---*/ if (localFaces[i].periodicIndex == 0) { - ii = localFaces[i].elemID1 - beg_node[rank]; - if (ii >= 0 && ii < (long)nElem) adjacency[ii].push_back(localFaces[i].elemID0); + if (elemPartitioner.GetRankContainingIndex(localFaces[i].elemID1) == static_cast(rank)) { + const unsigned long elem1 = localFaces[i].elemID1 - elemPartitioner.GetFirstIndexOnRank(rank); + adjacency[elem1].push_back(localFaces[i].elemID0); + } } } @@ -2360,7 +992,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { are present. ParMETIS is not able to deal with self entries, hence they must be removed as well. */ for (unsigned long i = 0; i < nElem; ++i) { - const unsigned long globalElemID = i + beg_node[rank]; + const unsigned long globalElemID = i + +elemPartitioner.GetFirstIndexOnRank(rank); unsigned long nEntriesNew = adjacency[i].size(); for (unsigned long j = 0; j < adjacency[i].size(); ++j) { @@ -2383,7 +1015,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { for (unsigned long l = 0; l < nElem_Bound[iMarker]; ++l) { /* Get the global and local element ID adjacent to this boundary face. */ const unsigned long globalElemID = bound[iMarker][l]->GetDomainElement(); - const unsigned long elemID = globalElemID - beg_node[rank]; + const unsigned long elemID = globalElemID - elemPartitioner.GetFirstIndexOnRank(rank); /* Get the number of donor elements for the wall function treatment and the pointer to the array which stores this info. */ @@ -2401,10 +1033,10 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { sort(adjacency[elemID].begin(), adjacency[elemID].end()); /* Check if the donor element is stored locally. */ - if (donors[i] >= beg_node[rank] && donors[i] < beg_node[rank] + nElem) { + if (elemPartitioner.GetRankContainingIndex(donors[i]) == static_cast(rank)) { /* Donor is stored locally. Add the entry to the graph and sort it afterwards. */ - const unsigned long localDonorID = donors[i] - beg_node[rank]; + const unsigned long localDonorID = donors[i] - elemPartitioner.GetFirstIndexOnRank(rank); adjacency[localDonorID].push_back(globalElemID); sort(adjacency[localDonorID].begin(), adjacency[localDonorID].end()); } else { @@ -2426,18 +1058,9 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { vector sendToRank(size, 0); for (unsigned long i = 0; i < additionalExternalEntriesGraph.size(); i += 2) { - /* Determine the rank where this external is stored. */ - const unsigned long elemID = additionalExternalEntriesGraph[i]; - int rankElem; - if (elemID >= beg_node[size - 1]) - rankElem = size - 1; - else { - const unsigned long* low; - low = lower_bound(beg_node, beg_node + size, elemID); - - rankElem = low - beg_node; - if (*low > elemID) --rankElem; - } + /*--- Determine the rank where this external is stored and update + the corresponding communication buffers accordingly. ---*/ + const unsigned long rankElem = elemPartitioner.GetRankContainingIndex(additionalExternalEntriesGraph[i]); sendBufsGraphData[rankElem].push_back(additionalExternalEntriesGraph[i]); sendBufsGraphData[rankElem].push_back(additionalExternalEntriesGraph[i + 1]); @@ -2483,7 +1106,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { /* Loop over the contents of the receive buffer and update the graph accordingly. */ for (int j = 0; j < sizeMess; j += 2) { - const unsigned long elemID = recvBuf[j] - beg_node[rank]; + const unsigned long elemID = recvBuf[j] - elemPartitioner.GetFirstIndexOnRank(rank); adjacency[elemID].push_back(recvBuf[j + 1]); sort(adjacency[elemID].begin(), adjacency[elemID].end()); } @@ -2526,8 +1149,7 @@ void CPhysicalGeometry::SetColorFEMGrid_Parallel(CConfig* config) { /*--- Determine the array, which stores the distribution of the graph nodes over the ranks. ---*/ vector vtxdist(size + 1); - vtxdist[0] = 0; - for (int i = 0; i < size; ++i) vtxdist[i + 1] = (idx_t)end_node[i]; + for (int i = 0; i <= size; ++i) vtxdist[i] = static_cast(elemPartitioner.GetCumulativeSizeBeforeRank(i)); /* Create the array xadjPar, which contains the number of edges for each vertex of the graph in ParMETIS format. */ @@ -3329,6 +1951,9 @@ void CPhysicalGeometry::DetermineDonorElementsWallFunctions(CConfig* config) { /*--- the local elements. ---*/ /*--------------------------------------------------------------------------*/ + /*--- Define the linear partitioning of the elements. ---*/ + CLinearPartitioner elemPartitioner(Global_nElem, 0); + /* Define the vectors, which store the boundary marker, boundary element ID and exchange coordinates for the integration points for which no donor element was found in the locally stored volume elements. */ @@ -3356,7 +1981,8 @@ void CPhysicalGeometry::DetermineDonorElementsWallFunctions(CConfig* config) { /* Easier storage of the element type, the corresponding volume element and the polynomial degree for the solution and grid. */ const unsigned short VTK_Type = bound[iMarker][l]->GetVTK_Type(); - const unsigned long elemID = bound[iMarker][l]->GetDomainElement() - beg_node[rank]; + const unsigned long elemID = + bound[iMarker][l]->GetDomainElement() - elemPartitioner.GetFirstIndexOnRank(rank); const unsigned short nPolyGrid = bound[iMarker][l]->GetNPolyGrid(); const unsigned short nPolySol = elem[elemID]->GetNPolySol(); const unsigned short VTK_Elem = elem[elemID]->GetVTK_Type(); @@ -3615,7 +2241,7 @@ void CPhysicalGeometry::DetermineDonorElementsWallFunctions(CConfig* config) { carried out for each rank and store them in such a way that the info can be used directly in Allgatherv. */ vector recvCounts(size), displs(size); - int nLocalSearchPoints = (int)markerIDGlobalSearch.size(); + int nLocalSearchPoints = static_cast(markerIDGlobalSearch.size()); SU2_MPI::Allgather(&nLocalSearchPoints, 1, MPI_INT, recvCounts.data(), 1, MPI_INT, SU2_MPI::GetComm()); displs[0] = 0; @@ -3787,6 +2413,9 @@ void CPhysicalGeometry::DetermineDonorElementsWallFunctions(CConfig* config) { void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector& localFaces, map& mapExternalElemIDToTimeLevel) { + /*--- Define the linear partitioning of the elements. ---*/ + CLinearPartitioner elemPartitioner(Global_nElem, 0); + /*--------------------------------------------------------------------------*/ /*--- Step 1: Initialize the map mapExternalElemIDToTimeLevel. ---*/ /*--------------------------------------------------------------------------*/ @@ -3796,7 +2425,10 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector for (auto FI = localFaces.begin(); FI != localFaces.end(); ++FI) { if (FI->elemID1 < Global_nElem) { // Safeguard against non-matching faces. - if (FI->elemID1 < beg_node[rank] || FI->elemID1 >= beg_node[rank] + nElem) { + /*--- Check for external element. This is done by checking the + local elements and if it is not found, it is an external. ---*/ + const auto UMI = Global_to_Local_Elem.find(FI->elemID1); + if (UMI == Global_to_Local_Elem.end()) { /* This element is an external element. Store it in the map mapExternalElemIDToTimeLevel if not already done so. */ map::iterator MI; @@ -3823,30 +2455,22 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector const unsigned short nDonors = bound[iMarker][l]->GetNDonorsWallFunctions(); const unsigned long* donors = bound[iMarker][l]->GetDonorsWallFunctions(); - /* Loop over the number of donors and add the externals to - mapExternalElemIDToTimeLevel, if not already present. */ + /*--- Loop over the number of donors for this boundary element. ---*/ for (unsigned short i = 0; i < nDonors; ++i) { - if (donors[i] < beg_node[rank] || donors[i] >= beg_node[rank] + nElem) { - map::iterator MI; - MI = mapExternalElemIDToTimeLevel.find(donors[i]); + /*--- Check if the donor element is an external element. ---*/ + const auto UMI = Global_to_Local_Elem.find(donors[i]); + if (UMI == Global_to_Local_Elem.end()) { + /*--- Check if element is not already present in + mapExternalElemIDToTimeLevel. If not, add it. ---*/ + const auto MI = mapExternalElemIDToTimeLevel.find(donors[i]); if (MI == mapExternalElemIDToTimeLevel.end()) { - /* Element not present in external. Add it. */ mapExternalElemIDToTimeLevel[donors[i]] = CUnsignedShort2T(0, 0); } - /* The reverse connection may not be present either. Store the global - ID of this element in the send buffers for the additional - externals. */ - int rankDonor; - if (donors[i] >= beg_node[size - 1]) - rankDonor = size - 1; - else { - const unsigned long* low; - low = lower_bound(beg_node, beg_node + size, donors[i]); - - rankDonor = (int)(low - beg_node); - if (*low > donors[i]) --rankDonor; - } + /*--- The reverse connection may not be present either. Store the global + ID of this element in the send buffers for the additional + externals. ---*/ + const unsigned long rankDonor = elemPartitioner.GetRankContainingIndex(donors[i]); sendBufAddExternals[rankDonor].push_back(bound[iMarker][l]->GetDomainElement()); recvFromRank[rankDonor] = 1; @@ -4006,20 +2630,9 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector recvFromRank.assign(size, 0); for (MI = mapExternalElemIDToTimeLevel.begin(); MI != mapExternalElemIDToTimeLevel.end(); ++MI) { - /* Determine the rank where this external is stored. */ - const unsigned long elemID = MI->first; - int rankElem; - if (elemID >= beg_node[size - 1]) - rankElem = size - 1; - else { - const unsigned long* low; - low = lower_bound(beg_node, beg_node + size, elemID); - - rankElem = low - beg_node; - if (*low > elemID) --rankElem; - } - - /* Set the corresponding index of recvFromRank to 1. */ + /*--- Determine the rank where this external is stored and set + the corresponding index of recvFromRank to 1. ---*/ + const unsigned long rankElem = elemPartitioner.GetRankContainingIndex(MI->first); recvFromRank[rankElem] = 1; } @@ -4042,18 +2655,9 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector for (MI = mapExternalElemIDToTimeLevel.begin(); MI != mapExternalElemIDToTimeLevel.end(); ++MI) { const unsigned long elemID = MI->first; - int rankElem; - if (elemID >= beg_node[size - 1]) - rankElem = size - 1; - else { - const unsigned long* low; - low = lower_bound(beg_node, beg_node + size, elemID); - - rankElem = low - beg_node; - if (*low > elemID) --rankElem; - } + const unsigned long rankElem = elemPartitioner.GetRankContainingIndex(elemID); - map::const_iterator MRI = mapRankToIndRecv.find(rankElem); + const auto MRI = mapRankToIndRecv.find(rankElem); recvElem[MRI->second].push_back(elemID); } @@ -4089,7 +2693,7 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector SU2_MPI::Recv(sendElem[i].data(), sizeMess, MPI_UNSIGNED_LONG, sendRank[i], rank, SU2_MPI::GetComm(), &status); - for (int j = 0; j < sizeMess; ++j) sendElem[i][j] -= beg_node[rank]; + for (int j = 0; j < sizeMess; ++j) sendElem[i][j] -= elemPartitioner.GetFirstIndexOnRank(rank); } /* Complete the non-blocking sends. Synchronize the processors afterwards, @@ -4180,7 +2784,8 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector for (unsigned short iMarker = 0; iMarker < nMarker; ++iMarker) { for (unsigned long l = 0; l < nElem_Bound[iMarker]; ++l) { /* Determine the ID of the adjacent element. */ - const unsigned long elemID = bound[iMarker][l]->GetDomainElement() - beg_node[rank]; + const unsigned long elemID = + bound[iMarker][l]->GetDomainElement() - elemPartitioner.GetFirstIndexOnRank(rank); /* Get the number of donor elements for the wall function treatment and the pointer to the array which stores this info. */ @@ -4190,10 +2795,10 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector /* Loop over the number of donors and check the time levels. */ for (unsigned short i = 0; i < nDonors; ++i) { /* Determine the status of the donor element. */ - if (donors[i] >= beg_node[rank] && donors[i] < beg_node[rank] + nElem) { + if (elemPartitioner.GetRankContainingIndex(donors[i]) == static_cast(rank)) { /* Donor is stored locally. Determine its local ID and get the time levels of both elements. */ - const unsigned long donorID = donors[i] - beg_node[rank]; + const unsigned long donorID = donors[i] - elemPartitioner.GetFirstIndexOnRank(rank); const unsigned short timeLevelB = elem[elemID]->GetTimeLevel(); const unsigned short timeLevelD = elem[donorID]->GetTimeLevel(); const unsigned short timeLevel = min(timeLevelB, timeLevelD); @@ -4244,15 +2849,15 @@ void CPhysicalGeometry::DetermineTimeLevelElements(CConfig* config, const vector if (FI->elemID1 < Global_nElem) { /* Local element ID of the first element. Per definition this is always a locally stored element. Also store its time level. */ - const unsigned long elemID0 = FI->elemID0 - beg_node[rank]; + const unsigned long elemID0 = FI->elemID0 - elemPartitioner.GetFirstIndexOnRank(rank); const unsigned short timeLevel0 = elem[elemID0]->GetTimeLevel(); /* Determine the status of the second element. */ - if (FI->elemID1 >= beg_node[rank] && FI->elemID1 < beg_node[rank] + nElem) { + if (elemPartitioner.GetRankContainingIndex(FI->elemID1) == static_cast(rank)) { /* Both elements are stored locally. Determine the local element of the second element and determine the minimum time level. */ - const unsigned long elemID1 = FI->elemID1 - beg_node[rank]; + const unsigned long elemID1 = FI->elemID1 - elemPartitioner.GetFirstIndexOnRank(rank); const unsigned short timeLevel1 = elem[elemID1]->GetTimeLevel(); const unsigned short timeLevel = min(timeLevel0, timeLevel1); @@ -4400,6 +3005,9 @@ void CPhysicalGeometry::ComputeFEMGraphWeights(CConfig* config, const vector >& adjacency, const map& mapExternalElemIDToTimeLevel, vector& vwgt, vector >& adjwgt) { + /*--- Define the linear partitioning of the elements. ---*/ + CLinearPartitioner elemPartitioner(Global_nElem, 0); + /*--- Determine the maximum time level that occurs in the grid. ---*/ unsigned short maxTimeLevel = 0; for (unsigned long i = 0; i < nElem; ++i) maxTimeLevel = max(maxTimeLevel, elem[i]->GetTimeLevel()); @@ -4461,7 +3069,7 @@ void CPhysicalGeometry::ComputeFEMGraphWeights(CConfig* config, const vectorGetVTK_Type(); - const unsigned long elemID = bound[iMarker][l]->GetDomainElement() - beg_node[rank]; + const unsigned long elemID = + bound[iMarker][l]->GetDomainElement() - elemPartitioner.GetFirstIndexOnRank(rank); const unsigned short nPolySol = elem[elemID]->GetNPolySol(); const unsigned short VTK_Type_Elem = elem[elemID]->GetVTK_Type(); const bool JacIsConstant = bound[iMarker][l]->GetJacobianConsideredConstant(); @@ -4661,17 +3270,16 @@ void CPhysicalGeometry::ComputeFEMGraphWeights(CConfig* config, const vector= beg_node[rank] && adjacency[i][j] < beg_node[rank] + nElem) { + if (elemPartitioner.GetRankContainingIndex(adjacency[i][j]) == static_cast(rank)) { /* Locally stored element. Determine its local ID and set the time level and number of solution DOFs. */ - unsigned long elemID1 = adjacency[i][j] - beg_node[rank]; + unsigned long elemID1 = adjacency[i][j] - elemPartitioner.GetFirstIndexOnRank(rank); timeLevel1 = elem[elemID1]->GetTimeLevel(); nDOFs1 = elem[elemID1]->GetNDOFsSol(); } else { /* The neighbor is an external element. Find it in mapExternalElemIDToTimeLevel and set the time level and number of solution DOFs accordingly. */ - map::const_iterator MI; - MI = mapExternalElemIDToTimeLevel.find(adjacency[i][j]); + const auto MI = mapExternalElemIDToTimeLevel.find(adjacency[i][j]); if (MI == mapExternalElemIDToTimeLevel.end()) SU2_MPI::Error("Entry not found in mapExternalElemIDToTimeLevel", CURRENT_FUNCTION); timeLevel1 = MI->second.short0; diff --git a/Common/src/fem/meson.build b/Common/src/fem/meson.build index 028f179d6e4..263fccc0e84 100644 --- a/Common/src/fem/meson.build +++ b/Common/src/fem/meson.build @@ -4,5 +4,4 @@ common_src += files(['geometry_structure_fem_part.cpp', 'fem_work_estimate_metis.cpp', 'fem_standard_element.cpp', 'fem_wall_distance.cpp', - 'fem_gauss_jacobi_quadrature.cpp', - 'fem_cgns_elements.cpp']) + 'fem_gauss_jacobi_quadrature.cpp']) diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index cf1a6948c5f..e771960b638 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -183,8 +183,8 @@ void CGeometry::PreprocessP2PComms(CGeometry* geometry, CConfig* config) { /*--- If we have not visited this element yet, increment our number of elements that must be sent to a particular proc. ---*/ - if ((nPoint_Flag[iRank] != (int)iMarker)) { - nPoint_Flag[iRank] = (int)iMarker; + if ((nPoint_Flag[iRank] != static_cast(iMarker))) { + nPoint_Flag[iRank] = static_cast(iMarker); nPoint_Send_All[iRank + 1] += nVertexS; } } @@ -819,13 +819,13 @@ void CGeometry::PreprocessPeriodicComms(CGeometry* geometry, CConfig* config) { /*--- Get the rank that holds the matching periodic point on the other marker in the periodic pair. ---*/ - iRank = (int)geometry->vertex[iMarker][iVertex]->GetDonorProcessor(); + iRank = static_cast(geometry->vertex[iMarker][iVertex]->GetDonorProcessor()); /*--- If we have not visited this point last, increment our number of points that must be sent to a particular proc. ---*/ - if ((nPoint_Flag[iRank] != (int)iPoint)) { - nPoint_Flag[iRank] = (int)iPoint; + if ((nPoint_Flag[iRank] != static_cast(iPoint))) { + nPoint_Flag[iRank] = static_cast(iPoint); nPoint_Send_All[iRank + 1] += 1; } } @@ -962,7 +962,7 @@ void CGeometry::PreprocessPeriodicComms(CGeometry* geometry, CConfig* config) { /*--- Get the rank that holds the matching periodic point on the other marker in the periodic pair. ---*/ - iRank = (int)geometry->vertex[iMarker][iVertex]->GetDonorProcessor(); + iRank = static_cast(geometry->vertex[iMarker][iVertex]->GetDonorProcessor()); /*--- If the rank for the current periodic point matches the rank of the current send message, then store the local point @@ -971,11 +971,11 @@ void CGeometry::PreprocessPeriodicComms(CGeometry* geometry, CConfig* config) { if (iRank == Neighbors_PeriodicSend[iSend]) { Local_Point_PeriodicSend[ii] = iPoint; - Local_Marker_PeriodicSend[ii] = (unsigned long)iMarker; + Local_Marker_PeriodicSend[ii] = static_cast(iMarker); jj = ii * nPackets; idSend[jj] = geometry->vertex[iMarker][iVertex]->GetDonorPoint(); jj++; - idSend[jj] = (unsigned long)iPeriodic; + idSend[jj] = static_cast(iPeriodic); ii++; } } diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index 6bbbaa38e3d..eab91fca43c 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -31,9 +31,13 @@ #include "../../include/toolboxes/CLinearPartitioner.hpp" #include "../../include/toolboxes/C1DInterpolation.hpp" #include "../../include/toolboxes/geometry_toolbox.hpp" +#include "../../include/geometry/meshreader/CSU2ASCIIMeshReaderFEM.hpp" #include "../../include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp" #include "../../include/geometry/meshreader/CCGNSMeshReaderFVM.hpp" +#include "../../include/geometry/meshreader/CCGNSMeshReaderFEM.hpp" +#include "../../include/geometry/meshreader/CRectangularMeshReaderFEM.hpp" #include "../../include/geometry/meshreader/CRectangularMeshReaderFVM.hpp" +#include "../../include/geometry/meshreader/CBoxMeshReaderFEM.hpp" #include "../../include/geometry/meshreader/CBoxMeshReaderFVM.hpp" #include "../../include/geometry/primal_grid/CPrimalGrid.hpp" @@ -72,38 +76,18 @@ CPhysicalGeometry::CPhysicalGeometry(CConfig* config, unsigned short val_iZone, string val_mesh_filename = config->GetMesh_FileName(); unsigned short val_format = config->GetMesh_FileFormat(); - /*--- Determine whether or not a FEM discretization is used ---*/ + /*--- Check for a valid mesh format ---*/ - const bool fem_solver = config->GetFEMSolver(); - - /*--- Initialize counters for local/global points & elements ---*/ - - if (fem_solver) { - switch (val_format) { - case SU2: - Read_SU2_Format_Parallel_FEM(config, val_mesh_filename, val_iZone, val_nZone); - break; - - case CGNS_GRID: - Read_CGNS_Format_Parallel_FEM(config, val_mesh_filename, val_iZone, val_nZone); - break; - - default: - SU2_MPI::Error("Unrecognized mesh format specified for the FEM solver!", CURRENT_FUNCTION); - break; - } - } else { - switch (val_format) { - case SU2: - case CGNS_GRID: - case RECTANGLE: - case BOX: - Read_Mesh_FVM(config, val_mesh_filename, val_iZone, val_nZone); - break; - default: - SU2_MPI::Error("Unrecognized mesh format specified!", CURRENT_FUNCTION); - break; - } + switch (val_format) { + case SU2: + case CGNS_GRID: + case RECTANGLE: + case BOX: + Read_Mesh(config, val_mesh_filename, val_iZone, val_nZone); + break; + default: + SU2_MPI::Error("Unrecognized mesh format specified!", CURRENT_FUNCTION); + break; } /*--- After reading the mesh, assert that the dimension is equal to 2 or 3. ---*/ @@ -563,8 +547,8 @@ void CPhysicalGeometry::DistributeColoring(const CConfig* config, CGeometry* geo /*--- If we have not visited this node yet, increment our number of points that must be sent to a particular proc. ---*/ - if (nPoint_Flag[iProcessor] != (int)iPoint) { - nPoint_Flag[iProcessor] = (int)iPoint; + if (nPoint_Flag[iProcessor] != static_cast(iPoint)) { + nPoint_Flag[iProcessor] = static_cast(iPoint); nPoint_Send[iProcessor + 1]++; } } @@ -623,8 +607,8 @@ void CPhysicalGeometry::DistributeColoring(const CConfig* config, CGeometry* geo /*--- If we have not visited this node yet, increment our counters and load up the global ID and color. ---*/ - if (nPoint_Flag[iProcessor] != (int)iPoint) { - nPoint_Flag[iProcessor] = (int)iPoint; + if (nPoint_Flag[iProcessor] != static_cast(iPoint)) { + nPoint_Flag[iProcessor] = static_cast(iPoint); unsigned long nn = index[iProcessor]; /*--- Load the data values. ---*/ @@ -796,8 +780,8 @@ void CPhysicalGeometry::DistributeVolumeConnectivity(const CConfig* config, CGeo /*--- If we have not visited this element yet, increment our number of elements that must be sent to a particular proc. ---*/ - if ((nElem_Flag[iProcessor] != (int)iElem)) { - nElem_Flag[iProcessor] = (int)iElem; + if ((nElem_Flag[iProcessor] != static_cast(iElem))) { + nElem_Flag[iProcessor] = static_cast(iElem); nElem_Send[iProcessor + 1]++; } } @@ -863,8 +847,8 @@ void CPhysicalGeometry::DistributeVolumeConnectivity(const CConfig* config, CGeo /*--- Load connectivity and IDs into the buffer for sending ---*/ - if (nElem_Flag[iProcessor] != (int)iElem) { - nElem_Flag[iProcessor] = (int)iElem; + if (nElem_Flag[iProcessor] != static_cast(iElem)) { + nElem_Flag[iProcessor] = static_cast(iElem); unsigned long nn = index[iProcessor]; unsigned long mm = idIndex[iProcessor]; @@ -1087,8 +1071,8 @@ void CPhysicalGeometry::DistributePoints(const CConfig* config, CGeometry* geome /*--- If we have not visited this node yet, increment our number of points that must be sent to a particular proc. ---*/ - if (nPoint_Flag[iProcessor] != (int)iPoint) { - nPoint_Flag[iProcessor] = (int)iPoint; + if (nPoint_Flag[iProcessor] != static_cast(iPoint)) { + nPoint_Flag[iProcessor] = static_cast(iPoint); nPoint_Send[iProcessor + 1]++; } } @@ -1156,8 +1140,8 @@ void CPhysicalGeometry::DistributePoints(const CConfig* config, CGeometry* geome /*--- If we have not visited this node yet, increment our counters and load up the colors, ids, and coords. ---*/ - if (nPoint_Flag[iProcessor] != (int)iPoint) { - nPoint_Flag[iProcessor] = (int)iPoint; + if (nPoint_Flag[iProcessor] != static_cast(iPoint)) { + nPoint_Flag[iProcessor] = static_cast(iPoint); unsigned long nn = index[iProcessor]; /*--- Load the global ID, color, and coordinate values. ---*/ @@ -1264,7 +1248,7 @@ void CPhysicalGeometry::DistributePoints(const CConfig* config, CGeometry* geome Local_Points[iRecv] = idRecv[iRecv]; Local_Colors[iRecv] = colorRecv[iRecv]; for (iDim = 0; iDim < nDim; iDim++) Local_Coords[iRecv * nDim + iDim] = coordRecv[iRecv * nDim + iDim]; - if (Local_Colors[iRecv] == (unsigned long)rank) + if (Local_Colors[iRecv] == static_cast(rank)) nLocal_PointDomain++; else nLocal_PointGhost++; @@ -1375,8 +1359,8 @@ void CPhysicalGeometry::PartitionSurfaceConnectivity(CConfig* config, CGeometry* /*--- If we have not visited this element yet, increment our number of elements that must be sent to a particular proc. ---*/ - if ((nElem_Flag[iProcessor] != (int)iElem)) { - nElem_Flag[iProcessor] = (int)iElem; + if ((nElem_Flag[iProcessor] != static_cast(iElem))) { + nElem_Flag[iProcessor] = static_cast(iElem); nElem_Send[iProcessor + 1]++; } } @@ -1456,8 +1440,8 @@ void CPhysicalGeometry::PartitionSurfaceConnectivity(CConfig* config, CGeometry* /*--- Load connectivity into the buffer for sending ---*/ - if ((nElem_Flag[iProcessor] != (int)iElem)) { - nElem_Flag[iProcessor] = (int)iElem; + if ((nElem_Flag[iProcessor] != static_cast(iElem))) { + nElem_Flag[iProcessor] = static_cast(iElem); unsigned long nn = index[iProcessor]; unsigned long mm = markerIndex[iProcessor]; @@ -1728,8 +1712,8 @@ void CPhysicalGeometry::DistributeSurfaceConnectivity(CConfig* config, CGeometry /*--- If we have not visited this element yet, increment our number of elements that must be sent to a particular proc. ---*/ - if ((nElem_Flag[iProcessor] != (int)iElem)) { - nElem_Flag[iProcessor] = (int)iElem; + if ((nElem_Flag[iProcessor] != static_cast(iElem))) { + nElem_Flag[iProcessor] = static_cast(iElem); nElem_Send[iProcessor + 1]++; } } @@ -1797,8 +1781,8 @@ void CPhysicalGeometry::DistributeSurfaceConnectivity(CConfig* config, CGeometry /*--- If we have not visited this element yet, load up the data for sending. ---*/ - if (nElem_Flag[iProcessor] != (int)iElem) { - nElem_Flag[iProcessor] = (int)iElem; + if (nElem_Flag[iProcessor] != static_cast(iElem)) { + nElem_Flag[iProcessor] = static_cast(iElem); unsigned long nn = index[iProcessor]; unsigned long mm = markerIndex[iProcessor]; @@ -2006,7 +1990,8 @@ void CPhysicalGeometry::DistributeMarkerTags(CConfig* config, CGeometry* geometr /*--- Broadcast the string names of the variables. ---*/ - SU2_MPI::Bcast(mpi_str_buf, (int)nMarker_Global * MAX_STRING_SIZE, MPI_CHAR, MASTER_NODE, SU2_MPI::GetComm()); + SU2_MPI::Bcast(mpi_str_buf, static_cast(nMarker_Global) * MAX_STRING_SIZE, MPI_CHAR, MASTER_NODE, + SU2_MPI::GetComm()); /*--- Now parse the string names and load into our marker tag vector. We also need to set the values of all markers into the config. ---*/ @@ -2058,7 +2043,7 @@ void CPhysicalGeometry::LoadPoints(CConfig* config, CGeometry* geometry) { for (iPoint = 0; iPoint < nPoint; iPoint++) { /*--- Set the starting point to the correct counter for this point. ---*/ - if (Local_Colors[iPoint] == (unsigned long)rank) { + if (Local_Colors[iPoint] == static_cast(rank)) { if (Local_Points[iPoint] < geometry->GetGlobal_nPointDomain()) jPoint = iOwned; else @@ -2082,7 +2067,7 @@ void CPhysicalGeometry::LoadPoints(CConfig* config, CGeometry* geometry) { /*--- Increment the correct counter before moving to the next point. ---*/ - if (Local_Colors[iPoint] == (unsigned long)rank) { + if (Local_Colors[iPoint] == static_cast(rank)) { if (Local_Points[iPoint] < geometry->GetGlobal_nPointDomain()) iOwned++; else @@ -2891,7 +2876,7 @@ unsigned long CPhysicalGeometry::GetLinearPartition(unsigned long val_global_ind /*--- Guard against going over size. ---*/ - if (iProcessor >= (unsigned long)size) iProcessor = (unsigned long)size - 1; + if (iProcessor >= static_cast(size)) iProcessor = static_cast(size) - 1; /*--- Move up or down until we find the processor. ---*/ @@ -3006,7 +2991,7 @@ void CPhysicalGeometry::SetSendReceive(const CConfig* config) { iPoint = elem[iElem]->GetNode(iNode); iDomain = nodes->GetColor(iPoint); - if (iDomain == (unsigned long)rank) { + if (iDomain == static_cast(rank)) { for (jNode = 0; jNode < elem[iElem]->GetnNodes(); jNode++) { jPoint = elem[iElem]->GetNode(jNode); jDomain = nodes->GetColor(jPoint); @@ -3433,8 +3418,8 @@ void CPhysicalGeometry::SetBoundaries(CConfig* config) { delete[] nElem_Bound_Copy; } -void CPhysicalGeometry::Read_Mesh_FVM(CConfig* config, const string& val_mesh_filename, unsigned short val_iZone, - unsigned short val_nZone) { +void CPhysicalGeometry::Read_Mesh(CConfig* config, const string& val_mesh_filename, unsigned short val_iZone, + unsigned short val_nZone) { /*--- Initialize counters for local/global points & elements ---*/ Global_nPoint = 0; @@ -3463,20 +3448,33 @@ void CPhysicalGeometry::Read_Mesh_FVM(CConfig* config, const string& val_mesh_fi /*--- Create a mesh reader to read a CGNS grid into linear partitions. ---*/ unsigned short val_format = config->GetMesh_FileFormat(); + const bool fem_solver = config->GetFEMSolver(); - CMeshReaderFVM* MeshFVM = nullptr; + CMeshReaderBase* Mesh = nullptr; switch (val_format) { case SU2: - MeshFVM = new CSU2ASCIIMeshReaderFVM(config, val_iZone, val_nZone); + if (fem_solver) + Mesh = new CSU2ASCIIMeshReaderFEM(config, val_iZone, val_nZone); + else + Mesh = new CSU2ASCIIMeshReaderFVM(config, val_iZone, val_nZone); break; case CGNS_GRID: - MeshFVM = new CCGNSMeshReaderFVM(config, val_iZone, val_nZone); + if (fem_solver) + Mesh = new CCGNSMeshReaderFEM(config, val_iZone, val_nZone); + else + Mesh = new CCGNSMeshReaderFVM(config, val_iZone, val_nZone); break; case RECTANGLE: - MeshFVM = new CRectangularMeshReaderFVM(config, val_iZone, val_nZone); + if (fem_solver) + Mesh = new CRectangularMeshReaderFEM(config, val_iZone, val_nZone); + else + Mesh = new CRectangularMeshReaderFVM(config, val_iZone, val_nZone); break; case BOX: - MeshFVM = new CBoxMeshReaderFVM(config, val_iZone, val_nZone); + if (fem_solver) + Mesh = new CBoxMeshReaderFEM(config, val_iZone, val_nZone); + else + Mesh = new CBoxMeshReaderFVM(config, val_iZone, val_nZone); break; default: SU2_MPI::Error("Unrecognized mesh format specified!", CURRENT_FUNCTION); @@ -3485,7 +3483,7 @@ void CPhysicalGeometry::Read_Mesh_FVM(CConfig* config, const string& val_mesh_fi /*--- Store the dimension of the problem ---*/ - nDim = MeshFVM->GetDimension(); + nDim = Mesh->GetDimension(); if (rank == MASTER_NODE) { if (nDim == 2) cout << "Two dimensional problem." << endl; if (nDim == 3) cout << "Three dimensional problem." << endl; @@ -3493,10 +3491,10 @@ void CPhysicalGeometry::Read_Mesh_FVM(CConfig* config, const string& val_mesh_fi /*--- Store the local and global number of nodes for this rank. ---*/ - nPoint = MeshFVM->GetNumberOfLocalPoints(); - nPointDomain = MeshFVM->GetNumberOfLocalPoints(); - Global_nPoint = MeshFVM->GetNumberOfGlobalPoints(); - Global_nPointDomain = MeshFVM->GetNumberOfGlobalPoints(); + nPoint = Mesh->GetNumberOfLocalPoints(); + nPointDomain = Mesh->GetNumberOfLocalPoints(); + Global_nPoint = Mesh->GetNumberOfGlobalPoints(); + Global_nPointDomain = Mesh->GetNumberOfGlobalPoints(); if ((rank == MASTER_NODE) && (size > SINGLE_NODE)) { cout << Global_nPoint << " grid points before partitioning." << endl; @@ -3506,9 +3504,9 @@ void CPhysicalGeometry::Read_Mesh_FVM(CConfig* config, const string& val_mesh_fi /*--- Store the local and global number of interior elements. ---*/ - nElem = MeshFVM->GetNumberOfLocalElements(); - Global_nElem = MeshFVM->GetNumberOfGlobalElements(); - Global_nElemDomain = MeshFVM->GetNumberOfGlobalElements(); + nElem = Mesh->GetNumberOfLocalElements(); + Global_nElem = Mesh->GetNumberOfGlobalElements(); + Global_nElemDomain = Mesh->GetNumberOfGlobalElements(); if ((rank == MASTER_NODE) && (size > SINGLE_NODE)) { cout << Global_nElem << " volume elements before partitioning." << endl; @@ -3516,24 +3514,35 @@ void CPhysicalGeometry::Read_Mesh_FVM(CConfig* config, const string& val_mesh_fi cout << Global_nElem << " volume elements." << endl; } - /*--- Load the grid points, volume elements, and surface elements - from the mesh object into the proper SU2 data structures. ---*/ + /*--- Make a distinction between the FVM solver and FEM solver how to load + the grid data in the member variables of CPhysicalGeometry. ---*/ + if (fem_solver) { + /*--- Load the grid points, volume elements, and surface elements + from the mesh object into the proper SU2 data structures. ---*/ + + LoadLinearlyPartitionedPointsFEM(config, Mesh); + LoadLinearlyPartitionedVolumeElementsFEM(config, Mesh); + LoadLinearlyPartitionedSurfaceElementsFEM(config, Mesh); + } else { + /*--- Load the grid points, volume elements, and surface elements + from the mesh object into the proper SU2 data structures. ---*/ - LoadLinearlyPartitionedPoints(config, MeshFVM); - LoadLinearlyPartitionedVolumeElements(config, MeshFVM); - LoadUnpartitionedSurfaceElements(config, MeshFVM); + LoadLinearlyPartitionedPoints(config, Mesh); + LoadLinearlyPartitionedVolumeElements(config, Mesh); + LoadUnpartitionedSurfaceElements(config, Mesh); - /*--- Prepare the nodal adjacency structures for ParMETIS. ---*/ + /*--- Prepare the nodal adjacency structures for ParMETIS. ---*/ - PrepareAdjacency(config); + PrepareAdjacency(config); + } /*--- Now that we have loaded all information from the mesh, delete the mesh reader object. ---*/ - delete MeshFVM; + delete Mesh; } -void CPhysicalGeometry::LoadLinearlyPartitionedPoints(CConfig* config, CMeshReaderFVM* mesh) { +void CPhysicalGeometry::LoadLinearlyPartitionedPoints(CConfig* config, CMeshReaderBase* mesh) { /*--- Get the linearly partitioned coordinates from the mesh object. ---*/ const auto& gridCoords = mesh->GetLocalPointCoordinates(); @@ -3556,7 +3565,7 @@ void CPhysicalGeometry::LoadLinearlyPartitionedPoints(CConfig* config, CMeshRead } } -void CPhysicalGeometry::LoadLinearlyPartitionedVolumeElements(CConfig* config, CMeshReaderFVM* mesh) { +void CPhysicalGeometry::LoadLinearlyPartitionedVolumeElements(CConfig* config, CMeshReaderBase* mesh) { /*--- Reset the global to local element mapping. ---*/ Global_to_Local_Elem.clear(); @@ -3643,7 +3652,7 @@ void CPhysicalGeometry::LoadLinearlyPartitionedVolumeElements(CConfig* config, C reduce(nelem_pyramid, Global_nelem_pyramid); } -void CPhysicalGeometry::LoadUnpartitionedSurfaceElements(CConfig* config, CMeshReaderFVM* mesh) { +void CPhysicalGeometry::LoadUnpartitionedSurfaceElements(CConfig* config, CMeshReaderBase* mesh) { /*--- The master node takes care of loading all markers and surface elements from the file. This information is later put into linear partitions to make its redistribution easier @@ -3709,11 +3718,11 @@ void CPhysicalGeometry::LoadUnpartitionedSurfaceElements(CConfig* config, CMeshR /*--- Not a mixed section. We already know the element type, which is stored ---*/ - vtk_type = (int)connElems[jElem * SU2_CONN_SIZE + 1]; + vtk_type = static_cast(connElems[jElem * SU2_CONN_SIZE + 1]); /*--- Store the loop size more easily. ---*/ - npe = (int)(SU2_CONN_SIZE - SU2_CONN_SKIP); + npe = static_cast(SU2_CONN_SIZE - SU2_CONN_SKIP); /*--- Store the nodes for this element more clearly. ---*/ @@ -5089,7 +5098,7 @@ void CPhysicalGeometry::SetTurboVertex(CConfig* config, unsigned short val_iZone if (config->GetMarker_All_TurbomachineryFlag(iMarker) == marker_flag) { /*--- compute the amount of vertexes for each span-wise section to initialize the CTurboVertex pointers and * auxiliary pointers ---*/ - for (iVertex = 0; (unsigned long)iVertex < nVertex[iMarker]; iVertex++) { + for (iVertex = 0; static_cast(iVertex) < nVertex[iMarker]; iVertex++) { iPoint = vertex[iMarker][iVertex]->GetNode(); if (nDim == 3) { dist = 10E+06; @@ -5189,7 +5198,7 @@ void CPhysicalGeometry::SetTurboVertex(CConfig* config, unsigned short val_iZone } /*--- store the vertexes in a ordered manner in span-wise directions but not yet ordered pitch-wise ---*/ - for (iVertex = 0; (unsigned long)iVertex < nVertex[iMarker]; iVertex++) { + for (iVertex = 0; static_cast(iVertex) < nVertex[iMarker]; iVertex++) { iPoint = vertex[iMarker][iVertex]->GetNode(); if (nDim == 3) { dist = 10E+06; @@ -5515,7 +5524,7 @@ void CPhysicalGeometry::SetTurboVertex(CConfig* config, unsigned short val_iZone SetnVertexSpanMax(marker_flag, nVert); } /*--- for all the processor should be known the amount of total turbovertex per span ---*/ - nTotVertex_gb[iSpan] = (int)nVert; + nTotVertex_gb[iSpan] = static_cast(nVert); for (iMarker = 0; iMarker < nMarker; iMarker++) { for (iMarkerTP = 1; iMarkerTP < config->GetnMarker_Turbomachinery() + 1; iMarkerTP++) { @@ -8267,7 +8276,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- First, read the number of variables and points. ---*/ ret = fread(Restart_Vars, sizeof(int), nRestart_Vars, fhw); - if (ret != (unsigned long)nRestart_Vars) { + if (ret != static_cast(nRestart_Vars)) { SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); } @@ -8294,7 +8303,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { config->fields.push_back("Point_ID"); for (iVar = 0; iVar < nFields; iVar++) { ret = fread(str_buf, sizeof(char), CGNS_STRING_SIZE, fhw); - if (ret != (unsigned long)CGNS_STRING_SIZE) { + if (ret != static_cast(CGNS_STRING_SIZE)) { SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); } config->fields.push_back(str_buf); @@ -8307,7 +8316,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- Read in the data for the restart at all local points. ---*/ ret = fread(Restart_Data, sizeof(passivedouble), nFields * GetnPointDomain(), fhw); - if (ret != (unsigned long)nFields * GetnPointDomain()) { + if (ret != static_cast(nFields) * GetnPointDomain()) { SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); } @@ -8402,7 +8411,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { config->fields.emplace_back("Point_ID"); for (iVar = 0; iVar < nFields; iVar++) { index = iVar * CGNS_STRING_SIZE; - for (iChar = 0; iChar < (unsigned long)CGNS_STRING_SIZE; iChar++) { + for (iChar = 0; iChar < static_cast(CGNS_STRING_SIZE); iChar++) { str_buf[iChar] = mpi_str_buf[index + iChar]; } field_buf.append(str_buf); diff --git a/Common/src/geometry/meshreader/CBoxMeshReaderFEM.cpp b/Common/src/geometry/meshreader/CBoxMeshReaderFEM.cpp new file mode 100644 index 00000000000..44dfde1dc7f --- /dev/null +++ b/Common/src/geometry/meshreader/CBoxMeshReaderFEM.cpp @@ -0,0 +1,348 @@ +/*! + * \file CBoxMeshReaderFEM.cpp + * \brief Reads a 3D box grid into linear partitions for the + * finite element solver (FEM). + * \author T. Economon, E. van der Weide + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CBoxMeshReaderFEM.hpp" + +CBoxMeshReaderFEM::CBoxMeshReaderFEM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CMeshReaderBase(val_config, val_iZone, val_nZone) { + /* The box mesh is always 3D. */ + dimension = 3; + + /* Set the VTK type for the interior elements and the boundary elements. */ + KindElem = HEXAHEDRON; + KindBound = QUADRILATERAL; + + /* The number of grid nodes in the i, j and k directions. */ + nNode = config->GetMeshBoxSize(0); + mNode = config->GetMeshBoxSize(1); + pNode = config->GetMeshBoxSize(2); + + /* Lengths for non-square domains. */ + Lx = config->GetMeshBoxLength(0); + Ly = config->GetMeshBoxLength(1); + Lz = config->GetMeshBoxLength(2); + + /* Offsets in x, y and z directions from 0.0. */ + Ox = config->GetMeshBoxOffset(0); + Oy = config->GetMeshBoxOffset(1); + Oz = config->GetMeshBoxOffset(2); + + /* Polynomial degree of the solution. */ + nPolySol = config->GetMeshBoxPSolFEM(); + + /*--- Compute and store the interior elements, points, and surface elements + for this rank, for which simple analytic formulae can be used. ---*/ + ComputeBoxVolumeConnectivity(); + ComputeBoxPointCoordinates(); + ComputeBoxSurfaceConnectivity(); +} + +CBoxMeshReaderFEM::~CBoxMeshReaderFEM() = default; + +void CBoxMeshReaderFEM::ComputeBoxPointCoordinates() { + /*--- Set the global count of points based on the grid dimensions. ---*/ + numberOfGlobalPoints = nNode * mNode * pNode; + + /*--- Loop over the local elements to determine the global + point IDs to be stored on this rank. --*/ + unsigned long ind = 0; + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + /*--- Store the number of grid DOFs for this element and + skip the meta data for this element (5 entries). ---*/ + const unsigned long nDOFsGrid = localVolumeElementConnectivity[ind + 3]; + ind += 5; + + /*--- Copy the connectivity to globalPointIDs. ---*/ + unsigned long* conn = localVolumeElementConnectivity.data() + ind; + ind += nDOFsGrid; + globalPointIDs.insert(globalPointIDs.end(), conn, conn + nDOFsGrid); + } + + /*--- Sort the globalPointIDs and remove the duplicate entries. ---*/ + sort(globalPointIDs.begin(), globalPointIDs.end()); + vector::iterator lastNode; + lastNode = unique(globalPointIDs.begin(), globalPointIDs.end()); + globalPointIDs.erase(lastNode, globalPointIDs.end()); + + /*--- Determine the number of locally stored points. ---*/ + numberOfLocalPoints = globalPointIDs.size(); + + /*--- Allocate the memory for the locally stored points. ---*/ + localPointCoordinates.resize(dimension); + for (int k = 0; k < dimension; k++) localPointCoordinates[k].resize(numberOfLocalPoints); + + /*--- Loop over the locally stored points. ---*/ + const unsigned long nPointIJ = nNode * mNode; + for (unsigned long i = 0; i < numberOfLocalPoints; ++i) { + /*--- Convert the global index to i,j,k indices. ---*/ + const unsigned long kNode = globalPointIDs[i] / nPointIJ; + const unsigned long rem = globalPointIDs[i] - kNode * nPointIJ; + const unsigned long jNode = rem / nNode; + const unsigned long iNode = rem - jNode * nNode; + + /*--- Store the coordinates of the point. ---*/ + localPointCoordinates[0][i] = SU2_TYPE::GetValue(Lx * ((su2double)iNode) / ((su2double)(nNode - 1)) + Ox); + localPointCoordinates[1][i] = SU2_TYPE::GetValue(Ly * ((su2double)jNode) / ((su2double)(mNode - 1)) + Oy); + localPointCoordinates[2][i] = SU2_TYPE::GetValue(Lz * ((su2double)kNode) / ((su2double)(pNode - 1)) + Oz); + } +} + +void CBoxMeshReaderFEM::ComputeBoxVolumeConnectivity() { + /*--- Set the global count of elements based on the grid dimensions. ---*/ + const unsigned long nElemI = nNode - 1; + const unsigned long nElemIJ = nElemI * (mNode - 1); + numberOfGlobalElements = nElemIJ * (pNode - 1); + + /*--- Get a partitioner to help with linear partitioning. ---*/ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /*--- Determine the index of the first and last element to be stored + on this rank and the number of local elements. ---*/ + const unsigned long firstIndex = elemPartitioner.GetFirstIndexOnRank(rank); + const unsigned long lastIndex = elemPartitioner.GetLastIndexOnRank(rank); + numberOfLocalElements = elemPartitioner.GetSizeOnRank(rank); + + /*--- Loop over the owned element range. ---*/ + for (unsigned long elem = firstIndex; elem < lastIndex; ++elem) { + /*--- Retrieve the i,j,k indices of this element, which are the indices + of the lower, left, back point of the element. --*/ + const unsigned long kNode = elem / nElemIJ; + const unsigned long rem = elem - kNode * nElemIJ; + const unsigned long jNode = rem / nElemI; + const unsigned long iNode = rem - jNode * nElemI; + + /*--- Store the meta data of this element. ---*/ + localVolumeElementConnectivity.push_back(KindElem); + localVolumeElementConnectivity.push_back(1); // Pol. degree grid. + localVolumeElementConnectivity.push_back(nPolySol); + localVolumeElementConnectivity.push_back(8); // Number of grid DOFs. + localVolumeElementConnectivity.push_back(elem); // Global elem ID. + + /*--- Store the volume connectivity in the format used + by the FEM solver. ---*/ + localVolumeElementConnectivity.push_back(kNode * mNode * nNode + jNode * nNode + iNode); + localVolumeElementConnectivity.push_back(kNode * mNode * nNode + jNode * nNode + iNode + 1); + localVolumeElementConnectivity.push_back(kNode * mNode * nNode + (jNode + 1) * nNode + iNode); + localVolumeElementConnectivity.push_back(kNode * mNode * nNode + (jNode + 1) * nNode + iNode + 1); + localVolumeElementConnectivity.push_back((kNode + 1) * mNode * nNode + jNode * nNode + iNode); + localVolumeElementConnectivity.push_back((kNode + 1) * mNode * nNode + jNode * nNode + iNode + 1); + localVolumeElementConnectivity.push_back((kNode + 1) * mNode * nNode + (jNode + 1) * nNode + iNode); + localVolumeElementConnectivity.push_back((kNode + 1) * mNode * nNode + (jNode + 1) * nNode + iNode + 1); + } +} + +void CBoxMeshReaderFEM::ComputeBoxSurfaceConnectivity() { + /*--- Determine the number of elements in i-direction and the + number of elements on a k-face. ---*/ + const unsigned long nElemI = nNode - 1; + const unsigned long nElemIJ = nElemI * (mNode - 1); + + /*--- Get a partitioner to help with linear partitioning. ---*/ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /*--- The box always has 6 markers. Allocate the required memory. ---*/ + numberOfMarkers = 6; + numberOfLocalSurfaceElements.resize(numberOfMarkers, 0); + surfaceElementConnectivity.resize(numberOfMarkers); + markerNames.resize(numberOfMarkers); + + /*--- Loop over all faces on the xMin (= iMin) boundary. ---*/ + markerNames[0] = "x_minus"; + + unsigned long ind = 0; + for (unsigned long kNode = 0; kNode < pNode - 1; ++kNode) { + for (unsigned long jNode = 0; jNode < mNode - 1; ++jNode, ++ind) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = kNode * nElemIJ + jNode * nElemI; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[0].push_back(KindBound); // VTK type. + surfaceElementConnectivity[0].push_back(1); // Poly degree grid. + surfaceElementConnectivity[0].push_back(4); // Number of grid DOFs. + surfaceElementConnectivity[0].push_back(ind); // Global surface element ID. + surfaceElementConnectivity[0].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[0].push_back(kNode * mNode * nNode + jNode * nNode); + surfaceElementConnectivity[0].push_back((kNode + 1) * mNode * nNode + jNode * nNode); + surfaceElementConnectivity[0].push_back(kNode * mNode * nNode + (jNode + 1) * nNode); + surfaceElementConnectivity[0].push_back((kNode + 1) * mNode * nNode + (jNode + 1) * nNode); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[0]; + } + } + } + + /*--- Loop over all faces on the xMax (= iMax) boundary. ---*/ + markerNames[1] = "x_plus"; + + ind = 0; + for (unsigned long kNode = 0; kNode < pNode - 1; ++kNode) { + for (unsigned long jNode = 0; jNode < mNode - 1; ++jNode, ++ind) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = kNode * nElemIJ + jNode * nElemI + nElemI - 1; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[1].push_back(KindBound); // VTK type. + surfaceElementConnectivity[1].push_back(1); // Poly degree grid. + surfaceElementConnectivity[1].push_back(4); // Number of grid DOFs. + surfaceElementConnectivity[1].push_back(ind); // Global surface element ID. + surfaceElementConnectivity[1].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[1].push_back(kNode * mNode * nNode + jNode * nNode + (nNode - 1)); + surfaceElementConnectivity[1].push_back(kNode * mNode * nNode + (jNode + 1) * nNode + (nNode - 1)); + surfaceElementConnectivity[1].push_back((kNode + 1) * mNode * nNode + jNode * nNode + (nNode - 1)); + surfaceElementConnectivity[1].push_back((kNode + 1) * mNode * nNode + (jNode + 1) * nNode + (nNode - 1)); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[1]; + } + } + } + + /*--- Loop over all faces on the yMin (= jMin) boundary. ---*/ + markerNames[2] = "y_minus"; + + ind = 0; + for (unsigned long kNode = 0; kNode < pNode - 1; ++kNode) { + for (unsigned long iNode = 0; iNode < nNode - 1; ++iNode, ++ind) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = kNode * nElemIJ + iNode; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[2].push_back(KindBound); // VTK type. + surfaceElementConnectivity[2].push_back(1); // Poly degree grid. + surfaceElementConnectivity[2].push_back(4); // Number of grid DOFs. + surfaceElementConnectivity[2].push_back(ind); // Global surface element ID. + surfaceElementConnectivity[2].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[2].push_back(kNode * mNode * nNode + iNode); + surfaceElementConnectivity[2].push_back(kNode * mNode * nNode + iNode + 1); + surfaceElementConnectivity[2].push_back((kNode + 1) * mNode * nNode + iNode); + surfaceElementConnectivity[2].push_back((kNode + 1) * mNode * nNode + iNode + 1); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[2]; + } + } + } + + /*--- Loop over all faces on the yMax (= jMax) boundary. ---*/ + markerNames[3] = "y_plus"; + + ind = 0; + for (unsigned long kNode = 0; kNode < pNode - 1; ++kNode) { + for (unsigned long iNode = 0; iNode < nNode - 1; ++iNode, ++ind) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = kNode * nElemIJ + (mNode - 2) * nElemI + iNode; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[3].push_back(KindBound); // VTK type. + surfaceElementConnectivity[3].push_back(1); // Poly degree grid. + surfaceElementConnectivity[3].push_back(4); // Number of grid DOFs. + surfaceElementConnectivity[3].push_back(ind); // Global surface element ID. + surfaceElementConnectivity[3].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[3].push_back(kNode * mNode * nNode + (mNode - 1) * nNode + iNode); + surfaceElementConnectivity[3].push_back(kNode * mNode * nNode + (mNode - 1) * nNode + iNode + 1); + surfaceElementConnectivity[3].push_back((kNode + 1) * mNode * nNode + (mNode - 1) * nNode + iNode); + surfaceElementConnectivity[3].push_back((kNode + 1) * mNode * nNode + (mNode - 1) * nNode + iNode + 1); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[3]; + } + } + } + + /*--- Loop over all faces on the zMin (= kMin) boundary. ---*/ + markerNames[4] = "z_minus"; + + ind = 0; + for (unsigned long jNode = 0; jNode < mNode - 1; ++jNode) { + for (unsigned long iNode = 0; iNode < nNode - 1; ++iNode, ++ind) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = jNode * nElemI + iNode; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[4].push_back(KindBound); // VTK type. + surfaceElementConnectivity[4].push_back(1); // Poly degree grid. + surfaceElementConnectivity[4].push_back(4); // Number of grid DOFs. + surfaceElementConnectivity[4].push_back(ind); // Global surface element ID. + surfaceElementConnectivity[4].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[4].push_back(jNode * nNode + iNode); + surfaceElementConnectivity[4].push_back(jNode * nNode + iNode + 1); + surfaceElementConnectivity[4].push_back((jNode + 1) * nNode + iNode); + surfaceElementConnectivity[4].push_back((jNode + 1) * nNode + iNode + 1); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[4]; + } + } + } + + /*--- Loop over all faces on the zMax (= kMax) boundary. ---*/ + markerNames[5] = "z_plus"; + + ind = 0; + for (unsigned long jNode = 0; jNode < mNode - 1; ++jNode) { + for (unsigned long iNode = 0; iNode < nNode - 1; ++iNode, ++ind) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = (pNode - 2) * nElemIJ + jNode * nElemI + iNode; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[5].push_back(KindBound); // VTK type. + surfaceElementConnectivity[5].push_back(1); // Poly degree grid. + surfaceElementConnectivity[5].push_back(4); // Number of grid DOFs. + surfaceElementConnectivity[5].push_back(ind); // Global surface element ID. + surfaceElementConnectivity[5].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[5].push_back((pNode - 1) * mNode * nNode + jNode * nNode + iNode); + surfaceElementConnectivity[5].push_back((pNode - 1) * mNode * nNode + jNode * nNode + iNode + 1); + surfaceElementConnectivity[5].push_back((pNode - 1) * mNode * nNode + (jNode + 1) * nNode + iNode); + surfaceElementConnectivity[5].push_back((pNode - 1) * mNode * nNode + (jNode + 1) * nNode + iNode + 1); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[5]; + } + } + } +} diff --git a/Common/src/geometry/meshreader/CBoxMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CBoxMeshReaderFVM.cpp index e4ee811eaec..e2c0f52c3b1 100644 --- a/Common/src/geometry/meshreader/CBoxMeshReaderFVM.cpp +++ b/Common/src/geometry/meshreader/CBoxMeshReaderFVM.cpp @@ -29,8 +29,8 @@ #include "../../../include/toolboxes/CLinearPartitioner.hpp" #include "../../../include/geometry/meshreader/CBoxMeshReaderFVM.hpp" -CBoxMeshReaderFVM::CBoxMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) - : CMeshReaderFVM(val_config, val_iZone, val_nZone) { +CBoxMeshReaderFVM::CBoxMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CMeshReaderBase(val_config, val_iZone, val_nZone) { /* The box mesh is always 3D. */ dimension = 3; @@ -74,7 +74,7 @@ void CBoxMeshReaderFVM::ComputeBoxPointCoordinates() { /* Determine number of local points */ for (unsigned long globalIndex = 0; globalIndex < numberOfGlobalPoints; globalIndex++) { - if ((int)pointPartitioner.GetRankContainingIndex(globalIndex) == rank) { + if (static_cast(pointPartitioner.GetRankContainingIndex(globalIndex)) == rank) { numberOfLocalPoints++; } } @@ -88,7 +88,7 @@ void CBoxMeshReaderFVM::ComputeBoxPointCoordinates() { for (unsigned long kNode = 0; kNode < pNode; kNode++) { for (unsigned long jNode = 0; jNode < mNode; jNode++) { for (unsigned long iNode = 0; iNode < nNode; iNode++) { - if ((int)pointPartitioner.GetRankContainingIndex(globalIndex) == rank) { + if (static_cast(pointPartitioner.GetRankContainingIndex(globalIndex)) == rank) { /* Store the coordinates more clearly. */ const passivedouble x = SU2_TYPE::GetValue(Lx * ((su2double)iNode) / ((su2double)(nNode - 1)) + Ox); const passivedouble y = SU2_TYPE::GetValue(Ly * ((su2double)jNode) / ((su2double)(mNode - 1)) + Oy); @@ -133,7 +133,7 @@ void CBoxMeshReaderFVM::ComputeBoxVolumeConnectivity() { /* Check whether any of the points is in our linear partition. */ bool isOwned = false; for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { - if ((int)pointPartitioner.GetRankContainingIndex(connectivity[i]) == rank) { + if (static_cast(pointPartitioner.GetRankContainingIndex(connectivity[i])) == rank) { isOwned = true; } } diff --git a/Common/src/fem/fem_cgns_elements.cpp b/Common/src/geometry/meshreader/CCGNSElementType.cpp similarity index 68% rename from Common/src/fem/fem_cgns_elements.cpp rename to Common/src/geometry/meshreader/CCGNSElementType.cpp index e4df7f0393b..53a549c8c78 100644 --- a/Common/src/fem/fem_cgns_elements.cpp +++ b/Common/src/geometry/meshreader/CCGNSElementType.cpp @@ -1,6 +1,6 @@ /*! - * \file fem_cgns_elements.cpp - * \brief CGNS element definitions and conversions to the SU2 standard. + * \file CCGNSElementType.cpp + * \brief Class that converts the CGNS element definitions to the SU2 standard. * \author E. van der Weide * \version 8.1.0 "Harrier" * @@ -26,9 +26,8 @@ */ #ifdef HAVE_CGNS -#include "../../include/fem/fem_cgns_elements.hpp" -#include "../../include/fem/geometry_structure_fem_part.hpp" -#include "../../include/parallelization/mpi_structure.hpp" +#include "../../../include/geometry/meshreader/CCGNSElementType.hpp" +#include "../../../include/option_structure.hpp" #include #include @@ -36,434 +35,139 @@ using namespace std; -#if CGNS_VERSION >= 3300 - -void CCGNSElementType::DetermineMetaData(const unsigned short nDim, const int fn, const int iBase, const int iZone, - const int iConn) { - /* Store the connectivity ID. */ - connID = iConn; - - /* Read the element type and range from the CGNS file. */ - char cgnsname[CGNS_STRING_SIZE]; - int nBndry, parentFlag; - if (cg_section_read(fn, iBase, iZone, iConn, cgnsname, &elemType, &indBeg, &indEnd, &nBndry, &parentFlag) != CG_OK) - cg_error_exit(); - - /* Store the name of this connectivity. */ - connName = cgnsname; - - /* Determine the number of elements present in this connectivity section. */ - nElem = indEnd - indBeg + 1; - - /* Determine the dimension of this element type, i.e. the number of - parametric coordinates and conclude whether or not this is a volume or - surface connectivity. Note that it is possible that it is neither of these, - e.g. line elements for 3D. This information is not needed for the DG - flow solver and is therefore ignored. */ - const unsigned short nDimElem = DetermineElementDimension(fn, iBase, iZone); - volumeConn = (nDimElem == nDim); - surfaceConn = (nDimElem == nDim - 1); -} - -void CCGNSElementType::ReadBoundaryConnectivityRange(const int fn, const int iBase, const int iZone, - const unsigned long offsetRank, const unsigned long nBoundElemRank, - const unsigned long startingBoundElemIDRank, - unsigned long& locBoundElemCount, - vector& boundElems) { - /* Determine the index range to be read for this rank. */ - const cgsize_t iBeg = indBeg + offsetRank; - const cgsize_t iEnd = iBeg + nBoundElemRank - 1; - - /* Determine the size of the vector needed to read the connectivity - data from the CGNS file. */ - cgsize_t sizeNeeded; - if (cg_ElementPartialSize(fn, iBase, iZone, connID, iBeg, iEnd, &sizeNeeded) != CG_OK) cg_error_exit(); - - /* Allocate the memory for the connectivity and read the data. */ - vector connCGNSVec(sizeNeeded); - if (elemType == MIXED) { - vector connCGNSOffsetVec(iEnd - iBeg + 2); - if (cg_poly_elements_partial_read(fn, iBase, iZone, connID, iBeg, iEnd, connCGNSVec.data(), - connCGNSOffsetVec.data(), nullptr) != CG_OK) - cg_error_exit(); - } else { - if (cg_elements_partial_read(fn, iBase, iZone, connID, iBeg, iEnd, connCGNSVec.data(), nullptr) != CG_OK) - cg_error_exit(); - } - - /* Define the variables needed to convert the connectivities from CGNS to - SU2 format. Note that the vectors are needed to support a connectivity - section with mixed element types. */ - vector CGNS_Type; - vector VTK_Type; - vector nPoly; - vector nDOFs; - - vector > SU2ToCGNS; - - /* Definition of variables used in the loop below. */ - cgsize_t* connCGNS = connCGNSVec.data(); - ElementType_t typeElem = elemType; - vector connSU2; - - /* Loop over the elements just read. */ - for (unsigned long i = 0; i < nBoundElemRank; ++i, ++locBoundElemCount) { - /* Determine the element type for this element if this is a mixed - connectivity and set the pointer to the actual connectivity data. */ - if (elemType == MIXED) { - typeElem = (ElementType_t)connCGNS[0]; - ++connCGNS; +void CCGNSElementType::CGNSToSU2(const ElementType_t val_elemType, const unsigned long val_globalID, + const cgsize_t* connCGNS, std::vector& connSU2) { + /*--- Clear the contents of connSU2. ---*/ + connSU2.clear(); + + /*--- Search in the stored elements if the current element type is present. ---*/ + unsigned long ind; + for (ind = 0; ind < CGNSTypeStored.size(); ++ind) + if (val_elemType == CGNSTypeStored[ind]) break; + + /*--- If the element type is not present in the stored element types, + create the data. ---*/ + if (ind == CGNSTypeStored.size()) { + /*--- Call the appropriate routine to determine the actual data. ---*/ + unsigned short VTK_Type, nPoly, nDOFs; + vector SU2ToCGNS; + + switch (val_elemType) { + case NODE: + CreateDataNODE(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case BAR_2: + CreateDataBAR_2(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case BAR_3: + CreateDataBAR_3(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case BAR_4: + CreateDataBAR_4(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case BAR_5: + CreateDataBAR_5(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TRI_3: + CreateDataTRI_3(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TRI_6: + CreateDataTRI_6(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TRI_10: + CreateDataTRI_10(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TRI_15: + CreateDataTRI_15(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case QUAD_4: + CreateDataQUAD_4(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case QUAD_9: + CreateDataQUAD_9(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case QUAD_16: + CreateDataQUAD_16(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case QUAD_25: + CreateDataQUAD_25(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TETRA_4: + CreateDataTETRA_4(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TETRA_10: + CreateDataTETRA_10(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TETRA_20: + CreateDataTETRA_20(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case TETRA_35: + CreateDataTETRA_35(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PYRA_5: + CreateDataPYRA_5(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PYRA_14: + CreateDataPYRA_14(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PYRA_30: + CreateDataPYRA_30(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PYRA_55: + CreateDataPYRA_55(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PENTA_6: + CreateDataPENTA_6(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PENTA_18: + CreateDataPENTA_18(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PENTA_40: + CreateDataPENTA_40(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case PENTA_75: + CreateDataPENTA_75(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case HEXA_8: + CreateDataHEXA_8(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case HEXA_27: + CreateDataHEXA_27(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case HEXA_64: + CreateDataHEXA_64(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + case HEXA_125: + CreateDataHEXA_125(VTK_Type, nPoly, nDOFs, SU2ToCGNS); + break; + + default: + ostringstream message; + message << "CGNS element type " << val_elemType << " not supported."; + SU2_MPI::Error(message.str(), CURRENT_FUNCTION); } - /* Determine the index in the stored vectors (CGNS_Type, VTK_Type, etc), - which corresponds to this element. If the type is not stored yet, - a new entry in these vectors will be created. */ - const unsigned short ind = IndexInStoredTypes(typeElem, CGNS_Type, VTK_Type, nPoly, nDOFs, SU2ToCGNS); - - /* Resize the connSU2 vector to the appropriate size. */ - connSU2.resize(nDOFs[ind]); - - /* Create the connectivity used in SU2 by carrying out the renumbering. - Note that in CGNS the numbering starts at 1, while in SU2 it starts - at 0. This explains the addition of -1. */ - for (unsigned short j = 0; j < nDOFs[ind]; ++j) connSU2[j] = connCGNS[SU2ToCGNS[ind][j]] - 1; - - /* Set the pointer for connCGNS for the data of the next element. */ - connCGNS += nDOFs[ind]; - - /* Determine the global boundary element ID of this element. */ - const unsigned long globBoundElemID = startingBoundElemIDRank + locBoundElemCount; - - /* Create an object of CBoundaryFace and store it in boundElems. - Note that the corresponding domain element is not known yet and - is therefore set to ULONG_MAX. */ - CBoundaryFace thisBoundFace; - thisBoundFace.VTK_Type = VTK_Type[ind]; - thisBoundFace.nPolyGrid = nPoly[ind]; - thisBoundFace.nDOFsGrid = nDOFs[ind]; - thisBoundFace.globalBoundElemID = globBoundElemID; - thisBoundFace.domainElementID = ULONG_MAX; - thisBoundFace.Nodes = connSU2; - - boundElems.push_back(thisBoundFace); - } -} - -void CCGNSElementType::ReadConnectivityRange(const int fn, const int iBase, const int iZone, - const unsigned long offsetRank, const unsigned long nElemRank, - const unsigned long startingElemIDRank, CPrimalGrid**& elem, - unsigned long& locElemCount, unsigned long& nDOFsLoc) { - /* Determine the index range to be read for this rank. */ - const cgsize_t iBeg = indBeg + offsetRank; - const cgsize_t iEnd = iBeg + nElemRank - 1; - - /* Determine the size of the vector needed to read the connectivity - data from the CGNS file. */ - cgsize_t sizeNeeded; - if (cg_ElementPartialSize(fn, iBase, iZone, connID, iBeg, iEnd, &sizeNeeded) != CG_OK) cg_error_exit(); - - /* Allocate the memory for the connectivity and read the data. */ - vector connCGNSVec(sizeNeeded); - if (elemType == MIXED) { - vector connCGNSOffsetVec(iEnd - iBeg + 2); - if (cg_poly_elements_partial_read(fn, iBase, iZone, connID, iBeg, iEnd, connCGNSVec.data(), - connCGNSOffsetVec.data(), nullptr) != CG_OK) - cg_error_exit(); - - } else { - if (cg_elements_partial_read(fn, iBase, iZone, connID, iBeg, iEnd, connCGNSVec.data(), nullptr) != CG_OK) - cg_error_exit(); + /*--- Store the data just created at the end of the corresponding vectors. ---*/ + CGNSTypeStored.push_back(val_elemType); + VTKTypeStored.push_back(VTK_Type); + nPolyStored.push_back(nPoly); + nDOFsStored.push_back(nDOFs); + SU2ToCGNSStored.push_back(SU2ToCGNS); } - /* Define the variables needed to convert the connectivities from CGNS to - SU2 format. Note that the vectors are needed to support a connectivity - section with mixed element types. */ - vector CGNS_Type; - vector VTK_Type; - vector nPoly; - vector nDOFs; - - vector > SU2ToCGNS; - - /* Definition of variables used in the loop below. */ - cgsize_t* connCGNS = connCGNSVec.data(); - ElementType_t typeElem = elemType; - vector connSU2; - - /* Loop over the elements just read. */ - for (unsigned long i = 0; i < nElemRank; ++i, ++locElemCount) { - /* Determine the element type for this element if this is a mixed - connectivity and set the pointer to the actual connectivity data. */ - if (elemType == MIXED) { - typeElem = (ElementType_t)connCGNS[0]; - ++connCGNS; - } + /*--- Allocate the memory for connSU2 and store the meta data. ---*/ + connSU2.resize(nDOFsStored[ind] + 5); - /* Determine the index in the stored vectors (CGNS_Type, VTK_Type, etc), - which corresponds to this element. If the type is not stored yet, - a new entry in these vectors will be created. */ - const unsigned short ind = IndexInStoredTypes(typeElem, CGNS_Type, VTK_Type, nPoly, nDOFs, SU2ToCGNS); + connSU2[0] = VTKTypeStored[ind]; + connSU2[1] = nPolyStored[ind]; + connSU2[2] = nPolyStored[ind]; + connSU2[3] = nDOFsStored[ind]; + connSU2[4] = val_globalID; - /* Resize the connSU2 vector to the appropriate size. */ - connSU2.resize(nDOFs[ind]); - - /* Create the connectivity used in SU2 by carrying out the renumbering. - Note that in CGNS the numbering starts at 1, while in SU2 it starts - at 0. This explains the addition of -1. */ - for (unsigned short j = 0; j < nDOFs[ind]; ++j) connSU2[j] = connCGNS[SU2ToCGNS[ind][j]] - 1; - - /* Set the pointer for connCGNS for the data of the next element. */ - connCGNS += nDOFs[ind]; - - /* Determine the global element ID of this element. */ - const unsigned long globElemID = startingElemIDRank + locElemCount; - - /* Create a new FEM primal grid element, which stores the required - information. Note that the second to last argument contains the local - offset of the DOFs. This will be corrected to the global offset - afterwards in CPhysicalGeometry::Read_CGNS_Format_Parallel_FEM. - Also note that in CGNS it is not possible (at least at the moment) - to specify a different polynomial degree for the grid and solution. */ - elem[locElemCount] = new CPrimalGridFEM(globElemID, VTK_Type[ind], nPoly[ind], nPoly[ind], nDOFs[ind], nDOFs[ind], - nDOFsLoc, connSU2.data()); - - /* Update the counter nDOFsLoc. */ - nDOFsLoc += nDOFs[ind]; - } -} - -unsigned short CCGNSElementType::DetermineElementDimension(const int fn, const int iBase, const int iZone) { - /*--- Determine the element type and return the appropriate element - dimension, i.e. the number of parametric coordinates needed to - describe the element. ---*/ - switch (elemType) { - case NODE: - return 0; - - case BAR_2: - case BAR_3: - case BAR_4: - case BAR_5: - return 1; - - case TRI_3: - case TRI_6: - case TRI_9: - case TRI_10: - case TRI_12: - case TRI_15: - case QUAD_4: - case QUAD_8: - case QUAD_9: - case QUAD_12: - case QUAD_16: - case QUAD_P4_16: - case QUAD_25: - return 2; - - case TETRA_4: - case TETRA_10: - case TETRA_16: - case TETRA_20: - case TETRA_22: - case TETRA_34: - case TETRA_35: - case PYRA_5: - case PYRA_14: - case PYRA_13: - case PYRA_21: - case PYRA_29: - case PYRA_30: - case PYRA_P4_29: - case PYRA_50: - case PYRA_55: - case PENTA_6: - case PENTA_15: - case PENTA_18: - case PENTA_24: - case PENTA_38: - case PENTA_40: - case PENTA_33: - case PENTA_66: - case PENTA_75: - case HEXA_8: - case HEXA_20: - case HEXA_27: - case HEXA_32: - case HEXA_56: - case HEXA_64: - case HEXA_44: - case HEXA_98: - case HEXA_125: - return 3; - - case MIXED: - return DetermineElementDimensionMixed(fn, iBase, iZone); - - default: - ostringstream message; - message << "Unsupported CGNS element type, " << elemType << ", encountered."; - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } - - /* Just return a value to avoid a compiler warning. The program should - never reach this position. */ - return 0; -} - -unsigned short CCGNSElementType::DetermineElementDimensionMixed(const int fn, const int iBase, const int iZone) { - /* Determine the required size of the vector to read the - data of the first element of this connectivity. */ - cgsize_t sizeNeeded; - if (cg_ElementPartialSize(fn, iBase, iZone, connID, indBeg, indBeg, &sizeNeeded) != CG_OK) cg_error_exit(); - - /* Read the data of the first element in this section. */ - vector buf(sizeNeeded); - vector buf_offset(2, 0); - if (cg_poly_elements_partial_read(fn, iBase, iZone, connID, indBeg, indBeg, buf.data(), buf_offset.data(), nullptr) != - CG_OK) - cg_error_exit(); - - /* The first entry of buf contains the element type. Copy this value - temporarily into the member variable elemType and determine the - element dimension of this element. */ - elemType = (ElementType_t)buf[0]; - - unsigned short nDimElem = DetermineElementDimension(fn, iBase, iZone); - - /* Reset the element type to MIXED and return the element dimension. */ - elemType = MIXED; - return nDimElem; -} - -unsigned short CCGNSElementType::IndexInStoredTypes(const ElementType_t typeElem, vector& CGNS_Type, - vector& VTK_Type, vector& nPoly, - vector& nDOFs, - vector >& SU2ToCGNS) { - /* Loop over the available types and check if the current type is present. - If so, break the loop, such that the correct index is stored. */ - unsigned short ind; - for (ind = 0; ind < CGNS_Type.size(); ++ind) - if (typeElem == CGNS_Type[ind]) break; - - /* If the current element type is not present yet, the data for this - type must be created. */ - if (ind == CGNS_Type.size()) { - unsigned short VTKElem, nPolyElem, nDOFsElem; - vector SU2ToCGNSElem; - CreateDataElementType(typeElem, VTKElem, nPolyElem, nDOFsElem, SU2ToCGNSElem); - - CGNS_Type.push_back(typeElem); - VTK_Type.push_back(VTKElem); - nPoly.push_back(nPolyElem); - nDOFs.push_back(nDOFsElem); - SU2ToCGNS.push_back(SU2ToCGNSElem); - } - - /* Return the index. */ - return ind; -} - -void CCGNSElementType::CreateDataElementType(const ElementType_t typeElem, unsigned short& VTK_Type, - unsigned short& nPoly, unsigned short& nDOFs, - vector& SU2ToCGNS) { - /*--- Determine the element type and call the corresponding function - to determine the actual data. ---*/ - switch (typeElem) { - case NODE: - CreateDataNODE(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case BAR_2: - CreateDataBAR_2(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case BAR_3: - CreateDataBAR_3(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case BAR_4: - CreateDataBAR_4(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case BAR_5: - CreateDataBAR_5(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TRI_3: - CreateDataTRI_3(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TRI_6: - CreateDataTRI_6(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TRI_10: - CreateDataTRI_10(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TRI_15: - CreateDataTRI_15(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case QUAD_4: - CreateDataQUAD_4(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case QUAD_9: - CreateDataQUAD_9(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case QUAD_16: - CreateDataQUAD_16(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case QUAD_25: - CreateDataQUAD_25(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TETRA_4: - CreateDataTETRA_4(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TETRA_10: - CreateDataTETRA_10(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TETRA_20: - CreateDataTETRA_20(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case TETRA_35: - CreateDataTETRA_35(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PYRA_5: - CreateDataPYRA_5(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PYRA_14: - CreateDataPYRA_14(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PYRA_30: - CreateDataPYRA_30(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PYRA_55: - CreateDataPYRA_55(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PENTA_6: - CreateDataPENTA_6(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PENTA_18: - CreateDataPENTA_18(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PENTA_40: - CreateDataPENTA_40(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case PENTA_75: - CreateDataPENTA_75(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case HEXA_8: - CreateDataHEXA_8(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case HEXA_27: - CreateDataHEXA_27(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case HEXA_64: - CreateDataHEXA_64(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - case HEXA_125: - CreateDataHEXA_125(VTK_Type, nPoly, nDOFs, SU2ToCGNS); - break; - - default: - /* Print an error message that this element type is not supported and exit. */ - ostringstream message; - message << "CGNS element type " << typeElem << " not supported."; - SU2_MPI::Error(message.str(), CURRENT_FUNCTION); - } + /*--- Loop over the DOFs and convert the connectivity from CGNS to SU2 + format. Keep in mind that CGNS start the numbering at 1 while + SU2 starts at 0. ---*/ + for (unsigned short i = 0; i < nDOFsStored[ind]; ++i) connSU2[i + 5] = connCGNS[SU2ToCGNSStored[ind][i]] - 1; } /*------------------------------------------------------------------------*/ @@ -976,9 +680,9 @@ void CCGNSElementType::CreateDataPYRA_30(unsigned short& VTK_Type, unsigned shor SU2ToCGNS[10] = 23; SU2ToCGNS[11] = 8; SU2ToCGNS[12] = 3; - SU2ToCGNS[13] = 20; + SU2ToCGNS[13] = 10; SU2ToCGNS[14] = 9; - SU2ToCGNS[15] = 3; + SU2ToCGNS[15] = 2; SU2ToCGNS[16] = 13; SU2ToCGNS[17] = 25; SU2ToCGNS[18] = 15; @@ -1575,4 +1279,3 @@ void CCGNSElementType::CreateDataHEXA_125(unsigned short& VTK_Type, unsigned sho } #endif -#endif diff --git a/Common/src/geometry/meshreader/CCGNSMeshReaderBase.cpp b/Common/src/geometry/meshreader/CCGNSMeshReaderBase.cpp new file mode 100644 index 00000000000..042ef3f9111 --- /dev/null +++ b/Common/src/geometry/meshreader/CCGNSMeshReaderBase.cpp @@ -0,0 +1,443 @@ +/*! + * \file CCGNSMeshReaderBase.cpp + * \brief Helper class for the reading of a CGNS grid file. + * linear partitions across all ranks. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CCGNSMeshReaderBase.hpp" + +CCGNSMeshReaderBase::CCGNSMeshReaderBase(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CMeshReaderBase(val_config, val_iZone, val_nZone) { +#ifdef HAVE_CGNS + /*--- Leave the option to do something in the future here. ---*/ +#else + SU2_MPI::Error(string(" SU2 built without CGNS support. \n") + string(" To use CGNS, build SU2 accordingly."), + CURRENT_FUNCTION); +#endif +} + +CCGNSMeshReaderBase::~CCGNSMeshReaderBase() = default; + +#ifdef HAVE_CGNS +void CCGNSMeshReaderBase::OpenCGNSFile(const string& val_filename) { + /*--- Check whether the supplied file is truly a CGNS file. ---*/ + + int file_type; + float file_version; + if (cg_is_cgns(val_filename.c_str(), &file_type) != CG_OK) { + SU2_MPI::Error(val_filename + string(" was not found or is not a properly formatted") + + string(" CGNS file.\nNote that SU2 expects unstructured") + + string(" CGNS files in ADF data format."), + CURRENT_FUNCTION); + } + + /*--- Open the CGNS file for reading. The value of cgnsFileID returned + is the specific index number for this file and will be + repeatedly used in the function calls. ---*/ + + if (cg_open(val_filename.c_str(), CG_MODE_READ, &cgnsFileID)) cg_error_exit(); + if (rank == MASTER_NODE) { + cout << "Reading the CGNS file: "; + cout << val_filename.c_str() << "." << endl; + } + if (cg_version(cgnsFileID, &file_version)) cg_error_exit(); + if (rank == MASTER_NODE) { + if (file_version < 4.0) { + cout + << "WARNING: The CGNS file version (" << file_version + << ") is old and may cause high memory usage issues, consider updating the file with the cgnsupdate tool.\n"; + } + } +} + +void CCGNSMeshReaderBase::ReadCGNSDatabaseMetadata() { + /*--- Get the number of databases. This is the highest node + in the CGNS heirarchy. ---*/ + + int nbases; + if (cg_nbases(cgnsFileID, &nbases)) cg_error_exit(); + if (rank == MASTER_NODE) cout << "CGNS file contains " << nbases << " database(s)." << endl; + + /*--- Check if there is more than one database. Throw an + error if there is because this reader can currently + only handle one database. ---*/ + + if (nbases > 1) { + SU2_MPI::Error("CGNS reader currently can only handle 1 database.", CURRENT_FUNCTION); + } + + /*--- Read the database. Note that the CGNS indexing starts at 1. ---*/ + + int cell_dim, phys_dim; + char basename[CGNS_STRING_SIZE]; + if (cg_base_read(cgnsFileID, cgnsBase, basename, &cell_dim, &phys_dim)) cg_error_exit(); + if (rank == MASTER_NODE) { + cout << "Database " << cgnsBase << ", " << basename << ": "; + cout << " cell dimension of " << cell_dim << ", physical "; + cout << "dimension of " << phys_dim << "." << endl; + } + + /*--- Set the number of dimensions baed on cell_dim. ---*/ + + dimension = static_cast(cell_dim); +} + +void CCGNSMeshReaderBase::ReadCGNSZoneMetadata() { + /*--- First, check all sections to find the element types and to + classify them as either surface or volume elements. We will also + perform some error checks here to avoid partitioning issues. ---*/ + + /*--- Get the number of zones for this base. ---*/ + + int nzones; + if (cg_nzones(cgnsFileID, cgnsBase, &nzones)) cg_error_exit(); + if (rank == MASTER_NODE) { + cout << nzones << " total zone(s)." << endl; + } + + /*--- Check if there is more than one zone. Until we enable it, we + will require a single zone CGNS file. Multizone problems can still + be run with CGNS by using separate CGNS files for each zone. ---*/ + + if (nzones > 1) { + SU2_MPI::Error(string("CGNS reader currently expects only 1 zone per CGNS file.") + + string("Multizone problems can be run with separate CGNS files for each zone."), + CURRENT_FUNCTION); + } + + /*--- Read the basic information for this zone, including + the name and the number of vertices, cells, and + boundary cells which are stored in the cgsize variable. ---*/ + + vector cgsize(3); + ZoneType_t zonetype; + char zonename[CGNS_STRING_SIZE]; + if (cg_zone_read(cgnsFileID, cgnsBase, cgnsZone, zonename, cgsize.data())) cg_error_exit(); + + /*--- Rename the zone size information for clarity. + NOTE: The number of cells here may be only the number of + interior elements or it may be the total. This needs to + be counted explicitly later. ---*/ + + numberOfGlobalPoints = cgsize[0]; + int nElemCGNS = cgsize[1]; + + /*--- Get some additional information about the current zone. ---*/ + + if (cg_zone_type(cgnsFileID, cgnsBase, cgnsZone, &zonetype)) cg_error_exit(); + + /*--- Check for an unstructured mesh. Throw an error if not found. ---*/ + + if (zonetype != Unstructured) + SU2_MPI::Error("Structured CGNS zone found while unstructured expected.", CURRENT_FUNCTION); + + /*--- Print current zone info to the console. ---*/ + + if (rank == MASTER_NODE) { + cout << "Zone " << cgnsZone << ", " << zonename << ": "; + cout << numberOfGlobalPoints << " total vertices, "; + cout << nElemCGNS << " total elements." << endl; + } + + /*--- Retrieve the number of grids in this zone. For now, we know + this is one, but to be more general, this will need to check and + allow for a loop over all grids. ---*/ + + int ngrids; + if (cg_ngrids(cgnsFileID, cgnsBase, cgnsZone, &ngrids)) cg_error_exit(); + if (ngrids > 1) { + SU2_MPI::Error("CGNS reader currently handles only 1 grid per zone.", CURRENT_FUNCTION); + } +} + +void CCGNSMeshReaderBase::ReadCGNSPointCoordinates() { + /*--- Compute the number of points that will be on each processor. + This is a linear partitioning with the addition of a simple load + balancing for any remainder points. ---*/ + + CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); + + /*--- Store the local number of nodes for this rank. ---*/ + + numberOfLocalPoints = pointPartitioner.GetSizeOnRank(rank); + + /*--- Create buffer to hold the grid coordinates for our rank. ---*/ + + localPointCoordinates.resize(dimension); + for (int k = 0; k < dimension; k++) localPointCoordinates[k].resize(numberOfLocalPoints, 0.0); + + /*--- Set the value of range_max to the total number of nodes in + the unstructured mesh. Also allocate memory for the temporary array + that will hold the grid coordinates as they are extracted. Note the + +1 for CGNS convention. ---*/ + + cgsize_t range_min = (cgsize_t)pointPartitioner.GetFirstIndexOnRank(rank) + 1; + auto range_max = (cgsize_t)pointPartitioner.GetLastIndexOnRank(rank); + + /*--- Loop over each set of coordinates. ---*/ + + for (int k = 0; k < dimension; k++) { + /*--- Read the coordinate info. This will retrieve the + data type (either RealSingle or RealDouble) as + well as the coordname which will specify the + type of data that it is based in the SIDS convention. + This might be "CoordinateX," for instance. ---*/ + + char coordname[CGNS_STRING_SIZE]; + DataType_t datatype; + if (cg_coord_info(cgnsFileID, cgnsBase, cgnsZone, k + 1, &datatype, coordname)) cg_error_exit(); + if (rank == MASTER_NODE) { + cout << "Loading " << coordname; + if (size > SINGLE_NODE) { + cout << " values into linear partitions." << endl; + } else { + cout << " values." << endl; + } + } + + /*--- Check the coordinate name to decide the index for storage. ---*/ + + unsigned short indC = 0; + if (string(coordname) == "CoordinateX") + indC = 0; + else if (string(coordname) == "CoordinateY") + indC = 1; + else if (string(coordname) == "CoordinateZ") + indC = 2; + else + SU2_MPI::Error(string("Unknown coordinate name, ") + coordname + string(", in the CGNS file."), CURRENT_FUNCTION); + + /*--- Now read our rank's chunk of coordinates from the file. + Ask for datatype RealDouble and let CGNS library do the translation + when RealSingle is found. ---*/ + + if (cg_coord_read(cgnsFileID, cgnsBase, cgnsZone, coordname, RealDouble, &range_min, &range_max, + localPointCoordinates[indC].data())) + cg_error_exit(); + } +} + +void CCGNSMeshReaderBase::ReadCGNSSectionMetadata() { + /*--- Begin section for retrieving the connectivity info. ---*/ + + if ((rank == MASTER_NODE) && (size > SINGLE_NODE)) cout << "Distributing connectivity across all ranks." << endl; + + /*--- First check the number of sections. ---*/ + + if (cg_nsections(cgnsFileID, cgnsBase, cgnsZone, &nSections)) cg_error_exit(); + if (rank == MASTER_NODE) { + cout << "Number of connectivity sections is "; + cout << nSections << "." << endl; + } + + /*--- Prepare several data structures to hold the various + pieces of information describing each section. ---*/ + + isInterior.resize(nSections); + nElems.resize(nSections, 0); + elemOffset.resize(nSections + 1, 0); + elemOffset[0] = 0; + connElems.resize(nSections); + sectionNames.resize(nSections, vector(CGNS_STRING_SIZE)); + numberOfGlobalElements = 0; + + for (int s = 0; s < nSections; s++) { + /*--- Read the connectivity details for this section. ---*/ + + int nbndry, parent_flag, vtk_type; + cgsize_t startE, endE, sizeNeeded; + ElementType_t elemType; + if (cg_section_read(cgnsFileID, cgnsBase, cgnsZone, s + 1, sectionNames[s].data(), &elemType, &startE, &endE, + &nbndry, &parent_flag)) + cg_error_exit(); + + /*--- Compute the total element count in this section (global). ---*/ + + unsigned long element_count = (endE - startE + 1); + + /* Get the details for the CGNS element type in this section. */ + + string elem_name = GetCGNSElementType(elemType, vtk_type); + + /* We assume that each section contains interior elements by default. + If we find 1D elements in a 2D problem or 2D elements in a 3D + problem, then we know the section must contain boundary elements. + We assume that each section is composed of either entirely interior + or entirely boundary elements. */ + + isInterior[s] = true; + + if (elemType == MIXED) { + /* For a mixed section, we check the type of the first element + so that we can correctly label this section as an interior or + boundary element section. Here, we also assume that a section + can not hold both interior and boundary elements. First, get + the size required to read a single element from the section. */ + + if (cg_ElementPartialSize(cgnsFileID, cgnsBase, cgnsZone, s + 1, startE, startE, &sizeNeeded) != CG_OK) + cg_error_exit(); + + /* A couple of auxiliary vectors for mixed element sections. */ + + vector connElemCGNS(sizeNeeded); + vector connOffsetCGNS(2, 0); + + /* Retrieve the connectivity information for the first element. */ + + if (cg_poly_elements_partial_read(cgnsFileID, cgnsBase, cgnsZone, s + 1, startE, startE, connElemCGNS.data(), + connOffsetCGNS.data(), nullptr) != CG_OK) + cg_error_exit(); + + /* The element type is in the first position of the connectivity + information that we retrieved from the CGNS file. */ + + elemType = ElementType_t(connElemCGNS[0]); + } + + /* Check for 1D elements in 2D problems, or for 2D elements in + 3D problems. If found, mark the section as a boundary section. */ + + if ((dimension == 2) && (elemType == BAR_2 || elemType == BAR_3)) isInterior[s] = false; + if ((dimension == 3) && (elemType == TRI_3 || elemType == QUAD_4)) isInterior[s] = false; + + /*--- Increment the global element offset for each section + based on whether or not this is a surface or volume section. + We also keep a running count of the total elements globally. ---*/ + + elemOffset[s + 1] = elemOffset[s]; + if (!isInterior[s]) + elemOffset[s + 1] += element_count; + else + numberOfGlobalElements += element_count; + + /*--- Print some information to the console. ---*/ + + if (rank == MASTER_NODE) { + cout << "Section " << string(sectionNames[s].data()); + cout << " contains " << element_count << " elements"; + cout << " of type " << elem_name << "." << endl; + } + } +} + +string CCGNSMeshReaderBase::GetCGNSElementType(ElementType_t val_elem_type, int& val_vtk_type) { + /* Check the CGNS element type and return the string name + for the element and the associated VTK type index. */ + + string elem_name; + switch (val_elem_type) { + case NODE: + elem_name = "Vertex"; + val_vtk_type = 1; + SU2_MPI::Error("Vertex elements detected. Please remove.", CURRENT_FUNCTION); + break; + case BAR_2: + case BAR_3: + case BAR_4: + case BAR_5: + elem_name = "Line"; + val_vtk_type = 3; + if (dimension == 3) SU2_MPI::Error("Line elements detected in a 3D mesh. Please remove.", CURRENT_FUNCTION); + break; + case TRI_3: + case TRI_6: + case TRI_9: + case TRI_10: + case TRI_12: + case TRI_15: + elem_name = "Triangle"; + val_vtk_type = 5; + break; + case QUAD_4: + case QUAD_8: + case QUAD_9: + case QUAD_12: + case QUAD_16: + case QUAD_P4_16: + case QUAD_25: + elem_name = "Quadrilateral"; + val_vtk_type = 9; + break; + case TETRA_4: + case TETRA_10: + case TETRA_16: + case TETRA_20: + case TETRA_22: + case TETRA_34: + case TETRA_35: + elem_name = "Tetrahedron"; + val_vtk_type = 10; + break; + case HEXA_8: + case HEXA_20: + case HEXA_27: + case HEXA_32: + case HEXA_56: + case HEXA_64: + case HEXA_44: + case HEXA_98: + case HEXA_125: + elem_name = "Hexahedron"; + val_vtk_type = 12; + break; + case PENTA_6: + case PENTA_15: + case PENTA_18: + case PENTA_24: + case PENTA_38: + case PENTA_40: + case PENTA_33: + case PENTA_66: + case PENTA_75: + elem_name = "Prism"; + val_vtk_type = 13; + break; + case PYRA_5: + case PYRA_14: + case PYRA_13: + case PYRA_21: + case PYRA_29: + case PYRA_30: + case PYRA_P4_29: + case PYRA_50: + case PYRA_55: + elem_name = "Pyramid"; + val_vtk_type = 14; + break; + case MIXED: + elem_name = "Mixed"; + val_vtk_type = -1; + break; + default: + char buf[100]; + SPRINTF(buf, "Unsupported or unknown CGNS element type: (type %d)\n", val_elem_type); + SU2_MPI::Error(string(buf), CURRENT_FUNCTION); + break; + } + + return elem_name; +} +#endif diff --git a/Common/src/geometry/meshreader/CCGNSMeshReaderFEM.cpp b/Common/src/geometry/meshreader/CCGNSMeshReaderFEM.cpp new file mode 100644 index 00000000000..32cb790291c --- /dev/null +++ b/Common/src/geometry/meshreader/CCGNSMeshReaderFEM.cpp @@ -0,0 +1,516 @@ +/*! + * \file CCGNSMeshReaderFEM.cpp + * \brief Class that reads a single zone of a CGNS mesh file from disk into + * linear partitions across all ranks. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CCGNSMeshReaderFEM.hpp" +#include "../../../include/geometry/meshreader/CCGNSElementType.hpp" + +CCGNSMeshReaderFEM::CCGNSMeshReaderFEM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CCGNSMeshReaderBase(val_config, val_iZone, val_nZone) { +#ifdef HAVE_CGNS + OpenCGNSFile(config->GetMesh_FileName()); + + /*--- Read the basic information about the database and zone(s). ---*/ + ReadCGNSDatabaseMetadata(); + ReadCGNSZoneMetadata(); + + /*--- Read the volume connectivity and distribute it + linearly over the MPI ranks. ---*/ + ReadCGNSVolumeElementConnectivity(); + + /*--- Read the coordinates of the points and communicate the ones that + are needed on this MPI rank. ---*/ + ReadCGNSPointCoordinates(); + CommPointCoordinates(); + + /*--- Read the surface connectivity and store the surface elements whose + corresponding volume element is stored on this MPI rank. ---*/ + ReadCGNSSurfaceElementConnectivity(); + + /*--- We have extracted all CGNS data. Close the CGNS file. ---*/ + if (cg_close(cgnsFileID)) cg_error_exit(); + +#else + SU2_MPI::Error(string(" SU2 built without CGNS support. \n") + string(" To use CGNS, build SU2 accordingly."), + CURRENT_FUNCTION); +#endif +} + +CCGNSMeshReaderFEM::~CCGNSMeshReaderFEM() = default; + +#ifdef HAVE_CGNS +void CCGNSMeshReaderFEM::ReadCGNSConnectivityRangeSection(const int val_section, const unsigned long val_firstIndex, + const unsigned long val_lastIndex, unsigned long& elemCount, + unsigned long& localElemCount, + vector& localConn) { + /*--- Read the connectivity details for this section. ---*/ + int nbndry, parent_flag; + cgsize_t startE, endE; + ElementType_t elemType; + char sectionName[CGNS_STRING_SIZE]; + + if (cg_section_read(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, sectionName, &elemType, &startE, &endE, &nbndry, + &parent_flag)) + cg_error_exit(); + + /*--- Determine the number of elements in this section and update + the element counters accordingly. ---*/ + const unsigned long nElemSection = (endE - startE + 1); + const unsigned long elemCountOld = elemCount; + elemCount += nElemSection; + + /*--- Check for overlap with the element range this rank is responsible for. ---*/ + const unsigned long indBegOverlap = max(elemCountOld, val_firstIndex); + const unsigned long indEndOverlap = min(elemCount, val_lastIndex); + + if (indEndOverlap > indBegOverlap) { + /*--- This rank must read element data from this connectivity section. + Determine the offset relative to the start of this section and + the number of elements to be read by this rank. ---*/ + const unsigned long offsetRank = indBegOverlap - elemCountOld; + const unsigned long nElemRank = indEndOverlap - indBegOverlap; + nElems[val_section] = nElemRank; + + /*--- Determine the index range to be read for this rank. ---*/ + const cgsize_t iBeg = startE + offsetRank; + const cgsize_t iEnd = iBeg + nElemRank - 1; + + /*--- Determine the size of the vector needed to read + the connectivity data from the CGNS file. ---*/ + cgsize_t sizeNeeded; + if (cg_ElementPartialSize(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, iBeg, iEnd, &sizeNeeded) != CG_OK) + cg_error_exit(); + + /*--- Allocate the memory for the connectivity and read the data. ---*/ + vector connCGNSVec(sizeNeeded); + if (elemType == MIXED) { + vector connCGNSOffsetVec(iEnd - iBeg + 2); + if (cg_poly_elements_partial_read(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, iBeg, iEnd, connCGNSVec.data(), + connCGNSOffsetVec.data(), NULL) != CG_OK) + cg_error_exit(); + + } else { + if (cg_elements_partial_read(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, iBeg, iEnd, connCGNSVec.data(), + NULL) != CG_OK) + cg_error_exit(); + } + + /*--- Define the variables needed for the conversion of the CGNS + format to the SU2 internal format. ---*/ + CCGNSElementType CGNSElem; + std::vector connSU2; + ElementType_t typeElem = elemType; + const cgsize_t* connCGNS = connCGNSVec.data(); + + /*--- Loop over the elements just read. ---*/ + for (unsigned long i = 0; i < nElemRank; ++i, ++localElemCount) { + /*--- Determine the element type for this element if this is a mixed + connectivity and set the pointer to the actual connectivity data. ---*/ + if (elemType == MIXED) { + typeElem = (ElementType_t)connCGNS[0]; + ++connCGNS; + } + + /*--- Convert the CGNS connectivity to SU2 connectivity. ---*/ + const unsigned long globalID = val_firstIndex + localElemCount; + CGNSElem.CGNSToSU2(typeElem, globalID, connCGNS, connSU2); + + /*--- Update the pointer for the connectivity for the next + element and store the SU2 connectivity in localConn. ---*/ + connCGNS += connSU2[3]; + + localConn.insert(localConn.end(), connSU2.begin(), connSU2.end()); + } + } +} + +void CCGNSMeshReaderFEM::ReadCGNSVolumeElementConnectivity(void) { + /*--- Write a message that the volume elements are loaded. ---*/ + if (rank == MASTER_NODE) { + if (size > SINGLE_NODE) + cout << "Loading volume elements into linear partitions." << endl; + else + cout << "Loading volume elements." << endl; + } + + /*--- Get a partitioner to help with linear partitioning. ---*/ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /*--- Determine the index of the first and last element to be stored + on this rank and the number of local elements. ---*/ + const unsigned long firstIndex = elemPartitioner.GetFirstIndexOnRank(rank); + const unsigned long lastIndex = elemPartitioner.GetLastIndexOnRank(rank); + numberOfLocalElements = elemPartitioner.GetSizeOnRank(rank); + + /*--- Loop over the section and check for a section with volume elements. ---*/ + unsigned long elemCount = 0, localElemCount = 0; + for (int s = 0; s < nSections; ++s) { + if (isInterior[s]) { + /*--- Read the connectivity of this section and store the + data in localVolumeElementConnectivity. ---*/ + ReadCGNSConnectivityRangeSection(s, firstIndex, lastIndex, elemCount, localElemCount, + localVolumeElementConnectivity); + } + } +} + +void CCGNSMeshReaderFEM::ReadCGNSSurfaceElementConnectivity(void) { + /*--- Write a message that the surface elements are loaded. ---*/ + if (rank == MASTER_NODE) cout << "Loading surface elements." << endl; + + /*--- Determine the vector to hold the faces of the local elements. ---*/ + vector localFaces; + DetermineFacesVolumeElements(localFaces); + + /*--- Determine the number of markers. ---*/ + numberOfMarkers = 0; + for (int s = 0; s < nSections; s++) + if (!isInterior[s]) ++numberOfMarkers; + + /*--- Allocate the memory for the number of markers and local surface elements. + Also allocate the first index of surfaceElementConnectivity. ---*/ + markerNames.resize(numberOfMarkers); + numberOfLocalSurfaceElements.resize(numberOfMarkers); + surfaceElementConnectivity.resize(numberOfMarkers); + + /*--- Loop over the number of sections and check for a surface. ---*/ + int markerCount = 0; + for (int s = 0; s < nSections; ++s) { + if (!isInterior[s]) { + /*--- Create the marker name. ---*/ + string Marker_Tag = string(sectionNames[s].data()); + Marker_Tag.erase(remove(Marker_Tag.begin(), Marker_Tag.end(), ' '), Marker_Tag.end()); + markerNames[markerCount] = Marker_Tag; + + /*--- Call the function ReadCGNSSurfaceSection to carry out + the actual reading and storing of the required faces. ---*/ + ReadCGNSSurfaceSection(s, localFaces, numberOfLocalSurfaceElements[markerCount], + surfaceElementConnectivity[markerCount]); + + /*--- Update the marker counter. ---*/ + ++markerCount; + } + } +} + +void CCGNSMeshReaderFEM::ReadCGNSSurfaceSection(const int val_section, const vector& localFaces, + unsigned long& nSurfElem, vector& surfConn) { + /*--- Initialize nSurfElem to zero. ---*/ + nSurfElem = 0; + + /*--- Read the connectivity details for this section and determine the number + of elements present in this section. ---*/ + int nbndry, parent_flag; + cgsize_t startE, endE; + ElementType_t elemType; + char sectionName[CGNS_STRING_SIZE]; + + if (cg_section_read(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, sectionName, &elemType, &startE, &endE, &nbndry, + &parent_flag)) + cg_error_exit(); + + const unsigned long nElemSection = (endE - startE + 1); + + /*--- Determine the number of chunks used for the reading of the surface + elements. This is done to avoid a memory bottleneck for extremely + big cases. For reasonably sized grids this connectivity can be + read in a single call. ---*/ + unsigned long nChunks = nElemSection / localFaces.size(); + if (nChunks * localFaces.size() != nElemSection) ++nChunks; + const unsigned long nElemChunk = nElemSection / nChunks; + + /*--- Loop over the number of chunks. ---*/ + for (unsigned long iChunk = 0; iChunk < nChunks; ++iChunk) { + /*--- Determine the start and end index for this chunk. ---*/ + const cgsize_t iBeg = startE + iChunk * nElemChunk; + const cgsize_t iEnd = (iChunk == (nChunks - 1)) ? endE : iBeg + nElemChunk - 1; + + /*--- Determine the size of the vector needed to read + the connectivity data from the CGNS file. ---*/ + cgsize_t sizeNeeded; + if (cg_ElementPartialSize(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, iBeg, iEnd, &sizeNeeded) != CG_OK) + cg_error_exit(); + + /*--- Allocate the memory for the connectivity and read the data. ---*/ + vector connCGNSVec(sizeNeeded); + if (elemType == MIXED) { + vector connCGNSOffsetVec(iEnd - iBeg + 2); + if (cg_poly_elements_partial_read(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, iBeg, iEnd, connCGNSVec.data(), + connCGNSOffsetVec.data(), NULL) != CG_OK) + cg_error_exit(); + + } else { + if (cg_elements_partial_read(cgnsFileID, cgnsBase, cgnsZone, val_section + 1, iBeg, iEnd, connCGNSVec.data(), + NULL) != CG_OK) + cg_error_exit(); + } + + /*--- Define the variables needed for the conversion of the CGNS + format to the SU2 internal format. ---*/ + CCGNSElementType CGNSElem; + std::vector connSU2; + ElementType_t typeElem = elemType; + const cgsize_t* connCGNS = connCGNSVec.data(); + + /*--- Loop over the elements just read. ---*/ + for (cgsize_t i = iBeg; i <= iEnd; ++i) { + /*--- Determine the element type for this element if this is a mixed + connectivity and set the pointer to the actual connectivity data. ---*/ + if (elemType == MIXED) { + typeElem = (ElementType_t)connCGNS[0]; + ++connCGNS; + } + + /*--- Convert the CGNS connectivity to SU2 connectivity and update + the pointer for the next surface element. ---*/ + const unsigned long globalID = i - 1; + CGNSElem.CGNSToSU2(typeElem, globalID, connCGNS, connSU2); + connCGNS += connSU2[3]; + + /*--- Easier storage of the VTK type, polynomial degree + and number of DOFs of the surface element. ---*/ + const unsigned short VTK_Type = static_cast(connSU2[0]); + const unsigned short nPolyGrid = static_cast(connSU2[1]); + const unsigned short nDOFsGrid = static_cast(connSU2[3]); + + /*--- Make a distinction between the possible element surface types and + determine the corner points in local numbering of the element. ---*/ + const unsigned short nDOFEdgeGrid = nPolyGrid + 1; + + CFaceOfElement thisFace; + thisFace.cornerPoints[0] = 0; + thisFace.cornerPoints[1] = nPolyGrid; + + switch (VTK_Type) { + case LINE: + thisFace.nCornerPoints = 2; + break; + + case TRIANGLE: + thisFace.nCornerPoints = 3; + thisFace.cornerPoints[2] = nDOFsGrid - 1; + break; + + case QUADRILATERAL: + thisFace.nCornerPoints = 4; + thisFace.cornerPoints[2] = static_cast(nPolyGrid) * nDOFEdgeGrid; + thisFace.cornerPoints[3] = nDOFsGrid - 1; + break; + + default: + ostringstream message; + message << "Unsupported FEM boundary element value, " << typeElem << ", in surface section " << sectionName; + SU2_MPI::Error(message.str(), CURRENT_FUNCTION); + } + + /*--- Convert the local numbering of thisFace to global numbering + and create a unique numbering of corner points. ---*/ + for (unsigned short j = 0; j < thisFace.nCornerPoints; ++j) + thisFace.cornerPoints[j] = connSU2[thisFace.cornerPoints[j] + 5]; + thisFace.CreateUniqueNumbering(); + + /*--- Check if this boundary face must be stored on this rank. ---*/ + vector::const_iterator low; + low = lower_bound(localFaces.begin(), localFaces.end(), thisFace); + if (low != localFaces.end()) { + if (!(thisFace < *low)) { + /*--- Update the number of local surface elements. ---*/ + ++nSurfElem; + + /*--- Store the meta data in the first 5 locations of connSU2. ---*/ + connSU2[0] = VTK_Type; + connSU2[1] = nPolyGrid; + connSU2[2] = nDOFsGrid; + connSU2[3] = globalID; // Global surface elem ID. + connSU2[4] = low->elemID0; // Global volume elem ID. + + /*--- Store the connectivity data in surfConn. ---*/ + surfConn.insert(surfConn.end(), connSU2.begin(), connSU2.end()); + } + } + } + } +} +#endif + +void CCGNSMeshReaderFEM::CommPointCoordinates(void) { + /*--- Determine the linear partitioning of the points. ---*/ + CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); + + /*--- Loop over the local elements to determine the global + point IDs to be stored on this rank. --*/ + unsigned long ind = 0; + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + /*--- Store the number of grid DOFs for this element and + skip the meta data for this element (5 entries). ---*/ + const unsigned long nDOFsGrid = localVolumeElementConnectivity[ind + 3]; + ind += 5; + + /*--- Copy the connectivity to globalPointIDs. ---*/ + unsigned long* conn = localVolumeElementConnectivity.data() + ind; + ind += nDOFsGrid; + globalPointIDs.insert(globalPointIDs.end(), conn, conn + nDOFsGrid); + } + + /*--- Sort the globalPointIDs and remove the duplicate entries. ---*/ + sort(globalPointIDs.begin(), globalPointIDs.end()); + vector::iterator lastNode; + lastNode = unique(globalPointIDs.begin(), globalPointIDs.end()); + globalPointIDs.erase(lastNode, globalPointIDs.end()); + + /*--- Determine the number of locally stored points. ---*/ + numberOfLocalPoints = globalPointIDs.size(); + + /*--- This rest of this function only needs to done when MPI is used. ---*/ +#ifdef HAVE_MPI + + /*--- Store the global ID's of the points in such a way that they can + be sent to the rank that actually stores the coordinates.. ---*/ + vector > pointBuf(size, vector(0)); + + for (unsigned long i = 0; i < globalPointIDs.size(); ++i) + pointBuf[pointPartitioner.GetRankContainingIndex(globalPointIDs[i])].push_back(globalPointIDs[i]); + + /*--- Determine the total number of ranks to which this rank will send + a message and also determine the number of ranks from which this + rank will receive a message. Furthermore, determine the starting + indices where data from the different ranks should be stored in + localPointCoordinates. ---*/ + int nRankSend = 0; + vector sendToRank(size, 0); + vector startingIndRanksInPoint(size + 1); + startingIndRanksInPoint[0] = 0; + + for (int i = 0; i < size; ++i) { + startingIndRanksInPoint[i + 1] = startingIndRanksInPoint[i] + pointBuf[i].size(); + + if (pointBuf[i].size()) { + ++nRankSend; + sendToRank[i] = 1; + } + } + + int nRankRecv; + vector sizeRecv(size, 1); + SU2_MPI::Reduce_scatter(sendToRank.data(), &nRankRecv, sizeRecv.data(), MPI_INT, MPI_SUM, SU2_MPI::GetComm()); + + /*--- Send out the messages with the global point numbers. Use nonblocking + sends to avoid deadlock. ---*/ + vector sendReqs(nRankSend); + nRankSend = 0; + for (int i = 0; i < size; ++i) { + if (pointBuf[i].size()) { + SU2_MPI::Isend(pointBuf[i].data(), pointBuf[i].size(), MPI_UNSIGNED_LONG, i, i, SU2_MPI::GetComm(), + &sendReqs[nRankSend]); + ++nRankSend; + } + } + + /*--- Define the communication buffer for the coordinates and the vector + for the return communication requests. */ + vector returnReqs(nRankRecv); + vector > coorReturnBuf(nRankRecv, vector(0)); + + /*--- Get the first index of the points as well as the number of points + currently stored on this rank. ---*/ + const unsigned long firstIndex = pointPartitioner.GetFirstIndexOnRank(rank); + const unsigned long nPointsRead = pointPartitioner.GetSizeOnRank(rank); + + /*--- Loop over the number of ranks from which this rank receives global + point numbers that should be stored on this rank. ---*/ + for (int i = 0; i < nRankRecv; ++i) { + /* Block until a message arrives. Determine the source and size + of the message. */ + SU2_MPI::Status status; + SU2_MPI::Probe(MPI_ANY_SOURCE, rank, SU2_MPI::GetComm(), &status); + int source = status.MPI_SOURCE; + + int sizeMess; + SU2_MPI::Get_count(&status, MPI_UNSIGNED_LONG, &sizeMess); + + /*--- Allocate the memory for a buffer to receive this message and also + for the buffer to return to coordinates. ---*/ + vector pointRecvBuf(sizeMess); + coorReturnBuf[i].resize(static_cast(dimension) * sizeMess); + + /* Receive the message using a blocking receive. */ + SU2_MPI::Recv(pointRecvBuf.data(), sizeMess, MPI_UNSIGNED_LONG, source, rank, SU2_MPI::GetComm(), &status); + + /*--- Loop over the nodes just received and fill the return communication + buffer with the coordinates of the requested nodes. ---*/ + for (int j = 0; j < sizeMess; ++j) { + const int jj = dimension * j; + const long kk = pointRecvBuf[j] - firstIndex; + if (kk < 0 || kk >= static_cast(nPointsRead)) + SU2_MPI::Error("Invalid point requested. This should not happen.", CURRENT_FUNCTION); + + for (int k = 0; k < dimension; ++k) coorReturnBuf[i][jj + k] = localPointCoordinates[k][kk]; + } + + /* Send the buffer just filled back to the requesting rank. + Use a non-blocking send to avoid deadlock. */ + SU2_MPI::Isend(coorReturnBuf[i].data(), coorReturnBuf[i].size(), MPI_DOUBLE, source, source + 1, SU2_MPI::GetComm(), + &returnReqs[i]); + } + + /*--- Resize the second indices of localPointCoordinates. ---*/ + for (int k = 0; k < dimension; ++k) localPointCoordinates[k].resize(numberOfLocalPoints); + + /*--- Loop over the ranks from which this rank has requested coordinates. ---*/ + for (int i = 0; i < nRankSend; ++i) { + /* Block until a message arrives. Determine the source of the message. */ + SU2_MPI::Status status; + SU2_MPI::Probe(MPI_ANY_SOURCE, rank + 1, SU2_MPI::GetComm(), &status); + int source = status.MPI_SOURCE; + + /* Allocate the memory for the coordinate receive buffer. */ + vector coorRecvBuf(dimension * pointBuf[source].size()); + + /* Receive the message using a blocking receive. */ + SU2_MPI::Recv(coorRecvBuf.data(), coorRecvBuf.size(), MPI_DOUBLE, source, rank + 1, SU2_MPI::GetComm(), &status); + + /*--- Loop over the points just received. ---*/ + for (unsigned long j = 0; j < pointBuf[source].size(); ++j) { + /*--- Copy the data into the correct location of localPointCoordinates. ---*/ + const unsigned long jj = dimension * j; + const unsigned long kk = startingIndRanksInPoint[source] + j; + + for (int k = 0; k < dimension; ++k) localPointCoordinates[k][kk] = SU2_TYPE::GetValue(coorRecvBuf[jj + k]); + } + } + + /* Complete the non-blocking sends of both rounds. */ + SU2_MPI::Waitall(sendReqs.size(), sendReqs.data(), MPI_STATUSES_IGNORE); + SU2_MPI::Waitall(returnReqs.size(), returnReqs.data(), MPI_STATUSES_IGNORE); + + /*--- Wild cards have been used in the communication, + so synchronize the ranks to avoid problems. ---*/ + SU2_MPI::Barrier(SU2_MPI::GetComm()); + +#endif +} diff --git a/Common/src/geometry/meshreader/CCGNSMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CCGNSMeshReaderFVM.cpp index cdd06c285d6..2c0dabc8236 100644 --- a/Common/src/geometry/meshreader/CCGNSMeshReaderFVM.cpp +++ b/Common/src/geometry/meshreader/CCGNSMeshReaderFVM.cpp @@ -29,8 +29,8 @@ #include "../../../include/toolboxes/CLinearPartitioner.hpp" #include "../../../include/geometry/meshreader/CCGNSMeshReaderFVM.hpp" -CCGNSMeshReaderFVM::CCGNSMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) - : CMeshReaderFVM(val_config, val_iZone, val_nZone) { +CCGNSMeshReaderFVM::CCGNSMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CCGNSMeshReaderBase(val_config, val_iZone, val_nZone) { #ifdef HAVE_CGNS OpenCGNSFile(config->GetMesh_FileName()); @@ -74,307 +74,6 @@ CCGNSMeshReaderFVM::CCGNSMeshReaderFVM(CConfig* val_config, unsigned short val_i CCGNSMeshReaderFVM::~CCGNSMeshReaderFVM() = default; #ifdef HAVE_CGNS -void CCGNSMeshReaderFVM::OpenCGNSFile(const string& val_filename) { - /*--- Check whether the supplied file is truly a CGNS file. ---*/ - - int file_type; - float file_version; - if (cg_is_cgns(val_filename.c_str(), &file_type) != CG_OK) { - SU2_MPI::Error(val_filename + string(" was not found or is not a properly formatted") + - string(" CGNS file.\nNote that SU2 expects unstructured") + - string(" CGNS files in ADF data format."), - CURRENT_FUNCTION); - } - - /*--- Open the CGNS file for reading. The value of cgnsFileID returned - is the specific index number for this file and will be - repeatedly used in the function calls. ---*/ - - if (cg_open(val_filename.c_str(), CG_MODE_READ, &cgnsFileID)) cg_error_exit(); - if (rank == MASTER_NODE) { - cout << "Reading the CGNS file: "; - cout << val_filename.c_str() << "." << endl; - } - if (cg_version(cgnsFileID, &file_version)) cg_error_exit(); - if (rank == MASTER_NODE) { - if (file_version < 4.0) { - cout - << "WARNING: The CGNS file version (" << file_version - << ") is old and may cause high memory usage issues, consider updating the file with the cgnsupdate tool.\n"; - } - } -} - -void CCGNSMeshReaderFVM::ReadCGNSDatabaseMetadata() { - /*--- Get the number of databases. This is the highest node - in the CGNS heirarchy. ---*/ - - int nbases; - if (cg_nbases(cgnsFileID, &nbases)) cg_error_exit(); - if (rank == MASTER_NODE) cout << "CGNS file contains " << nbases << " database(s)." << endl; - - /*--- Check if there is more than one database. Throw an - error if there is because this reader can currently - only handle one database. ---*/ - - if (nbases > 1) { - SU2_MPI::Error("CGNS reader currently can only handle 1 database.", CURRENT_FUNCTION); - } - - /*--- Read the database. Note that the CGNS indexing starts at 1. ---*/ - - int cell_dim, phys_dim; - char basename[CGNS_STRING_SIZE]; - if (cg_base_read(cgnsFileID, cgnsBase, basename, &cell_dim, &phys_dim)) cg_error_exit(); - if (rank == MASTER_NODE) { - cout << "Database " << cgnsBase << ", " << basename << ": "; - cout << " cell dimension of " << cell_dim << ", physical "; - cout << "dimension of " << phys_dim << "." << endl; - } - - /*--- Set the number of dimensions baed on cell_dim. ---*/ - - dimension = (unsigned short)cell_dim; -} - -void CCGNSMeshReaderFVM::ReadCGNSZoneMetadata() { - /*--- First, check all sections to find the element types and to - classify them as either surface or volume elements. We will also - perform some error checks here to avoid partitioning issues. ---*/ - - /*--- Get the number of zones for this base. ---*/ - - int nzones; - if (cg_nzones(cgnsFileID, cgnsBase, &nzones)) cg_error_exit(); - if (rank == MASTER_NODE) { - cout << nzones << " total zone(s)." << endl; - } - - /*--- Check if there is more than one zone. Until we enable it, we - will require a single zone CGNS file. Multizone problems can still - be run with CGNS by using separate CGNS files for each zone. ---*/ - - if (nzones > 1) { - SU2_MPI::Error(string("CGNS reader currently expects only 1 zone per CGNS file.") + - string("Multizone problems can be run with separate CGNS files for each zone."), - CURRENT_FUNCTION); - } - - /*--- Read the basic information for this zone, including - the name and the number of vertices, cells, and - boundary cells which are stored in the cgsize variable. ---*/ - - vector cgsize(3); - ZoneType_t zonetype; - char zonename[CGNS_STRING_SIZE]; - if (cg_zone_read(cgnsFileID, cgnsBase, cgnsZone, zonename, cgsize.data())) cg_error_exit(); - - /*--- Rename the zone size information for clarity. - NOTE: The number of cells here may be only the number of - interior elements or it may be the total. This needs to - be counted explicitly later. ---*/ - - numberOfGlobalPoints = cgsize[0]; - int nElemCGNS = cgsize[1]; - - /*--- Get some additional information about the current zone. ---*/ - - if (cg_zone_type(cgnsFileID, cgnsBase, cgnsZone, &zonetype)) cg_error_exit(); - - /*--- Check for an unstructured mesh. Throw an error if not found. ---*/ - - if (zonetype != Unstructured) - SU2_MPI::Error("Structured CGNS zone found while unstructured expected.", CURRENT_FUNCTION); - - /*--- Print current zone info to the console. ---*/ - - if (rank == MASTER_NODE) { - cout << "Zone " << cgnsZone << ", " << zonename << ": "; - cout << numberOfGlobalPoints << " total vertices, "; - cout << nElemCGNS << " total elements." << endl; - } - - /*--- Retrieve the number of grids in this zone. For now, we know - this is one, but to be more general, this will need to check and - allow for a loop over all grids. ---*/ - - int ngrids; - if (cg_ngrids(cgnsFileID, cgnsBase, cgnsZone, &ngrids)) cg_error_exit(); - if (ngrids > 1) { - SU2_MPI::Error("CGNS reader currently handles only 1 grid per zone.", CURRENT_FUNCTION); - } -} - -void CCGNSMeshReaderFVM::ReadCGNSPointCoordinates() { - /*--- Compute the number of points that will be on each processor. - This is a linear partitioning with the addition of a simple load - balancing for any remainder points. ---*/ - - CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); - - /*--- Store the local number of nodes for this rank. ---*/ - - numberOfLocalPoints = pointPartitioner.GetSizeOnRank(rank); - - /*--- Create buffer to hold the grid coordinates for our rank. ---*/ - - localPointCoordinates.resize(dimension); - for (int k = 0; k < dimension; k++) localPointCoordinates[k].resize(numberOfLocalPoints, 0.0); - - /*--- Set the value of range_max to the total number of nodes in - the unstructured mesh. Also allocate memory for the temporary array - that will hold the grid coordinates as they are extracted. Note the - +1 for CGNS convention. ---*/ - - cgsize_t range_min = (cgsize_t)pointPartitioner.GetFirstIndexOnRank(rank) + 1; - auto range_max = (cgsize_t)pointPartitioner.GetLastIndexOnRank(rank); - - /*--- Loop over each set of coordinates. ---*/ - - for (int k = 0; k < dimension; k++) { - /*--- Read the coordinate info. This will retrieve the - data type (either RealSingle or RealDouble) as - well as the coordname which will specify the - type of data that it is based in the SIDS convention. - This might be "CoordinateX," for instance. ---*/ - - char coordname[CGNS_STRING_SIZE]; - DataType_t datatype; - if (cg_coord_info(cgnsFileID, cgnsBase, cgnsZone, k + 1, &datatype, coordname)) cg_error_exit(); - if (rank == MASTER_NODE) { - cout << "Loading " << coordname; - if (size > SINGLE_NODE) { - cout << " values into linear partitions." << endl; - } else { - cout << " values." << endl; - } - } - - /*--- Check the coordinate name to decide the index for storage. ---*/ - - unsigned short indC = 0; - if (string(coordname) == "CoordinateX") - indC = 0; - else if (string(coordname) == "CoordinateY") - indC = 1; - else if (string(coordname) == "CoordinateZ") - indC = 2; - else - SU2_MPI::Error(string("Unknown coordinate name, ") + coordname + string(", in the CGNS file."), CURRENT_FUNCTION); - - /*--- Now read our rank's chunk of coordinates from the file. - Ask for datatype RealDouble and let CGNS library do the translation - when RealSingle is found. ---*/ - - if (cg_coord_read(cgnsFileID, cgnsBase, cgnsZone, coordname, RealDouble, &range_min, &range_max, - localPointCoordinates[indC].data())) - cg_error_exit(); - } -} - -void CCGNSMeshReaderFVM::ReadCGNSSectionMetadata() { - /*--- Begin section for retrieving the connectivity info. ---*/ - - if ((rank == MASTER_NODE) && (size > SINGLE_NODE)) cout << "Distributing connectivity across all ranks." << endl; - - /*--- First check the number of sections. ---*/ - - if (cg_nsections(cgnsFileID, cgnsBase, cgnsZone, &nSections)) cg_error_exit(); - if (rank == MASTER_NODE) { - cout << "Number of connectivity sections is "; - cout << nSections << "." << endl; - } - - /*--- Prepare several data structures to hold the various - pieces of information describing each section. ---*/ - - isInterior.resize(nSections); - nElems.resize(nSections, 0); - elemOffset.resize(nSections + 1, 0); - elemOffset[0] = 0; - connElems.resize(nSections); - sectionNames.resize(nSections, vector(CGNS_STRING_SIZE)); - numberOfGlobalElements = 0; - - for (int s = 0; s < nSections; s++) { - /*--- Read the connectivity details for this section. ---*/ - - int nbndry, parent_flag, vtk_type; - cgsize_t startE, endE, sizeNeeded; - ElementType_t elemType; - if (cg_section_read(cgnsFileID, cgnsBase, cgnsZone, s + 1, sectionNames[s].data(), &elemType, &startE, &endE, - &nbndry, &parent_flag)) - cg_error_exit(); - - /*--- Compute the total element count in this section (global). ---*/ - - unsigned long element_count = (endE - startE + 1); - - /* Get the details for the CGNS element type in this section. */ - - string elem_name = GetCGNSElementType(elemType, vtk_type); - - /* We assume that each section contains interior elements by default. - If we find 1D elements in a 2D problem or 2D elements in a 3D - problem, then we know the section must contain boundary elements. - We assume that each section is composed of either entirely interior - or entirely boundary elements. */ - - isInterior[s] = true; - - if (elemType == MIXED) { - /* For a mixed section, we check the type of the first element - so that we can correctly label this section as an interior or - boundary element section. Here, we also assume that a section - can not hold both interior and boundary elements. First, get - the size required to read a single element from the section. */ - - if (cg_ElementPartialSize(cgnsFileID, cgnsBase, cgnsZone, s + 1, startE, startE, &sizeNeeded) != CG_OK) - cg_error_exit(); - - /* A couple of auxiliary vectors for mixed element sections. */ - - vector connElemCGNS(sizeNeeded); - vector connOffsetCGNS(2, 0); - - /* Retrieve the connectivity information for the first element. */ - - if (cg_poly_elements_partial_read(cgnsFileID, cgnsBase, cgnsZone, s + 1, startE, startE, connElemCGNS.data(), - connOffsetCGNS.data(), nullptr) != CG_OK) - cg_error_exit(); - - /* The element type is in the first position of the connectivity - information that we retrieved from the CGNS file. */ - - elemType = ElementType_t(connElemCGNS[0]); - } - - /* Check for 1D elements in 2D problems, or for 2D elements in - 3D problems. If found, mark the section as a boundary section. */ - - if ((dimension == 2) && (elemType == BAR_2 || elemType == BAR_3)) isInterior[s] = false; - if ((dimension == 3) && (elemType == TRI_3 || elemType == QUAD_4)) isInterior[s] = false; - - /*--- Increment the global element offset for each section - based on whether or not this is a surface or volume section. - We also keep a running count of the total elements globally. ---*/ - - elemOffset[s + 1] = elemOffset[s]; - if (!isInterior[s]) - elemOffset[s + 1] += element_count; - else - numberOfGlobalElements += element_count; - - /*--- Print some information to the console. ---*/ - - if (rank == MASTER_NODE) { - cout << "Section " << string(sectionNames[s].data()); - cout << " contains " << element_count << " elements"; - cout << " of type " << elem_name << "." << endl; - } - } -} - void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { /*--- In this routine, each rank will read a chunk of the element connectivity for a single specified section of the CGNS mesh file. @@ -545,7 +244,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { entry if this is a mixed element section. ---*/ if (isMixed) counterCGNS++; - for (iNode = 0; iNode < (unsigned long)nPoinPerElem[iElem]; iNode++) { + for (iNode = 0; iNode < static_cast(nPoinPerElem[iElem]); iNode++) { connElemTemp[nn] = connElemCGNS[counterCGNS + iNode] - 1; nn++; } @@ -582,7 +281,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); for (iElem = 0; iElem < nElems[val_section]; iElem++) { - for (iNode = 0; iNode < (unsigned long)nPoinPerElem[iElem]; iNode++) { + for (iNode = 0; iNode < static_cast(nPoinPerElem[iElem]); iNode++) { /*--- Get the index of the current point. ---*/ iPoint = connElemTemp[iElem * SU2_CONN_SIZE + SU2_CONN_SKIP + iNode]; @@ -594,7 +293,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { /*--- If we have not visited this element yet, increment our number of elements that must be sent to a particular proc. ---*/ - if ((nElem_Flag[iProcessor] != (int)iElem)) { + if ((nElem_Flag[iProcessor] != static_cast(iElem))) { nElem_Flag[iProcessor] = iElem; nElem_Send[iProcessor + 1]++; } @@ -630,7 +329,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { + 2 extra values for the ID and VTK. ---*/ unsigned long *connSend = nullptr, iSend = 0; - unsigned long sendSize = (unsigned long)SU2_CONN_SIZE * nElem_Send[size]; + unsigned long sendSize = static_cast(SU2_CONN_SIZE) * nElem_Send[size]; connSend = new unsigned long[sendSize]; for (iSend = 0; iSend < sendSize; iSend++) connSend[iSend] = 0; @@ -643,8 +342,8 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { /*--- Loop through our elements and load the elems and their additional data that we will send to the other procs. ---*/ - for (iElem = 0; iElem < (unsigned long)nElems[val_section]; iElem++) { - for (iNode = 0; iNode < (unsigned long)nPoinPerElem[iElem]; iNode++) { + for (iElem = 0; iElem < static_cast(nElems[val_section]); iElem++) { + for (iNode = 0; iNode < static_cast(nPoinPerElem[iElem]); iNode++) { /*--- Get the index of the current point. ---*/ iPoint = connElemTemp[iElem * SU2_CONN_SIZE + SU2_CONN_SKIP + iNode]; @@ -655,7 +354,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { /*--- Load connectivity into the buffer for sending ---*/ - if (nElem_Flag[iProcessor] != (int)iElem) { + if (nElem_Flag[iProcessor] != static_cast(iElem)) { nElem_Flag[iProcessor] = iElem; unsigned long nn = index[iProcessor]; @@ -689,7 +388,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { directly copy our own data later. ---*/ unsigned long *connRecv = nullptr, iRecv = 0; - unsigned long recvSize = (unsigned long)SU2_CONN_SIZE * nElem_Recv[size]; + unsigned long recvSize = static_cast(SU2_CONN_SIZE) * nElem_Recv[size]; connRecv = new unsigned long[recvSize]; for (iRecv = 0; iRecv < recvSize; iRecv++) connRecv[iRecv] = 0; @@ -731,7 +430,7 @@ void CCGNSMeshReaderFVM::ReadCGNSVolumeSection(int val_section) { if (nElem_Recv[size] > 0) { connElems[val_section].resize(nElem_Recv[size] * SU2_CONN_SIZE, 0); unsigned long count = 0; - for (iElem = 0; iElem < (unsigned long)nElem_Recv[size]; iElem++) { + for (iElem = 0; iElem < static_cast(nElem_Recv[size]); iElem++) { for (iNode = 0; iNode < SU2_CONN_SIZE; iNode++) { unsigned long nn = iElem * SU2_CONN_SIZE + iNode; connElems[val_section][count] = (cgsize_t)connRecv[nn]; @@ -874,7 +573,7 @@ void CCGNSMeshReaderFVM::ReadCGNSSurfaceSection(int val_section) { connElems[val_section][iElem * SU2_CONN_SIZE + 0] = 0; connElems[val_section][iElem * SU2_CONN_SIZE + 1] = vtk_type; - for (iNode = 0; iNode < (unsigned long)npe; iNode++) { + for (iNode = 0; iNode < static_cast(npe); iNode++) { unsigned long nn = iElem * SU2_CONN_SIZE + SU2_CONN_SKIP + iNode; connElems[val_section][nn] = connElemTemp[counterCGNS] - 1; counterCGNS++; @@ -907,7 +606,7 @@ void CCGNSMeshReaderFVM::ReformatCGNSVolumeConnectivity() { for (unsigned long iElem = 0; iElem < nElems[s]; iElem++) { for (unsigned long iNode = 0; iNode < SU2_CONN_SIZE; iNode++) { unsigned long nn = iElem * SU2_CONN_SIZE + iNode; - localVolumeElementConnectivity[count] = (unsigned long)connElems[s][nn]; + localVolumeElementConnectivity[count] = static_cast(connElems[s][nn]); count++; } } @@ -941,7 +640,7 @@ void CCGNSMeshReaderFVM::ReformatCGNSSurfaceConnectivity() { for (unsigned long iElem = 0; iElem < nElems[s]; iElem++) { for (unsigned long iNode = 0; iNode < SU2_CONN_SIZE; iNode++) { unsigned long nn = iElem * SU2_CONN_SIZE + iNode; - surfaceElementConnectivity[markerCount][elementCount] = (unsigned long)connElems[s][nn]; + surfaceElementConnectivity[markerCount][elementCount] = static_cast(connElems[s][nn]); elementCount++; } } @@ -951,65 +650,6 @@ void CCGNSMeshReaderFVM::ReformatCGNSSurfaceConnectivity() { } } } - -string CCGNSMeshReaderFVM::GetCGNSElementType(ElementType_t val_elem_type, int& val_vtk_type) { - /* Check the CGNS element type and return the string name - for the element and the associated VTK type index. */ - - string elem_name; - switch (val_elem_type) { - case NODE: - elem_name = "Vertex"; - val_vtk_type = 1; - SU2_MPI::Error("Vertex elements detected. Please remove.", CURRENT_FUNCTION); - break; - case BAR_2: - elem_name = "Line"; - val_vtk_type = 3; - if (dimension == 3) SU2_MPI::Error("Line elements detected in a 3D mesh. Please remove.", CURRENT_FUNCTION); - break; - case BAR_3: - elem_name = "Line"; - val_vtk_type = 3; - if (dimension == 3) SU2_MPI::Error("Line elements detected in a 3D mesh. Please remove.", CURRENT_FUNCTION); - break; - case TRI_3: - elem_name = "Triangle"; - val_vtk_type = 5; - break; - case QUAD_4: - elem_name = "Quadrilateral"; - val_vtk_type = 9; - break; - case TETRA_4: - elem_name = "Tetrahedron"; - val_vtk_type = 10; - break; - case HEXA_8: - elem_name = "Hexahedron"; - val_vtk_type = 12; - break; - case PENTA_6: - elem_name = "Prism"; - val_vtk_type = 13; - break; - case PYRA_5: - elem_name = "Pyramid"; - val_vtk_type = 14; - break; - case MIXED: - elem_name = "Mixed"; - val_vtk_type = -1; - break; - default: - char buf[100]; - SPRINTF(buf, "Unsupported or unknown CGNS element type: (type %d)\n", val_elem_type); - SU2_MPI::Error(string(buf), CURRENT_FUNCTION); - break; - } - - return elem_name; -} #endif void CCGNSMeshReaderFVM::InitiateCommsAll(void* bufSend, const int* nElemSend, SU2_MPI::Request* sendReq, void* bufRecv, diff --git a/Common/src/geometry/meshreader/CMeshReaderBase.cpp b/Common/src/geometry/meshreader/CMeshReaderBase.cpp new file mode 100644 index 00000000000..bb20e0d97b9 --- /dev/null +++ b/Common/src/geometry/meshreader/CMeshReaderBase.cpp @@ -0,0 +1,95 @@ +/*! + * \file CMeshReaderBase.cpp + * \brief Helper class that provides the counts for each rank in a linear + * partitioning given the global count as input. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/geometry/meshreader/CMeshReaderBase.hpp" + +CMeshReaderBase::CMeshReaderBase(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : rank(SU2_MPI::GetRank()), size(SU2_MPI::GetSize()), config(val_config) {} + +void CMeshReaderBase::DetermineFacesVolumeElements(vector& localFaces) { + /*--- Loop over the locally stored volume elements. ---*/ + unsigned long ind = 0; + for (unsigned long k = 0; k < numberOfLocalElements; ++k) { + /*--- Set the pointer where the information of this element + is stored and determine the number of faces as well + as the corner points of these faces. Note that 6 faces + is the maximum for the elements considered. ---*/ + unsigned short nFaces; + unsigned short nPointsPerFace[6]; + unsigned long faceConn[6][4]; + + const unsigned long* elemInfo = localVolumeElementConnectivity.data() + ind; + + GetCornerPointsAllFaces(elemInfo, nFaces, nPointsPerFace, faceConn); + + /*--- Loop over the faces and add them to localFaces. ---*/ + for (unsigned short i = 0; i < nFaces; ++i) { + CFaceOfElement thisFace; + thisFace.nCornerPoints = nPointsPerFace[i]; + for (unsigned short j = 0; j < nPointsPerFace[i]; ++j) thisFace.cornerPoints[j] = faceConn[i][j]; + thisFace.elemID0 = elemInfo[4]; + + thisFace.CreateUniqueNumbering(); + localFaces.push_back(thisFace); + } + + /*--- Update the index for the next element. ---*/ + const unsigned long nDOFsGrid = localVolumeElementConnectivity[ind + 3]; + ind += nDOFsGrid + 5; + } + + /*--- Sort localFaces in increasing order and remove the double entities, + such that the binary search later on is a bit more efficient. ---*/ + sort(localFaces.begin(), localFaces.end()); + vector::iterator lastFace; + lastFace = unique(localFaces.begin(), localFaces.end()); + localFaces.erase(lastFace, localFaces.end()); +} + +void CMeshReaderBase::GetCornerPointsAllFaces(const unsigned long* elemInfo, unsigned short& numFaces, + unsigned short nPointsPerFace[], unsigned long faceConn[6][4]) { + /*--- Retrieve the element type, polynomial degree of the grid and + number of DOFs for this element and set the pointer for the + connectivity information. ---*/ + const unsigned short VTK_Type = (const unsigned short)elemInfo[0]; + const unsigned short nPolyGrid = (const unsigned short)elemInfo[1]; + const unsigned short nDOFsGrid = (const unsigned short)elemInfo[3]; + const unsigned long* conn = elemInfo + 5; + + /*--- Call the static function GetLocalCornerPointsAllFaces of CPrimalGridFEM + to determine the local numbering of the corner points of the faces. ---*/ + CPrimalGridFEM::GetLocalCornerPointsAllFaces(VTK_Type, nPolyGrid, nDOFsGrid, numFaces, nPointsPerFace, faceConn); + + /*--- Convert the local values of faceConn to global values. ---*/ + for (unsigned short i = 0; i < numFaces; ++i) { + for (unsigned short j = 0; j < nPointsPerFace[i]; ++j) { + unsigned long nn = faceConn[i][j]; + faceConn[i][j] = conn[nn]; + } + } +} diff --git a/Common/src/geometry/meshreader/CMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CMeshReaderFVM.cpp deleted file mode 100644 index 17069bf7b25..00000000000 --- a/Common/src/geometry/meshreader/CMeshReaderFVM.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * \file CMeshReaderFVM.cpp - * \brief Helper class that provides the counts for each rank in a linear - * partitioning given the global count as input. - * \author T. Economon - * \version 8.1.0 "Harrier" - * - * SU2 Project Website: https://su2code.github.io - * - * The SU2 Project is maintained by the SU2 Foundation - * (http://su2foundation.org) - * - * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) - * - * SU2 is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * SU2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with SU2. If not, see . - */ - -#include "../../../include/geometry/meshreader/CMeshReaderFVM.hpp" - -CMeshReaderFVM::CMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) - : rank(SU2_MPI::GetRank()), size(SU2_MPI::GetSize()), config(val_config) {} diff --git a/Common/src/geometry/meshreader/CRectangularMeshReaderFEM.cpp b/Common/src/geometry/meshreader/CRectangularMeshReaderFEM.cpp new file mode 100644 index 00000000000..8b6c6cf8400 --- /dev/null +++ b/Common/src/geometry/meshreader/CRectangularMeshReaderFEM.cpp @@ -0,0 +1,255 @@ +/*! + * \file CRectangularMeshReaderFEM.cpp + * \brief Reads a 2D rectangular grid into linear partitions for the + * finite element solver (FEM). + * \author T. Economon, E. van der Weide + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CRectangularMeshReaderFEM.hpp" + +CRectangularMeshReaderFEM::CRectangularMeshReaderFEM(const CConfig* val_config, unsigned short val_iZone, + unsigned short val_nZone) + : CMeshReaderBase(val_config, val_iZone, val_nZone) { + /* The rectangular mesh is always 2D. */ + dimension = 2; + + /* Set the VTK type for the interior elements and the boundary elements. */ + KindElem = QUADRILATERAL; + KindBound = LINE; + + /* The number of nodes in the i and j directions. */ + nNode = config->GetMeshBoxSize(0); + mNode = config->GetMeshBoxSize(1); + + /* Lengths for non-square domains. */ + Lx = config->GetMeshBoxLength(0); + Ly = config->GetMeshBoxLength(1); + + /* Offsets in x and y directions from 0.0. */ + Ox = config->GetMeshBoxOffset(0); + Oy = config->GetMeshBoxOffset(1); + + /* Polynomial degree of the solution. */ + nPolySol = config->GetMeshBoxPSolFEM(); + + /*--- Compute and store the interior elements, points, and surface elements + for this rank, for which simple analytic formulae can be used. ---*/ + ComputeRectangularVolumeConnectivity(); + ComputeRectangularPointCoordinates(); + ComputeRectangularSurfaceConnectivity(); +} + +CRectangularMeshReaderFEM::~CRectangularMeshReaderFEM() = default; + +void CRectangularMeshReaderFEM::ComputeRectangularPointCoordinates() { + /*--- Set the global count of points based on the grid dimensions. ---*/ + numberOfGlobalPoints = nNode * mNode; + + /*--- Loop over the local elements to determine the global + point IDs to be stored on this rank. --*/ + unsigned long ind = 0; + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + /*--- Store the number of grid DOFs for this element and + skip the meta data for this element (5 entries). ---*/ + const unsigned long nDOFsGrid = localVolumeElementConnectivity[ind + 3]; + ind += 5; + + /*--- Copy the connectivity to globalPointIDs. ---*/ + unsigned long* conn = localVolumeElementConnectivity.data() + ind; + ind += nDOFsGrid; + globalPointIDs.insert(globalPointIDs.end(), conn, conn + nDOFsGrid); + } + + /*--- Sort the globalPointIDs and remove the duplicate entries. ---*/ + sort(globalPointIDs.begin(), globalPointIDs.end()); + vector::iterator lastNode; + lastNode = unique(globalPointIDs.begin(), globalPointIDs.end()); + globalPointIDs.erase(lastNode, globalPointIDs.end()); + + /*--- Determine the number of locally stored points. ---*/ + numberOfLocalPoints = globalPointIDs.size(); + + /*--- Allocate the memory for the locally stored points. ---*/ + localPointCoordinates.resize(dimension); + for (int k = 0; k < dimension; k++) localPointCoordinates[k].resize(numberOfLocalPoints); + + /*--- Loop over the locally stored points. ---*/ + for (unsigned long i = 0; i < numberOfLocalPoints; ++i) { + /*--- Convert the global index to i,j indices. ---*/ + const unsigned long jNode = globalPointIDs[i] / nNode; + const unsigned long iNode = globalPointIDs[i] - jNode * nNode; + + /*--- Store the coordinates of the point. ---*/ + localPointCoordinates[0][i] = SU2_TYPE::GetValue(Lx * ((su2double)iNode) / ((su2double)(nNode - 1)) + Ox); + localPointCoordinates[1][i] = SU2_TYPE::GetValue(Ly * ((su2double)jNode) / ((su2double)(mNode - 1)) + Oy); + } +} + +void CRectangularMeshReaderFEM::ComputeRectangularVolumeConnectivity() { + /*--- Set the global count of elements based on the grid dimensions. ---*/ + const unsigned long nElemI = nNode - 1; + numberOfGlobalElements = nElemI * (mNode - 1); + + /*--- Get a partitioner to help with linear partitioning. ---*/ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /*--- Determine the index of the first and last element to be stored + on this rank and the number of local elements. ---*/ + const unsigned long firstIndex = elemPartitioner.GetFirstIndexOnRank(rank); + const unsigned long lastIndex = elemPartitioner.GetLastIndexOnRank(rank); + numberOfLocalElements = elemPartitioner.GetSizeOnRank(rank); + + /*--- Loop over the owned element range. ---*/ + for (unsigned long elem = firstIndex; elem < lastIndex; ++elem) { + /*--- Retrieve the i,j indices of this element, which are the indices + of the lower, left point of the element. --*/ + const unsigned long jNode = elem / nElemI; + const unsigned long iNode = elem - jNode * nElemI; + + /*--- Store the meta data of this element. ---*/ + localVolumeElementConnectivity.push_back(KindElem); + localVolumeElementConnectivity.push_back(1); // Pol. degree grid. + localVolumeElementConnectivity.push_back(nPolySol); + localVolumeElementConnectivity.push_back(4); // Number of grid DOFs. + localVolumeElementConnectivity.push_back(elem); // Global elem ID. + + /*--- Store the volume connectivity in the format used + by the FEM solver. ---*/ + localVolumeElementConnectivity.push_back(jNode * nNode + iNode); + localVolumeElementConnectivity.push_back(jNode * nNode + iNode + 1); + localVolumeElementConnectivity.push_back((jNode + 1) * nNode + iNode); + localVolumeElementConnectivity.push_back((jNode + 1) * nNode + iNode + 1); + } +} + +void CRectangularMeshReaderFEM::ComputeRectangularSurfaceConnectivity() { + /*--- Determine the number of elements in i-direction. ---*/ + const unsigned long nElemI = nNode - 1; + + /*--- Get a partitioner to help with linear partitioning. ---*/ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /*--- The rectangle always has 4 markers. Allocate the required memory. ---*/ + numberOfMarkers = 4; + numberOfLocalSurfaceElements.resize(numberOfMarkers, 0); + surfaceElementConnectivity.resize(numberOfMarkers); + markerNames.resize(numberOfMarkers); + + /*--- Loop over all faces on the yMin (= jMin) boundary. ---*/ + markerNames[0] = "y_minus"; + + for (unsigned long iNode = 0; iNode < nNode - 1; ++iNode) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = iNode; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[0].push_back(KindBound); // VTK type. + surfaceElementConnectivity[0].push_back(1); // Poly degree grid. + surfaceElementConnectivity[0].push_back(2); // Number of grid DOFs. + surfaceElementConnectivity[0].push_back(iNode); // Global surface element ID. + surfaceElementConnectivity[0].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[0].push_back(iNode); + surfaceElementConnectivity[0].push_back(iNode + 1); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[0]; + } + } + + /*--- Loop over all faces on the xMax (= iMax) boundary. ---*/ + markerNames[1] = "x_plus"; + + for (unsigned long jNode = 0; jNode < mNode - 1; ++jNode) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = jNode * nElemI + nElemI - 1; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[1].push_back(KindBound); // VTK type. + surfaceElementConnectivity[1].push_back(1); // Poly degree grid. + surfaceElementConnectivity[1].push_back(2); // Number of grid DOFs. + surfaceElementConnectivity[1].push_back(jNode); // Global surface element ID. + surfaceElementConnectivity[1].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[1].push_back(jNode * nNode + (nNode - 1)); + surfaceElementConnectivity[1].push_back((jNode + 1) * nNode + (nNode - 1)); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[1]; + } + } + + /*--- Loop over all faces on the yMax (= jMax) boundary. ---*/ + markerNames[2] = "y_plus"; + + for (unsigned long iNode = 0; iNode < nNode - 1; ++iNode) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = (mNode - 2) * nElemI + iNode; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[2].push_back(KindBound); // VTK type. + surfaceElementConnectivity[2].push_back(1); // Poly degree grid. + surfaceElementConnectivity[2].push_back(2); // Number of grid DOFs. + surfaceElementConnectivity[2].push_back(iNode); // Global surface element ID. + surfaceElementConnectivity[2].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[2].push_back((mNode - 1) * nNode + iNode + 1); + surfaceElementConnectivity[2].push_back((mNode - 1) * nNode + iNode); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[2]; + } + } + + /*--- Loop over all faces on the xMin (= iMin) boundary. ---*/ + markerNames[3] = "x_minus"; + + for (unsigned long jNode = 0; jNode < mNode - 1; ++jNode) { + /*--- Determine the corresponding global element ID and check + if it is stored on this rank. ---*/ + const unsigned long globalElemID = jNode * nElemI; + if (elemPartitioner.GetRankContainingIndex(globalElemID) == static_cast(rank)) { + /*--- The corresponding volume element is stored on this rank, + hence store the surface element as well. ---*/ + surfaceElementConnectivity[3].push_back(KindBound); // VTK type. + surfaceElementConnectivity[3].push_back(1); // Poly degree grid. + surfaceElementConnectivity[3].push_back(2); // Number of grid DOFs. + surfaceElementConnectivity[3].push_back(jNode); // Global surface element ID. + surfaceElementConnectivity[3].push_back(globalElemID); // Global volume element ID. + + surfaceElementConnectivity[3].push_back((jNode + 1) * nNode); + surfaceElementConnectivity[3].push_back(jNode * nNode); + + /*--- Update the number of surface elements for this marker. ---*/ + ++numberOfLocalSurfaceElements[3]; + } + } +} diff --git a/Common/src/geometry/meshreader/CRectangularMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CRectangularMeshReaderFVM.cpp index 5b89744bf2a..f1e8f6177a2 100644 --- a/Common/src/geometry/meshreader/CRectangularMeshReaderFVM.cpp +++ b/Common/src/geometry/meshreader/CRectangularMeshReaderFVM.cpp @@ -31,7 +31,7 @@ CRectangularMeshReaderFVM::CRectangularMeshReaderFVM(const CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) - : CMeshReaderFVM(val_config, val_iZone, val_nZone) { + : CMeshReaderBase(val_config, val_iZone, val_nZone) { /* The rectangular mesh is always 2D. */ dimension = 2; @@ -61,6 +61,8 @@ CRectangularMeshReaderFVM::CRectangularMeshReaderFVM(const CConfig* val_config, ComputeRectangularSurfaceConnectivity(); } +CRectangularMeshReaderFVM::~CRectangularMeshReaderFVM() = default; + void CRectangularMeshReaderFVM::ComputeRectangularPointCoordinates() { /* Set the global count of points based on the grid dimensions. */ numberOfGlobalPoints = (nNode) * (mNode); @@ -70,7 +72,7 @@ void CRectangularMeshReaderFVM::ComputeRectangularPointCoordinates() { /* Determine number of local points */ for (unsigned long globalIndex = 0; globalIndex < numberOfGlobalPoints; globalIndex++) { - if ((int)pointPartitioner.GetRankContainingIndex(globalIndex) == rank) { + if (static_cast(pointPartitioner.GetRankContainingIndex(globalIndex)) == rank) { numberOfLocalPoints++; } } @@ -82,7 +84,7 @@ void CRectangularMeshReaderFVM::ComputeRectangularPointCoordinates() { unsigned long globalIndex = 0; for (unsigned long jNode = 0; jNode < mNode; jNode++) { for (unsigned long iNode = 0; iNode < nNode; iNode++) { - if ((int)pointPartitioner.GetRankContainingIndex(globalIndex) == rank) { + if (static_cast(pointPartitioner.GetRankContainingIndex(globalIndex)) == rank) { /* Store the coordinates more clearly. */ const passivedouble x = SU2_TYPE::GetValue(Lx * ((su2double)iNode) / ((su2double)(nNode - 1)) + Ox); const passivedouble y = SU2_TYPE::GetValue(Ly * ((su2double)jNode) / ((su2double)(mNode - 1)) + Oy); @@ -119,7 +121,7 @@ void CRectangularMeshReaderFVM::ComputeRectangularVolumeConnectivity() { /* Check whether any of the points is in our linear partition. */ bool isOwned = false; for (unsigned short i = 0; i < N_POINTS_QUADRILATERAL; i++) { - if ((int)pointPartitioner.GetRankContainingIndex(connectivity[i]) == rank) { + if (static_cast(pointPartitioner.GetRankContainingIndex(connectivity[i])) == rank) { isOwned = true; } } diff --git a/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp new file mode 100644 index 00000000000..d8c524eff4c --- /dev/null +++ b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp @@ -0,0 +1,519 @@ +/*! + * \file CSU2ASCIIMeshReaderBase.cpp + * \brief Helper class for the reading of a native SU2 ASCII grid file. + * \author T. Economon + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp" + +CSU2ASCIIMeshReaderBase::CSU2ASCIIMeshReaderBase(CConfig* val_config, unsigned short val_iZone, + unsigned short val_nZone) + : CMeshReaderBase(val_config, val_iZone, val_nZone), + myZone(val_iZone), + nZones(val_nZone), + meshFilename(config->GetMesh_FileName()) {} + +CSU2ASCIIMeshReaderBase::~CSU2ASCIIMeshReaderBase(void) = default; + +bool CSU2ASCIIMeshReaderBase::ReadMetadata(const bool single_pass, CConfig* config) { + const bool harmonic_balance = config->GetTime_Marching() == TIME_MARCHING::HARMONIC_BALANCE; + const bool multizone_file = config->GetMultizone_Mesh(); + + /*--- Open grid file ---*/ + + mesh_file.open(meshFilename); + if (mesh_file.fail()) { + SU2_MPI::Error( + "Error opening SU2 ASCII grid.\n" + "Check that the file exists.", + CURRENT_FUNCTION); + } + + /*--- If more than one, find the curent zone in the mesh file. ---*/ + + string text_line; + if ((nZones > 1 && multizone_file) || harmonic_balance) { + if (harmonic_balance) { + if (rank == MASTER_NODE) cout << "Reading time instance " << config->GetiInst() + 1 << "." << endl; + } else { + bool foundZone = false; + while (getline(mesh_file, text_line)) { + /*--- Search for the current domain ---*/ + if (text_line.find("IZONE=", 0) != string::npos) { + text_line.erase(0, 6); + unsigned short jZone = atoi(text_line.c_str()); + if (jZone == myZone + 1) { + if (rank == MASTER_NODE) cout << "Reading zone " << myZone << " from native SU2 ASCII mesh." << endl; + foundZone = true; + break; + } + } + } + if (!foundZone) { + SU2_MPI::Error( + "Could not find the IZONE= keyword or the zone contents.\n" + "Check the SU2 ASCII file format.", + CURRENT_FUNCTION); + } + } + } + + /*--- Read the metadata: problem dimension, offsets for angle + of attack and angle of sideslip, global points, global elements, + and number of markers. Perform error checks as we go. ---*/ + + bool foundNDIME = false, foundNPOIN = false; + bool foundNELEM = false, foundNMARK = false; + + int current_section_idx = 0; + bool single_pass_active = false; + + while (getline(mesh_file, text_line)) { + /*--- Read the dimension of the problem ---*/ + + if (!foundNDIME && text_line.find("NDIME=", 0) != string::npos) { + text_line.erase(0, 6); + dimension = atoi(text_line.c_str()); + foundNDIME = true; + continue; + } + + /*--- The AoA and AoS offset values are optional. ---*/ + + if (text_line.find("AOA_OFFSET=", 0) != string::npos) { + text_line.erase(0, 11); + su2double AoA_Offset = atof(text_line.c_str()); + + /*--- The offset is in deg ---*/ + const su2double AoA_Current = config->GetAoA() + AoA_Offset; + config->SetAoA_Offset(AoA_Offset); + config->SetAoA(AoA_Current); + + if (AoA_Offset != 0.0) { + if (!config->GetDiscard_InFiles()) { + cout << "WARNING: AoA in the config file (" << config->GetAoA() << " deg.) +\n"; + cout << " AoA offset in mesh file (" << AoA_Offset << " deg.) = " << AoA_Current << " deg." << endl; + } else { + cout << "WARNING: Discarding the AoA offset in the mesh file." << endl; + } + } + continue; + } + + if (text_line.find("AOS_OFFSET=", 0) != string::npos) { + text_line.erase(0, 11); + su2double AoS_Offset = atof(text_line.c_str()); + + /*--- The offset is in deg ---*/ + const su2double AoS_Current = config->GetAoS() + AoS_Offset; + config->SetAoS_Offset(AoS_Offset); + config->SetAoS(AoS_Current); + + if (AoS_Offset != 0.0) { + if (!config->GetDiscard_InFiles()) { + cout << "WARNING: AoS in the config file (" << config->GetAoS() << " deg.) +\n"; + cout << " AoS offset in mesh file (" << AoS_Offset << " deg.) = " << AoS_Current << " deg." << endl; + } else { + cout << "WARNING: Discarding the AoS offset in the mesh file." << endl; + } + } + continue; + } + + if (!foundNPOIN && text_line.find("NPOIN=", 0) != string::npos) { + text_line.erase(0, 6); + numberOfGlobalPoints = atoi(text_line.c_str()); + + /* If the points were found first, read them, otherwise just consume the lines. */ + if (single_pass && foundNDIME && current_section_idx == 0) { + single_pass_active = true; + ReadPointCoordinates(true); + } else { + for (auto iPoint = 0ul; iPoint < numberOfGlobalPoints; iPoint++) getline(mesh_file, text_line); + } + SectionOrder[current_section_idx++] = FileSection::POINTS; + foundNPOIN = true; + continue; + } + + if (!foundNELEM && text_line.find("NELEM=", 0) != string::npos) { + text_line.erase(0, 6); + numberOfGlobalElements = atoi(text_line.c_str()); + + if (single_pass_active) { + ReadVolumeElementConnectivity(true); + } else { + for (auto iElem = 0ul; iElem < numberOfGlobalElements; iElem++) getline(mesh_file, text_line); + } + SectionOrder[current_section_idx++] = FileSection::ELEMENTS; + foundNELEM = true; + continue; + } + + if (!foundNMARK && text_line.find("NMARK=", 0) != string::npos) { + text_line.erase(0, 6); + numberOfMarkers = atoi(text_line.c_str()); + + if (current_section_idx != 2) { + SU2_MPI::Error("Markers must be listed after points and elements in the SU2 mesh file.", CURRENT_FUNCTION); + } + + if (single_pass_active) ReadSurfaceElementConnectivity(true); + + SectionOrder[current_section_idx++] = FileSection::MARKERS; + foundNMARK = true; + continue; + } + + /* Stop before we reach the next zone then check for errors below. */ + if (text_line.find("IZONE=", 0) != string::npos) { + break; + } + } + + mesh_file.close(); + + /* Throw an error if any of the keywords was not found. */ + if (!foundNDIME) { + SU2_MPI::Error( + "Could not find the keyword \"NDIME=\".\n" + "Check the SU2 ASCII file format.", + CURRENT_FUNCTION); + } + if (!foundNPOIN) { + SU2_MPI::Error( + "Could not find the keyword \"NPOIN=\".\n" + "Check the SU2 ASCII file format.", + CURRENT_FUNCTION); + } + if (!foundNELEM) { + SU2_MPI::Error( + "Could not find the keyword \"NELEM=\".\n" + "Check the SU2 ASCII file format.", + CURRENT_FUNCTION); + } + if (!foundNMARK) { + SU2_MPI::Error( + "Could not find the keyword \"NMARK=\".\n" + "Check the SU2 ASCII file format.", + CURRENT_FUNCTION); + } + + return single_pass_active; +} + +void CSU2ASCIIMeshReaderBase::ReadPointCoordinates(const bool single_pass) { + /* Get a partitioner to help with linear partitioning. */ + CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); + + /* Determine number of local points */ + numberOfLocalPoints = pointPartitioner.GetSizeOnRank(rank); + + /* Prepare our data structure for the point coordinates. */ + localPointCoordinates.resize(dimension); + for (int k = 0; k < dimension; k++) localPointCoordinates[k].reserve(numberOfLocalPoints); + + /*--- Read the point coordinates into our data structure. ---*/ + + while (true) { + string text_line; + if (!single_pass) { + getline(mesh_file, text_line); + if (text_line.find("NPOIN=", 0) == string::npos) continue; + } + + for (unsigned long GlobalIndex = 0; GlobalIndex < numberOfGlobalPoints; ++GlobalIndex) { + if (!actuator_disk) { + getline(mesh_file, text_line); + } else { + if (GlobalIndex < numberOfGlobalPoints - ActDiskNewPoints) { + getline(mesh_file, text_line); + } else { + /* This is a new actuator disk point, so we must construct a + string with the new point's coordinates. */ + ostringstream strsX, strsY, strsZ; + unsigned long BackActDisk_Index = GlobalIndex; + unsigned long LocalIndex = BackActDisk_Index - (numberOfGlobalPoints - ActDiskNewPoints); + strsX.precision(20); + strsY.precision(20); + strsZ.precision(20); + su2double CoordX = CoordXActDisk[LocalIndex]; + strsX << scientific << CoordX; + su2double CoordY = CoordYActDisk[LocalIndex]; + strsY << scientific << CoordY; + su2double CoordZ = CoordZActDisk[LocalIndex]; + strsZ << scientific << CoordZ; + text_line = strsX.str() + "\t" + strsY.str() + "\t" + strsZ.str(); + } + } + + /*--- We only read information for this node if it is owned by this + rank based upon our initial linear partitioning. ---*/ + + passivedouble Coords[3] = {0.0, 0.0, 0.0}; + if (pointPartitioner.IndexBelongsToRank(GlobalIndex, rank)) { + istringstream point_line(text_line); + + /* Store the coordinates more clearly. */ + point_line >> Coords[0]; + point_line >> Coords[1]; + if (dimension == 3) { + point_line >> Coords[2]; + } + + /* Load into the coordinate class data structure. */ + for (unsigned short iDim = 0; iDim < dimension; iDim++) { + localPointCoordinates[iDim].push_back(Coords[iDim]); + } + } + } + break; + } +} + +void CSU2ASCIIMeshReaderBase::ReadVolumeElementConnectivity(const bool single_pass) { + /* Get a partitioner to help with linear partitioning. */ + CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); + + /* Loop over our analytically defined of elements and store only those + that contain a node within our linear partition of points. */ + numberOfLocalElements = 0; + array connectivity{}; + + while (true) { + string text_line; + if (!single_pass) { + if (!getline(mesh_file, text_line)) break; + if (text_line.find("NELEM=", 0) == string::npos) continue; + } + + /*--- Loop over all the volumetric elements and store any element that + contains at least one of an owned node for this rank (i.e., there will + be element redundancy, since multiple ranks will store the same elems + on the boundaries of the initial linear partitioning. ---*/ + + numberOfLocalElements = 0; + + for (unsigned long GlobalIndex = 0; GlobalIndex < numberOfGlobalElements; ++GlobalIndex) { + getline(mesh_file, text_line); + istringstream elem_line(text_line); + + /*--- Decide whether this rank needs each element. ---*/ + + unsigned short VTK_Type; + elem_line >> VTK_Type; + + const auto nPointsElem = nPointsOfElementType(VTK_Type); + + for (unsigned short i = 0; i < nPointsElem; i++) { + elem_line >> connectivity[i]; + } + + if (actuator_disk) { + for (unsigned short i = 0; i < nPointsElem; i++) { + if (ActDisk_Bool[connectivity[i]]) { + su2double Xcg = 0.0; + unsigned long Counter = 0; + for (unsigned short j = 0; j < nPointsElem; j++) { + if (connectivity[j] < numberOfGlobalPoints - ActDiskNewPoints) { + Xcg += CoordXVolumePoint[VolumePoint_Inv[connectivity[j]]]; + Counter++; + } + } + Xcg = Xcg / su2double(Counter); + + if (Counter != 0 && Xcg > Xloc) { + connectivity[i] = ActDiskPoint_Back[connectivity[i]]; + } + } + } + } + + /* Check whether any of the points reside in our linear partition. */ + bool isOwned = false; + for (unsigned short i = 0; i < nPointsElem; i++) { + if (pointPartitioner.IndexBelongsToRank(connectivity[i], rank)) { + isOwned = true; + break; + } + } + + /* If element is owned, we need to store it locally. */ + if (isOwned) { + localVolumeElementConnectivity.push_back(GlobalIndex); + localVolumeElementConnectivity.push_back(VTK_Type); + /// TODO: Use a compressed format. + for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { + localVolumeElementConnectivity.push_back(connectivity[i]); + } + numberOfLocalElements++; + } + } + break; + } +} + +void CSU2ASCIIMeshReaderBase::ReadSurfaceElementConnectivity(const bool single_pass) { + /* We already read in the number of markers with the metadata. */ + surfaceElementConnectivity.resize(numberOfMarkers); + markerNames.resize(numberOfMarkers); + + array connectivity{}; + + /*--- In this routine, the boundary info is read by all ranks, + however, the surface connectivity is still handled by the + master node (and eventually distributed by the master as well). ---*/ + + while (true) { + string text_line; + if (!single_pass) { + if (!getline(mesh_file, text_line)) break; + if (text_line.find("NMARK=", 0) == string::npos) continue; + } + + for (unsigned long iMarker = 0; iMarker < numberOfMarkers; ++iMarker) { + getline(mesh_file, text_line); + text_line.erase(0, 11); + string::size_type position; + + for (unsigned short iChar = 0; iChar < 20; iChar++) { + position = text_line.find(' ', 0); + if (position != string::npos) text_line.erase(position, 1); + position = text_line.find('\r', 0); + if (position != string::npos) text_line.erase(position, 1); + position = text_line.find('\n', 0); + if (position != string::npos) text_line.erase(position, 1); + } + markerNames[iMarker] = text_line; + + bool duplicate = false; + if ((actuator_disk) && (markerNames[iMarker] == config->GetMarker_ActDiskInlet_TagBound(0))) { + duplicate = true; + markerNames[iMarker + 1] = config->GetMarker_ActDiskOutlet_TagBound(0); + } + + /*--- Physical boundaries definition ---*/ + + if (markerNames[iMarker] == "SEND_RECEIVE") { + /*--- Throw an error if we find deprecated references to SEND_RECEIVE + boundaries in the mesh. ---*/ + SU2_MPI::Error( + "Mesh file contains deprecated SEND_RECEIVE marker!\n" + "Please remove any SEND_RECEIVE markers from the SU2 ASCII mesh.", + CURRENT_FUNCTION); + } + + getline(mesh_file, text_line); + text_line.erase(0, 13); + unsigned long nElem_Bound = atoi(text_line.c_str()); + + /*--- Allocate space for elements ---*/ + + for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound; iElem_Bound++) { + getline(mesh_file, text_line); + istringstream bound_line(text_line); + + unsigned short VTK_Type; + bound_line >> VTK_Type; + + const auto nPointsElem = nPointsOfElementType(VTK_Type); + + if (dimension == 3 && VTK_Type == LINE) { + SU2_MPI::Error( + "Line boundary conditions are not possible for 3D calculations.\n" + "Please check the SU2 ASCII mesh file.", + CURRENT_FUNCTION); + } + + for (unsigned short i = 0; i < nPointsElem; i++) { + bound_line >> connectivity[i]; + } + + surfaceElementConnectivity[iMarker].push_back(0); + surfaceElementConnectivity[iMarker].push_back(VTK_Type); + for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { + surfaceElementConnectivity[iMarker].push_back(connectivity[i]); + } + + if (duplicate) { + for (unsigned short i = 0; i < nPointsElem; i++) { + if (ActDisk_Bool[connectivity[i]]) { + connectivity[i] = ActDiskPoint_Back[connectivity[i]]; + } + } + surfaceElementConnectivity[iMarker + 1].push_back(0); + surfaceElementConnectivity[iMarker + 1].push_back(VTK_Type); + for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { + surfaceElementConnectivity[iMarker + 1].push_back(connectivity[i]); + } + } + } + /*--- Increment the counter an extra time if we stored a duplicate. ---*/ + iMarker += duplicate; + } + break; + } + + if (rank != MASTER_NODE) return; + + /*--- Final error check for deprecated periodic BC format. ---*/ + + string text_line; + while (getline(mesh_file, text_line)) { + /*--- Find any periodic transformation information. ---*/ + + if (text_line.find("NPERIODIC=", 0) != string::npos) { + /*--- Read and store the number of transformations. ---*/ + text_line.erase(0, 10); + unsigned short nPeriodic = atoi(text_line.c_str()); + if (nPeriodic - 1 != 0) { + SU2_MPI::Error( + "Mesh file contains deprecated periodic format!\n\n" + "For SU2 v7.0.0 and later, preprocessing of periodic grids by SU2_MSH\n" + "is no longer necessary. Please use the original mesh file (prior to SU2_MSH)\n" + "with the same MARKER_PERIODIC definition in the configuration file.", + CURRENT_FUNCTION); + } + } + + /*--- Stop before we reach the next zone. ---*/ + if (text_line.find("IZONE=", 0) != string::npos) break; + } +} + +void CSU2ASCIIMeshReaderBase::FastForwardToMyZone() { + /*--- If more than one, fast-forward to my zone in the mesh file. ---*/ + + if (nZones == 1 || !config->GetMultizone_Mesh()) return; + + string text_line; + while (getline(mesh_file, text_line)) { + /*--- Search for the current domain ---*/ + if (text_line.find("IZONE=", 0) == string::npos) continue; + text_line.erase(0, 6); + unsigned short jZone = atoi(text_line.c_str()); + if (jZone == myZone + 1) break; + } +} diff --git a/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFEM.cpp b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFEM.cpp new file mode 100644 index 00000000000..78dc46b3f10 --- /dev/null +++ b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFEM.cpp @@ -0,0 +1,360 @@ +/*! + * \file CSU2ASCIIMeshReaderFEM.cpp + * \brief Reads a native SU2 ASCII grid into linear partitions for the + * finite element solver (FEM). + * \author T. Economon, E. van der Weide + * \version 8.1.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CSU2ASCIIMeshReaderFEM.hpp" +#include "../../../include/fem/fem_standard_element.hpp" + +CSU2ASCIIMeshReaderFEM::CSU2ASCIIMeshReaderFEM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CSU2ASCIIMeshReaderBase(val_config, val_iZone, val_nZone) { + /* Read the basic metadata and perform some basic error checks. */ + ReadMetadata(true, val_config); + + /*--- Read the volume connectivity and distribute it + linearly over the MPI ranks. ---*/ + ReadVolumeElementConnectivity(); + + /*--- Read the coordinates of the points that are needed + on this MPI rank. ---*/ + ReadPointCoordinates(); + + /*--- Read the surface connectivity and store the surface elements whose + corresponding volume element is stored on this MPI rank. ---*/ + ReadSurfaceElementConnectivity(); +} + +CSU2ASCIIMeshReaderFEM::~CSU2ASCIIMeshReaderFEM() = default; + +void CSU2ASCIIMeshReaderFEM::ReadPointCoordinates() { + /*--- Loop over the local elements to determine the global + point IDs to be stored on this rank. --*/ + unsigned long ind = 0; + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + /*--- Store the number of grid DOFs for this element and + skip the meta data for this element (5 entries). ---*/ + const unsigned long nDOFsGrid = localVolumeElementConnectivity[ind + 3]; + ind += 5; + + /*--- Copy the connectivity to globalPointIDs. ---*/ + unsigned long* conn = localVolumeElementConnectivity.data() + ind; + ind += nDOFsGrid; + globalPointIDs.insert(globalPointIDs.end(), conn, conn + nDOFsGrid); + } + + /*--- Sort the globalPointIDs and remove the duplicate entries. ---*/ + sort(globalPointIDs.begin(), globalPointIDs.end()); + vector::iterator lastNode; + lastNode = unique(globalPointIDs.begin(), globalPointIDs.end()); + globalPointIDs.erase(lastNode, globalPointIDs.end()); + + /*--- Determine the number of locally stored points. ---*/ + numberOfLocalPoints = globalPointIDs.size(); + + /*--- Prepare our data structure for the point coordinates. ---*/ + localPointCoordinates.resize(dimension); + for (int k = 0; k < dimension; ++k) localPointCoordinates[k].reserve(numberOfLocalPoints); + + /*--- Open the mesh file and jump to our zone. ---*/ + mesh_file.open(meshFilename, ios::in); + FastForwardToMyZone(); + + /*--- Find the section containing the coordinates. ---*/ + string text_line; + while (getline(mesh_file, text_line)) { + string::size_type position = text_line.find("NPOIN=", 0); + if (position != string::npos) break; + } + + /*--- Loop over the global number of points in the grid. ---*/ + for (unsigned long i = 0; i < numberOfGlobalPoints; ++i) { + /*--- Read the line in the grid file. This line must always + be read, even if the point is not stored on this rank. ---*/ + getline(mesh_file, text_line); + + /*--- Determine whether this point must be stored on this rank. ---*/ + if (binary_search(globalPointIDs.begin(), globalPointIDs.end(), i)) { + /*--- Read the coordinates from the string and store them + in localPointCoordinates. ---*/ + istringstream point_line(text_line); + for (unsigned short iDim = 0; iDim < dimension; ++iDim) { + passivedouble Coord; + point_line >> Coord; + localPointCoordinates[iDim].push_back(Coord); + } + } + } + + /*--- Close the mesh file again. ---*/ + mesh_file.close(); +} + +void CSU2ASCIIMeshReaderFEM::ReadVolumeElementConnectivity() { + /* Get a partitioner to help with linear partitioning. */ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /*--- Open the mesh file and jump to our zone. ---*/ + mesh_file.open(meshFilename, ios::in); + FastForwardToMyZone(); + + /*--- Find the section containing the interior elements. ---*/ + string text_line; + while (getline(mesh_file, text_line)) { + string::size_type position = text_line.find("NELEM=", 0); + if (position != string::npos) break; + } + + /*--- Skip the elements, which are read by ranks lower than my rank. ---*/ + const unsigned long firstIndex = elemPartitioner.GetFirstIndexOnRank(rank); + for (unsigned long i = 0; i < firstIndex; ++i) getline(mesh_file, text_line); + + /*--- Determine the number of local elements. ---*/ + numberOfLocalElements = elemPartitioner.GetSizeOnRank(rank); + + /*--- Loop over the elements that must be stored on this rank. ---*/ + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + /*--- Read the line for this element and couple it to an istringstream + to enable the actual reading of the data. ---*/ + getline(mesh_file, text_line); + istringstream elem_line(text_line); + + /*--- Read the value that defines the element type and the polynomial degree + of the geometry and the solution. Extract this info as well. ---*/ + unsigned long typeRead; + elem_line >> typeRead; + unsigned long typeReadErrorMessage = typeRead; + + unsigned short nPolySol, nPolyGrid; + if (typeRead > 10000) { + nPolySol = typeRead / 10000 - 1; + typeRead = typeRead % 10000; + nPolyGrid = typeRead / 100 + 1; + } else { + nPolyGrid = typeRead / 100 + 1; + nPolySol = nPolyGrid; + } + + unsigned short VTK_Type = typeRead % 100; + + /*--- Determine the number of grid DOFs for this element. ---*/ + unsigned short nDOFsGrid = CFEMStandardElementBase::GetNDOFsStatic(VTK_Type, nPolyGrid); + if (nDOFsGrid == 0) { + ostringstream message; + message << "Unknown FEM element type, " << typeReadErrorMessage << ", encountered."; + SU2_MPI::Error(message.str(), CURRENT_FUNCTION); + } + + /*--- Store the data just created in localVolumeElementConnectivity. ---*/ + localVolumeElementConnectivity.push_back(VTK_Type); + localVolumeElementConnectivity.push_back(nPolyGrid); + localVolumeElementConnectivity.push_back(nPolySol); + localVolumeElementConnectivity.push_back(nDOFsGrid); + localVolumeElementConnectivity.push_back(firstIndex + i); // Global elem ID. + + /*--- Store the current index, as this is needed when the + connectivity of linear elements is swapped. ---*/ + const unsigned long ind = localVolumeElementConnectivity.size(); + + /*--- Read the connectivity from elem_line and store it in + localVolumeElementConnectivity. ---*/ + for (unsigned short j = 0; j < nDOFsGrid; ++j) { + unsigned long nodeID; + elem_line >> nodeID; + localVolumeElementConnectivity.push_back(nodeID); + } + + /*--- If a linear element is used, the node numbering for non-simplices + must be adapted. The reason is that compatability with the original + SU2 format is maintained for linear elements, but for the FEM solver + the nodes of the elements are stored row-wise. ---*/ + if (nPolyGrid == 1) { + switch (VTK_Type) { + case QUADRILATERAL: + swap(localVolumeElementConnectivity[ind + 2], localVolumeElementConnectivity[ind + 3]); + break; + + case HEXAHEDRON: + swap(localVolumeElementConnectivity[ind + 2], localVolumeElementConnectivity[ind + 3]); + swap(localVolumeElementConnectivity[ind + 6], localVolumeElementConnectivity[ind + 7]); + break; + + case PYRAMID: + swap(localVolumeElementConnectivity[ind + 2], localVolumeElementConnectivity[ind + 3]); + break; + } + } + } + + /*--- Close the mesh file again. ---*/ + mesh_file.close(); +} + +void CSU2ASCIIMeshReaderFEM::ReadSurfaceElementConnectivity() { + /*--- Determine the vector to hold the faces of the local elements. ---*/ + vector localFaces; + DetermineFacesVolumeElements(localFaces); + + /*--- We already read in the number of markers with the metadata. + Allocate the memory for the marker names, number of local surface + elements and the first index of the surface element connectivity. ---*/ + surfaceElementConnectivity.resize(numberOfMarkers); + markerNames.resize(numberOfMarkers); + numberOfLocalSurfaceElements.resize(numberOfMarkers, 0); + + /*--- Open the mesh file and jump to our zone. ---*/ + mesh_file.open(meshFilename, ios::in); + FastForwardToMyZone(); + + /*--- Find the section containing the markers. ---*/ + string text_line; + while (getline(mesh_file, text_line)) { + string::size_type position = text_line.find("NMARK=", 0); + if (position != string::npos) break; + } + + /*--- Loop over the number of boundary markers. ---*/ + for (unsigned long iMarker = 0; iMarker < numberOfMarkers; ++iMarker) { + /*--- Find the section containing the marker name. ---*/ + while (getline(mesh_file, text_line)) { + string::size_type position = text_line.find("MARKER_TAG=", 0); + if (position != string::npos) break; + } + + /*--- Extract the marker name. Remove spaces returns and tabs + and store the name in markerNames. ---*/ + text_line.erase(0, 11); + + for (unsigned short iChar = 0; iChar < 20; iChar++) { + auto position = text_line.find(" ", 0); + if (position != string::npos) text_line.erase(position, 1); + position = text_line.find("\r", 0); + if (position != string::npos) text_line.erase(position, 1); + position = text_line.find("\n", 0); + if (position != string::npos) text_line.erase(position, 1); + } + markerNames[iMarker] = text_line.c_str(); + + /*--- Find the section containing the number of surface elements + for this marker and determine this number. ---*/ + while (getline(mesh_file, text_line)) { + auto position = text_line.find("MARKER_ELEMS=", 0); + if (position != string::npos) break; + } + + text_line.erase(0, 13); + unsigned long nElem_Bound = atoi(text_line.c_str()); + + /*--- Loop over the surface elements for this marker. ---*/ + for (unsigned long i = 0; i < nElem_Bound; ++i) { + /*--- Read the line for this element and couple it to an istringstream + to enable the actual reading of the data. ---*/ + getline(mesh_file, text_line); + istringstream bound_line(text_line); + + /*--- Determine the element type and polynomial degree. ---*/ + unsigned long typeRead; + bound_line >> typeRead; + + const unsigned short nPolyGrid = typeRead / 100 + 1; + const unsigned short VTK_Type = typeRead % 100; + + /*--- Make a distinction between the possible element surface types and + determine the corner points in local numbering of the element. ---*/ + const unsigned short nDOFEdgeGrid = nPolyGrid + 1; + + unsigned short nDOFsGrid = 0; + CFaceOfElement thisFace; + thisFace.cornerPoints[0] = 0; + thisFace.cornerPoints[1] = nPolyGrid; + + switch (VTK_Type) { + case LINE: + nDOFsGrid = nDOFEdgeGrid; + thisFace.nCornerPoints = 2; + break; + + case TRIANGLE: + nDOFsGrid = nDOFEdgeGrid * (nDOFEdgeGrid + 1) / 2; + thisFace.nCornerPoints = 3; + thisFace.cornerPoints[2] = nDOFsGrid - 1; + break; + + case QUADRILATERAL: + nDOFsGrid = nDOFEdgeGrid * nDOFEdgeGrid; + thisFace.nCornerPoints = 4; + thisFace.cornerPoints[2] = static_cast(nPolyGrid) * nDOFEdgeGrid; + thisFace.cornerPoints[3] = nDOFsGrid - 1; + break; + + default: + ostringstream message; + message << "Unknown FEM boundary element value, " << typeRead << ", in " << meshFilename; + SU2_MPI::Error(message.str(), CURRENT_FUNCTION); + } + + /*--- Read the connectivity information. ---*/ + vector connFace(nDOFsGrid); + for (unsigned short j = 0; j < nDOFsGrid; ++j) bound_line >> connFace[j]; + + /*--- If a linear quadrilateral is used, the node numbering must be adapted. + The reason is that compatability with the original SU2 format is + maintained for linear elements, but for the FEM solver the nodes + of the elements are stored row-wise. ---*/ + if ((nPolyGrid == 1) && (VTK_Type == QUADRILATERAL)) swap(connFace[2], connFace[3]); + + /*--- Convert the local numbering of thisFace to global numbering + and create a unique numbering of corner points. ---*/ + for (unsigned short j = 0; j < thisFace.nCornerPoints; ++j) + thisFace.cornerPoints[j] = connFace[thisFace.cornerPoints[j]]; + thisFace.CreateUniqueNumbering(); + + /*--- Check if this boundary face must be stored on this rank. ---*/ + vector::iterator low; + low = lower_bound(localFaces.begin(), localFaces.end(), thisFace); + if (low != localFaces.end()) { + if (!(thisFace < *low)) { + /*--- Update the counter for this boundary marker and store + the meta data in surfaceElementConnectivity. ---*/ + ++numberOfLocalSurfaceElements[iMarker]; + + surfaceElementConnectivity[iMarker].push_back(VTK_Type); + surfaceElementConnectivity[iMarker].push_back(nPolyGrid); + surfaceElementConnectivity[iMarker].push_back(nDOFsGrid); + surfaceElementConnectivity[iMarker].push_back(i); // Global surface elem ID. + surfaceElementConnectivity[iMarker].push_back(low->elemID0); // Global volume elem ID. + + /*--- Copy the connectivity to surfaceElementConnectivity. ---*/ + surfaceElementConnectivity[iMarker].insert(surfaceElementConnectivity[iMarker].end(), connFace.begin(), + connFace.end()); + } + } + } + } + + /*--- Close the mesh file again. ---*/ + mesh_file.close(); +} diff --git a/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFVM.cpp index e0ebfcc5acb..6801123dd03 100644 --- a/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFVM.cpp +++ b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderFVM.cpp @@ -26,14 +26,10 @@ * License along with SU2. If not, see . */ -#include "../../../include/toolboxes/CLinearPartitioner.hpp" #include "../../../include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp" CSU2ASCIIMeshReaderFVM::CSU2ASCIIMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) - : CMeshReaderFVM(val_config, val_iZone, val_nZone), - myZone(val_iZone), - nZones(val_nZone), - meshFilename(config->GetMesh_FileName()) { + : CSU2ASCIIMeshReaderBase(val_config, val_iZone, val_nZone) { actuator_disk = (((config->GetnMarker_ActDiskInlet() != 0) || (config->GetnMarker_ActDiskOutlet() != 0)) && ((config->GetKind_SU2() == SU2_COMPONENT::SU2_CFD) || ((config->GetKind_SU2() == SU2_COMPONENT::SU2_DEF) && (config->GetActDisk_SU2_DEF())))); @@ -75,192 +71,7 @@ CSU2ASCIIMeshReaderFVM::CSU2ASCIIMeshReaderFVM(CConfig* val_config, unsigned sho mesh_file.close(); } -bool CSU2ASCIIMeshReaderFVM::ReadMetadata(const bool single_pass, CConfig* config) { - const bool harmonic_balance = config->GetTime_Marching() == TIME_MARCHING::HARMONIC_BALANCE; - const bool multizone_file = config->GetMultizone_Mesh(); - - /*--- Open grid file ---*/ - - mesh_file.open(meshFilename); - if (mesh_file.fail()) { - SU2_MPI::Error( - "Error opening SU2 ASCII grid.\n" - "Check that the file exists.", - CURRENT_FUNCTION); - } - - /*--- If more than one, find the curent zone in the mesh file. ---*/ - - string text_line; - if ((nZones > 1 && multizone_file) || harmonic_balance) { - if (harmonic_balance) { - if (rank == MASTER_NODE) cout << "Reading time instance " << config->GetiInst() + 1 << "." << endl; - } else { - bool foundZone = false; - while (getline(mesh_file, text_line)) { - /*--- Search for the current domain ---*/ - if (text_line.find("IZONE=", 0) != string::npos) { - text_line.erase(0, 6); - unsigned short jZone = atoi(text_line.c_str()); - if (jZone == myZone + 1) { - if (rank == MASTER_NODE) cout << "Reading zone " << myZone << " from native SU2 ASCII mesh." << endl; - foundZone = true; - break; - } - } - } - if (!foundZone) { - SU2_MPI::Error( - "Could not find the IZONE= keyword or the zone contents.\n" - "Check the SU2 ASCII file format.", - CURRENT_FUNCTION); - } - } - } - - /*--- Read the metadata: problem dimension, offsets for angle - of attack and angle of sideslip, global points, global elements, - and number of markers. Perform error checks as we go. ---*/ - - bool foundNDIME = false, foundNPOIN = false; - bool foundNELEM = false, foundNMARK = false; - - int current_section_idx = 0; - bool single_pass_active = false; - - while (getline(mesh_file, text_line)) { - /*--- Read the dimension of the problem ---*/ - - if (!foundNDIME && text_line.find("NDIME=", 0) != string::npos) { - text_line.erase(0, 6); - dimension = atoi(text_line.c_str()); - foundNDIME = true; - continue; - } - - /*--- The AoA and AoS offset values are optional. ---*/ - - if (text_line.find("AOA_OFFSET=", 0) != string::npos) { - text_line.erase(0, 11); - su2double AoA_Offset = atof(text_line.c_str()); - - /*--- The offset is in deg ---*/ - const su2double AoA_Current = config->GetAoA() + AoA_Offset; - config->SetAoA_Offset(AoA_Offset); - config->SetAoA(AoA_Current); - - if (AoA_Offset != 0.0) { - if (!config->GetDiscard_InFiles()) { - cout << "WARNING: AoA in the config file (" << config->GetAoA() << " deg.) +\n"; - cout << " AoA offset in mesh file (" << AoA_Offset << " deg.) = " << AoA_Current << " deg." << endl; - } else { - cout << "WARNING: Discarding the AoA offset in the mesh file." << endl; - } - } - continue; - } - - if (text_line.find("AOS_OFFSET=", 0) != string::npos) { - text_line.erase(0, 11); - su2double AoS_Offset = atof(text_line.c_str()); - - /*--- The offset is in deg ---*/ - const su2double AoS_Current = config->GetAoS() + AoS_Offset; - config->SetAoS_Offset(AoS_Offset); - config->SetAoS(AoS_Current); - - if (AoS_Offset != 0.0) { - if (!config->GetDiscard_InFiles()) { - cout << "WARNING: AoS in the config file (" << config->GetAoS() << " deg.) +\n"; - cout << " AoS offset in mesh file (" << AoS_Offset << " deg.) = " << AoS_Current << " deg." << endl; - } else { - cout << "WARNING: Discarding the AoS offset in the mesh file." << endl; - } - } - continue; - } - - if (!foundNPOIN && text_line.find("NPOIN=", 0) != string::npos) { - text_line.erase(0, 6); - numberOfGlobalPoints = atoi(text_line.c_str()); - - /* If the points were found first, read them, otherwise just consume the lines. */ - if (single_pass && foundNDIME && current_section_idx == 0) { - single_pass_active = true; - ReadPointCoordinates(true); - } else { - for (auto iPoint = 0ul; iPoint < numberOfGlobalPoints; iPoint++) getline(mesh_file, text_line); - } - SectionOrder[current_section_idx++] = FileSection::POINTS; - foundNPOIN = true; - continue; - } - - if (!foundNELEM && text_line.find("NELEM=", 0) != string::npos) { - text_line.erase(0, 6); - numberOfGlobalElements = atoi(text_line.c_str()); - - if (single_pass_active) { - ReadVolumeElementConnectivity(true); - } else { - for (auto iElem = 0ul; iElem < numberOfGlobalElements; iElem++) getline(mesh_file, text_line); - } - SectionOrder[current_section_idx++] = FileSection::ELEMENTS; - foundNELEM = true; - continue; - } - - if (!foundNMARK && text_line.find("NMARK=", 0) != string::npos) { - text_line.erase(0, 6); - numberOfMarkers = atoi(text_line.c_str()); - - if (current_section_idx != 2) { - SU2_MPI::Error("Markers must be listed after points and elements in the SU2 mesh file.", CURRENT_FUNCTION); - } - - if (single_pass_active) ReadSurfaceElementConnectivity(true); - - SectionOrder[current_section_idx++] = FileSection::MARKERS; - foundNMARK = true; - continue; - } - - /* Stop before we reach the next zone then check for errors below. */ - if (text_line.find("IZONE=", 0) != string::npos) { - break; - } - } - - mesh_file.close(); - - /* Throw an error if any of the keywords was not found. */ - if (!foundNDIME) { - SU2_MPI::Error( - "Could not find the keyword \"NDIME=\".\n" - "Check the SU2 ASCII file format.", - CURRENT_FUNCTION); - } - if (!foundNPOIN) { - SU2_MPI::Error( - "Could not find the keyword \"NPOIN=\".\n" - "Check the SU2 ASCII file format.", - CURRENT_FUNCTION); - } - if (!foundNELEM) { - SU2_MPI::Error( - "Could not find the keyword \"NELEM=\".\n" - "Check the SU2 ASCII file format.", - CURRENT_FUNCTION); - } - if (!foundNMARK) { - SU2_MPI::Error( - "Could not find the keyword \"NMARK=\".\n" - "Check the SU2 ASCII file format.", - CURRENT_FUNCTION); - } - - return single_pass_active; -} +CSU2ASCIIMeshReaderFVM::~CSU2ASCIIMeshReaderFVM() = default; void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { /*--- Actuator disk preprocesing ---*/ @@ -594,14 +405,14 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { elem_line >> connectivity[1]; elem_line >> connectivity[2]; InElem = false; - for (unsigned long i = 0; i < (unsigned long)N_POINTS_TRIANGLE; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_TRIANGLE); i++) { if (ActDisk_Bool[connectivity[i]]) { InElem = true; break; } } if (InElem) { - for (unsigned long i = 0; i < (unsigned long)N_POINTS_TRIANGLE; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_TRIANGLE); i++) { VolumePoint.push_back(connectivity[i]); } } @@ -612,14 +423,14 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { elem_line >> connectivity[2]; elem_line >> connectivity[3]; InElem = false; - for (unsigned long i = 0; i < (unsigned long)N_POINTS_QUADRILATERAL; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_QUADRILATERAL); i++) { if (ActDisk_Bool[connectivity[i]]) { InElem = true; break; } } if (InElem) { - for (unsigned long i = 0; i < (unsigned long)N_POINTS_QUADRILATERAL; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_QUADRILATERAL); i++) { VolumePoint.push_back(connectivity[i]); } } @@ -630,14 +441,14 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { elem_line >> connectivity[2]; elem_line >> connectivity[3]; InElem = false; - for (unsigned long i = 0; i < (unsigned long)N_POINTS_TETRAHEDRON; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_TETRAHEDRON); i++) { if (ActDisk_Bool[connectivity[i]]) { InElem = true; break; } } if (InElem) { - for (unsigned long i = 0; i < (unsigned long)N_POINTS_TETRAHEDRON; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_TETRAHEDRON); i++) { VolumePoint.push_back(connectivity[i]); } } @@ -652,14 +463,14 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { elem_line >> connectivity[6]; elem_line >> connectivity[7]; InElem = false; - for (unsigned long i = 0; i < (unsigned long)N_POINTS_HEXAHEDRON; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_HEXAHEDRON); i++) { if (ActDisk_Bool[connectivity[i]]) { InElem = true; break; } } if (InElem) { - for (unsigned long i = 0; i < (unsigned long)N_POINTS_HEXAHEDRON; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_HEXAHEDRON); i++) { VolumePoint.push_back(connectivity[i]); } } @@ -672,14 +483,14 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { elem_line >> connectivity[4]; elem_line >> connectivity[5]; InElem = false; - for (unsigned long i = 0; i < (unsigned long)N_POINTS_PRISM; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_PRISM); i++) { if (ActDisk_Bool[connectivity[i]]) { InElem = true; break; } } if (InElem) { - for (unsigned long i = 0; i < (unsigned long)N_POINTS_PRISM; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_PRISM); i++) { VolumePoint.push_back(connectivity[i]); } } @@ -691,14 +502,14 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { elem_line >> connectivity[3]; elem_line >> connectivity[4]; InElem = false; - for (unsigned long i = 0; i < (unsigned long)N_POINTS_PYRAMID; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_PYRAMID); i++) { if (ActDisk_Bool[connectivity[i]]) { InElem = true; break; } } if (InElem) { - for (unsigned long i = 0; i < (unsigned long)N_POINTS_PYRAMID; i++) { + for (unsigned long i = 0; i < static_cast(N_POINTS_PYRAMID); i++) { VolumePoint.push_back(connectivity[i]); } } @@ -783,297 +594,3 @@ void CSU2ASCIIMeshReaderFVM::SplitActuatorDiskSurface() { mesh_file.close(); } - -void CSU2ASCIIMeshReaderFVM::ReadPointCoordinates(const bool single_pass) { - /* Get a partitioner to help with linear partitioning. */ - CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); - - /* Determine number of local points */ - numberOfLocalPoints = pointPartitioner.GetSizeOnRank(rank); - - /* Prepare our data structure for the point coordinates. */ - localPointCoordinates.resize(dimension); - for (int k = 0; k < dimension; k++) localPointCoordinates[k].reserve(numberOfLocalPoints); - - /*--- Read the point coordinates into our data structure. ---*/ - - while (true) { - string text_line; - if (!single_pass) { - getline(mesh_file, text_line); - if (text_line.find("NPOIN=", 0) == string::npos) continue; - } - - for (unsigned long GlobalIndex = 0; GlobalIndex < numberOfGlobalPoints; ++GlobalIndex) { - if (!actuator_disk) { - getline(mesh_file, text_line); - } else { - if (GlobalIndex < numberOfGlobalPoints - ActDiskNewPoints) { - getline(mesh_file, text_line); - } else { - /* This is a new actuator disk point, so we must construct a - string with the new point's coordinates. */ - ostringstream strsX, strsY, strsZ; - unsigned long BackActDisk_Index = GlobalIndex; - unsigned long LocalIndex = BackActDisk_Index - (numberOfGlobalPoints - ActDiskNewPoints); - strsX.precision(20); - strsY.precision(20); - strsZ.precision(20); - su2double CoordX = CoordXActDisk[LocalIndex]; - strsX << scientific << CoordX; - su2double CoordY = CoordYActDisk[LocalIndex]; - strsY << scientific << CoordY; - su2double CoordZ = CoordZActDisk[LocalIndex]; - strsZ << scientific << CoordZ; - text_line = strsX.str() + "\t" + strsY.str() + "\t" + strsZ.str(); - } - } - - /*--- We only read information for this node if it is owned by this - rank based upon our initial linear partitioning. ---*/ - - passivedouble Coords[3] = {0.0, 0.0, 0.0}; - if (pointPartitioner.IndexBelongsToRank(GlobalIndex, rank)) { - istringstream point_line(text_line); - - /* Store the coordinates more clearly. */ - point_line >> Coords[0]; - point_line >> Coords[1]; - if (dimension == 3) { - point_line >> Coords[2]; - } - - /* Load into the coordinate class data structure. */ - for (unsigned short iDim = 0; iDim < dimension; iDim++) { - localPointCoordinates[iDim].push_back(Coords[iDim]); - } - } - } - break; - } -} - -void CSU2ASCIIMeshReaderFVM::ReadVolumeElementConnectivity(const bool single_pass) { - /* Get a partitioner to help with linear partitioning. */ - CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); - - /* Loop over our analytically defined of elements and store only those - that contain a node within our linear partition of points. */ - numberOfLocalElements = 0; - array connectivity{}; - - while (true) { - string text_line; - if (!single_pass) { - if (!getline(mesh_file, text_line)) break; - if (text_line.find("NELEM=", 0) == string::npos) continue; - } - - /*--- Loop over all the volumetric elements and store any element that - contains at least one of an owned node for this rank (i.e., there will - be element redundancy, since multiple ranks will store the same elems - on the boundaries of the initial linear partitioning. ---*/ - - numberOfLocalElements = 0; - - for (unsigned long GlobalIndex = 0; GlobalIndex < numberOfGlobalElements; ++GlobalIndex) { - getline(mesh_file, text_line); - istringstream elem_line(text_line); - - /*--- Decide whether this rank needs each element. ---*/ - - unsigned short VTK_Type; - elem_line >> VTK_Type; - - const auto nPointsElem = nPointsOfElementType(VTK_Type); - - for (unsigned short i = 0; i < nPointsElem; i++) { - elem_line >> connectivity[i]; - } - - if (actuator_disk) { - for (unsigned short i = 0; i < nPointsElem; i++) { - if (ActDisk_Bool[connectivity[i]]) { - su2double Xcg = 0.0; - unsigned long Counter = 0; - for (unsigned short j = 0; j < nPointsElem; j++) { - if (connectivity[j] < numberOfGlobalPoints - ActDiskNewPoints) { - Xcg += CoordXVolumePoint[VolumePoint_Inv[connectivity[j]]]; - Counter++; - } - } - Xcg = Xcg / su2double(Counter); - - if (Counter != 0 && Xcg > Xloc) { - connectivity[i] = ActDiskPoint_Back[connectivity[i]]; - } - } - } - } - - /* Check whether any of the points reside in our linear partition. */ - bool isOwned = false; - for (unsigned short i = 0; i < nPointsElem; i++) { - if (pointPartitioner.IndexBelongsToRank(connectivity[i], rank)) { - isOwned = true; - break; - } - } - - /* If element is owned, we need to store it locally. */ - if (isOwned) { - localVolumeElementConnectivity.push_back(GlobalIndex); - localVolumeElementConnectivity.push_back(VTK_Type); - /// TODO: Use a compressed format. - for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { - localVolumeElementConnectivity.push_back(connectivity[i]); - } - numberOfLocalElements++; - } - } - break; - } -} - -void CSU2ASCIIMeshReaderFVM::ReadSurfaceElementConnectivity(const bool single_pass) { - /* We already read in the number of markers with the metadata. */ - surfaceElementConnectivity.resize(numberOfMarkers); - markerNames.resize(numberOfMarkers); - - array connectivity{}; - - /*--- In this routine, the boundary info is read by all ranks, - however, the surface connectivity is still handled by the - master node (and eventually distributed by the master as well). ---*/ - - while (true) { - string text_line; - if (!single_pass) { - if (!getline(mesh_file, text_line)) break; - if (text_line.find("NMARK=", 0) == string::npos) continue; - } - - for (unsigned short iMarker = 0; iMarker < numberOfMarkers; ++iMarker) { - getline(mesh_file, text_line); - text_line.erase(0, 11); - string::size_type position; - - for (unsigned short iChar = 0; iChar < 20; iChar++) { - position = text_line.find(' ', 0); - if (position != string::npos) text_line.erase(position, 1); - position = text_line.find('\r', 0); - if (position != string::npos) text_line.erase(position, 1); - position = text_line.find('\n', 0); - if (position != string::npos) text_line.erase(position, 1); - } - markerNames[iMarker] = text_line; - - bool duplicate = false; - if ((actuator_disk) && (markerNames[iMarker] == config->GetMarker_ActDiskInlet_TagBound(0))) { - duplicate = true; - markerNames[iMarker + 1] = config->GetMarker_ActDiskOutlet_TagBound(0); - } - - /*--- Physical boundaries definition ---*/ - - if (markerNames[iMarker] == "SEND_RECEIVE") { - /*--- Throw an error if we find deprecated references to SEND_RECEIVE - boundaries in the mesh. ---*/ - SU2_MPI::Error( - "Mesh file contains deprecated SEND_RECEIVE marker!\n" - "Please remove any SEND_RECEIVE markers from the SU2 ASCII mesh.", - CURRENT_FUNCTION); - } - - getline(mesh_file, text_line); - text_line.erase(0, 13); - unsigned long nElem_Bound = atoi(text_line.c_str()); - - /*--- Allocate space for elements ---*/ - - for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound; iElem_Bound++) { - getline(mesh_file, text_line); - istringstream bound_line(text_line); - - unsigned short VTK_Type; - bound_line >> VTK_Type; - - const auto nPointsElem = nPointsOfElementType(VTK_Type); - - if (dimension == 3 && VTK_Type == LINE) { - SU2_MPI::Error( - "Line boundary conditions are not possible for 3D calculations.\n" - "Please check the SU2 ASCII mesh file.", - CURRENT_FUNCTION); - } - - for (unsigned short i = 0; i < nPointsElem; i++) { - bound_line >> connectivity[i]; - } - - surfaceElementConnectivity[iMarker].push_back(0); - surfaceElementConnectivity[iMarker].push_back(VTK_Type); - for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { - surfaceElementConnectivity[iMarker].push_back(connectivity[i]); - } - - if (duplicate) { - for (unsigned short i = 0; i < nPointsElem; i++) { - if (ActDisk_Bool[connectivity[i]]) { - connectivity[i] = ActDiskPoint_Back[connectivity[i]]; - } - } - surfaceElementConnectivity[iMarker + 1].push_back(0); - surfaceElementConnectivity[iMarker + 1].push_back(VTK_Type); - for (unsigned short i = 0; i < N_POINTS_HEXAHEDRON; i++) { - surfaceElementConnectivity[iMarker + 1].push_back(connectivity[i]); - } - } - } - /*--- Increment the counter an extra time if we stored a duplicate. ---*/ - iMarker += duplicate; - } - break; - } - - if (rank != MASTER_NODE) return; - - /*--- Final error check for deprecated periodic BC format. ---*/ - - string text_line; - while (getline(mesh_file, text_line)) { - /*--- Find any periodic transformation information. ---*/ - - if (text_line.find("NPERIODIC=", 0) != string::npos) { - /*--- Read and store the number of transformations. ---*/ - text_line.erase(0, 10); - unsigned short nPeriodic = atoi(text_line.c_str()); - if (nPeriodic - 1 != 0) { - SU2_MPI::Error( - "Mesh file contains deprecated periodic format!\n\n" - "For SU2 v7.0.0 and later, preprocessing of periodic grids by SU2_MSH\n" - "is no longer necessary. Please use the original mesh file (prior to SU2_MSH)\n" - "with the same MARKER_PERIODIC definition in the configuration file.", - CURRENT_FUNCTION); - } - } - - /*--- Stop before we reach the next zone. ---*/ - if (text_line.find("IZONE=", 0) != string::npos) break; - } -} - -void CSU2ASCIIMeshReaderFVM::FastForwardToMyZone() { - /*--- If more than one, fast-forward to my zone in the mesh file. ---*/ - - if (nZones == 1 || !config->GetMultizone_Mesh()) return; - - string text_line; - while (getline(mesh_file, text_line)) { - /*--- Search for the current domain ---*/ - if (text_line.find("IZONE=", 0) == string::npos) continue; - text_line.erase(0, 6); - unsigned short jZone = atoi(text_line.c_str()); - if (jZone == myZone + 1) break; - } -} diff --git a/Common/src/geometry/meshreader/meson.build b/Common/src/geometry/meshreader/meson.build index 3f1e12595de..543bdfcf97a 100644 --- a/Common/src/geometry/meshreader/meson.build +++ b/Common/src/geometry/meshreader/meson.build @@ -1,5 +1,12 @@ -common_src += files(['CBoxMeshReaderFVM.cpp', +common_src += files(['CBoxMeshReaderFEM.cpp', + 'CBoxMeshReaderFVM.cpp', + 'CCGNSElementType.cpp', + 'CCGNSMeshReaderBase.cpp', + 'CCGNSMeshReaderFEM.cpp', 'CCGNSMeshReaderFVM.cpp', - 'CMeshReaderFVM.cpp', + 'CMeshReaderBase.cpp', + 'CRectangularMeshReaderFEM.cpp', 'CRectangularMeshReaderFVM.cpp', + 'CSU2ASCIIMeshReaderBase.cpp', + 'CSU2ASCIIMeshReaderFEM.cpp', 'CSU2ASCIIMeshReaderFVM.cpp']) diff --git a/Common/src/geometry/primal_grid/CPrimalGridBoundFEM.cpp b/Common/src/geometry/primal_grid/CPrimalGridBoundFEM.cpp index cebb3aa2ebf..43fd3606a09 100644 --- a/Common/src/geometry/primal_grid/CPrimalGridBoundFEM.cpp +++ b/Common/src/geometry/primal_grid/CPrimalGridBoundFEM.cpp @@ -27,27 +27,17 @@ #include "../../../include/geometry/primal_grid/CPrimalGridBoundFEM.hpp" -CPrimalGridBoundFEM::CPrimalGridBoundFEM(unsigned long val_elemGlobalID, unsigned long val_domainElementID, - unsigned short val_VTK_Type, unsigned short val_nPolyGrid, - unsigned short val_nDOFsGrid, std::vector& val_nodes) - : CPrimalGrid(true, val_nDOFsGrid, 1) { - /*--- Store the integer data in the member variables of this object. ---*/ - VTK_Type = val_VTK_Type; - - nPolyGrid = val_nPolyGrid; - nDOFsGrid = val_nDOFsGrid; - - boundElemIDGlobal = val_elemGlobalID; - GlobalIndex_DomainElement = val_domainElementID; - - /*--- Copy face structure of the element from val_nodes. ---*/ - - for (unsigned short i = 0; i < nDOFsGrid; i++) Nodes[i] = val_nodes[i]; - - /*--- For a linear quadrilateral the two last node numbers must be swapped, - such that the element numbering is consistent with the FEM solver. ---*/ - - if (nPolyGrid == 1 && VTK_Type == QUADRILATERAL) std::swap(Nodes[2], Nodes[3]); +CPrimalGridBoundFEM::CPrimalGridBoundFEM(const unsigned long* dataElem) : CPrimalGrid(true, dataElem[2], 1) { + /*--- Store the meta data for this element. ---*/ + VTK_Type = static_cast(dataElem[0]); + nPolyGrid = static_cast(dataElem[1]); + nDOFsGrid = static_cast(dataElem[2]); + boundElemIDGlobal = dataElem[3]; + GlobalIndex_DomainElement = dataElem[4]; + + /*--- Allocate the memory for the global nodes of the element to define + the geometry and copy them from val_nodes. ---*/ + for (unsigned short i = 0; i < nDOFsGrid; i++) Nodes[i] = dataElem[i + 5]; } void CPrimalGridBoundFEM::GetLocalCornerPointsFace(unsigned short elementType, unsigned short nPoly, diff --git a/Common/src/geometry/primal_grid/CPrimalGridFEM.cpp b/Common/src/geometry/primal_grid/CPrimalGridFEM.cpp index 1d1405ddbb8..738c443f3e8 100644 --- a/Common/src/geometry/primal_grid/CPrimalGridFEM.cpp +++ b/Common/src/geometry/primal_grid/CPrimalGridFEM.cpp @@ -26,67 +26,27 @@ */ #include "../../../include/geometry/primal_grid/CPrimalGridFEM.hpp" +#include "../../../include/fem/fem_standard_element.hpp" -CPrimalGridFEM::CPrimalGridFEM(unsigned long val_elemGlobalID, unsigned short val_VTK_Type, - unsigned short val_nPolyGrid, unsigned short val_nPolySol, unsigned short val_nDOFsGrid, - unsigned short val_nDOFsSol, unsigned long val_offDOfsSol, std::istringstream& elem_line) - : CPrimalGrid(true, val_nDOFsGrid, nFacesOfElementType(val_VTK_Type)) { - /*--- Store the integer data in the member variables of this object. ---*/ - VTK_Type = val_VTK_Type; - nFaces = nFacesOfElementType(VTK_Type); - - nPolyGrid = val_nPolyGrid; - nPolySol = val_nPolySol; - nDOFsGrid = val_nDOFsGrid; - nDOFsSol = val_nDOFsSol; - - elemIDGlobal = val_elemGlobalID; - offsetDOFsSolGlobal = val_offDOfsSol; - - /*--- Read face structure of the element from elem_line. ---*/ - - for (unsigned short i = 0; i < nDOFsGrid; i++) elem_line >> Nodes[i]; - - /*--- If a linear element is used, the node numbering for non-simplices - must be adapted. The reason is that compatability with the original - SU2 format is maintained for linear elements, but for the FEM solver - the nodes of the elements are stored row-wise. ---*/ - if (nPolyGrid == 1) { - switch (VTK_Type) { - case QUADRILATERAL: - std::swap(Nodes[2], Nodes[3]); - break; +CPrimalGridFEM::CPrimalGridFEM(const unsigned long* dataElem, unsigned long& offsetSolDOFs) + : CPrimalGrid(true, dataElem[3], nFacesOfElementType(dataElem[0])) { + /*--- Store the meta data for this element. ---*/ + VTK_Type = static_cast(dataElem[0]); + nPolyGrid = static_cast(dataElem[1]); + nPolySol = static_cast(dataElem[2]); + nDOFsGrid = static_cast(dataElem[3]); + nDOFsSol = CFEMStandardElementBase::GetNDOFsStatic(VTK_Type, nPolySol); + elemIDGlobal = dataElem[4]; - case HEXAHEDRON: - std::swap(Nodes[2], Nodes[3]); - std::swap(Nodes[6], Nodes[7]); - break; + offsetDOFsSolGlobal = offsetSolDOFs; + offsetSolDOFs += nDOFsSol; - case PYRAMID: - std::swap(Nodes[2], Nodes[3]); - break; - } - } -} - -CPrimalGridFEM::CPrimalGridFEM(unsigned long val_elemGlobalID, unsigned short val_VTK_Type, - unsigned short val_nPolyGrid, unsigned short val_nPolySol, unsigned short val_nDOFsGrid, - unsigned short val_nDOFsSol, unsigned long val_offDOfsSol, const unsigned long* connGrid) - : CPrimalGrid(true, val_nDOFsGrid, nFacesOfElementType(val_VTK_Type)) { - /*--- Store the integer data in the member variables of this object. ---*/ - VTK_Type = val_VTK_Type; nFaces = nFacesOfElementType(VTK_Type); - nPolyGrid = val_nPolyGrid; - nPolySol = val_nPolySol; - nDOFsGrid = val_nDOFsGrid; - nDOFsSol = val_nDOFsSol; - - elemIDGlobal = val_elemGlobalID; - offsetDOFsSolGlobal = val_offDOfsSol; + /*--- Allocate the memory for the global nodes of the element to define + the geometry and copy the data from dataElem. ---*/ - /*--- Copy face structure of the element from connGrid. ---*/ - for (unsigned short i = 0; i < nDOFsGrid; i++) Nodes[i] = connGrid[i]; + for (unsigned short i = 0; i < nDOFsGrid; ++i) Nodes[i] = dataElem[i + 5]; } void CPrimalGridFEM::GetLocalCornerPointsAllFaces(unsigned short elementType, unsigned short nPoly,