fix: Fracture/3D cell co-location in parallel mesh redistribution#4008
fix: Fracture/3D cell co-location in parallel mesh redistribution#4008
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #4008 +/- ##
==========================================
Coverage ? 64.50%
==========================================
Files ? 1384
Lines ? 123603
Branches ? 0
==========================================
Hits ? 79729
Misses ? 43874
Partials ? 0 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jafranc
left a comment
There was a problem hiding this comment.
Great ! Thanks, some shortening suggestion here and there
| for( vtkIdType const * q: quad ) | ||
| { | ||
| fracture->InsertNextCell( VTK_QUAD, numPoints, q ); | ||
| fracture->InsertNextCell( VTK_QUAD, 4, q ); |
There was a problem hiding this comment.
isn't it weird to hardcode there and still use numPoints above ?
| } | ||
|
|
||
| array1d< real_t > tpwgts( numParts ); | ||
| tpwgts.setValues< serialPolicy >( 1.0f / static_cast< real_t >( numParts ) ); |
There was a problem hiding this comment.
isn't parmetis dividing in equal weights if NULL is pass as tpwgts ? If so, it might avoid a check for zero-div here
| CollocatedNodes::CollocatedNodes( string const & faceBlockName, | ||
| vtkSmartPointer< vtkDataSet > faceMesh ) | ||
| vtkSmartPointer< vtkDataSet > faceMesh, | ||
| bool performGlobalCheck ) | ||
| { | ||
| // The vtk field to the collocated nodes for fractures. | ||
| string const COLLOCATED_NODES = "collocated_nodes"; | ||
|
|
||
| vtkIdTypeArray const * collocatedNodes = vtkIdTypeArray::FastDownCast( faceMesh->GetPointData()->GetArray( COLLOCATED_NODES.c_str() ) ); | ||
|
|
||
| // Depending on the parallel split, the vtk face mesh may be empty on a rank. | ||
| // In that case, vtk will not provide any field for the emtpy mesh. | ||
| // Therefore, not finding the duplicated nodes field on a rank cannot be interpreted as a globally missing field. | ||
| // Converting the address into an integer and exchanging it through the MPI ranks let us find out | ||
| // if the field is globally missing or not. | ||
| std::uintptr_t const address = MpiWrapper::max( reinterpret_cast< std::uintptr_t >(collocatedNodes) ); | ||
| if( address == 0 ) | ||
| if( performGlobalCheck ) | ||
| { | ||
| GEOS_LOG_RANK_0( "Available point data fields in '" << faceBlockName << "':" ); | ||
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | ||
| // Depending on the parallel split, the vtk face mesh may be empty on a rank. | ||
| // In that case, vtk will not provide any field for the empty mesh. | ||
| // Therefore, not finding the duplicated nodes field on a rank cannot be interpreted as a globally missing field. | ||
| // Converting the address into an integer and exchanging it through the MPI ranks let us find out | ||
| // if the field is globally missing or not. | ||
| std::uintptr_t const address = MpiWrapper::max( reinterpret_cast< std::uintptr_t >(collocatedNodes) ); | ||
| if( address == 0 ) | ||
| { | ||
| GEOS_LOG_RANK_0( " - " << faceMesh->GetPointData()->GetArrayName( i ) << " of type '" << faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() << "'" ); | ||
| GEOS_LOG_RANK_0( GEOS_FMT( "Available point data fields in '{}':", faceBlockName ) ); | ||
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | ||
| { | ||
| GEOS_LOG_RANK_0( GEOS_FMT( " - {} of type '{}'", | ||
| faceMesh->GetPointData()->GetArrayName( i ), | ||
| faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() ) ); | ||
| } | ||
| GEOS_ERROR( GEOS_FMT( "Could not find valid field \"{}\" for fracture \"{}\".", | ||
| COLLOCATED_NODES, | ||
| faceBlockName ) ); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| if( !collocatedNodes ) | ||
| { | ||
| GEOS_LOG_RANK_0( GEOS_FMT( "Available point data fields in '{}':", faceBlockName ) ); | ||
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | ||
| { | ||
| GEOS_LOG_RANK_0( GEOS_FMT( " - {} of type '{}'", | ||
| faceMesh->GetPointData()->GetArrayName( i ), | ||
| faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() ) ); | ||
| } | ||
| GEOS_ERROR( GEOS_FMT( "Could not find valid field \"{}\" for fracture \"{}\" on this rank.", | ||
| COLLOCATED_NODES, | ||
| faceBlockName ) ); |
There was a problem hiding this comment.
| CollocatedNodes::CollocatedNodes( string const & faceBlockName, | |
| vtkSmartPointer< vtkDataSet > faceMesh ) | |
| vtkSmartPointer< vtkDataSet > faceMesh, | |
| bool performGlobalCheck ) | |
| { | |
| // The vtk field to the collocated nodes for fractures. | |
| string const COLLOCATED_NODES = "collocated_nodes"; | |
| vtkIdTypeArray const * collocatedNodes = vtkIdTypeArray::FastDownCast( faceMesh->GetPointData()->GetArray( COLLOCATED_NODES.c_str() ) ); | |
| // Depending on the parallel split, the vtk face mesh may be empty on a rank. | |
| // In that case, vtk will not provide any field for the emtpy mesh. | |
| // Therefore, not finding the duplicated nodes field on a rank cannot be interpreted as a globally missing field. | |
| // Converting the address into an integer and exchanging it through the MPI ranks let us find out | |
| // if the field is globally missing or not. | |
| std::uintptr_t const address = MpiWrapper::max( reinterpret_cast< std::uintptr_t >(collocatedNodes) ); | |
| if( address == 0 ) | |
| if( performGlobalCheck ) | |
| { | |
| GEOS_LOG_RANK_0( "Available point data fields in '" << faceBlockName << "':" ); | |
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | |
| // Depending on the parallel split, the vtk face mesh may be empty on a rank. | |
| // In that case, vtk will not provide any field for the empty mesh. | |
| // Therefore, not finding the duplicated nodes field on a rank cannot be interpreted as a globally missing field. | |
| // Converting the address into an integer and exchanging it through the MPI ranks let us find out | |
| // if the field is globally missing or not. | |
| std::uintptr_t const address = MpiWrapper::max( reinterpret_cast< std::uintptr_t >(collocatedNodes) ); | |
| if( address == 0 ) | |
| { | |
| GEOS_LOG_RANK_0( " - " << faceMesh->GetPointData()->GetArrayName( i ) << " of type '" << faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() << "'" ); | |
| GEOS_LOG_RANK_0( GEOS_FMT( "Available point data fields in '{}':", faceBlockName ) ); | |
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | |
| { | |
| GEOS_LOG_RANK_0( GEOS_FMT( " - {} of type '{}'", | |
| faceMesh->GetPointData()->GetArrayName( i ), | |
| faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() ) ); | |
| } | |
| GEOS_ERROR( GEOS_FMT( "Could not find valid field \"{}\" for fracture \"{}\".", | |
| COLLOCATED_NODES, | |
| faceBlockName ) ); | |
| } | |
| } | |
| else | |
| { | |
| if( !collocatedNodes ) | |
| { | |
| GEOS_LOG_RANK_0( GEOS_FMT( "Available point data fields in '{}':", faceBlockName ) ); | |
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | |
| { | |
| GEOS_LOG_RANK_0( GEOS_FMT( " - {} of type '{}'", | |
| faceMesh->GetPointData()->GetArrayName( i ), | |
| faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() ) ); | |
| } | |
| GEOS_ERROR( GEOS_FMT( "Could not find valid field \"{}\" for fracture \"{}\" on this rank.", | |
| COLLOCATED_NODES, | |
| faceBlockName ) ); | |
| CollocatedNodes::CollocatedNodes( string const & faceBlockName, | |
| vtkSmartPointer< vtkDataSet > faceMesh, | |
| bool performGlobalCheck ) | |
| { | |
| string const COLLOCATED_NODES = "collocated_nodes"; | |
| vtkIdTypeArray const * collocatedNodes = vtkIdTypeArray::FastDownCast( | |
| faceMesh->GetPointData()->GetArray( COLLOCATED_NODES.c_str() ) ); | |
| // Determine whether the field is missing: | |
| // - if globalCheck: reduce across ranks so an empty-mesh detection is any ranks | |
| bool const isMissing = performGlobalCheck | |
| ? ( MpiWrapper::max( reinterpret_cast< std::uintptr_t >( collocatedNodes ) ) == 0 ) | |
| : ( collocatedNodes == nullptr ); | |
| if( isMissing ) | |
| { | |
| GEOS_LOG_RANK_0( GEOS_FMT( "Available point data fields in '{}':", faceBlockName ) ); | |
| for( int i = 0; i < faceMesh->GetPointData()->GetNumberOfArrays(); ++i ) | |
| { | |
| GEOS_LOG_RANK_0( GEOS_FMT( " - {} of type '{}'", | |
| faceMesh->GetPointData()->GetArrayName( i ), | |
| faceMesh->GetPointData()->GetArray( i )->GetDataTypeAsString() ) ); | |
| } | |
| GEOS_ERROR( GEOS_FMT( "Could not find valid field \"{}\" for fracture \"{}\"{}.", | |
| COLLOCATED_NODES, | |
| faceBlockName, | |
| performGlobalCheck ? "" : " on this rank" ) ); | |
| } | |
| else | |
| init( collocatedNodes ); | |
| } | |
| // Options: [use_defaults, log_level, seed, coupling] | ||
| // PARMETIS_PSR_UNCOUPLED = 0 (default - uses PartitionSmallGraph) | ||
| // PARMETIS_PSR_COUPLED = 1 (forces distributed algorithm) | ||
| idx_t options[4] = { 1, 0, 2022, 0 }; |
There was a problem hiding this comment.
| idx_t options[4] = { 1, 0, 2022, 0 }; | |
| idx_t options[4] = { 1, 0, 2022, PARMETIS_PSR_UNCOUPLED }; |
|
|
||
| // GetCellAtId() is conditionally thread-safe, use POLICY argument | ||
| forAll< POLICY >( num3dCells, [&cells, &globalPointId, elemToNodes = elemToNodes.toView()] ( localIndex const cellIdx ) | ||
| forAll< POLICY >( numCells, [&cells, &globalPointId, elemToNodes = elemToNodes.toView()] ( localIndex const cellIdx ) |
There was a problem hiding this comment.
why aren't we using GetCellPoints() here ?
https://vtk.org/doc/nightly/html/classvtkDataSet.html#ab4d677c257a58e4eb4a80757f9b371ea
There was a problem hiding this comment.
This might the need for passing cells through the interface and the IsShareable logic
There was a problem hiding this comment.
yes, that part was refactored.
@jafranc, could you review the changes and confirm that they correctly addressed your comments.
| int myEdgeCount = static_cast< int >( myEdgesSrc.size() ); | ||
|
|
||
| // Gather counts from all ranks | ||
| array1d< int > allEdgeCounts( numRanks ); | ||
| MpiWrapper::allgather( &myEdgeCount, 1, allEdgeCounts.data(), 1, comm ); | ||
|
|
||
| // Compute displacements for symmetrization | ||
| array1d< int > edgeDispls( numRanks + 1 ); | ||
| edgeDispls[0] = 0; | ||
| for( int r = 0; r < numRanks; ++r ) | ||
| { | ||
| edgeDispls[r + 1] = edgeDispls[r] + allEdgeCounts[r]; | ||
| } |
There was a problem hiding this comment.
| int myEdgeCount = static_cast< int >( myEdgesSrc.size() ); | |
| // Gather counts from all ranks | |
| array1d< int > allEdgeCounts( numRanks ); | |
| MpiWrapper::allgather( &myEdgeCount, 1, allEdgeCounts.data(), 1, comm ); | |
| // Compute displacements for symmetrization | |
| array1d< int > edgeDispls( numRanks + 1 ); | |
| edgeDispls[0] = 0; | |
| for( int r = 0; r < numRanks; ++r ) | |
| { | |
| edgeDispls[r + 1] = edgeDispls[r] + allEdgeCounts[r]; | |
| } | |
| int myEdgeCount = static_cast< int >( myEdgesSrc.size() ); | |
| // Gather counts from all ranks | |
| array1d< int > allEdgeCounts( numRanks ); | |
| MpiWrapper::allgather( &myEdgeCount, 1, allEdgeCounts.data(), 1, comm ); | |
| // Compute displacements for symmetrization | |
| array1d< int > edgeDispls( numRanks + 1 ); | |
| edgeDispls[0] = 0; | |
| std::partial_sum(allEdgeCounts.begin(), allEdgeCounts.end(), | |
| edgeDispls.begin() + 1); | |
| // Check 6: Graph symmetry for local edges | ||
| // ----------------------------------------------------------------------- | ||
| { | ||
| std::unordered_set< uint64_t > localOutgoingNeighbors; |
There was a problem hiding this comment.
can we refactor this duplication into a single locally declared function ?
| // SECTION 5: PARTITION UNPACKING (after ParMETIS) | ||
| // ============================================================================================= | ||
| array1d< int64_t > | ||
| unpackSuperCellPartitioning( |
There was a problem hiding this comment.
as packing/unpacking is rather specific in GEOS context, unless related, I would suggest renaming
There was a problem hiding this comment.
renamed expandSuperCellPartitioningToCells
| // ----------------------------------------------------------------------- | ||
| // Step 1: Build 3D cell connectivity graph via fractures | ||
| // ----------------------------------------------------------------------- | ||
| std::map< vtkIdType, std::set< vtkIdType > > fractureGraph; |
There was a problem hiding this comment.
isn't the use of stdMap rather than std::map a directive ?
There was a problem hiding this comment.
yes, changed all of them in favor of StdContainerWrappers
| vtkIdType const numCells = cells3D->GetNumberOfCells(); | ||
| array1d< int64_t > cellPartitioning( numCells ); | ||
|
|
||
| for( vtkIdType i = 0; i < numCells; ++i ) | ||
| { | ||
| vtkIdType superCellId = superCellIdArray->GetValue( i ); | ||
| auto it = superCellIdToLocalIdx.find( superCellId ); | ||
|
|
||
| GEOS_ERROR_IF( it == superCellIdToLocalIdx.end(), | ||
| GEOS_FMT( "Rank {}: Cell {} has unknown super-cell ID {}", | ||
| rank, i, superCellId ) ); | ||
|
|
||
| cellPartitioning[i] = superPartitioning[it->second]; | ||
| } | ||
|
|
||
| // ----------------------------------------------------------------------- | ||
| // Step 3: Validate that super-cells weren't split | ||
| // ----------------------------------------------------------------------- | ||
| stdMap< vtkIdType, std::set< int64_t > > superCellToRanks; | ||
|
|
||
| for( vtkIdType i = 0; i < numCells; ++i ) | ||
| { | ||
| vtkIdType const scId = superCellIdArray->GetValue( i ); | ||
| superCellToRanks.get_inserted( scId ).insert( cellPartitioning[i] ); | ||
| } | ||
|
|
||
| vtkIdType numSplitSuperCells = 0; | ||
| for( auto const & [superCellId, ranks] : superCellToRanks ) | ||
| { | ||
| if( ranks.size() > 1 ) | ||
| { | ||
| ++numSplitSuperCells; | ||
| } | ||
| } | ||
|
|
||
| vtkIdType totalSplitSuperCells = MpiWrapper::sum( numSplitSuperCells, comm ); | ||
|
|
||
| GEOS_ERROR_IF( totalSplitSuperCells > 0, | ||
| GEOS_FMT( "Partitioning failed: {:L} super-cells were split across ranks!", | ||
| totalSplitSuperCells ) ); | ||
|
|
||
| return cellPartitioning; |
There was a problem hiding this comment.
Can't we group these do-check architecture into a single loop leveraging set's lookup insertion idiom (like above) and disjunction on the second value of the tuple ?

Replaces #3985