Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
mbtempest.cpp File Reference

MOAB-Tempest: A powerful mesh generation and remapping tool for climate and weather applications. More...

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <vector>
#include <string>
#include <memory>
#include <sstream>
#include <cassert>
#include "moab/Core.hpp"
#include "moab/IntxMesh/IntxUtils.hpp"
#include "moab/Remapping/TempestRemapper.hpp"
#include "moab/Remapping/TempestOnlineMap.hpp"
#include "moab/ProgOptions.hpp"
#include "moab/CpuTimer.hpp"
#include "DebugOutput.hpp"
+ Include dependency graph for mbtempest.cpp:

Go to the source code of this file.

Classes

class  ToolContext
 Context class for MOAB-TempestRemap tool configuration and state management. More...
 

Namespaces

 anonymous_namespace{mbtempest.cpp}
 Sample functions for testing remapping operations.
 

Macros

#define TR_CHK_SET_ERR(err, msg)
 

Functions

static moab::ErrorCode CreateTempestMesh (ToolContext &ctx, moab::TempestRemapper &remapper, Mesh *tempest_mesh)
 Creates a TempestRemap mesh based on the provided context and mesh type. More...
 
static constexpr double sample_constant (double, double) noexcept
 Constant sample function. More...
 
static double sample_slow_harmonic (double dLon, double dLat) noexcept
 Sample function with slow harmonic variation. More...
 
static double sample_fast_harmonic (double dLon, double dLat) noexcept
 Sample function with fast harmonic variation. More...
 
static double sample_stationary_vortex (double dLon, double dLat) noexcept
 Sample function representing a stationary vortex. More...
 
int main (int argc, char *argv[])
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::handleOverlapMemory (ToolContext &ctx, moab::TempestRemapper &remapper, Mesh *tempest_mesh)
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::handleOverlapMOAB (ToolContext &ctx, moab::TempestRemapper &remapper)
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::handleOverlapFiles (ToolContext &ctx, Mesh *tempest_mesh)
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::convertAndWriteMOABMesh (ToolContext &ctx, moab::TempestRemapper &remapper, Mesh *tempest_mesh)
 Convert a generated TempestRemap mesh to MOAB format and write as h5m file. More...
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::handleICOMesh (ToolContext &ctx, moab::TempestRemapper &remapper, Mesh *tempest_mesh)
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::handleRLLMesh (ToolContext &ctx, moab::TempestRemapper &remapper, Mesh *tempest_mesh)
 
moab::ErrorCode anonymous_namespace{mbtempest.cpp}::handleCSMesh (ToolContext &ctx, moab::TempestRemapper &remapper, Mesh *tempest_mesh)
 

Variables

constexpr double anonymous_namespace{mbtempest.cpp}::VORTEX_LON0 = 0.0
 
constexpr double anonymous_namespace{mbtempest.cpp}::VORTEX_LAT0 = 0.6
 
constexpr double anonymous_namespace{mbtempest.cpp}::VORTEX_R0 = 3.0
 
constexpr double anonymous_namespace{mbtempest.cpp}::VORTEX_D = 5.0
 
constexpr double anonymous_namespace{mbtempest.cpp}::VORTEX_T = 6.0
 

Detailed Description

MOAB-Tempest: A powerful mesh generation and remapping tool for climate and weather applications.

Overview

MOAB-Tempest is a command-line tool that provides mesh generation and conservative remapping capabilities for climate and weather modeling. It combines the power of MOAB (Mesh-Oriented datABase) with the TempestRemap library to enable high-performance, parallel mesh generation and remapping operations.

Key Features

  • Generation of various spherical mesh types (Cubed-Sphere, RLL, Icosahedral)
  • Support for high-order discretization methods (FV, CGLL, DGLL)
  • Conservative remapping between different mesh types
  • Parallel processing support via MPI
  • Flexible I/O with support for multiple file formats
  • Built-in analytical functions for testing and validation

Supported Algorithms

  • Mesh Generation:
    • Cubed-Sphere (CS) meshes
    • Regular Latitude-Longitude (RLL) meshes
    • Icosahedral (ICO) meshes
    • Overlap meshes for remapping
  • Remapping Methods:
    • Finite Volume (FV)
    • Continuous Galerkin (CGLL)
    • Discontinuous Galerkin (DGLL)
    • Monotonic and high-order variants

Basic Usage Examples

# Generate a Cubed-Sphere mesh with resolution 25
./mbtempest --type 0 --res 25 --file cubed_sphere_mesh.h5m
# Generate a RLL mesh with resolution 90x180 (lon x lat)
./mbtempest --type 1 --res 90 --file rll_mesh.h5m
# Generate an Icosahedral mesh with resolution 25 (dual mesh)
./mbtempest --type 2 --res 25 --dual --file icosahedral_dual_mesh.h5m
# Compute overlap between two meshes
./mbtempest --type 5 --load mesh1.h5m --load mesh2.h5m intx intersection_mesh.h5m
# Generate a remapping weights file between two meshes: FV to FV (default)
./mbtempest --type 5 --load source_mesh.h5m --load target_mesh.h5m --file weights.nc
# Generate remapping weights file between two meshes: making it explicit (SE to FV)
./mbtempest --type 5 --load source_mesh.h5m --load target_mesh.h5m \
--order 4 --method cgll --global_id GLOBAL_DOFS \
--order 1 --method fv --global_id GLOBAL_ID \
--file weights_se_to_fv.nc

Command Line Options

Run './mbtempest –help' for a complete list of available options.

Notes

  • For parallel execution, use MPI launcher (e.g., mpirun, mpiexec)
  • Output formats: .h5m (MOAB), .nc (NetCDF), .exo (ExodusII)
  • Requires MOAB and TempestRemap libraries
Author
MOAB Development Team
Date
Created: 2023

Definition in file mbtempest.cpp.

Macro Definition Documentation

◆ TR_CHK_SET_ERR

#define TR_CHK_SET_ERR (   err,
  msg 
)
Value:
if( err ) \
{ \
std::cout << "MOAB-TempestRemap Failure. ErrorCode (" << ( err ) << ") "; \
MB_CHK_SET_ERR( moab::MB_FAILURE, msg ); \
}

Definition at line 1592 of file mbtempest.cpp.

Function Documentation

◆ CreateTempestMesh()

static moab::ErrorCode CreateTempestMesh ( ToolContext ctx,
moab::TempestRemapper remapper,
Mesh *  tempest_mesh 
)
static

Creates a TempestRemap mesh based on the provided context and mesh type.

Parameters
ctxTool context containing configuration and state
remapperTempestRemap instance for mesh operations
tempest_meshOutput parameter for the created mesh
Returns
moab::ErrorCode Status of the operation

Definition at line 1897 of file mbtempest.cpp.

1898 {
1899  using namespace moab;
1900  using RemapperType = moab::TempestRemapper;
1901 
1902  auto& outputFormatter = ctx.outputFormatter;
1903 
1904  try
1905  {
1906  switch( ctx.meshType )
1907  {
1908  case RemapperType::OVERLAP_FILES:
1909  if( !ctx.proc_id ) outputFormatter.printf( 0, "Creating TempestRemap overlap mesh ...\n" );
1910  return handleOverlapFiles( ctx, tempest_mesh );
1911 
1912  case RemapperType::OVERLAP_MEMORY:
1913  if( !ctx.proc_id )
1914  outputFormatter.printf( 0, "Convert MOAB overlap files to TempestRemap format in-memory ...\n" );
1915  return handleOverlapMemory( ctx, remapper, tempest_mesh );
1916 
1917  case RemapperType::OVERLAP_MOAB:
1918  if( !ctx.proc_id )
1919  outputFormatter.printf( 0, "Convert MOAB meshes to TempestRemap format in-memory ...\n" );
1920  return handleOverlapMOAB( ctx, remapper );
1921 
1922  case RemapperType::ICO:
1923  if( !ctx.proc_id ) outputFormatter.printf( 0, "Creating TempestRemap ICO mesh ...\n" );
1924  return handleICOMesh( ctx, remapper, tempest_mesh );
1925 
1926  case RemapperType::RLL:
1927  if( !ctx.proc_id ) outputFormatter.printf( 0, "Creating TempestRemap RLL mesh ...\n" );
1928  return handleRLLMesh( ctx, remapper, tempest_mesh );
1929 
1930  default: // Default to CS mesh
1931  if( !ctx.proc_id ) outputFormatter.printf( 0, "Creating TempestRemap CS mesh ...\n" );
1932  return handleCSMesh( ctx, remapper, tempest_mesh );
1933  }
1934  }
1935  catch( const std::exception& e )
1936  {
1937  std::cerr << "Error in CreateTempestMesh: " << e.what() << "\n";
1938  return MB_FAILURE;
1939  }
1940 }

References anonymous_namespace{mbtempest.cpp}::handleCSMesh(), anonymous_namespace{mbtempest.cpp}::handleICOMesh(), anonymous_namespace{mbtempest.cpp}::handleOverlapFiles(), anonymous_namespace{mbtempest.cpp}::handleOverlapMemory(), anonymous_namespace{mbtempest.cpp}::handleOverlapMOAB(), anonymous_namespace{mbtempest.cpp}::handleRLLMesh(), ToolContext::meshType, ToolContext::outputFormatter, moab::DebugOutput::printf(), and ToolContext::proc_id.

Referenced by main().

◆ main()

int main ( int  argc,
char *  argv[] 
)

Definition at line 922 of file mbtempest.cpp.

923 {
924  try
925  {
926  NcError error( NcError::verbose_nonfatal );
927  std::stringstream sstr;
928  std::string historyStr;
929 
930  int proc_id = 0, nprocs = 1;
931 #ifdef MOAB_HAVE_MPI
932  MPI_Init( &argc, &argv );
933  MPI_Comm_rank( MPI_COMM_WORLD, &proc_id );
934  MPI_Comm_size( MPI_COMM_WORLD, &nprocs );
935 #endif
936 
937  moab::Core* mbCore = new( std::nothrow ) moab::Core;
938 
939  if( nullptr == mbCore )
940  {
941  return 1;
942  }
943 
944  // Build the history string
945  for( int ia = 0; ia < argc; ++ia )
946  historyStr += std::string( argv[ia] ) + " ";
947 
948  ToolContext* runCtx;
949 #ifdef MOAB_HAVE_MPI
950  moab::ParallelComm* pcomm = new moab::ParallelComm( mbCore, MPI_COMM_WORLD, 0 );
951 
952  runCtx = new ToolContext( mbCore, pcomm );
953  const char* writeOptions = ( nprocs > 1 ? "PARALLEL=WRITE_PART" : "" );
954 #else
955  runCtx = new ToolContext( mbCore );
956  const char* writeOptions = "";
957 #endif
958  runCtx->ParseCLOptions( argc, argv );
959 
960  const double radius_src = 1.0 /*2.0*acos(-1.0)*/;
961  const double radius_dest = 1.0 /*2.0*acos(-1.0)*/;
962 
963  moab::DebugOutput& outputFormatter = runCtx->outputFormatter;
964 
965 #ifdef MOAB_HAVE_MPI
966  moab::TempestRemapper remapper( mbCore, pcomm );
967 #else
968  moab::TempestRemapper remapper( mbCore );
969 #endif
970  remapper.meshValidate = true;
971  remapper.constructEdgeMap = true;
972  remapper.initialize();
973 
974  // Default area_method = lHuiller; Options: Girard, lHuiller, GaussQuadrature (if TR is available)
976 
977  Mesh* tempest_mesh = new Mesh();
978  MB_CHK_SET_ERR( CreateTempestMesh( *runCtx, remapper, tempest_mesh ), "Failed to create tempest mesh" );
979 
981  {
982  // Compute intersections with MOAB
983  // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
984  assert( runCtx->meshes.size() == 3 );
985 
986 #ifdef MOAB_HAVE_MPI
987  MB_CHK_SET_ERR( pcomm->check_all_shared_handles(), "Failed to check all shared handles" );
988 #endif
989 
990  // Load the meshes and validate
991  MB_CHK_SET_ERR( remapper.ConvertTempestMesh( moab::Remapper::SourceMesh ), "Failed to convert source mesh" );
992  MB_CHK_SET_ERR( remapper.ConvertTempestMesh( moab::Remapper::TargetMesh ), "Failed to convert target mesh" );
993  MB_CHK_SET_ERR( remapper.ConvertTempestMesh( moab::Remapper::OverlapMesh ), "Failed to convert overlap mesh" );
994  if( !runCtx->skip_io )
995  {
996  MB_CHK_SET_ERR( mbCore->write_mesh( "tempest_intersection.h5m", &runCtx->meshsets[2], 1 ),
997  "Failed to write TempestRemap intersection mesh in MOAB format" );
998  }
999 
1000  // print verbosely about the problem setting
1001  size_t velist[6], gvelist[6];
1002  {
1003  moab::Range rintxverts, rintxelems;
1004  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[0], 0, rintxverts ),
1005  "Failed to get vertices" );
1006  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[0], 2, rintxelems ),
1007  "Failed to get elements" );
1008  velist[0] = rintxverts.size();
1009  velist[1] = rintxelems.size();
1010 
1011  moab::Range bintxverts, bintxelems;
1012  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, bintxverts ),
1013  "Failed to get vertices" );
1014  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, bintxelems ),
1015  "Failed to get elements" );
1016  velist[2] = bintxverts.size();
1017  velist[3] = bintxelems.size();
1018  }
1019 
1020  moab::EntityHandle intxset; // == remapper.GetMeshSet(moab::Remapper::OverlapMesh);
1021 
1022  // Compute intersections with MOAB
1023  {
1024  // Create the intersection on the sphere object
1025  runCtx->timer_push( "setup the intersector" );
1026 
1027  moab::Intx2MeshOnSphere* mbintx = new moab::Intx2MeshOnSphere( mbCore );
1028  mbintx->set_error_tolerance( runCtx->epsrel );
1029  mbintx->set_box_error( runCtx->boxeps );
1030  mbintx->set_radius_source_mesh( radius_src );
1031  mbintx->set_radius_destination_mesh( radius_dest );
1032 #ifdef MOAB_HAVE_MPI
1033  mbintx->set_parallel_comm( pcomm );
1034 #endif
1035  MB_CHK_SET_ERR( mbintx->FindMaxEdges( runCtx->meshsets[0], runCtx->meshsets[1] ),
1036  "Failed to find max edges" );
1037 
1038 #ifdef MOAB_HAVE_MPI
1039  moab::Range local_verts;
1040  MB_CHK_SET_ERR( mbintx->build_processor_euler_boxes( runCtx->meshsets[1], local_verts ),
1041  "Failed to build processor euler boxes" );
1042 
1043  runCtx->timer_pop();
1044 
1045  moab::EntityHandle covering_set;
1046  runCtx->timer_push( "communicate the mesh" );
1047  // we compute just intersection here, no need for extra ghost layers anyway
1048  // ghost layers are needed in coverage for bilinear map, which does not actually need intersection
1049  // this will be fixed in the future, bilinear map needs just coverage, not intersection
1050  // so I am not passing the ghost layer here, even though there is an option in runCtx for a ghost layer
1051  // NOTE: This is a communication-heavy kernel if mesh is distributed very differently
1052  MB_CHK_SET_ERR( mbintx->construct_covering_set( runCtx->meshsets[0], covering_set ),
1053  "Failed to construct covering set" );
1054  runCtx->timer_pop();
1055 
1056  // print verbosely about the problem setting
1057  {
1058  moab::Range cintxverts, cintxelems;
1059  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( covering_set, 0, cintxverts ),
1060  "Failed to get vertices" );
1061  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( covering_set, 2, cintxelems ),
1062  "Failed to get elements" );
1063  velist[4] = cintxverts.size();
1064  velist[5] = cintxelems.size();
1065  }
1066 
1067  MPI_Reduce( velist, gvelist, 6, MPI_UINT64_T, MPI_SUM, 0, MPI_COMM_WORLD );
1068 
1069 #else
1070  moab::EntityHandle covering_set = runCtx->meshsets[0];
1071  for( int i = 0; i < 6; i++ )
1072  gvelist[i] = velist[i];
1073 #endif
1074 
1075  if( !proc_id )
1076  {
1077  outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", gvelist[0],
1078  gvelist[0] );
1079  outputFormatter.printf( 0, "The covering set contains %lu vertices and %lu elements \n", gvelist[2],
1080  gvelist[2] );
1081  outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", gvelist[1],
1082  gvelist[1] );
1083  }
1084 
1085  // Now let's invoke the MOAB intersection algorithm in parallel with a
1086  // source and target mesh set representing two different decompositions
1087  runCtx->timer_push( "compute intersections with MOAB" );
1088  MB_CHK_SET_ERR( mbCore->create_meshset( moab::MESHSET_SET, intxset ), "Can't create new set" );
1089  MB_CHK_SET_ERR( mbintx->intersect_meshes( covering_set, runCtx->meshsets[1], intxset ),
1090  "Can't compute the intersection of meshes on the sphere" );
1091  runCtx->timer_pop();
1092 
1093  // free the memory
1094  delete mbintx;
1095  }
1096 
1097  {
1098  moab::Range intxelems, intxverts;
1099  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( intxset, 2, intxelems ), "Failed to get elements" );
1100  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( intxset, 0, intxverts, true ),
1101  "Failed to get vertices" );
1102  outputFormatter.printf( 0, "The intersection set contains %lu elements and %lu vertices \n",
1103  intxelems.size(), intxverts.size() );
1104 
1105  double initial_sarea =
1106  areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[0],
1107  radius_src ); // use the target to compute the initial area
1108  double initial_tarea =
1109  areaAdaptor.area_on_sphere( mbCore, runCtx->meshsets[1],
1110  radius_dest ); // use the target to compute the initial area
1111  double intx_area = areaAdaptor.area_on_sphere( mbCore, intxset, radius_src );
1112 
1113  outputFormatter.printf( 0, "mesh areas: source = %12.10f, target = %12.10f, intersection = %12.10f \n",
1114  initial_sarea, initial_tarea, intx_area );
1115  outputFormatter.printf( 0, "relative error w.r.t source = %12.10e, target = %12.10e \n",
1116  fabs( intx_area - initial_sarea ) / initial_sarea,
1117  fabs( intx_area - initial_tarea ) / initial_tarea );
1118  }
1119 
1120  // Write out our computed intersection file
1121  if( !runCtx->skip_io )
1122  {
1123  MB_CHK_SET_ERR( mbCore->write_mesh( "moab_intersection.h5m", &intxset, 1 ),
1124  "Failed to write the intersection" );
1125  }
1126 
1127  if( runCtx->computeWeights )
1128  {
1129  runCtx->timer_push( "compute weights with the Tempest meshes" );
1130  // Call to generate an offline map with the tempest meshes
1131  OfflineMap weightMap;
1132  if( GenerateOfflineMapWithMeshes( *runCtx->meshes[0], *runCtx->meshes[1], *runCtx->meshes[2],
1133  runCtx->disc_methods[0], // std::string strInputType
1134  runCtx->disc_methods[1], // std::string strOutputType,
1135  runCtx->mapOptions, weightMap ) != 0 )
1136  throw std::runtime_error( "Could not generate offline map with TempestRemap" );
1137  runCtx->timer_pop();
1138 
1139  std::map< std::string, std::string > mapAttributes;
1140  if( !runCtx->skip_io ) weightMap.Write( "outWeights.nc", mapAttributes );
1141  }
1142  }
1143  else if( runCtx->meshType == moab::TempestRemapper::OVERLAP_MOAB )
1144  {
1145  // Usage: mpiexec -n 2 tools/mbtempest -t 5 -l mycs_2.h5m -l myico_2.h5m -f myoverlap_2.h5m
1146 #ifdef MOAB_HAVE_MPI
1147  MB_CHK_SET_ERR( pcomm->check_all_shared_handles(), "Checking shared handles failed." );
1148 #endif
1149 
1150  // print verbosely about the problem setting
1151  size_t velist[4] = { 0, 0, 0, 0 }, gvelist[4] = { 0, 0, 0, 0 };
1152  {
1153  moab::Range srcverts, srcelems;
1154  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[0], 0, srcverts ),
1155  "Failed to get vertices" );
1156  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[0], 2, srcelems ),
1157  "Failed to get elements" );
1159  "Failed to fix degenerate quads" );
1160  if( runCtx->enforceConvexity )
1161  {
1162  MB_CHK_SET_ERR( moab::IntxUtils::enforce_convexity( mbCore, runCtx->meshsets[0], proc_id ),
1163  "Failed to enforce convexity" );
1164  }
1165  MB_CHK_SET_ERR( areaAdaptor.positive_orientation( mbCore, runCtx->meshsets[0], radius_src ),
1166  "Failed to enforce positive orientation" );
1167  velist[0] = srcverts.size();
1168  velist[1] = srcelems.size();
1169 
1170  moab::Range tgtverts, tgtelems;
1171  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, tgtverts ),
1172  "Failed to get vertices" );
1173  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, tgtelems ),
1174  "Failed to get elements" );
1176  "Failed to fix degenerate quads" );
1177  if( runCtx->enforceConvexity )
1178  {
1179  MB_CHK_SET_ERR( moab::IntxUtils::enforce_convexity( mbCore, runCtx->meshsets[1], proc_id ),
1180  "Failed to enforce convexity" );
1181  }
1182  MB_CHK_SET_ERR( areaAdaptor.positive_orientation( mbCore, runCtx->meshsets[1], radius_dest ),
1183  "Failed to enforce positive orientation" );
1184  velist[2] = tgtverts.size();
1185  velist[3] = tgtelems.size();
1186  }
1187  //MB_CHK_SET_ERR( mbCore->write_file( "source_mesh.h5m", nullptr, writeOptions, &runCtx->meshsets[0], 1 ), "Could not write source mesh" );
1188  //MB_CHK_SET_ERR( mbCore->write_file( "target_mesh.h5m", nullptr, writeOptions, &runCtx->meshsets[1], 1 ), "Could not write target mesh" );
1189 
1190  // if( runCtx->nlayers && nprocs > 1 )
1191  // {
1192  // remapper.ResetMeshSet( moab::Remapper::SourceMesh, runCtx->meshsets[3] );
1193  // runCtx->meshes[0] = remapper.GetMesh( moab::Remapper::SourceMesh ); // ?
1194  // }
1195 
1196  // First compute the covering set such that the target elements are fully covered by the
1197  // local source grid
1198  runCtx->timer_push( "construct covering set for intersection" );
1199  // if ghosting, do not use gnomonic projection
1200  if( runCtx->nlayers > 0 ) runCtx->useGnomonicProjection = false;
1201  MB_CHK_SET_ERR( remapper.ConstructCoveringSet( runCtx->epsrel, 1.0, 1.0, runCtx->boxeps, runCtx->rrmGrids,
1202  runCtx->useGnomonicProjection, runCtx->nlayers ),
1203  "Failed to construct covering set" );
1204  runCtx->timer_pop();
1205 
1206 #ifdef MOAB_HAVE_MPI
1207  MPI_Reduce( velist, gvelist, 4, MPI_UINT64_T, MPI_SUM, 0, MPI_COMM_WORLD );
1208 #else
1209  for( int i = 0; i < 4; i++ )
1210  gvelist[i] = velist[i];
1211 #endif
1212  if( !proc_id && runCtx->print_diagnostics )
1213  {
1214  outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", gvelist[0],
1215  gvelist[1] );
1216  outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", gvelist[2],
1217  gvelist[3] );
1218  }
1219 
1220  if( runCtx->skip_intersection )
1221  {
1222  if( !proc_id ) outputFormatter.printf( 0, "Skipping mesh intersection computation.\n" );
1223  }
1224  else
1225  {
1226  // Compute intersections with MOAB with either the Kd-tree or the advancing front algorithm
1227  runCtx->timer_push( "setup and compute mesh intersections" );
1228  MB_CHK_SET_ERR( remapper.ComputeOverlapMesh( runCtx->kdtreeSearch, false ),
1229  "Failed to compute mesh intersections" );
1230  runCtx->timer_pop();
1231  }
1232 
1233  // print some diagnostic checks to see if the overlap grid resolved the input meshes
1234  // correctly
1235  // Compute ghost overlap elements once; reused for both area diagnostics and intx file write
1236  moab::Range ghostOverlapElems;
1237 #ifdef MOAB_HAVE_MPI
1238  if( nprocs > 1 && !runCtx->skip_intersection )
1239  MB_CHK_SET_ERR( remapper.GetOverlapAugmentedEntities( ghostOverlapElems ),
1240  "Failed to get ghost overlap entities" );
1241 #endif
1242 
1243  double dTotalOverlapArea = 0.0;
1244  if( runCtx->print_diagnostics && !runCtx->skip_intersection )
1245  {
1246  // Areas for source, target, overlap meshes
1247  double local_areas[3] = { 0, 0, 0 },
1248  global_areas[3] = { 0, 0, 0 };
1249 
1250  // Helper: compute area of a meshset excluding cells with GRID_IMASK==0.
1251  // Both source and target SCRIP grids may have a land/sea mask; the intersection
1252  // only covers unmasked cells, so comparing full-mesh areas gives a misleading error.
1253  auto area_unmasked = [&]( moab::EntityHandle meshset, double radius ) -> double {
1254  moab::Tag imaskTag = 0;
1255  mbCore->tag_get_handle( "GRID_IMASK", imaskTag );
1256  if( !imaskTag ) return areaAdaptor.area_on_sphere( mbCore, meshset, radius );
1257  moab::Range cells;
1258  mbCore->get_entities_by_dimension( meshset, 2, cells );
1259  std::vector< int > masks( cells.size(), 1 );
1260  mbCore->tag_get_data( imaskTag, cells, masks.data() );
1261  moab::Range maskedCells;
1262  size_t idx = 0;
1263  for( auto it = cells.begin(); it != cells.end(); ++it, ++idx )
1264  if( !masks[idx] ) maskedCells.insert( *it );
1265  moab::Range unmasked = moab::subtract( cells, maskedCells );
1266  moab::EntityHandle tmpSet;
1267  mbCore->create_meshset( moab::MESHSET_SET, tmpSet );
1268  mbCore->add_entities( tmpSet, unmasked );
1269  double area = areaAdaptor.area_on_sphere( mbCore, tmpSet, radius );
1270  mbCore->delete_entities( &tmpSet, 1 );
1271  return area;
1272  };
1273 
1274  local_areas[0] = area_unmasked( runCtx->meshsets[0], radius_src );
1275  local_areas[1] = area_unmasked( runCtx->meshsets[1], radius_dest );
1276  // Exclude ghost overlap elements from area sum to avoid double-counting after MPI_Allreduce
1277  {
1278  moab::Range ownedOverlapElems;
1279  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[2], 2, ownedOverlapElems ),
1280  "Failed to get overlap elements" );
1281  ownedOverlapElems = moab::subtract( ownedOverlapElems, ghostOverlapElems );
1282  moab::EntityHandle ownedOverlapSet;
1283  MB_CHK_SET_ERR( mbCore->create_meshset( moab::MESHSET_SET, ownedOverlapSet ),
1284  "Can't create owned overlap meshset" );
1285  MB_CHK_SET_ERR( mbCore->add_entities( ownedOverlapSet, ownedOverlapElems ),
1286  "Can't add owned overlap elements" );
1287  local_areas[2] = areaAdaptor.area_on_sphere( mbCore, ownedOverlapSet, radius_src );
1288  MB_CHK_SET_ERR( mbCore->delete_entities( &ownedOverlapSet, 1 ), "Can't delete temp meshset" );
1289  }
1290 
1291 #ifdef MOAB_HAVE_MPI
1292  MPI_Allreduce( &local_areas[0], &global_areas[0], 3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD );
1293 #else
1294  global_areas[0] = local_areas[0];
1295  global_areas[1] = local_areas[1];
1296  global_areas[2] = local_areas[2];
1297 #endif
1298  if( !proc_id )
1299  {
1300  outputFormatter.printf( 0,
1301  "initial area: source mesh = %12.14f, target mesh = "
1302  "%12.14f, overlap mesh = %12.14f\n",
1303  global_areas[0], global_areas[1], global_areas[2] );
1304  outputFormatter.printf( 0, "relative error w.r.t source = %12.14e, and target = %12.14e\n",
1305  fabs( global_areas[0] - global_areas[2] ) / global_areas[0],
1306  fabs( global_areas[1] - global_areas[2] ) / global_areas[1] );
1307  }
1308  dTotalOverlapArea = global_areas[2];
1309  }
1310 
1311  if( runCtx->intxFilename.size() && !runCtx->skip_intersection )
1312  {
1313  moab::EntityHandle writableOverlapSet;
1314  MB_CHK_SET_ERR( mbCore->create_meshset( moab::MESHSET_SET, writableOverlapSet ), "Can't create new set" );
1315  moab::EntityHandle meshOverlapSet = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
1316  moab::Range ovEnts;
1317  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( meshOverlapSet, 2, ovEnts ), "Can't create new set" );
1318  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( meshOverlapSet, 0, ovEnts ), "Can't create new set" );
1319 
1320 #ifdef MOAB_HAVE_MPI
1321  // Exclude ghost overlap elements from the write: each ghost element is owned by another
1322  // rank and will be written from there. Including ghosts here causes duplicate entity
1323  // handles in the parallel HDF5 output and deadlocks the collective write.
1324  if( nprocs > 1 )
1325  {
1326  ovEnts = moab::subtract( ovEnts, ghostOverlapElems );
1327 #ifdef MOAB_DBG
1328  if( !runCtx->skip_io )
1329  {
1330  std::stringstream filename;
1331  filename << "aug_overlap" << runCtx->pcomm->rank() << ".h5m";
1332  MB_CHK_SET_ERR( mbCore->write_file( filename.str().c_str(), 0, 0, &meshOverlapSet, 1 ),
1333  "Failed to write the overlap set" );
1334  }
1335 #endif
1336  }
1337 #endif
1338  MB_CHK_SET_ERR( mbCore->add_entities( writableOverlapSet, ovEnts ), "adding local intx cells failed" );
1339 
1340 #ifdef MOAB_HAVE_MPI
1341 #ifdef MOAB_DBG
1342  if( nprocs > 1 && !runCtx->skip_io )
1343  {
1344  std::stringstream filename;
1345  filename << "writable_intx_" << runCtx->pcomm->rank() << ".h5m";
1346  MB_CHK_SET_ERR( mbCore->write_file( filename.str().c_str(), 0, 0, &writableOverlapSet, 1 ),
1347  "Failed to write the writable overlap set" );
1348  }
1349 #endif
1350 #endif
1351 
1352  size_t lastindex = runCtx->intxFilename.find_last_of( "." );
1353  sstr.str( "" );
1354  sstr << runCtx->intxFilename.substr( 0, lastindex ) << ".h5m";
1355  if( !runCtx->proc_id )
1356  std::cout << "Writing out the MOAB intersection mesh file to " << sstr.str() << std::endl;
1357 
1358  // Write out our computed intersection file
1359  if( !runCtx->skip_io )
1360  {
1361  MB_CHK_SET_ERR( mbCore->write_file( sstr.str().c_str(), nullptr, writeOptions, &writableOverlapSet, 1 ),
1362  "Failed to write the writable overlap set" );
1363  }
1364  }
1365 
1366  if( runCtx->computeWeights )
1367  {
1368  runCtx->meshes[2] = remapper.GetMesh( moab::Remapper::OverlapMesh );
1369  if( !runCtx->proc_id ) std::cout << std::endl;
1370 
1371  runCtx->timer_push( "setup computation of weights" );
1372  // Call to generate the remapping weights with the tempest meshes
1373  moab::TempestOnlineMap* weightMap = new moab::TempestOnlineMap( &remapper );
1374  runCtx->timer_pop();
1375 
1376  runCtx->timer_push( "compute weights with TempestRemap" );
1378  runCtx->disc_methods[0], // std::string strInputType
1379  runCtx->disc_methods[1], // std::string strOutputType,
1380  runCtx->mapOptions, // const GenerateOfflineMapAlgorithmOptions& options
1381  runCtx->doftag_names[0], // const std::string& source_tag_name
1382  runCtx->doftag_names[1] // const std::string& target_tag_name
1383  ),
1384  "Failed to generate remapping weights" );
1385  runCtx->timer_pop();
1386 
1387  weightMap->PrintMapStatistics();
1388 
1389  // Invoke the CheckMap routine on the TempestRemap serial interface directly, if running
1390  // on a single process
1391  if( runCtx->fCheck )
1392  {
1393  const double dNormalTolerance = 1.0E-8;
1394  const double dStrictTolerance = 1.0E-12;
1395  weightMap->CheckMap( runCtx->fCheck, runCtx->fCheck, runCtx->fCheck && ( runCtx->ensureMonotonicity ),
1396  dNormalTolerance, dStrictTolerance, dTotalOverlapArea );
1397  }
1398 
1399  if( runCtx->outFilename.size() && !runCtx->skip_io )
1400  {
1401  std::map< std::string, std::string > attrMap;
1402  attrMap["MOABversion"] = std::string( MOAB_PACKAGE_VERSION );
1403  attrMap["Title"] = "MOAB-TempestRemap (mbtempest) Offline Regridding Weight Generator";
1404  attrMap["normalization"] = "ovarea";
1405  attrMap["remap_options"] = runCtx->mapOptions.strMethod;
1406  attrMap["domain_a"] = runCtx->inFilenames[0];
1407  attrMap["domain_b"] = runCtx->inFilenames[1];
1408  if( runCtx->intxFilename.size() ) attrMap["domain_aUb"] = runCtx->intxFilename;
1409  attrMap["map_aPb"] = runCtx->outFilename;
1410  attrMap["methodorder_a"] = runCtx->disc_methods[0] + ":" + std::to_string( runCtx->disc_orders[0] ) +
1411  ":" + std::string( runCtx->doftag_names[0] );
1412  attrMap["concave_a"] = runCtx->mapOptions.fSourceConcave ? "true" : "false";
1413  attrMap["methodorder_b"] = runCtx->disc_methods[1] + ":" + std::to_string( runCtx->disc_orders[1] ) +
1414  ":" + std::string( runCtx->doftag_names[1] );
1415  attrMap["concave_b"] = runCtx->mapOptions.fTargetConcave ? "true" : "false";
1416  attrMap["bubble"] = runCtx->mapOptions.fNoBubble ? "false" : "true";
1417  attrMap["history"] = historyStr;
1418 
1419  // Write the map file to disk in parallel using either HDF5 or SCRIP interface
1420  // in extra case; maybe need a better solution, just create it with the right meshset
1421  // from the beginning;
1422  MB_CHK_SET_ERR( weightMap->WriteParallelMap( runCtx->outFilename.c_str(), attrMap ),
1423  "Failed writing the parallel map to disk" );
1424  }
1425 
1426  if( runCtx->verifyWeights )
1427  {
1428  // Let us pick a sampling test function for solution evaluation
1429  // SH, SV, FH, C, USERVAR
1430  bool userVariable = false;
1432  if( !runCtx->variableToVerify.compare( "SH" ) )
1433  testFunction = &sample_slow_harmonic;
1434  else if( !runCtx->variableToVerify.compare( "FH" ) )
1435  testFunction = &sample_fast_harmonic;
1436  else if( !runCtx->variableToVerify.compare( "SV" ) )
1437  testFunction = &sample_stationary_vortex;
1438  else if( !runCtx->variableToVerify.compare( "C" ) )
1439  testFunction = &sample_constant;
1440  else
1441  {
1442  userVariable = runCtx->variableToVerify.size() ? true : false;
1443  testFunction = runCtx->variableToVerify.size() ? nullptr : sample_stationary_vortex;
1444  }
1445 
1446  moab::Tag srcAnalyticalFunction;
1447  moab::Tag tgtAnalyticalFunction;
1448  moab::Tag tgtProjectedFunction;
1449  if( testFunction )
1450  {
1451  runCtx->timer_push( "describe a solution on source grid" );
1452  // MB_CHK_SET_ERR( mbCore->tag_get_handle( runCtx->variableToVerify.c_str(), srcAnalyticalFunction ),
1453  // "Failed to get analytical solution on source grid" );
1454  MB_CHK_SET_ERR( weightMap->DefineAnalyticalSolution( srcAnalyticalFunction,
1455  "AnalyticalSolnSrcExact",
1456  moab::Remapper::SourceMesh, testFunction ),
1457  "Failed to define analytical solution on source grid" );
1458  runCtx->timer_pop();
1459 
1460  // runCtx->timer_push( "exchange solution on source grid" );
1461  // moab::Range& srccovEnts = remapper.GetMeshEntities( moab::Remapper::CoveringMesh );
1462  // MB_CHK_SET_ERR( pcomm->exchange_tags( srcAnalyticalFunction, srccovEnts ),
1463  // "Failed to exchange analytical solution on source grid" );
1464  // runCtx->timer_pop();
1465 
1466  runCtx->timer_push( "describe a solution on target grid" );
1468  tgtAnalyticalFunction, "AnalyticalSolnTgtExact", moab::Remapper::TargetMesh,
1469  testFunction, &tgtProjectedFunction, "ProjectedSolnTgt" ),
1470  "Failed to define analytical solution on target grid" );
1471  runCtx->timer_pop();
1472  }
1473  else
1474  {
1475  MB_CHK_SET_ERR( mbCore->tag_get_handle( runCtx->variableToVerify.c_str(), srcAnalyticalFunction ),
1476  "Failed to get analytical solution on source grid" );
1477  MB_CHK_SET_ERR( mbCore->tag_get_handle( "ProjectedSolnTgt", 1, moab::MB_TYPE_DOUBLE,
1478  tgtProjectedFunction,
1480  "Failed to get projected solution on target grid" );
1481  }
1482 
1483  // if( !runCtx->skip_io )
1484  {
1485  MB_CHK_SET_ERR( mbCore->write_file( "srcWithSolnTag.h5m", nullptr, writeOptions,
1486  &runCtx->meshsets[0], 1 ),
1487  "Failed to write the source mesh with solution tag" );
1488  }
1489 
1490  runCtx->timer_push( "compute solution projection on target grid" );
1491  MB_CHK_SET_ERR( weightMap->ApplyWeights( srcAnalyticalFunction, tgtProjectedFunction, false,
1492  runCtx->cassType ),
1493  "Failed to apply weights" );
1494  runCtx->timer_pop();
1495 
1496  // if( !runCtx->skip_io )
1497  {
1498  MB_CHK_SET_ERR( mbCore->write_file( "tgtWithSolnTag2.h5m", nullptr, writeOptions,
1499  &runCtx->meshsets[1], 1 ),
1500  "Failed to write the target mesh with projected solution tag" );
1501  }
1502 
1503  if( nprocs == 1 && runCtx->baselineFile.size() )
1504  {
1505  // save the field from tgtWithSolnTag2 in a text file, and global ids for cells
1506  moab::Range tgtEntities;
1507  if( runCtx->disc_methods[1] == "pcloud" )
1508  {
1509  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, tgtEntities ),
1510  "Failed to get entities by dimension" );
1511  }
1512  else
1513  {
1514  MB_CHK_SET_ERR( mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, tgtEntities ),
1515  "Failed to get entities by dimension" );
1516  }
1517  std::vector< int > globIds( tgtEntities.size() );
1518  std::vector< double > vals( tgtEntities.size() );
1519  moab::Tag projTag;
1520  MB_CHK_SET_ERR( mbCore->tag_get_handle( "ProjectedSolnTgt", projTag ),
1521  "Failed to get projected solution tag" );
1522  moab::Tag gid = mbCore->globalId_tag();
1523  MB_CHK_SET_ERR( mbCore->tag_get_data( gid, tgtEntities, &globIds[0] ), "Failed to get global ids" );
1524  MB_CHK_SET_ERR( mbCore->tag_get_data( projTag, tgtEntities, &vals[0] ),
1525  "Failed to get projected solution" );
1526  std::fstream fs;
1527  fs.open( runCtx->baselineFile.c_str(), std::fstream::out );
1528  fs << std::setprecision( 15 ); // maximum precision for doubles
1529  for( size_t i = 0; i < tgtEntities.size(); i++ )
1530  fs << globIds[i] << " " << vals[i] << "\n";
1531  fs.close();
1532  // for good measure, save the source file too, with the tag AnalyticalSolnSrcExact
1533  // it will be used later to test, along with a target file
1534  if( !runCtx->skip_io )
1535  {
1536  MB_CHK_SET_ERR( mbCore->write_file( "srcWithSolnTag.h5m", nullptr, writeOptions,
1537  &runCtx->meshsets[0], 1 ),
1538  "Failed to write the source mesh with solution tag" );
1539  }
1540  }
1541 
1542  // compute error metrics if it is a known analytical functional
1543  if( !userVariable )
1544  {
1545  runCtx->timer_push( "compute error metrics against analytical solution on target grid" );
1546  std::map< std::string, double > errMetrics;
1547  MB_CHK_SET_ERR( weightMap->ComputeMetrics( moab::Remapper::TargetMesh, tgtAnalyticalFunction,
1548  tgtProjectedFunction, errMetrics, true ),
1549  "Failed to compute error metrics" );
1550  runCtx->timer_pop();
1551  }
1552  }
1553 
1554  delete weightMap;
1555  }
1556  }
1557 
1558  // Clean up
1559  remapper.clear();
1560  delete runCtx;
1561  delete mbCore;
1562 
1563 #ifdef MOAB_HAVE_MPI
1564  MPI_Finalize();
1565 #endif
1566  return 0;
1567  }
1568  catch( const std::exception& e )
1569  {
1570  std::cerr << "[mbtempest] Fatal error: " << e.what() << std::endl;
1571 #ifdef MOAB_HAVE_MPI
1572  MPI_Abort( MPI_COMM_WORLD, 1 );
1573 #endif
1574  return 1;
1575  }
1576  catch( ... )
1577  {
1578  std::cerr << "[mbtempest] Fatal: unknown exception caught" << std::endl;
1579 #ifdef MOAB_HAVE_MPI
1580  MPI_Abort( MPI_COMM_WORLD, 1 );
1581 #endif
1582  return 1;
1583  }
1584 }

References moab::Core::add_entities(), moab::TempestOnlineMap::ApplyWeights(), moab::IntxAreaUtils::area_on_sphere(), ToolContext::baselineFile, moab::Range::begin(), ToolContext::boxeps, ToolContext::cassType, moab::ParallelComm::check_all_shared_handles(), moab::TempestRemapper::clear(), moab::TempestOnlineMap::ComputeMetrics(), moab::TempestRemapper::ComputeOverlapMesh(), ToolContext::computeWeights, moab::TempestRemapper::ConstructCoveringSet(), moab::TempestRemapper::constructEdgeMap, moab::TempestRemapper::ConvertTempestMesh(), moab::Core::create_meshset(), CreateTempestMesh(), moab::TempestOnlineMap::DefineAnalyticalSolution(), moab::Core::delete_entities(), ToolContext::disc_methods, ToolContext::disc_orders, ToolContext::doftag_names, moab::Range::end(), moab::IntxUtils::enforce_convexity(), ToolContext::enforceConvexity, ToolContext::ensureMonotonicity, ToolContext::epsrel, error(), ToolContext::fCheck, moab::Intx2Mesh::FindMaxEdges(), moab::IntxUtils::fix_degenerate_quads(), moab::TempestOnlineMap::GenerateRemappingWeights(), moab::Core::get_entities_by_dimension(), moab::TempestRemapper::GetMesh(), moab::TempestRemapper::GetMeshSet(), moab::TempestRemapper::GetOverlapAugmentedEntities(), moab::Core::globalId_tag(), ToolContext::inFilenames, moab::TempestRemapper::initialize(), moab::Range::insert(), moab::Intx2Mesh::intersect_meshes(), ToolContext::intxFilename, ToolContext::kdtreeSearch, moab::IntxAreaUtils::lHuiller, ToolContext::mapOptions, MB_CHK_SET_ERR, MB_TAG_CREAT, MB_TAG_DENSE, MB_TYPE_DOUBLE, ToolContext::meshes, MESHSET_SET, ToolContext::meshsets, ToolContext::meshType, moab::TempestRemapper::meshValidate, MOAB_PACKAGE_VERSION, ToolContext::nlayers, ToolContext::outFilename, ToolContext::outputFormatter, moab::TempestRemapper::OVERLAP_MEMORY, moab::TempestRemapper::OVERLAP_MOAB, moab::Remapper::OverlapMesh, ToolContext::ParseCLOptions(), moab::IntxAreaUtils::positive_orientation(), ToolContext::print_diagnostics, moab::DebugOutput::printf(), moab::TempestOnlineMap::PrintMapStatistics(), ToolContext::proc_id, radius, ToolContext::rrmGrids, sample_constant(), sample_fast_harmonic(), sample_slow_harmonic(), sample_stationary_vortex(), moab::Intx2Mesh::set_box_error(), moab::Intx2Mesh::set_error_tolerance(), moab::Intx2MeshOnSphere::set_radius_destination_mesh(), moab::Intx2MeshOnSphere::set_radius_source_mesh(), moab::Range::size(), ToolContext::skip_intersection, ToolContext::skip_io, moab::Remapper::SourceMesh, moab::subtract(), moab::Core::tag_get_data(), moab::Core::tag_get_handle(), moab::Remapper::TargetMesh, ToolContext::timer_pop(), ToolContext::timer_push(), ToolContext::ToolContext(), ToolContext::useGnomonicProjection, ToolContext::variableToVerify, ToolContext::verifyWeights, moab::Core::write_file(), moab::Core::write_mesh(), and moab::TempestOnlineMap::WriteParallelMap().

◆ sample_constant()

static constexpr double sample_constant ( double  ,
double   
)
inlinestaticconstexprnoexcept

Constant sample function.

Returns
double Always returns 1.0

Definition at line 1969 of file mbtempest.cpp.

1970 {
1971  return 1.0;
1972 }

Referenced by main().

◆ sample_fast_harmonic()

static double sample_fast_harmonic ( double  dLon,
double  dLat 
)
inlinestaticnoexcept

Sample function with fast harmonic variation.

Parameters
dLonLongitude in radians
dLatLatitude in radians
Returns
double Function value at (dLon, dLat)

Definition at line 1994 of file mbtempest.cpp.

1995 {
1996  const double sin2Lat = std::sin( 2.0 * dLat );
1997  return 2.0 +
1998  sin2Lat * sin2Lat * sin2Lat * sin2Lat * sin2Lat * sin2Lat * sin2Lat * sin2Lat * std::cos( 16.0 * dLon );
1999 }

Referenced by main().

◆ sample_slow_harmonic()

static double sample_slow_harmonic ( double  dLon,
double  dLat 
)
inlinestaticnoexcept

Sample function with slow harmonic variation.

Parameters
dLonLongitude in radians
dLatLatitude in radians
Returns
double Function value at (dLon, dLat)

Definition at line 1981 of file mbtempest.cpp.

1982 {
1983  const double cosLat = std::cos( dLat );
1984  return 2.0 + cosLat * cosLat * std::cos( 2.0 * dLon );
1985 }

Referenced by main().

◆ sample_stationary_vortex()

static double sample_stationary_vortex ( double  dLon,
double  dLat 
)
inlinestaticnoexcept

Sample function representing a stationary vortex.

Parameters
dLonLongitude in radians
dLatLatitude in radians
Returns
double Function value at (dLon, dLat)

Definition at line 2008 of file mbtempest.cpp.

2009 {
2010  // Find the rotated longitude and latitude of a point on a sphere
2011  // with pole at (dLonC, dLatC)
2012  const double dSinC = std::sin( VORTEX_LAT0 );
2013  const double dCosC = std::cos( VORTEX_LAT0 );
2014  const double dSinT = std::sin( dLat );
2015  const double dCosT = std::cos( dLat );
2016 
2017  const double dTrm = dCosT * std::cos( dLon - VORTEX_LON0 );
2018  const double dX = dSinC * dTrm - dCosC * dSinT;
2019  const double dY = dCosT * std::sin( dLon - VORTEX_LON0 );
2020  const double dZ = dSinC * dSinT + dCosC * dTrm;
2021 
2022  // Calculate new longitude and latitude in rotated coordinate system
2023  double dNewLon = std::atan2( dY, dX );
2024  if( dNewLon < 0.0 )
2025  {
2026  dNewLon += 2.0 * M_PI;
2027  }
2028  const double dNewLat = std::asin( dZ );
2029 
2030  // Calculate vortex profile
2031  const double dRho = VORTEX_R0 * std::cos( dNewLat );
2032  const double dVt = 3.0 * std::sqrt( 3.0 ) / 2.0 / std::cosh( dRho ) / std::cosh( dRho ) * std::tanh( dRho );
2033 
2034  // Calculate angular velocity (avoid division by zero)
2035  const double dOmega = ( dRho == 0.0 ) ? 0.0 : ( dVt / dRho );
2036 
2037  // Return the final vortex profile
2038  return ( 1.0 - std::tanh( dRho / VORTEX_D * std::sin( dNewLon - dOmega * VORTEX_T ) ) );
2039 }

References anonymous_namespace{mbtempest.cpp}::VORTEX_D, anonymous_namespace{mbtempest.cpp}::VORTEX_LAT0, anonymous_namespace{mbtempest.cpp}::VORTEX_LON0, anonymous_namespace{mbtempest.cpp}::VORTEX_R0, and anonymous_namespace{mbtempest.cpp}::VORTEX_T.

Referenced by main().