Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
CrystalRouterExample.cpp
Go to the documentation of this file.
1 /**
2  * @file CrystalRouterExample.cpp
3  * @brief Example demonstrating parallel communication using TupleList and Crystal Router
4  *
5  * This example shows how to:
6  * - Use MOAB's TupleList for efficient data communication
7  * - Perform sparse all-to-all communication using Crystal Router
8  * - Handle parallel data exchange with varying communication patterns
9  * - Verify received data integrity
10  * - Measure communication performance
11  *
12  * The Crystal Router is used for sparse communication patterns where
13  * each processor communicates with only a subset of other processors,
14  * avoiding the overhead of full all-to-all communication.
15  *
16  * @author MOAB Development Team
17  * @date 2024
18  *
19  * <b>To run</b>: mpiexec -np \c n CrystalRouterExample -r [reportrank] -t [num_tuples] -n
20  * [num_comms] \n
21  *
22  * @param argc Number of command line arguments
23  * @param argv Command line arguments array
24  * @return 0 on success, 1 on failure
25  */
26 // This example will show one of the building blocks of parallel infrastructure in MOAB
27 // More exactly, if we have some homogeneous data to communicate from each processor to a list of
28 // other processors, how do we do it?
29 //
30 // introduce the TupleList and crystal router to MOAB users.
31 //
32 // This technology is used in resolving shared vertices / sets between partitions
33 // It is used in the mbcoupler for sending data (target points) to the proper processor, and
34 // communicate back the results. Also, it is used to communicate departure mesh for intersection in
35 // parallel
36 //
37 // It is a way of doing MPI_gatheralltoallv(), when the communication matrix is sparse
38 //
39 // It is assumed that every proc needs to communicate only with a few of the other processors.
40 // If every processor needs to communicate with all other, then we will have to use paired isend
41 // and irecv, the communication matrix is full
42 //
43 // the example needs to be launched in parallel.
44 // Every proc will build a list of tuples, that will be send to a few procs;
45 // In general, we will send to num_comms tasks, and about num_tuples to each task
46 // We vary num_comms and num_tuples for processor
47 //
48 // we will send long ints of the form
49 // 100000 * send + 1000* rank +j, where j is the index of tuple
50 //
51 // after routing, we verify we received
52 // 100000 * rank + 1000 * from
53 //
54 // For some reportrank we also print the tuples.
55 //
56 // after routing, we will see if we received, as expected. Should run on at least 2 processors.
57 //
58 // Note: We do not need a moab instance for this example
59 //
60 //
61 #include "moab/MOABConfig.h"
62 #ifdef MOAB_HAVE_MPI
63 #include "moab/ProcConfig.hpp"
64 #endif
65 #include "moab/TupleList.hpp"
66 #include "moab/ProgOptions.hpp"
67 #include "moab/ErrorHandler.hpp"
68 #include <ctime>
69 #include <iostream>
70 #include <sstream>
71 
72 const char BRIEF_DESC[] = "Example of gather scatter with tuple lists \n";
73 std::ostringstream LONG_DESC;
74 
75 using namespace moab;
76 using namespace std;
77 
78 int main( int argc, char** argv )
79 {
80 #ifdef MOAB_HAVE_MPI
81  MPI_Init( &argc, &argv );
82 
83  // Initialize error handler, required for this example (not using a moab instance)
85 
86  ProcConfig pc( MPI_COMM_WORLD );
87  int size = pc.proc_size();
88  int rank = pc.proc_rank();
89 
90  // Start copy
91  LONG_DESC << "This program does a gather scatter with a list of tuples. \n"
92  " It tries to see how much communication costs in terms of time and memory. \n"
93  << "It starts with creating a list of tuples to be sent from each processor, \n to a "
94  "list of other processors.\n"
95  << "The number of tuples and how many tasks to communicate to are controlled by "
96  "input parameters.\n"
97  << "After communication, we verify locally if we received what we expected. \n";
98  ProgOptions opts( LONG_DESC.str(), BRIEF_DESC );
99 
100  // How many procs communicate to current proc, on average (we will vary that too)
101  int num_comms = 2;
102  opts.addOpt< int >( "num_comms,n", "each task will send to about num_comms other tasks some tuples (default 2)",
103  &num_comms );
104 
105  int num_tuples = 4;
106  opts.addOpt< int >( "num_tuples,t", "each task will send to some task about num_tuples tuples (default 4)",
107  &num_tuples );
108 
109  int reportrank = size + 1;
110  opts.addOpt< int >( "reporting_rank,r",
111  "this rank will report the tuples sent and the tuples received; it could "
112  "be higher than num_procs, then no reporting",
113  &reportrank );
114 
115  opts.parseCommandLine( argc, argv );
116 
117  if( rank == reportrank || ( reportrank >= size && 0 == rank ) )
118  {
119  cout << " There are " << size << " tasks in example.\n";
120  cout << " We will send groups of " << num_tuples << " from each task towards " << num_comms
121  << " other tasks.\n";
122  }
123 
124  // Send some data from proc i to i + n/2, also to i + n/2 + 1 modulo n, where n is num procs
125 
127 
128  long total_n_tuples = num_comms * num_tuples;
129 
130  // Vary the number of tasks to send to, and the number of tuples to send
131  if( rank < size / 2 )
132  num_comms--;
133  else
134  num_comms++;
135 
136  if( rank < size / 3 )
137  num_tuples *= 2;
138  else if( rank > size - size / 3 )
139  num_tuples /= 2;
140 
141  TupleList tl;
142  // At most num_tuples* num_comms to send
143  // We do a preallocate with this; some tuples on some processors might need more memory, to be
144  // able to grow locally; Some tasks might receive more tuples though, and in the process, some
145  // might grow more than others. By doing these logP sends/receives, we do not grow local memory
146  // too much.
147  tl.initialize( 1, 1, 0, 1, num_tuples * num_comms );
148  tl.enableWriteAccess();
149  // Form num_tuples*num_comms tuples, send to various ranks
150  unsigned int n = tl.get_n();
151  for( int i = 0; i < num_comms; i++ )
152  {
153  int sendTo = rank + i * size / 2 + 1; // Spread out the send to, for a stress-like test
154  sendTo = sendTo % size; //
155  long intToSend = 1000 * rank + 100000 * sendTo;
156  for( int j = 0; j < num_tuples; j++ )
157  {
158  n = tl.get_n();
159  tl.vi_wr[n] = sendTo;
160  tl.vl_wr[n] = intToSend + j;
161  tl.vr_wr[n] = 10000. * rank + j;
162  tl.inc_n();
163  }
164  }
165 
166  if( rank == reportrank )
167  {
168  cout << "rank " << rank << "\n";
169  tl.print( " before sending" );
170  }
171 
172  clock_t tt = clock();
173  // All communication happens here; no mpi calls for the user
174  MB_CHK_SET_ERR( cd->gs_transfer( 1, tl, 0 ), "Error in tuple transfer" );
175 
176  double secs = 0;
177  if( rank == reportrank || ( reportrank >= size && 0 == rank ) )
178  {
179  secs = ( clock() - tt ) / (double)CLOCKS_PER_SEC;
180  }
181  if( rank == reportrank )
182  {
183  cout << "rank " << rank << "\n";
184  tl.print( " after transfer" );
185  }
186 
187  // Check that all tuples received have the form 10000*rank + 100*from
188  unsigned int received = tl.get_n();
189  for( int i = 0; i < (int)received; i++ )
190  {
191  int from = tl.vi_rd[i];
192  long valrec = tl.vl_rd[i];
193  int remainder = valrec - 100000 * rank - 1000 * from;
194  if( remainder < 0 || remainder >= num_tuples * 4 )
195  cout << " error: tuple " << i << " received at proc rank " << rank << " from proc " << from << " has value "
196  << valrec << " remainder " << remainder << "\n";
197  }
198 
199  if( rank == reportrank || ( reportrank >= size && 0 == rank ) )
200  {
201  cout << "communication of about " << total_n_tuples << " tuples/per proc took " << secs << " seconds" << "\n";
202  tt = clock();
203  }
204 
205  // Finalize error handler, required for this example (not using a moab instance)
207 
208  MPI_Finalize();
209 #else
210  std::cout << " Build with MPI for this example to work\n";
211 #endif
212 
213  return 0;
214 }