Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
NCWriteESMF.cpp
Go to the documentation of this file.
1 //-------------------------------------------------------------------------
2 // Filename : NCWriteESMF.cpp
3 //
4 // Purpose : ESMF unstructured grid file writer. See NCWriteESMF.hpp
5 // for design notes.
6 //
7 // Creator : Vijay Mahadevan, 2026-06-13
8 //-------------------------------------------------------------------------
9 
10 #include "NCWriteESMF.hpp"
11 #include "MBTagConventions.hpp"
12 
13 #include <algorithm>
14 #include <cmath>
15 #include <cstring>
16 #include <map>
17 #include <set>
18 
19 namespace moab
20 {
21 
23 
24 namespace
25 {
26 
27 constexpr double kPi = 3.14159265358979323846;
28 constexpr double kRadToDegree = 180.0 / kPi;
29 
30 inline void xyz_to_latlon_deg( double x, double y, double z, double& lat, double& lon )
31 {
32  const double r = std::sqrt( x * x + y * y + z * z );
33  if( r < 1.0e-30 )
34  {
35  lat = 0.0;
36  lon = 0.0;
37  return;
38  }
39  lat = std::asin( z / r ) * kRadToDegree;
40  lon = std::atan2( y, x ) * kRadToDegree;
41  if( lon < 0.0 ) lon += 360.0;
42 }
43 
44 } // namespace
45 
46 // ============================================================================
47 // collect_mesh_info — gather owned cells; compute per-rank vertex lat/lon
48 // (de-duplicated within the rank), per-cell global-id connectivity, and
49 // center lat/lon.
50 // ============================================================================
52 {
53  Interface*& mbImpl = _writeNC->mbImpl;
54  Tag& mGlobalIdTag = _writeNC->mGlobalIdTag;
55  DebugOutput& dbgOut = _writeNC->dbgOut;
56 
57  Range allCells;
58  MB_CHK_SET_ERR( mbImpl->get_entities_by_dimension( _fileSet, 2, allCells ),
59  "Failed to gather 2-D cells for ESMF write" );
60  if( allCells.empty() ) MB_SET_ERR( MB_FAILURE, "No 2-D cells in file set; cannot write ESMF grid" );
61 
62  localCellsOwned = allCells;
63 #ifdef MOAB_HAVE_MPI
64  bool& isParallel = _writeNC->isParallel;
65  if( isParallel )
66  {
67  ParallelComm*& myPcomm = _writeNC->myPcomm;
68  if( myPcomm && myPcomm->proc_config().proc_size() > 1 )
69  {
71  "Failed to filter owned cells for ESMF write" );
72  }
73  }
74 #endif
75 
76  mLocalCells = static_cast< long >( localCellsOwned.size() );
77 
78  // Global max corners per cell.
79  int localMaxCorners = 0;
80  for( Range::iterator cit = localCellsOwned.begin(); cit != localCellsOwned.end(); ++cit )
81  {
82  const EntityHandle* conn = nullptr;
83  int numConn = 0;
84  if( MB_SUCCESS == mbImpl->get_connectivity( *cit, conn, numConn ) && numConn > localMaxCorners )
85  localMaxCorners = numConn;
86  }
87 #ifdef MOAB_HAVE_MPI
88  if( _writeNC->isParallel && _writeNC->myPcomm )
89  {
90  MPI_Allreduce( &localMaxCorners, &mMaxCornersGlobal, 1, MPI_INT, MPI_MAX,
91  _writeNC->myPcomm->proc_config().proc_comm() );
92  }
93  else
94 #endif
95  {
96  mMaxCornersGlobal = localMaxCorners;
97  }
98  if( mMaxCornersGlobal <= 0 ) MB_SET_ERR( MB_FAILURE, "Cells reported zero connectivity; cannot write ESMF" );
99 
100  // First pass: collect the set of vertex handles referenced by owned
101  // cells, plus per-cell (numNodes, connectivity-by-EntityHandle).
102  std::set< EntityHandle > usedVerts;
103  std::vector< std::vector< EntityHandle > > perCellConn( mLocalCells );
104  mElementNumNodes.assign( mLocalCells, 0 );
105  mLocalCellGids.assign( mLocalCells, 0 );
106  mCenterLat.assign( mLocalCells, 0.0 );
107  mCenterLon.assign( mLocalCells, 0.0 );
108 
109  long ci = 0;
110  for( Range::iterator cit = localCellsOwned.begin(); cit != localCellsOwned.end(); ++cit, ++ci )
111  {
112  int gid = 0;
113  if( mGlobalIdTag ) mbImpl->tag_get_data( mGlobalIdTag, &( *cit ), 1, &gid );
114  mLocalCellGids[ci] = gid;
115 
116  const EntityHandle* conn = nullptr;
117  int numConn = 0;
118  MB_CHK_SET_ERR( mbImpl->get_connectivity( *cit, conn, numConn ), "Cell connectivity lookup failed" );
119  mElementNumNodes[ci] = numConn;
120  perCellConn[ci].assign( conn, conn + numConn );
121 
122  // Compute the per-cell center directly from vertex coords.
123  double cx = 0.0, cy = 0.0, cz = 0.0;
124  for( int k = 0; k < numConn; ++k )
125  {
126  usedVerts.insert( conn[k] );
127  double vc[3] = { 0.0, 0.0, 0.0 };
128  MB_CHK_SET_ERR( mbImpl->get_coords( &conn[k], 1, vc ), "Vertex coordinate lookup failed" );
129  cx += vc[0];
130  cy += vc[1];
131  cz += vc[2];
132  }
133  cx /= numConn;
134  cy /= numConn;
135  cz /= numConn;
136  xyz_to_latlon_deg( cx, cy, cz, mCenterLat[ci], mCenterLon[ci] );
137  }
138 
139  // Build per-rank node arrays (vertex GID + lat/lon).
140  const size_t localNodeCount = usedVerts.size();
141  mNodeGids.assign( localNodeCount, 0 );
142  mNodeLon.assign( localNodeCount, 0.0 );
143  mNodeLat.assign( localNodeCount, 0.0 );
144  {
145  size_t ni = 0;
146  for( EntityHandle v : usedVerts )
147  {
148  int vgid = 0;
149  if( mGlobalIdTag ) mbImpl->tag_get_data( mGlobalIdTag, &v, 1, &vgid );
150  mNodeGids[ni] = vgid;
151  double vc[3] = { 0.0, 0.0, 0.0 };
152  mbImpl->get_coords( &v, 1, vc );
153  xyz_to_latlon_deg( vc[0], vc[1], vc[2], mNodeLat[ni], mNodeLon[ni] );
154  ++ni;
155  }
156  }
157 
158  // Build per-rank connectivity as vertex GIDs (1-based index will be
159  // assigned later at rank 0 after global dedup).
160  mElementConn.assign( static_cast< size_t >( mLocalCells ) * mMaxCornersGlobal, 0 );
161  for( long c = 0; c < mLocalCells; ++c )
162  {
163  const std::vector< EntityHandle >& cv = perCellConn[c];
164  int firstGid = 0;
165  for( int k = 0; k < (int)cv.size(); ++k )
166  {
167  int vgid = 0;
168  if( mGlobalIdTag ) mbImpl->tag_get_data( mGlobalIdTag, &cv[k], 1, &vgid );
169  mElementConn[c * mMaxCornersGlobal + k] = vgid;
170  if( k == 0 ) firstGid = vgid;
171  }
172  // Pad unused slots with first GID (writers convention; the
173  // numElementConn field tells the reader the real per-cell count).
174  for( int k = (int)cv.size(); k < mMaxCornersGlobal; ++k )
175  mElementConn[c * mMaxCornersGlobal + k] = firstGid;
176  }
177 
178  // Optional masks / areas from familiar tags (matches what NCHelperESMF
179  // would produce on read).
180  Tag maskTag = 0;
181  if( MB_SUCCESS == mbImpl->tag_get_handle( "ELEMENT_MASK", 1, MB_TYPE_INTEGER, maskTag ) && maskTag )
182  {
183  mMask.assign( mLocalCells, 1 );
184  if( MB_SUCCESS == mbImpl->tag_get_data( maskTag, localCellsOwned, mMask.data() ) )
185  mHasMask = true;
186  else
187  mMask.clear();
188  }
189  Tag areaTag = 0;
190  if( MB_SUCCESS == mbImpl->tag_get_handle( "GRID_AREA", 1, MB_TYPE_DOUBLE, areaTag ) && areaTag )
191  {
192  mAreas.assign( mLocalCells, 0.0 );
193  if( MB_SUCCESS == mbImpl->tag_get_data( areaTag, localCellsOwned, mAreas.data() ) )
194  mHasAreas = true;
195  else
196  mAreas.clear();
197  }
198 
199  // Global cell count.
201 #ifdef MOAB_HAVE_MPI
202  if( _writeNC->isParallel && _writeNC->myPcomm )
203  {
204  long localN = mLocalCells;
205  MPI_Allreduce( &localN, &mGlobalCells, 1, MPI_LONG, MPI_SUM,
206  _writeNC->myPcomm->proc_config().proc_comm() );
207  }
208 #endif
209  // Global node count is set after rank 0 does the dedup in write_values;
210  // here we just stash a lower bound for the init_file dim size.
211  mGlobalNodes = static_cast< long >( localNodeCount );
212 #ifdef MOAB_HAVE_MPI
213  if( _writeNC->isParallel && _writeNC->myPcomm )
214  {
215  // Worst-case (no sharing): sum local node counts. The reader
216  // doesn't care if we declare a slightly larger nodeCount than
217  // actually used, but for cleanliness we'll set the dim from the
218  // actual deduplicated count once we know it (init_file uses
219  // mGlobalNodes; we overwrite it before init_file runs).
220  long localN = static_cast< long >( localNodeCount );
221  MPI_Allreduce( &localN, &mGlobalNodes, 1, MPI_LONG, MPI_SUM,
222  _writeNC->myPcomm->proc_config().proc_comm() );
223  }
224 #endif
225 
226  dbgOut.tprintf( 1, " ESMF write: local_cells=%ld global_cells=%ld local_nodes=%zu max_corners=%d\n", mLocalCells,
227  mGlobalCells, localNodeCount, mMaxCornersGlobal );
228 
229  return MB_SUCCESS;
230 }
231 
232 // ============================================================================
233 // init_file — define the ESMF schema. nodeCount is sized to a safe upper
234 // bound here (sum of per-rank node counts); write_values may reduce it
235 // after the rank-0 dedup. NetCDF allows defining a dim of a known size
236 // and writing fewer entries via the count[] argument.
237 // ============================================================================
238 ErrorCode NCWriteESMF::init_file( std::vector< std::string >& /*var_names*/,
239  std::vector< std::string >& /*desired_names*/,
240  bool /*_append*/ )
241 {
242  // Dimensions
243  if( NCFUNC( def_dim )( _fileId, "nodeCount", static_cast< size_t >( mGlobalNodes ), &mDimNodeCount ) )
244  MB_SET_ERR( MB_FAILURE, "Failed to define nodeCount dim" );
245  if( NCFUNC( def_dim )( _fileId, "elementCount", static_cast< size_t >( mGlobalCells ), &mDimElementCount ) )
246  MB_SET_ERR( MB_FAILURE, "Failed to define elementCount dim" );
247  if( NCFUNC( def_dim )( _fileId, "maxNodePElement", static_cast< size_t >( mMaxCornersGlobal ),
249  MB_SET_ERR( MB_FAILURE, "Failed to define maxNodePElement dim" );
250  if( NCFUNC( def_dim )( _fileId, "coordDim", static_cast< size_t >( mCoordDim ), &mDimCoordDim ) )
251  MB_SET_ERR( MB_FAILURE, "Failed to define coordDim dim" );
252 
253  const int dimsNode[2] = { mDimNodeCount, mDimCoordDim };
254  const int dimsElem[2] = { mDimElementCount, mDimMaxNodePElement };
255  const int dimsCenter[2] = { mDimElementCount, mDimCoordDim };
256  const int dimsElem1[1] = { mDimElementCount };
257 
258  if( NCFUNC( def_var )( _fileId, "nodeCoords", NC_DOUBLE, 2, dimsNode, &mVarNodeCoords ) )
259  MB_SET_ERR( MB_FAILURE, "Failed to define nodeCoords var" );
260  if( NCFUNC( def_var )( _fileId, "elementConn", NC_INT, 2, dimsElem, &mVarElementConn ) )
261  MB_SET_ERR( MB_FAILURE, "Failed to define elementConn var" );
262  if( NCFUNC( def_var )( _fileId, "numElementConn", NC_INT, 1, dimsElem1, &mVarNumElementConn ) )
263  MB_SET_ERR( MB_FAILURE, "Failed to define numElementConn var" );
264  if( NCFUNC( def_var )( _fileId, "centerCoords", NC_DOUBLE, 2, dimsCenter, &mVarCenterCoords ) )
265  MB_SET_ERR( MB_FAILURE, "Failed to define centerCoords var" );
266 
267  if( mHasAreas )
268  {
269  if( NCFUNC( def_var )( _fileId, "elementArea", NC_DOUBLE, 1, dimsElem1, &mVarElementArea ) )
270  MB_SET_ERR( MB_FAILURE, "Failed to define elementArea var" );
271  }
272  if( mHasMask )
273  {
274  if( NCFUNC( def_var )( _fileId, "elementMask", NC_INT, 1, dimsElem1, &mVarElementMask ) )
275  MB_SET_ERR( MB_FAILURE, "Failed to define elementMask var" );
276  }
277 
278  const char* deg = "degrees";
279  NCFUNC( put_att_text )( _fileId, mVarNodeCoords, "units", 7, deg );
280  NCFUNC( put_att_text )( _fileId, mVarCenterCoords, "units", 7, deg );
281  if( mHasAreas )
282  {
283  const char* rad2 = "radians^2";
284  NCFUNC( put_att_text )( _fileId, mVarElementArea, "units", 9, rad2 );
285  }
286  const char* gridtype = "unstructured";
287  NCFUNC( put_att_text )( _fileId, NC_GLOBAL, "gridType", std::strlen( gridtype ), gridtype );
288  const char* title = "MOAB:NCWriteESMF generated ESMF unstructured grid file";
289  NCFUNC( put_att_text )( _fileId, NC_GLOBAL, "title", std::strlen( title ), title );
290 
291  if( NCFUNC( enddef )( _fileId ) ) MB_SET_ERR( MB_FAILURE, "enddef failed for ESMF write" );
292 
293  return MB_SUCCESS;
294 }
295 
296 // ============================================================================
297 // write_values — gather to rank 0, dedup vertices by global id, write.
298 //
299 // The rank-0 dedup uses vertex GIDs (which are partition-invariant) as the
300 // canonical identity. After dedup we map each cell's vertex-GID array to
301 // 1-based indices into the unique-vertex list.
302 // ============================================================================
303 ErrorCode NCWriteESMF::write_values( std::vector< std::string >& /*var_names*/,
304  std::vector< int >& /*tstep_nums*/ )
305 {
306  DebugOutput& dbgOut = _writeNC->dbgOut;
307 
308  // Helper assembling the rank-0 unified arrays and writing the file.
309  auto writeAll = [&]( const std::vector< int >& allCellGids, const std::vector< int >& allNumNodes,
310  const std::vector< int >& allConnGid, const std::vector< double >& allCenterLat,
311  const std::vector< double >& allCenterLon, const std::vector< int >& allNodeGids,
312  const std::vector< double >& allNodeLat, const std::vector< double >& allNodeLon,
313  const std::vector< double >& allAreas, const std::vector< int >& allMask ) -> ErrorCode {
314  const int ncpc = mMaxCornersGlobal;
315  const long nCellsG = static_cast< long >( allCellGids.size() );
316  const long nNodesIn = static_cast< long >( allNodeGids.size() );
317 
318  // ---- Dedup nodes by GID ----------------------------------------
319  // Build a sorted, unique vertex-GID list and a map gid -> 1-based
320  // index for the elementConn rewrite.
321  std::vector< int > nodeOrder( nNodesIn );
322  for( long i = 0; i < nNodesIn; ++i )
323  nodeOrder[i] = static_cast< int >( i );
324  std::sort( nodeOrder.begin(), nodeOrder.end(),
325  [&]( int a, int b ) { return allNodeGids[a] < allNodeGids[b]; } );
326 
327  std::vector< int > uniqNodeGids;
328  std::vector< double > uniqNodeLat, uniqNodeLon;
329  std::map< int, int > gid2Idx; // global vertex GID -> 1-based unique index
330  uniqNodeGids.reserve( nNodesIn );
331  uniqNodeLat.reserve( nNodesIn );
332  uniqNodeLon.reserve( nNodesIn );
333  int prevGid = -1;
334  for( long i = 0; i < nNodesIn; ++i )
335  {
336  const int idx = nodeOrder[i];
337  const int g = allNodeGids[idx];
338  if( g != prevGid )
339  {
340  uniqNodeGids.push_back( g );
341  uniqNodeLat.push_back( allNodeLat[idx] );
342  uniqNodeLon.push_back( allNodeLon[idx] );
343  gid2Idx[g] = static_cast< int >( uniqNodeGids.size() ); // 1-based
344  prevGid = g;
345  }
346  }
347  const long nNodesU = static_cast< long >( uniqNodeGids.size() );
348 
349  // ---- Sort cells by GID for deterministic on-disk ordering ------
350  std::vector< long > cellOrder( nCellsG );
351  for( long i = 0; i < nCellsG; ++i )
352  cellOrder[i] = i;
353  std::sort( cellOrder.begin(), cellOrder.end(),
354  [&]( long a, long b ) { return allCellGids[a] < allCellGids[b]; } );
355 
356  // ---- Build the final writable arrays in cell-sorted order ------
357  std::vector< int > numNodes( nCellsG );
358  std::vector< int > connIdx( static_cast< size_t >( nCellsG ) * ncpc );
359  std::vector< double > centerLat( nCellsG ), centerLon( nCellsG );
360  std::vector< double > areas;
361  std::vector< int > mask;
362  if( !allAreas.empty() ) areas.resize( nCellsG );
363  if( !allMask.empty() ) mask.resize( nCellsG );
364 
365  for( long i = 0; i < nCellsG; ++i )
366  {
367  const long src = cellOrder[i];
368  numNodes[i] = allNumNodes[src];
369  centerLat[i] = allCenterLat[src];
370  centerLon[i] = allCenterLon[src];
371  if( !areas.empty() ) areas[i] = allAreas[src];
372  if( !mask.empty() ) mask[i] = allMask[src];
373 
374  for( int k = 0; k < ncpc; ++k )
375  {
376  const int g = allConnGid[src * ncpc + k];
377  auto it = gid2Idx.find( g );
378  connIdx[i * ncpc + k] = ( it == gid2Idx.end() ) ? 0 : it->second;
379  }
380  }
381 
382  // ---- Interleave node and center coords as [lon, lat] -----------
383  std::vector< double > nodeCoords( nNodesU * 2 ), centerCoords( nCellsG * 2 );
384  for( long i = 0; i < nNodesU; ++i )
385  {
386  nodeCoords[i * 2 + 0] = uniqNodeLon[i];
387  nodeCoords[i * 2 + 1] = uniqNodeLat[i];
388  }
389  for( long i = 0; i < nCellsG; ++i )
390  {
391  centerCoords[i * 2 + 0] = centerLon[i];
392  centerCoords[i * 2 + 1] = centerLat[i];
393  }
394 
395  // ---- Write to disk via the dispatch layer ----------------------
396  const size_t startN[2] = { 0, 0 };
397  const size_t countN[2] = { static_cast< size_t >( nNodesU ), static_cast< size_t >( mCoordDim ) };
398  if( NCFUNCAP( _vara_double )( _fileId, mVarNodeCoords, startN, countN, nodeCoords.data() ) )
399  MB_SET_ERR( MB_FAILURE, "Failed to write nodeCoords" );
400 
401  const size_t startE[2] = { 0, 0 };
402  const size_t countE[2] = { static_cast< size_t >( nCellsG ), static_cast< size_t >( ncpc ) };
403  if( NCFUNCAP( _vara_int )( _fileId, mVarElementConn, startE, countE, connIdx.data() ) )
404  MB_SET_ERR( MB_FAILURE, "Failed to write elementConn" );
405 
406  size_t s1 = 0, c1 = static_cast< size_t >( nCellsG );
407  if( NCFUNCAP( _vara_int )( _fileId, mVarNumElementConn, &s1, &c1, numNodes.data() ) )
408  MB_SET_ERR( MB_FAILURE, "Failed to write numElementConn" );
409 
410  const size_t startC[2] = { 0, 0 };
411  const size_t countC[2] = { static_cast< size_t >( nCellsG ), static_cast< size_t >( mCoordDim ) };
412  if( NCFUNCAP( _vara_double )( _fileId, mVarCenterCoords, startC, countC, centerCoords.data() ) )
413  MB_SET_ERR( MB_FAILURE, "Failed to write centerCoords" );
414 
415  if( !areas.empty() )
416  {
417  if( NCFUNCAP( _vara_double )( _fileId, mVarElementArea, &s1, &c1, areas.data() ) )
418  MB_SET_ERR( MB_FAILURE, "Failed to write elementArea" );
419  }
420  if( !mask.empty() )
421  {
422  if( NCFUNCAP( _vara_int )( _fileId, mVarElementMask, &s1, &c1, mask.data() ) )
423  MB_SET_ERR( MB_FAILURE, "Failed to write elementMask" );
424  }
425 
426  return MB_SUCCESS;
427  };
428 
429 #ifdef MOAB_HAVE_MPI
430  if( _writeNC->isParallel && _writeNC->myPcomm && _writeNC->myPcomm->proc_config().proc_size() > 1 )
431  {
432  MPI_Comm comm = _writeNC->myPcomm->proc_config().proc_comm();
433  const int rank = _writeNC->myPcomm->proc_config().proc_rank();
434  const int size = _writeNC->myPcomm->proc_config().proc_size();
435  const int ncpc = mMaxCornersGlobal;
436 
437  // Per-rank cell counts + displacements for gather.
438  int myCells = static_cast< int >( mLocalCells );
439  std::vector< int > cellCounts( size, 0 ), cellDispls( size, 0 );
440  MPI_Gather( &myCells, 1, MPI_INT, cellCounts.data(), 1, MPI_INT, 0, comm );
441 
442  // Per-rank node counts (these vary per rank — each rank reports
443  // its own local node count).
444  int myNodes = static_cast< int >( mNodeGids.size() );
445  std::vector< int > nodeCounts( size, 0 ), nodeDispls( size, 0 );
446  MPI_Gather( &myNodes, 1, MPI_INT, nodeCounts.data(), 1, MPI_INT, 0, comm );
447 
448  // Rank 0 sets up the displacement arrays.
449  std::vector< int > cellCountsC( size, 0 ), cellDisplsC( size, 0 );
450  if( rank == 0 )
451  {
452  int accC = 0, accCC = 0, accN = 0;
453  for( int i = 0; i < size; ++i )
454  {
455  cellDispls[i] = accC;
456  accC += cellCounts[i];
457  cellCountsC[i] = cellCounts[i] * ncpc;
458  cellDisplsC[i] = accCC;
459  accCC += cellCountsC[i];
460  nodeDispls[i] = accN;
461  accN += nodeCounts[i];
462  }
463  }
464 
465  long nCellsGlobal = mGlobalCells;
466  long nNodesGlobal = 0;
467  if( rank == 0 ) for( int i = 0; i < size; ++i ) nNodesGlobal += nodeCounts[i];
468 
469  // Per-rank gather targets on rank 0.
470  std::vector< int > allCellGids, allNumNodes, allConnGid, allNodeGids;
471  std::vector< double > allCenterLat, allCenterLon, allNodeLat, allNodeLon;
472  std::vector< double > allAreas;
473  std::vector< int > allMask;
474  if( rank == 0 )
475  {
476  allCellGids.resize( nCellsGlobal );
477  allNumNodes.resize( nCellsGlobal );
478  allConnGid.resize( static_cast< size_t >( nCellsGlobal ) * ncpc );
479  allCenterLat.resize( nCellsGlobal );
480  allCenterLon.resize( nCellsGlobal );
481  allNodeGids.resize( nNodesGlobal );
482  allNodeLat.resize( nNodesGlobal );
483  allNodeLon.resize( nNodesGlobal );
484  if( mHasAreas ) allAreas.resize( nCellsGlobal );
485  if( mHasMask ) allMask.resize( nCellsGlobal );
486  }
487 
488  MPI_Gatherv( mLocalCellGids.data(), myCells, MPI_INT, allCellGids.data(), cellCounts.data(),
489  cellDispls.data(), MPI_INT, 0, comm );
490  MPI_Gatherv( mElementNumNodes.data(), myCells, MPI_INT, allNumNodes.data(), cellCounts.data(),
491  cellDispls.data(), MPI_INT, 0, comm );
492  MPI_Gatherv( mCenterLat.data(), myCells, MPI_DOUBLE, allCenterLat.data(), cellCounts.data(), cellDispls.data(),
493  MPI_DOUBLE, 0, comm );
494  MPI_Gatherv( mCenterLon.data(), myCells, MPI_DOUBLE, allCenterLon.data(), cellCounts.data(), cellDispls.data(),
495  MPI_DOUBLE, 0, comm );
496  MPI_Gatherv( mElementConn.data(), myCells * ncpc, MPI_INT, allConnGid.data(), cellCountsC.data(),
497  cellDisplsC.data(), MPI_INT, 0, comm );
498 
499  MPI_Gatherv( mNodeGids.data(), myNodes, MPI_INT, allNodeGids.data(), nodeCounts.data(), nodeDispls.data(),
500  MPI_INT, 0, comm );
501  MPI_Gatherv( mNodeLat.data(), myNodes, MPI_DOUBLE, allNodeLat.data(), nodeCounts.data(), nodeDispls.data(),
502  MPI_DOUBLE, 0, comm );
503  MPI_Gatherv( mNodeLon.data(), myNodes, MPI_DOUBLE, allNodeLon.data(), nodeCounts.data(), nodeDispls.data(),
504  MPI_DOUBLE, 0, comm );
505 
506  if( mHasAreas )
507  MPI_Gatherv( mAreas.data(), myCells, MPI_DOUBLE, allAreas.data(), cellCounts.data(), cellDispls.data(),
508  MPI_DOUBLE, 0, comm );
509  if( mHasMask )
510  MPI_Gatherv( mMask.data(), myCells, MPI_INT, allMask.data(), cellCounts.data(), cellDispls.data(), MPI_INT,
511  0, comm );
512 
513  ErrorCode rc = MB_SUCCESS;
514  if( rank == 0 )
515  rc = writeAll( allCellGids, allNumNodes, allConnGid, allCenterLat, allCenterLon, allNodeGids, allNodeLat,
516  allNodeLon, allAreas, allMask );
517  int rcInt = static_cast< int >( rc );
518  MPI_Bcast( &rcInt, 1, MPI_INT, 0, comm );
519  if( rcInt != MB_SUCCESS ) MB_SET_ERR( MB_FAILURE, "ESMF write failed on rank 0" );
520 
521  dbgOut.tprintf( 1, " ESMF write: gathered+wrote %ld cells from %d ranks\n", mGlobalCells, size );
522  return MB_SUCCESS;
523  }
524 #endif
525 
527  mNodeLon, mAreas, mMask );
528 }
529 
530 ErrorCode NCWriteESMF::write_nonset_variables( std::vector< WriteNC::VarData >& /*vdatas*/,
531  std::vector< int >& /*tstep_nums*/ )
532 {
533  return MB_SUCCESS;
534 }
535 
536 } // namespace moab