Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
TempestOnlineMapIO.cpp
Go to the documentation of this file.
1 /*
2  * =====================================================================================
3  *
4  * Filename: TempestOnlineMapIO.cpp
5  *
6  * Description: All I/O implementations related to TempestOnlineMap
7  *
8  * Version: 1.0
9  * Created: 02/06/2021 02:35:41
10  *
11  * Author: Vijay S. Mahadevan (vijaysm), [email protected]
12  * Company: Argonne National Lab
13  *
14  * =====================================================================================
15  */
16 
17 #include "FiniteElementTools.h"
19 #include "moab/TupleList.hpp"
20 
21 #ifdef MOAB_HAVE_NETCDFPAR
22 #include "netcdfcpp_par.hpp"
23 #else
24 #include "netcdfcpp.h"
25 #endif
26 
27 #ifdef MOAB_HAVE_PNETCDF
28 #include <pnetcdf.h>
29 
30 #define ERR_PARNC( err ) \
31  if( err != NC_NOERR ) \
32  { \
33  fprintf( stderr, "Error at line %d: %s\n", __LINE__, ncmpi_strerror( err ) ); \
34  MPI_Abort( MPI_COMM_WORLD, 1 ); \
35  }
36 
37 #endif
38 
39 #ifdef MOAB_HAVE_MPI
40 
41 #define MPI_CHK_ERR( err ) \
42  if( err ) \
43  { \
44  std::cout << "MPI Failure. ErrorCode (" << ( err ) << ") "; \
45  std::cout << "\nMPI Aborting... \n"; \
46  return moab::MB_FAILURE; \
47  }
48 
49 #ifdef MOAB_HAVE_EIGEN3
50 
51 // Function to serialize a sparse matrix to disk in text format
52 template < typename SparseMatrixType >
53 void moab::TempestOnlineMap::serializeSparseMatrix( const SparseMatrixType& mat, const std::string& filename )
54 {
55  std::ofstream ofs( filename );
56  if( !ofs.is_open() )
57  {
58  std::cerr << "Failed to open file for writing: " << filename << std::endl;
59  return;
60  }
61 
62  // Write matrix dimensions and number of non-zeros
63  int rows = mat.rows();
64  int cols = mat.cols();
65  typename SparseMatrixType::Index nnz = mat.nonZeros();
66  ofs << rows << " " << cols << " " << nnz << "\n";
67 
68  // Iterate over non-zero elements
69  for( int k = 0; k < mat.outerSize(); ++k )
70  {
71  for( typename SparseMatrixType::InnerIterator it( mat, k ); it; ++it )
72  {
73  // int row = it.row(); // row index
74  // int col = it.col(); // col index (equals k)
75  int row = 1 + this->GetRowGlobalDoF( it.row() ); // row index
76  int col = 1 + this->GetColGlobalDoF( it.col() ); // col index
77  auto value = it.value();
78  ofs << row << " " << col << " " << value << "\n";
79  }
80  }
81  ofs.close();
82 }
83 
84 #endif
85 
86 int moab::TempestOnlineMap::rearrange_arrays_by_dofs( const std::vector< unsigned int >& gdofmap,
87  DataArray1D< double >& vecFaceArea,
88  DataArray1D< double >& dCenterLon,
89  DataArray1D< double >& dCenterLat,
90  DataArray2D< double >& dVertexLon,
91  DataArray2D< double >& dVertexLat,
92  std::vector< int >& masks,
93  unsigned& N, // will have the local, after
94  int nv,
95  int& maxdof )
96 {
97  // first decide maxdof, for partitioning
98  unsigned int localmax = 0;
99  for( unsigned i = 0; i < N; i++ )
100  if( gdofmap[i] > localmax ) localmax = gdofmap[i];
101 
102  // decide partitioning based on maxdof/size
103  MPI_Allreduce( &localmax, &maxdof, 1, MPI_INT, MPI_MAX, m_pcomm->comm() );
104  // maxdof is 0 based, so actual number is +1
105  // maxdof
106  int size_per_task = ( maxdof + 1 ) / size; // based on this, processor to process dof x is x/size_per_task
107  // so we decide to reorder by actual dof, such that task 0 has dofs from [0 to size_per_task), etc
108  moab::TupleList tl;
109  unsigned numr = 2 * nv + 3; // doubles: area, centerlon, center lat, nv (vertex lon, vertex lat)
110  tl.initialize( 3, 0, 0, numr, N ); // to proc, dof, then
111  tl.enableWriteAccess();
112 
113  // populate
114  for( unsigned i = 0; i < N; i++ )
115  {
116  int gdof = gdofmap[i];
117  int to_proc = gdof / size_per_task;
118  int mask = (i >= masks.size() ? 1: masks[i]); // assume mask=1 if size is deficient (typically for SE-FV)
119  if( to_proc >= size ) to_proc = size - 1; // the last ones go to last proc
120  int n = tl.get_n();
121  tl.vi_wr[3 * n] = to_proc;
122  tl.vi_wr[3 * n + 1] = gdof;
123  tl.vi_wr[3 * n + 2] = mask;
124  tl.vr_wr[n * numr] = vecFaceArea[i];
125  tl.vr_wr[n * numr + 1] = dCenterLon[i];
126  tl.vr_wr[n * numr + 2] = dCenterLat[i];
127  for( int j = 0; j < nv; j++ )
128  {
129  tl.vr_wr[n * numr + 3 + j] = dVertexLon[i][j];
130  tl.vr_wr[n * numr + 3 + nv + j] = dVertexLat[i][j];
131  }
132  tl.inc_n();
133  }
134 
135  // now do the heavy communication
136  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tl, 0 );
137 
138  // after communication, on each processor we should have tuples coming in
139  // still need to order by global dofs; then rearrange input vectors
140  moab::TupleList::buffer sort_buffer;
141  sort_buffer.buffer_init( tl.get_n() );
142  tl.sort( 1, &sort_buffer );
143  // count how many are unique, and collapse
144  int nb_unique = 1;
145  for( unsigned i = 0; i < tl.get_n() - 1; i++ )
146  {
147  if( tl.vi_wr[3 * i + 1] != tl.vi_wr[3 * i + 4] ) nb_unique++;
148  }
149  vecFaceArea.Allocate( nb_unique );
150  dCenterLon.Allocate( nb_unique );
151  dCenterLat.Allocate( nb_unique );
152  dVertexLon.Allocate( nb_unique, nv );
153  dVertexLat.Allocate( nb_unique, nv );
154  masks.resize( nb_unique );
155  int current_size = 1;
156  vecFaceArea[0] = tl.vr_wr[0];
157  dCenterLon[0] = tl.vr_wr[1];
158  dCenterLat[0] = tl.vr_wr[2];
159  masks[0] = tl.vi_wr[2];
160  for( int j = 0; j < nv; j++ )
161  {
162  dVertexLon[0][j] = tl.vr_wr[3 + j];
163  dVertexLat[0][j] = tl.vr_wr[3 + nv + j];
164  }
165  for( unsigned i = 0; i < tl.get_n() - 1; i++ )
166  {
167  int i1 = i + 1;
168  if( tl.vi_wr[3 * i + 1] != tl.vi_wr[3 * i + 4] )
169  {
170  vecFaceArea[current_size] = tl.vr_wr[i1 * numr];
171  dCenterLon[current_size] = tl.vr_wr[i1 * numr + 1];
172  dCenterLat[current_size] = tl.vr_wr[i1 * numr + 2];
173  for( int j = 0; j < nv; j++ )
174  {
175  dVertexLon[current_size][j] = tl.vr_wr[i1 * numr + 3 + j];
176  dVertexLat[current_size][j] = tl.vr_wr[i1 * numr + 3 + nv + j];
177  }
178  masks[current_size] = tl.vi_wr[3 * i1 + 2];
179  current_size++;
180  }
181  else
182  {
183  vecFaceArea[current_size - 1] += tl.vr_wr[i1 * numr]; // accumulate areas; will come here only for cgll ?
184  }
185  }
186 
187  N = current_size; // or nb_unique, should be the same
188  return 0;
189 }
190 #endif
191 
192 ///////////////////////////////////////////////////////////////////////////////
193 
195  const std::map< std::string, std::string >& attrMap )
196 {
197  size_t lastindex = strFilename.find_last_of( "." );
198  std::string extension = strFilename.substr( lastindex + 1, strFilename.size() );
199 
200  // Write the map file to disk in parallel
201  if( extension == "nc" )
202  {
203 #if !defined( MOAB_HAVE_NETCDFPAR )
204  // Without parallel NetCDF, the SCRIP writer cannot handle multiple MPI ranks
205  // writing to the same file. Fall back to the HDF5 format with a .h5m extension
206  // and then the map can be converted to SCRIP format offline if needed.
207  if( this->size > 1 )
208  {
209  std::string h5mFilename = strFilename.substr( 0, lastindex ) + ".h5m";
210  if( !this->rank )
211  {
212  std::cout << " [WriteParallelMap]: Parallel NetCDF not available; writing map to "
213  << "HDF5 format (" << h5mFilename << ") instead of SCRIP (.nc)\n";
214  }
215  MB_CHK_ERR( this->WriteHDF5MapFile( h5mFilename.c_str() ) );
216  return moab::MB_SUCCESS;
217  }
218 #endif
219  /* Invoke the actual call to write the parallel map to disk in SCRIP format */
220  MB_CHK_ERR( this->WriteSCRIPMapFile( strFilename.c_str(), attrMap ) );
221  }
222  else
223  {
224  /* Write to the parallel H5M format */
225  MB_CHK_ERR( this->WriteHDF5MapFile( strFilename.c_str() ) );
226  }
227 
228  return moab::MB_SUCCESS;
229 }
230 
231 ///////////////////////////////////////////////////////////////////////////////
232 
234  const std::map< std::string, std::string >& attrMap )
235 {
236  NcError error( NcError::silent_nonfatal );
237 
238 #ifdef MOAB_HAVE_NETCDFPAR
239  bool is_independent = true;
240  ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strFilename.c_str(), NcFile::Replace, NcFile::Netcdf4 );
241  // ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strFilename.c_str(), NcmpiFile::replace, NcmpiFile::classic5 );
242 #else
243  NcFile ncMap( strFilename.c_str(), NcFile::Replace );
244 #endif
245 
246  if( !ncMap.is_valid() )
247  {
248  _EXCEPTION1( "Unable to open output map file \"%s\"", strFilename.c_str() );
249  }
250 
251  // Attributes
252  // ncMap.add_att( "Title", "MOAB-TempestRemap Online Regridding Weight Generator" );
253  auto it = attrMap.begin();
254  while( it != attrMap.end() )
255  {
256  // set the map attributes
257  ncMap.add_att( it->first.c_str(), it->second.c_str() );
258  // increment iterator
259  it++;
260  }
261 
262  /**
263  * Need to get the global maximum of number of vertices per element
264  * Key issue is that when calling InitializeCoordinatesFromMeshFV, the allocation for
265  *dVertexLon/dVertexLat are made based on the maximum vertices in the current process. However,
266  *when writing this out, other processes may have a different size for the same array. This is
267  *hence a mess to consolidate in h5mtoscrip eventually.
268  **/
269 
270  /* Let us compute all relevant data for the current original source mesh on the process */
271  DataArray1D< double > vecSourceFaceArea, vecTargetFaceArea;
272  DataArray1D< double > dSourceCenterLon, dSourceCenterLat, dTargetCenterLon, dTargetCenterLat;
273  DataArray2D< double > dSourceVertexLon, dSourceVertexLat, dTargetVertexLon, dTargetVertexLat;
274  if( m_srcDiscType == DiscretizationType_FV || m_srcDiscType == DiscretizationType_PCLOUD )
275  {
276  this->InitializeCoordinatesFromMeshFV(
277  *m_meshInput, dSourceCenterLon, dSourceCenterLat, dSourceVertexLon, dSourceVertexLat,
278  ( this->m_remapper->m_source_type == moab::TempestRemapper::RLL ), /* fLatLon = false */
279  m_remapper->max_source_edges );
280 
281  vecSourceFaceArea.Allocate( m_meshInput->vecFaceArea.GetRows() );
282  for( unsigned i = 0; i < m_meshInput->vecFaceArea.GetRows(); ++i )
283  vecSourceFaceArea[i] = m_meshInput->vecFaceArea[i];
284  }
285  else
286  {
287  DataArray3D< double > dataGLLJacobianSrc;
288  this->InitializeCoordinatesFromMeshFE( *m_meshInput, m_nDofsPEl_Src, dataGLLNodesSrc, dSourceCenterLon,
289  dSourceCenterLat, dSourceVertexLon, dSourceVertexLat );
290 
291  // Generate the continuous Jacobian for input mesh
292  GenerateMetaData( *m_meshInput, m_nDofsPEl_Src, false /* fBubble */, dataGLLNodesSrc, dataGLLJacobianSrc );
293 
294  if( m_srcDiscType == DiscretizationType_CGLL )
295  {
296  GenerateUniqueJacobian( dataGLLNodesSrc, dataGLLJacobianSrc, vecSourceFaceArea );
297  }
298  else
299  {
300  GenerateDiscontinuousJacobian( dataGLLJacobianSrc, vecSourceFaceArea );
301  }
302  }
303 
304  if( m_destDiscType == DiscretizationType_FV || m_destDiscType == DiscretizationType_PCLOUD )
305  {
306  this->InitializeCoordinatesFromMeshFV(
307  *m_meshOutput, dTargetCenterLon, dTargetCenterLat, dTargetVertexLon, dTargetVertexLat,
308  ( this->m_remapper->m_target_type == moab::TempestRemapper::RLL ), /* fLatLon = false */
309  m_remapper->max_target_edges );
310 
311  vecTargetFaceArea.Allocate( m_meshOutput->vecFaceArea.GetRows() );
312  for( unsigned i = 0; i < m_meshOutput->vecFaceArea.GetRows(); ++i )
313  {
314  vecTargetFaceArea[i] = m_meshOutput->vecFaceArea[i];
315  }
316  }
317  else
318  {
319  DataArray3D< double > dataGLLJacobianDest;
320  this->InitializeCoordinatesFromMeshFE( *m_meshOutput, m_nDofsPEl_Dest, dataGLLNodesDest, dTargetCenterLon,
321  dTargetCenterLat, dTargetVertexLon, dTargetVertexLat );
322 
323  // Generate the continuous Jacobian for input mesh
324  GenerateMetaData( *m_meshOutput, m_nDofsPEl_Dest, false /* fBubble */, dataGLLNodesDest, dataGLLJacobianDest );
325 
326  if( m_destDiscType == DiscretizationType_CGLL )
327  {
328  GenerateUniqueJacobian( dataGLLNodesDest, dataGLLJacobianDest, vecTargetFaceArea );
329  }
330  else
331  {
332  GenerateDiscontinuousJacobian( dataGLLJacobianDest, vecTargetFaceArea );
333  }
334  }
335 
336  // Map dimensions
337  unsigned nA = ( vecSourceFaceArea.GetRows() );
338  unsigned nB = ( vecTargetFaceArea.GetRows() );
339 
340  std::vector< int > masksA, masksB;
341  MB_CHK_SET_ERR( m_remapper->GetIMasks( moab::Remapper::SourceMesh, masksA ), "Trouble getting masks for source" );
342  MB_CHK_SET_ERR( m_remapper->GetIMasks( moab::Remapper::TargetMesh, masksB ), "Trouble getting masks for target" );
343 
344  // Number of nodes per Face
345  int nSourceNodesPerFace = dSourceVertexLon.GetColumns();
346  int nTargetNodesPerFace = dTargetVertexLon.GetColumns();
347 
348  // if source or target cells have triangles at poles, center of those triangles need to come from
349  // the original quad, not from center in 3d, converted to 2d again
350  // start copy OnlineMap.cpp tempestremap
351  // right now, do this only for source mesh; copy the logic for target mesh
352  for( unsigned i = 0; i < nA; i++ )
353  {
354  const Face& face = m_meshInput->faces[i];
355 
356  int nNodes = face.edges.size();
357  int indexNodeAtPole = -1;
358  if( 3 == nNodes ) // check if one node at the poles
359  {
360  for( int j = 0; j < nNodes; j++ )
361  if( fabs( fabs( dSourceVertexLat[i][j] ) - 90.0 ) < 1.0e-12 )
362  {
363  indexNodeAtPole = j;
364  break;
365  }
366  }
367  if( indexNodeAtPole < 0 ) continue; // continue i loop, do nothing
368  // recompute center of cell, from 3d data; add one 2 nodes at pole, and average
369  int nodeAtPole = face[indexNodeAtPole]; // use the overloaded operator
370  Node nodePole = m_meshInput->nodes[nodeAtPole];
371  Node newCenter = nodePole * 2;
372  for( int j = 1; j < nNodes; j++ )
373  {
374  int indexi = ( indexNodeAtPole + j ) % nNodes; // nNodes is 3 !
375  const Node& node = m_meshInput->nodes[face[indexi]];
376  newCenter = newCenter + node;
377  }
378  newCenter = newCenter * 0.25;
379  newCenter = newCenter.Normalized();
380 
381 #ifdef VERBOSE
382  double iniLon = dSourceCenterLon[i], iniLat = dSourceCenterLat[i];
383 #endif
384  // dSourceCenterLon, dSourceCenterLat
385  XYZtoRLL_Deg( newCenter.x, newCenter.y, newCenter.z, dSourceCenterLon[i], dSourceCenterLat[i] );
386 #ifdef VERBOSE
387  std::cout << " modify center of triangle from " << iniLon << " " << iniLat << " to " << dSourceCenterLon[i]
388  << " " << dSourceCenterLat[i] << "\n";
389 #endif
390  }
391 
392  // first move data if in parallel
393 #if defined( MOAB_HAVE_MPI )
394  int max_row_dof, max_col_dof; // output; arrays will be re-distributed in chunks [maxdof/size]
395  // if (size > 1)
396  {
397  int ierr = rearrange_arrays_by_dofs( srccol_gdofmap, vecSourceFaceArea, dSourceCenterLon, dSourceCenterLat,
398  dSourceVertexLon, dSourceVertexLat, masksA, nA, nSourceNodesPerFace,
399  max_col_dof ); // now nA will be close to maxdof/size
400  if( ierr != 0 )
401  {
402  _EXCEPTION1( "Unable to arrange source data %d ", nA );
403  }
404  // rearrange target data: (nB)
405  //
406  ierr = rearrange_arrays_by_dofs( row_gdofmap, vecTargetFaceArea, dTargetCenterLon, dTargetCenterLat,
407  dTargetVertexLon, dTargetVertexLat, masksB, nB, nTargetNodesPerFace,
408  max_row_dof ); // now nA will be close to maxdof/size
409  if( ierr != 0 )
410  {
411  _EXCEPTION1( "Unable to arrange target data %d ", nB );
412  }
413  }
414 #endif
415 
416  // Number of non-zeros in the remap matrix operator
417  int nS = m_weightMatrix.nonZeros();
418 
419 #if defined( MOAB_HAVE_MPI ) && defined( MOAB_HAVE_NETCDFPAR )
420  int locbuf[5] = { (int)nA, (int)nB, nS, nSourceNodesPerFace, nTargetNodesPerFace };
421  int offbuf[3] = { 0, 0, 0 };
422  int globuf[5] = { 0, 0, 0, 0, 0 };
423  MPI_Scan( locbuf, offbuf, 3, MPI_INT, MPI_SUM, m_pcomm->comm() );
424  MPI_Allreduce( locbuf, globuf, 3, MPI_INT, MPI_SUM, m_pcomm->comm() );
425  MPI_Allreduce( &locbuf[3], &globuf[3], 2, MPI_INT, MPI_MAX, m_pcomm->comm() );
426 
427  // MPI_Scan is inclusive of data in current rank; modify accordingly.
428  offbuf[0] -= nA;
429  offbuf[1] -= nB;
430  offbuf[2] -= nS;
431 
432 #else
433  int offbuf[3] = { 0, 0, 0 };
434  int globuf[5] = { (int)nA, (int)nB, nS, nSourceNodesPerFace, nTargetNodesPerFace };
435 #endif
436 
437  std::vector< std::string > srcdimNames, tgtdimNames;
438  std::vector< int > srcdimSizes, tgtdimSizes;
439  {
440  if( m_remapper->m_source_type == moab::TempestRemapper::RLL && m_remapper->m_source_metadata.size() )
441  {
442  srcdimNames.push_back( "lat" );
443  srcdimNames.push_back( "lon" );
444  srcdimSizes.resize( 2, 0 );
445  srcdimSizes[0] = m_remapper->m_source_metadata[0];
446  srcdimSizes[1] = m_remapper->m_source_metadata[1];
447  }
448  else
449  {
450  srcdimNames.push_back( "num_elem" );
451  srcdimSizes.push_back( globuf[0] );
452  }
453 
454  if( m_remapper->m_target_type == moab::TempestRemapper::RLL && m_remapper->m_target_metadata.size() )
455  {
456  tgtdimNames.push_back( "lat" );
457  tgtdimNames.push_back( "lon" );
458  tgtdimSizes.resize( 2, 0 );
459  tgtdimSizes[0] = m_remapper->m_target_metadata[0];
460  tgtdimSizes[1] = m_remapper->m_target_metadata[1];
461  }
462  else
463  {
464  tgtdimNames.push_back( "num_elem" );
465  tgtdimSizes.push_back( globuf[1] );
466  }
467  }
468 
469  // Write output dimensions entries
470  unsigned nSrcGridDims = ( srcdimSizes.size() );
471  unsigned nDstGridDims = ( tgtdimSizes.size() );
472 
473  NcDim* dimSrcGridRank = ncMap.add_dim( "src_grid_rank", nSrcGridDims );
474  NcDim* dimDstGridRank = ncMap.add_dim( "dst_grid_rank", nDstGridDims );
475 
476  NcVar* varSrcGridDims = ncMap.add_var( "src_grid_dims", ncInt, dimSrcGridRank );
477  NcVar* varDstGridDims = ncMap.add_var( "dst_grid_dims", ncInt, dimDstGridRank );
478 
479 #ifdef MOAB_HAVE_NETCDFPAR
480  ncMap.enable_var_par_access( varSrcGridDims, is_independent );
481  ncMap.enable_var_par_access( varDstGridDims, is_independent );
482 #endif
483 
484  // write dimension names
485  {
486  char szDim[64];
487  for( unsigned i = 0; i < srcdimSizes.size(); i++ )
488  {
489  varSrcGridDims->set_cur( nSrcGridDims - i - 1 );
490  varSrcGridDims->put( &( srcdimSizes[nSrcGridDims - i - 1] ), 1 );
491  }
492 
493  for( unsigned i = 0; i < srcdimSizes.size(); i++ )
494  {
495  snprintf( szDim, 64, "name%u", i );
496  varSrcGridDims->add_att( szDim, srcdimNames[nSrcGridDims - i - 1].c_str() );
497  }
498 
499  for( unsigned i = 0; i < tgtdimSizes.size(); i++ )
500  {
501  varDstGridDims->set_cur( nDstGridDims - i - 1 );
502  varDstGridDims->put( &( tgtdimSizes[nDstGridDims - i - 1] ), 1 );
503  }
504 
505  for( unsigned i = 0; i < tgtdimSizes.size(); i++ )
506  {
507  snprintf( szDim, 64, "name%u", i );
508  varDstGridDims->add_att( szDim, tgtdimNames[nDstGridDims - i - 1].c_str() );
509  }
510  }
511 
512  // Source and Target mesh resolutions
513  NcDim* dimNA = ncMap.add_dim( "n_a", globuf[0] );
514  NcDim* dimNB = ncMap.add_dim( "n_b", globuf[1] );
515 
516  // Number of nodes per Face
517  NcDim* dimNVA = ncMap.add_dim( "nv_a", globuf[3] );
518  NcDim* dimNVB = ncMap.add_dim( "nv_b", globuf[4] );
519 
520  // Write coordinates
521  NcVar* varYCA = ncMap.add_var( "yc_a", ncDouble, dimNA );
522  NcVar* varYCB = ncMap.add_var( "yc_b", ncDouble, dimNB );
523 
524  NcVar* varXCA = ncMap.add_var( "xc_a", ncDouble, dimNA );
525  NcVar* varXCB = ncMap.add_var( "xc_b", ncDouble, dimNB );
526 
527  NcVar* varYVA = ncMap.add_var( "yv_a", ncDouble, dimNA, dimNVA );
528  NcVar* varYVB = ncMap.add_var( "yv_b", ncDouble, dimNB, dimNVB );
529 
530  NcVar* varXVA = ncMap.add_var( "xv_a", ncDouble, dimNA, dimNVA );
531  NcVar* varXVB = ncMap.add_var( "xv_b", ncDouble, dimNB, dimNVB );
532 
533  // Write masks
534  NcVar* varMaskA = ncMap.add_var( "mask_a", ncInt, dimNA );
535  NcVar* varMaskB = ncMap.add_var( "mask_b", ncInt, dimNB );
536 
537 #ifdef MOAB_HAVE_NETCDFPAR
538  ncMap.enable_var_par_access( varYCA, is_independent );
539  ncMap.enable_var_par_access( varYCB, is_independent );
540  ncMap.enable_var_par_access( varXCA, is_independent );
541  ncMap.enable_var_par_access( varXCB, is_independent );
542  ncMap.enable_var_par_access( varYVA, is_independent );
543  ncMap.enable_var_par_access( varYVB, is_independent );
544  ncMap.enable_var_par_access( varXVA, is_independent );
545  ncMap.enable_var_par_access( varXVB, is_independent );
546  ncMap.enable_var_par_access( varMaskA, is_independent );
547  ncMap.enable_var_par_access( varMaskB, is_independent );
548 #endif
549 
550  varYCA->add_att( "units", "degrees" );
551  varYCB->add_att( "units", "degrees" );
552 
553  varXCA->add_att( "units", "degrees" );
554  varXCB->add_att( "units", "degrees" );
555 
556  varYVA->add_att( "units", "degrees" );
557  varYVB->add_att( "units", "degrees" );
558 
559  varXVA->add_att( "units", "degrees" );
560  varXVB->add_att( "units", "degrees" );
561 
562  // Verify dimensionality
563  if( dSourceCenterLon.GetRows() != nA )
564  {
565  _EXCEPTIONT( "Mismatch between dSourceCenterLon and nA" );
566  }
567  if( dSourceCenterLat.GetRows() != nA )
568  {
569  _EXCEPTIONT( "Mismatch between dSourceCenterLat and nA" );
570  }
571  if( dTargetCenterLon.GetRows() != nB )
572  {
573  _EXCEPTIONT( "Mismatch between dTargetCenterLon and nB" );
574  }
575  if( dTargetCenterLat.GetRows() != nB )
576  {
577  _EXCEPTIONT( "Mismatch between dTargetCenterLat and nB" );
578  }
579  if( dSourceVertexLon.GetRows() != nA )
580  {
581  _EXCEPTIONT( "Mismatch between dSourceVertexLon and nA" );
582  }
583  if( dSourceVertexLat.GetRows() != nA )
584  {
585  _EXCEPTIONT( "Mismatch between dSourceVertexLat and nA" );
586  }
587  if( dTargetVertexLon.GetRows() != nB )
588  {
589  _EXCEPTIONT( "Mismatch between dTargetVertexLon and nB" );
590  }
591  if( dTargetVertexLat.GetRows() != nB )
592  {
593  _EXCEPTIONT( "Mismatch between dTargetVertexLat and nB" );
594  }
595 
596  varYCA->set_cur( (long)offbuf[0] );
597  varYCA->put( &( dSourceCenterLat[0] ), nA );
598  varYCB->set_cur( (long)offbuf[1] );
599  varYCB->put( &( dTargetCenterLat[0] ), nB );
600 
601  varXCA->set_cur( (long)offbuf[0] );
602  varXCA->put( &( dSourceCenterLon[0] ), nA );
603  varXCB->set_cur( (long)offbuf[1] );
604  varXCB->put( &( dTargetCenterLon[0] ), nB );
605 
606  varYVA->set_cur( (long)offbuf[0] );
607  varYVA->put( &( dSourceVertexLat[0][0] ), nA, nSourceNodesPerFace );
608  varYVB->set_cur( (long)offbuf[1] );
609  varYVB->put( &( dTargetVertexLat[0][0] ), nB, nTargetNodesPerFace );
610 
611  varXVA->set_cur( (long)offbuf[0] );
612  varXVA->put( &( dSourceVertexLon[0][0] ), nA, nSourceNodesPerFace );
613  varXVB->set_cur( (long)offbuf[1] );
614  varXVB->put( &( dTargetVertexLon[0][0] ), nB, nTargetNodesPerFace );
615 
616  varMaskA->set_cur( (long)offbuf[0] );
617  varMaskA->put( &( masksA[0] ), nA );
618  varMaskB->set_cur( (long)offbuf[1] );
619  varMaskB->put( &( masksB[0] ), nB );
620 
621  // Write areas
622  NcVar* varAreaA = ncMap.add_var( "area_a", ncDouble, dimNA );
623 #ifdef MOAB_HAVE_NETCDFPAR
624  ncMap.enable_var_par_access( varAreaA, is_independent );
625 #endif
626  varAreaA->set_cur( (long)offbuf[0] );
627  varAreaA->put( &( vecSourceFaceArea[0] ), nA );
628 
629  NcVar* varAreaB = ncMap.add_var( "area_b", ncDouble, dimNB );
630 #ifdef MOAB_HAVE_NETCDFPAR
631  ncMap.enable_var_par_access( varAreaB, is_independent );
632 #endif
633  varAreaB->set_cur( (long)offbuf[1] );
634  varAreaB->put( &( vecTargetFaceArea[0] ), nB );
635 
636  // Write SparseMatrix entries
637  DataArray1D< int > vecRow( nS );
638  DataArray1D< int > vecCol( nS );
639  DataArray1D< double > vecS( nS );
640  DataArray1D< double > dFracA( nA );
641  DataArray1D< double > dFracB( nB );
642 
643  moab::TupleList tlValRow, tlValCol;
644  unsigned numr = 1; //
645  // value has to be sent to processor row/nB for for fracA and col/nA for fracB
646  // vecTargetArea (indexRow ) has to be sent for fracA (index col?)
647  // vecTargetFaceArea will have to be sent to col index, with its index !
648  tlValRow.initialize( 2, 0, 0, numr, nS ); // to proc(row), global row , value
649  tlValCol.initialize( 3, 0, 0, numr, nS ); // to proc(col), global row / col, value
650  tlValRow.enableWriteAccess();
651  tlValCol.enableWriteAccess();
652  /*
653  dFracA[ col ] += val / vecSourceFaceArea[ col ] * vecTargetFaceArea[ row ];
654  dFracB[ row ] += val ;
655  */
656  int offset = 0;
657 #if defined( MOAB_HAVE_MPI )
658  int nAbase = ( max_col_dof + 1 ) / size; // it is nA, except last rank ( == size - 1 )
659  int nBbase = ( max_row_dof + 1 ) / size; // it is nB, except last rank ( == size - 1 )
660 #endif
661  for( int i = 0; i < m_weightMatrix.outerSize(); ++i )
662  {
663  for( WeightMatrix::InnerIterator it( m_weightMatrix, i ); it; ++it )
664  {
665  vecRow[offset] = 1 + this->GetRowGlobalDoF( it.row() ); // row index
666  vecCol[offset] = 1 + this->GetColGlobalDoF( it.col() ); // col index
667  vecS[offset] = it.value(); // value
668 
669 #if defined( MOAB_HAVE_MPI )
670  {
671  // value M(row, col) will contribute to procRow and procCol values for fracA and fracB
672  int procRow = ( vecRow[offset] - 1 ) / nBbase;
673  if( procRow >= size ) procRow = size - 1;
674  int procCol = ( vecCol[offset] - 1 ) / nAbase;
675  if( procCol >= size ) procCol = size - 1;
676  int nrInd = tlValRow.get_n();
677  tlValRow.vi_wr[2 * nrInd] = procRow;
678  tlValRow.vi_wr[2 * nrInd + 1] = vecRow[offset] - 1;
679  tlValRow.vr_wr[nrInd] = vecS[offset];
680  tlValRow.inc_n();
681  int ncInd = tlValCol.get_n();
682  tlValCol.vi_wr[3 * ncInd] = procCol;
683  tlValCol.vi_wr[3 * ncInd + 1] = vecRow[offset] - 1;
684  tlValCol.vi_wr[3 * ncInd + 2] = vecCol[offset] - 1; // this is column
685  tlValCol.vr_wr[ncInd] = vecS[offset];
686  tlValCol.inc_n();
687  }
688 
689 #endif
690  offset++;
691  }
692  }
693 #if defined( MOAB_HAVE_MPI )
694  // need to send values for their row and col processors, to compute fractions there
695  // now do the heavy communication
696  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tlValCol, 0 );
697  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tlValRow, 0 );
698 
699  // we have now, for example, dFracB[ row ] += val ;
700  // so we know that on current task, we received tlValRow
701  // reminder dFracA[ col ] += val / vecSourceFaceArea[ col ] * vecTargetFaceArea[ row ];
702  // dFracB[ row ] += val ;
703  for( unsigned i = 0; i < tlValRow.get_n(); i++ )
704  {
705  // int fromProc = tlValRow.vi_wr[2 * i];
706  int gRowInd = tlValRow.vi_wr[2 * i + 1];
707  int localIndexRow = gRowInd - nBbase * rank; // modulo nBbase rank is from 0 to size - 1;
708  double wgt = tlValRow.vr_wr[i];
709  assert( localIndexRow >= 0 );
710  assert( nB - localIndexRow > 0 );
711  dFracB[localIndexRow] += wgt;
712  }
713  // to compute dFracA we need vecTargetFaceArea[ row ]; we know the row, and we can get the proc we need it from
714 
715  std::set< int > neededRows;
716  for( unsigned i = 0; i < tlValCol.get_n(); i++ )
717  {
718  int rRowInd = tlValCol.vi_wr[3 * i + 1];
719  neededRows.insert( rRowInd );
720  // we need vecTargetFaceAreaGlobal[ rRowInd ]; this exists on proc procRow
721  }
722  moab::TupleList tgtAreaReq;
723  tgtAreaReq.initialize( 2, 0, 0, 0, neededRows.size() );
724  tgtAreaReq.enableWriteAccess();
725  for( std::set< int >::iterator sit = neededRows.begin(); sit != neededRows.end(); ++sit )
726  {
727  int neededRow = *sit;
728  int procRow = neededRow / nBbase;
729  if( procRow >= size ) procRow = size - 1;
730  int nr = tgtAreaReq.get_n();
731  tgtAreaReq.vi_wr[2 * nr] = procRow;
732  tgtAreaReq.vi_wr[2 * nr + 1] = neededRow;
733  tgtAreaReq.inc_n();
734  }
735 
736  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tgtAreaReq, 0 );
737  // we need to send back the tgtArea corresponding to row
738  moab::TupleList tgtAreaInfo; // load it with tgtArea at row
739  tgtAreaInfo.initialize( 2, 0, 0, 1, tgtAreaReq.get_n() );
740  tgtAreaInfo.enableWriteAccess();
741  for( unsigned i = 0; i < tgtAreaReq.get_n(); i++ )
742  {
743  int from_proc = tgtAreaReq.vi_wr[2 * i];
744  int row = tgtAreaReq.vi_wr[2 * i + 1];
745  int locaIndexRow = row - rank * nBbase;
746  double areaToSend = vecTargetFaceArea[locaIndexRow];
747  // int remoteIndex = tgtAreaReq.vi_wr[3*i + 2] ;
748 
749  tgtAreaInfo.vi_wr[2 * i] = from_proc; // send back requested info
750  tgtAreaInfo.vi_wr[2 * i + 1] = row;
751  tgtAreaInfo.vr_wr[i] = areaToSend; // this will be tgt area at row
752  tgtAreaInfo.inc_n();
753  }
754  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tgtAreaInfo, 0 );
755 
756  std::map< int, double > areaAtRow;
757  for( unsigned i = 0; i < tgtAreaInfo.get_n(); i++ )
758  {
759  // we have received from proc, value for row !
760  int row = tgtAreaInfo.vi_wr[2 * i + 1];
761  areaAtRow[row] = tgtAreaInfo.vr_wr[i];
762  }
763 
764  // we have now for rows the
765  // it is ordered by index, so:
766  // now compute reminder dFracA[ col ] += val / vecSourceFaceArea[ col ] * vecTargetFaceArea[ row ];
767  // tgtAreaInfo will have at index i the area we need (from row)
768  // there should be an easier way :(
769  for( unsigned i = 0; i < tlValCol.get_n(); i++ )
770  {
771  int rRowInd = tlValCol.vi_wr[3 * i + 1];
772  int colInd = tlValCol.vi_wr[3 * i + 2];
773  double val = tlValCol.vr_wr[i];
774  int localColInd = colInd - rank * nAbase; // < local nA
775  // we need vecTargetFaceAreaGlobal[ rRowInd ]; this exists on proc procRow
776  auto itMap = areaAtRow.find( rRowInd ); // it should be different from end
777  if( itMap != areaAtRow.end() )
778  {
779  double areaRow = itMap->second; // we fished a lot for this !
780  dFracA[localColInd] += val / vecSourceFaceArea[localColInd] * areaRow;
781  }
782  }
783 
784 #endif
785  // Load in data
786  NcDim* dimNS = ncMap.add_dim( "n_s", globuf[2] );
787 
788  NcVar* varRow = ncMap.add_var( "row", ncInt, dimNS );
789  NcVar* varCol = ncMap.add_var( "col", ncInt, dimNS );
790  NcVar* varS = ncMap.add_var( "S", ncDouble, dimNS );
791 #ifdef MOAB_HAVE_NETCDFPAR
792  ncMap.enable_var_par_access( varRow, is_independent );
793  ncMap.enable_var_par_access( varCol, is_independent );
794  ncMap.enable_var_par_access( varS, is_independent );
795 #endif
796 
797  varRow->set_cur( (long)offbuf[2] );
798  varRow->put( vecRow, nS );
799 
800  varCol->set_cur( (long)offbuf[2] );
801  varCol->put( vecCol, nS );
802 
803  varS->set_cur( (long)offbuf[2] );
804  varS->put( &( vecS[0] ), nS );
805 
806  // Calculate and write fractional coverage arrays
807  NcVar* varFracA = ncMap.add_var( "frac_a", ncDouble, dimNA );
808 #ifdef MOAB_HAVE_NETCDFPAR
809  ncMap.enable_var_par_access( varFracA, is_independent );
810 #endif
811  varFracA->add_att( "name", "fraction of target coverage of source dof" );
812  varFracA->add_att( "units", "unitless" );
813  varFracA->set_cur( (long)offbuf[0] );
814  varFracA->put( &( dFracA[0] ), nA );
815 
816  NcVar* varFracB = ncMap.add_var( "frac_b", ncDouble, dimNB );
817 #ifdef MOAB_HAVE_NETCDFPAR
818  ncMap.enable_var_par_access( varFracB, is_independent );
819 #endif
820  varFracB->add_att( "name", "fraction of source coverage of target dof" );
821  varFracB->add_att( "units", "unitless" );
822  varFracB->set_cur( (long)offbuf[1] );
823  varFracB->put( &( dFracB[0] ), nB );
824 
825  // Add global attributes
826  // std::map<std::string, std::string>::const_iterator iterAttributes =
827  // mapAttributes.begin();
828  // for (; iterAttributes != mapAttributes.end(); iterAttributes++) {
829  // ncMap.add_att(
830  // iterAttributes->first.c_str(),
831  // iterAttributes->second.c_str());
832  // }
833 
834  ncMap.close();
835 
836 #ifdef VERBOSE
837  serializeSparseMatrix( m_weightMatrix, "map_operator_" + std::to_string( rank ) + ".txt" );
838 #endif
839  return moab::MB_SUCCESS;
840 }
841 
842 ///////////////////////////////////////////////////////////////////////////////
843 
845 {
846  /**
847  * Need to get the global maximum of number of vertices per element
848  * Key issue is that when calling InitializeCoordinatesFromMeshFV, the allocation for
849  *dVertexLon/dVertexLat are made based on the maximum vertices in the current process. However,
850  *when writing this out, other processes may have a different size for the same array. This is
851  *hence a mess to consolidate in h5mtoscrip eventually.
852  **/
853 
854  /* Let us compute all relevant data for the current original source mesh on the process */
855  DataArray1D< double > vecSourceFaceArea, vecTargetFaceArea;
856  DataArray1D< double > dSourceCenterLon, dSourceCenterLat, dTargetCenterLon, dTargetCenterLat;
857  DataArray2D< double > dSourceVertexLon, dSourceVertexLat, dTargetVertexLon, dTargetVertexLat;
858  if( m_srcDiscType == DiscretizationType_FV || m_srcDiscType == DiscretizationType_PCLOUD )
859  {
860  this->InitializeCoordinatesFromMeshFV(
861  *m_meshInput, dSourceCenterLon, dSourceCenterLat, dSourceVertexLon, dSourceVertexLat,
862  ( this->m_remapper->m_source_type == moab::TempestRemapper::RLL ) /* fLatLon = false */,
863  m_remapper->max_source_edges );
864 
865  vecSourceFaceArea.Allocate( m_meshInput->vecFaceArea.GetRows() );
866  for( unsigned i = 0; i < m_meshInput->vecFaceArea.GetRows(); ++i )
867  vecSourceFaceArea[i] = m_meshInput->vecFaceArea[i];
868  }
869  else
870  {
871  DataArray3D< double > dataGLLJacobianSrc;
872  this->InitializeCoordinatesFromMeshFE( *m_meshInput, m_nDofsPEl_Src, dataGLLNodesSrc, dSourceCenterLon,
873  dSourceCenterLat, dSourceVertexLon, dSourceVertexLat );
874 
875  // Generate the continuous Jacobian for input mesh
876  GenerateMetaData( *m_meshInput, m_nDofsPEl_Src, false /* fBubble */, dataGLLNodesSrc, dataGLLJacobianSrc );
877 
878  if( m_srcDiscType == DiscretizationType_CGLL )
879  {
880  GenerateUniqueJacobian( dataGLLNodesSrc, dataGLLJacobianSrc, vecSourceFaceArea );
881  }
882  else
883  {
884  GenerateDiscontinuousJacobian( dataGLLJacobianSrc, vecSourceFaceArea );
885  }
886  }
887 
888  if( m_destDiscType == DiscretizationType_FV || m_destDiscType == DiscretizationType_PCLOUD )
889  {
890  this->InitializeCoordinatesFromMeshFV(
891  *m_meshOutput, dTargetCenterLon, dTargetCenterLat, dTargetVertexLon, dTargetVertexLat,
892  ( this->m_remapper->m_target_type == moab::TempestRemapper::RLL ) /* fLatLon = false */,
893  m_remapper->max_target_edges );
894 
895  vecTargetFaceArea.Allocate( m_meshOutput->vecFaceArea.GetRows() );
896  for( unsigned i = 0; i < m_meshOutput->vecFaceArea.GetRows(); ++i )
897  vecTargetFaceArea[i] = m_meshOutput->vecFaceArea[i];
898  }
899  else
900  {
901  DataArray3D< double > dataGLLJacobianDest;
902  this->InitializeCoordinatesFromMeshFE( *m_meshOutput, m_nDofsPEl_Dest, dataGLLNodesDest, dTargetCenterLon,
903  dTargetCenterLat, dTargetVertexLon, dTargetVertexLat );
904 
905  // Generate the continuous Jacobian for input mesh
906  GenerateMetaData( *m_meshOutput, m_nDofsPEl_Dest, false /* fBubble */, dataGLLNodesDest, dataGLLJacobianDest );
907 
908  if( m_destDiscType == DiscretizationType_CGLL )
909  {
910  GenerateUniqueJacobian( dataGLLNodesDest, dataGLLJacobianDest, vecTargetFaceArea );
911  }
912  else
913  {
914  GenerateDiscontinuousJacobian( dataGLLJacobianDest, vecTargetFaceArea );
915  }
916  }
917 
918  moab::EntityHandle& m_meshOverlapSet = m_remapper->m_overlap_set;
919  int tot_src_ents = m_remapper->m_source_entities.size();
920  int tot_tgt_ents = m_remapper->m_target_entities.size();
921  int tot_src_size = dSourceCenterLon.GetRows();
922  int tot_tgt_size = m_dTargetCenterLon.GetRows();
923  int tot_vsrc_size = dSourceVertexLon.GetRows() * dSourceVertexLon.GetColumns();
924  int tot_vtgt_size = m_dTargetVertexLon.GetRows() * m_dTargetVertexLon.GetColumns();
925 
926  const int weightMatNNZ = m_weightMatrix.nonZeros();
927  moab::Tag tagMapMetaData, tagMapIndexRow, tagMapIndexCol, tagMapValues, srcEleIDs, tgtEleIDs;
928  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_DATA", 13, moab::MB_TYPE_INTEGER, tagMapMetaData,
930  "Retrieving tag handles failed" );
931  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_ROWS", weightMatNNZ, moab::MB_TYPE_INTEGER, tagMapIndexRow,
933  "Retrieving tag handles failed" );
934  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_COLS", weightMatNNZ, moab::MB_TYPE_INTEGER, tagMapIndexCol,
936  "Retrieving tag handles failed" );
937  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_VALS", weightMatNNZ, moab::MB_TYPE_DOUBLE, tagMapValues,
939  "Retrieving tag handles failed" );
940  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceGIDS", tot_src_size, moab::MB_TYPE_INTEGER, srcEleIDs,
942  "Retrieving tag handles failed" );
943  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetGIDS", tot_tgt_size, moab::MB_TYPE_INTEGER, tgtEleIDs,
945  "Retrieving tag handles failed" );
946  moab::Tag srcAreaValues, tgtAreaValues;
947  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceAreas", tot_src_size, moab::MB_TYPE_DOUBLE, srcAreaValues,
949  "Retrieving tag handles failed" );
950  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetAreas", tot_tgt_size, moab::MB_TYPE_DOUBLE, tgtAreaValues,
952  "Retrieving tag handles failed" );
953  moab::Tag tagSrcCoordsCLon, tagSrcCoordsCLat, tagTgtCoordsCLon, tagTgtCoordsCLat;
954  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordCenterLon", tot_src_size, moab::MB_TYPE_DOUBLE,
955  tagSrcCoordsCLon,
957  "Retrieving tag handles failed" );
958  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordCenterLat", tot_src_size, moab::MB_TYPE_DOUBLE,
959  tagSrcCoordsCLat,
961  "Retrieving tag handles failed" );
962  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordCenterLon", tot_tgt_size, moab::MB_TYPE_DOUBLE,
963  tagTgtCoordsCLon,
965  "Retrieving tag handles failed" );
966  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordCenterLat", tot_tgt_size, moab::MB_TYPE_DOUBLE,
967  tagTgtCoordsCLat,
969  "Retrieving tag handles failed" );
970  moab::Tag tagSrcCoordsVLon, tagSrcCoordsVLat, tagTgtCoordsVLon, tagTgtCoordsVLat;
971  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordVertexLon", tot_vsrc_size, moab::MB_TYPE_DOUBLE,
972  tagSrcCoordsVLon,
974  "Retrieving tag handles failed" );
975  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordVertexLat", tot_vsrc_size, moab::MB_TYPE_DOUBLE,
976  tagSrcCoordsVLat,
978  "Retrieving tag handles failed" );
979  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordVertexLon", tot_vtgt_size, moab::MB_TYPE_DOUBLE,
980  tagTgtCoordsVLon,
982  "Retrieving tag handles failed" );
983  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordVertexLat", tot_vtgt_size, moab::MB_TYPE_DOUBLE,
984  tagTgtCoordsVLat,
986  "Retrieving tag handles failed" );
987  moab::Tag srcMaskValues, tgtMaskValues;
988  if( m_iSourceMask.IsAttached() )
989  {
990  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceMask", m_iSourceMask.GetRows(), moab::MB_TYPE_INTEGER,
991  srcMaskValues,
993  "Retrieving tag handles failed" );
994  }
995  if( m_iTargetMask.IsAttached() )
996  {
997  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetMask", m_iTargetMask.GetRows(), moab::MB_TYPE_INTEGER,
998  tgtMaskValues,
1000  "Retrieving tag handles failed" );
1001  }
1002 
1003  std::vector< int > smatrowvals( weightMatNNZ ), smatcolvals( weightMatNNZ );
1004  std::vector< double > smatvals( weightMatNNZ );
1005  // const double* smatvals = m_weightMatrix.valuePtr();
1006  // Loop over the matrix entries and find the max global ID for rows and columns
1007  for( int k = 0, offset = 0; k < m_weightMatrix.outerSize(); ++k )
1008  {
1009  for( moab::TempestOnlineMap::WeightMatrix::InnerIterator it( m_weightMatrix, k ); it; ++it, ++offset )
1010  {
1011  smatrowvals[offset] = this->GetRowGlobalDoF( it.row() );
1012  smatcolvals[offset] = this->GetColGlobalDoF( it.col() );
1013  smatvals[offset] = it.value();
1014  }
1015  }
1016 
1017  /* Set the global IDs for the DoFs */
1018  ////
1019  // col_gdofmap [ col_ldofmap [ 0 : local_ndofs ] ] = GDOF
1020  // row_gdofmap [ row_ldofmap [ 0 : local_ndofs ] ] = GDOF
1021  ////
1022  int maxrow = 0, maxcol = 0;
1023  std::vector< int > src_global_dofs( tot_src_size ), tgt_global_dofs( tot_tgt_size );
1024  for( int i = 0; i < tot_src_size; ++i )
1025  {
1026  src_global_dofs[i] = srccol_gdofmap[i];
1027  maxcol = ( src_global_dofs[i] > maxcol ) ? src_global_dofs[i] : maxcol;
1028  }
1029 
1030  for( int i = 0; i < tot_tgt_size; ++i )
1031  {
1032  tgt_global_dofs[i] = row_gdofmap[i];
1033  maxrow = ( tgt_global_dofs[i] > maxrow ) ? tgt_global_dofs[i] : maxrow;
1034  }
1035 
1036  ///////////////////////////////////////////////////////////////////////////
1037  // The metadata in H5M file contains the following data:
1038  //
1039  // 1. n_a: Total source entities: (number of elements in source mesh)
1040  // 2. n_b: Total target entities: (number of elements in target mesh)
1041  // 3. nv_a: Max edge size of elements in source mesh
1042  // 4. nv_b: Max edge size of elements in target mesh
1043  // 5. maxrows: Number of rows in remap weight matrix
1044  // 6. maxcols: Number of cols in remap weight matrix
1045  // 7. nnz: Number of total nnz in sparse remap weight matrix
1046  // 8. np_a: The order of the field description on the source mesh: >= 1
1047  // 9. np_b: The order of the field description on the target mesh: >= 1
1048  // 10. method_a: The type of discretization for field on source mesh: [0 = FV, 1 = cGLL, 2 =
1049  // dGLL]
1050  // 11. method_b: The type of discretization for field on target mesh: [0 = FV, 1 = cGLL, 2 =
1051  // dGLL]
1052  // 12. conserved: Flag to specify whether the remap operator has conservation constraints: [0,
1053  // 1]
1054  // 13. monotonicity: Flags to specify whether the remap operator has monotonicity constraints:
1055  // [0, 1, 2]
1056  //
1057  ///////////////////////////////////////////////////////////////////////////
1058  int map_disc_details[6];
1059  map_disc_details[0] = m_nDofsPEl_Src;
1060  map_disc_details[1] = m_nDofsPEl_Dest;
1061  map_disc_details[2] = ( m_srcDiscType == DiscretizationType_FV || m_srcDiscType == DiscretizationType_PCLOUD
1062  ? 0
1063  : ( m_srcDiscType == DiscretizationType_CGLL ? 1 : 2 ) );
1064  map_disc_details[3] = ( m_destDiscType == DiscretizationType_FV || m_destDiscType == DiscretizationType_PCLOUD
1065  ? 0
1066  : ( m_destDiscType == DiscretizationType_CGLL ? 1 : 2 ) );
1067  map_disc_details[4] = ( m_bConserved ? 1 : 0 );
1068  map_disc_details[5] = m_iMonotonicity;
1069 
1070 #ifdef MOAB_HAVE_MPI
1071  int loc_smatmetadata[13] = { tot_src_ents,
1072  tot_tgt_ents,
1073  m_remapper->max_source_edges,
1074  m_remapper->max_target_edges,
1075  maxrow + 1,
1076  maxcol + 1,
1077  weightMatNNZ,
1078  map_disc_details[0],
1079  map_disc_details[1],
1080  map_disc_details[2],
1081  map_disc_details[3],
1082  map_disc_details[4],
1083  map_disc_details[5] };
1084  MB_CHK_SET_ERR( m_interface->tag_set_data( tagMapMetaData, &m_meshOverlapSet, 1, &loc_smatmetadata[0] ),
1085  "Setting local tag data failed" );
1086  int glb_smatmetadata[13] = { 0,
1087  0,
1088  0,
1089  0,
1090  0,
1091  0,
1092  0,
1093  map_disc_details[0],
1094  map_disc_details[1],
1095  map_disc_details[2],
1096  map_disc_details[3],
1097  map_disc_details[4],
1098  map_disc_details[5] };
1099  int loc_buf[7] = {
1100  tot_src_ents, tot_tgt_ents, weightMatNNZ, m_remapper->max_source_edges, m_remapper->max_target_edges,
1101  maxrow, maxcol };
1102  int glb_buf[4] = { 0, 0, 0, 0 };
1103  MPI_Reduce( &loc_buf[0], &glb_buf[0], 3, MPI_INT, MPI_SUM, 0, m_pcomm->comm() );
1104  glb_smatmetadata[0] = glb_buf[0];
1105  glb_smatmetadata[1] = glb_buf[1];
1106  glb_smatmetadata[6] = glb_buf[2];
1107  MPI_Reduce( &loc_buf[3], &glb_buf[0], 4, MPI_INT, MPI_MAX, 0, m_pcomm->comm() );
1108  glb_smatmetadata[2] = glb_buf[0];
1109  glb_smatmetadata[3] = glb_buf[1];
1110  glb_smatmetadata[4] = glb_buf[2];
1111  glb_smatmetadata[5] = glb_buf[3];
1112 #else
1113  int glb_smatmetadata[13] = { tot_src_ents,
1114  tot_tgt_ents,
1115  m_remapper->max_source_edges,
1116  m_remapper->max_target_edges,
1117  maxrow,
1118  maxcol,
1119  weightMatNNZ,
1120  map_disc_details[0],
1121  map_disc_details[1],
1122  map_disc_details[2],
1123  map_disc_details[3],
1124  map_disc_details[4],
1125  map_disc_details[5] };
1126 #endif
1127  // These values represent number of rows and columns. So should be 1-based.
1128  glb_smatmetadata[4]++;
1129  glb_smatmetadata[5]++;
1130 
1131  if( this->is_root )
1132  {
1133  std::cout << " " << this->rank << " Writing remap weights with size [" << glb_smatmetadata[4] << " X "
1134  << glb_smatmetadata[5] << "] and NNZ = " << glb_smatmetadata[6] << std::endl;
1135  EntityHandle root_set = 0;
1136  MB_CHK_SET_ERR( m_interface->tag_set_data( tagMapMetaData, &root_set, 1, &glb_smatmetadata[0] ),
1137  "Setting local tag data failed" );
1138  }
1139 
1140  int dsize;
1141  const int numval = weightMatNNZ;
1142  const void* smatrowvals_d = smatrowvals.data();
1143  const void* smatcolvals_d = smatcolvals.data();
1144  const void* smatvals_d = smatvals.data();
1145  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagMapIndexRow, &m_meshOverlapSet, 1, &smatrowvals_d, &numval ),
1146  "Setting local tag data failed" );
1147  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagMapIndexCol, &m_meshOverlapSet, 1, &smatcolvals_d, &numval ),
1148  "Setting local tag data failed" );
1149  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagMapValues, &m_meshOverlapSet, 1, &smatvals_d, &numval ),
1150  "Setting local tag data failed" );
1151 
1152  /* Set the global IDs for the DoFs */
1153  const void* srceleidvals_d = src_global_dofs.data();
1154  const void* tgteleidvals_d = tgt_global_dofs.data();
1155  dsize = src_global_dofs.size();
1156  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( srcEleIDs, &m_meshOverlapSet, 1, &srceleidvals_d, &dsize ),
1157  "Setting local tag data failed" );
1158  dsize = tgt_global_dofs.size();
1159  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tgtEleIDs, &m_meshOverlapSet, 1, &tgteleidvals_d, &dsize ),
1160  "Setting local tag data failed" );
1161 
1162  /* Set the source and target areas */
1163  const void* srcareavals_d = vecSourceFaceArea;
1164  const void* tgtareavals_d = vecTargetFaceArea;
1165  dsize = tot_src_size;
1166  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( srcAreaValues, &m_meshOverlapSet, 1, &srcareavals_d, &dsize ),
1167  "Setting local tag data failed" );
1168  dsize = tot_tgt_size;
1169  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tgtAreaValues, &m_meshOverlapSet, 1, &tgtareavals_d, &dsize ),
1170  "Setting local tag data failed" );
1171 
1172  /* Set the coordinates for source and target center vertices */
1173  const void* srccoordsclonvals_d = &dSourceCenterLon[0];
1174  const void* srccoordsclatvals_d = &dSourceCenterLat[0];
1175  dsize = dSourceCenterLon.GetRows();
1176  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsCLon, &m_meshOverlapSet, 1, &srccoordsclonvals_d, &dsize ),
1177  "Setting local tag data failed" );
1178  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsCLat, &m_meshOverlapSet, 1, &srccoordsclatvals_d, &dsize ),
1179  "Setting local tag data failed" );
1180  const void* tgtcoordsclonvals_d = &m_dTargetCenterLon[0];
1181  const void* tgtcoordsclatvals_d = &m_dTargetCenterLat[0];
1182  dsize = vecTargetFaceArea.GetRows();
1183  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsCLon, &m_meshOverlapSet, 1, &tgtcoordsclonvals_d, &dsize ),
1184  "Setting local tag data failed" );
1185  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsCLat, &m_meshOverlapSet, 1, &tgtcoordsclatvals_d, &dsize ),
1186  "Setting local tag data failed" );
1187 
1188  /* Set the coordinates for source and target element vertices */
1189  const void* srccoordsvlonvals_d = &( dSourceVertexLon[0][0] );
1190  const void* srccoordsvlatvals_d = &( dSourceVertexLat[0][0] );
1191  dsize = dSourceVertexLon.GetRows() * dSourceVertexLon.GetColumns();
1192  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsVLon, &m_meshOverlapSet, 1, &srccoordsvlonvals_d, &dsize ),
1193  "Setting local tag data failed" );
1194  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsVLat, &m_meshOverlapSet, 1, &srccoordsvlatvals_d, &dsize ),
1195  "Setting local tag data failed" );
1196  const void* tgtcoordsvlonvals_d = &( m_dTargetVertexLon[0][0] );
1197  const void* tgtcoordsvlatvals_d = &( m_dTargetVertexLat[0][0] );
1198  dsize = m_dTargetVertexLon.GetRows() * m_dTargetVertexLon.GetColumns();
1199  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsVLon, &m_meshOverlapSet, 1, &tgtcoordsvlonvals_d, &dsize ),
1200  "Setting local tag data failed" );
1201  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsVLat, &m_meshOverlapSet, 1, &tgtcoordsvlatvals_d, &dsize ),
1202  "Setting local tag data failed" );
1203 
1204  /* Set the masks for source and target meshes if available */
1205  if( m_iSourceMask.IsAttached() )
1206  {
1207  const void* srcmaskvals_d = m_iSourceMask;
1208  dsize = m_iSourceMask.GetRows();
1209  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( srcMaskValues, &m_meshOverlapSet, 1, &srcmaskvals_d, &dsize ),
1210  "Setting local tag data failed" );
1211  }
1212 
1213  if( m_iTargetMask.IsAttached() )
1214  {
1215  const void* tgtmaskvals_d = m_iTargetMask;
1216  dsize = m_iTargetMask.GetRows();
1217  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tgtMaskValues, &m_meshOverlapSet, 1, &tgtmaskvals_d, &dsize ),
1218  "Setting local tag data failed" );
1219  }
1220 
1221 #ifdef MOAB_HAVE_MPI
1222  const char* writeOptions = ( this->size > 1 ? "PARALLEL=WRITE_PART" : "" );
1223 #else
1224  const char* writeOptions = "";
1225 #endif
1226 
1227  // EntityHandle sets[3] = {m_remapper->m_source_set, m_remapper->m_target_set, m_remapper->m_overlap_set};
1228  EntityHandle sets[1] = { m_remapper->m_overlap_set };
1229  MB_CHK_ERR( m_interface->write_file( strOutputFile.c_str(), NULL, writeOptions, sets, 1 ) );
1230 
1231 #ifdef WRITE_SCRIP_FILE
1232  sstr.str( "" );
1233  sstr << ctx.outFilename.substr( 0, lastindex ) << "_" << proc_id << ".nc";
1234  std::map< std::string, std::string > mapAttributes;
1235  mapAttributes["Creator"] = "MOAB mbtempest workflow";
1236  if( !ctx.proc_id ) std::cout << "Writing offline map to file: " << sstr.str() << std::endl;
1237  this->Write( strOutputFile.c_str(), mapAttributes, NcFile::Netcdf4 );
1238  sstr.str( "" );
1239 #endif
1240 
1241  return moab::MB_SUCCESS;
1242 }
1243 
1244 ///////////////////////////////////////////////////////////////////////////////
1245 
1246 void print_progress( const int barWidth, const float progress, const char* message )
1247 {
1248  std::cout << message << " [";
1249  int pos = barWidth * progress;
1250  for( int i = 0; i < barWidth; ++i )
1251  {
1252  if( i < pos )
1253  std::cout << "=";
1254  else if( i == pos )
1255  std::cout << ">";
1256  else
1257  std::cout << " ";
1258  }
1259  std::cout << "] " << int( progress * 100.0 ) << " %\r";
1260  std::cout.flush();
1261 }
1262 
1263 ///////////////////////////////////////////////////////////////////////////////
1264 
1265 ///////////////////////////////////////////////////////////////////////////////
1266 //
1267 // ReadParallelMap: Read a SCRIP-format map file and distribute sparse matrix
1268 // data across MPI ranks.
1269 //
1270 // Strategy (adaptive, based on NNZ count):
1271 // 1. Serial (size == 1): rank 0 reads entire file directly.
1272 // 2. Buffered read (size > 1, nS <= NNZ threshold): rank 0 reads the file
1273 // using serial NcFile in fixed-size chunks, determines row ownership for
1274 // each entry, and scatters data to owning ranks via MPI point-to-point.
1275 // This avoids the need for parallel NetCDF and scales well for small-to-
1276 // medium maps by reducing file system contention.
1277 // 3. Direct parallel read (size > 1, nS > NNZ threshold): all ranks read
1278 // their stripe of the file simultaneously using PNetCDF (preferred) or
1279 // parallel HDF5-backed NetCDF (NETCDFPAR). Falls back to buffered read
1280 // if neither is available.
1281 //
1282 // After the initial read/scatter, the downstream TupleList redistribution
1283 // (for owned_dof_ids-based repartitioning) and Eigen sparse matrix assembly
1284 // are unchanged regardless of which read strategy was used.
1285 //
1286 ///////////////////////////////////////////////////////////////////////////////
1287 
1288 // Tuning constants for the buffered read strategy.
1289 // Adjust these for scalability studies on different platforms.
1290 
1291 /// NNZ threshold: maps with nS <= this value use the buffered read strategy.
1292 /// Maps with nS > this value use direct parallel I/O (if available).
1293 /// Default 3M entries corresponds to ~36 MB of raw data (row+col+S).
1294 static constexpr int BUFFERED_READ_NNZ_THRESHOLD = 3000000;
1295 
1296 /// Buffer size in bytes for each chunk read by rank 0 in buffered mode.
1297 /// Each sparse matrix entry is 12 bytes (2 ints + 1 double), so 64KB holds
1298 /// ~5461 entries. Larger buffers reduce the number of read+scatter rounds
1299 /// but increase peak memory on rank 0.
1300 static constexpr int BUFFERED_READ_CHUNK_BYTES = 64 * 1024;
1301 
1302 // Map file NetCDF format classification used for parallel reader selection.
1303 // Classic (CDF-1/2/5) is preferred for PNetCDF; NetCDF-4 (HDF5) requires
1304 // parallel NetCDF (NETCDFPAR). Detection only inspects the file header,
1305 // so no NetCDF library dependency is required for the probe itself.
1307 {
1309  MAP_FORMAT_CLASSIC = 1, // CDF-1, CDF-2, CDF-5
1310  MAP_FORMAT_NETCDF4 = 2 // NetCDF-4 / HDF5
1311 };
1312 
1313 /// Inspect the file header to classify NetCDF format. Reads only the first
1314 /// 8 bytes — relies on the well-known "CDF\\xNN" and HDF5 magic signatures.
1315 /// Returns MAP_FORMAT_UNKNOWN if the file cannot be opened or the signature
1316 /// does not match. Safe to call from a single rank.
1317 ///
1318 /// References:
1319 /// - NetCDF classic format spec: https://docs.unidata.ucar.edu/nug/current/file_format_specifications.html
1320 /// ("CDF\\x01" / "CDF\\x02" / "CDF\\x05")
1321 /// - HDF5 superblock signature: \\x89 H D F \\r \\n \\x1a \\n
1322 static int detectMapFileNetCDFFormat( const char* path )
1323 {
1324  std::FILE* fp = std::fopen( path, "rb" );
1325  if( !fp ) return MAP_FORMAT_UNKNOWN;
1326 
1327  unsigned char magic[8] = { 0 };
1328  const size_t nread = std::fread( magic, 1, sizeof( magic ), fp );
1329  std::fclose( fp );
1330 
1331  if( nread < 4 ) return MAP_FORMAT_UNKNOWN;
1332 
1333  // Classic NetCDF families: 'C','D','F' + version byte (0x01, 0x02, or 0x05)
1334  if( magic[0] == 'C' && magic[1] == 'D' && magic[2] == 'F' )
1335  {
1336  if( magic[3] == 0x01 || magic[3] == 0x02 || magic[3] == 0x05 ) return MAP_FORMAT_CLASSIC;
1337  }
1338 
1339  // HDF5 signature (used by NetCDF-4)
1340  if( nread >= 8 && magic[0] == 0x89 && magic[1] == 'H' && magic[2] == 'D' && magic[3] == 'F' && magic[4] == 0x0D &&
1341  magic[5] == 0x0A && magic[6] == 0x1A && magic[7] == 0x0A )
1342  {
1343  return MAP_FORMAT_NETCDF4;
1344  }
1345 
1346  return MAP_FORMAT_UNKNOWN;
1347 }
1348 
1350  const std::vector< int >& owned_dof_ids,
1351  int arearead,
1352  std::vector< double >& vecAreaA,
1353  int& nA,
1354  std::vector< double >& vecAreaB,
1355  int& nB )
1356 {
1357  NcError error( NcError::silent_nonfatal );
1358 
1359  const bool readAreaA = ( 1 == arearead || 3 == arearead );
1360  const bool readAreaB = ( 2 == arearead || 3 == arearead );
1361  int nS = 0;
1362 
1363  // =========================================================================
1364  // Phase 1: Read map dimensions (nA, nB, nS) and sparse matrix data.
1365  //
1366  // The read strategy is selected adaptively:
1367  // - Serial or buffered read: rank 0 opens the file with serial NcFile,
1368  // reads dimensions, and (for buffered mode) scatters data in chunks.
1369  // - Direct parallel read: all ranks open the file with PNetCDF or
1370  // NETCDFPAR and read their stripe directly.
1371  // =========================================================================
1372 
1373  std::vector< int > vecRow, vecCol;
1374  std::vector< double > vecS;
1375  int localSize = 0; // number of sparse matrix entries on this rank after read
1376 
1377  // Determine which read strategy to use. For size == 1, always serial.
1378  // For size > 1, decide after reading dimensions (need nS).
1379  // We use a two-phase approach: first read dimensions on rank 0 and broadcast,
1380  // then select the strategy based on nS.
1381 
1382 #ifdef MOAB_HAVE_MPI
1383  if( size > 1 )
1384  {
1385  // --- Multi-process path: read dimensions on rank 0 and broadcast ---
1386  int dims[3] = { 0, 0, 0 }; // nA, nB, nS
1387 
1388  if( rank == 0 )
1389  {
1390  NcFile ncDims( strSource, NcFile::ReadOnly );
1391  if( !ncDims.is_valid() )
1392  {
1393  _EXCEPTION1( "Unable to open input map file \"%s\" on rank 0", strSource );
1394  }
1395  NcDim* dimNA = ncDims.get_dim( "n_a" );
1396  NcDim* dimNB = ncDims.get_dim( "n_b" );
1397  NcDim* dimNS = ncDims.get_dim( "n_s" );
1398  if( !dimNA || !dimNB || !dimNS )
1399  {
1400  _EXCEPTION1( "Map file \"%s\" missing required dimensions (n_a, n_b, n_s)", strSource );
1401  }
1402  dims[0] = static_cast< int >( dimNA->size() );
1403  dims[1] = static_cast< int >( dimNB->size() );
1404  dims[2] = static_cast< int >( dimNS->size() );
1405  ncDims.close();
1406  }
1407 
1408  MPI_Bcast( dims, 3, MPI_INT, 0, m_pcomm->comm() );
1409  nA = dims[0];
1410  nB = dims[1];
1411  nS = dims[2];
1412 
1413  // Select read strategy based on NNZ count and available parallel I/O
1414  bool useBufferedRead = true; // default for small maps or no parallel I/O
1415 
1416  if( nS > BUFFERED_READ_NNZ_THRESHOLD )
1417  {
1418  // Large map: prefer direct parallel read if available
1419 #if defined( MOAB_HAVE_PNETCDF ) || defined( MOAB_HAVE_NETCDFPAR )
1420  useBufferedRead = false;
1421 #endif
1422  // If neither is available, fall back to buffered read regardless of size
1423  }
1424 
1425  if( useBufferedRead )
1426  {
1427  // =================================================================
1428  // Buffered read: rank 0 reads in chunks and scatters to owners.
1429  //
1430  // Row ownership is determined by trivial partitioning: row i is
1431  // owned by rank (i / nRowPerPart), with remainder on rank 0.
1432  // Each chunk is read, ownership is computed per entry, and the
1433  // data is scattered via MPI_Scatter + MPI_Isend/MPI_Irecv.
1434  // =================================================================
1435  if( rank == 0 )
1436  {
1437  std::cout << " [ReadParallelMap]: Using buffered read strategy for " << nS
1438  << " NNZ entries (threshold=" << BUFFERED_READ_NNZ_THRESHOLD << ")\n";
1439  }
1440 
1441  const int nNNZBytes = 2 * sizeof( int ) + sizeof( double );
1442  const int nMaxPerChunk = BUFFERED_READ_CHUNK_BYTES / nNNZBytes;
1443  const int nBufferedReads = static_cast< int >( std::ceil( 1.0 * nS / nMaxPerChunk ) );
1444 
1445  // Row ownership: trivial partitioning of nB rows across ranks
1446  const int nRowPerPart = nB / size;
1447  const int nRowRemainder = nB % size;
1448  std::vector< int > rowOwnership( size );
1449  rowOwnership[0] = nRowPerPart + nRowRemainder;
1450  for( int ip = 1; ip < size; ++ip )
1451  rowOwnership[ip] = rowOwnership[ip - 1] + nRowPerPart;
1452 
1453  // File handle and variable pointers (rank 0 only)
1454  NcFile* ncMap = nullptr;
1455  NcVar *varRowF = nullptr, *varColF = nullptr, *varSF = nullptr;
1456  NcVar *varAreaAF = nullptr, *varAreaBF = nullptr;
1457 
1458  if( rank == 0 )
1459  {
1460  ncMap = new NcFile( strSource, NcFile::ReadOnly );
1461  if( !ncMap->is_valid() )
1462  {
1463  _EXCEPTION1( "Unable to open map file \"%s\" for buffered read", strSource );
1464  }
1465  varRowF = ncMap->get_var( "row" );
1466  varColF = ncMap->get_var( "col" );
1467  varSF = ncMap->get_var( "S" );
1468  if( readAreaA ) varAreaAF = ncMap->get_var( "area_a" );
1469  if( readAreaB ) varAreaBF = ncMap->get_var( "area_b" );
1470  }
1471 
1472  // Accumulate received entries per rank
1473  std::vector< int > localRows, localCols;
1474  std::vector< double > localVals;
1475  localRows.reserve( nS / size + nS / ( size * 10 ) ); // slight overalloc
1476  localCols.reserve( nS / size + nS / ( size * 10 ) );
1477  localVals.reserve( nS / size + nS / ( size * 10 ) );
1478 
1479  int nEntriesRemaining = nS;
1480  long fileOffset = 0;
1481 
1482  for( int iRead = 0; iRead < nBufferedReads; ++iRead )
1483  {
1484  // Per-chunk data and ownership (rank 0 only)
1485  std::vector< int > chunkRow, chunkCol;
1486  std::vector< double > chunkS;
1487  std::vector< std::vector< int > > entriesPerProc( size );
1488  std::vector< int > nPerProc( size, 0 );
1489 
1490  if( rank == 0 )
1491  {
1492  int chunkSize = std::min( nEntriesRemaining, nMaxPerChunk );
1493 
1494  chunkRow.resize( chunkSize );
1495  chunkCol.resize( chunkSize );
1496  chunkS.resize( chunkSize );
1497 
1498  varRowF->set_cur( fileOffset );
1499  varRowF->get( chunkRow.data(), chunkSize );
1500  varColF->set_cur( fileOffset );
1501  varColF->get( chunkCol.data(), chunkSize );
1502  varSF->set_cur( fileOffset );
1503  varSF->get( chunkS.data(), chunkSize );
1504 
1505  // Determine ownership of each entry by its row index (1-based in file)
1506  for( int ip = 0; ip < size; ++ip )
1507  entriesPerProc[ip].reserve( chunkSize / size + 64 );
1508 
1509  for( int i = 0; i < chunkSize; ++i )
1510  {
1511  int rowIdx = chunkRow[i] - 1; // convert to 0-based
1512  int owner = 0;
1513  if( rowIdx >= rowOwnership[0] )
1514  {
1515  // Binary search for owner
1516  owner = static_cast< int >(
1517  std::upper_bound( rowOwnership.begin(), rowOwnership.end(), rowIdx ) -
1518  rowOwnership.begin() );
1519  if( owner >= size ) owner = size - 1;
1520  }
1521  entriesPerProc[owner].push_back( i );
1522  }
1523 
1524  fileOffset += chunkSize;
1525  nEntriesRemaining -= chunkSize;
1526 
1527  for( int ip = 0; ip < size; ++ip )
1528  nPerProc[ip] = static_cast< int >( entriesPerProc[ip].size() );
1529  }
1530 
1531  // Scatter count of entries each rank will receive in this chunk
1532  int nRecv = 0;
1533  MPI_Scatter( nPerProc.data(), 1, MPI_INT, &nRecv, 1, MPI_INT, 0, m_pcomm->comm() );
1534 
1535  if( rank == 0 )
1536  {
1537  // Send data to remote ranks via non-blocking sends
1538  std::vector< MPI_Request > requests;
1539  requests.reserve( 2 * ( size - 1 ) );
1540 
1541  // Pack and send to each remote rank
1542  std::vector< std::vector< int > > sendRowCol( size );
1543  std::vector< std::vector< double > > sendVals( size );
1544 
1545  for( int ip = 1; ip < size; ++ip )
1546  {
1547  const int nDPP = nPerProc[ip];
1548  if( nDPP > 0 )
1549  {
1550  sendRowCol[ip].resize( 2 * nDPP );
1551  sendVals[ip].resize( nDPP );
1552  for( int j = 0; j < nDPP; ++j )
1553  {
1554  int idx = entriesPerProc[ip][j];
1555  sendRowCol[ip][2 * j] = chunkRow[idx];
1556  sendRowCol[ip][2 * j + 1] = chunkCol[idx];
1557  sendVals[ip][j] = chunkS[idx];
1558  }
1559 
1560  MPI_Request rqRC, rqV;
1561  MPI_Isend( sendRowCol[ip].data(), 2 * nDPP, MPI_INT, ip,
1562  iRead * 1000, m_pcomm->comm(), &rqRC );
1563  MPI_Isend( sendVals[ip].data(), nDPP, MPI_DOUBLE, ip,
1564  iRead * 1000 + 1, m_pcomm->comm(), &rqV );
1565  requests.push_back( rqRC );
1566  requests.push_back( rqV );
1567  }
1568  }
1569 
1570  // Process rank 0's own entries while sends are in flight
1571  for( int j = 0; j < nRecv; ++j )
1572  {
1573  int idx = entriesPerProc[0][j];
1574  localRows.push_back( chunkRow[idx] );
1575  localCols.push_back( chunkCol[idx] );
1576  localVals.push_back( chunkS[idx] );
1577  }
1578 
1579  // Wait for all sends to complete
1580  if( !requests.empty() )
1581  {
1582  std::vector< MPI_Status > stats( requests.size() );
1583  MPI_Waitall( static_cast< int >( requests.size() ), requests.data(), stats.data() );
1584  }
1585  }
1586  else if( nRecv > 0 )
1587  {
1588  // Receive data from rank 0
1589  std::vector< int > recvRowCol( 2 * nRecv );
1590  std::vector< double > recvVals( nRecv );
1591 
1592  MPI_Request rqs[2];
1593  MPI_Irecv( recvRowCol.data(), 2 * nRecv, MPI_INT, 0,
1594  iRead * 1000, m_pcomm->comm(), &rqs[0] );
1595  MPI_Irecv( recvVals.data(), nRecv, MPI_DOUBLE, 0,
1596  iRead * 1000 + 1, m_pcomm->comm(), &rqs[1] );
1597 
1598  MPI_Status sts[2];
1599  MPI_Waitall( 2, rqs, sts );
1600 
1601  for( int j = 0; j < nRecv; ++j )
1602  {
1603  localRows.push_back( recvRowCol[2 * j] );
1604  localCols.push_back( recvRowCol[2 * j + 1] );
1605  localVals.push_back( recvVals[j] );
1606  }
1607  }
1608 
1609  MPI_Barrier( m_pcomm->comm() );
1610  } // end buffered read loop
1611 
1612  // Read area arrays on rank 0 and scatter the trivial (nA/size, nB/size)
1613  // partition to each rank. iMOAB's set_aream_from_trivial_distribution
1614  // (iMOAB.cpp) assumes each rank holds exactly its trivial slice of size
1615  // N/size (last rank gets the N%size remainder) and computes its local
1616  // index as `marker - 1 - rank * (N/size)`. Broadcasting the full array
1617  // would silently scramble the per-cell aream tag on every rank > 0,
1618  // which breaks BfB on the CAAS dual-map path while leaving the plain
1619  // SpMV (lo, hi) projections BfB-correct (those don't use aream).
1620  auto scatter_trivial = []( int Ntot, int rk, int sz, NcVar* var, std::vector< double >& localSlice,
1621  MPI_Comm comm ) {
1622  const int base = Ntot / sz;
1623  const int rem = Ntot % sz;
1624  const int localCount = ( rk == sz - 1 ) ? ( base + rem ) : base;
1625  localSlice.resize( localCount );
1626  if( rk == 0 )
1627  {
1628  std::vector< double > fullBuf( Ntot );
1629  if( var )
1630  {
1631  var->set_cur( 0L );
1632  var->get( fullBuf.data(), Ntot );
1633  }
1634  // Copy rank 0's own slice and send each other rank its slice.
1635  std::copy( fullBuf.begin(), fullBuf.begin() + localCount, localSlice.begin() );
1636  for( int dst = 1; dst < sz; dst++ )
1637  {
1638  const int dstCount = ( dst == sz - 1 ) ? ( base + rem ) : base;
1639  MPI_Send( fullBuf.data() + dst * base, dstCount, MPI_DOUBLE, dst, 0xA9EA, comm );
1640  }
1641  }
1642  else
1643  {
1644  MPI_Recv( localSlice.data(), localCount, MPI_DOUBLE, 0, 0xA9EA, comm, MPI_STATUS_IGNORE );
1645  }
1646  };
1647  if( readAreaA ) scatter_trivial( nA, rank, size, varAreaAF, vecAreaA, m_pcomm->comm() );
1648  if( readAreaB ) scatter_trivial( nB, rank, size, varAreaBF, vecAreaB, m_pcomm->comm() );
1649 
1650  if( rank == 0 )
1651  {
1652  ncMap->close();
1653  delete ncMap;
1654  }
1655 
1656  // Move accumulated data into the standard vecRow/vecCol/vecS vectors
1657  localSize = static_cast< int >( localRows.size() );
1658  vecRow.swap( localRows );
1659  vecCol.swap( localCols );
1660  vecS.swap( localVals );
1661  }
1662  else
1663  {
1664  // =================================================================
1665  // Direct parallel read: choose reader based on detected file format.
1666  //
1667  // Selection logic (per format):
1668  // - Classic (CDF-1/2/5): prefer PNetCDF (best fit for the classic
1669  // family); fall back to NETCDFPAR only if PNetCDF is not built in.
1670  // - NetCDF-4 (HDF5): must use NETCDFPAR — PNetCDF cannot read
1671  // HDF5-backed NetCDF-4 files.
1672  // - Unknown/other: hard error.
1673  //
1674  // Format is determined by reading the file's magic bytes on rank 0
1675  // and broadcasting the answer. This avoids opening the file twice
1676  // in parallel just to probe its format.
1677  // =================================================================
1678  if( rank == 0 )
1679  {
1680  std::cout << " [ReadParallelMap]: Using direct parallel read for " << nS
1681  << " NNZ entries (threshold=" << BUFFERED_READ_NNZ_THRESHOLD << ")\n";
1682  }
1683 
1684  int fileFormat = MAP_FORMAT_UNKNOWN;
1685  if( rank == 0 ) fileFormat = detectMapFileNetCDFFormat( strSource );
1686  MPI_Bcast( &fileFormat, 1, MPI_INT, 0, m_pcomm->comm() );
1687 
1688  const bool isClassic = ( fileFormat == MAP_FORMAT_CLASSIC );
1689  const bool isNetCDF4 = ( fileFormat == MAP_FORMAT_NETCDF4 );
1690 
1691  if( !isClassic && !isNetCDF4 )
1692  {
1693  _EXCEPTION1( "Map file \"%s\" is not in a recognized NetCDF format "
1694  "(expected classic CDF-1/2/5 or NetCDF-4/HDF5)",
1695  strSource );
1696  }
1697 
1698  // Compute this rank's stripe of the sparse matrix
1699  localSize = nS / size;
1700  long offsetRead = rank * localSize;
1701  if( rank == size - 1 ) localSize += nS % size;
1702 
1703  vecRow.resize( localSize );
1704  vecCol.resize( localSize );
1705  vecS.resize( localSize );
1706 
1707  // Compute this rank's stripe of area arrays
1708  int localSizeA = nA / size;
1709  long offsetReadA = rank * localSizeA;
1710  if( rank == size - 1 ) localSizeA += nA % size;
1711 
1712  int localSizeB = nB / size;
1713  long offsetReadB = rank * localSizeB;
1714  if( rank == size - 1 ) localSizeB += nB % size;
1715 
1716  if( readAreaA ) vecAreaA.resize( localSizeA );
1717  if( readAreaB ) vecAreaB.resize( localSizeB );
1718 
1719  bool parReadDone = false;
1720 
1721  // --- Classic format: prefer PNetCDF, fall back to NETCDFPAR -----
1722  if( isClassic )
1723  {
1724 #ifdef MOAB_HAVE_PNETCDF
1725  {
1726  // PNetCDF — collective I/O, native fit for CDF-1/2/5
1727  int ncfile = -1;
1728  int pnc_err = ncmpi_open( m_pcomm->comm(), strSource, NC_NOWRITE, MPI_INFO_NULL, &ncfile );
1729  if( pnc_err == NC_NOERR )
1730  {
1731  if( rank == 0 )
1732  std::cout << " [ReadParallelMap]: Reading classic-format file via PNetCDF\n";
1733 
1734  MPI_Offset start = static_cast< MPI_Offset >( offsetRead );
1735  MPI_Offset count = static_cast< MPI_Offset >( localSize );
1736  int varid;
1737 
1738  ERR_PARNC( ncmpi_inq_varid( ncfile, "S", &varid ) );
1739  ERR_PARNC( ncmpi_get_vara_double_all( ncfile, varid, &start, &count, vecS.data() ) );
1740  ERR_PARNC( ncmpi_inq_varid( ncfile, "row", &varid ) );
1741  ERR_PARNC( ncmpi_get_vara_int_all( ncfile, varid, &start, &count, vecRow.data() ) );
1742  ERR_PARNC( ncmpi_inq_varid( ncfile, "col", &varid ) );
1743  ERR_PARNC( ncmpi_get_vara_int_all( ncfile, varid, &start, &count, vecCol.data() ) );
1744 
1745  if( readAreaA )
1746  {
1747  MPI_Offset startA = static_cast< MPI_Offset >( offsetReadA );
1748  MPI_Offset countA = static_cast< MPI_Offset >( localSizeA );
1749  ERR_PARNC( ncmpi_inq_varid( ncfile, "area_a", &varid ) );
1750  ERR_PARNC( ncmpi_get_vara_double_all( ncfile, varid, &startA, &countA, vecAreaA.data() ) );
1751  }
1752  if( readAreaB )
1753  {
1754  MPI_Offset startB = static_cast< MPI_Offset >( offsetReadB );
1755  MPI_Offset countB = static_cast< MPI_Offset >( localSizeB );
1756  ERR_PARNC( ncmpi_inq_varid( ncfile, "area_b", &varid ) );
1757  ERR_PARNC( ncmpi_get_vara_double_all( ncfile, varid, &startB, &countB, vecAreaB.data() ) );
1758  }
1759  ERR_PARNC( ncmpi_close( ncfile ) );
1760  parReadDone = true;
1761  }
1762  }
1763 #endif
1764 
1765 #ifdef MOAB_HAVE_NETCDFPAR
1766  if( !parReadDone )
1767  {
1768  // PNetCDF not configured (or its open failed) — try NETCDFPAR.
1769  // Works only if NetCDF-4 was built with parallel-IO support
1770  // for classic files (typically requires NetCDF linked against PNetCDF).
1771  if( rank == 0 )
1772  std::cout << " [ReadParallelMap]: PNetCDF unavailable; reading classic-format "
1773  "file via parallel NetCDF (NETCDFPAR)\n";
1774  ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strSource, NcFile::ReadOnly, NcFile::Classic );
1775  if( ncMap.is_valid() )
1776  {
1777  NcVar* varRowP = ncMap.get_var( "row" );
1778  NcVar* varColP = ncMap.get_var( "col" );
1779  NcVar* varSP = ncMap.get_var( "S" );
1780  ncMap.enable_var_par_access( varRowP, true );
1781  ncMap.enable_var_par_access( varColP, true );
1782  ncMap.enable_var_par_access( varSP, true );
1783 
1784  varRowP->set_cur( offsetRead );
1785  varRowP->get( vecRow.data(), localSize );
1786  varColP->set_cur( offsetRead );
1787  varColP->get( vecCol.data(), localSize );
1788  varSP->set_cur( offsetRead );
1789  varSP->get( vecS.data(), localSize );
1790 
1791  if( readAreaA )
1792  {
1793  NcVar* varAreaAP = ncMap.get_var( "area_a" );
1794  ncMap.enable_var_par_access( varAreaAP, true );
1795  varAreaAP->set_cur( offsetReadA );
1796  varAreaAP->get( vecAreaA.data(), localSizeA );
1797  }
1798  if( readAreaB )
1799  {
1800  NcVar* varAreaBP = ncMap.get_var( "area_b" );
1801  ncMap.enable_var_par_access( varAreaBP, true );
1802  varAreaBP->set_cur( offsetReadB );
1803  varAreaBP->get( vecAreaB.data(), localSizeB );
1804  }
1805  ncMap.close();
1806  parReadDone = true;
1807  }
1808  }
1809 #endif
1810 
1811  if( !parReadDone )
1812  {
1813  _EXCEPTION1( "Classic-format map file \"%s\" cannot be read in parallel: "
1814  "neither PNetCDF nor parallel NetCDF (NETCDFPAR) is configured "
1815  "(or both failed to open the file)",
1816  strSource );
1817  }
1818  }
1819  // --- NetCDF-4/HDF5 format: only NETCDFPAR can handle it ---------
1820  else // isNetCDF4
1821  {
1822 #ifdef MOAB_HAVE_NETCDFPAR
1823  {
1824  if( rank == 0 )
1825  std::cout << " [ReadParallelMap]: Reading NetCDF-4/HDF5 file via parallel NetCDF\n";
1826  ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strSource, NcFile::ReadOnly, NcFile::Netcdf4 );
1827  if( ncMap.is_valid() )
1828  {
1829  NcVar* varRowP = ncMap.get_var( "row" );
1830  NcVar* varColP = ncMap.get_var( "col" );
1831  NcVar* varSP = ncMap.get_var( "S" );
1832  ncMap.enable_var_par_access( varRowP, true );
1833  ncMap.enable_var_par_access( varColP, true );
1834  ncMap.enable_var_par_access( varSP, true );
1835 
1836  varRowP->set_cur( offsetRead );
1837  varRowP->get( vecRow.data(), localSize );
1838  varColP->set_cur( offsetRead );
1839  varColP->get( vecCol.data(), localSize );
1840  varSP->set_cur( offsetRead );
1841  varSP->get( vecS.data(), localSize );
1842 
1843  if( readAreaA )
1844  {
1845  NcVar* varAreaAP = ncMap.get_var( "area_a" );
1846  ncMap.enable_var_par_access( varAreaAP, true );
1847  varAreaAP->set_cur( offsetReadA );
1848  varAreaAP->get( vecAreaA.data(), localSizeA );
1849  }
1850  if( readAreaB )
1851  {
1852  NcVar* varAreaBP = ncMap.get_var( "area_b" );
1853  ncMap.enable_var_par_access( varAreaBP, true );
1854  varAreaBP->set_cur( offsetReadB );
1855  varAreaBP->get( vecAreaB.data(), localSizeB );
1856  }
1857  ncMap.close();
1858  parReadDone = true;
1859  }
1860  }
1861 #endif
1862 
1863  if( !parReadDone )
1864  {
1865  _EXCEPTION1( "NetCDF-4/HDF5 map file \"%s\" cannot be read in parallel: "
1866  "parallel NetCDF (NETCDFPAR) is not configured "
1867  "(PNetCDF cannot read NetCDF-4 files)",
1868  strSource );
1869  }
1870  }
1871  } // end direct parallel read
1872  }
1873  else
1874 #endif // MOAB_HAVE_MPI
1875  {
1876  // =================================================================
1877  // Serial path (size == 1): read entire file on the single process.
1878  // =================================================================
1879  std::cout << " [ReadParallelMap]: Using serial read (single process)\n";
1880  NcFile ncMap( strSource, NcFile::ReadOnly );
1881  if( !ncMap.is_valid() )
1882  {
1883  _EXCEPTION1( "Unable to open input map file \"%s\"", strSource );
1884  }
1885 
1886  NcDim* dimNS = ncMap.get_dim( "n_s" );
1887  NcDim* dimNA = ncMap.get_dim( "n_a" );
1888  NcDim* dimNB = ncMap.get_dim( "n_b" );
1889  if( !dimNS || !dimNA || !dimNB )
1890  {
1891  _EXCEPTION1( "Map file \"%s\" missing required dimensions", strSource );
1892  }
1893  nS = static_cast< int >( dimNS->size() );
1894  nA = static_cast< int >( dimNA->size() );
1895  nB = static_cast< int >( dimNB->size() );
1896 
1897  localSize = nS;
1898  vecRow.resize( nS );
1899  vecCol.resize( nS );
1900  vecS.resize( nS );
1901 
1902  NcVar* varRowS = ncMap.get_var( "row" );
1903  NcVar* varColS = ncMap.get_var( "col" );
1904  NcVar* varSS = ncMap.get_var( "S" );
1905  varRowS->get( vecRow.data(), nS );
1906  varColS->get( vecCol.data(), nS );
1907  varSS->get( vecS.data(), nS );
1908 
1909  if( readAreaA )
1910  {
1911  vecAreaA.resize( nA );
1912  NcVar* varAreaAS = ncMap.get_var( "area_a" );
1913  if( varAreaAS ) varAreaAS->get( vecAreaA.data(), nA );
1914  }
1915  if( readAreaB )
1916  {
1917  vecAreaB.resize( nB );
1918  NcVar* varAreaBS = ncMap.get_var( "area_b" );
1919  if( varAreaBS ) varAreaBS->get( vecAreaB.data(), nB );
1920  }
1921  ncMap.close();
1922  }
1923 
1924  // =========================================================================
1925  // Phase 2: Redistribute sparse matrix entries to their final owning ranks.
1926  //
1927  // After Phase 1, each rank holds a portion of the sparse matrix entries
1928  // (either its owned rows from the buffered read, or a stripe from the
1929  // direct parallel read). The rows/cols are still 1-based (SCRIP format).
1930  //
1931  // This phase uses TupleList-based crystal router communication to send
1932  // entries to the rank that owns each row (trivial nB/size partitioning),
1933  // and optionally a second redistribution based on owned_dof_ids.
1934  // =========================================================================
1935 
1936 #ifdef MOAB_HAVE_EIGEN3
1937 
1938  typedef Eigen::Triplet< double > Triplet;
1939  std::vector< Triplet > tripletList;
1940 
1941 #ifdef MOAB_HAVE_MPI
1942  if( size > 1 )
1943  {
1944  // Trivial row partitioning for redistribution
1945  const int nPerPart = nB / size;
1946 
1947  moab::TupleList* tl = new moab::TupleList;
1948  unsigned numr = 1;
1949  tl->initialize( 3, 0, 0, numr, localSize ); // to_proc, row, col, value
1950  tl->enableWriteAccess();
1951 
1952  for( int i = 0; i < localSize; i++ )
1953  {
1954  int rowval = vecRow[i] - 1; // convert from 1-based (SCRIP) to 0-based
1955  int colval = vecCol[i] - 1;
1956  int to_proc = rowval / nPerPart;
1957  if( to_proc >= size ) to_proc = size - 1;
1958 
1959  int n = tl->get_n();
1960  tl->vi_wr[3 * n] = to_proc;
1961  tl->vi_wr[3 * n + 1] = rowval;
1962  tl->vi_wr[3 * n + 2] = colval;
1963  tl->vr_wr[n] = vecS[i];
1964  tl->inc_n();
1965  }
1966 
1967  // Crystal router: redistribute entries by row ownership
1968  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, *tl, 0 );
1969 
1970  if( owned_dof_ids.size() > 0 )
1971  {
1972  // we need to send desired dof to the rendezvous point
1973  moab::TupleList tl_re; //
1974  tl_re.initialize( 2, 0, 0, 0, owned_dof_ids.size() ); // to proc, value
1975  tl_re.enableWriteAccess();
1976  // send first to rendez_vous point, decided by trivial partitioning
1977 
1978  for( size_t i = 0; i < owned_dof_ids.size(); i++ )
1979  {
1980  int to_proc = -1;
1981  int dof_val = owned_dof_ids[i] - 1; // dofs are 1 based in the file, partition from 0 ?
1982  to_proc = dof_val / nPerPart;
1983  if( to_proc == size ) to_proc = size - 1;
1984 
1985  int n = tl_re.get_n();
1986  tl_re.vi_wr[2 * n] = to_proc;
1987  tl_re.vi_wr[2 * n + 1] = dof_val;
1988 
1989  tl_re.inc_n();
1990  }
1991  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tl_re, 0 );
1992  // now we know in tl_re where do we need to send back dof_val
1993  moab::TupleList::buffer sort_buffer;
1994  sort_buffer.buffer_init( tl_re.get_n() );
1995  tl_re.sort( 1, &sort_buffer ); // so now we order by value
1996 
1997  //sort_buffer.buffer_init( tl->get_n() );
1998 
1999  std::map< int, int > startDofIndex, endDofIndex; // indices in tl_re for values we want
2000  int dofVal = -1;
2001  if( tl_re.get_n() > 0 )
2002  {
2003  dofVal = tl_re.vi_rd[1]; // first dof val on this rank tl_re.vi_rd[2 * 0 + 1];
2004 
2005  startDofIndex[dofVal] = 0;
2006  endDofIndex[dofVal] = 0; // start and end
2007  for( unsigned k = 1; k < tl_re.get_n(); k++ )
2008  {
2009  int newDof = tl_re.vi_rd[2 * k + 1];
2010  if( dofVal == newDof )
2011  {
2012  endDofIndex[dofVal] = k; // increment by 1 actually
2013  }
2014  else
2015  {
2016  dofVal = newDof;
2017  startDofIndex[dofVal] = k;
2018  endDofIndex[dofVal] = k;
2019  }
2020  }
2021  }
2022  // basically, for each value we are interested in, index in tl_re with those values are
2023  // tl_re.vi_rd[2*startDofIndex+1] == valDof == tl_re.vi_rd[2*endDofIndex+1]
2024  // so now we have ordered
2025  // tl_re shows to what proc do we need to send the tuple (row, col, val)
2026  moab::TupleList* tl_back = new moab::TupleList;
2027  unsigned numr = 1; //
2028  // localSize is a good guess, but maybe it should be bigger ?
2029  // this could be bigger for repeated dofs
2030  tl_back->initialize( 3, 0, 0, numr, tl->get_n() ); // to proc, row, col, value
2031  tl_back->enableWriteAccess();
2032  // now loop over tl and tl_re to see where to send
2033  // form the new tuple, which will contain the desired dofs per task, per row or column distribution
2034 
2035  for( unsigned k = 0; k < tl->get_n(); k++ )
2036  {
2037  int valDof = tl->vi_rd[3 * k + 1]; // 1 for row, 2 for column // first value, it should be
2038  if( startDofIndex.find( valDof ) == startDofIndex.end() ) continue;
2039  for( int ire = startDofIndex[valDof]; ire <= endDofIndex[valDof]; ire++ )
2040  {
2041  int to_proc = tl_re.vi_rd[2 * ire];
2042  int n = tl_back->get_n();
2043  tl_back->vi_wr[3 * n] = to_proc;
2044  tl_back->vi_wr[3 * n + 1] = tl->vi_rd[3 * k + 1]; // row
2045  tl_back->vi_wr[3 * n + 2] = tl->vi_rd[3 * k + 2]; // col
2046  tl_back->vr_wr[n] = tl->vr_rd[k];
2047  tl_back->inc_n();
2048  }
2049  }
2050 
2051  // now communicate to the desired tasks:
2052  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, *tl_back, 0 );
2053 
2054  tl_re.reset(); // clear memory, although this will go out of scope
2055  tl->reset();
2056  tl = tl_back;
2057  }
2058 
2059  // set of row and col used on this task
2060  std::set< int > rowSet;
2061  std::set< int > colSet;
2062  // populate the sparsematrix, using rowMap and colMap
2063  int n = tl->get_n();
2064  for( int i = 0; i < n; i++ )
2065  {
2066  const int vecRowValue = tl->vi_wr[3 * i + 1];
2067  const int vecColValue = tl->vi_wr[3 * i + 2];
2068  rowSet.insert( vecRowValue );
2069  colSet.insert( vecColValue );
2070  }
2071  int index = 0;
2072  row_gdofmap.resize( rowSet.size() );
2073  for( auto setIt : rowSet )
2074  {
2075  row_gdofmap[index] = setIt;
2076  rowMap[setIt] = index++;
2077  }
2078  m_nTotDofs_Dest = index;
2079  index = 0;
2080  col_gdofmap.resize( colSet.size() );
2081  for( auto setIt : colSet )
2082  {
2083  col_gdofmap[index] = setIt;
2084  colMap[setIt] = index++;
2085  }
2086  m_nTotDofs_SrcCov = index;
2087 
2088  tripletList.reserve( n );
2089  for( int i = 0; i < n; i++ )
2090  {
2091  const int vecRowValue = tl->vi_wr[3 * i + 1];
2092  const int vecColValue = tl->vi_wr[3 * i + 2];
2093  double value = tl->vr_wr[i];
2094  tripletList.emplace_back( rowMap[vecRowValue], colMap[vecColValue], value );
2095  }
2096  tl->reset();
2097  }
2098  else
2099 #endif
2100  {
2101  // set of row and col used on this task
2102  std::set< int > rowSet;
2103  std::set< int > colSet;
2104  // populate the sparsematrix, using rowMap and colMap
2105  for( int i = 0; i < nS; i++ )
2106  {
2107  const int vecRowValue = vecRow[i] - 1;
2108  const int vecColValue = vecCol[i] - 1;
2109  rowSet.insert( vecRowValue );
2110  colSet.insert( vecColValue );
2111  }
2112 
2113  int index = 0;
2114  row_gdofmap.resize( rowSet.size() );
2115  for( auto setIt : rowSet )
2116  {
2117  row_gdofmap[index] = setIt;
2118  rowMap[setIt] = index++;
2119  }
2120  m_nTotDofs_Dest = index;
2121  index = 0;
2122  col_gdofmap.resize( colSet.size() );
2123  for( auto setIt : colSet )
2124  {
2125  col_gdofmap[index] = setIt;
2126  colMap[setIt] = index++;
2127  }
2128  m_nTotDofs_SrcCov = index;
2129 
2130  tripletList.reserve( nS );
2131  for( int i = 0; i < nS; i++ )
2132  {
2133  const int vecRowValue = vecRow[i] - 1; // the rows, cols are 1 based in the file
2134  const int vecColValue = vecCol[i] - 1; // sparse matrix will be 0 based
2135  double value = vecS[i];
2136  tripletList.emplace_back( rowMap[vecRowValue], colMap[vecColValue], value );
2137  }
2138  }
2139 
2140  m_weightMatrix.resize( m_nTotDofs_Dest, m_nTotDofs_SrcCov );
2141  m_rowVector.resize( m_nTotDofs_Dest );
2142  m_colVector.resize( m_nTotDofs_SrcCov );
2143  m_nTotDofs_Src = m_nTotDofs_SrcCov; // do we need both?
2144  // Preserve the map file's global source-DoF count (n_a) so the migration
2145  // can tell a masked source mesh (fewer cells than n_a -> drop is BfB-safe)
2146  // from a complete one (== n_a but a column missing -> real error).
2147  m_nTotDofs_SrcGlobal = nA;
2148  m_weightMatrix.setFromTriplets( tripletList.begin(), tripletList.end() );
2149  // Reset the source and target data first
2150  m_rowVector.setZero();
2151  m_colVector.setZero();
2152 #ifdef VERBOSE
2153  serializeSparseMatrix( m_weightMatrix, "map_operator_" + std::to_string( rank ) + ".txt" );
2154 #endif
2155 // #ifdef MOAB_HAVE_EIGEN3
2156 #endif
2157  // TODO: make this flexible and read the order from map with help of metadata
2158  m_nDofsPEl_Src = 1; // always assume FV-FV maps are read from file
2159  m_nDofsPEl_Dest = 1; // always assume FV-FV maps are read from file
2160 
2161  return moab::MB_SUCCESS;
2162 }
2163 
2164 ///////////////////////////////////////////////////////////////////////////////