FMS  2024.03
Flexible Modeling System
fms_diag_reduction_methods.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 ! for any debug prints
21 #ifndef DEBUG_REDUCT
22 #define DEBUG_REDUCT .false.
23 #endif
24 
25 !> @brief Do the time_none reduction method (i.e copy the correct portion of the input data)
26 subroutine do_time_none_ (data_out, data_in, mask, is_masked, bounds_in, bounds_out, missing_value)
27  real(FMS_TRM_KIND_), intent(inout) :: data_out(:,:,:,:,:) !< output data
28  real(FMS_TRM_KIND_), intent(in) :: data_in(:,:,:,:) !< data to update the buffer with
29  logical, intent(in) :: mask(:,:,:,:) !< mask
30  logical, intent(in) :: is_masked !< .True. if the field is using a mask
31  type(fmsDiagIbounds_type), intent(in) :: bounds_in !< indices indicating the correct portion
32  !! of the input buffer
33  type(fmsDiagIbounds_type), intent(in) :: bounds_out !< indices indicating the correct portion
34  !! of the output buffer
35  real(FMS_TRM_KIND_), intent(in) :: missing_value !< Missing_value for data points that are masked
36 
37  integer :: is_in, ie_in, js_in, je_in, ks_in, ke_in !< Starting and ending indices of each dimention for
38  !! the input buffer
39  integer :: is_out, ie_out, js_out, je_out, ks_out, ke_out !< Starting and ending indices of each dimention for
40  !! the output buffer
41 
42  is_out = bounds_out%get_imin()
43  ie_out = bounds_out%get_imax()
44  js_out = bounds_out%get_jmin()
45  je_out = bounds_out%get_jmax()
46  ks_out = bounds_out%get_kmin()
47  ke_out = bounds_out%get_kmax()
48 
49  is_in = bounds_in%get_imin()
50  ie_in = bounds_in%get_imax()
51  js_in = bounds_in%get_jmin()
52  je_in = bounds_in%get_jmax()
53  ks_in = bounds_in%get_kmin()
54  ke_in = bounds_in%get_kmax()
55 
56  if (is_masked) then
57  where (mask(is_in:ie_in, js_in:je_in, ks_in:ke_in, :))
58  data_out(is_out:ie_out, js_out:je_out, ks_out:ke_out, :, 1) = &
59  data_in(is_in:ie_in, js_in:je_in, ks_in:ke_in, :)
60  elsewhere
61  data_out(is_out:ie_out, js_out:je_out, ks_out:ke_out, :, 1) = missing_value
62  end where
63  else
64  data_out(is_out:ie_out, js_out:je_out, ks_out:ke_out, :, 1) = &
65  data_in(is_in:ie_in, js_in:je_in, ks_in:ke_in, :)
66  endif
67 
68 end subroutine do_time_none_
69 
70 !> @brief Do the time_min reduction method (i.e maintain the minimum value of the averaging time)
71 subroutine do_time_min_ (data_out, data_in, mask, is_masked, bounds_in, bounds_out, missing_value)
72  real(FMS_TRM_KIND_), intent(inout) :: data_out(:,:,:,:,:) !< output data
73  real(FMS_TRM_KIND_), intent(in) :: data_in(:,:,:,:) !< data to update the buffer with
74  logical, intent(in) :: mask(:,:,:,:) !< mask
75  logical, intent(in) :: is_masked !< .True. if the field is using a mask
76  type(fmsDiagIbounds_type), intent(in) :: bounds_in !< indices indicating the correct portion
77  !! of the input buffer
78  type(fmsDiagIbounds_type), intent(in) :: bounds_out !< indices indicating the correct portion
79  !! of the output buffer
80  real(FMS_TRM_KIND_), intent(in) :: missing_value !< Missing_value for data points that are masked
81 
82  integer :: is_in, ie_in, js_in, je_in, ks_in, ke_in !< Starting and ending indices of each dimention for
83  !! the input buffer
84  integer :: is_out, ie_out, js_out, je_out, ks_out, ke_out !< Starting and ending indices of each dimention for
85  !! the output buffer
86 
87  integer :: i, j, k, l !< For looping
88 
89  is_out = bounds_out%get_imin()
90  ie_out = bounds_out%get_imax()
91  js_out = bounds_out%get_jmin()
92  je_out = bounds_out%get_jmax()
93  ks_out = bounds_out%get_kmin()
94  ke_out = bounds_out%get_kmax()
95 
96  is_in = bounds_in%get_imin()
97  ie_in = bounds_in%get_imax()
98  js_in = bounds_in%get_jmin()
99  je_in = bounds_in%get_jmax()
100  ks_in = bounds_in%get_kmin()
101  ke_in = bounds_in%get_kmax()
102 
103  !> Seperated this loops for performance. If is_masked = .false. (i.e "mask" and "rmask" were never passed in)
104  !! then mask will always be .True. so the if (mask) is redudant.
105  if (is_masked) then
106  do l = 0, size(data_out, 4) - 1
107  do k = 0, ke_out - ks_out
108  do j = 0, je_out - js_out
109  do i = 0, ie_out - is_out
110  if (mask(is_in + i, js_in + j, ks_in + k, l + 1)) then
111  if (data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) .gt. &
112  data_in(is_in + i, js_in + j, ks_in + k, l + 1) ) then
113  data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) = &
114  data_in(is_in +i, js_in + j, ks_in + k, l + 1)
115  endif
116  else
117  data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) = missing_value
118  endif
119  enddo
120  enddo
121  enddo
122  enddo
123  else
124  do l = 0, size(data_out, 4) - 1
125  do k = 0, ke_out - ks_out
126  do j = 0, je_out - js_out
127  do i = 0, ie_out - is_out
128  if (data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) .gt. &
129  data_in(is_in + i, js_in + j, ks_in + k, l + 1) ) then
130  data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) = &
131  data_in(is_in +i, js_in + j, ks_in + k, l + 1)
132  endif
133  enddo
134  enddo
135  enddo
136  enddo
137  endif
138 
139 end subroutine do_time_min_
140 
141 !> @brief Do the time_max reduction method (i.e maintain the maximum value of the averaging time)
142 subroutine do_time_max_ (data_out, data_in, mask, is_masked, bounds_in, bounds_out, missing_value)
143  real(FMS_TRM_KIND_), intent(inout) :: data_out(:,:,:,:,:) !< output data
144  real(FMS_TRM_KIND_), intent(in) :: data_in(:,:,:,:) !< data to update the buffer with
145  logical, intent(in) :: mask(:,:,:,:) !< mask
146  logical, intent(in) :: is_masked !< .True. if the field is using a mask
147  type(fmsDiagIbounds_type), intent(in) :: bounds_in !< indices indicating the correct portion
148  !! of the input buffer
149  type(fmsDiagIbounds_type), intent(in) :: bounds_out !< indices indicating the correct portion
150  !! of the output buffer
151  real(FMS_TRM_KIND_), intent(in) :: missing_value !< Missing_value for data points that are masked
152 
153  integer :: is_in, ie_in, js_in, je_in, ks_in, ke_in !< Starting and ending indices of each dimention for
154  !! the input buffer
155  integer :: is_out, ie_out, js_out, je_out, ks_out, ke_out !< Starting and ending indices of each dimention for
156  !! the output buffer
157 
158  integer :: i, j, k, l !< For looping
159 
160  is_out = bounds_out%get_imin()
161  ie_out = bounds_out%get_imax()
162  js_out = bounds_out%get_jmin()
163  je_out = bounds_out%get_jmax()
164  ks_out = bounds_out%get_kmin()
165  ke_out = bounds_out%get_kmax()
166 
167  is_in = bounds_in%get_imin()
168  ie_in = bounds_in%get_imax()
169  js_in = bounds_in%get_jmin()
170  je_in = bounds_in%get_jmax()
171  ks_in = bounds_in%get_kmin()
172  ke_in = bounds_in%get_kmax()
173 
174  !> Seperated this loops for performance. If is_masked = .false. (i.e "mask" and "rmask" were never passed in)
175  !! then mask will always be .True. so the if (mask) is redudant.
176  if (is_masked) then
177  do l = 0, size(data_out, 4) - 1
178  do k = 0, ke_out - ks_out
179  do j = 0, je_out - js_out
180  do i = 0, ie_out - is_out
181  if (mask(is_in + i, js_in + j, ks_in + k, l + 1)) then
182  if (data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) .lt. &
183  data_in(is_in + i, js_in + j, ks_in + k, l + 1) ) then
184  data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) = &
185  data_in(is_in +i, js_in + j, ks_in + k, l + 1)
186  endif
187  else
188  data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) = missing_value
189  endif
190  enddo
191  enddo
192  enddo
193  enddo
194  else
195  do l = 0, size(data_out, 4) - 1
196  do k = 0, ke_out - ks_out
197  do j = 0, je_out - js_out
198  do i = 0, ie_out - is_out
199  if (data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) .lt. &
200  data_in(is_in + i, js_in + j, ks_in + k, l + 1) ) then
201  data_out(is_out + i, js_out + j, ks_out + k, l + 1, 1) = &
202  data_in(is_in +i, js_in + j, ks_in + k, l + 1)
203  endif
204  enddo
205  enddo
206  enddo
207  enddo
208  endif
209 end subroutine do_time_max_
210 
211 !> Update the output buffer for reductions that involve summation (sum, avg, rms, pow).
212 !! Elements of the running field output buffer (data_out) are set with the following:
213 !!
214 !! buffer(l) = buffer(l) + (weight * field(l)) ^ pow
215 !!
216 !! Where l are the indices passed in through the bounds_in/out
217 subroutine do_time_sum_update_(data_out, weight_sum, data_in, mask, is_masked, mask_variant, bounds_in, bounds_out, &
218  missing_value, diurnal_section, weight, pow)
219  real(FMS_TRM_KIND_), intent(inout) :: data_out(:,:,:,:,:) !< output data
220  real(r8_kind), intent(inout) :: weight_sum(:,:,:,:) !< Sum of weights from the output buffer object
221  real(FMS_TRM_KIND_), intent(in) :: data_in(:,:,:,:) !< data to update the buffer with
222  logical, intent(in) :: mask(:,:,:,:) !< mask
223  logical, intent(in) :: is_masked !< .True. if the field is using a mask
224  logical, intent(in) :: mask_variant !< .True. if the mask changes over time
225  type(fmsDiagIbounds_type), intent(in) :: bounds_in !< indices indicating the correct portion
226  !! of the input buffer
227  type(fmsDiagIbounds_type), intent(in) :: bounds_out !< indices indicating the correct portion
228  !! of the output buffer
229  real(FMS_TRM_KIND_), intent(in) :: missing_value !< Missing_value for data points that are masked
230  integer, intent(in) :: diurnal_section !< the diurnal "section" if doing a diurnal reduction
231  !! indicates which index to add data on 5th axis
232  !! if not doing a diurnal reduction, this should always =1
233  real(r8_kind),optional, intent(in) :: weight !< Weight applied to data_in before added to data_out
234  !! used for weighted averages, default 1.0
235  integer ,optional, intent(in) :: pow !< Used for pow(er) reduction,
236  !! calculates field_data^pow before adding to buffer
237 
238  integer :: is_in, ie_in, js_in, je_in, ks_in, ke_in !< Starting and ending indices of each dimention for
239  !! the input buffer
240  integer :: is_out, ie_out, js_out, je_out, ks_out, ke_out !< Starting and ending indices of each dimention for
241  !! the output buffer
242  integer :: i, j, k, l !< For looping
243  real(FMS_TRM_KIND_) :: weight_scale !< local copy of optional weight
244  integer :: pow_loc !> local copy of optional pow value (set if using pow reduction)
245  integer, parameter :: kindl = fms_trm_kind_ !< real kind size as set by macro
246  integer :: diurnal !< diurnal index to indicate which daily section is updated
247  !! will be 1 unless using a diurnal reduction
248 
249  if(present(weight)) then
250  weight_scale = real(weight, kind=kindl)
251  else
252  weight_scale = 1.0_kindl
253  endif
254 
255  if(present(pow)) then
256  pow_loc = pow
257  else
258  pow_loc = 1.0_kindl
259  endif
260 
261  if(diurnal_section .lt. 0) then
262  diurnal = 1
263  else
264  diurnal = diurnal_section
265  endif
266 
267  is_out = bounds_out%get_imin()
268  ie_out = bounds_out%get_imax()
269  js_out = bounds_out%get_jmin()
270  je_out = bounds_out%get_jmax()
271  ks_out = bounds_out%get_kmin()
272  ke_out = bounds_out%get_kmax()
273 
274  is_in = bounds_in%get_imin()
275  ie_in = bounds_in%get_imax()
276  js_in = bounds_in%get_jmin()
277  je_in = bounds_in%get_jmax()
278  ks_in = bounds_in%get_kmin()
279  ke_in = bounds_in%get_kmax()
280 
281  !> Seperated this loops for performance. If is_masked = .false. (i.e "mask" and "rmask" were never passed in)
282  !! then mask will always be .True. so the if (mask) is redudant.
283  ! TODO check if performance gain by not doing weight and pow if not needed
284  if (is_masked) then
285  if (mask_variant) then
286  ! Mask changes over time so the weight is an array
287  do k = 0, ke_out - ks_out
288  do j = 0, je_out - js_out
289  do i = 0, ie_out - is_out
290  where (mask(is_in + i, js_in + j, ks_in + k, :))
291  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) = &
292  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) &
293  + (data_in(is_in +i, js_in + j, ks_in + k, :) * weight_scale) ** pow_loc
294  !Increase the weight sum for the grid point that was not masked
295  weight_sum(is_out + i, js_out + j, ks_out + k, :) = &
296  weight_sum(is_out + i, js_out + j, ks_out + k, :) + weight_scale
297  endwhere
298  enddo
299  enddo
300  enddo
301  else
302  weight_sum = weight_sum + weight_scale
303  do k = 0, ke_out - ks_out
304  do j = 0, je_out - js_out
305  do i = 0, ie_out - is_out
306  where (mask(is_in + i, js_in + j, ks_in + k, :))
307  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) = &
308  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) &
309  + (data_in(is_in +i, js_in + j, ks_in + k, :) * weight_scale) ** pow_loc
310  elsewhere
311  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) = missing_value
312  endwhere
313  enddo
314  enddo
315  enddo
316  endif
317  else
318  weight_sum = weight_sum + weight_scale
319  ! doesn't need to loop through l if no mask, just sums the 1d slices
320  do k = 0, ke_out - ks_out
321  do j = 0, je_out - js_out
322  do i = 0, ie_out - is_out
323  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) = &
324  data_out(is_out + i, js_out + j, ks_out + k, :, diurnal) &
325  + (data_in(is_in +i, js_in + j, ks_in + k, :) * weight_scale) ** pow_loc
326  enddo
327  enddo
328  enddo
329  endif
330 end subroutine do_time_sum_update_
331 
332 !> To be called with diag_send_complete, finishes reductions
333 !! Just divides the buffer by the counter array(which is just the sum of the weights used in the buffer's reduction)
334 !! TODO: change has_mask to an actual logical mask so we don't have to check for missing values
335 subroutine sum_update_done_(out_buffer_data, weight_sum, reduction_method, missing_val, has_mask, mask_variant, &
336  n_diurnal_samples)
337  real(FMS_TRM_KIND_), intent(inout) :: out_buffer_data(:,:,:,:,:) !< data buffer previously updated with
338  !! do_time_sum_update
339  real(r8_kind), intent(in) :: weight_sum(:,:,:,:) !< sum of weights for averaging,
340  !! provided via argument to send data
341  integer, intent(in) :: reduction_method !< which reduction method to use
342  !! should always be one of time_avg, time_diurnal, or time_rms
343  real(FMS_TRM_KIND_), intent(in) :: missing_val !< missing value for masked elements
344  logical, intent(in) :: has_mask !< indicates if mask is used so missing values can be skipped
345  logical, intent(in) :: mask_variant !< Indicates if the mask changes over time
346  integer, optional, intent(in) :: n_diurnal_samples !< number of diurnal samples as set in reduction method
347  integer, allocatable :: wsum(:,:,:,:) !< local cp of weight_sum, only changed if using diurnal
348  !! TODO replace conditional in the `where` with passed in and ajusted mask from the original call
349  !logical, optional, intent(in) :: mask(:,:,:,:) !< logical mask from accept data call, if using one.
350  !logical :: has_mask !< whether or not mask is present
351 
352  integer :: i, j, k, l !< For do loops
353 
354  allocate(wsum(size(weight_sum,1), size(weight_sum,3), size(weight_sum,3), size(weight_sum,4)))
355  ! need to divide weight sum by amount of samples to get the actual
356  ! number of times that the diurnal section was incremented
357  ! legacy diag manager stored these weights explicitly, this doesn't so assumes uniformity in when data is sent
358  if(reduction_method .eq. time_diurnal) then
359  if(.not. present(n_diurnal_samples)) call mpp_error(fatal, &
360  "SUM_UPDATE_DONE_ :: reduction method is diurnal but no sample size was given")
361  wsum = weight_sum / n_diurnal_samples
362  else
363  wsum = weight_sum
364  endif
365 
366  if ( has_mask ) then
367  if (.not. mask_variant) then
368  ! The mask does not change over time so wsum is just an integer and it is the same value for all fields
369  where(out_buffer_data(:,:,:,:,:) .ne. missing_val)
370  out_buffer_data(:,:,:,:,:) = out_buffer_data(:,:,:,:,:) &
371  / wsum(1,1,1,1)
372  endwhere
373  else
374  ! The mask changes over time
375  do l = 1, size(out_buffer_data, 4)
376  do k = 1, size(out_buffer_data, 3)
377  do j = 1, size(out_buffer_data, 2)
378  do i = 1, size(out_buffer_data, 1)
379  if (wsum(i, j, k, l) .gt. 0) then
380  out_buffer_data(i,j,k,l,:) = out_buffer_data(i,j,k,l,:)/ wsum(i,j,k,l)
381  else
382  ! Data was never received
383  out_buffer_data(i,j,k,l,:) = missing_val
384  endif
385  enddo
386  enddo
387  enddo
388  enddo
389  endif
390  else
391  ! There is no mask!
392  out_buffer_data(:,:,:,:,:) = out_buffer_data(:,:,:,:,:) &
393  / wsum(1,1,1,1)
394  endif
395 
396  if(reduction_method .eq. time_rms .and. has_mask) then
397  where(out_buffer_data(:,:,:,:,1) .ne. missing_val)
398  out_buffer_data(:,:,:,:,1) = sqrt(out_buffer_data(:,:,:,:,1))
399  endwhere
400  else if(reduction_method .eq. time_rms) then
401  out_buffer_data(:,:,:,:,1) = sqrt(out_buffer_data(:,:,:,:,1))
402  endif
403 
404 end subroutine
405