Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
moab::TempestOnlineMap Class Reference

An offline map between two Meshes. More...

#include <TempestOnlineMap.hpp>

+ Inheritance diagram for moab::TempestOnlineMap:
+ Collaboration diagram for moab::TempestOnlineMap:

Public Types

enum  DiscretizationType { DiscretizationType_FV = 0 , DiscretizationType_CGLL = 1 , DiscretizationType_DGLL = 2 , DiscretizationType_PCLOUD = 3 }
 
enum  CAASType {
  CAAS_NONE = 0 , CAAS_GLOBAL = 1 , CAAS_LOCAL = 2 , CAAS_LOCAL_ADJACENT = 3 ,
  CAAS_QLT = 4
}
 
typedef double(* sample_function) (double, double)
 

Public Member Functions

 TempestOnlineMap (moab::TempestRemapper *remapper)
 Generate the metadata associated with the offline map. More...
 
virtual ~TempestOnlineMap ()
 Define a virtual destructor. More...
 
moab::ErrorCode GenerateRemappingWeights (std::string strInputType, std::string strOutputType, const GenerateOfflineMapAlgorithmOptions &mapOptions, const std::string &srcDofTagName="GLOBAL_ID", const std::string &tgtDofTagName="GLOBAL_ID")
 Generate the offline map, given the source and target mesh and discretization details. This method generates the mapping between the two meshes based on the overlap and stores the result in the SparseMatrix. More...
 
moab::ErrorCode ReadParallelMap (const char *strSource, const std::vector< int > &tgt_dof_ids, int arearead, std::vector< double > &areaA, int &nA, std::vector< double > &areaB, int &nB)
 Generate the metadata associated with the offline map. More...
 
moab::ErrorCode WriteParallelMap (const std::string &strTarget, const std::map< std::string, std::string > &attrMap)
 Write the TempestOnlineMap to a parallel NetCDF file. More...
 
virtual int IsConsistent (double dTolerance)
 Determine if the map is first-order accurate. More...
 
virtual int IsConservative (double dTolerance)
 Determine if the map is conservative. More...
 
virtual int IsMonotone (double dTolerance)
 Determine if the map is monotone. More...
 
const DataArray1D< double > & GetGlobalSourceAreas () const
 If we computed the reduction, get the vector representing the source areas for all entities in the mesh More...
 
const DataArray1D< double > & GetGlobalTargetAreas () const
 If we computed the reduction, get the vector representing the target areas for all entities in the mesh More...
 
void PrintMapStatistics ()
 Print information and metadata about the remapping weights. More...
 
moab::ErrorCode SetDOFmapTags (const std::string srcDofTagName, const std::string tgtDofTagName)
 Store the tag names associated with global DoF ids for source and target meshes to be used for mapping.

Parameters
srcDofTagNameThe tag name associated with global DoF ids for the source mesh
tgtDofTagNameThe tag name associated with global DoF ids for the target mesh
More...
 
moab::ErrorCode SetDOFmapAssociation (DiscretizationType srcType, int srcOrder, bool isSrcContinuous, DataArray3D< int > *srcdataGLLNodes, DataArray3D< int > *srcdataGLLNodesSrc, DiscretizationType destType, int destOrder, bool isTgtContinuous, DataArray3D< int > *tgtdataGLLNodes)
 
std::pair< double, double > ApplyBoundsLimiting (std::vector< double > &dataInDouble, std::vector< double > &dataOutDouble, CAASType caasType=CAAS_GLOBAL, int caasIteration=0, double mismatch=0.0)
 
void ComputeAdjacencyRelations (std::vector< std::unordered_set< int > > &vecAdjFaces, int nrings, const Range &entities, bool useMOABAdjacencies=true, Mesh *trMesh=nullptr)
 
int GetSourceGlobalNDofs ()
 Get the number of total Degrees-Of-Freedom defined on the source mesh. More...
 
int GetDestinationGlobalNDofs ()
 Get the number of total Degrees-Of-Freedom defined on the destination mesh. More...
 
int GetSourceLocalNDofs ()
 Get the number of local Degrees-Of-Freedom defined on the source mesh. More...
 
int GetDestinationLocalNDofs ()
 Get the number of local Degrees-Of-Freedom defined on the destination mesh. More...
 
int GetSourceNDofsPerElement ()
 Get the number of Degrees-Of-Freedom per element on the source mesh. More...
 
int GetDestinationNDofsPerElement ()
 Get the number of Degrees-Of-Freedom per element on the destination mesh. More...
 
void SetSourceNDofsPerElement (int ns)
 Set the number of Degrees-Of-Freedom per element on the source mesh. More...
 
void SetDestinationNDofsPerElement (int nt)
 Get the number of Degrees-Of-Freedom per element on the destination mesh. More...
 
int GetRowGlobalDoF (int localID) const
 Get the global Degrees-Of-Freedom ID on the destination mesh. More...
 
int GetIndexOfRowGlobalDoF (int globalRowDoF) const
 Get the index of globaRowDoF. More...
 
int GetColGlobalDoF (int localID) const
 Get the global Degrees-Of-Freedom ID on the source mesh. More...
 
int GetIndexOfColGlobalDoF (int globalColDoF) const
 Get the index of globaColDoF. More...
 
moab::ErrorCode ApplyWeights (moab::Tag srcSolutionTag, moab::Tag tgtSolutionTag, bool transpose=false, CAASType caasType=CAAS_NONE, double default_projection=0.0)
 Apply the weight matrix onto the source vector (tag) provided as input, and return the column vector (solution projection) in a tag, after the map application Compute: tgtVals = A(S->T) * More...
 
moab::ErrorCode ApplyWeightsWithDualMap (moab::Tag srcSolutionTag, moab::Tag tgtSolutionTag, TempestOnlineMap *loWeightMap, CAASType caasType=CAAS_LOCAL)
 Apply the high-order weight matrix onto the source vector (tag), and then enforce bounds computed from the stencil of a separate low-order weight map using the Clip-And-Assert-Sum (CAAS) algorithm. This implements the dual-map nonlinear remapping pattern used in E3SM coupling. loWeightMap provides the low-order (monotone) map whose per-row stencil defines the min/max bounds. The CAAS filter clips the high-order result to those bounds and redistributes mass proportionally to maintain conservation. More...
 
moab::ErrorCode DefineAnalyticalSolution (moab::Tag &exactSolnTag, const std::string &solnName, Remapper::IntersectionContext ctx, sample_function testFunction, moab::Tag *clonedSolnTag=NULL, std::string cloneSolnName="")
 Define an analytical solution over the given (source or target) mesh, as specificed in the context. This routine will define a tag that is compatible with the specified discretization method type and order and sample the solution exactly using the analytical function provided by the user. More...
 
moab::ErrorCode ComputeMetrics (Remapper::IntersectionContext ctx, moab::Tag &exactTag, moab::Tag &approxTag, std::map< std::string, double > &metrics, bool verbose=true)
 Compute the error between a sampled (exact) solution and a projected solution in various error norms. More...
 
moab::ErrorCode fill_col_ids (std::vector< int > &ids_of_interest)
 
moab::ErrorCode set_col_dc_dofs (std::vector< int > &values_entities)
 
moab::ErrorCode set_row_dc_dofs (std::vector< int > &values_entities)
 
int GlobalSourceDofCount () const
 Global number of source DoFs declared by the map file (n_a), or -1 if unknown. More...
 
int CountAbsentColumns (int &first_absent_gid) const
 Count weight-matrix columns that the migrated coverage did not supply (i.e. no covering cell maps to them). For offline file maps this equals the set of source DoFs the map references but that are absent from the source mesh. Returns the count and, in first_absent_gid, one example 1-based source GID (or -1 if none). More...
 
int DropAbsentColumns ()
 Zero the weights of columns the migrated coverage did not supply, then compact the matrix. Use ONLY for columns proven globally absent (masked source grid): the projection already treats them as zero-source, so this is a BfB-safe drop that lets the dual-map CAAS bounds check skip them instead of aborting. Returns the number of columns dropped. More...
 
void SetMeshInput (Mesh *imesh)
 
const std::vector< int > & GetRowDofMap () const
 Read-only access to the matrix-row -> matrix-col DOF index maps. Used by callers (e.g. iMOAB diagnostic helpers) that need to translate matrix indices back into source/target tag-vector indices. More...
 
const std::vector< int > & GetColDofMap () const
 

Private Member Functions

moab::ErrorCode LinearRemapNN_MOAB (bool use_GID_matching=false, bool strict_check=false)
 Compute the remapping weights as a permutation matrix that relates DoFs on the source mesh to DoFs on the target mesh. More...
 
void LinearRemapFVtoFV_Tempest_MOAB (int nOrder)
 Compute the remapping weights for a FV field defined on the source to a FV field defined on the target mesh. More...
 
void LinearRemapSE0_Tempest_MOAB (const DataArray3D< int > &dataGLLNodes, const DataArray3D< double > &dataGLLJacobian)
 Generate the OfflineMap for linear conserative element-average spectral element to element average remapping. More...
 
void LinearRemapSE4_Tempest_MOAB (const DataArray3D< int > &dataGLLNodes, const DataArray3D< double > &dataGLLJacobian, int nMonotoneType, bool fContinuousIn, bool fNoConservation, bool fSparseConstraints)
 Generate the OfflineMap for cubic conserative element-average spectral element to element average remapping. More...
 
void LinearRemapFVtoGLL_MOAB (const DataArray3D< int > &dataGLLNodes, const DataArray3D< double > &dataGLLJacobian, const DataArray1D< double > &dataGLLNodalArea, int nOrder, int nMonotoneType, bool fContinuous, bool fNoConservation)
 Generate the OfflineMap for remapping from finite volumes to finite elements. More...
 
void LinearRemapGLLtoGLL2_MOAB (const DataArray3D< int > &dataGLLNodesIn, const DataArray3D< double > &dataGLLJacobianIn, const DataArray3D< int > &dataGLLNodesOut, const DataArray3D< double > &dataGLLJacobianOut, const DataArray1D< double > &dataNodalAreaOut, int nPin, int nPout, int nMonotoneType, bool fContinuousIn, bool fContinuousOut, bool fNoConservation)
 Generate the OfflineMap for remapping from finite elements to finite elements. More...
 
void LinearRemapGLLtoGLL2_Pointwise_MOAB (const DataArray3D< int > &dataGLLNodesIn, const DataArray3D< double > &dataGLLJacobianIn, const DataArray3D< int > &dataGLLNodesOut, const DataArray3D< double > &dataGLLJacobianOut, const DataArray1D< double > &dataNodalAreaOut, int nPin, int nPout, int nMonotoneType, bool fContinuousIn, bool fContinuousOut)
 Generate the OfflineMap for remapping from finite elements to finite elements (pointwise interpolation). More...
 
moab::ErrorCode WriteSCRIPMapFile (const std::string &strOutputFile, const std::map< std::string, std::string > &attrMap)
 Copy the local matrix from Tempest SparseMatrix representation (ELL) to the parallel CSR Eigen Matrix for scalable application of matvec needed for projections. More...
 
moab::ErrorCode WriteHDF5MapFile (const std::string &filename)
 Parallel I/O with NetCDF to write out the SCRIP file from multiple processors. More...
 
template<typename SparseMatrixType >
void serializeSparseMatrix (const SparseMatrixType &mat, const std::string &filename)
 
void setup_sizes_dimensions ()
 
void CAASLimiter (std::vector< double > &dataCorrectedField, std::vector< double > &dataLowerBound, std::vector< double > &dataUpperBound, double &dMass)
 
double QLTLimiter (int caasIteration, std::vector< double > &dataCorrectedField, std::vector< double > &dataLowerBound, std::vector< double > &dataUpperBound, std::vector< double > &dMassDefect)
 
moab::ErrorCode ApplyWeights (std::vector< double > &srcVals, std::vector< double > &tgtVals, bool transpose=false)
 Apply the weight matrix onto the source vector provided as input, and return the column vector (solution projection) after the map application Compute: tgtVals = A(S->T) * More...
 

Private Attributes

moab::TempestRemapperm_remapper
 The fundamental remapping operator object. More...
 
moab::Interfacem_interface
 The reference to the moab::Core object that contains source/target and overlap sets. More...
 
moab::Tag m_dofTagSrc
 The original tag data and local to global DoF mapping to associate matrix values to solution More...
 
moab::Tag m_dofTagDest
 
std::vector< unsigned > row_gdofmap
 
std::vector< unsigned > col_gdofmap
 
std::vector< unsigned > srccol_gdofmap
 
std::vector< int > row_dtoc_dofmap
 
std::vector< int > col_dtoc_dofmap
 
std::vector< int > srccol_dtoc_dofmap
 
std::map< int, int > rowMap
 
std::map< int, int > colMap
 
int m_input_order
 
int m_output_order
 
DataArray3D< int > dataGLLNodesSrc
 
DataArray3D< int > dataGLLNodesSrcCov
 
DataArray3D< int > dataGLLNodesDest
 
DiscretizationType m_srcDiscType
 
DiscretizationType m_destDiscType
 
int m_nTotDofs_Src
 
int m_nTotDofs_SrcCov
 
int m_nTotDofs_Dest
 
int m_nTotDofs_SrcGlobal
 
int m_nDofsPEl_Src
 
int m_nDofsPEl_Dest
 
DiscretizationType m_eInputType
 
DiscretizationType m_eOutputType
 
bool m_bConserved
 
int m_iMonotonicity
 
Mesh * m_meshInput
 
Mesh * m_meshInputCov
 
Mesh * m_meshOutput
 
Mesh * m_meshOverlap
 
bool is_parallel
 
bool is_root
 
int rank
 
int size
 

Detailed Description

An offline map between two Meshes.

Definition at line 68 of file TempestOnlineMap.hpp.

Member Typedef Documentation

◆ sample_function

typedef double( * moab::TempestOnlineMap::sample_function) (double, double)

Definition at line 437 of file TempestOnlineMap.hpp.

Member Enumeration Documentation

◆ CAASType

Enumerator
CAAS_NONE 
CAAS_GLOBAL 
CAAS_LOCAL 
CAAS_LOCAL_ADJACENT 
CAAS_QLT 

Definition at line 93 of file TempestOnlineMap.hpp.

94  {
95  CAAS_NONE = 0,
96  CAAS_GLOBAL = 1,
97  CAAS_LOCAL = 2,
99  CAAS_QLT = 4
100  };

◆ DiscretizationType

Enumerator
DiscretizationType_FV 
DiscretizationType_CGLL 
DiscretizationType_DGLL 
DiscretizationType_PCLOUD 

Definition at line 84 of file TempestOnlineMap.hpp.

Constructor & Destructor Documentation

◆ TempestOnlineMap()

moab::TempestOnlineMap::TempestOnlineMap ( moab::TempestRemapper remapper)

Generate the metadata associated with the offline map.

Definition at line 66 of file TempestOnlineMap.cpp.

66  : OfflineMap(), m_remapper( remapper )
67 {
68  // Get the references for the MOAB core objects
70 #ifdef MOAB_HAVE_MPI
71  m_pcomm = m_remapper->get_parallel_communicator();
72 #endif
73 
74  // now let us re-update the reference to the input source mesh
76  // now let us re-update the reference to the covering mesh
78  // now let us re-update the reference to the output target mesh
80  // now let us re-update the reference to the output target mesh
82 
83  is_parallel = remapper->is_parallel;
84  is_root = remapper->is_root;
85  rank = remapper->rank;
86  size = remapper->size;
87 
88  // set default order
90 
91  // unknown until a map file is read (ReadParallelMap sets it to n_a)
93 
94  // Initialize dimension information from file
95  this->setup_sizes_dimensions();
96 }

References moab::Remapper::get_interface(), moab::TempestRemapper::GetCoveringMesh(), moab::TempestRemapper::GetMesh(), is_parallel, moab::TempestRemapper::is_parallel, is_root, moab::TempestRemapper::is_root, m_input_order, m_interface, m_meshInput, m_meshInputCov, m_meshOutput, m_meshOverlap, m_nTotDofs_SrcGlobal, m_output_order, m_remapper, moab::Remapper::OverlapMesh, rank, moab::TempestRemapper::rank, setup_sizes_dimensions(), size, moab::TempestRemapper::size, moab::Remapper::SourceMesh, and moab::Remapper::TargetMesh.

◆ ~TempestOnlineMap()

moab::TempestOnlineMap::~TempestOnlineMap ( )
virtual

Define a virtual destructor.

Definition at line 123 of file TempestOnlineMap.cpp.

124 {
125  m_interface = nullptr;
126 #ifdef MOAB_HAVE_MPI
127  m_pcomm = nullptr;
128 #endif
129  m_meshInput = nullptr;
130  m_meshOutput = nullptr;
131  m_meshOverlap = nullptr;
132 }

Member Function Documentation

◆ ApplyBoundsLimiting()

std::pair< double, double > moab::TempestOnlineMap::ApplyBoundsLimiting ( std::vector< double > &  dataInDouble,
std::vector< double > &  dataOutDouble,
CAASType  caasType = CAAS_GLOBAL,
int  caasIteration = 0,
double  mismatch = 0.0 
)

ApplyBoundsLimiting - Apply bounds limiting to the data field

Parameters
dataInDouble- input data field
dataOutDouble- output data field
caasType- type of limiter
caasIteration- iteration number of limiter
Returns
- pair of mass defect pre and post limiter application

Definition at line 638 of file TempestLinearRemap.cpp.

643 {
644  // Currently only implemented for FV to FV remapping
645  // We should generalize this to other types of remapping
646  assert( !dataGLLNodesSrcCov.IsAttached() && !dataGLLNodesDest.IsAttached() );
647 
648  std::pair< double, double > massDefect( 0.0, 0.0 );
649 
650  // Check if the source and target data are of the same size
651  const size_t nTargetCount = dataOutDouble.size();
652  const DataArray1D< double >& m_dOverlapAreas = this->m_remapper->m_overlap->vecFaceArea;
653 
654  // Apply the offline map to the data
655  double dMassDiff = 0.0;
656  std::vector< double > x( nTargetCount );
657  std::vector< double > dataLowerBound( nTargetCount );
658  std::vector< double > dataUpperBound( nTargetCount );
659  std::vector< double > massVector( nTargetCount );
660  std::vector< std::unordered_set< int > > vecSourceOvTarget( nTargetCount );
661 
662 #undef USE_ComputeAdjacencyRelations
663  constexpr bool useMOABAdjacencies = true;
664 #ifdef USE_ComputeAdjacencyRelations
665  // Compute the adjacent faces to the source face
666  // However, calling MOAB to do this does not work correctly as we need ixS to be the index
667  // Cannot just iterate over all entities in the source covering mesh
668  if( caasType == CAAS_QLT || caasType == CAAS_LOCAL_ADJACENT )
669  {
670  if( useMOABAdjacencies )
671  {
672  moab::ErrorCode rval =
673  ComputeAdjacencyRelations( vecSourceOvTarget, caasIteration, m_remapper->m_covering_source_entities,
674  useMOABAdjacencies );MB_CHK_SET_ERR_CONT( rval, "Failed to get adjacent faces" );
675  }
676  else
677  {
678  moab::ErrorCode rval =
679  ComputeAdjacencyRelations( vecSourceOvTarget, caasIteration, m_remapper->m_covering_source_entities,
680  useMOABAdjacencies, m_meshInputCov );MB_CHK_SET_ERR_CONT( rval, "Failed to get adjacent faces" );
681  }
682  }
683 #else
685 #endif
686 
687  // Initialize the bounds on the given source and target data
688  double dSourceMin = dataInDouble[0];
689  double dSourceMax = dataInDouble[0];
690  double dTargetMin = dataOutDouble[0];
691  double dTargetMax = dataOutDouble[0];
692  for( size_t i = 0; i < m_meshOverlap->faces.size(); i++ )
693  {
694  const int ixS = m_meshOverlap->vecSourceFaceIx[i];
695  const int ixT = m_meshOverlap->vecTargetFaceIx[i];
696 
697  if( ixT < 0 ) continue; // skip ghost target faces
698 
699  assert( m_dOverlapAreas[i] > 0.0 );
700  assert( ixS >= 0 );
701  assert( ixT >= 0 );
702 
703 #ifndef USE_ComputeAdjacencyRelations
704  // Compute the adjacent faces to the target face
705  vecSourceOvTarget[ixT].insert( ixS ); // map target face to source face
706  if( ( caasType == CAAS_QLT || caasType == CAAS_LOCAL_ADJACENT ) )
707  {
708  if( useMOABAdjacencies )
709  {
710  moab::Range ents;
712  moab::Range adjEnts;
713  moab::ErrorCode rval = mtu.get_bridge_adjacencies( ents, 0, 2, adjEnts, caasIteration );MB_CHK_SET_ERR_CONT( rval, "Failed to get adjacent faces" );
714  for( moab::Range::iterator it = adjEnts.begin(); it != adjEnts.end(); ++it )
715  {
716  int adjIndex = m_remapper->m_covering_source_entities.index( *it );
717  if( adjIndex >= 0 ) vecSourceOvTarget[ixT].insert( adjIndex );
718  }
719  }
720  else
721  {
722  // Compute the adjacent faces to the target face
723  AdjacentFaceVector vecAdjFaces;
724  GetAdjacentFaceVectorByEdge( *m_meshInputCov, ixS,
725  ( caasIteration ) * ( m_input_order + 1 ) * ( m_input_order + 1 ),
726  vecAdjFaces );
727 
728  //Compute min/max over neighboring faces
729  for( size_t iadj = 0; iadj < vecAdjFaces.size(); iadj++ )
730  vecSourceOvTarget[ixT].insert( vecAdjFaces[iadj].first ); // map target face to source face
731  }
732  }
733 #endif
734 
735  // Update the min and max values of the source data
736  dSourceMax = fmax( dSourceMax, dataInDouble[ixS] );
737  dSourceMin = fmin( dSourceMin, dataInDouble[ixS] );
738 
739  // Update the min and max values of the target data
740  dTargetMin = fmin( dTargetMin, dataOutDouble[ixT] );
741  dTargetMax = fmax( dTargetMax, dataOutDouble[ixT] );
742 
743  const double locMassDiff = ( dataInDouble[ixS] * m_dOverlapAreas[i] ) - // source mass
744  ( dataOutDouble[ixT] * m_dOverlapAreas[i] ); // target mass
745 
746  // Update the mass difference between source and target faces
747  // linked to the overlap mesh element
748  dMassDiff += locMassDiff; // target mass
749  massVector[ixT] += locMassDiff;
750  }
751 
752 #ifdef MOAB_HAVE_MPI
753  std::vector< double > localMinMaxDefects( 5, 0.0 ), globalMinMaxDefects( 5, 0.0 );
754  localMinMaxDefects[0] = dSourceMin;
755  localMinMaxDefects[1] = dTargetMin;
756  localMinMaxDefects[2] = dSourceMax;
757  localMinMaxDefects[3] = dTargetMax;
758  localMinMaxDefects[4] = dMassDiff;
759 
760  if( caasType == CAAS_GLOBAL )
761  {
762  MPI_Allreduce( localMinMaxDefects.data(), globalMinMaxDefects.data(), 2, MPI_DOUBLE, MPI_MIN, m_pcomm->comm() );
763  MPI_Allreduce( localMinMaxDefects.data() + 2, globalMinMaxDefects.data() + 2, 2, MPI_DOUBLE, MPI_MAX,
764  m_pcomm->comm() );
765  dSourceMin = globalMinMaxDefects[0];
766  dSourceMax = globalMinMaxDefects[2];
767  dTargetMin = globalMinMaxDefects[1];
768  dTargetMax = globalMinMaxDefects[3];
769  }
770  if( caasIteration == 1 )
771  MPI_Allreduce( localMinMaxDefects.data() + 4, globalMinMaxDefects.data() + 4, 1, MPI_DOUBLE, MPI_SUM,
772  m_pcomm->comm() );
773  else
774  globalMinMaxDefects[4] = mismatch;
775 
776  dMassDiff = localMinMaxDefects[4];
777  // massDefect.first = localMinMaxDefects[4];
778  massDefect.first = globalMinMaxDefects[4];
779 #else
780 
781  // massDefect.first = fabs( dMassDiff / ( dSourceMax - dSourceMin ) );
782  massDefect.first = dMassDiff;
783 #endif
784 
785  // Early exit if the values are monotone already.
786  // if( ( dTargetMax <= dSourceMax && dTargetMin <= dSourceMin ) || fabs( massDefect.first ) < 1e-16 )
787  if( fabs( massDefect.first ) > 1e-20 )
788  {
789  if( caasType == CAAS_GLOBAL )
790  {
791  for( size_t i = 0; i < nTargetCount; i++ )
792  {
793  dataLowerBound[i] = dSourceMin - dataOutDouble[i];
794  dataUpperBound[i] = dSourceMax - dataOutDouble[i];
795  }
796  } // if( caasType == CAAS_GLOBAL )
797  else // caasType == CAAS_LOCAL
798  {
799  // Compute the local min and max values of the target data
800  std::vector< double > vecLocalUpperBound( nTargetCount );
801  std::vector< double > vecLocalLowerBound( nTargetCount );
802  // Loop over the target faces and compute the min and max values
803  // of the source data linked to the target faces
804  for( size_t i = 0; i < nTargetCount; i++ )
805  {
806  assert( vecSourceOvTarget[i].size() );
807 
808  double dMinI = 1E10; // dataInDouble[vecSourceOvTarget[i][0]];
809  double dMaxI = -1E10; // dataInDouble[vecSourceOvTarget[i][0]];
810 
811  // Compute max over intersecting source faces
812  for( const auto& srcElem : vecSourceOvTarget[i] )
813  {
814  dMinI = fmin( dMinI, dataInDouble[srcElem] ); // min over intersecting source faces
815  dMaxI = fmax( dMaxI, dataInDouble[srcElem] ); // max over intersecting source faces
816  }
817 
818  // Update the min and max values of the target data
819  vecLocalLowerBound[i] = dMinI;
820  vecLocalUpperBound[i] = dMaxI;
821  }
822 
823  for( size_t i = 0; i < nTargetCount; i++ )
824  {
825  dataLowerBound[i] = vecLocalLowerBound[i] - dataOutDouble[i];
826  dataUpperBound[i] = vecLocalUpperBound[i] - dataOutDouble[i];
827  }
828  } // caasType == CAAS_LOCAL
829 
830  // Invoke CAAS or QLT application on the map
831  if( fabs( dMassDiff ) > 1e-20 )
832  {
833  if( caasType == CAAS_QLT )
834  dMassDiff = QLTLimiter( caasIteration, dataOutDouble, dataLowerBound, dataUpperBound, massVector );
835  else
836  CAASLimiter( dataOutDouble, dataLowerBound, dataUpperBound, dMassDiff );
837  }
838 
839  // Announce output mass
840  double dMassDiffPost = 0.0;
841  for( size_t i = 0; i < m_meshOverlap->faces.size(); i++ )
842  {
843  const int ixS = m_meshOverlap->vecSourceFaceIx[i];
844  const int ixT = m_meshOverlap->vecTargetFaceIx[i];
845 
846  if( ixT < 0 ) continue; // skip ghost target faces
847 
848  // Update the mass difference between source and target faces
849  // linked to the overlap mesh element
850  dMassDiffPost += ( dataInDouble[ixS] * m_dOverlapAreas[i] ) - // source mass
851  ( dataOutDouble[ixT] * m_dOverlapAreas[i] ); // target mass
852  }
853  // massDefect.second = fabs( dMassDiffPost / ( dSourceMax - dSourceMin ) );
854  massDefect.second = dMassDiffPost;
855  }
856 
857  // Ideally should perform an AllReduce here to get the global mass difference across all processors
858  // But if we satisfy the constraint on every task, essentially, the global mass difference should be zero!
859  return massDefect;
860 }

References moab::Range::begin(), moab::Range::end(), ErrorCode, moab::GeomUtil::first(), moab::MeshTopoUtil::get_bridge_adjacencies(), moab::Range::insert(), and MB_CHK_SET_ERR_CONT.

◆ ApplyWeights() [1/2]

moab::ErrorCode moab::TempestOnlineMap::ApplyWeights ( moab::Tag  srcSolutionTag,
moab::Tag  tgtSolutionTag,
bool  transpose = false,
CAASType  caasType = CAAS_NONE,
double  default_projection = 0.0 
)

Apply the weight matrix onto the source vector (tag) provided as input, and return the column vector (solution projection) in a tag, after the map application Compute: tgtVals = A(S->T) *

Note
Source values, or if (transpose) tgtVals = [A(T->S)]^T *
Source values

Definition at line 1531 of file TempestOnlineMap.cpp.

1536 {
1537  std::vector< double > solSTagVals;
1538  std::vector< double > solTTagVals;
1539 
1540  moab::Range sents, tents;
1542  {
1544  {
1546  solSTagVals.resize( covSrcEnts.size(), default_projection );
1547  sents = covSrcEnts;
1548  }
1549  else
1550  {
1552  solSTagVals.resize( covSrcEnts.size() * this->GetSourceNDofsPerElement() * this->GetSourceNDofsPerElement(),
1553  default_projection );
1554  sents = covSrcEnts;
1555  }
1557  {
1559  solTTagVals.resize( tgtEnts.size(), default_projection );
1560  tents = tgtEnts;
1561  }
1562  else
1563  {
1565  solTTagVals.resize( tgtEnts.size() * this->GetDestinationNDofsPerElement() *
1566  this->GetDestinationNDofsPerElement(),
1567  default_projection );
1568  tents = tgtEnts;
1569  }
1570  }
1571  else
1572  {
1575  solSTagVals.resize( covSrcEnts.size() * this->GetSourceNDofsPerElement() * this->GetSourceNDofsPerElement(),
1576  default_projection );
1577  solTTagVals.resize( tgtEnts.size() * this->GetDestinationNDofsPerElement() *
1578  this->GetDestinationNDofsPerElement(),
1579  default_projection );
1580 
1581  sents = covSrcEnts;
1582  tents = tgtEnts;
1583  }
1584 
1585  // The tag data is np*np*n_el_src
1586  MB_CHK_SET_ERR( m_interface->tag_get_data( srcSolutionTag, sents, &solSTagVals[0] ),
1587  "Getting local tag data failed" );
1588 
1589  // Compute the application of weights on the suorce solution data and store it in the
1590  // destination solution vector data Optionally, can also perform the transpose application of
1591  // the weight matrix. Set the 3rd argument to true if this is needed
1592  MB_CHK_SET_ERR( this->ApplyWeights( solSTagVals, solTTagVals, transpose ),
1593  "Applying remap operator onto source vector data failed" );
1594 
1595  // The tag data is np*np*n_el_dest
1596  MB_CHK_SET_ERR( m_interface->tag_set_data( tgtSolutionTag, tents, &solTTagVals[0] ),
1597  "Setting target tag data failed" );
1598 
1599  if( caasType != CAAS_NONE )
1600  {
1601  std::string tgtSolutionTagName;
1602  MB_CHK_SET_ERR( m_interface->tag_get_name( tgtSolutionTag, tgtSolutionTagName ), "Getting tag name failed" );
1603 
1604  // Perform CAAS iterations iteratively until convergence
1605  constexpr int nmax_caas_iterations = 10;
1606  double mismatch = 1.0;
1607  int caasIteration = 0;
1608  double initialMismatch = 0.0;
1609  while( ( fabs( mismatch / initialMismatch ) > 1e-15 && fabs( mismatch ) > 1e-15 ) &&
1610  caasIteration++ < nmax_caas_iterations ) // iterate until convergence or a maximum of 5 iterations
1611  {
1612  double dMassDiffPostGlobal;
1613  std::pair< double, double > mDefect =
1614  this->ApplyBoundsLimiting( solSTagVals, solTTagVals, caasType, caasIteration, mismatch );
1615 #ifdef MOAB_HAVE_MPI
1616  double dMassDiffPost = mDefect.second;
1617  MPI_Allreduce( &dMassDiffPost, &dMassDiffPostGlobal, 1, MPI_DOUBLE, MPI_SUM, m_pcomm->comm() );
1618 #else
1619  dMassDiffPostGlobal = mDefect.second;
1620 #endif
1621  if( caasIteration == 1 ) initialMismatch = mDefect.first;
1622  if( m_remapper->verbose && is_root )
1623  {
1624  printf( "Field {%s} -> CAAS iteration: %d, mass defect: %3.4e, post-CAAS: %3.4e\n",
1625  tgtSolutionTagName.c_str(), caasIteration, mDefect.first, dMassDiffPostGlobal );
1626  }
1627  mismatch = dMassDiffPostGlobal;
1628 
1629  // The tag data is np*np*n_el_dest
1630  MB_CHK_SET_ERR( m_interface->tag_set_data( tgtSolutionTag, tents, &solTTagVals[0] ),
1631  "Setting local tag data failed" );
1632  }
1633  }
1634 
1635  return moab::MB_SUCCESS;
1636 }

References moab::Remapper::CoveringMesh, MB_CHK_SET_ERR, MB_SUCCESS, moab::Range::size(), and moab::Remapper::TargetMesh.

Referenced by ApplyWeightsWithDualMap(), and main().

◆ ApplyWeights() [2/2]

moab::ErrorCode moab::TempestOnlineMap::ApplyWeights ( std::vector< double > &  srcVals,
std::vector< double > &  tgtVals,
bool  transpose = false 
)
private

Apply the weight matrix onto the source vector provided as input, and return the column vector (solution projection) after the map application Compute: tgtVals = A(S->T) *

Note
Source values, or if (transpose) tgtVals = [A(T->S)]^T *
Source values

Definition at line 205 of file ApplyWeights.cpp.

208 {
209  // Reset the source and target data first
210  m_rowVector.setZero();
211  m_colVector.setZero();
212 
213 #ifdef VERBOSE
214  std::stringstream sstr;
215  static int callId = 0;
216  callId++;
217  sstr << "projection_id_" << callId << "_s_" << size << "_rk_" << rank << ".txt";
218  std::ofstream output_file( sstr.str() );
219 #endif
220  // Perform the actual projection of weights: application of weight matrix onto the source
221  // solution vector
222 
223  if( transpose )
224  {
225  // Permute the source data first
226  for( unsigned i = 0; i < srcVals.size(); ++i )
227  {
228  if( row_dtoc_dofmap[i] >= 0 )
229  m_rowVector( row_dtoc_dofmap[i] ) = srcVals[i]; // permute and set the row (source) vector properly
230  }
231 
232  // Now apply the adjoint operator: m_colVector = m_weightMatrix.adjoint() * m_rowVector;
233  deterministicSparseMatTransposeVecMulClean( m_weightMatrix, m_rowVector, m_colVector );
234  // deterministicSparseMatTransposeVecMul( m_weightMatrix, m_rowVector, m_colVector );
235  // deterministicSparseMatTransposeVecMulNative( m_weightMatrix, m_rowVector, m_colVector );
236 
237  // Permute the resulting target data back
238  for( unsigned i = 0; i < tgtVals.size(); ++i )
239  {
240  if( col_dtoc_dofmap[i] >= 0 )
241  tgtVals[i] = m_colVector( col_dtoc_dofmap[i] ); // permute and set the row (source) vector properly
242  }
243  }
244  else
245  {
246 #ifdef VERBOSE
247  output_file << "ColVector: " << m_colVector.size() << ", SrcVals: " << srcVals.size()
248  << ", Sizes: " << m_nTotDofs_SrcCov << ", " << col_dtoc_dofmap.size() << "\n";
249 #endif
250  for( unsigned i = 0; i < srcVals.size(); ++i )
251  {
252  if( col_dtoc_dofmap[i] >= 0 )
253  m_colVector( col_dtoc_dofmap[i] ) = srcVals[i]; // permute and set the row (source) vector properly
254 #ifdef VERBOSE
255  output_file << i << " " << col_gdofmap[col_dtoc_dofmap[i]] + 1 << " " << srcVals[i] << "\n";
256 #endif
257  }
258 
259  // Now apply the operator: m_rowVector = m_weightMatrix * m_colVector;
260  deterministicSparseMatVecMulClean( m_weightMatrix, m_colVector, m_rowVector );
261  // deterministicSparseMatVecMul( m_weightMatrix, m_colVector, m_rowVector );
262  // deterministicSparseMatVecMulNative( m_weightMatrix, m_colVector, m_rowVector );
263  // deterministicSparseMatVecMulKahan( m_weightMatrix, m_colVector, m_rowVector );
264 
265  // Permute the resulting target data back
266 #ifdef VERBOSE
267  output_file << "RowVector: " << m_rowVector.size() << ", TgtVals:" << tgtVals.size()
268  << ", Sizes: " << m_nTotDofs_Dest << ", " << row_gdofmap.size() << "\n";
269 #endif
270  for( unsigned i = 0; i < tgtVals.size(); ++i )
271  {
272  if( row_dtoc_dofmap[i] >= 0 )
273  {
274  tgtVals[i] = m_rowVector( row_dtoc_dofmap[i] ); // permute and set the row (source) vector properly
275 #ifdef VERBOSE
276  output_file << i << " " << row_gdofmap[row_dtoc_dofmap[i]] + 1 << " " << tgtVals[i] << "\n";
277 #endif
278  }
279  }
280  }
281 
282  // if( caasType != CAAS_NONE )
283  // {
284  // constexpr int nmax_caas_iterations = 5;
285  // double mismatch = 1.0;
286  // int caasIteration = 0;
287  // while( mismatch > 1e-15 &&
288  // caasIteration++ < nmax_caas_iterations ) // iterate until convergence or a maximum of 5 iterations
289  // {
290  // std::pair< double, double > mDefect = this->ApplyCAASLimiting( srcVals, tgtVals, caasType );
291  // if( m_remapper->verbose )
292  // printf( "Rank %d: -- Iteration: %d, Net original mass defect: %3.4e, mass defect post-CAAS: %3.4e\n",
293  // m_remapper->rank, caasIteration, mDefect.first, mDefect.second );
294  // mismatch = mDefect.second;
295  // }
296  // }
297 
298 #ifdef VERBOSE
299  output_file.flush(); // required here
300  output_file.close();
301 #endif
302 
303  // All done with matvec application
304  return moab::MB_SUCCESS;
305 }

References col_dtoc_dofmap, col_gdofmap, deterministicSparseMatTransposeVecMulClean(), deterministicSparseMatVecMulClean(), m_nTotDofs_Dest, m_nTotDofs_SrcCov, MB_SUCCESS, rank, row_dtoc_dofmap, row_gdofmap, and size.

◆ ApplyWeightsWithDualMap()

moab::ErrorCode moab::TempestOnlineMap::ApplyWeightsWithDualMap ( moab::Tag  srcSolutionTag,
moab::Tag  tgtSolutionTag,
TempestOnlineMap loWeightMap,
CAASType  caasType = CAAS_LOCAL 
)

Apply the high-order weight matrix onto the source vector (tag), and then enforce bounds computed from the stencil of a separate low-order weight map using the Clip-And-Assert-Sum (CAAS) algorithm. This implements the dual-map nonlinear remapping pattern used in E3SM coupling. loWeightMap provides the low-order (monotone) map whose per-row stencil defines the min/max bounds. The CAAS filter clips the high-order result to those bounds and redistributes mass proportionally to maintain conservation.

Definition at line 1638 of file TempestOnlineMap.cpp.

1642 {
1643  // Setup entity ranges (same pattern as ApplyWeights(Tag, Tag))
1644  std::vector< double > solSTagVals, solTTagVals;
1645  moab::Range sents, tents;
1646 
1648  {
1650  {
1652  solSTagVals.resize( covSrcEnts.size(), 0.0 );
1653  sents = covSrcEnts;
1654  }
1655  else
1656  {
1658  solSTagVals.resize( covSrcEnts.size() * this->GetSourceNDofsPerElement() *
1659  this->GetSourceNDofsPerElement(),
1660  0.0 );
1661  sents = covSrcEnts;
1662  }
1664  {
1666  solTTagVals.resize( tgtEnts.size(), 0.0 );
1667  tents = tgtEnts;
1668  }
1669  else
1670  {
1672  solTTagVals.resize( tgtEnts.size() * this->GetDestinationNDofsPerElement() *
1673  this->GetDestinationNDofsPerElement(),
1674  0.0 );
1675  tents = tgtEnts;
1676  }
1677  }
1678  else
1679  {
1682  solSTagVals.resize( covSrcEnts.size() * this->GetSourceNDofsPerElement() * this->GetSourceNDofsPerElement(),
1683  0.0 );
1684  solTTagVals.resize(
1685  tgtEnts.size() * this->GetDestinationNDofsPerElement() * this->GetDestinationNDofsPerElement(), 0.0 );
1686  sents = covSrcEnts;
1687  tents = tgtEnts;
1688  }
1689 
1690  // Read source tag data from coverage mesh
1691  MB_CHK_SET_ERR( m_interface->tag_get_data( srcSolutionTag, sents, &solSTagVals[0] ),
1692  "Getting source tag data failed" );
1693 
1694  // Apply high-order projection only (no CAAS — bounds come from the low-order map)
1695  MB_CHK_SET_ERR( this->ApplyWeights( solSTagVals, solTTagVals, false ),
1696  "High-order projection failed" );
1697 
1698  // Write initial projection to target tag
1699  MB_CHK_SET_ERR( m_interface->tag_set_data( tgtSolutionTag, tents, &solTTagVals[0] ),
1700  "Setting target tag data failed" );
1701 
1702  if( caasType == CAAS_NONE || loWeightMap == nullptr ) return moab::MB_SUCCESS;
1703 
1704  // =====================================================================
1705  // Dual-map CAAS (Clip-And-Assured-Sum) — bit-for-bit port of MCT's
1706  // seq_nlmap_avNormArr (driver-mct/main/seq_nlmap_mod.F90).
1707  //
1708  // PURPOSE
1709  // Conservative, bounds-preserving remap of a source field x onto a
1710  // target mesh using TWO weight matrices: a high-order
1711  // non-monotone map (A, = `this`) and a low-order monotone &
1712  // conservative map (Am, = `loWeightMap`). The high-order map gives
1713  // accuracy; the low-order map gives the conservation reference and
1714  // the bounds-preservation safety net. This routine wires them
1715  // together using the Clip-And-Assured-Sum scheme of
1716  // Bradley, Bosler & Guba, "Conservation with bounded variation
1717  // and limiters in semi-Lagrangian transport schemes",
1718  // SIAM J. Sci. Comput. 41(5), 2019, doi:10.1137/18M1165414.
1719  //
1720  // NOTATION (matching the reference)
1721  // x source field values on the coverage mesh (solSTagVals)
1722  // A high-order map (this->m_weightMatrix)
1723  // Am low-order map (loWeightMap)
1724  // y_hi = A * x high-order projection (in solTTagVals)
1725  // y_lo = Am * x low-order projection (mass reference)
1726  // [lo, hi] per-row source-value bounds taken over A's stencil
1727  // gmins/gmaxs unscaled global min/max of the per-row [lo, hi] —
1728  // used as a final safety clip
1729  // norm8wt fractional-coverage weight (one scalar per source cell)
1730  // propagated by the E3SM driver as a side-channel tag;
1731  // when present, all bounds & redistribution arithmetic is
1732  // rescaled to match MCT's lnorm=.true. branch exactly
1733  //
1734  // ALGORITHM (one pass, FP-order-preserved vs MCT)
1735  // 1) y_hi = A * x (done above, in solTTagVals)
1736  // 2) y_lo = Am * x [Step 2]
1737  // 2b) Pull source norm8wt side-channel tag if present [Step 2b]
1738  // 3) Per-row bounds [lo, hi] over A's stencil columns [Step 3]
1739  // Divide source value by srcNorm8wt before tracking
1740  // min/max so bounds are over RECOVERED x, not (frac*x).
1741  // 4) y_lo == 0 mask: where the low-order projection is zero,
1742  // force y_hi = lo = hi = 0 to drop the cell. [Step 4]
1743  // 4b) Snapshot UNSCALED global extrema gmins/gmaxs from the
1744  // masked, but not-yet-norm-scaled, per-row [lo, hi]. [Step 4b]
1745  // 4c) mappedNorm8wt = Am * srcNorm8wt (or Am * 1 if absent). [Step 4c]
1746  // 4d) Scale per-row bounds: lo *= mappedNorm8wt, hi *= ... [Step 4d]
1747  // 5) Per-cell CAAS quantities (clipping defect, room to lower/raise) [Step 5]
1748  // 6) Reproducible global reductions of the per-cell quantities [Step 7]
1749  // 7) dM_total = dM_clip + (M_low - M_hi_unclipped) [Step 8]
1750  // 8) Redistribute the deficit across cells with room. [Step 9]
1751  // 9) Final hard clip to gmins/gmaxs (skipping yLow==0 cells).
1752  //
1753  // REPRODUCIBILITY MODEL
1754  // "BfB with MCT" means: for the same inputs, this routine produces
1755  // the same target values MCT produces, BIT FOR BIT, regardless of
1756  // MPI rank count or mesh decomposition. This requires three things
1757  // that the code below enforces explicitly:
1758  //
1759  // (i) Same area values. MCT uses 'aream' = area_b from the netcdf
1760  // map file. We read the same MOAB 'aream' tag (loaded by
1761  // iMOAB_LoadMapFile). Recomputing spherical-polygon areas via
1762  // lHuiller from mesh geometry is FP-different and is only used
1763  // as a final fallback for online-computed maps with no aream.
1764  //
1765  // (ii) Same FP operation order in the per-cell arithmetic. The
1766  // redistribute step computes `(hi - yc)/cap_g * dM_total`, NOT
1767  // the algebraically-equivalent `(hi - yc) * (dM_total/cap_g)`.
1768  // See Step 9 below for why. The dM_total computation also uses
1769  // MCT's exact two-step form (subtract, then add), preserving
1770  // the catastrophic-cancellation residual MCT carries.
1771  //
1772  // (iii) Order-independent global reductions. We use Worley's
1773  // IntegerReprosum (the MOAB port of shr_reprosum_int), which
1774  // is MCT's default reprosum path. It is decomposition- and
1775  // order-independent by construction (integer-vector MPI sum).
1776  // A Kahan + sort-by-gid summation lambda is also defined
1777  // below as a reference alternative but is not the active
1778  // reducer — using two different algorithms would defeat BfB.
1779  //
1780  // GUARDRAILS
1781  // Bounds extraction (Step 3) hard-aborts the run if any owned
1782  // high-order row references a coverage column not present on this
1783  // rank. Silently dropping such columns would produce
1784  // decomposition-dependent bounds and break BfB. The error message
1785  // tells the caller exactly which row/column/weight failed and
1786  // recommends widening the ghost-layer count (nghlay_cov in the
1787  // E3SM coupler driver). See lines below the bounds loop.
1788  //
1789  // EARLY RETURN
1790  // If caasType == CAAS_NONE or loWeightMap is null, we keep the raw
1791  // high-order projection that was already written to tgtSolutionTag
1792  // above. The dual-map machinery only runs when the caller explicitly
1793  // activates it with a non-null low-order map and a non-CAAS_NONE
1794  // filter type.
1795 
1796  const size_t nTargetDofs = solTTagVals.size();
1797  const size_t nSourceDofs = solSTagVals.size();
1798 
1799  // Map from target tag index to matrix row index. Both A and Am must share
1800  // the same row layout (same target mesh, same partitioning); this is true
1801  // because both maps are loaded onto the same intersection application.
1802  if( row_dtoc_dofmap.size() < nTargetDofs )
1803  {
1804  MB_CHK_SET_ERR( moab::MB_FAILURE, "row_dtoc_dofmap smaller than target tag size" );
1805  }
1806 
1807  // ----- Step 2: low-order projection y_lo = Am * x ---------------------
1808  std::vector< double > yLow( nTargetDofs, 0.0 );
1809  MB_CHK_SET_ERR( loWeightMap->ApplyWeights( solSTagVals, yLow, false ),
1810  "Low-order projection failed" );
1811 
1812  // ----- Step 2b: pull source norm8wt side-channel (if present) ---------
1813  //
1814  // BACKGROUND
1815  // When the E3SM driver coupler asks for a normalized projection
1816  // (lnorm=.true.), it does NOT send raw source values x to the
1817  // remapper. Instead, in seq_map_avNormArr it pre-multiplies each
1818  // source data field by a fractional-coverage weight `frac`, and
1819  // sends the products (frac * x) over to the intersection app
1820  // together with a parallel single-component tag named "norm8wt"
1821  // that carries (frac) on each source coverage cell.
1822  //
1823  // The driver later UN-DOES this pre-norm on the target side by
1824  // dividing each mapped data field by the mapped norm8wt — so the
1825  // final value on the target is (Am*(frac*x)) / (Am*frac). That
1826  // per-cell weighted average is the conservative answer when source
1827  // cells are only partially covered (e.g. land/ocean coastlines).
1828  //
1829  // WHY THE CAAS KERNEL NEEDS TO SEE norm8wt
1830  // To match MCT's seq_nlmap_avNormArr bit-for-bit, two things have
1831  // to happen INSIDE the CAAS kernel — neither can be done by the
1832  // driver as a post-pass:
1833  //
1834  // (a) Per-row bounds [lo, hi] must be the min/max of RECOVERED x
1835  // over the high-order stencil, not the min/max of (frac*x).
1836  // MCT does
1837  // tmp = solSTagVals[srcIdx]
1838  // tmp = tmp / xPrimeAV(natt+1, col) ! divide by frac
1839  // in sMat_avMult_and_calc_bounds before extending bounds, and
1840  // skips columns where frac == 0 (the field can't say anything
1841  // meaningful at a cell with no source coverage). Without this
1842  // divide, bounds would be 0-suppressed in coastal regions and
1843  // the CAAS clip would lose accuracy.
1844  //
1845  // (b) The mapped-norm8wt scale factor used in Step 4d must be the
1846  // LOW-ORDER projection of the ACTUAL source `frac`, not the
1847  // low-order projection of constant-1. MCT computes this in
1848  // the same mct_sMat_avMult call that produces avp_o data — the
1849  // natt+1 column gets sum_l w_lo[j,l] * frac(l), and that's
1850  // what the bounds get scaled by.
1851  //
1852  // FALLBACK
1853  // If no "norm8wt" tag exists on the intersection-side mesh (callers
1854  // that never pre-normed), we set hasNorm8wt=false. In that branch
1855  // srcNorm8wt is treated as constant-1 for both (a) and (b), which is
1856  // mathematically correct: with no pre-norm, frac would have been
1857  // 1.0 everywhere and the divide / scale are no-ops.
1858  //
1859  // SHAPE CONSTRAINT
1860  // norm8wt is single-component (one double per source coverage cell).
1861  // For the FV-FV configuration on the active CAAS path,
1862  // sents.size() == nSourceDofs and the tag values map directly to
1863  // solSTagVals indices. If a future caller wires an SE source layout
1864  // where nSourceDofs > sents.size() (multi-DOF per cell), the
1865  // per-cell norm8wt cannot be unambiguously expanded to per-DOF
1866  // values here — we deliberately fall back to the constant-1 path
1867  // rather than guess an expansion that would silently break BfB.
1868  std::vector< double > srcNorm8wt;
1869  bool hasNorm8wt = false;
1870  {
1871  moab::Tag normTag = nullptr;
1872  moab::ErrorCode rvalN = m_interface->tag_get_handle( "norm8wt", normTag );
1873  if( MB_SUCCESS == rvalN && normTag != nullptr )
1874  {
1875  // Single-component tag (one double per source coverage entity).
1876  // For FV-FV (the only configuration on the active CAAS path)
1877  // sents.size() == nSourceDofs. If the source layout is multi-DOF
1878  // (e.g. SE) the per-cell norm8wt cannot be unambiguously expanded
1879  // to per-DOF values here; bail to the constant-1 fallback rather
1880  // than guess.
1881  srcNorm8wt.resize( sents.size(), 0.0 );
1882  moab::ErrorCode rvalD = m_interface->tag_get_data( normTag, sents, &srcNorm8wt[0] );
1883  if( MB_SUCCESS == rvalD && srcNorm8wt.size() == nSourceDofs )
1884  {
1885  hasNorm8wt = true;
1886  }
1887  else
1888  {
1889  srcNorm8wt.clear();
1890  }
1891  }
1892  }
1893 
1894  // ----- Step 3: per-row bounds from HIGH-ORDER stencil -----------------
1895  // bounds(A, x): for each target row r, [lo, hi] = [min, max] of x over
1896  // A(r,:)'s nonzero columns. The proof in the reference paper requires
1897  // bounds to come from the larger (high-order) stencil so the constraint
1898  // set is provably nonempty.
1899  std::vector< double > lcl_lo( nTargetDofs, 1e308 );
1900  std::vector< double > lcl_hi( nTargetDofs, -1e308 );
1901 
1902  WeightMatrix& hiW = this->m_weightMatrix;
1903  for( size_t i = 0; i < nTargetDofs; i++ )
1904  {
1905  int r = row_dtoc_dofmap[i];
1906  if( r < 0 || r >= hiW.outerSize() ) continue;
1907  for( WeightMatrix::InnerIterator it( hiW, r ); it; ++it )
1908  {
1909  // it.col() is a matrix column index; map it to source vector index
1910  // by inverting col_dtoc_dofmap. For FV-FV with cell-based DOFs
1911  // and one-to-one mapping, this is the identity for owned columns.
1912  int mc = (int)it.col();
1913  // Search col_dtoc_dofmap[k]==mc; for typical FV cases the mapping
1914  // is dense and contiguous, so a linear scan over solSTagVals is
1915  // avoided by precomputing an inverse (below). For correctness we
1916  // fall back to scanning if the inverse is not available.
1917  // Build inverse once outside the loop (see below).
1918  (void)mc;
1919  }
1920  }
1921 
1922  // Precompute matrix-col -> source-vector-index inverse (cached per call;
1923  // O(nSourceDofs) construction). col_dtoc_dofmap has size nSourceDofs and
1924  // maps source-vector-index -> matrix-col.
1925  int maxMatCol = -1;
1926  for( size_t k = 0; k < nSourceDofs && k < col_dtoc_dofmap.size(); k++ )
1927  if( col_dtoc_dofmap[k] > maxMatCol ) maxMatCol = col_dtoc_dofmap[k];
1928  std::vector< int > col_inv( maxMatCol + 1, -1 );
1929  for( size_t k = 0; k < nSourceDofs && k < col_dtoc_dofmap.size(); k++ )
1930  if( col_dtoc_dofmap[k] >= 0 ) col_inv[col_dtoc_dofmap[k]] = (int)k;
1931 
1932  // Track whether any owned row's high-order stencil column failed to
1933  // resolve into the local coverage source vector. If that happens the
1934  // [lcl_lo, lcl_hi] bounds are computed over an INCOMPLETE stencil and
1935  // the CAAS clip + redistribute will produce decomposition-dependent
1936  // values — exactly the symptom seen as 1-2 ULP cross-rank-count drift
1937  // on file-loaded maps. Fail loudly with the offending coordinates so
1938  // the coverage layout (nghlay_cov in the calling code) can be widened.
1939  int bndsLocalErr = 0;
1940  int bndsFirstRowG = -1; // global target row id where the first failure happened
1941  int bndsFirstMc = -1; // matrix col index that failed to resolve
1942  double bndsFirstWgt = 0.0; // the dropped (nonzero) weight value
1943  int bndsFirstKind = 0; // 1 = mc out of maxMatCol; 2 = col_inv -> -1; 3 = srcIdx OOB
1944 
1945  for( size_t i = 0; i < nTargetDofs; i++ )
1946  {
1947  int r = row_dtoc_dofmap[i];
1948  if( r < 0 || r >= hiW.outerSize() ) continue;
1949  for( WeightMatrix::InnerIterator it( hiW, r ); it; ++it )
1950  {
1951  // Skip explicit-zero entries. Eigen's InnerIterator visits any
1952  // stored coefficient regardless of value; TempestRemap offline
1953  // maps routinely emit explicit zeros. MCT's
1954  // sMat_avMult_and_calc_bounds explicitly does
1955  // if (wgt == 0) cycle
1956  // before extending bounds (seq_nlmap_mod.F90:855). We can't use
1957  // an exact-zero compare here (FP-fragile), but 1e-50 is below
1958  // any physically meaningful map weight while still robust to
1959  // sign and denormal noise — entries this small can't shift the
1960  // per-row [lo, hi] enough to cross a clip threshold either.
1961  if( fabs( it.value() ) < 1e-50 ) continue;
1962  const int mc = (int)it.col();
1963 
1964  // Hard checks: a nonzero high-order weight at column mc means
1965  // this owned row genuinely depends on source-coverage column mc.
1966  // If we cannot resolve mc to a local source-vector index, the
1967  // 3-ring coverage on this rank is too narrow for the high-order
1968  // stencil. Either the caller asked for too few ghost layers,
1969  // or the map file references columns not present in any rank's
1970  // coverage (a catastrophic mismatch). Either way, silently
1971  // skipping corrupts the bounds and breaks BFB.
1972  if( mc < 0 || mc > maxMatCol )
1973  {
1974  if( !bndsLocalErr )
1975  {
1976  bndsLocalErr = 1;
1977  bndsFirstRowG = (r >= 0 && r < (int)row_gdofmap.size()) ? (int)row_gdofmap[r] : -1;
1978  bndsFirstMc = mc;
1979  bndsFirstWgt = it.value();
1980  bndsFirstKind = 1;
1981  }
1982  continue;
1983  }
1984  const int srcIdx = col_inv[mc];
1985  if( srcIdx < 0 )
1986  {
1987  if( !bndsLocalErr )
1988  {
1989  bndsLocalErr = 1;
1990  bndsFirstRowG = (r >= 0 && r < (int)row_gdofmap.size()) ? (int)row_gdofmap[r] : -1;
1991  bndsFirstMc = mc;
1992  bndsFirstWgt = it.value();
1993  bndsFirstKind = 2;
1994  }
1995  continue;
1996  }
1997  if( srcIdx >= (int)nSourceDofs )
1998  {
1999  if( !bndsLocalErr )
2000  {
2001  bndsLocalErr = 1;
2002  bndsFirstRowG = (r >= 0 && r < (int)row_gdofmap.size()) ? (int)row_gdofmap[r] : -1;
2003  bndsFirstMc = mc;
2004  bndsFirstWgt = it.value();
2005  bndsFirstKind = 3;
2006  }
2007  continue;
2008  }
2009  // solSTagVals[srcIdx] holds (frac * x) when the driver pre-normed
2010  // (hasNorm8wt true); divide by frac to recover x for bounds, and
2011  // skip the source cell when frac == 0 (matches MCT
2012  // seq_nlmap_mod.F90:857 "if xPrimeAV(natt+1,col) == 0 cycle").
2013  // When hasNorm8wt is false, solSTagVals already holds raw x.
2014  double v = solSTagVals[srcIdx];
2015  if( hasNorm8wt )
2016  {
2017  const double n = srcNorm8wt[srcIdx];
2018  if( fabs(n) < 1E-20 ) continue;
2019  v /= n;
2020  }
2021  if( v < lcl_lo[i] ) lcl_lo[i] = v;
2022  if( v > lcl_hi[i] ) lcl_hi[i] = v;
2023  }
2024  // If row had no nonzero columns in the high-order stencil, set bounds
2025  // to 0 (matching MCT's sMat_avMult_and_calc_bounds: "lop = 0; hip = 0").
2026  // Together with the y_lo == 0 masking step below, this forces the cell
2027  // to 0 — a "rare, local reduction in order to one" per the reference.
2028  if( lcl_lo[i] > lcl_hi[i] )
2029  {
2030  lcl_lo[i] = 0.0;
2031  lcl_hi[i] = 0.0;
2032  }
2033  }
2034 
2035  // Globalize: any rank with bndsLocalErr triggers a collective failure.
2036 #ifdef MOAB_HAVE_MPI
2037  {
2038  MPI_Comm comm = m_pcomm ? m_pcomm->comm() : MPI_COMM_SELF;
2039  int bndsGlobalErr = 0;
2040  MPI_Allreduce( &bndsLocalErr, &bndsGlobalErr, 1, MPI_INT, MPI_MAX, comm );
2041  if( bndsGlobalErr )
2042  {
2043  int myRank = 0;
2044  MPI_Comm_rank( comm, &myRank );
2045  if( bndsLocalErr )
2046  {
2047  static const char* kindStr[4] = { "?", "mc>maxMatCol", "col_inv[mc]==-1", "srcIdx>=nSourceDofs" };
2048  fprintf( stderr,
2049  "FATAL: ApplyWeightsWithDualMap bounds extraction dropped a nonzero "
2050  "high-order stencil column on rank %d.\n"
2051  " global_target_row=%d matrix_col=%d weight=%.17e reason=%s\n"
2052  " This means the source coverage on this rank does NOT contain a "
2053  "column the owned high-order row references — the 3-ring (or whatever) "
2054  "ghost layer setting is too narrow, or the map file was generated against "
2055  "a different mesh. Bounds computed over an incomplete stencil break BFB; "
2056  "aborting rather than silently producing wrong CAAS output.\n",
2057  myRank, bndsFirstRowG, bndsFirstMc, bndsFirstWgt, kindStr[bndsFirstKind] );
2058  fflush( stderr );
2059  }
2060  MPI_Abort( comm, 1 );
2061  }
2062  }
2063 #else
2064  if( bndsLocalErr )
2065  {
2066  static const char* kindStr[4] = { "?", "mc>maxMatCol", "col_inv[mc]==-1", "srcIdx>=nSourceDofs" };
2067  fprintf( stderr,
2068  "FATAL: ApplyWeightsWithDualMap bounds extraction dropped a nonzero "
2069  "high-order stencil column.\n"
2070  " global_target_row=%d matrix_col=%d weight=%.17e reason=%s\n",
2071  bndsFirstRowG, bndsFirstMc, bndsFirstWgt, kindStr[bndsFirstKind] );
2072  fflush( stderr );
2073  return moab::MB_FAILURE;
2074  }
2075 #endif
2076 
2077  // ----- Step 4: mask -- where y_lo == 0, zero out y_hi and bounds ------
2078  // (Per reference: "An exact 0 in the low-order field will mask the
2079  // high-order field unnecessarily, but that's OK: it's a rare, local
2080  // reduction in order to one, not a wrong value.")
2081  for( size_t i = 0; i < nTargetDofs; i++ )
2082  {
2083  if( yLow[i] == 0.0 )
2084  {
2085  solTTagVals[i] = 0.0;
2086  lcl_lo[i] = 0.0;
2087  lcl_hi[i] = 0.0;
2088  }
2089  }
2090 
2091  // ----- Step 4b: compute UNSCALED global extrema for the final safety
2092  // clip (Item 4). MCT's seq_nlmap_avNormArr clips the redistributed result
2093  // against gmins/gmaxs = global min/max of the per-row (post-mask) bounds,
2094  // not against the per-row bounds themselves. Snapshot here, BEFORE the
2095  // bounds get scaled by the mapped norm in Step 4d below.
2096  double g_lo = 1e308, g_hi = -1e308;
2097  for( size_t i = 0; i < nTargetDofs; i++ )
2098  {
2099  int r = row_dtoc_dofmap[i];
2100  if( r < 0 || r >= (int)row_gdofmap.size() ) continue; // not owned
2101  if( lcl_lo[i] < g_lo ) g_lo = lcl_lo[i];
2102  if( lcl_hi[i] > g_hi ) g_hi = lcl_hi[i];
2103  }
2104 #ifdef MOAB_HAVE_MPI
2105  {
2106  MPI_Comm comm = m_pcomm ? m_pcomm->comm() : MPI_COMM_SELF;
2107  double tmp_min = g_lo, tmp_max = g_hi;
2108  MPI_Allreduce( &tmp_min, &g_lo, 1, MPI_DOUBLE, MPI_MIN, comm );
2109  MPI_Allreduce( &tmp_max, &g_hi, 1, MPI_DOUBLE, MPI_MAX, comm );
2110  }
2111 #endif
2112 
2113  // ----- Step 4c: compute mapped norm8wt = low-order map applied to a
2114  // source-norm8wt vector. For target row i this equals
2115  // sum_l w_lo[i,l] * srcNorm8wt(l)
2116  // — equivalently, the value MCT carries in the natt+1 column of avp_o
2117  // after mct_sMat_avMult is applied to avp_i (whose norm8wt slot holds
2118  // frac post-pre-norm). When no "norm8wt" tag is available on the intx
2119  // side, fall back to applying the low-order map to a constant-1 vector
2120  // (equivalent to srcNorm8wt(l) == 1 everywhere — consistent with the
2121  // bounds-extraction fallback above).
2122  std::vector< double > mappedNorm8wt( nTargetDofs, 0.0 );
2123  if( hasNorm8wt )
2124  {
2125  MB_CHK_SET_ERR( loWeightMap->ApplyWeights( srcNorm8wt, mappedNorm8wt, false ),
2126  "Mapped-norm8wt computation (low-order on source norm8wt) failed" );
2127  }
2128  else
2129  {
2130  std::vector< double > srcOnes( nSourceDofs, 1.0 );
2131  MB_CHK_SET_ERR( loWeightMap->ApplyWeights( srcOnes, mappedNorm8wt, false ),
2132  "Mapped-norm8wt computation (low-order on ones) failed" );
2133  }
2134 
2135  // ----- Step 4d: scale per-row bounds by mapped norm8wt (Item 2).
2136  // MCT does this inside the CAAS loop:
2137  // if (lnorm) then
2138  // lo = lo*avp_o%rAttr(natt+1,j)
2139  // hi = hi*avp_o%rAttr(natt+1,j)
2140  // end if
2141  // Doing it once here propagates correctly into Step 5 (clipping) and
2142  // Step 9 (redistribution) which both use lcl_lo/lcl_hi. Note: g_lo/g_hi
2143  // were already snapshotted above and remain UNSCALED (matching MCT's
2144  // gmins/gmaxs which are the global min/max of the unscaled per-row bounds).
2145  for( size_t i = 0; i < nTargetDofs; i++ )
2146  {
2147  const double w = mappedNorm8wt[i];
2148  lcl_lo[i] *= w;
2149  lcl_hi[i] *= w;
2150  }
2151 
2152  // ----- Get target areas (per matrix-row) ------------------------------
2153  // For BFB with MCT we MUST use the same area values MCT does. MCT uses
2154  // 'aream' (= area_b from the netcdf map file, loaded once when the map
2155  // is read). iMOAB_LoadMapFile populates the 'aream' tag on the target
2156  // mesh from area_b when arearead != 0 (e.g. arearead=3 for F-maps).
2157  //
2158  // The CAAS path MUST NOT recompute spherical-polygon areas from mesh
2159  // geometry via lHuiller (or any other re-derivation): doing so differs
2160  // from area_b at FP precision and silently breaks BfB with MCT. If the
2161  // caller has not loaded an area-bearing map and there are no online
2162  // areas (m_dTargetAreas) either, fail the run loudly so the caller can
2163  // fix their map-load configuration instead of getting silent non-BfB
2164  // results.
2165  std::vector< double > tgtAreas( nTargetDofs, 0.0 );
2166  {
2167  std::vector< moab::EntityHandle > tentVec;
2168  tentVec.reserve( tents.size() );
2169  for( moab::Range::iterator it = tents.begin(); it != tents.end(); ++it )
2170  tentVec.push_back( *it );
2171 
2172  bool got_areas = false;
2173 
2174  // Preferred: pull the 'aream' tag from the target MOAB mesh — this
2175  // is the area_b value loaded by iMOAB_LoadMapFile and is byte-identical
2176  // to the 'aream' field MCT uses in seq_nlmap_avNormArr.
2177  moab::Tag aream_tag = nullptr;
2178  moab::ErrorCode rval = m_interface->tag_get_handle( "aream", aream_tag );
2179  if( MB_SUCCESS == rval && aream_tag != nullptr && !tentVec.empty() )
2180  {
2181  const size_t nents = std::min< size_t >( tentVec.size(), nTargetDofs );
2182  std::vector< double > aream_vals( nents, 0.0 );
2183  rval = m_interface->tag_get_data( aream_tag, &tentVec[0], (int)nents, &aream_vals[0] );
2184  if( MB_SUCCESS == rval )
2185  {
2186  for( size_t i = 0; i < nents; i++ ) tgtAreas[i] = aream_vals[i];
2187  got_areas = true;
2188  }
2189  }
2190 
2191  // Fallback: areas were computed online and live in OfflineMap's
2192  // m_dTargetAreas (indexed by matrix row). This is BFB with MCT only
2193  // when the same online-area code path is used on both couplers; it
2194  // is acceptable for runs that build the map online.
2195  if( !got_areas )
2196  {
2197  const DataArray1D< double >& dTargetAreas = this->GetTargetAreas();
2198  const size_t nRows = dTargetAreas.GetRows();
2199  if( nRows >= nTargetDofs )
2200  {
2201  for( size_t i = 0; i < nTargetDofs; i++ )
2202  {
2203  int r = row_dtoc_dofmap[i];
2204  if( r >= 0 && (size_t)r < nRows )
2205  tgtAreas[i] = dTargetAreas[r];
2206  }
2207  got_areas = true;
2208  }
2209  }
2210 
2211  // No fallback to lHuiller. Recomputing areas from mesh geometry is
2212  // not BFB with MCT and there is no safe silent default — abort.
2213  if( !got_areas )
2214  {
2215  MB_SET_ERR( moab::MB_FAILURE,
2216  "ApplyWeightsWithDualMap: no target-cell areas available. "
2217  "Neither the 'aream' tag (from iMOAB_LoadMapFile with "
2218  "arearead != 0) nor OfflineMap::GetTargetAreas() (from an "
2219  "online map build) provided areas. Recomputing areas from "
2220  "mesh geometry is not bit-for-bit with MCT and is no longer "
2221  "permitted in the CAAS path. Re-load the map file with an "
2222  "area-bearing arearead setting (e.g. arearead=3 for F-maps), "
2223  "or build the online map so target areas are populated." );
2224  }
2225  }
2226 
2227  // ----- Step 5: build per-cell CAAS weights ----------------------------
2228  // For BFB summation, we accumulate per-row (gid, value) pairs and reduce
2229  // them deterministically.
2230  std::vector< int > rowGids( nTargetDofs, -1 );
2231  std::vector< double > massLowPerRow( nTargetDofs, 0.0 );
2232  std::vector< double > massHiUnclippedPerRow( nTargetDofs, 0.0 ); // y_hi BEFORE clipping
2233  std::vector< double > clipDefectPerRow( nTargetDofs, 0.0 );
2234  std::vector< double > capLowPerRow( nTargetDofs, 0.0 );
2235  std::vector< double > capHighPerRow( nTargetDofs, 0.0 );
2236 
2237  for( size_t i = 0; i < nTargetDofs; i++ )
2238  {
2239  int r = row_dtoc_dofmap[i];
2240  if( r < 0 || r >= (int)row_gdofmap.size() )
2241  rowGids[i] = -1; // not owned by this rank
2242  else
2243  rowGids[i] = (int)row_gdofmap[r];
2244 
2245  const double area = tgtAreas[i];
2246  const double y = solTTagVals[i]; // y_hi (pre-clip)
2247  const double lo = lcl_lo[i];
2248  const double hi = lcl_hi[i];
2249  double yc = y; // clipped value
2250  double dm = 0.0;
2251  if( y < lo )
2252  {
2253  yc = lo;
2254  dm = ( y - lo ) * area; // negative: cell exceeded below
2255  }
2256  else if( y > hi )
2257  {
2258  yc = hi;
2259  dm = ( y - hi ) * area; // positive: cell exceeded above
2260  }
2261  clipDefectPerRow[i] = dm;
2262  capLowPerRow[i] = ( yc - lo ) * area; // room to subtract
2263  capHighPerRow[i] = ( hi - yc ) * area; // room to add
2264  massLowPerRow[i] = yLow[i] * area;
2265  // Per-row mass of the UNCLIPPED high-order projection. MCT reduces
2266  // exactly this quantity to obtain glbl_masses(natt+k) (M_hi_unclipped),
2267  // and then computes dM_total = dM_clip + (M_low - M_hi_unclipped) in
2268  // that 2-step order. We store the unclipped y here (rather than the
2269  // clipped yc as before) so MOAB's reprosum byte-matches MCT's, which
2270  // in turn lets the MCT-matching dM_total formula below produce the
2271  // same last bits as MCT.
2272  massHiUnclippedPerRow[i] = y * area;
2273  // Update solTTagVals to the clipped value for the next stage
2274  solTTagVals[i] = yc;
2275  }
2276 
2277 
2278  // ----- Step 6: BFB-deterministic global reductions --------------------
2279 
2280  // Reproducible global reductions via Worley's integer-vector algorithm
2281  // (moab::IntegerReprosum) — bit-identical to MCT's shr_reprosum_int
2282  // regardless of MPI rank count, mesh decomposition, or local iteration
2283  // order. This is MCT's default reprosum path (the namelist default
2284  // repro_sum_use_ddpdd=.false. on the MCT side). The integer-vector
2285  // algorithm is order-independent by construction (MPI_Allreduce with
2286  // MPI_SUM on int64), which eliminates the cross-rank-count ULP drift
2287  // that an order-sensitive reducer (e.g. Kahan or DDPDD) would otherwise
2288  // leak into the CAAS bounds and mass totals.
2289 #ifdef MOAB_HAVE_MPI
2290  MPI_Comm reduce_comm = m_pcomm ? m_pcomm->comm() : MPI_COMM_SELF;
2291 #else
2292  int reduce_comm = 0; // serial build: comm unused but kept for API symmetry
2293 #endif
2294 #ifdef MOAB_HAVE_MPI
2295  moab::IntegerReprosum repro( reduce_comm );
2296 #else
2297  moab::IntegerReprosum repro;
2298 #endif
2299  // Build the ownership mask once (rowGids[i] >= 0 ↔ owned).
2300  const std::vector< int >& reduce_mask = rowGids;
2301  // Batched reduction: one MPI_Allreduce for the per-field metadata
2302  // (gmax_exp / gmin_exp / max_nsummands across the 5 fields) and one
2303  // MPI_Allreduce for the concatenated integer-vector encoding of all 5
2304  // fields. Bit-for-bit identical to calling sum_masked() five times
2305  // separately (each field still uses its own per-field metadata and
2306  // decode pass), but goes from 10 collective calls to 2.
2307  const std::vector< std::vector< double > > caasFields = {
2308  massLowPerRow, massHiUnclippedPerRow, clipDefectPerRow,
2309  capLowPerRow, capHighPerRow };
2310  std::vector< double > caasGsums;
2311  repro.sum_masked_batch( caasFields, reduce_mask, caasGsums );
2312  const double M_low = caasGsums[0];
2313  const double M_hi_unclipped = caasGsums[1];
2314  const double dM_clip = caasGsums[2];
2315  const double cap_low_g = caasGsums[3];
2316  const double cap_high_g = caasGsums[4];
2317 
2318  // ----- Step 8: total mass deficit between low-order and clipped high-order
2319  // The redistribution must drive the (clipped) high-order solution back to
2320  // the low-order mass. MCT's seq_nlmap_avNormArr (line 616) computes this
2321  // in EXACTLY the following 2-step form, and floating-point rounding makes
2322  // it FP-different from the algebraically-equivalent (M_low - M_hi_clip):
2323  //
2324  // ! MCT (Fortran array assignment, evaluated element-wise)
2325  // gwts(k) = gwts(k) ! gwts(k) holds dM_clip after reprosum
2326  // + (glbl_masses(k) ! M_low
2327  // - glbl_masses(natt+k)) ! M_hi_unclipped
2328  //
2329  // Reproducing MCT's exact bit pattern requires:
2330  // (a) reducing the UNCLIPPED per-cell high-order mass directly via
2331  // reprosum, NOT deriving it as M_hi_clip + dM_clip — that derivation
2332  // drops 1-2 ULP because reprosum is exact only on its inputs.
2333  // => see massHiUnclippedPerRow above.
2334  // (b) computing dM_total in MCT's order: subtract first, then add.
2335  //
2336  // Sign: dM_total > 0 means low-order carries more mass than the clipped
2337  // high-order, so we need to ADD mass; dM_total < 0 means we need to REMOVE.
2338  //
2339  const double diff = M_low - M_hi_unclipped; // step 1: subtraction
2340  const double dM_total = dM_clip + diff; // step 2: addition (MCT order)
2341 
2342  // ----- Step 9: redistribute -------------------------------------------
2343  // For BfB with MCT seq_nlmap_avNormArr we MUST match its FP operation
2344  // order exactly. MCT does, per cell:
2345  // y = max(lo, min(hi, nl_avp_o(k,j))) ! re-clip
2346  // nl_avp_o(k,j) = y + ((hi - y)/tmp)*gwts(k) ! line 648 / 662
2347  // i.e. divide-then-multiply, with the loop-invariant denominator
2348  // (cap_high_g or cap_low_g) and numerator (dM_total) NOT precomputed
2349  // into a single `scale = dM_total/cap_g`. Doing so introduces ULP-level
2350  // per-cell differences (a/b*c reorders to (c/b)*a). Similarly the
2351  // earlier MOAB pattern computed `room = (hi-yc)*area` and then
2352  // `room/area`, which doesn't algebraically cancel in FP and added two
2353  // extra roundings per cell. The straightforward `(hi - yc)/cap_g *
2354  // dM_total` form below matches MCT bit-for-bit.
2355  if( dM_total > 0.0 && cap_high_g > 0.0 )
2356  {
2357  for( size_t i = 0; i < nTargetDofs; i++ )
2358  {
2359  const double area = tgtAreas[i];
2360  const double yc = solTTagVals[i];
2361  if( area > 0.0 )
2362  solTTagVals[i] = yc + ( ( lcl_hi[i] - yc ) / cap_high_g ) * dM_total;
2363  }
2364  }
2365  else if( dM_total < 0.0 && cap_low_g > 0.0 )
2366  {
2367  for( size_t i = 0; i < nTargetDofs; i++ )
2368  {
2369  const double area = tgtAreas[i];
2370  const double yc = solTTagVals[i];
2371  if( area > 0.0 )
2372  solTTagVals[i] = yc + ( ( yc - lcl_lo[i] ) / cap_low_g ) * dM_total;
2373  }
2374  }
2375 
2376  // Final hard clip for floating-point safety, against UNSCALED global
2377  // extrema (Item 4). MCT's seq_nlmap_avNormArr does:
2378  // if (avp_o(k,j) == 0) cycle ! 0-mask skip
2379  // nl_avp_o%rAttr(k,j) = max(gmins(k), min(gmaxs(k), nl_avp_o%rAttr(k,j)))
2380  // Per-row bounds (lcl_lo/lcl_hi) are now SCALED by mapped_norm8wt and so
2381  // would be a tighter clip than MCT applies; using global g_lo/g_hi keeps
2382  // the safety net loose, as the reference algorithm intends. The 0-mask
2383  // skip is critical: without it, target cells that were zeroed in Step 4
2384  // (yLow == 0) get bumped from 0 up to g_lo when g_lo > 0 (e.g.
2385  // positive-only fields like temperature/pressure), and the post-norm
2386  // divide in the driver then amplifies that wrong value by 1/wghts at
2387  // coastal coverage cells where wghts is tiny but nonzero.
2388  for( size_t i = 0; i < nTargetDofs; i++ )
2389  {
2390  if( fabs(yLow[i]) < 1E-40 ) continue;
2391  if( solTTagVals[i] < g_lo ) solTTagVals[i] = g_lo;
2392  if( solTTagVals[i] > g_hi ) solTTagVals[i] = g_hi;
2393  }
2394 
2395  // Store result back to the target tag
2396  MB_CHK_SET_ERR( m_interface->tag_set_data( tgtSolutionTag, tents, &solTTagVals[0] ),
2397  "Setting target tag data failed" );
2398 
2399  return moab::MB_SUCCESS;
2400 }

References ApplyWeights(), moab::Range::begin(), moab::Remapper::CoveringMesh, moab::E, moab::Range::end(), ErrorCode, MB_CHK_SET_ERR, MB_SET_ERR, MB_SUCCESS, moab::Range::size(), moab::IntegerReprosum::sum_masked_batch(), and moab::Remapper::TargetMesh.

◆ CAASLimiter()

void moab::TempestOnlineMap::CAASLimiter ( std::vector< double > &  dataCorrectedField,
std::vector< double > &  dataLowerBound,
std::vector< double > &  dataUpperBound,
double &  dMass 
)
private

Definition at line 546 of file TempestLinearRemap.cpp.

550 {
551  const size_t nrows = dataCorrectedField.size();
552  double dMassL = 0.0;
553  double dMassU = 0.0;
554  std::vector< double > dataCorrection( nrows );
555  const DataArray1D< double >& dTargetAreas = this->m_remapper->m_target->vecFaceArea;
556  double dMassDiff = dMass;
557  double dLMinusU = fabs( dataUpperBound[0] - dataLowerBound[0] );
558  double dMassCorrectU = 0.0;
559  double dMassCorrectL = 0.0;
560  for( size_t i = 0; i < nrows; i++ )
561  {
562  dataCorrection[i] = fmax( dataLowerBound[i], fmin( dataUpperBound[i], 0.0 ) );
563  dMassL += dTargetAreas[i] * dataLowerBound[i];
564  dMassU += dTargetAreas[i] * dataUpperBound[i];
565  dMassDiff -= dTargetAreas[i] * dataCorrection[i];
566  dLMinusU = fmax( dLMinusU, fabs( dataUpperBound[i] - dataLowerBound[i] ) );
567  dMassCorrectL += dTargetAreas[i] * ( dataCorrection[i] - dataLowerBound[i] );
568  dMassCorrectU += dTargetAreas[i] * ( dataUpperBound[i] - dataCorrection[i] );
569  }
570 
571 #ifdef MOAB_HAVE_MPI
572  std::vector< double > localDefects( 5, 0.0 ), globalDefects( 5, 0.0 );
573  localDefects[0] = dMassL;
574  localDefects[1] = dMassU;
575  localDefects[2] = dMassDiff;
576  localDefects[3] = dMassCorrectL;
577  localDefects[4] = dMassCorrectU;
578 
579  MPI_Allreduce( localDefects.data(), globalDefects.data(), 5, MPI_DOUBLE, MPI_SUM, m_pcomm->comm() );
580 
581  dMassL = globalDefects[0];
582  dMassU = globalDefects[1];
583  dMassDiff = globalDefects[2];
584  dMassCorrectL = globalDefects[3];
585  dMassCorrectU = globalDefects[4];
586 #endif
587 
588  //If the upper and lower bounds are too close together, just clip
589  if( fabs( dMassDiff ) < 1e-15 || fabs( dLMinusU ) < 1e-15 )
590  {
591  for( size_t i = 0; i < nrows; i++ )
592  dataCorrectedField[i] += dataCorrection[i];
593  return;
594  }
595  else
596  {
597  if( dMassL > dMassDiff )
598  {
599  Announce( "%d: Lower bound mass exceeds target mass by %1.15e: CAAS will need another iteration", rank,
600  dMassL - dMassDiff );
601  dMassDiff = dMassL;
602  dMass -= dMassL;
603  }
604  else if( dMassU < dMassDiff )
605  {
606  Announce( "%d: Target mass exceeds upper bound mass by %1.15e: CAAS will need another iteration", rank,
607  dMassDiff - dMassU );
608  dMassDiff = dMassU;
609  dMass -= dMassU;
610  }
611 
612  // TODO: optimize away dataMassVec by a simple transient double within the loop
613  DataArray1D< double > dataMassVec( nrows ); //vector of mass redistribution
614  if( dMassDiff > 0.0 )
615  {
616  for( size_t i = 0; i < nrows; i++ )
617  {
618  dataMassVec[i] = ( dataUpperBound[i] - dataCorrection[i] ) / dMassCorrectU;
619  dataCorrection[i] += dMassDiff * dataMassVec[i];
620  }
621  }
622  else
623  {
624  for( size_t i = 0; i < nrows; i++ )
625  {
626  dataMassVec[i] = ( dataCorrection[i] - dataLowerBound[i] ) / dMassCorrectL;
627  dataCorrection[i] += dMassDiff * dataMassVec[i];
628  }
629  }
630 
631  for( size_t i = 0; i < nrows; i++ )
632  dataCorrectedField[i] += dataCorrection[i];
633  }
634 
635  return;
636 }

◆ ComputeAdjacencyRelations()

void moab::TempestOnlineMap::ComputeAdjacencyRelations ( std::vector< std::unordered_set< int > > &  vecAdjFaces,
int  nrings,
const Range entities,
bool  useMOABAdjacencies = true,
Mesh *  trMesh = nullptr 
)
Parameters
vecAdjFaces
nrings
entities
useMOABAdjacencies
trMesh
Returns

Vector storing adjacent Faces.

Definition at line 1482 of file TempestOnlineMap.cpp.

1487 {
1488  assert( nrings > 0 );
1489  assert( useMOABAdjacencies || trMesh != nullptr );
1490 
1491  const size_t nrows = vecAdjFaces.size();
1493  for( size_t index = 0; index < nrows; index++ )
1494  {
1495  vecAdjFaces[index].insert( index ); // add self target face first
1496  {
1497  // Compute the adjacent faces to the target face
1498  if( useMOABAdjacencies )
1499  {
1500  moab::Range ents;
1501  // ents.insert( entities.index( entities[index] ) );
1502  ents.insert( entities[index] );
1503  moab::Range adjEnts;
1504  moab::ErrorCode rval = mtu.get_bridge_adjacencies( ents, 0, 2, adjEnts, nrings );MB_CHK_SET_ERR_CONT( rval, "Failed to get adjacent faces" );
1505  for( moab::Range::iterator it = adjEnts.begin(); it != adjEnts.end(); ++it )
1506  {
1507  // int adjIndex = m_interface->id_from_handle(*it)-1;
1508  int adjIndex = entities.index( *it );
1509  // printf("rank: %d, Element %lu, entity: %lu, adjIndex %d\n", rank, index, *it, adjIndex);
1510  if( adjIndex >= 0 ) vecAdjFaces[index].insert( adjIndex );
1511  }
1512  }
1513  else
1514  {
1515  /// Vector storing adjacent Faces.
1516  typedef std::pair< int, int > FaceDistancePair;
1517  typedef std::vector< FaceDistancePair > AdjacentFaceVector;
1518  AdjacentFaceVector adjFaces;
1519  Face& face = trMesh->faces[index];
1520  GetAdjacentFaceVectorByEdge( *trMesh, index, nrings * face.edges.size(), adjFaces );
1521 
1522  // Add the adjacent faces to the target face list
1523  for( auto adjFace : adjFaces )
1524  if( adjFace.first >= 0 )
1525  vecAdjFaces[index].insert( adjFace.first ); // map target face to source face
1526  }
1527  }
1528  }
1529 }

References moab::Range::begin(), moab::Range::end(), ErrorCode, moab::MeshTopoUtil::get_bridge_adjacencies(), moab::index, moab::Range::index(), moab::Range::insert(), and MB_CHK_SET_ERR_CONT.

◆ ComputeMetrics()

moab::ErrorCode moab::TempestOnlineMap::ComputeMetrics ( Remapper::IntersectionContext  ctx,
moab::Tag exactTag,
moab::Tag approxTag,
std::map< std::string, double > &  metrics,
bool  verbose = true 
)

Compute the error between a sampled (exact) solution and a projected solution in various error norms.

Definition at line 2768 of file TempestOnlineMap.cpp.

2773 {
2774  const bool outputEnabled = ( is_root );
2775  int discOrder;
2776  // DiscretizationType discMethod;
2777  // moab::EntityHandle meshset;
2778  moab::Range entities;
2779  // Mesh* trmesh;
2780  switch( ctx )
2781  {
2782  case Remapper::SourceMesh:
2783  // meshset = m_remapper->m_covering_source_set;
2784  // trmesh = m_remapper->m_covering_source;
2787  discOrder = m_nDofsPEl_Src;
2788  // discMethod = m_eInputType;
2789  break;
2790 
2791  case Remapper::TargetMesh:
2792  // meshset = m_remapper->m_target_set;
2793  // trmesh = m_remapper->m_target;
2794  entities =
2796  discOrder = m_nDofsPEl_Dest;
2797  // discMethod = m_eOutputType;
2798  break;
2799 
2800  default:
2801  if( outputEnabled )
2802  std::cout << "Invalid context specified for defining an analytical solution tag" << std::endl;
2803  return moab::MB_FAILURE;
2804  }
2805 
2806  // Let us create teh solution tag with appropriate information for name, discretization order
2807  // (DoF space)
2808  std::string exactTagName, projTagName;
2809  const int ntotsize = entities.size() * discOrder * discOrder;
2810  std::vector< double > exactSolution( ntotsize, 0.0 ), projSolution( ntotsize, 0.0 );
2811  MB_CHK_ERR( m_interface->tag_get_name( exactTag, exactTagName ) );
2812  MB_CHK_ERR( m_interface->tag_get_data( exactTag, entities, &exactSolution[0] ) );
2813  MB_CHK_ERR( m_interface->tag_get_name( approxTag, projTagName ) );
2814  MB_CHK_ERR( m_interface->tag_get_data( approxTag, entities, &projSolution[0] ) );
2815 
2816  const auto& ovents = m_remapper->m_overlap_entities;
2817 
2818  std::vector< double > errnorms( 4, 0.0 ), globerrnorms( 4, 0.0 ); // L1Err, L2Err, LinfErr
2819  double sumarea = 0.0;
2820  for( size_t i = 0; i < ovents.size(); ++i )
2821  {
2822  const int srcidx = m_remapper->m_overlap->vecSourceFaceIx[i];
2823  if( srcidx < 0 ) continue; // Skip non-overlapping entities
2824  const int tgtidx = m_remapper->m_overlap->vecTargetFaceIx[i];
2825  if( tgtidx < 0 ) continue; // skip ghost target faces
2826  const double ovarea = m_remapper->m_overlap->vecFaceArea[i];
2827  const double error = fabs( exactSolution[tgtidx] - projSolution[tgtidx] );
2828  errnorms[0] += ovarea * error;
2829  errnorms[1] += ovarea * error * error;
2830  errnorms[3] = ( error > errnorms[3] ? error : errnorms[3] );
2831  sumarea += ovarea;
2832  }
2833  errnorms[2] = sumarea;
2834 #ifdef MOAB_HAVE_MPI
2835  if( m_pcomm )
2836  {
2837  MPI_Reduce( &errnorms[0], &globerrnorms[0], 3, MPI_DOUBLE, MPI_SUM, 0, m_pcomm->comm() );
2838  MPI_Reduce( &errnorms[3], &globerrnorms[3], 1, MPI_DOUBLE, MPI_MAX, 0, m_pcomm->comm() );
2839  }
2840 #else
2841  for( int i = 0; i < 4; ++i )
2842  globerrnorms[i] = errnorms[i];
2843 #endif
2844 
2845  globerrnorms[0] = ( globerrnorms[0] / globerrnorms[2] );
2846  globerrnorms[1] = std::sqrt( globerrnorms[1] / globerrnorms[2] );
2847 
2848  metrics.clear();
2849  metrics["L1Error"] = globerrnorms[0];
2850  metrics["L2Error"] = globerrnorms[1];
2851  metrics["LinfError"] = globerrnorms[3];
2852 
2853  if( verbose && is_root )
2854  {
2855  std::cout << "Error metrics when comparing " << projTagName << " against " << exactTagName << std::endl;
2856  std::cout << "\t Total Intersection area = " << globerrnorms[2] << std::endl;
2857  std::cout << "\t L_1 error = " << globerrnorms[0] << std::endl;
2858  std::cout << "\t L_2 error = " << globerrnorms[1] << std::endl;
2859  std::cout << "\t L_inf error = " << globerrnorms[3] << std::endl;
2860  }
2861 
2862  return moab::MB_SUCCESS;
2863 }

References moab::error(), MB_CHK_ERR, MB_SUCCESS, moab::Range::size(), moab::Remapper::SourceMesh, moab::Remapper::TargetMesh, and verbose.

Referenced by main().

◆ CountAbsentColumns()

int moab::TempestOnlineMap::CountAbsentColumns ( int &  first_absent_gid) const

Count weight-matrix columns that the migrated coverage did not supply (i.e. no covering cell maps to them). For offline file maps this equals the set of source DoFs the map references but that are absent from the source mesh. Returns the count and, in first_absent_gid, one example 1-based source GID (or -1 if none).

Definition at line 475 of file TempestOnlineMap.cpp.

476 {
477  first_absent_gid = -1;
478  const int ncols = m_nTotDofs_SrcCov;
479  std::vector< bool > delivered;
480  compute_delivered_columns( ncols, col_dtoc_dofmap, delivered );
481  int cnt = 0;
482  for( int mc = 0; mc < ncols; mc++ )
483  {
484  if( !delivered[mc] )
485  {
486  cnt++;
487  if( first_absent_gid < 0 && mc < (int)col_gdofmap.size() )
488  first_absent_gid = (int)col_gdofmap[mc] + 1; // col_gdofmap is 0-based
489  }
490  }
491  return cnt;
492 }

References compute_delivered_columns().

◆ DefineAnalyticalSolution()

moab::ErrorCode moab::TempestOnlineMap::DefineAnalyticalSolution ( moab::Tag exactSolnTag,
const std::string &  solnName,
Remapper::IntersectionContext  ctx,
sample_function  testFunction,
moab::Tag clonedSolnTag = NULL,
std::string  cloneSolnName = "" 
)

Define an analytical solution over the given (source or target) mesh, as specificed in the context. This routine will define a tag that is compatible with the specified discretization method type and order and sample the solution exactly using the analytical function provided by the user.

Definition at line 2402 of file TempestOnlineMap.cpp.

2408 {
2409  const bool outputEnabled = ( is_root );
2410  int discOrder;
2411  DiscretizationType discMethod;
2412  // moab::EntityHandle meshset;
2413  moab::Range entities;
2414  Mesh* trmesh;
2415  switch( ctx )
2416  {
2417  case Remapper::SourceMesh:
2418  // meshset = m_remapper->m_covering_source_set;
2419  trmesh = m_remapper->m_covering_source;
2422  discOrder = m_nDofsPEl_Src;
2423  discMethod = m_eInputType;
2424  break;
2425 
2426  case Remapper::TargetMesh:
2427  // meshset = m_remapper->m_target_set;
2428  trmesh = m_remapper->m_target;
2429  entities =
2431  discOrder = m_nDofsPEl_Dest;
2432  discMethod = m_eOutputType;
2433  break;
2434 
2435  default:
2436  if( outputEnabled )
2437  std::cout << "Invalid context specified for defining an analytical solution tag" << std::endl;
2438  return moab::MB_FAILURE;
2439  }
2440 
2441  // Let us create teh solution tag with appropriate information for name, discretization order
2442  // (DoF space)
2443  MB_CHK_ERR( m_interface->tag_get_handle( solnName.c_str(), discOrder * discOrder, MB_TYPE_DOUBLE, solnTag,
2444  MB_TAG_DENSE | MB_TAG_CREAT ) );
2445  if( clonedSolnTag != nullptr )
2446  {
2447  if( cloneSolnName.size() == 0 )
2448  {
2449  cloneSolnName = solnName + std::string( "Cloned" );
2450  }
2451  MB_CHK_ERR( m_interface->tag_get_handle( cloneSolnName.c_str(), discOrder * discOrder, MB_TYPE_DOUBLE,
2452  *clonedSolnTag, MB_TAG_DENSE | MB_TAG_CREAT ) );
2453  }
2454 
2455  // Triangular quadrature rule
2456  const int TriQuadratureOrder = 10;
2457 
2458  if( outputEnabled ) std::cout << "Using triangular quadrature of order " << TriQuadratureOrder << std::endl;
2459 
2460  TriangularQuadratureRule triquadrule( TriQuadratureOrder );
2461 
2462  const int TriQuadraturePoints = triquadrule.GetPoints();
2463 
2464  const DataArray2D< double >& TriQuadratureG = triquadrule.GetG();
2465  const DataArray1D< double >& TriQuadratureW = triquadrule.GetW();
2466 
2467  // Output data
2468  DataArray1D< double > dVar;
2469  DataArray1D< double > dVarMB; // re-arranged local MOAB vector
2470 
2471  // Nodal geometric area
2472  DataArray1D< double > dNodeArea;
2473 
2474  // Calculate element areas
2475  // trmesh->CalculateFaceAreas(fContainsConcaveFaces);
2476 
2477  if( discMethod == DiscretizationType_CGLL || discMethod == DiscretizationType_DGLL )
2478  {
2479  /* Get the spectral points and sample the functionals accordingly */
2480  const bool fGLL = true;
2481  const bool fGLLIntegrate = false;
2482 
2483  // Generate grid metadata
2484  DataArray3D< int > dataGLLNodes;
2485  DataArray3D< double > dataGLLJacobian;
2486 
2487  GenerateMetaData( *trmesh, discOrder, false, dataGLLNodes, dataGLLJacobian );
2488 
2489  // Number of elements
2490  int nElements = trmesh->faces.size();
2491 
2492  // Verify all elements are quadrilaterals
2493  for( int k = 0; k < nElements; k++ )
2494  {
2495  const Face& face = trmesh->faces[k];
2496 
2497  if( face.edges.size() != 4 )
2498  {
2499  _EXCEPTIONT( "Non-quadrilateral face detected; "
2500  "incompatible with --gll" );
2501  }
2502  }
2503 
2504  // Number of unique nodes (CGLL) or total element-local DOFs (DGLL)
2505  const bool fDiscontinuous = ( discMethod == DiscretizationType_DGLL );
2506  int iMaxNode = 0;
2507  if( fDiscontinuous )
2508  {
2509  // DGLL: each element has independent DOFs
2510  iMaxNode = nElements * discOrder * discOrder;
2511  }
2512  else
2513  {
2514  // CGLL: shared nodes at element boundaries
2515  for( int i = 0; i < discOrder; i++ )
2516  for( int j = 0; j < discOrder; j++ )
2517  for( int k = 0; k < nElements; k++ )
2518  if( dataGLLNodes[i][j][k] > iMaxNode )
2519  iMaxNode = dataGLLNodes[i][j][k];
2520  }
2521 
2522  // Get Gauss-Lobatto quadrature nodes
2523  DataArray1D< double > dG;
2524  DataArray1D< double > dW;
2525 
2526  GaussLobattoQuadrature::GetPoints( discOrder, 0.0, 1.0, dG, dW );
2527 
2528  // Get Gauss quadrature nodes
2529  const int nGaussP = 10;
2530 
2531  DataArray1D< double > dGaussG;
2532  DataArray1D< double > dGaussW;
2533 
2534  GaussQuadrature::GetPoints( nGaussP, 0.0, 1.0, dGaussG, dGaussW );
2535 
2536  // Allocate data
2537  dVar.Allocate( iMaxNode );
2538  dVarMB.Allocate( discOrder * discOrder * nElements );
2539  dNodeArea.Allocate( iMaxNode );
2540 
2541  // Sample data
2542  for( int k = 0; k < nElements; k++ )
2543  {
2544  const Face& face = trmesh->faces[k];
2545 
2546  // Sample data at GLL nodes
2547  if( fGLL )
2548  {
2549  for( int i = 0; i < discOrder; i++ )
2550  {
2551  for( int j = 0; j < discOrder; j++ )
2552  {
2553 
2554  // Apply local map
2555  Node node;
2556  Node dDx1G;
2557  Node dDx2G;
2558 
2559  ApplyLocalMap( face, trmesh->nodes, dG[i], dG[j], node, dDx1G, dDx2G );
2560 
2561  // Sample data at this point
2562  double dNodeLon = atan2( node.y, node.x );
2563  if( dNodeLon < 0.0 )
2564  {
2565  dNodeLon += 2.0 * M_PI;
2566  }
2567  double dNodeLat = asin( node.z );
2568 
2569  double dSample = ( *testFunction )( dNodeLon, dNodeLat );
2570 
2571  if( fDiscontinuous )
2572  dVar[k * discOrder * discOrder + j * discOrder + i] = dSample;
2573  else
2574  dVar[dataGLLNodes[j][i][k] - 1] = dSample;
2575  }
2576  }
2577  // High-order Gaussian integration over basis function
2578  }
2579  else
2580  {
2581  DataArray2D< double > dCoeff( discOrder, discOrder );
2582 
2583  for( int p = 0; p < nGaussP; p++ )
2584  {
2585  for( int q = 0; q < nGaussP; q++ )
2586  {
2587 
2588  // Apply local map
2589  Node node;
2590  Node dDx1G;
2591  Node dDx2G;
2592 
2593  ApplyLocalMap( face, trmesh->nodes, dGaussG[p], dGaussG[q], node, dDx1G, dDx2G );
2594 
2595  // Cross product gives local Jacobian
2596  Node nodeCross = CrossProduct( dDx1G, dDx2G );
2597 
2598  double dJacobian =
2599  sqrt( nodeCross.x * nodeCross.x + nodeCross.y * nodeCross.y + nodeCross.z * nodeCross.z );
2600 
2601  // Find components of quadrature point in basis
2602  // of the first Face
2603  SampleGLLFiniteElement( 0, discOrder, dGaussG[p], dGaussG[q], dCoeff );
2604 
2605  // Sample data at this point
2606  double dNodeLon = atan2( node.y, node.x );
2607  if( dNodeLon < 0.0 )
2608  {
2609  dNodeLon += 2.0 * M_PI;
2610  }
2611  double dNodeLat = asin( node.z );
2612 
2613  double dSample = ( *testFunction )( dNodeLon, dNodeLat );
2614 
2615  // Integrate
2616  for( int i = 0; i < discOrder; i++ )
2617  {
2618  for( int j = 0; j < discOrder; j++ )
2619  {
2620 
2621  double dNodalArea = dCoeff[i][j] * dGaussW[p] * dGaussW[q] * dJacobian;
2622 
2623  dVar[dataGLLNodes[i][j][k] - 1] += dSample * dNodalArea;
2624 
2625  dNodeArea[dataGLLNodes[i][j][k] - 1] += dNodalArea;
2626  }
2627  }
2628  }
2629  }
2630  }
2631  }
2632 
2633  // Divide by area
2634  if( fGLLIntegrate )
2635  {
2636  for( size_t i = 0; i < dVar.GetRows(); i++ )
2637  {
2638  dVar[i] /= dNodeArea[i];
2639  }
2640  }
2641 
2642  // Let us rearrange the data based on DoF ID specification
2643  if( ctx == Remapper::SourceMesh )
2644  {
2645  for( unsigned j = 0; j < entities.size(); j++ )
2646  for( int p = 0; p < discOrder; p++ )
2647  for( int q = 0; q < discOrder; q++ )
2648  {
2649  const int offsetDOF = j * discOrder * discOrder + p * discOrder + q;
2650  dVarMB[offsetDOF] = dVar[col_dtoc_dofmap[offsetDOF]];
2651  }
2652  }
2653  else
2654  {
2655  for( unsigned j = 0; j < entities.size(); j++ )
2656  for( int p = 0; p < discOrder; p++ )
2657  for( int q = 0; q < discOrder; q++ )
2658  {
2659  const int offsetDOF = j * discOrder * discOrder + p * discOrder + q;
2660  dVarMB[offsetDOF] = dVar[row_dtoc_dofmap[offsetDOF]];
2661  }
2662  }
2663 
2664  // Set the tag data
2665  MB_CHK_ERR( m_interface->tag_set_data( solnTag, entities, &dVarMB[0] ) );
2666  }
2667  else
2668  {
2669  // assert( discOrder == 1 );
2670  if( discMethod == DiscretizationType_FV )
2671  {
2672  /* Compute an element-wise integral to store the sampled solution based on Quadrature
2673  * rules */
2674  // Resize the array
2675  dVar.Allocate( trmesh->faces.size() );
2676 
2677  std::vector< Node >& nodes = trmesh->nodes;
2678 
2679  // Loop through all Faces
2680  for( size_t i = 0; i < trmesh->faces.size(); i++ )
2681  {
2682  const Face& face = trmesh->faces[i];
2683 
2684  // Loop through all sub-triangles
2685  for( size_t j = 0; j < face.edges.size() - 2; j++ )
2686  {
2687 
2688  const Node& node0 = nodes[face[0]];
2689  const Node& node1 = nodes[face[j + 1]];
2690  const Node& node2 = nodes[face[j + 2]];
2691 
2692  // Triangle area
2693  Face faceTri( 3 );
2694  faceTri.SetNode( 0, face[0] );
2695  faceTri.SetNode( 1, face[j + 1] );
2696  faceTri.SetNode( 2, face[j + 2] );
2697 
2698  double dTriangleArea = CalculateFaceArea( faceTri, nodes );
2699 
2700  // Calculate the element average
2701  double dTotalSample = 0.0;
2702 
2703  // Loop through all quadrature points
2704  for( int k = 0; k < TriQuadraturePoints; k++ )
2705  {
2706  Node node( TriQuadratureG[k][0] * node0.x + TriQuadratureG[k][1] * node1.x +
2707  TriQuadratureG[k][2] * node2.x,
2708  TriQuadratureG[k][0] * node0.y + TriQuadratureG[k][1] * node1.y +
2709  TriQuadratureG[k][2] * node2.y,
2710  TriQuadratureG[k][0] * node0.z + TriQuadratureG[k][1] * node1.z +
2711  TriQuadratureG[k][2] * node2.z );
2712 
2713  double dMagnitude = node.Magnitude();
2714  node.x /= dMagnitude;
2715  node.y /= dMagnitude;
2716  node.z /= dMagnitude;
2717 
2718  double dLon = atan2( node.y, node.x );
2719  if( dLon < 0.0 )
2720  {
2721  dLon += 2.0 * M_PI;
2722  }
2723  double dLat = asin( node.z );
2724 
2725  double dSample = ( *testFunction )( dLon, dLat );
2726 
2727  dTotalSample += dSample * TriQuadratureW[k] * dTriangleArea;
2728  }
2729 
2730  dVar[i] += dTotalSample / trmesh->vecFaceArea[i];
2731  }
2732  }
2733  MB_CHK_ERR( m_interface->tag_set_data( solnTag, entities, &dVar[0] ) );
2734  }
2735  else /* discMethod == DiscretizationType_PCLOUD */
2736  {
2737  /* Get the coordinates of the vertices and sample the functionals accordingly */
2738  std::vector< Node >& nodes = trmesh->nodes;
2739 
2740  // Resize the array
2741  dVar.Allocate( nodes.size() );
2742 
2743  for( size_t j = 0; j < nodes.size(); j++ )
2744  {
2745  Node& node = nodes[j];
2746  double dMagnitude = node.Magnitude();
2747  node.x /= dMagnitude;
2748  node.y /= dMagnitude;
2749  node.z /= dMagnitude;
2750  double dLon = atan2( node.y, node.x );
2751  if( dLon < 0.0 )
2752  {
2753  dLon += 2.0 * M_PI;
2754  }
2755  double dLat = asin( node.z );
2756 
2757  double dSample = ( *testFunction )( dLon, dLat );
2758  dVar[j] = dSample;
2759  }
2760 
2761  MB_CHK_ERR( m_interface->tag_set_data( solnTag, entities, &dVar[0] ) );
2762  }
2763  }
2764 
2765  return moab::MB_SUCCESS;
2766 }

References MB_CHK_ERR, MB_SUCCESS, MB_TAG_CREAT, MB_TAG_DENSE, MB_TYPE_DOUBLE, moab::Range::size(), moab::Remapper::SourceMesh, and moab::Remapper::TargetMesh.

Referenced by main().

◆ DropAbsentColumns()

int moab::TempestOnlineMap::DropAbsentColumns ( )

Zero the weights of columns the migrated coverage did not supply, then compact the matrix. Use ONLY for columns proven globally absent (masked source grid): the projection already treats them as zero-source, so this is a BfB-safe drop that lets the dual-map CAAS bounds check skip them instead of aborting. Returns the number of columns dropped.

Definition at line 494 of file TempestOnlineMap.cpp.

495 {
496  const int ncols = m_nTotDofs_SrcCov;
497  std::vector< bool > delivered;
498  compute_delivered_columns( ncols, col_dtoc_dofmap, delivered );
499 
500  // Zero every stored coefficient whose column was not supplied by coverage.
501  // The projection already treats these columns as zero-source (ApplyWeights
502  // leaves m_colVector at 0 for them), so this changes no projected value; it
503  // only lets the dual-map CAAS bounds loop skip them via its |w|<1e-50 test.
504  int dropped = 0;
505  for( int r = 0; r < m_weightMatrix.outerSize(); r++ )
506  {
507  for( WeightMatrix::InnerIterator it( m_weightMatrix, r ); it; ++it )
508  {
509  const int mc = (int)it.col();
510  if( mc < 0 || mc >= ncols || !delivered[mc] )
511  {
512  if( it.value() != 0.0 ) dropped++;
513  it.valueRef() = 0.0;
514  }
515  }
516  }
517  // Remove the explicit zeros so iterators no longer visit them.
518  m_weightMatrix.prune( []( const Eigen::Index&, const Eigen::Index&, const double& v ) { return v != 0.0; } );
519  return dropped;
520 }

References compute_delivered_columns().

◆ fill_col_ids()

moab::ErrorCode moab::TempestOnlineMap::fill_col_ids ( std::vector< int > &  ids_of_interest)
inline

Definition at line 462 of file TempestOnlineMap.hpp.

463  {
464  ids_of_interest.reserve( col_gdofmap.size() );
465  // need to add 1
466  for( auto it = col_gdofmap.begin(); it != col_gdofmap.end(); it++ )
467  ids_of_interest.push_back( *it + 1 );
468  return moab::MB_SUCCESS;
469  }

References col_gdofmap, and MB_SUCCESS.

◆ GenerateRemappingWeights()

moab::ErrorCode moab::TempestOnlineMap::GenerateRemappingWeights ( std::string  strInputType,
std::string  strOutputType,
const GenerateOfflineMapAlgorithmOptions &  mapOptions,
const std::string &  srcDofTagName = "GLOBAL_ID",
const std::string &  tgtDofTagName = "GLOBAL_ID" 
)

Generate the offline map, given the source and target mesh and discretization details. This method generates the mapping between the two meshes based on the overlap and stores the result in the SparseMatrix.

the tag should be created already in the e3sm workflow; if not, create it here

Definition at line 523 of file TempestOnlineMap.cpp.

528 {
529  NcError error( NcError::silent_nonfatal );
530 
531  moab::DebugOutput dbgprint( std::cout, rank, 0 );
532  dbgprint.set_prefix( "[TempestOnlineMap]: " );
533  moab::ErrorCode rval;
534 
535  const bool m_bPointCloudSource = ( m_remapper->point_cloud_source );
536  const bool m_bPointCloudTarget = ( m_remapper->point_cloud_target );
537  const bool m_bPointCloud = m_bPointCloudSource || m_bPointCloudTarget;
538 
539  // Build a matrix of source and target discretization so that we know how
540  // to assign the global DoFs in parallel for the mapping weights.
541  // For example,
542  // for FV->FV: the rows represented target DoFs and cols represent source DoFs
543  try
544  {
545  // Check command line parameters (data type arguments)
546  STLStringHelper::ToLower( strInputType );
547  STLStringHelper::ToLower( strOutputType );
548 
549  DiscretizationType eInputType;
550  DiscretizationType eOutputType;
551 
552  if( strInputType == "fv" )
553  {
554  eInputType = DiscretizationType_FV;
555  }
556  else if( strInputType == "cgll" )
557  {
558  eInputType = DiscretizationType_CGLL;
559  }
560  else if( strInputType == "dgll" )
561  {
562  eInputType = DiscretizationType_DGLL;
563  }
564  else if( strInputType == "pcloud" )
565  {
566  eInputType = DiscretizationType_PCLOUD;
567  }
568  else
569  {
570  _EXCEPTION1( "Invalid \"in_type\" value (%s), expected [fv|cgll|dgll]", strInputType.c_str() );
571  }
572 
573  if( strOutputType == "fv" )
574  {
575  eOutputType = DiscretizationType_FV;
576  }
577  else if( strOutputType == "cgll" )
578  {
579  eOutputType = DiscretizationType_CGLL;
580  }
581  else if( strOutputType == "dgll" )
582  {
583  eOutputType = DiscretizationType_DGLL;
584  }
585  else if( strOutputType == "pcloud" )
586  {
587  eOutputType = DiscretizationType_PCLOUD;
588  }
589  else
590  {
591  _EXCEPTION1( "Invalid \"out_type\" value (%s), expected [fv|cgll|dgll]", strOutputType.c_str() );
592  }
593 
594  // set all required input params
595  m_bConserved = !mapOptions.fNoConservation;
596  m_eInputType = eInputType;
597  m_eOutputType = eOutputType;
598 
599  // Method flags
600  std::string strMapAlgorithm( "" );
601  int nMonotoneType = ( mapOptions.fMonotone ) ? ( 1 ) : ( 0 );
602 
603  // Make an index of method arguments
604  std::set< std::string > setMethodStrings;
605  {
606  int iLast = 0;
607  for( size_t i = 0; i <= mapOptions.strMethod.length(); i++ )
608  {
609  if( ( i == mapOptions.strMethod.length() ) || ( mapOptions.strMethod[i] == ';' ) )
610  {
611  std::string strMethodString = mapOptions.strMethod.substr( iLast, i - iLast );
612  STLStringHelper::RemoveWhitespaceInPlace( strMethodString );
613  if( strMethodString.length() > 0 )
614  {
615  setMethodStrings.insert( strMethodString );
616  }
617  iLast = i + 1;
618  }
619  }
620  }
621 
622  for( const auto& it : setMethodStrings )
623  {
624  // Piecewise constant monotonicity
625  if( it == "mono2" )
626  {
628  {
629  _EXCEPTIONT( "--method \"mono2\" is only used when remapping to/from CGLL or DGLL grids" );
630  }
631  nMonotoneType = 2;
632 
633  // Piecewise linear monotonicity
634  }
635  else if( it == "mono3" )
636  {
638  {
639  _EXCEPTIONT( "--method \"mono3\" is only used when remapping to/from CGLL or DGLL grids" );
640  }
641  nMonotoneType = 3;
642 
643  // Volumetric remapping from FV to GLL
644  }
645  else if( it == "volumetric" )
646  {
648  {
649  _EXCEPTIONT( "--method \"volumetric\" may only be used for FV->CGLL or FV->DGLL remapping" );
650  }
651  strMapAlgorithm = "volumetric";
652 
653  // Inverse distance mapping
654  }
655  else if( it == "invdist" )
656  {
658  {
659  _EXCEPTIONT( "--method \"invdist\" may only be used for FV->FV remapping" );
660  }
661  strMapAlgorithm = "invdist";
662 
663  // Delaunay triangulation mapping
664  }
665  else if( it == "delaunay" )
666  {
668  {
669  _EXCEPTIONT( "--method \"delaunay\" may only be used for FV->FV remapping" );
670  }
671  strMapAlgorithm = "delaunay";
672 
673  // Bilinear
674  }
675  else if( it == "bilin" )
676  {
678  {
679  _EXCEPTIONT( "--method \"bilin\" may only be used for FV->FV remapping" );
680  }
681  strMapAlgorithm = "fvbilin";
682 
683  // Integrated bilinear (same as mono3 when source grid is CGLL/DGLL)
684  }
685  else if( it == "intbilin" )
686  {
688  {
689  _EXCEPTIONT( "--method \"intbilin\" may only be used when mapping to FV." );
690  }
692  {
693  strMapAlgorithm = "fvintbilin";
694  }
695  else
696  {
697  strMapAlgorithm = "mono3";
698  }
699 
700  // Integrated bilinear with generalized Barycentric coordinates
701  }
702  else if( it == "intbilingb" )
703  {
705  {
706  _EXCEPTIONT( "--method \"intbilingb\" may only be used for FV->FV remapping" );
707  }
708  strMapAlgorithm = "fvintbilingb";
709  }
710  else
711  {
712  _EXCEPTION1( "Invalid --method argument \"%s\"", it.c_str() );
713  }
714  }
715 
718  : mapOptions.nPin );
721  : mapOptions.nPout );
722 
723  // Set the source and target mesh objects
724  MB_CHK_ERR( SetDOFmapTags( srcDofTagName, tgtDofTagName ) );
725 
726  /// the tag should be created already in the e3sm workflow; if not, create it here
727  Tag areaTag;
728  rval = m_interface->tag_get_handle( "aream", 1, MB_TYPE_DOUBLE, areaTag,
730  if( MB_ALREADY_ALLOCATED == rval )
731  {
732  if( is_root ) dbgprint.printf( 0, "aream tag already defined \n" );
733  }
734 
735  double local_areas[3] = { 0.0, 0.0, 0.0 }, global_areas[3] = { 0.0, 0.0, 0.0 };
736  if( !m_bPointCloudSource )
737  {
738  // Calculate Input Mesh Face areas
739  if( is_root ) dbgprint.printf( 0, "Calculating input mesh Face areas\n" );
740  local_areas[0] = m_meshInput->CalculateFaceAreas( mapOptions.fSourceConcave );
741  // Set source element areas as tag on the source mesh
743 
744  // Update coverage source mesh areas as well.
745  m_meshInputCov->CalculateFaceAreas( mapOptions.fSourceConcave );
746  }
747 
748  if( !m_bPointCloudTarget )
749  {
750  // Calculate Output Mesh Face areas
751  if( is_root ) dbgprint.printf( 0, "Calculating output mesh Face areas\n" );
752  local_areas[1] = m_meshOutput->CalculateFaceAreas( mapOptions.fTargetConcave );
753  // Set target element areas as tag on the target mesh
754  MB_CHK_ERR(
755  m_interface->tag_set_data( areaTag, m_remapper->m_target_entities, m_meshOutput->vecFaceArea ) );
756  }
757 
758  if( !m_bPointCloud )
759  {
760  // Calculate Face areas
761  if (m_meshOverlap)
762  {
763  // Verify that overlap mesh is in the correct order (sanity check)
764  assert( m_meshOverlap->vecSourceFaceIx.size() == m_meshOverlap->vecTargetFaceIx.size() );
765 
766  if( is_root ) dbgprint.printf( 0, "Calculating overlap mesh Face areas\n" );
767  local_areas[2] =
768  m_meshOverlap->CalculateFaceAreas( mapOptions.fSourceConcave || mapOptions.fTargetConcave );
769  }
770 
771  // store it as global output for now - used later in reduction
772  std::copy( local_areas, local_areas + 3, global_areas );
773 #ifdef MOAB_HAVE_MPI
774  // reduce the local source, target and overlap mesh areas to global areas
775  if( m_pcomm && is_parallel )
776  MPI_Reduce( local_areas, global_areas, 3, MPI_DOUBLE, MPI_SUM, 0, m_pcomm->comm() );
777 #endif
778  if( is_root )
779  {
780  dbgprint.printf( 0, "Input Mesh Geometric Area: %1.15e\n", global_areas[0] );
781  dbgprint.printf( 0, "Output Mesh Geometric Area: %1.15e\n", global_areas[1] );
782  if (m_meshOverlap) dbgprint.printf( 0, "Overlap Mesh Recovered Area: %1.15e\n", global_areas[2] );
783  }
784 
785  // Correct areas to match the areas calculated in the overlap mesh
786  constexpr bool fCorrectAreas = true;
787  if( fCorrectAreas && m_meshOverlap ) // In MOAB-TempestRemap, we will always keep this to be true
788  {
789  if( is_root ) dbgprint.printf( 0, "Correcting source/target areas to overlap mesh areas\n" );
790  DataArray1D< double > dSourceArea( m_meshInputCov->faces.size() );
791  DataArray1D< double > dTargetArea( m_meshOutput->faces.size() );
792 
793  assert( m_meshOverlap->vecSourceFaceIx.size() == m_meshOverlap->faces.size() );
794  assert( m_meshOverlap->vecTargetFaceIx.size() == m_meshOverlap->faces.size() );
795  assert( m_meshOverlap->vecFaceArea.GetRows() == m_meshOverlap->faces.size() );
796 
797  assert( m_meshInputCov->vecFaceArea.GetRows() == m_meshInputCov->faces.size() );
798  assert( m_meshOutput->vecFaceArea.GetRows() == m_meshOutput->faces.size() );
799 
800  for( size_t i = 0; i < m_meshOverlap->faces.size(); i++ )
801  {
802  if( m_meshOverlap->vecSourceFaceIx[i] < 0 || m_meshOverlap->vecTargetFaceIx[i] < 0 )
803  continue; // skip this cell since it is ghosted
804 
805  // let us recompute the source/target areas based on overlap mesh areas
806  assert( static_cast< size_t >( m_meshOverlap->vecSourceFaceIx[i] ) < m_meshInputCov->faces.size() );
807  dSourceArea[m_meshOverlap->vecSourceFaceIx[i]] += m_meshOverlap->vecFaceArea[i];
808  assert( static_cast< size_t >( m_meshOverlap->vecTargetFaceIx[i] ) < m_meshOutput->faces.size() );
809  dTargetArea[m_meshOverlap->vecTargetFaceIx[i]] += m_meshOverlap->vecFaceArea[i];
810  }
811 
812  for( size_t i = 0; i < m_meshInputCov->faces.size(); i++ )
813  {
814  if( fabs( dSourceArea[i] - m_meshInputCov->vecFaceArea[i] ) < 1.0e-10 )
815  {
816  m_meshInputCov->vecFaceArea[i] = dSourceArea[i];
817  }
818  }
819  for( size_t i = 0; i < m_meshOutput->faces.size(); i++ )
820  {
821  if( fabs( dTargetArea[i] - m_meshOutput->vecFaceArea[i] ) < 1.0e-10 )
822  {
823  m_meshOutput->vecFaceArea[i] = dTargetArea[i];
824  }
825  }
826  }
827 
828  // Set source mesh areas in map
829  if( !m_bPointCloudSource && eInputType == DiscretizationType_FV )
830  {
831  this->SetSourceAreas( m_meshInputCov->vecFaceArea );
832  if( m_meshInputCov->vecMask.size() )
833  {
834  this->SetSourceMask( m_meshInputCov->vecMask );
835  }
836  }
837 
838  // Set target mesh areas in map
839  if( !m_bPointCloudTarget && eOutputType == DiscretizationType_FV )
840  {
841  this->SetTargetAreas( m_meshOutput->vecFaceArea );
842  if( m_meshOutput->vecMask.size() )
843  {
844  this->SetTargetMask( m_meshOutput->vecMask );
845  }
846  }
847 
848  /*
849  // Recalculate input mesh area from overlap mesh
850  if (fabs(dTotalAreaOverlap - dTotalAreaInput) > 1.0e-10) {
851  dbgprint.printf(0, "Overlap mesh only covers a sub-area of the sphere\n");
852  dbgprint.printf(0, "Recalculating source mesh areas\n");
853  dTotalAreaInput = m_meshInput->CalculateFaceAreasFromOverlap(m_meshOverlap);
854  dbgprint.printf(0, "New Input Mesh Geometric Area: %1.15e\n", dTotalAreaInput);
855  }
856  */
857  }
858 
859  // Finite volume input / Finite volume output
860  if( ( eInputType == DiscretizationType_FV ) && ( eOutputType == DiscretizationType_FV ) )
861  {
862  // Generate reverse node array and edge map
863  if( m_meshInputCov->revnodearray.size() == 0 ) m_meshInputCov->ConstructReverseNodeArray();
864  if( m_meshInputCov->edgemap.size() == 0 ) m_meshInputCov->ConstructEdgeMap( false );
865 
866  // Initialize coordinates for map
867  this->InitializeSourceCoordinatesFromMeshFV( *m_meshInputCov );
868  this->InitializeTargetCoordinatesFromMeshFV( *m_meshOutput );
869 
870  this->m_pdataGLLNodesIn = nullptr;
871  this->m_pdataGLLNodesOut = nullptr;
872 
873  // Finite volume input / Finite element output
874  MB_CHK_ERR( this->SetDOFmapAssociation( eInputType, mapOptions.nPin, false, nullptr, nullptr, eOutputType,
875  mapOptions.nPout, false, nullptr ) );
876 
877  // Construct remap for FV-FV
878  if( is_root ) dbgprint.printf( 0, "Calculating remap weights\n" );
879 
880  // Construct OfflineMap
881  if( strMapAlgorithm == "invdist" )
882  {
883  if( m_meshInputCov->faces.size() )
884  {
885  if( is_root ) dbgprint.printf( 0, "Calculating map (invdist)\n" );
886  LinearRemapFVtoFVInvDist( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, *this );
887  }
888  }
889  else if( strMapAlgorithm == "delaunay" ) // does not need intersection mesh
890  {
891  if( m_meshInputCov->faces.size() )
892  {
893  if( is_root ) dbgprint.printf( 0, "Calculating map (delaunay)\n" );
894  if (m_meshOverlap) LinearRemapTriangulation( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, *this );
895  else
896  {
897  Mesh dummy;
898  LinearRemapTriangulation( *m_meshInputCov, *m_meshOutput, dummy, *this );
899  }
900  }
901  }
902  else if( strMapAlgorithm == "fvintbilin" )
903  {
904  if( m_meshInputCov->faces.size() )
905  {
906  if( is_root ) dbgprint.printf( 0, "Calculating map (intbilin)\n" );
907  LinearRemapIntegratedBilinear( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, *this );
908  }
909  }
910  else if( strMapAlgorithm == "fvintbilingb" )
911  {
912  if( m_meshInputCov->faces.size() )
913  {
914  if( is_root ) dbgprint.printf( 0, "Calculating map (intbilingb)\n" );
915  LinearRemapIntegratedGeneralizedBarycentric( *m_meshInputCov, *m_meshOutput, *m_meshOverlap,
916  *this );
917  }
918  }
919  else if( strMapAlgorithm == "fvbilin" ) // does not need intersection mesh
920  {
921 #ifdef VERBOSE
922  if( is_root )
923  {
924  m_meshInputCov->Write( "SourceMeshMBTR.g" );
925  m_meshOutput->Write( "TargetMeshMBTR.g" );
926  }
927  else
928  {
929  m_meshInputCov->Write( "SourceMeshMBTR" + std::to_string( rank ) + ".g" );
930  m_meshOutput->Write( "TargetMeshMBTR" + std::to_string( rank ) + ".g" );
931  }
932 #endif
933 
934  if( m_meshInputCov->faces.size() )
935  {
936  if( is_root ) dbgprint.printf( 0, "Calculating map (bilin)\n" );
937  if (m_meshOverlap) LinearRemapBilinear( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, *this );
938  else
939  {
940  Mesh dummy;
941  LinearRemapBilinear( *m_meshInputCov, *m_meshOutput, dummy, *this );
942  }
943  }
944  }
945  else
946  {
947  if( is_root ) dbgprint.printf( 0, "Calculating conservative FV-FV map\n" );
948  if( m_meshInputCov->faces.size() )
949  {
950 #ifdef USE_NATIVE_TEMPESTREMAP_ROUTINES
951  LinearRemapFVtoFV( *m_meshInputCov, *m_meshOutput, *m_meshOverlap,
952  ( mapOptions.fMonotone ) ? ( 1 ) : ( mapOptions.nPin ), *this );
953 #else
954  LinearRemapFVtoFV_Tempest_MOAB( ( mapOptions.fMonotone ? 1 : mapOptions.nPin ) );
955 #endif
956  }
957  }
958  }
959  else if( eInputType == DiscretizationType_FV )
960  {
961  DataArray3D< double > dataGLLJacobian;
962 
963  if( is_root ) dbgprint.printf( 0, "Generating output mesh meta data\n" );
964  double dNumericalArea_loc = GenerateMetaData( *m_meshOutput, mapOptions.nPout, mapOptions.fNoBubble,
965  dataGLLNodesDest, dataGLLJacobian );
966 
967  double dNumericalArea = dNumericalArea_loc;
968 #ifdef MOAB_HAVE_MPI
969  if( m_pcomm )
970  MPI_Reduce( &dNumericalArea_loc, &dNumericalArea, 1, MPI_DOUBLE, MPI_SUM, 0, m_pcomm->comm() );
971 #endif
972  if( is_root ) dbgprint.printf( 0, "Output Mesh Numerical Area: %1.15e\n", dNumericalArea );
973 
974  // Initialize coordinates for map
975  this->InitializeSourceCoordinatesFromMeshFV( *m_meshInputCov );
976  this->InitializeTargetCoordinatesFromMeshFE( *m_meshOutput, mapOptions.nPout, dataGLLNodesDest );
977 
978  this->m_pdataGLLNodesIn = nullptr;
979  this->m_pdataGLLNodesOut = &dataGLLNodesDest;
980 
981  // Generate the continuous Jacobian
982  bool fContinuous = ( eOutputType == DiscretizationType_CGLL );
983 
984  if( eOutputType == DiscretizationType_CGLL )
985  {
986  GenerateUniqueJacobian( dataGLLNodesDest, dataGLLJacobian, this->GetTargetAreas() );
987  }
988  else
989  {
990  GenerateDiscontinuousJacobian( dataGLLJacobian, this->GetTargetAreas() );
991  }
992 
993  // Generate reverse node array and edge map
994  if( m_meshInputCov->revnodearray.size() == 0 ) m_meshInputCov->ConstructReverseNodeArray();
995  if( m_meshInputCov->edgemap.size() == 0 ) m_meshInputCov->ConstructEdgeMap( false );
996 
997  // Finite volume input / Finite element output
998  MB_CHK_ERR( this->SetDOFmapAssociation( eInputType, mapOptions.nPin, false, nullptr, nullptr, eOutputType,
999  mapOptions.nPout, ( eOutputType == DiscretizationType_CGLL ),
1000  &dataGLLNodesDest ) );
1001 
1002  // Generate remap weights
1003  if( strMapAlgorithm == "volumetric" )
1004  {
1005  if( is_root ) dbgprint.printf( 0, "Calculating remapping weights for FV->GLL (volumetric)\n" );
1006  LinearRemapFVtoGLL_Volumetric( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, dataGLLNodesDest,
1007  dataGLLJacobian, this->GetTargetAreas(), mapOptions.nPin, *this,
1008  nMonotoneType, fContinuous, mapOptions.fNoConservation );
1009  }
1010  else
1011  {
1012  if( is_root ) dbgprint.printf( 0, "Calculating remapping weights for FV->GLL\n" );
1013  LinearRemapFVtoGLL( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, dataGLLNodesDest, dataGLLJacobian,
1014  this->GetTargetAreas(), mapOptions.nPin, *this, nMonotoneType, fContinuous,
1015  mapOptions.fNoConservation );
1016  }
1017  }
1018  else if( ( eInputType == DiscretizationType_PCLOUD ) || ( eOutputType == DiscretizationType_PCLOUD ) )
1019  {
1020  DataArray3D< double > dataGLLJacobian;
1021  if( !m_bPointCloudSource )
1022  {
1023  // Generate reverse node array and edge map
1024  if( m_meshInputCov->revnodearray.size() == 0 ) m_meshInputCov->ConstructReverseNodeArray();
1025  if( m_meshInputCov->edgemap.size() == 0 ) m_meshInputCov->ConstructEdgeMap( false );
1026 
1027  // Initialize coordinates for map
1028  if( eInputType == DiscretizationType_FV )
1029  {
1030  this->InitializeSourceCoordinatesFromMeshFV( *m_meshInputCov );
1031  }
1032  else
1033  {
1034  if( is_root ) dbgprint.printf( 0, "Generating input mesh meta data\n" );
1035  DataArray3D< double > dataGLLJacobianSrc;
1036  GenerateMetaData( *m_meshInputCov, mapOptions.nPin, mapOptions.fNoBubble, dataGLLNodesSrcCov,
1037  dataGLLJacobian );
1038  GenerateMetaData( *m_meshInput, mapOptions.nPin, mapOptions.fNoBubble, dataGLLNodesSrc,
1039  dataGLLJacobianSrc );
1040  }
1041  }
1042  // else { /* Source is a point cloud dataset */ }
1043 
1044  if( !m_bPointCloudTarget )
1045  {
1046  // Generate reverse node array and edge map
1047  if( m_meshOutput->revnodearray.size() == 0 ) m_meshOutput->ConstructReverseNodeArray();
1048  if( m_meshOutput->edgemap.size() == 0 ) m_meshOutput->ConstructEdgeMap( false );
1049 
1050  // Initialize coordinates for map
1051  if( eOutputType == DiscretizationType_FV )
1052  {
1053  this->InitializeSourceCoordinatesFromMeshFV( *m_meshOutput );
1054  }
1055  else
1056  {
1057  if( is_root ) dbgprint.printf( 0, "Generating output mesh meta data\n" );
1058  GenerateMetaData( *m_meshOutput, mapOptions.nPout, mapOptions.fNoBubble, dataGLLNodesDest,
1059  dataGLLJacobian );
1060  }
1061  }
1062  // else { /* Target is a point cloud dataset */ }
1063 
1064  // Finite volume input / Finite element output
1066  eInputType, mapOptions.nPin, ( eInputType == DiscretizationType_CGLL ),
1067  ( m_bPointCloudSource || eInputType == DiscretizationType_FV ? nullptr : &dataGLLNodesSrcCov ),
1068  ( m_bPointCloudSource || eInputType == DiscretizationType_FV ? nullptr : &dataGLLNodesSrc ),
1069  eOutputType, mapOptions.nPout, ( eOutputType == DiscretizationType_CGLL ),
1070  ( m_bPointCloudTarget ? nullptr : &dataGLLNodesDest ) ) );
1071 
1072  // Construct remap
1073  if( is_root ) dbgprint.printf( 0, "Calculating remap weights with Nearest-Neighbor method\n" );
1074  MB_CHK_ERR( LinearRemapNN_MOAB( true /*use_GID_matching*/, false /*strict_check*/ ) );
1075  }
1076  else if( ( eInputType != DiscretizationType_FV ) && ( eOutputType == DiscretizationType_FV ) )
1077  {
1078  DataArray3D< double > dataGLLJacobianSrc, dataGLLJacobian;
1079 
1080  if( is_root ) dbgprint.printf( 0, "Generating input mesh meta data\n" );
1081  // generate metadata for the input meshes (both source and covering source)
1082  GenerateMetaData( *m_meshInput, mapOptions.nPin, mapOptions.fNoBubble, dataGLLNodesSrc,
1083  dataGLLJacobianSrc );
1084  GenerateMetaData( *m_meshInputCov, mapOptions.nPin, mapOptions.fNoBubble, dataGLLNodesSrcCov,
1085  dataGLLJacobian );
1086 
1087  if( dataGLLNodesSrcCov.GetSubColumns() != m_meshInputCov->faces.size() )
1088  {
1089  _EXCEPTIONT( "Number of element does not match between metadata and "
1090  "input mesh" );
1091  }
1092 
1093  // Initialize coordinates for map
1094  this->InitializeSourceCoordinatesFromMeshFE( *m_meshInputCov, mapOptions.nPin, dataGLLNodesSrcCov );
1095  this->InitializeTargetCoordinatesFromMeshFV( *m_meshOutput );
1096 
1097  // Generate the continuous Jacobian for input mesh
1098  bool fContinuousIn = ( eInputType == DiscretizationType_CGLL );
1099 
1100  if( eInputType == DiscretizationType_CGLL )
1101  {
1102  GenerateUniqueJacobian( dataGLLNodesSrcCov, dataGLLJacobian, this->GetSourceAreas() );
1103  }
1104  else
1105  {
1106  GenerateDiscontinuousJacobian( dataGLLJacobian, this->GetSourceAreas() );
1107  }
1108 
1109  // Finite element input / Finite volume output
1110  MB_CHK_ERR( this->SetDOFmapAssociation( eInputType, mapOptions.nPin,
1111  ( eInputType == DiscretizationType_CGLL ), &dataGLLNodesSrcCov,
1112  &dataGLLNodesSrc, eOutputType, mapOptions.nPout, false, nullptr ) );
1113 
1114  // Generate remap
1115  if( is_root ) dbgprint.printf( 0, "Calculating remap weights\n" );
1116 
1117  if( strMapAlgorithm == "volumetric" )
1118  {
1119  _EXCEPTIONT( "Unimplemented: Volumetric currently unavailable for"
1120  "GLL input mesh" );
1121  }
1122 
1123  this->m_pdataGLLNodesIn = &dataGLLNodesSrcCov;
1124  this->m_pdataGLLNodesOut = nullptr;
1125 
1126 #ifdef USE_NATIVE_TEMPESTREMAP_ROUTINES
1127  LinearRemapSE4( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, dataGLLNodesSrcCov, dataGLLJacobian,
1128  nMonotoneType, fContinuousIn, mapOptions.fNoConservation, mapOptions.fSparseConstraints,
1129  *this );
1130 #else
1131  LinearRemapSE4_Tempest_MOAB( dataGLLNodesSrcCov, dataGLLJacobian, nMonotoneType, fContinuousIn,
1132  mapOptions.fNoConservation, mapOptions.fSparseConstraints );
1133 #endif
1134  }
1135  else if( ( eInputType != DiscretizationType_FV ) && ( eOutputType != DiscretizationType_FV ) )
1136  {
1137  DataArray3D< double > dataGLLJacobianIn, dataGLLJacobianSrc;
1138  DataArray3D< double > dataGLLJacobianOut;
1139 
1140  // Input metadata
1141  if( is_root ) dbgprint.printf( 0, "Generating input mesh meta data\n" );
1142  // generate metadata for the input meshes (both source and covering source)
1143  GenerateMetaData( *m_meshInput, mapOptions.nPin, mapOptions.fNoBubble, dataGLLNodesSrc,
1144  dataGLLJacobianSrc );
1145  // now coverage
1146  GenerateMetaData( *m_meshInputCov, mapOptions.nPin, mapOptions.fNoBubble, dataGLLNodesSrcCov,
1147  dataGLLJacobianIn );
1148  // Output metadata
1149  if( is_root ) dbgprint.printf( 0, "Generating output mesh meta data\n" );
1150  GenerateMetaData( *m_meshOutput, mapOptions.nPout, mapOptions.fNoBubble, dataGLLNodesDest,
1151  dataGLLJacobianOut );
1152 
1153  // Initialize coordinates for map
1154  this->InitializeSourceCoordinatesFromMeshFE( *m_meshInputCov, mapOptions.nPin, dataGLLNodesSrcCov );
1155  this->InitializeTargetCoordinatesFromMeshFE( *m_meshOutput, mapOptions.nPout, dataGLLNodesDest );
1156 
1157  // Generate the continuous Jacobian for input mesh
1158  bool fContinuousIn = ( eInputType == DiscretizationType_CGLL );
1159 
1160  if( eInputType == DiscretizationType_CGLL )
1161  {
1162  GenerateUniqueJacobian( dataGLLNodesSrcCov, dataGLLJacobianIn, this->GetSourceAreas() );
1163  }
1164  else
1165  {
1166  GenerateDiscontinuousJacobian( dataGLLJacobianIn, this->GetSourceAreas() );
1167  }
1168 
1169  // Generate the continuous Jacobian for output mesh
1170  bool fContinuousOut = ( eOutputType == DiscretizationType_CGLL );
1171 
1172  if( eOutputType == DiscretizationType_CGLL )
1173  {
1174  GenerateUniqueJacobian( dataGLLNodesDest, dataGLLJacobianOut, this->GetTargetAreas() );
1175  }
1176  else
1177  {
1178  GenerateDiscontinuousJacobian( dataGLLJacobianOut, this->GetTargetAreas() );
1179  }
1180 
1181  // Input Finite Element to Output Finite Element
1182  MB_CHK_ERR( this->SetDOFmapAssociation( eInputType, mapOptions.nPin,
1183  ( eInputType == DiscretizationType_CGLL ), &dataGLLNodesSrcCov,
1184  &dataGLLNodesSrc, eOutputType, mapOptions.nPout,
1185  ( eOutputType == DiscretizationType_CGLL ), &dataGLLNodesDest ) );
1186 
1187  this->m_pdataGLLNodesIn = &dataGLLNodesSrcCov;
1188  this->m_pdataGLLNodesOut = &dataGLLNodesDest;
1189 
1190  // Generate remap
1191  if( is_root ) dbgprint.printf( 0, "Calculating remap weights\n" );
1192 
1193 #ifdef USE_NATIVE_TEMPESTREMAP_ROUTINES
1194  LinearRemapGLLtoGLL_Integrated( *m_meshInputCov, *m_meshOutput, *m_meshOverlap, dataGLLNodesSrcCov,
1195  dataGLLJacobianIn, dataGLLNodesDest, dataGLLJacobianOut,
1196  this->GetTargetAreas(), mapOptions.nPin, mapOptions.nPout, nMonotoneType,
1197  fContinuousIn, fContinuousOut, mapOptions.fSparseConstraints, *this );
1198 #else
1199  LinearRemapGLLtoGLL2_MOAB( dataGLLNodesSrcCov, dataGLLJacobianIn, dataGLLNodesDest, dataGLLJacobianOut,
1200  this->GetTargetAreas(), mapOptions.nPin, mapOptions.nPout, nMonotoneType,
1201  fContinuousIn, fContinuousOut, mapOptions.fNoConservation );
1202 #endif
1203  }
1204  else
1205  {
1206  _EXCEPTIONT( "Not implemented" );
1207  }
1208 
1209 #ifdef MOAB_HAVE_EIGEN3
1210  copy_tempest_sparsemat_to_eigen3();
1211 #endif
1212 
1213 #ifdef MOAB_HAVE_MPI
1214  if (m_meshOverlap)
1215  {
1216  // Remove ghosted entities from overlap set
1217  moab::Range ghostedEnts;
1220  MB_CHK_SET_ERR( m_interface->remove_entities( m_meshOverlapSet, ghostedEnts ),
1221  "Deleting ghosted entities failed" );
1222  }
1223 #endif
1224  // Verify consistency, conservation and monotonicity, globally
1225  if( !mapOptions.fNoCheck )
1226  {
1227  if( is_root ) dbgprint.printf( 0, "Verifying map" );
1228  this->IsConsistent( 1.0e-8 );
1229  if( !mapOptions.fNoConservation ) this->IsConservative( 1.0e-8 );
1230 
1231  if( nMonotoneType != 0 )
1232  {
1233  this->IsMonotone( 1.0e-12 );
1234  }
1235  }
1236  }
1237  catch( Exception& e )
1238  {
1239  dbgprint.printf( 0, "%s", e.ToString().c_str() );
1240  return ( moab::MB_FAILURE );
1241  }
1242  catch( ... )
1243  {
1244  return ( moab::MB_FAILURE );
1245  }
1246  return moab::MB_SUCCESS;
1247 }

References dbgprint, moab::error(), ErrorCode, MB_ALREADY_ALLOCATED, MB_CHK_ERR, MB_CHK_SET_ERR, MB_SUCCESS, MB_TAG_CREAT, MB_TAG_DENSE, MB_TAG_EXCL, MB_TYPE_DOUBLE, and moab::Remapper::OverlapMesh.

Referenced by main().

◆ GetColDofMap()

const std::vector< int >& moab::TempestOnlineMap::GetColDofMap ( ) const
inline

Definition at line 502 of file TempestOnlineMap.hpp.

502 { return col_dtoc_dofmap; }

References col_dtoc_dofmap.

◆ GetColGlobalDoF()

int moab::TempestOnlineMap::GetColGlobalDoF ( int  localID) const
inline

Get the global Degrees-Of-Freedom ID on the source mesh.

Definition at line 621 of file TempestOnlineMap.hpp.

622 {
623  return col_gdofmap[localColID];
624 }

◆ GetDestinationGlobalNDofs()

int moab::TempestOnlineMap::GetDestinationGlobalNDofs ( )

Get the number of total Degrees-Of-Freedom defined on the destination mesh.

◆ GetDestinationLocalNDofs()

int moab::TempestOnlineMap::GetDestinationLocalNDofs ( )

Get the number of local Degrees-Of-Freedom defined on the destination mesh.

◆ GetDestinationNDofsPerElement()

int moab::TempestOnlineMap::GetDestinationNDofsPerElement ( )
inline

Get the number of Degrees-Of-Freedom per element on the destination mesh.

Definition at line 639 of file TempestOnlineMap.hpp.

640 {
641  return m_nDofsPEl_Dest;
642 }

◆ GetGlobalSourceAreas()

const DataArray1D< double >& moab::TempestOnlineMap::GetGlobalSourceAreas ( ) const

If we computed the reduction, get the vector representing the source areas for all entities in the mesh

◆ GetGlobalTargetAreas()

const DataArray1D< double >& moab::TempestOnlineMap::GetGlobalTargetAreas ( ) const

If we computed the reduction, get the vector representing the target areas for all entities in the mesh

◆ GetIndexOfColGlobalDoF()

int moab::TempestOnlineMap::GetIndexOfColGlobalDoF ( int  globalColDoF) const
inline

Get the index of globaColDoF.

Definition at line 626 of file TempestOnlineMap.hpp.

627 {
628  return globalColDoF + 1; // temporary
629 }

◆ GetIndexOfRowGlobalDoF()

int moab::TempestOnlineMap::GetIndexOfRowGlobalDoF ( int  globalRowDoF) const
inline

Get the index of globaRowDoF.

Definition at line 615 of file TempestOnlineMap.hpp.

616 {
617  return globalRowDoF + 1;
618 }

◆ GetRowDofMap()

const std::vector< int >& moab::TempestOnlineMap::GetRowDofMap ( ) const
inline

Read-only access to the matrix-row -> matrix-col DOF index maps. Used by callers (e.g. iMOAB diagnostic helpers) that need to translate matrix indices back into source/target tag-vector indices.

Definition at line 501 of file TempestOnlineMap.hpp.

501 { return row_dtoc_dofmap; }

References row_dtoc_dofmap.

◆ GetRowGlobalDoF()

int moab::TempestOnlineMap::GetRowGlobalDoF ( int  localID) const
inline

Get the global Degrees-Of-Freedom ID on the destination mesh.

Definition at line 610 of file TempestOnlineMap.hpp.

611 {
612  return row_gdofmap[localRowID];
613 }

◆ GetSourceGlobalNDofs()

int moab::TempestOnlineMap::GetSourceGlobalNDofs ( )

Get the number of total Degrees-Of-Freedom defined on the source mesh.

◆ GetSourceLocalNDofs()

int moab::TempestOnlineMap::GetSourceLocalNDofs ( )

Get the number of local Degrees-Of-Freedom defined on the source mesh.

◆ GetSourceNDofsPerElement()

int moab::TempestOnlineMap::GetSourceNDofsPerElement ( )
inline

Get the number of Degrees-Of-Freedom per element on the source mesh.

Definition at line 632 of file TempestOnlineMap.hpp.

633 {
634  return m_nDofsPEl_Src;
635 }

◆ GlobalSourceDofCount()

int moab::TempestOnlineMap::GlobalSourceDofCount ( ) const
inline

Global number of source DoFs declared by the map file (n_a), or -1 if unknown.

Definition at line 476 of file TempestOnlineMap.hpp.

476 { return m_nTotDofs_SrcGlobal; }

References m_nTotDofs_SrcGlobal.

◆ IsConservative()

int moab::TempestOnlineMap::IsConservative ( double  dTolerance)
virtual

Determine if the map is conservative.

Definition at line 1297 of file TempestOnlineMap.cpp.

1298 {
1299 #ifndef MOAB_HAVE_MPI
1300 
1301  return OfflineMap::IsConservative( dTolerance );
1302 
1303 #else
1304  // return OfflineMap::IsConservative(dTolerance);
1305 
1306  int ierr;
1307  // Get map entries
1308  DataArray1D< int > dataRows;
1309  DataArray1D< int > dataCols;
1310  DataArray1D< double > dataEntries;
1311  const DataArray1D< double >& dTargetAreas = this->GetTargetAreas();
1312  const DataArray1D< double >& dSourceAreas = this->GetSourceAreas();
1313 
1314  // Calculate column sums
1315  std::vector< int > dColumnsUnique;
1316  std::vector< double > dColumnSums;
1317 
1318  int nColumns = m_mapRemap.GetColumns();
1319  m_mapRemap.GetEntries( dataRows, dataCols, dataEntries );
1320  dColumnSums.resize( m_nTotDofs_SrcCov, 0.0 );
1321  dColumnsUnique.resize( m_nTotDofs_SrcCov, -1 );
1322 
1323  for( unsigned i = 0; i < dataEntries.GetRows(); i++ )
1324  {
1325  dColumnSums[dataCols[i]] += dataEntries[i] * dTargetAreas[dataRows[i]] / dSourceAreas[dataCols[i]];
1326 
1327  assert( dataCols[i] < m_nTotDofs_SrcCov );
1328 
1329  // GID for column DoFs: col_gdofmap[ col_ldofmap [ dataCols[i] ] ]
1330  int colGID = this->GetColGlobalDoF( dataCols[i] ); // col_gdofmap[ col_ldofmap [ dataCols[i] ] ];
1331  // int colGID = col_gdofmap[ col_ldofmap [ dataCols[i] ] ];
1332  dColumnsUnique[dataCols[i]] = colGID;
1333 
1334  // std::cout << "Column dataCols[i]=" << dataCols[i] << " with GID = " << colGID <<
1335  // std::endl;
1336  }
1337 
1338  int rootProc = 0;
1339  std::vector< int > nElementsInProc;
1340  const int nDATA = 3;
1341  nElementsInProc.resize( size * nDATA );
1342  int senddata[nDATA] = { nColumns, m_nTotDofs_SrcCov, m_nTotDofs_Src };
1343  ierr = MPI_Gather( senddata, nDATA, MPI_INT, nElementsInProc.data(), nDATA, MPI_INT, rootProc, m_pcomm->comm() );
1344  if( ierr != MPI_SUCCESS ) return -1;
1345 
1346  int nTotVals = 0, nTotColumns = 0; // nTotColumnsUnq = 0;
1347  std::vector< int > dColumnIndices;
1348  std::vector< double > dColumnSumsTotal;
1349  std::vector< int > displs, rcount;
1350  if( rank == rootProc )
1351  {
1352  displs.resize( size + 1, 0 );
1353  rcount.resize( size, 0 );
1354  int gsum = 0;
1355  for( int ir = 0; ir < size; ++ir )
1356  {
1357  nTotVals += nElementsInProc[ir * nDATA];
1358  nTotColumns += nElementsInProc[ir * nDATA + 1];
1359  // nTotColumnsUnq += nElementsInProc[ir * nDATA + 2];
1360 
1361  displs[ir] = gsum;
1362  rcount[ir] = nElementsInProc[ir * nDATA + 1];
1363  gsum += rcount[ir];
1364 
1365  // printf( "%d: nTotColumns: %d, Displs: %d, rcount: %d, gsum = %d\n", ir, nTotColumns, displs[ir], rcount[ir], gsum );
1366  }
1367 
1368  printf( "Total nnz: %d, global source elements = %d\n", nTotVals, gsum );
1369 
1370  dColumnIndices.resize( nTotColumns, -1 );
1371  dColumnSumsTotal.resize( nTotColumns, 0.0 );
1372  // dColumnSourceAreas.resize ( nTotColumns, 0.0 );
1373  }
1374 
1375  // Gather all ColumnSums to root process and accumulate
1376  // We expect that the sums of all columns equate to 1.0 within user specified tolerance
1377  // Need to do a gatherv here since different processes have different number of elements
1378  // MPI_Reduce(&dColumnSums[0], &dColumnSumsTotal[0], m_mapRemap.GetColumns(), MPI_DOUBLE,
1379  // MPI_SUM, 0, m_pcomm->comm());
1380  // Use .data() rather than &vec[0] -- on non-root ranks dColumnIndices /
1381  // dColumnSumsTotal are empty (only resized on root, see ~10 lines above),
1382  // and &vec[0] indexing into an empty vector is undefined behavior. The
1383  // .data() form returns nullptr for an empty vector, which MPI_Gatherv
1384  // ignores since recvcount on non-root paths is effectively zero.
1385  ierr = MPI_Gatherv( dColumnsUnique.data(), m_nTotDofs_SrcCov, MPI_INT, dColumnIndices.data(), rcount.data(),
1386  displs.data(), MPI_INT, rootProc, m_pcomm->comm() );
1387  if( ierr != MPI_SUCCESS ) return -1;
1388  ierr = MPI_Gatherv( dColumnSums.data(), m_nTotDofs_SrcCov, MPI_DOUBLE, dColumnSumsTotal.data(), rcount.data(),
1389  displs.data(), MPI_DOUBLE, rootProc, m_pcomm->comm() );
1390  if( ierr != MPI_SUCCESS ) return -1;
1391  // ierr = MPI_Gatherv ( &dSourceAreas[0], m_nTotDofs_SrcCov, MPI_DOUBLE, &dColumnSourceAreas[0],
1392  // rcount.data(), displs.data(), MPI_DOUBLE, rootProc, m_pcomm->comm() ); if ( ierr !=
1393  // MPI_SUCCESS ) return -1;
1394 
1395  // Clean out unwanted arrays now
1396  dColumnSums.clear();
1397  dColumnsUnique.clear();
1398 
1399  // Verify all column sums equal the input Jacobian
1400  int fConservative = 0;
1401  if( rank == rootProc )
1402  {
1403  displs[size] = ( nTotColumns );
1404  // std::vector<double> dColumnSumsOnRoot(nTotColumnsUnq, 0.0);
1405  std::map< int, double > dColumnSumsOnRoot;
1406  // std::map<int, double> dColumnSourceAreasOnRoot;
1407  for( int ir = 0; ir < size; ir++ )
1408  {
1409  for( int ips = displs[ir]; ips < displs[ir + 1]; ips++ )
1410  {
1411  if( dColumnIndices[ips] < 0 ) continue;
1412  // printf("%d, %d: dColumnIndices[ips]: %d\n", ir, ips, dColumnIndices[ips]);
1413  // assert( dColumnIndices[ips] < nTotColumnsUnq );
1414  dColumnSumsOnRoot[dColumnIndices[ips]] += dColumnSumsTotal[ips]; // / dColumnSourceAreas[ips];
1415  // dColumnSourceAreasOnRoot[ dColumnIndices[ips] ] = dColumnSourceAreas[ips];
1416  // dColumnSourceAreas[ dColumnIndices[ips] ]
1417  }
1418  }
1419 
1420  for( std::map< int, double >::iterator it = dColumnSumsOnRoot.begin(); it != dColumnSumsOnRoot.end(); ++it )
1421  {
1422  // if ( fabs ( it->second - dColumnSourceAreasOnRoot[it->first] ) > dTolerance )
1423  if( fabs( it->second - 1.0 ) > dTolerance )
1424  {
1425  fConservative++;
1426  Announce( "TempestOnlineMap is not conservative in column "
1427  // "%i (%1.15e)", it->first, it->second );
1428  "%i (%1.15e)",
1429  it->first, it->second /* / dColumnSourceAreasOnRoot[it->first] */ );
1430  }
1431  }
1432  }
1433 
1434  // TODO: Just do a broadcast from root instead of a reduction
1435  ierr = MPI_Bcast( &fConservative, 1, MPI_INT, rootProc, m_pcomm->comm() );
1436  if( ierr != MPI_SUCCESS ) return -1;
1437 
1438  return fConservative;
1439 #endif
1440 }

◆ IsConsistent()

int moab::TempestOnlineMap::IsConsistent ( double  dTolerance)
virtual

Determine if the map is first-order accurate.

Definition at line 1251 of file TempestOnlineMap.cpp.

1252 {
1253 #ifndef MOAB_HAVE_MPI
1254 
1255  return OfflineMap::IsConsistent( dTolerance );
1256 
1257 #else
1258 
1259  // Get map entries
1260  DataArray1D< int > dataRows;
1261  DataArray1D< int > dataCols;
1262  DataArray1D< double > dataEntries;
1263 
1264  // Calculate row sums
1265  DataArray1D< double > dRowSums;
1266  m_mapRemap.GetEntries( dataRows, dataCols, dataEntries );
1267  dRowSums.Allocate( m_mapRemap.GetRows() );
1268 
1269  for( unsigned i = 0; i < dataRows.GetRows(); i++ )
1270  {
1271  dRowSums[dataRows[i]] += dataEntries[i];
1272  }
1273 
1274  // Verify all row sums are equal to 1
1275  int fConsistent = 0;
1276  for( unsigned i = 0; i < dRowSums.GetRows(); i++ )
1277  {
1278  if( fabs( dRowSums[i] - 1.0 ) > dTolerance )
1279  {
1280  fConsistent++;
1281  int rowGID = row_gdofmap[i];
1282  Announce( "TempestOnlineMap is not consistent in row %i (%1.15e)", rowGID, dRowSums[i] );
1283  }
1284  }
1285 
1286  int ierr;
1287  int fConsistentGlobal = 0;
1288  ierr = MPI_Allreduce( &fConsistent, &fConsistentGlobal, 1, MPI_INT, MPI_SUM, m_pcomm->comm() );
1289  if( ierr != MPI_SUCCESS ) return -1;
1290 
1291  return fConsistentGlobal;
1292 #endif
1293 }

◆ IsMonotone()

int moab::TempestOnlineMap::IsMonotone ( double  dTolerance)
virtual

Determine if the map is monotone.

Definition at line 1444 of file TempestOnlineMap.cpp.

1445 {
1446 #ifndef MOAB_HAVE_MPI
1447 
1448  return OfflineMap::IsMonotone( dTolerance );
1449 
1450 #else
1451 
1452  // Get map entries
1453  DataArray1D< int > dataRows;
1454  DataArray1D< int > dataCols;
1455  DataArray1D< double > dataEntries;
1456 
1457  m_mapRemap.GetEntries( dataRows, dataCols, dataEntries );
1458 
1459  // Verify all entries are in the range [0,1]
1460  int fMonotone = 0;
1461  for( unsigned i = 0; i < dataRows.GetRows(); i++ )
1462  {
1463  if( ( dataEntries[i] < -dTolerance ) || ( dataEntries[i] > 1.0 + dTolerance ) )
1464  {
1465  fMonotone++;
1466 
1467  Announce( "TempestOnlineMap is not monotone in entry (%i): %1.15e", i, dataEntries[i] );
1468  }
1469  }
1470 
1471  int ierr;
1472  int fMonotoneGlobal = 0;
1473  ierr = MPI_Allreduce( &fMonotone, &fMonotoneGlobal, 1, MPI_INT, MPI_SUM, m_pcomm->comm() );
1474  if( ierr != MPI_SUCCESS ) return -1;
1475 
1476  return fMonotoneGlobal;
1477 #endif
1478 }

◆ LinearRemapFVtoFV_Tempest_MOAB()

void moab::TempestOnlineMap::LinearRemapFVtoFV_Tempest_MOAB ( int  nOrder)
private

Compute the remapping weights for a FV field defined on the source to a FV field defined on the target mesh.

Definition at line 121 of file TempestLinearRemap.cpp.

122 {
123  // Order of triangular quadrature rule
124  const int TriQuadRuleOrder = 4;
125 
126  // Verify ReverseNodeArray has been calculated
127  if( m_meshInputCov->faces.size() > 0 && m_meshInputCov->revnodearray.size() == 0 )
128  {
129  _EXCEPTIONT( "ReverseNodeArray has not been calculated for m_meshInputCov" );
130  }
131 
132  // Triangular quadrature rule
133  TriangularQuadratureRule triquadrule( TriQuadRuleOrder );
134 
135  // Number of coefficients needed at this order
136 #ifdef RECTANGULAR_TRUNCATION
137  int nCoefficients = nOrder * nOrder;
138 #endif
139 #ifdef TRIANGULAR_TRUNCATION
140  int nCoefficients = nOrder * ( nOrder + 1 ) / 2;
141 #endif
142 
143  // Number of faces you need
144  const int nRequiredFaceSetSize = nCoefficients;
145 
146  // Fit weight exponent
147  const int nFitWeightsExponent = nOrder + 2;
148 
149  // Announcements
150  moab::DebugOutput dbgprint( std::cout, this->rank, 0 );
151  dbgprint.set_prefix( "[LinearRemapFVtoFV_Tempest_MOAB]: " );
152  if( is_root )
153  {
154  dbgprint.printf( 0, "Finite Volume to Finite Volume Projection\n" );
155  dbgprint.printf( 0, "Triangular quadrature rule order %i\n", TriQuadRuleOrder );
156  dbgprint.printf( 0, "Number of coefficients: %i\n", nCoefficients );
157  dbgprint.printf( 0, "Required adjacency set size: %i\n", nRequiredFaceSetSize );
158  dbgprint.printf( 0, "Fit weights exponent: %i\n", nFitWeightsExponent );
159  }
160 
161  // Current overlap face
162  int ixOverlap = 0;
163 #ifdef VERBOSE
164  const unsigned outputFrequency = ( m_meshInputCov->faces.size() / 10 ) + 1;
165 #endif
166  DataArray2D< double > dIntArray;
167  DataArray1D< double > dConstraint( nCoefficients );
168 
169  // Loop through all faces on m_meshInputCov
170  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
171  {
172  // Output every 1000 elements
173 #ifdef VERBOSE
174  if( ixFirst % outputFrequency == 0 && is_root )
175  {
176  dbgprint.printf( 0, "Element %zu/%lu\n", ixFirst, m_meshInputCov->faces.size() );
177  }
178 #endif
179  // Find the set of Faces that overlap faceFirst
180  int ixOverlapBegin = ixOverlap;
181  unsigned ixOverlapEnd = ixOverlapBegin;
182 
183  for( ; ixOverlapEnd < m_meshOverlap->faces.size(); ixOverlapEnd++ )
184  {
185  if( ixFirst - m_meshOverlap->vecSourceFaceIx[ixOverlapEnd] != 0 ) break;
186  }
187 
188  unsigned nOverlapFaces = ixOverlapEnd - ixOverlapBegin;
189 
190  if( nOverlapFaces == 0 ) continue;
191 
192  // Build integration array
193  BuildIntegrationArray( *m_meshInputCov, *m_meshOverlap, triquadrule, ixFirst, ixOverlapBegin, ixOverlapEnd,
194  nOrder, dIntArray );
195 
196  // Set of Faces to use in building the reconstruction and associated
197  // distance metric.
198  AdjacentFaceVector vecAdjFaces;
199 
200  GetAdjacentFaceVectorByEdge( *m_meshInputCov, ixFirst, nRequiredFaceSetSize, vecAdjFaces );
201 
202  // Number of adjacent Faces
203  int nAdjFaces = vecAdjFaces.size();
204 
205  // Determine the conservative constraint equation
206  double dFirstArea = m_meshInputCov->vecFaceArea[ixFirst];
207  dConstraint.Zero();
208  for( int p = 0; p < nCoefficients; p++ )
209  {
210  for( unsigned j = 0; j < nOverlapFaces; j++ )
211  {
212  dConstraint[p] += dIntArray[p][j];
213  }
214  dConstraint[p] /= dFirstArea;
215  }
216 
217  // Build the fit array from the integration operator
218  DataArray2D< double > dFitArray;
219  DataArray1D< double > dFitWeights;
220  DataArray2D< double > dFitArrayPlus;
221 
222  BuildFitArray( *m_meshInputCov, triquadrule, ixFirst, vecAdjFaces, nOrder, nFitWeightsExponent, dConstraint,
223  dFitArray, dFitWeights );
224 
225  // Compute the inverse fit array
226  bool fSuccess = InvertFitArray_Corrected( dConstraint, dFitArray, dFitWeights, dFitArrayPlus );
227 
228  // Multiply integration array and fit array
229  DataArray2D< double > dComposedArray( nAdjFaces, nOverlapFaces );
230  if( fSuccess )
231  {
232  // Multiply integration array and inverse fit array
233  for( int i = 0; i < nAdjFaces; i++ )
234  {
235  for( size_t j = 0; j < nOverlapFaces; j++ )
236  {
237  for( int k = 0; k < nCoefficients; k++ )
238  {
239  dComposedArray( i, j ) += dIntArray( k, j ) * dFitArrayPlus( i, k );
240  }
241  }
242  }
243 
244  // Unable to invert fit array, drop to 1st order. In this case
245  // dFitArrayPlus(0,0) = 1 and all other entries are zero.
246  }
247  else
248  {
249  dComposedArray.Zero();
250  for( size_t j = 0; j < nOverlapFaces; j++ )
251  {
252  dComposedArray( 0, j ) += dIntArray( 0, j );
253  }
254  }
255 
256  // Put composed array into map
257  for( unsigned i = 0; i < vecAdjFaces.size(); i++ )
258  {
259  for( unsigned j = 0; j < nOverlapFaces; j++ )
260  {
261  int& ixFirstFaceLoc = vecAdjFaces[i].first;
262  int& ixSecondFaceLoc = m_meshOverlap->vecTargetFaceIx[ixOverlap + j];
263  // int ixFirstFaceGlob = m_remapper->GetGlobalID(moab::Remapper::SourceMesh,
264  // ixFirstFaceLoc); int ixSecondFaceGlob =
265  // m_remapper->GetGlobalID(moab::Remapper::TargetMesh, ixSecondFaceLoc);
266 
267  // signal to not participate, because it is a ghost target
268  if( ixSecondFaceLoc < 0 ) continue; // do not do anything
269 
270  m_mapRemap( ixSecondFaceLoc, ixFirstFaceLoc ) +=
271  dComposedArray[i][j] / m_meshOutput->vecFaceArea[ixSecondFaceLoc];
272  }
273  }
274 
275  // Increment the current overlap index
276  ixOverlap += nOverlapFaces;
277  }
278 
279  return;
280 }

References dbgprint.

◆ LinearRemapFVtoGLL_MOAB()

void moab::TempestOnlineMap::LinearRemapFVtoGLL_MOAB ( const DataArray3D< int > &  dataGLLNodes,
const DataArray3D< double > &  dataGLLJacobian,
const DataArray1D< double > &  dataGLLNodalArea,
int  nOrder,
int  nMonotoneType,
bool  fContinuous,
bool  fNoConservation 
)
private

Generate the OfflineMap for remapping from finite volumes to finite elements.

◆ LinearRemapGLLtoGLL2_MOAB()

void moab::TempestOnlineMap::LinearRemapGLLtoGLL2_MOAB ( const DataArray3D< int > &  dataGLLNodesIn,
const DataArray3D< double > &  dataGLLJacobianIn,
const DataArray3D< int > &  dataGLLNodesOut,
const DataArray3D< double > &  dataGLLJacobianOut,
const DataArray1D< double > &  dataNodalAreaOut,
int  nPin,
int  nPout,
int  nMonotoneType,
bool  fContinuousIn,
bool  fContinuousOut,
bool  fNoConservation 
)
private

Generate the OfflineMap for remapping from finite elements to finite elements.

Definition at line 1303 of file TempestLinearRemap.cpp.

1314 {
1315  // Triangular quadrature rule
1316  TriangularQuadratureRule triquadrule( 8 );
1317 
1318  const DataArray2D< double >& dG = triquadrule.GetG();
1319  const DataArray1D< double >& dW = triquadrule.GetW();
1320 
1321  // Get SparseMatrix represntation of the OfflineMap
1322  SparseMatrix< double >& smatMap = this->GetSparseMatrix();
1323 
1324  // Sample coefficients
1325  DataArray2D< double > dSampleCoeffIn( nPin, nPin );
1326  DataArray2D< double > dSampleCoeffOut( nPout, nPout );
1327 
1328  // Announcemnets
1329  moab::DebugOutput dbgprint( std::cout, this->rank, 0 );
1330  dbgprint.set_prefix( "[LinearRemapGLLtoGLL2_MOAB]: " );
1331  if( is_root )
1332  {
1333  dbgprint.printf( 0, "Finite Element to Finite Element Projection\n" );
1334  dbgprint.printf( 0, "Order of the input FE polynomial interpolant: %i\n", nPin );
1335  dbgprint.printf( 0, "Order of the output FE polynomial interpolant: %i\n", nPout );
1336  }
1337 
1338  // Build the integration array for each element on m_meshOverlap
1339  DataArray3D< double > dGlobalIntArray( nPin * nPin, m_meshOverlap->faces.size(), nPout * nPout );
1340 
1341  // Number of overlap Faces per source Face
1342  DataArray1D< int > nAllOverlapFaces( m_meshInputCov->faces.size() );
1343 
1344  int ixOverlap = 0;
1345  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
1346  {
1347  // Determine how many overlap Faces and triangles are present
1348  int nOverlapFaces = 0;
1349  size_t ixOverlapTemp = ixOverlap;
1350  for( ; ixOverlapTemp < m_meshOverlap->faces.size(); ixOverlapTemp++ )
1351  {
1352  // const Face & faceOverlap = m_meshOverlap->faces[ixOverlapTemp];
1353  if( ixFirst - m_meshOverlap->vecSourceFaceIx[ixOverlapTemp] != 0 )
1354  {
1355  break;
1356  }
1357 
1358  nOverlapFaces++;
1359  }
1360 
1361  nAllOverlapFaces[ixFirst] = nOverlapFaces;
1362 
1363  // Increment the current overlap index
1364  ixOverlap += nAllOverlapFaces[ixFirst];
1365  }
1366 
1367  // Geometric area of each output node
1368  DataArray2D< double > dGeometricOutputArea( m_meshOutput->faces.size(), nPout * nPout );
1369 
1370  // Area of each overlap element in the output basis
1371  DataArray2D< double > dOverlapOutputArea( m_meshOverlap->faces.size(), nPout * nPout );
1372 
1373  // Loop through all faces on m_meshInputCov
1374  ixOverlap = 0;
1375 #ifdef VERBOSE
1376  const unsigned outputFrequency = ( m_meshInputCov->faces.size() / 10 ) + 1;
1377 #endif
1378  if( is_root ) dbgprint.printf( 0, "Building conservative distribution maps\n" );
1379 
1380  // generic triangle used for area computation, for triangles around the center of overlap face;
1381  // used for overlap faces with more than 4 edges;
1382  // nodes array will be set for each triangle;
1383  // these triangles are not part of the mesh structure, they are just temporary during
1384  // aforementioned decomposition.
1385  Face faceTri( 3 );
1386  NodeVector nodes( 3 );
1387  faceTri.SetNode( 0, 0 );
1388  faceTri.SetNode( 1, 1 );
1389  faceTri.SetNode( 2, 2 );
1390 
1391  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
1392  {
1393 #ifdef VERBOSE
1394  // Announce computation progress
1395  if( ixFirst % outputFrequency == 0 && is_root )
1396  {
1397  dbgprint.printf( 0, "Element %zu/%lu\n", ixFirst, m_meshInputCov->faces.size() );
1398  }
1399 #endif
1400  // Quantities from the First Mesh
1401  const Face& faceFirst = m_meshInputCov->faces[ixFirst];
1402 
1403  const NodeVector& nodesFirst = m_meshInputCov->nodes;
1404 
1405  // Number of overlapping Faces and triangles
1406  int nOverlapFaces = nAllOverlapFaces[ixFirst];
1407 
1408  if( !nOverlapFaces ) continue;
1409 
1410  // // Calculate total element Jacobian
1411  // double dTotalJacobian = 0.0;
1412  // for (int s = 0; s < nPin; s++) {
1413  // for (int t = 0; t < nPin; t++) {
1414  // dTotalJacobian += dataGLLJacobianIn[s][t][ixFirst];
1415  // }
1416  // }
1417 
1418  // Loop through all Overlap Faces
1419  for( int i = 0; i < nOverlapFaces; i++ )
1420  {
1421  // Quantities from the overlap Mesh
1422  const Face& faceOverlap = m_meshOverlap->faces[ixOverlap + i];
1423 
1424  const NodeVector& nodesOverlap = m_meshOverlap->nodes;
1425 
1426  // Quantities from the Second Mesh
1427  int ixSecond = m_meshOverlap->vecTargetFaceIx[ixOverlap + i];
1428 
1429  // signal to not participate, because it is a ghost target
1430  if( ixSecond < 0 ) continue; // do not do anything
1431 
1432  const NodeVector& nodesSecond = m_meshOutput->nodes;
1433 
1434  const Face& faceSecond = m_meshOutput->faces[ixSecond];
1435 
1436  int nbEdges = faceOverlap.edges.size();
1437  int nOverlapTriangles = 1;
1438  Node center; // not used if nbEdges == 3
1439  if( nbEdges > 3 )
1440  { // decompose from center in this case
1441  nOverlapTriangles = nbEdges;
1442  for( int k = 0; k < nbEdges; k++ )
1443  {
1444  const Node& node = nodesOverlap[faceOverlap[k]];
1445  center = center + node;
1446  }
1447  center = center / nbEdges;
1448  center = center.Normalized(); // project back on sphere of radius 1
1449  }
1450 
1451  Node node0, node1, node2;
1452  double dTriArea;
1453 
1454  // Loop over all sub-triangles of this Overlap Face
1455  for( int j = 0; j < nOverlapTriangles; j++ )
1456  {
1457  if( nbEdges == 3 ) // will come here only once, nOverlapTriangles == 1 in this case
1458  {
1459  node0 = nodesOverlap[faceOverlap[0]];
1460  node1 = nodesOverlap[faceOverlap[1]];
1461  node2 = nodesOverlap[faceOverlap[2]];
1462  dTriArea = CalculateFaceArea( faceOverlap, nodesOverlap );
1463  }
1464  else // decompose polygon in triangles around the center
1465  {
1466  node0 = center;
1467  node1 = nodesOverlap[faceOverlap[j]];
1468  int j1 = ( j + 1 ) % nbEdges;
1469  node2 = nodesOverlap[faceOverlap[j1]];
1470  nodes[0] = center;
1471  nodes[1] = node1;
1472  nodes[2] = node2;
1473  dTriArea = CalculateFaceArea( faceTri, nodes );
1474  }
1475 
1476  for( int k = 0; k < triquadrule.GetPoints(); k++ )
1477  {
1478  // Get the nodal location of this point
1479  double dX[3];
1480 
1481  dX[0] = dG( k, 0 ) * node0.x + dG( k, 1 ) * node1.x + dG( k, 2 ) * node2.x;
1482  dX[1] = dG( k, 0 ) * node0.y + dG( k, 1 ) * node1.y + dG( k, 2 ) * node2.y;
1483  dX[2] = dG( k, 0 ) * node0.z + dG( k, 1 ) * node1.z + dG( k, 2 ) * node2.z;
1484 
1485  double dMag = sqrt( dX[0] * dX[0] + dX[1] * dX[1] + dX[2] * dX[2] );
1486 
1487  dX[0] /= dMag;
1488  dX[1] /= dMag;
1489  dX[2] /= dMag;
1490 
1491  Node nodeQuadrature( dX[0], dX[1], dX[2] );
1492 
1493  // Find the components of this quadrature point in the basis
1494  // of the first Face.
1495  double dAlphaIn;
1496  double dBetaIn;
1497 
1498  ApplyInverseMap( faceFirst, nodesFirst, nodeQuadrature, dAlphaIn, dBetaIn );
1499 
1500  // Find the components of this quadrature point in the basis
1501  // of the second Face.
1502  double dAlphaOut;
1503  double dBetaOut;
1504 
1505  ApplyInverseMap( faceSecond, nodesSecond, nodeQuadrature, dAlphaOut, dBetaOut );
1506 
1507  /*
1508  // Check inverse map value
1509  if ((dAlphaIn < 0.0) || (dAlphaIn > 1.0) ||
1510  (dBetaIn < 0.0) || (dBetaIn > 1.0)
1511  ) {
1512  _EXCEPTION2("Inverse Map out of range (%1.5e %1.5e)",
1513  dAlphaIn, dBetaIn);
1514  }
1515 
1516  // Check inverse map value
1517  if ((dAlphaOut < 0.0) || (dAlphaOut > 1.0) ||
1518  (dBetaOut < 0.0) || (dBetaOut > 1.0)
1519  ) {
1520  _EXCEPTION2("Inverse Map out of range (%1.5e %1.5e)",
1521  dAlphaOut, dBetaOut);
1522  }
1523  */
1524  // Sample the First finite element at this point
1525  SampleGLLFiniteElement( nMonotoneType, nPin, dAlphaIn, dBetaIn, dSampleCoeffIn );
1526 
1527  // Sample the Second finite element at this point
1528  SampleGLLFiniteElement( nMonotoneType, nPout, dAlphaOut, dBetaOut, dSampleCoeffOut );
1529 
1530  // Overlap output area
1531  for( int s = 0; s < nPout; s++ )
1532  {
1533  for( int t = 0; t < nPout; t++ )
1534  {
1535  double dNodeArea = dSampleCoeffOut[s][t] * dW[k] * dTriArea;
1536 
1537  dOverlapOutputArea[ixOverlap + i][s * nPout + t] += dNodeArea;
1538 
1539  dGeometricOutputArea[ixSecond][s * nPout + t] += dNodeArea;
1540  }
1541  }
1542 
1543  // Compute overlap integral
1544  int ixp = 0;
1545  for( int p = 0; p < nPin; p++ )
1546  {
1547  for( int q = 0; q < nPin; q++ )
1548  {
1549  int ixs = 0;
1550  for( int s = 0; s < nPout; s++ )
1551  {
1552  for( int t = 0; t < nPout; t++ )
1553  {
1554  // Sample the Second finite element at this point
1555  dGlobalIntArray[ixp][ixOverlap + i][ixs] +=
1556  dSampleCoeffOut[s][t] * dSampleCoeffIn[p][q] * dW[k] * dTriArea;
1557 
1558  ixs++;
1559  }
1560  }
1561 
1562  ixp++;
1563  }
1564  }
1565  }
1566  }
1567  }
1568 
1569  // Coefficients
1570  DataArray2D< double > dCoeff( nOverlapFaces * nPout * nPout, nPin * nPin );
1571 
1572  for( int i = 0; i < nOverlapFaces; i++ )
1573  {
1574  // int ixSecondFace = m_meshOverlap->vecTargetFaceIx[ixOverlap + i];
1575 
1576  int ixp = 0;
1577  for( int p = 0; p < nPin; p++ )
1578  {
1579  for( int q = 0; q < nPin; q++ )
1580  {
1581  int ixs = 0;
1582  for( int s = 0; s < nPout; s++ )
1583  {
1584  for( int t = 0; t < nPout; t++ )
1585  {
1586  dCoeff[i * nPout * nPout + ixs][ixp] = dGlobalIntArray[ixp][ixOverlap + i][ixs] /
1587  dOverlapOutputArea[ixOverlap + i][s * nPout + t];
1588 
1589  ixs++;
1590  }
1591  }
1592 
1593  ixp++;
1594  }
1595  }
1596  }
1597 
1598  // Source areas
1599  DataArray1D< double > vecSourceArea( nPin * nPin );
1600 
1601  for( int p = 0; p < nPin; p++ )
1602  {
1603  for( int q = 0; q < nPin; q++ )
1604  {
1605  vecSourceArea[p * nPin + q] = dataGLLJacobianIn[p][q][ixFirst];
1606  }
1607  }
1608 
1609  // Target areas
1610  DataArray1D< double > vecTargetArea( nOverlapFaces * nPout * nPout );
1611 
1612  for( int i = 0; i < nOverlapFaces; i++ )
1613  {
1614  // int ixSecond = m_meshOverlap->vecTargetFaceIx[ixOverlap + i];
1615  int ixs = 0;
1616  for( int s = 0; s < nPout; s++ )
1617  {
1618  for( int t = 0; t < nPout; t++ )
1619  {
1620  vecTargetArea[i * nPout * nPout + ixs] = dOverlapOutputArea[ixOverlap + i][nPout * s + t];
1621 
1622  ixs++;
1623  }
1624  }
1625  }
1626 
1627  // Force consistency and conservation
1628  if( !fNoConservation )
1629  {
1630  ForceIntArrayConsistencyConservation( vecSourceArea, vecTargetArea, dCoeff, ( nMonotoneType != 0 ) );
1631  }
1632 
1633  // Update global coefficients
1634  for( int i = 0; i < nOverlapFaces; i++ )
1635  {
1636  int ixp = 0;
1637  for( int p = 0; p < nPin; p++ )
1638  {
1639  for( int q = 0; q < nPin; q++ )
1640  {
1641  int ixs = 0;
1642  for( int s = 0; s < nPout; s++ )
1643  {
1644  for( int t = 0; t < nPout; t++ )
1645  {
1646  dGlobalIntArray[ixp][ixOverlap + i][ixs] =
1647  dCoeff[i * nPout * nPout + ixs][ixp] * dOverlapOutputArea[ixOverlap + i][s * nPout + t];
1648 
1649  ixs++;
1650  }
1651  }
1652 
1653  ixp++;
1654  }
1655  }
1656  }
1657 
1658 #ifdef VVERBOSE
1659  // Check column sums (conservation)
1660  for( int i = 0; i < nPin * nPin; i++ )
1661  {
1662  double dColSum = 0.0;
1663  for( int j = 0; j < nOverlapFaces * nPout * nPout; j++ )
1664  {
1665  dColSum += dCoeff[j][i] * vecTargetArea[j];
1666  }
1667  printf( "Col %i: %1.15e\n", i, dColSum / vecSourceArea[i] );
1668  }
1669 
1670  // Check row sums (consistency)
1671  for( int j = 0; j < nOverlapFaces * nPout * nPout; j++ )
1672  {
1673  double dRowSum = 0.0;
1674  for( int i = 0; i < nPin * nPin; i++ )
1675  {
1676  dRowSum += dCoeff[j][i];
1677  }
1678  printf( "Row %i: %1.15e\n", j, dRowSum );
1679  }
1680 #endif
1681 
1682  // Increment the current overlap index
1683  ixOverlap += nOverlapFaces;
1684  }
1685 
1686  // Build redistribution map within target element
1687  if( is_root ) dbgprint.printf( 0, "Building redistribution maps on target mesh\n" );
1688  DataArray1D< double > dRedistSourceArea( nPout * nPout );
1689  DataArray1D< double > dRedistTargetArea( nPout * nPout );
1690  std::vector< DataArray2D< double > > dRedistributionMaps;
1691  dRedistributionMaps.resize( m_meshOutput->faces.size() );
1692 
1693  for( size_t ixSecond = 0; ixSecond < m_meshOutput->faces.size(); ixSecond++ )
1694  {
1695  dRedistributionMaps[ixSecond].Allocate( nPout * nPout, nPout * nPout );
1696 
1697  for( int i = 0; i < nPout * nPout; i++ )
1698  {
1699  dRedistributionMaps[ixSecond][i][i] = 1.0;
1700  }
1701 
1702  for( int s = 0; s < nPout * nPout; s++ )
1703  {
1704  dRedistSourceArea[s] = dGeometricOutputArea[ixSecond][s];
1705  }
1706 
1707  for( int s = 0; s < nPout * nPout; s++ )
1708  {
1709  dRedistTargetArea[s] = dataGLLJacobianOut[s / nPout][s % nPout][ixSecond];
1710  }
1711 
1712  if( !fNoConservation )
1713  {
1714  ForceIntArrayConsistencyConservation( dRedistSourceArea, dRedistTargetArea, dRedistributionMaps[ixSecond],
1715  ( nMonotoneType != 0 ) );
1716 
1717  for( int s = 0; s < nPout * nPout; s++ )
1718  {
1719  for( int t = 0; t < nPout * nPout; t++ )
1720  {
1721  dRedistributionMaps[ixSecond][s][t] *= dRedistTargetArea[s] / dRedistSourceArea[t];
1722  }
1723  }
1724  }
1725  }
1726 
1727  // Construct the total geometric area
1728  DataArray1D< double > dTotalGeometricArea( dataNodalAreaOut.GetRows() );
1729  for( size_t ixSecond = 0; ixSecond < m_meshOutput->faces.size(); ixSecond++ )
1730  {
1731  for( int s = 0; s < nPout; s++ )
1732  {
1733  for( int t = 0; t < nPout; t++ )
1734  {
1735  dTotalGeometricArea[dataGLLNodesOut[s][t][ixSecond] - 1] +=
1736  dGeometricOutputArea[ixSecond][s * nPout + t];
1737  }
1738  }
1739  }
1740 
1741  // Compose the integration operator with the output map
1742  ixOverlap = 0;
1743 
1744  if( is_root ) dbgprint.printf( 0, "Assembling map\n" );
1745 
1746  // Map from source DOFs to target DOFs with redistribution applied
1747  DataArray2D< double > dRedistributedOp( nPin * nPin, nPout * nPout );
1748 
1749  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
1750  {
1751 #ifdef VERBOSE
1752  // Announce computation progress
1753  if( ixFirst % outputFrequency == 0 && is_root )
1754  {
1755  dbgprint.printf( 0, "Element %zu/%lu\n", ixFirst, m_meshInputCov->faces.size() );
1756  }
1757 #endif
1758  // Number of overlapping Faces and triangles
1759  int nOverlapFaces = nAllOverlapFaces[ixFirst];
1760 
1761  if( !nOverlapFaces ) continue;
1762 
1763  // Put composed array into map
1764  for( int j = 0; j < nOverlapFaces; j++ )
1765  {
1766  int ixSecondFace = m_meshOverlap->vecTargetFaceIx[ixOverlap + j];
1767 
1768  // signal to not participate, because it is a ghost target
1769  if( ixSecondFace < 0 ) continue; // do not do anything
1770 
1771  dRedistributedOp.Zero();
1772  for( int p = 0; p < nPin * nPin; p++ )
1773  {
1774  for( int s = 0; s < nPout * nPout; s++ )
1775  {
1776  for( int t = 0; t < nPout * nPout; t++ )
1777  {
1778  dRedistributedOp[p][s] +=
1779  dRedistributionMaps[ixSecondFace][s][t] * dGlobalIntArray[p][ixOverlap + j][t];
1780  }
1781  }
1782  }
1783 
1784  int ixp = 0;
1785  for( int p = 0; p < nPin; p++ )
1786  {
1787  for( int q = 0; q < nPin; q++ )
1788  {
1789  int ixFirstNode;
1790  if( fContinuousIn )
1791  {
1792  ixFirstNode = dataGLLNodesIn[p][q][ixFirst] - 1;
1793  }
1794  else
1795  {
1796  ixFirstNode = ixFirst * nPin * nPin + p * nPin + q;
1797  }
1798 
1799  int ixs = 0;
1800  for( int s = 0; s < nPout; s++ )
1801  {
1802  for( int t = 0; t < nPout; t++ )
1803  {
1804  int ixSecondNode;
1805  if( fContinuousOut )
1806  {
1807  ixSecondNode = dataGLLNodesOut[s][t][ixSecondFace] - 1;
1808 
1809  if( !fNoConservation )
1810  {
1811  smatMap( ixSecondNode, ixFirstNode ) +=
1812  dRedistributedOp[ixp][ixs] / dataNodalAreaOut[ixSecondNode];
1813  }
1814  else
1815  {
1816  smatMap( ixSecondNode, ixFirstNode ) +=
1817  dRedistributedOp[ixp][ixs] / dTotalGeometricArea[ixSecondNode];
1818  }
1819  }
1820  else
1821  {
1822  ixSecondNode = ixSecondFace * nPout * nPout + s * nPout + t;
1823 
1824  if( !fNoConservation )
1825  {
1826  smatMap( ixSecondNode, ixFirstNode ) +=
1827  dRedistributedOp[ixp][ixs] / dataGLLJacobianOut[s][t][ixSecondFace];
1828  }
1829  else
1830  {
1831  smatMap( ixSecondNode, ixFirstNode ) +=
1832  dRedistributedOp[ixp][ixs] / dGeometricOutputArea[ixSecondFace][s * nPout + t];
1833  }
1834  }
1835 
1836  ixs++;
1837  }
1838  }
1839 
1840  ixp++;
1841  }
1842  }
1843  }
1844 
1845  // Increment the current overlap index
1846  ixOverlap += nOverlapFaces;
1847  }
1848 
1849  return;
1850 }

References center(), dbgprint, and ForceIntArrayConsistencyConservation().

◆ LinearRemapGLLtoGLL2_Pointwise_MOAB()

void moab::TempestOnlineMap::LinearRemapGLLtoGLL2_Pointwise_MOAB ( const DataArray3D< int > &  dataGLLNodesIn,
const DataArray3D< double > &  dataGLLJacobianIn,
const DataArray3D< int > &  dataGLLNodesOut,
const DataArray3D< double > &  dataGLLJacobianOut,
const DataArray1D< double > &  dataNodalAreaOut,
int  nPin,
int  nPout,
int  nMonotoneType,
bool  fContinuousIn,
bool  fContinuousOut 
)
private

Generate the OfflineMap for remapping from finite elements to finite elements (pointwise interpolation).

Definition at line 1854 of file TempestLinearRemap.cpp.

1864 {
1865  // Gauss-Lobatto quadrature within Faces
1866  DataArray1D< double > dGL;
1867  DataArray1D< double > dWL;
1868 
1869  GaussLobattoQuadrature::GetPoints( nPout, 0.0, 1.0, dGL, dWL );
1870 
1871  // Get SparseMatrix represntation of the OfflineMap
1872  SparseMatrix< double >& smatMap = this->GetSparseMatrix();
1873 
1874  // Sample coefficients
1875  DataArray2D< double > dSampleCoeffIn( nPin, nPin );
1876 
1877  // Announcemnets
1878  moab::DebugOutput dbgprint( std::cout, this->rank, 0 );
1879  dbgprint.set_prefix( "[LinearRemapGLLtoGLL2_Pointwise_MOAB]: " );
1880  if( is_root )
1881  {
1882  dbgprint.printf( 0, "Finite Element to Finite Element (Pointwise) Projection\n" );
1883  dbgprint.printf( 0, "Order of the input FE polynomial interpolant: %i\n", nPin );
1884  dbgprint.printf( 0, "Order of the output FE polynomial interpolant: %i\n", nPout );
1885  }
1886 
1887  // Number of overlap Faces per source Face
1888  DataArray1D< int > nAllOverlapFaces( m_meshInputCov->faces.size() );
1889 
1890  int ixOverlap = 0;
1891 
1892  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
1893  {
1894  size_t ixOverlapTemp = ixOverlap;
1895  for( ; ixOverlapTemp < m_meshOverlap->faces.size(); ixOverlapTemp++ )
1896  {
1897  // const Face & faceOverlap = m_meshOverlap->faces[ixOverlapTemp];
1898 
1899  if( ixFirst - m_meshOverlap->vecSourceFaceIx[ixOverlapTemp] != 0 ) break;
1900 
1901  nAllOverlapFaces[ixFirst]++;
1902  }
1903 
1904  // Increment the current overlap index
1905  ixOverlap += nAllOverlapFaces[ixFirst];
1906  }
1907 
1908  // Number of times this point was found
1909  DataArray1D< bool > fSecondNodeFound( dataNodalAreaOut.GetRows() );
1910 
1911  ixOverlap = 0;
1912 #ifdef VERBOSE
1913  const unsigned outputFrequency = ( m_meshInputCov->faces.size() / 10 ) + 1;
1914 #endif
1915  // Loop through all faces on m_meshInputCov
1916  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
1917  {
1918 #ifdef VERBOSE
1919  // Announce computation progress
1920  if( ixFirst % outputFrequency == 0 && is_root )
1921  {
1922  dbgprint.printf( 0, "Element %zu/%lu\n", ixFirst, m_meshInputCov->faces.size() );
1923  }
1924 #endif
1925  // Quantities from the First Mesh
1926  const Face& faceFirst = m_meshInputCov->faces[ixFirst];
1927 
1928  const NodeVector& nodesFirst = m_meshInputCov->nodes;
1929 
1930  // Number of overlapping Faces and triangles
1931  int nOverlapFaces = nAllOverlapFaces[ixFirst];
1932 
1933  // Loop through all Overlap Faces
1934  for( int i = 0; i < nOverlapFaces; i++ )
1935  {
1936  // Quantities from the Second Mesh
1937  int ixSecond = m_meshOverlap->vecTargetFaceIx[ixOverlap + i];
1938 
1939  // signal to not participate, because it is a ghost target
1940  if( ixSecond < 0 ) continue; // do not do anything
1941 
1942  const NodeVector& nodesSecond = m_meshOutput->nodes;
1943  const Face& faceSecond = m_meshOutput->faces[ixSecond];
1944 
1945  // Loop through all nodes on the second face
1946  for( int s = 0; s < nPout; s++ )
1947  {
1948  for( int t = 0; t < nPout; t++ )
1949  {
1950  size_t ixSecondNode;
1951  if( fContinuousOut )
1952  {
1953  ixSecondNode = dataGLLNodesOut[s][t][ixSecond] - 1;
1954  }
1955  else
1956  {
1957  ixSecondNode = ixSecond * nPout * nPout + s * nPout + t;
1958  }
1959 
1960  if( ixSecondNode >= fSecondNodeFound.GetRows() ) _EXCEPTIONT( "Logic error" );
1961 
1962  // Check if this node has been found already
1963  if( fSecondNodeFound[ixSecondNode] ) continue;
1964 
1965  // Check this node
1966  Node node;
1967  Node dDx1G;
1968  Node dDx2G;
1969 
1970  ApplyLocalMap( faceSecond, nodesSecond, dGL[t], dGL[s], node, dDx1G, dDx2G );
1971 
1972  // Find the components of this quadrature point in the basis
1973  // of the first Face.
1974  double dAlphaIn;
1975  double dBetaIn;
1976 
1977  ApplyInverseMap( faceFirst, nodesFirst, node, dAlphaIn, dBetaIn );
1978 
1979  // Check if this node is within the first Face
1980  if( ( dAlphaIn < -1.0e-10 ) || ( dAlphaIn > 1.0 + 1.0e-10 ) || ( dBetaIn < -1.0e-10 ) ||
1981  ( dBetaIn > 1.0 + 1.0e-10 ) )
1982  continue;
1983 
1984  // Node is within the overlap region, mark as found
1985  fSecondNodeFound[ixSecondNode] = true;
1986 
1987  // Sample the First finite element at this point
1988  SampleGLLFiniteElement( nMonotoneType, nPin, dAlphaIn, dBetaIn, dSampleCoeffIn );
1989 
1990  // Add to map
1991  for( int p = 0; p < nPin; p++ )
1992  {
1993  for( int q = 0; q < nPin; q++ )
1994  {
1995  int ixFirstNode;
1996  if( fContinuousIn )
1997  {
1998  ixFirstNode = dataGLLNodesIn[p][q][ixFirst] - 1;
1999  }
2000  else
2001  {
2002  ixFirstNode = ixFirst * nPin * nPin + p * nPin + q;
2003  }
2004 
2005  smatMap( ixSecondNode, ixFirstNode ) += dSampleCoeffIn[p][q];
2006  }
2007  }
2008  }
2009  }
2010  }
2011 
2012  // Increment the current overlap index
2013  ixOverlap += nOverlapFaces;
2014  }
2015 
2016  // Check for missing samples
2017  for( size_t i = 0; i < fSecondNodeFound.GetRows(); i++ )
2018  {
2019  if( !fSecondNodeFound[i] )
2020  {
2021  _EXCEPTION1( "Can't sample point %i", i );
2022  }
2023  }
2024 
2025  return;
2026 }

References dbgprint.

◆ LinearRemapNN_MOAB()

moab::ErrorCode moab::TempestOnlineMap::LinearRemapNN_MOAB ( bool  use_GID_matching = false,
bool  strict_check = false 
)
private

Compute the remapping weights as a permutation matrix that relates DoFs on the source mesh to DoFs on the target mesh.

Definition at line 58 of file TempestLinearRemap.cpp.

59 {
60  /* m_mapRemap size = (m_nTotDofs_Dest X m_nTotDofs_SrcCov) */
61 
62 #ifdef VVERBOSE
63  {
64  std::ofstream output_file( "rowcolindices.txt", std::ios::out );
65  output_file << m_nTotDofs_Dest << " " << m_nTotDofs_SrcCov << " " << row_gdofmap.size() << " "
66  << row_ldofmap.size() << " " << col_gdofmap.size() << " " << col_ldofmap.size() << "\n";
67  output_file << "Rows \n";
68  for( unsigned iv = 0; iv < row_gdofmap.size(); iv++ )
69  output_file << row_gdofmap[iv] << " " << row_dofmap[iv] << "\n";
70  output_file << "Cols \n";
71  for( unsigned iv = 0; iv < col_gdofmap.size(); iv++ )
72  output_file << col_gdofmap[iv] << " " << col_dofmap[iv] << "\n";
73  output_file.flush(); // required here
74  output_file.close();
75  }
76 #endif
77 
78  if( use_GID_matching )
79  {
80  std::map< unsigned, unsigned > src_gl;
81  for( unsigned it = 0; it < col_gdofmap.size(); ++it )
82  src_gl[col_gdofmap[it]] = it;
83 
84  std::map< unsigned, unsigned >::iterator iter;
85  for( unsigned it = 0; it < row_gdofmap.size(); ++it )
86  {
87  unsigned row = row_gdofmap[it];
88  iter = src_gl.find( row );
89  if( strict_check && iter == src_gl.end() )
90  {
91  std::cout << "Searching for global target DOF " << row
92  << " but could not find correspondence in source mesh.\n";
93  assert( false );
94  }
95  else if( iter == src_gl.end() )
96  {
97  continue;
98  }
99  else
100  {
101  unsigned icol = src_gl[row];
102  unsigned irow = it;
103 
104  // Set the permutation matrix in local space
105  m_mapRemap( irow, icol ) = 1.0;
106  }
107  }
108 
109  return moab::MB_SUCCESS;
110  }
111  else
112  {
113  /* Create a Kd-tree to perform local queries to find nearest neighbors */
114 
115  return moab::MB_FAILURE;
116  }
117 }

References col_gdofmap, m_nTotDofs_Dest, m_nTotDofs_SrcCov, MB_SUCCESS, and row_gdofmap.

◆ LinearRemapSE0_Tempest_MOAB()

void moab::TempestOnlineMap::LinearRemapSE0_Tempest_MOAB ( const DataArray3D< int > &  dataGLLNodes,
const DataArray3D< double > &  dataGLLJacobian 
)
private

Generate the OfflineMap for linear conserative element-average spectral element to element average remapping.

◆ LinearRemapSE4_Tempest_MOAB()

void moab::TempestOnlineMap::LinearRemapSE4_Tempest_MOAB ( const DataArray3D< int > &  dataGLLNodes,
const DataArray3D< double > &  dataGLLJacobian,
int  nMonotoneType,
bool  fContinuousIn,
bool  fNoConservation,
bool  fSparseConstraints 
)
private

Generate the OfflineMap for cubic conserative element-average spectral element to element average remapping.

Definition at line 879 of file TempestLinearRemap.cpp.

885 {
886  // Order of the polynomial interpolant
887  int nP = dataGLLNodes.GetRows();
888 
889  // Order of triangular quadrature rule
890  const int TriQuadRuleOrder = 4;
891 
892  // Triangular quadrature rule
893  TriangularQuadratureRule triquadrule( TriQuadRuleOrder );
894 
895  int TriQuadraturePoints = triquadrule.GetPoints();
896 
897  const DataArray2D< double >& TriQuadratureG = triquadrule.GetG();
898 
899  const DataArray1D< double >& TriQuadratureW = triquadrule.GetW();
900 
901  // Sample coefficients
902  DataArray2D< double > dSampleCoeff( nP, nP );
903 
904  // GLL Quadrature nodes on quadrilateral elements
905  DataArray1D< double > dG;
906  DataArray1D< double > dW;
907  GaussLobattoQuadrature::GetPoints( nP, 0.0, 1.0, dG, dW );
908 
909  // Announcements
910  moab::DebugOutput dbgprint( std::cout, this->rank, 0 );
911  dbgprint.set_prefix( "[LinearRemapSE4_Tempest_MOAB]: " );
912  if( is_root )
913  {
914  dbgprint.printf( 0, "Finite Element to Finite Volume Projection\n" );
915  dbgprint.printf( 0, "Triangular quadrature rule order %i\n", TriQuadRuleOrder );
916  dbgprint.printf( 0, "Order of the FE polynomial interpolant: %i\n", nP );
917  }
918 
919  // Get SparseMatrix represntation of the OfflineMap
920  SparseMatrix< double >& smatMap = this->GetSparseMatrix();
921 
922  // NodeVector from m_meshOverlap
923  const NodeVector& nodesOverlap = m_meshOverlap->nodes;
924  const NodeVector& nodesFirst = m_meshInputCov->nodes;
925 
926  // Vector of source areas
927  DataArray1D< double > vecSourceArea( nP * nP );
928 
929  DataArray1D< double > vecTargetArea;
930  DataArray2D< double > dCoeff;
931 
932 #ifdef VERBOSE
933  std::stringstream sstr;
934  sstr << "remapdata_" << rank << ".txt";
935  std::ofstream output_file( sstr.str() );
936 #endif
937 
938  // Current Overlap Face
939  int ixOverlap = 0;
940 #ifdef VERBOSE
941  const unsigned outputFrequency = ( m_meshInputCov->faces.size() / 10 ) + 1;
942 #endif
943  // generic triangle used for area computation, for triangles around the center of overlap face;
944  // used for overlap faces with more than 4 edges;
945  // nodes array will be set for each triangle;
946  // these triangles are not part of the mesh structure, they are just temporary during
947  // aforementioned decomposition.
948  Face faceTri( 3 );
949  NodeVector nodes( 3 );
950  faceTri.SetNode( 0, 0 );
951  faceTri.SetNode( 1, 1 );
952  faceTri.SetNode( 2, 2 );
953 
954  // Loop over all input Faces
955  for( size_t ixFirst = 0; ixFirst < m_meshInputCov->faces.size(); ixFirst++ )
956  {
957  const Face& faceFirst = m_meshInputCov->faces[ixFirst];
958 
959  if( faceFirst.edges.size() != 4 )
960  {
961  _EXCEPTIONT( "Only quadrilateral elements allowed for SE remapping" );
962  }
963 #ifdef VERBOSE
964  // Announce computation progress
965  if( ixFirst % outputFrequency == 0 && is_root )
966  {
967  dbgprint.printf( 0, "Element %zu/%lu\n", ixFirst, m_meshInputCov->faces.size() );
968  }
969 #endif
970  // Need to re-number the overlap elements such that vecSourceFaceIx[a:b] = 0, then 1 and so
971  // on wrt the input mesh data Then the overlap_end and overlap_begin will be correct.
972  // However, the relation with MOAB and Tempest will go out of the roof
973 
974  // Determine how many overlap Faces and triangles are present
975  int nOverlapFaces = 0;
976  size_t ixOverlapTemp = ixOverlap;
977  for( ; ixOverlapTemp < m_meshOverlap->faces.size(); ixOverlapTemp++ )
978  {
979  // if( m_meshOverlap->vecTargetFaceIx[ixOverlapTemp] < 0 ) continue; // skip ghost target faces
980  // const Face & faceOverlap = m_meshOverlap->faces[ixOverlapTemp];
981  if( ixFirst - m_meshOverlap->vecSourceFaceIx[ixOverlapTemp] != 0 ) break;
982 
983  nOverlapFaces++;
984  }
985 
986  // No overlaps
987  if( nOverlapFaces == 0 ) continue;
988 
989  // Allocate remap coefficients array for meshFirst Face
990  DataArray3D< double > dRemapCoeff( nP, nP, nOverlapFaces );
991 
992  // Find the local remap coefficients
993  for( int j = 0; j < nOverlapFaces; j++ )
994  {
995  const Face& faceOverlap = m_meshOverlap->faces[ixOverlap + j];
996  if( m_meshOverlap->vecFaceArea[ixOverlap + j] < std::numeric_limits<double>::epsilon() ) // machine precision
997  {
998  if (false) { // verbose detailed output about small overlap elements (near machine precision area)
999  Announce( "Very small overlap at index %i area polygon: (%1.10e )", ixOverlap + j,
1000  m_meshOverlap->vecFaceArea[ixOverlap + j] );
1001  int n = faceOverlap.edges.size();
1002  Announce( "Number nodes: %d", n );
1003  for( int k = 0; k < n; k++ )
1004  {
1005  Node nd = nodesOverlap[faceOverlap[k]];
1006  Announce( "Node %d %d : %1.10e %1.10e %1.10e ", k, faceOverlap[k], nd.x, nd.y, nd.z );
1007  }
1008  }
1009  continue;
1010  }
1011 
1012  // #ifdef VERBOSE
1013  // if ( is_root )
1014  // Announce ( "\tLocal ID: %i/%i = %i, areas = %2.8e", j + ixOverlap, nOverlapFaces,
1015  // m_remapper->lid_to_gid_covsrc[m_meshOverlap->vecSourceFaceIx[ixOverlap + j]],
1016  // m_meshOverlap->vecFaceArea[ixOverlap + j] );
1017  // #endif
1018 
1019  int nbEdges = faceOverlap.edges.size();
1020  int nOverlapTriangles = 1;
1021  Node center; // not used if nbEdges == 3
1022  if( nbEdges > 3 )
1023  { // decompose from center in this case
1024  nOverlapTriangles = nbEdges;
1025  for( int k = 0; k < nbEdges; k++ )
1026  {
1027  const Node& node = nodesOverlap[faceOverlap[k]];
1028  center = center + node;
1029  }
1030  center = center / nbEdges;
1031  center = center.Normalized(); // project back on sphere of radius 1
1032  }
1033 
1034  Node node0, node1, node2;
1035  double dTriangleArea;
1036 
1037  // Loop over all sub-triangles of this Overlap Face
1038  for( int k = 0; k < nOverlapTriangles; k++ )
1039  {
1040  if( nbEdges == 3 ) // will come here only once, nOverlapTriangles == 1 in this case
1041  {
1042  node0 = nodesOverlap[faceOverlap[0]];
1043  node1 = nodesOverlap[faceOverlap[1]];
1044  node2 = nodesOverlap[faceOverlap[2]];
1045  dTriangleArea = CalculateFaceArea( faceOverlap, nodesOverlap );
1046  }
1047  else // decompose polygon in triangles around the center
1048  {
1049  node0 = center;
1050  node1 = nodesOverlap[faceOverlap[k]];
1051  int k1 = ( k + 1 ) % nbEdges;
1052  node2 = nodesOverlap[faceOverlap[k1]];
1053  nodes[0] = center;
1054  nodes[1] = node1;
1055  nodes[2] = node2;
1056  dTriangleArea = CalculateFaceArea( faceTri, nodes );
1057  }
1058  // Coordinates of quadrature Node
1059  for( int l = 0; l < TriQuadraturePoints; l++ )
1060  {
1061  Node nodeQuadrature;
1062  nodeQuadrature.x = TriQuadratureG[l][0] * node0.x + TriQuadratureG[l][1] * node1.x +
1063  TriQuadratureG[l][2] * node2.x;
1064 
1065  nodeQuadrature.y = TriQuadratureG[l][0] * node0.y + TriQuadratureG[l][1] * node1.y +
1066  TriQuadratureG[l][2] * node2.y;
1067 
1068  nodeQuadrature.z = TriQuadratureG[l][0] * node0.z + TriQuadratureG[l][1] * node1.z +
1069  TriQuadratureG[l][2] * node2.z;
1070 
1071  nodeQuadrature = nodeQuadrature.Normalized();
1072 
1073  // Find components of quadrature point in basis
1074  // of the first Face
1075  double dAlpha;
1076  double dBeta;
1077 
1078  ApplyInverseMap( faceFirst, nodesFirst, nodeQuadrature, dAlpha, dBeta );
1079 
1080  // Check inverse map value
1081  if( ( dAlpha < -1.0e-13 ) || ( dAlpha > 1.0 + 1.0e-13 ) || ( dBeta < -1.0e-13 ) ||
1082  ( dBeta > 1.0 + 1.0e-13 ) )
1083  {
1084  _EXCEPTION4( "Inverse Map for element %d and subtriangle %d out of range "
1085  "(%1.5e %1.5e)",
1086  j, l, dAlpha, dBeta );
1087  }
1088 
1089  // Sample the finite element at this point
1090  SampleGLLFiniteElement( nMonotoneType, nP, dAlpha, dBeta, dSampleCoeff );
1091 
1092  // Add sample coefficients to the map if m_meshOverlap->vecFaceArea[ixOverlap + j] > 0
1093  for( int p = 0; p < nP; p++ )
1094  {
1095  for( int q = 0; q < nP; q++ )
1096  {
1097  dRemapCoeff[p][q][j] += TriQuadratureW[l] * dTriangleArea * dSampleCoeff[p][q] /
1098  m_meshOverlap->vecFaceArea[ixOverlap + j];
1099  }
1100  }
1101  }
1102  }
1103  }
1104 
1105 #ifdef VERBOSE
1106  output_file << "[" << m_remapper->lid_to_gid_covsrc[ixFirst] << "] \t";
1107  for( int j = 0; j < nOverlapFaces; j++ )
1108  {
1109  for( int p = 0; p < nP; p++ )
1110  {
1111  for( int q = 0; q < nP; q++ )
1112  {
1113  output_file << dRemapCoeff[p][q][j] << " ";
1114  }
1115  }
1116  }
1117  output_file << std::endl;
1118 #endif
1119 
1120  // Force consistency and conservation
1121  if( !fNoConservation )
1122  {
1123  double dTargetArea = 0.0;
1124  for( int j = 0; j < nOverlapFaces; j++ )
1125  {
1126  dTargetArea += m_meshOverlap->vecFaceArea[ixOverlap + j];
1127  }
1128 
1129  for( int p = 0; p < nP; p++ )
1130  {
1131  for( int q = 0; q < nP; q++ )
1132  {
1133  vecSourceArea[p * nP + q] = dataGLLJacobian[p][q][ixFirst];
1134  }
1135  }
1136 
1137  const double areaTolerance = 1e-10;
1138  // Source elements are completely covered by target volumes
1139  if( fabs( m_meshInputCov->vecFaceArea[ixFirst] - dTargetArea ) <= areaTolerance )
1140  {
1141  vecTargetArea.Allocate( nOverlapFaces );
1142  for( int j = 0; j < nOverlapFaces; j++ )
1143  {
1144  vecTargetArea[j] = m_meshOverlap->vecFaceArea[ixOverlap + j];
1145  }
1146 
1147  dCoeff.Allocate( nOverlapFaces, nP * nP );
1148 
1149  for( int j = 0; j < nOverlapFaces; j++ )
1150  {
1151  for( int p = 0; p < nP; p++ )
1152  {
1153  for( int q = 0; q < nP; q++ )
1154  {
1155  dCoeff[j][p * nP + q] = dRemapCoeff[p][q][j];
1156  }
1157  }
1158  }
1159 
1160  // Target volumes only partially cover source elements
1161  }
1162  else if( m_meshInputCov->vecFaceArea[ixFirst] - dTargetArea > areaTolerance )
1163  {
1164  double dExtraneousArea = m_meshInputCov->vecFaceArea[ixFirst] - dTargetArea;
1165 
1166  vecTargetArea.Allocate( nOverlapFaces + 1 );
1167  for( int j = 0; j < nOverlapFaces; j++ )
1168  {
1169  vecTargetArea[j] = m_meshOverlap->vecFaceArea[ixOverlap + j];
1170  }
1171  vecTargetArea[nOverlapFaces] = dExtraneousArea;
1172 
1173 #ifdef VERBOSE
1174  Announce( "Partial volume: %i (%1.10e / %1.10e)", ixFirst, dTargetArea,
1175  m_meshInputCov->vecFaceArea[ixFirst] );
1176 #endif
1177  if( dTargetArea > m_meshInputCov->vecFaceArea[ixFirst] )
1178  {
1179  _EXCEPTIONT( "Partial element area exceeds total element area" );
1180  }
1181 
1182  dCoeff.Allocate( nOverlapFaces + 1, nP * nP );
1183 
1184  for( int j = 0; j < nOverlapFaces; j++ )
1185  {
1186  for( int p = 0; p < nP; p++ )
1187  {
1188  for( int q = 0; q < nP; q++ )
1189  {
1190  dCoeff[j][p * nP + q] = dRemapCoeff[p][q][j];
1191  }
1192  }
1193  }
1194  for( int p = 0; p < nP; p++ )
1195  {
1196  for( int q = 0; q < nP; q++ )
1197  {
1198  dCoeff[nOverlapFaces][p * nP + q] = dataGLLJacobian[p][q][ixFirst];
1199  }
1200  }
1201  for( int j = 0; j < nOverlapFaces; j++ )
1202  {
1203  for( int p = 0; p < nP; p++ )
1204  {
1205  for( int q = 0; q < nP; q++ )
1206  {
1207  dCoeff[nOverlapFaces][p * nP + q] -=
1208  dRemapCoeff[p][q][j] * m_meshOverlap->vecFaceArea[ixOverlap + j];
1209  }
1210  }
1211  }
1212  for( int p = 0; p < nP; p++ )
1213  {
1214  for( int q = 0; q < nP; q++ )
1215  {
1216  dCoeff[nOverlapFaces][p * nP + q] /= dExtraneousArea;
1217  }
1218  }
1219 
1220  // Source elements only partially cover target volumes
1221  }
1222  else
1223  {
1224  Announce( "Coverage area: %1.10e, and target element area: %1.10e)", ixFirst,
1225  m_meshInputCov->vecFaceArea[ixFirst], dTargetArea );
1226  _EXCEPTIONT( "Target grid must be a subset of source grid" );
1227  }
1228 
1229  ForceConsistencyConservation3( vecSourceArea, vecTargetArea, dCoeff, ( nMonotoneType > 0 ),
1230  fSparseConstraints );
1231 
1232  for( int j = 0; j < nOverlapFaces; j++ )
1233  {
1234  for( int p = 0; p < nP; p++ )
1235  {
1236  for( int q = 0; q < nP; q++ )
1237  {
1238  dRemapCoeff[p][q][j] = dCoeff[j][p * nP + q];
1239  }
1240  }
1241  }
1242  }
1243 
1244 #ifdef VERBOSE
1245  // output_file << "[" << m_remapper->lid_to_gid_covsrc[ixFirst] << "] \t";
1246  // for ( int j = 0; j < nOverlapFaces; j++ )
1247  // {
1248  // for ( int p = 0; p < nP; p++ )
1249  // {
1250  // for ( int q = 0; q < nP; q++ )
1251  // {
1252  // output_file << dRemapCoeff[p][q][j] << " ";
1253  // }
1254  // }
1255  // }
1256  // output_file << std::endl;
1257 #endif
1258 
1259  // Put these remap coefficients into the SparseMatrix map
1260  for( int j = 0; j < nOverlapFaces; j++ )
1261  {
1262  int ixSecondFace = m_meshOverlap->vecTargetFaceIx[ixOverlap + j];
1263 
1264  // signal to not participate, because it is a ghost target
1265  if( ixSecondFace < 0 ) continue; // do not do anything
1266 
1267  for( int p = 0; p < nP; p++ )
1268  {
1269  for( int q = 0; q < nP; q++ )
1270  {
1271  if( fContinuousIn )
1272  {
1273  int ixFirstNode = dataGLLNodes[p][q][ixFirst] - 1;
1274 
1275  smatMap( ixSecondFace, ixFirstNode ) += dRemapCoeff[p][q][j] *
1276  m_meshOverlap->vecFaceArea[ixOverlap + j] /
1277  m_meshOutput->vecFaceArea[ixSecondFace];
1278  }
1279  else
1280  {
1281  int ixFirstNode = ixFirst * nP * nP + p * nP + q;
1282 
1283  smatMap( ixSecondFace, ixFirstNode ) += dRemapCoeff[p][q][j] *
1284  m_meshOverlap->vecFaceArea[ixOverlap + j] /
1285  m_meshOutput->vecFaceArea[ixSecondFace];
1286  }
1287  }
1288  }
1289  }
1290  // Increment the current overlap index
1291  ixOverlap += nOverlapFaces;
1292  }
1293 #ifdef VERBOSE
1294  output_file.flush(); // required here
1295  output_file.close();
1296 #endif
1297 
1298  return;
1299 }

References center(), dbgprint, and ForceConsistencyConservation3().

◆ PrintMapStatistics()

void moab::TempestOnlineMap::PrintMapStatistics ( )

Print information and metadata about the remapping weights.

Definition at line 284 of file TempestLinearRemap.cpp.

285 {
286  int nrows = m_weightMatrix.rows(); // Number of rows
287  int ncols = m_weightMatrix.cols(); // Number of columns
288  int NNZ = m_weightMatrix.nonZeros(); // Number of non zero values
289 #ifdef MOAB_HAVE_MPI
290  // find out min/max for NNZ, ncols, nrows
291  // should work on std c++ 11
292  int arr3[6] = { NNZ, nrows, ncols, -NNZ, -nrows, -ncols };
293  int rarr3[6] = {0, 0, 0, 0, 0, 0};
294  MPI_Reduce( arr3, rarr3, 6, MPI_INT, MPI_MIN, 0, m_pcomm->comm() );
295 
296  int total[3] = {0, 0, 0};
297  MPI_Reduce( arr3, total, 3, MPI_INT, MPI_SUM, 0, m_pcomm->comm() );
298  if( !rank )
299  std::cout << "-> Rows (min/max/sum): (" << rarr3[1] << " / " << -rarr3[4] << " / " << total[1] << "), "
300  << " Cols (min/max/sum): (" << rarr3[2] << " / " << -rarr3[5] << " / " << total[2] << "), "
301  << " NNZ (min/max/sum): (" << rarr3[0] << " / " << -rarr3[3] << " / " << total[0] << ")\n";
302 #else
303  std::cout << "-> Rows: " << nrows << ", Cols: " << ncols << ", NNZ: " << NNZ << "\n";
304 #endif
305 }

Referenced by main().

◆ QLTLimiter()

double moab::TempestOnlineMap::QLTLimiter ( int  caasIteration,
std::vector< double > &  dataCorrectedField,
std::vector< double > &  dataLowerBound,
std::vector< double > &  dataUpperBound,
std::vector< double > &  dMassDefect 
)
private

Definition at line 388 of file TempestLinearRemap.cpp.

393 {
394  const size_t nrows = dataCorrectedField.size();
395  double dMassL = 0.0;
396  double dMassU = 0.0;
397  std::vector< double > dataCorrection( nrows );
398  double dMassDiffCum = 0.0;
399  double dLMinusU = fabs( dataUpperBound[0] - dataLowerBound[0] );
400  const DataArray1D< double >& dTargetAreas = this->m_remapper->m_target->vecFaceArea;
401 
402  // std::vector< size_t > sortedIdx = sort_indexes( dMassDefect );
403  std::vector< std::unordered_set< int > > vecAdjTargetFaces( nrows );
404  constexpr bool useMOABAdjacencies = true;
405 #ifdef USE_ComputeAdjacencyRelations
406  if( useMOABAdjacencies )
407  ComputeAdjacencyRelations( vecAdjTargetFaces, caasIteration, m_remapper->m_target_entities,
408  useMOABAdjacencies );
409  else
410  ComputeAdjacencyRelations( vecAdjTargetFaces, caasIteration, m_remapper->m_target_entities, useMOABAdjacencies,
411  this->m_remapper->m_target );
412 #else
414  ;
415 #endif
416 
417  for( size_t i = 0; i < nrows; i++ )
418  {
419  // size_t index = sortedIdx[i];
420  size_t index = i;
421  dataCorrection[index] = fmax( dataLowerBound[index], fmin( dataUpperBound[index], 0.0 ) );
422  // dMassDiff[index] = dMassDefect[index] - dTargetAreas[index] * dataCorrection[index];
423  // dMassDiff[index] = dMassDefect[index];
424 
425  dMassL += dTargetAreas[index] * dataLowerBound[index];
426  dMassU += dTargetAreas[index] * dataUpperBound[index];
427  dLMinusU = fmax( dLMinusU, fabs( dataUpperBound[index] - dataLowerBound[index] ) );
428  dMassDiffCum += dMassDefect[index] - dTargetAreas[index] * dataCorrection[index];
429 
430 #ifndef USE_ComputeAdjacencyRelations
431  vecAdjTargetFaces[index].insert( index ); // add self target face first
432  {
433  // Compute the adjacent faces to the target face
434  if( useMOABAdjacencies )
435  {
436  moab::Range ents;
437  // ents.insert( m_remapper->m_target_entities.index( m_remapper->m_target_entities[index] ) );
439  moab::Range adjEnts;
440  moab::ErrorCode rval = mtu.get_bridge_adjacencies( ents, 0, 2, adjEnts, caasIteration );MB_CHK_SET_ERR_CONT( rval, "Failed to get adjacent faces" );
441  for( moab::Range::iterator it = adjEnts.begin(); it != adjEnts.end(); ++it )
442  {
443  // int adjIndex = m_interface->id_from_handle(*it)-1;
444  int adjIndex = m_remapper->m_target_entities.index( *it );
445  // printf("rank: %d, Element %lu, entity: %lu, adjIndex %d\n", rank, index, *it, adjIndex);
446  if( adjIndex >= 0 ) vecAdjTargetFaces[index].insert( adjIndex );
447  }
448  }
449  else
450  {
451  AdjacentFaceVector vecAdjFaces;
452  GetAdjacentFaceVectorByEdge( *this->m_remapper->m_target, index,
453  ( m_output_order + 1 ) * ( m_output_order + 1 ) * ( m_output_order + 1 ),
454  // ( m_output_order + 1 ) * ( m_output_order + 1 ),
455  // ( 4 ) * ( m_output_order + 1 ) * ( m_output_order + 1 ),
456  vecAdjFaces );
457 
458  // Add the adjacent faces to the target face list
459  for( auto adjFace : vecAdjFaces )
460  if( adjFace.first >= 0 )
461  vecAdjTargetFaces[index].insert( adjFace.first ); // map target face to source face
462  }
463  }
464 #endif
465  }
466 
467 #ifdef MOAB_HAVE_MPI
468  std::vector< double > localDefects( 5, 0.0 ), globalDefects( 5, 0.0 );
469  localDefects[0] = dMassL;
470  localDefects[1] = dMassU;
471  localDefects[2] = dMassDiffCum;
472  localDefects[3] = dLMinusU;
473  // localDefects[4] = dMassCorrectU;
474 
475  MPI_Allreduce( localDefects.data(), globalDefects.data(), 4, MPI_DOUBLE, MPI_SUM, m_pcomm->comm() );
476 
477  dMassL = globalDefects[0];
478  dMassU = globalDefects[1];
479  dMassDiffCum = globalDefects[2];
480  dLMinusU = globalDefects[3];
481  // dMassCorrectU = globalDefects[4];
482 #endif
483 
484  //If the upper and lower bounds are too close together, just clip
485  if( fabs( dMassDiffCum ) < 1e-15 || dLMinusU < 1e-15 )
486  {
487  for( size_t i = 0; i < nrows; i++ )
488  dataCorrectedField[i] += dataCorrection[i];
489  return dMassDiffCum;
490  }
491  else
492  {
493  if( dMassL > dMassDiffCum )
494  {
495  Announce( "Lower bound mass exceeds target mass by %1.15e: CAAS will need another iteration",
496  dMassL - dMassDiffCum );
497  dMassDiffCum = dMassL;
498  // dMass -= dMassL;
499  }
500  else if( dMassU < dMassDiffCum )
501  {
502  Announce( "Target mass exceeds upper bound mass by %1.15e: CAAS will need another iteration",
503  dMassDiffCum - dMassU );
504  dMassDiffCum = dMassU;
505  // dMass -= dMassU;
506  }
507 
508  // TODO: optimize away dataMassVec by a simple transient double within the loop
509  // DataArray1D< double > dataMassVec( nrows ); //vector of mass redistribution
510  for( size_t i = 0; i < nrows; i++ )
511  {
512  // size_t index = sortedIdx[i];
513  size_t index = i;
514  const std::unordered_set< int >& neighbors = vecAdjTargetFaces[index];
515  if( dMassDefect[index] > 0.0 )
516  {
517  double dMassCorrectU = 0.0;
518  for( auto it : neighbors )
519  dMassCorrectU += dTargetAreas[it] * ( dataUpperBound[it] - dataCorrection[it] );
520 
521  // double dMassDiffCumOld = dMassDefect[index];
522  for( auto it : neighbors )
523  dataCorrection[it] +=
524  dMassDefect[index] * ( dataUpperBound[it] - dataCorrection[it] ) / dMassCorrectU;
525  }
526  else
527  {
528  double dMassCorrectL = 0.0;
529  for( auto it : neighbors )
530  dMassCorrectL += dTargetAreas[it] * ( dataCorrection[it] - dataLowerBound[it] );
531 
532  // double dMassDiffCumOld = dMassDefect[index];
533  for( auto it : neighbors )
534  dataCorrection[it] +=
535  dMassDefect[index] * ( dataCorrection[it] - dataLowerBound[it] ) / dMassCorrectL;
536  }
537  }
538 
539  for( size_t i = 0; i < nrows; i++ )
540  dataCorrectedField[i] += dataCorrection[i];
541  }
542 
543  return dMassDiffCum;
544 }

References moab::Range::begin(), moab::Range::end(), ErrorCode, moab::MeshTopoUtil::get_bridge_adjacencies(), moab::index, moab::Range::insert(), and MB_CHK_SET_ERR_CONT.

◆ ReadParallelMap()

moab::ErrorCode moab::TempestOnlineMap::ReadParallelMap ( const char *  strSource,
const std::vector< int > &  tgt_dof_ids,
int  arearead,
std::vector< double > &  areaA,
int &  nA,
std::vector< double > &  areaB,
int &  nB 
)

Generate the metadata associated with the offline map.

Read the OfflineMap from a NetCDF file.

Definition at line 1349 of file TempestOnlineMapIO.cpp.

1356 {
1357  NcError error( NcError::silent_nonfatal );
1358 
1359  const bool readAreaA = ( 1 == arearead || 3 == arearead );
1360  const bool readAreaB = ( 2 == arearead || 3 == arearead );
1361  int nS = 0;
1362 
1363  // =========================================================================
1364  // Phase 1: Read map dimensions (nA, nB, nS) and sparse matrix data.
1365  //
1366  // The read strategy is selected adaptively:
1367  // - Serial or buffered read: rank 0 opens the file with serial NcFile,
1368  // reads dimensions, and (for buffered mode) scatters data in chunks.
1369  // - Direct parallel read: all ranks open the file with PNetCDF or
1370  // NETCDFPAR and read their stripe directly.
1371  // =========================================================================
1372 
1373  std::vector< int > vecRow, vecCol;
1374  std::vector< double > vecS;
1375  int localSize = 0; // number of sparse matrix entries on this rank after read
1376 
1377  // Determine which read strategy to use. For size == 1, always serial.
1378  // For size > 1, decide after reading dimensions (need nS).
1379  // We use a two-phase approach: first read dimensions on rank 0 and broadcast,
1380  // then select the strategy based on nS.
1381 
1382 #ifdef MOAB_HAVE_MPI
1383  if( size > 1 )
1384  {
1385  // --- Multi-process path: read dimensions on rank 0 and broadcast ---
1386  int dims[3] = { 0, 0, 0 }; // nA, nB, nS
1387 
1388  if( rank == 0 )
1389  {
1390  NcFile ncDims( strSource, NcFile::ReadOnly );
1391  if( !ncDims.is_valid() )
1392  {
1393  _EXCEPTION1( "Unable to open input map file \"%s\" on rank 0", strSource );
1394  }
1395  NcDim* dimNA = ncDims.get_dim( "n_a" );
1396  NcDim* dimNB = ncDims.get_dim( "n_b" );
1397  NcDim* dimNS = ncDims.get_dim( "n_s" );
1398  if( !dimNA || !dimNB || !dimNS )
1399  {
1400  _EXCEPTION1( "Map file \"%s\" missing required dimensions (n_a, n_b, n_s)", strSource );
1401  }
1402  dims[0] = static_cast< int >( dimNA->size() );
1403  dims[1] = static_cast< int >( dimNB->size() );
1404  dims[2] = static_cast< int >( dimNS->size() );
1405  ncDims.close();
1406  }
1407 
1408  MPI_Bcast( dims, 3, MPI_INT, 0, m_pcomm->comm() );
1409  nA = dims[0];
1410  nB = dims[1];
1411  nS = dims[2];
1412 
1413  // Select read strategy based on NNZ count and available parallel I/O
1414  bool useBufferedRead = true; // default for small maps or no parallel I/O
1415 
1416  if( nS > BUFFERED_READ_NNZ_THRESHOLD )
1417  {
1418  // Large map: prefer direct parallel read if available
1419 #if defined( MOAB_HAVE_PNETCDF ) || defined( MOAB_HAVE_NETCDFPAR )
1420  useBufferedRead = false;
1421 #endif
1422  // If neither is available, fall back to buffered read regardless of size
1423  }
1424 
1425  if( useBufferedRead )
1426  {
1427  // =================================================================
1428  // Buffered read: rank 0 reads in chunks and scatters to owners.
1429  //
1430  // Row ownership is determined by trivial partitioning: row i is
1431  // owned by rank (i / nRowPerPart), with remainder on rank 0.
1432  // Each chunk is read, ownership is computed per entry, and the
1433  // data is scattered via MPI_Scatter + MPI_Isend/MPI_Irecv.
1434  // =================================================================
1435  if( rank == 0 )
1436  {
1437  std::cout << " [ReadParallelMap]: Using buffered read strategy for " << nS
1438  << " NNZ entries (threshold=" << BUFFERED_READ_NNZ_THRESHOLD << ")\n";
1439  }
1440 
1441  const int nNNZBytes = 2 * sizeof( int ) + sizeof( double );
1442  const int nMaxPerChunk = BUFFERED_READ_CHUNK_BYTES / nNNZBytes;
1443  const int nBufferedReads = static_cast< int >( std::ceil( 1.0 * nS / nMaxPerChunk ) );
1444 
1445  // Row ownership: trivial partitioning of nB rows across ranks
1446  const int nRowPerPart = nB / size;
1447  const int nRowRemainder = nB % size;
1448  std::vector< int > rowOwnership( size );
1449  rowOwnership[0] = nRowPerPart + nRowRemainder;
1450  for( int ip = 1; ip < size; ++ip )
1451  rowOwnership[ip] = rowOwnership[ip - 1] + nRowPerPart;
1452 
1453  // File handle and variable pointers (rank 0 only)
1454  NcFile* ncMap = nullptr;
1455  NcVar *varRowF = nullptr, *varColF = nullptr, *varSF = nullptr;
1456  NcVar *varAreaAF = nullptr, *varAreaBF = nullptr;
1457 
1458  if( rank == 0 )
1459  {
1460  ncMap = new NcFile( strSource, NcFile::ReadOnly );
1461  if( !ncMap->is_valid() )
1462  {
1463  _EXCEPTION1( "Unable to open map file \"%s\" for buffered read", strSource );
1464  }
1465  varRowF = ncMap->get_var( "row" );
1466  varColF = ncMap->get_var( "col" );
1467  varSF = ncMap->get_var( "S" );
1468  if( readAreaA ) varAreaAF = ncMap->get_var( "area_a" );
1469  if( readAreaB ) varAreaBF = ncMap->get_var( "area_b" );
1470  }
1471 
1472  // Accumulate received entries per rank
1473  std::vector< int > localRows, localCols;
1474  std::vector< double > localVals;
1475  localRows.reserve( nS / size + nS / ( size * 10 ) ); // slight overalloc
1476  localCols.reserve( nS / size + nS / ( size * 10 ) );
1477  localVals.reserve( nS / size + nS / ( size * 10 ) );
1478 
1479  int nEntriesRemaining = nS;
1480  long fileOffset = 0;
1481 
1482  for( int iRead = 0; iRead < nBufferedReads; ++iRead )
1483  {
1484  // Per-chunk data and ownership (rank 0 only)
1485  std::vector< int > chunkRow, chunkCol;
1486  std::vector< double > chunkS;
1487  std::vector< std::vector< int > > entriesPerProc( size );
1488  std::vector< int > nPerProc( size, 0 );
1489 
1490  if( rank == 0 )
1491  {
1492  int chunkSize = std::min( nEntriesRemaining, nMaxPerChunk );
1493 
1494  chunkRow.resize( chunkSize );
1495  chunkCol.resize( chunkSize );
1496  chunkS.resize( chunkSize );
1497 
1498  varRowF->set_cur( fileOffset );
1499  varRowF->get( chunkRow.data(), chunkSize );
1500  varColF->set_cur( fileOffset );
1501  varColF->get( chunkCol.data(), chunkSize );
1502  varSF->set_cur( fileOffset );
1503  varSF->get( chunkS.data(), chunkSize );
1504 
1505  // Determine ownership of each entry by its row index (1-based in file)
1506  for( int ip = 0; ip < size; ++ip )
1507  entriesPerProc[ip].reserve( chunkSize / size + 64 );
1508 
1509  for( int i = 0; i < chunkSize; ++i )
1510  {
1511  int rowIdx = chunkRow[i] - 1; // convert to 0-based
1512  int owner = 0;
1513  if( rowIdx >= rowOwnership[0] )
1514  {
1515  // Binary search for owner
1516  owner = static_cast< int >(
1517  std::upper_bound( rowOwnership.begin(), rowOwnership.end(), rowIdx ) -
1518  rowOwnership.begin() );
1519  if( owner >= size ) owner = size - 1;
1520  }
1521  entriesPerProc[owner].push_back( i );
1522  }
1523 
1524  fileOffset += chunkSize;
1525  nEntriesRemaining -= chunkSize;
1526 
1527  for( int ip = 0; ip < size; ++ip )
1528  nPerProc[ip] = static_cast< int >( entriesPerProc[ip].size() );
1529  }
1530 
1531  // Scatter count of entries each rank will receive in this chunk
1532  int nRecv = 0;
1533  MPI_Scatter( nPerProc.data(), 1, MPI_INT, &nRecv, 1, MPI_INT, 0, m_pcomm->comm() );
1534 
1535  if( rank == 0 )
1536  {
1537  // Send data to remote ranks via non-blocking sends
1538  std::vector< MPI_Request > requests;
1539  requests.reserve( 2 * ( size - 1 ) );
1540 
1541  // Pack and send to each remote rank
1542  std::vector< std::vector< int > > sendRowCol( size );
1543  std::vector< std::vector< double > > sendVals( size );
1544 
1545  for( int ip = 1; ip < size; ++ip )
1546  {
1547  const int nDPP = nPerProc[ip];
1548  if( nDPP > 0 )
1549  {
1550  sendRowCol[ip].resize( 2 * nDPP );
1551  sendVals[ip].resize( nDPP );
1552  for( int j = 0; j < nDPP; ++j )
1553  {
1554  int idx = entriesPerProc[ip][j];
1555  sendRowCol[ip][2 * j] = chunkRow[idx];
1556  sendRowCol[ip][2 * j + 1] = chunkCol[idx];
1557  sendVals[ip][j] = chunkS[idx];
1558  }
1559 
1560  MPI_Request rqRC, rqV;
1561  MPI_Isend( sendRowCol[ip].data(), 2 * nDPP, MPI_INT, ip,
1562  iRead * 1000, m_pcomm->comm(), &rqRC );
1563  MPI_Isend( sendVals[ip].data(), nDPP, MPI_DOUBLE, ip,
1564  iRead * 1000 + 1, m_pcomm->comm(), &rqV );
1565  requests.push_back( rqRC );
1566  requests.push_back( rqV );
1567  }
1568  }
1569 
1570  // Process rank 0's own entries while sends are in flight
1571  for( int j = 0; j < nRecv; ++j )
1572  {
1573  int idx = entriesPerProc[0][j];
1574  localRows.push_back( chunkRow[idx] );
1575  localCols.push_back( chunkCol[idx] );
1576  localVals.push_back( chunkS[idx] );
1577  }
1578 
1579  // Wait for all sends to complete
1580  if( !requests.empty() )
1581  {
1582  std::vector< MPI_Status > stats( requests.size() );
1583  MPI_Waitall( static_cast< int >( requests.size() ), requests.data(), stats.data() );
1584  }
1585  }
1586  else if( nRecv > 0 )
1587  {
1588  // Receive data from rank 0
1589  std::vector< int > recvRowCol( 2 * nRecv );
1590  std::vector< double > recvVals( nRecv );
1591 
1592  MPI_Request rqs[2];
1593  MPI_Irecv( recvRowCol.data(), 2 * nRecv, MPI_INT, 0,
1594  iRead * 1000, m_pcomm->comm(), &rqs[0] );
1595  MPI_Irecv( recvVals.data(), nRecv, MPI_DOUBLE, 0,
1596  iRead * 1000 + 1, m_pcomm->comm(), &rqs[1] );
1597 
1598  MPI_Status sts[2];
1599  MPI_Waitall( 2, rqs, sts );
1600 
1601  for( int j = 0; j < nRecv; ++j )
1602  {
1603  localRows.push_back( recvRowCol[2 * j] );
1604  localCols.push_back( recvRowCol[2 * j + 1] );
1605  localVals.push_back( recvVals[j] );
1606  }
1607  }
1608 
1609  MPI_Barrier( m_pcomm->comm() );
1610  } // end buffered read loop
1611 
1612  // Read area arrays on rank 0 and scatter the trivial (nA/size, nB/size)
1613  // partition to each rank. iMOAB's set_aream_from_trivial_distribution
1614  // (iMOAB.cpp) assumes each rank holds exactly its trivial slice of size
1615  // N/size (last rank gets the N%size remainder) and computes its local
1616  // index as `marker - 1 - rank * (N/size)`. Broadcasting the full array
1617  // would silently scramble the per-cell aream tag on every rank > 0,
1618  // which breaks BfB on the CAAS dual-map path while leaving the plain
1619  // SpMV (lo, hi) projections BfB-correct (those don't use aream).
1620  auto scatter_trivial = []( int Ntot, int rk, int sz, NcVar* var, std::vector< double >& localSlice,
1621  MPI_Comm comm ) {
1622  const int base = Ntot / sz;
1623  const int rem = Ntot % sz;
1624  const int localCount = ( rk == sz - 1 ) ? ( base + rem ) : base;
1625  localSlice.resize( localCount );
1626  if( rk == 0 )
1627  {
1628  std::vector< double > fullBuf( Ntot );
1629  if( var )
1630  {
1631  var->set_cur( 0L );
1632  var->get( fullBuf.data(), Ntot );
1633  }
1634  // Copy rank 0's own slice and send each other rank its slice.
1635  std::copy( fullBuf.begin(), fullBuf.begin() + localCount, localSlice.begin() );
1636  for( int dst = 1; dst < sz; dst++ )
1637  {
1638  const int dstCount = ( dst == sz - 1 ) ? ( base + rem ) : base;
1639  MPI_Send( fullBuf.data() + dst * base, dstCount, MPI_DOUBLE, dst, 0xA9EA, comm );
1640  }
1641  }
1642  else
1643  {
1644  MPI_Recv( localSlice.data(), localCount, MPI_DOUBLE, 0, 0xA9EA, comm, MPI_STATUS_IGNORE );
1645  }
1646  };
1647  if( readAreaA ) scatter_trivial( nA, rank, size, varAreaAF, vecAreaA, m_pcomm->comm() );
1648  if( readAreaB ) scatter_trivial( nB, rank, size, varAreaBF, vecAreaB, m_pcomm->comm() );
1649 
1650  if( rank == 0 )
1651  {
1652  ncMap->close();
1653  delete ncMap;
1654  }
1655 
1656  // Move accumulated data into the standard vecRow/vecCol/vecS vectors
1657  localSize = static_cast< int >( localRows.size() );
1658  vecRow.swap( localRows );
1659  vecCol.swap( localCols );
1660  vecS.swap( localVals );
1661  }
1662  else
1663  {
1664  // =================================================================
1665  // Direct parallel read: choose reader based on detected file format.
1666  //
1667  // Selection logic (per format):
1668  // - Classic (CDF-1/2/5): prefer PNetCDF (best fit for the classic
1669  // family); fall back to NETCDFPAR only if PNetCDF is not built in.
1670  // - NetCDF-4 (HDF5): must use NETCDFPAR — PNetCDF cannot read
1671  // HDF5-backed NetCDF-4 files.
1672  // - Unknown/other: hard error.
1673  //
1674  // Format is determined by reading the file's magic bytes on rank 0
1675  // and broadcasting the answer. This avoids opening the file twice
1676  // in parallel just to probe its format.
1677  // =================================================================
1678  if( rank == 0 )
1679  {
1680  std::cout << " [ReadParallelMap]: Using direct parallel read for " << nS
1681  << " NNZ entries (threshold=" << BUFFERED_READ_NNZ_THRESHOLD << ")\n";
1682  }
1683 
1684  int fileFormat = MAP_FORMAT_UNKNOWN;
1685  if( rank == 0 ) fileFormat = detectMapFileNetCDFFormat( strSource );
1686  MPI_Bcast( &fileFormat, 1, MPI_INT, 0, m_pcomm->comm() );
1687 
1688  const bool isClassic = ( fileFormat == MAP_FORMAT_CLASSIC );
1689  const bool isNetCDF4 = ( fileFormat == MAP_FORMAT_NETCDF4 );
1690 
1691  if( !isClassic && !isNetCDF4 )
1692  {
1693  _EXCEPTION1( "Map file \"%s\" is not in a recognized NetCDF format "
1694  "(expected classic CDF-1/2/5 or NetCDF-4/HDF5)",
1695  strSource );
1696  }
1697 
1698  // Compute this rank's stripe of the sparse matrix
1699  localSize = nS / size;
1700  long offsetRead = rank * localSize;
1701  if( rank == size - 1 ) localSize += nS % size;
1702 
1703  vecRow.resize( localSize );
1704  vecCol.resize( localSize );
1705  vecS.resize( localSize );
1706 
1707  // Compute this rank's stripe of area arrays
1708  int localSizeA = nA / size;
1709  long offsetReadA = rank * localSizeA;
1710  if( rank == size - 1 ) localSizeA += nA % size;
1711 
1712  int localSizeB = nB / size;
1713  long offsetReadB = rank * localSizeB;
1714  if( rank == size - 1 ) localSizeB += nB % size;
1715 
1716  if( readAreaA ) vecAreaA.resize( localSizeA );
1717  if( readAreaB ) vecAreaB.resize( localSizeB );
1718 
1719  bool parReadDone = false;
1720 
1721  // --- Classic format: prefer PNetCDF, fall back to NETCDFPAR -----
1722  if( isClassic )
1723  {
1724 #ifdef MOAB_HAVE_PNETCDF
1725  {
1726  // PNetCDF — collective I/O, native fit for CDF-1/2/5
1727  int ncfile = -1;
1728  int pnc_err = ncmpi_open( m_pcomm->comm(), strSource, NC_NOWRITE, MPI_INFO_NULL, &ncfile );
1729  if( pnc_err == NC_NOERR )
1730  {
1731  if( rank == 0 )
1732  std::cout << " [ReadParallelMap]: Reading classic-format file via PNetCDF\n";
1733 
1734  MPI_Offset start = static_cast< MPI_Offset >( offsetRead );
1735  MPI_Offset count = static_cast< MPI_Offset >( localSize );
1736  int varid;
1737 
1738  ERR_PARNC( ncmpi_inq_varid( ncfile, "S", &varid ) );
1739  ERR_PARNC( ncmpi_get_vara_double_all( ncfile, varid, &start, &count, vecS.data() ) );
1740  ERR_PARNC( ncmpi_inq_varid( ncfile, "row", &varid ) );
1741  ERR_PARNC( ncmpi_get_vara_int_all( ncfile, varid, &start, &count, vecRow.data() ) );
1742  ERR_PARNC( ncmpi_inq_varid( ncfile, "col", &varid ) );
1743  ERR_PARNC( ncmpi_get_vara_int_all( ncfile, varid, &start, &count, vecCol.data() ) );
1744 
1745  if( readAreaA )
1746  {
1747  MPI_Offset startA = static_cast< MPI_Offset >( offsetReadA );
1748  MPI_Offset countA = static_cast< MPI_Offset >( localSizeA );
1749  ERR_PARNC( ncmpi_inq_varid( ncfile, "area_a", &varid ) );
1750  ERR_PARNC( ncmpi_get_vara_double_all( ncfile, varid, &startA, &countA, vecAreaA.data() ) );
1751  }
1752  if( readAreaB )
1753  {
1754  MPI_Offset startB = static_cast< MPI_Offset >( offsetReadB );
1755  MPI_Offset countB = static_cast< MPI_Offset >( localSizeB );
1756  ERR_PARNC( ncmpi_inq_varid( ncfile, "area_b", &varid ) );
1757  ERR_PARNC( ncmpi_get_vara_double_all( ncfile, varid, &startB, &countB, vecAreaB.data() ) );
1758  }
1759  ERR_PARNC( ncmpi_close( ncfile ) );
1760  parReadDone = true;
1761  }
1762  }
1763 #endif
1764 
1765 #ifdef MOAB_HAVE_NETCDFPAR
1766  if( !parReadDone )
1767  {
1768  // PNetCDF not configured (or its open failed) — try NETCDFPAR.
1769  // Works only if NetCDF-4 was built with parallel-IO support
1770  // for classic files (typically requires NetCDF linked against PNetCDF).
1771  if( rank == 0 )
1772  std::cout << " [ReadParallelMap]: PNetCDF unavailable; reading classic-format "
1773  "file via parallel NetCDF (NETCDFPAR)\n";
1774  ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strSource, NcFile::ReadOnly, NcFile::Classic );
1775  if( ncMap.is_valid() )
1776  {
1777  NcVar* varRowP = ncMap.get_var( "row" );
1778  NcVar* varColP = ncMap.get_var( "col" );
1779  NcVar* varSP = ncMap.get_var( "S" );
1780  ncMap.enable_var_par_access( varRowP, true );
1781  ncMap.enable_var_par_access( varColP, true );
1782  ncMap.enable_var_par_access( varSP, true );
1783 
1784  varRowP->set_cur( offsetRead );
1785  varRowP->get( vecRow.data(), localSize );
1786  varColP->set_cur( offsetRead );
1787  varColP->get( vecCol.data(), localSize );
1788  varSP->set_cur( offsetRead );
1789  varSP->get( vecS.data(), localSize );
1790 
1791  if( readAreaA )
1792  {
1793  NcVar* varAreaAP = ncMap.get_var( "area_a" );
1794  ncMap.enable_var_par_access( varAreaAP, true );
1795  varAreaAP->set_cur( offsetReadA );
1796  varAreaAP->get( vecAreaA.data(), localSizeA );
1797  }
1798  if( readAreaB )
1799  {
1800  NcVar* varAreaBP = ncMap.get_var( "area_b" );
1801  ncMap.enable_var_par_access( varAreaBP, true );
1802  varAreaBP->set_cur( offsetReadB );
1803  varAreaBP->get( vecAreaB.data(), localSizeB );
1804  }
1805  ncMap.close();
1806  parReadDone = true;
1807  }
1808  }
1809 #endif
1810 
1811  if( !parReadDone )
1812  {
1813  _EXCEPTION1( "Classic-format map file \"%s\" cannot be read in parallel: "
1814  "neither PNetCDF nor parallel NetCDF (NETCDFPAR) is configured "
1815  "(or both failed to open the file)",
1816  strSource );
1817  }
1818  }
1819  // --- NetCDF-4/HDF5 format: only NETCDFPAR can handle it ---------
1820  else // isNetCDF4
1821  {
1822 #ifdef MOAB_HAVE_NETCDFPAR
1823  {
1824  if( rank == 0 )
1825  std::cout << " [ReadParallelMap]: Reading NetCDF-4/HDF5 file via parallel NetCDF\n";
1826  ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strSource, NcFile::ReadOnly, NcFile::Netcdf4 );
1827  if( ncMap.is_valid() )
1828  {
1829  NcVar* varRowP = ncMap.get_var( "row" );
1830  NcVar* varColP = ncMap.get_var( "col" );
1831  NcVar* varSP = ncMap.get_var( "S" );
1832  ncMap.enable_var_par_access( varRowP, true );
1833  ncMap.enable_var_par_access( varColP, true );
1834  ncMap.enable_var_par_access( varSP, true );
1835 
1836  varRowP->set_cur( offsetRead );
1837  varRowP->get( vecRow.data(), localSize );
1838  varColP->set_cur( offsetRead );
1839  varColP->get( vecCol.data(), localSize );
1840  varSP->set_cur( offsetRead );
1841  varSP->get( vecS.data(), localSize );
1842 
1843  if( readAreaA )
1844  {
1845  NcVar* varAreaAP = ncMap.get_var( "area_a" );
1846  ncMap.enable_var_par_access( varAreaAP, true );
1847  varAreaAP->set_cur( offsetReadA );
1848  varAreaAP->get( vecAreaA.data(), localSizeA );
1849  }
1850  if( readAreaB )
1851  {
1852  NcVar* varAreaBP = ncMap.get_var( "area_b" );
1853  ncMap.enable_var_par_access( varAreaBP, true );
1854  varAreaBP->set_cur( offsetReadB );
1855  varAreaBP->get( vecAreaB.data(), localSizeB );
1856  }
1857  ncMap.close();
1858  parReadDone = true;
1859  }
1860  }
1861 #endif
1862 
1863  if( !parReadDone )
1864  {
1865  _EXCEPTION1( "NetCDF-4/HDF5 map file \"%s\" cannot be read in parallel: "
1866  "parallel NetCDF (NETCDFPAR) is not configured "
1867  "(PNetCDF cannot read NetCDF-4 files)",
1868  strSource );
1869  }
1870  }
1871  } // end direct parallel read
1872  }
1873  else
1874 #endif // MOAB_HAVE_MPI
1875  {
1876  // =================================================================
1877  // Serial path (size == 1): read entire file on the single process.
1878  // =================================================================
1879  std::cout << " [ReadParallelMap]: Using serial read (single process)\n";
1880  NcFile ncMap( strSource, NcFile::ReadOnly );
1881  if( !ncMap.is_valid() )
1882  {
1883  _EXCEPTION1( "Unable to open input map file \"%s\"", strSource );
1884  }
1885 
1886  NcDim* dimNS = ncMap.get_dim( "n_s" );
1887  NcDim* dimNA = ncMap.get_dim( "n_a" );
1888  NcDim* dimNB = ncMap.get_dim( "n_b" );
1889  if( !dimNS || !dimNA || !dimNB )
1890  {
1891  _EXCEPTION1( "Map file \"%s\" missing required dimensions", strSource );
1892  }
1893  nS = static_cast< int >( dimNS->size() );
1894  nA = static_cast< int >( dimNA->size() );
1895  nB = static_cast< int >( dimNB->size() );
1896 
1897  localSize = nS;
1898  vecRow.resize( nS );
1899  vecCol.resize( nS );
1900  vecS.resize( nS );
1901 
1902  NcVar* varRowS = ncMap.get_var( "row" );
1903  NcVar* varColS = ncMap.get_var( "col" );
1904  NcVar* varSS = ncMap.get_var( "S" );
1905  varRowS->get( vecRow.data(), nS );
1906  varColS->get( vecCol.data(), nS );
1907  varSS->get( vecS.data(), nS );
1908 
1909  if( readAreaA )
1910  {
1911  vecAreaA.resize( nA );
1912  NcVar* varAreaAS = ncMap.get_var( "area_a" );
1913  if( varAreaAS ) varAreaAS->get( vecAreaA.data(), nA );
1914  }
1915  if( readAreaB )
1916  {
1917  vecAreaB.resize( nB );
1918  NcVar* varAreaBS = ncMap.get_var( "area_b" );
1919  if( varAreaBS ) varAreaBS->get( vecAreaB.data(), nB );
1920  }
1921  ncMap.close();
1922  }
1923 
1924  // =========================================================================
1925  // Phase 2: Redistribute sparse matrix entries to their final owning ranks.
1926  //
1927  // After Phase 1, each rank holds a portion of the sparse matrix entries
1928  // (either its owned rows from the buffered read, or a stripe from the
1929  // direct parallel read). The rows/cols are still 1-based (SCRIP format).
1930  //
1931  // This phase uses TupleList-based crystal router communication to send
1932  // entries to the rank that owns each row (trivial nB/size partitioning),
1933  // and optionally a second redistribution based on owned_dof_ids.
1934  // =========================================================================
1935 
1936 #ifdef MOAB_HAVE_EIGEN3
1937 
1938  typedef Eigen::Triplet< double > Triplet;
1939  std::vector< Triplet > tripletList;
1940 
1941 #ifdef MOAB_HAVE_MPI
1942  if( size > 1 )
1943  {
1944  // Trivial row partitioning for redistribution
1945  const int nPerPart = nB / size;
1946 
1947  moab::TupleList* tl = new moab::TupleList;
1948  unsigned numr = 1;
1949  tl->initialize( 3, 0, 0, numr, localSize ); // to_proc, row, col, value
1950  tl->enableWriteAccess();
1951 
1952  for( int i = 0; i < localSize; i++ )
1953  {
1954  int rowval = vecRow[i] - 1; // convert from 1-based (SCRIP) to 0-based
1955  int colval = vecCol[i] - 1;
1956  int to_proc = rowval / nPerPart;
1957  if( to_proc >= size ) to_proc = size - 1;
1958 
1959  int n = tl->get_n();
1960  tl->vi_wr[3 * n] = to_proc;
1961  tl->vi_wr[3 * n + 1] = rowval;
1962  tl->vi_wr[3 * n + 2] = colval;
1963  tl->vr_wr[n] = vecS[i];
1964  tl->inc_n();
1965  }
1966 
1967  // Crystal router: redistribute entries by row ownership
1968  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, *tl, 0 );
1969 
1970  if( owned_dof_ids.size() > 0 )
1971  {
1972  // we need to send desired dof to the rendezvous point
1973  moab::TupleList tl_re; //
1974  tl_re.initialize( 2, 0, 0, 0, owned_dof_ids.size() ); // to proc, value
1975  tl_re.enableWriteAccess();
1976  // send first to rendez_vous point, decided by trivial partitioning
1977 
1978  for( size_t i = 0; i < owned_dof_ids.size(); i++ )
1979  {
1980  int to_proc = -1;
1981  int dof_val = owned_dof_ids[i] - 1; // dofs are 1 based in the file, partition from 0 ?
1982  to_proc = dof_val / nPerPart;
1983  if( to_proc == size ) to_proc = size - 1;
1984 
1985  int n = tl_re.get_n();
1986  tl_re.vi_wr[2 * n] = to_proc;
1987  tl_re.vi_wr[2 * n + 1] = dof_val;
1988 
1989  tl_re.inc_n();
1990  }
1991  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tl_re, 0 );
1992  // now we know in tl_re where do we need to send back dof_val
1993  moab::TupleList::buffer sort_buffer;
1994  sort_buffer.buffer_init( tl_re.get_n() );
1995  tl_re.sort( 1, &sort_buffer ); // so now we order by value
1996 
1997  //sort_buffer.buffer_init( tl->get_n() );
1998 
1999  std::map< int, int > startDofIndex, endDofIndex; // indices in tl_re for values we want
2000  int dofVal = -1;
2001  if( tl_re.get_n() > 0 )
2002  {
2003  dofVal = tl_re.vi_rd[1]; // first dof val on this rank tl_re.vi_rd[2 * 0 + 1];
2004 
2005  startDofIndex[dofVal] = 0;
2006  endDofIndex[dofVal] = 0; // start and end
2007  for( unsigned k = 1; k < tl_re.get_n(); k++ )
2008  {
2009  int newDof = tl_re.vi_rd[2 * k + 1];
2010  if( dofVal == newDof )
2011  {
2012  endDofIndex[dofVal] = k; // increment by 1 actually
2013  }
2014  else
2015  {
2016  dofVal = newDof;
2017  startDofIndex[dofVal] = k;
2018  endDofIndex[dofVal] = k;
2019  }
2020  }
2021  }
2022  // basically, for each value we are interested in, index in tl_re with those values are
2023  // tl_re.vi_rd[2*startDofIndex+1] == valDof == tl_re.vi_rd[2*endDofIndex+1]
2024  // so now we have ordered
2025  // tl_re shows to what proc do we need to send the tuple (row, col, val)
2026  moab::TupleList* tl_back = new moab::TupleList;
2027  unsigned numr = 1; //
2028  // localSize is a good guess, but maybe it should be bigger ?
2029  // this could be bigger for repeated dofs
2030  tl_back->initialize( 3, 0, 0, numr, tl->get_n() ); // to proc, row, col, value
2031  tl_back->enableWriteAccess();
2032  // now loop over tl and tl_re to see where to send
2033  // form the new tuple, which will contain the desired dofs per task, per row or column distribution
2034 
2035  for( unsigned k = 0; k < tl->get_n(); k++ )
2036  {
2037  int valDof = tl->vi_rd[3 * k + 1]; // 1 for row, 2 for column // first value, it should be
2038  if( startDofIndex.find( valDof ) == startDofIndex.end() ) continue;
2039  for( int ire = startDofIndex[valDof]; ire <= endDofIndex[valDof]; ire++ )
2040  {
2041  int to_proc = tl_re.vi_rd[2 * ire];
2042  int n = tl_back->get_n();
2043  tl_back->vi_wr[3 * n] = to_proc;
2044  tl_back->vi_wr[3 * n + 1] = tl->vi_rd[3 * k + 1]; // row
2045  tl_back->vi_wr[3 * n + 2] = tl->vi_rd[3 * k + 2]; // col
2046  tl_back->vr_wr[n] = tl->vr_rd[k];
2047  tl_back->inc_n();
2048  }
2049  }
2050 
2051  // now communicate to the desired tasks:
2052  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, *tl_back, 0 );
2053 
2054  tl_re.reset(); // clear memory, although this will go out of scope
2055  tl->reset();
2056  tl = tl_back;
2057  }
2058 
2059  // set of row and col used on this task
2060  std::set< int > rowSet;
2061  std::set< int > colSet;
2062  // populate the sparsematrix, using rowMap and colMap
2063  int n = tl->get_n();
2064  for( int i = 0; i < n; i++ )
2065  {
2066  const int vecRowValue = tl->vi_wr[3 * i + 1];
2067  const int vecColValue = tl->vi_wr[3 * i + 2];
2068  rowSet.insert( vecRowValue );
2069  colSet.insert( vecColValue );
2070  }
2071  int index = 0;
2072  row_gdofmap.resize( rowSet.size() );
2073  for( auto setIt : rowSet )
2074  {
2075  row_gdofmap[index] = setIt;
2076  rowMap[setIt] = index++;
2077  }
2079  index = 0;
2080  col_gdofmap.resize( colSet.size() );
2081  for( auto setIt : colSet )
2082  {
2083  col_gdofmap[index] = setIt;
2084  colMap[setIt] = index++;
2085  }
2087 
2088  tripletList.reserve( n );
2089  for( int i = 0; i < n; i++ )
2090  {
2091  const int vecRowValue = tl->vi_wr[3 * i + 1];
2092  const int vecColValue = tl->vi_wr[3 * i + 2];
2093  double value = tl->vr_wr[i];
2094  tripletList.emplace_back( rowMap[vecRowValue], colMap[vecColValue], value );
2095  }
2096  tl->reset();
2097  }
2098  else
2099 #endif
2100  {
2101  // set of row and col used on this task
2102  std::set< int > rowSet;
2103  std::set< int > colSet;
2104  // populate the sparsematrix, using rowMap and colMap
2105  for( int i = 0; i < nS; i++ )
2106  {
2107  const int vecRowValue = vecRow[i] - 1;
2108  const int vecColValue = vecCol[i] - 1;
2109  rowSet.insert( vecRowValue );
2110  colSet.insert( vecColValue );
2111  }
2112 
2113  int index = 0;
2114  row_gdofmap.resize( rowSet.size() );
2115  for( auto setIt : rowSet )
2116  {
2117  row_gdofmap[index] = setIt;
2118  rowMap[setIt] = index++;
2119  }
2121  index = 0;
2122  col_gdofmap.resize( colSet.size() );
2123  for( auto setIt : colSet )
2124  {
2125  col_gdofmap[index] = setIt;
2126  colMap[setIt] = index++;
2127  }
2129 
2130  tripletList.reserve( nS );
2131  for( int i = 0; i < nS; i++ )
2132  {
2133  const int vecRowValue = vecRow[i] - 1; // the rows, cols are 1 based in the file
2134  const int vecColValue = vecCol[i] - 1; // sparse matrix will be 0 based
2135  double value = vecS[i];
2136  tripletList.emplace_back( rowMap[vecRowValue], colMap[vecColValue], value );
2137  }
2138  }
2139 
2140  m_weightMatrix.resize( m_nTotDofs_Dest, m_nTotDofs_SrcCov );
2141  m_rowVector.resize( m_nTotDofs_Dest );
2142  m_colVector.resize( m_nTotDofs_SrcCov );
2143  m_nTotDofs_Src = m_nTotDofs_SrcCov; // do we need both?
2144  // Preserve the map file's global source-DoF count (n_a) so the migration
2145  // can tell a masked source mesh (fewer cells than n_a -> drop is BfB-safe)
2146  // from a complete one (== n_a but a column missing -> real error).
2147  m_nTotDofs_SrcGlobal = nA;
2148  m_weightMatrix.setFromTriplets( tripletList.begin(), tripletList.end() );
2149  // Reset the source and target data first
2150  m_rowVector.setZero();
2151  m_colVector.setZero();
2152 #ifdef VERBOSE
2153  serializeSparseMatrix( m_weightMatrix, "map_operator_" + std::to_string( rank ) + ".txt" );
2154 #endif
2155 // #ifdef MOAB_HAVE_EIGEN3
2156 #endif
2157  // TODO: make this flexible and read the order from map with help of metadata
2158  m_nDofsPEl_Src = 1; // always assume FV-FV maps are read from file
2159  m_nDofsPEl_Dest = 1; // always assume FV-FV maps are read from file
2160 
2161  return moab::MB_SUCCESS;
2162 }

References BUFFERED_READ_CHUNK_BYTES, BUFFERED_READ_NNZ_THRESHOLD, detectMapFileNetCDFFormat(), moab::TupleList::enableWriteAccess(), moab::error(), moab::TupleList::get_n(), moab::TupleList::inc_n(), moab::index, moab::TupleList::initialize(), MAP_FORMAT_CLASSIC, MAP_FORMAT_NETCDF4, MAP_FORMAT_UNKNOWN, MB_SUCCESS, moab::TupleList::reset(), moab::TupleList::sort(), moab::TupleList::vi_rd, moab::TupleList::vi_wr, moab::TupleList::vr_rd, and moab::TupleList::vr_wr.

◆ serializeSparseMatrix()

template<typename SparseMatrixType >
void moab::TempestOnlineMap::serializeSparseMatrix ( const SparseMatrixType &  mat,
const std::string &  filename 
)
private

◆ set_col_dc_dofs()

moab::ErrorCode moab::TempestOnlineMap::set_col_dc_dofs ( std::vector< int > &  values_entities)

Definition at line 429 of file TempestOnlineMap.cpp.

430 {
431  // col_gdofmap has global dofs , that should be in the list of values, such that
432  // row_dtoc_dofmap[offsetDOF] = localDOF;
433  // we need to find col_dtoc_dofmap such that: col_gdofmap[ col_dtoc_dofmap[i] ] == values_entities [i];
434  // we know that col_gdofmap[0..(nbcols-1)] = global_col_dofs -> in values_entities
435  // form first inverse
436  //
437  // resize and initialize to -1 to signal that this value should not be used, if not set below
438  col_dtoc_dofmap.resize( values_entities.size(), -1 );
439  for( size_t j = 0; j < values_entities.size(); j++ )
440  {
441  // values are 1 based, but rowMap, colMap are not
442  const auto it = colMap.find( values_entities[j] - 1 );
443  if( it != colMap.end() ) col_dtoc_dofmap[j] = it->second;
444  }
445  return moab::MB_SUCCESS;
446 }

References MB_SUCCESS.

◆ set_row_dc_dofs()

moab::ErrorCode moab::TempestOnlineMap::set_row_dc_dofs ( std::vector< int > &  values_entities)

Definition at line 448 of file TempestOnlineMap.cpp.

449 {
450  // we need to find row_dtoc_dofmap such that: row_gdofmap[ row_dtoc_dofmap[i] ] == values_entities [i];
451  // resize and initialize to -1 to signal that this value should not be used, if not set below
452  row_dtoc_dofmap.resize( values_entities.size(), -1 );
453  for( size_t j = 0; j < values_entities.size(); j++ )
454  {
455  // values are 1 based, but rowMap, colMap are not
456  const auto it = rowMap.find( values_entities[j] - 1 );
457  if( it != rowMap.end() ) row_dtoc_dofmap[j] = it->second;
458  }
459  return moab::MB_SUCCESS;
460 }

References MB_SUCCESS.

◆ SetDestinationNDofsPerElement()

void moab::TempestOnlineMap::SetDestinationNDofsPerElement ( int  nt)
inline

Get the number of Degrees-Of-Freedom per element on the destination mesh.

Definition at line 650 of file TempestOnlineMap.hpp.

651 {
652  m_nDofsPEl_Dest = nt;
653 }

◆ SetDOFmapAssociation()

moab::ErrorCode moab::TempestOnlineMap::SetDOFmapAssociation ( DiscretizationType  srcType,
int  srcOrder,
bool  isSrcContinuous,
DataArray3D< int > *  srcdataGLLNodes,
DataArray3D< int > *  srcdataGLLNodesSrc,
DiscretizationType  destType,
int  destOrder,
bool  isTgtContinuous,
DataArray3D< int > *  tgtdataGLLNodes 
)

Compute the association between the solution tag global DoF numbering and the local matrix numbering so that matvec operations can be performed consistently.

Parameters
srcTypeThe discretization type of the source mesh
srcOrderThe order of the discretization on the source mesh
isSrcContinuousThe continuity of the discretization on the source mesh
srcdataGLLNodesThe GLL nodes on the source mesh
srcdataGLLNodesSrcThe GLL nodes on the source mesh
destTypeThe discretization type of the destination mesh
destOrderThe order of the discretization on the destination mesh
isTgtContinuousThe continuity of the discretization on the destination mesh
tgtdataGLLNodesThe GLL nodes on the destination mesh

Definition at line 168 of file TempestOnlineMap.cpp.

177 {
178  std::vector< bool > dgll_cgll_row_ldofmap, dgll_cgll_col_ldofmap, dgll_cgll_covcol_ldofmap;
179  std::vector< int > src_soln_gdofs, locsrc_soln_gdofs, tgt_soln_gdofs;
180 
181  // We are assuming that these are element based tags that are sized: np * np
182  m_srcDiscType = srcType;
183  m_destDiscType = destType;
184  m_input_order = srcOrder;
185  m_output_order = destOrder;
186 
187  bool vprint = is_root && false;
188 
189  // Compute and store the total number of source and target DoFs corresponding
190  // to number of rows and columns in the mapping.
191  // Now compute the mapping and store it for the covering mesh
192  int srcTagSize = ( m_eInputType == DiscretizationType_FV ? 1 : m_nDofsPEl_Src * m_nDofsPEl_Src );
194  {
195  assert( m_nDofsPEl_Src == 1 );
196  col_gdofmap.resize( m_remapper->m_covering_source_vertices.size(), UINT_MAX );
198  src_soln_gdofs.resize( m_remapper->m_covering_source_vertices.size(), -1 );
199  MB_CHK_ERR(
201  srcTagSize = 1;
202  }
203  else
204  {
205  col_gdofmap.resize( m_remapper->m_covering_source_entities.size() * srcTagSize, UINT_MAX );
206  col_dtoc_dofmap.resize( m_remapper->m_covering_source_entities.size() * srcTagSize, -1 );
207  src_soln_gdofs.resize( m_remapper->m_covering_source_entities.size() * srcTagSize, -1 );
208  MB_CHK_ERR(
210  }
211 
212  m_nTotDofs_SrcCov = 0;
213  if( srcdataGLLNodes == nullptr )
214  {
215  /* we only have a mapping for elements as DoFs */
216  for( unsigned i = 0; i < col_gdofmap.size(); ++i )
217  {
218  auto gdof = src_soln_gdofs[i];
219  assert( gdof > 0 );
220  col_gdofmap[i] = gdof - 1;
221  col_dtoc_dofmap[i] = i;
222  if( vprint ) std::cout << "Col: " << i << ", " << col_gdofmap[i] << "\n";
224  }
225  }
226  else
227  {
228  if( isSrcContinuous )
229  dgll_cgll_covcol_ldofmap.resize( m_remapper->m_covering_source_entities.size() * srcTagSize, false );
230  // Put these remap coefficients into the SparseMatrix map
231  for( unsigned j = 0; j < m_remapper->m_covering_source_entities.size(); j++ )
232  {
233  for( int p = 0; p < m_nDofsPEl_Src; p++ )
234  {
235  for( int q = 0; q < m_nDofsPEl_Src; q++ )
236  {
237  const int localDOF = ( *srcdataGLLNodes )[p][q][j] - 1;
238  const int offsetDOF = j * srcTagSize + p * m_nDofsPEl_Src + q;
239  if( isSrcContinuous && !dgll_cgll_covcol_ldofmap[localDOF] )
240  {
242  dgll_cgll_covcol_ldofmap[localDOF] = true;
243  }
244  if( !isSrcContinuous ) m_nTotDofs_SrcCov++;
245  assert( src_soln_gdofs[offsetDOF] > 0 );
246  // For CGLL: weight matrix uses localDOF (continuous shared node index)
247  // as column index → col_gdofmap must be indexed by localDOF
248  // For DGLL: weight matrix uses offsetDOF (= elem*nP*nP + p*nP + q)
249  // as column index → col_gdofmap must be indexed by offsetDOF
250  if( isSrcContinuous )
251  {
252  col_gdofmap[localDOF] = src_soln_gdofs[offsetDOF] - 1;
253  col_dtoc_dofmap[offsetDOF] = localDOF;
254  }
255  else
256  {
257  col_gdofmap[offsetDOF] = src_soln_gdofs[offsetDOF] - 1;
258  col_dtoc_dofmap[offsetDOF] = offsetDOF;
259  }
260  }
261  }
262  }
263  }
264 
266  {
267  assert( m_nDofsPEl_Src == 1 );
268  srccol_gdofmap.resize( m_remapper->m_source_vertices.size(), UINT_MAX );
270  locsrc_soln_gdofs.resize( m_remapper->m_source_vertices.size(), -1 );
272  }
273  else
274  {
275  srccol_gdofmap.resize( m_remapper->m_source_entities.size() * srcTagSize, UINT_MAX );
276  srccol_dtoc_dofmap.resize( m_remapper->m_source_entities.size() * srcTagSize, -1 );
277  locsrc_soln_gdofs.resize( m_remapper->m_source_entities.size() * srcTagSize, -1 );
279  }
280 
281  // Now compute the mapping and store it for the original source mesh
282  m_nTotDofs_Src = 0;
283  if( srcdataGLLNodesSrc == nullptr )
284  {
285  /* we only have a mapping for elements as DoFs */
286  for( unsigned i = 0; i < srccol_gdofmap.size(); ++i )
287  {
288  auto gdof = locsrc_soln_gdofs[i];
289  assert( gdof > 0 );
290  srccol_gdofmap[i] = gdof - 1;
291  srccol_dtoc_dofmap[i] = i;
292  m_nTotDofs_Src++;
293  }
294  }
295  else
296  {
297  if( isSrcContinuous ) dgll_cgll_col_ldofmap.resize( m_remapper->m_source_entities.size() * srcTagSize, false );
298  // Put these remap coefficients into the SparseMatrix map
299  for( unsigned j = 0; j < m_remapper->m_source_entities.size(); j++ )
300  {
301  for( int p = 0; p < m_nDofsPEl_Src; p++ )
302  {
303  for( int q = 0; q < m_nDofsPEl_Src; q++ )
304  {
305  const int localDOF = ( *srcdataGLLNodesSrc )[p][q][j] - 1;
306  const int offsetDOF = j * srcTagSize + p * m_nDofsPEl_Src + q;
307  if( isSrcContinuous && !dgll_cgll_col_ldofmap[localDOF] )
308  {
309  m_nTotDofs_Src++;
310  dgll_cgll_col_ldofmap[localDOF] = true;
311  }
312  if( !isSrcContinuous ) m_nTotDofs_Src++;
313  assert( locsrc_soln_gdofs[offsetDOF] > 0 );
314  if( isSrcContinuous )
315  {
316  srccol_gdofmap[localDOF] = locsrc_soln_gdofs[offsetDOF] - 1;
317  srccol_dtoc_dofmap[offsetDOF] = localDOF;
318  }
319  else
320  {
321  srccol_gdofmap[offsetDOF] = locsrc_soln_gdofs[offsetDOF] - 1;
322  srccol_dtoc_dofmap[offsetDOF] = offsetDOF;
323  }
324  }
325  }
326  }
327  }
328 
329  int tgtTagSize = ( m_eOutputType == DiscretizationType_FV ? 1 : m_nDofsPEl_Dest * m_nDofsPEl_Dest );
331  {
332  assert( m_nDofsPEl_Dest == 1 );
333  row_gdofmap.resize( m_remapper->m_target_vertices.size(), UINT_MAX );
335  tgt_soln_gdofs.resize( m_remapper->m_target_vertices.size(), -1 );
337  tgtTagSize = 1;
338  }
339  else
340  {
341  row_gdofmap.resize( m_remapper->m_target_entities.size() * tgtTagSize, UINT_MAX );
342  row_dtoc_dofmap.resize( m_remapper->m_target_entities.size() * tgtTagSize, -1 );
343  tgt_soln_gdofs.resize( m_remapper->m_target_entities.size() * tgtTagSize, -1 );
345  }
346 
347  // Now compute the mapping and store it for the target mesh
348  // To access the GID for each row: row_gdofmap [ row_ldofmap [ 0 : local_ndofs ] ] = GDOF
349  m_nTotDofs_Dest = 0;
350  if( tgtdataGLLNodes == nullptr )
351  {
352  /* we only have a mapping for elements as DoFs */
353  for( unsigned i = 0; i < row_gdofmap.size(); ++i )
354  {
355  auto gdof = tgt_soln_gdofs[i];
356  assert( gdof > 0 );
357  row_gdofmap[i] = gdof - 1;
358  row_dtoc_dofmap[i] = i;
359  if( vprint ) std::cout << "Row: " << i << ", " << row_gdofmap[i] << "\n";
360  m_nTotDofs_Dest++;
361  }
362  }
363  else
364  {
365  if( isTgtContinuous ) dgll_cgll_row_ldofmap.resize( m_remapper->m_target_entities.size() * tgtTagSize, false );
366  // Put these remap coefficients into the SparseMatrix map
367  for( unsigned j = 0; j < m_remapper->m_target_entities.size(); j++ )
368  {
369  for( int p = 0; p < m_nDofsPEl_Dest; p++ )
370  {
371  for( int q = 0; q < m_nDofsPEl_Dest; q++ )
372  {
373  const int localDOF = ( *tgtdataGLLNodes )[p][q][j] - 1;
374  const int offsetDOF = j * tgtTagSize + p * m_nDofsPEl_Dest + q;
375  if( isTgtContinuous && !dgll_cgll_row_ldofmap[localDOF] )
376  {
377  m_nTotDofs_Dest++;
378  dgll_cgll_row_ldofmap[localDOF] = true;
379  }
380  if( !isTgtContinuous ) m_nTotDofs_Dest++;
381  assert( tgt_soln_gdofs[offsetDOF] > 0 );
382  if( isTgtContinuous )
383  {
384  row_gdofmap[localDOF] = tgt_soln_gdofs[offsetDOF] - 1;
385  row_dtoc_dofmap[offsetDOF] = localDOF;
386  }
387  else
388  {
389  row_gdofmap[offsetDOF] = tgt_soln_gdofs[offsetDOF] - 1;
390  row_dtoc_dofmap[offsetDOF] = offsetDOF;
391  }
392  if( vprint )
393  std::cout << "Row: " << offsetDOF << ", " << localDOF << ", " << row_gdofmap[offsetDOF] << ", "
394  << m_nTotDofs_Dest << "\n";
395  }
396  }
397  }
398  }
399 
400  // Let us also allocate the local representation of the sparse matrix
401 #if defined( MOAB_HAVE_EIGEN3 ) && defined( VERBOSE )
402  if( is_root )
403  {
404  std::cout << "[" << rank << "] DoFs: row = " << m_nTotDofs_Dest << " (gdofmap.size=" << row_gdofmap.size()
405  << "), col_src = " << m_nTotDofs_Src << ", col_cov = " << m_nTotDofs_SrcCov
406  << " (gdofmap.size=" << col_gdofmap.size() << ")\n";
407  }
408 #endif
409 
410  // check monotonicity of row_gdofmap and col_gdofmap
411 #ifdef CHECK_INCREASING_DOF
412  for( size_t i = 0; i < row_gdofmap.size() - 1; i++ )
413  {
414  if( row_gdofmap[i] > row_gdofmap[i + 1] )
415  std::cout << " on rank " << rank << " in row_gdofmap[" << i << "]=" << row_gdofmap[i] << " > row_gdofmap["
416  << i + 1 << "]=" << row_gdofmap[i + 1] << " \n";
417  }
418  for( size_t i = 0; i < col_gdofmap.size() - 1; i++ )
419  {
420  if( col_gdofmap[i] > col_gdofmap[i + 1] )
421  std::cout << " on rank " << rank << " in col_gdofmap[" << i << "]=" << col_gdofmap[i] << " > col_gdofmap["
422  << i + 1 << "]=" << col_gdofmap[i + 1] << " \n";
423  }
424 #endif
425 
426  return moab::MB_SUCCESS;
427 }

References MB_CHK_ERR, and MB_SUCCESS.

◆ SetDOFmapTags()

moab::ErrorCode moab::TempestOnlineMap::SetDOFmapTags ( const std::string  srcDofTagName,
const std::string  tgtDofTagName 
)

Store the tag names associated with global DoF ids for source and target meshes to be used for mapping.

Parameters
srcDofTagNameThe tag name associated with global DoF ids for the source mesh
tgtDofTagNameThe tag name associated with global DoF ids for the target mesh

Definition at line 136 of file TempestOnlineMap.cpp.

138 {
139  moab::ErrorCode rval;
140 
141  int tagSize = 0;
143  rval =
144  m_interface->tag_get_handle( srcDofTagName.c_str(), tagSize, MB_TYPE_INTEGER, this->m_dofTagSrc, MB_TAG_ANY );
145 
147  {
148  MB_CHK_SET_ERR( MB_FAILURE, "DoF tag is not set correctly for source mesh." );
149  }
150  else
151  MB_CHK_ERR( rval );
152 
154  rval =
155  m_interface->tag_get_handle( tgtDofTagName.c_str(), tagSize, MB_TYPE_INTEGER, this->m_dofTagDest, MB_TAG_ANY );
157  {
158  MB_CHK_SET_ERR( MB_FAILURE, "DoF tag is not set correctly for target mesh." );
159  }
160  else
161  MB_CHK_ERR( rval );
162 
163  return moab::MB_SUCCESS;
164 }

References ErrorCode, MB_CHK_ERR, MB_CHK_SET_ERR, MB_SUCCESS, MB_TAG_ANY, MB_TAG_NOT_FOUND, and MB_TYPE_INTEGER.

◆ SetMeshInput()

void moab::TempestOnlineMap::SetMeshInput ( Mesh *  imesh)
inline

Definition at line 493 of file TempestOnlineMap.hpp.

494  {
495  m_meshInput = imesh;
496  };

References m_meshInput.

◆ SetSourceNDofsPerElement()

void moab::TempestOnlineMap::SetSourceNDofsPerElement ( int  ns)
inline

Set the number of Degrees-Of-Freedom per element on the source mesh.

Definition at line 645 of file TempestOnlineMap.hpp.

646 {
647  m_nDofsPEl_Src = ns;
648 }

◆ setup_sizes_dimensions()

void moab::TempestOnlineMap::setup_sizes_dimensions ( )
private

Definition at line 98 of file TempestOnlineMap.cpp.

99 {
100  if( m_meshInputCov )
101  {
102  std::vector< std::string > dimNames;
103  std::vector< int > dimSizes;
104  dimNames.push_back( "num_elem" );
105  dimSizes.push_back( m_meshInputCov->faces.size() );
106 
107  this->InitializeSourceDimensions( dimNames, dimSizes );
108  }
109 
110  if( m_meshOutput )
111  {
112  std::vector< std::string > dimNames;
113  std::vector< int > dimSizes;
114  dimNames.push_back( "num_elem" );
115  dimSizes.push_back( m_meshOutput->faces.size() );
116 
117  this->InitializeTargetDimensions( dimNames, dimSizes );
118  }
119 }

Referenced by TempestOnlineMap().

◆ WriteHDF5MapFile()

moab::ErrorCode moab::TempestOnlineMap::WriteHDF5MapFile ( const std::string &  filename)
private

Parallel I/O with NetCDF to write out the SCRIP file from multiple processors.

Need to get the global maximum of number of vertices per element Key issue is that when calling InitializeCoordinatesFromMeshFV, the allocation for dVertexLon/dVertexLat are made based on the maximum vertices in the current process. However, when writing this out, other processes may have a different size for the same array. This is hence a mess to consolidate in h5mtoscrip eventually.

Definition at line 844 of file TempestOnlineMapIO.cpp.

845 {
846  /**
847  * Need to get the global maximum of number of vertices per element
848  * Key issue is that when calling InitializeCoordinatesFromMeshFV, the allocation for
849  *dVertexLon/dVertexLat are made based on the maximum vertices in the current process. However,
850  *when writing this out, other processes may have a different size for the same array. This is
851  *hence a mess to consolidate in h5mtoscrip eventually.
852  **/
853 
854  /* Let us compute all relevant data for the current original source mesh on the process */
855  DataArray1D< double > vecSourceFaceArea, vecTargetFaceArea;
856  DataArray1D< double > dSourceCenterLon, dSourceCenterLat, dTargetCenterLon, dTargetCenterLat;
857  DataArray2D< double > dSourceVertexLon, dSourceVertexLat, dTargetVertexLon, dTargetVertexLat;
859  {
860  this->InitializeCoordinatesFromMeshFV(
861  *m_meshInput, dSourceCenterLon, dSourceCenterLat, dSourceVertexLon, dSourceVertexLat,
862  ( this->m_remapper->m_source_type == moab::TempestRemapper::RLL ) /* fLatLon = false */,
864 
865  vecSourceFaceArea.Allocate( m_meshInput->vecFaceArea.GetRows() );
866  for( unsigned i = 0; i < m_meshInput->vecFaceArea.GetRows(); ++i )
867  vecSourceFaceArea[i] = m_meshInput->vecFaceArea[i];
868  }
869  else
870  {
871  DataArray3D< double > dataGLLJacobianSrc;
872  this->InitializeCoordinatesFromMeshFE( *m_meshInput, m_nDofsPEl_Src, dataGLLNodesSrc, dSourceCenterLon,
873  dSourceCenterLat, dSourceVertexLon, dSourceVertexLat );
874 
875  // Generate the continuous Jacobian for input mesh
876  GenerateMetaData( *m_meshInput, m_nDofsPEl_Src, false /* fBubble */, dataGLLNodesSrc, dataGLLJacobianSrc );
877 
879  {
880  GenerateUniqueJacobian( dataGLLNodesSrc, dataGLLJacobianSrc, vecSourceFaceArea );
881  }
882  else
883  {
884  GenerateDiscontinuousJacobian( dataGLLJacobianSrc, vecSourceFaceArea );
885  }
886  }
887 
889  {
890  this->InitializeCoordinatesFromMeshFV(
891  *m_meshOutput, dTargetCenterLon, dTargetCenterLat, dTargetVertexLon, dTargetVertexLat,
892  ( this->m_remapper->m_target_type == moab::TempestRemapper::RLL ) /* fLatLon = false */,
894 
895  vecTargetFaceArea.Allocate( m_meshOutput->vecFaceArea.GetRows() );
896  for( unsigned i = 0; i < m_meshOutput->vecFaceArea.GetRows(); ++i )
897  vecTargetFaceArea[i] = m_meshOutput->vecFaceArea[i];
898  }
899  else
900  {
901  DataArray3D< double > dataGLLJacobianDest;
902  this->InitializeCoordinatesFromMeshFE( *m_meshOutput, m_nDofsPEl_Dest, dataGLLNodesDest, dTargetCenterLon,
903  dTargetCenterLat, dTargetVertexLon, dTargetVertexLat );
904 
905  // Generate the continuous Jacobian for input mesh
906  GenerateMetaData( *m_meshOutput, m_nDofsPEl_Dest, false /* fBubble */, dataGLLNodesDest, dataGLLJacobianDest );
907 
909  {
910  GenerateUniqueJacobian( dataGLLNodesDest, dataGLLJacobianDest, vecTargetFaceArea );
911  }
912  else
913  {
914  GenerateDiscontinuousJacobian( dataGLLJacobianDest, vecTargetFaceArea );
915  }
916  }
917 
918  moab::EntityHandle& m_meshOverlapSet = m_remapper->m_overlap_set;
919  int tot_src_ents = m_remapper->m_source_entities.size();
920  int tot_tgt_ents = m_remapper->m_target_entities.size();
921  int tot_src_size = dSourceCenterLon.GetRows();
922  int tot_tgt_size = m_dTargetCenterLon.GetRows();
923  int tot_vsrc_size = dSourceVertexLon.GetRows() * dSourceVertexLon.GetColumns();
924  int tot_vtgt_size = m_dTargetVertexLon.GetRows() * m_dTargetVertexLon.GetColumns();
925 
926  const int weightMatNNZ = m_weightMatrix.nonZeros();
927  moab::Tag tagMapMetaData, tagMapIndexRow, tagMapIndexCol, tagMapValues, srcEleIDs, tgtEleIDs;
928  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_DATA", 13, moab::MB_TYPE_INTEGER, tagMapMetaData,
930  "Retrieving tag handles failed" );
931  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_ROWS", weightMatNNZ, moab::MB_TYPE_INTEGER, tagMapIndexRow,
933  "Retrieving tag handles failed" );
934  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_COLS", weightMatNNZ, moab::MB_TYPE_INTEGER, tagMapIndexCol,
936  "Retrieving tag handles failed" );
937  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SMAT_VALS", weightMatNNZ, moab::MB_TYPE_DOUBLE, tagMapValues,
939  "Retrieving tag handles failed" );
940  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceGIDS", tot_src_size, moab::MB_TYPE_INTEGER, srcEleIDs,
942  "Retrieving tag handles failed" );
943  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetGIDS", tot_tgt_size, moab::MB_TYPE_INTEGER, tgtEleIDs,
945  "Retrieving tag handles failed" );
946  moab::Tag srcAreaValues, tgtAreaValues;
947  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceAreas", tot_src_size, moab::MB_TYPE_DOUBLE, srcAreaValues,
949  "Retrieving tag handles failed" );
950  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetAreas", tot_tgt_size, moab::MB_TYPE_DOUBLE, tgtAreaValues,
952  "Retrieving tag handles failed" );
953  moab::Tag tagSrcCoordsCLon, tagSrcCoordsCLat, tagTgtCoordsCLon, tagTgtCoordsCLat;
954  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordCenterLon", tot_src_size, moab::MB_TYPE_DOUBLE,
955  tagSrcCoordsCLon,
957  "Retrieving tag handles failed" );
958  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordCenterLat", tot_src_size, moab::MB_TYPE_DOUBLE,
959  tagSrcCoordsCLat,
961  "Retrieving tag handles failed" );
962  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordCenterLon", tot_tgt_size, moab::MB_TYPE_DOUBLE,
963  tagTgtCoordsCLon,
965  "Retrieving tag handles failed" );
966  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordCenterLat", tot_tgt_size, moab::MB_TYPE_DOUBLE,
967  tagTgtCoordsCLat,
969  "Retrieving tag handles failed" );
970  moab::Tag tagSrcCoordsVLon, tagSrcCoordsVLat, tagTgtCoordsVLon, tagTgtCoordsVLat;
971  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordVertexLon", tot_vsrc_size, moab::MB_TYPE_DOUBLE,
972  tagSrcCoordsVLon,
974  "Retrieving tag handles failed" );
975  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceCoordVertexLat", tot_vsrc_size, moab::MB_TYPE_DOUBLE,
976  tagSrcCoordsVLat,
978  "Retrieving tag handles failed" );
979  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordVertexLon", tot_vtgt_size, moab::MB_TYPE_DOUBLE,
980  tagTgtCoordsVLon,
982  "Retrieving tag handles failed" );
983  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetCoordVertexLat", tot_vtgt_size, moab::MB_TYPE_DOUBLE,
984  tagTgtCoordsVLat,
986  "Retrieving tag handles failed" );
987  moab::Tag srcMaskValues, tgtMaskValues;
988  if( m_iSourceMask.IsAttached() )
989  {
990  MB_CHK_SET_ERR( m_interface->tag_get_handle( "SourceMask", m_iSourceMask.GetRows(), moab::MB_TYPE_INTEGER,
991  srcMaskValues,
993  "Retrieving tag handles failed" );
994  }
995  if( m_iTargetMask.IsAttached() )
996  {
997  MB_CHK_SET_ERR( m_interface->tag_get_handle( "TargetMask", m_iTargetMask.GetRows(), moab::MB_TYPE_INTEGER,
998  tgtMaskValues,
1000  "Retrieving tag handles failed" );
1001  }
1002 
1003  std::vector< int > smatrowvals( weightMatNNZ ), smatcolvals( weightMatNNZ );
1004  std::vector< double > smatvals( weightMatNNZ );
1005  // const double* smatvals = m_weightMatrix.valuePtr();
1006  // Loop over the matrix entries and find the max global ID for rows and columns
1007  for( int k = 0, offset = 0; k < m_weightMatrix.outerSize(); ++k )
1008  {
1009  for( moab::TempestOnlineMap::WeightMatrix::InnerIterator it( m_weightMatrix, k ); it; ++it, ++offset )
1010  {
1011  smatrowvals[offset] = this->GetRowGlobalDoF( it.row() );
1012  smatcolvals[offset] = this->GetColGlobalDoF( it.col() );
1013  smatvals[offset] = it.value();
1014  }
1015  }
1016 
1017  /* Set the global IDs for the DoFs */
1018  ////
1019  // col_gdofmap [ col_ldofmap [ 0 : local_ndofs ] ] = GDOF
1020  // row_gdofmap [ row_ldofmap [ 0 : local_ndofs ] ] = GDOF
1021  ////
1022  int maxrow = 0, maxcol = 0;
1023  std::vector< int > src_global_dofs( tot_src_size ), tgt_global_dofs( tot_tgt_size );
1024  for( int i = 0; i < tot_src_size; ++i )
1025  {
1026  src_global_dofs[i] = srccol_gdofmap[i];
1027  maxcol = ( src_global_dofs[i] > maxcol ) ? src_global_dofs[i] : maxcol;
1028  }
1029 
1030  for( int i = 0; i < tot_tgt_size; ++i )
1031  {
1032  tgt_global_dofs[i] = row_gdofmap[i];
1033  maxrow = ( tgt_global_dofs[i] > maxrow ) ? tgt_global_dofs[i] : maxrow;
1034  }
1035 
1036  ///////////////////////////////////////////////////////////////////////////
1037  // The metadata in H5M file contains the following data:
1038  //
1039  // 1. n_a: Total source entities: (number of elements in source mesh)
1040  // 2. n_b: Total target entities: (number of elements in target mesh)
1041  // 3. nv_a: Max edge size of elements in source mesh
1042  // 4. nv_b: Max edge size of elements in target mesh
1043  // 5. maxrows: Number of rows in remap weight matrix
1044  // 6. maxcols: Number of cols in remap weight matrix
1045  // 7. nnz: Number of total nnz in sparse remap weight matrix
1046  // 8. np_a: The order of the field description on the source mesh: >= 1
1047  // 9. np_b: The order of the field description on the target mesh: >= 1
1048  // 10. method_a: The type of discretization for field on source mesh: [0 = FV, 1 = cGLL, 2 =
1049  // dGLL]
1050  // 11. method_b: The type of discretization for field on target mesh: [0 = FV, 1 = cGLL, 2 =
1051  // dGLL]
1052  // 12. conserved: Flag to specify whether the remap operator has conservation constraints: [0,
1053  // 1]
1054  // 13. monotonicity: Flags to specify whether the remap operator has monotonicity constraints:
1055  // [0, 1, 2]
1056  //
1057  ///////////////////////////////////////////////////////////////////////////
1058  int map_disc_details[6];
1059  map_disc_details[0] = m_nDofsPEl_Src;
1060  map_disc_details[1] = m_nDofsPEl_Dest;
1062  ? 0
1063  : ( m_srcDiscType == DiscretizationType_CGLL ? 1 : 2 ) );
1065  ? 0
1066  : ( m_destDiscType == DiscretizationType_CGLL ? 1 : 2 ) );
1067  map_disc_details[4] = ( m_bConserved ? 1 : 0 );
1068  map_disc_details[5] = m_iMonotonicity;
1069 
1070 #ifdef MOAB_HAVE_MPI
1071  int loc_smatmetadata[13] = { tot_src_ents,
1072  tot_tgt_ents,
1075  maxrow + 1,
1076  maxcol + 1,
1077  weightMatNNZ,
1078  map_disc_details[0],
1079  map_disc_details[1],
1080  map_disc_details[2],
1081  map_disc_details[3],
1082  map_disc_details[4],
1083  map_disc_details[5] };
1084  MB_CHK_SET_ERR( m_interface->tag_set_data( tagMapMetaData, &m_meshOverlapSet, 1, &loc_smatmetadata[0] ),
1085  "Setting local tag data failed" );
1086  int glb_smatmetadata[13] = { 0,
1087  0,
1088  0,
1089  0,
1090  0,
1091  0,
1092  0,
1093  map_disc_details[0],
1094  map_disc_details[1],
1095  map_disc_details[2],
1096  map_disc_details[3],
1097  map_disc_details[4],
1098  map_disc_details[5] };
1099  int loc_buf[7] = {
1100  tot_src_ents, tot_tgt_ents, weightMatNNZ, m_remapper->max_source_edges, m_remapper->max_target_edges,
1101  maxrow, maxcol };
1102  int glb_buf[4] = { 0, 0, 0, 0 };
1103  MPI_Reduce( &loc_buf[0], &glb_buf[0], 3, MPI_INT, MPI_SUM, 0, m_pcomm->comm() );
1104  glb_smatmetadata[0] = glb_buf[0];
1105  glb_smatmetadata[1] = glb_buf[1];
1106  glb_smatmetadata[6] = glb_buf[2];
1107  MPI_Reduce( &loc_buf[3], &glb_buf[0], 4, MPI_INT, MPI_MAX, 0, m_pcomm->comm() );
1108  glb_smatmetadata[2] = glb_buf[0];
1109  glb_smatmetadata[3] = glb_buf[1];
1110  glb_smatmetadata[4] = glb_buf[2];
1111  glb_smatmetadata[5] = glb_buf[3];
1112 #else
1113  int glb_smatmetadata[13] = { tot_src_ents,
1114  tot_tgt_ents,
1117  maxrow,
1118  maxcol,
1119  weightMatNNZ,
1120  map_disc_details[0],
1121  map_disc_details[1],
1122  map_disc_details[2],
1123  map_disc_details[3],
1124  map_disc_details[4],
1125  map_disc_details[5] };
1126 #endif
1127  // These values represent number of rows and columns. So should be 1-based.
1128  glb_smatmetadata[4]++;
1129  glb_smatmetadata[5]++;
1130 
1131  if( this->is_root )
1132  {
1133  std::cout << " " << this->rank << " Writing remap weights with size [" << glb_smatmetadata[4] << " X "
1134  << glb_smatmetadata[5] << "] and NNZ = " << glb_smatmetadata[6] << std::endl;
1135  EntityHandle root_set = 0;
1136  MB_CHK_SET_ERR( m_interface->tag_set_data( tagMapMetaData, &root_set, 1, &glb_smatmetadata[0] ),
1137  "Setting local tag data failed" );
1138  }
1139 
1140  int dsize;
1141  const int numval = weightMatNNZ;
1142  const void* smatrowvals_d = smatrowvals.data();
1143  const void* smatcolvals_d = smatcolvals.data();
1144  const void* smatvals_d = smatvals.data();
1145  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagMapIndexRow, &m_meshOverlapSet, 1, &smatrowvals_d, &numval ),
1146  "Setting local tag data failed" );
1147  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagMapIndexCol, &m_meshOverlapSet, 1, &smatcolvals_d, &numval ),
1148  "Setting local tag data failed" );
1149  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagMapValues, &m_meshOverlapSet, 1, &smatvals_d, &numval ),
1150  "Setting local tag data failed" );
1151 
1152  /* Set the global IDs for the DoFs */
1153  const void* srceleidvals_d = src_global_dofs.data();
1154  const void* tgteleidvals_d = tgt_global_dofs.data();
1155  dsize = src_global_dofs.size();
1156  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( srcEleIDs, &m_meshOverlapSet, 1, &srceleidvals_d, &dsize ),
1157  "Setting local tag data failed" );
1158  dsize = tgt_global_dofs.size();
1159  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tgtEleIDs, &m_meshOverlapSet, 1, &tgteleidvals_d, &dsize ),
1160  "Setting local tag data failed" );
1161 
1162  /* Set the source and target areas */
1163  const void* srcareavals_d = vecSourceFaceArea;
1164  const void* tgtareavals_d = vecTargetFaceArea;
1165  dsize = tot_src_size;
1166  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( srcAreaValues, &m_meshOverlapSet, 1, &srcareavals_d, &dsize ),
1167  "Setting local tag data failed" );
1168  dsize = tot_tgt_size;
1169  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tgtAreaValues, &m_meshOverlapSet, 1, &tgtareavals_d, &dsize ),
1170  "Setting local tag data failed" );
1171 
1172  /* Set the coordinates for source and target center vertices */
1173  const void* srccoordsclonvals_d = &dSourceCenterLon[0];
1174  const void* srccoordsclatvals_d = &dSourceCenterLat[0];
1175  dsize = dSourceCenterLon.GetRows();
1176  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsCLon, &m_meshOverlapSet, 1, &srccoordsclonvals_d, &dsize ),
1177  "Setting local tag data failed" );
1178  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsCLat, &m_meshOverlapSet, 1, &srccoordsclatvals_d, &dsize ),
1179  "Setting local tag data failed" );
1180  const void* tgtcoordsclonvals_d = &m_dTargetCenterLon[0];
1181  const void* tgtcoordsclatvals_d = &m_dTargetCenterLat[0];
1182  dsize = vecTargetFaceArea.GetRows();
1183  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsCLon, &m_meshOverlapSet, 1, &tgtcoordsclonvals_d, &dsize ),
1184  "Setting local tag data failed" );
1185  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsCLat, &m_meshOverlapSet, 1, &tgtcoordsclatvals_d, &dsize ),
1186  "Setting local tag data failed" );
1187 
1188  /* Set the coordinates for source and target element vertices */
1189  const void* srccoordsvlonvals_d = &( dSourceVertexLon[0][0] );
1190  const void* srccoordsvlatvals_d = &( dSourceVertexLat[0][0] );
1191  dsize = dSourceVertexLon.GetRows() * dSourceVertexLon.GetColumns();
1192  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsVLon, &m_meshOverlapSet, 1, &srccoordsvlonvals_d, &dsize ),
1193  "Setting local tag data failed" );
1194  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagSrcCoordsVLat, &m_meshOverlapSet, 1, &srccoordsvlatvals_d, &dsize ),
1195  "Setting local tag data failed" );
1196  const void* tgtcoordsvlonvals_d = &( m_dTargetVertexLon[0][0] );
1197  const void* tgtcoordsvlatvals_d = &( m_dTargetVertexLat[0][0] );
1198  dsize = m_dTargetVertexLon.GetRows() * m_dTargetVertexLon.GetColumns();
1199  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsVLon, &m_meshOverlapSet, 1, &tgtcoordsvlonvals_d, &dsize ),
1200  "Setting local tag data failed" );
1201  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tagTgtCoordsVLat, &m_meshOverlapSet, 1, &tgtcoordsvlatvals_d, &dsize ),
1202  "Setting local tag data failed" );
1203 
1204  /* Set the masks for source and target meshes if available */
1205  if( m_iSourceMask.IsAttached() )
1206  {
1207  const void* srcmaskvals_d = m_iSourceMask;
1208  dsize = m_iSourceMask.GetRows();
1209  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( srcMaskValues, &m_meshOverlapSet, 1, &srcmaskvals_d, &dsize ),
1210  "Setting local tag data failed" );
1211  }
1212 
1213  if( m_iTargetMask.IsAttached() )
1214  {
1215  const void* tgtmaskvals_d = m_iTargetMask;
1216  dsize = m_iTargetMask.GetRows();
1217  MB_CHK_SET_ERR( m_interface->tag_set_by_ptr( tgtMaskValues, &m_meshOverlapSet, 1, &tgtmaskvals_d, &dsize ),
1218  "Setting local tag data failed" );
1219  }
1220 
1221 #ifdef MOAB_HAVE_MPI
1222  const char* writeOptions = ( this->size > 1 ? "PARALLEL=WRITE_PART" : "" );
1223 #else
1224  const char* writeOptions = "";
1225 #endif
1226 
1227  // EntityHandle sets[3] = {m_remapper->m_source_set, m_remapper->m_target_set, m_remapper->m_overlap_set};
1228  EntityHandle sets[1] = { m_remapper->m_overlap_set };
1229  MB_CHK_ERR( m_interface->write_file( strOutputFile.c_str(), NULL, writeOptions, sets, 1 ) );
1230 
1231 #ifdef WRITE_SCRIP_FILE
1232  sstr.str( "" );
1233  sstr << ctx.outFilename.substr( 0, lastindex ) << "_" << proc_id << ".nc";
1234  std::map< std::string, std::string > mapAttributes;
1235  mapAttributes["Creator"] = "MOAB mbtempest workflow";
1236  if( !ctx.proc_id ) std::cout << "Writing offline map to file: " << sstr.str() << std::endl;
1237  this->Write( strOutputFile.c_str(), mapAttributes, NcFile::Netcdf4 );
1238  sstr.str( "" );
1239 #endif
1240 
1241  return moab::MB_SUCCESS;
1242 }

References MB_CHK_ERR, MB_CHK_SET_ERR, MB_SUCCESS, MB_TAG_CREAT, MB_TAG_SPARSE, MB_TAG_VARLEN, MB_TYPE_DOUBLE, MB_TYPE_INTEGER, and moab::TempestRemapper::RLL.

◆ WriteParallelMap()

moab::ErrorCode moab::TempestOnlineMap::WriteParallelMap ( const std::string &  strTarget,
const std::map< std::string, std::string > &  attrMap 
)

Write the TempestOnlineMap to a parallel NetCDF file.

Definition at line 194 of file TempestOnlineMapIO.cpp.

196 {
197  size_t lastindex = strFilename.find_last_of( "." );
198  std::string extension = strFilename.substr( lastindex + 1, strFilename.size() );
199 
200  // Write the map file to disk in parallel
201  if( extension == "nc" )
202  {
203 #if !defined( MOAB_HAVE_NETCDFPAR )
204  // Without parallel NetCDF, the SCRIP writer cannot handle multiple MPI ranks
205  // writing to the same file. Fall back to the HDF5 format with a .h5m extension
206  // and then the map can be converted to SCRIP format offline if needed.
207  if( this->size > 1 )
208  {
209  std::string h5mFilename = strFilename.substr( 0, lastindex ) + ".h5m";
210  if( !this->rank )
211  {
212  std::cout << " [WriteParallelMap]: Parallel NetCDF not available; writing map to "
213  << "HDF5 format (" << h5mFilename << ") instead of SCRIP (.nc)\n";
214  }
215  MB_CHK_ERR( this->WriteHDF5MapFile( h5mFilename.c_str() ) );
216  return moab::MB_SUCCESS;
217  }
218 #endif
219  /* Invoke the actual call to write the parallel map to disk in SCRIP format */
220  MB_CHK_ERR( this->WriteSCRIPMapFile( strFilename.c_str(), attrMap ) );
221  }
222  else
223  {
224  /* Write to the parallel H5M format */
225  MB_CHK_ERR( this->WriteHDF5MapFile( strFilename.c_str() ) );
226  }
227 
228  return moab::MB_SUCCESS;
229 }

References MB_CHK_ERR, and MB_SUCCESS.

Referenced by main().

◆ WriteSCRIPMapFile()

moab::ErrorCode moab::TempestOnlineMap::WriteSCRIPMapFile ( const std::string &  strOutputFile,
const std::map< std::string, std::string > &  attrMap 
)
private

Copy the local matrix from Tempest SparseMatrix representation (ELL) to the parallel CSR Eigen Matrix for scalable application of matvec needed for projections.

Parallel I/O with HDF5 to write out the remapping weights from multiple processors.

Need to get the global maximum of number of vertices per element Key issue is that when calling InitializeCoordinatesFromMeshFV, the allocation for dVertexLon/dVertexLat are made based on the maximum vertices in the current process. However, when writing this out, other processes may have a different size for the same array. This is hence a mess to consolidate in h5mtoscrip eventually.

Definition at line 233 of file TempestOnlineMapIO.cpp.

235 {
236  NcError error( NcError::silent_nonfatal );
237 
238 #ifdef MOAB_HAVE_NETCDFPAR
239  bool is_independent = true;
240  ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strFilename.c_str(), NcFile::Replace, NcFile::Netcdf4 );
241  // ParNcFile ncMap( m_pcomm->comm(), MPI_INFO_NULL, strFilename.c_str(), NcmpiFile::replace, NcmpiFile::classic5 );
242 #else
243  NcFile ncMap( strFilename.c_str(), NcFile::Replace );
244 #endif
245 
246  if( !ncMap.is_valid() )
247  {
248  _EXCEPTION1( "Unable to open output map file \"%s\"", strFilename.c_str() );
249  }
250 
251  // Attributes
252  // ncMap.add_att( "Title", "MOAB-TempestRemap Online Regridding Weight Generator" );
253  auto it = attrMap.begin();
254  while( it != attrMap.end() )
255  {
256  // set the map attributes
257  ncMap.add_att( it->first.c_str(), it->second.c_str() );
258  // increment iterator
259  it++;
260  }
261 
262  /**
263  * Need to get the global maximum of number of vertices per element
264  * Key issue is that when calling InitializeCoordinatesFromMeshFV, the allocation for
265  *dVertexLon/dVertexLat are made based on the maximum vertices in the current process. However,
266  *when writing this out, other processes may have a different size for the same array. This is
267  *hence a mess to consolidate in h5mtoscrip eventually.
268  **/
269 
270  /* Let us compute all relevant data for the current original source mesh on the process */
271  DataArray1D< double > vecSourceFaceArea, vecTargetFaceArea;
272  DataArray1D< double > dSourceCenterLon, dSourceCenterLat, dTargetCenterLon, dTargetCenterLat;
273  DataArray2D< double > dSourceVertexLon, dSourceVertexLat, dTargetVertexLon, dTargetVertexLat;
275  {
276  this->InitializeCoordinatesFromMeshFV(
277  *m_meshInput, dSourceCenterLon, dSourceCenterLat, dSourceVertexLon, dSourceVertexLat,
278  ( this->m_remapper->m_source_type == moab::TempestRemapper::RLL ), /* fLatLon = false */
280 
281  vecSourceFaceArea.Allocate( m_meshInput->vecFaceArea.GetRows() );
282  for( unsigned i = 0; i < m_meshInput->vecFaceArea.GetRows(); ++i )
283  vecSourceFaceArea[i] = m_meshInput->vecFaceArea[i];
284  }
285  else
286  {
287  DataArray3D< double > dataGLLJacobianSrc;
288  this->InitializeCoordinatesFromMeshFE( *m_meshInput, m_nDofsPEl_Src, dataGLLNodesSrc, dSourceCenterLon,
289  dSourceCenterLat, dSourceVertexLon, dSourceVertexLat );
290 
291  // Generate the continuous Jacobian for input mesh
292  GenerateMetaData( *m_meshInput, m_nDofsPEl_Src, false /* fBubble */, dataGLLNodesSrc, dataGLLJacobianSrc );
293 
295  {
296  GenerateUniqueJacobian( dataGLLNodesSrc, dataGLLJacobianSrc, vecSourceFaceArea );
297  }
298  else
299  {
300  GenerateDiscontinuousJacobian( dataGLLJacobianSrc, vecSourceFaceArea );
301  }
302  }
303 
305  {
306  this->InitializeCoordinatesFromMeshFV(
307  *m_meshOutput, dTargetCenterLon, dTargetCenterLat, dTargetVertexLon, dTargetVertexLat,
308  ( this->m_remapper->m_target_type == moab::TempestRemapper::RLL ), /* fLatLon = false */
310 
311  vecTargetFaceArea.Allocate( m_meshOutput->vecFaceArea.GetRows() );
312  for( unsigned i = 0; i < m_meshOutput->vecFaceArea.GetRows(); ++i )
313  {
314  vecTargetFaceArea[i] = m_meshOutput->vecFaceArea[i];
315  }
316  }
317  else
318  {
319  DataArray3D< double > dataGLLJacobianDest;
320  this->InitializeCoordinatesFromMeshFE( *m_meshOutput, m_nDofsPEl_Dest, dataGLLNodesDest, dTargetCenterLon,
321  dTargetCenterLat, dTargetVertexLon, dTargetVertexLat );
322 
323  // Generate the continuous Jacobian for input mesh
324  GenerateMetaData( *m_meshOutput, m_nDofsPEl_Dest, false /* fBubble */, dataGLLNodesDest, dataGLLJacobianDest );
325 
327  {
328  GenerateUniqueJacobian( dataGLLNodesDest, dataGLLJacobianDest, vecTargetFaceArea );
329  }
330  else
331  {
332  GenerateDiscontinuousJacobian( dataGLLJacobianDest, vecTargetFaceArea );
333  }
334  }
335 
336  // Map dimensions
337  unsigned nA = ( vecSourceFaceArea.GetRows() );
338  unsigned nB = ( vecTargetFaceArea.GetRows() );
339 
340  std::vector< int > masksA, masksB;
341  MB_CHK_SET_ERR( m_remapper->GetIMasks( moab::Remapper::SourceMesh, masksA ), "Trouble getting masks for source" );
342  MB_CHK_SET_ERR( m_remapper->GetIMasks( moab::Remapper::TargetMesh, masksB ), "Trouble getting masks for target" );
343 
344  // Number of nodes per Face
345  int nSourceNodesPerFace = dSourceVertexLon.GetColumns();
346  int nTargetNodesPerFace = dTargetVertexLon.GetColumns();
347 
348  // if source or target cells have triangles at poles, center of those triangles need to come from
349  // the original quad, not from center in 3d, converted to 2d again
350  // start copy OnlineMap.cpp tempestremap
351  // right now, do this only for source mesh; copy the logic for target mesh
352  for( unsigned i = 0; i < nA; i++ )
353  {
354  const Face& face = m_meshInput->faces[i];
355 
356  int nNodes = face.edges.size();
357  int indexNodeAtPole = -1;
358  if( 3 == nNodes ) // check if one node at the poles
359  {
360  for( int j = 0; j < nNodes; j++ )
361  if( fabs( fabs( dSourceVertexLat[i][j] ) - 90.0 ) < 1.0e-12 )
362  {
363  indexNodeAtPole = j;
364  break;
365  }
366  }
367  if( indexNodeAtPole < 0 ) continue; // continue i loop, do nothing
368  // recompute center of cell, from 3d data; add one 2 nodes at pole, and average
369  int nodeAtPole = face[indexNodeAtPole]; // use the overloaded operator
370  Node nodePole = m_meshInput->nodes[nodeAtPole];
371  Node newCenter = nodePole * 2;
372  for( int j = 1; j < nNodes; j++ )
373  {
374  int indexi = ( indexNodeAtPole + j ) % nNodes; // nNodes is 3 !
375  const Node& node = m_meshInput->nodes[face[indexi]];
376  newCenter = newCenter + node;
377  }
378  newCenter = newCenter * 0.25;
379  newCenter = newCenter.Normalized();
380 
381 #ifdef VERBOSE
382  double iniLon = dSourceCenterLon[i], iniLat = dSourceCenterLat[i];
383 #endif
384  // dSourceCenterLon, dSourceCenterLat
385  XYZtoRLL_Deg( newCenter.x, newCenter.y, newCenter.z, dSourceCenterLon[i], dSourceCenterLat[i] );
386 #ifdef VERBOSE
387  std::cout << " modify center of triangle from " << iniLon << " " << iniLat << " to " << dSourceCenterLon[i]
388  << " " << dSourceCenterLat[i] << "\n";
389 #endif
390  }
391 
392  // first move data if in parallel
393 #if defined( MOAB_HAVE_MPI )
394  int max_row_dof, max_col_dof; // output; arrays will be re-distributed in chunks [maxdof/size]
395  // if (size > 1)
396  {
397  int ierr = rearrange_arrays_by_dofs( srccol_gdofmap, vecSourceFaceArea, dSourceCenterLon, dSourceCenterLat,
398  dSourceVertexLon, dSourceVertexLat, masksA, nA, nSourceNodesPerFace,
399  max_col_dof ); // now nA will be close to maxdof/size
400  if( ierr != 0 )
401  {
402  _EXCEPTION1( "Unable to arrange source data %d ", nA );
403  }
404  // rearrange target data: (nB)
405  //
406  ierr = rearrange_arrays_by_dofs( row_gdofmap, vecTargetFaceArea, dTargetCenterLon, dTargetCenterLat,
407  dTargetVertexLon, dTargetVertexLat, masksB, nB, nTargetNodesPerFace,
408  max_row_dof ); // now nA will be close to maxdof/size
409  if( ierr != 0 )
410  {
411  _EXCEPTION1( "Unable to arrange target data %d ", nB );
412  }
413  }
414 #endif
415 
416  // Number of non-zeros in the remap matrix operator
417  int nS = m_weightMatrix.nonZeros();
418 
419 #if defined( MOAB_HAVE_MPI ) && defined( MOAB_HAVE_NETCDFPAR )
420  int locbuf[5] = { (int)nA, (int)nB, nS, nSourceNodesPerFace, nTargetNodesPerFace };
421  int offbuf[3] = { 0, 0, 0 };
422  int globuf[5] = { 0, 0, 0, 0, 0 };
423  MPI_Scan( locbuf, offbuf, 3, MPI_INT, MPI_SUM, m_pcomm->comm() );
424  MPI_Allreduce( locbuf, globuf, 3, MPI_INT, MPI_SUM, m_pcomm->comm() );
425  MPI_Allreduce( &locbuf[3], &globuf[3], 2, MPI_INT, MPI_MAX, m_pcomm->comm() );
426 
427  // MPI_Scan is inclusive of data in current rank; modify accordingly.
428  offbuf[0] -= nA;
429  offbuf[1] -= nB;
430  offbuf[2] -= nS;
431 
432 #else
433  int offbuf[3] = { 0, 0, 0 };
434  int globuf[5] = { (int)nA, (int)nB, nS, nSourceNodesPerFace, nTargetNodesPerFace };
435 #endif
436 
437  std::vector< std::string > srcdimNames, tgtdimNames;
438  std::vector< int > srcdimSizes, tgtdimSizes;
439  {
441  {
442  srcdimNames.push_back( "lat" );
443  srcdimNames.push_back( "lon" );
444  srcdimSizes.resize( 2, 0 );
445  srcdimSizes[0] = m_remapper->m_source_metadata[0];
446  srcdimSizes[1] = m_remapper->m_source_metadata[1];
447  }
448  else
449  {
450  srcdimNames.push_back( "num_elem" );
451  srcdimSizes.push_back( globuf[0] );
452  }
453 
455  {
456  tgtdimNames.push_back( "lat" );
457  tgtdimNames.push_back( "lon" );
458  tgtdimSizes.resize( 2, 0 );
459  tgtdimSizes[0] = m_remapper->m_target_metadata[0];
460  tgtdimSizes[1] = m_remapper->m_target_metadata[1];
461  }
462  else
463  {
464  tgtdimNames.push_back( "num_elem" );
465  tgtdimSizes.push_back( globuf[1] );
466  }
467  }
468 
469  // Write output dimensions entries
470  unsigned nSrcGridDims = ( srcdimSizes.size() );
471  unsigned nDstGridDims = ( tgtdimSizes.size() );
472 
473  NcDim* dimSrcGridRank = ncMap.add_dim( "src_grid_rank", nSrcGridDims );
474  NcDim* dimDstGridRank = ncMap.add_dim( "dst_grid_rank", nDstGridDims );
475 
476  NcVar* varSrcGridDims = ncMap.add_var( "src_grid_dims", ncInt, dimSrcGridRank );
477  NcVar* varDstGridDims = ncMap.add_var( "dst_grid_dims", ncInt, dimDstGridRank );
478 
479 #ifdef MOAB_HAVE_NETCDFPAR
480  ncMap.enable_var_par_access( varSrcGridDims, is_independent );
481  ncMap.enable_var_par_access( varDstGridDims, is_independent );
482 #endif
483 
484  // write dimension names
485  {
486  char szDim[64];
487  for( unsigned i = 0; i < srcdimSizes.size(); i++ )
488  {
489  varSrcGridDims->set_cur( nSrcGridDims - i - 1 );
490  varSrcGridDims->put( &( srcdimSizes[nSrcGridDims - i - 1] ), 1 );
491  }
492 
493  for( unsigned i = 0; i < srcdimSizes.size(); i++ )
494  {
495  snprintf( szDim, 64, "name%u", i );
496  varSrcGridDims->add_att( szDim, srcdimNames[nSrcGridDims - i - 1].c_str() );
497  }
498 
499  for( unsigned i = 0; i < tgtdimSizes.size(); i++ )
500  {
501  varDstGridDims->set_cur( nDstGridDims - i - 1 );
502  varDstGridDims->put( &( tgtdimSizes[nDstGridDims - i - 1] ), 1 );
503  }
504 
505  for( unsigned i = 0; i < tgtdimSizes.size(); i++ )
506  {
507  snprintf( szDim, 64, "name%u", i );
508  varDstGridDims->add_att( szDim, tgtdimNames[nDstGridDims - i - 1].c_str() );
509  }
510  }
511 
512  // Source and Target mesh resolutions
513  NcDim* dimNA = ncMap.add_dim( "n_a", globuf[0] );
514  NcDim* dimNB = ncMap.add_dim( "n_b", globuf[1] );
515 
516  // Number of nodes per Face
517  NcDim* dimNVA = ncMap.add_dim( "nv_a", globuf[3] );
518  NcDim* dimNVB = ncMap.add_dim( "nv_b", globuf[4] );
519 
520  // Write coordinates
521  NcVar* varYCA = ncMap.add_var( "yc_a", ncDouble, dimNA );
522  NcVar* varYCB = ncMap.add_var( "yc_b", ncDouble, dimNB );
523 
524  NcVar* varXCA = ncMap.add_var( "xc_a", ncDouble, dimNA );
525  NcVar* varXCB = ncMap.add_var( "xc_b", ncDouble, dimNB );
526 
527  NcVar* varYVA = ncMap.add_var( "yv_a", ncDouble, dimNA, dimNVA );
528  NcVar* varYVB = ncMap.add_var( "yv_b", ncDouble, dimNB, dimNVB );
529 
530  NcVar* varXVA = ncMap.add_var( "xv_a", ncDouble, dimNA, dimNVA );
531  NcVar* varXVB = ncMap.add_var( "xv_b", ncDouble, dimNB, dimNVB );
532 
533  // Write masks
534  NcVar* varMaskA = ncMap.add_var( "mask_a", ncInt, dimNA );
535  NcVar* varMaskB = ncMap.add_var( "mask_b", ncInt, dimNB );
536 
537 #ifdef MOAB_HAVE_NETCDFPAR
538  ncMap.enable_var_par_access( varYCA, is_independent );
539  ncMap.enable_var_par_access( varYCB, is_independent );
540  ncMap.enable_var_par_access( varXCA, is_independent );
541  ncMap.enable_var_par_access( varXCB, is_independent );
542  ncMap.enable_var_par_access( varYVA, is_independent );
543  ncMap.enable_var_par_access( varYVB, is_independent );
544  ncMap.enable_var_par_access( varXVA, is_independent );
545  ncMap.enable_var_par_access( varXVB, is_independent );
546  ncMap.enable_var_par_access( varMaskA, is_independent );
547  ncMap.enable_var_par_access( varMaskB, is_independent );
548 #endif
549 
550  varYCA->add_att( "units", "degrees" );
551  varYCB->add_att( "units", "degrees" );
552 
553  varXCA->add_att( "units", "degrees" );
554  varXCB->add_att( "units", "degrees" );
555 
556  varYVA->add_att( "units", "degrees" );
557  varYVB->add_att( "units", "degrees" );
558 
559  varXVA->add_att( "units", "degrees" );
560  varXVB->add_att( "units", "degrees" );
561 
562  // Verify dimensionality
563  if( dSourceCenterLon.GetRows() != nA )
564  {
565  _EXCEPTIONT( "Mismatch between dSourceCenterLon and nA" );
566  }
567  if( dSourceCenterLat.GetRows() != nA )
568  {
569  _EXCEPTIONT( "Mismatch between dSourceCenterLat and nA" );
570  }
571  if( dTargetCenterLon.GetRows() != nB )
572  {
573  _EXCEPTIONT( "Mismatch between dTargetCenterLon and nB" );
574  }
575  if( dTargetCenterLat.GetRows() != nB )
576  {
577  _EXCEPTIONT( "Mismatch between dTargetCenterLat and nB" );
578  }
579  if( dSourceVertexLon.GetRows() != nA )
580  {
581  _EXCEPTIONT( "Mismatch between dSourceVertexLon and nA" );
582  }
583  if( dSourceVertexLat.GetRows() != nA )
584  {
585  _EXCEPTIONT( "Mismatch between dSourceVertexLat and nA" );
586  }
587  if( dTargetVertexLon.GetRows() != nB )
588  {
589  _EXCEPTIONT( "Mismatch between dTargetVertexLon and nB" );
590  }
591  if( dTargetVertexLat.GetRows() != nB )
592  {
593  _EXCEPTIONT( "Mismatch between dTargetVertexLat and nB" );
594  }
595 
596  varYCA->set_cur( (long)offbuf[0] );
597  varYCA->put( &( dSourceCenterLat[0] ), nA );
598  varYCB->set_cur( (long)offbuf[1] );
599  varYCB->put( &( dTargetCenterLat[0] ), nB );
600 
601  varXCA->set_cur( (long)offbuf[0] );
602  varXCA->put( &( dSourceCenterLon[0] ), nA );
603  varXCB->set_cur( (long)offbuf[1] );
604  varXCB->put( &( dTargetCenterLon[0] ), nB );
605 
606  varYVA->set_cur( (long)offbuf[0] );
607  varYVA->put( &( dSourceVertexLat[0][0] ), nA, nSourceNodesPerFace );
608  varYVB->set_cur( (long)offbuf[1] );
609  varYVB->put( &( dTargetVertexLat[0][0] ), nB, nTargetNodesPerFace );
610 
611  varXVA->set_cur( (long)offbuf[0] );
612  varXVA->put( &( dSourceVertexLon[0][0] ), nA, nSourceNodesPerFace );
613  varXVB->set_cur( (long)offbuf[1] );
614  varXVB->put( &( dTargetVertexLon[0][0] ), nB, nTargetNodesPerFace );
615 
616  varMaskA->set_cur( (long)offbuf[0] );
617  varMaskA->put( &( masksA[0] ), nA );
618  varMaskB->set_cur( (long)offbuf[1] );
619  varMaskB->put( &( masksB[0] ), nB );
620 
621  // Write areas
622  NcVar* varAreaA = ncMap.add_var( "area_a", ncDouble, dimNA );
623 #ifdef MOAB_HAVE_NETCDFPAR
624  ncMap.enable_var_par_access( varAreaA, is_independent );
625 #endif
626  varAreaA->set_cur( (long)offbuf[0] );
627  varAreaA->put( &( vecSourceFaceArea[0] ), nA );
628 
629  NcVar* varAreaB = ncMap.add_var( "area_b", ncDouble, dimNB );
630 #ifdef MOAB_HAVE_NETCDFPAR
631  ncMap.enable_var_par_access( varAreaB, is_independent );
632 #endif
633  varAreaB->set_cur( (long)offbuf[1] );
634  varAreaB->put( &( vecTargetFaceArea[0] ), nB );
635 
636  // Write SparseMatrix entries
637  DataArray1D< int > vecRow( nS );
638  DataArray1D< int > vecCol( nS );
639  DataArray1D< double > vecS( nS );
640  DataArray1D< double > dFracA( nA );
641  DataArray1D< double > dFracB( nB );
642 
643  moab::TupleList tlValRow, tlValCol;
644  unsigned numr = 1; //
645  // value has to be sent to processor row/nB for for fracA and col/nA for fracB
646  // vecTargetArea (indexRow ) has to be sent for fracA (index col?)
647  // vecTargetFaceArea will have to be sent to col index, with its index !
648  tlValRow.initialize( 2, 0, 0, numr, nS ); // to proc(row), global row , value
649  tlValCol.initialize( 3, 0, 0, numr, nS ); // to proc(col), global row / col, value
650  tlValRow.enableWriteAccess();
651  tlValCol.enableWriteAccess();
652  /*
653  dFracA[ col ] += val / vecSourceFaceArea[ col ] * vecTargetFaceArea[ row ];
654  dFracB[ row ] += val ;
655  */
656  int offset = 0;
657 #if defined( MOAB_HAVE_MPI )
658  int nAbase = ( max_col_dof + 1 ) / size; // it is nA, except last rank ( == size - 1 )
659  int nBbase = ( max_row_dof + 1 ) / size; // it is nB, except last rank ( == size - 1 )
660 #endif
661  for( int i = 0; i < m_weightMatrix.outerSize(); ++i )
662  {
663  for( WeightMatrix::InnerIterator it( m_weightMatrix, i ); it; ++it )
664  {
665  vecRow[offset] = 1 + this->GetRowGlobalDoF( it.row() ); // row index
666  vecCol[offset] = 1 + this->GetColGlobalDoF( it.col() ); // col index
667  vecS[offset] = it.value(); // value
668 
669 #if defined( MOAB_HAVE_MPI )
670  {
671  // value M(row, col) will contribute to procRow and procCol values for fracA and fracB
672  int procRow = ( vecRow[offset] - 1 ) / nBbase;
673  if( procRow >= size ) procRow = size - 1;
674  int procCol = ( vecCol[offset] - 1 ) / nAbase;
675  if( procCol >= size ) procCol = size - 1;
676  int nrInd = tlValRow.get_n();
677  tlValRow.vi_wr[2 * nrInd] = procRow;
678  tlValRow.vi_wr[2 * nrInd + 1] = vecRow[offset] - 1;
679  tlValRow.vr_wr[nrInd] = vecS[offset];
680  tlValRow.inc_n();
681  int ncInd = tlValCol.get_n();
682  tlValCol.vi_wr[3 * ncInd] = procCol;
683  tlValCol.vi_wr[3 * ncInd + 1] = vecRow[offset] - 1;
684  tlValCol.vi_wr[3 * ncInd + 2] = vecCol[offset] - 1; // this is column
685  tlValCol.vr_wr[ncInd] = vecS[offset];
686  tlValCol.inc_n();
687  }
688 
689 #endif
690  offset++;
691  }
692  }
693 #if defined( MOAB_HAVE_MPI )
694  // need to send values for their row and col processors, to compute fractions there
695  // now do the heavy communication
696  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tlValCol, 0 );
697  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tlValRow, 0 );
698 
699  // we have now, for example, dFracB[ row ] += val ;
700  // so we know that on current task, we received tlValRow
701  // reminder dFracA[ col ] += val / vecSourceFaceArea[ col ] * vecTargetFaceArea[ row ];
702  // dFracB[ row ] += val ;
703  for( unsigned i = 0; i < tlValRow.get_n(); i++ )
704  {
705  // int fromProc = tlValRow.vi_wr[2 * i];
706  int gRowInd = tlValRow.vi_wr[2 * i + 1];
707  int localIndexRow = gRowInd - nBbase * rank; // modulo nBbase rank is from 0 to size - 1;
708  double wgt = tlValRow.vr_wr[i];
709  assert( localIndexRow >= 0 );
710  assert( nB - localIndexRow > 0 );
711  dFracB[localIndexRow] += wgt;
712  }
713  // to compute dFracA we need vecTargetFaceArea[ row ]; we know the row, and we can get the proc we need it from
714 
715  std::set< int > neededRows;
716  for( unsigned i = 0; i < tlValCol.get_n(); i++ )
717  {
718  int rRowInd = tlValCol.vi_wr[3 * i + 1];
719  neededRows.insert( rRowInd );
720  // we need vecTargetFaceAreaGlobal[ rRowInd ]; this exists on proc procRow
721  }
722  moab::TupleList tgtAreaReq;
723  tgtAreaReq.initialize( 2, 0, 0, 0, neededRows.size() );
724  tgtAreaReq.enableWriteAccess();
725  for( std::set< int >::iterator sit = neededRows.begin(); sit != neededRows.end(); ++sit )
726  {
727  int neededRow = *sit;
728  int procRow = neededRow / nBbase;
729  if( procRow >= size ) procRow = size - 1;
730  int nr = tgtAreaReq.get_n();
731  tgtAreaReq.vi_wr[2 * nr] = procRow;
732  tgtAreaReq.vi_wr[2 * nr + 1] = neededRow;
733  tgtAreaReq.inc_n();
734  }
735 
736  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tgtAreaReq, 0 );
737  // we need to send back the tgtArea corresponding to row
738  moab::TupleList tgtAreaInfo; // load it with tgtArea at row
739  tgtAreaInfo.initialize( 2, 0, 0, 1, tgtAreaReq.get_n() );
740  tgtAreaInfo.enableWriteAccess();
741  for( unsigned i = 0; i < tgtAreaReq.get_n(); i++ )
742  {
743  int from_proc = tgtAreaReq.vi_wr[2 * i];
744  int row = tgtAreaReq.vi_wr[2 * i + 1];
745  int locaIndexRow = row - rank * nBbase;
746  double areaToSend = vecTargetFaceArea[locaIndexRow];
747  // int remoteIndex = tgtAreaReq.vi_wr[3*i + 2] ;
748 
749  tgtAreaInfo.vi_wr[2 * i] = from_proc; // send back requested info
750  tgtAreaInfo.vi_wr[2 * i + 1] = row;
751  tgtAreaInfo.vr_wr[i] = areaToSend; // this will be tgt area at row
752  tgtAreaInfo.inc_n();
753  }
754  ( m_pcomm->proc_config().crystal_router() )->gs_transfer( 1, tgtAreaInfo, 0 );
755 
756  std::map< int, double > areaAtRow;
757  for( unsigned i = 0; i < tgtAreaInfo.get_n(); i++ )
758  {
759  // we have received from proc, value for row !
760  int row = tgtAreaInfo.vi_wr[2 * i + 1];
761  areaAtRow[row] = tgtAreaInfo.vr_wr[i];
762  }
763 
764  // we have now for rows the
765  // it is ordered by index, so:
766  // now compute reminder dFracA[ col ] += val / vecSourceFaceArea[ col ] * vecTargetFaceArea[ row ];
767  // tgtAreaInfo will have at index i the area we need (from row)
768  // there should be an easier way :(
769  for( unsigned i = 0; i < tlValCol.get_n(); i++ )
770  {
771  int rRowInd = tlValCol.vi_wr[3 * i + 1];
772  int colInd = tlValCol.vi_wr[3 * i + 2];
773  double val = tlValCol.vr_wr[i];
774  int localColInd = colInd - rank * nAbase; // < local nA
775  // we need vecTargetFaceAreaGlobal[ rRowInd ]; this exists on proc procRow
776  auto itMap = areaAtRow.find( rRowInd ); // it should be different from end
777  if( itMap != areaAtRow.end() )
778  {
779  double areaRow = itMap->second; // we fished a lot for this !
780  dFracA[localColInd] += val / vecSourceFaceArea[localColInd] * areaRow;
781  }
782  }
783 
784 #endif
785  // Load in data
786  NcDim* dimNS = ncMap.add_dim( "n_s", globuf[2] );
787 
788  NcVar* varRow = ncMap.add_var( "row", ncInt, dimNS );
789  NcVar* varCol = ncMap.add_var( "col", ncInt, dimNS );
790  NcVar* varS = ncMap.add_var( "S", ncDouble, dimNS );
791 #ifdef MOAB_HAVE_NETCDFPAR
792  ncMap.enable_var_par_access( varRow, is_independent );
793  ncMap.enable_var_par_access( varCol, is_independent );
794  ncMap.enable_var_par_access( varS, is_independent );
795 #endif
796 
797  varRow->set_cur( (long)offbuf[2] );
798  varRow->put( vecRow, nS );
799 
800  varCol->set_cur( (long)offbuf[2] );
801  varCol->put( vecCol, nS );
802 
803  varS->set_cur( (long)offbuf[2] );
804  varS->put( &( vecS[0] ), nS );
805 
806  // Calculate and write fractional coverage arrays
807  NcVar* varFracA = ncMap.add_var( "frac_a", ncDouble, dimNA );
808 #ifdef MOAB_HAVE_NETCDFPAR
809  ncMap.enable_var_par_access( varFracA, is_independent );
810 #endif
811  varFracA->add_att( "name", "fraction of target coverage of source dof" );
812  varFracA->add_att( "units", "unitless" );
813  varFracA->set_cur( (long)offbuf[0] );
814  varFracA->put( &( dFracA[0] ), nA );
815 
816  NcVar* varFracB = ncMap.add_var( "frac_b", ncDouble, dimNB );
817 #ifdef MOAB_HAVE_NETCDFPAR
818  ncMap.enable_var_par_access( varFracB, is_independent );
819 #endif
820  varFracB->add_att( "name", "fraction of source coverage of target dof" );
821  varFracB->add_att( "units", "unitless" );
822  varFracB->set_cur( (long)offbuf[1] );
823  varFracB->put( &( dFracB[0] ), nB );
824 
825  // Add global attributes
826  // std::map<std::string, std::string>::const_iterator iterAttributes =
827  // mapAttributes.begin();
828  // for (; iterAttributes != mapAttributes.end(); iterAttributes++) {
829  // ncMap.add_att(
830  // iterAttributes->first.c_str(),
831  // iterAttributes->second.c_str());
832  // }
833 
834  ncMap.close();
835 
836 #ifdef VERBOSE
837  serializeSparseMatrix( m_weightMatrix, "map_operator_" + std::to_string( rank ) + ".txt" );
838 #endif
839  return moab::MB_SUCCESS;
840 }

References moab::TupleList::enableWriteAccess(), moab::error(), moab::TupleList::get_n(), moab::TupleList::inc_n(), moab::TupleList::initialize(), MB_CHK_SET_ERR, MB_SUCCESS, moab::TempestRemapper::RLL, moab::Remapper::SourceMesh, moab::Remapper::TargetMesh, moab::TupleList::vi_wr, and moab::TupleList::vr_wr.

Member Data Documentation

◆ col_dtoc_dofmap

std::vector< int > moab::TempestOnlineMap::col_dtoc_dofmap
private

Definition at line 576 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and GetColDofMap().

◆ col_gdofmap

std::vector< unsigned > moab::TempestOnlineMap::col_gdofmap
private

Definition at line 573 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), fill_col_ids(), and LinearRemapNN_MOAB().

◆ colMap

std::map< int, int > moab::TempestOnlineMap::colMap
private

Definition at line 578 of file TempestOnlineMap.hpp.

◆ dataGLLNodesDest

DataArray3D< int > moab::TempestOnlineMap::dataGLLNodesDest
private

Definition at line 581 of file TempestOnlineMap.hpp.

◆ dataGLLNodesSrc

DataArray3D< int > moab::TempestOnlineMap::dataGLLNodesSrc
private

Definition at line 581 of file TempestOnlineMap.hpp.

◆ dataGLLNodesSrcCov

DataArray3D< int > moab::TempestOnlineMap::dataGLLNodesSrcCov
private

Definition at line 581 of file TempestOnlineMap.hpp.

◆ is_parallel

bool moab::TempestOnlineMap::is_parallel
private

Definition at line 601 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ is_root

bool moab::TempestOnlineMap::is_root
private

Definition at line 601 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_bConserved

bool moab::TempestOnlineMap::m_bConserved
private

Definition at line 593 of file TempestOnlineMap.hpp.

◆ m_destDiscType

DiscretizationType moab::TempestOnlineMap::m_destDiscType
private

Definition at line 582 of file TempestOnlineMap.hpp.

◆ m_dofTagDest

moab::Tag moab::TempestOnlineMap::m_dofTagDest
private

Definition at line 572 of file TempestOnlineMap.hpp.

◆ m_dofTagSrc

moab::Tag moab::TempestOnlineMap::m_dofTagSrc
private

The original tag data and local to global DoF mapping to associate matrix values to solution

Definition at line 572 of file TempestOnlineMap.hpp.

◆ m_eInputType

DiscretizationType moab::TempestOnlineMap::m_eInputType
private

Definition at line 592 of file TempestOnlineMap.hpp.

◆ m_eOutputType

DiscretizationType moab::TempestOnlineMap::m_eOutputType
private

Definition at line 592 of file TempestOnlineMap.hpp.

◆ m_iMonotonicity

int moab::TempestOnlineMap::m_iMonotonicity
private

Definition at line 594 of file TempestOnlineMap.hpp.

◆ m_input_order

int moab::TempestOnlineMap::m_input_order
private

Definition at line 579 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_interface

moab::Interface* moab::TempestOnlineMap::m_interface
private

The reference to the moab::Core object that contains source/target and overlap sets.

Definition at line 560 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_meshInput

Mesh* moab::TempestOnlineMap::m_meshInput
private

Definition at line 596 of file TempestOnlineMap.hpp.

Referenced by SetMeshInput(), and TempestOnlineMap().

◆ m_meshInputCov

Mesh* moab::TempestOnlineMap::m_meshInputCov
private

Definition at line 597 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_meshOutput

Mesh* moab::TempestOnlineMap::m_meshOutput
private

Definition at line 598 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_meshOverlap

Mesh* moab::TempestOnlineMap::m_meshOverlap
private

Definition at line 599 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_nDofsPEl_Dest

int moab::TempestOnlineMap::m_nDofsPEl_Dest
private

Definition at line 591 of file TempestOnlineMap.hpp.

◆ m_nDofsPEl_Src

int moab::TempestOnlineMap::m_nDofsPEl_Src
private

Definition at line 591 of file TempestOnlineMap.hpp.

◆ m_nTotDofs_Dest

int moab::TempestOnlineMap::m_nTotDofs_Dest
private

Definition at line 583 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and LinearRemapNN_MOAB().

◆ m_nTotDofs_Src

int moab::TempestOnlineMap::m_nTotDofs_Src
private

Definition at line 583 of file TempestOnlineMap.hpp.

◆ m_nTotDofs_SrcCov

int moab::TempestOnlineMap::m_nTotDofs_SrcCov
private

Definition at line 583 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and LinearRemapNN_MOAB().

◆ m_nTotDofs_SrcGlobal

int moab::TempestOnlineMap::m_nTotDofs_SrcGlobal
private

Definition at line 588 of file TempestOnlineMap.hpp.

Referenced by GlobalSourceDofCount(), and TempestOnlineMap().

◆ m_output_order

int moab::TempestOnlineMap::m_output_order
private

Definition at line 579 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_remapper

moab::TempestRemapper* moab::TempestOnlineMap::m_remapper
private

The fundamental remapping operator object.

Definition at line 546 of file TempestOnlineMap.hpp.

Referenced by TempestOnlineMap().

◆ m_srcDiscType

DiscretizationType moab::TempestOnlineMap::m_srcDiscType
private

Definition at line 582 of file TempestOnlineMap.hpp.

◆ rank

int moab::TempestOnlineMap::rank
private

Definition at line 602 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and TempestOnlineMap().

◆ row_dtoc_dofmap

std::vector< int > moab::TempestOnlineMap::row_dtoc_dofmap
private

Definition at line 576 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and GetRowDofMap().

◆ row_gdofmap

std::vector< unsigned > moab::TempestOnlineMap::row_gdofmap
private

Definition at line 573 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and LinearRemapNN_MOAB().

◆ rowMap

std::map< int, int > moab::TempestOnlineMap::rowMap
private

Definition at line 578 of file TempestOnlineMap.hpp.

◆ size

int moab::TempestOnlineMap::size
private

Definition at line 602 of file TempestOnlineMap.hpp.

Referenced by ApplyWeights(), and TempestOnlineMap().

◆ srccol_dtoc_dofmap

std::vector< int > moab::TempestOnlineMap::srccol_dtoc_dofmap
private

Definition at line 576 of file TempestOnlineMap.hpp.

◆ srccol_gdofmap

std::vector< unsigned > moab::TempestOnlineMap::srccol_gdofmap
private

Definition at line 573 of file TempestOnlineMap.hpp.


The documentation for this class was generated from the following files: