#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <vector>
#include <string>
#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"
Go to the source code of this file.
Classes | |
struct | ToolContext |
Functions | |
static moab::ErrorCode | CreateTempestMesh (ToolContext &, moab::TempestRemapper &remapper, Mesh *) |
double | sample_slow_harmonic (double dLon, double dLat) |
double | sample_fast_harmonic (double dLon, double dLat) |
double | sample_constant (double dLon, double dLat) |
double | sample_stationary_vortex (double dLon, double dLat) |
std::string | get_file_read_options (ToolContext &ctx, std::string filename) |
int | main (int argc, char *argv[]) |
|
static |
Definition at line 1039 of file mbtempest.cpp.
1040 {
1041 moab::ErrorCode rval = moab::MB_SUCCESS;
1042 int err;
1043 moab::DebugOutput& outputFormatter = ctx.outputFormatter;
1044
1045 if( !ctx.proc_id )
1046 {
1047 outputFormatter.printf( 0, "Creating TempestRemap Mesh object ...\n" );
1048 }
1049
1050 if( ctx.meshType == moab::TempestRemapper::OVERLAP_FILES )
1051 {
1052 ctx.timer_push( "create Tempest OverlapMesh" );
1053 // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
1054 err = GenerateOverlapMesh( ctx.inFilenames[0], ctx.inFilenames[1], *tempest_mesh, ctx.outFilename, "NetCDF4",
1055 "exact", true );
1056 if( err )
1057 rval = moab::MB_FAILURE;
1058 else
1059 ctx.meshes.push_back( tempest_mesh );
1060 ctx.timer_pop();
1061 }
1062 else if( ctx.meshType == moab::TempestRemapper::OVERLAP_MEMORY )
1063 {
1064 // Load the meshes and validate
1065 ctx.meshsets.resize( 3 );
1066 ctx.meshes.resize( 3 );
1067 ctx.meshsets[0] = remapper.GetMeshSet( moab::Remapper::SourceMesh );
1068 ctx.meshsets[1] = remapper.GetMeshSet( moab::Remapper::TargetMesh );
1069 ctx.meshsets[2] = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
1070
1071 ctx.timer_push( "load MOAB Source mesh" );
1072 // First the source
1073 rval = remapper.LoadMesh( moab::Remapper::SourceMesh, ctx.inFilenames[0], moab::TempestRemapper::DEFAULT );MB_CHK_ERR( rval );
1074 ctx.meshes[0] = remapper.GetMesh( moab::Remapper::SourceMesh );
1075 ctx.timer_pop();
1076
1077 ctx.timer_push( "load MOAB Target mesh" );
1078 // Next the target
1079 rval = remapper.LoadMesh( moab::Remapper::TargetMesh, ctx.inFilenames[1], moab::TempestRemapper::DEFAULT );MB_CHK_ERR( rval );
1080 ctx.meshes[1] = remapper.GetMesh( moab::Remapper::TargetMesh );
1081 ctx.timer_pop();
1082
1083 ctx.timer_push( "generate TempestRemap OverlapMesh" );
1084 // Now let us construct the overlap mesh, by calling TempestRemap interface directly
1085 // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
1086 err = GenerateOverlapWithMeshes( *ctx.meshes[0], *ctx.meshes[1], *tempest_mesh, "" /*ctx.outFilename*/,
1087 "NetCDF4", "exact", false );
1088 ctx.timer_pop();
1089 if( err )
1090 rval = moab::MB_FAILURE;
1091 else
1092 {
1093 remapper.SetMesh( moab::Remapper::OverlapMesh, tempest_mesh );
1094 ctx.meshes[2] = remapper.GetMesh( moab::Remapper::OverlapMesh );
1095 // ctx.meshes.push_back(*tempest_mesh);
1096 }
1097 }
1098 else if( ctx.meshType == moab::TempestRemapper::OVERLAP_MOAB )
1099 {
1100 ctx.meshsets.resize( 3 );
1101 ctx.meshes.resize( 3 );
1102 ctx.meshsets[0] = remapper.GetMeshSet( moab::Remapper::SourceMesh );
1103 ctx.meshsets[1] = remapper.GetMeshSet( moab::Remapper::TargetMesh );
1104 ctx.meshsets[2] = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
1105
1106 const double radius_src = 1.0 /*2.0*acos(-1.0)*/;
1107 const double radius_dest = 1.0 /*2.0*acos(-1.0)*/;
1108
1109 std::vector< int > smetadata, tmetadata;
1110 //const char* additional_read_opts = ( ctx.n_procs > 1 ? "NO_SET_CONTAINING_PARENTS;" : "" );
1111 std::string additional_read_opts_src = get_file_read_options( ctx, ctx.inFilenames[0] );
1112
1113 ctx.timer_push( "load MOAB Source mesh" );
1114 // Load the source mesh and validate
1115 rval =
1116 remapper.LoadNativeMesh( ctx.inFilenames[0], ctx.meshsets[0], smetadata, additional_read_opts_src.c_str() );MB_CHK_ERR( rval );
1117 if( smetadata.size() ) remapper.SetMeshType( moab::Remapper::SourceMesh, smetadata );
1118 ctx.timer_pop();
1119
1120 ctx.timer_push( "preprocess MOAB Source mesh" );
1121 // Rescale the radius of both to compute the intersection
1122 rval = moab::IntxUtils::ScaleToRadius( ctx.mbcore, ctx.meshsets[0], radius_src );MB_CHK_ERR( rval );
1123
1124 ctx.timer_pop();
1125
1126 // Load the target mesh and validate
1127 std::string addititional_read_opts_tgt = get_file_read_options( ctx, ctx.inFilenames[1] );
1128 // addititional_read_opts_tgt += "PARALLEL_GHOSTS=2.0.3;PARALLEL_THIN_GHOST_LAYER;";
1129
1130 ctx.timer_push( "load MOAB Target mesh" );
1131 rval = remapper.LoadNativeMesh( ctx.inFilenames[1], ctx.meshsets[1], tmetadata,
1132 addititional_read_opts_tgt.c_str() );MB_CHK_ERR( rval );
1133 if( tmetadata.size() ) remapper.SetMeshType( moab::Remapper::TargetMesh, tmetadata );
1134 ctx.timer_pop();
1135
1136 ctx.timer_push( "preprocess MOAB Target mesh" );
1137 rval = moab::IntxUtils::ScaleToRadius( ctx.mbcore, ctx.meshsets[1], radius_dest );MB_CHK_ERR( rval );
1138 ctx.timer_pop();
1139
1140 if( ctx.computeWeights )
1141 {
1142 ctx.timer_push( "convert MOAB meshes to TempestRemap meshes in memory" );
1143 // convert MOAB representation to TempestRemap's Mesh for source
1144 rval = remapper.ConvertMeshToTempest( moab::Remapper::SourceMesh );MB_CHK_ERR( rval );
1145 ctx.meshes[0] = remapper.GetMesh( moab::Remapper::SourceMesh );
1146
1147 // convert MOAB representation to TempestRemap's Mesh for target
1148 rval = remapper.ConvertMeshToTempest( moab::Remapper::TargetMesh );MB_CHK_ERR( rval );
1149 ctx.meshes[1] = remapper.GetMesh( moab::Remapper::TargetMesh );
1150 ctx.timer_pop();
1151 }
1152 }
1153 else if( ctx.meshType == moab::TempestRemapper::ICO )
1154 {
1155 ctx.timer_push( "generate ICO mesh with TempestRemap" );
1156 err = GenerateICOMesh( *tempest_mesh, ctx.blockSize, ctx.computeDual, ctx.outFilename, "NetCDF4" );
1157 ctx.timer_pop();
1158
1159 if( err )
1160 rval = moab::MB_FAILURE;
1161 else
1162 ctx.meshes.push_back( tempest_mesh );
1163 }
1164 else if( ctx.meshType == moab::TempestRemapper::RLL )
1165 {
1166 ctx.timer_push( "generate RLL mesh with TempestRemap" );
1167 err = GenerateRLLMesh( *tempest_mesh, // Mesh& meshOut,
1168 ctx.blockSize * 2, ctx.blockSize, // int nLongitudes, int nLatitudes,
1169 0.0, 360.0, // double dLonBegin, double dLonEnd,
1170 -90.0, 90.0, // double dLatBegin, double dLatEnd,
1171 false, false, false, // bool fGlobalCap, bool fFlipLatLon, bool fForceGlobal,
1172 "" /*ctx.inFilename*/, "",
1173 "", // std::string strInputFile, std::string strInputFileLonName, std::string
1174 // strInputFileLatName,
1175 ctx.outFilename, "NetCDF4", // std::string strOutputFile, std::string strOutputFormat
1176 true // bool fVerbose
1177 );
1178 ctx.timer_pop();
1179
1180 if( err )
1181 rval = moab::MB_FAILURE;
1182 else
1183 ctx.meshes.push_back( tempest_mesh );
1184 }
1185 else // default
1186 {
1187 ctx.timer_push( "generate CS mesh with TempestRemap" );
1188 err = GenerateCSMesh( *tempest_mesh, ctx.blockSize, ctx.outFilename, "NetCDF4" );
1189 ctx.timer_pop();
1190 if( err )
1191 rval = moab::MB_FAILURE;
1192 else
1193 ctx.meshes.push_back( tempest_mesh );
1194 }
1195
1196 if( ctx.meshType != moab::TempestRemapper::OVERLAP_MOAB && !tempest_mesh )
1197 {
1198 std::cout << "Tempest Mesh is not a complete object; Quitting...";
1199 exit( -1 );
1200 }
1201
1202 return rval;
1203 }
References ToolContext::blockSize, ToolContext::computeDual, ToolContext::computeWeights, moab::TempestRemapper::ConvertMeshToTempest(), moab::TempestRemapper::DEFAULT, ErrorCode, get_file_read_options(), moab::TempestRemapper::GetMesh(), moab::TempestRemapper::GetMeshSet(), moab::TempestRemapper::ICO, ToolContext::inFilenames, moab::TempestRemapper::LoadMesh(), moab::Remapper::LoadNativeMesh(), MB_CHK_ERR, MB_SUCCESS, ToolContext::mbcore, ToolContext::meshes, ToolContext::meshsets, ToolContext::meshType, ToolContext::outFilename, ToolContext::outputFormatter, moab::TempestRemapper::OVERLAP_FILES, moab::TempestRemapper::OVERLAP_MEMORY, moab::TempestRemapper::OVERLAP_MOAB, moab::Remapper::OverlapMesh, moab::DebugOutput::printf(), ToolContext::proc_id, moab::TempestRemapper::RLL, moab::IntxUtils::ScaleToRadius(), moab::TempestRemapper::SetMesh(), moab::TempestRemapper::SetMeshType(), moab::Remapper::SourceMesh, moab::Remapper::TargetMesh, ToolContext::timer_pop(), and ToolContext::timer_push().
Referenced by main().
std::string get_file_read_options | ( | ToolContext & | ctx, |
std::string | filename | ||
) |
Definition at line 417 of file mbtempest.cpp.
418 {
419 // if running in serial, blank option may suffice in general
420 std::string opts = "";
421 if( ctx.n_procs > 1 )
422 {
423 size_t lastindex = filename.find_last_of( "." );
424 std::string extension = filename.substr( lastindex + 1, filename.size() );
425 if( extension == "h5m" ) return "PARALLEL=READ_PART;PARTITION=PARALLEL_PARTITION;PARALLEL_RESOLVE_SHARED_ENTS;";
426 // "PARALLEL_GHOSTS=2.0.3;PARALLEL_THIN_GHOST_LAYER;";
427 else if( extension == "nc" )
428 {
429 // set default set of options
430 opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;";
431
432 if( ctx.proc_id == 0 )
433 {
434 {
435 NcFile ncFile( filename.c_str(), NcFile::ReadOnly );
436 if( !ncFile.is_valid() )
437 _EXCEPTION1( "Unable to open grid file \"%s\" for reading", filename.c_str() );
438
439 // Check for dimension names "grid_size", "grid_rank" and "grid_corners"
440 int iSCRIPFormat = 0, iMPASFormat = 0, iESMFFormat = 0;
441 for( int i = 0; i < ncFile.num_dims(); i++ )
442 {
443 std::string strDimName = ncFile.get_dim( i )->name();
444 if( strDimName == "grid_size" || strDimName == "grid_corners" || strDimName == "grid_rank" )
445 iSCRIPFormat++;
446 if( strDimName == "nodeCount" || strDimName == "elementCount" ||
447 strDimName == "maxNodePElement" )
448 iESMFFormat++;
449 if( strDimName == "nCells" || strDimName == "nEdges" || strDimName == "nVertices" ||
450 strDimName == "vertexDegree" )
451 iMPASFormat++;
452 }
453 ncFile.close();
454
455 if( iESMFFormat == 3 ) // Input from a NetCDF ESMF file
456 {
457 opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;PARALLEL_RESOLVE_SHARED_ENTS;VARIABLE=;";
458 }
459 if( iSCRIPFormat == 3 ) // Input from a NetCDF SCRIP file
460 {
461 opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;";
462 }
463 if( iMPASFormat == 4 ) // Input from a NetCDF SCRIP file
464 {
465 opts = "PARALLEL=READ_PART;PARTITION_METHOD=RCBZOLTAN;"
466 "PARALLEL_RESOLVE_SHARED_ENTS;NO_EDGES;NO_MIXED_ELEMENTS;VARIABLE=;";
467 }
468 }
469 }
470
471 #ifdef MOAB_HAVE_MPI
472 int line_size = opts.size();
473 MPI_Bcast( &line_size, 1, MPI_INT, 0, MPI_COMM_WORLD );
474 if( ctx.proc_id != 0 ) opts.resize( line_size );
475 MPI_Bcast( const_cast< char* >( opts.data() ), line_size, MPI_CHAR, 0, MPI_COMM_WORLD );
476 #endif
477 }
478 else
479 return "PARALLEL=BCAST_DELETE;PARTITION=TRIVIAL;PARALLEL_RESOLVE_SHARED_ENTS;";
480 }
481 return opts;
482 }
References ToolContext::n_procs, ncFile, and ToolContext::proc_id.
Referenced by CreateTempestMesh().
int main | ( | int | argc, |
char * | argv[] | ||
) |
Definition at line 484 of file mbtempest.cpp.
485 {
486 moab::ErrorCode rval;
487 NcError error( NcError::verbose_nonfatal );
488 std::stringstream sstr;
489 std::string historyStr;
490
491 int proc_id = 0, nprocs = 1;
492 #ifdef MOAB_HAVE_MPI
493 MPI_Init( &argc, &argv );
494 MPI_Comm_rank( MPI_COMM_WORLD, &proc_id );
495 MPI_Comm_size( MPI_COMM_WORLD, &nprocs );
496 #endif
497
498 moab::Core* mbCore = new( std::nothrow ) moab::Core;
499
500 if( nullptr == mbCore )
501 {
502 return 1;
503 }
504
505 // Build the history string
506 for( int ia = 0; ia < argc; ++ia )
507 historyStr += std::string( argv[ia] ) + " ";
508
509 ToolContext* runCtx;
510 #ifdef MOAB_HAVE_MPI
511 moab::ParallelComm* pcomm = new moab::ParallelComm( mbCore, MPI_COMM_WORLD, 0 );
512
513 runCtx = new ToolContext( mbCore, pcomm );
514 const char* writeOptions = ( nprocs > 1 ? "PARALLEL=WRITE_PART" : "" );
515 #else
516 runCtx = new ToolContext( mbCore );
517 const char* writeOptions = "";
518 #endif
519 runCtx->ParseCLOptions( argc, argv );
520
521 const double radius_src = 1.0 /*2.0*acos(-1.0)*/;
522 const double radius_dest = 1.0 /*2.0*acos(-1.0)*/;
523
524 moab::DebugOutput& outputFormatter = runCtx->outputFormatter;
525
526 #ifdef MOAB_HAVE_MPI
527 moab::TempestRemapper remapper( mbCore, pcomm );
528 #else
529 moab::TempestRemapper remapper( mbCore );
530 #endif
531 remapper.meshValidate = true;
532 remapper.constructEdgeMap = true;
533 remapper.initialize();
534
535 // Default area_method = lHuiller; Options: Girard, lHuiller, GaussQuadrature (if TR is available)
536 moab::IntxAreaUtils areaAdaptor( moab::IntxAreaUtils::GaussQuadrature );
537
538 Mesh* tempest_mesh = new Mesh();
539 rval = CreateTempestMesh( *runCtx, remapper, tempest_mesh );MB_CHK_ERR( rval );
540
541 if( runCtx->meshType == moab::TempestRemapper::OVERLAP_MEMORY )
542 {
543 // Compute intersections with MOAB
544 // For the overlap method, choose between: "fuzzy", "exact" or "mixed"
545 assert( runCtx->meshes.size() == 3 );
546
547 #ifdef MOAB_HAVE_MPI
548 rval = pcomm->check_all_shared_handles();MB_CHK_ERR( rval );
549 #endif
550
551 // Load the meshes and validate
552 rval = remapper.ConvertTempestMesh( moab::Remapper::SourceMesh );MB_CHK_ERR( rval );
553 rval = remapper.ConvertTempestMesh( moab::Remapper::TargetMesh );MB_CHK_ERR( rval );
554 rval = remapper.ConvertTempestMesh( moab::Remapper::OverlapMesh );MB_CHK_ERR( rval );
555 if( !runCtx->skip_io )
556 {
557 rval = mbCore->write_mesh( "tempest_intersection.h5m", &runCtx->meshsets[2], 1 );MB_CHK_ERR( rval );
558 }
559
560 // print verbosely about the problem setting
561 size_t velist[6], gvelist[6];
562 {
563 moab::Range rintxverts, rintxelems;
564 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 0, rintxverts );MB_CHK_ERR( rval );
565 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 2, rintxelems );MB_CHK_ERR( rval );
566 velist[0] = rintxverts.size();
567 velist[1] = rintxelems.size();
568
569 moab::Range bintxverts, bintxelems;
570 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, bintxverts );MB_CHK_ERR( rval );
571 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, bintxelems );MB_CHK_ERR( rval );
572 velist[2] = bintxverts.size();
573 velist[3] = bintxelems.size();
574 }
575
576 moab::EntityHandle intxset; // == remapper.GetMeshSet(moab::Remapper::OverlapMesh);
577
578 // Compute intersections with MOAB
579 {
580 // Create the intersection on the sphere object
581 runCtx->timer_push( "setup the intersector" );
582
583 moab::Intx2MeshOnSphere* mbintx = new moab::Intx2MeshOnSphere( mbCore );
584 mbintx->set_error_tolerance( runCtx->epsrel );
585 mbintx->set_box_error( runCtx->boxeps );
586 mbintx->set_radius_source_mesh( radius_src );
587 mbintx->set_radius_destination_mesh( radius_dest );
588 #ifdef MOAB_HAVE_MPI
589 mbintx->set_parallel_comm( pcomm );
590 #endif
591 rval = mbintx->FindMaxEdges( runCtx->meshsets[0], runCtx->meshsets[1] );MB_CHK_ERR( rval );
592
593 #ifdef MOAB_HAVE_MPI
594 moab::Range local_verts;
595 rval = mbintx->build_processor_euler_boxes( runCtx->meshsets[1], local_verts );MB_CHK_ERR( rval );
596
597 runCtx->timer_pop();
598
599 moab::EntityHandle covering_set;
600 runCtx->timer_push( "communicate the mesh" );
601 // TODO:: needs clarification
602 // we compute just intersection here, no need for extra ghost layers anyway
603 // ghost layers are needed in coverage for bilinear map, which does not actually need intersection
604 // this will be fixed in the future, bilinear map needs just coverage, not intersection
605 // so I am not passing the ghost layer here, even though there is an option in runCtx for a ghost layer
606 rval = mbintx->construct_covering_set( runCtx->meshsets[0], covering_set );MB_CHK_ERR( rval ); // lots of communication if mesh is distributed very differently
607 runCtx->timer_pop();
608
609 // print verbosely about the problem setting
610 {
611 moab::Range cintxverts, cintxelems;
612 rval = mbCore->get_entities_by_dimension( covering_set, 0, cintxverts );MB_CHK_ERR( rval );
613 rval = mbCore->get_entities_by_dimension( covering_set, 2, cintxelems );MB_CHK_ERR( rval );
614 velist[4] = cintxverts.size();
615 velist[5] = cintxelems.size();
616 }
617
618 MPI_Reduce( velist, gvelist, 6, MPI_UINT64_T, MPI_SUM, 0, MPI_COMM_WORLD );
619
620 #else
621 moab::EntityHandle covering_set = runCtx->meshsets[0];
622 for( int i = 0; i < 6; i++ )
623 gvelist[i] = velist[i];
624 #endif
625
626 if( !proc_id )
627 {
628 outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", gvelist[0],
629 gvelist[0] );
630 outputFormatter.printf( 0, "The covering set contains %lu vertices and %lu elements \n", gvelist[2],
631 gvelist[2] );
632 outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", gvelist[1],
633 gvelist[1] );
634 }
635
636 // Now let's invoke the MOAB intersection algorithm in parallel with a
637 // source and target mesh set representing two different decompositions
638 runCtx->timer_push( "compute intersections with MOAB" );
639 rval = mbCore->create_meshset( moab::MESHSET_SET, intxset );MB_CHK_SET_ERR( rval, "Can't create new set" );
640 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" );
641 runCtx->timer_pop();
642
643 // free the memory
644 delete mbintx;
645 }
646
647 {
648 moab::Range intxelems, intxverts;
649 rval = mbCore->get_entities_by_dimension( intxset, 2, intxelems );MB_CHK_ERR( rval );
650 rval = mbCore->get_entities_by_dimension( intxset, 0, intxverts, true );MB_CHK_ERR( rval );
651 outputFormatter.printf( 0, "The intersection set contains %lu elements and %lu vertices \n",
652 intxelems.size(), intxverts.size() );
653
654 moab::IntxAreaUtils areaAdaptorHuiller( moab::IntxAreaUtils::lHuiller ); // lHuiller, GaussQuadrature
655 double initial_sarea =
656 areaAdaptorHuiller.area_on_sphere( mbCore, runCtx->meshsets[0],
657 radius_src ); // use the target to compute the initial area
658 double initial_tarea =
659 areaAdaptorHuiller.area_on_sphere( mbCore, runCtx->meshsets[1],
660 radius_dest ); // use the target to compute the initial area
661 double intx_area = areaAdaptorHuiller.area_on_sphere( mbCore, intxset, radius_src );
662
663 outputFormatter.printf( 0, "mesh areas: source = %12.10f, target = %12.10f, intersection = %12.10f \n",
664 initial_sarea, initial_tarea, intx_area );
665 outputFormatter.printf( 0, "relative error w.r.t source = %12.10e, target = %12.10e \n",
666 fabs( intx_area - initial_sarea ) / initial_sarea,
667 fabs( intx_area - initial_tarea ) / initial_tarea );
668 }
669
670 // Write out our computed intersection file
671 if( !runCtx->skip_io )
672 {
673 rval = mbCore->write_mesh( "moab_intersection.h5m", &intxset, 1 );MB_CHK_ERR( rval );
674 }
675
676 if( runCtx->computeWeights )
677 {
678 runCtx->timer_push( "compute weights with the Tempest meshes" );
679 // Call to generate an offline map with the tempest meshes
680 OfflineMap weightMap;
681 int err = GenerateOfflineMapWithMeshes( *runCtx->meshes[0], *runCtx->meshes[1], *runCtx->meshes[2],
682 runCtx->disc_methods[0], // std::string strInputType
683 runCtx->disc_methods[1], // std::string strOutputType,
684 runCtx->mapOptions, weightMap );
685 runCtx->timer_pop();
686
687 std::map< std::string, std::string > mapAttributes;
688 if( err )
689 {
690 rval = moab::MB_FAILURE;
691 }
692 else
693 {
694 if( !runCtx->skip_io ) weightMap.Write( "outWeights.nc", mapAttributes );
695 }
696 }
697 }
698 else if( runCtx->meshType == moab::TempestRemapper::OVERLAP_MOAB )
699 {
700 // Usage: mpiexec -n 2 tools/mbtempest -t 5 -l mycs_2.h5m -l myico_2.h5m -f myoverlap_2.h5m
701 #ifdef MOAB_HAVE_MPI
702 rval = pcomm->check_all_shared_handles();MB_CHK_ERR( rval );
703 #endif
704
705 // print verbosely about the problem setting
706 size_t velist[4] = {}, gvelist[4] = {};
707 {
708 moab::Range srcverts, srcelems;
709 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 0, srcverts );MB_CHK_ERR( rval );
710 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[0], 2, srcelems );MB_CHK_ERR( rval );
711 rval = moab::IntxUtils::fix_degenerate_quads( mbCore, runCtx->meshsets[0] );MB_CHK_ERR( rval );
712 if( runCtx->enforceConvexity )
713 {
714 rval = moab::IntxUtils::enforce_convexity( mbCore, runCtx->meshsets[0], proc_id );MB_CHK_ERR( rval );
715 }
716 rval = areaAdaptor.positive_orientation( mbCore, runCtx->meshsets[0], radius_src );MB_CHK_ERR( rval );
717 // if( !proc_id )
718 // outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", srcverts.size(),
719 // srcelems.size() );
720 velist[0] = srcverts.size();
721 velist[1] = srcelems.size();
722
723 moab::Range tgtverts, tgtelems;
724 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 0, tgtverts );MB_CHK_ERR( rval );
725 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, tgtelems );MB_CHK_ERR( rval );
726 rval = moab::IntxUtils::fix_degenerate_quads( mbCore, runCtx->meshsets[1] );MB_CHK_ERR( rval );
727 if( runCtx->enforceConvexity )
728 {
729 rval = moab::IntxUtils::enforce_convexity( mbCore, runCtx->meshsets[1], proc_id );MB_CHK_ERR( rval );
730 }
731 rval = areaAdaptor.positive_orientation( mbCore, runCtx->meshsets[1], radius_dest );MB_CHK_ERR( rval );
732 // if( !proc_id )
733 // outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", tgtverts.size(),
734 // tgtelems.size() );
735 velist[2] = tgtverts.size();
736 velist[3] = tgtelems.size();
737 }
738 //rval = mbCore->write_file( "source_mesh.h5m", nullptr, writeOptions, &runCtx->meshsets[0], 1 );MB_CHK_ERR( rval );
739 //rval = mbCore->write_file( "target_mesh.h5m", nullptr, writeOptions, &runCtx->meshsets[1], 1 );MB_CHK_ERR( rval );
740
741 // if( runCtx->nlayers && nprocs > 1 )
742 // {
743 // remapper.ResetMeshSet( moab::Remapper::SourceMesh, runCtx->meshsets[3] );
744 // runCtx->meshes[0] = remapper.GetMesh( moab::Remapper::SourceMesh ); // ?
745 // }
746
747 // First compute the covering set such that the target elements are fully covered by the
748 // local source grid
749 runCtx->timer_push( "construct covering set for intersection" );
750 rval = remapper.ConstructCoveringSet( runCtx->epsrel, 1.0, 1.0, runCtx->boxeps, runCtx->rrmGrids,
751 runCtx->useGnomonicProjection, runCtx->nlayers );MB_CHK_ERR( rval );
752 runCtx->timer_pop();
753
754 #ifdef MOAB_HAVE_MPI
755 MPI_Reduce( velist, gvelist, 4, MPI_UINT64_T, MPI_SUM, 0, MPI_COMM_WORLD );
756 #else
757 for( int i = 0; i < 4; i++ )
758 gvelist[i] = velist[i];
759 #endif
760
761 if( !proc_id && runCtx->print_diagnostics )
762 {
763 outputFormatter.printf( 0, "The source set contains %lu vertices and %lu elements \n", gvelist[0],
764 gvelist[1] );
765 outputFormatter.printf( 0, "The target set contains %lu vertices and %lu elements \n", gvelist[2],
766 gvelist[3] );
767 }
768
769 // Compute intersections with MOAB with either the Kd-tree or the advancing front algorithm
770 runCtx->timer_push( "setup and compute mesh intersections" );
771 rval = remapper.ComputeOverlapMesh( runCtx->kdtreeSearch, false );MB_CHK_ERR( rval );
772 runCtx->timer_pop();
773
774 // print some diagnostic checks to see if the overlap grid resolved the input meshes
775 // correctly
776 double dTotalOverlapArea = 0.0;
777 if( runCtx->print_diagnostics )
778 {
779 moab::IntxAreaUtils areaAdaptorHuiller(
780 moab::IntxAreaUtils::GaussQuadrature ); // lHuiller, GaussQuadrature
781 double local_areas[3],
782 global_areas[3]; // Array for Initial area, and through Method 1 and Method 2
783 // local_areas[0] = area_on_sphere_lHuiller ( mbCore, runCtx->meshsets[1], radius_src );
784 local_areas[0] = areaAdaptorHuiller.area_on_sphere( mbCore, runCtx->meshsets[0], radius_src );
785 local_areas[1] = areaAdaptorHuiller.area_on_sphere( mbCore, runCtx->meshsets[1], radius_dest );
786 local_areas[2] = areaAdaptorHuiller.area_on_sphere( mbCore, runCtx->meshsets[2], radius_src );
787
788 #ifdef MOAB_HAVE_MPI
789 MPI_Allreduce( &local_areas[0], &global_areas[0], 3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD );
790 #else
791 global_areas[0] = local_areas[0];
792 global_areas[1] = local_areas[1];
793 global_areas[2] = local_areas[2];
794 #endif
795 if( !proc_id )
796 {
797 outputFormatter.printf( 0,
798 "initial area: source mesh = %12.14f, target mesh = "
799 "%12.14f, overlap mesh = %12.14f\n",
800 global_areas[0], global_areas[1], global_areas[2] );
801 // outputFormatter.printf ( 0, " area with l'Huiller: %12.14f with Girard:
802 // %12.14f\n", global_areas[2], global_areas[3] ); outputFormatter.printf ( 0, "
803 // relative difference areas = %12.10e\n", fabs ( global_areas[2] - global_areas[3]
804 // ) / global_areas[2] );
805 outputFormatter.printf( 0, "relative error w.r.t source = %12.14e, and target = %12.14e\n",
806 fabs( global_areas[0] - global_areas[2] ) / global_areas[0],
807 fabs( global_areas[1] - global_areas[2] ) / global_areas[1] );
808 }
809 dTotalOverlapArea = global_areas[2];
810 }
811
812 if( runCtx->intxFilename.size() )
813 {
814 moab::EntityHandle writableOverlapSet;
815 rval = mbCore->create_meshset( moab::MESHSET_SET, writableOverlapSet );MB_CHK_SET_ERR( rval, "Can't create new set" );
816 moab::EntityHandle meshOverlapSet = remapper.GetMeshSet( moab::Remapper::OverlapMesh );
817 moab::Range ovEnts;
818 rval = mbCore->get_entities_by_dimension( meshOverlapSet, 2, ovEnts );MB_CHK_SET_ERR( rval, "Can't create new set" );
819 rval = mbCore->get_entities_by_dimension( meshOverlapSet, 0, ovEnts );MB_CHK_SET_ERR( rval, "Can't create new set" );
820
821 #ifdef MOAB_HAVE_MPI
822 // Do not remove ghosted entities if we still haven't computed weights
823 // Remove ghosted entities from overlap set before writing the new mesh set to file
824 if( nprocs > 1 )
825 {
826 moab::Range ghostedEnts;
827 rval = remapper.GetOverlapAugmentedEntities( ghostedEnts );MB_CHK_ERR( rval );
828 ovEnts = moab::subtract( ovEnts, ghostedEnts );
829 #ifdef MOAB_DBG
830 if( !runCtx->skip_io )
831 {
832 std::stringstream filename;
833 filename << "aug_overlap" << runCtx->pcomm->rank() << ".h5m";
834 rval = runCtx->mbcore->write_file( filename.str().c_str(), 0, 0, &meshOverlapSet, 1 );MB_CHK_ERR( rval );
835 }
836 #endif
837 }
838 #endif
839 rval = mbCore->add_entities( writableOverlapSet, ovEnts );MB_CHK_SET_ERR( rval, "adding local intx cells failed" );
840
841 #ifdef MOAB_HAVE_MPI
842 #ifdef MOAB_DBG
843 if( nprocs > 1 && !runCtx->skip_io )
844 {
845 std::stringstream filename;
846 filename << "writable_intx_" << runCtx->pcomm->rank() << ".h5m";
847 rval = runCtx->mbcore->write_file( filename.str().c_str(), 0, 0, &writableOverlapSet, 1 );MB_CHK_ERR( rval );
848 }
849 #endif
850 #endif
851
852 size_t lastindex = runCtx->intxFilename.find_last_of( "." );
853 sstr.str( "" );
854 sstr << runCtx->intxFilename.substr( 0, lastindex ) << ".h5m";
855 if( !runCtx->proc_id )
856 std::cout << "Writing out the MOAB intersection mesh file to " << sstr.str() << std::endl;
857
858 // Write out our computed intersection file
859 if( !runCtx->skip_io )
860 {
861 rval = mbCore->write_file( sstr.str().c_str(), nullptr, writeOptions, &writableOverlapSet, 1 );MB_CHK_ERR( rval );
862 }
863 }
864
865 if( runCtx->computeWeights )
866 {
867 runCtx->meshes[2] = remapper.GetMesh( moab::Remapper::OverlapMesh );
868 if( !runCtx->proc_id ) std::cout << std::endl;
869
870 runCtx->timer_push( "setup computation of weights" );
871 // Call to generate the remapping weights with the tempest meshes
872 moab::TempestOnlineMap* weightMap = new moab::TempestOnlineMap( &remapper );
873 runCtx->timer_pop();
874
875 runCtx->timer_push( "compute weights with TempestRemap" );
876 rval = weightMap->GenerateRemappingWeights(
877 runCtx->disc_methods[0], // std::string strInputType
878 runCtx->disc_methods[1], // std::string strOutputType,
879 runCtx->mapOptions, // const GenerateOfflineMapAlgorithmOptions& options
880 runCtx->doftag_names[0], // const std::string& source_tag_name
881 runCtx->doftag_names[1] // const std::string& target_tag_name
882 );MB_CHK_ERR( rval );
883 runCtx->timer_pop();
884
885 weightMap->PrintMapStatistics();
886
887 // Invoke the CheckMap routine on the TempestRemap serial interface directly, if running
888 // on a single process
889 if( runCtx->fCheck )
890 {
891 const double dNormalTolerance = 1.0E-8;
892 const double dStrictTolerance = 1.0E-12;
893 weightMap->CheckMap( runCtx->fCheck, runCtx->fCheck, runCtx->fCheck && ( runCtx->ensureMonotonicity ),
894 dNormalTolerance, dStrictTolerance, dTotalOverlapArea );
895 }
896
897 if( runCtx->outFilename.size() )
898 {
899 std::map< std::string, std::string > attrMap;
900 attrMap["MOABversion"] = std::string( MOAB_VERSION );
901 attrMap["Title"] = "MOAB-TempestRemap (mbtempest) Offline Regridding Weight Generator";
902 attrMap["normalization"] = "ovarea";
903 attrMap["remap_options"] = runCtx->mapOptions.strMethod;
904 attrMap["domain_a"] = runCtx->inFilenames[0];
905 attrMap["domain_b"] = runCtx->inFilenames[1];
906 if( runCtx->intxFilename.size() ) attrMap["domain_aUb"] = runCtx->intxFilename;
907 attrMap["map_aPb"] = runCtx->outFilename;
908 attrMap["methodorder_a"] = runCtx->disc_methods[0] + ":" + std::to_string( runCtx->disc_orders[0] ) +
909 ":" + std::string( runCtx->doftag_names[0] );
910 attrMap["concave_a"] = runCtx->mapOptions.fSourceConcave ? "true" : "false";
911 attrMap["methodorder_b"] = runCtx->disc_methods[1] + ":" + std::to_string( runCtx->disc_orders[1] ) +
912 ":" + std::string( runCtx->doftag_names[1] );
913 attrMap["concave_b"] = runCtx->mapOptions.fTargetConcave ? "true" : "false";
914 attrMap["bubble"] = runCtx->mapOptions.fNoBubble ? "false" : "true";
915 attrMap["history"] = historyStr;
916
917 // Write the map file to disk in parallel using either HDF5 or SCRIP interface
918 // in extra case; maybe need a better solution, just create it with the right meshset
919 // from the beginning;
920 if( !runCtx->skip_io )
921 {
922 rval = weightMap->WriteParallelMap( runCtx->outFilename.c_str(), attrMap );MB_CHK_ERR( rval );
923 }
924 }
925
926 if( runCtx->verifyWeights )
927 {
928 // Let us pick a sampling test function for solution evaluation
929 // SH, SV, FH, USERVAR
930 bool userVariable = false;
931 moab::TempestOnlineMap::sample_function testFunction;
932 if( !runCtx->variableToVerify.compare( "SH" ) )
933 testFunction = &sample_slow_harmonic;
934 else if( !runCtx->variableToVerify.compare( "FH" ) )
935 testFunction = &sample_fast_harmonic;
936 else if( !runCtx->variableToVerify.compare( "SV" ) )
937 testFunction = &sample_stationary_vortex;
938 else
939 {
940 userVariable = runCtx->variableToVerify.size() ? true : false;
941 testFunction = runCtx->variableToVerify.size() ? nullptr : sample_stationary_vortex;
942 }
943
944 moab::Tag srcAnalyticalFunction;
945 moab::Tag tgtAnalyticalFunction;
946 moab::Tag tgtProjectedFunction;
947 if( testFunction )
948 {
949 runCtx->timer_push( "describe a solution on source grid" );
950 rval = weightMap->DefineAnalyticalSolution( srcAnalyticalFunction, "AnalyticalSolnSrcExact",
951 moab::Remapper::SourceMesh, testFunction );MB_CHK_ERR( rval );
952 runCtx->timer_pop();
953
954 runCtx->timer_push( "describe a solution on target grid" );
955
956 rval = weightMap->DefineAnalyticalSolution( tgtAnalyticalFunction, "AnalyticalSolnTgtExact",
957 moab::Remapper::TargetMesh, testFunction,
958 &tgtProjectedFunction, "ProjectedSolnTgt" );MB_CHK_ERR( rval );
959 // rval = mbCore->write_file ( "tgtWithSolnTag.h5m", nullptr, writeOptions,
960 // &runCtx->meshsets[1], 1 ); MB_CHK_ERR ( rval );
961 runCtx->timer_pop();
962 }
963 else
964 {
965 rval = mbCore->tag_get_handle( runCtx->variableToVerify.c_str(), srcAnalyticalFunction );MB_CHK_ERR( rval );
966
967 rval = mbCore->tag_get_handle( "ProjectedSolnTgt", 1, moab::MB_TYPE_DOUBLE, tgtProjectedFunction,
968 moab::MB_TAG_DENSE | moab::MB_TAG_CREAT );MB_CHK_ERR( rval );
969 }
970
971 if( !runCtx->skip_io )
972 {
973 rval = mbCore->write_file( "srcWithSolnTag.h5m", nullptr, writeOptions, &runCtx->meshsets[0], 1 );MB_CHK_ERR( rval );
974 }
975
976 runCtx->timer_push( "compute solution projection on target grid" );
977 rval = weightMap->ApplyWeights( srcAnalyticalFunction, tgtProjectedFunction, false, runCtx->cassType );MB_CHK_ERR( rval );
978 runCtx->timer_pop();
979
980 if( !runCtx->skip_io )
981 {
982 rval = mbCore->write_file( "tgtWithSolnTag2.h5m", nullptr, writeOptions, &runCtx->meshsets[1], 1 );MB_CHK_ERR( rval );
983 }
984
985 if( nprocs == 1 && runCtx->baselineFile.size() )
986 {
987 // save the field from tgtWithSolnTag2 in a text file, and global ids for cells
988 moab::Range tgtCells;
989 rval = mbCore->get_entities_by_dimension( runCtx->meshsets[1], 2, tgtCells );MB_CHK_ERR( rval );
990 std::vector< int > globIds;
991 globIds.resize( tgtCells.size() );
992 std::vector< double > vals;
993 vals.resize( tgtCells.size() );
994 moab::Tag projTag;
995 rval = mbCore->tag_get_handle( "ProjectedSolnTgt", projTag );MB_CHK_ERR( rval );
996 moab::Tag gid = mbCore->globalId_tag();
997 rval = mbCore->tag_get_data( gid, tgtCells, &globIds[0] );MB_CHK_ERR( rval );
998 rval = mbCore->tag_get_data( projTag, tgtCells, &vals[0] );MB_CHK_ERR( rval );
999 std::fstream fs;
1000 fs.open( runCtx->baselineFile.c_str(), std::fstream::out );
1001 fs << std::setprecision( 15 ); // maximum precision for doubles
1002 for( size_t i = 0; i < tgtCells.size(); i++ )
1003 fs << globIds[i] << " " << vals[i] << "\n";
1004 fs.close();
1005 // for good measure, save the source file too, with the tag AnalyticalSolnSrcExact
1006 // it will be used later to test, along with a target file
1007 if( !runCtx->skip_io )
1008 {
1009 rval = mbCore->write_file( "srcWithSolnTag.h5m", nullptr, writeOptions, &runCtx->meshsets[0], 1 );MB_CHK_ERR( rval );
1010 }
1011 }
1012
1013 // compute error metrics if it is a known analytical functional
1014 if( !userVariable )
1015 {
1016 runCtx->timer_push( "compute error metrics against analytical solution on target grid" );
1017 std::map< std::string, double > errMetrics;
1018 rval = weightMap->ComputeMetrics( moab::Remapper::TargetMesh, tgtAnalyticalFunction,
1019 tgtProjectedFunction, errMetrics, true );MB_CHK_ERR( rval );
1020 runCtx->timer_pop();
1021 }
1022 }
1023
1024 delete weightMap;
1025 }
1026 }
1027
1028 // Clean up
1029 remapper.clear();
1030 delete runCtx;
1031 delete mbCore;
1032
1033 #ifdef MOAB_HAVE_MPI
1034 MPI_Finalize();
1035 #endif
1036 exit( 0 );
1037 }
References moab::Core::add_entities(), moab::TempestOnlineMap::ApplyWeights(), moab::IntxAreaUtils::area_on_sphere(), ToolContext::baselineFile, 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(), ToolContext::disc_methods, ToolContext::disc_orders, ToolContext::doftag_names, moab::IntxUtils::enforce_convexity(), ToolContext::enforceConvexity, ToolContext::ensureMonotonicity, ToolContext::epsrel, error(), ErrorCode, ToolContext::fCheck, moab::Intx2Mesh::FindMaxEdges(), moab::IntxUtils::fix_degenerate_quads(), moab::IntxAreaUtils::GaussQuadrature, 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::Intx2Mesh::intersect_meshes(), ToolContext::intxFilename, ToolContext::kdtreeSearch, moab::IntxAreaUtils::lHuiller, ToolContext::mapOptions, MB_CHK_ERR, MB_CHK_SET_ERR, MB_TAG_CREAT, MB_TAG_DENSE, MB_TYPE_DOUBLE, ToolContext::mbcore, ToolContext::meshes, MESHSET_SET, ToolContext::meshsets, ToolContext::meshType, moab::TempestRemapper::meshValidate, MOAB_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, ToolContext::rrmGrids, 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_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::useGnomonicProjection, ToolContext::variableToVerify, ToolContext::verifyWeights, moab::Core::write_file(), moab::Core::write_mesh(), and moab::TempestOnlineMap::WriteParallelMap().
|
inline |
Definition at line 1220 of file mbtempest.cpp.
1221 {
1222 return 1.0;
1223 }
|
inline |
Definition at line 1214 of file mbtempest.cpp.
1215 {
1216 return ( 2.0 + pow( sin( 2.0 * dLat ), 16.0 ) * cos( 16.0 * dLon ) );
1217 // return (2.0 + pow(cos(2.0 * dLat), 16.0) * cos(16.0 * dLon));
1218 }
Referenced by main().
|
inline |
Definition at line 1209 of file mbtempest.cpp.
1210 {
1211 return ( 2.0 + cos( dLat ) * cos( dLat ) * cos( 2.0 * dLon ) );
1212 }
Referenced by main().
|
inline |
Find the rotated longitude and latitude of a point on a sphere with pole at (dLonC, dLatC).
Definition at line 1225 of file mbtempest.cpp.
1226 {
1227 const double dLon0 = 0.0;
1228 const double dLat0 = 0.6;
1229 const double dR0 = 3.0;
1230 const double dD = 5.0;
1231 const double dT = 6.0;
1232
1233 /// Find the rotated longitude and latitude of a point on a sphere
1234 /// with pole at (dLonC, dLatC).
1235 {
1236 double dSinC = sin( dLat0 );
1237 double dCosC = cos( dLat0 );
1238 double dCosT = cos( dLat );
1239 double dSinT = sin( dLat );
1240
1241 double dTrm = dCosT * cos( dLon - dLon0 );
1242 double dX = dSinC * dTrm - dCosC * dSinT;
1243 double dY = dCosT * sin( dLon - dLon0 );
1244 double dZ = dSinC * dSinT + dCosC * dTrm;
1245
1246 dLon = atan2( dY, dX );
1247 if( dLon < 0.0 )
1248 {
1249 dLon += 2.0 * M_PI;
1250 }
1251 dLat = asin( dZ );
1252 }
1253
1254 double dRho = dR0 * cos( dLat );
1255 double dVt = 3.0 * sqrt( 3.0 ) / 2.0 / cosh( dRho ) / cosh( dRho ) * tanh( dRho );
1256
1257 double dOmega;
1258 if( dRho == 0.0 )
1259 {
1260 dOmega = 0.0;
1261 }
1262 else
1263 {
1264 dOmega = dVt / dRho;
1265 }
1266
1267 return ( 1.0 - tanh( dRho / dD * sin( dLon - dOmega * dT ) ) );
1268 }
Referenced by main().