diff --git a/src/coreComponents/common/MpiWrapper.cpp b/src/coreComponents/common/MpiWrapper.cpp index b80d0aa802a..0443e8f4f58 100644 --- a/src/coreComponents/common/MpiWrapper.cpp +++ b/src/coreComponents/common/MpiWrapper.cpp @@ -518,6 +518,28 @@ template<> MPI_Datatype getMpiPairType< double, double >() } /* namespace internal */ +template<> +void MpiWrapper::gatherStringOnRank0< std::function< void(string_view) > > + ( 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 geos */ #if defined(__clang__) diff --git a/src/coreComponents/common/MpiWrapper.hpp b/src/coreComponents/common/MpiWrapper.hpp index fd3264a5317..1278e90c0c2 100644 --- a/src/coreComponents/common/MpiWrapper.hpp +++ b/src/coreComponents/common/MpiWrapper.hpp @@ -331,6 +331,85 @@ struct MpiWrapper */ static int nodeCommSize(); + /** + * @brief Structure holding the result from all the gather operation + * @tparam CONTAINER The container type holding the data. + */ + template< typename CONTAINER > + struct GatherResult + { + CONTAINER data; // Collected data + stdVector< integer > counts; // Number of elements per row + stdVector< integer > offsets; // Starting index for each row in 'data' + }; + +/** + * @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 ) + { + integer const numRanks = MpiWrapper::commSize(); + integer const numValues = static_cast< integer >(localBuffer.size()); + + GatherResult< CONTAINER > gatherResult; + + if( MpiWrapper::commRank() == 0 ) + { + gatherResult.counts.resize( numRanks ); + gatherResult.offsets.resize( numRanks ); + } + + + MpiWrapper::gather( &numValues, 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(), + numValues, + gatherResult.data.data(), + gatherResult.counts.data(), + gatherResult.offsets.data(), + 0 ); + + return gatherResult; + } + + /** + * @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. + */ + template< typename FUNC > + static void gatherStringOnRank0( string_view str, + FUNC && func ); + /** * @brief Strongly typed wrapper around MPI_Allgather. * @tparam T_SEND The pointer type for \p sendbuf diff --git a/src/coreComponents/common/format/table/TableData.cpp b/src/coreComponents/common/format/table/TableData.cpp index b9bfc885584..72c3908a82f 100644 --- a/src/coreComponents/common/format/table/TableData.cpp +++ b/src/coreComponents/common/format/table/TableData.cpp @@ -59,7 +59,27 @@ 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; } @@ -180,4 +200,43 @@ TableData2D::TableDataHolder TableData2D::buildTableData( string_view targetUnit return tableData1D; } + +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..254aeb883d7 100644 --- a/src/coreComponents/common/format/table/TableData.hpp +++ b/src/coreComponents/common/format/table/TableData.hpp @@ -24,6 +24,7 @@ #include "common/DataTypes.hpp" #include "common/format/Format.hpp" #include "TableTypes.hpp" +#include namespace geos { @@ -54,6 +55,13 @@ class TableData */ bool operator<( TableData const & other ) const; + /** + * @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 data in TableData */ @@ -63,20 +71,7 @@ class TableData CellType type; /// The cell value string value; - - /// @cond DO_NOT_DOCUMENT - bool operator==( CellData const & other ) const - { - return value == other.value; - } - - bool operator<( CellData const & other ) const - { - return value < other.value; - } - ///@endcond }; - /// Alias for table data rows with cells values using DataRows = stdVector< stdVector< CellData > >; @@ -129,14 +124,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 @@ -148,8 +135,16 @@ class TableData * @brief Get all error messages * @return The list of error messages */ + TableErrorListing & getErrorsList() { return *m_errors; } + + /** + * @brief Gather all the TableData rows to the rank 0 + * @param func The callable comparison function object to sort TableData rows, by default none + */ + template< typename SortingFunc = std::nullptr_t > + void gatherRowsRank0( SortingFunc && func ); private: /// @brief vector containing all rows with cell values @@ -302,5 +297,17 @@ void TableData2D::addCell( real64 const rowValue, real64 const columnValue, T co m_data.get_inserted( rowValue ).get_inserted( columnValue ) = GEOS_FMT( "{}", value ); } +// 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 ); +} + } #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..65f609bd3e1 100644 --- a/src/coreComponents/common/format/table/TableFormatter.cpp +++ b/src/coreComponents/common/format/table/TableFormatter.cpp @@ -805,30 +805,29 @@ void TableTextFormatter::formatCell( std::ostream & tableOutput, } } -void TableTextFormatter::outputLines( PreparedTableLayout const & tableLayout, - CellLayoutRows const & rows, - std::ostream & tableOutput ) const +void TableTextFormatter::outputLine( PreparedTableLayout const & tableLayout, + CellLayoutRows const & rows, + CellLayoutRow const & row, + std::ostream & tableOutput, + size_t const idxRow ) const { size_t const nbRows = rows.size(); size_t const nbColumns = !rows.empty() ? rows.front().cells.size() : 0; + size_t const nbBorderSpaces = tableLayout.getBorderMargin(); size_t const nbColumnSpaces = ( tableLayout.getColumnMargin() - 1 ) / 2; - - size_t idxRow = 0; - for( CellLayoutRow const & row : rows ) + for( size_t idxSubLine = 0; idxSubLine < row.sublinesCount; idxSubLine++ ) { - for( size_t idxSubLine = 0; idxSubLine < row.sublinesCount; idxSubLine++ ) - { - bool isLeftBorderCell = true; + bool isLeftBorderCell = true; - for( size_t idxColumn = 0; idxColumn < nbColumns; ++idxColumn ) + for( size_t idxColumn = 0; idxColumn < nbColumns; ++idxColumn ) + { + auto & cell = row.cells[idxColumn]; + bool const isRightBorderCell = idxColumn == nbColumns - 1; + if( cell.m_cellType != CellType::MergeNext || isRightBorderCell ) { - auto & cell = row.cells[idxColumn]; - bool const isRightBorderCell = idxColumn == nbColumns - 1; - if( cell.m_cellType != CellType::MergeNext || isRightBorderCell ) - { - bool const isSeparator = cell.m_cellType == CellType::Separator; - char const cellSpaceChar = isSeparator ? m_horizontalLine : ' '; + bool const isSeparator = cell.m_cellType == CellType::Separator; + char const cellSpaceChar = isSeparator ? m_horizontalLine : ' '; if( isLeftBorderCell ) { // left table border @@ -841,25 +840,37 @@ void TableTextFormatter::outputLines( PreparedTableLayout const & tableLayout, tableOutput << string( nbColumnSpaces, cellSpaceChar ); } - // cell content / fill - formatCell( tableOutput, cell, idxSubLine ); - - if( !isRightBorderCell ) - { // right side of a cell that have a neightboor - bool const isNextSeparator = row.cells[idxColumn + 1].m_cellType == CellType::Separator; - bool const upMerged = idxRow > 0 && rows[idxRow - 1].cells[idxColumn].m_cellType == CellType::MergeNext; - bool const downMerged = idxRow < nbRows - 1 && rows[idxRow + 1].cells[idxColumn].m_cellType == CellType::MergeNext; - tableOutput << string( nbColumnSpaces, cellSpaceChar ); - tableOutput << ( isSeparator && isNextSeparator && (upMerged || downMerged) ? - m_horizontalLine : m_verticalLine ); - } - else - { // right table border - tableOutput << string( nbBorderSpaces, cellSpaceChar ) << m_verticalLine << "\n"; - } + // cell content / fill + formatCell( tableOutput, cell, idxSubLine ); + + if( !isRightBorderCell ) + { // right side of a cell that have a neightboor + bool const isNextSeparator = row.cells[idxColumn + 1].m_cellType == CellType::Separator; + bool const upMerged = idxRow > 0 && rows[idxRow - 1].cells[idxColumn].m_cellType == CellType::MergeNext; + bool const downMerged = idxRow < nbRows - 1 && rows[idxRow + 1].cells[idxColumn].m_cellType == CellType::MergeNext; + tableOutput << string( nbColumnSpaces, cellSpaceChar ); + tableOutput << ( isSeparator && isNextSeparator && (upMerged || downMerged) ? + m_horizontalLine : m_verticalLine ); + } + else + { // right table border + tableOutput << string( nbBorderSpaces, cellSpaceChar ) << m_verticalLine << "\n"; } } } + } + +} + +void TableTextFormatter::outputLines( PreparedTableLayout const & tableLayout, + CellLayoutRows const & rows, + std::ostream & tableOutput ) const +{ + + size_t idxRow=0; + for( CellLayoutRow const & row : rows ) + { + outputLine( tableLayout, rows, row, tableOutput, idxRow ); idxRow++; } } diff --git a/src/coreComponents/common/format/table/TableFormatter.hpp b/src/coreComponents/common/format/table/TableFormatter.hpp index eed907975c2..4c974391b22 100644 --- a/src/coreComponents/common/format/table/TableFormatter.hpp +++ b/src/coreComponents/common/format/table/TableFormatter.hpp @@ -308,6 +308,20 @@ class TableTextFormatter : public TableFormatter string_view separatorLine, bool hasData ) const; + /** + * @brief Outputs the formatted table line to the output stream. + * @param tableLayout The layout of the table + * @param rows The data rows in a grid layout + * @param row Represent a row of the Table (header or values) to output + * @param tableOutput A reference to an `std::ostream` where the formatted table will be written. + * @param idxRow the row index to output + */ + void outputLine( PreparedTableLayout const & tableLayout, + CellLayoutRows const & rows, + CellLayoutRow const & row, + std::ostream & tableOutput, + size_t const idxRow ) const; + private: /** diff --git a/src/coreComponents/common/format/table/TableMpiComponents.cpp b/src/coreComponents/common/format/table/TableMpiComponents.cpp index 4bf969282c9..0f8300e3736 100644 --- a/src/coreComponents/common/format/table/TableMpiComponents.cpp +++ b/src/coreComponents/common/format/table/TableMpiComponents.cpp @@ -35,52 +35,6 @@ TableTextMpiOutput::TableTextMpiOutput( TableLayout const & 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 { @@ -103,13 +57,33 @@ 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 +stdVector< TableData::CellData > TableTextMpiOutput::parseStringRow( string_view rowString ) const { - integer const ranksCount = MpiWrapper::commSize(); + if( rowString.empty() ) + return stdVector< TableData::CellData >{}; + if( rowString.front() == '|' ) + rowString.remove_prefix( 1 ); + string_view rowContent =rowString; + string cell; + stdVector< TableData::CellData > dataRow; + + std::string::size_type end = 0; + + while( (end = rowContent.find( m_verticalLine )) != string_view::npos ) + { + cell =std::string( stringutilities::trimSpaces( rowContent.substr( 0, end ))); + dataRow.emplace_back( TableData::CellData( {CellType::Value, cell} )); + rowContent.remove_prefix( end + 1 ); + } + return dataRow; +} + +void TableTextMpiOutput::gatherAndOutputTableDataInRankOrder( std::ostream & tableOutput, + CellLayoutRows const & rows, + PreparedTableLayout const & tableLayout, + TableTextMpiOutput::Status & status ) const +{ // 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,44 +92,80 @@ 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; + + MpiWrapper::gatherStringOnRank0( rankStr, std::function< void(string_view) >( [&]( string_view str ){ + status.m_hasContent = true; + strsAccrossRanks.emplace_back( str ); + } ) ); - // 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 ) + if( status.m_isMasterRank && status.m_hasContent ) { - ranksStrsOffsets[rankId] = ranksStrsTotalSize; - ranksStrsTotalSize += ranksStrsSizes[rankId]; + for( string_view str : strsAccrossRanks ) + tableOutput << str; } +} + +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 + "" + }; - // 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 ) + if( m_sortingFunctor ) { - // master rank status - status.m_hasContent = !dataCellsLayout.empty(); + tableData.serialize( X ); + gatherTableDataOnRank0( tableData, X ); - for( integer rankId = 1; rankId < ranksCount; ++rankId ) + if( status.m_isMasterRank ) + { + tableData.sort( m_sortingFunctor ); + TableTextFormatter::toStream( tableOutput, tableData ); + } + } + else + { // this version is faster (MPI cooperation) but can only be ordered by rank id + CellLayoutRows headerCellsLayout; + 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, + headerCellsLayout, dataRows, errorRows, + tableTotalWidth, columnWidthModifier ); + status.m_sepLine = string( tableTotalWidth, m_horizontalLine ); + } + + if( status.m_isMasterRank ) + { + outputTableHeader( tableOutput, m_tableLayout, headerCellsLayout, status.m_sepLine ); + tableOutput.flush(); + } + gatherAndOutputTableDataInRankOrder( tableOutput, dataRows, m_tableLayout, status ); + if( status.m_isMasterRank ) { - if( ranksStrsSizes[rankId] > 0 ) - { - status.m_hasContent = true; - tableOutput << string_view( &ranksStrs[ranksStrsOffsets[rankId]], ranksStrsSizes[rankId] ); - } + 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..2aaf3ad67b9 100644 --- a/src/coreComponents/common/format/table/TableMpiComponents.hpp +++ b/src/coreComponents/common/format/table/TableMpiComponents.hpp @@ -47,7 +47,8 @@ class TableTextMpiOutput : 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. @@ -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,25 @@ 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 Parse a string row to a TablaData cells. + * @param rowString The string row string to parse. + * @return The parsed row as a vector of CellData. + */ + stdVector< TableData::CellData > parseStringRow( string_view rowString ) 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, + TableTextMpiOutput::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..cc64f1116ae 100644 --- a/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp +++ b/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp @@ -14,6 +14,7 @@ */ // Source includes +#include "common/format/table/TableData.hpp" #include "common/format/table/TableMpiComponents.hpp" #include "common/initializeEnvironment.hpp" #include "common/MpiWrapper.hpp" @@ -99,13 +100,82 @@ TEST( testMpiTables, testDifferentRankData ) "-------------------------------------------\n" }, }; + for( TestCase const & testCase: testCases ) + { + int const rankId = MpiWrapper::commRank(); + int const nbRanks = MpiWrapper::commSize(); + if( nbRanks > 1 ) + { + ASSERT_EQ( nbRanks, 4 ); + + 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 ); + } + } + + TableTextMpiOutput const formatter = TableTextMpiOutput( layout, mpiLayout ); + std::ostringstream oss; + formatter.toStream( oss, data ); + if( rankId == 0 ) + { + EXPECT_STREQ( testCase.m_expectedResult.data(), + 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 + { {1, 0.502} }, + { {2, 0.624}, {3, 0.791} }, + {}, + { {4, 0.243}, {5, 0.804}, {6, 0.302} }, + }, + "\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."; - - for( TestCase const & testCase: testCases ) + if( nbRanks > 1 ) { + ASSERT_EQ( nbRanks, 4 ); + TableLayout const layout = TableLayout(). setTitle( "Summary of negative pressure elements" ). addColumns( { "Global Id", "pressure [Pa]" } ). @@ -125,15 +195,62 @@ TEST( testMpiTables, testDifferentRankData ) } } - TableTextMpiOutput const formatter = TableTextMpiOutput( layout, mpiLayout ); + TableTextMpiOutput formatter = TableTextMpiOutput( 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 ) { + std::cout << "ma boula """<< oss.str()<( [&]( WellElementRegion const & wellRegion ){ + WellElementSubRegion const & + wellSubRegion = wellRegion.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( wellRegion.getSubRegionName() ); + TableLayout const layoutPerforation ( GEOS_FMT( "Well '{}' Perforation Table", + wellRegion.getWellGeneratorName()), + { + "Rank", "Perforation", "Well element", "Coordinates", + "Cell region", "Cell sub-region", "Cell ID" + } ); + + PerforationData const * wellSubRegionPerforationData= wellSubRegion.getPerforationData(); + arrayView2d< const real64 > wsrPerfLocation = wellSubRegionPerforationData->getLocation(); + TableData localPerfoData; + for( globalIndex iperfLocal = 0; iperfLocal < wellSubRegionPerforationData->getNumPerforationsGlobal(); ++iperfLocal ) + { + integer const cellId = wellSubRegionPerforationData->getReservoirElementGlobalIndex()[iperfLocal]; + arrayView1d< globalIndex const > const globalIperf = wellSubRegionPerforationData->localToGlobalMap(); + + array1d< integer > localCoords; + if( cellId != 0 ) + { + auto const & meshElems = wellSubRegionPerforationData->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 ); + integer const localWellElemIndices = wellSubRegion.getGlobalWellElementIndex()[iperfLocal]; + localCoords.emplace_back( wsrPerfLocation[iperfLocal][0] ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][1] ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][2] ); + localPerfoData.addRow( rankId, globalIperf[iperfLocal], localWellElemIndices, localCoords, + region.getName(), subRegion.getName(), cellId ); + } + } + + TableMpiLayout mpiLayout; + TableTextMpiOutput formatter = TableTextMpiOutput( layoutPerforation, mpiLayout ); + + formatter.setSortingFunc( + []( std::vector< TableData::CellData > const & row1, + std::vector< TableData::CellData > const & row2 ) { + return tabledatasorting::positiveNumberStringComp( row1[1].value, row2[1].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/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