Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
HelloParMOAB.cpp
Go to the documentation of this file.
1 /** @example HelloParMOAB.cpp
2  * @brief Demonstrates parallel mesh operations with MOAB, including shared entity resolution and ghost exchange.
3  *
4  * This example shows how to:
5  * - Load a mesh in parallel with specific read options
6  * - Work with MPI communicators and process groups
7  * - Identify and report shared entities across processors
8  * - Exchange ghost elements between processors
9  * - Gather and report statistics about the parallel decomposition
10  *
11  * @note This example requires MOAB to be compiled with MPI and HDF5 support.
12  *
13  * @par Usage:
14  * @code
15  * mpiexec -np <num_procs> HelloParMOAB [filename] [num_communicators]
16  * @endcode
17  *
18  * @par Example:
19  * @code
20  * mpiexec -np 8 HelloParMOAB mesh.h5m 2
21  * @endcode
22  *
23  * This will load the mesh on 8 processes, split into 2 communicator groups (4 processes each).
24  *
25  * @see Core, ParallelComm, Range, MBParallelConventions
26  */
27 
28 #include "moab/Core.hpp"
29 #include "moab/Range.hpp"
30 #include "MBParallelConventions.h"
31 #include <iostream>
32 #include <memory>
33 #include <vector>
34 #include <string>
35 #include <cstdlib>
36 
37 // Only include MPI-specific headers if MOAB was built with MPI support
38 #ifdef MOAB_HAVE_MPI
39 #include "moab/ParallelComm.hpp"
40 #include <mpi.h>
41 #endif
42 
43 // Using declarations for cleaner code
44 using moab::Core;
45 using moab::ErrorCode;
46 using moab::ParallelComm;
47 using moab::Range;
48 
49 namespace
50 {
51 // Default mesh file path
52 const char* const DEFAULT_MESH_FILE = "/64bricks_512hex_256part.h5m";
53 
54 // Default read options
55 const char* const DEFAULT_READ_OPTS = "PARALLEL=READ_PART;PARTITION=PARALLEL_PARTITION;PARALLEL_RESOLVE_SHARED_ENTS";
56 
57 // Print usage information
58 void print_usage( const char* program_name )
59 {
60  std::cout << "Usage: mpiexec -np <num_procs> " << program_name << " [meshfile] [num_communicators]\n"
61  << "\nOptions:"
62  << "\n meshfile - Path to the mesh file (default: " << MESH_DIR << DEFAULT_MESH_FILE << ")"
63  << "\n num_communicators - Number of MPI communicator groups to create (default: 1)\n";
64 }
65 
66 // Print entity statistics
67 void print_entity_stats( const std::vector< int >& counts, int nprocs, const std::string& title = "" )
68 {
69  if( !title.empty() )
70  {
71  std::cout << "\n" << title << ":\n";
72  }
73 
74  for( int i = 0; i < nprocs; ++i )
75  {
76  std::cout << " Shared, owned entities on proc " << i << ": " << counts[4 * i] << " verts, "
77  << counts[4 * i + 1] << " edges, " << counts[4 * i + 2] << " faces, " << counts[4 * i + 3]
78  << " elements\n";
79  }
80 }
81 } // namespace
82 
83 int main( int argc, char** argv )
84 {
85  // Initialize MPI
86  int mpi_initialized = 0;
87  MPI_Initialized( &mpi_initialized );
88  if( !mpi_initialized )
89  {
90  MPI_Init( &argc, &argv );
91  }
92 
93  int global_rank = 0, global_size = 1;
94  MPI_Comm_rank( MPI_COMM_WORLD, &global_rank );
95  MPI_Comm_size( MPI_COMM_WORLD, &global_size );
96 
97  try
98  {
99  // Parse command line arguments
100  std::string mesh_file = std::string( MESH_DIR ) + DEFAULT_MESH_FILE;
101  int num_comms = 1;
102 
103  if( argc > 1 && ( std::string( argv[1] ) == "-h" || std::string( argv[1] ) == "--help" ) )
104  {
105  if( global_rank == 0 ) print_usage( argv[0] );
106  MPI_Finalize();
107  return 0;
108  }
109 
110  if( argc > 1 )
111  {
112  mesh_file = argv[1];
113  }
114 
115  if( argc > 2 )
116  {
117  num_comms = std::atoi( argv[2] );
118  if( num_comms < 1 )
119  {
120  if( global_rank == 0 )
121  {
122  std::cerr << "Error: Number of communicators must be at least 1\n";
123  print_usage( argv[0] );
124  }
125  MPI_Finalize();
126  return 1;
127  }
128  }
129 
130  // Create MOAB instance with smart pointer for automatic cleanup
131  auto moab = std::make_unique< Core >();
132  if( !moab )
133  {
134  std::cerr << "Error: Failed to create MOAB instance\n";
135  MPI_Abort( MPI_COMM_WORLD, 1 );
136  }
137 
138  // Split the communicator if requested
139  MPI_Comm comm;
140  int color = global_rank % num_comms; // Determine color based on requested number of communicators
141 
142  if( num_comms > 1 )
143  {
144  // Split the communicator into the specified number of groups
145  MPI_Comm_split( MPI_COMM_WORLD, color, global_rank, &comm );
146  }
147  else
148  {
149  // Use the default communicator
150  comm = MPI_COMM_WORLD;
151  }
152 
153  // Get the local communicator information
154  int local_rank = 0, local_size = 1;
155  MPI_Comm_rank( comm, &local_rank );
156  MPI_Comm_size( comm, &local_size );
157 
158  // Print process information
159  if( global_rank == 0 )
160  {
161  std::cout << "\n=== MOAB Parallel Hello World ===\n"
162  << "Global processes: " << global_size << "\n"
163  << "Number of communicator groups: " << num_comms << "\n"
164  << "Processes per group: ~" << ( global_size + num_comms - 1 ) / num_comms << "\n"
165  << "Reading file: " << mesh_file << "\n"
166  << "Read options: " << DEFAULT_READ_OPTS << "\n\n";
167  }
168 
169  // Read the mesh file in parallel
170  if( global_rank == 0 )
171  {
172  std::cout << "Loading mesh file..." << std::endl;
173  }
174 
175  // Create root sets for each mesh. Then pass these
176  // to the load_file functions to be populated.
177  moab::EntityHandle rootset, partnset;
178  MB_CHK_SET_ERR( moab->create_meshset( moab::MESHSET_SET, rootset ), "Creating root set failed" );
179  MB_CHK_SET_ERR( moab->create_meshset( moab::MESHSET_SET, partnset ), "Creating partition set failed" );
180 
181  // Create the parallel communicator object with the partition handle associated with MOAB
182  auto pcomm = std::auto_ptr< moab::ParallelComm >( ParallelComm::get_pcomm( moab.get(), partnset, &comm ) );
183 
184  MB_CHK_SET_ERR( moab->load_file( mesh_file.c_str(), &rootset, DEFAULT_READ_OPTS ),
185  "Failed to load mesh file: " << mesh_file );
186 
187  // Create parallel communication interface
188  // auto pcomm = std::make_unique<ParallelComm>(moab.get(), comm);
189  // auto pcomm = std::make_unique<ParallelComm>(moab.get(), comm);
190 
191  // Get shared entities and report statistics
192  Range shared_sets, shared_ents, owned_entities;
193 
194  // Range src_elems;
195  // MB_CHK_ERR( pcomm->get_part_entities( src_elems, 3 ) );
196  // src_elems.print("Source elements: ");
197 
198  // Get sets shared with all other processors
199  MB_CHK_SET_ERR( pcomm->get_shared_sets( shared_sets ), "Failed to get shared sets" );
200 
201  shared_sets.print( "Shared sets: " );
202 
203  // Get entities shared with all other processors
204  for( int dim = 0; dim < 4; ++dim )
205  {
206  MB_CHK_SET_ERR( pcomm->get_part_entities( shared_ents, dim ), "Failed to get shared entities" );
207  shared_ents.print( "Shared entities: " );
208  }
209 
210  // MB_CHK_SET_ERR(
211  // pcomm->get_shared_entities(-1, shared_ents),
212  // "Failed to get shared entities"
213  // );
214 
215  // Filter to get only locally owned shared entities
216  MB_CHK_SET_ERR( pcomm->filter_pstatus( shared_ents, PSTATUS_NOT_OWNED, PSTATUS_NOT, -1, &owned_entities ),
217  "Failed to filter owned entities" );
218 
219  shared_ents.print( "Owned entities: " );
220 
221  // Count owned entities by dimension
222  unsigned int entity_counts[4] = { 0 };
223  for( int dim = 0; dim < 4; ++dim )
224  {
225  entity_counts[dim] = static_cast< unsigned int >( owned_entities.num_of_dimension( dim ) );
226  }
227 
228  // Gather statistics on process 0 of each communicator
229  std::vector< int > recv_buffer( local_size * 4, 0 );
230  MPI_Gather( entity_counts, 4, MPI_INT, recv_buffer.data(), 4, MPI_INT, 0, comm );
231 
232  // Print initial statistics
233  if( local_rank == 0 )
234  {
235  std::cout << "\n=== Initial Shared Entity Statistics (Group " << color << ") ===\n";
236  print_entity_stats( std::vector< int >( entity_counts, entity_counts + 4 ), local_size );
237  print_entity_stats( recv_buffer, local_size );
238  }
239 
240  // Exchange ghost elements (1 layer, using vertices as bridge)
241  if( global_rank == 0 ) std::cout << "\nExchanging ghost elements..." << std::endl;
243  pcomm->exchange_ghost_cells( 3, // ghost_dim: exchange 3D elements (hexahedra)
244  0, // bridge_dim: use vertices as bridge
245  1, // num_layers: exchange 1 layer of ghost elements
246  0, // addl_ents: no additional entities
247  true // store_remote_handles: store remote handles for ghost entities
248  ),
249  "Failed to exchange ghost cells" );
250 
251  // Update shared entity information after ghost exchange
252  shared_ents.clear();
253  owned_entities.clear();
254 
255  MB_CHK_SET_ERR( pcomm->get_shared_entities( -1, shared_ents ),
256  "Failed to get shared entities after ghost exchange" );
257 
258  MB_CHK_SET_ERR( pcomm->filter_pstatus( shared_ents, PSTATUS_NOT_OWNED, PSTATUS_NOT, -1, &owned_entities ),
259  "Failed to filter owned entities after ghost exchange" );
260 
261  // Recalculate entity counts
262  for( int dim = 0; dim < 4; ++dim )
263  {
264  entity_counts[dim] = static_cast< unsigned int >( owned_entities.num_of_dimension( dim ) );
265  }
266 
267  // Gather updated statistics
268  MPI_Gather( entity_counts, 4, MPI_INT, recv_buffer.data(), 4, MPI_INT, 0, comm );
269 
270  // Print final statistics
271  if( local_rank == 0 )
272  {
273  std::cout << "\n=== Final Shared Entity Statistics After Ghost Exchange (Group " << color << ") ===\n";
274  print_entity_stats( recv_buffer, local_size );
275  std::cout << "\n=== Parallel Example Completed Successfully ===\n\n";
276  }
277 
278  // Clean up MPI communicator if we created it
279  if( num_comms > 1 )
280  {
281  MPI_Comm_free( &comm );
282  }
283 
284  // Clean up MOAB and MPI
285  pcomm.reset();
286  moab.reset();
287 
288  // Only finalize MPI if we initialized it
289  if( !mpi_initialized )
290  {
291  MPI_Finalize();
292  }
293 
294  return 0;
295  }
296  catch( const std::exception& e )
297  {
298  std::cerr << "Error on rank " << global_rank << ": " << e.what() << std::endl;
299  MPI_Abort( MPI_COMM_WORLD, 1 );
300  return 1;
301  }
302  catch( ... )
303  {
304  std::cerr << "Unknown error occurred on rank " << global_rank << std::endl;
305  MPI_Abort( MPI_COMM_WORLD, 1 );
306  return 1;
307  }
308 }