!*********************************************************************** !* GNU Lesser General Public License !* !* This file is part of the GFDL Flexible Modeling System (FMS). !* !* FMS is free software: you can redistribute it and/or modify it under !* the terms of the GNU Lesser General Public License as published by !* the Free Software Foundation, either version 3 of the License, or (at !* your option) any later version. !* !* FMS is distributed in the hope that it will be useful, but WITHOUT !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License !* for more details. !* !* You should have received a copy of the GNU Lesser General Public !* License along with FMS. If not, see . !*********************************************************************** module time_interp_mod ! ! Bruce Wyman ! ! ! ! Computes a weight and dates/indices for linearly interpolating between two dates. ! ! ! A time type is converted into two consecutive dates plus ! a fraction representing the distance between the dates. ! This information can be used to interpolate between the dates. ! The dates may be expressed as years, months, or days or ! as indices in an array. ! ! ! Description summarizing public interface. ! !----------------------------------------------------------------------- use time_manager_mod, only: time_type, get_date, set_date, set_time, & days_in_year, days_in_month, leap_year, & time_type_to_real, real_to_time_type, & get_calendar_type, JULIAN, GREGORIAN, NO_CALENDAR, & operator(+), operator(-), operator(>), & operator(<), operator( // ), operator( / ), & operator(>=), operator(<=), operator( * ), & operator(==), print_date, print_time,& time_list_error, date_to_string use fms_mod, only: write_version_number, & error_mesg, FATAL, stdout, stdlog, & open_namelist_file, close_file, check_nml_error, & fms_error_handler use mpp_mod, only: input_nml_file implicit none private !----------------------------------------------------------------------- public :: time_interp_init, time_interp, fraction_of_year ! ! ! Returns a weight and dates or indices for interpolating between two dates. The ! interface fraction_of_year is provided for backward compatibility with the ! previous version. ! ! ! Returns weight by interpolating Time between Time1 and Time2. ! i.e. weight = (Time-Time1)/(Time2-Time1) ! Time1 and Time2 may be specified by any of several different ways, ! which is the reason for multiple interfaces. ! If Time1 and Time2 are the begining and end of the year in which ! Time falls, use first interface. ! If Time1 and Time2 fall on year boundaries, use second interface. ! If Time1 and Time2 fall on month boundaries, use third. ! If Time1 and Time2 fall on day boundaries, use fourth. ! If Time1 and Time2 are consecutive elements of an assending list, use fifth. ! The fifth also returns the indices of Timelist between which Time falls. ! The sixth interface is for cyclical data. Time_beg and Time_end specify the ! begining and end of a repeating period. In this case: ! weight = (Time_adjusted - Time1) / (Time2 - Time1) ! Where: ! Time1 = Timelist(index1) ! Time2 = Timelist(index2) ! Time_adjusted = Time - N*Period ! Period = Time_end-Time_beg ! N is between (Time-Time_end)/Period and (Time-Time_beg)/Period ! That is, N is the integer that results in Time_adjusted that is between Time_beg and Time_end. ! ! ! ! ! ! ! ! ! ! The time at which the the weight is computed. ! ! ! For cyclical interpolation: Time_beg specifies the begining time of a cycle. ! ! ! For cyclical interpolation: Time_end specifies the ending time of a cycle. ! ! ! For cyclical interpolation: Timelist is an array of times between Time_beg and Time_end. ! Must be monotonically increasing. ! ! ! ! ! Timelist(index1) = The largest value of Timelist which is less than mod(Time,Time_end-Time_beg) ! ! ! Timelist(index2) = The smallest value of Timelist which is greater than mod(Time,Time_end-Time_beg) ! ! ! Turns on a kluge for an inconsistency which may occur in a special case. ! When the modulo time period (i.e. Time_end - Time_beg) is a whole number of years ! and is not a multiple of 4, and the calendar in use has leap years, then it is ! likely that the interpolation will involve mapping a common year onto a leap year. ! In this case it is often desirable, but not absolutely necessary, to use data for ! Feb 28 of the leap year when it is mapped onto a common year. ! To turn this on, set correct_leap_year_inconsistency=.true. ! ! ! weight = (mod(Time,Time_end-Time_beg) - Timelist(index1)) / (Timelist(index2) - Timelist(index1)) ! ! ! ! ! ! ! ! ! ! ! The list of input time types must have ascending dates. ! ! ! The length of the current month for input Time and Time_list ! must be the same when using the modulo month option. The ! modulo month option is available but not supported. ! ! ! The optional argument modtime must have a value set by one ! of the public parameters: NONE, YEAR, MONTH, DAY. The ! MONTH and DAY options are available but not supported. ! ! ! The difference between the last and first values in the input ! Time list/array exceeds the length of the modulo period. ! ! ! The difference between the last and first values in the input ! These errors occur when you are not using a modulo axis and ! the input Time occurs before the first value in the Time ! list/array or after the last value in the Time list/array. ! ! ! Examples: !
!       Time: Jan 01 00z    weight = 0.0
!       Time: Jul 01        weight ~ 0.5
!       Time: Dec 31 23z    weight ~ 1.0
!     
!
interface time_interp module procedure time_interp_frac, time_interp_year, & time_interp_month, time_interp_day, & time_interp_list, time_interp_modulo end interface !
integer, public, parameter :: NONE=0, YEAR=1, MONTH=2, DAY=3 !----------------------------------------------------------------------- integer, parameter :: secmin = 60, minhour = 60, hourday = 24, & sechour = secmin*minhour, & secday = secmin*minhour*hourday integer, parameter :: monyear = 12 integer, parameter :: halfday = secday/2 integer :: yrmod, momod, dymod logical :: mod_leapyear ! Include variable "version" to be written to log file. #include logical :: module_is_initialized=.FALSE. logical :: perthlike_behavior=.FALSE. namelist / time_interp_nml / perthlike_behavior contains subroutine time_interp_init() integer :: ierr, io, namelist_unit, logunit if ( module_is_initialized ) return #ifdef INTERNAL_FILE_NML read (input_nml_file, time_interp_nml, iostat=io) ierr = check_nml_error (io, 'time_interp_nml') #else namelist_unit = open_namelist_file() ierr=1 do while (ierr /= 0) read(namelist_unit, nml=time_interp_nml, iostat=io, end=20) ierr = check_nml_error (io, 'time_interp_nml') enddo 20 call close_file (namelist_unit) #endif call write_version_number("TIME_INTERP_MOD", version) logunit = stdlog() write(logunit,time_interp_nml) module_is_initialized = .TRUE. end subroutine time_interp_init !####################################################################### ! ! ! ! ! returns the fractional time into the current year subroutine time_interp_frac ( Time, weight ) type(time_type), intent(in) :: Time real , intent(out) :: weight integer :: year, month, day, hour, minute, second type(time_type) :: Year_beg, Year_end if ( .not. module_is_initialized ) call time_interp_init ! ---- compute fractional time of year ----- call get_date (Time, year, month, day, hour, minute, second) Year_beg = set_date(year , 1, 1) Year_end = set_date(year+1, 1, 1) weight = (Time - Year_beg) // (Year_end - Year_beg) end subroutine time_interp_frac !####################################################################### ! ! ! Wrapper for backward compatibility ! ! function fraction_of_year (Time) type(time_type), intent(in) :: Time real :: fraction_of_year call time_interp_frac ( Time, fraction_of_year ) end function fraction_of_year !####################################################################### ! ! ! ! ! ! ! returns fractional time between mid points of consecutive years subroutine time_interp_year ( Time, weight, year1, year2 ) type(time_type), intent(in) :: Time real , intent(out) :: weight integer , intent(out) :: year1, year2 integer :: year, month, day, hour, minute, second type (time_type) :: Mid_year, Mid_year1, Mid_year2 if ( .not. module_is_initialized ) call time_interp_init() call get_date (Time, year, month, day, hour, minute, second) ! mid point of current year Mid_year = year_midpt(year) if ( Time >= Mid_year ) then ! current time is after mid point of current year year1 = year year2 = year+1 Mid_year2 = year_midpt(year2) weight = (Time - Mid_year) // (Mid_year2 - Mid_year) else ! current time is before mid point of current year year2 = year year1 = year-1 Mid_year1 = year_midpt(year1) weight = (Time - Mid_year1) // (Mid_year - Mid_year1) endif end subroutine time_interp_year !####################################################################### ! ! ! ! ! ! ! ! ! returns fractional time between mid points of consecutive months subroutine time_interp_month ( Time, weight, year1, year2, month1, month2 ) type(time_type), intent(in) :: Time real , intent(out) :: weight integer , intent(out) :: year1, year2, month1, month2 integer :: year, month, day, hour, minute, second, & mid_month, cur_month, mid1, mid2 if ( .not. module_is_initialized ) call time_interp_init() call get_date (Time, year, month, day, hour, minute, second) ! mid point of current month in seconds mid_month = days_in_month(Time) * halfday ! time into current month in seconds cur_month = second + secmin*minute + sechour*hour + secday*(day-1) if ( cur_month >= mid_month ) then ! current time is after mid point of current month year1 = year; month1 = month year2 = year; month2 = month+1 if (month2 > monyear) then year2 = year2+1; month2 = 1 endif mid1 = mid_month mid2 = days_in_month(set_date(year2,month2,2)) * halfday weight = real(cur_month - mid1) / real(mid1+mid2) else ! current time is before mid point of current month year2 = year; month2 = month year1 = year; month1 = month-1 if (month1 < 1) then year1 = year1-1; month1 = monyear endif if (year1>0) then mid1 = days_in_month(set_date(year1,month1,2)) * halfday else ! this can happen if we are at the beginning of year 1. In this case ! use December 0001 to calculate the duration of December 0000. ! This should work for all calendars mid1 = days_in_month(set_date(1,month1,2)) * halfday endif mid2 = mid_month weight = real(cur_month + mid1) / real(mid1+mid2) endif end subroutine time_interp_month !####################################################################### ! ! ! ! ! ! ! ! ! ! ! returns fractional time between mid points of consecutive days subroutine time_interp_day ( Time, weight, year1, year2, month1, month2, day1, day2 ) type(time_type), intent(in) :: Time real , intent(out) :: weight integer , intent(out) :: year1, year2, month1, month2, day1, day2 integer :: year, month, day, hour, minute, second, sday if ( .not. module_is_initialized ) call time_interp_init() call get_date (Time, year, month, day, hour, minute, second) ! time into current day in seconds sday = second + secmin*minute + sechour*hour if ( sday >= halfday ) then ! current time is after mid point of day year1 = year; month1 = month; day1 = day year2 = year; month2 = month; day2 = day + 1 weight = real(sday - halfday) / real(secday) if (day2 > days_in_month(Time)) then month2 = month2 + 1 day2 = 1 if (month2 > monyear) then month2 = 1; year2 = year2+1 endif endif else ! current time is before mid point of day year2 = year; month2 = month; day2 = day year1 = year; month1 = month; day1 = day - 1 weight = real(sday + halfday) / real(secday) if (day1 < 1) then month1 = month1 - 1 if (month1 < 1) then month1 = monyear; year1 = year1-1 endif day1 = days_in_month(set_date(year1,month1,2)) endif endif end subroutine time_interp_day !####################################################################### ! ! ! ! ! ! ! Turns on a kluge for an inconsistency which may occur in a special case. ! When the modulo time period (i.e. Time_end - Time_beg) is a whole number of years ! and is not a multiple of 4, and the calendar in use has leap years, then it is ! likely that the interpolation will involve mapping a common year onto a leap year. ! In this case it is often desirable, but not absolutely necessary, to use data for ! Feb 28 of the leap year when it is mapped onto a common year. ! To turn this on, set correct_leap_year_inconsistency=.true. ! ! ! ! subroutine time_interp_modulo(Time, Time_beg, Time_end, Timelist, weight, index1, index2, & correct_leap_year_inconsistency, err_msg) type(time_type), intent(in) :: Time, Time_beg, Time_end, Timelist(:) real , intent(out) :: weight integer , intent(out) :: index1, index2 logical, intent(in), optional :: correct_leap_year_inconsistency character(len=*), intent(out), optional :: err_msg type(time_type) :: Period, T integer :: is, ie,i1,i2 integer :: ys,ms,ds,hs,mins,ss ! components of the starting date integer :: ye,me,de,he,mine,se ! components of the ending date integer :: yt,mt,dt,ht,mint,st ! components of the current date integer :: dt1 ! temporary value for day integer :: n ! size of Timelist integer :: stdoutunit logical :: correct_lyr, calendar_has_leap_years, do_the_lyr_correction if ( .not. module_is_initialized ) call time_interp_init if( present(err_msg) ) err_msg = '' stdoutunit = stdout() n = size(Timelist) if (Time_beg>=Time_end) then if(fms_error_handler('time_interp_modulo', & 'end of the specified time loop interval must be later than its beginning',err_msg)) return endif calendar_has_leap_years = (get_calendar_type() == JULIAN .or. get_calendar_type() == GREGORIAN) Period = Time_end-Time_beg ! period of the time axis if(present(correct_leap_year_inconsistency)) then correct_lyr = correct_leap_year_inconsistency else correct_lyr = .false. endif ! bring the requested time inside the specified time period T = Time do_the_lyr_correction = .false. ! Determine if the leap year correction needs to be done. ! It never needs to be done unless 3 conditions are met: ! 1) We are using a calendar with leap years ! 2) optional argument correct_leap_year_inconsistency is present and equals .true. ! 3) The modulo time period is an integer number of years ! If all of these are true then set do_the_lyr_correction to .true. if(calendar_has_leap_years .and. correct_lyr) then call get_date(Time_beg,ys,ms,ds,hs,mins,ss) call get_date(Time_end,ye,me,de,he,mine,se) if(ms==me.and.ds==de.and.hs==he.and.mins==mine.and.ss==se) then ! whole number of years do_the_lyr_correction = .true. endif endif if(do_the_lyr_correction) then call get_date(T,yt,mt,dt,ht,mint,st) yt = ys+modulo(yt-ys,ye-ys) dt1 = dt ! If it is Feb 29, but we map into a common year, use Feb 28 if(mt==2.and.dt==29.and..not.leap_year(set_date(yt,1,1))) dt1=28 T = set_date(yt,mt,dt1,ht,mint,st) if (T < Time_beg) then ! the requested time is within the first year, ! but before the starting date. So we shift it to the last year. if(mt==2.and.dt==29.and..not.leap_year(set_date(ye,1,1))) dt=28 T = set_date(ye,mt,dt,ht,mint,st) endif else do while ( T >= Time_end ) T = T-Period enddo do while ( T < Time_beg ) T = T+Period enddo endif ! find indices of the first and last records in the Timelist that are within ! the requested time period. if (Time_end<=Timelist(1).or.Time_beg>=Timelist(n)) then if(get_calendar_type() == NO_CALENDAR) then call print_time(Time_beg, 'Time_beg' ) call print_time(Time_end, 'Time_end' ) call print_time(Timelist(1), 'Timelist(1)' ) call print_time(Timelist(n), 'Timelist(n)' ) else call print_date(Time_beg, 'Time_beg' ) call print_date(Time_end, 'Time_end' ) call print_date(Timelist(1), 'Timelist(1)' ) call print_date(Timelist(n), 'Timelist(n)' ) endif write(stdoutunit,*)'where n = size(Timelist) =',n if(fms_error_handler('time_interp_modulo', & 'the entire time list is outside the specified time loop interval',err_msg)) return endif call bisect(Timelist,Time_beg,index1=i1,index2=i2) if (i1 < 1) then is = 1 ! Time_beg before lower boundary else if (Time_beg == Timelist(i1)) then is = i1 ! Time_beg right on the lower boundary else is = i2 ! Time_beg inside the interval or on upper boundary endif call bisect(Timelist,Time_end,index1=i1,index2=i2) if (Time_end > Timelist(i1)) then ie = i1 else if (Time_end == Timelist(i1)) then if(Time_beg == Timelist(is)) then ! Timelist includes time levels at both the lower and upper ends of the period. ! The endpoints of Timelist specify the same point in the cycle. ! This ambiguity is resolved by ignoring the last time level. ie = i1-1 else ie = i1 endif else ! This should never happen because bisect does not return i1 such that Time_end < Timelist(i1) endif if (is>=ie) then if(get_calendar_type() == NO_CALENDAR) then call print_time(Time_beg, 'Time_beg =') call print_time(Time_end, 'Time_end =') call print_time(Timelist(1), 'Timelist(1)=') call print_time(Timelist(n), 'Timelist(n)=') else call print_date(Time_beg, 'Time_beg =') call print_date(Time_end, 'Time_end =') call print_date(Timelist(1), 'Timelist(1)=') call print_date(Timelist(n), 'Timelist(n)=') endif write(stdoutunit,*)'where n = size(Timelist) =',n write(stdoutunit,*)'is =',is,'ie =',ie if(fms_error_handler('time_interp_modulo', & 'error in calculation of time list bounds within the specified time loop interval',err_msg)) return endif ! handle special cases: if( T>=Timelist(ie) ) then ! time is after the end of the portion of the time list within the requested period index1 = ie; index2 = is weight = (T-Timelist(ie))//(Period-(Timelist(ie)-Timelist(is))) else if (T 1) i = (iu+il)/2 if(Timelist(i) > Time) then iu = i else il = i endif enddo i1 = il ; i2 = il+1 endif if(PRESENT(index1)) index1 = i1 if(PRESENT(index2)) index2 = i2 end subroutine bisect !####################################################################### ! ! ! ! ! ! ! ! subroutine time_interp_list ( Time, Timelist, weight, index1, index2, modtime, err_msg ) type(time_type) , intent(in) :: Time, Timelist(:) real , intent(out) :: weight integer , intent(out) :: index1, index2 integer, optional, intent(in) :: modtime character(len=*), intent(out), optional :: err_msg integer :: n, hr, mn, se, mtime type(time_type) :: T, Ts, Te, Td, Period, Time_mod character(len=:),allocatable :: terr, tserr, teerr if ( .not. module_is_initialized ) call time_interp_init if( present(err_msg) ) err_msg = '' weight = 0.; index1 = 0; index2 = 0 n = size(Timelist(:)) ! setup modular time axis? mtime = NONE if (present(modtime)) then mtime = modtime Time_mod = (Timelist(1)+Timelist(n))/2 call get_date (Time_mod, yrmod, momod, dymod, hr, mn, se) mod_leapyear = leap_year(Time_mod) endif ! set period for modulo axis select case (mtime) case (NONE) ! do nothing case (YEAR) Period = set_time(0,days_in_year(Time_mod)) case (MONTH) ! month length must be equal if (days_in_month(Time_mod) /= days_in_month(Time)) then if(fms_error_handler ('time_interp_list','modulo months must have same length',err_msg)) return endif Period = set_time(0,days_in_month(Time_mod)) case (DAY) Period = set_time(0,1) case default if(fms_error_handler ('time_interp_list','invalid value for argument modtime',err_msg)) return end select ! If modulo time is in effect and Timelist spans a time interval exactly equal to ! the modulo period, then the endpoints of Timelist specify the same point in the cycle. ! This ambiguity is resolved by ignoring the last time level. if (mtime /= NONE .and. Timelist(size(Timelist))-Timelist(1) == Period) then n = size(Timelist) - 1 else n = size(Timelist) endif ! starting and ending times from list Ts = Timelist(1) Te = Timelist(n) Td = Te-Ts T = set_modtime(Time,mtime) ! Check that Timelist does not span a time interval greater than the modulo period if (mtime /= NONE) then if (Td > Period) then if(fms_error_handler ('time_interp_list','period of list exceeds modulo period',err_msg)) return endif endif ! time falls on start or between start and end list values if ( T >= Ts .and. T < Te ) then call bisect(Timelist(1:n),T,index1,index2) weight = (T-Timelist(index1)) // (Timelist(index2)-Timelist(index1)) ! time falls before starting list value else if ( T < Ts ) then if (mtime == NONE) then call time_list_error(T,terr) call time_list_error(Ts,tserr) call time_list_error(Te,teerr) if(fms_error_handler ('time_interp_list',& 'time '//trim(terr)//' ('//date_to_string(T)//' is before range of list '//trim(tserr)//'-'//trim(teerr)//& '('//date_to_string(Ts)//' - '//date_to_string(Te)//')',& err_msg)) return deallocate(terr,tserr,teerr) endif Td = Te-Ts weight = 1. - ((Ts-T) // (Period-Td)) index1 = n index2 = 1 ! time falls on ending list value else if ( T == Te ) then if(perthlike_behavior) then weight = 1.0 index1 = n-1 index2 = n else weight = 0. index1 = n if (mtime == NONE) then index2 = n else index2 = 1 endif endif ! time falls after ending list value else if ( T > Te ) then if (mtime == NONE) then call time_list_error(T,terr) call time_list_error(Ts,tserr) call time_list_error(Te,teerr) if(fms_error_handler ('time_interp_list',& 'time '//trim(terr)//' ('//date_to_string(T)//' is after range of list '//trim(tserr)//'-'//trim(teerr)//& '('//date_to_string(Ts)//' - '//date_to_string(Te)//')',& err_msg)) return deallocate(terr,tserr,teerr) endif Td = Te-Ts weight = (T-Te) // (Period-Td) index1 = n index2 = 1 endif end subroutine time_interp_list !####################################################################### ! private routines !####################################################################### function year_midpt (year) integer, intent(in) :: year type (time_type) :: year_midpt, year_beg, year_end year_beg = set_date(year , 1, 1) year_end = set_date(year+1, 1, 1) year_midpt = (year_beg + year_end) / 2 end function year_midpt !####################################################################### function month_midpt (year, month) integer, intent(in) :: year, month type (time_type) :: month_midpt, month_beg, month_end ! --- beginning of this month --- month_beg = set_date(year, month, 1) ! --- start of next month --- if (month < 12) then month_end = set_date(year, month+1, 1) else month_end = set_date(year+1, 1, 1) endif month_midpt = (month_beg + month_end) / 2 end function month_midpt !####################################################################### function set_modtime (Tin, modtime) result (Tout) type(time_type), intent(in) :: Tin integer, intent(in), optional :: modtime type(time_type) :: Tout integer :: yr, mo, dy, hr, mn, se, mtime if(present(modtime)) then mtime = modtime else mtime = NONE endif select case (mtime) case (NONE) Tout = Tin case (YEAR) call get_date (Tin, yr, mo, dy, hr, mn, se) yr = yrmod ! correct leap year dates if (.not.mod_leapyear .and. mo == 2 .and. dy > 28) then mo = 3; dy = dy-28 endif Tout = set_date (yr, mo, dy, hr, mn, se) case (MONTH) call get_date (Tin, yr, mo, dy, hr, mn, se) yr = yrmod; mo = momod Tout = set_date (yr, mo, dy, hr, mn, se) case (DAY) call get_date (Tin, yr, mo, dy, hr, mn, se) yr = yrmod; mo = momod; dy = dymod Tout = set_date (yr, mo, dy, hr, mn, se) end select end function set_modtime !####################################################################### subroutine error_handler (string) character(len=*), intent(in) :: string call error_mesg ('time_interp_mod', trim(string), FATAL) ! write (*,'(a)') 'ERROR in time_interp: ' // trim(string) ! stop 111 end subroutine error_handler !####################################################################### end module time_interp_mod ! ! ! The list of input time types must have ascending dates. ! * ! ! The length of the current month for input Time and Time_list ! must be the same when using the modulo month option. ! The modulo month option is available but not supported. ! * ! ! The optional argument modtime must have a value set by one ! of the public parameters: NONE, YEAR, MONTH, DAY. ! The MONTH and DAY options are available but not supported. ! * ! ! The difference between the last and first values in the ! input Time list/array exceeds the length of the modulo period. ! * ! ! These errors occur when you are not using a modulo axis and the ! input Time occurs before the first value in the Time list/array ! or after the last value in the Time list/array. ! * ! ! For all routines in this module the calendar type in module ! time_manager must be set. ! ! ! The following private parameters are set by this module: !
!           seconds per minute = 60
!           minutes per hour   = 60
!           hours   per day    = 24
!           months  per year   = 12
! 
!
!