Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
iMOAB.cpp
Go to the documentation of this file.
1 /**
2  * @file iMOAB.cpp
3  * @brief Implementation of iMOAB C API for MOAB mesh database operations
4  *
5  * This file implements the iMOAB interface - a C API for MOAB (Mesh Oriented datABase)
6  * that provides simplified access to MOAB functionality for coupled applications,
7  * particularly in climate and multiphysics simulations.
8  */
9 
10 /**
11  * @defgroup iMOAB iMOAB C Interface
12  * @brief C API for MOAB mesh database operations in coupled applications
13  *
14  * The iMOAB interface provides a simplified C API for accessing MOAB functionality,
15  * designed for use in coupled climate and multiphysics applications. It handles
16  * mesh I/O, parallel communication, remapping, and data transfer between components.
17  *
18  * @{
19  */
20 
21 /**
22  * @defgroup iMOABInit Initialization and Finalization
23  * @brief Functions for initializing and finalizing the iMOAB library
24  * @ingroup iMOAB
25  */
26 
27 /**
28  * @defgroup iMOABApp Application Management
29  * @brief Functions for registering and managing application instances
30  * @ingroup iMOAB
31  */
32 
33 /**
34  * @defgroup iMOABIO Mesh I/O Operations
35  * @brief Functions for reading and writing mesh files
36  * @ingroup iMOAB
37  */
38 
39 /**
40  * @defgroup iMOABQuery Mesh Query Operations
41  * @brief Functions for querying mesh information (vertices, elements, blocks, BCs)
42  * @ingroup iMOAB
43  */
44 
45 /**
46  * @defgroup iMOABTag Tag Operations
47  * @brief Functions for defining and accessing tag data on mesh entities
48  * @ingroup iMOAB
49  */
50 
51 /**
52  * @defgroup iMOABParallel Parallel Communication
53  * @brief Functions for parallel mesh operations and data transfer
54  * @ingroup iMOAB
55  */
56 
57 /**
58  * @defgroup iMOABRemap Remapping and Intersection
59  * @brief Functions for mesh intersection and field remapping (TempestRemap integration)
60  * @ingroup iMOAB
61  */
62 
63 /** @} */ // end of iMOAB group
64 
65 #include "moab/MOABConfig.h"
66 #include "moab/Core.hpp"
67 
68 #ifdef MOAB_HAVE_MPI
69 #include "moab_mpi.h"
70 #include "moab/ParallelComm.hpp"
71 #include "moab/ParCommGraph.hpp"
74 #endif
75 #include "DebugOutput.hpp"
76 #include "moab/iMOAB.h"
77 
78 /* this is needed because of direct access to hdf5/mhdf */
79 #ifdef MOAB_HAVE_HDF5
80 #include "mhdf.h"
81 #include <H5Tpublic.h>
82 #endif
83 
84 #include "moab/CartVect.hpp"
85 #include "MBTagConventions.hpp"
86 #include "moab/MeshTopoUtil.hpp"
87 #include "moab/ReadUtilIface.hpp"
88 #include "moab/MergeMesh.hpp"
89 
90 #ifdef MOAB_HAVE_TEMPESTREMAP
91 #include "STLStringHelper.h"
93 
96 #endif
97 
98 // C++ includes
99 #include <cassert>
100 #include <sstream>
101 #include <iostream>
102 
103 using namespace moab;
104 
105 // #define VERBOSE
106 
107 // global variables ; should they be organized in a structure, for easier references?
108 // or how do we keep them global?
109 
110 #ifdef __cplusplus
111 extern "C" {
112 #endif
113 
114 #ifdef MOAB_HAVE_TEMPESTREMAP
115 struct TempestMapAppData
116 {
117  moab::TempestRemapper* remapper;
118  std::map< std::string, moab::TempestOnlineMap* > weightMaps;
119  iMOAB_AppID pid_src;
120  iMOAB_AppID pid_dest;
121  int num_src_ghost_layers; // number of ghost layers
122  int num_tgt_ghost_layers; // number of ghost layers
123 };
124 #endif
125 
126 struct appData
127 {
128  EntityHandle file_set; // primary file set containing all entities of interest
129  int global_id; // external component id, unique for application
130  std::string name; // name of the application
131  Range all_verts; // local vertices would be all_verts if no ghosting was required
132  Range local_verts; // it could include shared, but not owned at the interface
133  Range owned_verts; // owned_verts <= local_verts <= all_verts
134  Range ghost_vertices; // locally ghosted from other processors
135  Range primary_elems; // all primary entities (owned + ghosted)
136  Range owned_elems; // only owned entities
137  Range ghost_elems; // only ghosted entities (filtered)
138  int dimension; // 2 or 3, dimension of primary elements (redundant?)
139  long num_global_elements; // reunion of all elements in primary_elements; either from hdf5
140  // reading or from reduce
141  long num_global_vertices; // reunion of all nodes, after sharing is resolved; it could be
142  // determined from hdf5 reading
143  int num_ghost_layers; // number of ghost layers
145  std::map< int, int > matIndex; // map from global block id to index in mat_sets
148  std::map< std::string, Tag > tagMap;
149  std::vector< Tag > tagList;
152 
153 #ifdef MOAB_HAVE_MPI
155  // constructor for this ParCommGraph takes the joint comm and the MPI groups for each
156  // application
157  std::map< int, ParCommGraph* > pgraph; // map from context () to the parcommgraph*
158 #endif
159 
160 #ifdef MOAB_HAVE_TEMPESTREMAP
161  EntityHandle secondary_file_set; // secondary file set (typically a child set like covering mesh)
162  // so we assume only one covering set for all maps on this intx app
163  // we can have multiple weightMaps, but only one coverage set for all maps
164  TempestMapAppData tempestData;
165  std::map< std::string, std::string > metadataMap;
166 #endif
167 };
168 
170 {
171  // are there reasons to have multiple moab inits? Is ref count needed?
173  // we should also have the default tags stored, initialized
174  Tag material_tag, neumann_tag, dirichlet_tag,
175  globalID_tag; // material, neumann, dirichlet, globalID
177  int iArgc;
179 
180  std::map< std::string, int > appIdMap; // from app string (uppercase) to app id
181  std::map< int, appData > appDatas; // the same order as pcomms
182  int globalrank, worldprocs;
184 
186  {
187  MBI = 0;
188  refCountMB = 0;
189  }
190 };
191 
192 static struct GlobalContext context;
193 
194 /**
195  * @brief Initialize iMOAB library and create MOAB instance.
196  * @ingroup iMOABInit
197  *
198  * @details Initializes the iMOAB interface by creating a MOAB Core instance and setting up
199  * standard tags for material sets, boundary conditions, and global IDs. Uses reference counting
200  * to support multiple initialization calls. Detects MPI initialization status if built with MPI support.
201  *
202  * @par Initialization Steps:
203  * -# Store command-line arguments (argc/argv) for later use
204  * -# Create MOAB Core instance on first initialization (refCountMB == 0)
205  * -# Retrieve standard tag handles: MATERIAL_SET, NEUMANN_SET, DIRICHLET_SET, GLOBAL_ID
206  * -# Store tag handles in global context for efficient access
207  * -# Detect MPI initialization and store world size/rank if MPI is active
208  * -# Increment reference count to support nested initialization
209  *
210  * @param[in] argc Number of command-line arguments (can be 0)
211  * @param[in] argv Array of command-line argument strings (can be NULL if argc is 0)
212  *
213  * @pre MPI must be initialized before calling if using parallel features
214  * @post MOAB Core instance created and ready for use
215  * @post Standard tags available via context.material_tag, context.neumann_tag, etc.
216  *
217  * @note Uses reference counting - safe to call multiple times
218  * @note Fortran interface should use iMOAB_InitializeFortran() instead
219  *
220  * @return moab::MB_SUCCESS on success, error code otherwise
221  *
222  * @see iMOAB_Finalize()
223  */
225 {
226  if( argc ) IMOAB_CHECKPOINTER( argv, 1 );
227 
228  // Store command-line arguments for potential later use
229  context.iArgc = argc;
230  context.iArgv = argv; // Shallow copy - caller must maintain argv lifetime
231 
232  // Create MOAB instance only on first initialization
233  if( 0 == context.refCountMB )
234  {
235  context.MBI = new( std::nothrow ) moab::Core;
236 
237  // Retrieve standard MOAB tags that are commonly used across applications
238  const char* const shared_set_tag_names[] = { MATERIAL_SET_TAG_NAME, NEUMANN_SET_TAG_NAME,
240  // Tag purposes: materials (blocks), surface-BC (Neumann), vertex-BC (Dirichlet), global-id
241  Tag gtags[4];
242  for( int i = 0; i < 4; i++ )
243  {
244  MB_CHK_ERR(
245  context.MBI->tag_get_handle( shared_set_tag_names[i], 1, MB_TYPE_INTEGER, gtags[i], MB_TAG_ANY ) );
246  }
247 
248  // Cache standard tag handles in global context for efficient repeated access
249  context.material_tag = gtags[0]; // Material/block identification
250  context.neumann_tag = gtags[1]; // Surface boundary conditions
251  context.dirichlet_tag = gtags[2]; // Vertex boundary conditions
252  context.globalID_tag = gtags[3]; // Global entity identification
253 
254  // Check if MPI is initialized and cache world communicator info
255  context.MPI_initialized = false;
256 #ifdef MOAB_HAVE_MPI
257  int flagInit;
258  MPI_Initialized( &flagInit );
259 
260  if( flagInit && !context.MPI_initialized )
261  {
262  MPI_Comm_size( MPI_COMM_WORLD, &context.worldprocs );
263  MPI_Comm_rank( MPI_COMM_WORLD, &context.globalrank );
264  context.MPI_initialized = true;
265  }
266 #endif
267  }
268 
269  // Increment reference count to track active users
271  return moab::MB_SUCCESS;
272 }
273 
274 /**
275  * @brief Fortran-compatible wrapper for iMOAB_Initialize.
276  * @ingroup iMOABInit
277  *
278  * @details Calls iMOAB_Initialize with zero arguments, suitable for Fortran applications
279  * that don't pass command-line arguments through the interface.
280  *
281  * @return moab::MB_SUCCESS on success, error code otherwise
282  *
283  * @see iMOAB_Initialize()
284  */
286 {
287  return iMOAB_Initialize( 0, 0 );
288 }
289 
290 /**
291  * @brief Finalize iMOAB library and cleanup MOAB instance.
292  * @ingroup iMOABInit
293  *
294  * @details Decrements reference count and deletes MOAB Core instance when count reaches zero.
295  * Safe to call multiple times - must be called once for each Initialize call.
296  *
297  * @post Reference count decremented
298  * @post MOAB instance deleted when refCountMB reaches 0
299  * @post All application data should be cleaned up before final Finalize
300  *
301  * @warning Must call iMOAB_DeregisterApplication for all applications before final Finalize
302  * @note Uses reference counting to support nested Initialize/Finalize pairs
303  *
304  * @return moab::MB_SUCCESS on success
305  *
306  * @see iMOAB_Initialize()
307  */
309 {
310  // Decrement reference count
312 
313  // Delete MOAB instance only when last user finalizes
314  if( 0 == context.refCountMB )
315  {
316  delete context.MBI;
317  }
318 
319  return MB_SUCCESS;
320 }
321 
322 /**
323  * @brief Fast string-to-integer hash function for generating application IDs.
324  *
325  * @details Uses Fowler-Noll-Vo (FNV) hash algorithm to generate unique integer IDs from
326  * application names combined with component IDs. Non-cryptographic but fast and robust.
327  *
328  * @param[in] str Application name string to hash
329  * @param[in] identifier Optional component ID to incorporate into hash (default 0)
330  *
331  * @return Positive integer hash value (masked to ensure non-negative)
332  *
333  * @see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
334  */
335 static int apphash( const iMOAB_String str, int identifier = 0 )
336 {
337  std::string appstr( str );
338  // FNV-1a: Fast non-cryptographic hash by Fowler, Noll, and Vo
339  unsigned int h = 2166136261u; // FNV offset basis
340 
341  // Hash the application name string
342  for( char c : appstr )
343  {
344  h ^= static_cast< unsigned char >( c ); // XOR with byte
345  h *= 16777619u; // Multiply by FNV prime
346  }
347 
348  // Incorporate component identifier into the hash for uniqueness
349  if( identifier )
350  {
351  h ^= static_cast< unsigned int >( identifier & 0xFFFFFFFF );
352  h *= 16777619u; // FNV prime again
353  }
354  return static_cast< int >( h & 0x7FFFFFFF ); // Mask to ensure positive value
355 }
356 
357 /**
358  * @brief Register a new application instance with iMOAB.
359  * @ingroup iMOABApp
360  *
361  * @details Creates a new application context with unique ID, allocates a mesh set for data storage,
362  * and sets up parallel communicator if MPI is enabled. Each application represents a distinct
363  * component in a coupled simulation (e.g., atmosphere, ocean, land).
364  *
365  * @par Registration Process:
366  * -# Validate application name is unique (not already registered)
367  * -# Generate unique application ID using FNV hash of name + component ID
368  * -# Create MOAB mesh set for storing application's mesh data
369  * -# Initialize application data structure (appData) with default values
370  * -# Create ParallelComm instance for parallel operations if MPI enabled
371  * -# Store application context in global map indexed by application ID
372  *
373  * @param[in] app_name Unique name for this application instance
374  * @param[in] comm MPI communicator for this application (MPI builds only)
375  * @param[in] compid External component ID (must be positive)
376  * @param[out] pid Generated application ID (output parameter)
377  *
378  * @pre iMOAB_Initialize must have been called
379  * @pre app_name must be unique (not already registered)
380  * @pre compid must be positive
381  *
382  * @post Application registered and ready for mesh loading
383  * @post Application ID stored in pid
384  * @post Mesh set created and stored in context.appDatas[pid]
385  * @post ParallelComm created if MPI enabled
386  *
387  * @note Application ID is computed as hash(app_name, compid) for uniqueness
388  * @note Fortran interface should use iMOAB_RegisterApplicationFortran()
389  *
390  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if already registered or compid invalid
391  *
392  * @see iMOAB_DeregisterApplication()
393  */
395 #ifdef MOAB_HAVE_MPI
396  MPI_Comm* comm,
397 #endif
398  int* compid,
399  iMOAB_AppID pid )
400 {
401  IMOAB_CHECKPOINTER( app_name, 1 );
402 #ifdef MOAB_HAVE_MPI
403  IMOAB_CHECKPOINTER( comm, 2 );
404  IMOAB_CHECKPOINTER( compid, 3 );
405 #else
406  IMOAB_CHECKPOINTER( compid, 2 );
407 #endif
408 
409  // will create a parallel comm for this application too, so there will be a
410  // mapping from *pid to file set and to parallel comm instances
411  std::string name( app_name );
412 
413  if( context.appIdMap.find( name ) != context.appIdMap.end() )
414  {
415  std::cout << " application " << name << " already registered \n";
416  return moab::MB_FAILURE;
417  }
418 
419  // trivial hash: just use the id that the user provided and assume it is unique
420  // *pid = *compid;
421  // hash: compute a unique hash as a combination of the appname and the component id
422  *pid = apphash( app_name, *compid );
423  // store map of application name and the ID we just generated
424  context.appIdMap[name] = *pid;
425  int rankHere = 0;
426 #ifdef MOAB_HAVE_MPI
427  MPI_Comm_rank( *comm, &rankHere );
428 #endif
429  if( !rankHere )
430  std::cout << " application " << name << " with ID = " << *pid << " and external id: " << *compid
431  << " is registered now \n";
432  if( *compid <= 0 )
433  {
434  std::cout << " convention for external application is to have its id positive \n";
435  return moab::MB_FAILURE;
436  }
437 
438  // create now the file set that will be used for loading the model in
439  EntityHandle file_set;
440  MB_CHK_SET_ERR( context.MBI->create_meshset( MESHSET_SET, file_set ), "can't create file set" );
441 
442  appData app_data;
443  app_data.file_set = file_set;
444  app_data.global_id = *compid; // will be used mostly for par comm graph
445  app_data.name = name; // save the name of application
446 
447 #ifdef MOAB_HAVE_TEMPESTREMAP
448  app_data.tempestData.remapper = nullptr; // Only allocate as needed
449  app_data.tempestData.num_src_ghost_layers = 0;
450  app_data.tempestData.num_tgt_ghost_layers = 0;
451 #endif
452 
453  // set some default values
454  app_data.num_ghost_layers = 0;
455  app_data.point_cloud = false;
456  app_data.is_fortran = false;
457 #ifdef MOAB_HAVE_TEMPESTREMAP
458  app_data.secondary_file_set = app_data.file_set;
459 #endif
460 
461 #ifdef MOAB_HAVE_MPI
462  if( *comm ) app_data.pcomm = new ParallelComm( context.MBI, *comm );
463 #endif
464  context.appDatas[*pid] = app_data; // Store application data indexed by generated ID
465  return moab::MB_SUCCESS;
466 }
467 
468 /**
469  * @brief Fortran-compatible wrapper for iMOAB_RegisterApplication.
470  * @ingroup iMOABApp
471  *
472  * @details Registers application from Fortran code by converting Fortran MPI communicator to C
473  * and calling C-style registration. Sets is_fortran flag to enable proper MPI handle conversions.
474  *
475  * @param[in] app_name Unique name for this application instance
476  * @param[in] comm Fortran MPI communicator (integer handle, MPI builds only)
477  * @param[in] compid External component ID (must be positive)
478  * @param[out] pid Generated application ID (output parameter)
479  *
480  * @note Automatically converts Fortran MPI_Comm to C MPI_Comm using MPI_Comm_f2c
481  * @note Sets is_fortran flag in application data for future MPI handle conversions
482  *
483  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE otherwise
484  *
485  * @see iMOAB_RegisterApplication(), MPI_Comm_f2c()
486  */
488 #ifdef MOAB_HAVE_MPI
489  int* comm,
490 #endif
491  int* compid,
492  iMOAB_AppID pid )
493 {
494  IMOAB_CHECKPOINTER( app_name, 1 );
495 #ifdef MOAB_HAVE_MPI
496  IMOAB_CHECKPOINTER( comm, 2 );
497  IMOAB_CHECKPOINTER( compid, 3 );
498 #else
499  IMOAB_CHECKPOINTER( compid, 2 );
500 #endif
501 
502  ErrCode err;
503  assert( app_name != nullptr );
504  std::string name( app_name );
505 
506 #ifdef MOAB_HAVE_MPI
507  MPI_Comm ccomm;
508  if( comm )
509  {
510  // Convert from Fortran communicator (integer) to C communicator
511  // See MPI standard: http://www.mpi-forum.org/docs/mpi-2.2/mpi22-report/node361.htm
512  ccomm = MPI_Comm_f2c( (MPI_Fint)*comm );
513  }
514 #endif
515 
516  // Call C-style registration function with converted communicator
517  err = iMOAB_RegisterApplication( app_name,
518 #ifdef MOAB_HAVE_MPI
519  &ccomm,
520 #endif
521  compid, pid );
522 
523  // Mark application as Fortran-based for proper MPI handle conversions in future calls
524  context.appDatas[*pid].is_fortran = true;
525 
526  return err;
527 }
528 
529 /**
530  * @brief Deregister an application instance and cleanup associated resources.
531  * @ingroup iMOABApp
532  *
533  * @details Removes application from iMOAB, deletes mesh entities, frees parallel communicator,
534  * and cleans up all associated data structures. Must be called before iMOAB_Finalize().
535  *
536  * @par Cleanup Process:
537  * -# Validate application ID exists
538  * -# Delete all mesh entities in application's file set
539  * -# Delete communication graphs (ParCommGraph instances)
540  * -# Delete parallel communicator if MPI enabled
541  * -# Delete TempestRemap objects if present
542  * -# Delete application's mesh set
543  * -# Remove application from global context maps
544  *
545  * @param[in] pid Application ID to deregister
546  *
547  * @pre Application must have been registered via iMOAB_RegisterApplication
548  * @post All mesh data deleted
549  * @post Parallel communicator deleted
550  * @post Application ID invalid and cannot be reused
551  *
552  * @warning Do not access application ID after deregistration
553  * @note Safe to deregister in any order relative to other applications
554  *
555  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if application not found
556  *
557  * @see iMOAB_RegisterApplication()
558  */
560 {
561  // Look up application in global context
562  auto appIterator = context.appDatas.find( *pid );
563 
564  // Validate application exists before attempting deletion
565  if( appIterator == context.appDatas.end() ) return MB_FAILURE;
566 
567  // we found the application
568  appData& data = appIterator->second;
569  int rankHere = 0;
570 #ifdef MOAB_HAVE_MPI
571  rankHere = data.pcomm->rank();
572 #endif
573  if( !rankHere )
574  std::cout << " application with ID: " << *pid << " global id: " << data.global_id << " name: " << data.name
575  << " is de-registered now \n";
576 
577  EntityHandle fileSet = data.file_set;
578  // get all entities part of the file set
579  Range fileents;
580  MB_CHK_SET_ERR( context.MBI->get_entities_by_handle( fileSet, fileents, /*recursive */ true ),
581  "can't get file entities" );
582  fileents.insert( fileSet );
584  "can't get file entities" ); // append all mesh sets
585 
586 #ifdef MOAB_HAVE_TEMPESTREMAP
587  if( data.tempestData.remapper ) delete data.tempestData.remapper;
588  if( data.tempestData.weightMaps.size() ) data.tempestData.weightMaps.clear();
589 #endif
590 
591 #ifdef MOAB_HAVE_MPI
592  // NOTE: we could get the pco also with the following workflow.
593  // ParallelComm * pcomm = ParallelComm::get_pcomm(context.MBI, *pid);
594 
595  auto& pargs = data.pgraph;
596  // free the parallel comm graphs associated with this app
597  for( auto mt = pargs.begin(); mt != pargs.end(); ++mt )
598  {
599  ParCommGraph* pgr = mt->second;
600  if( pgr != nullptr )
601  {
602  delete pgr;
603  pgr = nullptr;
604  }
605  }
606  // now free the ParallelComm resources
607  if( data.pcomm )
608  {
609  delete data.pcomm;
610  data.pcomm = nullptr;
611  }
612 #endif
613 
614  // delete first all except vertices
615  Range vertices = fileents.subset_by_type( MBVERTEX );
616  Range noverts = subtract( fileents, vertices );
617 
618  MB_CHK_SET_ERR( context.MBI->delete_entities( noverts ), "can't delete entities" );
619  // now retrieve connected elements that still exist (maybe in other sets, pids?)
620  Range adj_ents_left;
621  MB_CHK_SET_ERR( context.MBI->get_adjacencies( vertices, 1, false, adj_ents_left, Interface::UNION ),
622  "can't get 1D adjacencies" );
623  MB_CHK_SET_ERR( context.MBI->get_adjacencies( vertices, 2, false, adj_ents_left, Interface::UNION ),
624  "can't get 2D adjacencies" );
625  MB_CHK_SET_ERR( context.MBI->get_adjacencies( vertices, 3, false, adj_ents_left, Interface::UNION ),
626  "can't get 3D adjacencies" );
627 
628  if( !adj_ents_left.empty() )
629  {
630  Range conn_verts;
631  MB_CHK_SET_ERR( context.MBI->get_connectivity( adj_ents_left, conn_verts ), "can't get connectivity" );
632  vertices = subtract( vertices, conn_verts );
633  }
634 
635  MB_CHK_SET_ERR( context.MBI->delete_entities( vertices ), "can't delete vertices" );
636 
637  for( auto mit = context.appIdMap.begin(); mit != context.appIdMap.end(); ++mit )
638  {
639  if( *pid == mit->second )
640  {
641 #ifdef MOAB_HAVE_MPI
642  if( data.pcomm )
643  {
644  delete data.pcomm;
645  data.pcomm = nullptr;
646  }
647 #endif
648  context.appIdMap.erase( mit );
649  break;
650  }
651  }
652 
653  // now we can finally delete the application data itself
654  context.appDatas.erase( appIterator );
655 
656  return moab::MB_SUCCESS;
657 }
658 
659 /**
660  * @brief Fortran-compatible wrapper for iMOAB_DeregisterApplication.
661  * @ingroup iMOABApp
662  *
663  * @details Deregisters application from Fortran code by clearing Fortran flag and calling
664  * C-style deregistration.
665  *
666  * @param[in] pid Application ID to deregister
667  *
668  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if application not found
669  *
670  * @see iMOAB_DeregisterApplication()
671  */
673 {
674  // Clear Fortran-specific flag before passing to C-style deregistration
675  context.appDatas[*pid].is_fortran = false;
676 
677  // Release all data structure allocations via C-style function
678  return iMOAB_DeregisterApplication( pid );
679 }
680 
681 /**
682  * @brief Read mesh file header information without loading full mesh.
683  * @ingroup iMOABIO
684  *
685  * @details Quickly extracts metadata from HDF5 mesh file including vertex count, element count,
686  * spatial dimension, and partition count. Useful for memory planning before full load.
687  *
688  * @par Implementation:
689  * - Opens HDF5 file in read-only mode using mhdf library
690  * - Reads file summary without loading actual mesh data
691  * - Counts elements by type (edges, faces, regions)
692  * - Extracts partition information from parallel decomposition tags
693  *
694  * @param[in] filename Path to HDF5 mesh file
695  * @param[out] num_global_vertices Total number of vertices in mesh
696  * @param[out] num_global_elements Total number of elements in mesh (all types)
697  * @param[out] num_dimension Spatial dimension (2D or 3D)
698  * @param[out] num_parts Number of parallel partitions
699  *
700  * @pre HDF5 support must be enabled (MOAB_HAVE_HDF5)
701  * @pre File must be valid HDF5 mesh file
702  *
703  * @post Output parameters populated with file metadata
704  * @post File remains closed (no mesh data loaded)
705  *
706  * @note Lightweight operation - does not load mesh into memory
707  * @note All output parameters are optional (can be NULL)
708  * @warning Returns failure if HDF5 support not enabled
709  *
710  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE on error
711  *
712  * @see iMOAB_LoadMesh()
713  */
715  int* num_global_vertices,
716  int* num_global_elements,
717  int* num_dimension,
718  int* num_parts )
719 {
720  IMOAB_CHECKPOINTER( filename, 1 );
721  IMOAB_ASSERT( strlen( filename ), "Invalid filename length." );
722 
723 #ifdef MOAB_HAVE_HDF5
724  std::string filen( filename );
725 
726  int edges = 0;
727  int faces = 0;
728  int regions = 0;
729  if( num_global_vertices ) *num_global_vertices = 0;
730  if( num_global_elements ) *num_global_elements = 0;
731  if( num_dimension ) *num_dimension = 0;
732  if( num_parts ) *num_parts = 0;
733 
734  mhdf_FileHandle file;
735  mhdf_Status status;
736  unsigned long max_id;
737  struct mhdf_FileDesc* data;
738 
739  file = mhdf_openFile( filen.c_str(), 0, &max_id, -1, &status );
740 
741  if( mhdf_isError( &status ) )
742  {
743  fprintf( stderr, "%s: %s\n", filename, mhdf_message( &status ) );
744  return moab::MB_FAILURE;
745  }
746 
747  data = mhdf_getFileSummary( file, H5T_NATIVE_ULONG, &status,
748  1 ); // will use extra set info; will get parallel partition tag info too!
749 
750  if( mhdf_isError( &status ) )
751  {
752  fprintf( stderr, "%s: %s\n", filename, mhdf_message( &status ) );
753  return moab::MB_FAILURE;
754  }
755 
756  if( num_dimension ) *num_dimension = data->nodes.vals_per_ent;
757  if( num_global_vertices ) *num_global_vertices = (int)data->nodes.count;
758 
759  for( int i = 0; i < data->num_elem_desc; i++ )
760  {
761  struct mhdf_ElemDesc* el_desc = &( data->elems[i] );
762  struct mhdf_EntDesc* ent_d = &( el_desc->desc );
763 
764  if( 0 == strcmp( el_desc->type, mhdf_EDGE_TYPE_NAME ) )
765  {
766  edges += ent_d->count;
767  }
768 
769  if( 0 == strcmp( el_desc->type, mhdf_TRI_TYPE_NAME ) )
770  {
771  faces += ent_d->count;
772  }
773 
774  if( 0 == strcmp( el_desc->type, mhdf_QUAD_TYPE_NAME ) )
775  {
776  faces += ent_d->count;
777  }
778 
779  if( 0 == strcmp( el_desc->type, mhdf_POLYGON_TYPE_NAME ) )
780  {
781  faces += ent_d->count;
782  }
783 
784  if( 0 == strcmp( el_desc->type, mhdf_TET_TYPE_NAME ) )
785  {
786  regions += ent_d->count;
787  }
788 
789  if( 0 == strcmp( el_desc->type, mhdf_PYRAMID_TYPE_NAME ) )
790  {
791  regions += ent_d->count;
792  }
793 
794  if( 0 == strcmp( el_desc->type, mhdf_PRISM_TYPE_NAME ) )
795  {
796  regions += ent_d->count;
797  }
798 
799  if( 0 == strcmp( el_desc->type, mdhf_KNIFE_TYPE_NAME ) )
800  {
801  regions += ent_d->count;
802  }
803 
804  if( 0 == strcmp( el_desc->type, mdhf_HEX_TYPE_NAME ) )
805  {
806  regions += ent_d->count;
807  }
808 
809  if( 0 == strcmp( el_desc->type, mhdf_POLYHEDRON_TYPE_NAME ) )
810  {
811  regions += ent_d->count;
812  }
813 
814  if( 0 == strcmp( el_desc->type, mhdf_SEPTAHEDRON_TYPE_NAME ) )
815  {
816  regions += ent_d->count;
817  }
818  }
819 
820  if( num_parts ) *num_parts = data->numEntSets[0];
821 
822  // is this required?
823  if( edges > 0 )
824  {
825  if( num_dimension ) *num_dimension = 1; // I don't think it will ever return 1
826  if( num_global_elements ) *num_global_elements = edges;
827  }
828 
829  if( faces > 0 )
830  {
831  if( num_dimension ) *num_dimension = 2;
832  if( num_global_elements ) *num_global_elements = faces;
833  }
834 
835  if( regions > 0 )
836  {
837  if( num_dimension ) *num_dimension = 3;
838  if( num_global_elements ) *num_global_elements = regions;
839  }
840 
841  mhdf_closeFile( file, &status );
842 
843  free( data );
844 
845 #else
846  std::cout << filename
847  << ": Please reconfigure with HDF5. Cannot retrieve header information for file "
848  "formats other than a h5m file.\n";
849  if( num_global_vertices ) *num_global_vertices = 0;
850  if( num_global_elements ) *num_global_elements = 0;
851  if( num_dimension ) *num_dimension = 0;
852  if( num_parts ) *num_parts = 0;
853 #endif
854 
855  return moab::MB_SUCCESS;
856 }
857 
858 /**
859  * @brief Load mesh file into application's mesh set.
860  * @ingroup iMOABIO
861  *
862  * @details Loads mesh from file (HDF5, Exodus, VTK, etc.) into application's mesh set with optional
863  * ghost layer exchange for parallel simulations. Automatically configures parallel reading options.
864  *
865  * @par Loading Process:
866  * -# Construct read options string from user options + automatic parallel settings
867  * -# Add PARALLEL_COMM option for parallel HDF5/NetCDF files
868  * -# Add ghost layer options (PARALLEL_GHOSTS, PARTITION_DISTRIBUTE) if requested
869  * -# Load mesh into application's file set using MOAB::load_file()
870  * -# Update mesh information (vertex/element counts, ranges, etc.)
871  * -# Exchange ghost layers if num_ghost_layers > 0
872  *
873  * @param[in] pid Application ID
874  * @param[in] filename Path to mesh file to load
875  * @param[in] read_options Optional reader options (semicolon-separated, can be NULL)
876  * @param[in] num_ghost_layers Number of ghost element layers to exchange (0 for none)
877  *
878  * @pre Application must be registered via iMOAB_RegisterApplication
879  * @pre File must exist and be readable
880  * @pre For parallel: MPI must be initialized
881  *
882  * @post Mesh loaded into application's file set
883  * @post Mesh info updated (vertices, elements, ranges)
884  * @post Ghost layers exchanged if requested
885  *
886  * @note Supported formats: HDF5 (.h5m), Exodus (.exo), VTK (.vtk), NetCDF (.nc), etc.
887  * @note PARALLEL_COMM option automatically added for parallel HDF5/NetCDF
888  * @note Ghost exchange requires PARALLEL_GHOSTS and PARTITION_DISTRIBUTE options
889  * @warning Do not manually specify PARALLEL_COMM in read_options (will cause error)
890  *
891  * @return moab::MB_SUCCESS on success, error code otherwise
892  *
893  * @see iMOAB_WriteMesh(), iMOAB_UpdateMeshInfo()
894  */
896  const iMOAB_String filename,
897  const iMOAB_String read_options,
898  int* num_ghost_layers )
899 {
900  IMOAB_CHECKPOINTER( filename, 2 );
901  IMOAB_ASSERT( strlen( filename ), "Invalid filename length." );
902  IMOAB_CHECKPOINTER( num_ghost_layers, 4 );
903 
904  // make sure we use the file set and pcomm associated with the *pid
905  std::ostringstream newopts;
906  if( read_options ) newopts << read_options;
907 
908 #ifdef MOAB_HAVE_MPI
909 
911  {
912  if( context.worldprocs > 1 )
913  {
914  std::string opts( ( read_options ? read_options : "" ) );
915  std::string pcid( "PARALLEL_COMM=" );
916  std::size_t found = opts.find( pcid );
917 
918  if( found != std::string::npos )
919  {
920  std::cerr << " cannot specify PARALLEL_COMM option, it is implicit \n";
921  return moab::MB_FAILURE;
922  }
923 
924  // in serial, apply PARALLEL_COMM option only for h5m files; it does not work for .g
925  // files (used in test_remapping)
926  std::string filen( filename );
927  std::string::size_type idx = filen.rfind( '.' );
928 
929  if( idx != std::string::npos )
930  {
931  ParallelComm* pco = context.appDatas[*pid].pcomm;
932  std::string extension = filen.substr( idx + 1 );
933  if( ( extension == std::string( "h5m" ) ) || ( extension == std::string( "nc" ) ) )
934  newopts << ";;PARALLEL_COMM=" << pco->get_id();
935  }
936 
937  if( *num_ghost_layers >= 1 )
938  {
939  // if we want ghosts, we will want additional entities, the last .1
940  // because the addl ents can be edges, faces that are part of the neumann sets
941  std::string pcid2( "PARALLEL_GHOSTS=" );
942  std::size_t found2 = opts.find( pcid2 );
943 
944  if( found2 != std::string::npos )
945  {
946  std::cout << " PARALLEL_GHOSTS option is already specified, ignore passed "
947  "number of layers \n";
948  }
949  else
950  {
951  // dimension of primary entities is 3 here, but it could be 2 for climate
952  // meshes; we would need to pass PARALLEL_GHOSTS explicitly for 2d meshes, for
953  // example: ";PARALLEL_GHOSTS=2.0.1"
954  newopts << ";PARALLEL_GHOSTS=3.0." << *num_ghost_layers << ".3";
955  }
956  }
957  }
958  }
959 #else
960  IMOAB_ASSERT( *num_ghost_layers == 0, "Cannot provide ghost layers in serial." );
961 #endif
962 
963  // Now let us actually load the MOAB file with the appropriate read options
964  MB_CHK_SET_ERR( context.MBI->load_file( filename, &context.appDatas[*pid].file_set, newopts.str().c_str() ),
965  "can't load file" );
966 
967 #ifdef VERBOSE
968  // some debugging stuff
969  std::ostringstream outfile;
970 #ifdef MOAB_HAVE_MPI
971  ParallelComm* pco = context.appDatas[*pid].pcomm;
972  int rank = pco->rank();
973  int nprocs = pco->size();
974  outfile << "TaskMesh_n" << nprocs << "." << rank << ".h5m";
975 #else
976  outfile << "TaskMesh_n1.0.h5m";
977 #endif
978  // the mesh contains ghosts too, but they are not part of mat/neumann set
979  // write in serial the file, to see what tags are missing
980  MB_CHK_SET_ERR( context.MBI->write_file( outfile.str().c_str() ),
981  "can't write file" ); // everything on current task, written in serial
982 #endif
983 
984  // Update ghost layer information
985  context.appDatas[*pid].num_ghost_layers = *num_ghost_layers;
986 
987  // Update mesh information
988  return iMOAB_UpdateMeshInfo( pid );
989 }
990 
992  const iMOAB_String filename,
993  const iMOAB_String write_options,
994  bool primary_set = true )
995 {
996  IMOAB_CHECKPOINTER( filename, 2 );
997  IMOAB_ASSERT( strlen( filename ), "Invalid filename length." );
998 
999  appData& data = context.appDatas[*pid];
1000  EntityHandle fileSet = ( primary_set ? data.file_set : 0 );
1001 
1002  std::ostringstream newopts;
1003 #ifdef MOAB_HAVE_MPI
1004  std::string write_opts( ( write_options ? write_options : "" ) );
1005  std::string pcid( "PARALLEL_COMM=" );
1006 
1007  if( write_opts.find( pcid ) != std::string::npos )
1008  {
1009  std::cerr << " cannot specify PARALLEL_COMM option, it is implicit \n";
1010  return moab::MB_FAILURE;
1011  }
1012 
1013  // if write in parallel, add pc option, to be sure about which ParallelComm instance is used
1014  std::string pw( "PARALLEL=WRITE_PART" );
1015  if( write_opts.find( pw ) != std::string::npos )
1016  {
1017  ParallelComm* pco = data.pcomm;
1018  newopts << "PARALLEL_COMM=" << pco->get_id() << ";";
1019  }
1020 
1021 #endif
1022 
1023 #ifdef MOAB_HAVE_TEMPESTREMAP
1024  if( !primary_set )
1025  {
1026  if( data.tempestData.remapper != nullptr )
1027  fileSet = data.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
1028  else if( data.file_set != data.secondary_file_set )
1029  fileSet = data.secondary_file_set;
1030  else
1031  MB_CHK_SET_ERR( moab::MB_FAILURE, "Invalid secondary file set handle" );
1032  }
1033 #endif
1034 
1035  // append user write options to the one we have built
1036  if( write_options ) newopts << write_options;
1037 
1038  std::vector< Tag > copyTagList = data.tagList;
1039  // append Global ID and Parallel Partition
1040  std::string gid_name_tag( "GLOBAL_ID" );
1041 
1042  // export global id tag, we need it always
1043  if( data.tagMap.find( gid_name_tag ) == data.tagMap.end() )
1044  {
1045  Tag gid = context.MBI->globalId_tag();
1046  copyTagList.push_back( gid );
1047  }
1048  // also Parallel_Partition PARALLEL_PARTITION
1049  std::string pp_name_tag( "PARALLEL_PARTITION" );
1050 
1051  // write parallel part tag too, if it exists
1052  if( data.tagMap.find( pp_name_tag ) == data.tagMap.end() )
1053  {
1054  Tag ptag;
1055  context.MBI->tag_get_handle( pp_name_tag.c_str(), ptag );
1056  if( ptag ) copyTagList.push_back( ptag );
1057  }
1058 
1059  // Now let us actually write the file to disk with appropriate options
1060  if( primary_set )
1061  {
1062  MB_CHK_ERR( context.MBI->write_file( filename, 0, newopts.str().c_str(), &fileSet, 1, copyTagList.data(),
1063  copyTagList.size() ) );
1064  }
1065  else
1066  {
1067  MB_CHK_ERR( context.MBI->write_file( filename, 0, newopts.str().c_str(), &fileSet, 1 ) );
1068  }
1069 
1070  return moab::MB_SUCCESS;
1071 }
1072 
1073 /**
1074  * @brief Write application's mesh to file.
1075  * @ingroup iMOABIO
1076  *
1077  * @details Writes mesh data from application's mesh set to file in various formats (HDF5, Exodus, VTK, etc.).
1078  * Automatically includes GLOBAL_ID and PARALLEL_PARTITION tags for parallel simulations.
1079  *
1080  * @par Writing Process:
1081  * -# Construct write options from user options + automatic parallel settings
1082  * -# Add PARALLEL=WRITE_PART for parallel HDF5 output if needed
1083  * -# Append GLOBAL_ID tag if not already in tag list
1084  * -# Append PARALLEL_PARTITION tag if available and not in tag list
1085  * -# Write mesh using MOAB::write_file() with appropriate options
1086  *
1087  * @param[in] pid Application ID
1088  * @param[in] filename Output file path
1089  * @param[in] write_options Optional writer options (semicolon-separated, can be NULL)
1090  *
1091  * @pre Application must be registered and mesh loaded
1092  * @pre For parallel: output directory must be writable by all processes
1093  *
1094  * @post Mesh written to file with requested format
1095  * @post GLOBAL_ID and PARALLEL_PARTITION tags included if available
1096  *
1097  * @note Supported formats: HDF5 (.h5m), Exodus (.exo), VTK (.vtk), etc.
1098  * @note For parallel writes, use PARALLEL=WRITE_PART option
1099  * @note Tags in application's tagList are automatically written
1100  *
1101  * @return moab::MB_SUCCESS on success, error code otherwise
1102  *
1103  * @see iMOAB_LoadMesh(), iMOAB_WriteLocalMesh()
1104  */
1105 ErrCode iMOAB_WriteMesh( iMOAB_AppID pid, const iMOAB_String filename, const iMOAB_String write_options )
1106 {
1107  return internal_WriteMesh( pid, filename, write_options );
1108 }
1109 
1110 /**
1111  * @brief Write each process's local mesh to separate file for debugging.
1112  * @ingroup iMOABIO
1113  *
1114  * @details Creates per-process HDF5 file with naming convention: prefix_nprocs_rank.h5m.
1115  * Useful for debugging parallel mesh distribution and ghost layer issues.
1116  *
1117  * @param[in] pid Application ID
1118  * @param[in] prefix Output file prefix (rank and process count appended automatically)
1119  *
1120  * @pre Application must be registered and mesh loaded
1121  *
1122  * @post Each process writes its local mesh to: prefix_<nprocs>_<rank>.h5m
1123  *
1124  * @note Output files show per-process mesh partition (useful for parallel debugging)
1125  * @note Files include both owned and ghost entities
1126  *
1127  * @return moab::MB_SUCCESS on success, error code otherwise
1128  *
1129  * @see iMOAB_WriteMesh()
1130  */
1132 {
1133  IMOAB_CHECKPOINTER( prefix, 2 );
1134  IMOAB_ASSERT( strlen( prefix ), "Invalid prefix string length." );
1135 
1136  // Construct filename with parallel decomposition info: prefix_nprocs_rank.h5m
1137  std::ostringstream file_name;
1138  int rank = 0, size = 1;
1139 #ifdef MOAB_HAVE_MPI
1140  ParallelComm* pcomm = context.appDatas[*pid].pcomm;
1141  rank = pcomm->rank();
1142  size = pcomm->size();
1143 #endif
1144  file_name << prefix << "_" << size << "_" << rank << ".h5m";
1145 
1146  // Write this process's local mesh partition to its own file
1147  MB_CHK_ERR( context.MBI->write_file( file_name.str().c_str(), 0, 0, &context.appDatas[*pid].file_set, 1 ) );
1148 
1149  return moab::MB_SUCCESS;
1150 }
1151 
1152 /**
1153  * @brief Update application's mesh information after loading or modification.
1154  * @ingroup iMOABQuery
1155  *
1156  * @details Refreshes all cached mesh metadata including vertex/element counts, ranges, owned/ghost
1157  * entities, and material/boundary condition sets. Must be called after mesh loading or ghosting.
1158  *
1159  * @par Update Process:
1160  * -# Clear all existing cached ranges (vertices, elements, sets)
1161  * -# Query all vertices from file set
1162  * -# Determine mesh dimension (3D → 2D → 1D → 0D) by checking for elements
1163  * -# Extract primary elements of detected dimension
1164  * -# Separate owned vs ghost entities based on parallel ownership
1165  * -# Query material sets (MATERIAL_SET tag)
1166  * -# Query boundary condition sets (NEUMANN_SET, DIRICHLET_SET tags)
1167  * -# Separate local vs owned vertices based on parallel partitioning
1168  *
1169  * @param[in] pid Application ID
1170  *
1171  * @pre Application must be registered and mesh loaded
1172  *
1173  * @post Mesh dimension determined and cached
1174  * @post All vertex/element ranges updated
1175  * @post Owned/ghost entity ranges computed
1176  * @post Material and BC sets identified
1177  *
1178  * @note Call this after iMOAB_LoadMesh or ghost layer exchange
1179  * @note Automatically called by iMOAB_LoadMesh
1180  * @warning Clears all existing cached mesh information
1181  *
1182  * @return moab::MB_SUCCESS on success, error code otherwise
1183  *
1184  * @see iMOAB_LoadMesh(), iMOAB_GetMeshInfo()
1185  */
1187 {
1188  // this will include ghost elements info
1189  appData& data = context.appDatas[*pid];
1190  EntityHandle fileSet = data.file_set;
1191 
1192  // first clear all data ranges; this can be called after ghosting
1193  data.all_verts.clear();
1194  data.primary_elems.clear();
1195  data.local_verts.clear();
1196  data.owned_verts.clear();
1197  data.ghost_vertices.clear();
1198  data.owned_elems.clear();
1199  data.ghost_elems.clear();
1200  data.mat_sets.clear();
1201  data.neu_sets.clear();
1202  data.diri_sets.clear();
1203 
1204  // Let us get all the vertex entities
1206  "can't get vertices" );
1207 
1208  // Let us check first entities of dimension = 3
1209  data.dimension = 3;
1211  "can't get primary elements" );
1212 
1213  if( data.primary_elems.empty() )
1214  {
1215  // Now 3-D elements. Let us check entities of dimension = 2
1216  data.dimension = 2;
1218  "can't get primary elements" );
1219 
1220  if( data.primary_elems.empty() )
1221  {
1222  // Now 3-D/2-D elements. Let us check entities of dimension = 1
1223  data.dimension = 1;
1225  "can't get primary elements" );
1226 
1227  if( data.primary_elems.empty() )
1228  {
1229  // no elements of dimension 1 or 2 or 3; it could happen for point clouds
1230  data.dimension = 0;
1231  }
1232  }
1233  }
1234 
1235  // check if the current mesh is just a point cloud
1236  data.point_cloud = ( ( data.primary_elems.size() == 0 && data.all_verts.size() > 0 ) || data.dimension == 0 );
1237 
1238 #ifdef MOAB_HAVE_MPI
1239 
1240  if( context.MPI_initialized )
1241  {
1242  ParallelComm* pco = context.appDatas[*pid].pcomm;
1243 
1244  // filter ghost vertices, from local
1246  "can't filter ghost vertices" );
1247 
1248  // Store handles for all ghosted entities
1249  data.ghost_vertices = subtract( data.all_verts, data.local_verts );
1250 
1251  // filter ghost elements, from local
1253  "can't filter ghost elements" );
1254 
1255  data.ghost_elems = subtract( data.primary_elems, data.owned_elems );
1256  // now update global number of primary cells and global number of vertices
1257  // determine first number of owned vertices
1258  // Get local owned vertices
1260  "can't filter ghost vertices" );
1261  int local[2], global[2];
1262  local[0] = data.owned_verts.size();
1263  local[1] = data.owned_elems.size();
1264  MPI_Allreduce( local, global, 2, MPI_INT, MPI_SUM, pco->comm() );
1265  MB_CHK_SET_ERR( iMOAB_SetGlobalInfo( pid, &( global[0] ), &( global[1] ) ), "can't set global info" );
1266  }
1267  else
1268  {
1269  data.local_verts = data.all_verts;
1270  data.owned_elems = data.primary_elems;
1271  }
1272 
1273 #else
1274 
1275  data.local_verts = data.all_verts;
1276  data.owned_elems = data.primary_elems;
1277 
1278 #endif
1279 
1280  // Get the references for some standard internal tags such as material blocks, BCs, etc
1282  data.mat_sets, Interface::UNION ),
1283  "can't get material sets" );
1284 
1286  data.neu_sets, Interface::UNION ),
1287  "can't get neumann sets" );
1288 
1290  data.diri_sets, Interface::UNION ),
1291  "can't get dirichlet sets" );
1292 
1293  return moab::MB_SUCCESS;
1294 }
1295 
1296 /**
1297  * @brief Query mesh statistics including vertices, elements, and boundary conditions.
1298  * @ingroup iMOABQuery
1299  *
1300  * @details Retrieves counts of vertices, elements, material blocks, and boundary condition sets.
1301  * Each output array has 3 components: [owned, ghost, total]. Useful for memory allocation
1302  * and understanding parallel mesh distribution.
1303  *
1304  * @par Array Format:
1305  * All output arrays use 3-element format: [owned, ghost, total]
1306  * - owned: Entities owned by this process
1307  * - ghost: Ghost/halo entities from neighboring processes
1308  * - total: Sum of owned and ghost entities
1309  *
1310  * @param[in] pid Application ID
1311  * @param[out] num_visible_vertices Vertex counts [local(non-ghost), ghost, total]
1312  * @param[out] num_visible_elements Element counts [owned, ghost, total]
1313  * @param[out] num_visible_blocks Material block counts [owned, ghost, total]
1314  * @param[out] num_visible_surfaceBC Surface BC counts (total face count across all BC sets)
1315  * @param[out] num_visible_vertexBC Vertex BC counts (total vertex count across all BC sets)
1316  *
1317  * @pre Application must be registered and mesh loaded
1318  * @pre iMOAB_UpdateMeshInfo must have been called
1319  *
1320  * @post Output arrays populated with mesh statistics
1321  *
1322  * @note All parameters are optional (can be NULL if not needed)
1323  * @note Surface BC count includes all faces in Neumann sets
1324  * @note Vertex BC count includes all vertices in Dirichlet sets
1325  * @note Material blocks queried via MATERIAL_SET tag
1326  * @note BCs queried via NEUMANN_SET and DIRICHLET_SET tags
1327  *
1328  * @return moab::MB_SUCCESS on success, error code otherwise
1329  *
1330  * @see iMOAB_UpdateMeshInfo(), iMOAB_LoadMesh()
1331  */
1333  int* num_visible_vertices,
1334  int* num_visible_elements,
1335  int* num_visible_blocks,
1336  int* num_visible_surfaceBC,
1337  int* num_visible_vertexBC )
1338 {
1339  appData& data = context.appDatas[*pid];
1340  EntityHandle fileSet = data.file_set;
1341 
1342  // this will include ghost elements
1343  // first clear all data ranges; this can be called after ghosting
1344  if( num_visible_elements )
1345  {
1346  num_visible_elements[2] = static_cast< int >( data.primary_elems.size() );
1347  // separate ghost and local/owned primary elements
1348  num_visible_elements[0] = static_cast< int >( data.owned_elems.size() );
1349  num_visible_elements[1] = static_cast< int >( data.ghost_elems.size() );
1350  }
1351  if( num_visible_vertices )
1352  {
1353  num_visible_vertices[2] = static_cast< int >( data.all_verts.size() );
1354  num_visible_vertices[1] = static_cast< int >( data.ghost_vertices.size() );
1355  // local are those that are not ghosts; they may include shared, not owned vertices
1356  num_visible_vertices[0] = num_visible_vertices[2] - num_visible_vertices[1];
1357  }
1358 
1359  if( num_visible_blocks )
1360  {
1362  1, data.mat_sets, Interface::UNION ),
1363  "can't get material sets" );
1364 
1365  num_visible_blocks[2] = data.mat_sets.size();
1366  num_visible_blocks[0] = num_visible_blocks[2];
1367  num_visible_blocks[1] = 0;
1368  }
1369 
1370  if( num_visible_surfaceBC )
1371  {
1373  data.neu_sets, Interface::UNION ),
1374  "can't get neumann sets" );
1375 
1376  num_visible_surfaceBC[2] = 0;
1377  // count how many faces are in each neu set, and how many regions are
1378  // adjacent to them;
1379  int numNeuSets = (int)data.neu_sets.size();
1380 
1381  for( int i = 0; i < numNeuSets; i++ )
1382  {
1383  Range subents;
1384  EntityHandle nset = data.neu_sets[i];
1385  MB_CHK_SET_ERR( context.MBI->get_entities_by_dimension( nset, data.dimension - 1, subents ),
1386  "can't get neumann sets" );
1387 
1388  for( Range::iterator it = subents.begin(); it != subents.end(); ++it )
1389  {
1390  EntityHandle subent = *it;
1391  Range adjPrimaryEnts;
1392  MB_CHK_SET_ERR( context.MBI->get_adjacencies( &subent, 1, data.dimension, false, adjPrimaryEnts ),
1393  "can't get adjacencies" );
1394 
1395  num_visible_surfaceBC[2] += (int)adjPrimaryEnts.size();
1396  }
1397  }
1398 
1399  num_visible_surfaceBC[0] = num_visible_surfaceBC[2];
1400  num_visible_surfaceBC[1] = 0;
1401  }
1402 
1403  if( num_visible_vertexBC )
1404  {
1406  1, data.diri_sets, Interface::UNION ),
1407  "can't get dirichlet sets" );
1408 
1409  num_visible_vertexBC[2] = 0;
1410  int numDiriSets = (int)data.diri_sets.size();
1411 
1412  for( int i = 0; i < numDiriSets; i++ )
1413  {
1414  Range verts;
1415  EntityHandle diset = data.diri_sets[i];
1416  MB_CHK_SET_ERR( context.MBI->get_entities_by_dimension( diset, 0, verts ), "can't get dirichlet sets" );
1417 
1418  num_visible_vertexBC[2] += (int)verts.size();
1419  }
1420 
1421  num_visible_vertexBC[0] = num_visible_vertexBC[2];
1422  num_visible_vertexBC[1] = 0;
1423  }
1424 
1425  return moab::MB_SUCCESS;
1426 }
1427 
1428 /**
1429  * @brief Retrieve global IDs for all vertices in application's mesh.
1430  * @ingroup iMOABQuery
1431  *
1432  * @details Extracts GLOBAL_ID tag values for all vertices. Global IDs are unique across all
1433  * processes and are used for parallel mesh operations and entity identification.
1434  *
1435  * @param[in] pid Application ID
1436  * @param[in] vertices_length Number of vertices (must match actual vertex count)
1437  * @param[out] global_vertex_ID Array of global IDs (size = vertices_length)
1438  *
1439  * @pre Application must be registered and mesh loaded
1440  * @pre vertices_length must equal total vertex count from iMOAB_GetMeshInfo
1441  * @pre global_vertex_ID array must be pre-allocated
1442  *
1443  * @post global_vertex_ID populated with GLOBAL_ID tag values
1444  *
1445  * @note Global IDs are 1-indexed and unique across all processes
1446  * @note Array must be sized correctly or assertion will fail
1447  *
1448  * @return moab::MB_SUCCESS on success, error code otherwise
1449  *
1450  * @see iMOAB_GetMeshInfo(), iMOAB_GetVertexOwnership()
1451  */
1452 ErrCode iMOAB_GetVertexID( iMOAB_AppID pid, int* vertices_length, iMOAB_GlobalID* global_vertex_ID )
1453 {
1454  IMOAB_CHECKPOINTER( vertices_length, 2 );
1455  IMOAB_CHECKPOINTER( global_vertex_ID, 3 );
1456 
1457  const Range& verts = context.appDatas[*pid].all_verts;
1458 
1459  // Validate array size matches actual vertex count to prevent buffer overruns
1460  IMOAB_ASSERT( *vertices_length == static_cast< int >( verts.size() ), "Invalid vertices length provided" );
1461 
1462  // Extract GLOBAL_ID tag values for all vertices in order
1463  return context.MBI->tag_get_data( context.globalID_tag, verts, global_vertex_ID );
1464 }
1465 
1466 /**
1467  * @brief Retrieve parallel ownership information for all vertices.
1468  * @ingroup iMOABQuery
1469  *
1470  * @details Queries which MPI rank owns each vertex. In parallel simulations, each vertex is owned
1471  * by exactly one process, though it may be ghosted on others.
1472  *
1473  * @param[in] pid Application ID
1474  * @param[in] vertices_length Number of vertices (must match actual count)
1475  * @param[out] visible_global_rank_ID Array of owning ranks (size = vertices_length)
1476  *
1477  * @pre Application must be registered and mesh loaded
1478  * @pre For parallel: mesh must have parallel ownership information
1479  * @pre visible_global_rank_ID array must be pre-allocated
1480  *
1481  * @post visible_global_rank_ID[i] = rank that owns vertex i
1482  *
1483  * @note In serial runs, all vertices owned by rank 0
1484  * @note Ghost vertices report the rank of their owner, not local rank
1485  *
1486  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if count mismatch
1487  *
1488  * @see iMOAB_GetVertexID(), iMOAB_GetElementOwnership()
1489  */
1490 ErrCode iMOAB_GetVertexOwnership( iMOAB_AppID pid, int* vertices_length, int* visible_global_rank_ID )
1491 {
1492  assert( vertices_length && *vertices_length );
1493  assert( visible_global_rank_ID );
1494 
1495  Range& verts = context.appDatas[*pid].all_verts;
1496 
1497 #ifdef MOAB_HAVE_MPI
1498  // Parallel: query actual ownership from ParallelComm
1499  ParallelComm* pco = context.appDatas[*pid].pcomm;
1500 
1501  int i = 0;
1502  for( Range::iterator vit = verts.begin(); vit != verts.end(); ++vit, i++ )
1503  {
1504  // Get owning rank for this vertex (may be local or remote)
1505  MB_CHK_SET_ERR( pco->get_owner( *vit, visible_global_rank_ID[i] ), "can't get owner" );
1506  }
1507 
1508  // Validate we processed exactly the expected number of vertices
1509  if( i != *vertices_length )
1510  {
1511  return moab::MB_INVALID_SIZE; // Array size mismatch
1512  }
1513 
1514 #else
1515  // Serial: all vertices owned by rank 0
1516  if( (int)verts.size() != *vertices_length )
1517  {
1518  return moab::MB_INVALID_SIZE; // Array size mismatch
1519  } // warning array allocation problem
1520 
1521  int i = 0;
1522  for( Range::iterator vit = verts.begin(); vit != verts.end(); ++vit, i++ )
1523  {
1524  visible_global_rank_ID[i] = 0;
1525  } // all vertices are owned by processor 0, as this is serial run
1526 
1527 #endif
1528 
1529  return moab::MB_SUCCESS;
1530 }
1531 
1532 /**
1533  * @brief Retrieve coordinates for all vertices in interleaved format.
1534  * @ingroup iMOABQuery
1535  *
1536  * @details Extracts 3D coordinates (x,y,z) for all vertices in interleaved format:
1537  * [x0,y0,z0, x1,y1,z1, ...]. Coordinates are returned in same order as vertex IDs.
1538  *
1539  * @param[in] pid Application ID
1540  * @param[in] coords_length Length of coordinates array (must equal 3 * num_vertices)
1541  * @param[out] coordinates Interleaved coordinate array (size = 3 * num_vertices)
1542  *
1543  * @pre Application must be registered and mesh loaded
1544  * @pre coords_length must equal 3 * vertex count
1545  * @pre coordinates array must be pre-allocated
1546  *
1547  * @post coordinates filled with interleaved x,y,z values
1548  *
1549  * @note Format: [x0,y0,z0, x1,y1,z1, x2,y2,z2, ...]
1550  * @note Always returns 3D coordinates even for 2D meshes (z=0)
1551  * @warning Array size must be exact or function returns failure
1552  *
1553  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if size mismatch
1554  *
1555  * @see iMOAB_GetVertexID(), iMOAB_GetMeshInfo()
1556  */
1557 ErrCode iMOAB_GetVisibleVerticesCoordinates( iMOAB_AppID pid, int* coords_length, double* coordinates )
1558 {
1559  Range& verts = context.appDatas[*pid].all_verts;
1560 
1561  // Validate array size: need 3 coordinates (x,y,z) per vertex
1562  if( *coords_length != 3 * (int)verts.size() )
1563  {
1564  return moab::MB_INVALID_SIZE; // Size mismatch
1565  }
1566 
1567  // Extract coordinates in interleaved format (deep copy)
1568  MB_CHK_SET_ERR( context.MBI->get_coords( verts, coordinates ), "can't get coordinates" );
1569 
1570  return moab::MB_SUCCESS;
1571 }
1572 
1573 /**
1574  * @brief Retrieve global IDs for all material blocks in mesh.
1575  * @ingroup iMOABQuery
1576  *
1577  * @details Extracts MATERIAL_SET tag values for all material blocks. Blocks are identified by
1578  * unique integer IDs and contain elements with similar material properties.
1579  *
1580  * @param[in] pid Application ID
1581  * @param[in] block_length Number of blocks (must match actual count)
1582  * @param[out] global_block_IDs Array of material set IDs (size = block_length)
1583  *
1584  * @pre Application must be registered and mesh loaded
1585  * @pre block_length must match block count from iMOAB_GetMeshInfo
1586  * @pre global_block_IDs array must be pre-allocated
1587  *
1588  * @post global_block_IDs populated with MATERIAL_SET tag values
1589  * @post Internal matIndex map populated for fast block lookup
1590  *
1591  * @note Material blocks are mesh sets containing elements of same material
1592  * @note Creates internal index map for subsequent block queries
1593  * @note Block IDs are user-defined and may not be consecutive
1594  *
1595  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if count mismatch
1596  *
1597  * @see iMOAB_GetBlockInfo(), iMOAB_GetMeshInfo()
1598  */
1599 ErrCode iMOAB_GetBlockID( iMOAB_AppID pid, int* block_length, iMOAB_GlobalID* global_block_IDs )
1600 {
1601  Range& matSets = context.appDatas[*pid].mat_sets;
1602 
1603  // Validate array size matches actual block count
1604  if( *block_length != (int)matSets.size() )
1605  {
1606  return moab::MB_INVALID_SIZE; // Size mismatch
1607  }
1608 
1609  // Extract MATERIAL_SET tag values for all material blocks
1610  MB_CHK_SET_ERR( context.MBI->tag_get_data( context.material_tag, matSets, global_block_IDs ),
1611  "can't get material IDs" );
1612 
1613  // Build internal index map: block_id -> array_index for fast lookup
1614  std::map< int, int >& matIdx = context.appDatas[*pid].matIndex;
1615  for( unsigned i = 0; i < matSets.size(); i++ )
1616  {
1617  matIdx[global_block_IDs[i]] = i; // Cache block index for future queries
1618  }
1619 
1620  return moab::MB_SUCCESS;
1621 }
1622 
1623 /**
1624  * @brief Retrieve information about a specific material block.
1625  * @ingroup iMOABQuery
1626  *
1627  * @details Queries element count and connectivity size for specified material block.
1628  * Assumes all elements in block have same topology type.
1629  *
1630  * @param[in] pid Application ID
1631  * @param[in] global_block_ID Material block ID to query
1632  * @param[out] vertices_per_element Number of vertices per element in this block
1633  * @param[out] num_elements_in_block Total number of elements in this block
1634  *
1635  * @pre Application must be registered and mesh loaded
1636  * @pre iMOAB_GetBlockID must have been called first (to populate matIndex)
1637  * @pre global_block_ID must be valid block ID from iMOAB_GetBlockID
1638  *
1639  * @post vertices_per_element set to connectivity size
1640  * @post num_elements_in_block set to element count
1641  *
1642  * @note All elements in block assumed to have same topology
1643  * @note vertices_per_element depends on element type (4=tet, 8=hex, etc.)
1644  * @warning Returns failure if block ID not found in matIndex map
1645  *
1646  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if block not found
1647  *
1648  * @see iMOAB_GetBlockID(), iMOAB_GetBlockElementConnectivities()
1649  */
1651  iMOAB_GlobalID* global_block_ID,
1652  int* vertices_per_element,
1653  int* num_elements_in_block )
1654 {
1655  assert( global_block_ID );
1656 
1657  std::map< int, int >& matMap = context.appDatas[*pid].matIndex;
1658  std::map< int, int >::iterator it = matMap.find( *global_block_ID );
1659 
1660  if( it == matMap.end() )
1661  {
1662  return moab::MB_FAILURE;
1663  } // error in finding block with id
1664 
1665  int blockIndex = matMap[*global_block_ID];
1666  EntityHandle matMeshSet = context.appDatas[*pid].mat_sets[blockIndex];
1667  Range blo_elems;
1668  MB_CHK_SET_ERR( context.MBI->get_entities_by_handle( matMeshSet, blo_elems ), "can't get block elements" );
1669 
1670  if( blo_elems.empty() ) return moab::MB_FAILURE;
1671 
1672  EntityType type = context.MBI->type_from_handle( blo_elems[0] );
1673 
1674  if( !blo_elems.all_of_type( type ) ) return moab::MB_FAILURE;
1675 
1676  const EntityHandle* conn = nullptr;
1677  int num_verts = 0;
1678  MB_CHK_SET_ERR( context.MBI->get_connectivity( blo_elems[0], conn, num_verts ), "can't get connectivity" );
1679 
1680  *vertices_per_element = num_verts;
1681  *num_elements_in_block = (int)blo_elems.size();
1682 
1683  return moab::MB_SUCCESS;
1684 }
1685 
1687  int* num_visible_elements,
1688  iMOAB_GlobalID* element_global_IDs,
1689  int* ranks,
1690  iMOAB_GlobalID* block_IDs )
1691 {
1692  appData& data = context.appDatas[*pid];
1693 #ifdef MOAB_HAVE_MPI
1694  ParallelComm* pco = context.appDatas[*pid].pcomm;
1695 #endif
1696 
1697  MB_CHK_SET_ERR( context.MBI->tag_get_data( context.globalID_tag, data.primary_elems, element_global_IDs ),
1698  "can't get global IDs" );
1699 
1700  int i = 0;
1701 
1702  for( Range::iterator eit = data.primary_elems.begin(); eit != data.primary_elems.end(); ++eit, ++i )
1703  {
1704 #ifdef MOAB_HAVE_MPI
1705  MB_CHK_SET_ERR( pco->get_owner( *eit, ranks[i] ), "can't get owner" );
1706 
1707 #else
1708  /* everything owned by task 0 */
1709  ranks[i] = 0;
1710 #endif
1711  }
1712 
1713  for( Range::iterator mit = data.mat_sets.begin(); mit != data.mat_sets.end(); ++mit )
1714  {
1715  EntityHandle matMeshSet = *mit;
1716  Range elems;
1717  MB_CHK_SET_ERR( context.MBI->get_entities_by_handle( matMeshSet, elems ), "can't get block elements" );
1718 
1719  int valMatTag;
1720  MB_CHK_SET_ERR( context.MBI->tag_get_data( context.material_tag, &matMeshSet, 1, &valMatTag ),
1721  "can't get material tag" );
1722 
1723  for( Range::iterator eit = elems.begin(); eit != elems.end(); ++eit )
1724  {
1725  EntityHandle eh = *eit;
1726  int index = data.primary_elems.index( eh );
1727 
1728  if( -1 == index )
1729  {
1730  return moab::MB_FAILURE;
1731  }
1732 
1733  if( -1 >= *num_visible_elements )
1734  {
1735  return moab::MB_FAILURE;
1736  }
1737 
1738  block_IDs[index] = valMatTag;
1739  }
1740  }
1741 
1742  return moab::MB_SUCCESS;
1743 }
1744 
1746  iMOAB_GlobalID* global_block_ID,
1747  int* connectivity_length,
1748  int* element_connectivity )
1749 {
1750  assert( global_block_ID ); // ensure global block ID argument is not null
1751  assert( connectivity_length ); // ensure connectivity length argument is not null
1752 
1753  appData& data = context.appDatas[*pid];
1754  std::map< int, int >& matMap = data.matIndex;
1755  std::map< int, int >::iterator it = matMap.find( *global_block_ID );
1756 
1757  if( it == matMap.end() )
1758  {
1759  return moab::MB_FAILURE;
1760  } // error in finding block with id
1761 
1762  int blockIndex = matMap[*global_block_ID];
1763  EntityHandle matMeshSet = data.mat_sets[blockIndex];
1764  std::vector< EntityHandle > elems;
1765 
1766  MB_CHK_SET_ERR( context.MBI->get_entities_by_handle( matMeshSet, elems ), "can't get block elements" );
1767 
1768  if( elems.empty() ) return moab::MB_FAILURE;
1769 
1770  std::vector< EntityHandle > vconnect;
1771  MB_CHK_SET_ERR( context.MBI->get_connectivity( &elems[0], elems.size(), vconnect ), "can't get connectivity" );
1772 
1773  if( *connectivity_length != (int)vconnect.size() )
1774  {
1775  return moab::MB_FAILURE;
1776  } // mismatched sizes
1777 
1778  for( int i = 0; i < *connectivity_length; i++ )
1779  {
1780  int inx = data.all_verts.index( vconnect[i] );
1781 
1782  if( -1 == inx )
1783  {
1784  return moab::MB_FAILURE;
1785  } // error, vertex not in local range
1786 
1787  element_connectivity[i] = inx;
1788  }
1789 
1790  return moab::MB_SUCCESS;
1791 }
1792 
1794  iMOAB_LocalID* elem_index,
1795  int* connectivity_length,
1796  int* element_connectivity )
1797 {
1798  assert( elem_index ); // ensure element index argument is not null
1799  assert( connectivity_length ); // ensure connectivity length argument is not null
1800 
1801  appData& data = context.appDatas[*pid];
1802  assert( ( *elem_index >= 0 ) && ( *elem_index < (int)data.primary_elems.size() ) );
1803 
1804  int num_nodes;
1805  const EntityHandle* conn;
1806 
1807  EntityHandle eh = data.primary_elems[*elem_index];
1808 
1809  MB_CHK_SET_ERR( context.MBI->get_connectivity( eh, conn, num_nodes ), "can't get connectivity" );
1810 
1811  if( *connectivity_length < num_nodes )
1812  {
1813  return moab::MB_FAILURE;
1814  } // wrong number of vertices
1815 
1816  for( int i = 0; i < num_nodes; i++ )
1817  {
1818  int index = data.all_verts.index( conn[i] );
1819 
1820  if( -1 == index )
1821  {
1822  return moab::MB_FAILURE;
1823  }
1824 
1825  element_connectivity[i] = index;
1826  }
1827 
1828  *connectivity_length = num_nodes;
1829 
1830  return moab::MB_SUCCESS;
1831 }
1832 
1834  iMOAB_GlobalID* global_block_ID,
1835  int* num_elements_in_block,
1836  int* element_ownership )
1837 {
1838  assert( global_block_ID ); // ensure global block ID argument is not null
1839  assert( num_elements_in_block ); // ensure number of elements in block argument is not null
1840 
1841  std::map< int, int >& matMap = context.appDatas[*pid].matIndex;
1842 
1843  std::map< int, int >::iterator it = matMap.find( *global_block_ID );
1844 
1845  if( it == matMap.end() )
1846  {
1847  return moab::MB_FAILURE;
1848  } // error in finding block with id
1849 
1850  int blockIndex = matMap[*global_block_ID];
1851  EntityHandle matMeshSet = context.appDatas[*pid].mat_sets[blockIndex];
1852  Range elems;
1853 
1854  MB_CHK_SET_ERR( context.MBI->get_entities_by_handle( matMeshSet, elems ), "can't get block elements" );
1855 
1856  if( elems.empty() ) return moab::MB_FAILURE;
1857 
1858  if( *num_elements_in_block != (int)elems.size() )
1859  {
1860  return moab::MB_FAILURE;
1861  } // bad memory allocation
1862 
1863  int i = 0;
1864 #ifdef MOAB_HAVE_MPI
1865  ParallelComm* pco = context.appDatas[*pid].pcomm;
1866 #endif
1867 
1868  for( Range::iterator vit = elems.begin(); vit != elems.end(); vit++, i++ )
1869  {
1870 #ifdef MOAB_HAVE_MPI
1871  MB_CHK_SET_ERR( pco->get_owner( *vit, element_ownership[i] ), "can't get owner" );
1872 #else
1873  element_ownership[i] = 0; /* owned by 0 */
1874 #endif
1875  }
1876 
1877  return moab::MB_SUCCESS;
1878 }
1879 
1881  iMOAB_GlobalID* global_block_ID,
1882  int* num_elements_in_block,
1883  iMOAB_GlobalID* global_element_ID,
1884  iMOAB_LocalID* local_element_ID )
1885 {
1886  assert( global_block_ID ); // ensure global block ID argument is not null
1887  assert( num_elements_in_block ); // ensure number of elements in block argument is not null
1888 
1889  appData& data = context.appDatas[*pid];
1890  std::map< int, int >& matMap = data.matIndex;
1891 
1892  std::map< int, int >::iterator it = matMap.find( *global_block_ID );
1893 
1894  if( it == matMap.end() )
1895  {
1896  return moab::MB_FAILURE;
1897  } // error in finding block with id
1898 
1899  int blockIndex = matMap[*global_block_ID];
1900  EntityHandle matMeshSet = data.mat_sets[blockIndex];
1901  Range elems;
1902  MB_CHK_SET_ERR( context.MBI->get_entities_by_handle( matMeshSet, elems ), "can't get block elements" );
1903 
1904  if( elems.empty() ) return moab::MB_FAILURE;
1905 
1906  if( *num_elements_in_block != (int)elems.size() )
1907  {
1908  return moab::MB_FAILURE;
1909  } // bad memory allocation
1910 
1911  MB_CHK_SET_ERR( context.MBI->tag_get_data( context.globalID_tag, elems, global_element_ID ),
1912  "can't get global IDs" );
1913 
1914  // check that elems are among primary_elems in data
1915  for( int i = 0; i < *num_elements_in_block; i++ )
1916  {
1917  local_element_ID[i] = data.primary_elems.index( elems[i] );
1918 
1919  if( -1 == local_element_ID[i] )
1920  {
1921  return moab::MB_FAILURE;
1922  } // error, not in local primary elements
1923  }
1924 
1925  return moab::MB_SUCCESS;
1926 }
1927 
1929  int* surface_BC_length,
1930  iMOAB_LocalID* local_element_ID,
1931  int* reference_surface_ID,
1932  int* boundary_condition_value )
1933 {
1934  // we have to fill bc data for neumann sets;/
1935 
1936  // it was counted above, in GetMeshInfo
1937  appData& data = context.appDatas[*pid];
1938  int numNeuSets = (int)data.neu_sets.size();
1939 
1940  int index = 0; // index [0, surface_BC_length) for the arrays returned
1941 
1942  for( int i = 0; i < numNeuSets; i++ )
1943  {
1944  Range subents;
1945  EntityHandle nset = data.neu_sets[i];
1946  MB_CHK_SET_ERR( context.MBI->get_entities_by_dimension( nset, data.dimension - 1, subents ),
1947  "can't get subentities" );
1948 
1949  int neuVal;
1950  MB_CHK_SET_ERR( context.MBI->tag_get_data( context.neumann_tag, &nset, 1, &neuVal ), "can't get neumann tag" );
1951 
1952  for( Range::iterator it = subents.begin(); it != subents.end(); ++it )
1953  {
1954  EntityHandle subent = *it;
1955  Range adjPrimaryEnts;
1956  MB_CHK_SET_ERR( context.MBI->get_adjacencies( &subent, 1, data.dimension, false, adjPrimaryEnts ),
1957  "can't get adjacencies" );
1958 
1959  // get global id of the primary ents, and side number of the quad/subentity
1960  // this is moab ordering
1961  for( Range::iterator pit = adjPrimaryEnts.begin(); pit != adjPrimaryEnts.end(); pit++ )
1962  {
1963  EntityHandle primaryEnt = *pit;
1964  // get global id
1965  /*int globalID;
1966  rval = context.MBI->tag_get_data(gtags[3], &primaryEnt, 1, &globalID);
1967  if (MB_SUCCESS!=rval)
1968  return moab::MB_FAILURE;
1969  global_element_ID[index] = globalID;*/
1970 
1971  // get local element id
1972  local_element_ID[index] = data.primary_elems.index( primaryEnt );
1973 
1974  if( -1 == local_element_ID[index] )
1975  {
1976  return moab::MB_FAILURE;
1977  } // did not find the element locally
1978 
1979  int side_number, sense, offset;
1980  MB_CHK_SET_ERR( context.MBI->side_number( primaryEnt, subent, side_number, sense, offset ),
1981  "can't get side number" );
1982 
1983  reference_surface_ID[index] = side_number + 1; // moab is from 0 to 5, it needs 1 to 6
1984  boundary_condition_value[index] = neuVal;
1985  index++;
1986  }
1987  }
1988  }
1989 
1990  if( index != *surface_BC_length )
1991  {
1992  return moab::MB_FAILURE;
1993  } // error in array allocations
1994 
1995  return moab::MB_SUCCESS;
1996 }
1997 
1999  int* vertex_BC_length,
2000  iMOAB_LocalID* local_vertex_ID,
2001  int* boundary_condition_value )
2002 {
2003  // it was counted above, in GetMeshInfo
2004  appData& data = context.appDatas[*pid];
2005  int numDiriSets = (int)data.diri_sets.size();
2006  int index = 0; // index [0, *vertex_BC_length) for the arrays returned
2007 
2008  for( int i = 0; i < numDiriSets; i++ )
2009  {
2010  Range verts;
2011  EntityHandle diset = data.diri_sets[i];
2012  MB_CHK_SET_ERR( context.MBI->get_entities_by_dimension( diset, 0, verts ), "can't get vertices" );
2013 
2014  int diriVal;
2015  MB_CHK_SET_ERR( context.MBI->tag_get_data( context.dirichlet_tag, &diset, 1, &diriVal ),
2016  "can't get dirichlet tag" );
2017 
2018  for( Range::iterator vit = verts.begin(); vit != verts.end(); ++vit )
2019  {
2020  EntityHandle vt = *vit;
2021  /*int vgid;
2022  rval = context.MBI->tag_get_data(gtags[3], &vt, 1, &vgid);
2023  if (MB_SUCCESS!=rval)
2024  return moab::MB_FAILURE;
2025  global_vertext_ID[index] = vgid;*/
2026  local_vertex_ID[index] = data.all_verts.index( vt );
2027 
2028  if( -1 == local_vertex_ID[index] )
2029  {
2030  return moab::MB_FAILURE;
2031  } // vertex was not found
2032 
2033  boundary_condition_value[index] = diriVal;
2034  index++;
2035  }
2036  }
2037 
2038  if( *vertex_BC_length != index )
2039  {
2040  return moab::MB_FAILURE;
2041  } // array allocation issue
2042 
2043  return moab::MB_SUCCESS;
2044 }
2045 
2046 // Utility function
2047 static void split_tag_names( std::string input_names,
2048  std::string& separator,
2049  std::vector< std::string >& list_tag_names )
2050 {
2051  size_t pos = 0;
2052  std::string token;
2053  while( ( pos = input_names.find( separator ) ) != std::string::npos )
2054  {
2055  token = input_names.substr( 0, pos );
2056  if( !token.empty() ) list_tag_names.push_back( token );
2057  // std::cout << token << std::endl;
2058  input_names.erase( 0, pos + separator.length() );
2059  }
2060  if( !input_names.empty() )
2061  {
2062  // if leftover something, or if not ended with delimiter
2063  list_tag_names.push_back( input_names );
2064  }
2065  return;
2066 }
2067 
2069  const iMOAB_String tag_storage_name,
2070  int* tag_type,
2071  int* components_per_entity,
2072  int* tag_index )
2073 {
2074  // we have 6 types of tags supported so far
2075  // check if tag type is valid
2076  if( *tag_type < 0 || *tag_type > 5 ) return moab::MB_FAILURE;
2077 
2078  DataType tagDataType;
2079  TagType tagType;
2080  void* defaultVal = nullptr;
2081  int* defInt = new int[*components_per_entity];
2082  double* defDouble = new double[*components_per_entity];
2083  EntityHandle* defHandle = new EntityHandle[*components_per_entity];
2084 
2085  for( int i = 0; i < *components_per_entity; i++ )
2086  {
2087  defInt[i] = 0;
2088  defDouble[i] = -1e+10;
2089  defHandle[i] = static_cast< EntityHandle >( 0 );
2090  }
2091 
2092  switch( *tag_type )
2093  {
2094  case 0:
2095  tagDataType = MB_TYPE_INTEGER;
2096  tagType = MB_TAG_DENSE;
2097  defaultVal = defInt;
2098  break;
2099 
2100  case 1:
2101  tagDataType = MB_TYPE_DOUBLE;
2102  tagType = MB_TAG_DENSE;
2103  defaultVal = defDouble;
2104  break;
2105 
2106  case 2:
2107  tagDataType = MB_TYPE_HANDLE;
2108  tagType = MB_TAG_DENSE;
2109  defaultVal = defHandle;
2110  break;
2111 
2112  case 3:
2113  tagDataType = MB_TYPE_INTEGER;
2114  tagType = MB_TAG_SPARSE;
2115  defaultVal = defInt;
2116  break;
2117 
2118  case 4:
2119  tagDataType = MB_TYPE_DOUBLE;
2120  tagType = MB_TAG_SPARSE;
2121  defaultVal = defDouble;
2122  break;
2123 
2124  case 5:
2125  tagDataType = MB_TYPE_HANDLE;
2126  tagType = MB_TAG_SPARSE;
2127  defaultVal = defHandle;
2128  break;
2129 
2130  default: {
2131  delete[] defInt;
2132  delete[] defDouble;
2133  delete[] defHandle;
2134  return moab::MB_FAILURE;
2135  } // error
2136  }
2137 
2138  // split storage names if separated list
2139  std::string tag_name( tag_storage_name );
2140  // first separate the names of the tags
2141  // we assume that there are separators ":" between the tag names
2142  std::vector< std::string > tagNames;
2143  std::string separator( ":" );
2144  split_tag_names( tag_name, separator, tagNames );
2145 
2146  ErrorCode rval = moab::MB_SUCCESS; // assume success already :)
2147  appData& data = context.appDatas[*pid];
2148  int already_defined_tags = 0;
2149 
2150  Tag tagHandle;
2151  for( size_t i = 0; i < tagNames.size(); i++ )
2152  {
2153  rval = context.MBI->tag_get_handle( tagNames[i].c_str(), *components_per_entity, tagDataType, tagHandle,
2154  tagType | MB_TAG_EXCL | MB_TAG_CREAT, defaultVal );
2155 
2156  if( MB_ALREADY_ALLOCATED == rval )
2157  {
2158  std::map< std::string, Tag >& mTags = data.tagMap;
2159  std::map< std::string, Tag >::iterator mit = mTags.find( tagNames[i].c_str() );
2160 
2161  if( mit == mTags.end() )
2162  {
2163  // add it to the map
2164  mTags[tagNames[i]] = tagHandle;
2165  // push it to the list of tags, too
2166  *tag_index = (int)data.tagList.size();
2167  data.tagList.push_back( tagHandle );
2168  }
2169  rval = MB_SUCCESS;
2170  already_defined_tags++;
2171  }
2172  else if( MB_SUCCESS == rval )
2173  {
2174  data.tagMap[tagNames[i]] = tagHandle;
2175  *tag_index = (int)data.tagList.size();
2176  data.tagList.push_back( tagHandle );
2177  }
2178  else
2179  {
2180  rval = moab::MB_FAILURE; // some tags were not created
2181  }
2182  }
2183  // we don't need default values anymore, avoid leaks
2184  int rankHere = 0;
2185 #ifdef MOAB_HAVE_MPI
2186  ParallelComm* pco = context.appDatas[*pid].pcomm;
2187  rankHere = pco->rank();
2188 #endif
2189  if( !rankHere && already_defined_tags )
2190  std::cout << " application with ID: " << *pid << " global id: " << data.global_id << " name: " << data.name
2191  << " has " << already_defined_tags << " already defined tags out of " << tagNames.size()
2192  << " tags \n";
2193  delete[] defInt;
2194  delete[] defDouble;
2195  delete[] defHandle;
2196  return rval;
2197 }
2198 
2200  const iMOAB_String tag_storage_name,
2201  int* num_tag_storage_length,
2202  int* ent_type,
2203  int* tag_storage_data )
2204 {
2205  std::string tag_name( tag_storage_name );
2206 
2207  // Get the application data
2208  appData& data = context.appDatas[*pid];
2209  // check if tag is defined
2210  if( data.tagMap.find( tag_name ) == data.tagMap.end() ) return moab::MB_FAILURE;
2211 
2212  Tag tag = data.tagMap[tag_name];
2213 
2214  int tagLength = 0;
2215  MB_CHK_ERR( context.MBI->tag_get_length( tag, tagLength ) );
2216 
2217  DataType dtype;
2218  MB_CHK_ERR( context.MBI->tag_get_data_type( tag, dtype ) );
2219 
2220  if( dtype != MB_TYPE_INTEGER )
2221  {
2222  MB_CHK_SET_ERR( moab::MB_FAILURE, "The tag is not of integer type." );
2223  }
2224 
2225  // set it on a subset of entities, based on type and length
2226  // if *entity_type = 0, then use vertices; else elements
2227  Range* ents_to_set = ( *ent_type == 0 ? &data.all_verts : &data.primary_elems );
2228  int nents_to_be_set = *num_tag_storage_length / tagLength;
2229 
2230  if( nents_to_be_set > (int)ents_to_set->size() )
2231  {
2232  return moab::MB_FAILURE;
2233  } // to many entities to be set or too few
2234 
2235  // now set the tag data
2236  MB_CHK_ERR( context.MBI->tag_set_data( tag, *ents_to_set, tag_storage_data ) );
2237 
2238  return moab::MB_SUCCESS; // no error
2239 }
2240 
2242  const iMOAB_String tag_storage_name,
2243  int* num_tag_storage_length,
2244  int* ent_type,
2245  int* tag_storage_data )
2246 {
2247  std::string tag_name( tag_storage_name );
2248 
2249  appData& data = context.appDatas[*pid];
2250 
2251  if( data.tagMap.find( tag_name ) == data.tagMap.end() )
2252  {
2253  return moab::MB_FAILURE;
2254  } // tag not defined
2255 
2256  Tag tag = data.tagMap[tag_name];
2257 
2258  int tagLength = 0;
2259  MB_CHK_ERR( context.MBI->tag_get_length( tag, tagLength ) );
2260 
2261  DataType dtype;
2262  MB_CHK_ERR( context.MBI->tag_get_data_type( tag, dtype ) );
2263 
2264  if( dtype != MB_TYPE_INTEGER )
2265  {
2266  MB_CHK_SET_ERR( moab::MB_FAILURE, "The tag is not of integer type." );
2267  }
2268 
2269  // set it on a subset of entities, based on type and length
2270  // if *entity_type = 0, then use vertices; else elements
2271  Range* ents_to_get = ( *ent_type == 0 ? &data.all_verts : &data.primary_elems );
2272  int nents_to_get = *num_tag_storage_length / tagLength;
2273 
2274  if( nents_to_get > (int)ents_to_get->size() )
2275  {
2276  return moab::MB_FAILURE;
2277  } // to many entities to get, or too little
2278 
2279  // now set the tag data
2280  MB_CHK_ERR( context.MBI->tag_get_data( tag, *ents_to_get, tag_storage_data ) );
2281 
2282  return moab::MB_SUCCESS; // no error
2283 }
2284 
2286  const iMOAB_String tag_storage_names,
2287  int* num_tag_storage_length,
2288  int* ent_type,
2289  double* tag_storage_data )
2290 {
2291  std::string tag_names( tag_storage_names );
2292  // exactly the same code as for int tag :) maybe should check the type of tag too
2293  std::vector< std::string > tagNames;
2294  std::vector< Tag > tagHandles;
2295  std::string separator( ":" );
2296  split_tag_names( tag_names, separator, tagNames );
2297 
2298  appData& data = context.appDatas[*pid];
2299  // set it on a subset of entities, based on type and length
2300  // if *entity_type = 0, then use vertices; else elements
2301  Range* ents_to_set = ( *ent_type == 0 ? &data.all_verts : &data.primary_elems );
2302 
2303  int nents_to_be_set = (int)( *ents_to_set ).size();
2304  int position = 0;
2305  for( size_t i = 0; i < tagNames.size(); i++ )
2306  {
2307  if( data.tagMap.find( tagNames[i] ) == data.tagMap.end() )
2308  {
2309  return moab::MB_FAILURE;
2310  } // some tag not defined yet in the app
2311 
2312  Tag tag = data.tagMap[tagNames[i]];
2313 
2314  int tagLength = 0;
2315  MB_CHK_ERR( context.MBI->tag_get_length( tag, tagLength ) );
2316 
2317  DataType dtype;
2318  MB_CHK_ERR( context.MBI->tag_get_data_type( tag, dtype ) );
2319 
2320  if( dtype != MB_TYPE_DOUBLE )
2321  {
2322  return moab::MB_FAILURE;
2323  }
2324 
2325  // set it on the subset of entities, based on type and length
2326  if( position + tagLength * nents_to_be_set > *num_tag_storage_length )
2327  return moab::MB_FAILURE; // too many entity values to be set
2328 
2329  MB_CHK_ERR( context.MBI->tag_set_data( tag, *ents_to_set, &tag_storage_data[position] ) );
2330  // increment position to next tag
2331  position = position + tagLength * nents_to_be_set;
2332  }
2333  return moab::MB_SUCCESS; // no error
2334 }
2335 
2337  const iMOAB_String tag_storage_names,
2338  int* num_tag_storage_length,
2339  int* ent_type,
2340  double* tag_storage_data,
2341  int* globalIds )
2342 {
2343  std::string tag_names( tag_storage_names );
2344  // exactly the same code as for int tag :) maybe should check the type of tag too
2345  std::vector< std::string > tagNames;
2346  std::vector< Tag > tagHandles;
2347  std::string separator( ":" );
2348  split_tag_names( tag_names, separator, tagNames );
2349 
2350  appData& data = context.appDatas[*pid];
2351  // set it on a subset of entities, based on type and length
2352  // if *entity_type = 0, then use vertices; else elements
2353  Range* ents_to_set = ( *ent_type == 0 ? &data.all_verts : &data.primary_elems );
2354  int nents_to_be_set = (int)( *ents_to_set ).size();
2355 
2356  Tag gidTag = context.MBI->globalId_tag();
2357  std::vector< int > gids;
2358  gids.resize( nents_to_be_set );
2359  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, *ents_to_set, &gids[0] ) );
2360 
2361  // so we will need to set the tags according to the global id passed;
2362  // so the order in tag_storage_data is the same as the order in globalIds, but the order
2363  // in local range is gids
2364  std::map< int, EntityHandle > eh_by_gid;
2365  int i = 0;
2366  for( Range::iterator it = ents_to_set->begin(); it != ents_to_set->end(); ++it, ++i )
2367  {
2368  eh_by_gid[gids[i]] = *it;
2369  }
2370 
2371  /// @todo Allow for tags of different length
2372  size_t nbLocalVals = *num_tag_storage_length / tagNames.size(); // assumes all tags have the same length?
2373 
2374  // check global ids to have different values
2375  std::set< int > globalIdsSet( globalIds, globalIds + nbLocalVals );
2376  if( globalIdsSet.size() < nbLocalVals )
2377  {
2378  std::cout << "iMOAB_SetDoubleTagStorageWithGid: for pid:" << *pid << " tags[0]:" << tagNames[0]
2379  << " global ids passed are not unique, major error\n";
2380  std::cout << " nbLocalVals:" << nbLocalVals << " globalIdsSet.size():" << globalIdsSet.size()
2381  << " first global id:" << globalIds[0] << "\n";
2382  return moab::MB_FAILURE;
2383  }
2384 
2385  std::vector< int > tagLengths( tagNames.size() );
2386  std::vector< Tag > tagList;
2387 #ifdef MOAB_HAVE_MPI
2388  size_t total_tag_len = 0;
2389 #endif
2390  for( size_t i = 0; i < tagNames.size(); i++ )
2391  {
2392  if( data.tagMap.find( tagNames[i] ) == data.tagMap.end() )
2393  {
2394  MB_SET_ERR( moab::MB_FAILURE, "tag missing" );
2395  } // some tag not defined yet in the app
2396 
2397  Tag tag = data.tagMap[tagNames[i]];
2398  tagList.push_back( tag );
2399 
2400  int tagLength = 0;
2401  MB_CHK_ERR( context.MBI->tag_get_length( tag, tagLength ) );
2402 
2403 #ifdef MOAB_HAVE_MPI
2404  total_tag_len += tagLength;
2405 #endif
2406  tagLengths[i] = tagLength;
2407  DataType dtype;
2408  MB_CHK_ERR( context.MBI->tag_get_data_type( tag, dtype ) );
2409 
2410  if( dtype != MB_TYPE_DOUBLE )
2411  {
2412  MB_SET_ERR( moab::MB_FAILURE, "tag not double type" );
2413  }
2414  }
2415  bool serial = true;
2416 #ifdef MOAB_HAVE_MPI
2417  ParallelComm* pco = context.appDatas[*pid].pcomm;
2418  unsigned num_procs = pco->size();
2419  if( num_procs > 1 ) serial = false;
2420 #endif
2421 
2422  if( serial )
2423  {
2424  // we do not assume anymore that the number of entities has to match
2425  // we will set only what matches, and skip entities that do not have corresponding global ids
2426  //assert( total_tag_len * nents_to_be_set - *num_tag_storage_length == 0 );
2427  // tags are unrolled, we loop over global ids first, then careful about tags
2428  for( int i = 0; i < nents_to_be_set; i++ )
2429  {
2430  int gid = globalIds[i];
2431  std::map< int, EntityHandle >::iterator mapIt = eh_by_gid.find( gid );
2432  if( mapIt == eh_by_gid.end() ) continue;
2433  EntityHandle eh = mapIt->second;
2434  // now loop over tags
2435  int indexInTagValues = 0; //
2436  for( size_t j = 0; j < tagList.size(); j++ )
2437  {
2438  indexInTagValues += i * tagLengths[j];
2439  MB_CHK_ERR( context.MBI->tag_set_data( tagList[j], &eh, 1, &tag_storage_data[indexInTagValues] ) );
2440  // advance the pointer/index
2441  indexInTagValues += ( nents_to_be_set - i ) * tagLengths[j]; // at the end of tag data
2442  }
2443  }
2444  }
2445 #ifdef MOAB_HAVE_MPI
2446  else // it can be not serial only if pco->size() > 1, parallel
2447  {
2448  // in this case, we have to use 2 crystal routers, to send data to the processor that needs it
2449  // we will create first a tuple to rendevous points, then from there send to the processor that requested it
2450  // it is a 2-hop global gather scatter
2451  // we do not expect the sizes to match
2452  //assert( nbLocalVals * tagNames.size() - *num_tag_storage_length == 0 );
2453  TupleList TLsend;
2454  TLsend.initialize( 2, 0, 0, total_tag_len, nbLocalVals ); // to proc, marker(gid), total_tag_len doubles
2455  TLsend.enableWriteAccess();
2456  // the processor id that processes global_id is global_id / num_ents_per_proc
2457 
2458  int indexInRealLocal = 0;
2459  for( size_t i = 0; i < nbLocalVals; i++ )
2460  {
2461  // to proc, marker, element local index, index in el
2462  int marker = globalIds[i];
2463  int to_proc = marker % num_procs;
2464  int n = TLsend.get_n();
2465  TLsend.vi_wr[2 * n] = to_proc; // send to processor
2466  TLsend.vi_wr[2 * n + 1] = marker;
2467  int indexInTagValues = 0;
2468  // tag data collect by number of tags
2469  for( size_t j = 0; j < tagList.size(); j++ )
2470  {
2471  indexInTagValues += i * tagLengths[j];
2472  for( int k = 0; k < tagLengths[j]; k++ )
2473  {
2474  TLsend.vr_wr[indexInRealLocal++] = tag_storage_data[indexInTagValues + k];
2475  }
2476  indexInTagValues += ( nbLocalVals - i ) * tagLengths[j];
2477  }
2478  TLsend.inc_n();
2479  }
2480 
2481  //assert( nbLocalVals * total_tag_len - indexInRealLocal == 0 );
2482  // send now requests, basically inform the rendez-vous point who needs a particular global id
2483  // send the data to the other processors:
2484  ( pco->proc_config().crystal_router() )->gs_transfer( 1, TLsend, 0 );
2485  TupleList TLreq;
2486  TLreq.initialize( 2, 0, 0, 0, nents_to_be_set );
2487  TLreq.enableWriteAccess();
2488  for( int i = 0; i < nents_to_be_set; i++ )
2489  {
2490  // to proc, marker
2491  int marker = gids[i];
2492  int to_proc = marker % num_procs;
2493  int n = TLreq.get_n();
2494  TLreq.vi_wr[2 * n] = to_proc; // send to processor
2495  TLreq.vi_wr[2 * n + 1] = marker;
2496  // tag data collect by number of tags
2497  TLreq.inc_n();
2498  }
2499 
2500  // perform communication with crystal router
2501  pco->proc_config().crystal_router()->gs_transfer( 1, TLreq, 0 );
2502 
2503  // we know now that process TLreq.vi_wr[2 * n] needs tags for gid TLreq.vi_wr[2 * n + 1]
2504  // we should first order by global id, and then build the new TL with send to proc, global id and
2505  // tags for it
2506  // sort by global ids the tuple lists
2507  moab::TupleList::buffer sort_buffer;
2508  sort_buffer.buffer_init( TLreq.get_n() );
2509  TLreq.sort( 1, &sort_buffer );
2510  sort_buffer.reset();
2511  sort_buffer.buffer_init( TLsend.get_n() );
2512  TLsend.sort( 1, &sort_buffer );
2513  sort_buffer.reset();
2514  // now send the tag values to the proc that requested it
2515  // in theory, for a full partition, TLreq and TLsend should have the same size, and
2516  // each dof should have exactly one target proc. Is that true or not in general ?
2517  // how do we plan to use this? Is it better to store the comm graph for future
2518 
2519  // start copy from comm graph settle
2520  TupleList TLBack;
2521  TLBack.initialize( 3, 0, 0, total_tag_len, 0 ); // to proc, marker, tag from proc , tag values
2522  TLBack.enableWriteAccess();
2523 
2524  int n1 = TLreq.get_n();
2525  int n2 = TLsend.get_n();
2526 
2527  int indexInTLreq = 0;
2528  int indexInTLsend = 0; // advance both, according to the marker
2529  if( n1 > 0 && n2 > 0 )
2530  {
2531 
2532  while( indexInTLreq < n1 && indexInTLsend < n2 ) // if any is over, we are done
2533  {
2534  int currentValue1 = TLreq.vi_rd[2 * indexInTLreq + 1];
2535  int currentValue2 = TLsend.vi_rd[2 * indexInTLsend + 1];
2536  if( currentValue1 < currentValue2 )
2537  {
2538  // we have a big problem; basically, we are saying that
2539  // dof currentValue is on one model and not on the other
2540  indexInTLreq++;
2541  continue;
2542  }
2543 
2544  if( currentValue1 > currentValue2 )
2545  {
2546  indexInTLsend++;
2547  continue;
2548  }
2549 
2550  int size1 = 1;
2551  while( indexInTLreq + size1 < n1 && currentValue1 == TLreq.vi_rd[2 * ( indexInTLreq + size1 ) + 1] )
2552  size1++;
2553  int size2 = 1;
2554  while( indexInTLsend + size2 < n2 && currentValue2 == TLsend.vi_rd[2 * ( indexInTLsend + size2 ) + 1] )
2555  size2++;
2556 
2557  // must be found in both lists, find the start and end indices
2558  for( int i1 = 0; i1 < size1; i1++ )
2559  {
2560  for( int i2 = 0; i2 < size2; i2++ )
2561  {
2562  // send the info back to components
2563  int n = TLBack.get_n();
2564  TLBack.reserve();
2565  TLBack.vi_wr[3 * n] = TLreq.vi_rd[2 * ( indexInTLreq + i1 )]; // send back to the proc marker
2566  // came from, info from comp2
2567  TLBack.vi_wr[3 * n + 1] = currentValue1; // initial value (resend, just for verif ?)
2568  TLBack.vi_wr[3 * n + 2] = TLsend.vi_rd[2 * ( indexInTLsend + i2 )]; // from proc on comp2
2569  // also fill tag values
2570  for( size_t k = 0; k < total_tag_len; k++ )
2571  {
2572  TLBack.vr_rd[total_tag_len * n + k] =
2573  TLsend.vr_rd[total_tag_len * indexInTLsend + k]; // deep copy of tag values
2574  }
2575  }
2576  }
2577  indexInTLreq += size1;
2578  indexInTLsend += size2;
2579  }
2580  }
2581 
2582  // invoke crystal router
2583  pco->proc_config().crystal_router()->gs_transfer( 1, TLBack, 0 );
2584 
2585  // end copy from comm graph
2586  // after we are done sending, we need to set those tag values, in a reverse process compared to send
2587  n1 = TLBack.get_n();
2588  double* ptrVal = &TLBack.vr_rd[0]; //
2589  for( int i = 0; i < n1; i++ )
2590  {
2591  const int gid = TLBack.vi_rd[3 * i + 1]; // marker
2592  const auto mapIt = eh_by_gid.find( gid );
2593  if( mapIt == eh_by_gid.end() ) continue;
2594 
2595  EntityHandle eh = mapIt->second;
2596  // now loop over tags
2597  for( size_t j = 0; j < tagList.size(); j++ )
2598  {
2599  MB_CHK_ERR( context.MBI->tag_set_data( tagList[j], &eh, 1, (void*)ptrVal ) );
2600  // advance the pointer/index
2601  ptrVal += tagLengths[j]; // at the end of tag data per call
2602  }
2603  }
2604  }
2605 #endif
2606  return MB_SUCCESS;
2607 }
2608 
2609 #ifdef MOAB_HAVE_TEMPESTREMAP
2610 // Helper: get the 2D entities of the TempestRemap CoveringMesh attached to this app.
2611 // Returns an empty range (and success) if no remapper is attached.
2612 static ErrCode get_coverage_entities( iMOAB_AppID pid, moab::Range& covEnts )
2613 {
2614  appData& data = context.appDatas[*pid];
2615  covEnts.clear();
2616  if( data.tempestData.remapper == nullptr ) return moab::MB_SUCCESS;
2617  covEnts = data.tempestData.remapper->GetMeshEntities( moab::Remapper::CoveringMesh );
2618  return moab::MB_SUCCESS;
2619 }
2620 
2621 /**
2622  * \brief Return the number of 2D elements in the TempestRemap CoveringMesh of this app
2623  * and (optionally) their global IDs and centroid coordinates.
2624  *
2625  * Used to populate analytic source fields directly on the coverage entities of a
2626  * dual-map intersection app, bypassing the cmpAtm->cplAtm->cplDualMap migration
2627  * chain — useful for BFB testing of dual-map CAAS where every rank must see an
2628  * identical input set independent of how the source mesh was partitioned.
2629  *
2630  * Call once with gids=NULL, centroids=NULL to query num_cov_elems, then allocate
2631  * and call again to fill the buffers.
2632  *
2633  * \param[in] pid Application ID with an attached TempestRemap remapper
2634  * (e.g. the dual-map intersection app).
2635  * \param[inout] num_cov_elems On input: ignored (or capacity); on output: number of
2636  * 2D entities in the covering mesh on this rank.
2637  * \param[out] gids Optional. If non-null, filled with the global IDs of
2638  * the covering entities (size num_cov_elems).
2639  * \param[out] centroids Optional. If non-null, filled with the arithmetic mean
2640  * of vertex coords per element (size 3*num_cov_elems,
2641  * x,y,z interleaved).
2642  * \return ErrCode MB_SUCCESS on success.
2643  */
2644 ErrCode iMOAB_GetCoverageMeshInfo( iMOAB_AppID pid, int* num_cov_elems, int* gids, double* centroids )
2645 {
2646  moab::Range covEnts;
2647  MB_CHK_ERR( get_coverage_entities( pid, covEnts ) );
2648  *num_cov_elems = static_cast< int >( covEnts.size() );
2649  if( covEnts.empty() ) return moab::MB_SUCCESS;
2650 
2651  if( gids != nullptr )
2652  {
2653  std::vector< int > tmpGids( covEnts.size() );
2654  MB_CHK_ERR( context.MBI->tag_get_data( context.globalID_tag, covEnts, tmpGids.data() ) );
2655  std::copy( tmpGids.begin(), tmpGids.end(), gids );
2656  }
2657 
2658  if( centroids != nullptr )
2659  {
2660  const moab::EntityHandle* conn;
2661  int numNodes;
2662  std::vector< double > coords;
2663  int i = 0;
2664  for( moab::Range::iterator it = covEnts.begin(); it != covEnts.end(); ++it, ++i )
2665  {
2666  MB_CHK_ERR( context.MBI->get_connectivity( *it, conn, numNodes ) );
2667  coords.resize( 3 * numNodes );
2668  MB_CHK_ERR( context.MBI->get_coords( conn, numNodes, coords.data() ) );
2669  double cx = 0.0, cy = 0.0, cz = 0.0;
2670  for( int v = 0; v < numNodes; v++ )
2671  {
2672  cx += coords[3 * v + 0];
2673  cy += coords[3 * v + 1];
2674  cz += coords[3 * v + 2];
2675  }
2676  centroids[3 * i + 0] = cx / numNodes;
2677  centroids[3 * i + 1] = cy / numNodes;
2678  centroids[3 * i + 2] = cz / numNodes;
2679  }
2680  }
2681  return moab::MB_SUCCESS;
2682 }
2683 
2684 /**
2685  * \brief Set a double tag on every entity of the TempestRemap CoveringMesh of this app.
2686  *
2687  * Values are written in the same order as iMOAB_GetCoverageMeshInfo returns gids
2688  * (i.e. moab::Range iteration order over the covering set entities). The tag must
2689  * already be defined on the app via iMOAB_DefineTagStorage.
2690  *
2691  * This bypasses the partition-dependent migration path and is useful for BFB tests
2692  * that must populate the source field on the actual coverage cells consumed by
2693  * iMOAB_ApplyScalarProjectionWeights.
2694  *
2695  * \param[in] pid Application ID with an attached TempestRemap remapper.
2696  * \param[in] tag_storage_name Name of the (already-defined) double tag.
2697  * \param[in] num_tag_storage_length Total number of values supplied (= num_cov_elems *
2698  * components_per_entity).
2699  * \param[in] tag_storage_data The values to write, in covering-Range order.
2700  * \return ErrCode MB_SUCCESS on success.
2701  */
2702 ErrCode iMOAB_SetDoubleTagStorageOnCoverage( iMOAB_AppID pid,
2703  const iMOAB_String tag_storage_name,
2704  int* num_tag_storage_length,
2705  double* tag_storage_data )
2706 {
2707  moab::Range covEnts;
2708  MB_CHK_ERR( get_coverage_entities( pid, covEnts ) );
2709  if( covEnts.empty() ) return moab::MB_SUCCESS;
2710 
2711  std::string tagName( tag_storage_name );
2712  moab::Tag tag = nullptr;
2713  if( moab::MB_SUCCESS != context.MBI->tag_get_handle( tagName.c_str(), tag ) || !tag ) return moab::MB_FAILURE;
2714 
2715  int tagLen = 0;
2716  MB_CHK_ERR( context.MBI->tag_get_length( tag, tagLen ) );
2717  moab::DataType dtype;
2718  MB_CHK_ERR( context.MBI->tag_get_data_type( tag, dtype ) );
2719  if( dtype != moab::MB_TYPE_DOUBLE ) return moab::MB_FAILURE;
2720 
2721  const int needed = tagLen * static_cast< int >( covEnts.size() );
2722  if( *num_tag_storage_length < needed ) return moab::MB_FAILURE;
2723 
2724  MB_CHK_ERR( context.MBI->tag_set_data( tag, covEnts, tag_storage_data ) );
2725  return moab::MB_SUCCESS;
2726 }
2727 #endif // MOAB_HAVE_TEMPESTREMAP
2728 
2730  const iMOAB_String tag_storage_names,
2731  int* num_tag_storage_length,
2732  int* ent_type,
2733  double* tag_storage_data )
2734 {
2735  // exactly the same code, except tag type check
2736  std::string tag_names( tag_storage_names );
2737  // exactly the same code as for int tag :) maybe should check the type of tag too
2738  std::vector< std::string > tagNames;
2739  std::vector< Tag > tagHandles;
2740  std::string separator( ":" );
2741  split_tag_names( tag_names, separator, tagNames );
2742 
2743  appData& data = context.appDatas[*pid];
2744 
2745  // set it on a subset of entities, based on type and length
2746  Range* ents_to_get = nullptr;
2747 
2748  if( *ent_type == 0 ) // vertices
2749  {
2750  ents_to_get = &data.all_verts;
2751  }
2752  else if( *ent_type == 1 )
2753  {
2754  ents_to_get = &data.primary_elems;
2755  }
2756  int nents_to_get = (int)ents_to_get->size();
2757  int position = 0;
2758  for( size_t i = 0; i < tagNames.size(); i++ )
2759  {
2760  if( data.tagMap.find( tagNames[i] ) == data.tagMap.end() )
2761  {
2762  return moab::MB_FAILURE;
2763  } // tag not defined
2764 
2765  Tag tag = data.tagMap[tagNames[i]];
2766 
2767  int tagLength = 0;
2768  MB_CHK_ERR( context.MBI->tag_get_length( tag, tagLength ) );
2769 
2770  DataType dtype;
2771  MB_CHK_ERR( context.MBI->tag_get_data_type( tag, dtype ) );
2772 
2773  if( dtype != MB_TYPE_DOUBLE )
2774  {
2775  return moab::MB_FAILURE;
2776  }
2777 
2778  if( position + nents_to_get * tagLength > *num_tag_storage_length )
2779  return moab::MB_FAILURE; // too many entity values to get
2780 
2781  MB_CHK_ERR( context.MBI->tag_get_data( tag, *ents_to_get, &tag_storage_data[position] ) );
2782  position = position + nents_to_get * tagLength;
2783  }
2784 
2785  return moab::MB_SUCCESS; // no error
2786 }
2787 
2788 ErrCode iMOAB_SynchronizeTags( iMOAB_AppID pid, int* num_tag, int* tag_indices, int* ent_type )
2789 {
2790 #ifdef MOAB_HAVE_MPI
2791  appData& data = context.appDatas[*pid];
2792  Range ent_exchange;
2793  std::vector< Tag > tags;
2794 
2795  for( int i = 0; i < *num_tag; i++ )
2796  {
2797  if( tag_indices[i] < 0 || tag_indices[i] >= (int)data.tagList.size() )
2798  {
2799  return moab::MB_FAILURE;
2800  } // error in tag index
2801 
2802  tags.push_back( data.tagList[tag_indices[i]] );
2803  }
2804 
2805  if( *ent_type == 0 )
2806  {
2807  ent_exchange = data.all_verts;
2808  }
2809  else if( *ent_type == 1 )
2810  {
2811  ent_exchange = data.primary_elems;
2812  }
2813  else
2814  {
2815  return moab::MB_FAILURE;
2816  } // unexpected type
2817 
2818  ParallelComm* pco = context.appDatas[*pid].pcomm;
2819 
2820  MB_CHK_ERR( pco->exchange_tags( tags, tags, ent_exchange ) );
2821 
2822 #else
2823  /* do nothing if serial */
2824  UNUSED( pid );
2825  UNUSED( num_tag );
2826  UNUSED( tag_indices );
2827  UNUSED( ent_type );
2828 #endif
2829 
2830  return moab::MB_SUCCESS;
2831 }
2832 
2833 ErrCode iMOAB_ReduceTagsMax( iMOAB_AppID pid, int* tag_index, int* ent_type )
2834 {
2835 #ifdef MOAB_HAVE_MPI
2836  appData& data = context.appDatas[*pid];
2837  Range ent_exchange;
2838 
2839  if( *tag_index < 0 || *tag_index >= (int)data.tagList.size() )
2840  {
2841  return moab::MB_FAILURE;
2842  } // error in tag index
2843 
2844  Tag tagh = data.tagList[*tag_index];
2845 
2846  if( *ent_type == 0 )
2847  {
2848  ent_exchange = data.all_verts;
2849  }
2850  else if( *ent_type == 1 )
2851  {
2852  ent_exchange = data.primary_elems;
2853  }
2854  else
2855  {
2856  return moab::MB_FAILURE;
2857  } // unexpected type
2858 
2859  ParallelComm* pco = context.appDatas[*pid].pcomm;
2860  // we could do different MPI_Op; do not bother now, we will call from fortran
2861  MB_CHK_ERR( pco->reduce_tags( tagh, MPI_MAX, ent_exchange ) );
2862 
2863 #else
2864  /* do nothing if serial */
2865  UNUSED( pid );
2866  UNUSED( tag_index );
2867  UNUSED( ent_type );
2868 #endif
2869  return moab::MB_SUCCESS;
2870 }
2871 
2873  iMOAB_LocalID* local_index,
2874  int* num_adjacent_elements,
2875  iMOAB_LocalID* adjacent_element_IDs )
2876 {
2877  assert( local_index && *local_index >= 0 );
2878 
2879  // one neighbor for each subentity of dimension-1
2880  MeshTopoUtil mtu( context.MBI );
2881  appData& data = context.appDatas[*pid];
2882  EntityHandle eh = data.primary_elems[*local_index];
2883  Range adjs;
2884  MB_CHK_SET_ERR( mtu.get_bridge_adjacencies( eh, data.dimension - 1, data.dimension, adjs ),
2885  "Getting bridge adjacencies failed" );
2886 
2887  if( *num_adjacent_elements < (int)adjs.size() )
2888  {
2889  return moab::MB_FAILURE;
2890  } // not dimensioned correctly
2891 
2892  *num_adjacent_elements = (int)adjs.size();
2893 
2894  for( int i = 0; i < *num_adjacent_elements; i++ )
2895  {
2896  adjacent_element_IDs[i] = data.primary_elems.index( adjs[i] );
2897  }
2898 
2899  return moab::MB_SUCCESS;
2900 }
2901 
2902 #if 0
2903 
2904 ErrCode iMOAB_GetNeighborVertices ( iMOAB_AppID pid, iMOAB_LocalID* local_vertex_ID, int* num_adjacent_vertices, iMOAB_LocalID* adjacent_vertex_IDs )
2905 {
2906  return moab::MB_SUCCESS;
2907 }
2908 
2909 #endif
2910 
2911 ErrCode iMOAB_CreateVertices( iMOAB_AppID pid, int* coords_len, int* dim, double* coordinates )
2912 {
2913  appData& data = context.appDatas[*pid];
2914 
2915  if( !data.local_verts.empty() ) // we should have no vertices in the app
2916  {
2917  return moab::MB_FAILURE;
2918  }
2919 
2920  int nverts = *coords_len / *dim;
2921 
2922  MB_CHK_ERR( context.MBI->create_vertices( coordinates, nverts, data.local_verts ) );
2923 
2925 
2926  // also add the vertices to the all_verts range
2927  data.all_verts.merge( data.local_verts );
2928  return moab::MB_SUCCESS;
2929 }
2930 
2932  int* num_elem,
2933  int* type,
2934  int* num_nodes_per_element,
2935  int* connectivity,
2936  int* block_ID )
2937 {
2938  // Create elements
2939  appData& data = context.appDatas[*pid];
2940 
2941  ReadUtilIface* read_iface;
2942  MB_CHK_ERR( context.MBI->query_interface( read_iface ) );
2943 
2944  EntityType mbtype = (EntityType)( *type );
2945  EntityHandle actual_start_handle;
2946  EntityHandle* array = nullptr;
2947  MB_CHK_ERR(
2948  read_iface->get_element_connect( *num_elem, *num_nodes_per_element, mbtype, 1, actual_start_handle, array ) );
2949 
2950  // fill up with actual connectivity from input; assume the vertices are in order, and start
2951  // vertex is the first in the current data vertex range
2952  EntityHandle firstVertex = data.local_verts[0];
2953 
2954  for( int j = 0; j < *num_elem * ( *num_nodes_per_element ); j++ )
2955  {
2956  array[j] = connectivity[j] + firstVertex - 1;
2957  } // assumes connectivity uses 1 based array (from fortran, mostly)
2958 
2959  Range new_elems( actual_start_handle, actual_start_handle + *num_elem - 1 );
2960 
2961  MB_CHK_ERR( context.MBI->add_entities( data.file_set, new_elems ) );
2962 
2963  data.primary_elems.merge( new_elems );
2964 
2965  // add to adjacency
2966  MB_CHK_ERR( read_iface->update_adjacencies( actual_start_handle, *num_elem, *num_nodes_per_element, array ) );
2967 
2968  // organize all new elements in block, with the given block ID; if the block set is not
2969  // existing, create a new mesh set;
2970  Range sets;
2971  int set_no = *block_ID;
2972  const void* setno_ptr = &set_no;
2973 
2974  EntityHandle block_set;
2976  &setno_ptr, 1, sets );
2977 
2978  if( MB_FAILURE == rval || sets.empty() )
2979  {
2980  // create a new set, with this block ID
2981  MB_CHK_ERR( context.MBI->create_meshset( MESHSET_SET, block_set ) );
2982 
2983  MB_CHK_ERR( context.MBI->tag_set_data( context.material_tag, &block_set, 1, &set_no ) );
2984 
2985  // add the material set to file set
2986  MB_CHK_ERR( context.MBI->add_entities( data.file_set, &block_set, 1 ) );
2987  }
2988  else
2989  {
2990  block_set = sets[0];
2991  } // first set is the one we want
2992 
2993  /// add the new ents to the clock set
2994  MB_CHK_ERR( context.MBI->add_entities( block_set, new_elems ) );
2995 
2996  return moab::MB_SUCCESS;
2997 }
2998 
2999 ErrCode iMOAB_SetGlobalInfo( iMOAB_AppID pid, int* num_global_verts, int* num_global_elems )
3000 {
3001  appData& data = context.appDatas[*pid];
3002  data.num_global_vertices = *num_global_verts;
3003  data.num_global_elements = *num_global_elems;
3004  return moab::MB_SUCCESS;
3005 }
3006 
3007 ErrCode iMOAB_GetGlobalInfo( iMOAB_AppID pid, int* num_global_verts, int* num_global_elems )
3008 {
3009  appData& data = context.appDatas[*pid];
3010  if( nullptr != num_global_verts )
3011  {
3012  *num_global_verts = data.num_global_vertices;
3013  }
3014  if( nullptr != num_global_elems )
3015  {
3016  *num_global_elems = data.num_global_elements;
3017  }
3018 
3019  return moab::MB_SUCCESS;
3020 }
3021 
3022 #ifdef MOAB_HAVE_MPI
3023 
3024 // this makes sense only for parallel runs
3025 ErrCode iMOAB_ResolveSharedEntities( iMOAB_AppID pid, int* num_verts, int* marker )
3026 {
3027  appData& data = context.appDatas[*pid];
3028  ParallelComm* pco = context.appDatas[*pid].pcomm;
3029  EntityHandle cset = data.file_set;
3030  int dum_id = 0;
3031  ErrorCode rval;
3032  if( data.primary_elems.empty() )
3033  {
3034  // skip actual resolve, assume vertices are distributed already ,
3035  // no need to share them
3036  }
3037  else
3038  {
3039  // create an integer tag for resolving ; maybe it can be a long tag in the future
3040  // (more than 2 B vertices;)
3041 
3042  Tag stag;
3043  MB_CHK_ERR( context.MBI->tag_get_handle( "__sharedmarker", 1, MB_TYPE_INTEGER, stag,
3044  MB_TAG_CREAT | MB_TAG_DENSE, &dum_id ) );
3045 
3046  if( *num_verts > (int)data.local_verts.size() )
3047  {
3048  return moab::MB_FAILURE;
3049  } // we are not setting the size
3050 
3051  MB_CHK_ERR( context.MBI->tag_set_data( stag, data.local_verts, (void*)marker ) ); // assumes integer tag
3052 
3053  MB_CHK_ERR( pco->resolve_shared_ents( cset, -1, -1, &stag ) );
3054 
3055  MB_CHK_ERR( context.MBI->tag_delete( stag ) );
3056  }
3057  // provide partition tag equal to rank
3058  Tag part_tag;
3059  dum_id = -1;
3060  rval = context.MBI->tag_get_handle( "PARALLEL_PARTITION", 1, MB_TYPE_INTEGER, part_tag,
3061  MB_TAG_CREAT | MB_TAG_SPARSE, &dum_id );
3062 
3063  if( part_tag == nullptr || ( ( rval != MB_SUCCESS ) && ( rval != MB_ALREADY_ALLOCATED ) ) )
3064  {
3065  std::cout << " can't get par part tag.\n";
3066  return moab::MB_FAILURE;
3067  }
3068 
3069  int rank = pco->rank();
3070  MB_CHK_ERR( context.MBI->tag_set_data( part_tag, &cset, 1, &rank ) );
3071 
3072  return moab::MB_SUCCESS;
3073 }
3074 
3075 // this assumes that this was not called before
3076 ErrCode iMOAB_DetermineGhostEntities( iMOAB_AppID pid, int* ghost_dim, int* num_ghost_layers, int* bridge_dim )
3077 {
3078  // verify we have valid ghost layers input specified. If invalid, exit quick.
3079  if( num_ghost_layers && *num_ghost_layers <= 0 )
3080  {
3081  return moab::MB_SUCCESS;
3082  } // nothing to do
3083 
3084  appData& data = context.appDatas[*pid];
3085  ParallelComm* pco = context.appDatas[*pid].pcomm;
3086 
3087  // maybe we should be passing this too; most of the time we do not need additional ents collective call
3088  constexpr int addl_ents = 0;
3089  MB_CHK_ERR( pco->exchange_ghost_cells( *ghost_dim, *bridge_dim, 1, // get only one layer of ghost entities
3090  addl_ents, true, true, &data.file_set ) );
3091  for( int i = 2; i <= *num_ghost_layers; i++ )
3092  {
3093  MB_CHK_ERR( pco->correct_thin_ghost_layers() ); // correct for thin layers
3094  MB_CHK_ERR( pco->exchange_ghost_cells( *ghost_dim, *bridge_dim, i, // iteratively get one extra layer
3095  addl_ents, true, true, &data.file_set ) );
3096  }
3097 
3098  // Update ghost layer information
3099  data.num_ghost_layers = *num_ghost_layers;
3100 
3101  // now re-establish all mesh info; will reconstruct mesh info, based solely on what is in the file set
3102  return iMOAB_UpdateMeshInfo( pid );
3103 }
3104 
3105 /**
3106  * @brief Transfer mesh data from sender component to receiver component.
3107  * @ingroup iMOABParallel
3108  *
3109  * @details Implements distributed mesh transfer protocol that moves mesh entities (elements, vertices,
3110  * and associated data) from one set of MPI processes to another. Transfer is orchestrated through a
3111  * communication graph that maps sender processes to receiver processes based on partitioning strategy.
3112  *
3113  * @par Algorithm Workflow:
3114  * -# <b>COMMUNICATOR SETUP:</b> Resolve MPI communicator and group handles (handles Fortran F2C conversions).
3115  * Extract sender group from MOAB ParallelComm. Create ParCommGraph for sender→receiver mapping.
3116  * -# <b>ENTITY SELECTION:</b> Determine owned entities to transfer: owned_elems or local_verts (point cloud mode).
3117  * Store ParCommGraph in context.appDatas[pid].pgraph[rcompid].
3118  * -# <b>PARTITIONING STRATEGY:</b>
3119  * - method == 0 (TRIVIAL): Gather global entity counts, compute block distribution, broadcast via send_graph
3120  * - method != 0 (COMPUTED): Use graph/geometric partitioning considering connectivity and spatial distribution
3121  * -# <b>MESH TRANSFER:</b> Pack mesh entities and data, use ParCommGraph to route to receivers via send_mesh_parts
3122  *
3123  * @par Data Transferred:
3124  * - <b>Mesh entities:</b> elements (triangles, quads, etc.) and vertices
3125  * - <b>Geometric data:</b> vertex coordinates, element connectivity
3126  * - <b>Metadata:</b> material sets, boundary conditions, tags
3127  * - <b>Global IDs:</b> for entity identification across processes
3128  *
3129  * @param[in] pid Application ID for sender component
3130  * @param[in] joint_communicator MPI communicator spanning both sender and receiver groups
3131  * @param[in] receivingGroup MPI group of receiving processes
3132  * @param[in] rcompid Receiver component ID (key for communication graph storage)
3133  * @param[in] method Partitioning method: 0=trivial, !=0=computed
3134  *
3135  * @note <b>Communication:</b> Collective (MPI_Allgather) and point-to-point (non-blocking sends)
3136  * @warning Cleans up MPI groups on early return to avoid resource leaks
3137  *
3138  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE on error
3139  */
3141  MPI_Comm* joint_communicator,
3142  MPI_Group* receivingGroup,
3143  int* rcompid,
3144  int* method )
3145 {
3146  // Validate input parameters to ensure they are not null pointers
3147  assert( joint_communicator != nullptr );
3148  assert( receivingGroup != nullptr );
3149  assert( rcompid != nullptr );
3150 
3151  int ierr;
3152  // Get application data and parallel communicator for this component
3153  appData& data = context.appDatas[*pid];
3154  ParallelComm* pco = context.appDatas[*pid].pcomm;
3155 
3156  // Handle Fortran-to-C MPI handle conversion if needed
3157  // Fortran passes MPI handles as integers, C++ expects MPI_Comm/MPI_Group objects
3158  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3159  : *joint_communicator );
3160  MPI_Group recvGroup =
3161  ( data.is_fortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( receivingGroup ) ) : *receivingGroup );
3162 
3163  // Extract sender communicator from MOAB's parallel communicator
3164  // This represents the group of processes that will send mesh data
3165  MPI_Comm sender = pco->comm();
3166  // Extract sender group from the sender communicator to understand sender process topology
3167  MPI_Group senderGroup;
3168  ierr = MPI_Comm_group( sender, &senderGroup );
3169  if( ierr != 0 ) return moab::MB_FAILURE;
3170 
3171  // Create communication graph to manage sender->receiver process mapping
3172  // This graph will determine which entities go to which receiver processes
3173  ParCommGraph* cgraph =
3174  new ParCommGraph( global, senderGroup, recvGroup, context.appDatas[*pid].global_id, *rcompid );
3175 
3176  // Store the communication graph in the application's graph map for later use
3177  // The key is the receiver component ID (*rcompid)
3178  context.appDatas[*pid].pgraph[*rcompid] = cgraph;
3179 
3180  // Get current sender rank for potential debugging/logging
3181  int sender_rank = -1;
3182  MPI_Comm_rank( sender, &sender_rank );
3183 
3184  // Determine which entities to send: primary elements or vertices (for point clouds)
3185  std::vector< int > number_elems_per_part;
3186  Range owned = context.appDatas[*pid].owned_elems;
3187  if( owned.size() == 0 )
3188  {
3189  // If no elements, this must be a point cloud - send vertices instead
3190  owned = context.appDatas[*pid].local_verts;
3191  }
3192 
3193  // Choose partitioning strategy based on method parameter
3194  if( *method == 0 ) // Trivial partitioning: simple block distribution
3195  {
3196  // Count local entities on this sender process
3197  int local_owned_elem = (int)owned.size();
3198  int size = pco->size();
3199  int rank = pco->rank();
3200 
3201  // Prepare array to hold entity counts from all sender processes
3202  number_elems_per_part.resize( size );
3203  number_elems_per_part[rank] = local_owned_elem;
3204  // Gather entity counts from all sender processes using MPI_Allgather
3205  // This allows each sender to know the total entity count for load balancing
3206 #if ( MPI_VERSION >= 2 )
3207  // Use "in place" option for efficiency (MPI 2.0+)
3208  ierr = MPI_Allgather( MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, &number_elems_per_part[0], 1, MPI_INT, sender );
3209 #else
3210  // Fallback for older MPI versions
3211  {
3212  std::vector< int > all_tmp( size );
3213  ierr = MPI_Allgather( &number_elems_per_part[rank], 1, MPI_INT, &all_tmp[0], 1, MPI_INT, sender );
3214  number_elems_per_part = all_tmp;
3215  }
3216 #endif
3217 
3218  if( ierr != 0 )
3219  {
3220  return moab::MB_FAILURE;
3221  }
3222 
3223  // Compute trivial partition: each receiver gets roughly equal share of entities
3224  // This creates a simple block distribution based on entity counts
3225  MB_CHK_ERR( cgraph->compute_trivial_partition( number_elems_per_part ) );
3226 
3227  // Send the partition mapping to all receiver processes
3228  // This tells receivers which senders will send them data
3229  MB_CHK_ERR( cgraph->send_graph( global ) );
3230  }
3231  else // Advanced partitioning: graph-based or geometric partitioning
3232  {
3233  // Use sophisticated partitioning that considers entity connectivity or spatial distribution
3234  // This can lead to better load balancing and reduced communication
3235  MB_CHK_ERR( cgraph->compute_partition( pco, owned, *method ) );
3236 
3237  // Send the computed partition mapping to receiver processes
3238  // This includes more complex sender->receiver mappings than trivial partition
3239  MB_CHK_ERR( cgraph->send_graph_partition( pco, global ) );
3240  }
3241  // Execute the actual mesh transfer based on the communication graph
3242  // This packs mesh entities and sends them to appropriate receiver processes
3243  // pco is needed for MOAB operations (packing), not for MPI communication
3244  MB_CHK_ERR( cgraph->send_mesh_parts( global, pco, owned ) );
3245 
3246  // Clean up temporary MPI group to prevent memory leaks
3247  MPI_Group_free( &senderGroup );
3248  return moab::MB_SUCCESS;
3249 }
3250 
3251 /**
3252  * @brief Receive mesh data on target component from sending component.
3253  * @ingroup iMOABParallel
3254  *
3255  * @details Implements receiver side of distributed mesh transfer protocol. Receives mesh entities
3256  * and data from multiple sender processes, consolidates them into local mesh, and handles entity
3257  * deduplication based on global IDs.
3258  *
3259  * @par Algorithm Workflow:
3260  * -# <b>COMMUNICATOR SETUP:</b> Resolve MPI communicator and group handles (Fortran F2C conversions).
3261  * Extract receiver group from MOAB ParallelComm. Create ParCommGraph with reverse mapping.
3262  * -# <b>COMMUNICATION GRAPH RECEPTION:</b> Receive communication graph via receive_comm_graph.
3263  * Parse packed graph array to extract sender ranks for this receiver process.
3264  * -# <b>SENDER IDENTIFICATION:</b> Determine current receiver's rank index. Walk through packed
3265  * graph array to find all sender ranks that contribute. Build senders_local list.
3266  * -# <b>MESH RECEPTION:</b> Invoke receive_mesh to pull mesh parts from identified sender processes.
3267  * Deposit mesh entities into data.file_set (local mesh container).
3268  * -# <b>POST-PROCESSING:</b> Optional vertex merging for entities with identical GLOBAL_ID from
3269  * different senders. Re-establish mesh information and update local mesh metadata.
3270  *
3271  * @par Data Received:
3272  * - <b>Mesh entities:</b> elements and vertices with geometric data
3273  * - <b>Connectivity:</b> element-to-vertex relationships
3274  * - <b>Tags:</b> material properties, boundary conditions, custom data
3275  * - <b>Global IDs:</b> for entity identification and deduplication
3276  *
3277  * @param[in] pid Application ID for receiver component
3278  * @param[in] joint_communicator MPI communicator spanning both sender and receiver groups
3279  * @param[in] sendingGroup MPI group of sending processes
3280  * @param[in] scompid Sender component ID (key for communication graph storage)
3281  *
3282  * @note <b>Communication:</b> Collective (receive graph) and point-to-point (receive mesh data)
3283  * @warning Vertex merging only performed if receiving from 2+ senders
3284  *
3285  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE on error
3286  */
3287 ErrCode iMOAB_ReceiveMesh( iMOAB_AppID pid, MPI_Comm* joint_communicator, MPI_Group* sendingGroup, int* scompid )
3288 {
3289  // Validate input parameters to ensure they are not null pointers
3290  assert( joint_communicator != nullptr );
3291  assert( sendingGroup != nullptr );
3292  assert( scompid != nullptr );
3293 
3294  ErrorCode rval;
3295  // Get application data and parallel communicator for this component
3296  appData& data = context.appDatas[*pid];
3297  ParallelComm* pco = context.appDatas[*pid].pcomm;
3298  MPI_Comm receive = pco->comm();
3299  EntityHandle local_set = data.file_set;
3300 
3301  // Handle Fortran-to-C MPI handle conversion if needed
3302  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3303  : *joint_communicator );
3304  MPI_Group sendGroup =
3305  ( data.is_fortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( sendingGroup ) ) : *sendingGroup );
3306 
3307  // Extract receiver group from the receiver communicator to understand receiver process topology
3308  MPI_Group receiverGroup;
3309  int ierr = MPI_Comm_group( receive, &receiverGroup );CHK_MPI_ERR( ierr );
3310 
3311  // Create communication graph with reverse mapping (sender->receiver)
3312  // This graph will manage the reception of mesh data from sender processes
3313  ParCommGraph* cgraph =
3314  new ParCommGraph( global, sendGroup, receiverGroup, *scompid, context.appDatas[*pid].global_id );
3315 
3316  // Store the communication graph in the application's graph map for later use
3317  // The key is the sender component ID (*scompid)
3318  context.appDatas[*pid].pgraph[*scompid] = cgraph;
3319 
3320  // Get current receiver rank for process identification
3321  int receiver_rank = -1;
3322  MPI_Comm_rank( receive, &receiver_rank );
3323 
3324  // Receive the communication graph from sender processes
3325  // This graph tells this receiver which senders will send it data
3326  std::vector< int > pack_array;
3327  MB_CHK_ERR( cgraph->receive_comm_graph( global, pco, pack_array ) );
3328 
3329  // Determine this receiver's index within the receiver group
3330  int current_receiver = cgraph->receiver( receiver_rank );
3331 
3332  // Parse the packed communication graph to find which senders contribute to this receiver
3333  std::vector< int > senders_local;
3334  size_t n = 0;
3335 
3336  // Walk through the packed graph array to find sender ranks for this receiver
3337  while( n < pack_array.size() )
3338  {
3339  if( current_receiver == pack_array[n] )
3340  {
3341  // Found this receiver's entry - extract all contributing sender ranks
3342  for( int j = 0; j < pack_array[n + 1]; j++ )
3343  {
3344  senders_local.push_back( pack_array[n + 2 + j] );
3345  }
3346  break;
3347  }
3348  // Move to next receiver's entry in the packed array
3349  n = n + 2 + pack_array[n + 1];
3350  }
3351 
3352 #ifdef VERBOSE
3353  std::cout << " receiver " << current_receiver << " at rank " << receiver_rank << " will receive from "
3354  << senders_local.size() << " tasks: ";
3355 
3356  for( int k = 0; k < (int)senders_local.size(); k++ )
3357  {
3358  std::cout << " " << senders_local[k];
3359  }
3360 
3361  std::cout << "\n";
3362 #endif
3363 
3364  // Validate that we have senders contributing to this receiver
3365  if( senders_local.empty() )
3366  {
3367  std::cout << " we do not have any senders for receiver rank " << receiver_rank << "\n";
3368  }
3369 
3370  // Receive mesh data from all identified sender processes
3371  // This unpacks mesh entities and deposits them into the local mesh set
3372  MB_CHK_ERR( cgraph->receive_mesh( global, pco, local_set, senders_local ) );
3373 
3374  // Post-process received mesh data to handle potential vertex duplication
3375  // Vertices with identical GLOBAL_ID from different senders need to be merged
3376  Tag idtag;
3377  MB_CHK_ERR( context.MBI->tag_get_handle( "GLOBAL_ID", idtag ) );
3378 
3379  // Get all entities in the local mesh set
3380  Range local_ents;
3381  MB_CHK_ERR( context.MBI->get_entities_by_handle( local_set, local_ents ) );
3382 
3383  // Only perform vertex merging for regular meshes (not point clouds)
3384  if( !local_ents.all_of_type( MBVERTEX ) )
3385  {
3386  // Merge vertices only if we received data from multiple senders
3387  if( (int)senders_local.size() >= 2 ) // Need to remove duplicate vertices
3388  {
3389 
3390  // Separate vertices from elements for processing
3391  Range local_verts = local_ents.subset_by_type( MBVERTEX );
3392  Range local_elems = subtract( local_ents, local_verts );
3393 
3394  // Temporarily remove vertices from local set for merging
3395  MB_CHK_ERR( context.MBI->remove_entities( local_set, local_verts ) );
3396 
3397 #ifdef VERBOSE
3398  std::cout << "current_receiver " << current_receiver << " local verts: " << local_verts.size() << "\n";
3399 #endif
3400  // Merge vertices with identical GLOBAL_ID values
3401  MergeMesh mm( context.MBI );
3402  MB_CHK_ERR( mm.merge_using_integer_tag( local_verts, idtag ) );
3403 
3404  // Get the merged vertices back from element connectivity
3405  Range new_verts;
3406  MB_CHK_ERR( context.MBI->get_connectivity( local_elems, new_verts ) );
3407 
3408 #ifdef VERBOSE
3409  std::cout << "after merging: new verts: " << new_verts.size() << "\n";
3410 #endif
3411  // Add the merged vertices back to the local set
3412  MB_CHK_ERR( context.MBI->add_entities( local_set, new_verts ) );
3413  }
3414  }
3415  else
3416  // Mark this as a point cloud if we only have vertices
3417  data.point_cloud = true;
3418 
3419  if( !data.point_cloud )
3420  {
3421  // For regular meshes, resolve shared entities (vertices) across process boundaries
3422  MB_CHK_ERR( pco->resolve_shared_ents( local_set, -1, -1, &idtag ) );
3423  }
3424  else
3425  {
3426  // For point clouds, set partition tag to current rank for visualization
3427  Tag densePartTag;
3428  rval = context.MBI->tag_get_handle( "partition", densePartTag );
3429  if( nullptr != densePartTag && MB_SUCCESS == rval )
3430  {
3431  Range local_verts;
3432  MB_CHK_ERR( context.MBI->get_entities_by_dimension( local_set, 0, local_verts ) );
3433  std::vector< int > vals;
3434  int rank = pco->rank();
3435  vals.resize( local_verts.size(), rank );
3436  MB_CHK_ERR( context.MBI->tag_set_data( densePartTag, local_verts, &vals[0] ) );
3437  }
3438  }
3439  // Set the parallel partition tag to identify which process owns this mesh set
3440  Tag part_tag;
3441  int dum_id = -1;
3442  rval = context.MBI->tag_get_handle( "PARALLEL_PARTITION", 1, MB_TYPE_INTEGER, part_tag,
3443  MB_TAG_CREAT | MB_TAG_SPARSE, &dum_id );
3444 
3445  if( part_tag == nullptr || ( ( rval != MB_SUCCESS ) && ( rval != MB_ALREADY_ALLOCATED ) ) )
3446  {
3447  std::cout << " can't get par part tag.\n";
3448  return moab::MB_FAILURE;
3449  }
3450 
3451  // Tag the local set with the current process rank
3452  int rank = pco->rank();
3453  MB_CHK_ERR( context.MBI->tag_set_data( part_tag, &local_set, 1, &rank ) );
3454 
3455  // Ensure that the GLOBAL_ID tag is properly defined for entity identification
3456  int tagtype = 0; // dense, integer
3457  int numco = 1; // size
3458  int tagindex; // not used
3459  MB_CHK_ERR( iMOAB_DefineTagStorage( pid, "GLOBAL_ID", &tagtype, &numco, &tagindex ) );
3460 
3461  // Update mesh information with current data statistics
3462  MB_CHK_ERR( iMOAB_UpdateMeshInfo( pid ) );
3463 
3464  // Clean up temporary MPI group to prevent memory leaks
3465  MPI_Group_free( &receiverGroup );
3466 
3467  return moab::MB_SUCCESS;
3468 }
3469 
3470 /**
3471  * @brief Send element tag data from calling component to another component.
3472  * @ingroup iMOABParallel
3473  *
3474  * @details Transfers tag data (scalar or vector values) associated with mesh elements from one
3475  * component to another using pre-established communication graph. Transfer based on communication
3476  * patterns determined by previous mesh transfer or communication graph computation operations.
3477  *
3478  * @par Algorithm Workflow:
3479  * -# <b>COMMUNICATION GRAPH VALIDATION:</b> Look up ParCommGraph for specified context_id,
3480  * validate existence and initialization, extract graph and parallel communicator.
3481  * -# <b>ENTITY SELECTION:</b> Determine target entities based on component type (point cloud: local_verts,
3482  * regular mesh: owned_elems). Handle special cases (TempestRemap coverage mesh, intersection coverage sets).
3483  * -# <b>TAG PROCESSING:</b> Parse tag names using colon separator for multiple tags, resolve tag handles,
3484  * validate existence. Support scalar and vector tag data.
3485  * -# <b>DATA TRANSFER:</b> Use ParCommGraph to determine routing, pack tag values associated with selected
3486  * entities, execute non-blocking point-to-point transfers via send_tag_values.
3487  *
3488  * @par Data Transferred:
3489  * - <b>Tag values:</b> Scalar or vector data associated with mesh entities
3490  * - <b>Entity mappings:</b> Which entities the tag values belong to
3491  * - <b>Tag metadata:</b> Tag type, size, and component information
3492  * - <b>Global IDs:</b> For entity identification across processes
3493  *
3494  * @param[in] pid Application ID for sender component
3495  * @param[in] tag_storage_name Tag name(s) to send (colon-separated for multiple tags)
3496  * @param[in] joint_communicator MPI communicator spanning sender and receiver groups
3497  * @param[in] context_id Communication graph context ID
3498  *
3499  * @note <b>Use cases:</b> Transfer solution data, send material properties/BCs, exchange computed quantities
3500  * @warning Communication graph must exist before calling (establish via mesh transfer or compute graph)
3501  *
3502  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if graph missing or tags not found
3503  */
3505  const iMOAB_String tag_storage_name,
3506  MPI_Comm* joint_communicator,
3507  int* context_id )
3508 {
3509  // Get application data and look up the communication graph for this context
3510  appData& data = context.appDatas[*pid];
3511  std::map< int, ParCommGraph* >::iterator mt = data.pgraph.find( *context_id );
3512  if( mt == data.pgraph.end() )
3513  {
3514  // Communication graph not found - this means mesh transfer hasn't been performed yet
3515  std::cout << " no par com graph for context_id:" << *context_id << " available contexts:";
3516  for( auto mit = data.pgraph.begin(); mit != data.pgraph.end(); mit++ )
3517  std::cout << " " << mit->first;
3518  std::cout << "\n";
3519  return moab::MB_FAILURE;
3520  }
3521 
3522  // Extract communication graph and parallel communicator
3523  ParCommGraph* cgraph = mt->second;
3524  ParallelComm* pco = context.appDatas[*pid].pcomm;
3525 
3526  // Handle Fortran-to-C MPI handle conversion if needed
3527  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3528  : *joint_communicator );
3529 
3530  // Determine target entities: vertices for point clouds, elements for regular meshes
3531  Range owned = ( data.point_cloud ? data.local_verts : data.owned_elems );
3532 
3533 #ifdef MOAB_HAVE_TEMPESTREMAP
3534  // Handle TempestRemap coverage mesh entities for intersection applications
3535  if( data.tempestData.remapper != nullptr ) // This is the case for intersection operations
3536  {
3537  EntityHandle cover_set = data.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3538  MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3539  }
3540 #else
3541  // Handle coverage set entities for intersection applications (non-TempestRemap case)
3542  // This covers cases where elements have been instantiated in coverage sets during intersection
3543  EntityHandle cover_set = cgraph->get_cover_set(); // Non-null only for intersection applications
3544  if( 0 != cover_set ) MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3545 #endif
3546 
3547  // Parse tag names from the input string (multiple tags separated by colons)
3548  std::string tag_name( tag_storage_name );
3549 
3550  // Parse multiple tag names separated by colons
3551  std::vector< std::string > tagNames;
3552  std::vector< Tag > tagHandles;
3553  std::string separator( ":" );
3554  split_tag_names( tag_name, separator, tagNames );
3555 
3556  // Resolve tag handles for each specified tag name
3557  for( size_t i = 0; i < tagNames.size(); i++ )
3558  {
3559  Tag tagHandle;
3560  ErrorCode rval = context.MBI->tag_get_handle( tagNames[i].c_str(), tagHandle );
3561  if( MB_SUCCESS != rval || nullptr == tagHandle )
3562  {
3563  MB_CHK_SET_ERR( moab::MB_FAILURE,
3564  "can't get tag handle with name: " << tagNames[i].c_str() << " at index " << i );
3565  }
3566  tagHandles.push_back( tagHandle );
3567  }
3568 
3569  // Execute the tag data transfer using the communication graph
3570  // This packs tag values and sends them to appropriate receiver processes
3571  // pco is needed for MOAB operations (packing), not for MPI communication
3572  MB_CHK_ERR( cgraph->send_tag_values( global, pco, owned, tagHandles ) );
3573 
3574  return moab::MB_SUCCESS;
3575 }
3576 
3577 /**
3578  * @brief Receive element tag data from another component.
3579  * @ingroup iMOABParallel
3580  *
3581  * @details Receives tag data (scalar or vector values) associated with mesh elements from another
3582  * component using pre-established communication graph. Received tag values are applied to
3583  * corresponding entities in local mesh, enabling data exchange between coupled components.
3584  *
3585  * @par Algorithm Workflow:
3586  * -# <b>COMMUNICATION GRAPH VALIDATION:</b> Look up ParCommGraph for specified context_id,
3587  * validate existence and initialization, extract graph and parallel communicator.
3588  * -# <b>ENTITY SELECTION:</b> Determine target entities based on component type (point cloud: local_verts,
3589  * regular mesh: owned_elems). Handle special cases (coverage set, TempestRemap coverage mesh).
3590  * -# <b>TAG PROCESSING:</b> Parse tag names using colon separator for multiple tags, resolve tag handles,
3591  * validate existence. Support scalar and vector tag data.
3592  * -# <b>DATA RECEPTION:</b> Use ParCommGraph to receive tag data via receive_tag_values, unpack received
3593  * tag values and associate with local entities, execute non-blocking point-to-point receives.
3594  *
3595  * @par Data Received:
3596  * - <b>Tag values:</b> Scalar or vector data from sender component
3597  * - <b>Entity mappings:</b> Which entities the tag values belong to
3598  * - <b>Tag metadata:</b> Tag type, size, and component information
3599  * - <b>Global IDs:</b> For entity identification and matching
3600  *
3601  * @param[in] pid Application ID for receiver component
3602  * @param[in] tag_storage_name Tag name(s) to receive (colon-separated for multiple tags)
3603  * @param[in] joint_communicator MPI communicator spanning sender and receiver groups
3604  * @param[in] context_id Communication graph context ID
3605  *
3606  * @note <b>Use cases:</b> Receive solution data, get material properties/BCs, accept computed quantities
3607  * @warning Communication graph must exist before calling (establish via mesh transfer or compute graph)
3608  *
3609  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if graph missing or tags not found
3610  */
3612  const iMOAB_String tag_storage_name,
3613  MPI_Comm* joint_communicator,
3614  int* context_id )
3615 {
3616  // Get application data and look up the communication graph for this context
3617  appData& data = context.appDatas[*pid];
3618  std::map< int, ParCommGraph* >::iterator mt = data.pgraph.find( *context_id );
3619  if( mt == data.pgraph.end() )
3620  {
3621  // Communication graph not found - this means mesh transfer hasn't been performed yet
3622  std::cout << " no par com graph for context_id:" << *context_id << " available contexts:";
3623  for( auto mit = data.pgraph.begin(); mit != data.pgraph.end(); mit++ )
3624  std::cout << " " << mit->first;
3625  std::cout << "\n";
3626  return moab::MB_FAILURE;
3627  }
3628 
3629  // Extract communication graph and parallel communicator
3630  ParCommGraph* cgraph = mt->second;
3631  ParallelComm* pco = context.appDatas[*pid].pcomm;
3632 
3633  // Handle Fortran-to-C MPI handle conversion if needed
3634  MPI_Comm global = ( data.is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
3635  : *joint_communicator );
3636 
3637  // Determine target entities: vertices for point clouds, elements for regular meshes
3638  Range owned = ( data.point_cloud ? data.local_verts : data.owned_elems );
3639 
3640  // Handle coverage set entities for intersection applications
3641  // This covers cases where elements have been instantiated in coverage sets during intersection
3642  EntityHandle cover_set = cgraph->get_cover_set();
3643  if( 0 != cover_set ) MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3644 
3645 #ifdef MOAB_HAVE_TEMPESTREMAP
3646  // Handle TempestRemap coverage mesh entities for intersection applications
3647  if( data.tempestData.remapper != nullptr ) // This is the case for intersection operations
3648  {
3649  cover_set = data.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3650  MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, owned ) );
3651  }
3652 #endif
3653 
3654  // Parse tag names from the input string (multiple tags separated by colons)
3655  std::string tag_name( tag_storage_name );
3656 
3657  // Parse multiple tag names separated by colons
3658  std::vector< std::string > tagNames;
3659  std::vector< Tag > tagHandles;
3660  std::string separator( ":" );
3661  split_tag_names( tag_name, separator, tagNames );
3662 
3663  // Resolve tag handles for each specified tag name
3664  for( size_t i = 0; i < tagNames.size(); i++ )
3665  {
3666  Tag tagHandle;
3667  ErrorCode rval = context.MBI->tag_get_handle( tagNames[i].c_str(), tagHandle );
3668  if( MB_SUCCESS != rval || nullptr == tagHandle )
3669  {
3670  MB_CHK_SET_ERR( moab::MB_FAILURE,
3671  " can't get tag handle for tag named:" << tagNames[i].c_str() << " at index " << i );
3672  }
3673  tagHandles.push_back( tagHandle );
3674  }
3675 
3676 #ifdef VERBOSE
3677  std::cout << pco->rank() << ". Looking to receive data for tags: " << tag_name
3678  << " and file set = " << ( data.file_set ) << "\n";
3679 #endif
3680  // Execute the tag data reception using the communication graph
3681  // This receives tag values from sender processes and applies them to local entities
3682  // pco is needed for MOAB operations (unpacking), not for MPI communication
3683  MB_CHK_SET_ERR( cgraph->receive_tag_values( global, pco, owned, tagHandles ), "failed to receive tag values" );
3684 
3685 #ifdef VERBOSE
3686  std::cout << pco->rank() << ". Looking to receive data for tags: " << tag_name << "\n";
3687 #endif
3688 
3689  return moab::MB_SUCCESS;
3690 }
3691 
3693 {
3694  // Find the communication graph that holds the send buffer information
3695  // This function is called on the sender side only to clean up after data transfer
3696  appData& data = context.appDatas[*pid];
3697  std::map< int, ParCommGraph* >::iterator mt = data.pgraph.find( *context_id );
3698  if( mt == data.pgraph.end() ) return moab::MB_FAILURE; // Communication graph not found
3699 
3700  // Release all non-blocking send buffers associated with this communication context
3701  // This frees memory and completes any pending asynchronous send operations
3702  mt->second->release_send_buffers();
3703  return moab::MB_SUCCESS;
3704 }
3705 
3706 /**
3707  * @brief Compute communication graph between two components using rendezvous algorithm.
3708  * @ingroup iMOABParallel
3709  *
3710  * @details Implements sophisticated rendezvous-based algorithm to determine which processes from
3711  * component 1 need to communicate with which processes from component 2. Uses global entity IDs
3712  * (or DOFs) as "rendezvous points" to discover communication patterns between components.
3713  *
3714  * @par Algorithm Workflow:
3715  * -# <b>COMMUNICATOR SETUP:</b> Resolve MPI communicator and group handles (Fortran F2C conversions),
3716  * create bidirectional ParCommGraph instances for both components, store graphs in pgraph maps.
3717  * -# <b>ENTITY ID COLLECTION:</b> Extract entity IDs based on type parameter:
3718  * - type=1: GLOBAL_DOFS from MBQUAD elements (spectral elements)
3719  * - type=2: GLOBAL_ID from MBVERTEX entities (vertex-based coupling)
3720  * - type=3: GLOBAL_ID from 2D elements (finite volume meshes)
3721  * - Handle TempestRemap coverage mesh entities
3722  * -# <b>RENDEZVOUS ALGORITHM:</b> Create TupleList for each component with (target_proc, entity_id) pairs.
3723  * Use hash-based distribution (entity_id % numProcs). Execute crystal router (gs_transfer) to send
3724  * entity IDs to rendezvous processes. Sort received data by entity ID for efficient matching.
3725  * -# <b>COMMUNICATION DISCOVERY:</b> Synchronously iterate through both sorted TupleLists, find matching
3726  * entity IDs between components (intersection), record which processes share each entity, build
3727  * reverse communication maps.
3728  * -# <b>GRAPH CONSTRUCTION:</b> Send communication information back to original processes via crystal
3729  * router. Each process learns communication partners. Store patterns in ParCommGraph.
3730  *
3731  * @par Data Processed:
3732  * - <b>Entity IDs:</b> Global identifiers serving as rendezvous points
3733  * - <b>Process mappings:</b> Which processes own which entities
3734  * - <b>Communication patterns:</b> Sender→receiver relationships
3735  *
3736  * @param[in] pid1 Application ID for component 1 (or negative if not on these ranks)
3737  * @param[in] pid2 Application ID for component 2 (or negative if not on these ranks)
3738  * @param[in] joint_communicator MPI communicator spanning both component groups
3739  * @param[in] group1 MPI group for component 1 processes
3740  * @param[in] group2 MPI group for component 2 processes
3741  * @param[in] type1 Entity type for component 1 (1=DOFs, 2=vertices, 3=elements)
3742  * @param[in] type2 Entity type for component 2 (1=DOFs, 2=vertices, 3=elements)
3743  * @param[in] comp1 Component 1 external ID
3744  * @param[in] comp2 Component 2 external ID
3745  *
3746  * @note <b>Complexity:</b> O(N log N) sorting, O(N/P) communication per process
3747  * @note <b>Use cases:</b> Coupling different mesh types, establishing comm for data transfer, mesh intersection prep
3748  * @warning Both components must have consistent global entity IDs for rendezvous to work
3749  *
3750  * @return moab::MB_SUCCESS on success, error code otherwise
3751  */
3752 //#define VERBOSE
3754  iMOAB_AppID pid2,
3755  MPI_Comm* joint_communicator,
3756  MPI_Group* group1,
3757  MPI_Group* group2,
3758  int* type1,
3759  int* type2,
3760  int* comp1,
3761  int* comp2 )
3762 {
3763  // Validate input parameters
3764  assert( joint_communicator );
3765  assert( group1 );
3766  assert( group2 );
3767  int localRank = 0, numProcs = 1;
3768 
3769  // Determine if either component is using Fortran (affects MPI handle conversion)
3770  bool isFortran = false;
3771  if( *pid1 >= 0 ) isFortran = isFortran || context.appDatas[*pid1].is_fortran;
3772  if( *pid2 >= 0 ) isFortran = isFortran || context.appDatas[*pid2].is_fortran;
3773 
3774  // Handle Fortran-to-C MPI handle conversion if needed
3775  MPI_Comm global =
3776  ( isFortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) ) : *joint_communicator );
3777  MPI_Group srcGroup = ( isFortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( group1 ) ) : *group1 );
3778  MPI_Group tgtGroup = ( isFortran ? MPI_Group_f2c( *reinterpret_cast< MPI_Fint* >( group2 ) ) : *group2 );
3779 
3780  // Get process information for the joint communicator
3781  MPI_Comm_rank( global, &localRank );
3782  MPI_Comm_size( global, &numProcs );
3783 
3784  // Create bidirectional communication graphs for both components
3785 
3786  // Create communication graphs for both components (bidirectional)
3787  ParCommGraph* cgraph = nullptr;
3788  ParCommGraph* cgraph_rev = nullptr;
3789 
3790  if( *pid1 >= 0 )
3791  {
3792  // Create communication graph for component 1 -> component 2
3793  appData& data = context.appDatas[*pid1];
3794  auto mt = data.pgraph.find( *comp2 );
3795  if( mt != data.pgraph.end() ) data.pgraph.erase( mt ); // Remove existing graph if present
3796  cgraph = new ParCommGraph( global, srcGroup, tgtGroup, *comp1, *comp2 );
3797  context.appDatas[*pid1].pgraph[*comp2] = cgraph; // Store with target component ID as key
3798  }
3799 
3800  if( *pid2 >= 0 )
3801  {
3802  // Create reverse communication graph for component 2 -> component 1
3803  appData& data = context.appDatas[*pid2];
3804  auto mt = data.pgraph.find( *comp1 );
3805  if( mt != data.pgraph.end() ) data.pgraph.erase( mt ); // Remove existing graph if present
3806  cgraph_rev = new ParCommGraph( global, tgtGroup, srcGroup, *comp2, *comp1 );
3807  context.appDatas[*pid2].pgraph[*comp1] = cgraph_rev; // Store with source component ID as key
3808  }
3809 
3810  // Initialize TupleLists for rendezvous algorithm
3811  // Each tuple contains: (target_process, entity_id) pairs for hash-based distribution
3812  TupleList TLcomp1;
3813  TLcomp1.initialize( 2, 0, 0, 0, 0 ); // 2 integers: target_proc, entity_id
3814  TupleList TLcomp2;
3815  TLcomp2.initialize( 2, 0, 0, 0, 0 ); // 2 integers: target_proc, entity_id
3816 
3817  // Enable write access for building the tuple lists
3818  TLcomp1.enableWriteAccess();
3819 
3820  // Get tag handles for entity identification
3821  // GLOBAL_DOFS: for spectral elements (type 1)
3822  // GLOBAL_ID: for vertices (type 2) and 2D elements (type 3)
3823  Tag gdsTag;
3824  int lenTagType1 = 1;
3825  if( 1 == *type1 || 1 == *type2 )
3826  {
3827  // Get GLOBAL_DOFS tag for spectral elements (usually 16 DOFs per element)
3828  MB_CHK_ERR( context.MBI->tag_get_handle( "GLOBAL_DOFS", gdsTag ) );
3829  MB_CHK_ERR( context.MBI->tag_get_length( gdsTag, lenTagType1 ) ); // Usually 16 DOFs per element
3830  }
3831  Tag gidTag = context.MBI->globalId_tag(); // Standard GLOBAL_ID tag
3832 
3833  // Collect entity IDs from component 1 for rendezvous algorithm
3834  std::vector< int > valuesComp1;
3835  if( *pid1 >= 0 )
3836  {
3837  appData& data1 = context.appDatas[*pid1];
3838  EntityHandle fset1 = data1.file_set;
3839 
3840  // Handle TempestRemap coverage mesh for intersection applications
3841 #ifdef MOAB_HAVE_TEMPESTREMAP
3842  if( data1.tempestData.remapper != nullptr ) // This is the case for intersection operations
3843  fset1 = data1.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3844 #endif
3845 
3846  Range ents_of_interest;
3847  if( *type1 == 1 ) // Spectral elements: get GLOBAL_DOFS from MBQUAD elements
3848  {
3849  assert( gdsTag );
3850  MB_CHK_ERR( context.MBI->get_entities_by_type( fset1, MBQUAD, ents_of_interest ) );
3851  valuesComp1.resize( ents_of_interest.size() * lenTagType1 );
3852  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, ents_of_interest, &valuesComp1[0] ) );
3853  }
3854  else if( *type1 == 2 ) // Vertex-based coupling: get GLOBAL_ID from MBVERTEX entities
3855  {
3856  MB_CHK_ERR( context.MBI->get_entities_by_type( fset1, MBVERTEX, ents_of_interest ) );
3857  valuesComp1.resize( ents_of_interest.size() );
3858  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp1[0] ) );
3859  }
3860  else if( *type1 == 3 ) // Finite volume meshes: get GLOBAL_ID from 2D elements
3861  {
3862  MB_CHK_ERR( context.MBI->get_entities_by_dimension( fset1, 2, ents_of_interest ) );
3863  valuesComp1.resize( ents_of_interest.size() );
3864  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp1[0] ) );
3865  }
3866  else
3867  {
3868  MB_CHK_ERR( MB_FAILURE ); // Only types 1, 2, or 3 are supported
3869  }
3870 
3871  // Build tuple list for component 1: (target_process, entity_id) pairs
3872  // Use hash-based distribution: entity_id % numProcs determines target process
3873  std::set< int > uniq( valuesComp1.begin(), valuesComp1.end() ); // Remove duplicates
3874  TLcomp1.resize( uniq.size() );
3875  for( std::set< int >::iterator sit = uniq.begin(); sit != uniq.end(); sit++ )
3876  {
3877  int marker = *sit; // Entity ID
3878  int to_proc = marker % numProcs; // Hash-based target process
3879  int n = TLcomp1.get_n();
3880  TLcomp1.vi_wr[2 * n] = to_proc; // Target process
3881  TLcomp1.vi_wr[2 * n + 1] = marker; // Entity ID
3882  TLcomp1.inc_n();
3883  }
3884  }
3885 
3886  // Execute crystal router transfer for component 1 entity IDs
3887  // This sends entity IDs to rendezvous processes based on hash distribution
3888  ProcConfig pc( global ); // Process configuration for crystal router
3889  pc.crystal_router()->gs_transfer( 1, TLcomp1, 0 ); // Transfer to joint tasks with markers
3890 
3891  // Sort component 1 tuple list by entity ID (key 1) for efficient matching
3892 #ifdef VERBOSE
3893  std::stringstream ff1;
3894  ff1 << "TLcomp1_" << localRank << ".txt";
3895  TLcomp1.print_to_file( ff1.str().c_str() ); // Debug output before sorting
3896 #endif
3897  moab::TupleList::buffer sort_buffer;
3898  sort_buffer.buffer_init( TLcomp1.get_n() );
3899  TLcomp1.sort( 1, &sort_buffer ); // Sort by entity ID (column 1)
3900  sort_buffer.reset();
3901 #ifdef VERBOSE
3902  TLcomp1.print_to_file( ff1.str().c_str() ); // Debug output after sorting
3903 #endif
3904  // Now process component 2 in the same way as component 1
3905  TLcomp2.enableWriteAccess();
3906 
3907  // Collect entity IDs from component 2 for rendezvous algorithm
3908  std::vector< int > valuesComp2;
3909  if( *pid2 >= 0 )
3910  {
3911  appData& data2 = context.appDatas[*pid2];
3912  EntityHandle fset2 = data2.file_set;
3913 
3914  // Handle TempestRemap coverage mesh for intersection applications
3915 #ifdef MOAB_HAVE_TEMPESTREMAP
3916  if( data2.tempestData.remapper != nullptr ) // This is the case for intersection operations
3917  fset2 = data2.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
3918 #endif
3919 
3920  Range ents_of_interest;
3921  if( *type2 == 1 ) // Spectral elements: get GLOBAL_DOFS from MBQUAD elements
3922  {
3923  assert( gdsTag );
3924  MB_CHK_ERR( context.MBI->get_entities_by_type( fset2, MBQUAD, ents_of_interest ) );
3925  valuesComp2.resize( ents_of_interest.size() * lenTagType1 );
3926  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, ents_of_interest, &valuesComp2[0] ) );
3927  }
3928  else if( *type2 == 2 ) // Vertex-based coupling: get GLOBAL_ID from MBVERTEX entities
3929  {
3930  MB_CHK_ERR( context.MBI->get_entities_by_type( fset2, MBVERTEX, ents_of_interest ) );
3931  valuesComp2.resize( ents_of_interest.size() );
3932  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp2[0] ) );
3933  }
3934  else if( *type2 == 3 ) // Finite volume meshes: get GLOBAL_ID from 2D elements
3935  {
3936  MB_CHK_ERR( context.MBI->get_entities_by_dimension( fset2, 2, ents_of_interest ) );
3937  valuesComp2.resize( ents_of_interest.size() );
3938  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp2[0] ) );
3939  }
3940  else
3941  {
3942  MB_CHK_ERR( MB_FAILURE ); // Only types 1, 2, or 3 are supported
3943  }
3944  // Build tuple list for component 2: (target_process, entity_id) pairs
3945  // Use hash-based distribution: entity_id % numProcs determines target process
3946  std::set< int > uniq( valuesComp2.begin(), valuesComp2.end() ); // Remove duplicates
3947  TLcomp2.resize( uniq.size() );
3948  for( std::set< int >::iterator sit = uniq.begin(); sit != uniq.end(); sit++ )
3949  {
3950  int marker = *sit; // Entity ID
3951  int to_proc = marker % numProcs; // Hash-based target process
3952  int n = TLcomp2.get_n();
3953  TLcomp2.vi_wr[2 * n] = to_proc; // Target process
3954  TLcomp2.vi_wr[2 * n + 1] = marker; // Entity ID
3955  TLcomp2.inc_n();
3956  }
3957  }
3958 
3959  // Execute crystal router transfer for component 2 entity IDs
3960  pc.crystal_router()->gs_transfer( 1, TLcomp2, 0 ); // Transfer to joint tasks with markers
3961 
3962  // Sort component 2 tuple list by entity ID (key 1) for efficient matching
3963 #ifdef VERBOSE
3964  std::stringstream ff2;
3965  ff2 << "TLcomp2_" << localRank << ".txt";
3966  TLcomp2.print_to_file( ff2.str().c_str() ); // Debug output before sorting
3967 #endif
3968  sort_buffer.buffer_reserve( TLcomp2.get_n() );
3969  TLcomp2.sort( 1, &sort_buffer ); // Sort by entity ID (column 1)
3970  sort_buffer.reset();
3971 #ifdef VERBOSE
3972  TLcomp2.print_to_file( ff2.str().c_str() ); // Debug output after sorting
3973 #endif
3974  // RENDEZVOUS ALGORITHM: Find matching entity IDs between components
3975  // Now we need to send back communication information from the rendezvous point
3976  // Loop synchronously over both sorted tuple lists to find matching entity IDs
3977  // Build new tuple lists to send communication information back to original processes
3978 
3979  // Tuple lists for sending communication info back to components
3980  // Format: (target_process, entity_id, source_process_from_other_component)
3981  TupleList TLBackToComp1;
3982  TLBackToComp1.initialize( 3, 0, 0, 0, 0 ); // 3 integers: target_proc, entity_id, source_proc_from_comp2
3983  TLBackToComp1.enableWriteAccess();
3984 
3985  TupleList TLBackToComp2;
3986  TLBackToComp2.initialize( 3, 0, 0, 0, 0 ); // 3 integers: target_proc, entity_id, source_proc_from_comp1
3987  TLBackToComp2.enableWriteAccess();
3988 
3989  // Get sizes of both tuple lists for synchronized iteration
3990  int n1 = TLcomp1.get_n();
3991  int n2 = TLcomp2.get_n();
3992 
3993  // Synchronized iteration through both sorted tuple lists to find matching entity IDs
3994  int indexInTLComp1 = 0;
3995  int indexInTLComp2 = 0;
3996  if( n1 > 0 && n2 > 0 )
3997  {
3998  while( indexInTLComp1 < n1 && indexInTLComp2 < n2 ) // Continue until either list is exhausted
3999  {
4000  // Get current entity IDs from both components
4001  int currentValue1 = TLcomp1.vi_rd[2 * indexInTLComp1 + 1]; // Entity ID from comp1
4002  int currentValue2 = TLcomp2.vi_rd[2 * indexInTLComp2 + 1]; // Entity ID from comp2
4003 
4004  if( currentValue1 < currentValue2 )
4005  {
4006  // Entity ID exists in comp1 but not in comp2 - skip comp1 entry
4007  // This is normal for non-overlapping meshes
4008  indexInTLComp1++;
4009  continue;
4010  }
4011  if( currentValue1 > currentValue2 )
4012  {
4013  // Entity ID exists in comp2 but not in comp1 - skip comp2 entry
4014  // This is normal for non-overlapping meshes
4015  indexInTLComp2++;
4016  continue;
4017  }
4018  // Found matching entity ID! Count consecutive entries with same ID in both lists
4019  int size1 = 1;
4020  int size2 = 1;
4021  while( indexInTLComp1 + size1 < n1 && currentValue1 == TLcomp1.vi_rd[2 * ( indexInTLComp1 + size1 ) + 1] )
4022  size1++; // Count consecutive entries with same entity ID in comp1
4023  while( indexInTLComp2 + size2 < n2 && currentValue2 == TLcomp2.vi_rd[2 * ( indexInTLComp2 + size2 ) + 1] )
4024  size2++; // Count consecutive entries with same entity ID in comp2
4025 
4026  // Create communication pairs for all combinations of processes that share this entity ID
4027  for( int i1 = 0; i1 < size1; i1++ )
4028  {
4029  for( int i2 = 0; i2 < size2; i2++ )
4030  {
4031  // Send communication info back to component 1 processes
4032  int n = TLBackToComp1.get_n();
4033  TLBackToComp1.reserve();
4034  TLBackToComp1.vi_wr[3 * n] =
4035  TLcomp1.vi_rd[2 * ( indexInTLComp1 + i1 )]; // Target process from comp1
4036  TLBackToComp1.vi_wr[3 * n + 1] = currentValue1; // Shared entity ID
4037  TLBackToComp1.vi_wr[3 * n + 2] =
4038  TLcomp2.vi_rd[2 * ( indexInTLComp2 + i2 )]; // Source process from comp2
4039 
4040  // Send communication info back to component 2 processes
4041  n = TLBackToComp2.get_n();
4042  TLBackToComp2.reserve();
4043  TLBackToComp2.vi_wr[3 * n] =
4044  TLcomp2.vi_rd[2 * ( indexInTLComp2 + i2 )]; // Target process from comp2
4045  TLBackToComp2.vi_wr[3 * n + 1] = currentValue1; // Shared entity ID
4046  TLBackToComp2.vi_wr[3 * n + 2] =
4047  TLcomp1.vi_rd[2 * ( indexInTLComp1 + i1 )]; // Source process from comp1
4048  }
4049  }
4050 
4051  // Advance indices past all entries with the current entity ID
4052  indexInTLComp1 += size1;
4053  indexInTLComp2 += size2;
4054  }
4055  }
4056  // Send communication information back to original component processes
4057  pc.crystal_router()->gs_transfer( 1, TLBackToComp1, 0 ); // Send to component 1 processes
4058  pc.crystal_router()->gs_transfer( 1, TLBackToComp2, 0 ); // Send to component 2 processes
4059 
4060  // Process communication information on component 1 processes
4061  if( *pid1 >= 0 )
4062  {
4063  // Sort received communication information by entity ID for efficient processing
4064 #ifdef VERBOSE
4065  std::stringstream f1;
4066  f1 << "TLBack1_" << localRank << ".txt";
4067  TLBackToComp1.print_to_file( f1.str().c_str() ); // Debug output before sorting
4068 #endif
4069  sort_buffer.buffer_reserve( TLBackToComp1.get_n() );
4070  TLBackToComp1.sort( 1, &sort_buffer ); // Sort by entity ID (column 1)
4071  sort_buffer.reset();
4072 #ifdef VERBOSE
4073  TLBackToComp1.print_to_file( f1.str().c_str() ); // Debug output after sorting
4074 #endif
4075 
4076  // Set up communication graph for component 1 based on discovered communication patterns
4077  // This establishes which processes component 1 needs to communicate with
4078  cgraph->settle_comm_by_ids( *comp1, TLBackToComp1, valuesComp1 );
4079  }
4080  // Process communication information on component 2 processes
4081  if( *pid2 >= 0 )
4082  {
4083  // Sort received communication information by source process for efficient processing
4084 #ifdef VERBOSE
4085  std::stringstream f2;
4086  f2 << "TLBack2_" << localRank << ".txt";
4087  TLBackToComp2.print_to_file( f2.str().c_str() ); // Debug output before sorting
4088 #endif
4089  sort_buffer.buffer_reserve( TLBackToComp2.get_n() );
4090  TLBackToComp2.sort( 2, &sort_buffer ); // Sort by source process (column 2)
4091  sort_buffer.reset();
4092 #ifdef VERBOSE
4093  TLBackToComp2.print_to_file( f2.str().c_str() ); // Debug output after sorting
4094 #endif
4095 
4096  // Set up reverse communication graph for component 2 based on discovered communication patterns
4097  // This establishes which processes component 2 needs to communicate with
4098  cgraph_rev->settle_comm_by_ids( *comp2, TLBackToComp2, valuesComp2 );
4099  }
4100 
4101  // Communication graph computation complete - both components now know their communication partners
4102  return moab::MB_SUCCESS;
4103 }
4104 
4105 /**
4106  * @brief Merge duplicate vertices in a mesh and update connectivity information.
4107  * @ingroup iMOABParallel
4108  *
4109  * @details This function consolidates duplicate vertices that may exist after mesh operations
4110  * such as intersection or migration. Vertices with identical GLOBAL_ID values are
4111  * merged, and connectivity of elements is updated accordingly. The function performs
4112  * both local (within-process) and parallel (across-process) vertex merging.
4113  *
4114  * @par Algorithm Workflow:
4115  * -# <b>TAG COLLECTION:</b> Collect critical tags that need to be preserved during merge:
4116  * - GLOBAL_ID: Required for vertex identification (fatal error if missing)
4117  * - area: Optional - element area values to preserve
4118  * - frac: Optional - fraction values for conservation
4119  * -# <b>LOCAL VERTEX DEDUPLICATION:</b> Remove duplicate vertices within each process using
4120  * spatial tolerance (1.0e-9). IntxUtils::remove_duplicate_vertices merges vertices and
4121  * updates connectivity, preserving tag values from first occurrence.
4122  * -# <b>MATERIAL SET CLEANUP:</b> Clear all elements from material sets and re-populate with
4123  * valid elements from the current file set. Ensures material set integrity after topology changes.
4124  * -# <b>MESH INFO UPDATE:</b> Refresh internal mesh information (vertex counts, element counts,
4125  * owned/ghosted entity information) and recalculate mesh statistics.
4126  * -# <b>PARALLEL VERTEX MERGING:</b> ParallelMergeMesh handles vertices shared across process
4127  * boundaries. Identifies vertices with identical GLOBAL_ID on different processes and merges
4128  * them while updating parallel communication graph.
4129  * -# <b>GLOBAL ID ASSIGNMENT:</b> Reassign consistent global IDs to vertices after merge.
4130  * Only updates vertex global IDs (cells already have correct IDs).
4131  * -# <b>PARTITION TAG SETUP:</b> Create/retrieve PARALLEL_PARTITION tag for visualization
4132  * indicating which process owns each mesh set.
4133  *
4134  * @par Data Processed:
4135  * - <b>Vertices:</b> Spatial coordinates and associated tags
4136  * - <b>Elements:</b> Connectivity information referencing vertices
4137  * - <b>Tags:</b> GLOBAL_ID (required), area, frac (optional)
4138  * - <b>Material sets:</b> Collections of elements with shared properties
4139  *
4140  * @par Common Use Cases:
4141  * - After mesh intersection: Overlapping meshes create duplicate vertices
4142  * - After mesh migration: Vertices copied to multiple processes need merging
4143  * - After remapping: Source/target vertices may coincide
4144  * - Quality improvement: Remove numerical duplicates from mesh operations
4145  *
4146  * @param[in] pid Application ID for the mesh
4147  *
4148  * @pre GLOBAL_ID tag must exist on vertices
4149  * @post Duplicate vertices merged both locally and across processes
4150  * @post Element connectivity updated to reference merged vertices
4151  * @post PARALLEL_PARTITION tag set for visualization
4152  *
4153  * @note <b>Parallelism:</b> Performs both local (per-process) and parallel (across-process) merging
4154  * @warning Modifies mesh topology and connectivity - may invalidate cached entity handles
4155  *
4156  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE if GLOBAL_ID tag missing
4157  */
4159 {
4160  // =========================================================================
4161  // Validate inputs and initialize local variables
4162  // =========================================================================
4163 
4164  // Get application data and parallel communicator for this component
4165  // data contains mesh info, ranges, and pcomm holds parallel communication state
4166  appData& data = context.appDatas[*pid];
4167  ParallelComm* pco = data.pcomm;
4168 
4169  // Initialize return value to success state
4170  ErrorCode rval = MB_SUCCESS;
4171 
4172  // =========================================================================
4173  // STEP 1: Collect tags that need to be preserved during vertex merging
4174  // =========================================================================
4175 
4176  // Initialize vector to hold tags that should be preserved during merge
4177  // These tags contain important data that must be maintained when vertices are merged
4178  // The merge operation will preserve tag values from the first vertex in each duplicate set
4179  std::vector< Tag > tagsList;
4180  Tag tag;
4181 
4182  // GLOBAL_ID is mandatory - used to identify duplicate vertices across processes
4183  // Without this tag, we cannot determine which vertices should be merged
4184  tag = context.MBI->globalId_tag();
4185  if( !tag )
4186  {
4187  // Fatal error: GLOBAL_ID tag is required for merge operations
4188  // Return failure as merge cannot proceed without vertex identification capability
4189  return moab::MB_FAILURE;
4190  }
4191  // Add GLOBAL_ID to the list of tags to preserve during merge
4192  tagsList.push_back( tag );
4193 
4194  // Area tag (optional) - contains element area values for conservation checks
4195  // Used in remapping applications to ensure conservation properties
4196  rval = context.MBI->tag_get_handle( "area", tag );
4197  if( tag && MB_SUCCESS == rval )
4198  {
4199  // Area tag found, include it in preserved tags
4200  tagsList.push_back( tag );
4201  }
4202 
4203  // frac tag (optional) - contains fractional coverage for remapping conservation
4204  // Used to track fractional ownership in intersection-based remapping
4205  rval = context.MBI->tag_get_handle( "frac", tag );
4206  if( tag && MB_SUCCESS == rval )
4207  {
4208  // Fraction tag found, include it in preserved tags
4209  tagsList.push_back( tag );
4210  }
4211 
4212  // ========================================================================
4213  // STEP 2: Remove duplicate vertices within this process (local merge)
4214  // ========================================================================
4215 
4216  // Set spatial tolerance for considering vertices identical (meters on unit sphere)
4217  // This tolerance accounts for numerical precision issues in floating-point coordinates
4218  // 1.0e-9 is a typical tolerance for spherical meshes near unit radius
4219  const double tol = 1.0e-9;
4220 
4221  // Perform local vertex deduplication using MOAB's IntxUtils
4222  // This function:
4223  // 1. Identifies spatially coincident vertices within tolerance
4224  // 2. Updates element connectivity to reference merged vertices
4225  // 3. Preserves tag values from the first vertex in each duplicate set
4226  // 4. Removes duplicate vertices from the mesh
4228  tagsList ) ); // Exit on failure to merge local vertices
4229 
4230  // ========================================================================
4231  // STEP 3: Clean up material sets that may reference deleted elements
4232  // ========================================================================
4233 
4234  // Material sets organize elements by material properties (e.g., land, ocean, ice)
4235  // After vertex merge, some elements may have been deleted or connectivity modified
4236  // Update material sets to maintain mesh data integrity
4237 
4238  // Retrieve all material sets currently defined on the file set
4239  // Material sets are identified by having a MATERIAL_SET_TAG_NAME tag
4241  data.file_set, MBENTITYSET, &( context.material_tag ), nullptr, 1, data.mat_sets,
4242  Interface::UNION ) ); // Exit if we can't retrieve material sets
4243 
4244  // Update material sets if any were found
4245  if( !data.mat_sets.empty() )
4246  {
4247  // For simplicity, we only clean the first material set
4248  // In production code, all material sets should be systematically updated
4249  // This is a limitation that could be improved in future versions
4250  EntityHandle matSet = data.mat_sets[0];
4251  Range elems;
4252 
4253  // Get current 2D elements in this material set (may include stale references)
4254  MB_CHK_ERR(
4255  context.MBI->get_entities_by_dimension( matSet, 2, elems ) ); // Exit if we can't get material set contents
4256 
4257  // Remove all current elements from the material set
4258  // This clears potentially stale element references
4259  MB_CHK_ERR( context.MBI->remove_entities( matSet, elems ) ); // Exit on failure to remove stale elements
4260 
4261  // Get only valid 2D elements from the current file set
4262  // This excludes any elements that may have been deleted during merge
4263  elems.clear(); // Reset range for reuse
4264  MB_CHK_ERR(
4265  context.MBI->get_entities_by_dimension( data.file_set, 2, elems ) ); // Exit if we can't get valid elements
4266 
4267  // Add back only the valid elements to the material set
4268  // This ensures material set consistency after topology changes
4269  MB_CHK_ERR( context.MBI->add_entities( matSet, elems ) ); // Exit on failure to update material set
4270  }
4271 
4272  // ========================================================================
4273  // STEP 4: Update internal mesh information after topology changes
4274  // ========================================================================
4275 
4276  // Recalculate mesh statistics and update internal data structures
4277  // This includes vertex/element counts, owned/ghosted entity information
4278  // iMOAB_UpdateMeshInfo() refreshes all cached mesh information
4279  MB_CHK_ERR( iMOAB_UpdateMeshInfo( pid ) ); // Exit on failure to update mesh info
4280 
4281  // ========================================================================
4282  // STEP 5: Perform parallel merge of vertices shared across processes
4283  // ========================================================================
4284 
4285  // Create parallel merge mesh object with the same spatial tolerance
4286  // ParallelMergeMesh handles vertices shared across process boundaries
4287  ParallelMergeMesh pmm( pco, tol );
4288 
4289  // Perform parallel vertex merging across all processes
4290  // This identifies vertices with identical GLOBAL_ID on different processes
4291  // and merges them while updating the parallel communication graph
4292  // Parameters:
4293  // - data.file_set: The mesh set to operate on
4294  // - false: Skip local merge (already done in step 2)
4295  // - 2: Update 2D elements after merge (dimension of elements to fix connectivity for)
4296  MB_CHK_ERR( pmm.merge( data.file_set,
4297  /* do not do local merge*/ false,
4298  /* 2d cells*/ 2 ) ); // Exit on failure of parallel merge
4299 
4300  // ========================================================================
4301  // STEP 6: Assign consistent global IDs to vertices after merge
4302  // ========================================================================
4303 
4304  // After merging, vertices need new globally unique IDs to maintain consistency
4305  // Only update vertices (dimension 0) - elements already have correct IDs
4306  // assign_global_ids() coordinates across all processes to ensure uniqueness
4307  MB_CHK_ERR( pco->assign_global_ids( data.file_set, /*dim*/ 0 ) ); // Exit on failure to assign global IDs
4308 
4309  // ========================================================================
4310  // STEP 7: Set partition tag for visualization and debugging
4311  // ========================================================================
4312 
4313  // Set up PARALLEL_PARTITION tag to indicate which process owns each mesh set
4314  // This is essential for visualization tools like ParaView/VisIt to show
4315  // domain decomposition and parallel partitioning
4316 
4317  Tag part_tag; // Handle for the partition tag
4318  int dum_id = -1; // Default value if tag doesn't exist yet
4319 
4320  // Create or retrieve the partition tag (sparse storage, one int per set)
4321  // Sparse storage is efficient since we only need one value per mesh set
4322  rval = context.MBI->tag_get_handle( "PARALLEL_PARTITION", 1, MB_TYPE_INTEGER, part_tag,
4323  MB_TAG_CREAT | MB_TAG_SPARSE, &dum_id );
4324 
4325  // Validate that partition tag was created or retrieved successfully
4326  if( part_tag == nullptr || ( ( MB_SUCCESS != rval ) && ( MB_ALREADY_ALLOCATED != rval ) ) )
4327  {
4328  // Error creating/retrieving partition tag
4329  std::cout << "Failed to create/retrieve PARALLEL_PARTITION tag.\n";
4330  return moab::MB_FAILURE;
4331  }
4332 
4333  // Tag the file set with this process's rank to show ownership
4334  // This allows visualization tools to distinguish between different process domains
4335  int rank = pco->rank();
4336  MB_CHK_ERR(
4337  context.MBI->tag_set_data( part_tag, &data.file_set, 1, &rank ) ); // Exit on failure to set partition data
4338 
4339  // ========================================================================
4340  // FUNCTION EPILOGUE: Return success status
4341  // ========================================================================
4342 
4343  // All merge operations completed successfully
4344  return moab::MB_SUCCESS;
4345 }
4346 
4347 #ifdef MOAB_HAVE_TEMPESTREMAP
4348 
4349 /**
4350  * @brief Establishes parallel communication graph for coverage mesh data exchange in coupled simulations.
4351  *
4352  * @details In multi-physics coupling (e.g., atmosphere-ocean), components run on different MPI task sets
4353  * with non-identical mesh decompositions. This function builds optimized communication patterns
4354  * based on intersection results, enabling targeted data exchange instead of all-to-all communication.
4355  *
4356  * @par Algorithm - Two-Hop Crystal Router:
4357  * -# <b>DISCOVERY PHASE:</b> Intersection/coupler tasks analyze coverage mesh elements, identify which
4358  * source tasks originally owned them (via orig_sending_processor tag), and send ID requirements
4359  * back to original source tasks via crystal router.
4360  * -# <b>GRAPH CONSTRUCTION:</b> Source tasks receive requirements and create new ParCommGraph with
4361  * coverage-specific send patterns. Receiver tasks create corresponding receive-side graph.
4362  * Both graphs stored with unique context_id for later data operations.
4363  *
4364  * @par Workflow:
4365  * - Validate application IDs and retrieve existing ParCommGraphs
4366  * - On receiver tasks: analyze intersection/coverage elements, build idsFromProcs map, create
4367  * receiver-side graph, pack requirements into TupleList
4368  * - Crystal router exchange: receivers send requirements to senders
4369  * - On sender tasks: create sender-side graph from received requirements
4370  *
4371  * @par Data Structures:
4372  * - <b>ParCommGraph:</b> Manages point-to-point MPI communication with sender/receiver ID mappings
4373  * - <b>TupleList:</b> Bulk MPI communication structure (vi_wr/vi_rd for integers, vr_wr/vr_rd for reals)
4374  * - <b>Tags:</b> GLOBAL_ID (element IDs), orig_sending_processor (source task), SourceParent (parent ID)
4375  *
4376  * @param[in] joint_communicator MPI communicator spanning all participating tasks (component + coupler)
4377  * @param[in] pid_src Source component application ID
4378  * @param[in] pid_migr Migrated/coverage mesh application ID (on coupler)
4379  * @param[in] pid_intx Intersection mesh application ID
4380  * @param[in] source_id External ID of source component
4381  * @param[in] migration_id External ID of migration/coupler component
4382  * @param[in] context_id Unique identifier for this coverage graph
4383  *
4384  * @pre Existing ParCommGraph between pid_src and pid_migr must exist
4385  * @pre Coverage mesh computed with orig_sending_processor tags
4386  * @pre Global IDs assigned consistently across all tasks
4387  *
4388  * @post New ParCommGraphs created on both sender and receiver sides with context_id
4389  * @post Graphs contain precise send/receive ID mappings for coverage data exchange
4390  *
4391  * @note <b>Performance:</b> O(N_cov * log(P)) time complexity, O(N_cov) communication volume
4392  * @warning <b>Collective operation:</b> Must be called on all tasks in joint_communicator
4393  *
4394  * @return moab::MB_SUCCESS on success, moab::MB_FAILURE on error
4395  */
4396 ErrCode iMOAB_CoverageGraph( MPI_Comm* joint_communicator,
4397  iMOAB_AppID pid_src,
4398  iMOAB_AppID pid_migr,
4399  iMOAB_AppID pid_intx,
4400  int* source_id,
4401  int* migration_id,
4402  int* context_id )
4403 {
4404  // =========================================================================
4405  // STEP 1: Input validation and retrieve existing communication graphs
4406  // =========================================================================
4407 
4408  assert( joint_communicator != nullptr );
4409 
4410  std::vector< int > srcSenders, receivers; // Process lists from existing graphs
4411  ParCommGraph* sendGraph = nullptr; // Original send graph (source → migration)
4412  bool is_fortran_context = false; // Handle Fortran/C communicator conversion
4413 
4414  // Validate application IDs exist before accessing application data
4415  if( pid_src && *pid_src > 0 && context.appDatas.find( *pid_src ) == context.appDatas.end() )
4416  MB_CHK_SET_ERR( moab::MB_FAILURE, "Invalid source application ID specified: " << *pid_src );
4417  if( pid_migr && *pid_migr > 0 && context.appDatas.find( *pid_migr ) == context.appDatas.end() )
4418  MB_CHK_SET_ERR( moab::MB_FAILURE, "Invalid migration/coverage application ID specified: " << *pid_migr );
4419  if( pid_intx && *pid_intx > 0 && context.appDatas.find( *pid_intx ) == context.appDatas.end() )
4420  MB_CHK_SET_ERR( moab::MB_FAILURE, "Invalid intersection application ID specified: " << *pid_intx );
4421 
4422  // Retrieve existing send graph from source application
4423  // This was created during initial migration and defines original communication pattern
4424  if( *pid_src >= 0 )
4425  {
4426  appData& dataSrc = context.appDatas[*pid_src];
4427  int default_context_id = *migration_id; // Original context uses migration_id as key
4428  assert( dataSrc.global_id == *source_id );
4429  is_fortran_context = dataSrc.is_fortran || is_fortran_context;
4430  if( dataSrc.pgraph.find( default_context_id ) != dataSrc.pgraph.end() )
4431  sendGraph = dataSrc.pgraph[default_context_id];
4432  else
4433  MB_CHK_SET_ERR( moab::MB_FAILURE, "Could not find source ParCommGraph with default migration context" );
4434 
4435  // Extract sender/receiver process lists from existing graph
4436  srcSenders = sendGraph->senders();
4437  receivers = sendGraph->receivers();
4438 #ifdef VERBOSE
4439  std::cout << "senders: " << srcSenders.size() << " first sender: " << srcSenders[0] << std::endl;
4440 #endif
4441  }
4442 
4443  // Retrieve existing receive graph from migration/coupler application
4444  // This graph will be copied and modified for coverage-specific communication
4445  ParCommGraph* recvGraph = nullptr;
4446  if( *pid_migr >= 0 )
4447  {
4448  appData& dataMigr = context.appDatas[*pid_migr];
4449  is_fortran_context = dataMigr.is_fortran || is_fortran_context;
4450  int default_context_id = *source_id; // Original context uses source_id as key
4451  assert( dataMigr.global_id == *migration_id );
4452  if( dataMigr.pgraph.find( default_context_id ) != dataMigr.pgraph.end() )
4453  recvGraph = dataMigr.pgraph[default_context_id];
4454  else
4455  MB_CHK_SET_ERR( moab::MB_FAILURE,
4456  "Could not find coverage receiver ParCommGraph with default migration context" );
4457  // Extract sender/receiver lists from migration perspective
4458  srcSenders = recvGraph->senders();
4459  receivers = recvGraph->receivers();
4460 #ifdef VERBOSE
4461  std::cout << "receivers: " << receivers.size() << " first receiver: " << receivers[0] << std::endl;
4462 #endif
4463  }
4464 
4465  // =========================================================================
4466  // STEP 2: Initialize TupleList for coverage ID exchange and get rank
4467  // =========================================================================
4468 
4469  if( *pid_intx >= 0 ) is_fortran_context = context.appDatas[*pid_intx].is_fortran || is_fortran_context;
4470 
4471  // TupleList for packing coverage element IDs to send back to source tasks
4472  // Format: (destination_proc, global_id)
4473  TupleList TLcovIDs;
4474  TLcovIDs.initialize( 2, 0, 0, 0, 0 ); // 2 integers per tuple, dynamic growth
4475  TLcovIDs.enableWriteAccess();
4476 
4477  // Handle Fortran vs C communicator conversion
4478  MPI_Comm global = ( is_fortran_context ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( joint_communicator ) )
4479  : *joint_communicator );
4480  int currentRankInJointComm = -1;CHK_MPI_ERR( MPI_Comm_rank( global, &currentRankInJointComm ) );
4481 
4482  // Get global ID tag for element identification
4483  Tag gidTag = context.MBI->globalId_tag();
4484  if( !gidTag ) return moab::MB_TAG_NOT_FOUND;
4485 
4486  // =========================================================================
4487  // STEP 3: On receiver tasks, analyze coverage mesh and build ID requirements
4488  // =========================================================================
4489 
4490  // Check if this rank is a receiver (intersection/coupler task)
4491  if( find( receivers.begin(), receivers.end(), currentRankInJointComm ) != receivers.end() )
4492  {
4493  appData& dataIntx = context.appDatas[*pid_intx];
4494  EntityHandle intx_set = dataIntx.file_set;
4495  EntityHandle cover_set = dataIntx.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
4496 
4497  // Get tag that identifies which source task originally sent each element
4498  Tag orgSendProcTag;
4499  MB_CHK_ERR( context.MBI->tag_get_handle( "orig_sending_processor", orgSendProcTag ) );
4500  if( !orgSendProcTag ) return moab::MB_TAG_NOT_FOUND;
4501 
4502  // Get intersection elements (if they exist)
4503  Range intx_cells;
4504  MB_CHK_ERR( context.MBI->get_entities_by_dimension( intx_set, 2, intx_cells ) );
4505 
4506  // Build map: source_processor → set_of_required_global_IDs
4507  // This tells each source task which elements we need from it
4508  std::map< int, std::set< int > > idsFromProcs;
4509  // Process intersection elements to find required source element IDs
4510  if( intx_cells.size() )
4511  {
4512  // SourceParent tag contains global ID of source element that created this intx element
4513  Tag parentTag;
4514  MB_CHK_ERR( context.MBI->tag_get_handle( "SourceParent", parentTag ) );
4515  if( !parentTag ) return moab::MB_TAG_NOT_FOUND;
4516 
4517  for( auto it = intx_cells.begin(); it != intx_cells.end(); ++it )
4518  {
4519  EntityHandle intx_cell = *it;
4520 
4521  // Get original processor that sent this element during migration
4522  int origProc;
4523  MB_CHK_ERR( context.MBI->tag_get_data( orgSendProcTag, &intx_cell, 1, &origProc ) );
4524 
4525  // Skip ghost elements (origProc == -1)
4526  if( origProc < 0 ) continue;
4527 
4528  // Get global ID of source parent element
4529  int gidCell;
4530  MB_CHK_ERR( context.MBI->tag_get_data( parentTag, &intx_cell, 1, &gidCell ) );
4531 
4532  // Record that we need gidCell from origProc
4533  idsFromProcs[origProc].insert( gidCell );
4534  }
4535  }
4536 
4537  // Process coverage mesh elements (superset of intersection elements)
4538  // Coverage includes all source elements needed, not just those in active intersection
4539  {
4540  Range cover_cells;
4541  MB_CHK_ERR( context.MBI->get_entities_by_dimension( cover_set, 2, cover_cells ) );
4542 
4543  Tag gidTag = context.MBI->globalId_tag();
4544 
4545  for( auto it = cover_cells.begin(); it != cover_cells.end(); ++it )
4546  {
4547  const EntityHandle cov_cell = *it;
4548 
4549  // Get original source processor for this coverage element
4550  int origProc;
4551  MB_CHK_ERR( context.MBI->tag_get_data( orgSendProcTag, &cov_cell, 1, &origProc ) );
4552 
4553  // Skip unassigned/ghost elements
4554  if( origProc < 0 ) continue;
4555 
4556  // Get global ID and add to requirements for this source processor
4557  int gidCell;
4558  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, &cov_cell, 1, &gidCell ) );
4559  assert( gidCell > 0 );
4560  idsFromProcs[origProc].insert( gidCell );
4561  }
4562  }
4563 
4564 #ifdef VERBOSE
4565  std::ofstream dbfile;
4566  std::stringstream outf;
4567  outf << "idsFromProc_0" << currentRankInJointComm << ".txt";
4568  dbfile.open( outf.str().c_str() );
4569  dbfile << "Writing this to a file.\n";
4570 
4571  dbfile << " map size:" << idsFromProcs.size()
4572  << std::endl; // on the receiver side, these show how much data to receive
4573  // from the sender (how many ids, and how much tag data later; we need to size up the
4574  // receiver buffer) arrange in tuples , use map iterators to send the ids
4575  for( std::map< int, std::set< int > >::iterator mt = idsFromProcs.begin(); mt != idsFromProcs.end(); mt++ )
4576  {
4577  std::set< int >& setIds = mt->second;
4578  dbfile << "from id: " << mt->first << " receive " << setIds.size() << " cells \n";
4579  int counter = 0;
4580  for( std::set< int >::iterator st = setIds.begin(); st != setIds.end(); st++ )
4581  {
4582  int valueID = *st;
4583  dbfile << " " << valueID;
4584  counter++;
4585  if( counter % 10 == 0 ) dbfile << "\n";
4586  }
4587  dbfile << "\n";
4588  }
4589  dbfile.close();
4590 #endif
4591 
4592  // Create new receiver-side ParCommGraph for coverage-specific communication
4593  if( nullptr != recvGraph )
4594  {
4595  ParCommGraph* newRecvGraph = new ParCommGraph( *recvGraph ); // Copy original structure
4596  newRecvGraph->set_context_id( *context_id ); // Differentiate from original graph
4597  newRecvGraph->SetReceivingAfterCoverage( idsFromProcs ); // Store coverage requirements
4598  newRecvGraph->set_cover_set( cover_set ); // Associate with coverage mesh set
4599 
4600  // Store in application's graph map; replace any existing graph for this context.
4601  auto& pgraphMap = context.appDatas[*pid_migr].pgraph;
4602  auto existing = pgraphMap.find( *context_id );
4603  if( existing != pgraphMap.end() )
4604  {
4605  delete existing->second;
4606  pgraphMap.erase( existing );
4607  }
4608  pgraphMap[*context_id] = newRecvGraph;
4609  }
4610 
4611  // Pack requirements into TupleList for crystal router communication
4612  // Format: (destination_processor, required_global_id)
4613  for( std::map< int, std::set< int > >::iterator mit = idsFromProcs.begin(); mit != idsFromProcs.end(); ++mit )
4614  {
4615  int procToSendTo = mit->first; // Source task that owns these IDs
4616  std::set< int >& idSet = mit->second;
4617  for( std::set< int >::iterator sit = idSet.begin(); sit != idSet.end(); ++sit )
4618  {
4619  int n = TLcovIDs.get_n();
4620  TLcovIDs.reserve();
4621  TLcovIDs.vi_wr[2 * n] = procToSendTo; // Destination processor
4622  TLcovIDs.vi_wr[2 * n + 1] = *sit; // Required global ID
4623  }
4624  }
4625  }
4626 
4627  // =========================================================================
4628  // STEP 4: Crystal router exchange - send ID requirements to source tasks
4629  // =========================================================================
4630 
4631  ProcConfig pc( global );
4632  // Collective operation: receivers send requirements → senders receive them
4633  pc.crystal_router()->gs_transfer( 1, TLcovIDs, 0 );
4634 
4635  // =========================================================================
4636  // STEP 5: On sender tasks, create sender-side graph from received requirements
4637  // =========================================================================
4638 
4639  if( nullptr != sendGraph )
4640  {
4641  appData& dataSrc = context.appDatas[*pid_src];
4642 
4643  // Create new sender-side ParCommGraph for coverage communication
4644  ParCommGraph* newSendGraph = new ParCommGraph( *sendGraph ); // Copy original structure
4645  newSendGraph->set_context_id( *context_id ); // Unique context for this graph
4646  dataSrc.pgraph[*context_id] = newSendGraph;
4647 
4648  // Process received TLcovIDs to build send patterns: receiver → vector<ids_to_send>
4649  MB_CHK_ERR( newSendGraph->settle_send_graph( TLcovIDs ) );
4650  }
4651 
4652  return moab::MB_SUCCESS;
4653 }
4654 
4655 ErrCode iMOAB_DumpCommGraph( iMOAB_AppID pid, int* context_id, int* is_sender, const iMOAB_String prefix )
4656 {
4657  assert( prefix && strlen( prefix ) );
4658 
4659  ParCommGraph* cgraph = context.appDatas[*pid].pgraph[*context_id];
4660  std::string prefix_str( prefix );
4661 
4662  if( nullptr != cgraph )
4663  cgraph->dump_comm_information( prefix_str, *is_sender );
4664  else
4665  {
4666  std::cout << " cannot find ParCommGraph on app with pid " << *pid << " name: " << context.appDatas[*pid].name
4667  << " context: " << *context_id << "\n";
4668  }
4669  return moab::MB_SUCCESS;
4670 }
4671 
4672 #endif // #ifdef MOAB_HAVE_TEMPESTREMAP
4673 
4674 #endif // #ifdef MOAB_HAVE_MPI
4675 
4676 #ifdef MOAB_HAVE_TEMPESTREMAP
4677 
4678 #ifdef MOAB_HAVE_NETCDF
4679 
4680 /**
4681  * @brief Internal helper: redistributes area values from trivial to arbitrary mesh distribution.
4682  *
4683  * @details NetCDF remapping files store areas in "trivial" distribution (contiguous blocks: rank 0 owns
4684  * IDs 1 to N/P, rank 1 owns N/P+1 to 2N/P, etc.), but MOAB meshes use spatial/graph partitioning
4685  * with non-contiguous global IDs. This function bridges the gap using two-hop crystal router.
4686  *
4687  * @par Algorithm - Two-Hop Rendezvous:
4688  * <b>TRIVIAL DISTRIBUTION:</b> Rank r owns global IDs [r*nL+1, (r+1)*nL] where nL=N/size @n
4689  * <b>MESH DISTRIBUTION:</b> Ranks own arbitrary non-contiguous IDs from spatial decomposition
4690  *
4691  * -# <b>HOP 1 (REQUESTS):</b> Each rank sends requests to trivial owners for its local element IDs
4692  * - For each local element with gid, compute trivial owner: (gid-1)/nL
4693  * - Pack tuple: (owner_rank, gid, local_index)
4694  * - Crystal router sends requests to trivial owners
4695  * -# <b>HOP 2 (RESPONSES):</b> Trivial owners look up areas and send back to requesters
4696  * - For each request, compute local index: gid - 1 - rank*nL
4697  * - Look up area value: trvArea[local_index]
4698  * - Pack response: (requester_rank, gid, original_index, area_value)
4699  * - Crystal router sends responses back
4700  * - Requesters apply area values to mesh elements using original_index
4701  *
4702  * @par Serial Optimization:
4703  * Direct mapping when size==1 (no communication needed)
4704  *
4705  * @par Data Structures:
4706  * - <b>TupleList:</b> Bulk MPI communication (vi_wr/vi_rd for ints, vr_wr/vr_rd for doubles)
4707  * - <b>Crystal router:</b> Collective all-to-all with O(log P) communication stages
4708  *
4709  * @param[in] pid Application ID for mesh
4710  * @param[in] N Total number of elements (global size)
4711  * @param[in] trvArea Area values in trivial distribution order
4712  * (size = N/size for most ranks, N/size + N%size for last rank)
4713  *
4714  * @pre Global IDs numbered 1 to N (1-indexed)
4715  * @pre "aream" tag exists on mesh elements
4716  * @pre trvArea on each rank contains its trivial partition slice
4717  *
4718  * @todo Auto-create "aream" tag if missing instead of failing
4719  * @todo Support variable-length tags, not just single double values
4720  *
4721  * @note <b>Performance:</b> O(n_local + log P) time, O(N) communication, 2 crystal router passes
4722  * @warning <b>Collective operation:</b> All ranks must call (crystal router is collective)
4723  *
4724  * @return moab::MB_SUCCESS on success, error code otherwise
4725  */
4726 static ErrCode set_aream_from_trivial_distribution( iMOAB_AppID pid, int N, std::vector< double >& trvArea )
4727 {
4728  // =========================================================================
4729  // STEP 1: Setup - Get parallel context and retrieve tags
4730  // =========================================================================
4731 
4732  appData& data = context.appDatas[*pid];
4733  int size = 1, rank = 0;
4734 #ifdef MOAB_HAVE_MPI
4735  ParallelComm* pcomm = context.appDatas[*pid].pcomm;
4736  size = pcomm->size();
4737  rank = pcomm->rank();
4738 #endif
4739 
4740  // Get "aream" tag for storing area values on mesh elements
4741  /// @todo Auto-create if missing instead of failing
4742  Tag areaTag;
4743  MB_CHK_ERR( context.MBI->tag_get_handle( "aream", areaTag ) );
4744 
4745  // Get local mesh elements and their global IDs
4746  const Range& ents_to_set = data.primary_elems;
4747  size_t nents_to_be_set = ents_to_set.size();
4748 
4749  Tag gidTag = context.MBI->globalId_tag();
4750  std::vector< int > globalIds( nents_to_be_set );
4751  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_to_set, &globalIds[0] ) );
4752 
4753  // =========================================================================
4754  // STEP 2: Serial path - direct mapping without communication
4755  // =========================================================================
4756 
4757  const bool serial = ( size == 1 );
4758  if( serial )
4759  {
4760  // Single process: directly map global IDs to trivial array indices
4761  // Since there's only one rank, trivial and mesh distributions are identical
4762  for( size_t i = 0; i < nents_to_be_set; i++ )
4763  {
4764  int gid = globalIds[i]; // Global ID (1-indexed)
4765  int indexInVal = gid - 1; // Convert to 0-indexed array position
4766  assert( indexInVal < N ); // Safety: prevent out-of-bounds
4767  EntityHandle eh = ents_to_set[i];
4768  // Direct lookup and assignment
4769  MB_CHK_ERR( context.MBI->tag_set_data( areaTag, &eh, 1, &trvArea[indexInVal] ) );
4770  }
4771  }
4772 #ifdef MOAB_HAVE_MPI
4773  // =========================================================================
4774  // STEP 3: Parallel path - HOP 1: Build and send requests
4775  // =========================================================================
4776  else
4777  {
4778  // Compute trivial distribution parameters
4779  int nL = N / size; // Base number of elements per rank in trivial distribution
4780 
4781  // Initialize request TupleList: (destination_rank, global_id, local_index)
4782  /// @todo Extend to support variable-length tags, not just single double values
4783  TupleList TLreq;
4784  TLreq.initialize( 3, 0, 0, 0, nents_to_be_set ); // 3 integers per tuple
4785  TLreq.enableWriteAccess();
4786 
4787  // Build requests: for each local element, determine trivial owner and request area
4788  for( size_t i = 0; i < nents_to_be_set; i++ )
4789  {
4790  int marker = globalIds[i]; // Global ID we need area for
4791  int to_proc = ( marker - 1 ) / nL; // Rank that owns this ID in trivial layout
4792 
4793  // Handle last rank which may own extra elements (N % size)
4794  if( to_proc == size ) to_proc = size - 1;
4795 
4796  // Pack request tuple
4797  int n = TLreq.get_n();
4798  TLreq.vi_wr[3 * n] = to_proc; // Destination: trivial owner rank
4799  TLreq.vi_wr[3 * n + 1] = marker; // Global ID being requested
4800  TLreq.vi_wr[3 * n + 2] = i; // Local index (for response routing)
4801  TLreq.inc_n();
4802  }
4803 
4804  // Crystal router: send requests to trivial owners (collective operation)
4805  ( pcomm->proc_config().crystal_router() )->gs_transfer( 1, TLreq, 0 );
4806 
4807  // =====================================================================
4808  // STEP 4: Process received requests and build responses
4809  // =====================================================================
4810 
4811  int sizeBack = TLreq.get_n(); // Number of requests received by this rank
4812 
4813  // Initialize response TupleList: (requester_rank, global_id, orig_index, area_value)
4814  TupleList TLBack;
4815  TLBack.initialize( 3, 0, 0, 1, sizeBack ); // 3 ints + 1 double per tuple
4816  TLBack.enableWriteAccess();
4817 
4818  // Process each request: lookup area value and prepare response
4819  for( int i = 0; i < sizeBack; i++ )
4820  {
4821  int from_proc = TLreq.vi_wr[3 * i]; // Who requested this
4822  int marker = TLreq.vi_wr[3 * i + 1]; // Which global ID
4823  int orig_index = TLreq.vi_wr[3 * i + 2]; // Their local index
4824 
4825  // Compute local index in trvArea for this rank's trivial partition
4826  // Formula: global_id - 1 - rank_offset where rank_offset = rank * nL
4827  int index = marker - 1 - rank * nL;
4828  double area_val = trvArea[index]; // Lookup area value
4829 
4830  // Pack response tuple
4831  TLBack.vi_wr[3 * i] = from_proc; // Destination: original requester
4832  TLBack.vi_wr[3 * i + 1] = marker; // Echo back global ID
4833  TLBack.vi_wr[3 * i + 2] = orig_index; // Echo back their local index
4834  TLBack.vr_wr[i] = area_val; // Area value payload
4835  TLBack.inc_n();
4836  }
4837 
4838  // =====================================================================
4839  // STEP 5: Crystal router - send responses back to requesters
4840  // =====================================================================
4841 
4842  ( pcomm->proc_config().crystal_router() )->gs_transfer( 1, TLBack, 0 );
4843 
4844  // =====================================================================
4845  // STEP 6: Apply received area values to local mesh elements
4846  // =====================================================================
4847 
4848  int n1 = TLBack.get_n(); // Number of responses received (should equal nents_to_be_set)
4849 
4850  for( int i = 0; i < n1; i++ )
4851  {
4852  // Extract response data
4853  // int gid = TLBack.vi_rd[3 * i + 1]; // Global ID (unused, we have orig_index)
4854  int origIndex = TLBack.vi_rd[3 * i + 2]; // Local element index from step 3
4855  double area_val = TLBack.vr_rd[i]; // Area value from trivial owner
4856 
4857  // Get element handle and set area tag
4858  EntityHandle eh = ents_to_set[origIndex];
4859  MB_CHK_ERR( context.MBI->tag_set_data( areaTag, &eh, 1, &area_val ) );
4860  }
4861  }
4862 #endif
4863 
4864  return MB_SUCCESS;
4865 }
4866 
4867 ErrCode iMOAB_LoadMapFile( iMOAB_AppID pid_source,
4868  iMOAB_AppID pid_target,
4869  iMOAB_AppID pid_intersection,
4870  int* srctype,
4871  int* tgttype,
4872  int* arearead,
4873  const iMOAB_String solution_weights_identifier, /* "scalar", "flux", "custom" */
4874  const iMOAB_String remap_weights_filename )
4875 {
4876  assert( srctype && tgttype );
4877 
4878  // get the local degrees of freedom, from the pid_cpl and type of mesh
4879  // Get the source and target data and pcomm objects
4880  appData& data_source = context.appDatas[*pid_source];
4881  appData& data_target = context.appDatas[*pid_target];
4882  appData& data_intx = context.appDatas[*pid_intersection];
4883  // do we need to check for tempest remap?
4884  // if we do not have it, do not do anything anyway
4885  //#ifdef MOAB_HAVE_TEMPESTREMAP
4886  TempestMapAppData& tdata = data_intx.tempestData;
4887 
4888  // check if the remapped context is null; we need to fix that, if so
4889  // what if we read a map and compute a map, on a particular iMOAB app a2o for example?
4890  // we compute an intx map and read a bilinear map
4891  // this is rather wrong, we need to fix it FIXME
4892  if( tdata.remapper == nullptr )
4893  {
4894  // do not compute coverage anymore in advance;
4895  // need to initialize the coverage set creation?
4896  // or should we really have just one enclosing coverage set for all maps in here ?
4897  // Now allocate and initialize the remapper object
4898 #ifdef MOAB_HAVE_MPI
4899  ParallelComm* pco_intx = data_intx.pcomm;
4900  tdata.remapper = new moab::TempestRemapper( context.MBI, pco_intx );
4901 #else
4902  tdata.remapper = new moab::TempestRemapper( context.MBI );
4903 #endif
4904  tdata.remapper->meshValidate = true;
4905  tdata.remapper->constructEdgeMap = true;
4906 
4907  // Do not create new filesets; Use the sets from our respective applications
4908  tdata.remapper->initialize( false );
4909  tdata.remapper->GetMeshSet( moab::Remapper::SourceMesh ) = data_source.file_set;
4910  tdata.remapper->GetMeshSet( moab::Remapper::TargetMesh ) = data_target.file_set;
4911  tdata.remapper->GetMeshSet( moab::Remapper::OverlapMesh ) = data_intx.file_set;
4912  // create a unique coverage set; it will be
4913  moab::EntityHandle covering_set_new;
4914  MB_CHK_SET_ERR( context.MBI->create_meshset( moab::MESHSET_SET, covering_set_new ), "Can't create new set" );
4915  tdata.remapper->GetMeshSet( moab::Remapper::CoveringMesh ) = covering_set_new;
4916  }
4917 
4918  // Setup loading of weights onto TempestOnlineMap
4919  // Set the context for the remapping weights computation
4920  tdata.weightMaps[std::string( solution_weights_identifier )] = new moab::TempestOnlineMap( tdata.remapper );
4921 
4922  // Now allocate and initialize the remapper object
4923  moab::TempestOnlineMap* weightMap = tdata.weightMaps[std::string( solution_weights_identifier )];
4924  assert( weightMap != nullptr );
4925 
4926  // EntityHandle source_set = data_source.file_set;
4927  // EntityHandle covering_set = tdata.remapper->GetMeshSet( Remapper::CoveringMesh );
4928  EntityHandle target_set = data_target.file_set; // default: row based partition
4929 
4930  int src_elem_dof_length = 1, tgt_elem_dof_length = 1; // default=1: FV - element average DoF value
4931  // tags of interest are either GLOBAL_DOFS (SE) or GLOBAL_ID (FV)
4932  Tag gdsTag = nullptr;
4933  if( *srctype == 1 || *tgttype == 1 )
4934  {
4935  MB_CHK_ERR( context.MBI->tag_get_handle( "GLOBAL_DOFS", gdsTag ) );
4936  assert( gdsTag );
4937  }
4938  //#endif
4939  // Find the DoF tag length
4940  // if it fails, usually it is 16
4941  // first check if we need to query the source
4942  if( *srctype == 1 ) // spectral element
4943  MB_CHK_ERR( context.MBI->tag_get_length( gdsTag, src_elem_dof_length ) );
4944  // find the DoF tag length
4945  // next check if we need to query the target
4946  if( *tgttype == 1 ) // spectral element
4947  MB_CHK_ERR( context.MBI->tag_get_length( gdsTag, tgt_elem_dof_length ) );
4948 
4949  Tag gidTag = context.MBI->globalId_tag();
4950  std::vector< int > tgtDofValues; // srcDofValues,
4951 
4952  // populate first tuple
4953  // will be filled with entities on coupler, from which we will get the DOFs, based on type
4954  Range tgt_ents_of_interest;
4955 
4956  if( *tgttype == 1 ) // spectral element
4957  {
4958  MB_CHK_ERR( context.MBI->tag_get_length( gdsTag, tgt_elem_dof_length ) );
4959  MB_CHK_ERR( context.MBI->get_entities_by_type( target_set, MBQUAD, tgt_ents_of_interest ) );
4960  tgtDofValues.resize( tgt_ents_of_interest.size() * tgt_elem_dof_length );
4961  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, tgt_ents_of_interest, &tgtDofValues[0] ) );
4962  }
4963  else if( *tgttype == 2 )
4964  {
4965  // vertex global ids
4966  MB_CHK_ERR( context.MBI->get_entities_by_type( target_set, MBVERTEX, tgt_ents_of_interest ) );
4967  tgtDofValues.resize( tgt_ents_of_interest.size() * tgt_elem_dof_length );
4968  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, tgt_ents_of_interest, &tgtDofValues[0] ) );
4969  }
4970  else if( *tgttype == 3 ) // for FV meshes, just get the global id of cell
4971  {
4972  // element global ids
4973  MB_CHK_ERR( context.MBI->get_entities_by_dimension( target_set, 2, tgt_ents_of_interest ) );
4974  tgtDofValues.resize( tgt_ents_of_interest.size() * tgt_elem_dof_length );
4975  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, tgt_ents_of_interest, &tgtDofValues[0] ) );
4976  }
4977  else
4978  {
4979  MB_CHK_ERR( MB_FAILURE ); // we know only type 1 or 2 or 3
4980  }
4981 
4982  // pass tgt ordered dofs, and unique
4983  // we need to read area_b and set aream tag on target cells, too
4984  std::vector< int > sortTgtDofs( tgtDofValues.begin(), tgtDofValues.end() );
4985  std::sort( sortTgtDofs.begin(), sortTgtDofs.end() );
4986  sortTgtDofs.erase( std::unique( sortTgtDofs.begin(), sortTgtDofs.end() ), sortTgtDofs.end() ); // remove duplicates
4987 
4988  /// the tag should be created already in the e3sm workflow; if not, create it here
4989  Tag areaTag;
4990  ErrorCode rval =
4992  if( MB_ALREADY_ALLOCATED == rval )
4993  {
4994 #ifdef MOAB_HAVE_MPI
4995  if( 0 == data_intx.pcomm->rank() )
4996 #endif
4997  std::cout << " aream tag already defined \n ";
4998  }
4999 
5000  std::vector< double > trvAreaA, trvAreaB; // passed by reference
5001  int nA, nB; // passed by reference, so returned
5002  MB_CHK_SET_ERR( weightMap->ReadParallelMap( remap_weights_filename, sortTgtDofs, *arearead, trvAreaA, nA, trvAreaB,
5003  nB ),
5004  "reading map from disk failed" );
5005  // trivially distributed areaAs and areaBs will need to be set on their correct source and target cells, as an aream tag
5006 
5007  // if we are on target mesh (row based partition)
5008  tdata.pid_src = pid_source;
5009  tdata.pid_dest = pid_target;
5010 
5011  /// @todo Perform filter based on available source/target nnz data in the map when coverage set exists.
5012  /// The idea is to look at the source coverage and target meshes and figure out only relevant elements
5013  /// that need to participate in the meshes.
5014 
5015  //tdata.remapper->SetMeshSet( Remapper::SourceMesh, source_set, &srcc_ents_of_interest );
5016  // we have read the area A from map file, and we will set it as a aream double tag on the source set, knowing that we
5017  // read it trivially, with a trivial distribution by the global DOFs
5018  // local , private method:
5019  if( 1 == *arearead || 3 == *arearead )
5020  MB_CHK_SET_ERR( set_aream_from_trivial_distribution( pid_source, nA, trvAreaA ),
5021  " fail to set aream on source " );
5022  if( 2 == *arearead || 3 == *arearead )
5023  MB_CHK_SET_ERR( set_aream_from_trivial_distribution( pid_target, nB, trvAreaB ),
5024  " fail to set aream on target " );
5025 
5026  //tdata.remapper->SetMeshSet( Remapper::CoveringMesh, covering_set, &src_ents_of_interest );
5027  weightMap->SetSourceNDofsPerElement( src_elem_dof_length );
5028  //weightMap->set_col_dc_dofs( srcDofValues ); // will set col_dtoc_dofmap
5029 
5030  tdata.remapper->SetMeshSet( Remapper::TargetMesh, target_set, &tgt_ents_of_interest );
5031  weightMap->SetDestinationNDofsPerElement( tgt_elem_dof_length );
5032  weightMap->set_row_dc_dofs( tgtDofValues ); // will set row_dtoc_dofmap
5033 
5034  /// @todo Ideally, we should get this metadata from remap_weights_filename and propagate it
5035  std::string metadataStr = std::string( remap_weights_filename ) + ";FV:1:GLOBAL_ID;FV:1:GLOBAL_ID";
5036 
5037  data_intx.metadataMap[std::string( solution_weights_identifier )] = metadataStr;
5038 
5039  return moab::MB_SUCCESS;
5040 }
5041 
5042 ErrCode iMOAB_WriteMapFile( iMOAB_AppID pid_intersection,
5043  const iMOAB_String solution_weights_identifier, /* "scalar", "flux", "custom" */
5044  const iMOAB_String remap_weights_filename )
5045 {
5046  assert( solution_weights_identifier && strlen( solution_weights_identifier ) );
5047  assert( remap_weights_filename && strlen( remap_weights_filename ) );
5048 
5049  // Get the source and target data and pcomm objects
5050  appData& data_intx = context.appDatas[*pid_intersection];
5051  TempestMapAppData& tdata = data_intx.tempestData;
5052 
5053  // Get the handle to the remapper object
5054  assert( tdata.remapper != nullptr );
5055 
5056  // Now get online weights object and ensure it is valid
5057  moab::TempestOnlineMap* weightMap = tdata.weightMaps[std::string( solution_weights_identifier )];
5058  assert( weightMap != nullptr );
5059 
5060  std::string filename = std::string( remap_weights_filename );
5061 
5062  std::string metadataStr = data_intx.metadataMap[std::string( solution_weights_identifier )];
5063  std::map< std::string, std::string > attrMap;
5064  attrMap["title"] = "MOAB-TempestRemap Online Regridding Weight Generator";
5065  attrMap["normalization"] = "ovarea";
5066  attrMap["map_aPb"] = filename;
5067 
5068  // const std::string delim = ";";
5069  // size_t pos = 0, index = 0;
5070  // std::vector< std::string > stringAttr( 3 );
5071  // // use find() function to get the position of the delimiters
5072  // while( ( pos = metadataStr.find( delim ) ) != std::string::npos )
5073  // {
5074  // std::string token1 = metadataStr.substr( 0, pos ); // store the substring
5075  // if( token1.size() > 0 || index == 0 ) stringAttr[index++] = token1;
5076  // std::cout << "\t Found token: " << token1 << std::endl;
5077  // metadataStr.erase(
5078  // 0, pos + delim.length() ); /* erase() function store the current positon and move to next token. */
5079  // }
5080  // std::cout << "\t Found token: " << metadataStr << " -- and index = " << index << std::endl;
5081  // stringAttr[index] = metadataStr; // store the last token of the string.
5082  // assert( index == 2 );
5083  // attrMap["remap_options"] = stringAttr[0];
5084  // attrMap["methodorder_b"] = stringAttr[1];
5085  // attrMap["methodorder_a"] = stringAttr[2];
5086  attrMap["concave_a"] = "false"; // defaults
5087  attrMap["concave_b"] = "false"; // defaults
5088  attrMap["bubble"] = "true"; // defaults
5089  attrMap["MOABversion"] = std::string( MOAB_PACKAGE_VERSION_STRING );
5090 
5091  // Write the map file to disk in parallel using either HDF5 or SCRIP interface
5092  MB_CHK_ERR( weightMap->WriteParallelMap( filename, attrMap ) );
5093 
5094  return moab::MB_SUCCESS;
5095 }
5096 //#define VERBOSE
5097 #ifdef MOAB_HAVE_MPI
5098 ErrCode iMOAB_MigrateMapMesh( iMOAB_AppID pid1,
5099  iMOAB_AppID pid2,
5100  MPI_Comm* jointcomm,
5101  MPI_Group* groupA,
5102  MPI_Group* groupB,
5103  int* type,
5104  int* comp1,
5105  int* comp2 )
5106 {
5107  assert( jointcomm );
5108  assert( groupA );
5109  assert( groupB );
5110  bool is_fortran = false;
5111  if( *pid1 >= 0 ) is_fortran = context.appDatas[*pid1].is_fortran || is_fortran;
5112  if( *pid2 >= 0 ) is_fortran = context.appDatas[*pid2].is_fortran || is_fortran;
5113 
5114  MPI_Comm joint_communicator =
5115  ( is_fortran ? MPI_Comm_f2c( *reinterpret_cast< MPI_Fint* >( jointcomm ) ) : *jointcomm );
5116 
5117  int localRank = 0, numProcs = 1;
5118 
5119  // Get the local rank and number of processes in the joint communicator
5120  MPI_Comm_rank( joint_communicator, &localRank );
5121  MPI_Comm_size( joint_communicator, &numProcs );
5122 
5123  // each model has a list of global ids that will need to be sent by gs to rendezvous the other
5124  // model on the joint_communicator
5125  TupleList TLcomp1;
5126  TLcomp1.initialize( 2, 0, 0, 0, 0 ); // to proc, marker
5127  TupleList TLcomp2;
5128  TLcomp2.initialize( 2, 0, 0, 0, 0 ); // to proc, marker
5129 
5130  // will push_back a new tuple, if needed
5131  TLcomp1.enableWriteAccess();
5132 
5133  // tags of interest are either GLOBAL_DOFS or GLOBAL_ID
5134  Tag gdsTag;
5135 
5136  // find the values on first cell
5137  int lenTagType1 = 1;
5138  if( *type == 1 )
5139  {
5140  MB_CHK_ERR( context.MBI->tag_get_handle( "GLOBAL_DOFS", gdsTag ) );
5141  MB_CHK_ERR( context.MBI->tag_get_length( gdsTag, lenTagType1 ) ); // usually it is 16
5142  }
5143  Tag gidTag = context.MBI->globalId_tag();
5144 
5145  std::vector< int > valuesComp1;
5146 
5147  // populate first tuple
5148  Range ents_of_interest; // will be filled with entities on pid1, that need to be distributed,
5149  // rearranged in split_ranges map
5150  if( *pid1 >= 0 )
5151  {
5152  appData& data1 = context.appDatas[*pid1];
5153  EntityHandle fset1 = data1.file_set;
5154 
5155  if( *type == 1 )
5156  {
5157  assert( gdsTag );
5158  MB_CHK_ERR( context.MBI->get_entities_by_type( fset1, MBQUAD, ents_of_interest ) );
5159  valuesComp1.resize( ents_of_interest.size() * lenTagType1 );
5160  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, ents_of_interest, &valuesComp1[0] ) );
5161  }
5162  else if( *type == 2 )
5163  {
5164  MB_CHK_ERR( context.MBI->get_entities_by_type( fset1, MBVERTEX, ents_of_interest ) );
5165  valuesComp1.resize( ents_of_interest.size() );
5166  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp1[0] ) ); // just global ids
5167  }
5168  else if( *type == 3 ) // for FV meshes, just get the global id of cell
5169  {
5170  MB_CHK_ERR( context.MBI->get_entities_by_dimension( fset1, 2, ents_of_interest ) );
5171  valuesComp1.resize( ents_of_interest.size() );
5172  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, ents_of_interest, &valuesComp1[0] ) ); // just global ids
5173  }
5174  else
5175  {
5176  MB_CHK_ERR( MB_FAILURE ); // we know only type 1 or 2 or 3
5177  }
5178  // now fill the tuple list with info and markers
5179  // because we will send only the ids, order and compress the list
5180  std::set< int > uniq( valuesComp1.begin(), valuesComp1.end() );
5181  TLcomp1.resize( uniq.size() );
5182  for( std::set< int >::iterator sit = uniq.begin(); sit != uniq.end(); sit++ )
5183  {
5184  // to proc, marker, element local index, index in el
5185  int marker = *sit;
5186  int to_proc = marker % numProcs;
5187  int n = TLcomp1.get_n();
5188  TLcomp1.vi_wr[2 * n] = to_proc; // send to processor
5189  TLcomp1.vi_wr[2 * n + 1] = marker;
5190  TLcomp1.inc_n();
5191  }
5192  }
5193 
5194  ProcConfig pc( joint_communicator ); // proc config does the crystal router
5195  pc.crystal_router()->gs_transfer( 1, TLcomp1,
5196  0 ); // communication towards joint tasks, with markers
5197  // sort by value (key 1)
5198 #ifdef VERBOSE
5199  std::stringstream ff1;
5200  ff1 << "TLcomp1_" << localRank << ".txt";
5201  TLcomp1.print_to_file( ff1.str().c_str() ); // it will append!
5202 #endif
5203  moab::TupleList::buffer sort_buffer;
5204  sort_buffer.buffer_init( TLcomp1.get_n() );
5205  TLcomp1.sort( 1, &sort_buffer );
5206  sort_buffer.reset();
5207 #ifdef VERBOSE
5208  // after sorting
5209  TLcomp1.print_to_file( ff1.str().c_str() ); // it will append!
5210 #endif
5211  // do the same, for the other component, number2, with type2
5212  // start copy
5213  TLcomp2.enableWriteAccess();
5214 
5215  //moab::TempestOnlineMap* weightMap = nullptr; // declare it outside, but it will make sense only for *pid2 >= 0
5216  // we know that :) (or *pid2 >= 0, it means we are on the coupler PEs, read map exists, and coupler procs exist)
5217  // populate second tuple with ids from read map: we need row_gdofmap and col_gdofmap
5218  std::vector< int > valuesComp2;
5219  if( *pid2 >= 0 ) // we are now on coupler, map side
5220  {
5221  appData& data2 = context.appDatas[*pid2];
5222  TempestMapAppData& tdata = data2.tempestData;
5223  // could be more than one map, read from file
5224  // get all column dofs, and create a union
5225  // assert( tdata.weightMaps.size() == 1 );
5226  // maybe we need to check it is the map we expect
5227  for( auto mapIt = tdata.weightMaps.begin(); mapIt != tdata.weightMaps.end(); ++mapIt )
5228  {
5229  moab::TempestOnlineMap* weightMap = mapIt->second;
5230  std::vector< int > valueDofs;
5231  MB_CHK_ERR( weightMap->fill_col_ids( valueDofs ) );
5232  valuesComp2.insert( valuesComp2.end(), valueDofs.begin(), valueDofs.end() );
5233  }
5234  // std::vector<int> ids_of_interest;
5235  // do a deep copy of the ids of interest: row ids
5236  // we are interested in col ids, source
5237  // new method from moab::TempestOnlineMap
5238 
5239  // now fill the tuple list with info and markers
5240  std::set< int > uniq( valuesComp2.begin(), valuesComp2.end() );
5241  TLcomp2.resize( uniq.size() );
5242  for( std::set< int >::iterator sit = uniq.begin(); sit != uniq.end(); sit++ )
5243  {
5244  // to proc, marker, element local index, index in el
5245  int marker = *sit;
5246  int to_proc = marker % numProcs;
5247  int n = TLcomp2.get_n();
5248  TLcomp2.vi_wr[2 * n] = to_proc; // send to processor
5249  TLcomp2.vi_wr[2 * n + 1] = marker;
5250  TLcomp2.inc_n();
5251  }
5252  }
5253  pc.crystal_router()->gs_transfer( 1, TLcomp2,
5254  0 ); // communication towards joint tasks, with markers
5255  // sort by value (key 1)
5256  // in the rendez-vous approach, markers meet at meet point (rendez-vous) processor marker % nprocs
5257 #ifdef VERBOSE
5258  std::stringstream ff2;
5259  ff2 << "TLcomp2_" << localRank << ".txt";
5260  TLcomp2.print_to_file( ff2.str().c_str() );
5261 #endif
5262  sort_buffer.buffer_reserve( TLcomp2.get_n() );
5263  TLcomp2.sort( 1, &sort_buffer );
5264  sort_buffer.reset();
5265  // end copy
5266 #ifdef VERBOSE
5267  TLcomp2.print_to_file( ff2.str().c_str() );
5268 #endif
5269  // need to send back the info, from the rendezvous point, for each of the values
5270  /* so go over each value, on local process in joint communicator,
5271 
5272  now have to send back the info needed for communication;
5273  loop in in sync over both TLComp1 and TLComp2, in local process;
5274  So, build new tuple lists, to send synchronous communication
5275  populate them at the same time, based on marker, that is indexed
5276  */
5277 
5278  TupleList TLBackToComp1;
5279  TLBackToComp1.initialize( 3, 0, 0, 0, 0 ); // to proc, marker, from proc on comp2,
5280  TLBackToComp1.enableWriteAccess();
5281 
5282  int n1 = TLcomp1.get_n();
5283  int n2 = TLcomp2.get_n();
5284 
5285  int indexInTLComp1 = 0;
5286  int indexInTLComp2 = 0; // advance both, according to the marker
5287  if( n1 > 0 && n2 > 0 )
5288  {
5289 
5290  while( indexInTLComp1 < n1 && indexInTLComp2 < n2 ) // if any is over, we are done
5291  {
5292  int currentValue1 = TLcomp1.vi_rd[2 * indexInTLComp1 + 1];
5293  int currentValue2 = TLcomp2.vi_rd[2 * indexInTLComp2 + 1];
5294  if( currentValue1 < currentValue2 )
5295  {
5296  // we have a big problem; basically, we are saying that
5297  // dof currentValue is on one model and not on the other
5298  // std::cout << " currentValue1:" << currentValue1 << " missing in comp2" << "\n";
5299  indexInTLComp1++;
5300  continue;
5301  }
5302  if( currentValue1 > currentValue2 )
5303  {
5304  // std::cout << " currentValue2:" << currentValue2 << " missing in comp1" << "\n";
5305  indexInTLComp2++;
5306  continue;
5307  }
5308  int size1 = 1;
5309  int size2 = 1;
5310  while( indexInTLComp1 + size1 < n1 && currentValue1 == TLcomp1.vi_rd[2 * ( indexInTLComp1 + size1 ) + 1] )
5311  size1++;
5312  while( indexInTLComp2 + size2 < n2 && currentValue2 == TLcomp2.vi_rd[2 * ( indexInTLComp2 + size2 ) + 1] )
5313  size2++;
5314  // must be found in both lists, find the start and end indices
5315  for( int i1 = 0; i1 < size1; i1++ )
5316  {
5317  for( int i2 = 0; i2 < size2; i2++ )
5318  {
5319  // send the info back to components
5320  int n = TLBackToComp1.get_n();
5321  TLBackToComp1.reserve();
5322  TLBackToComp1.vi_wr[3 * n] =
5323  TLcomp1.vi_rd[2 * ( indexInTLComp1 + i1 )]; // send back to the proc marker
5324  // came from, info from comp2
5325  TLBackToComp1.vi_wr[3 * n + 1] = currentValue1; // initial value (resend?)
5326  TLBackToComp1.vi_wr[3 * n + 2] = TLcomp2.vi_rd[2 * ( indexInTLComp2 + i2 )]; // from proc on comp2
5327  }
5328  }
5329  indexInTLComp1 += size1;
5330  indexInTLComp2 += size2;
5331  }
5332  }
5333  pc.crystal_router()->gs_transfer( 1, TLBackToComp1, 0 ); // communication towards original tasks, with info about
5334 
5335  TupleList TLv; // vertices
5336  TupleList TLc; // cells if needed (not type 2)
5337 
5338  if( *pid1 >= 0 )
5339  {
5340  // we are on original comp 1 tasks
5341  // before ordering
5342  // now for each value in TLBackToComp1.vi_rd[3*i+1], on current proc, we know the
5343  // processors it communicates with
5344 #ifdef VERBOSE
5345  std::stringstream f1;
5346  f1 << "TLBack1_" << localRank << ".txt";
5347  TLBackToComp1.print_to_file( f1.str().c_str() );
5348 #endif
5349  // so we are now on pid1, we know now each cell where it has to go
5350  int n = TLBackToComp1.get_n();
5351  std::map< int, std::set< int > > uniqueIDs;
5352  for( int i = 0; i < n; i++ )
5353  {
5354  int to_proc = TLBackToComp1.vi_wr[3 * i + 2];
5355  int globalId = TLBackToComp1.vi_wr[3 * i + 1];
5356  uniqueIDs[to_proc].insert( globalId );
5357  }
5358  // gidTag is gid tag
5359  // gdsTag is GLOBAL_DOFS , used only for spectral (type 1)
5360  // (*type 1 is spectral, right now not used in E3SM)
5361 
5362  std::map< int, Range > splits;
5363  for( size_t i = 0; i < ents_of_interest.size(); i++ )
5364  {
5365  EntityHandle ent = ents_of_interest[i];
5366  for( int j = 0; j < lenTagType1; j++ )
5367  {
5368  int marker = valuesComp1[i * lenTagType1 + j];
5369  for( auto mit = uniqueIDs.begin(); mit != uniqueIDs.end(); mit++ )
5370  {
5371  int proc = mit->first;
5372  std::set< int >& setIds = mit->second;
5373  if( setIds.find( marker ) != setIds.end() )
5374  {
5375  splits[proc].insert( ent );
5376  }
5377  }
5378  }
5379  }
5380 
5381  std::map< int, Range > verts_to_proc;
5382  int numv = 0, numc = 0;
5383  for( auto it = splits.begin(); it != splits.end(); it++ )
5384  {
5385  int to_proc = it->first;
5386  Range verts;
5387  if( *type != 2 )
5388  {
5389  MB_CHK_ERR( context.MBI->get_connectivity( it->second, verts ) );
5390  numc += (int)it->second.size();
5391  }
5392  else
5393  verts = it->second;
5394  verts_to_proc[to_proc] = verts;
5395  numv += (int)verts.size();
5396  }
5397  // first vertices:
5398  TLv.initialize( 2, 0, 0, 3, numv ); // to proc, GLOBAL ID, 3 real coordinates
5399  TLv.enableWriteAccess();
5400  // use the global id of vertices for connectivity
5401  for( auto it = verts_to_proc.begin(); it != verts_to_proc.end(); it++ )
5402  {
5403  int to_proc = it->first;
5404  Range& verts = it->second;
5405  for( Range::iterator vit = verts.begin(); vit != verts.end(); ++vit )
5406  {
5407  EntityHandle v = *vit;
5408  int n = TLv.get_n(); // current size of tuple list
5409  TLv.vi_wr[2 * n] = to_proc; // send to processor
5410 
5411  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, &v, 1, &( TLv.vi_wr[2 * n + 1] ) ) );
5412  MB_CHK_ERR( context.MBI->get_coords( &v, 1, &( TLv.vr_wr[3 * n] ) ) );
5413  TLv.inc_n(); // increment tuple list size
5414  }
5415  }
5416  if( *type != 2 )
5417  {
5418  // to proc, ID cell, gdsTag, nbv, id conn,
5419  int size_tuple =
5420  2 + ( ( *type != 1 ) ? 0 : lenTagType1 ) + 1 + 10; // 10 is the max number of vertices in cell
5421 
5422  std::vector< int > gdvals;
5423 
5424  TLc.initialize( size_tuple, 0, 0, 0, numc ); // to proc, GLOBAL ID, 3 real coordinates
5425  TLc.enableWriteAccess();
5426  for( auto it = splits.begin(); it != splits.end(); it++ )
5427  {
5428  int to_proc = it->first;
5429  Range& cells = it->second;
5430  for( Range::iterator cit = cells.begin(); cit != cells.end(); ++cit )
5431  {
5432  EntityHandle cell = *cit;
5433  int n = TLc.get_n(); // current size of tuple list
5434  TLc.vi_wr[size_tuple * n] = to_proc;
5435  int current_index = 2;
5436  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, &cell, 1, &( TLc.vi_wr[size_tuple * n + 1] ) ) );
5437  if( 1 == *type )
5438  {
5439  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, &cell, 1,
5440  &( TLc.vi_wr[size_tuple * n + current_index] ) ) );
5441  current_index += lenTagType1;
5442  }
5443  // now get connectivity
5444  const EntityHandle* conn = NULL;
5445  int nnodes = 0;
5446  MB_CHK_ERR( context.MBI->get_connectivity( cell, conn, nnodes ) );
5447  // fill nnodes:
5448  TLc.vi_wr[size_tuple * n + current_index] = nnodes;
5449  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, conn, nnodes,
5450  &( TLc.vi_wr[size_tuple * n + current_index + 1] ) ) );
5451  TLc.inc_n(); // increment tuple list size
5452  }
5453  }
5454  }
5455  }
5456  else if( *pid2 >= 0 ) // TLv and TLc should be able to receive if *pid2 >= 0
5457  // this case will not happen if pid1 and pid2 are both on the coupler side
5458  // we need to cover the case if map migrate was used directly
5459  // in one hop projection; right now, we prefer 2 hop projection
5460  {
5461  TLv.initialize( 2, 0, 0, 3, 0 ); // no vertices here yet, for sure
5462  TLv.enableWriteAccess(); // to be able to receive stuff, even if nothing is here yet, on this task
5463  if( *type != 2 ) // for point cloud, we do not need to initialize TLc (for cells)
5464  {
5465  // we still need to initialize the tuples with the right size, as in form_tuples_to_migrate_mesh
5466  int size_tuple = 2 + ( ( *type != 1 ) ? 0 : lenTagType1 ) + 1 +
5467  10; // 10 is the max number of vertices in cell; kind of arbitrary
5468  TLc.initialize( size_tuple, 0, 0, 0, 0 );
5469  TLc.enableWriteAccess();
5470  }
5471  }
5472  pc.crystal_router()->gs_transfer( 1, TLv, 0 ); // communication towards coupler tasks, with mesh vertices
5473  if( *type != 2 ) pc.crystal_router()->gs_transfer( 1, TLc, 0 ); // those are cells
5474 
5475  if( *pid2 >= 0 ) // will receive the mesh, on coupler pes!, the coverage mesh !
5476  {
5477  appData& dataIntx = context.appDatas[*pid2];
5478  TempestMapAppData& tdata = dataIntx.tempestData;
5479  Range primary_ents; // vertices for type 2, cells of dim 2 for type 1 or 3
5480  std::vector< int > values_entities; // will be the size of primary_ents3 * lenTagType1
5481  EntityHandle fset3 = tdata.remapper->GetMeshSet( Remapper::CoveringMesh );
5482 
5483  // When an intersection-based covering mesh already exists, ReceiveElementTag and
5484  // ApplyWeights must share the same entity handles. Skip populating fset3 with MAP
5485  // cells so we don't corrupt the set used by both callers.
5486  Range intx_cov_cells;
5487  MB_CHK_ERR( context.MBI->get_entities_by_dimension( fset3, 2, intx_cov_cells ) );
5488  const bool has_intersection_coverage = !intx_cov_cells.empty();
5489 
5490  if( !has_intersection_coverage )
5491  {
5492  // start copy
5493  std::map< int, EntityHandle > vertexMap; //
5494  Range verts;
5495  // always form vertices and add them to the fset3;
5496  int n = TLv.get_n();
5498  for( int i = 0; i < n; i++ )
5499  {
5500  int gid = TLv.vi_rd[2 * i + 1];
5501  if( vertexMap.find( gid ) == vertexMap.end() )
5502  {
5503  // need to form this vertex
5504  MB_CHK_ERR( context.MBI->create_vertex( &( TLv.vr_rd[3 * i] ), vertex ) );
5505  vertexMap[gid] = vertex;
5506  verts.insert( vertex );
5507  MB_CHK_ERR( context.MBI->tag_set_data( gidTag, &vertex, 1, &gid ) );
5508  }
5509  }
5510  MB_CHK_ERR( context.MBI->add_entities( fset3, verts ) );
5511  if( 2 == *type )
5512  {
5513  values_entities.resize( verts.size() ); // just get the ids of vertices
5514  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, verts, &values_entities[0] ) );
5515  primary_ents = verts;
5516  //return MB_SUCCESS;
5517  }
5518  else
5519  {
5520  n = TLc.get_n();
5521  int size_tuple =
5522  2 + ( ( *type != 1 ) ? 0 : lenTagType1 ) + 1 + 10; // 10 is the max number of vertices in cell
5523 
5524  EntityHandle new_element;
5525 
5526  std::map< int, EntityHandle >
5527  cellMap; // do not create one if it already exists, maybe from other processes
5528  for( int i = 0; i < n; i++ )
5529  {
5530  // int from_proc = TLc.vi_rd[size_tuple * i];
5531  int globalIdEl = TLc.vi_rd[size_tuple * i + 1];
5532  if( cellMap.find( globalIdEl ) == cellMap.end() ) // need to create the cell
5533  {
5534  int current_index = 2;
5535  if( 1 == *type ) current_index += lenTagType1;
5536  int nnodes = TLc.vi_rd[size_tuple * i + current_index];
5537  std::vector< EntityHandle > conn;
5538  conn.resize( nnodes );
5539  for( int j = 0; j < nnodes; j++ )
5540  {
5541  conn[j] = vertexMap[TLc.vi_rd[size_tuple * i + current_index + j + 1]];
5542  }
5543  //
5544  EntityType entType = MBQUAD;
5545  if( nnodes > 4 ) entType = MBPOLYGON;
5546  if( nnodes < 4 ) entType = MBTRI;
5547  MB_CHK_SET_ERR( context.MBI->create_element( entType, &conn[0], nnodes, new_element ),
5548  "can't create new element " );
5549  primary_ents.insert( new_element );
5550  cellMap[globalIdEl] = new_element;
5551  MB_CHK_SET_ERR( context.MBI->tag_set_data( gidTag, &new_element, 1, &globalIdEl ),
5552  "can't set global id tag on cell " );
5553  if( 1 == *type )
5554  {
5555  // set the gds tag
5556  MB_CHK_SET_ERR( context.MBI->tag_set_data( gdsTag, &new_element, 1,
5557  &( TLc.vi_rd[size_tuple * i + 2] ) ),
5558  "can't set gds tag on cell " );
5559  }
5560  }
5561  }
5562  MB_CHK_ERR( context.MBI->add_entities( fset3, primary_ents ) );
5563  if( 1 == *type )
5564  {
5565  values_entities.resize( lenTagType1 * primary_ents.size() );
5566  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, primary_ents, &values_entities[0] ) );
5567  }
5568  else // *type == 3
5569  {
5570  values_entities.resize( primary_ents.size() ); // just get the global ids !
5571  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, primary_ents, &values_entities[0] ) );
5572  }
5573  }
5574  } // end if( !has_intersection_coverage )
5575  else
5576  {
5577  // Reuse the intersection covering mesh as the entity set for weight application;
5578  // rebuild values_entities from it so col_dtoc_dofmap maps those entities to MAP columns.
5579  primary_ents = intx_cov_cells;
5580  if( 1 == *type )
5581  {
5582  values_entities.resize( lenTagType1 * primary_ents.size() );
5583  MB_CHK_ERR( context.MBI->tag_get_data( gdsTag, primary_ents, &values_entities[0] ) );
5584  }
5585  else
5586  {
5587  values_entities.resize( primary_ents.size() );
5588  MB_CHK_ERR( context.MBI->tag_get_data( gidTag, primary_ents, &values_entities[0] ) );
5589  }
5590  }
5591 
5592  int ndofPerEl = 1;
5593  if( 1 == *type ) ndofPerEl = (int)( sqrt( lenTagType1 ) );
5594 
5595  tdata.remapper->SetMeshSet( Remapper::CoveringMesh, fset3, &primary_ents );
5596  // dump covering mesh in a file, to look at it
5597  // should be one covering mesh per task, should cover the target mesh set
5598 #ifdef VERBOSE
5599  std::stringstream fcov;
5600  fcov << "MapCover_" << numProcs << "_" << localRank << ".h5m";
5601  context.MBI->write_file( fcov.str().c_str(), 0, 0, &fset3, 1 );
5602  EntityHandle fset2 = tdata.remapper->GetMeshSet( Remapper::TargetMesh );
5603  std::stringstream ftarg;
5604  ftarg << "TargMap_" << numProcs << "_" << localRank << ".h5m";
5605  context.MBI->write_file( ftarg.str().c_str(), 0, 0, &fset2, 1 );
5606 #endif
5607  for( auto mapIt = tdata.weightMaps.begin(); mapIt != tdata.weightMaps.end(); ++mapIt )
5608  {
5609  moab::TempestOnlineMap* weightMap = mapIt->second;
5610  weightMap->SetSourceNDofsPerElement( ndofPerEl );
5611  weightMap->set_col_dc_dofs( values_entities ); // will set col_dtoc_dofmap
5612  }
5613  }
5614 
5615  // compute par comm graph
5616  int ierr = iMOAB_ComputeCommGraph( pid1, pid2, jointcomm, groupA, groupB, type, type, comp1, comp2 );
5617  if( ierr != 0 ) return moab::MB_FAILURE;
5618 
5619  return moab::MB_SUCCESS;
5620 }
5621 #undef VERBOSE
5622 #endif // #ifdef MOAB_HAVE_MPI
5623 
5624 #endif // #ifdef MOAB_HAVE_NETCDF
5625 
5626 #define USE_API
5627 static ErrCode ComputeSphereRadius( iMOAB_AppID pid, double* radius )
5628 {
5629  moab::CartVect pos;
5630 
5631  Range& verts = context.appDatas[*pid].all_verts;
5632  *radius = 1.0;
5633  if( verts.empty() ) return moab::MB_SUCCESS;
5634  moab::EntityHandle firstVertex = ( verts[0] );
5635 
5636  // coordinate data
5637  MB_CHK_ERR( context.MBI->get_coords( &( firstVertex ), 1, (double*)&( pos[0] ) ) );
5638 
5639  // compute the distance from origin
5640  /// @todo We could do this in a loop to verify if the pid represents a spherical mesh
5641  *radius = pos.length();
5642  return moab::MB_SUCCESS;
5643 }
5644 
5645 ErrCode iMOAB_SetMapGhostLayers( iMOAB_AppID pid, int* n_src_ghost_layers, int* n_tgt_ghost_layers )
5646 {
5647  appData& data = context.appDatas[*pid];
5648 
5649  // Set the number of ghost layers
5650  data.tempestData.num_src_ghost_layers = *n_src_ghost_layers; // number of ghost layers for source
5651  data.tempestData.num_tgt_ghost_layers = *n_tgt_ghost_layers; // number of ghost layers for source
5652 
5653  return moab::MB_SUCCESS;
5654 }
5655 
5656 ErrCode iMOAB_ComputeCoverageMesh( iMOAB_AppID pid_src, iMOAB_AppID pid_tgt, iMOAB_AppID pid_intx )
5657 {
5658  // Default constant parameters
5659  constexpr bool validate = true;
5660  constexpr bool meshCleanup = true;
5661  bool gnomonic = true;
5662  constexpr double defaultradius = 1.0;
5663  constexpr double boxeps = 1.e-10;
5664 
5665  // Other constant parameters
5666  const double epsrel = ReferenceTolerance; // ReferenceTolerance is defined in Defines.h in tempestremap source ;
5667  double radius_source = 1.0;
5668  double radius_target = 1.0;
5669 
5670  // Error code definitions
5671  ErrCode ierr;
5672 
5673  // Get the source and target data and pcomm objects
5674  appData& data_src = context.appDatas[*pid_src];
5675  appData& data_tgt = context.appDatas[*pid_tgt];
5676  appData& data_intx = context.appDatas[*pid_intx];
5677 #ifdef MOAB_HAVE_MPI
5678  ParallelComm* pco_intx = context.appDatas[*pid_intx].pcomm;
5679 #endif
5680 
5681  // Mesh intersection has already been computed; Return early.
5682  TempestMapAppData& tdata = data_intx.tempestData;
5683  if( tdata.remapper != nullptr ) return moab::MB_SUCCESS; // nothing to do
5684 
5685  int rank = 0;
5686 #ifdef MOAB_HAVE_MPI
5687  if( pco_intx )
5688  {
5689  rank = pco_intx->rank();
5690  MB_CHK_ERR( pco_intx->check_all_shared_handles() );
5691  }
5692 #endif
5693 
5694  moab::DebugOutput outputFormatter( std::cout, rank, 0 );
5695  outputFormatter.set_prefix( "[iMOAB_ComputeCoverageMesh]: " );
5696 
5697  ierr = iMOAB_UpdateMeshInfo( pid_src );MB_CHK_ERR( ierr );
5698  ierr = iMOAB_UpdateMeshInfo( pid_tgt );MB_CHK_ERR( ierr );
5699 
5700  // Rescale the radius of both to compute the intersection
5701  MB_CHK_ERR( ComputeSphereRadius( pid_src, &radius_source ) );
5702  MB_CHK_ERR( ComputeSphereRadius( pid_tgt, &radius_target ) );
5703 #ifdef VERBOSE
5704  if( !rank )
5705  outputFormatter.printf( 0, "Radius of spheres: source = %12.14f, and target = %12.14f\n", radius_source,
5706  radius_target );
5707 #endif
5708 
5709  /* Let make sure that the radius match for source and target meshes. If not, rescale now and
5710  * unscale later. */
5711  if( fabs( radius_source - radius_target ) > 1e-10 )
5712  { /* the radii are different */
5713  MB_CHK_ERR( IntxUtils::ScaleToRadius( context.MBI, data_src.file_set, defaultradius ) );
5714  MB_CHK_ERR( IntxUtils::ScaleToRadius( context.MBI, data_tgt.file_set, defaultradius ) );
5715  }
5716 
5717  // Default area_method = lHuiller; Options: Girard, GaussQuadrature (if TR is available)
5718 #ifdef MOAB_HAVE_TEMPESTREMAP
5720 #else
5721  IntxAreaUtils areaAdaptor( IntxAreaUtils::lHuiller );
5722 #endif
5723 
5724  if( meshCleanup )
5725  {
5726  // Address issues for source mesh first
5727  // fixes to enforce positive orientation of the vertices (outward normal)
5728  MB_CHK_ERR(
5729  areaAdaptor.positive_orientation( context.MBI, data_src.file_set, defaultradius /*radius_source*/ ) );
5730 
5731  // fixes to clean up any degenerate quadrangular elements present in the mesh (RLL specifically?)
5733 
5734  // fixes to enforce convexity in case concave elements are present
5736 
5737  // Address issues for target mesh first
5738  // fixes to enforce positive orientation of the vertices (outward normal)
5739  MB_CHK_ERR(
5740  areaAdaptor.positive_orientation( context.MBI, data_tgt.file_set, defaultradius /*radius_target*/ ) );
5741 
5742  // fixes to clean up any degenerate quadrangular elements present in the mesh (RLL specifically?)
5744 
5745  // fixes to enforce convexity in case concave elements are present
5747  }
5748 
5749  // print verbosely about the problem setting
5750 #ifdef VERBOSE
5751  {
5752  moab::Range rintxverts, rintxelems;
5753  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_src.file_set, 0, rintxverts ) );
5754  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_src.file_set, data_src.dimension, rintxelems ) );
5755 
5756  moab::Range bintxverts, bintxelems;
5757  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_tgt.file_set, 0, bintxverts ) );
5758  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_tgt.file_set, data_tgt.dimension, bintxelems ) );
5759 
5760  if( !rank )
5761  {
5762  outputFormatter.printf( 0, "The source set contains %zu vertices and %zu elements\n", rintxverts.size(),
5763  rintxelems.size() );
5764  outputFormatter.printf( 0, "The target set contains %zu vertices and %zu elements\n", bintxverts.size(),
5765  bintxelems.size() );
5766  }
5767  }
5768 #endif
5769  // use_kdtree_search = ( srctgt_areas_glb[0] < srctgt_areas_glb[1] );
5770  // advancing front method will fail unless source mesh has no holes (area = 4 * pi) on unit sphere
5771  // use_kdtree_search = true;
5772 
5773  data_intx.dimension = data_tgt.dimension;
5774  // set the context for the source and destination applications
5775  tdata.pid_src = pid_src;
5776  tdata.pid_dest = pid_tgt;
5777 
5778  // Now allocate and initialize the remapper object
5779 #ifdef MOAB_HAVE_MPI
5780  tdata.remapper = new moab::TempestRemapper( context.MBI, pco_intx );
5781 #else
5782  tdata.remapper = new moab::TempestRemapper( context.MBI );
5783 #endif
5784  tdata.remapper->meshValidate = validate;
5785  tdata.remapper->constructEdgeMap = true;
5786 
5787  // Do not create new filesets; Use the sets from our respective applications
5788  tdata.remapper->initialize( false );
5789  tdata.remapper->GetMeshSet( moab::Remapper::SourceMesh ) = data_src.file_set;
5790  tdata.remapper->GetMeshSet( moab::Remapper::TargetMesh ) = data_tgt.file_set;
5791  tdata.remapper->GetMeshSet( moab::Remapper::OverlapMesh ) = data_intx.file_set;
5792 
5793  // First, compute the covering source set.
5794  if( tdata.num_src_ghost_layers >= 1 ) gnomonic = false; // do not use gnomonic when we need ghost layers;
5795  MB_CHK_SET_ERR( tdata.remapper->ConstructCoveringSet( epsrel, 1.0, 1.0, boxeps, false, gnomonic,
5796  tdata.num_src_ghost_layers ),
5797  "failed to compute covering set" );
5798 
5799 #ifdef MOAB_HAVE_TEMPESTREMAP
5800  // set the reference to the covering set in the source PID
5801  data_intx.secondary_file_set = tdata.remapper->GetMeshSet( moab::Remapper::CoveringMesh );
5802 #endif
5803 
5804  return moab::MB_SUCCESS;
5805 }
5806 
5807 #ifdef MOAB_HAVE_TEMPESTREMAP
5808 ErrCode iMOAB_WriteCoverageMesh( iMOAB_AppID pid, const iMOAB_String prefix )
5809 {
5810  IMOAB_CHECKPOINTER( prefix, 2 );
5811  IMOAB_ASSERT( strlen( prefix ), "Invalid prefix." );
5812 
5813  appData& data = context.appDatas[*pid];
5814  EntityHandle fileSet = data.file_set;
5815 
5816  if( data.tempestData.remapper != nullptr )
5817  fileSet = data.tempestData.remapper->GetMeshSet( Remapper::CoveringMesh );
5818  else if( data.file_set != data.secondary_file_set )
5819  fileSet = data.secondary_file_set;
5820  else
5821  MB_CHK_SET_ERR( moab::MB_FAILURE, "Invalid secondary file set handle" );
5822 
5823  std::ostringstream file_name;
5824  int rank = 0, size = 1;
5825 
5826 #ifdef MOAB_HAVE_MPI
5827  ParallelComm* pcomm = data.pcomm;
5828  rank = pcomm->rank();
5829  size = pcomm->size();
5830 #endif
5831 
5832  file_name << prefix << "_" << size << "_" << rank << ".h5m";
5833  // Now let us actually write the file to disk with appropriate options
5834  MB_CHK_ERR( context.MBI->write_file( file_name.str().c_str(), 0, 0, &fileSet, 1 ) );
5835  return moab::MB_SUCCESS;
5836 }
5837 #endif
5838 
5839 ErrCode iMOAB_ComputeMeshIntersectionOnSphere( iMOAB_AppID pid_src, iMOAB_AppID pid_tgt, iMOAB_AppID pid_intx )
5840 {
5841  // Default constant parameters
5842  constexpr bool validate = true;
5843  constexpr bool use_kdtree_search = true;
5844  constexpr double defaultradius = 1.0;
5845 
5846  // Get the source and target data and pcomm objects
5847  appData& data_src = context.appDatas[*pid_src];
5848  appData& data_tgt = context.appDatas[*pid_tgt];
5849  appData& data_intx = context.appDatas[*pid_intx];
5850 #ifdef MOAB_HAVE_MPI
5851  ParallelComm* pco_intx = context.appDatas[*pid_intx].pcomm;
5852 #endif
5853 
5854  // Mesh intersection has already been computed; Return early.
5855  TempestMapAppData& tdata = data_intx.tempestData;
5856 
5857  if( tdata.remapper == nullptr )
5858  {
5859  // user has not called the coverage mesh computation routine -- so explicitly call it now
5860  // this check supports the traditional workflow of directly computing mesh intersection
5861  // and letting this routine compute coverage mesh as needed
5862  MB_CHK_ERR( iMOAB_ComputeCoverageMesh( pid_src, pid_tgt, pid_intx ) );
5863  }
5864 
5865  int rank = 0;
5866 #ifdef MOAB_HAVE_MPI
5867  rank = pco_intx->rank();
5868 #endif
5869  moab::DebugOutput outputFormatter( std::cout, rank, 0 );
5870  outputFormatter.set_prefix( "[iMOAB_ComputeMeshIntersectionOnSphere]: " );
5871 
5872  // Next, compute intersections with MOAB.
5873  // for bilinear, this is an overkill
5874  MB_CHK_ERR( tdata.remapper->ComputeOverlapMesh( use_kdtree_search, false ) );
5875 
5876  // Mapping computation done
5877  if( validate )
5878  {
5879  // Default area_method = lHuiller; Options: Girard, GaussQuadrature (if TR is available)
5880  IntxAreaUtils areaAdaptor( IntxAreaUtils::lHuiller );
5881  double local_areas[3] = { 0.0, 0.0, 0.0 }, global_areas[3] = { 0.0, 0.0, 0.0 };
5882  local_areas[0] = areaAdaptor.area_on_sphere( context.MBI, data_src.file_set, defaultradius /*radius_source*/ );
5883  local_areas[1] = areaAdaptor.area_on_sphere( context.MBI, data_tgt.file_set, defaultradius /*radius_target*/ );
5884  local_areas[2] = areaAdaptor.area_on_sphere( context.MBI, data_intx.file_set, defaultradius );
5885 #ifdef MOAB_HAVE_MPI
5886  global_areas[0] = global_areas[1] = global_areas[2] = 0.0;
5887  MPI_Reduce( &local_areas[0], &global_areas[0], 3, MPI_DOUBLE, MPI_SUM, 0, pco_intx->comm() );
5888 #else
5889  global_areas[0] = local_areas[0];
5890  global_areas[1] = local_areas[1];
5891  global_areas[2] = local_areas[2];
5892 #endif
5893 
5894  if( rank == 0 )
5895  {
5896  outputFormatter.printf( 0,
5897  "initial area: source mesh = %12.14f, target mesh = %12.14f, "
5898  "overlap mesh = %12.14f\n",
5899  global_areas[0], global_areas[1], global_areas[2] );
5900  outputFormatter.printf( 0, " relative error w.r.t source = %12.14e, and target = %12.14e\n",
5901  fabs( global_areas[0] - global_areas[2] ) / global_areas[0],
5902  fabs( global_areas[1] - global_areas[2] ) / global_areas[1] );
5903  }
5904  }
5905 
5906  return moab::MB_SUCCESS;
5907 }
5908 
5909 ErrCode iMOAB_ComputePointDoFIntersection( iMOAB_AppID pid_src, iMOAB_AppID pid_tgt, iMOAB_AppID pid_intx )
5910 {
5911  ErrCode ierr;
5912 
5913  double radius_source = 1.0;
5914  double radius_target = 1.0;
5915  const double epsrel = ReferenceTolerance; // ReferenceTolerance is defined in Defines.h in tempestremap source ;
5916  const double boxeps = 1.e-8;
5917 
5918  // Get the source and target data and pcomm objects
5919  appData& data_src = context.appDatas[*pid_src];
5920  appData& data_tgt = context.appDatas[*pid_tgt];
5921  appData& data_intx = context.appDatas[*pid_intx];
5922 #ifdef MOAB_HAVE_MPI
5923  ParallelComm* pco_intx = context.appDatas[*pid_intx].pcomm;
5924 #endif
5925 
5926  // Mesh intersection has already been computed; Return early.
5927  TempestMapAppData& tdata = data_intx.tempestData;
5928  if( tdata.remapper != nullptr ) return moab::MB_SUCCESS; // nothing to do
5929 
5930 #ifdef MOAB_HAVE_MPI
5931  if( pco_intx )
5932  {
5933  MB_CHK_ERR( pco_intx->check_all_shared_handles() );
5934  }
5935 #endif
5936 
5937  ierr = iMOAB_UpdateMeshInfo( pid_src );MB_CHK_ERR( ierr );
5938  ierr = iMOAB_UpdateMeshInfo( pid_tgt );MB_CHK_ERR( ierr );
5939 
5940  // Rescale the radius of both to compute the intersection
5941  ComputeSphereRadius( pid_src, &radius_source );
5942  ComputeSphereRadius( pid_tgt, &radius_target );
5943 
5944  IntxAreaUtils areaAdaptor;
5945  // print verbosely about the problem setting
5946  {
5947  moab::Range rintxverts, rintxelems;
5948  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_src.file_set, 0, rintxverts ) );
5949  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_src.file_set, 2, rintxelems ) );
5951  MB_CHK_ERR( areaAdaptor.positive_orientation( context.MBI, data_src.file_set, radius_source ) );
5952 #ifdef VERBOSE
5953  std::cout << "The red set contains " << rintxverts.size() << " vertices and " << rintxelems.size()
5954  << " elements \n";
5955 #endif
5956 
5957  moab::Range bintxverts, bintxelems;
5958  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_tgt.file_set, 0, bintxverts ) );
5959  MB_CHK_ERR( context.MBI->get_entities_by_dimension( data_tgt.file_set, 2, bintxelems ) );
5961  MB_CHK_ERR( areaAdaptor.positive_orientation( context.MBI, data_tgt.file_set, radius_target ) );
5962 #ifdef VERBOSE
5963  std::cout << "The blue set contains " << bintxverts.size() << " vertices and " << bintxelems.size()
5964  << " elements \n";
5965 #endif
5966  }
5967 
5968  data_intx.dimension = data_tgt.dimension;
5969  // set the context for the source and destination applications
5970  // set the context for the source and destination applications
5971  tdata.pid_src = pid_src;
5972  tdata.pid_dest = pid_tgt;
5973  data_intx.point_cloud = ( data_src.point_cloud || data_tgt.point_cloud );
5974  assert( data_intx.point_cloud == true );
5975 
5976  // Now allocate and initialize the remapper object
5977 #ifdef MOAB_HAVE_MPI
5978  tdata.remapper = new moab::TempestRemapper( context.MBI, pco_intx );
5979 #else
5980  tdata.remapper = new moab::TempestRemapper( context.MBI );
5981 #endif
5982  tdata.remapper->meshValidate = true;
5983  tdata.remapper->constructEdgeMap = true;
5984 
5985  // Do not create new filesets; Use the sets from our respective applications
5986  tdata.remapper->initialize( false );
5987  tdata.remapper->GetMeshSet( moab::Remapper::SourceMesh ) = data_src.file_set;
5988  tdata.remapper->GetMeshSet( moab::Remapper::TargetMesh ) = data_tgt.file_set;
5989  tdata.remapper->GetMeshSet( moab::Remapper::OverlapMesh ) = data_intx.file_set;
5990 
5991  /* Let make sure that the radius match for source and target meshes. If not, rescale now and
5992  * unscale later. */
5993  if( fabs( radius_source - radius_target ) > 1e-10 )
5994  { /* the radii are different */
5997  }
5998 
5999  MB_CHK_ERR( tdata.remapper->ConvertMeshToTempest( moab::Remapper::SourceMesh ) );
6000  MB_CHK_ERR( tdata.remapper->ConvertMeshToTempest( moab::Remapper::TargetMesh ) );
6001 
6002  // First, compute the covering source set.
6003  MB_CHK_ERR( tdata.remapper->ConstructCoveringSet( epsrel, 1.0, 1.0, boxeps, false ) );
6004 
6005 #ifdef MOAB_HAVE_MPI
6006  /* VSM: This context should be set on the data_src but it would overwrite the source
6007  covering set context in case it is coupled to another APP as well.
6008  This needs a redesign. */
6009  // data_intx.covering_set = tdata.remapper->GetCoveringSet();
6010  // data_src.covering_set = tdata.remapper->GetCoveringSet();
6011 #endif
6012 
6013  // Now let us re-convert the MOAB mesh back to Tempest representation
6014  // MB_CHK_ERR( tdata.remapper->ComputeGlobalLocalMaps() );
6015 
6016  return moab::MB_SUCCESS;
6017 }
6018 
6019 ErrCode iMOAB_ComputeScalarProjectionWeights(
6020  iMOAB_AppID pid_intx,
6021  const iMOAB_String solution_weights_identifier, /* "scalar", "flux", "custom" */
6022  const iMOAB_String disc_method_source,
6023  int* disc_order_source,
6024  const iMOAB_String disc_method_target,
6025  int* disc_order_target,
6026  const iMOAB_String fv_method,
6027  int* fNoBubble,
6028  int* fMonotoneTypeID,
6029  int* fVolumetric,
6030  int* fInverseDistanceMap,
6031  int* fNoConservation,
6032  int* fValidate,
6033  const iMOAB_String source_solution_tag_dof_name,
6034  const iMOAB_String target_solution_tag_dof_name )
6035 {
6036  assert( disc_order_source && disc_order_target && *disc_order_source > 0 && *disc_order_target > 0 );
6037  assert( solution_weights_identifier && strlen( solution_weights_identifier ) );
6038  assert( disc_method_source && strlen( disc_method_source ) );
6039  assert( disc_method_target && strlen( disc_method_target ) );
6040  assert( source_solution_tag_dof_name && strlen( source_solution_tag_dof_name ) );
6041  assert( target_solution_tag_dof_name && strlen( target_solution_tag_dof_name ) );
6042 
6043  // Get the source and target data and pcomm objects
6044  appData& data_intx = context.appDatas[*pid_intx];
6045  TempestMapAppData& tdata = data_intx.tempestData;
6046 
6047  // Setup computation of weights
6048  // Set the context for the remapping weights computation
6049  tdata.weightMaps[std::string( solution_weights_identifier )] = new moab::TempestOnlineMap( tdata.remapper );
6050 
6051  // Call to generate the remap weights with the tempest meshes
6052  moab::TempestOnlineMap* weightMap = tdata.weightMaps[std::string( solution_weights_identifier )];
6053  assert( weightMap != nullptr );
6054 
6055  GenerateOfflineMapAlgorithmOptions mapOptions;
6056  mapOptions.nPin = *disc_order_source;
6057  mapOptions.nPout = *disc_order_target;
6058  mapOptions.fSourceConcave = false;
6059  mapOptions.fTargetConcave = false;
6060 
6061  mapOptions.strMethod = "";
6062  if( fv_method ) mapOptions.strMethod += std::string( fv_method ) + ";";
6063  if( fMonotoneTypeID )
6064  {
6065  switch( *fMonotoneTypeID )
6066  {
6067  case 0:
6068  mapOptions.fMonotone = false;
6069  break;
6070  case 3:
6071  mapOptions.strMethod += "mono3;";
6072  break;
6073  case 2:
6074  mapOptions.strMethod += "mono2;";
6075  break;
6076  default:
6077  mapOptions.fMonotone = true;
6078  }
6079  }
6080  else
6081  mapOptions.fMonotone = false;
6082 
6083  mapOptions.fNoBubble = ( fNoBubble ? *fNoBubble : false );
6084  mapOptions.fNoConservation = ( fNoConservation ? *fNoConservation > 0 : false );
6085  mapOptions.fNoCorrectAreas = false;
6086  // mapOptions.fNoCheck = !( fValidate ? *fValidate : true );
6087  mapOptions.fNoCheck = true;
6088  if( fVolumetric && *fVolumetric ) mapOptions.strMethod += "volumetric;";
6089  if( fInverseDistanceMap && *fInverseDistanceMap ) mapOptions.strMethod += "invdist;";
6090 
6091  std::string metadataStr = mapOptions.strMethod + ";" + std::string( disc_method_source ) + ":" +
6092  std::to_string( *disc_order_source ) + ":" + std::string( source_solution_tag_dof_name ) +
6093  ";" + std::string( disc_method_target ) + ":" + std::to_string( *disc_order_target ) +
6094  ":" + std::string( target_solution_tag_dof_name );
6095 
6096  data_intx.metadataMap[std::string( solution_weights_identifier )] = metadataStr;
6097 
6098  // Now let us compute the local-global mapping and store it in the context
6099  // We need this mapping when computing matvec products and to do reductions in parallel
6100  // Additionally, the call below will also compute weights with TempestRemap
6102  std::string( disc_method_source ), // const std::string& strInputType
6103  std::string( disc_method_target ), // const std::string& strOutputType
6104  mapOptions, // GenerateOfflineMapAlgorithmOptions& mapOptions
6105  std::string( source_solution_tag_dof_name ), // const std::string& srcDofTagName = "GLOBAL_ID"
6106  std::string( target_solution_tag_dof_name ) // const std::string& tgtDofTagName = "GLOBAL_ID"
6107  ) );
6108 
6109  // print some map statistics
6110  weightMap->PrintMapStatistics();
6111 
6112  if( fValidate && *fValidate )
6113  {
6114  const double dNormalTolerance = 1.0E-8;
6115  const double dStrictTolerance = 1.0E-12;
6116  weightMap->CheckMap( true, true, ( fMonotoneTypeID && *fMonotoneTypeID ), dNormalTolerance, dStrictTolerance );
6117  }
6118 
6119  return moab::MB_SUCCESS;
6120 }
6121 
6122 // Forward declaration of internal helper
6123 static ErrCode ComputeRowBounds( iMOAB_AppID pid_intersection,
6124  const iMOAB_String solution_weights_identifier,
6125  const iMOAB_String source_solution_tag_name,
6126  const iMOAB_String lower_bound_tag_name,
6127  const iMOAB_String upper_bound_tag_name );
6128 
6129 ErrCode iMOAB_ApplyScalarProjectionWeights(
6130  iMOAB_AppID pid_intersection,
6131  int* filter_type,
6132  const iMOAB_String solution_weights_identifier, /* "scalar", "flux", "custom" */
6133  const iMOAB_String source_solution_tag_name,
6134  const iMOAB_String target_solution_tag_name )
6135 {
6136  assert( solution_weights_identifier && strlen( solution_weights_identifier ) );
6137  assert( source_solution_tag_name && strlen( source_solution_tag_name ) );
6138  assert( target_solution_tag_name && strlen( target_solution_tag_name ) );
6139 
6140  moab::ErrorCode rval;
6141 
6142  // Get the source and target data and pcomm objects
6143  appData& data_intx = context.appDatas[*pid_intersection];
6144  TempestMapAppData& tdata = data_intx.tempestData;
6145 
6146  if( !tdata.weightMaps.count( std::string( solution_weights_identifier ) ) ) // key does not exist
6148  moab::TempestOnlineMap* weightMap = tdata.weightMaps[std::string( solution_weights_identifier )];
6149 
6150  // we assume that there are separators ":" between the tag names
6151  std::vector< std::string > srcNames;
6152  std::vector< std::string > tgtNames;
6153  std::vector< Tag > srcTagHandles;
6154  std::vector< Tag > tgtTagHandles;
6155  std::string separator( ":" );
6156  std::string src_name( source_solution_tag_name );
6157  std::string tgt_name( target_solution_tag_name );
6158  split_tag_names( src_name, separator, srcNames );
6159  split_tag_names( tgt_name, separator, tgtNames );
6160  if( srcNames.size() != tgtNames.size() )
6161  {
6162  std::cout << " error in parsing source and target tag names. \n";
6163  return moab::MB_FAILURE;
6164  }
6165 
6166  // get both source and target tag handles
6167  for( size_t i = 0; i < srcNames.size(); i++ )
6168  {
6169  Tag tagHandle;
6170  // get source tag handles and arrange in order
6171  rval = context.MBI->tag_get_handle( srcNames[i].c_str(), tagHandle );
6172  if( MB_SUCCESS != rval || nullptr == tagHandle )
6173  {
6174  return moab::MB_TAG_NOT_FOUND;
6175  }
6176  srcTagHandles.push_back( tagHandle );
6177 
6178  // get corresponding target handles and arrange in the same order
6179  rval = context.MBI->tag_get_handle( tgtNames[i].c_str(), tagHandle );
6180  if( MB_SUCCESS != rval || nullptr == tagHandle )
6181  {
6182  return moab::MB_TAG_NOT_FOUND;
6183  }
6184  tgtTagHandles.push_back( tagHandle );
6185  }
6186 
6187 #ifdef VERBOSE
6188  // Now get reference to the remapper object
6189  moab::TempestRemapper* remapper = tdata.remapper;
6190  std::vector< double > solSTagVals;
6191  std::vector< double > solTTagVals;
6192 
6193  moab::Range sents, tents;
6194  if( data_intx.point_cloud )
6195  {
6196  appData& data_src = context.appDatas[*tdata.pid_src];
6197  appData& data_tgt = context.appDatas[*tdata.pid_dest];
6198  if( data_src.point_cloud )
6199  {
6200  moab::Range& covSrcEnts = remapper->GetMeshVertices( moab::Remapper::CoveringMesh );
6201  solSTagVals.resize( covSrcEnts.size(), 0. );
6202  sents = covSrcEnts;
6203  }
6204  else
6205  {
6206  moab::Range& covSrcEnts = remapper->GetMeshEntities( moab::Remapper::CoveringMesh );
6207  solSTagVals.resize(
6208  covSrcEnts.size() * weightMap->GetSourceNDofsPerElement() * weightMap->GetSourceNDofsPerElement(), 0. );
6209  sents = covSrcEnts;
6210  }
6211  if( data_tgt.point_cloud )
6212  {
6213  moab::Range& tgtEnts = remapper->GetMeshVertices( moab::Remapper::TargetMesh );
6214  solTTagVals.resize( tgtEnts.size(), 0. );
6215  tents = tgtEnts;
6216  }
6217  else
6218  {
6219  moab::Range& tgtEnts = remapper->GetMeshEntities( moab::Remapper::TargetMesh );
6220  solTTagVals.resize( tgtEnts.size() * weightMap->GetDestinationNDofsPerElement() *
6221  weightMap->GetDestinationNDofsPerElement(),
6222  0. );
6223  tents = tgtEnts;
6224  }
6225  }
6226  else
6227  {
6228  moab::Range& covSrcEnts = remapper->GetMeshEntities( moab::Remapper::CoveringMesh );
6229  moab::Range& tgtEnts = remapper->GetMeshEntities( moab::Remapper::TargetMesh );
6230  solSTagVals.resize(
6231  covSrcEnts.size() * weightMap->GetSourceNDofsPerElement() * weightMap->GetSourceNDofsPerElement(), -1.0 );
6232  solTTagVals.resize( tgtEnts.size() * weightMap->GetDestinationNDofsPerElement() *
6233  weightMap->GetDestinationNDofsPerElement(),
6234  0. );
6235 
6236  sents = covSrcEnts;
6237  tents = tgtEnts;
6238  }
6239 #endif
6240 
6242  if( filter_type )
6243  {
6244  switch( *filter_type )
6245  {
6246  case 1:
6248  break;
6249  case 2:
6251  break;
6252  case 3:
6254  break;
6255  default:
6257  }
6258  }
6259 
6260  // Look up the low-order weight map
6261  std::string lo_weights_identifier =
6262  ( strlen( solution_weights_identifier ) > 3 ? std::string( solution_weights_identifier + 3 ) : "" );
6263  moab::TempestOnlineMap* loWeightMap = nullptr;
6264  bool useDualMapBounds = false;
6265  if( lo_weights_identifier.size() && tdata.weightMaps.count( lo_weights_identifier ) )
6266  {
6267  // store the reference to the weight map
6268  loWeightMap = tdata.weightMaps[lo_weights_identifier];
6269  // Dual-map nonlinear remapping: use low-order map stencil for CAAS bounds
6270  useDualMapBounds = ( loWeightMap && caasType != moab::TempestOnlineMap::CAAS_NONE );
6271  }
6272 
6273  if( useDualMapBounds )
6274  {
6275  for( size_t i = 0; i < srcTagHandles.size(); i++ )
6276  {
6277  // Apply high-order projection with dual-map CAAS bounds from low-order map
6278  // If caasType == CAAS_NONE, this just returns the low-order projection back
6279  MB_CHK_ERR(
6280  weightMap->ApplyWeightsWithDualMap( srcTagHandles[i], tgtTagHandles[i], loWeightMap, caasType ) );
6281  }
6282 
6283  // Store diagnostic per-row bounds from the HIGH-ORDER stencil on
6284  // target entities. ApplyWeightsWithDualMap uses the high-order
6285  // stencil to define [lo,hi] (per Bradley et al. 2019); the test
6286  // bounds must match for verification to be meaningful.
6287  for( size_t i = 0; i < srcNames.size(); i++ )
6288  {
6289  std::string loBoundName = srcNames[i] + "_DualMapLoBound";
6290  std::string hiBoundName = srcNames[i] + "_DualMapHiBound";
6291  MB_CHK_ERR( ComputeRowBounds( pid_intersection, solution_weights_identifier, srcNames[i].c_str(),
6292  loBoundName.c_str(), hiBoundName.c_str() ) );
6293  }
6294  }
6295  else
6296  {
6297  for( size_t i = 0; i < srcTagHandles.size(); i++ )
6298  {
6299  // Standard: apply projection with optional overlap-based CAAS
6300  MB_CHK_ERR( weightMap->ApplyWeights( srcTagHandles[i], tgtTagHandles[i], false, caasType ) );
6301  }
6302  }
6303 
6304 // #define VERBOSE
6305 #ifdef VERBOSE
6306  ParallelComm* pco_intx = context.appDatas[*pid_intersection].pcomm;
6307 
6308  int ivar = 0;
6309  {
6310  Tag ssolnTag = srcTagHandles[ivar];
6311  std::stringstream sstr;
6312  sstr << "covsrcTagData_" << *pid_intersection << "_" << ivar << "_" << pco_intx->rank() << ".txt";
6313  std::ofstream output_file( sstr.str().c_str() );
6314  for( unsigned i = 0; i < sents.size(); ++i )
6315  {
6316  EntityHandle elem = sents[i];
6317  std::vector< double > locsolSTagVals( 16 );
6318  MB_CHK_ERR( context.MBI->tag_get_data( ssolnTag, &elem, 1, &locsolSTagVals[0] ) );
6319  output_file << "\n" << remapper->GetGlobalID( Remapper::CoveringMesh, i ) << "-- \n\t";
6320  for( unsigned j = 0; j < 16; ++j )
6321  output_file << locsolSTagVals[j] << " ";
6322  }
6323  output_file.flush(); // required here
6324  output_file.close();
6325  }
6326  {
6327  std::stringstream sstr;
6328  sstr << "outputSrcDest_" << *pid_intersection << "_" << ivar << "_" << pco_intx->rank() << ".h5m";
6329  EntityHandle sets[2] = { context.appDatas[*tdata.pid_src].file_set,
6330  context.appDatas[*tdata.pid_dest].file_set };
6331  MB_CHK_ERR( context.MBI->write_file( sstr.str().c_str(), nullptr, "", sets, 2 ) );
6332  }
6333  {
6334  std::stringstream sstr;
6335  sstr << "outputCovSrcDest_" << *pid_intersection << "_" << ivar << "_" << pco_intx->rank() << ".h5m";
6336  // EntityHandle sets[2] = {data_intx.file_set, data_intx.covering_set};
6337  EntityHandle covering_set = remapper->GetCoveringSet();
6338  EntityHandle sets[2] = { covering_set, context.appDatas[*tdata.pid_dest].file_set };
6339  MB_CHK_ERR( context.MBI->write_file( sstr.str().c_str(), nullptr, "", sets, 2 ) );
6340  }
6341  {
6342  std::stringstream sstr;
6343  sstr << "covMesh_" << *pid_intersection << "_" << pco_intx->rank() << ".vtk";
6344  // EntityHandle sets[2] = {data_intx.file_set, data_intx.covering_set};
6345  EntityHandle covering_set = remapper->GetCoveringSet();
6346  MB_CHK_ERR( context.MBI->write_file( sstr.str().c_str(), nullptr, "", &covering_set, 1 ) );
6347  }
6348  {
6349  std::stringstream sstr;
6350  sstr << "tgtMesh_" << *pid_intersection << "_" << pco_intx->rank() << ".vtk";
6351  // EntityHandle sets[2] = {data_intx.file_set, data_intx.covering_set};
6352  EntityHandle target_set = remapper->GetMeshSet( Remapper::TargetMesh );
6353  MB_CHK_ERR( context.MBI->write_file( sstr.str().c_str(), nullptr, "", &target_set, 1 ) );
6354  }
6355  {
6356  std::stringstream sstr;
6357  sstr << "colvector_" << *pid_intersection << "_" << ivar << "_" << pco_intx->rank() << ".txt";
6358  std::ofstream output_file( sstr.str().c_str() );
6359  for( unsigned i = 0; i < solSTagVals.size(); ++i )
6360  output_file << i << " " << weightMap->col_dofmap[i] << " " << weightMap->col_gdofmap[i] << " "
6361  << solSTagVals[i] << "\n";
6362  output_file.flush(); // required here
6363  output_file.close();
6364  }
6365 #endif
6366  // #undef VERBOSE
6367 
6368  return moab::MB_SUCCESS;
6369 }
6370 
6371 static ErrCode ComputeRowBounds( iMOAB_AppID pid_intersection,
6372  const iMOAB_String solution_weights_identifier,
6373  const iMOAB_String source_solution_tag_name,
6374  const iMOAB_String lower_bound_tag_name,
6375  const iMOAB_String upper_bound_tag_name )
6376 {
6377  assert( solution_weights_identifier && strlen( solution_weights_identifier ) );
6378  assert( source_solution_tag_name && strlen( source_solution_tag_name ) );
6379  assert( lower_bound_tag_name && strlen( lower_bound_tag_name ) );
6380  assert( upper_bound_tag_name && strlen( upper_bound_tag_name ) );
6381 
6382  appData& data_intx = context.appDatas[*pid_intersection];
6383  TempestMapAppData& tdata = data_intx.tempestData;
6384 
6385  if( !tdata.weightMaps.count( std::string( solution_weights_identifier ) ) ) return moab::MB_INDEX_OUT_OF_RANGE;
6386  moab::TempestOnlineMap* weightMap = tdata.weightMaps[std::string( solution_weights_identifier )];
6387 
6388  // Get entity ranges
6389  moab::TempestRemapper* remapper = tdata.remapper;
6390  moab::Range covSrcEnts = remapper->GetMeshEntities( moab::Remapper::CoveringMesh );
6392 
6393  int srcNDof = weightMap->GetSourceNDofsPerElement();
6394  int tgtNDof = weightMap->GetDestinationNDofsPerElement();
6395 
6396  // Read source field values from coverage mesh
6397  Tag srcTag;
6398  moab::ErrorCode rval = context.MBI->tag_get_handle( source_solution_tag_name, srcTag );
6399  if( rval != moab::MB_SUCCESS ) return moab::MB_TAG_NOT_FOUND;
6400 
6401  std::vector< double > srcVals( covSrcEnts.size() * srcNDof * srcNDof, 0.0 );
6402  MB_CHK_ERR( context.MBI->tag_get_data( srcTag, covSrcEnts, &srcVals[0] ) );
6403 
6404  // Get or create lower/upper bound tags on target entities
6405  size_t nTargetDofs = tgtEnts.size() * tgtNDof * tgtNDof;
6406  Tag loTag, hiTag;
6407  MB_CHK_SET_ERR( context.MBI->tag_get_handle( lower_bound_tag_name, tgtNDof * tgtNDof, MB_TYPE_DOUBLE, loTag,
6409  "Failed to get or create lower bound tag on target entities" );
6410  MB_CHK_SET_ERR( context.MBI->tag_get_handle( upper_bound_tag_name, tgtNDof * tgtNDof, MB_TYPE_DOUBLE, hiTag,
6412  "Failed to get or create upper bound tag on target entities" );
6413 
6414  // Compute per-row bounds from weight matrix stencil.
6415  // Matrix column indices are NOT the same as srcVals indices; we must map
6416  // matrix-col -> source-vector-index via the inverse of col_dtoc_dofmap.
6417  auto& W = weightMap->GetWeightMatrix();
6418  const std::vector< int >& col_dtoc = weightMap->GetColDofMap();
6419  const std::vector< int >& row_dtoc = weightMap->GetRowDofMap();
6420  int maxMatCol = -1;
6421  for( size_t k = 0; k < srcVals.size() && k < col_dtoc.size(); k++ )
6422  if( col_dtoc[k] > maxMatCol ) maxMatCol = col_dtoc[k];
6423  std::vector< int > col_inv( maxMatCol + 1, -1 );
6424  for( size_t k = 0; k < srcVals.size() && k < col_dtoc.size(); k++ )
6425  if( col_dtoc[k] >= 0 ) col_inv[col_dtoc[k]] = (int)k;
6426 
6427  std::vector< double > loBound( nTargetDofs, 1e308 );
6428  std::vector< double > hiBound( nTargetDofs, -1e308 );
6429 
6430  for( size_t i = 0; i < nTargetDofs; i++ )
6431  {
6432  int r = ( i < row_dtoc.size() ) ? row_dtoc[i] : -1;
6433  if( r < 0 || r >= W.outerSize() )
6434  {
6435  loBound[i] = 0.0;
6436  hiBound[i] = 0.0;
6437  continue;
6438  }
6439  for( moab::TempestOnlineMap::WeightMatrix::InnerIterator it( W, r ); it; ++it )
6440  {
6441  int mc = (int)it.col();
6442  if( mc < 0 || mc > maxMatCol ) continue;
6443  int srcIdx = col_inv[mc];
6444  if( srcIdx < 0 || srcIdx >= (int)srcVals.size() ) continue;
6445  double v = srcVals[srcIdx];
6446  if( v < loBound[i] ) loBound[i] = v;
6447  if( v > hiBound[i] ) hiBound[i] = v;
6448  }
6449  if( loBound[i] > hiBound[i] )
6450  {
6451  loBound[i] = 0.0;
6452  hiBound[i] = 0.0;
6453  }
6454  }
6455 
6456  // Write bounds to tags
6457  MB_CHK_SET_ERR( context.MBI->tag_set_data( loTag, tgtEnts, &loBound[0] ),
6458  "Failed to set lower bound tag data on target entities" );
6459  MB_CHK_SET_ERR( context.MBI->tag_set_data( hiTag, tgtEnts, &hiBound[0] ),
6460  "Failed to set upper bound tag data on target entities" );
6461 
6462  return moab::MB_SUCCESS;
6463 }
6464 
6465 #endif // MOAB_HAVE_TEMPESTREMAP
6466 
6467 #ifdef __cplusplus
6468 }
6469 #endif