Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
Parallel Communication

Functions for parallel mesh operations and data transfer. More...

+ Collaboration diagram for Parallel Communication:

Functions

ErrCode iMOAB_SendMesh (iMOAB_AppID pid, MPI_Comm *joint_communicator, MPI_Group *receivingGroup, int *rcompid, int *method)
 Transfer mesh data from sender component to receiver component. More...
 
ErrCode iMOAB_ReceiveMesh (iMOAB_AppID pid, MPI_Comm *joint_communicator, MPI_Group *sendingGroup, int *scompid)
 Receive mesh data on target component from sending component. More...
 
ErrCode iMOAB_SendElementTag (iMOAB_AppID pid, const iMOAB_String tag_storage_name, MPI_Comm *joint_communicator, int *context_id)
 Send element tag data from calling component to another component. More...
 
ErrCode iMOAB_ReceiveElementTag (iMOAB_AppID pid, const iMOAB_String tag_storage_name, MPI_Comm *joint_communicator, int *context_id)
 Receive element tag data from another component. More...
 
ErrCode iMOAB_ComputeCommGraph (iMOAB_AppID pid1, iMOAB_AppID pid2, MPI_Comm *joint_communicator, MPI_Group *group1, MPI_Group *group2, int *type1, int *type2, int *comp1, int *comp2)
 Compute communication graph between two components using rendezvous algorithm. More...
 
ErrCode iMOAB_MergeVertices (iMOAB_AppID pid)
 Merge duplicate vertices in a mesh and update connectivity information. More...
 

Detailed Description

Functions for parallel mesh operations and data transfer.

Function Documentation

◆ iMOAB_ComputeCommGraph()

ErrCode iMOAB_ComputeCommGraph ( iMOAB_AppID  pid1,
iMOAB_AppID  pid2,
MPI_Comm *  joint_communicator,
MPI_Group *  group1,
MPI_Group *  group2,
int *  type1,
int *  type2,
int *  comp1,
int *  comp2 
)

Compute communication graph between two components using rendezvous algorithm.

Implements sophisticated rendezvous-based algorithm to determine which processes from component 1 need to communicate with which processes from component 2. Uses global entity IDs (or DOFs) as "rendezvous points" to discover communication patterns between components.

Algorithm Workflow:
  1. COMMUNICATOR SETUP: Resolve MPI communicator and group handles (Fortran F2C conversions), create bidirectional ParCommGraph instances for both components, store graphs in pgraph maps.
  2. ENTITY ID COLLECTION: Extract entity IDs based on type parameter:
    • type=1: GLOBAL_DOFS from MBQUAD elements (spectral elements)
    • type=2: GLOBAL_ID from MBVERTEX entities (vertex-based coupling)
    • type=3: GLOBAL_ID from 2D elements (finite volume meshes)
    • Handle TempestRemap coverage mesh entities
  3. RENDEZVOUS ALGORITHM: Create TupleList for each component with (target_proc, entity_id) pairs. Use hash-based distribution (entity_id % numProcs). Execute crystal router (gs_transfer) to send entity IDs to rendezvous processes. Sort received data by entity ID for efficient matching.
  4. COMMUNICATION DISCOVERY: Synchronously iterate through both sorted TupleLists, find matching entity IDs between components (intersection), record which processes share each entity, build reverse communication maps.
  5. GRAPH CONSTRUCTION: Send communication information back to original processes via crystal router. Each process learns communication partners. Store patterns in ParCommGraph.
Data Processed:
  • Entity IDs: Global identifiers serving as rendezvous points
  • Process mappings: Which processes own which entities
  • Communication patterns: Sender→receiver relationships
Parameters
[in]pid1Application ID for component 1 (or negative if not on these ranks)
[in]pid2Application ID for component 2 (or negative if not on these ranks)
[in]joint_communicatorMPI communicator spanning both component groups
[in]group1MPI group for component 1 processes
[in]group2MPI group for component 2 processes
[in]type1Entity type for component 1 (1=DOFs, 2=vertices, 3=elements)
[in]type2Entity type for component 2 (1=DOFs, 2=vertices, 3=elements)
[in]comp1Component 1 external ID
[in]comp2Component 2 external ID
Note
Complexity: O(N log N) sorting, O(N/P) communication per process
Use cases: Coupling different mesh types, establishing comm for data transfer, mesh intersection prep
Warning
Both components must have consistent global entity IDs for rendezvous to work
Returns
moab::MB_SUCCESS on success, error code otherwise

Definition at line 3753 of file iMOAB.cpp.

3762 {
3763  // Validate input parameters
3764  assert( joint_communicator );
3765  assert( group1 );
3766  assert( group2 );
3767  int localRank = 0, numProcs = 1;
3768 
3769  // Determine if either component is using Fortran (affects MPI handle conversion)
3770  bool isFortran = false;
3771  if( *pid1 >= 0 ) isFortran = isFortran || context.appDatas[*pid1].is_fortran;
3772  if( *pid2 >= 0 ) isFortran = isFortran || context.appDatas[*pid2].is_fortran;
3773 
3774  // Handle Fortran-to-C MPI handle conversion if needed
3775  MPI_Comm global =
3776  ( isFortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) ) : *joint_communicator );
3777  MPI_Group srcGroup = ( isFortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( group1 ) ) : *group1 );
3778  MPI_Group tgtGroup = ( isFortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( group2 ) ) : *group2 );
3779 
3780  // Get process information for the joint communicator
3781  MPI_Comm_rank( global, &localRank );
3782  MPI_Comm_size( global, &numProcs );
3783 
3784  // Create bidirectional communication graphs for both components
3785 
3786  // Create communication graphs for both components (bidirectional)
3787  ParCommGraph* cgraph = nullptr;
3788  ParCommGraph* cgraph_rev = nullptr;
3789 
3790  if( *pid1 >= 0 )
3791  {
3792  // Create communication graph for component 1 -> component 2
3793  appData& data = context.appDatas[*pid1];
3794  auto mt = data.pgraph.find( *comp2 );
3795  if( mt != data.pgraph.end() ) data.pgraph.erase( mt ); // Remove existing graph if present
3796  cgraph = new ParCommGraph( global, srcGroup, tgtGroup, *comp1, *comp2 );
3797  context.appDatas[*pid1].pgraph[*comp2] = cgraph; // Store with target component ID as key
3798  }
3799 
3800  if( *pid2 >= 0 )
3801  {
3802  // Create reverse communication graph for component 2 -> component 1
3803  appData& data = context.appDatas[*pid2];
3804  auto mt = data.pgraph.find( *comp1 );
3805  if( mt != data.pgraph.end() ) data.pgraph.erase( mt ); // Remove existing graph if present
3806  cgraph_rev = new ParCommGraph( global, tgtGroup, srcGroup, *comp2, *comp1 );
3807  context.appDatas[*pid2].pgraph[*comp1] = cgraph_rev; // Store with source component ID as key
3808  }
3809 
3810  // Initialize TupleLists for rendezvous algorithm
3811  // Each tuple contains: (target_process, entity_id) pairs for hash-based distribution
3812  TupleList TLcomp1;
3813  TLcomp1.initialize( 2, 0, 0, 0, 0 ); // 2 integers: target_proc, entity_id
3814  TupleList TLcomp2;
3815  TLcomp2.initialize( 2, 0, 0, 0, 0 ); // 2 integers: target_proc, entity_id
3816 
3817  // Enable write access for building the tuple lists
3818  TLcomp1.enableWriteAccess();
3819 
3820  // Get tag handles for entity identification
3821  // GLOBAL_DOFS: for spectral elements (type 1)
3822  // GLOBAL_ID: for vertices (type 2) and 2D elements (type 3)
3823  Tag gdsTag;
3824  int lenTagType1 = 1;
3825  if( 1 == *type1 || 1 == *type2 )
3826  {
3827  // Get GLOBAL_DOFS tag for spectral elements (usually 16 DOFs per element)
3828  MB_CHK_ERR( context.MBI->tag_get_handle( "GLOBAL_DOFS", gdsTag ) );
3829  MB_CHK_ERR( context.MBI->tag_get_length( gdsTag, lenTagType1 ) ); // Usually 16 DOFs per element
3830  }
3831  Tag gidTag = context.MBI->globalId_tag(); // Standard GLOBAL_ID tag
3832 
3833  // Collect entity IDs from component 1 for rendezvous algorithm
3834  std::vector< int > valuesComp1;
3835  if( *pid1 >= 0 )
3836  {
3837  appData& data1 = context.appDatas[*pid1];
3838  EntityHandle fset1 = data1.file_set;
3839 
3840  // Handle TempestRemap coverage mesh for intersection applications
3841 #ifdef MOAB_HAVE_TEMPESTREMAP
3842  if( data1.tempestData.remapper != nullptr ) // This is the case for intersection operations
3843  fset1 = data1.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3844 #endif
3845 
3846  Range ents_of_interest;
3847  if( *type1 == 1 ) // Spectral elements: get GLOBAL_DOFS from MBQUAD elements
3848  {
3849  assert( gdsTag );
3850  MB_CHK_ERR( context.MBI->get_entities_by_type( fset1, MBQUAD, ents_of_interest ) );
3851  valuesComp1.resize( ents_of_interest.size() * lenTagType1 );
3852  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, ents_of_interest, &valuesComp1[0] ) );
3853  }
3854  else if( *type1 == 2 ) // Vertex-based coupling: get GLOBAL_ID from MBVERTEX entities
3855  {
3856  MB_CHK_ERR( context.MBI->get_entities_by_type( fset1, MBVERTEX, ents_of_interest ) );
3857  valuesComp1.resize( ents_of_interest.size() );
3858  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp1[0] ) );
3859  }
3860  else if( *type1 == 3 ) // Finite volume meshes: get GLOBAL_ID from 2D elements
3861  {
3862  MB_CHK_ERR( context.MBI->get_entities_by_dimension( fset1, 2, ents_of_interest ) );
3863  valuesComp1.resize( ents_of_interest.size() );
3864  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp1[0] ) );
3865  }
3866  else
3867  {
3868  MB_CHK_ERR( MB_FAILURE ); // Only types 1, 2, or 3 are supported
3869  }
3870 
3871  // Build tuple list for component 1: (target_process, entity_id) pairs
3872  // Use hash-based distribution: entity_id % numProcs determines target process
3873  std::set< int > uniq( valuesComp1.begin(), valuesComp1.end() ); // Remove duplicates
3874  TLcomp1.resize( uniq.size() );
3875  for( std::set< int >::iterator sit = uniq.begin(); sit != uniq.end(); sit++ )
3876  {
3877  int marker = *sit; // Entity ID
3878  int to_proc = marker % numProcs; // Hash-based target process
3879  int n = TLcomp1.get_n();
3880  TLcomp1.vi_wr[2 * n] = to_proc; // Target process
3881  TLcomp1.vi_wr[2 * n + 1] = marker; // Entity ID
3882  TLcomp1.inc_n();
3883  }
3884  }
3885 
3886  // Execute crystal router transfer for component 1 entity IDs
3887  // This sends entity IDs to rendezvous processes based on hash distribution
3888  ProcConfig pc( global ); // Process configuration for crystal router
3889  pc.crystal_router()->gs_transfer( 1, TLcomp1, 0 ); // Transfer to joint tasks with markers
3890 
3891  // Sort component 1 tuple list by entity ID (key 1) for efficient matching
3892 #ifdef VERBOSE
3893  std::stringstream ff1;
3894  ff1 << "TLcomp1_" << localRank << ".txt";
3895  TLcomp1.print_to_file( ff1.str().c_str() ); // Debug output before sorting
3896 #endif
3897  moab::TupleList::buffer sort_buffer;
3898  sort_buffer.buffer_init( TLcomp1.get_n() );
3899  TLcomp1.sort( 1, &sort_buffer ); // Sort by entity ID (column 1)
3900  sort_buffer.reset();
3901 #ifdef VERBOSE
3902  TLcomp1.print_to_file( ff1.str().c_str() ); // Debug output after sorting
3903 #endif
3904  // Now process component 2 in the same way as component 1
3905  TLcomp2.enableWriteAccess();
3906 
3907  // Collect entity IDs from component 2 for rendezvous algorithm
3908  std::vector< int > valuesComp2;
3909  if( *pid2 >= 0 )
3910  {
3911  appData& data2 = context.appDatas[*pid2];
3912  EntityHandle fset2 = data2.file_set;
3913 
3914  // Handle TempestRemap coverage mesh for intersection applications
3915 #ifdef MOAB_HAVE_TEMPESTREMAP
3916  if( data2.tempestData.remapper != nullptr ) // This is the case for intersection operations
3917  fset2 = data2.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3918 #endif
3919 
3920  Range ents_of_interest;
3921  if( *type2 == 1 ) // Spectral elements: get GLOBAL_DOFS from MBQUAD elements
3922  {
3923  assert( gdsTag );
3924  MB_CHK_ERR( context.MBI->get_entities_by_type( fset2, MBQUAD, ents_of_interest ) );
3925  valuesComp2.resize( ents_of_interest.size() * lenTagType1 );
3926  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, ents_of_interest, &valuesComp2[0] ) );
3927  }
3928  else if( *type2 == 2 ) // Vertex-based coupling: get GLOBAL_ID from MBVERTEX entities
3929  {
3930  MB_CHK_ERR( context.MBI->get_entities_by_type( fset2, MBVERTEX, ents_of_interest ) );
3931  valuesComp2.resize( ents_of_interest.size() );
3932  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp2[0] ) );
3933  }
3934  else if( *type2 == 3 ) // Finite volume meshes: get GLOBAL_ID from 2D elements
3935  {
3936  MB_CHK_ERR( context.MBI->get_entities_by_dimension( fset2, 2, ents_of_interest ) );
3937  valuesComp2.resize( ents_of_interest.size() );
3938  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp2[0] ) );
3939  }
3940  else
3941  {
3942  MB_CHK_ERR( MB_FAILURE ); // Only types 1, 2, or 3 are supported
3943  }
3944  // Build tuple list for component 2: (target_process, entity_id) pairs
3945  // Use hash-based distribution: entity_id % numProcs determines target process
3946  std::set< int > uniq( valuesComp2.begin(), valuesComp2.end() ); // Remove duplicates
3947  TLcomp2.resize( uniq.size() );
3948  for( std::set< int >::iterator sit = uniq.begin(); sit != uniq.end(); sit++ )
3949  {
3950  int marker = *sit; // Entity ID
3951  int to_proc = marker % numProcs; // Hash-based target process
3952  int n = TLcomp2.get_n();
3953  TLcomp2.vi_wr[2 * n] = to_proc; // Target process
3954  TLcomp2.vi_wr[2 * n + 1] = marker; // Entity ID
3955  TLcomp2.inc_n();
3956  }
3957  }
3958 
3959  // Execute crystal router transfer for component 2 entity IDs
3960  pc.crystal_router()->gs_transfer( 1, TLcomp2, 0 ); // Transfer to joint tasks with markers
3961 
3962  // Sort component 2 tuple list by entity ID (key 1) for efficient matching
3963 #ifdef VERBOSE
3964  std::stringstream ff2;
3965  ff2 << "TLcomp2_" << localRank << ".txt";
3966  TLcomp2.print_to_file( ff2.str().c_str() ); // Debug output before sorting
3967 #endif
3968  sort_buffer.buffer_reserve( TLcomp2.get_n() );
3969  TLcomp2.sort( 1, &sort_buffer ); // Sort by entity ID (column 1)
3970  sort_buffer.reset();
3971 #ifdef VERBOSE
3972  TLcomp2.print_to_file( ff2.str().c_str() ); // Debug output after sorting
3973 #endif
3974  // RENDEZVOUS ALGORITHM: Find matching entity IDs between components
3975  // Now we need to send back communication information from the rendezvous point
3976  // Loop synchronously over both sorted tuple lists to find matching entity IDs
3977  // Build new tuple lists to send communication information back to original processes
3978 
3979  // Tuple lists for sending communication info back to components
3980  // Format: (target_process, entity_id, source_process_from_other_component)
3981  TupleList TLBackToComp1;
3982  TLBackToComp1.initialize( 3, 0, 0, 0, 0 ); // 3 integers: target_proc, entity_id, source_proc_from_comp2
3983  TLBackToComp1.enableWriteAccess();
3984 
3985  TupleList TLBackToComp2;
3986  TLBackToComp2.initialize( 3, 0, 0, 0, 0 ); // 3 integers: target_proc, entity_id, source_proc_from_comp1
3987  TLBackToComp2.enableWriteAccess();
3988 
3989  // Get sizes of both tuple lists for synchronized iteration
3990  int n1 = TLcomp1.get_n();
3991  int n2 = TLcomp2.get_n();
3992 
3993  // Synchronized iteration through both sorted tuple lists to find matching entity IDs
3994  int indexInTLComp1 = 0;
3995  int indexInTLComp2 = 0;
3996  if( n1 > 0 && n2 > 0 )
3997  {
3998  while( indexInTLComp1 < n1 && indexInTLComp2 < n2 ) // Continue until either list is exhausted
3999  {
4000  // Get current entity IDs from both components
4001  int currentValue1 = TLcomp1.vi_rd[2 * indexInTLComp1 + 1]; // Entity ID from comp1
4002  int currentValue2 = TLcomp2.vi_rd[2 * indexInTLComp2 + 1]; // Entity ID from comp2
4003 
4004  if( currentValue1 < currentValue2 )
4005  {
4006  // Entity ID exists in comp1 but not in comp2 - skip comp1 entry
4007  // This is normal for non-overlapping meshes
4008  indexInTLComp1++;
4009  continue;
4010  }
4011  if( currentValue1 > currentValue2 )
4012  {
4013  // Entity ID exists in comp2 but not in comp1 - skip comp2 entry
4014  // This is normal for non-overlapping meshes
4015  indexInTLComp2++;
4016  continue;
4017  }
4018  // Found matching entity ID! Count consecutive entries with same ID in both lists
4019  int size1 = 1;
4020  int size2 = 1;
4021  while( indexInTLComp1 + size1 < n1 && currentValue1 == TLcomp1.vi_rd[2 * ( indexInTLComp1 + size1 ) + 1] )
4022  size1++; // Count consecutive entries with same entity ID in comp1
4023  while( indexInTLComp2 + size2 < n2 && currentValue2 == TLcomp2.vi_rd[2 * ( indexInTLComp2 + size2 ) + 1] )
4024  size2++; // Count consecutive entries with same entity ID in comp2
4025 
4026  // Create communication pairs for all combinations of processes that share this entity ID
4027  for( int i1 = 0; i1 < size1; i1++ )
4028  {
4029  for( int i2 = 0; i2 < size2; i2++ )
4030  {
4031  // Send communication info back to component 1 processes
4032  int n = TLBackToComp1.get_n();
4033  TLBackToComp1.reserve();
4034  TLBackToComp1.vi_wr[3 * n] =
4035  TLcomp1.vi_rd[2 * ( indexInTLComp1 + i1 )]; // Target process from comp1
4036  TLBackToComp1.vi_wr[3 * n + 1] = currentValue1; // Shared entity ID
4037  TLBackToComp1.vi_wr[3 * n + 2] =
4038  TLcomp2.vi_rd[2 * ( indexInTLComp2 + i2 )]; // Source process from comp2
4039 
4040  // Send communication info back to component 2 processes
4041  n = TLBackToComp2.get_n();
4042  TLBackToComp2.reserve();
4043  TLBackToComp2.vi_wr[3 * n] =
4044  TLcomp2.vi_rd[2 * ( indexInTLComp2 + i2 )]; // Target process from comp2
4045  TLBackToComp2.vi_wr[3 * n + 1] = currentValue1; // Shared entity ID
4046  TLBackToComp2.vi_wr[3 * n + 2] =
4047  TLcomp1.vi_rd[2 * ( indexInTLComp1 + i1 )]; // Source process from comp1
4048  }
4049  }
4050 
4051  // Advance indices past all entries with the current entity ID
4052  indexInTLComp1 += size1;
4053  indexInTLComp2 += size2;
4054  }
4055  }
4056  // Send communication information back to original component processes
4057  pc.crystal_router()->gs_transfer( 1, TLBackToComp1, 0 ); // Send to component 1 processes
4058  pc.crystal_router()->gs_transfer( 1, TLBackToComp2, 0 ); // Send to component 2 processes
4059 
4060  // Process communication information on component 1 processes
4061  if( *pid1 >= 0 )
4062  {
4063  // Sort received communication information by entity ID for efficient processing
4064 #ifdef VERBOSE
4065  std::stringstream f1;
4066  f1 << "TLBack1_" << localRank << ".txt";
4067  TLBackToComp1.print_to_file( f1.str().c_str() ); // Debug output before sorting
4068 #endif
4069  sort_buffer.buffer_reserve( TLBackToComp1.get_n() );
4070  TLBackToComp1.sort( 1, &sort_buffer ); // Sort by entity ID (column 1)
4071  sort_buffer.reset();
4072 #ifdef VERBOSE
4073  TLBackToComp1.print_to_file( f1.str().c_str() ); // Debug output after sorting
4074 #endif
4075 
4076  // Set up communication graph for component 1 based on discovered communication patterns
4077  // This establishes which processes component 1 needs to communicate with
4078  cgraph->settle_comm_by_ids( *comp1, TLBackToComp1, valuesComp1 );
4079  }
4080  // Process communication information on component 2 processes
4081  if( *pid2 >= 0 )
4082  {
4083  // Sort received communication information by source process for efficient processing
4084 #ifdef VERBOSE
4085  std::stringstream f2;
4086  f2 << "TLBack2_" << localRank << ".txt";
4087  TLBackToComp2.print_to_file( f2.str().c_str() ); // Debug output before sorting
4088 #endif
4089  sort_buffer.buffer_reserve( TLBackToComp2.get_n() );
4090  TLBackToComp2.sort( 2, &sort_buffer ); // Sort by source process (column 2)
4091  sort_buffer.reset();
4092 #ifdef VERBOSE
4093  TLBackToComp2.print_to_file( f2.str().c_str() ); // Debug output after sorting
4094 #endif
4095 
4096  // Set up reverse communication graph for component 2 based on discovered communication patterns
4097  // This establishes which processes component 2 needs to communicate with
4098  cgraph_rev->settle_comm_by_ids( *comp2, TLBackToComp2, valuesComp2 );
4099  }
4100 
4101  // Communication graph computation complete - both components now know their communication partners
4102  return moab::MB_SUCCESS;
4103 }

References GlobalContext::appDatas, context, moab::Remapper::CoveringMesh, moab::ProcConfig::crystal_router(), moab::TupleList::enableWriteAccess(), appData::file_set, moab::Interface::get_entities_by_dimension(), moab::Interface::get_entities_by_type(), moab::TupleList::get_n(), moab::Interface::globalId_tag(), moab::TupleList::inc_n(), moab::TupleList::initialize(), MB_CHK_ERR, MB_SUCCESS, GlobalContext::MBI, MBQUAD, MBVERTEX, appData::pgraph, moab::TupleList::print_to_file(), moab::TupleList::reserve(), moab::TupleList::buffer::reset(), moab::TupleList::resize(), moab::ParCommGraph::settle_comm_by_ids(), moab::Range::size(), moab::TupleList::sort(), moab::Interface::tag_get_data(), moab::Interface::tag_get_handle(), moab::Interface::tag_get_length(), moab::TupleList::vi_rd, and moab::TupleList::vi_wr.

◆ iMOAB_MergeVertices()

ErrCode iMOAB_MergeVertices ( iMOAB_AppID  pid)

Merge duplicate vertices in a mesh and update connectivity information.

This function consolidates duplicate vertices that may exist after mesh operations such as intersection or migration. Vertices with identical GLOBAL_ID values are merged, and connectivity of elements is updated accordingly. The function performs both local (within-process) and parallel (across-process) vertex merging.

Algorithm Workflow:
  1. TAG COLLECTION: Collect critical tags that need to be preserved during merge:
    • GLOBAL_ID: Required for vertex identification (fatal error if missing)
    • area: Optional - element area values to preserve
    • frac: Optional - fraction values for conservation
  2. LOCAL VERTEX DEDUPLICATION: Remove duplicate vertices within each process using spatial tolerance (1.0e-9). IntxUtils::remove_duplicate_vertices merges vertices and updates connectivity, preserving tag values from first occurrence.
  3. MATERIAL SET CLEANUP: Clear all elements from material sets and re-populate with valid elements from the current file set. Ensures material set integrity after topology changes.
  4. MESH INFO UPDATE: Refresh internal mesh information (vertex counts, element counts, owned/ghosted entity information) and recalculate mesh statistics.
  5. PARALLEL VERTEX MERGING: ParallelMergeMesh handles vertices shared across process boundaries. Identifies vertices with identical GLOBAL_ID on different processes and merges them while updating parallel communication graph.
  6. GLOBAL ID ASSIGNMENT: Reassign consistent global IDs to vertices after merge. Only updates vertex global IDs (cells already have correct IDs).
  7. PARTITION TAG SETUP: Create/retrieve PARALLEL_PARTITION tag for visualization indicating which process owns each mesh set.
Data Processed:
  • Vertices: Spatial coordinates and associated tags
  • Elements: Connectivity information referencing vertices
  • Tags: GLOBAL_ID (required), area, frac (optional)
  • Material sets: Collections of elements with shared properties
Common Use Cases:
  • After mesh intersection: Overlapping meshes create duplicate vertices
  • After mesh migration: Vertices copied to multiple processes need merging
  • After remapping: Source/target vertices may coincide
  • Quality improvement: Remove numerical duplicates from mesh operations
Parameters
[in]pidApplication ID for the mesh
Precondition
GLOBAL_ID tag must exist on vertices
Postcondition
Duplicate vertices merged both locally and across processes
Element connectivity updated to reference merged vertices
PARALLEL_PARTITION tag set for visualization
Note
Parallelism: Performs both local (per-process) and parallel (across-process) merging
Warning
Modifies mesh topology and connectivity - may invalidate cached entity handles
Returns
moab::MB_SUCCESS on success, moab::MB_FAILURE if GLOBAL_ID tag missing

Definition at line 4158 of file iMOAB.cpp.

4159 {
4160  // =========================================================================
4161  // Validate inputs and initialize local variables
4162  // =========================================================================
4163 
4164  // Get application data and parallel communicator for this component
4165  // data contains mesh info, ranges, and pcomm holds parallel communication state
4166  appData& data = context.appDatas[*pid];
4167  ParallelComm* pco = data.pcomm;
4168 
4169  // Initialize return value to success state
4170  ErrorCode rval = MB_SUCCESS;
4171 
4172  // =========================================================================
4173  // STEP 1: Collect tags that need to be preserved during vertex merging
4174  // =========================================================================
4175 
4176  // Initialize vector to hold tags that should be preserved during merge
4177  // These tags contain important data that must be maintained when vertices are merged
4178  // The merge operation will preserve tag values from the first vertex in each duplicate set
4179  std::vector< Tag > tagsList;
4180  Tag tag;
4181 
4182  // GLOBAL_ID is mandatory - used to identify duplicate vertices across processes
4183  // Without this tag, we cannot determine which vertices should be merged
4184  tag = context.MBI->globalId_tag();
4185  if( !tag )
4186  {
4187  // Fatal error: GLOBAL_ID tag is required for merge operations
4188  // Return failure as merge cannot proceed without vertex identification capability
4189  return moab::MB_FAILURE;
4190  }
4191  // Add GLOBAL_ID to the list of tags to preserve during merge
4192  tagsList.push_back( tag );
4193 
4194  // Area tag (optional) - contains element area values for conservation checks
4195  // Used in remapping applications to ensure conservation properties
4196  rval = context.MBI->tag_get_handle( "area", tag );
4197  if( tag && MB_SUCCESS == rval )
4198  {
4199  // Area tag found, include it in preserved tags
4200  tagsList.push_back( tag );
4201  }
4202 
4203  // frac tag (optional) - contains fractional coverage for remapping conservation
4204  // Used to track fractional ownership in intersection-based remapping
4205  rval = context.MBI->tag_get_handle( "frac", tag );
4206  if( tag && MB_SUCCESS == rval )
4207  {
4208  // Fraction tag found, include it in preserved tags
4209  tagsList.push_back( tag );
4210  }
4211 
4212  // ========================================================================
4213  // STEP 2: Remove duplicate vertices within this process (local merge)
4214  // ========================================================================
4215 
4216  // Set spatial tolerance for considering vertices identical (meters on unit sphere)
4217  // This tolerance accounts for numerical precision issues in floating-point coordinates
4218  // 1.0e-9 is a typical tolerance for spherical meshes near unit radius
4219  const double tol = 1.0e-9;
4220 
4221  // Perform local vertex deduplication using MOAB's IntxUtils
4222  // This function:
4223  // 1. Identifies spatially coincident vertices within tolerance
4224  // 2. Updates element connectivity to reference merged vertices
4225  // 3. Preserves tag values from the first vertex in each duplicate set
4226  // 4. Removes duplicate vertices from the mesh
4227  MB_CHK_ERR( IntxUtils::remove_duplicate_vertices( context.MBI, data.file_set, tol,
4228  tagsList ) ); // Exit on failure to merge local vertices
4229 
4230  // ========================================================================
4231  // STEP 3: Clean up material sets that may reference deleted elements
4232  // ========================================================================
4233 
4234  // Material sets organize elements by material properties (e.g., land, ocean, ice)
4235  // After vertex merge, some elements may have been deleted or connectivity modified
4236  // Update material sets to maintain mesh data integrity
4237 
4238  // Retrieve all material sets currently defined on the file set
4239  // Material sets are identified by having a MATERIAL_SET_TAG_NAME tag
4241  data.file_set, MBENTITYSET, &( context.material_tag ), nullptr, 1, data.mat_sets,
4242  Interface::UNION ) ); // Exit if we can't retrieve material sets
4243 
4244  // Update material sets if any were found
4245  if( !data.mat_sets.empty() )
4246  {
4247  // For simplicity, we only clean the first material set
4248  // In production code, all material sets should be systematically updated
4249  // This is a limitation that could be improved in future versions
4250  EntityHandle matSet = data.mat_sets[0];
4251  Range elems;
4252 
4253  // Get current 2D elements in this material set (may include stale references)
4254  MB_CHK_ERR(
4255  context.MBI->get_entities_by_dimension( matSet, 2, elems ) ); // Exit if we can't get material set contents
4256 
4257  // Remove all current elements from the material set
4258  // This clears potentially stale element references
4259  MB_CHK_ERR( context.MBI->remove_entities( matSet, elems ) ); // Exit on failure to remove stale elements
4260 
4261  // Get only valid 2D elements from the current file set
4262  // This excludes any elements that may have been deleted during merge
4263  elems.clear(); // Reset range for reuse
4264  MB_CHK_ERR(
4265  context.MBI->get_entities_by_dimension( data.file_set, 2, elems ) ); // Exit if we can't get valid elements
4266 
4267  // Add back only the valid elements to the material set
4268  // This ensures material set consistency after topology changes
4269  MB_CHK_ERR( context.MBI->add_entities( matSet, elems ) ); // Exit on failure to update material set
4270  }
4271 
4272  // ========================================================================
4273  // STEP 4: Update internal mesh information after topology changes
4274  // ========================================================================
4275 
4276  // Recalculate mesh statistics and update internal data structures
4277  // This includes vertex/element counts, owned/ghosted entity information
4278  // iMOAB_UpdateMeshInfo() refreshes all cached mesh information
4279  MB_CHK_ERR( iMOAB_UpdateMeshInfo( pid ) ); // Exit on failure to update mesh info
4280 
4281  // ========================================================================
4282  // STEP 5: Perform parallel merge of vertices shared across processes
4283  // ========================================================================
4284 
4285  // Create parallel merge mesh object with the same spatial tolerance
4286  // ParallelMergeMesh handles vertices shared across process boundaries
4287  ParallelMergeMesh pmm( pco, tol );
4288 
4289  // Perform parallel vertex merging across all processes
4290  // This identifies vertices with identical GLOBAL_ID on different processes
4291  // and merges them while updating the parallel communication graph
4292  // Parameters:
4293  // - data.file_set: The mesh set to operate on
4294  // - false: Skip local merge (already done in step 2)
4295  // - 2: Update 2D elements after merge (dimension of elements to fix connectivity for)
4296  MB_CHK_ERR( pmm.merge( data.file_set,
4297  /* do not do local merge*/ false,
4298  /* 2d cells*/ 2 ) ); // Exit on failure of parallel merge
4299 
4300  // ========================================================================
4301  // STEP 6: Assign consistent global IDs to vertices after merge
4302  // ========================================================================
4303 
4304  // After merging, vertices need new globally unique IDs to maintain consistency
4305  // Only update vertices (dimension 0) - elements already have correct IDs
4306  // assign_global_ids() coordinates across all processes to ensure uniqueness
4307  MB_CHK_ERR( pco->assign_global_ids( data.file_set, /*dim*/ 0 ) ); // Exit on failure to assign global IDs
4308 
4309  // ========================================================================
4310  // STEP 7: Set partition tag for visualization and debugging
4311  // ========================================================================
4312 
4313  // Set up PARALLEL_PARTITION tag to indicate which process owns each mesh set
4314  // This is essential for visualization tools like ParaView/VisIt to show
4315  // domain decomposition and parallel partitioning
4316 
4317  Tag part_tag; // Handle for the partition tag
4318  int dum_id = -1; // Default value if tag doesn't exist yet
4319 
4320  // Create or retrieve the partition tag (sparse storage, one int per set)
4321  // Sparse storage is efficient since we only need one value per mesh set
4322  rval = context.MBI->tag_get_handle( "PARALLEL_PARTITION", 1, MB_TYPE_INTEGER, part_tag,
4323  MB_TAG_CREAT | MB_TAG_SPARSE, &dum_id );
4324 
4325  // Validate that partition tag was created or retrieved successfully
4326  if( part_tag == nullptr || ( ( MB_SUCCESS != rval ) && ( MB_ALREADY_ALLOCATED != rval ) ) )
4327  {
4328  // Error creating/retrieving partition tag
4329  std::cout << "Failed to create/retrieve PARALLEL_PARTITION tag.\n";
4330  return moab::MB_FAILURE;
4331  }
4332 
4333  // Tag the file set with this process's rank to show ownership
4334  // This allows visualization tools to distinguish between different process domains
4335  int rank = pco->rank();
4336  MB_CHK_ERR(
4337  context.MBI->tag_set_data( part_tag, &data.file_set, 1, &rank ) ); // Exit on failure to set partition data
4338 
4339  // ========================================================================
4340  // FUNCTION EPILOGUE: Return success status
4341  // ========================================================================
4342 
4343  // All merge operations completed successfully
4344  return moab::MB_SUCCESS;
4345 }

References moab::Interface::add_entities(), GlobalContext::appDatas, moab::ParallelComm::assign_global_ids(), moab::Range::clear(), context, moab::Range::empty(), ErrorCode, appData::file_set, moab::Interface::get_entities_by_dimension(), moab::Interface::get_entities_by_type_and_tag(), moab::Interface::globalId_tag(), iMOAB_UpdateMeshInfo(), appData::mat_sets, GlobalContext::material_tag, MB_ALREADY_ALLOCATED, MB_CHK_ERR, MB_SUCCESS, MB_TAG_CREAT, MB_TAG_SPARSE, MB_TYPE_INTEGER, MBENTITYSET, GlobalContext::MBI, moab::ParallelMergeMesh::merge(), appData::pcomm, moab::ParallelComm::rank(), moab::IntxUtils::remove_duplicate_vertices(), moab::Interface::remove_entities(), moab::Interface::tag_get_handle(), moab::Interface::tag_set_data(), and moab::Interface::UNION.

◆ iMOAB_ReceiveElementTag()

ErrCode iMOAB_ReceiveElementTag ( iMOAB_AppID  pid,
const iMOAB_String  tag_storage_name,
MPI_Comm *  joint_communicator,
int *  context_id 
)

Receive element tag data from another component.

Receives tag data (scalar or vector values) associated with mesh elements from another component using pre-established communication graph. Received tag values are applied to corresponding entities in local mesh, enabling data exchange between coupled components.

Algorithm Workflow:
  1. COMMUNICATION GRAPH VALIDATION: Look up ParCommGraph for specified context_id, validate existence and initialization, extract graph and parallel communicator.
  2. ENTITY SELECTION: Determine target entities based on component type (point cloud: local_verts, regular mesh: owned_elems). Handle special cases (coverage set, TempestRemap coverage mesh).
  3. TAG PROCESSING: Parse tag names using colon separator for multiple tags, resolve tag handles, validate existence. Support scalar and vector tag data.
  4. DATA RECEPTION: Use ParCommGraph to receive tag data via receive_tag_values, unpack received tag values and associate with local entities, execute non-blocking point-to-point receives.
Data Received:
  • Tag values: Scalar or vector data from sender component
  • Entity mappings: Which entities the tag values belong to
  • Tag metadata: Tag type, size, and component information
  • Global IDs: For entity identification and matching
Parameters
[in]pidApplication ID for receiver component
[in]tag_storage_nameTag name(s) to receive (colon-separated for multiple tags)
[in]joint_communicatorMPI communicator spanning sender and receiver groups
[in]context_idCommunication graph context ID
Note
Use cases: Receive solution data, get material properties/BCs, accept computed quantities
Warning
Communication graph must exist before calling (establish via mesh transfer or compute graph)
Returns
moab::MB_SUCCESS on success, moab::MB_FAILURE if graph missing or tags not found

Definition at line 3611 of file iMOAB.cpp.

3615 {
3616  // Get application data and look up the communication graph for this context
3617  appData& data = context.appDatas[*pid];
3618  std::map< int, ParCommGraph* >::iterator mt = data.pgraph.find( *context_id );
3619  if( mt == data.pgraph.end() )
3620  {
3621  // Communication graph not found - this means mesh transfer hasn't been performed yet
3622  std::cout << " no par com graph for context_id:" << *context_id << " available contexts:";
3623  for( auto mit = data.pgraph.begin(); mit != data.pgraph.end(); mit++ )
3624  std::cout << " " << mit->first;
3625  std::cout << "\n";
3626  return moab::MB_FAILURE;
3627  }
3628 
3629  // Extract communication graph and parallel communicator
3630  ParCommGraph* cgraph = mt->second;
3631  ParallelComm* pco = context.appDatas[*pid].pcomm;
3632 
3633  // Handle Fortran-to-C MPI handle conversion if needed
3634  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3635  : *joint_communicator );
3636 
3637  // Determine target entities: vertices for point clouds, elements for regular meshes
3638  Range owned = ( data.point_cloud ? data.local_verts : data.owned_elems );
3639 
3640  // Handle coverage set entities for intersection applications
3641  // This covers cases where elements have been instantiated in coverage sets during intersection
3642  EntityHandle cover_set = cgraph->get_cover_set();
3643  if( 0 != cover_set ) MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3644 
3645 #ifdef MOAB_HAVE_TEMPESTREMAP
3646  // Handle TempestRemap coverage mesh entities for intersection applications
3647  if( data.tempestData.remapper != nullptr ) // This is the case for intersection operations
3648  {
3649  cover_set = data.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3650  MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3651  }
3652 #endif
3653 
3654  // Parse tag names from the input string (multiple tags separated by colons)
3655  std::string tag_name( tag_storage_name );
3656 
3657  // Parse multiple tag names separated by colons
3658  std::vector< std::string > tagNames;
3659  std::vector< Tag > tagHandles;
3660  std::string separator( ":" );
3661  split_tag_names( tag_name, separator, tagNames );
3662 
3663  // Resolve tag handles for each specified tag name
3664  for( size_t i = 0; i < tagNames.size(); i++ )
3665  {
3666  Tag tagHandle;
3667  ErrorCode rval = context.MBI->tag_get_handle( tagNames[i].c_str(), tagHandle );
3668  if( MB_SUCCESS != rval || nullptr == tagHandle )
3669  {
3670  MB_CHK_SET_ERR( moab::MB_FAILURE,
3671  " can't get tag handle for tag named:" << tagNames[i].c_str() << " at index " << i );
3672  }
3673  tagHandles.push_back( tagHandle );
3674  }
3675 
3676 #ifdef VERBOSE
3677  std::cout << pco->rank() << ". Looking to receive data for tags: " << tag_name
3678  << " and file set = " << ( data.file_set ) << "\n";
3679 #endif
3680  // Execute the tag data reception using the communication graph
3681  // This receives tag values from sender processes and applies them to local entities
3682  // pco is needed for MOAB operations (unpacking), not for MPI communication
3683  MB_CHK_SET_ERR( cgraph->receive_tag_values( global, pco, owned, tagHandles ), "failed to receive tag values" );
3684 
3685 #ifdef VERBOSE
3686  std::cout << pco->rank() << ". Looking to receive data for tags: " << tag_name << "\n";
3687 #endif
3688 
3689  return moab::MB_SUCCESS;
3690 }

References GlobalContext::appDatas, context, moab::Remapper::CoveringMesh, ErrorCode, appData::file_set, moab::ParCommGraph::get_cover_set(), moab::Interface::get_entities_by_dimension(), appData::is_fortran, appData::local_verts, MB_CHK_ERR, MB_CHK_SET_ERR, MB_SUCCESS, GlobalContext::MBI, appData::owned_elems, appData::pgraph, appData::point_cloud, moab::ParallelComm::rank(), moab::ParCommGraph::receive_tag_values(), split_tag_names(), and moab::Interface::tag_get_handle().

◆ iMOAB_ReceiveMesh()

ErrCode iMOAB_ReceiveMesh ( iMOAB_AppID  pid,
MPI_Comm *  joint_communicator,
MPI_Group *  sendingGroup,
int *  scompid 
)

Receive mesh data on target component from sending component.

Implements receiver side of distributed mesh transfer protocol. Receives mesh entities and data from multiple sender processes, consolidates them into local mesh, and handles entity deduplication based on global IDs.

Algorithm Workflow:
  1. COMMUNICATOR SETUP: Resolve MPI communicator and group handles (Fortran F2C conversions). Extract receiver group from MOAB ParallelComm. Create ParCommGraph with reverse mapping.
  2. COMMUNICATION GRAPH RECEPTION: Receive communication graph via receive_comm_graph. Parse packed graph array to extract sender ranks for this receiver process.
  3. SENDER IDENTIFICATION: Determine current receiver's rank index. Walk through packed graph array to find all sender ranks that contribute. Build senders_local list.
  4. MESH RECEPTION: Invoke receive_mesh to pull mesh parts from identified sender processes. Deposit mesh entities into data.file_set (local mesh container).
  5. POST-PROCESSING: Optional vertex merging for entities with identical GLOBAL_ID from different senders. Re-establish mesh information and update local mesh metadata.
Data Received:
  • Mesh entities: elements and vertices with geometric data
  • Connectivity: element-to-vertex relationships
  • Tags: material properties, boundary conditions, custom data
  • Global IDs: for entity identification and deduplication
Parameters
[in]pidApplication ID for receiver component
[in]joint_communicatorMPI communicator spanning both sender and receiver groups
[in]sendingGroupMPI group of sending processes
[in]scompidSender component ID (key for communication graph storage)
Note
Communication: Collective (receive graph) and point-to-point (receive mesh data)
Warning
Vertex merging only performed if receiving from 2+ senders
Returns
moab::MB_SUCCESS on success, moab::MB_FAILURE on error

Definition at line 3287 of file iMOAB.cpp.

3288 {
3289  // Validate input parameters to ensure they are not null pointers
3290  assert( joint_communicator != nullptr );
3291  assert( sendingGroup != nullptr );
3292  assert( scompid != nullptr );
3293 
3294  ErrorCode rval;
3295  // Get application data and parallel communicator for this component
3296  appData& data = context.appDatas[*pid];
3297  ParallelComm* pco = context.appDatas[*pid].pcomm;
3298  MPI_Comm receive = pco->comm();
3299  EntityHandle local_set = data.file_set;
3300 
3301  // Handle Fortran-to-C MPI handle conversion if needed
3302  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3303  : *joint_communicator );
3304  MPI_Group sendGroup =
3305  ( data.is_fortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( sendingGroup ) ) : *sendingGroup );
3306 
3307  // Extract receiver group from the receiver communicator to understand receiver process topology
3308  MPI_Group receiverGroup;
3309  int ierr = MPI_Comm_group( receive, &receiverGroup );CHK_MPI_ERR( ierr );
3310 
3311  // Create communication graph with reverse mapping (sender->receiver)
3312  // This graph will manage the reception of mesh data from sender processes
3313  ParCommGraph* cgraph =
3314  new ParCommGraph( global, sendGroup, receiverGroup, *scompid, context.appDatas[*pid].global_id );
3315 
3316  // Store the communication graph in the application's graph map for later use
3317  // The key is the sender component ID (*scompid)
3318  context.appDatas[*pid].pgraph[*scompid] = cgraph;
3319 
3320  // Get current receiver rank for process identification
3321  int receiver_rank = -1;
3322  MPI_Comm_rank( receive, &receiver_rank );
3323 
3324  // Receive the communication graph from sender processes
3325  // This graph tells this receiver which senders will send it data
3326  std::vector< int > pack_array;
3327  MB_CHK_ERR( cgraph->receive_comm_graph( global, pco, pack_array ) );
3328 
3329  // Determine this receiver's index within the receiver group
3330  int current_receiver = cgraph->receiver( receiver_rank );
3331 
3332  // Parse the packed communication graph to find which senders contribute to this receiver
3333  std::vector< int > senders_local;
3334  size_t n = 0;
3335 
3336  // Walk through the packed graph array to find sender ranks for this receiver
3337  while( n < pack_array.size() )
3338  {
3339  if( current_receiver == pack_array[n] )
3340  {
3341  // Found this receiver's entry - extract all contributing sender ranks
3342  for( int j = 0; j < pack_array[n + 1]; j++ )
3343  {
3344  senders_local.push_back( pack_array[n + 2 + j] );
3345  }
3346  break;
3347  }
3348  // Move to next receiver's entry in the packed array
3349  n = n + 2 + pack_array[n + 1];
3350  }
3351 
3352 #ifdef VERBOSE
3353  std::cout << " receiver " << current_receiver << " at rank " << receiver_rank << " will receive from "
3354  << senders_local.size() << " tasks: ";
3355 
3356  for( int k = 0; k < (int)senders_local.size(); k++ )
3357  {
3358  std::cout << " " << senders_local[k];
3359  }
3360 
3361  std::cout << "\n";
3362 #endif
3363 
3364  // Validate that we have senders contributing to this receiver
3365  if( senders_local.empty() )
3366  {
3367  std::cout << " we do not have any senders for receiver rank " << receiver_rank << "\n";
3368  }
3369 
3370  // Receive mesh data from all identified sender processes
3371  // This unpacks mesh entities and deposits them into the local mesh set
3372  MB_CHK_ERR( cgraph->receive_mesh( global, pco, local_set, senders_local ) );
3373 
3374  // Post-process received mesh data to handle potential vertex duplication
3375  // Vertices with identical GLOBAL_ID from different senders need to be merged
3376  Tag idtag;
3377  MB_CHK_ERR( context.MBI->tag_get_handle( "GLOBAL_ID", idtag ) );
3378 
3379  // Get all entities in the local mesh set
3380  Range local_ents;
3381  MB_CHK_ERR( context.MBI->get_entities_by_handle( local_set, local_ents ) );
3382 
3383  // Only perform vertex merging for regular meshes (not point clouds)
3384  if( !local_ents.all_of_type( MBVERTEX ) )
3385  {
3386  // Merge vertices only if we received data from multiple senders
3387  if( (int)senders_local.size() >= 2 ) // Need to remove duplicate vertices
3388  {
3389 
3390  // Separate vertices from elements for processing
3391  Range local_verts = local_ents.subset_by_type( MBVERTEX );
3392  Range local_elems = subtract( local_ents, local_verts );
3393 
3394  // Temporarily remove vertices from local set for merging
3395  MB_CHK_ERR( context.MBI->remove_entities( local_set, local_verts ) );
3396 
3397 #ifdef VERBOSE
3398  std::cout << "current_receiver " << current_receiver << " local verts: " << local_verts.size() << "\n";
3399 #endif
3400  // Merge vertices with identical GLOBAL_ID values
3401  MergeMesh mm( context.MBI );
3402  MB_CHK_ERR( mm.merge_using_integer_tag( local_verts, idtag ) );
3403 
3404  // Get the merged vertices back from element connectivity
3405  Range new_verts;
3406  MB_CHK_ERR( context.MBI->get_connectivity( local_elems, new_verts ) );
3407 
3408 #ifdef VERBOSE
3409  std::cout << "after merging: new verts: " << new_verts.size() << "\n";
3410 #endif
3411  // Add the merged vertices back to the local set
3412  MB_CHK_ERR( context.MBI->add_entities( local_set, new_verts ) );
3413  }
3414  }
3415  else
3416  // Mark this as a point cloud if we only have vertices
3417  data.point_cloud = true;
3418 
3419  if( !data.point_cloud )
3420  {
3421  // For regular meshes, resolve shared entities (vertices) across process boundaries
3422  MB_CHK_ERR( pco->resolve_shared_ents( local_set, -1, -1, &idtag ) );
3423  }
3424  else
3425  {
3426  // For point clouds, set partition tag to current rank for visualization
3427  Tag densePartTag;
3428  rval = context.MBI->tag_get_handle( "partition", densePartTag );
3429  if( nullptr != densePartTag && MB_SUCCESS == rval )
3430  {
3431  Range local_verts;
3432  MB_CHK_ERR( context.MBI->get_entities_by_dimension( local_set, 0, local_verts ) );
3433  std::vector< int > vals;
3434  int rank = pco->rank();
3435  vals.resize( local_verts.size(), rank );
3436  MB_CHK_ERR( context.MBI->tag_set_data( densePartTag, local_verts, &vals[0] ) );
3437  }
3438  }
3439  // Set the parallel partition tag to identify which process owns this mesh set
3440  Tag part_tag;
3441  int dum_id = -1;
3442  rval = context.MBI->tag_get_handle( "PARALLEL_PARTITION", 1, MB_TYPE_INTEGER, part_tag,
3443  MB_TAG_CREAT | MB_TAG_SPARSE, &dum_id );
3444 
3445  if( part_tag == nullptr || ( ( rval != MB_SUCCESS ) && ( rval != MB_ALREADY_ALLOCATED ) ) )
3446  {
3447  std::cout << " can't get par part tag.\n";
3448  return moab::MB_FAILURE;
3449  }
3450 
3451  // Tag the local set with the current process rank
3452  int rank = pco->rank();
3453  MB_CHK_ERR( context.MBI->tag_set_data( part_tag, &local_set, 1, &rank ) );
3454 
3455  // Ensure that the GLOBAL_ID tag is properly defined for entity identification
3456  int tagtype = 0; // dense, integer
3457  int numco = 1; // size
3458  int tagindex; // not used
3459  MB_CHK_ERR( iMOAB_DefineTagStorage( pid, "GLOBAL_ID", &tagtype, &numco, &tagindex ) );
3460 
3461  // Update mesh information with current data statistics
3462  MB_CHK_ERR( iMOAB_UpdateMeshInfo( pid ) );
3463 
3464  // Clean up temporary MPI group to prevent memory leaks
3465  MPI_Group_free( &receiverGroup );
3466 
3467  return moab::MB_SUCCESS;
3468 }

References moab::Interface::add_entities(), moab::Range::all_of_type(), GlobalContext::appDatas, CHK_MPI_ERR, moab::ParallelComm::comm(), context, ErrorCode, appData::file_set, moab::Interface::get_connectivity(), moab::Interface::get_entities_by_dimension(), moab::Interface::get_entities_by_handle(), iMOAB_DefineTagStorage(), iMOAB_UpdateMeshInfo(), appData::is_fortran, MB_ALREADY_ALLOCATED, MB_CHK_ERR, MB_SUCCESS, MB_TAG_CREAT, MB_TAG_SPARSE, MB_TYPE_INTEGER, GlobalContext::MBI, MBVERTEX, moab::MergeMesh::merge_using_integer_tag(), appData::point_cloud, moab::ParallelComm::rank(), moab::ParCommGraph::receive_comm_graph(), moab::ParCommGraph::receive_mesh(), moab::ParCommGraph::receiver(), moab::Interface::remove_entities(), moab::ParallelComm::resolve_shared_ents(), moab::Range::size(), moab::Range::subset_by_type(), moab::subtract(), moab::Interface::tag_get_handle(), and moab::Interface::tag_set_data().

◆ iMOAB_SendElementTag()

ErrCode iMOAB_SendElementTag ( iMOAB_AppID  pid,
const iMOAB_String  tag_storage_name,
MPI_Comm *  joint_communicator,
int *  context_id 
)

Send element tag data from calling component to another component.

Transfers tag data (scalar or vector values) associated with mesh elements from one component to another using pre-established communication graph. Transfer based on communication patterns determined by previous mesh transfer or communication graph computation operations.

Algorithm Workflow:
  1. COMMUNICATION GRAPH VALIDATION: Look up ParCommGraph for specified context_id, validate existence and initialization, extract graph and parallel communicator.
  2. ENTITY SELECTION: Determine target entities based on component type (point cloud: local_verts, regular mesh: owned_elems). Handle special cases (TempestRemap coverage mesh, intersection coverage sets).
  3. TAG PROCESSING: Parse tag names using colon separator for multiple tags, resolve tag handles, validate existence. Support scalar and vector tag data.
  4. DATA TRANSFER: Use ParCommGraph to determine routing, pack tag values associated with selected entities, execute non-blocking point-to-point transfers via send_tag_values.
Data Transferred:
  • Tag values: Scalar or vector data associated with mesh entities
  • Entity mappings: Which entities the tag values belong to
  • Tag metadata: Tag type, size, and component information
  • Global IDs: For entity identification across processes
Parameters
[in]pidApplication ID for sender component
[in]tag_storage_nameTag name(s) to send (colon-separated for multiple tags)
[in]joint_communicatorMPI communicator spanning sender and receiver groups
[in]context_idCommunication graph context ID
Note
Use cases: Transfer solution data, send material properties/BCs, exchange computed quantities
Warning
Communication graph must exist before calling (establish via mesh transfer or compute graph)
Returns
moab::MB_SUCCESS on success, moab::MB_FAILURE if graph missing or tags not found

Definition at line 3504 of file iMOAB.cpp.

3508 {
3509  // Get application data and look up the communication graph for this context
3510  appData& data = context.appDatas[*pid];
3511  std::map< int, ParCommGraph* >::iterator mt = data.pgraph.find( *context_id );
3512  if( mt == data.pgraph.end() )
3513  {
3514  // Communication graph not found - this means mesh transfer hasn't been performed yet
3515  std::cout << " no par com graph for context_id:" << *context_id << " available contexts:";
3516  for( auto mit = data.pgraph.begin(); mit != data.pgraph.end(); mit++ )
3517  std::cout << " " << mit->first;
3518  std::cout << "\n";
3519  return moab::MB_FAILURE;
3520  }
3521 
3522  // Extract communication graph and parallel communicator
3523  ParCommGraph* cgraph = mt->second;
3524  ParallelComm* pco = context.appDatas[*pid].pcomm;
3525 
3526  // Handle Fortran-to-C MPI handle conversion if needed
3527  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3528  : *joint_communicator );
3529 
3530  // Determine target entities: vertices for point clouds, elements for regular meshes
3531  Range owned = ( data.point_cloud ? data.local_verts : data.owned_elems );
3532 
3533 #ifdef MOAB_HAVE_TEMPESTREMAP
3534  // Handle TempestRemap coverage mesh entities for intersection applications
3535  if( data.tempestData.remapper != nullptr ) // This is the case for intersection operations
3536  {
3537  EntityHandle cover_set = data.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3538  MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3539  }
3540 #else
3541  // Handle coverage set entities for intersection applications (non-TempestRemap case)
3542  // This covers cases where elements have been instantiated in coverage sets during intersection
3543  EntityHandle cover_set = cgraph->get_cover_set(); // Non-null only for intersection applications
3544  if( 0 != cover_set ) MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3545 #endif
3546 
3547  // Parse tag names from the input string (multiple tags separated by colons)
3548  std::string tag_name( tag_storage_name );
3549 
3550  // Parse multiple tag names separated by colons
3551  std::vector< std::string > tagNames;
3552  std::vector< Tag > tagHandles;
3553  std::string separator( ":" );
3554  split_tag_names( tag_name, separator, tagNames );
3555 
3556  // Resolve tag handles for each specified tag name
3557  for( size_t i = 0; i < tagNames.size(); i++ )
3558  {
3559  Tag tagHandle;
3560  ErrorCode rval = context.MBI->tag_get_handle( tagNames[i].c_str(), tagHandle );
3561  if( MB_SUCCESS != rval || nullptr == tagHandle )
3562  {
3563  MB_CHK_SET_ERR( moab::MB_FAILURE,
3564  "can't get tag handle with name: " << tagNames[i].c_str() << " at index " << i );
3565  }
3566  tagHandles.push_back( tagHandle );
3567  }
3568 
3569  // Execute the tag data transfer using the communication graph
3570  // This packs tag values and sends them to appropriate receiver processes
3571  // pco is needed for MOAB operations (packing), not for MPI communication
3572  MB_CHK_ERR( cgraph->send_tag_values( global, pco, owned, tagHandles ) );
3573 
3574  return moab::MB_SUCCESS;
3575 }

References GlobalContext::appDatas, context, moab::Remapper::CoveringMesh, ErrorCode, moab::ParCommGraph::get_cover_set(), moab::Interface::get_entities_by_dimension(), appData::is_fortran, appData::local_verts, MB_CHK_ERR, MB_CHK_SET_ERR, MB_SUCCESS, GlobalContext::MBI, appData::owned_elems, appData::pgraph, appData::point_cloud, moab::ParCommGraph::send_tag_values(), split_tag_names(), and moab::Interface::tag_get_handle().

◆ iMOAB_SendMesh()

ErrCode iMOAB_SendMesh ( iMOAB_AppID  pid,
MPI_Comm *  joint_communicator,
MPI_Group *  receivingGroup,
int *  rcompid,
int *  method 
)

Transfer mesh data from sender component to receiver component.

Implements distributed mesh transfer protocol that moves mesh entities (elements, vertices, and associated data) from one set of MPI processes to another. Transfer is orchestrated through a communication graph that maps sender processes to receiver processes based on partitioning strategy.

Algorithm Workflow:
  1. COMMUNICATOR SETUP: Resolve MPI communicator and group handles (handles Fortran F2C conversions). Extract sender group from MOAB ParallelComm. Create ParCommGraph for sender→receiver mapping.
  2. ENTITY SELECTION: Determine owned entities to transfer: owned_elems or local_verts (point cloud mode). Store ParCommGraph in context.appDatas[pid].pgraph[rcompid].
  3. PARTITIONING STRATEGY:
    • method == 0 (TRIVIAL): Gather global entity counts, compute block distribution, broadcast via send_graph
    • method != 0 (COMPUTED): Use graph/geometric partitioning considering connectivity and spatial distribution
  4. MESH TRANSFER: Pack mesh entities and data, use ParCommGraph to route to receivers via send_mesh_parts
Data Transferred:
  • Mesh entities: elements (triangles, quads, etc.) and vertices
  • Geometric data: vertex coordinates, element connectivity
  • Metadata: material sets, boundary conditions, tags
  • Global IDs: for entity identification across processes
Parameters
[in]pidApplication ID for sender component
[in]joint_communicatorMPI communicator spanning both sender and receiver groups
[in]receivingGroupMPI group of receiving processes
[in]rcompidReceiver component ID (key for communication graph storage)
[in]methodPartitioning method: 0=trivial, !=0=computed
Note
Communication: Collective (MPI_Allgather) and point-to-point (non-blocking sends)
Warning
Cleans up MPI groups on early return to avoid resource leaks
Returns
moab::MB_SUCCESS on success, moab::MB_FAILURE on error

Definition at line 3140 of file iMOAB.cpp.

3145 {
3146  // Validate input parameters to ensure they are not null pointers
3147  assert( joint_communicator != nullptr );
3148  assert( receivingGroup != nullptr );
3149  assert( rcompid != nullptr );
3150 
3151  int ierr;
3152  // Get application data and parallel communicator for this component
3153  appData& data = context.appDatas[*pid];
3154  ParallelComm* pco = context.appDatas[*pid].pcomm;
3155 
3156  // Handle Fortran-to-C MPI handle conversion if needed
3157  // Fortran passes MPI handles as integers, C++ expects MPI_Comm/MPI_Group objects
3158  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3159  : *joint_communicator );
3160  MPI_Group recvGroup =
3161  ( data.is_fortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( receivingGroup ) ) : *receivingGroup );
3162 
3163  // Extract sender communicator from MOAB's parallel communicator
3164  // This represents the group of processes that will send mesh data
3165  MPI_Comm sender = pco->comm();
3166  // Extract sender group from the sender communicator to understand sender process topology
3167  MPI_Group senderGroup;
3168  ierr = MPI_Comm_group( sender, &senderGroup );
3169  if( ierr != 0 ) return moab::MB_FAILURE;
3170 
3171  // Create communication graph to manage sender->receiver process mapping
3172  // This graph will determine which entities go to which receiver processes
3173  ParCommGraph* cgraph =
3174  new ParCommGraph( global, senderGroup, recvGroup, context.appDatas[*pid].global_id, *rcompid );
3175 
3176  // Store the communication graph in the application's graph map for later use
3177  // The key is the receiver component ID (*rcompid)
3178  context.appDatas[*pid].pgraph[*rcompid] = cgraph;
3179 
3180  // Get current sender rank for potential debugging/logging
3181  int sender_rank = -1;
3182  MPI_Comm_rank( sender, &sender_rank );
3183 
3184  // Determine which entities to send: primary elements or vertices (for point clouds)
3185  std::vector< int > number_elems_per_part;
3186  Range owned = context.appDatas[*pid].owned_elems;
3187  if( owned.size() == 0 )
3188  {
3189  // If no elements, this must be a point cloud - send vertices instead
3190  owned = context.appDatas[*pid].local_verts;
3191  }
3192 
3193  // Choose partitioning strategy based on method parameter
3194  if( *method == 0 ) // Trivial partitioning: simple block distribution
3195  {
3196  // Count local entities on this sender process
3197  int local_owned_elem = (int)owned.size();
3198  int size = pco->size();
3199  int rank = pco->rank();
3200 
3201  // Prepare array to hold entity counts from all sender processes
3202  number_elems_per_part.resize( size );
3203  number_elems_per_part[rank] = local_owned_elem;
3204  // Gather entity counts from all sender processes using MPI_Allgather
3205  // This allows each sender to know the total entity count for load balancing
3206 #if ( MPI_VERSION >= 2 )
3207  // Use "in place" option for efficiency (MPI 2.0+)
3208  ierr = MPI_Allgather( MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, &number_elems_per_part[0], 1, MPI_INT, sender );
3209 #else
3210  // Fallback for older MPI versions
3211  {
3212  std::vector< int > all_tmp( size );
3213  ierr = MPI_Allgather( &number_elems_per_part[rank], 1, MPI_INT, &all_tmp[0], 1, MPI_INT, sender );
3214  number_elems_per_part = all_tmp;
3215  }
3216 #endif
3217 
3218  if( ierr != 0 )
3219  {
3220  return moab::MB_FAILURE;
3221  }
3222 
3223  // Compute trivial partition: each receiver gets roughly equal share of entities
3224  // This creates a simple block distribution based on entity counts
3225  MB_CHK_ERR( cgraph->compute_trivial_partition( number_elems_per_part ) );
3226 
3227  // Send the partition mapping to all receiver processes
3228  // This tells receivers which senders will send them data
3229  MB_CHK_ERR( cgraph->send_graph( global ) );
3230  }
3231  else // Advanced partitioning: graph-based or geometric partitioning
3232  {
3233  // Use sophisticated partitioning that considers entity connectivity or spatial distribution
3234  // This can lead to better load balancing and reduced communication
3235  MB_CHK_ERR( cgraph->compute_partition( pco, owned, *method ) );
3236 
3237  // Send the computed partition mapping to receiver processes
3238  // This includes more complex sender->receiver mappings than trivial partition
3239  MB_CHK_ERR( cgraph->send_graph_partition( pco, global ) );
3240  }
3241  // Execute the actual mesh transfer based on the communication graph
3242  // This packs mesh entities and sends them to appropriate receiver processes
3243  // pco is needed for MOAB operations (packing), not for MPI communication
3244  MB_CHK_ERR( cgraph->send_mesh_parts( global, pco, owned ) );
3245 
3246  // Clean up temporary MPI group to prevent memory leaks
3247  MPI_Group_free( &senderGroup );
3248  return moab::MB_SUCCESS;
3249 }

References GlobalContext::appDatas, moab::ParallelComm::comm(), moab::ParCommGraph::compute_partition(), moab::ParCommGraph::compute_trivial_partition(), context, appData::is_fortran, MB_CHK_ERR, MB_SUCCESS, moab::ParallelComm::rank(), moab::ParCommGraph::send_graph(), moab::ParCommGraph::send_graph_partition(), moab::ParCommGraph::send_mesh_parts(), moab::Range::size(), and moab::ParallelComm::size().