diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a5158286872..ff47644a2c5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated +## [2.36.0] - 2023-03-23 + +### Added + +- Added config array overload to `MAPL_GetResource` +- Implemented new generic XY grid factory to create regional grids on any input set of 2D lons and lats + - **NOTE**: This grid factory is experimental and the API may change or it might be superseded by another grid factory + +### Changed + +- Updated `components.yaml` to match GEOSgcm v10.25.1 + - ESMA_env v4.8.0 → v4.9.1 (Move to Baselibs 7.8.1: ESMF v8.4.1) + - ESMA_cmake v3.24.0 → v3.28.0 (Detection of additional sites, updated Intel Fortran flags, updates for Python3 support) +- Converted files in `Python/MAPL` to Python 3. + - **NOTE 1**: This will require changes to codes that call MAPL's Python layer. + - **NOTE 2**: If building with F2PY support, you will need to use ESMA_cmake v3.28.0 or later if using in a mixed Python 2/3 environment. + ## [2.35.3] - 2023-03-17 ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index 436b84ff8161..a43f9584b666 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy (SET CMP0054 NEW) project ( MAPL - VERSION 2.35.3 + VERSION 2.36.0 LANGUAGES Fortran CXX C) # Note - CXX is required for ESMF # Set the default build type to release diff --git a/MAPL_cfio/CMakeLists.txt b/MAPL_cfio/CMakeLists.txt index ac0ed5257387..a4fabb5cace4 100644 --- a/MAPL_cfio/CMakeLists.txt +++ b/MAPL_cfio/CMakeLists.txt @@ -57,9 +57,9 @@ endif () if (USE_F2PY) if (precision STREQUAL "r4") - find_package(F2PY2) - if (F2PY2_FOUND) - esma_add_f2py2_module(ShaveMantissa_ + find_package(F2PY3) + if (F2PY3_FOUND) + esma_add_f2py3_module(ShaveMantissa_ SOURCES ShaveMantissa_py.F90 ShaveMantissa.c DESTINATION lib/Python/${this} INCLUDEDIRS ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR}/lib ${include_${this}} diff --git a/MAPL_cfio/shavemantissa.py b/MAPL_cfio/shavemantissa.py index 318d363c13b3..497d10554877 100644 --- a/MAPL_cfio/shavemantissa.py +++ b/MAPL_cfio/shavemantissa.py @@ -30,7 +30,7 @@ def shave(a,xbits=12,has_undef=0,undef=MISSING,chunksize=-1): a_shaved, rc = shave32(a,xbits,has_undef,undef,chunksize) if rc: - raise ValueError, 'shave: error on return from ShaveMantissa_.shave32: %d'%rc + raise ValueError('shave: error on return from ShaveMantissa_.shave32: %d'%rc) return a_shaved diff --git a/Python/MAPL/__init__.py b/Python/MAPL/__init__.py index 329656a7e887..ad6e2d0f4afa 100644 --- a/Python/MAPL/__init__.py +++ b/Python/MAPL/__init__.py @@ -11,7 +11,7 @@ carried out by means of several *jobs* which are submitted through a queueing system such as PBS. -job +job This package defines the base class *Job* which inherits from *Exp*. A *job* carries out a portion of the *experiment*, itself consisting of several *run* segments. @@ -38,19 +38,10 @@ |----------------------- Experiment ------------------------| |------ Job 1 ------|------ Job 2 ------|------ Job 3 ------| |- Run 1 -|- Run 2 -|- Run 3 -|- Run 4 -|- Run 5 -|- Run 6 -| - + If each run segment is 2 weeks long, each job performs a 4 week integration, and the the whole experiment is about 3 month long. """ -__version__ = "0.1.2" - -from exp import * -from job import * -from run import * -from config import * -from history import * -from Date import * -from filelock import * - +__version__ = "1.0.0" diff --git a/Python/MAPL/config.py b/Python/MAPL/config.py index 36cedb329ab2..b92e39eef966 100644 --- a/Python/MAPL/config.py +++ b/Python/MAPL/config.py @@ -12,7 +12,7 @@ import sys from types import * from datetime import datetime - + class Config(object): def __init__(self,RcFiles,delim=':',Environ=True): @@ -23,7 +23,7 @@ def __init__(self,RcFiles,delim=':',Environ=True): the current value of environment variables. """ - if type(RcFiles) is StringType: + if isinstance(RcFiles, str): Files = ( RcFiles, ) # in case a single file is given else: Files = RcFiles # more often, a List/Tuple of RC files @@ -39,10 +39,10 @@ def __init__(self,RcFiles,delim=':',Environ=True): if value is not None: value = string.Template(value).safe_substitute(os.environ) if name: - self.Rc[name] = { 'value': value, - 'comment': comment, + self.Rc[name] = { 'value': value, + 'comment': comment, 'flag': 0} - + def __call__(self,name,value=None): """Either get or set a resource depending on whether *value* is given""" if value == None: @@ -50,11 +50,11 @@ def __call__(self,name,value=None): return self.Rc[name]['value'] else: if self.Rc.__contains__(name): - self.Rc[name]['value'] = value + self.Rc[name]['value'] = value self.Rc[name]['flag'] = 1 return self.Rc[name]['value'] return None - + get = __call__ def set(self,name,value): @@ -65,7 +65,7 @@ def save(self,rcfile=None): if rcfile is None: f = sys.stdout else: - f = open(rcfile,'w') + f = open(rcfile,'w') for line in self.Lines: line = line.rstrip() name, value, comment = _parseLine(line,self.delim) @@ -76,16 +76,16 @@ def save(self,rcfile=None): else: comment = '' value = self.Rc[name]['value'] - print >>f, name + self.delim+' ' + str(value) + comment # this line has been edited + print(name + self.delim+' ' + str(value) + comment, file=f) # this line has been edited else: - print >>f, line + print(line, file=f) else: - print >>f, line + print(line, file=f) f.close() - + def upd(self,dict): pass - + def interp(self,str,outFile=None,**kws): """ Use the resource values for $-substitution (a.k.a. @@ -108,7 +108,7 @@ def interpFile(self,template,outFile,**kws): for tmpl in Tmpl: Text.append(self.interpStr(tmpl,**kws)) open(outFile,"w").writelines(Text) - + def interpStr(self,template,strict=False): """ Replace occurences of resource variables in the @@ -149,21 +149,21 @@ def setenv(self,Only=None): Use resources to set environment variables. Option, one can provide a list of strings (*Only*) with those resources to be turned into environment variables. - """ + """ for name in self.Rc: if Only is None: os.environ[name] = self.Rc[name]['value'] elif name in Only: os.environ[name] = self.Rc[name]['value'] - + def keys(self): - """ + """ Return list of resource names. """ - return self.Rc.keys() + return list(self.Rc.keys()) def values(self): - """ + """ Return list of resource names. """ vals = [] @@ -216,7 +216,7 @@ def strTemplate(templ,expid=None,nymd=None,nhms=None, dtime --- python datetime - Unlike GrADS, notice that seconds are expanded using the %S2 token. + Unlike GrADS, notice that seconds are expanded using the %S2 token. Input date/time can be either strings or integers. Examples: @@ -229,9 +229,9 @@ def strTemplate(templ,expid=None,nymd=None,nhms=None, """ - MMM = ( 'jan', 'feb', 'mar', 'apr', 'may', 'jun', - 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' ) - + MMM = ( 'jan', 'feb', 'mar', 'apr', 'may', 'jun', + 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' ) + str_ = templ[:] if dtime is not None: @@ -254,24 +254,24 @@ def strTemplate(templ,expid=None,nymd=None,nhms=None, m = (nhms - h * 10000)/100 s = nhms - (10000*h + 100*m) - if expid is not None: + if expid is not None: str_ = str_.replace('%s',expid) - if yy is not None: + if yy is not None: y2 = yy%100 str_ = str_.replace('%y4',str(yy)) str_ = str_.replace('%y2',"%02d"%y2) - if mm is not None: + if mm is not None: mm = int(mm) mmm = MMM[mm-1] str_ = str_.replace('%m2',"%02d"%mm) str_ = str_.replace('%m3',mmm) - if dd is not None: + if dd is not None: str_ = str_.replace('%d2',"%02d"%int(dd)) - if h is not None: + if h is not None: str_ = str_.replace('%h2',"%02d"%int(h)) - if m is not None: + if m is not None: str_ = str_.replace('%n2',"%02d"%int(m)) - if s is not None: + if s is not None: str_ = str_.replace('%S2',"%02d"%int(s)) return str_ @@ -284,14 +284,14 @@ def strTemplate(templ,expid=None,nymd=None,nhms=None, def _ut_strTemplate(): - + templ = "%s.aer_f.eta.%m3%y2.%y4%m2%d2_%h2:%n2:%S2z.nc" expid = "e0054A" yy = "2008" mm = "10" dd = "30" - + h = "1" m = "30" s = "47" @@ -301,20 +301,20 @@ def _ut_strTemplate(): nymd = int(yy) * 10000 + int(mm)*100 + int(dd) nhms = int(h) * 10000 + int(m) * 100 + int(s) - print "Template: "+templ - print strTemplate(templ) - print strTemplate(templ,expid=expid) - print strTemplate(templ,expid=expid,yy=2008) - print strTemplate(templ,expid=expid,yy=2008,mm=mm) - print strTemplate(templ,expid=expid,yy=2008,mm=mm,dd=dd) - print strTemplate(templ,expid=expid,yy=2008,mm=mm,dd=dd,h=h) - print strTemplate(templ,expid=expid,yy=2008,mm=mm,dd=dd,h=h,m=m,s=s) - print strTemplate(templ,expid=expid,nymd=nymd) - print strTemplate(templ,expid=expid,nymd=nymd,nhms=nhms) - print strTemplate(templ,expid=expid,dtime=dtime) + print("Template: "+templ) + print(strTemplate(templ)) + print(strTemplate(templ,expid=expid)) + print(strTemplate(templ,expid=expid,yy=2008)) + print(strTemplate(templ,expid=expid,yy=2008,mm=mm)) + print(strTemplate(templ,expid=expid,yy=2008,mm=mm,dd=dd)) + print(strTemplate(templ,expid=expid,yy=2008,mm=mm,dd=dd,h=h)) + print(strTemplate(templ,expid=expid,yy=2008,mm=mm,dd=dd,h=h,m=m,s=s)) + print(strTemplate(templ,expid=expid,nymd=nymd)) + print(strTemplate(templ,expid=expid,nymd=nymd,nhms=nhms)) + print(strTemplate(templ,expid=expid,dtime=dtime)) if __name__ == "__main__": cf = Config('test.rc', delim=' = ') - + # _ut_strTemplate() diff --git a/README.md b/README.md index 968e076a6615..0a4b99fb104b 100644 --- a/README.md +++ b/README.md @@ -13,27 +13,27 @@ MAPL is a foundation layer of the GEOS architecture, whose original purpose is t MAPL has 10 primary subdirectories for Fortran source code: -1. shared - low level utilities that are used throughout the remainder of MAPL. -2. profiler - time and memory profiling utility -3. pfio - high-performance client-server I/O layer -5. base (formerly MAPL_Base) - legacy core of MAPL. This layer will gradually evaporate under further refactoring. -6. generic (under construction) - new home for MAPL extension of ESMF framework. -7. oomph - next gen generic will eventually disappear -8. gridcomps - Cap, History, and ExtData gridcomps used by all GEOS configurations. -9. MAPL_cfio - this is a deprecated lower-level I/O layer that is generally replaced by GMAO_pFIO. Not all of the strings have been cut yet. Sometime soon, this directory will be eliminated. -10. griddedio - layer between ESMF container and pfio library +1. **shared** - low level utilities that are used throughout the remainder of MAPL. +2. **profiler** - time and memory profiling utility +3. [**pfio**](https://github.com/GEOS-ESM/MAPL/tree/main/pfio) - high-performance client-server I/O layer +5. **base** (formerly MAPL_Base) - legacy core of MAPL. This layer will gradually evaporate under further refactoring. +6. **generic** (under construction) - new home for MAPL extension of ESMF framework. +7. **oomph** - next gen generic will eventually disappear +8. **gridcomps** - Cap, [History](https://github.com/GEOS-ESM/MAPL/tree/main/gridcomps/History), and [ExtData](https://github.com/GEOS-ESM/MAPL/tree/main/gridcomps/ExtData2G) gridcomps used by all GEOS configurations. +9. **MAPL_cfio** - this is a deprecated lower-level I/O layer that is generally replaced by GMAO_pFIO. Not all of the strings have been cut yet. Sometime soon, this directory will be eliminated. +10. **griddedio** - layer between ESMF container and pfio library MAPL also has a variety of other auxiliary directories: -1. include - include files used by external gridded components. -2. Apps - various Python and Perl scripts used by gridded components. -3. Python - beginnings of a run-time scripting framework for GEOS configurations -4. cmake - CMake build macros -5. MAPL_pFUnit - implements extensions of pFUnit unit testing framework that enable unit tests of grid comp run methods. This layer should eventually be migrated into pFUnit itself. -6. Tests - miscellaneous standalone drivers. -7. pflogger_stub - workaround for apps that wish to avoid a dependency on pFlogger -8. pfunit - pFUnit (unit testing framework) extensions for ESMF components +1. **include** - include files used by external gridded components. +2. **Apps** - various Python and Perl scripts used by gridded components. +3. **Python** - beginnings of a run-time scripting framework for GEOS configurations +4. **cmake** - CMake build macros +5. **MAPL_pFUnit** - implements extensions of pFUnit unit testing framework that enable unit tests of grid comp run methods. This layer should eventually be migrated into pFUnit itself. +6. **Tests** - miscellaneous standalone drivers. +7. **pflogger_stub** - workaround for apps that wish to avoid a dependency on pFlogger +8. **pfunit** - pFUnit (unit testing framework) extensions for ESMF components ## Contributing diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt index 4f281e674fa1..34658358230c 100644 --- a/base/CMakeLists.txt +++ b/base/CMakeLists.txt @@ -54,6 +54,7 @@ set (srcs MAPL_ISO8601_DateTime_ESMF.F90 FieldUtilities.F90 MAPL_Resource.F90 + MAPL_XYGridFactory.F90 # Orphaned program: should not be in this library. # tstqsat.F90 ) diff --git a/base/MAPL_GridManager.F90 b/base/MAPL_GridManager.F90 index 2181eb241b28..e3b0ac058717 100644 --- a/base/MAPL_GridManager.F90 +++ b/base/MAPL_GridManager.F90 @@ -120,6 +120,7 @@ subroutine initialize_prototypes(this, unusable, rc) use MAPL_TripolarGridFactoryMod, only: TripolarGridFactory use MAPL_LlcGridFactoryMod, only: LlcGridFactory use MAPL_ExternalGridFactoryMod, only: ExternalGridFactory + use MAPL_XYGridFactoryMod, only: XYGridFactory class (GridManager), intent(inout) :: this class (KeywordEnforcer), optional, intent(in) :: unusable @@ -131,6 +132,7 @@ subroutine initialize_prototypes(this, unusable, rc) type (TripolarGridFactory) :: tripolar_factory type (LlcGridFactory) :: llc_factory type (ExternalGridFactory) :: external_factory + type (XYGridFactory) :: xy_factory ! This is a local variable to prevent the subroutine from running ! initialiazation twice. Calling functions have their own local variables @@ -148,6 +150,7 @@ subroutine initialize_prototypes(this, unusable, rc) call this%prototypes%insert('Tripolar', tripolar_factory) call this%prototypes%insert('llc', llc_factory) call this%prototypes%insert('External', external_factory) + call this%prototypes%insert('XY', xy_factory) initialized = .true. end if diff --git a/base/MAPL_Resource.F90 b/base/MAPL_Resource.F90 index 219381acb477..d1f198659668 100644 --- a/base/MAPL_Resource.F90 +++ b/base/MAPL_Resource.F90 @@ -1,59 +1,130 @@ #include "MAPL_Exceptions.h" #include "MAPL_ErrLog.h" #include "unused_dummy.H" - !============================================================================= !FPP macros for repeated (type-dependent) code -#ifdef SET_VAL -# undef SET_VAL +#ifdef IO_SUCCESS +# undef IO_SUCCESS +#endif + +#define IO_SUCCESS 0 + +!============================================================================= + +#ifdef SET_VALUE +# undef SET_VALUE #endif -#define SET_VAL(T, VAL) \ -type is (T) ;\ - if (default_is_present .and. .not. label_is_present) then ;\ +#define SET_VALUE(T, VAL) \ +type is(T) ;\ + if (label_is_present) then ;\ + call ESMF_ConfigGetAttribute(config, VAL, label = actual_label, _RC) ;\ + else ;\ select type(default) ;\ type is(T) ;\ VAL = default ;\ class default ;\ - _FAIL("Type of 'default' does not match type of 'VAL'.") ;\ + _FAIL(MISMATCH_MESSAGE) ;\ end select ;\ - else ;\ - call ESMF_ConfigGetAttribute(config, VAL, label = actual_label, _RC) ;\ - end if + value_is_default = .TRUE. ;\ + end if ;\ + call set_do_print(actual_label, do_print) +!============================================================================= -#ifdef SET_VALS -# undef SET_VALS +#ifdef SET_ARRAY_VALUE +# undef SET_ARRAY_VALUE #endif -#define SET_VALS(T, VALS) \ -type is (T) ;\ - if (default_is_present .and. .not. label_is_present) then ;\ +#define SET_ARRAY_VALUE(T, VAL) \ +type is(T) ;\ + if (label_is_present) then ;\ + call ESMF_ConfigGetAttribute(config, valuelist = VAL, count = count, label = actual_label, _RC) ;\ + else ;\ select type(default) ;\ type is(T) ;\ - VALS = default ;\ + VAL = default ;\ class default ;\ - _FAIL("Type of 'default' does not match type of 'VALS'.") ;\ + _FAIL(MISMATCH_MESSAGE) ;\ end select ;\ - else ;\ - call ESMF_ConfigGetAttribute(config, valuelist = VALS, count = count, label = actual_label, _RC) ;\ - end if + value_is_default = .TRUE. ;\ + end if ;\ + call set_do_print(actual_label, do_print) + +!============================================================================= + +#ifdef MAKE_STRINGS +# undef MAKE_STRINGS +#endif + +#define MAKE_STRINGS(T, VAL, TSTR, SFMT) \ + if (label_is_present) then ;\ + if(default_is_present) then ;\ + select type(default) ;\ + type is(T) ;\ + value_is_default = are_equal(VAL, default) ;\ + class default ;\ + _FAIL(MISMATCH_MESSAGE) ;\ + end select ;\ + else ;\ + value_is_default = .FALSE. ;\ + end if ;\ + else ;\ + value_is_default = .TRUE. ;\ + end if ;\ + if (.not. (print_nondefault_only .and. value_is_default)) then ;\ + type_string = TSTR ;\ + type_format = SFMT ;\ + write(formatted_value, type_format, iostat=io_stat) VAL ;\ + _ASSERT((io_stat == IO_SUCCESS), 'Failure writing scalar formatted_value: ' // trim(actual_label)) ;\ + else ;\ + do_print = .FALSE. ;\ + end if -#ifdef SET_STRINGS -# undef SET_STRINGS +!============================================================================= + +#ifdef MAKE_ARRAY_STRINGS +# undef MAKE_ARRAY_STRINGS #endif -#define SET_STRINGS(T, TSTR, TFMT) \ -type is (T) ;\ - type_str = TSTR ;\ - val_str = intrinsic_to_string(val, TFMT) ;\ - if (present(default)) then ;\ - default_str = intrinsic_to_string(default, TFMT) ;\ - end if +#define MAKE_ARRAY_STRINGS(T, VAL, TSTR, SFMT) \ + if (label_is_present) then ;\ + if(default_is_present) then ;\ + select type(default) ;\ + type is(T) ;\ + value_is_default = all(are_equal(VAL, default)) ;\ + class default ;\ + _FAIL(MISMATCH_MESSAGE) ;\ + end select ;\ + else ;\ + value_is_default = .FALSE. ;\ + end if ;\ + else ;\ + value_is_default = .TRUE. ;\ + end if ;\ + if (.not. (print_nondefault_only .and. value_is_default)) then ;\ + type_string = TSTR ;\ + write(array_size_string, '(i2)', iostat=io_stat) size(VAL) ;\ + _ASSERT((io_stat == IO_SUCCESS), 'Failure writing array size string: ' // trim(actual_label)) ;\ + type_format = array_format(SFMT, array_size_string) ;\ + write(formatted_value, type_format, iostat=io_stat) VAL ;\ + _ASSERT((io_stat == IO_SUCCESS), 'Failure writing array formatted_value: ' // trim(actual_label)) ;\ + else ;\ + do_print = .FALSE. ;\ + end if !============================================================================= +#ifdef ARE_EQUAL_FUNCTION +# undef ARE_EQUAL_FUNCTION +#endif + +#define ARE_EQUAL_FUNCTION(T) (a, b) result(res) ; T, intent(in) :: a, b ; logical :: res ; res = (a == b) + +!============================================================================= +!END FPP macros for repeated (type-dependent) code +!============================================================================= module MAPL_ResourceMod @@ -75,15 +146,106 @@ module MAPL_ResourceMod use, intrinsic :: iso_fortran_env, only: REAL32, REAL64, int32, int64 ! !PUBLIC MEMBER FUNCTIONS: - implicit none private public MAPL_GetResource_config_scalar public MAPL_GetResource_config_array + character(len=*), parameter :: MISMATCH_MESSAGE = "Type of 'default' does not match type of 'value'." + + character(len=*), parameter :: FMT_INT32 = '(i0.1)' + character(len=*), parameter :: FMT_INT64 = '(i0.1)' + character(len=*), parameter :: FMT_REAL32 = '(f0.6)' + character(len=*), parameter :: FMT_REAL64 = '(f0.6)' + character(len=*), parameter :: FMT_LOGICAL= '(l1)' + + character(len=*), parameter :: TYPE_INT32 = "'Integer*4 '" + character(len=*), parameter :: TYPE_INT64 = "'Integer*8 '" + character(len=*), parameter :: TYPE_REAL32 = "'Real*4 '" + character(len=*), parameter :: TYPE_REAL64 = "'Real*8 '" + character(len=*), parameter :: TYPE_LOGICAL = "'Logical '" + character(len=*), parameter :: TYPE_CHARACTER = "'Character '" + + interface are_equal + module procedure :: are_equivalent + module procedure :: are_equal_int32 + module procedure :: are_equal_int64 + module procedure :: are_equal_real32 + module procedure :: are_equal_real64 + module procedure :: are_equal_character + end interface are_equal + contains + ! Set do_print & print_nondefault_only based on config and if default is present + ! Print only (do_print) only if printrc is 0 or 1 + ! Print only nondefault values if printrc == 0 and if default is present + subroutine get_print_settings(config, default_is_present, do_print, print_nondefault_only, rc) + type(ESMF_Config), intent(inout) :: config + logical, intent(in) :: default_is_present + logical, intent(out) :: do_print + logical, intent(out) :: print_nondefault_only + integer, optional, intent(out) :: rc + + integer, parameter :: PRINT_ALL = 1 + integer, parameter :: PRINT_DIFFERENT = 0 + + integer :: printrc + integer :: status + + if (MAPL_AM_I_Root()) then + call ESMF_ConfigGetAttribute(config, printrc, label = 'PRINTRC:', default = 0, _RC) + do_print = (printrc == PRINT_ALL) .or. (printrc == PRINT_DIFFERENT) + print_nondefault_only = (printrc == PRINT_DIFFERENT) .and. default_is_present + else + do_print = .FALSE. + end if + + end subroutine get_print_settings + + ! Check if vector contains string + logical function vector_contains_str(vector, string) + type(StringVector), intent(in) :: vector + character(len=*), intent(in) :: string + type(StringVectorIterator) :: iter + + iter = vector%begin() + + vector_contains_str = .false. + + do while (iter /= vector%end()) + if (trim(string) == iter%of()) then + vector_contains_str = .true. + return + end if + call iter%next() + end do + + end function vector_contains_str + + ! Check if resource has already been printed (vector contains label) or should be printed + subroutine set_do_print(label, do_print) + character(*), intent(in) :: label + logical, intent(inout) :: do_print + + type(StringVector), pointer, save :: already_printed_labels => null() + + if (do_print) then + if (.not. associated(already_printed_labels)) then + allocate(already_printed_labels) + end if + + ! Do not print label more than once + if (.not. vector_contains_str(already_printed_labels, trim(label))) then + call already_printed_labels%push_back(trim(label)) + else + do_print = .FALSE. + end if + end if + + end subroutine set_do_print + ! MAPL searches for labels with certain prefixes as well as just the label itself pure function get_labels_with_prefix(label, component_name) result(labels_with_prefix) character(len=*), intent(in) :: label @@ -136,6 +298,8 @@ subroutine get_actual_label(config, label, label_is_present, actual_label, unusa if (label_is_present) exit end do + if (.not. label_is_present) actual_label = trim(label) + _RETURN(_SUCCESS) end subroutine get_actual_label @@ -144,60 +308,108 @@ subroutine MAPL_GetResource_config_scalar(config, val, label, value_is_set, unus type(ESMF_Config), intent(inout) :: config class(*), intent(inout) :: val character(len=*), intent(in) :: label - logical , intent(out) :: value_is_set + logical, intent(out) :: value_is_set class(KeywordEnforcer), optional, intent(in) :: unusable class(*), optional, intent(in) :: default character(len=*), optional, intent(in) :: component_name integer, optional, intent(out) :: rc - integer :: status, printrc - logical :: default_is_present, label_is_present - character(len=:), allocatable :: label_to_print character(len=:), allocatable :: actual_label + character(len=:), allocatable :: type_format + character(len=:), allocatable :: type_string + character(len=ESMF_MAXSTR) :: formatted_value - _UNUSED_DUMMY(unusable) + logical :: default_is_present + logical :: label_is_present + logical :: print_nondefault_only + logical :: do_print + logical :: value_is_default - value_is_set = .FALSE. + integer :: io_stat + integer :: status + + _UNUSED_DUMMY(unusable) default_is_present = present(default) + ! these need to be initialized explitictly + value_is_set = .FALSE. + label_is_present = .FALSE. + print_nondefault_only = .FALSE. + do_print = .FALSE. + value_is_default = .FALSE. + if (default_is_present) then _ASSERT(same_type_as(val, default), "Value and default must have same type") end if call get_actual_label(config, label, label_is_present, actual_label, component_name = component_name, _RC) - ! No default and not in config, error - ! label or default must be present - if (.not. label_is_present .and. .not. default_is_present) then + if(.not. (label_is_present .or. default_is_present)) then + ! label or default must be present value_is_set = .FALSE. return end if + call get_print_settings(config, default_is_present, do_print, print_nondefault_only, _RC) + select type(val) - SET_VAL(integer(int32), val) - SET_VAL(integer(int64), val) - SET_VAL(real(real32), val) - SET_VAL(real(real64), val) - SET_VAL(character(len=*), val) - SET_VAL(logical, val) + + SET_VALUE(integer(int32), val) + if (do_print) then + MAKE_STRINGS(integer(int32), val, TYPE_INT32, FMT_INT32) + end if + + SET_VALUE(integer(int64), val) + if (do_print) then + MAKE_STRINGS(integer(int64), val, TYPE_INT64, FMT_INT64) + end if + + SET_VALUE(real(real32), val) + if (do_print) then + MAKE_STRINGS(real(real32), val, TYPE_REAL32, FMT_REAL32) + end if + + SET_VALUE(real(real64), val) + if (do_print) then + MAKE_STRINGS(real(real64), val, TYPE_REAL64, FMT_REAL64) + end if + + SET_VALUE(logical, val) + if (do_print) then + MAKE_STRINGS(logical, val, TYPE_LOGICAL, FMT_LOGICAL) + end if + + SET_VALUE(character(len=*), val) + ! character value can't use the MAKE_STRINGS macro (formatted differently) + if (do_print) then + if (label_is_present) then + if(default_is_present) then + select type(default) + type is(character(len=*)) + value_is_default = (trim(val) == trim(default)) + class default + _FAIL(MISMATCH_MESSAGE) + end select + else + value_is_default = .FALSE. + end if + end if + if (.not. (print_nondefault_only .and. value_is_default)) then + type_string = TYPE_CHARACTER + formatted_value = trim(val) + else + do_print = .FALSE. + end if + end if + class default - _FAIL( "Unupported type") + _FAIL( "Unsupported type") end select - value_is_set = .TRUE. - - call ESMF_ConfigGetAttribute(config, printrc, label = 'PRINTRC:', default = 0, _RC) + if(do_print) call print_resource(type_string, actual_label, formatted_value, value_is_default, _RC) - ! Can set printrc to negative to not print at all - if (MAPL_AM_I_Root() .and. printrc >= 0) then - if (label_is_present) then - label_to_print = actual_label - else - label_to_print = trim(label) - end if - call print_resource(printrc, label_to_print, val, default=default, _RC) - end if + value_is_set = .TRUE. _RETURN(ESMF_SUCCESS) @@ -213,148 +425,197 @@ subroutine MAPL_GetResource_config_array(config, vals, label, value_is_set, unus class(*), optional, intent(in) :: default(:) character(len=*), optional, intent(in) :: component_name integer, optional, intent(out) :: rc + character(len=2) :: array_size_string + ! We assume we'll never have more than 99 values, hence len=2 character(len=:), allocatable :: actual_label - integer :: status, count - logical :: label_is_present, default_is_present + character(len=:), allocatable :: type_format + character(len=:), allocatable :: type_string + character(len=ESMF_MAXSTR) :: formatted_value + + logical :: default_is_present + logical :: label_is_present + logical :: print_nondefault_only + logical :: do_print + logical :: value_is_default + integer :: count + + integer :: io_stat + integer :: status _UNUSED_DUMMY(unusable) - value_is_set = .FALSE. - default_is_present = present(default) + ! these need to be initialized explitictly + value_is_set = .FALSE. + label_is_present = .FALSE. + print_nondefault_only = .FALSE. + do_print = .FALSE. + value_is_default = .FALSE. + if (default_is_present) then _ASSERT(same_type_as(vals, default), "Value and default must have same type") end if - _ASSERT(present(component_name), "Component name is necessary but not present.") call get_actual_label(config, label, label_is_present, actual_label, component_name = component_name, _RC) - ! No default and not in config, error ! label or default must be present if (.not. label_is_present .and. .not. default_is_present) then value_is_set = .FALSE. return end if + ! only print if root + call get_print_settings(config, default_is_present, do_print, print_nondefault_only, _RC) + count = size(vals) select type(vals) - SET_VALS(integer(int32), vals) - SET_VALS(integer(int64), vals) - SET_VALS(real(real32), vals) - SET_VALS(real(real64), vals) - SET_VALS(character(len=*), vals) - SET_VALS(logical, vals) + + SET_ARRAY_VALUE(integer(int32), vals) + if (do_print) then + MAKE_ARRAY_STRINGS(integer(int32), vals, TYPE_INT32, FMT_INT32) + end if + + SET_ARRAY_VALUE(integer(int64), vals) + if (do_print) then + MAKE_ARRAY_STRINGS(integer(int64), vals, TYPE_INT64, FMT_INT64) + end if + + SET_ARRAY_VALUE(real(real32), vals) + if (do_print) then + MAKE_ARRAY_STRINGS(real(int32), vals, TYPE_REAL32, FMT_REAL32) + end if + + SET_ARRAY_VALUE(real(real64), vals) + if (do_print) then + MAKE_ARRAY_STRINGS(real(int64), vals, TYPE_REAL64, FMT_REAL64) + end if + + SET_ARRAY_VALUE(logical, vals) + if (do_print) then + MAKE_ARRAY_STRINGS(logical, vals, TYPE_LOGICAL, FMT_LOGICAL) + end if + + SET_ARRAY_VALUE(character(len=*), vals) + if (do_print) then + if (label_is_present) then + if(default_is_present) then + select type(default) + type is(character(len=*)) + value_is_default = compare_all(vals, default) + class default + _FAIL(MISMATCH_MESSAGE) + end select + else + value_is_default = .FALSE. + end if + end if + if (.not. (print_nondefault_only .and. value_is_default)) then + type_string = TYPE_CHARACTER + write(array_size_string, '(i2)', iostat=io_stat) size(vals) + _ASSERT((io_stat == IO_SUCCESS), 'Failure writing array size string: ' // trim(actual_label)) + type_format = string_array_format(array_size_string) + write(formatted_value, type_format, iostat=io_stat) vals + _ASSERT((io_stat == IO_SUCCESS), 'Failure writing array formatted_value: ' // trim(actual_label)) + else + do_print = .FALSE. + end if + end if + class default _FAIL( "Unsupported type") end select + if(do_print) call print_resource(type_string, actual_label, formatted_value, value_is_default, _RC) + value_is_set = .TRUE. _RETURN(ESMF_SUCCESS) end subroutine MAPL_GetResource_config_array - ! Print the resource value according to the value of printrc - ! printrc = 0 - Only print non-default values - ! printrc = 1 - Print all values - subroutine print_resource(printrc, label, val, default, rc) - integer, intent(in) :: printrc + ! Print the resource value + subroutine print_resource(type_string, label, formatted_value, value_is_default, rc) + character(len=*), intent(in) :: type_string character(len=*), intent(in) :: label - class(*), intent(in) :: val - class(*), optional, intent(in) :: default + character(len=*), intent(in) :: formatted_value + logical, intent(in) :: value_is_default integer, optional, intent(out) :: rc - character(len=:), allocatable :: val_str, default_str, output_format, type_str, type_format - type(StringVector), pointer, save :: already_printed_labels => null() - integer :: status - - if (.not. associated(already_printed_labels)) then - allocate(already_printed_labels) - end if - - ! Do not print label more than once - if (.not. vector_contains_str(already_printed_labels, trim(label))) then - call already_printed_labels%push_back(trim(label)) - else - return - end if + character(len=*), parameter :: DEFAULT_ = ", (default value)" + character(len=*), parameter :: NONDEFAULT_ = '' + character(len=:), allocatable :: output_format + character(len=:), allocatable :: trailer + character(len=:), allocatable :: value_out - select type(val) - SET_STRINGS(integer(int32), "'Integer*4 '", '(i0.1)') - SET_STRINGS(integer(int64), "'Integer*8 '", '(i0.1)') - SET_STRINGS(real(real32), "'Real*4 '" , '(f0.6)') - SET_STRINGS(real(real64), "'Real*8 '" , '(f0.6)') - SET_STRINGS(logical, "'Logical '" , '(l1)' ) - SET_STRINGS(character(len=*),"'Character '", '(a)') - class default - _FAIL("Unsupported type") - end select + trailer = NONDEFAULT_ + if (value_is_default) trailer = DEFAULT_ - output_format = "(1x, " // type_str // ", 'Resource Parameter: '" // ", a"// ", a)a" + output_format = "(1x, " // type_string // ", 'Resource Parameter: '" // ", a"// ", a)" + value_out = trim(formatted_value) // trim(trailer) - ! printrc = 0 - Only print non-default values - ! printrc = 1 - Print all values - if (present(default)) then - if (trim(val_str) /= trim(default_str) .or. printrc == 1) then - print output_format, trim(label), trim(val_str) - end if - else - print output_format, trim(label), trim(val_str) - end if + print output_format, trim(label), value_out _RETURN(_SUCCESS) end subroutine print_resource - ! Check if vector contains string - logical function vector_contains_str(vector, string) - type(StringVector), intent(in) :: vector - character(len=*), intent(in) :: string - type(StringVectorIterator) :: iter + ! Create array format string from scalar format string + pure function array_format(scalar_format, array_size_string) + character(len=*), intent(in) :: scalar_format + character(len=*), intent(in) :: array_size_string + character(len=:), allocatable :: array_format - iter = vector%begin() + array_format = '('//trim(adjustl(array_size_string))//scalar_format(1:len_trim(scalar_format)-1)//',1X))' - vector_contains_str = .false. + end function array_format - do while (iter /= vector%end()) - if (trim(string) == iter%of()) then - vector_contains_str = .true. - return - end if - call iter%next() - end do + ! Create format string for array of strings + pure function string_array_format(array_size_string) + character(len=*), intent(in) :: array_size_string + character(len=:), allocatable :: string_array_format + character(len=:), allocatable :: N - end function vector_contains_str + ! N specifies the number of repeats in the format string. + N = trim(adjustl(array_size_string)) + string_array_format = '('//N//'(""a"",1X))' - ! Convert val to string according to str_format - function intrinsic_to_string(val, str_format, rc) result(formatted_str) - class(*), intent(in) :: val - character(len=*), intent(in) :: str_format - character(len=256) :: formatted_str - integer, optional, intent(out) :: rc + end function string_array_format + + ! Compare all the strings in two string arrays + pure function compare_all(astrings, bstrings) + character(len=*), dimension(:), intent(in) :: astrings + character(len=*), dimension(:), intent(in) :: bstrings + logical :: compare_all - select type(val) - type is(integer(int32)) - write(formatted_str, str_format) val - type is(integer(int64)) - write(formatted_str, str_format) val - type is(real(real32)) - write(formatted_str, str_format) val - type is(real(real64)) - write(formatted_str, str_format) val - type is(logical) - write(formatted_str, str_format) val - type is(character(len=*)) - formatted_str = trim(val) - class default - _FAIL( "Unsupported type in intrinsic_to_string") - end select + integer :: i - _RETURN(_SUCCESS) + compare_all = (size(astrings) == size(bstrings)) - end function intrinsic_to_string + do i=1, size(astrings) + if(.not. compare_all) exit + compare_all = (trim(astrings(i)) == trim(bstrings(i))) + end do + + end function compare_all + + ! Test if two logicals are equivalent + ! Basically a wrapper function for use in are_equal generic function + pure elemental function are_equivalent(a, b) result(res) + logical, intent(in) :: a + logical, intent(in) :: b + logical :: res + res = a .eqv. b + end function are_equivalent + + ! These are specific functions for the are_equal generic function. + ! Basically wrapper functions for the == binary relational operator + pure elemental function are_equal_int32 ARE_EQUAL_FUNCTION(integer(int32)) ; end function are_equal_int32 + pure elemental function are_equal_int64 ARE_EQUAL_FUNCTION(integer(int64)) ; end function are_equal_int64 + pure elemental function are_equal_real32 ARE_EQUAL_FUNCTION(real(real32)) ; end function are_equal_real32 + pure elemental function are_equal_real64 ARE_EQUAL_FUNCTION(real(real64)) ; end function are_equal_real64 + pure elemental function are_equal_character ARE_EQUAL_FUNCTION(character(len=*)) ; end function are_equal_character end module MAPL_ResourceMod diff --git a/base/MAPL_XYGridFactory.F90 b/base/MAPL_XYGridFactory.F90 new file mode 100644 index 000000000000..c4af2f496494 --- /dev/null +++ b/base/MAPL_XYGridFactory.F90 @@ -0,0 +1,883 @@ +#include "MAPL_Exceptions.h" +#include "MAPL_ErrLog.h" +#include "unused_dummy.H" + +module MAPL_XYGridFactoryMod + use MAPL_AbstractGridFactoryMod + use MAPL_KeywordEnforcerMod + use MAPL_ExceptionHandling + use MAPL_ShmemMod + use MAPL_Constants + use ESMF + use pFIO + use NetCDF + use, intrinsic :: iso_fortran_env, only: REAL32 + use, intrinsic :: iso_fortran_env, only: REAL64 + implicit none + private + + public :: XYGridFactory + + integer, parameter :: NUM_DIM = 2 + + type, extends(AbstractGridFactory) :: XYGridFactory + private + character(len=:), allocatable :: grid_file_name + character(len=:), allocatable :: grid_name + ! Grid dimensions + integer :: im_world = MAPL_UNDEFINED_INTEGER + integer :: jm_world = MAPL_UNDEFINED_INTEGER + integer :: lm + ! Domain decomposition: + integer :: nx = MAPL_UNDEFINED_INTEGER + integer :: ny = MAPL_UNDEFINED_INTEGER + integer, allocatable :: ims(:) + integer, allocatable :: jms(:) + logical :: has_corners + + logical :: initialized_from_metadata = .false. + contains + procedure :: make_new_grid + procedure :: create_basic_grid + procedure :: add_horz_coordinates_from_file + procedure :: init_halo + procedure :: halo + + + procedure :: initialize_from_file_metadata + procedure :: initialize_from_config_with_prefix + procedure :: initialize_from_esmf_distGrid + + procedure :: equals + + procedure :: check_and_fill_consistency + procedure :: generate_grid_name + procedure :: to_string + + procedure :: append_metadata + procedure :: get_grid_vars + procedure :: get_file_format_vars + procedure :: append_variable_metadata + procedure :: generate_file_bounds + procedure :: generate_file_corner_bounds + procedure :: generate_file_reference2D + procedure :: generate_file_reference3D + procedure :: decomps_are_equal + procedure :: physical_params_are_equal + procedure :: file_has_corners + end type XYGridFactory + + character(len=*), parameter :: MOD_NAME = 'MAPL_XYGridFactory::' + + interface XYGridFactory + module procedure XYGridFactory_from_parameters + end interface XYGridFactory + + interface set_with_default + module procedure set_with_default_integer + module procedure set_with_default_character + end interface set_with_default + + +contains + + function XYGridFactory_from_parameters(unusable, grid_file_name, grid_name, & + & im_world,jm_world,lm,nx, ny, rc) result(factory) + type (XYGridFactory) :: factory + class (KeywordEnforcer), optional, intent(in) :: unusable + + ! grid details: + character(len=*), intent(in) :: grid_file_name ! required + character(len=*), optional, intent(in) :: grid_name + integer, optional, intent(in) :: im_world + integer, optional, intent(in) :: jm_world + integer, optional, intent(in) :: lm + + ! decomposition: + integer, optional, intent(in) :: nx + integer, optional, intent(in) :: ny + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME // 'XYGridFactory_from_parameters' + + + if (present(unusable)) print*,shape(unusable) + + call set_with_default(factory%grid_name, grid_name, MAPL_GRID_NAME_DEFAULT) + call set_with_default(factory%grid_file_name, grid_file_name, MAPL_GRID_FILE_NAME_DEFAULT) + + call set_with_default(factory%ny, nx, MAPL_UNDEFINED_INTEGER) + call set_with_default(factory%nx, ny, MAPL_UNDEFINED_INTEGER) + call set_with_default(factory%im_world, im_world, MAPL_UNDEFINED_INTEGER) + call set_with_default(factory%jm_world, jm_world, MAPL_UNDEFINED_INTEGER) + call set_with_default(factory%lm, lm, MAPL_UNDEFINED_INTEGER) + + + + + call factory%check_and_fill_consistency(rc=status) + _VERIFY(status) + + _RETURN(_SUCCESS) + + end function XYGridFactory_from_parameters + + + function make_new_grid(this, unusable, rc) result(grid) + type (ESMF_Grid) :: grid + class (XYGridFactory), intent(in) :: this + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME // 'make_geos_grid' + + _UNUSED_DUMMY(unusable) + + grid = this%create_basic_grid(rc=status) + _VERIFY(status) + + call this%add_horz_coordinates_from_file(grid, rc=status) + _VERIFY(status) + + _RETURN(_SUCCESS) + + end function make_new_grid + + + + function create_basic_grid(this, unusable, rc) result(grid) + type (ESMF_Grid) :: grid + class (XYGridFactory), intent(in) :: this + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME // 'create_basic_grid' + + _UNUSED_DUMMY(unusable) + + grid = ESMF_GridCreateNoPeriDim( & + name=trim(this%grid_name) ,& + countsPerDEDim1=this%ims, & + countsPerDEDim2=this%jms, & + indexFlag=ESMF_INDEX_DELOCAL, & + gridEdgeLWidth=[0,0], & + gridEdgeUWidth=[0,1], & + coordDep1=[1,2], & + coordDep2=[1,2], & + coordSys=ESMF_COORDSYS_SPH_RAD, rc=status) + _VERIFY(status) + + ! Allocate coords at default stagger location + call ESMF_GridAddCoord(grid, rc=status) + _VERIFY(status) + if (this%has_corners) then + call ESMF_GridAddCoord(grid, staggerloc=ESMF_STAGGERLOC_CORNER, rc=status) + end if + + _VERIFY(status) + + if (this%lm /= MAPL_UNDEFINED_INTEGER) then + call ESMF_AttributeSet(grid, name='GRID_LM', value=this%lm, rc=status) + _VERIFY(status) + end if + + call ESMF_AttributeSet(grid, 'GridType', 'XY', rc=status) + _VERIFY(status) + + _RETURN(_SUCCESS) + end function create_basic_grid + + subroutine add_horz_coordinates_from_file(this, grid, unusable, rc) + use MAPL_BaseMod, only: MAPL_grid_interior, MAPL_gridget + use MAPL_CommsMod + use MAPL_IOMod + use MAPL_Constants + class (XYGridFactory), intent(in) :: this + type (ESMF_Grid), intent(inout) :: grid + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME // 'add_horz_coordinates' + + integer :: i_1,i_n,j_1,j_n, ncid, varid + integer :: ic_1,ic_n,jc_1,jc_n ! regional corner bounds + real, pointer :: centers(:,:), corners(:,:) + real(ESMF_KIND_R8), pointer :: fptr(:,:) + + integer :: IM, JM + integer :: IM_WORLD, JM_WORLD + integer :: COUNTS(3), DIMS(3) + character(len=:), allocatable :: lon_center_name, lat_center_name, lon_corner_name, lat_corner_name + + _UNUSED_DUMMY(unusable) + + + lon_center_name = "lons" + lat_center_name = "lats" + lon_corner_name = "corner_lons" + lat_corner_name = "corner_lats" + call MAPL_GridGet(GRID, localCellCountPerDim=COUNTS, globalCellCountPerDim=DIMS, RC=STATUS) + _VERIFY(STATUS) + IM = COUNTS(1) + JM = COUNTS(2) + IM_WORLD = DIMS(1) + JM_WORLD = DIMS(2) + call MAPL_Grid_Interior(grid, i_1, i_n, j_1, j_n) + + ic_1=i_1 + ic_n=i_n + + jc_1=j_1 + if (j_n == this%jm_world) then + jc_n=j_n+1 + else + jc_n=j_n + end if + + if (MAPL_AmNodeRoot .or. (.not. MAPL_ShmInitialized)) then + status = nf90_open(this%grid_file_name,NF90_NOWRITE,ncid) + _VERIFY(status) + end if + + call MAPL_AllocateShared(centers,[im_world,jm_world],transroot=.true.,_RC) + + call MAPL_SyncSharedMemory(_RC) + + ! do longitudes + if (MAPL_AmNodeRoot .or. (.not. MAPL_ShmInitialized)) then + status = nf90_inq_varid(ncid,lon_center_name,varid) + _VERIFY(status) + status = nf90_get_var(ncid,varid,centers) + _VERIFY(status) + centers=centers*MAPL_DEGREES_TO_RADIANS_R8 + end if + call MAPL_SyncSharedMemory(_RC) + + call ESMF_GridGetCoord(grid, coordDim=1, localDE=0, & + staggerloc=ESMF_STAGGERLOC_CENTER, & + farrayPtr=fptr, rc=status) + fptr=centers(i_1:i_n,j_1:j_n) + ! do latitudes + + call MAPL_SyncSharedMemory(_RC) + if (MAPL_AmNodeRoot .or. (.not. MAPL_ShmInitialized)) then + status = nf90_inq_varid(ncid,lat_center_name,varid) + _VERIFY(status) + status = nf90_get_var(ncid,varid,centers) + _VERIFY(status) + centers=centers*MAPL_DEGREES_TO_RADIANS_R8 + end if + call MAPL_SyncSharedMemory(_RC) + + call ESMF_GridGetCoord(grid, coordDim=2, localDE=0, & + staggerloc=ESMF_STAGGERLOC_CENTER, & + farrayPtr=fptr, rc=status) + fptr=centers(i_1:i_n,j_1:j_n) + + call MAPL_SyncSharedMemory(_RC) + if(MAPL_ShmInitialized) then + call MAPL_DeAllocNodeArray(centers,_RC) + else + deallocate(centers) + end if + !! now repeat for corners + if (this%has_corners) then + call MAPL_AllocateShared(corners,[im_world+1,jm_world+1],transroot=.true.,_RC) + + ! do longitudes + + call MAPL_SyncSharedMemory(_RC) + if (MAPL_AmNodeRoot .or. (.not. MAPL_ShmInitialized)) then + status = nf90_inq_varid(ncid,lon_corner_name,varid) + _VERIFY(status) + status = nf90_get_var(ncid,varid,corners) + _VERIFY(status) + corners=corners*MAPL_DEGREES_TO_RADIANS_R8 + end if + call MAPL_SyncSharedMemory(_RC) + + call ESMF_GridGetCoord(grid, coordDim=1, localDE=0, & + staggerloc=ESMF_STAGGERLOC_CORNER, & + farrayPtr=fptr, _RC) + fptr=corners(ic_1:ic_n,jc_1:jc_n) + ! do latitudes + + call MAPL_SyncSharedMemory(_RC) + if (MAPL_AmNodeRoot .or. (.not. MAPL_ShmInitialized)) then + status = nf90_inq_varid(ncid,lat_corner_name,varid) + _VERIFY(status) + status = nf90_get_var(ncid,varid,corners) + _VERIFY(status) + corners=corners*MAPL_DEGREES_TO_RADIANS_R8 + end if + call MAPL_SyncSharedMemory(_RC) + + call ESMF_GridGetCoord(grid, coordDim=2, localDE=0, & + staggerloc=ESMF_STAGGERLOC_CORNER, & + farrayPtr=fptr, _RC) + fptr=corners(ic_1:ic_n,jc_1:jc_n) + + call MAPL_SyncSharedMemory(_RC) + if(MAPL_ShmInitialized) then + call MAPL_DeAllocNodeArray(corners,_RC) + else + deallocate(corners) + end if + + end if + if (MAPL_AmNodeRoot .or. (.not. MAPL_ShmInitialized)) then + status=nf90_close(ncid) + _VERIFY(status) + end if + + _RETURN(_SUCCESS) + + end subroutine add_horz_coordinates_from_file + + subroutine initialize_from_file_metadata(this, file_metadata, unusable, force_file_coordinates, rc) + use MAPL_KeywordEnforcerMod + use MAPL_BaseMod, only: MAPL_DecomposeDim + class (XYGridFactory), intent(inout) :: this + type (FileMetadata), target, intent(in) :: file_metadata + class (KeywordEnforcer), optional, intent(in) :: unusable + logical, optional, intent(in) :: force_file_coordinates + integer, optional, intent(out) :: rc + + integer :: ncid,varid,status + + this%im_world = file_metadata%get_dimension('Xdim',_RC) + this%jm_world = file_Metadata%get_dimension('Ydim',_RC) + if (file_metadata%has_dimension('lev')) then + this%lm = file_metadata%get_dimension('lev',_RC) + end if + + this%grid_file_name=file_metadata%get_source_file() + + this%initialized_from_metadata = .true. + call this%make_arbitrary_decomposition(this%nx, this%ny, rc=status) + _VERIFY(status) + + ! Determine IMS and JMS with constraint for ESMF that each DE has at least an extent + ! of 2. Required for ESMF_FieldRegrid(). + allocate(this%ims(0:this%nx-1)) + allocate(this%jms(0:this%ny-1)) + call MAPL_DecomposeDim(this%im_world, this%ims, this%nx, min_DE_extent=2) + call MAPL_DecomposeDim(this%jm_world, this%jms, this%ny, min_DE_extent=2) + + call this%check_and_fill_consistency(rc=status) + _VERIFY(status) + + _UNUSED_DUMMY(this) + _UNUSED_DUMMY(unusable) + _UNUSED_DUMMY(rc) + + end subroutine initialize_from_file_metadata + + subroutine initialize_from_config_with_prefix(this, config, prefix, unusable, rc) + use esmf + class (XYGridFactory), intent(inout) :: this + type (ESMF_Config), intent(inout) :: config + character(len=*), intent(in) :: prefix ! effectively optional due to overload without this argument + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME//'make_geos_grid_from_config' + character(len=ESMF_MAXSTR) :: tmp + + if (present(unusable)) print*,shape(unusable) + + call ESMF_ConfigGetAttribute(config, tmp, label=prefix//'GRIDNAME:', default=MAPL_GRID_NAME_DEFAULT) + this%grid_name = trim(tmp) + + call ESMF_ConfigGetAttribute(config, tmp, label=prefix//'GRIDSPEC:', rc=status) + _VERIFY(status) + this%grid_file_name = trim(tmp) + + call ESMF_ConfigGetAttribute(config, this%nx, label=prefix//'NX:', default=MAPL_UNDEFINED_INTEGER) + call ESMF_ConfigGetAttribute(config, this%ny, label=prefix//'NY:', default=MAPL_UNDEFINED_INTEGER) + call ESMF_ConfigGetAttribute(config, this%im_world, label=prefix//'IM_WORLD:', default=MAPL_UNDEFINED_INTEGER) + call ESMF_ConfigGetAttribute(config, this%jm_world, label=prefix//'JM_WORLD:', default=MAPL_UNDEFINED_INTEGER) + call ESMF_ConfigGetAttribute(config, this%lm, label=prefix//'LM:', default=MAPL_UNDEFINED_INTEGER) + + call this%check_and_fill_consistency(rc=status) + + _RETURN(_SUCCESS) + + contains + + subroutine get_multi_integer(values, label, rc) + integer, allocatable, intent(out) :: values(:) + character(len=*) :: label + integer, optional, intent(out) :: rc + + integer :: i + integer :: n + integer :: tmp + integer :: status + logical :: isPresent + + call ESMF_ConfigFindLabel(config, label=prefix//label,isPresent=isPresent,rc=status) + _VERIFY(status) + if (.not. isPresent) then + _RETURN(_SUCCESS) + end if + + ! First pass: count values + n = 0 + do + call ESMF_ConfigGetAttribute(config, tmp, rc=status) + if (status /= _SUCCESS) then + exit + else + n = n + 1 + end if + end do + + ! Second pass: allocate and fill + allocate(values(n), stat=status) ! no point in checking status + _VERIFY(status) + call ESMF_ConfigFindLabel(config, label=prefix//label,rc=status) + _VERIFY(status) + do i = 1, n + call ESMF_ConfigGetAttribute(config, values(i), rc=status) + _VERIFY(status) + end do + + _RETURN(_SUCCESS) + + end subroutine get_multi_integer + + end subroutine initialize_from_config_with_prefix + + + + function to_string(this) result(string) + character(len=:), allocatable :: string + class (XYGridFactory), intent(in) :: this + + _UNUSED_DUMMY(this) + string = 'XYGridFactory' + + end function to_string + + + + subroutine check_and_fill_consistency(this, unusable, rc) + use MAPL_BaseMod, only: MAPL_DecomposeDim + class (XYGridFactory), intent(inout) :: this + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME // 'check_and_fill_consistency' + + _UNUSED_DUMMY(unusable) + + if (.not. allocated(this%grid_name)) then + this%grid_name = MAPL_GRID_NAME_DEFAULT + end if + ! local extents + call verify(this%nx, this%im_world, this%ims, rc=status) + call verify(this%ny, this%jm_world, this%jms, rc=status) + call this%file_has_corners(_RC) + + _RETURN(_SUCCESS) + + contains + + subroutine verify(n, m_world, ms, rc) + integer, intent(inout) :: n + integer, intent(inout) :: m_world + integer, allocatable, intent(inout) :: ms(:) + integer, optional, intent(out) :: rc + + integer :: status + + if (allocated(ms)) then + _ASSERT(size(ms) > 0,"needs message") + + if (n == MAPL_UNDEFINED_INTEGER) then + n = size(ms) + else + _ASSERT(n == size(ms),"needs message") + end if + + if (m_world == MAPL_UNDEFINED_INTEGER) then + m_world = sum(ms) + else + _ASSERT(m_world == sum(ms),"needs message") + end if + + else + + _ASSERT(n /= MAPL_UNDEFINED_INTEGER,"needs message") + _ASSERT(m_world /= MAPL_UNDEFINED_INTEGER,"needs message") + allocate(ms(n), stat=status) + _VERIFY(status) + call MAPL_DecomposeDim(m_world, ms, n) + + end if + + _RETURN(_SUCCESS) + + end subroutine verify + + end subroutine check_and_fill_consistency + + + elemental subroutine set_with_default_integer(to, from, default) + integer, intent(out) :: to + integer, optional, intent(in) :: from + integer, intent(in) :: default + + if (present(from)) then + to = from + else + to = default + end if + + end subroutine set_with_default_integer + + + subroutine set_with_default_character(to, from, default) + character(len=:), allocatable, intent(out) :: to + character(len=*), optional, intent(in) :: from + character(len=*), intent(in) :: default + + if (present(from)) then + to = from + else + to = default + end if + + end subroutine set_with_default_character + + ! MAPL uses values in lon_array and lat_array only to determine the + ! general positioning. Actual coordinates are then recomputed. + ! This helps to avoid roundoff differences from slightly different + ! input files. + subroutine initialize_from_esmf_distGrid(this, dist_grid, lon_array, lat_array, unusable, rc) + use MAPL_ConfigMod + use MAPL_Constants, only: PI => MAPL_PI_R8 + class (XYGridFactory), intent(inout) :: this + type (ESMF_DistGrid), intent(in) :: dist_grid + type (ESMF_LocalArray), intent(in) :: lon_array + type (ESMF_LocalArray), intent(in) :: lat_array + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + character(len=*), parameter :: Iam = MOD_NAME // 'initialize_from_esmf_distGrid' + + _UNUSED_DUMMY(this) + _UNUSED_DUMMY(unusable) + _UNUSED_DUMMY(dist_grid) + _UNUSED_DUMMY(lon_array) + _UNUSED_DUMMY(lat_array) + + + ! not supported + _FAIL("XY initialize from distgrid non supported") + + end subroutine initialize_from_esmf_distGrid + + function decomps_are_equal(this,a) result(equal) + class (XYGridFactory), intent(in) :: this + class (AbstractGridFactory), intent(in) :: a + logical :: equal + + select type (a) + class default + equal = .false. + return + class is (XYGridFactory) + equal = .true. + + ! same decomposition + equal = a%nx == this%nx .and. a%ny == this%ny + if (.not. equal) return + + end select + + end function decomps_are_equal + + + function physical_params_are_equal(this, a) result(equal) + class (XYGridFactory), intent(in) :: this + class (AbstractGridFactory), intent(in) :: a + logical :: equal + + select type (a) + class default + equal = .false. + return + class is (XYGridFactory) + equal = .true. + + !equal = (a%grid_file_name == this%grid_file_name) + !if (.not. equal) return + + equal = (a%im_world == this%im_world) .and. (a%jm_world == this%jm_world) + if (.not. equal) return + + end select + + end function physical_params_are_equal + + + logical function equals(a, b) + class (XYGridFactory), intent(in) :: a + class (AbstractGridFactory), intent(in) :: b + + select type (b) + class default + equals = .false. + return + class is (XYGridFactory) + equals = .true. + + equals = (a%lm == b%lm) + if (.not. equals) return + + equals = a%decomps_are_equal(b) + if (.not. equals) return + + equals = a%physical_params_are_equal(b) + if (.not. equals) return + + end select + + end function equals + + + function generate_grid_name(this) result(name) + character(len=:), allocatable :: name + class (XYGridFactory), intent(in) :: this + + _UNUSED_DUMMY(this) + + name = '' + ! needs to be implemented + error stop -1 + + end function generate_grid_name + + subroutine init_halo(this, unusable, rc) + class (XYGridFactory), intent(inout) :: this + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(out) :: rc + + integer :: status + + _RETURN(_SUCCESS) + + end subroutine init_halo + + + subroutine halo(this, array, unusable, halo_width, rc) + use MAPL_CommsMod + class (XYGridFactory), intent(inout) :: this + real(kind=REAL32), intent(inout) :: array(:,:) + class (KeywordEnforcer), optional, intent(in) :: unusable + integer, optional, intent(in) :: halo_width + integer, optional, intent(out) :: rc + + integer :: status + character(len=*), parameter :: Iam = MOD_NAME // 'halo' + + end subroutine halo + + subroutine append_metadata(this, metadata) + class (XYGridFactory), intent(inout) :: this + type (FileMetadata), intent(inout) :: metadata + + type (Variable) :: v + real(kind=real64), allocatable :: fake_coord(:) + integer :: i + + call metadata%add_dimension('Xdim', this%im_world) + call metadata%add_dimension('Ydim', this%jm_world) + if (this%has_corners) then + call metadata%add_dimension('XCdim', this%im_world+1) + call metadata%add_dimension('YCdim', this%jm_world+1) + end if + + allocate(fake_coord(this%im_world)) + do i=1,this%im_world + fake_coord(i)=dble(i) + enddo + + ! Coordinate variables + v = Variable(type=PFIO_REAL64, dimensions='Xdim') + call v%add_attribute('long_name', 'Fake Longitude for GrADS Compatibility') + call v%add_attribute('units', 'degrees_east') + call v%add_const_value(UnlimitedEntity(fake_coord)) + call metadata%add_variable('Xdim', v) + deallocate(fake_coord) + + allocate(fake_coord(this%jm_world)) + do i=1,this%jm_world + fake_coord(i)=dble(i) + enddo + + v = Variable(type=PFIO_REAL64, dimensions='Ydim') + call v%add_attribute('long_name', 'Fake Latitude for GrADS Compatibility') + call v%add_attribute('units', 'degrees_north') + call v%add_const_value(UnlimitedEntity(fake_coord)) + call metadata%add_variable('Ydim', v) + deallocate(fake_coord) + + v = Variable(type=PFIO_REAL64, dimensions='Xdim,Ydim') + call v%add_attribute('long_name','longitude') + call v%add_attribute('units','degrees_east') + call metadata%add_variable('lons',v) + + v = Variable(type=PFIO_REAL64, dimensions='Xdim,Ydim') + call v%add_attribute('long_name','latitude') + call v%add_attribute('units','degrees_north') + call metadata%add_variable('lats',v) + + if (this%has_corners) then + v = Variable(type=PFIO_REAL64, dimensions='XCdim,YCdim') + call v%add_attribute('long_name','longitude') + call v%add_attribute('units','degrees_east') + call metadata%add_variable('corner_lons',v) + + v = Variable(type=PFIO_REAL64, dimensions='XCdim,YCdim') + call v%add_attribute('long_name','latitude') + call v%add_attribute('units','degrees_north') + call metadata%add_variable('corner_lats',v) + end if +! + call metadata%add_attribute('grid_type','XY') + + end subroutine append_metadata + + function get_grid_vars(this) result(vars) + class (XYGridFactory), intent(inout) :: this + + character(len=:), allocatable :: vars + _UNUSED_DUMMY(this) + + vars = 'Xdim,Ydim' + + end function get_grid_vars + + function get_file_format_vars(this) result(vars) + class (XYGridFactory), intent(inout) :: this + + character(len=:), allocatable :: vars + _UNUSED_DUMMY(this) + + !vars = 'Xdim,Ydim,lons,lats,corner_lons,corner_lats' + vars = 'Xdim,Ydim,lons,lats' + + end function get_file_format_vars + + subroutine append_variable_metadata(this,var) + class (XYGridFactory), intent(inout) :: this + type(Variable), intent(inout) :: var + _UNUSED_DUMMY(this) + _UNUSED_DUMMY(var) + end subroutine append_variable_metadata + + subroutine generate_file_bounds(this,grid,local_start,global_start,global_count,metadata,rc) + use MAPL_BaseMod + class(XYGridFactory), intent(inout) :: this + type(ESMF_Grid), intent(inout) :: grid + integer, allocatable, intent(out) :: local_start(:) + integer, allocatable, intent(out) :: global_start(:) + integer, allocatable, intent(out) :: global_count(:) + type(FileMetaData), intent(in), optional :: metaData + integer, optional, intent(out) :: rc + + integer :: status + integer :: global_dim(3), i1,j1,in,jn + character(len=*), parameter :: Iam = MOD_NAME // 'generate_file_bounds' + _UNUSED_DUMMY(this) + + call MAPL_GridGet(grid,globalCellCountPerDim=global_dim,rc=status) + _VERIFY(status) + call MAPL_GridGetInterior(grid,i1,in,j1,jn) + allocate(local_start,source=[i1,j1]) + allocate(global_start,source=[1,1]) + allocate(global_count,source=[global_dim(1),global_dim(2)]) + + end subroutine generate_file_bounds + + subroutine generate_file_corner_bounds(this,grid,local_start,global_start,global_count,rc) + use MAPL_BaseMod + class (XYGridFactory), intent(inout) :: this + type(ESMF_Grid), intent(inout) :: grid + integer, allocatable, intent(out) :: local_start(:) + integer, allocatable, intent(out) :: global_start(:) + integer, allocatable, intent(out) :: global_count(:) + integer, optional, intent(out) :: rc + + character(len=*), parameter :: Iam = MOD_NAME // 'generate_file_corner_bounds' + + integer :: status + integer :: global_dim(3), i1,j1,in,jn + + call MAPL_GridGet(grid,globalCellCountPerDim=global_dim,rc=status) + _VERIFY(status) + call MAPL_GridGetInterior(grid,i1,in,j1,jn) + allocate(local_start,source=[i1,j1]) + allocate(global_start,source=[1,1]) + allocate(global_count,source=[global_dim(1)+1,global_dim(2)+1]) + + end subroutine generate_file_corner_bounds + + + function generate_file_reference2D(this,fpointer) result(ref) + use pFIO + type(ArrayReference) :: ref + class(XYGridFactory), intent(inout) :: this + real, pointer, intent(in) :: fpointer(:,:) + _UNUSED_DUMMY(this) + ref = ArrayReference(fpointer) + end function generate_file_reference2D + + function generate_file_reference3D(this,fpointer,metadata) result(ref) + use pFIO + type(ArrayReference) :: ref + class(XYGridFactory), intent(inout) :: this + real, pointer, intent(in) :: fpointer(:,:,:) + type(FileMetaData), intent(in), optional :: metaData + _UNUSED_DUMMY(this) + ref = ArrayReference(fpointer) + end function generate_file_reference3D + + subroutine file_has_corners(this,rc) + use MAPL_CommsMod + class(XYGridFactory), intent(inout) :: this + integer, intent(out), optional :: rc + + integer :: status, ncid,varid + type(ESMF_VM) :: vm + integer :: log_array(1) + + if (mapl_am_i_root()) then + status = nf90_open(this%grid_file_name,NF90_NOWRITE,ncid) + _VERIFY(status) + status = NF90_inq_varid(ncid,"corner_lons",varid) + if (status == 0) then + this%has_corners = .true. + log_array(1) = 1 + else + this%has_corners = .false. + log_array(1) = 0 + end if + end if + call ESMF_VMGetCurrent(vm,_RC) + call ESMF_VMBroadcast(vm,log_array,1,0,_RC) + this%has_corners = (1 == log_array(1)) + + _RETURN(_SUCCESS) + end subroutine + +end module MAPL_XYGridFactoryMod diff --git a/components.yaml b/components.yaml index 20d139d32841..615e0a155a61 100644 --- a/components.yaml +++ b/components.yaml @@ -5,13 +5,13 @@ MAPL: ESMA_env: local: ./ESMA_env remote: ../ESMA_env.git - tag: v4.8.0 + tag: v4.9.1 develop: main ESMA_cmake: local: ./ESMA_cmake remote: ../ESMA_cmake.git - tag: v3.24.0 + tag: v3.28.0 develop: develop ecbuild: diff --git a/generic/MAPL_Generic.F90 b/generic/MAPL_Generic.F90 index e5a5f687a2ee..752930840b8c 100644 --- a/generic/MAPL_Generic.F90 +++ b/generic/MAPL_Generic.F90 @@ -320,7 +320,8 @@ module MAPL_GenericMod interface MAPL_GetResource module procedure MAPL_GetResourceFromConfig_scalar module procedure MAPL_GetResourceFromMAPL_scalar - module procedure MAPL_GetResource_array + module procedure MAPL_GetResourceFromConfig_array + module procedure MAPL_GetResourceFromMAPL_array end interface MAPL_GetResource interface MAPL_CopyFriendliness @@ -351,7 +352,6 @@ module MAPL_GenericMod module procedure MAPL_AddAttributeToFields_I4 end interface - ! ======================================================================= @@ -8341,7 +8341,7 @@ end subroutine MAPL_GetResourceFromConfig_scalar ! This is a pass-through routine. It maintains the interface for ! MAPL_GetResource as-is instead of moving this subroutine to another module. - subroutine MAPL_GetResource_array(state, vals, label, default, rc) + subroutine MAPL_GetResourceFromMAPL_array(state, vals, label, default, rc) type(MAPL_MetaComp), intent(inout) :: state character(len=*), intent(in) :: label class(*), intent(inout) :: vals(:) @@ -8363,7 +8363,31 @@ subroutine MAPL_GetResource_array(state, vals, label, default, rc) _RETURN(_SUCCESS) - end subroutine MAPL_GetResource_array + end subroutine MAPL_GetResourceFromMAPL_array + + subroutine MAPL_GetResourceFromConfig_array(config, vals, label, default, rc) + type(ESMF_Config), intent(inout) :: config + character(len=*), intent(in) :: label + class(*), intent(inout) :: vals(:) + class(*), optional, intent(in) :: default(:) + integer, optional, intent(out) :: rc + + integer :: status + logical :: value_is_set + + call MAPL_GetResource_config_array(config, vals, label, value_is_set, & + default = default, rc = status) + + if(.not. value_is_set) then + if (present(rc)) rc = ESMF_FAILURE + return + end if + + _VERIFY(status) + + _RETURN(_SUCCESS) + + end subroutine MAPL_GetResourceFromConfig_array integer function MAPL_GetNumSubtiles(STATE, RC) type (MAPL_MetaComp), intent(INOUT) :: STATE @@ -11109,4 +11133,5 @@ end function wrap end subroutine MAPL_MethodAdd + end module MAPL_GenericMod diff --git a/gridcomps/ExtData2G/README.md b/gridcomps/ExtData2G/README.md new file mode 100644 index 000000000000..9bce6e9f2652 --- /dev/null +++ b/gridcomps/ExtData2G/README.md @@ -0,0 +1,43 @@ + +## The `ExtData` Gridded Component + +`ExtData` is an ESMF gridded component provided by MAPL to encapsulate access to geospatial data residing on disk. +It provides a flexible, run-time configurable mechanism for intepolating in time and regridding to arbitrary ESMF grids. +`ExtData` acts as the "provider of last resort" for any fields in model import states that have not been satisfied by the usual MAPL connection rules. + +`ExtData` is instantiated and all its registered methods (`Initialize`, `Run` and `Finalize`) + are run automatically by the `CapGridComp`. +In a MAPL application, fields added to the import state of a component +are propagated up the MAPL hierarchy until connected to an export state item by some ancestor component. +When no such connection is made, a field will eventually reach the `CapGridComp`, and is passed to +the `ExtData` gridded component for servicing. +`ExtData` is in essence a provider of last resort for Import fields that need to be filled with data. +Like other components, it has a `Run` method that gets called every step in your MAPL application. +What actually happens when it is run is determined by a `ExtData` resource file. +`ExtData` can be seen as a a centralized component providing external, time-varying data +to MAPL components such as chemical and aerosol emissions and forcings like sea surface temperature. + +The behavior of `ExtData` is is controlled through a YAML configuration file `extdata.yaml`. +The main goal of the file is to provide a connection between a field name (within the code) +and a variable name within a "collection" of NetCDF files. +`ExtData` analyzes each of the fields passed from `CapGridComp` and parses `extdata.yaml` +to determine if it can supply appropriate data. +`extdata.yaml` should be viewed as an description of what `ExtData` can provide, +*not* what it necessarily will provide. +In addition to simply announcing what `ExtData` can provide, the user can specify other information +such as how frequently to update the data from disk and how the data is organized. +This update could be at every step, just once when starting the model run, +or at a particular time each days. +It also allows tremendous flexibility as to how the user chooses to organize the data files. +`ExtData` also allows data to be shifted, scaled, and control what method is used to regrid +the file to the application grid. + +The file `extdata.yaml` allows several following settings that are used by `ExtData` to perform operations +(such as appropriate file selection, horizontal interpolation, time interpolation, etc.) on the fly. +To have additional information on `ExtData`, you may want to consult: + +[ExtData Next Generation User Guide](https://github.com/GEOS-ESM/MAPL/wiki/ExtData-Next-Generation---User-Guide) + +A sample `ExtData` configuration file is available at: + +[Sample ExtData Configuration YAML File](https://github.com/GEOS-ESM/MAPL/wiki/Sample_ExtData_configuration_yaml_file) diff --git a/gridcomps/History/README.md b/gridcomps/History/README.md new file mode 100644 index 000000000000..842624762b90 --- /dev/null +++ b/gridcomps/History/README.md @@ -0,0 +1,37 @@ + +## The `History` Gridded Component + +`History` is a highly-configurable ESMF gridded component provided my MAPL which is used to manage streams of output data from a MAPL hierarchy. +It is able to write any export item from any component into a specified file collection during the course of a run. This output is highly configurable, allowing specification of output grid, vertical interpolation, temporal frequency and/or averaging. The component also supports output of derived quantities which are computed from native fields with a small suite of mathematical operations. + +`History` uses [MAPL PFIO](https://github.com/GEOS-ESM/MAPL/wiki/PFIO:-a-High-Performance-Client-Server-I-O-Layer) + for creating and writing its files in the netCDF format. +Its behavior is controlled through its configuration file, `HISTORY.rc`, which primarily consists of a list +of collections to produced. +Each collection can have the following properties: +- All the fields are on the same grid. +- If fields have vertical levels, all of them should be either at the center or at the edge. We cannot have both in the same collection. +- Its fields may be `instantaneous` or `time-averaged`, but all fields within a collection use the same time discretization. +- A beginning and an end time may be specified for each collection. +- Collections are a set of files with a common name template. +- Files in a collection have a fixed number of time groups in them. +- Data in each time group are `time-stamped`; for time-averaged data, the center of the averaging period is used. +- Files in a collection can have time-templated names. The template values correspond to the times on the first group in the file. + +The component has no true export state, since its products are diagnostic file collections. +It does have both import and internal states, which can be treated as in any other MAPL +component, but it generally makes no sense to checkpoint and restart these. + +The main file in the `History` source code is `MAPL_HistoryGridComp.F90` +that contains the `Initialize`, `Run` and `Finalize` methods for `History`. +The three methods are called at the level of CAP. + +Additional information about the `History` griddec component can be found at: + +[MAPL History Component](https://github.com/GEOS-ESM/MAPL/wiki/MAPL-History-Component) + +A sample `History` configuration file is available at: + +[Sample History Configuration File](https://github.com/GEOS-ESM/MAPL/wiki/Sample_History_configuration_file) + + diff --git a/pfio/README.md b/pfio/README.md new file mode 100644 index 000000000000..6b3c69d1d03f --- /dev/null +++ b/pfio/README.md @@ -0,0 +1,33 @@ + +## `PFIO` + +`PFIO`, a MAPL subcomponent, is a parallel I/O tool that is designed +to facilitate the production of model netCDF output files (organized in collections) and +to efficiently use available resources in a distributed computing environment. +`PFIO` asynchronously creates output files therefore allowing the model to proceed with +calculations without waiting for the I/O tasks to be completed. +This allows the applications to achieve achieve higher effective write speeds, +and leads to a decrease of the overall model integration time. +The goal of `PFIO` is for models to spend more time doing calculations instead +of waiting on I/O activitiies to be done first. + +To implement `PFIO` in an application, it is important to note that `PFIO` +handles netCDF files and therefore follows the netCDF steps to read and create files. +However, the processes in PFIO are simpler because it works only with +variable names instead of variable identifier (as in netCDF). +When an application is run with `PFIO`, the available nodes (cores) are split into two groups: +the computing nodes (reserved for model calculations) and +the I/O nodes (used to perform I/O operations only). + +The [History gridded component](https://github.com/GEOS-ESM/MAPL/tree/main/gridcomps/History) +relies on `PFIO` to create and write files in the netCDF format. +`PFIO` distributes the output files (collections) to the I/O nodes based on the user's +configuration set at run time from the command line. +There are typically two main command line configurations: +- **Simple Server**: This the basic configuration where the compute nodes and I/O nodes overlap. `PFIO` is set to run the standard-like Message Passing Interface (MPI) root processor configuration (where IO are completed before calculations resume). This default is efficient at low resolution and/or with few file collections. +- **MultiGroupServer Class**: Here, the compute nodes and I/O nodes are completely separate. All the I/O procedures are handled by the I/O nodes that perform their tasks wven while the compute nodes do model integration. + +To have additional information on `PFIO`, you may want to consult: + +[PFIO: a High Performance Client Server I/O Layer](https://github.com/GEOS-ESM/MAPL/wiki/PFIO:-a-High-Performance-Client-Server-I-O-Layer) +