FMS  2024.03
Flexible Modeling System
time_interp.inc
1 !***********************************************************************
2 !* GNU Lesser General Public License
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* FMS is free software: you can redistribute it and/or modify it under
7 !* the terms of the GNU Lesser General Public License as published by
8 !* the Free Software Foundation, either version 3 of the License, or (at
9 !* your option) any later version.
10 !*
11 !* FMS is distributed in the hope that it will be useful, but WITHOUT
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 !* for more details.
15 !*
16 !* You should have received a copy of the GNU Lesser General Public
17 !* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
18 !***********************************************************************
19 
20 !> @addtogroup time_interp_mod
21 !> @{
22 
23  !> @brief Calculates the fractional time into the current year
24  subroutine time_interp_frac_ ( Time, weight )
25 
26  type(time_type), intent(in) :: Time
27  real(FMS_TI_KIND_), intent(out) :: weight !< fractional time
28 
29  integer :: yr, mo, dy, hour, minute, second
30  type(time_type) :: Year_beg, Year_end
31 
32 
33  if ( .not. module_is_initialized ) call time_interp_init
34 
35 ! ---- compute fractional time of year -----
36 
37  call get_date (time, yr, mo, dy, hour, minute, second)
38 
39  year_beg = set_date(yr , 1, 1)
40  year_end = set_date(yr+1, 1, 1)
41 
42  weight = real( (time - year_beg) // (year_end - year_beg) , kind=fms_ti_kind_)
43 
44  end subroutine time_interp_frac_
45 
46 
47  !> @brief Calculates fractional time between mid points of consecutive years
48  subroutine time_interp_year_ ( Time, weight, year1, year2 )
49 
50  type(time_type), intent(in) :: Time
51  real(FMS_TI_KIND_), intent(out) :: weight !< fractional time between midpoints of year1 and year2
52  integer , intent(out) :: year1, year2
53 
54  integer :: yr, mo, dy, hour, minute, second
55  type (time_type) :: Mid_year, Mid_year1, Mid_year2
56 
57 
58  if ( .not. module_is_initialized ) call time_interp_init()
59 
60  call get_date (time, yr, mo, dy, hour, minute, second)
61 
62  ! mid point of current year
63  mid_year = year_midpt(yr)
64 
65  if ( time >= mid_year ) then
66  ! current time is after mid point of current year
67  year1 = yr
68  year2 = yr+1
69  mid_year2 = year_midpt(year2)
70  weight = real( (time - mid_year) // (mid_year2 - mid_year) , kind=fms_ti_kind_ )
71  else
72  ! current time is before mid point of current year
73  year2 = yr
74  year1 = yr-1
75  mid_year1 = year_midpt(year1)
76  weight = real( (time - mid_year1) // (mid_year - mid_year1), kind=fms_ti_kind_ )
77  endif
78 
79  end subroutine time_interp_year_
80 
81  !> @brief Calculates fractional time between mid points of consecutive months
82  subroutine time_interp_month_ ( Time, weight, year1, year2, month1, month2 )
83 
84  type(time_type), intent(in) :: Time
85  real(FMS_TI_KIND_) , intent(out) :: weight
86  integer , intent(out) :: year1, year2, month1, month2
87 
88  integer :: yr, mo, dy, hour, minute, second, &
89  mid_month, cur_month, mid1, mid2
90 
91  if ( .not. module_is_initialized ) call time_interp_init()
92 
93  call get_date (time, yr, mo, dy, hour, minute, second)
94 
95  ! mid point of current month in seconds
96  mid_month = days_in_month(time) * halfday
97  ! time into current month in seconds
98  cur_month = second + secmin*minute + sechour*hour + secday*(dy-1)
99 
100  if ( cur_month >= mid_month ) then
101  ! current time is after mid point of current month
102  year1 = yr; month1 = mo
103  year2 = yr; month2 = mo+1
104  if (month2 > monyear) then
105  year2 = year2+1; month2 = 1
106  endif
107  mid1 = mid_month
108  mid2 = days_in_month(set_date(year2,month2,2)) * halfday
109  weight = real(cur_month - mid1, fms_ti_kind_) / real(mid1+mid2, fms_ti_kind_)
110  else
111  ! current time is before mid point of current month
112  year2 = yr; month2 = mo
113  year1 = yr; month1 = mo-1
114  if (month1 < 1) then
115  year1 = year1-1; month1 = monyear
116  endif
117  if (year1>0) then
118  mid1 = days_in_month(set_date(year1,month1,2)) * halfday
119  else
120  ! this can happen if we are at the beginning of year 1. In this case
121  ! use December 0001 to calculate the duration of December 0000.
122  ! This should work for all calendars
123  mid1 = days_in_month(set_date(1,month1,2)) * halfday
124  endif
125  mid2 = mid_month
126  weight = real(cur_month + mid1, fms_ti_kind_) / real(mid1+mid2, fms_ti_kind_)
127  endif
128 
129  end subroutine time_interp_month_
130 
131  !> @brief Calculates fractional time between mid points of consecutive days
132  subroutine time_interp_day_ ( Time, weight, year1, year2, month1, month2, day1, day2 )
133 
134  type(time_type), intent(in) :: Time
135  real(FMS_TI_KIND_), intent(out) :: weight
136  integer , intent(out) :: year1, year2, month1, month2, day1, day2
137 
138  integer :: yr, mo, dy, hour, minute, second, sday
139 
140  if ( .not. module_is_initialized ) call time_interp_init()
141 
142  call get_date (time, yr, mo, dy, hour, minute, second)
143 
144  ! time into current day in seconds
145  sday = second + secmin*minute + sechour*hour
146 
147  if ( sday >= halfday ) then
148  ! current time is after mid point of day
149  year1 = yr; month1 = mo; day1 = dy
150  year2 = yr; month2 = mo; day2 = dy + 1
151  weight = real(sday - halfday, fms_ti_kind_) / real(secday, fms_ti_kind_)
152 
153  if (day2 > days_in_month(time)) then
154  month2 = month2 + 1
155  day2 = 1
156  if (month2 > monyear) then
157  month2 = 1; year2 = year2+1
158  endif
159  endif
160  else
161  ! current time is before mid point of day
162  year2 = yr; month2 = mo ; day2 = dy
163  year1 = yr; month1 = mo; day1 = dy - 1
164  weight = real(sday + halfday,fms_ti_kind_) / real(secday,fms_ti_kind_)
165 
166  if (day1 < 1) then
167  month1 = month1 - 1
168  if (month1 < 1) then
169  month1 = monyear; year1 = year1-1
170  endif
171  day1 = days_in_month(set_date(year1,month1,2))
172  endif
173  endif
174 
175  end subroutine time_interp_day_
176 
177  !> Part of the time_interp interface, calculates for cyclical data
178  !! Time_beg and Time_end mark a repeating period
179  !!
180  !! Finds mid points and fractional weight for a time perioid
181 subroutine time_interp_modulo_(Time, Time_beg, Time_end, Timelist, weight, index1, index2, &
182  correct_leap_year_inconsistency, err_msg)
183 type(time_type), intent(in) :: Time !< a specific time value
184 type(time_type), intent(in) :: Time_beg !< begining of period to search with
185 type(time_type), intent(in) :: Time_end !< end of period to search with
186 type(time_type), intent(in) :: Timelist(:) !< ascending time values to search between
187 real(FMS_TI_KIND_) , intent(out) :: weight
188 integer , intent(out) :: index1, index2 !< indices of bounding time values within Timelist
189 logical, intent(in), optional :: correct_leap_year_inconsistency!< When true turns on a kluge for an
190  !! inconsistency which may occur in a special case.
191  !! When the modulo time period (i.e. Time_end - Time_beg) is a
192  !! whole number of years and is not a multiple of 4, and the calendar
193  !! in use has leap years, then it is likely that the interpolation
194  !! will involve mapping a common year onto a leap year. In this case
195  !! it is often desirable, but not absolutely necessary, to use data
196  !! for Feb 28 of the leap year when it is mapped onto a common year.
197 character(len=*), intent(out), optional :: err_msg
198 
199  type(time_type) :: Period, T
200  integer :: is, ie,i1,i2
201  integer :: ys,ms,ds,hs,mins,ss ! components of the starting date
202  integer :: ye,me,de,he,mine,se ! components of the ending date
203  integer :: yt,mt,dt,ht,mint,st ! components of the current date
204  integer :: dt1 ! temporary value for day
205  integer :: n ! size of Timelist
206  integer :: stdoutunit
207  logical :: correct_lyr, calendar_has_leap_years, do_the_lyr_correction
208  integer, parameter :: kindl = fms_ti_kind_
209 
210  if ( .not. module_is_initialized ) call time_interp_init
211  if( present(err_msg) ) err_msg = ''
212 
213  stdoutunit = stdout()
214  n = size(timelist)
215 
216  if (time_beg>=time_end) then
217  if(fms_error_handler('time_interp_modulo', &
218  'end of the specified time loop interval must be later than its beginning',err_msg)) return
219  endif
220 
221  calendar_has_leap_years = (get_calendar_type() == julian .or. get_calendar_type() == gregorian)
222 
223  period = time_end-time_beg ! period of the time axis
224 
225  if(present(correct_leap_year_inconsistency)) then
226  correct_lyr = correct_leap_year_inconsistency
227  else
228  correct_lyr = .false.
229  endif
230 
231  ! bring the requested time inside the specified time period
232  t = time
233 
234  do_the_lyr_correction = .false.
235 
236  ! Determine if the leap year correction needs to be done.
237  ! It never needs to be done unless 3 conditions are met:
238  ! 1) We are using a calendar with leap years
239  ! 2) optional argument correct_leap_year_inconsistency is present and equals .true.
240  ! 3) The modulo time period is an integer number of years
241  ! If all of these are true then set do_the_lyr_correction to .true.
242 
243  if(calendar_has_leap_years .and. correct_lyr) then
244  call get_date(time_beg,ys,ms,ds,hs,mins,ss)
245  call get_date(time_end,ye,me,de,he,mine,se)
246  if(ms==me.and.ds==de.and.hs==he.and.mins==mine.and.ss==se) then
247  ! whole number of years
248  do_the_lyr_correction = .true.
249  endif
250  endif
251 
252  if(do_the_lyr_correction) then
253  call get_date(t,yt,mt,dt,ht,mint,st)
254  yt = ys+modulo(yt-ys,ye-ys)
255  dt1 = dt
256  ! If it is Feb 29, but we map into a common year, use Feb 28
257  if(mt==2.and.dt==29.and..not.leap_year(set_date(yt,1,1))) dt1=28
258  t = set_date(yt,mt,dt1,ht,mint,st)
259  if (t < time_beg) then
260  ! the requested time is within the first year,
261  ! but before the starting date. So we shift it to the last year.
262  if(mt==2.and.dt==29.and..not.leap_year(set_date(ye,1,1))) dt=28
263  t = set_date(ye,mt,dt,ht,mint,st)
264  endif
265  else
266  do while ( t >= time_end )
267  t = t-period
268  enddo
269  do while ( t < time_beg )
270  t = t+period
271  enddo
272  endif
273 
274  ! find indices of the first and last records in the Timelist that are within
275  ! the requested time period.
276  if (time_end<=timelist(1).or.time_beg>=timelist(n)) then
277  if(get_calendar_type() == no_calendar) then
278  call print_time(time_beg, 'Time_beg' )
279  call print_time(time_end, 'Time_end' )
280  call print_time(timelist(1), 'Timelist(1)' )
281  call print_time(timelist(n), 'Timelist(n)' )
282  else
283  call print_date(time_beg, 'Time_beg' )
284  call print_date(time_end, 'Time_end' )
285  call print_date(timelist(1), 'Timelist(1)' )
286  call print_date(timelist(n), 'Timelist(n)' )
287  endif
288  write(stdoutunit,*)'where n = size(Timelist) =',n
289  if(fms_error_handler('time_interp_modulo', &
290  'the entire time list is outside the specified time loop interval',err_msg)) return
291  endif
292 
293  call bisect(timelist,time_beg,index1=i1,index2=i2)
294  if (i1 < 1) then
295  is = 1 ! Time_beg before lower boundary
296  else if (time_beg == timelist(i1)) then
297  is = i1 ! Time_beg right on the lower boundary
298  else
299  is = i2 ! Time_beg inside the interval or on upper boundary
300  endif
301  call bisect(timelist,time_end,index1=i1,index2=i2)
302  if (time_end > timelist(i1)) then
303  ie = i1
304  else if (time_end == timelist(i1)) then
305  if(time_beg == timelist(is)) then
306  ! Timelist includes time levels at both the lower and upper ends of the period.
307  ! The endpoints of Timelist specify the same point in the cycle.
308  ! This ambiguity is resolved by ignoring the last time level.
309  ie = i1-1
310  else
311  ie = i1
312  endif
313  else
314 ! This should never happen because bisect does not return i1 such that Time_end < Timelist(i1)
315  endif
316  if (is>=ie) then
317  if(get_calendar_type() == no_calendar) then
318  call print_time(time_beg, 'Time_beg =')
319  call print_time(time_end, 'Time_end =')
320  call print_time(timelist(1), 'Timelist(1)=')
321  call print_time(timelist(n), 'Timelist(n)=')
322  else
323  call print_date(time_beg, 'Time_beg =')
324  call print_date(time_end, 'Time_end =')
325  call print_date(timelist(1), 'Timelist(1)=')
326  call print_date(timelist(n), 'Timelist(n)=')
327  endif
328  write(stdoutunit,*)'where n = size(Timelist) =',n
329  write(stdoutunit,*)'is =',is,'ie =',ie
330  if(fms_error_handler('time_interp_modulo', &
331  'error in calculation of time list bounds within the specified time loop interval',err_msg)) return
332  endif
333 
334  ! handle special cases:
335  if( t>=timelist(ie) ) then
336  ! time is after the end of the portion of the time list within the requested period
337  index1 = ie; index2 = is
338  weight = real((t-timelist(ie))//(period-(timelist(ie)-timelist(is))), fms_ti_kind_ )
339  else if (t<timelist(is)) then
340  ! time is before the beginning of the portion of the time list within the requested period
341  index1 = ie; index2 = is
342  weight = 1.0_kindl - real(((timelist(is)-t)//(period-(timelist(ie)-timelist(is)))), fms_ti_kind_ )
343  else
344  call bisect(timelist,t,index1,index2)
345  weight = real((t-timelist(index1)) // (timelist(index2)-timelist(index1)), fms_ti_kind_ )
346  endif
347 
348 end subroutine time_interp_modulo_
349 
350 subroutine time_interp_list_ ( Time, Timelist, weight, index1, index2, modtime, err_msg )
351 type(time_type) , intent(in) :: Time, Timelist(:)
352 real(FMS_TI_KIND_) , intent(out) :: weight
353 integer , intent(out) :: index1, index2
354 integer, optional, intent(in) :: modtime
355 character(len=*), intent(out), optional :: err_msg
356 
357 integer :: n, hr, mn, se, mtime
358 type(time_type) :: T, Ts, Te, Td, Period, Time_mod
359 character(len=:),allocatable :: terr, tserr, teerr
360 integer, parameter :: kindl = fms_ti_kind_
361 
362  if ( .not. module_is_initialized ) call time_interp_init
363 
364  if( present(err_msg) ) err_msg = ''
365 
366  weight = 0.0_kindl; index1 = 0; index2 = 0
367  n = size(timelist(:))
368 
369 ! setup modular time axis?
370  mtime = none
371  if (present(modtime)) then
372  mtime = modtime
373  time_mod = (timelist(1)+timelist(n))/2
374  call get_date (time_mod, yrmod, momod, dymod, hr, mn, se)
375  mod_leapyear = leap_year(time_mod)
376  endif
377 
378 ! set period for modulo axis
379  select case (mtime)
380  case (none)
381  ! do nothing
382  case (year)
383  period = set_time(0,days_in_year(time_mod))
384  case (month)
385  ! month length must be equal
386  if (days_in_month(time_mod) /= days_in_month(time)) then
387  if(fms_error_handler('time_interp_list','modulo months must have same length',err_msg)) return
388  endif
389  period = set_time(0,days_in_month(time_mod))
390  case (day)
391  period = set_time(0,1)
392  case default
393  if(fms_error_handler('time_interp_list','invalid value for argument modtime',err_msg)) return
394  end select
395 
396 ! If modulo time is in effect and Timelist spans a time interval exactly equal to
397 ! the modulo period, then the endpoints of Timelist specify the same point in the cycle.
398 ! This ambiguity is resolved by ignoring the last time level.
399  if (mtime /= none .and. timelist(size(timelist))-timelist(1) == period) then
400  n = size(timelist) - 1
401  else
402  n = size(timelist)
403  endif
404 
405 ! starting and ending times from list
406  ts = timelist(1)
407  te = timelist(n)
408  td = te-ts
409  t = set_modtime(time,mtime)
410 
411 ! Check that Timelist does not span a time interval greater than the modulo period
412  if (mtime /= none) then
413  if (td > period) then
414  if(fms_error_handler('time_interp_list','period of list exceeds modulo period',err_msg)) return
415  endif
416  endif
417 
418 ! time falls on start or between start and end list values
419  if ( t >= ts .and. t < te ) then
420  call bisect(timelist(1:n),t,index1,index2)
421  weight = real( (t-timelist(index1)) // (timelist(index2)-timelist(index1)), fms_ti_kind_)
422 
423 ! time falls before starting list value
424  else if ( t < ts ) then
425  if (mtime == none) then
426  call time_list_error(t,terr)
427  call time_list_error(ts,tserr)
428  call time_list_error(te,teerr)
429  if(fms_error_handler('time_interp_list',&
430  'time '//trim(terr)//' ('//date_to_string(t)//' is before range of list '//trim(tserr)//'-'//trim(teerr)//&
431  '('//date_to_string(ts)//' - '//date_to_string(te)//')',&
432  err_msg)) return
433  deallocate(terr,tserr,teerr)
434  endif
435  td = te-ts
436  weight = 1.0_kindl - real(((ts-t) // (period-td)), fms_ti_kind_ )
437  index1 = n
438  index2 = 1
439 
440 ! time falls on ending list value
441  else if ( t == te ) then
442  if(perthlike_behavior) then
443  weight = 1.0_kindl
444  index1 = n-1
445  index2 = n
446  else
447  weight = 0.0_kindl
448  index1 = n
449  if (mtime == none) then
450  index2 = n
451  else
452  index2 = 1
453  endif
454  endif
455 
456 ! time falls after ending list value
457  else if ( t > te ) then
458  if (mtime == none) then
459  call time_list_error(t,terr)
460  call time_list_error(ts,tserr)
461  call time_list_error(te,teerr)
462  if(fms_error_handler('time_interp_list',&
463  'time '//trim(terr)//' ('//date_to_string(t)//' is after range of list '//trim(tserr)//'-'//trim(teerr)//&
464  '('//date_to_string(ts)//' - '//date_to_string(te)//')',&
465  err_msg)) return
466  deallocate(terr,tserr,teerr)
467  endif
468  td = te-ts
469  weight = real( (t-te) // (period-td), fms_ti_kind_)
470  index1 = n
471  index2 = 1
472  endif
473 
474 end subroutine time_interp_list_
475 !> }
integer function stdout()
This function returns the current standard fortran unit numbers for output.
Definition: mpp_util.inc:43
subroutine time_interp_year_(Time, weight, year1, year2)
Calculates fractional time between mid points of consecutive years.
Definition: time_interp.inc:49
subroutine time_interp_month_(Time, weight, year1, year2, month1, month2)
Calculates fractional time between mid points of consecutive months.
Definition: time_interp.inc:83
subroutine time_interp_frac_(Time, weight)
Calculates the fractional time into the current year.
Definition: time_interp.inc:25
subroutine time_interp_modulo_(Time, Time_beg, Time_end, Timelist, weight, index1, index2, correct_leap_year_inconsistency, err_msg)
Part of the time_interp interface, calculates for cyclical data Time_beg and Time_end mark a repeatin...
subroutine time_interp_day_(Time, weight, year1, year2, month1, month2, day1, day2)
Calculates fractional time between mid points of consecutive days.