MOM_coms.F90

1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> Interfaces to non-domain-oriented communication subroutines, including the
6!! MOM6 reproducing sums facility
7module mom_coms
8
9use, intrinsic :: iso_fortran_env, only : int64
10use mom_coms_infra, only : pe_here, root_pe, num_pes, set_rootpe, set_pelist, get_pelist
11use mom_coms_infra, only : broadcast, field_chksum, mom_infra_init, mom_infra_end
12use mom_coms_infra, only : sum_across_pes, max_across_pes, min_across_pes
13use mom_coms_infra, only : all_across_pes, any_across_pes
14use mom_error_handler, only : mom_error, mom_mesg, fatal, warning
15use mom_coms_infra, only : sync_pes
16
17implicit none ; private
18
19public :: pe_here, root_pe, num_pes, mom_infra_init, mom_infra_end
20public :: sync_pes
21public :: broadcast, sum_across_pes, min_across_pes, max_across_pes, field_chksum
22public :: all_across_pes, any_across_pes
23public :: set_pelist, get_pelist, set_rootpe
24public :: reproducing_sum, reproducing_sum_efp, efp_sum_across_pes, efp_list_sum_across_pes
26public :: operator(+), operator(-), assignment(=)
28
29integer, parameter :: accum_width = digits(1_int64)
30 !< Accumulator width; total available bits for summation (excluding sign bit)
31integer, parameter :: prec_width = 46
32 !< Precision width; total bits for computed results
33integer, parameter :: guard_width = accum_width - prec_width
34 !< Number of guard bits reserved for carry overflow
35
36! A sum of N points does N - 1 additions, which at most adds N - 1 carry bits.
37! For G guard bits, the maximum value is 2**G - 1. A summation of N values
38! therefore requires that N - 1 <= 2**G - 1, or simply N <= 2**G.
39
40integer, parameter :: max_summands = 2**guard_width
41 !< Maximum number of summable points that can guarantee no carry overflow.
42 !! Assumes that guard_bits is less than number of bits in a default integer.
43
44integer(kind=int64), parameter :: prec = (2_int64)**prec_width
45 !< EPF upper bound (exclusive). For each EPF bin e(i), 0 <= e(i) < prec.
46
47real, parameter :: r_prec = 2.**prec_width
48 !< Real-value of prec [nondim]
49real, parameter :: i_prec = 2.**(-prec_width)
50 !< Inverse real-value of prec [nondim]
51
52integer, parameter :: efp_digits = 6
53 !< The number of base `prec` digits used to represent an EFP value.
54real, parameter, dimension(efp_digits) :: &
55 pr = [r_prec**2, r_prec, 1., r_prec**(-1), r_prec**(-2), r_prec**(-3)]
56 !< An array of the real precision of each of the integers in arbitrary
57 !! units [a]
58real, parameter, dimension(efp_digits) :: &
59 i_pr = [r_prec**(-2), r_prec**(-1), 1., r_prec, r_prec**2, r_prec**3]
60 !< An array of the inverse of the real precision of each of the integers in
61 !! arbitrary units [a-1]
62real, parameter :: max_efp_float = pr(1) * real(huge(1_int64))
63 !< The largest float with an EFP representation in arbitrary units [a].
64 !! NOTE: Only the first bin can exceed precision, but is bounded by the
65 !! largest signed integer.
66
67logical :: overflow_error = .false.
68 !< This becomes true if an overflow is encountered.
69logical :: nan_error = .false.
70 !< This becomes true if a NaN is encountered.
71logical :: debug = .false.
72 !< Making this true enables debugging output.
73
74! This module provides interfaces to the non-domain-oriented communication subroutines.
75
76!> Find an accurate and order-invariant sum of a distributed 2d or 3d field, in some cases after
77!! undoing the scaling of the input array and restoring that scaling in the returned value
78interface reproducing_sum
80end interface reproducing_sum
81
82!> Find an accurate and order-invariant sum of a distributed 2d field, returning the result
83!! in the form of an extended fixed point value that can be converted back with EFP_to_real.
84interface reproducing_sum_efp
85 module procedure reproducing_efp_sum_2d
86end interface reproducing_sum_efp
87
88!> Sum a value or 1-d array of values across processors, returning the sums in place
89interface efp_sum_across_pes
91end interface efp_sum_across_pes
92
93!> The Extended Fixed Point (EFP) type provides a public interface for doing sums
94!! and taking differences with this type.
95!!
96!! The use of this type is documented in
97!! Hallberg, R. & A. Adcroft, 2014: An Order-invariant Real-to-Integer Conversion Sum.
98!! Parallel Computing, 40(5-6), doi:10.1016/j.parco.2014.04.007.
99type, public :: efp_type ; private
100 integer(kind=int64), dimension(efp_digits) :: v !< The value in this type
101end type efp_type
102
103!> Add two extended-fixed-point numbers
104interface operator (+) ; module procedure EFP_plus ; end interface
105!> Subtract one extended-fixed-point number from another
106interface operator (-) ; module procedure EFP_minus ; end interface
107!> Copy the value of one extended-fixed-point number into another
108interface assignment(=); module procedure EFP_assign ; end interface
109
110contains
111
112!> This subroutine uses a conversion to an integer representation of real numbers to give an
113!! order-invariant sum of distributed 2-D arrays that reproduces across domain decomposition, with
114!! the result returned as an extended fixed point type that can be converted back to a real number
115!! using EFP_to_real. This technique is described in Hallberg & Adcroft, 2014, Parallel Computing,
116!! doi:10.1016/j.parco.2014.04.007.
117function reproducing_efp_sum_2d(array, isr, ier, jsr, jer, overflow_check, err, only_on_PE, unscale) result(EFP_sum)
118 real, dimension(:,:), intent(in) :: array !< The array to be summed in arbitrary units [a], or in
119 !! arbitrary scaled units [A ~> a] if unscale is present
120 integer, optional, intent(in) :: isr !< The starting i-index of the sum, noting
121 !! that the array indices starts at 1
122 integer, optional, intent(in) :: ier !< The ending i-index of the sum, noting
123 !! that the array indices starts at 1
124 integer, optional, intent(in) :: jsr !< The starting j-index of the sum, noting
125 !! that the array indices starts at 1
126 integer, optional, intent(in) :: jer !< The ending j-index of the sum, noting
127 !! that the array indices starts at 1
128 logical, optional, intent(in) :: overflow_check !< If present and false, disable
129 !! checking for overflows in incremental results.
130 !! This can speed up calculations if the number
131 !! of values being summed is small enough
132 integer, optional, intent(out) :: err !< If present, return an error code instead of
133 !! triggering any fatal errors directly from
134 !! this routine.
135 logical, optional, intent(in) :: only_on_pe !< If present and true, do not do the sum
136 !! across processors, only reporting the local sum
137 real, optional, intent(in) :: unscale !< A factor that is used to undo scaling of array before it is
138 !! summed, often to compensate for the scaling in [a A-1 ~> 1]
139 type(efp_type) :: efp_sum !< The result in extended fixed point format
140
141 ! This subroutine uses a conversion to an integer representation
142 ! of real numbers to give order-invariant sums that will reproduce
143 ! across PE count. This idea comes from R. Hallberg and A. Adcroft.
144
145 integer(kind=int64), dimension(efp_digits) :: ints_sum
146 integer(kind=int64) :: ival, prec_error
147 real :: rs ! The remaining value to add, in arbitrary units [a]
148 real :: max_mag_term ! A running maximum magnitude of the values in arbitrary units [a]
149 real :: descale ! A local copy of unscale if it is present [a A-1 ~> 1] or 1
150 logical :: over_check, do_sum_across_pes, do_unscale
151 character(len=256) :: mesg
152 integer :: i, j, n, is, ie, js, je, sgn
153
154 if (num_pes() > max_summands) call mom_error(fatal, &
155 "reproducing_sum: Too many processors are being used for the value of "//&
156 "prec. Reduce prec to (2^63-1)/num_PEs.")
157
158 prec_error = huge(1_int64) / num_pes()
159
160 is = 1 ; ie = size(array,1) ; js = 1 ; je = size(array,2)
161 if (present(isr)) then
162 if (isr < is) call mom_error(fatal, "Value of isr too small in reproducing_EFP_sum_2d.")
163 is = isr
164 endif
165 if (present(ier)) then
166 if (ier > ie) call mom_error(fatal, "Value of ier too large in reproducing_EFP_sum_2d.")
167 ie = ier
168 endif
169 if (present(jsr)) then
170 if (jsr < js) call mom_error(fatal, "Value of jsr too small in reproducing_EFP_sum_2d.")
171 js = jsr
172 endif
173 if (present(jer)) then
174 if (jer > je) call mom_error(fatal, "Value of jer too large in reproducing_EFP_sum_2d.")
175 je = jer
176 endif
177
178 over_check = .true. ; if (present(overflow_check)) over_check = overflow_check
179 do_sum_across_pes = .true. ; if (present(only_on_pe)) do_sum_across_pes = .not.only_on_pe
180 do_unscale = .false. ; if (present(unscale)) do_unscale = (unscale /= 1.0)
181 descale = 1.0 ; if (do_unscale) descale = unscale
182
183 overflow_error = .false. ; nan_error = .false. ; max_mag_term = 0.0
184
185 ints_sum(:) = 0
186 if (over_check) then
187 call increment_block_ints(array, is, ie, js, je, descale, ints_sum, &
188 max_mag_term, prec_error)
189 else
190 do j=js,je ; do i=is,ie
191 sgn = 1 ; if (array(i,j)<0.0) sgn = -1
192 rs = abs(descale*array(i,j))
193 do n=1,efp_digits
194 ival = int(rs*i_pr(n), kind=int64)
195 rs = rs - ival*pr(n)
196 ints_sum(n) = ints_sum(n) + sgn*ival
197 enddo
198 enddo ; enddo
199 call carry_overflow(ints_sum, prec_error)
200 endif
201
202 if (present(err)) then
203 err = 0
204 if (overflow_error) &
205 err = err+2
206 if (nan_error) &
207 err = err+4
208 if (err > 0) then ; do n=1,efp_digits ; ints_sum(n) = 0 ; enddo ; endif
209 else
210 if (nan_error) then
211 call mom_error(fatal, "NaN in input field of reproducing_EFP_sum(_2d).")
212 endif
213 if (abs(max_mag_term) >= prec_error*pr(1)) then
214 write(mesg, '(ES13.5)') max_mag_term
215 call mom_error(fatal,"Overflow in reproducing_EFP_sum(_2d) conversion of "//trim(mesg))
216 endif
217 if (overflow_error) then
218 call mom_error(fatal, "Overflow in reproducing_EFP_sum(_2d).")
219 endif
220 endif
221
222 if (do_sum_across_pes) call sum_across_pes(ints_sum, efp_digits)
223
224 call regularize_ints(ints_sum)
225
226 efp_sum%v(:) = ints_sum(:)
227
228end function reproducing_efp_sum_2d
229
230
231!> This subroutine uses a conversion to an integer representation of real numbers to give an
232!! order-invariant sum of distributed 2-D arrays that reproduces across domain decomposition.
233!! This technique is described in Hallberg & Adcroft, 2014, Parallel Computing,
234!! doi:10.1016/j.parco.2014.04.007.
235function reproducing_sum_2d(array, isr, ier, jsr, jer, EFP_sum, reproducing, &
236 overflow_check, err, only_on_PE, unscale) result(sum)
237 real, dimension(:,:), intent(in) :: array !< The array to be summed in arbitrary units [a], or in
238 !! arbitrary scaled units [A ~> a] if unscale is present
239 integer, optional, intent(in) :: isr !< The starting i-index of the sum, noting
240 !! that the array indices starts at 1
241 integer, optional, intent(in) :: ier !< The ending i-index of the sum, noting
242 !! that the array indices starts at 1
243 integer, optional, intent(in) :: jsr !< The starting j-index of the sum, noting
244 !! that the array indices starts at 1
245 integer, optional, intent(in) :: jer !< The ending j-index of the sum, noting
246 !! that the array indices starts at 1
247 type(efp_type), optional, intent(out) :: efp_sum !< The result in extended fixed point format
248 logical, optional, intent(in) :: reproducing !< If present and false, do the sum
249 !! using the naive non-reproducing approach
250 logical, optional, intent(in) :: overflow_check !< If present and false, disable
251 !! checking for overflows in incremental results.
252 !! This can speed up calculations if the number
253 !! of values being summed is small enough
254 integer, optional, intent(out) :: err !< If present, return an error code instead of
255 !! triggering any fatal errors directly from
256 !! this routine.
257 logical, optional, intent(in) :: only_on_pe !< If present and true, do not do the sum
258 !! across processors, only reporting the local sum
259 real, optional, intent(in) :: unscale !< A factor that is used to undo scaling of array before it is
260 !! summed, often to compensate for the scaling in [a A-1 ~> 1]
261 real :: sum !< The sum of the values in array in the same
262 !! arbitrary units as array [a] or [A ~> a]
263
264 ! Local variables
265 integer(kind=int64), dimension(efp_digits) :: ints_sum
266 integer(kind=int64) :: prec_error
267 real :: rsum(1) ! The running sum, in arbitrary units [a]
268 real :: descale ! A local copy of unscale if it is present [a A-1 ~> 1] or 1
269 real :: i_unscale ! The reciprocal of unscale [A a-1 ~> 1]
270 logical :: repro, do_sum_across_pes, do_unscale
271 character(len=256) :: mesg
272 type(efp_type) :: efp_val ! An extended fixed point version of the sum
273 integer :: i, j, is, ie, js, je
274
275 if (num_pes() > max_summands) call mom_error(fatal, &
276 "reproducing_sum: Too many processors are being used for the value of "//&
277 "prec. Reduce prec to (2^63-1)/num_PEs.")
278
279 prec_error = huge(1_int64) / num_pes()
280
281 is = 1 ; ie = size(array,1) ; js = 1 ; je = size(array,2)
282 if (present(isr)) then
283 if (isr < is) call mom_error(fatal, "Value of isr too small in reproducing_sum_2d.")
284 is = isr
285 endif
286 if (present(ier)) then
287 if (ier > ie) call mom_error(fatal, "Value of ier too large in reproducing_sum_2d.")
288 ie = ier
289 endif
290 if (present(jsr)) then
291 if (jsr < js) call mom_error(fatal, "Value of jsr too small in reproducing_sum_2d.")
292 js = jsr
293 endif
294 if (present(jer)) then
295 if (jer > je) call mom_error(fatal, "Value of jer too large in reproducing_sum_2d.")
296 je = jer
297 endif
298
299 repro = .true. ; if (present(reproducing)) repro = reproducing
300 do_sum_across_pes = .true. ; if (present(only_on_pe)) do_sum_across_pes = .not.only_on_pe
301 do_unscale = .false. ; if (present(unscale)) do_unscale = (unscale /= 1.0)
302 descale = 1.0 ; i_unscale = 1.0
303 if (do_unscale) then
304 descale = unscale
305 if (abs(unscale) > 0.0) i_unscale = 1.0 / unscale
306 endif
307
308 if (repro) then
309 efp_val = reproducing_efp_sum_2d(array, isr, ier, jsr, jer, overflow_check, err, only_on_pe, unscale)
310 sum = ints_to_real(efp_val%v) * i_unscale
311 if (present(efp_sum)) efp_sum = efp_val
312 if (debug) ints_sum(:) = efp_sum%v(:)
313 else
314 rsum(1) = 0.0
315 do j=js,je ; do i=is,ie
316 rsum(1) = rsum(1) + descale*array(i,j)
317 enddo ; enddo
318 if (do_sum_across_pes) call sum_across_pes(rsum,1)
319 sum = rsum(1) * i_unscale
320
321 if (present(err)) then ; err = 0 ; endif
322
323 if (debug .or. present(efp_sum)) then
324 overflow_error = .false.
325 ints_sum = real_to_ints(sum, prec_error, overflow_error)
326 if (overflow_error) then
327 if (present(err)) then
328 err = err + 2
329 else
330 write(mesg, '(ES13.5)') sum
331 call mom_error(fatal,"Repro_sum_2d: Overflow in real_to_ints conversion of "//trim(mesg))
332 endif
333 endif
334 endif
335 if (present(efp_sum)) efp_sum%v(:) = ints_sum(:)
336 endif
337
338 if (debug) then
339 write(mesg,'("2d RS: ", ES24.16, 6 Z17.16)') sum*descale, ints_sum(1:efp_digits)
340 call mom_mesg(mesg, 3)
341 endif
342
343end function reproducing_sum_2d
344
345!> This subroutine uses a conversion to an integer representation of real numbers to give an
346!! order-invariant sum of distributed 3-D arrays that reproduces across domain decomposition.
347!! This technique is described in Hallberg & Adcroft, 2014, Parallel Computing,
348!! doi:10.1016/j.parco.2014.04.007.
349function reproducing_sum_3d(array, isr, ier, jsr, jer, sums, EFP_sum, EFP_lay_sums, err, only_on_PE, unscale) &
350 result(sum)
351 real, dimension(:,:,:), intent(in) :: array !< The array to be summed in arbitrary units [a], or in
352 !! arbitrary scaled units [A ~> a] if unscale is present
353 integer, optional, intent(in) :: isr !< The starting i-index of the sum, noting
354 !! that the array indices starts at 1
355 integer, optional, intent(in) :: ier !< The ending i-index of the sum, noting
356 !! that the array indices starts at 1
357 integer, optional, intent(in) :: jsr !< The starting j-index of the sum, noting
358 !! that the array indices starts at 1
359 integer, optional, intent(in) :: jer !< The ending j-index of the sum, noting
360 !! that the array indices starts at 1
361 real, dimension(:), optional, intent(out) :: sums !< The sums by vertical layer in the same
362 !! arbitrary units as array [a] or [A ~> a]
363 type(efp_type), optional, intent(out) :: efp_sum !< The result in extended fixed point format
364 type(efp_type), dimension(:), &
365 optional, intent(out) :: efp_lay_sums !< The sums by vertical layer in EFP format
366 integer, optional, intent(out) :: err !< If present, return an error code instead of
367 !! triggering any fatal errors directly from
368 !! this routine.
369 logical, optional, intent(in) :: only_on_pe !< If present and true, do not do the sum
370 !! across processors, only reporting the local sum
371 real, optional, intent(in) :: unscale !< A factor that is used to undo scaling of array before it is
372 !! summed, often to compensate for the scaling in [a A-1 ~> 1]
373 real :: sum !< The sum of the values in array in the same
374 !! arbitrary units as array [a] or [A ~> a]
375
376 ! Local variables
377 real :: val ! The real number that is extracted in arbitrary units [a]
378 real :: max_mag_term ! A running maximum magnitude of the val's in arbitrary units [a]
379 real :: descale ! A local copy of unscale if it is present [a A-1 ~> 1] or 1
380 real :: i_unscale ! The Adcroft reciprocal of unscale [A a-1 ~> 1]
381 integer(kind=int64), dimension(efp_digits) :: ints_sum
382 integer(kind=int64), dimension(efp_digits,size(array,3)) :: ints_sums
383 integer(kind=int64) :: prec_error
384 character(len=256) :: mesg
385 logical :: do_sum_across_pes, do_unscale
386 integer :: i, j, k, is, ie, js, je, ke, isz, jsz, n
387
388 if (num_pes() > max_summands) call mom_error(fatal, &
389 "reproducing_sum: Too many processors are being used for the value of "//&
390 "prec. Reduce prec to (2^63-1)/num_PEs.")
391
392 prec_error = huge(1_int64) / num_pes()
393 max_mag_term = 0.0
394
395 is = 1 ; ie = size(array,1) ; js = 1 ; je = size(array,2) ; ke = size(array,3)
396 if (present(isr)) then
397 if (isr < is) call mom_error(fatal, "Value of isr too small in reproducing_sum(_3d).")
398 is = isr
399 endif
400 if (present(ier)) then
401 if (ier > ie) call mom_error(fatal, "Value of ier too large in reproducing_sum(_3d).")
402 ie = ier
403 endif
404 if (present(jsr)) then
405 if (jsr < js) call mom_error(fatal, "Value of jsr too small in reproducing_sum(_3d).")
406 js = jsr
407 endif
408 if (present(jer)) then
409 if (jer > je) call mom_error(fatal, "Value of jer too large in reproducing_sum(_3d).")
410 je = jer
411 endif
412 jsz = je+1-js ; isz = ie+1-is
413
414 do_sum_across_pes = .true. ; if (present(only_on_pe)) do_sum_across_pes = .not.only_on_pe
415 do_unscale = .false. ; if (present(unscale)) do_unscale = (unscale /= 1.0)
416 descale = 1.0 ; if (do_unscale) descale = unscale
417
418 if (present(sums) .or. present(efp_lay_sums)) then
419 if (present(sums)) then ; if (size(sums) < ke) then
420 call mom_error(fatal, "Sums is smaller than the vertical extent of array in reproducing_sum(_3d).")
421 endif ; endif
422 if (present(efp_lay_sums)) then ; if (size(efp_lay_sums) < ke) then
423 call mom_error(fatal, "Sums is smaller than the vertical extent of array in reproducing_sum(_3d).")
424 endif ; endif
425
426 overflow_error = .false. ; nan_error = .false. ; max_mag_term = 0.0
427
428 ints_sums(:,:) = 0
429 do k=1,ke
430 call increment_block_ints(array(:,:,k), is, ie, js, je, descale, &
431 ints_sums(:,k), max_mag_term, prec_error)
432 enddo
433
434 if (present(err)) then
435 err = 0
436 if (abs(max_mag_term) >= prec_error*pr(1)) err = err+1
437 if (overflow_error) err = err+2
438 if (nan_error) err = err+2
439 if (err > 0) then ; do k=1,ke ; do n=1,efp_digits ; ints_sums(n,k) = 0 ; enddo ; enddo ; endif
440 else
441 if (nan_error) call mom_error(fatal, "NaN in input field of reproducing_sum(_3d).")
442 if (abs(max_mag_term) >= prec_error*pr(1)) then
443 write(mesg, '(ES13.5)') max_mag_term
444 call mom_error(fatal,"Overflow in reproducing_sum(_3d) conversion of "//trim(mesg))
445 endif
446 if (overflow_error) call mom_error(fatal, "Overflow in reproducing_sum(_3d).")
447 endif
448
449 if (do_sum_across_pes) call sum_across_pes(ints_sums(:,1:ke), efp_digits*ke)
450
451 sum = 0.0
452 do k=1,ke
453 call regularize_ints(ints_sums(:,k))
454 val = ints_to_real(ints_sums(:,k))
455 if (present(sums)) sums(k) = val
456 sum = sum + val
457 enddo
458 if (present(efp_lay_sums)) then ; do k=1,ke
459 efp_lay_sums(k)%v(:) = ints_sums(:,k)
460 enddo ; endif
461
462 if (present(efp_sum)) then
463 efp_sum%v(:) = 0
464 do k=1,ke ; call increment_ints(efp_sum%v(:), ints_sums(:,k)) ; enddo
465 endif
466
467 if (debug) then
468 do n=1,efp_digits ; ints_sum(n) = 0 ; enddo
469 do k=1,ke ; do n=1,efp_digits ; ints_sum(n) = ints_sum(n) + ints_sums(n,k) ; enddo ; enddo
470 write(mesg,'("3D RS: ", ES24.16, 6 Z17.16)') sum, ints_sum(1:efp_digits)
471 call mom_mesg(mesg, 3)
472 endif
473 else
474 overflow_error = .false. ; nan_error = .false. ; max_mag_term = 0.0
475
476 ints_sum(:) = 0
477 do k=1,ke
478 call increment_block_ints(array(:,:,k), is, ie, js, je, descale, &
479 ints_sum, max_mag_term, prec_error)
480 enddo
481
482 if (present(err)) then
483 err = 0
484 if (abs(max_mag_term) >= prec_error*pr(1)) err = err+1
485 if (overflow_error) err = err+2
486 if (nan_error) err = err+2
487 if (err > 0) then ; do n=1,efp_digits ; ints_sum(n) = 0 ; enddo ; endif
488 else
489 if (nan_error) call mom_error(fatal, "NaN in input field of reproducing_sum(_3d).")
490 if (abs(max_mag_term) >= prec_error*pr(1)) then
491 write(mesg, '(ES13.5)') max_mag_term
492 call mom_error(fatal,"Overflow in reproducing_sum(_3d) conversion of "//trim(mesg))
493 endif
494 if (overflow_error) call mom_error(fatal, "Overflow in reproducing_sum(_3d).")
495 endif
496
497 if (do_sum_across_pes) call sum_across_pes(ints_sum, efp_digits)
498
499 call regularize_ints(ints_sum)
500 sum = ints_to_real(ints_sum)
501
502 if (present(efp_sum)) efp_sum%v(:) = ints_sum(:)
503
504 if (debug) then
505 write(mesg,'("3d RS: ", ES24.16, 6 Z17.16)') sum, ints_sum(1:efp_digits)
506 call mom_mesg(mesg, 3)
507 endif
508 endif
509
510 if (do_unscale) then
511 ! Revise the sum to restore the scaling of input array before it is returned
512 i_unscale = 0.0 ; if (abs(unscale) > 0.0) i_unscale = 1.0 / unscale
513 sum = sum * i_unscale
514 if (present(sums)) then
515 do k=1,ke ; sums(k) = sums(k) * i_unscale ; enddo
516 endif
517 endif
518
519end function reproducing_sum_3d
520
521!> Convert a real number into the array of integers constitute its extended-fixed-point representation
522function real_to_ints(r, prec_error, overflow) result(ints)
523 real, intent(in) :: r !< The real number being converted in arbitrary units [a]
524 integer(kind=int64), optional, intent(in) :: prec_error !< The PE-count dependent precision of the
525 !! integers that is safe from overflows during global
526 !! sums. This will be larger than the compile-time
527 !! precision parameter, and is used to detect overflows.
528 logical, optional, intent(inout) :: overflow !< Returns true if the conversion is being
529 !! done on a value that is too large to be represented
530 integer(kind=int64), dimension(efp_digits) :: ints
531
532 ! This subroutine converts a real number to an equivalent representation
533 ! using several long integers.
534
535 ! Local variables
536 real :: rs ! The remaining value to add, in arbitrary units [a]
537 character(len=80) :: mesg
538 integer(kind=int64) :: ival, prec_err
539 integer :: sgn, i
540
541 prec_err = prec ; if (present(prec_error)) prec_err = prec_error
542 ints(:) = 0
543 if ((r >= 1e30) .eqv. (r < 1e30)) then ; nan_error = .true. ; return ; endif
544
545 sgn = 1 ; if (r<0.0) sgn = -1
546 rs = abs(r)
547
548 if (present(overflow)) then
549 if (.not.(rs < prec_err*pr(1))) overflow = .true.
550 if ((r >= 1e30) .eqv. (r < 1e30)) overflow = .true.
551 elseif (.not.(rs < prec_err*pr(1))) then
552 write(mesg, '(ES13.5)') r
553 call mom_error(fatal,"Overflow in real_to_ints conversion of "//trim(mesg))
554 endif
555
556 do i=1,efp_digits
557 ival = int(rs*i_pr(i), kind=int64)
558 rs = rs - ival*pr(i)
559 ints(i) = sgn*ival
560 enddo
561
562end function real_to_ints
563
564!> Convert the array of integers that constitute an extended-fixed-point
565!! representation into a real number
566function ints_to_real(ints) result(r)
567 integer(kind=int64), dimension(efp_digits), intent(in) :: ints !< The array of EFP integers
568 real :: r ! The real number that is extracted in arbitrary units [a]
569 ! This subroutine reverses the conversion in real_to_ints.
570
571 integer :: i
572
573 r = 0.0
574 do i=1,efp_digits ; r = r + pr(i)*ints(i) ; enddo
575end function ints_to_real
576
577!> Increment an array of integers that constitutes an extended-fixed-point
578!! representation with a another EFP number
579subroutine increment_ints(int_sum, int2, prec_error)
580 integer(kind=int64), dimension(efp_digits), intent(inout) :: int_sum !< The array of EFP integers being incremented
581 integer(kind=int64), dimension(efp_digits), intent(in) :: int2 !< The array of EFP integers being added
582 integer(kind=int64), optional, intent(in) :: prec_error !< The PE-count dependent precision of the
583 !! integers that is safe from overflows during global
584 !! sums. This will be larger than the compile-time
585 !! precision parameter, and is used to detect overflows.
586
587 ! This subroutine increments a number with another, both using the integer
588 ! representation in real_to_ints.
589 integer :: i
590
591 do i=efp_digits,2,-1
592 int_sum(i) = int_sum(i) + int2(i)
593 ! Carry the local overflow.
594 if (int_sum(i) > prec) then
595 int_sum(i) = int_sum(i) - prec
596 int_sum(i-1) = int_sum(i-1) + 1
597 elseif (int_sum(i) < -prec) then
598 int_sum(i) = int_sum(i) + prec
599 int_sum(i-1) = int_sum(i-1) - 1
600 endif
601 enddo
602 int_sum(1) = int_sum(1) + int2(1)
603 if (present(prec_error)) then
604 if (abs(int_sum(1)) > prec_error) overflow_error = .true.
605 else
606 if (abs(int_sum(1)) > prec) overflow_error = .true.
607 endif
608
609end subroutine increment_ints
610
611
612!> Sum the elements of an array in EFP form and append the result to an
613!! existing EFP array.
614subroutine increment_block_ints(array, is, ie, js, je, descale, ints_sum, &
615 max_mag_term, prec_error)
616 real, intent(in) :: array(:,:)
617 !< The field being added, in arbitrary units [A ~> a]
618 integer, intent(in) :: is
619 !< Start i-index of the summed domain
620 integer, intent(in) :: ie
621 !< End i-index of the summed domain
622 integer, intent(in) :: js
623 !< Start j-index of the summed domain
624 integer, intent(in) :: je
625 !< End j-index of the summed domain
626 real, intent(in) :: descale
627 !< Factor to descale array to physical value [a A-1 ~> 1]
628 integer(kind=int64), intent(inout) :: ints_sum(efp_digits)
629 !< The array of EFP integers being incremented
630 real, intent(inout) :: max_mag_term
631 !< A running maximum magnitude of the r's, in arbitrary units [a]
632 integer(kind=int64), intent(in) :: prec_error
633 !< The maximum resolvable value for a given number of PEs
634
635 integer :: i, j, ib, jb, ibs, ibe, jbs, jbe
636 ! Loop indices
637 integer :: b
638 ! Block counter
639 integer :: ni, nj
640 ! Array summation domain size along each axis
641 integer :: isize_max
642 ! Largest block size in i. Typically equal to ni
643 integer :: jsize
644 ! Number of j-rows per block.
645 integer :: nblocks, niblocks, njblocks
646 ! Number of total blocks, and number of blocks in i and j
647 integer(kind=int64) :: e(efp_digits)
648 ! The EPF representation of each array element
649 integer(kind=int64) :: block_sum(efp_digits), array_sum(efp_digits)
650 ! The cumulant per-block and total array EFP sums
651 real :: r, rmag
652 ! Local array element value and its magnitude [a]
653 real :: max_pos, max_neg, block_max_pos, block_max_neg
654 ! Largest positive and negative values (whole array and per-block) used to
655 ! find the largest maximum magnitude of array in a thread-safe manner [a]
656 integer :: inan, iovf, lnan, lovf
657 ! Thread-safe tracking of NaN and overflow state
658 integer :: max_sum_count
659 ! The total number of local sum operations that ensures no carry overflow
660
661 max_pos = max(0., max_mag_term)
662 max_neg = max(0., -max_mag_term)
663 inan = 0 ; iovf = 0
664
665 ! Reduce the maximum number of summations to account for the cumulant
666 ! summations of array_sum and ints_sum.
667 max_sum_count = max_summands - 2
668
669 ! Get the compute domain size
670 ni = ie - is + 1
671 nj = je - js + 1
672
673 ! Partition in i so that the widest i-slice fits within max_sum_count.
674 niblocks = (ni + max_sum_count - 1) / max_sum_count
675 ! = ⌈ni / max_sum_count⌉
676
677 ! NOTE: niblocks is typically one, since default max_sum_count is ~130k.
678
679 ! For a balanced i-partition, the number of i-points per block is either
680 ! ⌊ni / niblocks⌋ or ⌈ni / niblocks⌉. Use the upper bound to find jsize.
681
682 isize_max = (ni + niblocks - 1) / niblocks
683 ! = ⌈ni / niblocks⌉
684
685 ! Set jsize so that the widest i-slice times the number of j-rows does not
686 ! exceed max_sum_count.
687 jsize = max_sum_count / isize_max
688 ! = ⌊max_sum_count / isize_max⌋
689
690 ! Choose enough j-blocks so that no j-block has more than jsize rows.
691 njblocks = (nj + jsize - 1) / jsize
692 ! = ⌈nj / jsize⌉
693
694 nblocks = niblocks * njblocks
695
696 ! Abort if the number of blocks also exceeds the carry-bit summation limit.
697 ! For default settings, this would be over 17 billion points per PE.
698 if (nblocks > max_sum_count) call mom_error(fatal, &
699 "reproducing sum: Number of blocks exceeds summmation carry limit.")
700
701 array_sum(:) = 0
702
703 do jb=1,njblocks ; do ib=1,niblocks
704 ! Use evenly distributed blocks, either ⌊n / nblocks⌋ or ⌈n / nblocks⌉.
705 jbs = js + ((jb - 1) * nj) / njblocks
706 jbe = js + (jb * nj) / njblocks - 1
707
708 ibs = is + ((ib - 1) * ni) / niblocks
709 ibe = is + (ib * ni) / niblocks - 1
710
711 block_sum(:) = 0
712 block_max_pos = 0.
713 block_max_neg = 0.
714
715 ! Compute the sum of each block
716 do j=jbs,jbe ; do i=ibs,ibe
717
718 ! Convert array(i,j) to EFP form
719 r = descale * array(i,j)
720 call efp_decompose(r, e, rmag, lnan, lovf)
721
722 ! Verify that the conversion was completed
723 inan = max(inan, lnan)
724 iovf = max(iovf, lovf)
725
726 if (r >= 0.) then
727 if (rmag > block_max_pos) block_max_pos = rmag
728 else
729 if (rmag > block_max_neg) block_max_neg = rmag
730 endif
731
732 ! Add the EFP result (including potential carry bits)
733 block_sum(:) = block_sum(:) + e(:)
734 enddo ; enddo
735
736 array_sum(:) = array_sum(:) + block_sum(:)
737
738 ! Redistribute carry bits across bins
739 ! For the final pass (or single pass) this is handled by ints_sum.
740 b = (jb - 1) * niblocks + ib
741 if (b < nblocks) call carry_overflow(array_sum, prec_error)
742
743 ! Update maximum magnitudes
744 max_pos = max(max_pos, block_max_pos)
745 max_neg = max(max_neg, block_max_neg)
746 enddo ; enddo
747
748 ! Finally, apply the cumulant result
749 ints_sum(:) = ints_sum(:) + array_sum(:)
750
751 ! Redistribute carry bits to normalize the final result.
752 call carry_overflow(ints_sum, prec_error)
753
754 ! Extract the maximum value while preserving sign (NOTE: ties to positive)
755 if (max_pos >= max_neg) then
756 max_mag_term = max_pos
757 else
758 max_mag_term = -max_neg
759 endif
760
761 ! Transfer error/warning signals to module flags
762 if (inan /= 0) nan_error = .true.
763 if (iovf /= 0) overflow_error = .true.
764end subroutine increment_block_ints
765
766
767!> Decompose one real into its 6 signed EFP bin contributions. NaNs and
768!! overflows are reported by flags, rather than the module-level error
769!! logicals, so that the routine is free of side effects.
770pure subroutine efp_decompose(r, e, rmag, is_nan, is_ovf)
771 real, intent(in) :: r
772 !< The real number being decomposed [a]
773 integer(kind=int64), intent(out) :: e(efp_digits)
774 !< Signed contribution to EFP bins
775 real, intent(out) :: rmag
776 !< Equals abs(r), or 0 if r is NaN/Inf [a]
777 integer, intent(out) :: is_nan
778 !< Equals 1 if r is a NaN or Inf, else 0
779 integer, intent(out) :: is_ovf
780 !< Equals 1 if abs(r) has no EFP representation, else 0
781
782 real :: rs
783 ! The remaining value to add, in arbitrary units [a]
784 integer(kind=int64) :: ival
785 integer :: sgn
786 integer :: n
787
788 e(:) = 0
789 rmag = 0.0 ; is_nan = 0 ; is_ovf = 0
790
791 if ((r >= 1e30) .eqv. (r < 1e30)) then
792 is_nan = 1
793 return
794 endif
795
796 sgn = 1
797 if (r < 0.0) sgn = -1
798
799 rs = abs(r) ; rmag = rs
800
801 ! Abort if the number has no EFP representation
802 if (rs > max_efp_float) then
803 is_ovf = 1
804 return
805 endif
806
807 do n=1,efp_digits
808 ival = int(rs * i_pr(n), kind=int64)
809 rs = rs - ival * pr(n)
810 e(n) = sgn * ival
811 enddo
812end subroutine efp_decompose
813
814
815!> This subroutine handles carrying of the overflow.
816subroutine carry_overflow(int_sum, prec_error)
817 integer(kind=int64), dimension(efp_digits), intent(inout) :: int_sum !< The array of EFP integers being
818 !! modified by carries, but without changing value.
819 integer(kind=int64), intent(in) :: prec_error !< The PE-count dependent precision of the
820 !! integers that is safe from overflows during global
821 !! sums. This will be larger than the compile-time
822 !! precision parameter, and is used to detect overflows.
823
824 ! This subroutine handles carrying of the overflow.
825 integer :: i, num_carry
826
827 do i=efp_digits,2,-1 ; if (abs(int_sum(i)) >= prec) then
828 num_carry = int(int_sum(i) * i_prec)
829 int_sum(i) = int_sum(i) - num_carry*prec
830 int_sum(i-1) = int_sum(i-1) + num_carry
831 endif ; enddo
832 if (abs(int_sum(1)) > prec_error) then
833 overflow_error = .true.
834 endif
835
836end subroutine carry_overflow
837
838!> This subroutine carries the overflow, and then makes sure that
839!! all integers are of the same sign as the overall value.
840subroutine regularize_ints(int_sum)
841 integer(kind=int64), dimension(efp_digits), &
842 intent(inout) :: int_sum !< The array of integers being modified to take a
843 !! regular form with all integers of the same sign,
844 !! but without changing value.
845
846 ! This subroutine carries the overflow, and then makes sure that
847 ! all integers are of the same sign as the overall value.
848 logical :: positive
849 integer :: i, num_carry
850
851 do i=efp_digits,2,-1 ; if (abs(int_sum(i)) >= prec) then
852 num_carry = int(int_sum(i) * i_prec)
853 int_sum(i) = int_sum(i) - num_carry*prec
854 int_sum(i-1) = int_sum(i-1) + num_carry
855 endif ; enddo
856
857 ! Determine the sign of the final number.
858 positive = .true.
859 do i=1,efp_digits
860 if (abs(int_sum(i)) > 0) then
861 if (int_sum(i) < 0) positive = .false.
862 exit
863 endif
864 enddo
865
866 if (positive) then
867 do i=efp_digits,2,-1 ; if (int_sum(i) < 0) then
868 int_sum(i) = int_sum(i) + prec
869 int_sum(i-1) = int_sum(i-1) - 1
870 endif ; enddo
871 else
872 do i=efp_digits,2,-1 ; if (int_sum(i) > 0) then
873 int_sum(i) = int_sum(i) - prec
874 int_sum(i-1) = int_sum(i-1) + 1
875 endif ; enddo
876 endif
877
878end subroutine regularize_ints
879
880!> Returns the status of the module's error flag
882 logical :: query_efp_overflow_error
883 query_efp_overflow_error = overflow_error
884end function query_efp_overflow_error
885
886!> Reset the module's error flag to false
887subroutine reset_efp_overflow_error()
888 overflow_error = .false.
889end subroutine reset_efp_overflow_error
890
891!> Add two extended-fixed-point numbers
892function efp_plus(EFP1, EFP2)
893 type(efp_type) :: efp_plus !< The result in extended fixed point format
894 type(efp_type), intent(in) :: efp1 !< The first extended fixed point number
895 type(efp_type), intent(in) :: efp2 !< The second extended fixed point number
896
897 efp_plus = efp1
898
899 call increment_ints(efp_plus%v(:), efp2%v(:))
900end function efp_plus
901
902!> Subract one extended-fixed-point number from another
903function efp_minus(EFP1, EFP2)
904 type(efp_type) :: efp_minus !< The result in extended fixed point format
905 type(efp_type), intent(in) :: efp1 !< The first extended fixed point number
906 type(efp_type), intent(in) :: efp2 !< The extended fixed point number being
907 !! subtracted from the first extended fixed point number
908 integer :: i
909
910 do i=1,efp_digits ; efp_minus%v(i) = -1*efp2%v(i) ; enddo
911
912 call increment_ints(efp_minus%v(:), efp1%v(:))
913end function efp_minus
914
915!> Copy one extended-fixed-point number into another
916subroutine efp_assign(EFP1, EFP2)
917 type(efp_type), intent(out) :: EFP1 !< The recipient extended fixed point number
918 type(efp_type), intent(in) :: EFP2 !< The source extended fixed point number
919 integer i
920 ! This subroutine assigns all components of the extended fixed point type
921 ! variable on the RHS (EFP2) to the components of the variable on the LHS
922 ! (EFP1).
923
924 do i=1,efp_digits ; efp1%v(i) = efp2%v(i) ; enddo
925end subroutine efp_assign
926
927!> Return the real number that an extended-fixed-point number corresponds with
928function efp_to_real(EFP1)
929 type(efp_type), intent(inout) :: efp1 !< The extended fixed point number being converted
930 real :: efp_to_real !< The real version of the number in arbitrary units [a]
931
932 call regularize_ints(efp1%v)
933 efp_to_real = ints_to_real(efp1%v)
934end function efp_to_real
935
936!> Take the difference between two extended-fixed-point numbers (EFP1 - EFP2)
937!! and return the result as a real number
938function efp_real_diff(EFP1, EFP2)
939 type(efp_type), intent(in) :: efp1 !< The first extended fixed point number
940 type(efp_type), intent(in) :: efp2 !< The extended fixed point number being
941 !! subtracted from the first extended fixed point number
942 real :: efp_real_diff !< The real result in arbitrary units [a]
943
944 type(efp_type) :: efp_diff
945
946 efp_diff = efp1 - efp2
947 efp_real_diff = efp_to_real(efp_diff)
948
949end function efp_real_diff
950
951!> Return the extended-fixed-point number that a real number corresponds with
952function real_to_efp(val, overflow)
953 real, intent(in) :: val !< The real number being converted in arbitrary units [a]
954 logical, optional, intent(inout) :: overflow !< Returns true if the conversion is being
955 !! done on a value that is too large to be represented
956 type(efp_type) :: real_to_efp
957
958 logical :: over
959 character(len=80) :: mesg
960
961 if (present(overflow)) then
962 real_to_efp%v(:) = real_to_ints(val, overflow=overflow)
963 else
964 over = .false.
965 real_to_efp%v(:) = real_to_ints(val, overflow=over)
966 if (over) then
967 write(mesg, '(ES13.5)') val
968 call mom_error(fatal,"Overflow in real_to_EFP conversion of "//trim(mesg))
969 endif
970 endif
971
972end function real_to_efp
973
974!> This subroutine does a sum across PEs of a list of EFP variables,
975!! returning the sums in place, with all overflows carried.
976subroutine efp_list_sum_across_pes(EFPs, nval, errors)
977 type(efp_type), dimension(:), &
978 intent(inout) :: EFPs !< The list of extended fixed point numbers
979 !! being summed across PEs.
980 integer, intent(in) :: nval !< The number of values being summed.
981 logical, dimension(:), &
982 optional, intent(out) :: errors !< A list of error flags for each sum
983
984 ! This subroutine does a sum across PEs of a list of EFP variables,
985 ! returning the sums in place, with all overflows carried.
986
987 integer(kind=int64), dimension(efp_digits,nval) :: ints
988 integer(kind=int64) :: prec_error
989 logical :: error_found
990 character(len=256) :: mesg
991 integer :: i, n
992
993 if (num_pes() > max_summands) call mom_error(fatal, &
994 "reproducing_sum: Too many processors are being used for the value of "//&
995 "prec. Reduce prec to (2^63-1)/num_PEs.")
996
997 prec_error = huge(1_int64) / num_pes()
998
999 ! overflow_error is an overflow error flag for the whole module.
1000 overflow_error = .false. ; error_found = .false.
1001
1002 do i=1,nval ; do n=1,efp_digits ; ints(n,i) = efps(i)%v(n) ; enddo ; enddo
1003
1004 call sum_across_pes(ints(:,:), efp_digits*nval)
1005
1006 if (present(errors)) errors(:) = .false.
1007 do i=1,nval
1008 overflow_error = .false.
1009 call carry_overflow(ints(:,i), prec_error)
1010 do n=1,efp_digits ; efps(i)%v(n) = ints(n,i) ; enddo
1011 if (present(errors)) errors(i) = overflow_error
1012 if (overflow_error) then
1013 write (mesg,'("EFP_list_sum_across_PEs error at ",i0," val was ",ES12.6, ", prec_error = ",ES12.6)') &
1014 i, efp_to_real(efps(i)), real(prec_error)
1015 call mom_error(warning, mesg)
1016 endif
1017 error_found = error_found .or. overflow_error
1018 enddo
1019 if (error_found .and. .not.(present(errors))) then
1020 call mom_error(fatal, "Overflow in EFP_list_sum_across_PEs.")
1021 endif
1022
1023end subroutine efp_list_sum_across_pes
1024
1025!> This subroutine does a sum across PEs of an EFP variable,
1026!! returning the sums in place, with all overflows carried.
1027subroutine efp_val_sum_across_pes(EFP, error)
1028 type(efp_type), intent(inout) :: EFP !< The extended fixed point numbers
1029 !! being summed across PEs.
1030 logical, optional, intent(out) :: error !< An error flag for this sum
1031
1032 ! This subroutine does a sum across PEs of a list of EFP variables,
1033 ! returning the sums in place, with all overflows carried.
1034
1035 integer(kind=int64), dimension(efp_digits) :: ints
1036 integer(kind=int64) :: prec_error
1037 logical :: error_found
1038 character(len=256) :: mesg
1039 integer :: n
1040
1041 if (num_pes() > max_summands) call mom_error(fatal, &
1042 "reproducing_sum: Too many processors are being used for the value of "//&
1043 "prec. Reduce prec to (2^63-1)/num_PEs.")
1044
1045 prec_error = huge(1_int64) / num_pes()
1046
1047 ! overflow_error is an overflow error flag for the whole module.
1048 overflow_error = .false. ; error_found = .false.
1049
1050 do n=1,efp_digits ; ints(n) = efp%v(n) ; enddo
1051
1052 call sum_across_pes(ints(:), efp_digits)
1053
1054 if (present(error)) error = .false.
1055
1056 overflow_error = .false.
1057 call carry_overflow(ints(:), prec_error)
1058 do n=1,efp_digits ; efp%v(n) = ints(n) ; enddo
1059 if (present(error)) error = overflow_error
1060 if (overflow_error) then
1061 write (mesg,'("EFP_val_sum_across_PEs error val was ",ES12.6, ", prec_error = ",ES12.6)') &
1062 efp_to_real(efp), real(prec_error)
1063 call mom_error(warning, mesg)
1064 endif
1065 error_found = error_found .or. overflow_error
1066
1067 if (error_found .and. .not.(present(error))) then
1068 call mom_error(fatal, "Overflow in EFP_val_sum_across_PEs.")
1069 endif
1070
1071end subroutine efp_val_sum_across_pes
1072
1073end module mom_coms