Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
LloydRelaxation.cpp
Go to the documentation of this file.
1 /**
2  * @file LloydRelaxation.cpp
3  * @brief Example demonstrating Lloyd relaxation for mesh smoothing
4  *
5  * This example shows how to:
6  * - Load a parallel mesh with ghost layers
7  * - Perform Lloyd relaxation iterations for mesh smoothing
8  * - Handle fixed vertices (e.g., boundary vertices)
9  * - Exchange ghost data between processors
10  * - Measure convergence based on vertex movement
11  * - Write the smoothed mesh to a parallel file
12  *
13  * Lloyd relaxation is a technique to smooth out a mesh by iteratively
14  * moving vertices to the centroids of their connected cells.
15  *
16  * @author MOAB Development Team
17  * @date 2024
18  *
19 
20  * \brief Perform Lloyd relaxation on a mesh and its dual \n
21  * <b>To run</b>: mpiexec -np \c np LloydRelaxation [filename]\n
22  *
23  * Briefly, Lloyd relaxation is a technique to smooth out a mesh. The centroid of each cell is
24  * computed from its vertex positions, then vertices are placed at the average of their connected
25  * cells' centroids.
26  *
27  * In the parallel algorithm, an extra ghost layer of cells is exchanged. This allows us to compute
28  * the centroids for boundary cells on each processor where they appear; this eliminates the need
29  * for one round of data exchange (for those centroids) between processors. New vertex positions
30  * must be sent from owning processors to processors sharing those vertices. Convergence is
31  * measured as the maximum distance moved by any vertex.
32  *
33  * In this implementation, a fixed number of iterations is performed. The final mesh is output to
34  * 'lloydfinal.h5m' in the current directory (H5M format must be used since the file is written in
35  * parallel).
36  *
37  * @param argc Number of command line arguments
38  * @param argv Command line arguments array
39  * @return 0 on success, 1 on failure
40  */
41 
42 #include "moab/Core.hpp"
43 #ifdef MOAB_HAVE_MPI
44 #include "moab/ParallelComm.hpp"
45 #include "MBParallelConventions.h"
46 #endif
47 #include "moab/Skinner.hpp"
48 #include "moab/CN.hpp"
49 #include "moab/CartVect.hpp"
50 #include <iostream>
51 #include <sstream>
52 
53 using namespace moab;
54 using namespace std;
55 
56 string test_file_name = string( MESH_DIR ) + string( "/surfrandomtris-4part.h5m" );
57 
58 ErrorCode perform_lloyd_relaxation( Interface* mb, Range& verts, Range& cells, Tag fixed, int num_its, int report_its );
59 
60 int main( int argc, char** argv )
61 {
62  int num_its = 10;
63  int report_its = 1;
64 
65 #ifdef MOAB_HAVE_MPI
66  MPI_Init( &argc, &argv );
67 #endif
68 
69  // Need option handling here for input filename
70  if( argc > 1 )
71  {
72  // User has input a mesh file
73  test_file_name = argv[1];
74  }
75 
76  // Get MOAB instance
77  Interface* mb = new( std::nothrow ) Core;
78  if( NULL == mb ) return 1;
79 
80 #ifdef MOAB_HAVE_MPI
81  // Get the ParallelComm instance
82  ParallelComm* pcomm = new ParallelComm( mb, MPI_COMM_WORLD );
83  int nprocs = pcomm->size();
84 #else
85  int nprocs = 1;
86 #endif
87  string options;
88  if( nprocs > 1 ) // If reading in parallel, need to tell it how
89  options = "PARALLEL=READ_PART;PARTITION=PARALLEL_PARTITION;PARALLEL_RESOLVE_SHARED_ENTS;"
90  "PARALLEL_GHOSTS=2.0.1;DEBUG_IO=0;DEBUG_PIO=0";
91 
92  // Load the test file with specified options
93  MB_CHK_ERR( mb->load_file( test_file_name.c_str(), 0, options.c_str() ) );
94 
95  // Make tag to specify fixed vertices, since it's input to the algorithm; use a default value of
96  // non-fixed so we only need to set the fixed tag for skin vertices
97  Tag fixed;
98  int def_val = 0;
99  MB_CHK_ERR( mb->tag_get_handle( "fixed", 1, MB_TYPE_INTEGER, fixed, MB_TAG_CREAT | MB_TAG_DENSE, &def_val ) );
100 
101  // Get all vertices and faces
102  Range verts, faces, skin_verts;
103  MB_CHK_ERR( mb->get_entities_by_type( 0, MBVERTEX, verts ) );
104  MB_CHK_ERR( mb->get_entities_by_dimension( 0, 2, faces ) );
105 
106  // Get the skin vertices of those faces and mark them as fixed; we don't want to fix the
107  // vertices on a part boundary, but since we exchanged a layer of ghost faces, those vertices
108  // aren't on the skin locally ok to mark non-owned skin vertices too, I won't move those anyway
109  // use MOAB's skinner class to find the skin
110  Skinner skinner( mb );
111  MB_CHK_ERR(
112  skinner.find_skin( 0, faces, true, skin_verts ) ); // 'true' param indicates we want vertices back, not faces
113 
114  vector< int > fix_tag( skin_verts.size(), 1 ); // Initialized to 1 to indicate fixed
115  MB_CHK_ERR( mb->tag_set_data( fixed, skin_verts, &fix_tag[0] ) );
116 
117  // Now perform the Lloyd relaxation
118  MB_CHK_ERR( perform_lloyd_relaxation( mb, verts, faces, fixed, num_its, report_its ) );
119 
120  // Delete fixed tag, since we created it here
121  MB_CHK_ERR( mb->tag_delete( fixed ) );
122 
123  // Output file, using parallel write
124 
125 #ifdef MOAB_HAVE_MPI
126  options = "PARALLEL=WRITE_PART";
127 #endif
128 
129  MB_CHK_ERR( mb->write_file( "lloydfinal.h5m", NULL, options.c_str() ) );
130 
131  // Delete MOAB instance
132  delete mb;
133 
134 #ifdef MOAB_HAVE_MPI
135  MPI_Finalize();
136 #endif
137 
138  return 0;
139 }
140 
141 ErrorCode perform_lloyd_relaxation( Interface* mb, Range& verts, Range& faces, Tag fixed, int num_its, int report_its )
142 {
143  ErrorCode rval;
144 
145 #ifdef MOAB_HAVE_MPI
146  ParallelComm* pcomm = ParallelComm::get_pcomm( mb, 0 );
147  int nprocs = pcomm->size();
148 #else
149  int nprocs = 1;
150 #endif
151 
152  // Perform Lloyd relaxation:
153  // 1. Setup: set vertex centroids from vertex coords; filter to owned verts; get fixed tags
154 
155  // Get all verts coords into tag; don't need to worry about filtering out fixed verts,
156  // we'll just be setting to their fixed coords
157  vector< double > vcentroids( 3 * verts.size() );
158  MB_CHK_ERR( mb->get_coords( verts, &vcentroids[0] ) );
159 
160  Tag centroid;
161  MB_CHK_ERR( mb->tag_get_handle( "centroid", 3, MB_TYPE_DOUBLE, centroid, MB_TAG_CREAT | MB_TAG_DENSE ) );
162  MB_CHK_ERR( mb->tag_set_data( centroid, verts, &vcentroids[0] ) );
163 
164  // Filter verts down to owned ones and get fixed tag for them
165  Range owned_verts, shared_owned_verts;
166  if( nprocs > 1 )
167  {
168 #ifdef MOAB_HAVE_MPI
169  MB_CHK_ERR( pcomm->filter_pstatus( verts, PSTATUS_NOT_OWNED, PSTATUS_NOT, -1, &owned_verts ) );
170 #endif
171  }
172  else
173  owned_verts = verts;
174  vector< int > fix_tag( owned_verts.size() );
175  MB_CHK_ERR( mb->tag_get_data( fixed, owned_verts, &fix_tag[0] ) );
176 
177  // Now fill vcentroids array with positions of just owned vertices, since those are the ones
178  // we're actually computing
179  vcentroids.resize( 3 * owned_verts.size() );
180  MB_CHK_ERR( mb->tag_get_data( centroid, owned_verts, &vcentroids[0] ) );
181 
182 #ifdef MOAB_HAVE_MPI
183  // Get shared owned verts, for exchanging tags
184  MB_CHK_ERR( pcomm->get_shared_entities( -1, shared_owned_verts, 0, false, true ) );
185  // Workaround: if no shared owned verts, put a non-shared one in the list, to prevent exchanging
186  // tags for all shared entities
187  if( shared_owned_verts.empty() ) shared_owned_verts.insert( *verts.begin() );
188 #endif
189 
190  // Some declarations for later iterations
191  vector< double > fcentroids( 3 * faces.size() ); // fcentroids for face centroids
192  vector< double > ctag( 3 * CN::MAX_NODES_PER_ELEMENT ); // Temporary coordinate storage for verts bounding a face
193  const EntityHandle* conn; // const ptr & size to face connectivity
194  int nconn;
195  Range::iterator fit, vit; // For iterating over faces, verts
196  int f, v; // For indexing into centroid vectors
197  vector< EntityHandle > adj_faces; // Used in vertex iteration
198 
199  // 2. For num_its iterations:
200  for( int nit = 0; nit < num_its; nit++ )
201  {
202  double mxdelta = 0.0;
203 
204  // 2a. For each face: centroid = sum(vertex centroids)/num_verts_in_cell
205  for( fit = faces.begin(), f = 0; fit != faces.end(); ++fit, f++ )
206  {
207  // Get verts for this face
208  MB_CHK_ERR( mb->get_connectivity( *fit, conn, nconn ) );
209  // Get centroid tags for those verts
210  MB_CHK_ERR( mb->tag_get_data( centroid, conn, nconn, &ctag[0] ) );
211  fcentroids[3 * f + 0] = fcentroids[3 * f + 1] = fcentroids[3 * f + 2] = 0.0;
212  for( v = 0; v < nconn; v++ )
213  {
214  fcentroids[3 * f + 0] += ctag[3 * v + 0];
215  fcentroids[3 * f + 1] += ctag[3 * v + 1];
216  fcentroids[3 * f + 2] += ctag[3 * v + 2];
217  }
218  for( v = 0; v < 3; v++ )
219  fcentroids[3 * f + v] /= nconn;
220  }
221  MB_CHK_ERR( mb->tag_set_data( centroid, faces, &fcentroids[0] ) );
222 
223  // 2b. For each owned vertex:
224  for( vit = owned_verts.begin(), v = 0; vit != owned_verts.end(); ++vit, v++ )
225  {
226  // if !fixed
227  if( fix_tag[v] ) continue;
228  // vertex centroid = sum(cell centroids)/ncells
229  adj_faces.clear();
230  MB_CHK_ERR( mb->get_adjacencies( &( *vit ), 1, 2, false, adj_faces ) );
231  MB_CHK_ERR( mb->tag_get_data( centroid, &adj_faces[0], adj_faces.size(), &fcentroids[0] ) );
232  double vnew[] = { 0.0, 0.0, 0.0 };
233  for( f = 0; f < (int)adj_faces.size(); f++ )
234  {
235  vnew[0] += fcentroids[3 * f + 0];
236  vnew[1] += fcentroids[3 * f + 1];
237  vnew[2] += fcentroids[3 * f + 2];
238  }
239  for( f = 0; f < 3; f++ )
240  vnew[f] /= adj_faces.size();
241  double delta = ( CartVect( vnew ) - CartVect( &vcentroids[3 * v] ) ).length();
242  mxdelta = std::max( delta, mxdelta );
243  for( f = 0; f < 3; f++ )
244  vcentroids[3 * v + f] = vnew[f];
245  }
246 
247  // Set the centroid tag; having them only in vcentroids array isn't enough, as vertex
248  // centroids are accessed randomly in loop over faces
249  MB_CHK_ERR( mb->tag_set_data( centroid, owned_verts, &vcentroids[0] ) );
250 
251  // 2c. Exchange tags on owned verts
252  if( nprocs > 1 )
253  {
254 #ifdef MOAB_HAVE_MPI
255  MB_CHK_ERR( pcomm->exchange_tags( centroid, shared_owned_verts ) );
256 #endif
257  }
258 
259  if( !( nit % report_its ) )
260  {
261  // Global reduce for maximum delta, then report it
262  double global_max = mxdelta;
263  int myrank = 0;
264 #ifdef MOAB_HAVE_MPI
265  if( nprocs > 1 ) MPI_Reduce( &mxdelta, &global_max, 1, MPI_DOUBLE, MPI_MAX, 0, pcomm->comm() );
266  myrank = pcomm->rank();
267 #endif
268  if( 1 == nprocs || 0 == myrank ) cout << "Max delta = " << global_max << endl;
269  }
270  }
271 
272  // Write the tag back onto vertex coordinates
273  MB_CHK_ERR( mb->set_coords( owned_verts, &vcentroids[0] ) );
274 
275  // Delete the centroid tag, since we don't need it anymore
276  MB_CHK_ERR( mb->tag_delete( centroid ) );
277 
278  return MB_SUCCESS;
279 }