Mesh Oriented datABase  (version 5.5.0)
An array-based unstructured mesh library
mbtempest.cpp
Go to the documentation of this file.
1 /*
2  * Usage: MOAB-Tempest tool
3  *
4  * Generate a Cubed-Sphere mesh: ./mbtempest -t 0 -res 25 -f cubed_sphere_mesh.exo
5  * Generate a RLL mesh: ./mbtempest -t 1 -res 25 -f rll_mesh.exo
6  * Generate a Icosahedral-Sphere mesh: ./mbtempest -t 2 -res 25 <-dual> -f icosa_mesh.exo
7  *
8  * Now you can compute the intersections between the meshes too!
9  *
10  * Generate the overlap mesh: ./mbtempest -t 3 -l cubed_sphere_mesh.exo -l rll_mesh.exo -f
11  * overlap_mesh.exo
12  *
13  */
14 
15 // standard C++ includes
16 #include <iostream>
17 #include <iomanip>
18 #include <cstdlib>
19 #include <vector>
20 #include <string>
21 #include <sstream>
22 #include <cassert>
23 
24 // MOAB includes
25 #include "moab/Core.hpp"
29 #include "moab/ProgOptions.hpp"
30 #include "moab/CpuTimer.hpp"
31 #include "DebugOutput.hpp"
32 
33 //#ifndef MOAB_HAVE_MPI
34 // #error mbtempest tool requires MPI configuration
35 //#endif
36 
37 #ifdef MOAB_HAVE_MPI
38 // MPI includes
39 #include "moab_mpi.h"
40 #include "moab/ParallelComm.hpp"
41 #include "MBParallelConventions.h"
42 #endif
43 
45 {
47 #ifdef MOAB_HAVE_MPI
48  moab::ParallelComm* pcomm;
49 #endif
50  const int proc_id, n_procs;
52  int blockSize;
53  std::vector< std::string > inFilenames;
54  std::vector< Mesh* > meshes;
55  std::vector< moab::EntityHandle > meshsets;
56  std::vector< int > disc_orders;
57  std::vector< std::string > disc_methods;
58  std::vector< std::string > doftag_names;
59  std::string fvMethod;
60  std::string outFilename;
61  std::string intxFilename;
62  std::string baselineFile;
69  bool rrmGrids;
71  bool fCheck;
74  GenerateOfflineMapAlgorithmOptions mapOptions;
75 
76 #ifdef MOAB_HAVE_MPI
77  ToolContext( moab::Core* icore, moab::ParallelComm* p_pcomm )
78  : mbcore( icore ), pcomm( p_pcomm ), proc_id( pcomm->rank() ), n_procs( pcomm->size() ),
79  outputFormatter( std::cout, pcomm->rank(), 0 ),
80 #else
81  ToolContext( moab::Core* icore )
82  : mbcore( icore ), proc_id( 0 ), n_procs( 1 ), outputFormatter( std::cout, 0, 0 ),
83 #endif
84  blockSize( 5 ), fvMethod( "none" ), outFilename( "outputFile.nc" ), intxFilename( "intxFile.h5m" ),
85  baselineFile( "" ), meshType( moab::TempestRemapper::DEFAULT ), computeDual( false ), computeWeights( false ),
86  verifyWeights( false ), enforceConvexity( false ), ensureMonotonicity( 0 ), rrmGrids( false ),
87  kdtreeSearch( true ), fCheck( n_procs > 1 ? false : true ), fVolumetric( false ),
88  useGnomonicProjection( false )
89  {
90  inFilenames.resize( 2 );
91  doftag_names.resize( 2 );
92  timer = new moab::CpuTimer();
93 
94  outputFormatter.set_prefix( "[MBTempest]: " );
95  }
96 
98  {
99  // for (unsigned i=0; i < meshes.size(); ++i) delete meshes[i];
100  meshes.clear();
101  inFilenames.clear();
102  disc_orders.clear();
103  disc_methods.clear();
104  doftag_names.clear();
105  outFilename.clear();
106  intxFilename.clear();
107  baselineFile.clear();
108  meshsets.clear();
109  delete timer;
110  }
111 
112  void timer_push( std::string operation )
113  {
115  opName = operation;
116  }
117 
118  void timer_pop()
119  {
120  double locElapsed = timer->time_since_birth() - timer_ops, avgElapsed = 0, maxElapsed = 0;
121 #ifdef MOAB_HAVE_MPI
122  MPI_Reduce( &locElapsed, &maxElapsed, 1, MPI_DOUBLE, MPI_MAX, 0, pcomm->comm() );
123  MPI_Reduce( &locElapsed, &avgElapsed, 1, MPI_DOUBLE, MPI_SUM, 0, pcomm->comm() );
124 #else
125  maxElapsed = locElapsed;
126  avgElapsed = locElapsed;
127 #endif
128  if( !proc_id )
129  {
130  avgElapsed /= n_procs;
131  std::cout << "[LOG] Time taken to " << opName.c_str() << ": max = " << maxElapsed
132  << ", avg = " << avgElapsed << "\n";
133  }
134  // std::cout << "\n[LOG" << proc_id << "] Time taken to " << opName << " = " <<
135  // timer->time_since_birth() - timer_ops << std::endl;
136  opName.clear();
137  }
138 
139  void ParseCLOptions( int argc, char* argv[] )
140  {
141  ProgOptions opts;
142  int imeshType = 0;
143  std::string expectedFName = "output.exo";
144  std::string expectedMethod = "fv";
145  std::string expectedFVMethod = "none";
146  std::string expectedDofTagName = "GLOBAL_ID";
147  int expectedOrder = 1;
148 
149  if( !proc_id )
150  {
151  std::cout << "Command line options provided to mbtempest:\n ";
152  for( int iarg = 0; iarg < argc; ++iarg )
153  std::cout << argv[iarg] << " ";
154  std::cout << std::endl << std::endl;
155  }
156 
157  opts.addOpt< int >( "type,t",
158  "Type of mesh (default=CS; Choose from [CS=0, RLL=1, ICO=2, OVERLAP_FILES=3, "
159  "OVERLAP_MEMORY=4, OVERLAP_MOAB=5])",
160  &imeshType );
161 
162  opts.addOpt< int >( "res,r", "Resolution of the mesh (default=5)", &blockSize );
163 
164  opts.addOpt< void >( "dual,d", "Output the dual of the mesh (relevant only for ICO mesh type)", &computeDual );
165 
166  opts.addOpt< std::string >( "file,f", "Output computed mesh or remapping weights to specified filename",
167  &outFilename );
168  opts.addOpt< std::string >(
169  "load,l", "Input mesh filenames for source and target meshes. (relevant only when computing weights)",
170  &expectedFName );
171 
172  opts.addOpt< void >( "advfront,a",
173  "Use the advancing front intersection instead of the Kd-tree based algorithm "
174  "to compute mesh intersections. (relevant only when computing weights)" );
175 
176  opts.addOpt< std::string >( "intx,i", "Output TempestRemap intersection mesh filename", &intxFilename );
177 
178  opts.addOpt< void >( "weights,w",
179  "Compute and output the weights using the overlap mesh (generally "
180  "relevant only for OVERLAP mesh)",
181  &computeWeights );
182 
183  opts.addOpt< std::string >( "method,m", "Discretization method for the source and target solution fields",
184  &expectedMethod );
185 
186  opts.addOpt< int >( "order,o", "Discretization orders for the source and target solution fields",
187  &expectedOrder );
188 
189  opts.addOpt< std::string >( "global_id,g",
190  "Tag name that contains the global DoF IDs for source and target solution fields",
191  &expectedDofTagName );
192 
193  opts.addOpt< void >( "noconserve",
194  "Do not apply conservation to the resultant weights (relevant only "
195  "when computing weights)",
196  &mapOptions.fNoConservation );
197 
198  opts.addOpt< void >( "volumetric",
199  "Apply a volumetric projection to compute the weights (relevant only "
200  "when computing weights)",
201  &fVolumetric );
202 
203  opts.addOpt< int >( "monotonicity", "Ensure monotonicity in the weight generation. Options=[0,1,2,3]",
205 
206  opts.addOpt< void >( "gnomonic", "Use Gnomonic plane projections to compute coverage mesh.",
208 
209  opts.addOpt< std::string >( "fvmethod",
210  "Sub-type method for FV-FV projections (invdist, delaunay, bilin, "
211  "intbilin, intbilingb, none. Default: none)",
212  &expectedFVMethod );
213 
214  opts.addOpt< void >( "enforce_convexity", "check convexity of input meshes to compute mesh intersections",
215  &enforceConvexity );
216 
217  opts.addOpt< void >( "nobubble", "do not use bubble on interior of spectral element nodes",
218  &mapOptions.fNoBubble );
219 
220  opts.addOpt< void >(
221  "sparseconstraints",
222  "Use sparse solver for constraints when we have high-valence (typical with high-res RLL mesh)",
223  &mapOptions.fSparseConstraints );
224 
225  opts.addOpt< void >( "rrmgrids",
226  "At least one of the meshes is a regionally refined grid (relevant to "
227  "accelerate intersection computation)",
228  &rrmGrids );
229 
230  opts.addOpt< void >( "checkmap", "Check the generated map for conservation and consistency", &fCheck );
231 
232  opts.addOpt< void >( "verify",
233  "Verify the accuracy of the maps by projecting analytical functions "
234  "from source to target "
235  "grid by applying the maps",
236  &verifyWeights );
237 
238  opts.addOpt< std::string >( "baseline", "Output baseline file", &baselineFile );
239 
240  opts.parseCommandLine( argc, argv );
241 
242  // By default - use Kd-tree based search; if user asks for advancing front, disable Kd-tree
243  // algorithm
244  kdtreeSearch = opts.numOptSet( "advfront,a" ) == 0;
245 
246  switch( imeshType )
247  {
248  case 0:
250  break;
251 
252  case 1:
254  break;
255 
256  case 2:
258  break;
259 
260  case 3:
262  break;
263 
264  case 4:
266  break;
267 
268  case 5:
270  break;
271 
272  default:
274  break;
275  }
276 
277  if( meshType > moab::TempestRemapper::ICO ) // compute overlap mesh and maps possibly
278  {
279  opts.getOptAllArgs( "load,l", inFilenames );
280  opts.getOptAllArgs( "order,o", disc_orders );
281  opts.getOptAllArgs( "method,m", disc_methods );
282  opts.getOptAllArgs( "global_id,i", doftag_names );
283 
284  assert( inFilenames.size() == 2 );
285  assert( disc_orders.size() == 2 );
286  assert( disc_methods.size() == 2 );
287  assert( ensureMonotonicity >= 0 && ensureMonotonicity <= 3 );
288 
289  // get discretization order parameters
290  if( disc_orders.size() == 0 ) disc_orders.resize( 2, 1 );
291  if( disc_orders.size() == 1 ) disc_orders.push_back( 1 );
292 
293  // get discretization method parameters
294  if( disc_methods.size() == 0 ) disc_methods.resize( 2, "fv" );
295  if( disc_methods.size() == 1 ) disc_methods.push_back( "fv" );
296 
297  // get DoF tagname parameters
298  if( doftag_names.size() == 0 ) doftag_names.resize( 2, "GLOBAL_ID" );
299  if( doftag_names.size() == 1 ) doftag_names.push_back( "GLOBAL_ID" );
300 
301  // for computing maps and overlaps, set discretization orders
302  mapOptions.nPin = disc_orders[0];
303  mapOptions.nPout = disc_orders[1];
304  mapOptions.fSourceConcave = false;
305  mapOptions.fTargetConcave = false;
306 
307  mapOptions.strMethod = "";
308 
309  if( expectedFVMethod != "none" )
310  {
311  mapOptions.strMethod += expectedFVMethod + ";";
312  fvMethod = expectedFVMethod;
313 
314  // These FV projection methods are non-conservative; specify it explicitly
315  mapOptions.fNoConservation = true;
316  }
317  switch( ensureMonotonicity )
318  {
319  case 0:
320  mapOptions.fMonotone = false;
321  break;
322  case 3:
323  mapOptions.strMethod += "mono3;";
324  break;
325  case 2:
326  mapOptions.strMethod += "mono2;";
327  break;
328  default:
329  mapOptions.fMonotone = true;
330  }
331  mapOptions.fNoCorrectAreas = false;
332  mapOptions.fNoCheck = !fCheck;
333 
334  //assert( fVolumetric && fInverseDistanceMap == false ); // both options cannot be active
335  if( fVolumetric ) mapOptions.strMethod += "volumetric;";
336  }
337 
338  // clear temporary string name
339  expectedFName.clear();
340 
341  mapOptions.strOutputMapFile = outFilename;
342  mapOptions.strOutputFormat = "Netcdf4";
343  }
344 
345  private:
347  double timer_ops;
348  std::string opName;
349 };
350 
351 // Forward declare some methods
353 inline double sample_slow_harmonic( double dLon, double dLat );
354 inline double sample_fast_harmonic( double dLon, double dLat );
355 inline double sample_constant( double dLon, double dLat );
356 inline double sample_stationary_vortex( double dLon, double dLat );
357 
358 std::string get_file_read_options( ToolContext& ctx, std::string filename )
359 {
360  // if running in serial, blank option may suffice in general
361  std::string opts = "";
362  if( ctx.n_procs > 1 )
363  {
364  size_t lastindex = filename.find_last_of( "." );
365  std::string extension = filename.substr( lastindex + 1, filename.size() );
366  if( extension == "h5m" )
367  return "PARALLEL=READ_PART;PARTITION=PARALLEL_PARTITION;PARALLEL_RESOLVE_SHARED_ENTS;";
368  else if( extension == "nc" )
369  {
370  // set default set of options
371  opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;";
372 
373  if( ctx.proc_id == 0 )
374  {
375  {
376  NcFile ncFile( filename.c_str(), NcFile::ReadOnly );
377  if( !ncFile.is_valid() )
378  _EXCEPTION1( "Unable to open grid file \"%s\" for reading", filename.c_str() );
379 
380  // Check for dimension names "grid_size", "grid_rank" and "grid_corners"
381  int iSCRIPFormat = 0, iMPASFormat = 0, iESMFFormat = 0;
382  for( int i = 0; i < ncFile.num_dims(); i++ )
383  {
384  std::string strDimName = ncFile.get_dim( i )->name();
385  if( strDimName == "grid_size" || strDimName == "grid_corners" || strDimName == "grid_rank" )
386  iSCRIPFormat++;
387  if( strDimName == "nodeCount" || strDimName == "elementCount" ||
388  strDimName == "maxNodePElement" )
389  iESMFFormat++;
390  if( strDimName == "nCells" || strDimName == "nEdges" || strDimName == "nVertices" ||
391  strDimName == "vertexDegree" )
392  iMPASFormat++;
393  }
394  ncFile.close();
395 
396  if( iESMFFormat == 3 ) // Input from a NetCDF ESMF file
397  {
398  opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;PARALLEL_RESOLVE_SHARED_ENTS;VARIABLE=;";
399  }
400  if( iSCRIPFormat == 3 ) // Input from a NetCDF SCRIP file
401  {
402  opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;";
403  }
404  if( iMPASFormat == 4 ) // Input from a NetCDF SCRIP file
405  {
406  opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;"
407  "PARALLEL_RESOLVE_SHARED_ENTS;NO_EDGES;NO_MIXED_ELEMENTS;VARIABLE=;";
408  }
409  }
410  }
411 
412 #ifdef MOAB_HAVE_MPI
413  int line_size = opts.size();
414  MPI_Bcast( &line_size, 1, MPI_INT, 0, MPI_COMM_WORLD );
415  if( ctx.proc_id != 0 ) opts.resize( line_size );
416  MPI_Bcast( const_cast< char* >( opts.data() ), line_size, MPI_CHAR, 0, MPI_COMM_WORLD );
417 #endif
418  }
419  else
420  return "PARALLEL=BCAST_DELETE;PARTITION=TRIVIAL;PARALLEL_RESOLVE_SHARED_ENTS;";
421  }
422  return opts;
423 }
424 
425 int main( int argc, char* argv[] )
426 {
427  moab::ErrorCode rval;
428  NcError error( NcError::verbose_nonfatal );
429  std::stringstream sstr;
430  std::string historyStr;
431 
432  int proc_id = 0, nprocs = 1;
433 #ifdef MOAB_HAVE_MPI
434  MPI_Init( &argc, &argv );
435  MPI_Comm_rank( MPI_COMM_WORLD, &proc_id );
436  MPI_Comm_size( MPI_COMM_WORLD, &nprocs );
437 #endif
438 
439  moab::Core* mbCore = new( std::nothrow ) moab::Core;
440 
441  if( NULL == mbCore )
442  {
443  return 1;
444  }
445 
446  // Build the history string
447  for( int ia = 0; ia < argc; ++ia )
448  historyStr += std::string( argv[ia] ) + " ";
449 
450  ToolContext* runCtx;
451 #ifdef MOAB_HAVE_MPI
452  moab::ParallelComm* pcomm = new moab::ParallelComm( mbCore, MPI_COMM_WORLD, 0 );
453 
454  runCtx = new ToolContext( mbCore, pcomm );
455  const char* writeOptions = ( nprocs > 1 ? "PARALLEL=WRITE_PART" : "" );
456 #else
457  runCtx = new ToolContext( mbCore );
458  const char* writeOptions = "";
459 #endif
460  runCtx->ParseCLOptions( argc, argv );
461 
462  const double radius_src = 1.0 /*2.0*acos(-1.0)*/;
463  const double radius_dest = 1.0 /*2.0*acos(-1.0)*/;
464 
465  moab::DebugOutput& outputFormatter = runCtx->outputFormatter;
466 
467 #ifdef MOAB_HAVE_MPI
468  moab::TempestRemapper remapper( mbCore, pcomm );
469 #else
470  moab::TempestRemapper remapper( mbCore );
471 #endif
472  remapper.meshValidate = true;
473  remapper.constructEdgeMap = true;
474  remapper.initialize();
475 
476  // Default area_method = lHuiller; Options: Girard, GaussQuadrature (if TR is available)
477 #ifdef MOAB_HAVE_TEMPESTREMAP
479 #else
481 #endif
482 
483  Mesh* tempest_mesh = new Mesh();
484  runCtx->timer_push( "create Tempest mesh" );
485  rval = CreateTempestMesh( *runCtx, remapper, tempest_mesh );MB_CHK_ERR( rval );
486  runCtx->timer_pop();
487 
488  const double epsrel = ReferenceTolerance; // ReferenceTolerance is defined in Defines.h in tempestremap;
489  // Defines.h is included in SparseMatrix.h
490  // SparseMatrix.h is included in OfflineMap.h
491  // OfflineMap.h is included in TempestOnlineMap.hpp
492  // TempestOnlineMap.hpp is included in this file, and is part of MOAB
493  // Some constant parameters
494 
495  const double boxeps = 1e-1;
496 
498  {
499  // Compute intersections with MOAB
500  // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
501  assert( runCtx->meshes.size() == 3 );
502 
503 #ifdef MOAB_HAVE_MPI
504  rval = pcomm->check_all_shared_handles();MB_CHK_ERR( rval );
505 #endif
506 
507  // Load the meshes and validate
508  rval = remapper.ConvertTempestMesh( moab::Remapper::SourceMesh );MB_CHK_ERR( rval );
509  rval = remapper.ConvertTempestMesh( moab::Remapper::TargetMesh );MB_CHK_ERR( rval );
510  rval = remapper.ConvertTempestMesh( moab::Remapper::OverlapMesh );MB_CHK_ERR( rval );
511  rval = mbCore->write_mesh( "tempest_intersection.h5m", &runCtx->meshsets[2], 1 );MB_CHK_ERR( rval );
512 
513  // print verbosely about the problem setting
514  {
515  moab::Range rintxverts, rintxelems;
516  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 0, rintxverts );MB_CHK_ERR( rval );
517  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 2, rintxelems );MB_CHK_ERR( rval );
518  outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", rintxverts.size(),
519  rintxelems.size() );
520 
521  moab::Range bintxverts, bintxelems;
522  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, bintxverts );MB_CHK_ERR( rval );
523  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, bintxelems );MB_CHK_ERR( rval );
524  outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", bintxverts.size(),
525  bintxelems.size() );
526  }
527 
528  moab::EntityHandle intxset; // == remapper.GetMeshSet(moab::Remapper::OverlapMesh);
529 
530  // Compute intersections with MOAB
531  {
532  // Create the intersection on the sphere object
533  runCtx->timer_push( "setup the intersector" );
534 
535  moab::Intx2MeshOnSphere* mbintx = new moab::Intx2MeshOnSphere( mbCore );
536  mbintx->set_error_tolerance( epsrel );
537  mbintx->set_box_error( boxeps );
538  mbintx->set_radius_source_mesh( radius_src );
539  mbintx->set_radius_destination_mesh( radius_dest );
540 #ifdef MOAB_HAVE_MPI
541  mbintx->set_parallel_comm( pcomm );
542 #endif
543  rval = mbintx->FindMaxEdges( runCtx->meshsets[0], runCtx->meshsets[1] );MB_CHK_ERR( rval );
544 
545 #ifdef MOAB_HAVE_MPI
546  moab::Range local_verts;
547  rval = mbintx->build_processor_euler_boxes( runCtx->meshsets[1], local_verts );MB_CHK_ERR( rval );
548 
549  runCtx->timer_pop();
550 
551  moab::EntityHandle covering_set;
552  runCtx->timer_push( "communicate the mesh" );
553  rval = mbintx->construct_covering_set( runCtx->meshsets[0], covering_set );MB_CHK_ERR( rval ); // lots of communication if mesh is distributed very differently
554  runCtx->timer_pop();
555 #else
556  moab::EntityHandle covering_set = runCtx->meshsets[0];
557 #endif
558  // Now let's invoke the MOAB intersection algorithm in parallel with a
559  // source and target mesh set representing two different decompositions
560  runCtx->timer_push( "compute intersections with MOAB" );
561  rval = mbCore->create_meshset( moab::MESHSET_SET, intxset );MB_CHK_SET_ERR( rval, "Can't create new set" );
562  rval = mbintx->intersect_meshes( covering_set, runCtx->meshsets[1], intxset );MB_CHK_SET_ERR( rval, "Can't compute the intersection of meshes on the sphere" );
563  runCtx->timer_pop();
564 
565  // free the memory
566  delete mbintx;
567  }
568 
569  {
570  moab::Range intxelems, intxverts;
571  rval = mbCore->get_entities_by_dimension( intxset, 2, intxelems );MB_CHK_ERR( rval );
572  rval = mbCore->get_entities_by_dimension( intxset, 0, intxverts, true );MB_CHK_ERR( rval );
573  outputFormatter.printf( 0, "The intersection set contains %lu elements and %lu vertices \n",
574  intxelems.size(), intxverts.size() );
575 
576  double initial_sarea =
577  areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[0],
578  radius_src ); // use the target to compute the initial area
579  double initial_tarea =
580  areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[1],
581  radius_dest ); // use the target to compute the initial area
582  double intx_area = areaAdaptor.area_on_sphere( mbCore, intxset, radius_src );
583 
584  outputFormatter.printf( 0, "mesh areas: source = %12.10f, target = %12.10f, intersection = %12.10f \n",
585  initial_sarea, initial_tarea, intx_area );
586  outputFormatter.printf( 0, "relative error w.r.t source = %12.10e, target = %12.10e \n",
587  fabs( intx_area - initial_sarea ) / initial_sarea,
588  fabs( intx_area - initial_tarea ) / initial_tarea );
589  }
590 
591  // Write out our computed intersection file
592  rval = mbCore->write_mesh( "moab_intersection.h5m", &intxset, 1 );MB_CHK_ERR( rval );
593 
594  if( runCtx->computeWeights )
595  {
596  runCtx->timer_push( "compute weights with the Tempest meshes" );
597  // Call to generate an offline map with the tempest meshes
598  OfflineMap weightMap;
599  int err = GenerateOfflineMapWithMeshes( *runCtx->meshes[0], *runCtx->meshes[1], *runCtx->meshes[2],
600  runCtx->disc_methods[0], // std::string strInputType
601  runCtx->disc_methods[1], // std::string strOutputType,
602  runCtx->mapOptions, weightMap );
603  runCtx->timer_pop();
604 
605  std::map< std::string, std::string > mapAttributes;
606  if( err )
607  {
608  rval = moab::MB_FAILURE;
609  }
610  else
611  {
612  weightMap.Write( "outWeights.nc", mapAttributes );
613  }
614  }
615  }
616  else if( runCtx->meshType == moab::TempestRemapper::OVERLAP_MOAB )
617  {
618  // Usage: mpiexec -n 2 tools/mbtempest -t 5 -l mycs_2.h5m -l myico_2.h5m -f myoverlap_2.h5m
619 #ifdef MOAB_HAVE_MPI
620  rval = pcomm->check_all_shared_handles();MB_CHK_ERR( rval );
621 #endif
622  // print verbosely about the problem setting
623  {
624  moab::Range srcverts, srcelems;
625  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 0, srcverts );MB_CHK_ERR( rval );
626  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 2, srcelems );MB_CHK_ERR( rval );
627  rval = moab::IntxUtils::fix_degenerate_quads( mbCore, runCtx->meshsets[0] );MB_CHK_ERR( rval );
628  if( runCtx->enforceConvexity )
629  {
630  rval = moab::IntxUtils::enforce_convexity( mbCore, runCtx->meshsets[0], proc_id );MB_CHK_ERR( rval );
631  }
632  rval = areaAdaptor.positive_orientation( mbCore, runCtx->meshsets[0], radius_src );MB_CHK_ERR( rval );
633  if( !proc_id )
634  outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", srcverts.size(),
635  srcelems.size() );
636 
637  moab::Range tgtverts, tgtelems;
638  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, tgtverts );MB_CHK_ERR( rval );
639  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, tgtelems );MB_CHK_ERR( rval );
640  rval = moab::IntxUtils::fix_degenerate_quads( mbCore, runCtx->meshsets[1] );MB_CHK_ERR( rval );
641  if( runCtx->enforceConvexity )
642  {
643  rval = moab::IntxUtils::enforce_convexity( mbCore, runCtx->meshsets[1], proc_id );MB_CHK_ERR( rval );
644  }
645  rval = areaAdaptor.positive_orientation( mbCore, runCtx->meshsets[1], radius_dest );MB_CHK_ERR( rval );
646  if( !proc_id )
647  outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", tgtverts.size(),
648  tgtelems.size() );
649  }
650  //rval = mbCore->write_file( "source_mesh.h5m", NULL, writeOptions, &runCtx->meshsets[0], 1 );MB_CHK_ERR( rval );
651  //rval = mbCore->write_file( "target_mesh.h5m", NULL, writeOptions, &runCtx->meshsets[1], 1 );MB_CHK_ERR( rval );
652 
653  // First compute the covering set such that the target elements are fully covered by the
654  // lcoal source grid
655  runCtx->timer_push( "construct covering set for intersection" );
656  rval =
657  remapper.ConstructCoveringSet( epsrel, 1.0, 1.0, boxeps, runCtx->rrmGrids, runCtx->useGnomonicProjection );MB_CHK_ERR( rval );
658  runCtx->timer_pop();
659 
660  // Compute intersections with MOAB with either the Kd-tree or the advancing front algorithm
661  runCtx->timer_push( "setup and compute mesh intersections" );
662  rval = remapper.ComputeOverlapMesh( runCtx->kdtreeSearch, false );MB_CHK_ERR( rval );
663  runCtx->timer_pop();
664 
665  // print some diagnostic checks to see if the overlap grid resolved the input meshes
666  // correctly
667  {
668  double local_areas[3],
669  global_areas[3]; // Array for Initial area, and through Method 1 and Method 2
670  // local_areas[0] = area_on_sphere_lHuiller ( mbCore, runCtx->meshsets[1], radius_src );
671  local_areas[0] = areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[0], radius_src );
672  local_areas[1] = areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[1], radius_dest );
673  local_areas[2] = areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[2], radius_src );
674 
675 #ifdef MOAB_HAVE_MPI
676  MPI_Allreduce( &local_areas[0], &global_areas[0], 3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD );
677 #else
678  global_areas[0] = local_areas[0];
679  global_areas[1] = local_areas[1];
680  global_areas[2] = local_areas[2];
681 #endif
682  if( !proc_id )
683  {
684  outputFormatter.printf( 0,
685  "initial area: source mesh = %12.14f, target mesh = "
686  "%12.14f, overlap mesh = %12.14f\n",
687  global_areas[0], global_areas[1], global_areas[2] );
688  // outputFormatter.printf ( 0, " area with l'Huiller: %12.14f with Girard:
689  // %12.14f\n", global_areas[2], global_areas[3] ); outputFormatter.printf ( 0, "
690  // relative difference areas = %12.10e\n", fabs ( global_areas[2] - global_areas[3]
691  // ) / global_areas[2] );
692  outputFormatter.printf( 0, "relative error w.r.t source = %12.14e, and target = %12.14e\n",
693  fabs( global_areas[0] - global_areas[2] ) / global_areas[0],
694  fabs( global_areas[1] - global_areas[2] ) / global_areas[1] );
695  }
696  }
697 
698  if( runCtx->intxFilename.size() )
699  {
700  moab::EntityHandle writableOverlapSet;
701  rval = mbCore->create_meshset( moab::MESHSET_SET, writableOverlapSet );MB_CHK_SET_ERR( rval, "Can't create new set" );
702  moab::EntityHandle meshOverlapSet = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
703  moab::Range ovEnts;
704  rval = mbCore->get_entities_by_dimension( meshOverlapSet, 2, ovEnts );MB_CHK_SET_ERR( rval, "Can't create new set" );
705  rval = mbCore->get_entities_by_dimension( meshOverlapSet, 0, ovEnts );MB_CHK_SET_ERR( rval, "Can't create new set" );
706 
707 #ifdef MOAB_HAVE_MPI
708  // Do not remove ghosted entities if we still haven't computed weights
709  // Remove ghosted entities from overlap set before writing the new mesh set to file
710  if( nprocs > 1 )
711  {
712  moab::Range ghostedEnts;
713  rval = remapper.GetOverlapAugmentedEntities( ghostedEnts );MB_CHK_ERR( rval );
714  ovEnts = moab::subtract( ovEnts, ghostedEnts );
715  }
716 #endif
717  rval = mbCore->add_entities( writableOverlapSet, ovEnts );MB_CHK_SET_ERR( rval, "Deleting ghosted entities failed" );
718 
719  size_t lastindex = runCtx->intxFilename.find_last_of( "." );
720  sstr.str( "" );
721  sstr << runCtx->intxFilename.substr( 0, lastindex ) << ".h5m";
722  if( !runCtx->proc_id )
723  std::cout << "Writing out the MOAB intersection mesh file to " << sstr.str() << std::endl;
724 
725  // Write out our computed intersection file
726  rval = mbCore->write_file( sstr.str().c_str(), NULL, writeOptions, &writableOverlapSet, 1 );MB_CHK_ERR( rval );
727  }
728 
729  if( runCtx->computeWeights )
730  {
731  runCtx->meshes[2] = remapper.GetMesh( moab::Remapper::OverlapMesh );
732  if( !runCtx->proc_id ) std::cout << std::endl;
733 
734  runCtx->timer_push( "setup computation of weights" );
735  // Call to generate the remapping weights with the tempest meshes
736  moab::TempestOnlineMap* weightMap = new moab::TempestOnlineMap( &remapper );
737  runCtx->timer_pop();
738 
739  runCtx->timer_push( "compute weights with TempestRemap" );
740  rval = weightMap->GenerateRemappingWeights(
741  runCtx->disc_methods[0], // std::string strInputType
742  runCtx->disc_methods[1], // std::string strOutputType,
743  runCtx->mapOptions, // const GenerateOfflineMapAlgorithmOptions& options
744  runCtx->doftag_names[0], // const std::string& source_tag_name
745  runCtx->doftag_names[1] // const std::string& target_tag_name
746  );MB_CHK_ERR( rval );
747  runCtx->timer_pop();
748 
749  // Invoke the CheckMap routine on the TempestRemap serial interface directly, if running
750  // on a single process
751  if( nprocs == 1 )
752  {
753  const double dNormalTolerance = 1.0E-8;
754  const double dStrictTolerance = 1.0E-12;
755  weightMap->CheckMap( runCtx->fCheck, runCtx->fCheck, runCtx->fCheck && ( runCtx->ensureMonotonicity ),
756  dNormalTolerance, dStrictTolerance );
757  }
758 
759  if( runCtx->outFilename.size() )
760  {
761  std::map< std::string, std::string > attrMap;
762  attrMap["MOABversion"] = std::string( MOAB_VERSION );
763  attrMap["Title"] = "MOAB-TempestRemap (mbtempest) Offline Regridding Weight Generator";
764  attrMap["normalization"] = "ovarea";
765  attrMap["remap_options"] = runCtx->mapOptions.strMethod;
766  attrMap["domain_a"] = runCtx->inFilenames[0];
767  attrMap["domain_b"] = runCtx->inFilenames[1];
768  attrMap["domain_aUb"] = runCtx->intxFilename;
769  attrMap["map_aPb"] = runCtx->outFilename;
770  attrMap["methodorder_a"] = runCtx->disc_methods[0] + ":" + std::to_string( runCtx->disc_orders[0] ) +
771  ":" + std::string( runCtx->doftag_names[0] );
772  attrMap["concave_a"] = runCtx->mapOptions.fSourceConcave ? "true" : "false";
773  attrMap["methodorder_b"] = runCtx->disc_methods[1] + ":" + std::to_string( runCtx->disc_orders[1] ) +
774  ":" + std::string( runCtx->doftag_names[1] );
775  attrMap["concave_b"] = runCtx->mapOptions.fTargetConcave ? "true" : "false";
776  attrMap["bubble"] = runCtx->mapOptions.fNoBubble ? "false" : "true";
777  attrMap["history"] = historyStr;
778 
779  // Write the map file to disk in parallel using either HDF5 or SCRIP interface
780  rval = weightMap->WriteParallelMap( runCtx->outFilename.c_str(), attrMap );MB_CHK_ERR( rval );
781  }
782 
783  if( runCtx->verifyWeights )
784  {
785  // Let us pick a sampling test function for solution evaluation
787  &sample_stationary_vortex; // &sample_slow_harmonic;
788 
789  runCtx->timer_push( "describe a solution on source grid" );
790  moab::Tag srcAnalyticalFunction;
791  rval = weightMap->DefineAnalyticalSolution( srcAnalyticalFunction, "AnalyticalSolnSrcExact",
792  moab::Remapper::SourceMesh, testFunction );MB_CHK_ERR( rval );
793  runCtx->timer_pop();
794  // rval = mbCore->write_file ( "srcWithSolnTag.h5m", NULL, writeOptions,
795  // &runCtx->meshsets[0], 1 ); MB_CHK_ERR ( rval );
796 
797  runCtx->timer_push( "describe a solution on target grid" );
798  moab::Tag tgtAnalyticalFunction;
799  moab::Tag tgtProjectedFunction;
800  rval = weightMap->DefineAnalyticalSolution( tgtAnalyticalFunction, "AnalyticalSolnTgtExact",
801  moab::Remapper::TargetMesh, testFunction,
802  &tgtProjectedFunction, "ProjectedSolnTgt" );MB_CHK_ERR( rval );
803  // rval = mbCore->write_file ( "tgtWithSolnTag.h5m", NULL, writeOptions,
804  // &runCtx->meshsets[1], 1 ); MB_CHK_ERR ( rval );
805  runCtx->timer_pop();
806 
807  runCtx->timer_push( "compute solution projection on target grid" );
808  rval = weightMap->ApplyWeights( srcAnalyticalFunction, tgtProjectedFunction );MB_CHK_ERR( rval );
809  runCtx->timer_pop();
810  rval = mbCore->write_file( "tgtWithSolnTag2.h5m", NULL, writeOptions, &runCtx->meshsets[1], 1 );MB_CHK_ERR( rval );
811 
812  if( nprocs == 1 && runCtx->baselineFile.size() )
813  {
814  // save the field from tgtWithSolnTag2 in a text file, and global ids for cells
815  moab::Range tgtCells;
816  rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, tgtCells );MB_CHK_ERR( rval );
817  std::vector< int > globIds;
818  globIds.resize( tgtCells.size() );
819  std::vector< double > vals;
820  vals.resize( tgtCells.size() );
821  moab::Tag projTag;
822  rval = mbCore->tag_get_handle( "ProjectedSolnTgt", projTag );MB_CHK_ERR( rval );
823  moab::Tag gid = mbCore->globalId_tag();
824  rval = mbCore->tag_get_data( gid, tgtCells, &globIds[0] );MB_CHK_ERR( rval );
825  rval = mbCore->tag_get_data( projTag, tgtCells, &vals[0] );MB_CHK_ERR( rval );
826  std::fstream fs;
827  fs.open( runCtx->baselineFile.c_str(), std::fstream::out );
828  fs << std::setprecision( 15 ); // maximum precision for doubles
829  for( size_t i = 0; i < tgtCells.size(); i++ )
830  fs << globIds[i] << " " << vals[i] << "\n";
831  fs.close();
832  // for good measure, save the source file too, with the tag AnalyticalSolnSrcExact
833  // it will be used later to test, along with a target file
834  rval = mbCore->write_file( "srcWithSolnTag.h5m", NULL, writeOptions, &runCtx->meshsets[0], 1 );MB_CHK_ERR( rval );
835  }
836  runCtx->timer_push( "compute error metrics against analytical solution on target grid" );
837  std::map< std::string, double > errMetrics;
838  rval = weightMap->ComputeMetrics( moab::Remapper::TargetMesh, tgtAnalyticalFunction,
839  tgtProjectedFunction, errMetrics, true );MB_CHK_ERR( rval );
840  runCtx->timer_pop();
841  }
842 
843  delete weightMap;
844  }
845  }
846 
847  // Clean up
848  remapper.clear();
849  delete runCtx;
850  delete mbCore;
851 
852 #ifdef MOAB_HAVE_MPI
853  MPI_Finalize();
854 #endif
855  exit( 0 );
856 }
857 
858 static moab::ErrorCode CreateTempestMesh( ToolContext& ctx, moab::TempestRemapper& remapper, Mesh* tempest_mesh )
859 {
861  int err;
862  moab::DebugOutput& outputFormatter = ctx.outputFormatter;
863 
864  if( !ctx.proc_id )
865  {
866  outputFormatter.printf( 0, "Creating TempestRemap Mesh object ...\n" );
867  }
868 
870  {
871  // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
872  err = GenerateOverlapMesh( ctx.inFilenames[0], ctx.inFilenames[1], *tempest_mesh, ctx.outFilename, "NetCDF4",
873  "exact", true );
874 
875  if( err )
876  {
877  rval = moab::MB_FAILURE;
878  }
879  else
880  {
881  ctx.meshes.push_back( tempest_mesh );
882  }
883  }
885  {
886  // Load the meshes and validate
887  ctx.meshsets.resize( 3 );
888  ctx.meshes.resize( 3 );
889  ctx.meshsets[0] = remapper.GetMeshSet( moab::Remapper::SourceMesh );
890  ctx.meshsets[1] = remapper.GetMeshSet( moab::Remapper::TargetMesh );
891  ctx.meshsets[2] = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
892 
893  // First the source
895  ctx.meshes[0] = remapper.GetMesh( moab::Remapper::SourceMesh );
896 
897  // Next the target
899  ctx.meshes[1] = remapper.GetMesh( moab::Remapper::TargetMesh );
900 
901  // Now let us construct the overlap mesh, by calling TempestRemap interface directly
902  // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
903  err = GenerateOverlapWithMeshes( *ctx.meshes[0], *ctx.meshes[1], *tempest_mesh, "" /*ctx.outFilename*/,
904  "NetCDF4", "exact", false );
905 
906  if( err )
907  {
908  rval = moab::MB_FAILURE;
909  }
910  else
911  {
912  remapper.SetMesh( moab::Remapper::OverlapMesh, tempest_mesh );
913  ctx.meshes[2] = remapper.GetMesh( moab::Remapper::OverlapMesh );
914  // ctx.meshes.push_back(*tempest_mesh);
915  }
916  }
918  {
919  ctx.meshsets.resize( 3 );
920  ctx.meshes.resize( 3 );
921  ctx.meshsets[0] = remapper.GetMeshSet( moab::Remapper::SourceMesh );
922  ctx.meshsets[1] = remapper.GetMeshSet( moab::Remapper::TargetMesh );
923  ctx.meshsets[2] = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
924 
925  const double radius_src = 1.0 /*2.0*acos(-1.0)*/;
926  const double radius_dest = 1.0 /*2.0*acos(-1.0)*/;
927 
928  //const char* additional_read_opts = ( ctx.n_procs > 1 ? "NO_SET_CONTAINING_PARENTS;" : "" );
929  std::string additional_read_opts_src = get_file_read_options( ctx, ctx.inFilenames[0] );
930  std::vector< int > smetadata, tmetadata;
931  // Load the source mesh and validate
932  rval =
933  remapper.LoadNativeMesh( ctx.inFilenames[0], ctx.meshsets[0], smetadata, additional_read_opts_src.c_str() );MB_CHK_ERR( rval );
934  if( smetadata.size() )
935  {
936  remapper.SetMeshType( moab::Remapper::SourceMesh, smetadata );
937  }
938  // Rescale the radius of both to compute the intersection
939  rval = moab::IntxUtils::ScaleToRadius( ctx.mbcore, ctx.meshsets[0], radius_src );MB_CHK_ERR( rval );
941  ctx.meshes[0] = remapper.GetMesh( moab::Remapper::SourceMesh );
942 
943  // Load the target mesh and validate
944  std::string addititional_read_opts_tgt = get_file_read_options( ctx, ctx.inFilenames[1] );
945  rval = remapper.LoadNativeMesh( ctx.inFilenames[1], ctx.meshsets[1], tmetadata,
946  addititional_read_opts_tgt.c_str() );MB_CHK_ERR( rval );
947  if( tmetadata.size() )
948  {
949  remapper.SetMeshType( moab::Remapper::TargetMesh, tmetadata );
950  }
951  rval = moab::IntxUtils::ScaleToRadius( ctx.mbcore, ctx.meshsets[1], radius_dest );MB_CHK_ERR( rval );
953  ctx.meshes[1] = remapper.GetMesh( moab::Remapper::TargetMesh );
954 
955  // ctx.meshes[0]->Write( "SourceMeshMBTR.g" );
956  // ctx.meshes[1]->Write( "TargetMeshMBTR.g" );
957  }
958  else if( ctx.meshType == moab::TempestRemapper::ICO )
959  {
960  err = GenerateICOMesh( *tempest_mesh, ctx.blockSize, ctx.computeDual, ctx.outFilename, "NetCDF4" );
961 
962  if( err )
963  {
964  rval = moab::MB_FAILURE;
965  }
966  else
967  {
968  ctx.meshes.push_back( tempest_mesh );
969  }
970  }
971  else if( ctx.meshType == moab::TempestRemapper::RLL )
972  {
973  err = GenerateRLLMesh( *tempest_mesh, // Mesh& meshOut,
974  ctx.blockSize * 2, ctx.blockSize, // int nLongitudes, int nLatitudes,
975  0.0, 360.0, // double dLonBegin, double dLonEnd,
976  -90.0, 90.0, // double dLatBegin, double dLatEnd,
977  false, false, false, // bool fGlobalCap, bool fFlipLatLon, bool fForceGlobal,
978  "" /*ctx.inFilename*/, "",
979  "", // std::string strInputFile, std::string strInputFileLonName, std::string
980  // strInputFileLatName,
981  ctx.outFilename, "NetCDF4", // std::string strOutputFile, std::string strOutputFormat
982  true // bool fVerbose
983  );
984 
985  if( err )
986  {
987  rval = moab::MB_FAILURE;
988  }
989  else
990  {
991  ctx.meshes.push_back( tempest_mesh );
992  }
993  }
994  else // default
995  {
996  err = GenerateCSMesh( *tempest_mesh, ctx.blockSize, ctx.outFilename, "NetCDF4" );
997 
998  if( err )
999  {
1000  rval = moab::MB_FAILURE;
1001  }
1002  else
1003  {
1004  ctx.meshes.push_back( tempest_mesh );
1005  }
1006  }
1007 
1008  if( ctx.meshType != moab::TempestRemapper::OVERLAP_MOAB && !tempest_mesh )
1009  {
1010  std::cout << "Tempest Mesh is not a complete object; Quitting...";
1011  exit( -1 );
1012  }
1013 
1014  return rval;
1015 }
1016 
1017 ///////////////////////////////////////////////
1018 // Test functions
1019 
1020 double sample_slow_harmonic( double dLon, double dLat )
1021 {
1022  return ( 2.0 + cos( dLat ) * cos( dLat ) * cos( 2.0 * dLon ) );
1023 }
1024 
1025 double sample_fast_harmonic( double dLon, double dLat )
1026 {
1027  return ( 2.0 + pow( sin( 2.0 * dLat ), 16.0 ) * cos( 16.0 * dLon ) );
1028  // return (2.0 + pow(cos(2.0 * dLat), 16.0) * cos(16.0 * dLon));
1029 }
1030 
1031 double sample_constant( double /*dLon*/, double /*dLat*/ )
1032 {
1033  return 1.0;
1034 }
1035 
1036 double sample_stationary_vortex( double dLon, double dLat )
1037 {
1038  const double dLon0 = 0.0;
1039  const double dLat0 = 0.6;
1040  const double dR0 = 3.0;
1041  const double dD = 5.0;
1042  const double dT = 6.0;
1043 
1044  /// Find the rotated longitude and latitude of a point on a sphere
1045  /// with pole at (dLonC, dLatC).
1046  {
1047  double dSinC = sin( dLat0 );
1048  double dCosC = cos( dLat0 );
1049  double dCosT = cos( dLat );
1050  double dSinT = sin( dLat );
1051 
1052  double dTrm = dCosT * cos( dLon - dLon0 );
1053  double dX = dSinC * dTrm - dCosC * dSinT;
1054  double dY = dCosT * sin( dLon - dLon0 );
1055  double dZ = dSinC * dSinT + dCosC * dTrm;
1056 
1057  dLon = atan2( dY, dX );
1058  if( dLon < 0.0 )
1059  {
1060  dLon += 2.0 * M_PI;
1061  }
1062  dLat = asin( dZ );
1063  }
1064 
1065  double dRho = dR0 * cos( dLat );
1066  double dVt = 3.0 * sqrt( 3.0 ) / 2.0 / cosh( dRho ) / cosh( dRho ) * tanh( dRho );
1067 
1068  double dOmega;
1069  if( dRho == 0.0 )
1070  {
1071  dOmega = 0.0;
1072  }
1073  else
1074  {
1075  dOmega = dVt / dRho;
1076  }
1077 
1078  return ( 1.0 - tanh( dRho / dD * sin( dLon - dOmega * dT ) ) );
1079 }
1080 
1081 ///////////////////////////////////////////////