Skip to content

Commit

Permalink
Merge pull request #3099 from GEOS-ESM/feature/wdboggs/#3064_variant_…
Browse files Browse the repository at this point in the history
…spec_offset_extdata2g

Allow update offsets based on timestep for extdata2g
  • Loading branch information
darianboggs authored Oct 17, 2024
2 parents ba62073 + a82497f commit b8283be
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Allow update offsets of ±timestep in ExtData2G

### Changed

Expand Down
2 changes: 2 additions & 0 deletions gridcomps/ExtData2G/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ set_target_properties (${this} PROPERTIES Fortran_MODULE_DIRECTORY ${include_${t
if (CMAKE_Fortran_COMPILER_ID MATCHES Intel AND CMAKE_BUILD_TYPE MATCHES Release)
set_source_files_properties(ExtDataGridCompNG.F90 PROPERTIES COMPILE_OPTIONS ${FOPT1})
endif ()

add_subdirectory(tests EXCLUDE_FROM_ALL)
108 changes: 91 additions & 17 deletions gridcomps/ExtData2G/ExtDataUpdatePointer.F90
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module MAPL_ExtDataPointerUpdate
private

public :: ExtDataPointerUpdate
public :: HEARTBEAT_STRING

type :: ExtDataPointerUpdate
private
Expand All @@ -30,9 +31,12 @@ module MAPL_ExtDataPointerUpdate
procedure :: is_single_shot
procedure :: disable
procedure :: get_adjusted_time
procedure :: get_offset
end type

contains
character(len=*), parameter :: HEARTBEAT_STRING = 'HEARTBEAT'

contains

function get_adjusted_time(this,time,rc) result(adjusted_time)
type(ESMF_Time) :: adjusted_time
Expand All @@ -45,6 +49,14 @@ function get_adjusted_time(this,time,rc) result(adjusted_time)
_RETURN(_SUCCESS)
end function

function get_offset(this) result(offset)
type(ESMF_TimeInterval) :: offset
class(ExtDataPointerUpdate), intent(in) :: this

offset = this%offset

end function get_offset

subroutine create_from_parameters(this,update_time,update_freq,update_offset,time,clock,rc)
class(ExtDataPointerUpdate), intent(inout) :: this
character(len=*), intent(in) :: update_time
Expand All @@ -54,10 +66,15 @@ subroutine create_from_parameters(this,update_time,update_freq,update_offset,tim
type(ESMF_Clock), intent(inout) :: clock
integer, optional, intent(out) :: rc

integer :: status,int_time,year,month,day,hour,minute,second,neg_index
integer :: status,int_time,year,month,day,hour,minute,second
logical :: negative_offset
type(ESMF_TimeInterval) :: timestep
integer :: multiplier
integer :: i, j
logical :: is_heartbeat

this%last_checked = time
call ESMF_ClockGet(clock, timestep=timestep, _RC)
if (update_freq == "-") then
this%single_shot = .true.
else if (update_freq /= "PT0S") then
Expand All @@ -71,22 +88,79 @@ subroutine create_from_parameters(this,update_time,update_freq,update_offset,tim
this%last_ring = this%reference_time
this%update_freq = string_to_esmf_timeinterval(update_freq,_RC)
end if
negative_offset = .false.
if (index(update_offset,"-") > 0) then
negative_offset = .true.
neg_index = index(update_offset,"-")
end if
if (negative_offset) then
this%offset=string_to_esmf_timeinterval(update_offset(neg_index+1:),_RC)
this%offset = -this%offset
i = index(update_offset,"-") + 1
j = index(update_offset, '+') + 1
_ASSERT(i==1 .or. j==1, '"+" and "-" cannot both be present in update_offset string.')
negative_offset = i > 1
if(.not. negative_offset) i = j
call parse_heartbeat_timestring(update_offset(i:), is_heartbeat=is_heartbeat, multiplier=multiplier)
if(is_heartbeat) then
this%offset = multiplier * timestep
else
this%offset=string_to_esmf_timeinterval(update_offset,_RC)
this%offset=string_to_esmf_timeinterval(update_offset(i:),_RC)
end if
if(negative_offset) this%offset = -this%offset
_RETURN(_SUCCESS)
_UNUSED_DUMMY(clock)

end subroutine create_from_parameters

subroutine parse_heartbeat_timestring(timestring, is_heartbeat, multiplier, rc)
character(len=*), intent(in) :: timestring
logical, intent(out) :: is_heartbeat
integer, intent(out) :: multiplier
character(len=:), allocatable :: found_string
character(len=:), allocatable :: upper
integer, optional, intent(out) :: rc
integer :: status

multiplier = 1
upper = ESMF_UtilStringUpperCase(timestring, _RC)
call split_on(upper, HEARTBEAT_STRING, found_string=found_string)
is_heartbeat = len(found_string) > 0
! For now, multiplier is simply set to 1. In the future, as needed, the before_string
! and after_string arguments of split_on can be used to parse for a multiplier.

end subroutine parse_heartbeat_timestring

subroutine split_on(string, substring, found_string, before_string, after_string)
character(len=*), intent(in) :: string, substring
character(len=:), allocatable, intent(out) :: found_string
character(len=:), optional, allocatable, intent(out) :: before_string, after_string
integer :: i

i = index(string, substring)
found_string = ''
if(i > 0) found_string = string(i:i+len(substring)-1)
if(present(before_string)) then
before_string = ''
if(i > 1) before_string = string(:i-1)
end if
if(present(after_string)) then
after_string = ''
if(i + len(substring) <= len(string)) after_string = string(i+len(substring):)
end if

end subroutine split_on

function to_upper(s) result(u)
character(len=:), allocatable :: u
character(len=*), intent(in) :: s
character(len=*), parameter :: LOWER = 'qwertyuiopasdfghjklzxcvbnm'
character(len=*), parameter :: UPPER = 'QWERTYUIOPASDFGHJKLZXCVBNM'
character :: ch
integer :: i, j

u = s
do i = 1, len(u)
ch = u(i:i)
j = index(LOWER, ch)
if(j > 0) ch = UPPER(j:j)
u(i:i) = ch
end do

end function to_upper

subroutine check_update(this,do_update,use_time,current_time,first_time,rc)
class(ExtDataPointerUpdate), intent(inout) :: this
logical, intent(out) :: do_update
Expand All @@ -101,11 +175,11 @@ subroutine check_update(this,do_update,use_time,current_time,first_time,rc)
_RETURN(_SUCCESS)
end if
if (this%simple_alarm_created) then
use_time = current_time+this%offset
use_time = this%get_adjusted_time(current_time)
if (first_time) then
do_update = .true.
this%first_time_updated = .true.
use_time = this%last_ring + this%offset
use_time = this%get_adjusted_time(this%last_ring)
else
! normal flow
next_ring = this%last_ring
Expand All @@ -126,28 +200,28 @@ subroutine check_update(this,do_update,use_time,current_time,first_time,rc)
do while(next_ring >= current_time)
next_ring=next_ring-this%update_freq
enddo
use_time = next_ring+this%offset
use_time = this%get_adjusted_time(next_ring)
this%last_ring = next_ring
do_update = .true.
! alarm never rang during the previous advance, only update the previous update was the first time
else if (this%last_ring < current_time) then
if (this%first_time_updated) then
do_update=.true.
this%first_time_updated = .false.
use_time = this%last_ring + this%offset
use_time = this%get_adjusted_time(this%last_ring)
end if
! otherwise we land on a time when the alarm would ring and we would update
else if (this%last_ring == current_time) then
do_update =.true.
this%first_time_updated = .false.
use_time = current_time+this%offset
use_time = this%get_adjusted_time(current_time)
end if
end if
end if
else
do_update = .true.
if (this%single_shot) this%disabled = .true.
use_time = current_time+this%offset
use_time = this%get_adjusted_time(current_time)
end if
this%last_checked = current_time

Expand Down
20 changes: 20 additions & 0 deletions gridcomps/ExtData2G/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
set(MODULE_DIRECTORY "${esma_include}/gridcomps/ExtData2G/tests")

set (test_srcs
Test_ExtDataUpdatePointer.pf
)

add_pfunit_ctest(MAPL.ExtData2G.tests
TEST_SOURCES ${test_srcs}
LINK_LIBRARIES MAPL.ExtData2G
)
set_target_properties(MAPL.ExtData2G.tests PROPERTIES Fortran_MODULE_DIRECTORY ${MODULE_DIRECTORY})
set_tests_properties(MAPL.ExtData2G.tests PROPERTIES LABELS "ESSENTIAL")

# With this test, it was shown that if you are building with the GNU Fortran
# compiler and *not* on APPLE, then you need to link with the dl library.
if (CMAKE_Fortran_COMPILER_ID STREQUAL "GNU" AND NOT APPLE)
target_link_libraries(MAPL.ExtData2G.tests ${CMAKE_DL_LIBS})
endif ()

add_dependencies(build-tests MAPL.ExtData2G.tests)
Loading

0 comments on commit b8283be

Please sign in to comment.