diff --git a/src/coreComponents/common/MpiWrapper.cpp b/src/coreComponents/common/MpiWrapper.cpp index b80d0aa802a..5dd157a3d53 100644 --- a/src/coreComponents/common/MpiWrapper.cpp +++ b/src/coreComponents/common/MpiWrapper.cpp @@ -462,6 +462,26 @@ int MpiWrapper::nodeCommSize() return nodeCommSize; } +void MpiWrapper::gatherStringOnRank0( string_view rankStr, std::function< void(string_view) > && func ) +{ + std::vector< buffer_unit_type > localbuffer; + localbuffer.reserve( rankStr.size()); + localbuffer.insert( localbuffer.end(), rankStr.begin(), rankStr.end()); + auto [globalLogRecords, counts, offsets] = + MpiWrapper::gatherBufferRank0< std::vector< buffer_unit_type > >( localbuffer ); + if( MpiWrapper::commRank() == 0 ) + { + for( integer rankId = 0; rankId < MpiWrapper::commSize(); ++rankId ) + { + if( counts[rankId] > 0 ) + { + func( string( globalLogRecords.begin() + offsets[rankId], + globalLogRecords.begin() + offsets[rankId]+ counts[rankId] ) ); + } + } + } +} + namespace internal { diff --git a/src/coreComponents/common/MpiWrapper.hpp b/src/coreComponents/common/MpiWrapper.hpp index fd3264a5317..24f94711878 100644 --- a/src/coreComponents/common/MpiWrapper.hpp +++ b/src/coreComponents/common/MpiWrapper.hpp @@ -331,6 +331,53 @@ struct MpiWrapper */ static int nodeCommSize(); + /** + * @brief Structure holding the result from all the gather operation + * @tparam CONTAINER The container type holding the data. + * The underlying storage in CONTAINER must be contiguous + */ + template< typename CONTAINER > + struct GatherResult + { + // Collected data which must be trivially copyable + CONTAINER data; + // Number of elements per rank + stdVector< integer > counts; + // Starting index for each rank in 'data' + stdVector< integer > offsets; + }; + + /** + * @brief Gather buffers of varying sizes from all ranks to rank 0. + * @tparam CONTAINER The container type holding the data. + * @tparam VALUE_T The trivially copyable underlying data type (deduced automatically). + * @param localBuffer The local buffer to be gathered on rank 0. + * @return A struct containing: + * - 'data': all the gathered data on rank 0 + * - 'counts': number of elements for each rank + * - 'offsets': starting index for each rank in 'data' + */ + template< + typename CONTAINER, + typename VALUE_T = typename CONTAINER::value_type, + typename = std::enable_if_t< + std::is_trivially_copyable_v< VALUE_T > && + std::is_same_v< decltype(std::declval< CONTAINER >().data()), VALUE_T * > && + std::is_same_v< decltype(std::declval< CONTAINER >().size()), std::size_t > + > + > + static GatherResult< CONTAINER > + gatherBufferRank0( CONTAINER const & localBuffer ); + + /** + * @brief Gather srting from all ranks to rank 0 + * @tparam FUNC Callable type invoked as void(string_view) for each non-empty rank string. + * @param str The local string to send from the calling rank. + * @param func Callback invoked on rank 0 for each non-empty received string. + */ + static void gatherStringOnRank0( string_view str, + std::function< void(string_view) > && func ); + /** * @brief Strongly typed wrapper around MPI_Allgather. * @tparam T_SEND The pointer type for \p sendbuf @@ -978,6 +1025,45 @@ inline MPI_Op MpiWrapper::getMpiOp( Reduction const op ) } } +template< typename CONTAINER, typename VALUE_T, typename > +MpiWrapper::GatherResult< CONTAINER > +MpiWrapper::gatherBufferRank0( CONTAINER const & localBuffer ) +{ + integer const numRanks = MpiWrapper::commSize(); + integer const numLocalValues = static_cast< integer >(localBuffer.size()); + + GatherResult< CONTAINER > gatherResult; + + if( MpiWrapper::commRank() == 0 ) + { + gatherResult.counts.resize( numRanks ); + gatherResult.offsets.resize( numRanks ); + } + + + MpiWrapper::gather( &numLocalValues, 1, gatherResult.counts.data(), 1, 0 ); + + if( MpiWrapper::commRank() == 0 ) + { + integer totalSize = 0; + for( integer i = 0; i < numRanks; ++i ) + { + gatherResult.offsets[i] = totalSize; + totalSize += gatherResult.counts[i]; + } + gatherResult.data.resize( totalSize ); + } + + MpiWrapper::gatherv( localBuffer.data(), + numLocalValues, + gatherResult.data.data(), + gatherResult.counts.data(), + gatherResult.offsets.data(), + 0 ); + + return gatherResult; +} + template< typename T_SEND, typename T_RECV > int MpiWrapper::allgather( T_SEND const * const sendbuf, int sendcount, diff --git a/src/coreComponents/common/format/table/TableData.cpp b/src/coreComponents/common/format/table/TableData.cpp index b9bfc885584..b04f2658caf 100644 --- a/src/coreComponents/common/format/table/TableData.cpp +++ b/src/coreComponents/common/format/table/TableData.cpp @@ -59,7 +59,81 @@ TableData & TableData::operator=( TableData const & other ) bool TableData::operator<( TableData const & other ) const { - return m_rows < other.m_rows; + if( other.getCellsData().size()!= getCellsData().size()) + return false; + + for( size_t i = 0; i < getCellsData().size(); i++ ) + { + if( getCellsData()[i].data()->value > other.getCellsData()[i].data()->value ) + return false; + } + return true; +} + +bool TableData::operator==( TableData const & comparingTable ) const +{ + if( comparingTable.getCellsData().size()!= getCellsData().size()) + return false; + for( size_t i = 0; i < getCellsData().size(); i++ ) + { + if( getCellsData()[i].data()->value != comparingTable.getCellsData()[i].data()->value ) + return false; + } + return true; +} + +void TableData::CellData::serialize( stdVector< buffer_unit_type > & out ) const +{ + basicSerialization::serializePrimitive( type, out ); + basicSerialization::serializeString( value, out ); +} + + +size_t TableData::CellData::getSerializedSize() const +{ + return basicSerialization::sizeOfPrimitive( type ) + basicSerialization::sizeOfString( value ); +} + +size_t TableData::getSerializedSize() const +{ + size_t totalSize =0; + + if( m_rows.empty()) + return totalSize; + + for( auto & row : m_rows ) + { + size_t rowSize = 0; + for( auto & cell : row ) + { + rowSize += cell.getSerializedSize(); + } + totalSize += sizeof(size_t) + rowSize; + } + return totalSize; +} + +void TableData::serialize( stdVector< buffer_unit_type > & serializedTableData ) const +{ + if( m_rows.empty()) + return; + + for( auto & row : m_rows ) + { + { // pack row size; + size_t rowSize = 0; + for( auto const & cell : row ) + rowSize += cell.getSerializedSize(); + basicSerialization::serializePrimitive( rowSize, serializedTableData ); + } + + { // pack cells + for( auto const & cell : row ) + { + cell.serialize( serializedTableData ); + } + } + } } @@ -180,4 +254,63 @@ TableData2D::TableDataHolder TableData2D::buildTableData( string_view targetUnit return tableData1D; } + +void basicSerialization::serializeString ( string const & data, stdVector< buffer_unit_type > & out ) +{ + basicSerialization::serializePrimitive( data.size(), out ); + auto * begin = data.data(); + auto * end = begin + data.size(); + out.insert( out.end(), begin, end ); +} + +void basicSerialization::deserializeString( string & str, buffer_unit_type const * & ptr, buffer_unit_type const * end ) +{ + string::size_type strSize = 0; + basicSerialization::deserializePrimitive( strSize, ptr, end ); + if( static_cast< long >(strSize) > std::distance( ptr, end ) ) + { + throw std::runtime_error( "buffer overflow reading string" ); + } + str.assign( ptr, ptr + strSize ); + ptr += str.size(); +} + +bool tableDataSorting::positiveNumberStringComp( string_view s1, string_view s2 ) +{ + auto split = []( string_view s, string & intPart, string & decPart ) + { + size_t dotPos = s.find( '.' ); + if( dotPos == string::npos ) + { + intPart = s; + decPart = ""; + } + else + { + intPart = s.substr( 0, dotPos ); + decPart = s.substr( dotPos + 1 ); + } + }; + + string s1Int, s1Dec, s2Int, s2Dec; + split( s1, s1Int, s1Dec ); + split( s2, s2Int, s2Dec ); + + if( s1Int.length() != s2Int.length()) + return s1Int.length() < s2Int.length(); + + if( s1Int != s2Int ) + return s1Int < s2Int; + + size_t minLen = std::min( s1Dec.length(), s2Dec.length()); + for( size_t i = 0; i < minLen; ++i ) + { + if( s1Dec[i] != s2Dec[i] ) + return s1Dec[i] < s2Dec[i]; + } + + + return false; +} + } diff --git a/src/coreComponents/common/format/table/TableData.hpp b/src/coreComponents/common/format/table/TableData.hpp index e36afb037c5..f237b2ec038 100644 --- a/src/coreComponents/common/format/table/TableData.hpp +++ b/src/coreComponents/common/format/table/TableData.hpp @@ -55,7 +55,14 @@ class TableData bool operator<( TableData const & other ) const; /** - * @brief Representing a data in TableData + * @brief Comparison operator for data rows + * @param comparingTable The tableData values to compare + * @return The comparison result + */ + bool operator==( TableData const & comparingTable ) const; + + /** + * @brief Representing a single cell's data within a TableData row. */ struct CellData { @@ -64,22 +71,33 @@ class TableData /// The cell value string value; - /// @cond DO_NOT_DOCUMENT - bool operator==( CellData const & other ) const - { - return value == other.value; - } + /** + * @return Total size in bytes required to serialize this cell. + */ + size_t getSerializedSize() const; - bool operator<( CellData const & other ) const - { - return value < other.value; - } - ///@endcond + /** + * @brief Serializes the cell type and value into the output buffer. + * @param out Buffer to append the serialized cell data to. + */ + void serialize( stdVector< buffer_unit_type > & out ) const; }; + /** + * @brief Returns the total serialized byte size of all rows in the table. + * @return Total size in bytes required to serialize the entire TableData. + */ + size_t getSerializedSize() const; + /// Alias for table data rows with cells values using DataRows = stdVector< stdVector< CellData > >; + /** + * @brief Serializes the tableData into the output buffer. + * @param serializedTableData Buffer to append the serialized tableData. + */ + void serialize( stdVector< buffer_unit_type > & serializedTableData ) const; + /** * @brief Add a row to the table. * The values passed to addRow (can be any type). @@ -129,14 +147,6 @@ class TableData DataRows & getCellsData() { return m_rows; } - /** - * @brief Comparison operator for data rows - * @param comparingTable The tableData values to compare - * @return The comparison result - */ - inline bool operator==( TableData const & comparingTable ) const - { return getCellsData() == comparingTable.getCellsData(); } - /** * @brief Get all error messages * @return The list of error messages @@ -151,6 +161,17 @@ class TableData TableErrorListing & getErrorsList() { return *m_errors; } + /** + * @brief Sorts the rows using a custom comparator functor. + * @tparam SortingFunc Type of the sorting comparator functor. + * @param sortingFunctor Comparator functor used to sort the rows. + */ + template< typename SortingFunc > + void sort( SortingFunc sortingFunctor ) + { + std::sort( m_rows.begin(), m_rows.end(), sortingFunctor ); + } + private: /// @brief vector containing all rows with cell values DataRows m_rows; @@ -302,5 +323,91 @@ void TableData2D::addCell( real64 const rowValue, real64 const columnValue, T co m_data.get_inserted( rowValue ).get_inserted( columnValue ) = GEOS_FMT( "{}", value ); } +// Serialisation/ Deserialisation utils for common +namespace basicSerialization +{ + +/** + * @tparam T The trivial type + * @return Returns the size occupied by a trivial type in memory. + */ +template< typename T > +inline unsigned long sizeOfPrimitive( T ) +{ return sizeof(T); } + +/** + * @brief Returns the size of a string (header size + content). + * @param str The target string + * @return Size in bytes. + */ +inline unsigned long sizeOfString( string const & str ) +{ return sizeof(string::size_type) + str.size(); } + +/** + * @brief Write the data to the buffer. + * @tparam T The type of the data who must be trivially copiable + * @param data Destination variable. + * @param out The buffer to write in. + */ +template< typename T > +void serializePrimitive ( T const data, stdVector< buffer_unit_type > & out ); + +/** + * @brief Write a string value to the buffer. + * @param data String variable. + * @param out The buffer to write in. + */ +void serializeString ( string const & data, stdVector< buffer_unit_type > & out ); + +/** + * @brief Reads the data from the buffer and advances the pointer. + * @tparam T The type of the data who must be trivially copiable + * @param data Destination variable. + * @param ptr Current read pointer (advanced by sizeof(string)). + * @param end Safety: maximum buffer limit. + */ +template< typename T > +void deserializePrimitive( T & data, buffer_unit_type const * & ptr, buffer_unit_type const * end ); + +/** + * @brief Reads a string value from the buffer and advances the pointer. + * @param str Destination string variable. + * @param ptr Current read pointer (advanced by sizeof(string)). + * @param end Safety: maximum buffer limit. + */ +void deserializeString( string & str, buffer_unit_type const * & ptr, buffer_unit_type const * end ); +} + +// Custom Comp function; +namespace tableDataSorting +{ +/** + * @brief Compare two string number string by in ascending numerical order. + * @param a The string to compare + * @param b The string to compare + * @return True if a is greater than b + */ +bool positiveNumberStringComp( string_view a, string_view b ); +} + +template< typename T > +void basicSerialization::serializePrimitive ( T const data, stdVector< buffer_unit_type > & out ) +{ + static_assert( std::is_trivially_copyable_v< T > ); + buffer_unit_type const * begin = reinterpret_cast< buffer_unit_type const * >( &data ); + buffer_unit_type const * end = begin + sizeof(data); + out.insert( out.end(), begin, end ); +} + +template< typename T > +void basicSerialization::deserializePrimitive( T & data, buffer_unit_type const * & ptr, buffer_unit_type const * end ) +{ + static_assert( std::is_trivially_copyable_v< T > ); + if( ptr + sizeof(T)> end ) + throw std::runtime_error( "Buffer overflow" ); + data = *reinterpret_cast< T const * >(ptr); + ptr += sizeof(T); +} + } #endif /* GEOS_COMMON_FORMAT_TABLE_TABLEDATA_HPP */ diff --git a/src/coreComponents/common/format/table/TableFormatter.cpp b/src/coreComponents/common/format/table/TableFormatter.cpp index 3d454887da7..7b198202fd9 100644 --- a/src/coreComponents/common/format/table/TableFormatter.cpp +++ b/src/coreComponents/common/format/table/TableFormatter.cpp @@ -227,28 +227,28 @@ template<> string TableTextFormatter::toString< TableData >( TableData const & tableData ) const { std::ostringstream tableOutput; - CellLayoutRows headerCellsLayout; - CellLayoutRows dataCellsLayout; - CellLayoutRows errorCellsLayout; + CellLayoutRows headerRows; + CellLayoutRows dataRows; + CellLayoutRows errorRows; size_t tableTotalWidth = 0; initalizeTableGrids( m_tableLayout, tableData, - headerCellsLayout, dataCellsLayout, errorCellsLayout, + headerRows, dataRows, errorRows, tableTotalWidth, nullptr ); string const sepLine = string( tableTotalWidth, m_horizontalLine ); - outputTableHeader( tableOutput, m_tableLayout, headerCellsLayout, sepLine ); - outputTableData( tableOutput, m_tableLayout, dataCellsLayout ); - outputTableFooter( tableOutput, m_tableLayout, errorCellsLayout, sepLine, !dataCellsLayout.empty() ); + outputTableHeader( tableOutput, m_tableLayout, headerRows, sepLine ); + outputTableData( tableOutput, m_tableLayout, dataRows ); + outputTableFooter( tableOutput, m_tableLayout, errorRows, sepLine, !dataRows.empty() ); return tableOutput.str(); } void TableTextFormatter::initalizeTableGrids( PreparedTableLayout const & tableLayout, TableData const & tableInputData, - CellLayoutRows & headerCellsLayout, - CellLayoutRows & dataCellsLayout, - CellLayoutRows & errorCellsLayout, + CellLayoutRows & headerRows, + CellLayoutRows & dataRows, + CellLayoutRows & errorRows, size_t & tableTotalWidth, ColumnWidthModifier columnWidthModifier ) const { @@ -261,30 +261,30 @@ void TableTextFormatter::initalizeTableGrids( PreparedTableLayout const & tableL // this array will store the displayed width of all columns (it will be scaled by data & headers width) stdVector< size_t > columnsWidth = stdVector< size_t >( nbVisibleColumns, 0 ); - populateTitleCellsLayout( tableLayout, headerCellsLayout, nbVisibleColumns ); + populateTitleCellsLayout( tableLayout, headerRows, nbVisibleColumns ); if( hasColumnLayout ) { - populateHeaderCellsLayout( tableLayout, headerCellsLayout, nbVisibleColumns ); - populateDataCellsLayout( tableLayout, dataCellsLayout, inputDataValues, nbVisibleColumns ); + populateHeaderCellsLayout( tableLayout, headerRows, nbVisibleColumns ); + populateDataCellsLayout( tableLayout, dataRows, inputDataValues, nbVisibleColumns ); } else { - populateDataCellsLayout( tableLayout, dataCellsLayout, inputDataValues ); + populateDataCellsLayout( tableLayout, dataRows, inputDataValues ); } if( getErrorsList().hasErrors() || tableInputData.getErrorsList().hasErrors()) { - populateErrorCellsLayout( tableLayout, errorCellsLayout, tableInputData.getErrorsList() ); + populateErrorCellsLayout( tableLayout, errorRows, tableInputData.getErrorsList() ); } - stretchColumnsByCellsWidth( columnsWidth, headerCellsLayout ); - stretchColumnsByCellsWidth( columnsWidth, dataCellsLayout ); - stretchColumnsByCellsWidth( columnsWidth, errorCellsLayout ); + stretchColumnsByCellsWidth( columnsWidth, headerRows ); + stretchColumnsByCellsWidth( columnsWidth, dataRows ); + stretchColumnsByCellsWidth( columnsWidth, errorRows ); // only after all cells that are not merge, we can process the merged cells. - stretchColumnsByMergedCellsWidth( columnsWidth, headerCellsLayout, tableLayout, false ); - stretchColumnsByMergedCellsWidth( columnsWidth, dataCellsLayout, tableLayout, true ); - stretchColumnsByMergedCellsWidth( columnsWidth, errorCellsLayout, tableLayout, true ); + stretchColumnsByMergedCellsWidth( columnsWidth, headerRows, tableLayout, false ); + stretchColumnsByMergedCellsWidth( columnsWidth, dataRows, tableLayout, true ); + stretchColumnsByMergedCellsWidth( columnsWidth, errorRows, tableLayout, true ); if( columnWidthModifier ) columnWidthModifier( columnsWidth ); @@ -298,9 +298,9 @@ void TableTextFormatter::initalizeTableGrids( PreparedTableLayout const & tableL } // we can now propagate the columns width width to all cells - applyColumnsWidth( columnsWidth, headerCellsLayout, tableLayout ); - applyColumnsWidth( columnsWidth, dataCellsLayout, tableLayout ); - applyColumnsWidth( columnsWidth, errorCellsLayout, tableLayout ); + applyColumnsWidth( columnsWidth, headerRows, tableLayout ); + applyColumnsWidth( columnsWidth, dataRows, tableLayout ); + applyColumnsWidth( columnsWidth, errorRows, tableLayout ); } void TableTextFormatter::populateTitleCellsLayout( PreparedTableLayout const & tableLayout, diff --git a/src/coreComponents/common/format/table/TableMpiComponents.cpp b/src/coreComponents/common/format/table/TableMpiComponents.cpp index 4bf969282c9..da631658db1 100644 --- a/src/coreComponents/common/format/table/TableMpiComponents.cpp +++ b/src/coreComponents/common/format/table/TableMpiComponents.cpp @@ -20,69 +20,24 @@ #include "TableMpiComponents.hpp" #include "common/MpiWrapper.hpp" +#include "common/format/table/TableTypes.hpp" namespace geos { -TableTextMpiOutput::TableTextMpiOutput( TableMpiLayout mpiLayout ): +TableTextMpiFormatter::TableTextMpiFormatter( TableMpiLayout mpiLayout ): TableTextFormatter(), m_mpiLayout( mpiLayout ) {} -TableTextMpiOutput::TableTextMpiOutput( TableLayout const & tableLayout, - TableMpiLayout mpiLayout ): +TableTextMpiFormatter::TableTextMpiFormatter( TableLayout const & tableLayout, + TableMpiLayout mpiLayout ): TableTextFormatter( tableLayout ), m_mpiLayout( mpiLayout ) {} -template<> -void TableTextMpiOutput::toStream< TableData >( std::ostream & tableOutput, - TableData const & tableData ) const -{ - TableTextMpiOutput::Status status { - // m_isMasterRank (only the master rank does the output of the header && bottom of the table) - MpiWrapper::commRank() == 0, - // m_isContributing (some ranks does not have any output to produce) - !tableData.getCellsData().empty(), - // m_hasContent - false, - // m_sepLine - "" - }; - - CellLayoutRows headerCellsLayout; - CellLayoutRows dataCellsLayout; - CellLayoutRows errorCellsLayout; - size_t tableTotalWidth = 0; - - { - ColumnWidthModifier const columnWidthModifier = [this, status]( stdVector< size_t > & columnsWidth ) { - stretchColumnsByRanks( columnsWidth, status ); - }; - initalizeTableGrids( m_tableLayout, tableData, - headerCellsLayout, dataCellsLayout, errorCellsLayout, - tableTotalWidth, columnWidthModifier ); - status.m_sepLine = string( tableTotalWidth, m_horizontalLine ); - } - - if( status.m_isMasterRank ) - { - outputTableHeader( tableOutput, m_tableLayout, headerCellsLayout, status.m_sepLine ); - tableOutput.flush(); - } - - outputTableDataToRank0( tableOutput, m_tableLayout, dataCellsLayout, status ); - - if( status.m_isMasterRank ) - { - outputTableFooter( tableOutput, m_tableLayout, errorCellsLayout, - status.m_sepLine, status.m_hasContent ); - tableOutput.flush(); - } -} - -void TableTextMpiOutput::stretchColumnsByRanks( stdVector< size_t > & columnsWidth, - TableTextMpiOutput::Status const & status ) const +void TableTextMpiFormatter::stretchColumnsByRanks( stdVector< size_t > & columnsWidth, + TableTextMpiFormatter::Status const & status ) const { { // we ensure we have the correct amount of columns on all ranks (for correct MPI reduction operation) size_t const rankColumnsCount = columnsWidth.size(); @@ -103,13 +58,11 @@ void TableTextMpiOutput::stretchColumnsByRanks( stdVector< size_t > & columnsWid MpiWrapper::allReduce( columnsWidth, columnsWidth, MpiWrapper::Reduction::Max ); } -void TableTextMpiOutput::outputTableDataToRank0( std::ostream & tableOutput, - PreparedTableLayout const & tableLayout, - CellLayoutRows const & dataCellsLayout, - TableTextMpiOutput::Status & status ) const +void TableTextMpiFormatter::gatherAndOutputTableDataInRankOrder( std::ostream & tableOutput, + TableFormatter::CellLayoutRows const & rows, + PreparedTableLayout const & tableLayout, + TableTextMpiFormatter::Status & status ) const { - integer const ranksCount = MpiWrapper::commSize(); - // master rank does the output directly to the output, other ranks will have to send it through a string. std::ostringstream localStringStream; std::ostream & rankOutput = status.m_isMasterRank ? tableOutput : localStringStream; @@ -118,45 +71,130 @@ void TableTextMpiOutput::outputTableDataToRank0( std::ostream & tableOutput, { if( m_mpiLayout.m_separatorBetweenRanks ) { - string const rankSepLine = GEOS_FMT( "{:-^{}}", m_mpiLayout.m_rankTitle, status.m_sepLine.size() - 2 ); + size_t const sepWidth = status.m_sepLine.size() > 2 ? status.m_sepLine.size() - 2 : 0; + string const rankSepLine = GEOS_FMT( "{:-^{}}", m_mpiLayout.m_rankTitle, sepWidth ); rankOutput << tableLayout.getIndentationStr() << m_verticalLine << rankSepLine << m_verticalLine << '\n'; } - outputTableData( rankOutput, tableLayout, dataCellsLayout ); + outputTableData( rankOutput, tableLayout, rows ); } - - // all other ranks than rank 0 render their output in a string and comunicate its size - stdVector< integer > ranksStrsSizes = stdVector< integer >( ranksCount, 0 ); string const rankStr = !status.m_isMasterRank && status.m_isContributing ? localStringStream.str() : ""; - integer const rankStrSize = rankStr.size(); - MpiWrapper::gather( &rankStrSize, 1, ranksStrsSizes.data(), 1, 0 ); + stdVector< string > strsAccrossRanks; - // we compute the memory layout of the ranks strings - stdVector< integer > ranksStrsOffsets = stdVector< integer >( ranksCount, 0 ); - integer ranksStrsTotalSize = 0; - for( integer rankId = 1; rankId < ranksCount; ++rankId ) + MpiWrapper::gatherStringOnRank0( rankStr, [&]( string_view str ){ + status.m_hasContent = true; + strsAccrossRanks.emplace_back( str ); + } ); + + if( status.m_isMasterRank && status.m_hasContent ) { - ranksStrsOffsets[rankId] = ranksStrsTotalSize; - ranksStrsTotalSize += ranksStrsSizes[rankId]; + for( string_view str : strsAccrossRanks ) + tableOutput << str; } +} - // finally, we can send all text data to rank 0, then we output it in the output stream. - string ranksStrs = string( ranksStrsTotalSize, '\0' ); - MpiWrapper::gatherv( &rankStr[0], rankStrSize, - &ranksStrs[0], ranksStrsSizes.data(), ranksStrsOffsets.data(), - 0, MPI_COMM_GEOS ); - if( status.m_isMasterRank ) - { - // master rank status - status.m_hasContent = !dataCellsLayout.empty(); +TableData TableTextMpiFormatter::gatherTableDataRank0( TableData const & localTableData ) const +{ + stdVector< buffer_unit_type > serializedTableData( 0 ); + size_t totalSize = 0; + + { // allocation + totalSize = localTableData.getSerializedSize(); + serializedTableData.reserve( totalSize ); + } + + { // Packing + if( totalSize > 0 ) + { + localTableData.serialize( serializedTableData ); + } + } + auto [globalLogRecords, counts, offsets] = + MpiWrapper::gatherBufferRank0< stdVector< buffer_unit_type > >( serializedTableData ); - for( integer rankId = 1; rankId < ranksCount; ++rankId ) + { // Unpacking + TableData tableDataGathered; + if( MpiWrapper::commRank() == 0 ) { - if( ranksStrsSizes[rankId] > 0 ) + buffer_unit_type const * startBuff = globalLogRecords.data(); + for( size_t idxRank = 0; idxRank < (size_t)MpiWrapper::commSize(); ++idxRank ) { - status.m_hasContent = true; - tableOutput << string_view( &ranksStrs[ranksStrsOffsets[rankId]], ranksStrsSizes[rankId] ); + integer byteFromThisRank = counts[idxRank]; + buffer_unit_type const * endRowsBuff = startBuff + byteFromThisRank; + while( startBuff < endRowsBuff ) + { + size_t byteFromThisRow = 0; + basicSerialization::deserializePrimitive( byteFromThisRow, startBuff, endRowsBuff ); + buffer_unit_type const * endRowBuff= startBuff + byteFromThisRow; + stdVector< TableData::CellData > row; + while( startBuff < endRowBuff ) + { + CellType cellType; + basicSerialization::deserializePrimitive( cellType, startBuff, endRowBuff ); + string cellValue; + basicSerialization::deserializeString( cellValue, startBuff, endRowBuff ); + row.push_back( {cellType, cellValue} ); + } + tableDataGathered.addRow( row ); + } } } + return tableDataGathered; + } +} + +template<> +void TableTextMpiFormatter::toStream< TableData >( std::ostream & tableOutput, + TableData const & tableData ) const +{ + TableTextMpiFormatter::Status status { + // m_isMasterRank (only the master rank does the output of the header && bottom of the table) + MpiWrapper::commRank() == 0, + // m_isContributing (some ranks does not have any output to produce) + !tableData.getCellsData().empty(), + // m_hasContent + false, + // m_sepLine + "" + }; + + if( m_sortingFunctor ) + { + TableData tableDataGathered = gatherTableDataRank0( tableData ); + + if( status.m_isMasterRank ) + { + tableDataGathered.sort( *m_sortingFunctor ); + TableTextFormatter::toStream( tableOutput, tableDataGathered ); + } + } + else + { // this version is faster (MPI cooperation) but can only be ordered by rank id + CellLayoutRows headerRows; + CellLayoutRows dataRows; + CellLayoutRows errorRows; + size_t tableTotalWidth = 0; + { // compute layout + ColumnWidthModifier const columnWidthModifier = [this, status]( stdVector< size_t > & columnsWidth ) { + stretchColumnsByRanks( columnsWidth, status ); + }; + initalizeTableGrids( m_tableLayout, tableData, + headerRows, dataRows, errorRows, + tableTotalWidth, columnWidthModifier ); + status.m_sepLine = string( tableTotalWidth, m_horizontalLine ); + } + + if( status.m_isMasterRank ) + { + outputTableHeader( tableOutput, m_tableLayout, headerRows, status.m_sepLine ); + tableOutput.flush(); + } + gatherAndOutputTableDataInRankOrder( tableOutput, dataRows, m_tableLayout, status ); + if( status.m_isMasterRank ) + { + outputTableFooter( tableOutput, m_tableLayout, errorRows, + status.m_sepLine, status.m_hasContent ); + tableOutput.flush(); + } } } diff --git a/src/coreComponents/common/format/table/TableMpiComponents.hpp b/src/coreComponents/common/format/table/TableMpiComponents.hpp index aeb37caa0ce..655a8a343cb 100644 --- a/src/coreComponents/common/format/table/TableMpiComponents.hpp +++ b/src/coreComponents/common/format/table/TableMpiComponents.hpp @@ -42,26 +42,27 @@ struct TableMpiLayout * @brief class to format data in a formatted text format, allowing contributions from multiple * MPI ranks. */ -class TableTextMpiOutput : public TableTextFormatter +class TableTextMpiFormatter : public TableTextFormatter { public: /// base class using Base = TableTextFormatter; - + /// Callable comparison function object used for std::sort for a TableData + using SortingFunc = std::function< bool (stdVector< TableData::CellData >, stdVector< TableData::CellData >) >; /** * @brief Construct a default Table Formatter without layout specification (to only insert data in it, * without any column / title). Feature is not tested. * @param mpiLayout MPI-specific layout information (default is having contiguous ranks data). */ - TableTextMpiOutput( TableMpiLayout mpiLayout = TableMpiLayout() ); + TableTextMpiFormatter( TableMpiLayout mpiLayout = TableMpiLayout() ); /** * @brief Construct a new TableTextMpiOutput from a tableLayout * @param tableLayout Contain all tableColumnData names and optionnaly the table title * @param mpiLayout MPI-specific layout information (default is having contiguous ranks data). */ - TableTextMpiOutput( TableLayout const & tableLayout, - TableMpiLayout mpiLayout = TableMpiLayout() ); + TableTextMpiFormatter( TableLayout const & tableLayout, + TableMpiLayout mpiLayout = TableMpiLayout() ); /** * @brief Convert a data source to a table string. @@ -74,6 +75,14 @@ class TableTextMpiOutput : public TableTextFormatter template< typename DATASOURCE > void toStream( std::ostream & outputStream, DATASOURCE const & tableData ) const; + /** + * @brief Set the Sorting Func object + * @param func The callable comparison function object + */ + void setSortingFunc( SortingFunc && func ) + { m_sortingFunctor = std::make_unique< SortingFunc >( std::move( func )); } + + private: // hiding toString() methods as they are not implemented with MPI support. @@ -89,6 +98,9 @@ class TableTextMpiOutput : public TableTextFormatter TableMpiLayout m_mpiLayout; + /// The custom comparison function object for std::sort + std::unique_ptr< SortingFunc > m_sortingFunctor; + /** * @brief Expend the columns width to accomodate with the content of all MPI ranks. * As it is based on MPI communications, every ranks must call this method. @@ -98,11 +110,24 @@ class TableTextMpiOutput : public TableTextFormatter void stretchColumnsByRanks( stdVector< size_t > & columnsWidth, Status const & status ) const; - void outputTableDataToRank0( std::ostream & tableOutput, - PreparedTableLayout const & tableLayout, - CellLayoutRows const & dataCellsLayout, - Status & status ) const; + /** + * @brief Gather all the TableData to the rank 0. + * @param localTableData The local TableData to send to rank 0; + */ + TableData gatherTableDataRank0( TableData const & localTableData ) const; + + /** + * @brief Gather data cell rows across all MPI ranks and output them to rank 0 in rank order. + * @param tableOutput The output stream to display the resulting table + * @param dataCellsLayout The layout for the data cells + * @param tableLayout The layout of the table + * @param status The TableMpi status for the current rank + */ + void gatherAndOutputTableDataInRankOrder( std::ostream & tableOutput, + CellLayoutRows const & dataCellsLayout, + PreparedTableLayout const & tableLayout, + TableTextMpiFormatter::Status & status )const; }; } diff --git a/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp b/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp index be3a532565a..20e11600d64 100644 --- a/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp +++ b/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp @@ -99,7 +99,6 @@ TEST( testMpiTables, testDifferentRankData ) "-------------------------------------------\n" }, }; - int const rankId = MpiWrapper::commRank(); int const nbRanks = MpiWrapper::commSize(); ASSERT_EQ( nbRanks, 4 ) << "This unit test cases are designed for exactly 4 ranks to check row ordering consistency."; @@ -124,8 +123,7 @@ TEST( testMpiTables, testDifferentRankData ) data.addRow( id, value ); } } - - TableTextMpiOutput const formatter = TableTextMpiOutput( layout, mpiLayout ); + TableTextMpiFormatter const formatter = TableTextMpiFormatter( layout, mpiLayout ); std::ostringstream oss; formatter.toStream( oss, data ); if( rankId == 0 ) @@ -134,6 +132,119 @@ TEST( testMpiTables, testDifferentRankData ) oss.str().data() ); } } + +} + +TEST( testMpiTables, testSortingMethod ) +{ + struct TestCase + { + stdVector< stdVector< std::pair< integer, real64 > > > m_ranksValues; + string m_expectedResult; + }; + + TestCase const testCase = + { + { // m_ranksValues: in this test, rank 2 has no value + { {3, 0.791}, {2, 0.624}}, + { {1, 0.502} }, + { {4, 0.243}, {6, 0.302}, {5, 0.804} }, + {}, + }, + "\n" // m_expectedResult + "-------------------------------------------\n" + "| Summary of negative pressure elements |\n" + "|-----------------------------------------|\n" + "| Global Id | pressure [Pa] |\n" + "|------------------|----------------------|\n" + "| 1 | 0.502 |\n" + "| 2 | 0.624 |\n" + "| 3 | 0.791 |\n" + "| 4 | 0.243 |\n" + "| 5 | 0.804 |\n" + "| 6 | 0.302 |\n" + "-------------------------------------------\n" + }; + + int const rankId = MpiWrapper::commRank(); + int const nbRanks = MpiWrapper::commSize(); + ASSERT_EQ( nbRanks, 4 ) << "This unit test cases are designed for exactly 4 ranks to check row ordering consistency."; + + + TableLayout const layout = TableLayout(). + setTitle( "Summary of negative pressure elements" ). + addColumns( { "Global Id", "pressure [Pa]" } ). + setDefaultHeaderAlignment( TableLayout::Alignment::left ); + TableData data; + auto const & rankTestData = testCase.m_ranksValues[rankId]; + + TableMpiLayout mpiLayout; + mpiLayout.m_separatorBetweenRanks = true; + + if( !rankTestData.empty() ) + { + mpiLayout.m_rankTitle = GEOS_FMT( "Rank {}, {} values", rankId, rankTestData.size() ); + for( auto const & [id, value] : rankTestData ) + { + data.addRow( id, value ); + } + } + + TableTextMpiFormatter formatter = TableTextMpiFormatter( layout, mpiLayout ); + formatter.setSortingFunc( []( std::vector< TableData::CellData > const & row1, + std::vector< TableData::CellData > const & row2 ) { + return tableDataSorting::positiveNumberStringComp( row1[0].value, row2[0].value ); + } ); + + std::ostringstream oss; + formatter.toStream( oss, data ); + if( rankId == 0 ) + { + EXPECT_STREQ( testCase.m_expectedResult.data(), + oss.str().data() ); + } + + +} + +TEST( testMpiTables, testCompPositiveValueTable ) +{ + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "123", "45" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "45", "123" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "42", "42" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "0", "0" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "9", "1" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "10000000", "9999999" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "9999999", "10000000" )); + + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "10.5", "9.99" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "9.99", "10.5" )); + + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "10", "9.999" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "9.999", "10" )); + + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "1.2", "1.9" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "1.9", "1.2" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.5", "1.50" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.50", "1.5" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.500", "1.5" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.5", "1.500" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.51", "1.510" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "1.51", "1.509" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "3.14", "3.14" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "0.001", "0.001" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "100.0", "100.0" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "5", "5.0" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "5.0", "5" )); + + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "5595", "5155" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "5155", "5595" )); + + } int main( int argc, char * * argv ) diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 963db5c6c3c..c92b0f7c515 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -20,6 +20,8 @@ #include "common/DataLayouts.hpp" #include "common/TimingMacros.hpp" +#include "common/format/table/TableMpiComponents.hpp" +#include "mesh/WellElementSubRegion.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "SurfaceElementRegion.hpp" #include "constitutive/ConstitutiveManager.hpp" @@ -183,7 +185,7 @@ void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockM MeshLevel & meshLevel ) { NodeManager & nodeManager = meshLevel.getNodeManager(); - + int const rankId = MpiWrapper::commRank(); // get the offsets to construct local-to-global maps for well nodes and elements nodeManager.setMaxGlobalIndex(); globalIndex const nodeOffsetGlobal = nodeManager.maxGlobalIndex() + 1; @@ -229,6 +231,81 @@ void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockM } ); + forElementRegions< WellElementRegion >( [&]( WellElementRegion const & wellRegion ){ + WellElementSubRegion const & + wellSubRegion = wellRegion.getSubRegion< WellElementSubRegion >( wellRegion.getSubRegionName() ); + + PerforationData const * perforationData= wellSubRegion.getPerforationData(); + arrayView2d< const real64 > wsrPerfLocation = perforationData->getLocation(); + TableData localPerfoData; + for( globalIndex iperfLocal = 0; iperfLocal < perforationData->getNumPerforationsGlobal(); ++iperfLocal ) + { + arrayView1d< globalIndex const > const globalIperf = perforationData->localToGlobalMap(); + + array1d< integer > localCoords; + bool const localResElementFound = perforationData->hasLocalPerforationInReservoir( iperfLocal ); + bool const globalResElemFound =(bool)MpiWrapper::allReduce( + (integer)localResElementFound, MpiWrapper::Reduction::LogicalOr ); + if( !globalResElemFound ) + { + if( MpiWrapper::commRank() == 0 ) + localPerfoData.addRow( globalIperf[iperfLocal], "NONE", localCoords, + "NONE", "NONE", "NONE", rankId ); + } + else if( localResElementFound ) + { + integer const globalWellElemIndices = wellSubRegion.getGlobalWellElementIndex()[iperfLocal]; + integer const cellId = perforationData->getReservoirElementGlobalIndex()[iperfLocal]; + auto const & meshElems = perforationData->getMeshElements(); + localIndex const targetRegionIndex = meshElems.m_toElementRegion[iperfLocal]; + localIndex const targetSubRegionIndex = meshElems.m_toElementSubRegion[iperfLocal]; + + ElementRegionBase const & region = + meshLevel.getElemManager().getRegion< ElementRegionBase >( targetRegionIndex ); + + ElementSubRegionBase const & subRegion = + region.getSubRegion< ElementSubRegionBase >( targetSubRegionIndex ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][0] ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][1] ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][2] ); + localPerfoData.addRow( globalIperf[iperfLocal], globalWellElemIndices, localCoords, + region.getName(), subRegion.getName(), cellId, rankId ); + } + + } + + integer perfoDetected = MpiWrapper::max( localPerfoData.getCellsData().size() ) > 0; + + if( perfoDetected ) + { + TableLayout const layoutPerforation ( GEOS_FMT( "Well '{}' Perforation Table", + wellRegion.getWellGeneratorName()), + { + "Perforation", "Well element", "Coordinates", + "Cell region", "Cell sub-region", "Cell ID", "Rank" + } ); + + TableMpiLayout mpiLayout; + TableTextMpiFormatter formatter = TableTextMpiFormatter( layoutPerforation, mpiLayout ); + + formatter.setSortingFunc( + []( std::vector< TableData::CellData > const & row1, + std::vector< TableData::CellData > const & row2 ) { + return tableDataSorting::positiveNumberStringComp( row1[0].value, row2[0].value ); + } ); + + std::ostringstream outputStream; + formatter.toStream( outputStream, localPerfoData ); + + if( rankId == 0 ) + { + TableTextFormatter const globalFormatter( layoutPerforation ); + GEOS_LOG( outputStream.str()); + } + } + + } ); + // communicate to rebuild global node info since we modified global ordering nodeManager.setMaxGlobalIndex(); } diff --git a/src/coreComponents/mesh/PerforationData.hpp b/src/coreComponents/mesh/PerforationData.hpp index d4da86d8410..cfd06a55aae 100644 --- a/src/coreComponents/mesh/PerforationData.hpp +++ b/src/coreComponents/mesh/PerforationData.hpp @@ -152,6 +152,23 @@ class PerforationData : public ObjectManagerBase */ arrayView1d< localIndex > getWellElements() { return m_wellElementIndex; } + /** + * @return an array of booleans value of hasLocalPerforationInReservoir() per perforation. + */ + array1d< bool > & isReservoirElementFound() { return m_isReservoirElementFound; } + + /** + * @return an immutable accessor an array of booleans value of hasLocalPerforationInReservoir() + * per perforation. + */ + arrayView1d< bool const > isReservoirElementFound() const { return m_isReservoirElementFound; } + + /** + * @param perfoLocalIndex Local index of the perforation + * @return Whether the reservoir element associated with this local perforation is found. + */ + bool hasLocalPerforationInReservoir( localIndex perfoLocalIndex ) const + { return m_isReservoirElementFound[perfoLocalIndex]; } /** * @brief Provide an immutable accessor to a const perforation-to-well-element connectivity. @@ -342,6 +359,9 @@ class PerforationData : public ObjectManagerBase /// Global indices of reservoir cell containing perforation array1d< globalIndex > m_reservoirElementGlobalIndex; + /// Vector indicating if the local perforation is connected to a reservoir element + array1d< bool > m_isReservoirElementFound; + /// Location of the perforations array2d< real64 > m_location; diff --git a/src/coreComponents/mesh/WellElementSubRegion.cpp b/src/coreComponents/mesh/WellElementSubRegion.cpp index 1cacfc95d80..fcb43449ace 100644 --- a/src/coreComponents/mesh/WellElementSubRegion.cpp +++ b/src/coreComponents/mesh/WellElementSubRegion.cpp @@ -399,7 +399,7 @@ bool searchLocalElements( MeshLevel const & mesh, { ElementRegionBase const & region = mesh.getElemManager().getRegion< ElementRegionBase >( targetRegionIndex ); - bool resElemFound = false; + bool localResElemFound = false; for( localIndex esr = 0; esr < region.numSubRegions(); ++esr ) { ElementSubRegionBase const & subRegionBase = region.getSubRegion( esr ); @@ -446,7 +446,7 @@ bool searchLocalElements( MeshLevel const & mesh, // stop if a reservoir element containing the perforation is found // if not, enlarge the set "nodes" - resElemFound = + localResElemFound = visitNeighborElements< TYPEOFREF( subRegion ) >( mesh, location, nodes, @@ -457,9 +457,9 @@ bool searchLocalElements( MeshLevel const & mesh, giMatched, geomTol ); - if( resElemFound || nNodes == nodes.size()) + if( localResElemFound || nNodes == nodes.size()) { - if( resElemFound ) + if( localResElemFound ) { esrMatched = esr; GEOS_LOG( GEOS_FMT( " found {}/{}/{}", region.getName(), subRegion.getName(), giMatched ) ); @@ -469,13 +469,13 @@ bool searchLocalElements( MeshLevel const & mesh, } } ); - if( resElemFound ) + if( localResElemFound ) { break; } } - return resElemFound; + return localResElemFound; } } @@ -609,10 +609,10 @@ void WellElementSubRegion::assignUnownedElementsInReservoir( MeshLevel & mesh, localIndex esrMatched = -1; localIndex eiMatched = -1; globalIndex giMatched = -1; - integer const resElemFound = searchLocalElements( mesh, location, m_searchDepth, er, esrMatched, eiMatched, giMatched, geomTol ); + integer const localResElemFound = searchLocalElements( mesh, location, m_searchDepth, er, esrMatched, eiMatched, giMatched, geomTol ); // if the element was found - if( resElemFound ) + if( localResElemFound ) { // the well element is in the reservoir element (erMatched,esrMatched,eiMatched), so tag it as local localElems.insert( currGlobal ); @@ -620,7 +620,7 @@ void WellElementSubRegion::assignUnownedElementsInReservoir( MeshLevel & mesh, } // if one rank has found the element, all ranks exit the search - if( MpiWrapper::allReduce( resElemFound, MpiWrapper::Reduction::LogicalOr )) + if( MpiWrapper::allReduce( localResElemFound, MpiWrapper::Reduction::LogicalOr )) break; } } @@ -899,6 +899,7 @@ void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, string_array const & perfName = lineBlock.getPerfName(); string_array const & perfStatusTableName = lineBlock.getPerfStatusTableName(); m_perforationData.resize( perfCoordsGlobal.size( 0 ) ); + m_perforationData.isReservoirElementFound().resize( perfCoordsGlobal.size( 0 ) ); localIndex iperfLocal = 0; arrayView2d< real64 > const perfLocation = m_perforationData.getLocation(); @@ -908,6 +909,7 @@ void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, // loop over all the perforations for( globalIndex iperfGlobal = 0; iperfGlobal < perfCoordsGlobal.size( 0 ); ++iperfGlobal ) { + bool globalResElemFound = false; real64 const location[3] = { perfCoordsGlobal[iperfGlobal][0], perfCoordsGlobal[iperfGlobal][1], perfCoordsGlobal[iperfGlobal][2] }; @@ -917,10 +919,10 @@ void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, localIndex erStart = -1, erEnd = -1; - localIndex const targetRegionIndex = elemManager.getRegions().getIndex( perfTargetRegionGlobal[iperfGlobal] ); - if( targetRegionIndex >= 0 ) + localIndex const knownTargetRegionIndex = elemManager.getRegions().getIndex( perfTargetRegionGlobal[iperfGlobal] ); + if( knownTargetRegionIndex >= 0 ) { - erStart = targetRegionIndex; + erStart = knownTargetRegionIndex; erEnd = erStart + 1; } else // default is all regions @@ -929,27 +931,30 @@ void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, erEnd = elemManager.numRegions(); } + m_perforationData.isReservoirElementFound()[iperfLocal] = false; // for each perforation, we have to find the reservoir element that contains the perforation - for( localIndex er = erStart; er < erEnd; er++ ) + for( localIndex targetRegionIndex = erStart; targetRegionIndex < erEnd; targetRegionIndex++ ) { // search for the reservoir element that contains the well element localIndex esrMatched = -1; localIndex eiMatched = -1; globalIndex giMatched = -1; - integer const resElemFound = searchLocalElements( mesh, - location, - m_searchDepth, - er, - esrMatched, - eiMatched, - giMatched, - geomTol ); + integer const localResElemFound = searchLocalElements( mesh, + location, + m_searchDepth, + targetRegionIndex, + esrMatched, + eiMatched, + giMatched, + geomTol ); + + // if one rank has found the element + if( localResElemFound ) + { // if the element was found + m_perforationData.isReservoirElementFound()[iperfLocal] = true; - // if the element was found - if( resElemFound ) - { // set the indices for the matched reservoir element - m_perforationData.getMeshElements().m_toElementRegion[iperfLocal] = er; + m_perforationData.getMeshElements().m_toElementRegion[iperfLocal] = targetRegionIndex; m_perforationData.getMeshElements().m_toElementSubRegion[iperfLocal] = esrMatched; m_perforationData.getMeshElements().m_toElementIndex[iperfLocal] = eiMatched; m_perforationData.getReservoirElementGlobalIndex()[iperfLocal] = giMatched; @@ -965,11 +970,20 @@ void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, // increment the local to global map m_perforationData.localToGlobalMap()[iperfLocal++] = iperfGlobal; + } - // if one rank has found the element, all ranks exit the search - if( MpiWrapper::allReduce( resElemFound, MpiWrapper::Reduction::LogicalOr )) + globalResElemFound |= (bool) MpiWrapper::allReduce( (integer) localResElemFound, MpiWrapper::Reduction::LogicalOr ); + if( globalResElemFound ) + { // all ranks exit the search break; + } + } + + if( !globalResElemFound ) + { + GEOS_WARNING( GEOS_FMT( "Perforation {} not maching any regions", m_perforationData.getName() ), + m_perforationData.getDataContext()); } } diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.cpp b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp index ee3118133a8..3255384b01e 100644 --- a/src/coreComponents/mesh/generators/WellGeneratorBase.cpp +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp @@ -141,7 +141,6 @@ void WellGeneratorBase::generateWellGeometry( ) if( isLogLevelActive< logInfo::GenerateWell >( this->getLogLevel() ) && MpiWrapper::commRank() == 0 ) { logInternalWell(); - logPerforationTable(); } } @@ -569,18 +568,4 @@ void WellGeneratorBase::logInternalWell() const GEOS_LOG_RANK_0( wellFormatter.toString( wellData )); } -void WellGeneratorBase::logPerforationTable() const -{ - TableData dataPerforation; - for( globalIndex iperf = 0; iperf < m_numPerforations; ++iperf ) - { - dataPerforation.addRow( iperf, m_perfCoords[iperf], m_perfElemId[iperf] ); - } - - TableLayout const layoutPerforation ( GEOS_FMT( "Well '{}' Perforation Table", getName()), - { "Perforation no.", "Coordinates", "Well element no." } ); - TableTextFormatter const logPerforation( layoutPerforation ); - GEOS_LOG_RANK_0( logPerforation.toString( dataPerforation )); -} - } diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.hpp b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp index e9a2dd81046..0367b2f0109 100644 --- a/src/coreComponents/mesh/generators/WellGeneratorBase.hpp +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp @@ -306,7 +306,6 @@ class WellGeneratorBase : public MeshComponentBase /// @cond DO_NOT_DOCUMENT void logInternalWell() const; - void logPerforationTable() const; /// @endcond /// Global number of perforations diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp index 3d9366ef3b7..d1cd87b1976 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp @@ -102,7 +102,7 @@ void ElementsReporterOutput::outputTooLowValues( string_view linesPrefix, } } - TableTextMpiOutput const formatter = TableTextMpiOutput( layout, mpiLayout ); + TableTextMpiFormatter const formatter = TableTextMpiFormatter( layout, mpiLayout ); formatter.toStream( std::cout, data ); GEOS_LOG_RANK_0( '\n' ); }