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(=)
28public :: max_count_prec
29
30! This module provides interfaces to the non-domain-oriented communication subroutines.
31
32integer(kind=int64), parameter :: prec = (2_int64)**46 !< The precision of each integer.
33real, parameter :: r_prec=2.0**46 !< A real version of prec [nondim].
34real, parameter :: i_prec=1.0/(2.0**46) !< The inverse of prec [nondim].
35integer, parameter :: max_count_prec=2**(63-46)-1
36 !< The number of values that can be added together
37 !! with the current value of prec before there will
38 !! be roundoff problems.
39
40integer, parameter :: ni=6 !< The number of long integers to use to represent
41 !< a real number.
42real, parameter, dimension(ni) :: &
43 pr = (/ r_prec**2, r_prec, 1.0, 1.0/r_prec, 1.0/r_prec**2, 1.0/r_prec**3 /)
44 !< An array of the real precision of each of the integers in arbitrary units [a]
45real, parameter, dimension(ni) :: &
46 i_pr = (/ 1.0/r_prec**2, 1.0/r_prec, 1.0, r_prec, r_prec**2, r_prec**3 /)
47 !< An array of the inverse of the real precision of each of the integers in arbitrary units [a-1]
48real, parameter :: max_efp_float = pr(1) * (2.**63 - 1.)
49 !< The largest float with an EFP representation in arbitrary units [a].
50 !! NOTE: Only the first bin can exceed precision,
51 !! but is bounded by the largest signed integer.
52
53logical :: overflow_error = .false. !< This becomes true if an overflow is encountered.
54logical :: nan_error = .false. !< This becomes true if a NaN is encountered.
55logical :: debug = .false. !< Making this true enables debugging output.
56
57!> Find an accurate and order-invariant sum of a distributed 2d or 3d field, in some cases after
58!! undoing the scaling of the input array and restoring that scaling in the returned value
59interface reproducing_sum
61end interface reproducing_sum
62
63!> Find an accurate and order-invariant sum of a distributed 2d field, returning the result
64!! in the form of an extended fixed point value that can be converted back with EFP_to_real.
65interface reproducing_sum_efp
66 module procedure reproducing_efp_sum_2d
67end interface reproducing_sum_efp
68
69!> Sum a value or 1-d array of values across processors, returning the sums in place
70interface efp_sum_across_pes
72end interface efp_sum_across_pes
73
74!> The Extended Fixed Point (EFP) type provides a public interface for doing sums
75!! and taking differences with this type.
76!!
77!! The use of this type is documented in
78!! Hallberg, R. & A. Adcroft, 2014: An Order-invariant Real-to-Integer Conversion Sum.
79!! Parallel Computing, 40(5-6), doi:10.1016/j.parco.2014.04.007.
80type, public :: efp_type ; private
81 integer(kind=int64), dimension(ni) :: v !< The value in this type
82end type efp_type
83
84!> Add two extended-fixed-point numbers
85interface operator (+) ; module procedure EFP_plus ; end interface
86!> Subtract one extended-fixed-point number from another
87interface operator (-) ; module procedure EFP_minus ; end interface
88!> Copy the value of one extended-fixed-point number into another
89interface assignment(=); module procedure EFP_assign ; end interface
90
91contains
92
93!> This subroutine uses a conversion to an integer representation of real numbers to give an
94!! order-invariant sum of distributed 2-D arrays that reproduces across domain decomposition, with
95!! the result returned as an extended fixed point type that can be converted back to a real number
96!! using EFP_to_real. This technique is described in Hallberg & Adcroft, 2014, Parallel Computing,
97!! doi:10.1016/j.parco.2014.04.007.
98function reproducing_efp_sum_2d(array, isr, ier, jsr, jer, overflow_check, err, only_on_PE, unscale) result(EFP_sum)
99 real, dimension(:,:), intent(in) :: array !< The array to be summed in arbitrary units [a], or in
100 !! arbitrary scaled units [A ~> a] if unscale is present
101 integer, optional, intent(in) :: isr !< The starting i-index of the sum, noting
102 !! that the array indices starts at 1
103 integer, optional, intent(in) :: ier !< The ending i-index of the sum, noting
104 !! that the array indices starts at 1
105 integer, optional, intent(in) :: jsr !< The starting j-index of the sum, noting
106 !! that the array indices starts at 1
107 integer, optional, intent(in) :: jer !< The ending j-index of the sum, noting
108 !! that the array indices starts at 1
109 logical, optional, intent(in) :: overflow_check !< If present and false, disable
110 !! checking for overflows in incremental results.
111 !! This can speed up calculations if the number
112 !! of values being summed is small enough
113 integer, optional, intent(out) :: err !< If present, return an error code instead of
114 !! triggering any fatal errors directly from
115 !! this routine.
116 logical, optional, intent(in) :: only_on_pe !< If present and true, do not do the sum
117 !! across processors, only reporting the local sum
118 real, optional, intent(in) :: unscale !< A factor that is used to undo scaling of array before it is
119 !! summed, often to compensate for the scaling in [a A-1 ~> 1]
120 type(efp_type) :: efp_sum !< The result in extended fixed point format
121
122 ! This subroutine uses a conversion to an integer representation
123 ! of real numbers to give order-invariant sums that will reproduce
124 ! across PE count. This idea comes from R. Hallberg and A. Adcroft.
125
126 integer(kind=int64), dimension(ni) :: ints_sum
127 integer(kind=int64) :: ival, prec_error
128 real :: rs ! The remaining value to add, in arbitrary units [a]
129 real :: max_mag_term ! A running maximum magnitude of the values in arbitrary units [a]
130 real :: descale ! A local copy of unscale if it is present [a A-1 ~> 1] or 1
131 logical :: over_check, do_sum_across_pes, do_unscale
132 character(len=256) :: mesg
133 integer :: i, j, n, is, ie, js, je, sgn
134
135 if (num_pes() > max_count_prec) call mom_error(fatal, &
136 "reproducing_sum: Too many processors are being used for the value of "//&
137 "prec. Reduce prec to (2^63-1)/num_PEs.")
138
139 prec_error = ((2_int64)**62 + ((2_int64)**62 - 1)) / num_pes()
140
141 is = 1 ; ie = size(array,1) ; js = 1 ; je = size(array,2)
142 if (present(isr)) then
143 if (isr < is) call mom_error(fatal, "Value of isr too small in reproducing_EFP_sum_2d.")
144 is = isr
145 endif
146 if (present(ier)) then
147 if (ier > ie) call mom_error(fatal, "Value of ier too large in reproducing_EFP_sum_2d.")
148 ie = ier
149 endif
150 if (present(jsr)) then
151 if (jsr < js) call mom_error(fatal, "Value of jsr too small in reproducing_EFP_sum_2d.")
152 js = jsr
153 endif
154 if (present(jer)) then
155 if (jer > je) call mom_error(fatal, "Value of jer too large in reproducing_EFP_sum_2d.")
156 je = jer
157 endif
158
159 over_check = .true. ; if (present(overflow_check)) over_check = overflow_check
160 do_sum_across_pes = .true. ; if (present(only_on_pe)) do_sum_across_pes = .not.only_on_pe
161 do_unscale = .false. ; if (present(unscale)) do_unscale = (unscale /= 1.0)
162 descale = 1.0 ; if (do_unscale) descale = unscale
163
164 overflow_error = .false. ; nan_error = .false. ; max_mag_term = 0.0
165 ints_sum(:) = 0
166 if (over_check) then
167 if ((je+1-js)*(ie+1-is) < max_count_prec) then
168 ! This is the most common case, so handle the do_unscale case separately for efficiency.
169 if (do_unscale) then
170 do j=js,je ; do i=is,ie
171 call increment_ints_faster(ints_sum, unscale*array(i,j), max_mag_term)
172 enddo ; enddo
173 else
174 do j=js,je ; do i=is,ie
175 call increment_ints_faster(ints_sum, array(i,j), max_mag_term)
176 enddo ; enddo
177 endif
178 call carry_overflow(ints_sum, prec_error)
179 elseif ((ie+1-is) < max_count_prec) then
180 do j=js,je
181 do i=is,ie
182 call increment_ints_faster(ints_sum, descale*array(i,j), max_mag_term)
183 enddo
184 call carry_overflow(ints_sum, prec_error)
185 enddo
186 else
187 do j=js,je ; do i=is,ie
188 call increment_ints(ints_sum, real_to_ints(descale*array(i,j), prec_error), prec_error)
189 enddo ; enddo
190 endif
191 else
192 do j=js,je ; do i=is,ie
193 sgn = 1 ; if (array(i,j)<0.0) sgn = -1
194 rs = abs(descale*array(i,j))
195 do n=1,ni
196 ival = int(rs*i_pr(n), kind=int64)
197 rs = rs - ival*pr(n)
198 ints_sum(n) = ints_sum(n) + sgn*ival
199 enddo
200 enddo ; enddo
201 call carry_overflow(ints_sum, prec_error)
202 endif
203
204 if (present(err)) then
205 err = 0
206 if (overflow_error) &
207 err = err+2
208 if (nan_error) &
209 err = err+4
210 if (err > 0) then ; do n=1,ni ; ints_sum(n) = 0 ; enddo ; endif
211 else
212 if (nan_error) then
213 call mom_error(fatal, "NaN in input field of reproducing_EFP_sum(_2d).")
214 endif
215 if (abs(max_mag_term) >= prec_error*pr(1)) then
216 write(mesg, '(ES13.5)') max_mag_term
217 call mom_error(fatal,"Overflow in reproducing_EFP_sum(_2d) conversion of "//trim(mesg))
218 endif
219 if (overflow_error) then
220 call mom_error(fatal, "Overflow in reproducing_EFP_sum(_2d).")
221 endif
222 endif
223
224 if (do_sum_across_pes) call sum_across_pes(ints_sum, ni)
225
226 call regularize_ints(ints_sum)
227
228 efp_sum%v(:) = ints_sum(:)
229
230end function reproducing_efp_sum_2d
231
232
233!> This subroutine uses a conversion to an integer representation of real numbers to give an
234!! order-invariant sum of distributed 2-D arrays that reproduces across domain decomposition.
235!! This technique is described in Hallberg & Adcroft, 2014, Parallel Computing,
236!! doi:10.1016/j.parco.2014.04.007.
237function reproducing_sum_2d(array, isr, ier, jsr, jer, EFP_sum, reproducing, &
238 overflow_check, err, only_on_PE, unscale) result(sum)
239 real, dimension(:,:), intent(in) :: array !< The array to be summed in arbitrary units [a], or in
240 !! arbitrary scaled units [A ~> a] if unscale is present
241 integer, optional, intent(in) :: isr !< The starting i-index of the sum, noting
242 !! that the array indices starts at 1
243 integer, optional, intent(in) :: ier !< The ending i-index of the sum, noting
244 !! that the array indices starts at 1
245 integer, optional, intent(in) :: jsr !< The starting j-index of the sum, noting
246 !! that the array indices starts at 1
247 integer, optional, intent(in) :: jer !< The ending j-index of the sum, noting
248 !! that the array indices starts at 1
249 type(efp_type), optional, intent(out) :: efp_sum !< The result in extended fixed point format
250 logical, optional, intent(in) :: reproducing !< If present and false, do the sum
251 !! using the naive non-reproducing approach
252 logical, optional, intent(in) :: overflow_check !< If present and false, disable
253 !! checking for overflows in incremental results.
254 !! This can speed up calculations if the number
255 !! of values being summed is small enough
256 integer, optional, intent(out) :: err !< If present, return an error code instead of
257 !! triggering any fatal errors directly from
258 !! this routine.
259 logical, optional, intent(in) :: only_on_pe !< If present and true, do not do the sum
260 !! across processors, only reporting the local sum
261 real, optional, intent(in) :: unscale !< A factor that is used to undo scaling of array before it is
262 !! summed, often to compensate for the scaling in [a A-1 ~> 1]
263 real :: sum !< The sum of the values in array in the same
264 !! arbitrary units as array [a] or [A ~> a]
265
266 ! Local variables
267 integer(kind=int64), dimension(ni) :: ints_sum
268 integer(kind=int64) :: prec_error
269 real :: rsum(1) ! The running sum, in arbitrary units [a]
270 real :: descale ! A local copy of unscale if it is present [a A-1 ~> 1] or 1
271 real :: i_unscale ! The reciprocal of unscale [A a-1 ~> 1]
272 logical :: repro, do_sum_across_pes, do_unscale
273 character(len=256) :: mesg
274 type(efp_type) :: efp_val ! An extended fixed point version of the sum
275 integer :: i, j, is, ie, js, je
276
277 if (num_pes() > max_count_prec) call mom_error(fatal, &
278 "reproducing_sum: Too many processors are being used for the value of "//&
279 "prec. Reduce prec to (2^63-1)/num_PEs.")
280
281 prec_error = ((2_int64)**62 + ((2_int64)**62 - 1)) / num_pes()
282
283 is = 1 ; ie = size(array,1) ; js = 1 ; je = size(array,2)
284 if (present(isr)) then
285 if (isr < is) call mom_error(fatal, "Value of isr too small in reproducing_sum_2d.")
286 is = isr
287 endif
288 if (present(ier)) then
289 if (ier > ie) call mom_error(fatal, "Value of ier too large in reproducing_sum_2d.")
290 ie = ier
291 endif
292 if (present(jsr)) then
293 if (jsr < js) call mom_error(fatal, "Value of jsr too small in reproducing_sum_2d.")
294 js = jsr
295 endif
296 if (present(jer)) then
297 if (jer > je) call mom_error(fatal, "Value of jer too large in reproducing_sum_2d.")
298 je = jer
299 endif
300
301 repro = .true. ; if (present(reproducing)) repro = reproducing
302 do_sum_across_pes = .true. ; if (present(only_on_pe)) do_sum_across_pes = .not.only_on_pe
303 do_unscale = .false. ; if (present(unscale)) do_unscale = (unscale /= 1.0)
304 descale = 1.0 ; i_unscale = 1.0
305 if (do_unscale) then
306 descale = unscale
307 if (abs(unscale) > 0.0) i_unscale = 1.0 / unscale
308 endif
309
310 if (repro) then
311 efp_val = reproducing_efp_sum_2d(array, isr, ier, jsr, jer, overflow_check, err, only_on_pe, unscale)
312 sum = ints_to_real(efp_val%v) * i_unscale
313 if (present(efp_sum)) efp_sum = efp_val
314 if (debug) ints_sum(:) = efp_sum%v(:)
315 else
316 rsum(1) = 0.0
317 do j=js,je ; do i=is,ie
318 rsum(1) = rsum(1) + descale*array(i,j)
319 enddo ; enddo
320 if (do_sum_across_pes) call sum_across_pes(rsum,1)
321 sum = rsum(1) * i_unscale
322
323 if (present(err)) then ; err = 0 ; endif
324
325 if (debug .or. present(efp_sum)) then
326 overflow_error = .false.
327 ints_sum = real_to_ints(sum, prec_error, overflow_error)
328 if (overflow_error) then
329 if (present(err)) then
330 err = err + 2
331 else
332 write(mesg, '(ES13.5)') sum
333 call mom_error(fatal,"Repro_sum_2d: Overflow in real_to_ints conversion of "//trim(mesg))
334 endif
335 endif
336 endif
337 if (present(efp_sum)) efp_sum%v(:) = ints_sum(:)
338 endif
339
340 if (debug) then
341 write(mesg,'("2d RS: ", ES24.16, 6 Z17.16)') sum*descale, ints_sum(1:ni)
342 call mom_mesg(mesg, 3)
343 endif
344
345end function reproducing_sum_2d
346
347!> This subroutine uses a conversion to an integer representation of real numbers to give an
348!! order-invariant sum of distributed 3-D arrays that reproduces across domain decomposition.
349!! This technique is described in Hallberg & Adcroft, 2014, Parallel Computing,
350!! doi:10.1016/j.parco.2014.04.007.
351function reproducing_sum_3d(array, isr, ier, jsr, jer, sums, EFP_sum, EFP_lay_sums, err, only_on_PE, unscale) &
352 result(sum)
353 real, dimension(:,:,:), intent(in) :: array !< The array to be summed in arbitrary units [a], or in
354 !! arbitrary scaled units [A ~> a] if unscale is present
355 integer, optional, intent(in) :: isr !< The starting i-index of the sum, noting
356 !! that the array indices starts at 1
357 integer, optional, intent(in) :: ier !< The ending i-index of the sum, noting
358 !! that the array indices starts at 1
359 integer, optional, intent(in) :: jsr !< The starting j-index of the sum, noting
360 !! that the array indices starts at 1
361 integer, optional, intent(in) :: jer !< The ending j-index of the sum, noting
362 !! that the array indices starts at 1
363 real, dimension(:), optional, intent(out) :: sums !< The sums by vertical layer in the same
364 !! arbitrary units as array [a] or [A ~> a]
365 type(efp_type), optional, intent(out) :: efp_sum !< The result in extended fixed point format
366 type(efp_type), dimension(:), &
367 optional, intent(out) :: efp_lay_sums !< The sums by vertical layer in EFP format
368 integer, optional, intent(out) :: err !< If present, return an error code instead of
369 !! triggering any fatal errors directly from
370 !! this routine.
371 logical, optional, intent(in) :: only_on_pe !< If present and true, do not do the sum
372 !! across processors, only reporting the local sum
373 real, optional, intent(in) :: unscale !< A factor that is used to undo scaling of array before it is
374 !! summed, often to compensate for the scaling in [a A-1 ~> 1]
375 real :: sum !< The sum of the values in array in the same
376 !! arbitrary units as array [a] or [A ~> a]
377
378 ! Local variables
379 real :: val ! The real number that is extracted in arbitrary units [a]
380 real :: max_mag_term ! A running maximum magnitude of the val's in arbitrary units [a]
381 real :: descale ! A local copy of unscale if it is present [a A-1 ~> 1] or 1
382 real :: i_unscale ! The Adcroft reciprocal of unscale [A a-1 ~> 1]
383 integer(kind=int64), dimension(ni) :: ints_sum
384 integer(kind=int64), dimension(ni,size(array,3)) :: ints_sums
385 integer(kind=int64) :: prec_error
386 character(len=256) :: mesg
387 logical :: do_sum_across_pes, do_unscale
388 integer :: i, j, k, is, ie, js, je, ke, isz, jsz, n
389
390 if (num_pes() > max_count_prec) call mom_error(fatal, &
391 "reproducing_sum: Too many processors are being used for the value of "//&
392 "prec. Reduce prec to (2^63-1)/num_PEs.")
393
394 prec_error = ((2_int64)**62 + ((2_int64)**62 - 1)) / num_pes()
395 max_mag_term = 0.0
396
397 is = 1 ; ie = size(array,1) ; js = 1 ; je = size(array,2) ; ke = size(array,3)
398 if (present(isr)) then
399 if (isr < is) call mom_error(fatal, "Value of isr too small in reproducing_sum(_3d).")
400 is = isr
401 endif
402 if (present(ier)) then
403 if (ier > ie) call mom_error(fatal, "Value of ier too large in reproducing_sum(_3d).")
404 ie = ier
405 endif
406 if (present(jsr)) then
407 if (jsr < js) call mom_error(fatal, "Value of jsr too small in reproducing_sum(_3d).")
408 js = jsr
409 endif
410 if (present(jer)) then
411 if (jer > je) call mom_error(fatal, "Value of jer too large in reproducing_sum(_3d).")
412 je = jer
413 endif
414 jsz = je+1-js ; isz = ie+1-is
415
416 do_sum_across_pes = .true. ; if (present(only_on_pe)) do_sum_across_pes = .not.only_on_pe
417 do_unscale = .false. ; if (present(unscale)) do_unscale = (unscale /= 1.0)
418 descale = 1.0 ; if (do_unscale) descale = unscale
419
420 if (present(sums) .or. present(efp_lay_sums)) then
421 if (present(sums)) then ; if (size(sums) < ke) then
422 call mom_error(fatal, "Sums is smaller than the vertical extent of array in reproducing_sum(_3d).")
423 endif ; endif
424 if (present(efp_lay_sums)) then ; if (size(efp_lay_sums) < ke) then
425 call mom_error(fatal, "Sums is smaller than the vertical extent of array in reproducing_sum(_3d).")
426 endif ; endif
427 ints_sums(:,:) = 0
428 overflow_error = .false. ; nan_error = .false. ; max_mag_term = 0.0
429 if (jsz*isz < max_count_prec) then
430 do k=1,ke
431 if (do_unscale) then
432 do j=js,je ; do i=is,ie
433 call increment_ints_faster(ints_sums(:,k), unscale*array(i,j,k), max_mag_term)
434 enddo ; enddo
435 else
436 do j=js,je ; do i=is,ie
437 call increment_ints_faster(ints_sums(:,k), array(i,j,k), max_mag_term)
438 enddo ; enddo
439 endif
440 call carry_overflow(ints_sums(:,k), prec_error)
441 enddo
442 elseif (isz < max_count_prec) then
443 do k=1,ke ; do j=js,je
444 do i=is,ie
445 call increment_ints_faster(ints_sums(:,k), descale*array(i,j,k), max_mag_term)
446 enddo
447 call carry_overflow(ints_sums(:,k), prec_error)
448 enddo ; enddo
449 else
450 do k=1,ke ; do j=js,je ; do i=is,ie
451 call increment_ints(ints_sums(:,k), &
452 real_to_ints(descale*array(i,j,k), prec_error), prec_error)
453 enddo ; enddo ; enddo
454 endif
455 if (present(err)) then
456 err = 0
457 if (abs(max_mag_term) >= prec_error*pr(1)) err = err+1
458 if (overflow_error) err = err+2
459 if (nan_error) err = err+2
460 if (err > 0) then ; do k=1,ke ; do n=1,ni ; ints_sums(n,k) = 0 ; enddo ; enddo ; endif
461 else
462 if (nan_error) call mom_error(fatal, "NaN in input field of reproducing_sum(_3d).")
463 if (abs(max_mag_term) >= prec_error*pr(1)) then
464 write(mesg, '(ES13.5)') max_mag_term
465 call mom_error(fatal,"Overflow in reproducing_sum(_3d) conversion of "//trim(mesg))
466 endif
467 if (overflow_error) call mom_error(fatal, "Overflow in reproducing_sum(_3d).")
468 endif
469
470 if (do_sum_across_pes) call sum_across_pes(ints_sums(:,1:ke), ni*ke)
471
472 sum = 0.0
473 do k=1,ke
474 call regularize_ints(ints_sums(:,k))
475 val = ints_to_real(ints_sums(:,k))
476 if (present(sums)) sums(k) = val
477 sum = sum + val
478 enddo
479 if (present(efp_lay_sums)) then ; do k=1,ke
480 efp_lay_sums(k)%v(:) = ints_sums(:,k)
481 enddo ; endif
482
483 if (present(efp_sum)) then
484 efp_sum%v(:) = 0
485 do k=1,ke ; call increment_ints(efp_sum%v(:), ints_sums(:,k)) ; enddo
486 endif
487
488 if (debug) then
489 do n=1,ni ; ints_sum(n) = 0 ; enddo
490 do k=1,ke ; do n=1,ni ; ints_sum(n) = ints_sum(n) + ints_sums(n,k) ; enddo ; enddo
491 write(mesg,'("3D RS: ", ES24.16, 6 Z17.16)') sum, ints_sum(1:ni)
492 call mom_mesg(mesg, 3)
493 endif
494 else
495 ints_sum(:) = 0
496 overflow_error = .false. ; nan_error = .false. ; max_mag_term = 0.0
497 if (jsz*isz < max_count_prec) then
498 do k=1,ke
499 if (do_unscale) then
500 do j=js,je ; do i=is,ie
501 call increment_ints_faster(ints_sum, unscale*array(i,j,k), max_mag_term)
502 enddo ; enddo
503 else
504 do j=js,je ; do i=is,ie
505 call increment_ints_faster(ints_sum, array(i,j,k), max_mag_term)
506 enddo ; enddo
507 endif
508 call carry_overflow(ints_sum, prec_error)
509 enddo
510 elseif (isz < max_count_prec) then
511 do k=1,ke ; do j=js,je
512 do i=is,ie
513 call increment_ints_faster(ints_sum, descale*array(i,j,k), max_mag_term)
514 enddo
515 call carry_overflow(ints_sum, prec_error)
516 enddo ; enddo
517 else
518 do k=1,ke ; do j=js,je ; do i=is,ie
519 call increment_ints(ints_sum, real_to_ints(descale*array(i,j,k), prec_error), &
520 prec_error)
521 enddo ; enddo ; enddo
522 endif
523 if (present(err)) then
524 err = 0
525 if (abs(max_mag_term) >= prec_error*pr(1)) err = err+1
526 if (overflow_error) err = err+2
527 if (nan_error) err = err+2
528 if (err > 0) then ; do n=1,ni ; ints_sum(n) = 0 ; enddo ; endif
529 else
530 if (nan_error) call mom_error(fatal, "NaN in input field of reproducing_sum(_3d).")
531 if (abs(max_mag_term) >= prec_error*pr(1)) then
532 write(mesg, '(ES13.5)') max_mag_term
533 call mom_error(fatal,"Overflow in reproducing_sum(_3d) conversion of "//trim(mesg))
534 endif
535 if (overflow_error) call mom_error(fatal, "Overflow in reproducing_sum(_3d).")
536 endif
537
538 if (do_sum_across_pes) call sum_across_pes(ints_sum, ni)
539
540 call regularize_ints(ints_sum)
541 sum = ints_to_real(ints_sum)
542
543 if (present(efp_sum)) efp_sum%v(:) = ints_sum(:)
544
545 if (debug) then
546 write(mesg,'("3d RS: ", ES24.16, 6 Z17.16)') sum, ints_sum(1:ni)
547 call mom_mesg(mesg, 3)
548 endif
549 endif
550
551 if (do_unscale) then
552 ! Revise the sum to restore the scaling of input array before it is returned
553 i_unscale = 0.0 ; if (abs(unscale) > 0.0) i_unscale = 1.0 / unscale
554 sum = sum * i_unscale
555 if (present(sums)) then
556 do k=1,ke ; sums(k) = sums(k) * i_unscale ; enddo
557 endif
558 endif
559
560end function reproducing_sum_3d
561
562!> Convert a real number into the array of integers constitute its extended-fixed-point representation
563function real_to_ints(r, prec_error, overflow) result(ints)
564 real, intent(in) :: r !< The real number being converted in arbitrary units [a]
565 integer(kind=int64), optional, intent(in) :: prec_error !< The PE-count dependent precision of the
566 !! integers that is safe from overflows during global
567 !! sums. This will be larger than the compile-time
568 !! precision parameter, and is used to detect overflows.
569 logical, optional, intent(inout) :: overflow !< Returns true if the conversion is being
570 !! done on a value that is too large to be represented
571 integer(kind=int64), dimension(ni) :: ints
572
573 ! This subroutine converts a real number to an equivalent representation
574 ! using several long integers.
575
576 ! Local variables
577 real :: rs ! The remaining value to add, in arbitrary units [a]
578 character(len=80) :: mesg
579 integer(kind=int64) :: ival, prec_err
580 integer :: sgn, i
581
582 prec_err = prec ; if (present(prec_error)) prec_err = prec_error
583 ints(:) = 0
584 if ((r >= 1e30) .eqv. (r < 1e30)) then ; nan_error = .true. ; return ; endif
585
586 sgn = 1 ; if (r<0.0) sgn = -1
587 rs = abs(r)
588
589 if (present(overflow)) then
590 if (.not.(rs < prec_err*pr(1))) overflow = .true.
591 if ((r >= 1e30) .eqv. (r < 1e30)) overflow = .true.
592 elseif (.not.(rs < prec_err*pr(1))) then
593 write(mesg, '(ES13.5)') r
594 call mom_error(fatal,"Overflow in real_to_ints conversion of "//trim(mesg))
595 endif
596
597 do i=1,ni
598 ival = int(rs*i_pr(i), kind=int64)
599 rs = rs - ival*pr(i)
600 ints(i) = sgn*ival
601 enddo
602
603end function real_to_ints
604
605!> Convert the array of integers that constitute an extended-fixed-point
606!! representation into a real number
607function ints_to_real(ints) result(r)
608 integer(kind=int64), dimension(ni), intent(in) :: ints !< The array of EFP integers
609 real :: r ! The real number that is extracted in arbitrary units [a]
610 ! This subroutine reverses the conversion in real_to_ints.
611
612 integer :: i
613
614 r = 0.0
615 do i=1,ni ; r = r + pr(i)*ints(i) ; enddo
616end function ints_to_real
617
618!> Increment an array of integers that constitutes an extended-fixed-point
619!! representation with a another EFP number
620subroutine increment_ints(int_sum, int2, prec_error)
621 integer(kind=int64), dimension(ni), intent(inout) :: int_sum !< The array of EFP integers being incremented
622 integer(kind=int64), dimension(ni), intent(in) :: int2 !< The array of EFP integers being added
623 integer(kind=int64), optional, intent(in) :: prec_error !< The PE-count dependent precision of the
624 !! integers that is safe from overflows during global
625 !! sums. This will be larger than the compile-time
626 !! precision parameter, and is used to detect overflows.
627
628 ! This subroutine increments a number with another, both using the integer
629 ! representation in real_to_ints.
630 integer :: i
631
632 do i=ni,2,-1
633 int_sum(i) = int_sum(i) + int2(i)
634 ! Carry the local overflow.
635 if (int_sum(i) > prec) then
636 int_sum(i) = int_sum(i) - prec
637 int_sum(i-1) = int_sum(i-1) + 1
638 elseif (int_sum(i) < -prec) then
639 int_sum(i) = int_sum(i) + prec
640 int_sum(i-1) = int_sum(i-1) - 1
641 endif
642 enddo
643 int_sum(1) = int_sum(1) + int2(1)
644 if (present(prec_error)) then
645 if (abs(int_sum(1)) > prec_error) overflow_error = .true.
646 else
647 if (abs(int_sum(1)) > prec) overflow_error = .true.
648 endif
649
650end subroutine increment_ints
651
652!> Increment an EFP number with a real number without doing any carrying of
653!! of overflows and using only minimal error checking.
654subroutine increment_ints_faster(int_sum, r, max_mag_term)
655 integer(kind=int64), dimension(ni), intent(inout) :: int_sum !< The array of EFP integers being incremented
656 real, intent(in) :: r !< The real number being added in arbitrary units [a]
657 real, intent(inout) :: max_mag_term !< A running maximum magnitude of the r's
658 !! in arbitrary units [a]
659
660 ! This subroutine increments a number with another, both using the integer
661 ! representation in real_to_ints, but without doing any carrying of overflow.
662 ! The entire operation is embedded in a single call for greater speed.
663 real :: rs ! The remaining value to add, in arbitrary units [a]
664 integer(kind=int64) :: ival
665 integer :: sgn, i
666
667 if ((r >= 1e30) .eqv. (r < 1e30)) then ; nan_error = .true. ; return ; endif
668 sgn = 1 ; if (r<0.0) sgn = -1
669 rs = abs(r)
670 if (rs > abs(max_mag_term)) max_mag_term = r
671
672 ! Abort if the number has no EFP representation
673 if (rs > max_efp_float) then
674 overflow_error = .true.
675 return
676 endif
677
678 do i=1,ni
679 ival = int(rs*i_pr(i), kind=int64)
680 rs = rs - ival*pr(i)
681 int_sum(i) = int_sum(i) + sgn*ival
682 enddo
683
684end subroutine increment_ints_faster
685
686!> This subroutine handles carrying of the overflow.
687subroutine carry_overflow(int_sum, prec_error)
688 integer(kind=int64), dimension(ni), intent(inout) :: int_sum !< The array of EFP integers being
689 !! modified by carries, but without changing value.
690 integer(kind=int64), intent(in) :: prec_error !< The PE-count dependent precision of the
691 !! integers that is safe from overflows during global
692 !! sums. This will be larger than the compile-time
693 !! precision parameter, and is used to detect overflows.
694
695 ! This subroutine handles carrying of the overflow.
696 integer :: i, num_carry
697
698 do i=ni,2,-1 ; if (abs(int_sum(i)) >= prec) then
699 num_carry = int(int_sum(i) * i_prec)
700 int_sum(i) = int_sum(i) - num_carry*prec
701 int_sum(i-1) = int_sum(i-1) + num_carry
702 endif ; enddo
703 if (abs(int_sum(1)) > prec_error) then
704 overflow_error = .true.
705 endif
706
707end subroutine carry_overflow
708
709!> This subroutine carries the overflow, and then makes sure that
710!! all integers are of the same sign as the overall value.
711subroutine regularize_ints(int_sum)
712 integer(kind=int64), dimension(ni), &
713 intent(inout) :: int_sum !< The array of integers being modified to take a
714 !! regular form with all integers of the same sign,
715 !! but without changing value.
716
717 ! This subroutine carries the overflow, and then makes sure that
718 ! all integers are of the same sign as the overall value.
719 logical :: positive
720 integer :: i, num_carry
721
722 do i=ni,2,-1 ; if (abs(int_sum(i)) >= prec) then
723 num_carry = int(int_sum(i) * i_prec)
724 int_sum(i) = int_sum(i) - num_carry*prec
725 int_sum(i-1) = int_sum(i-1) + num_carry
726 endif ; enddo
727
728 ! Determine the sign of the final number.
729 positive = .true.
730 do i=1,ni
731 if (abs(int_sum(i)) > 0) then
732 if (int_sum(i) < 0) positive = .false.
733 exit
734 endif
735 enddo
736
737 if (positive) then
738 do i=ni,2,-1 ; if (int_sum(i) < 0) then
739 int_sum(i) = int_sum(i) + prec
740 int_sum(i-1) = int_sum(i-1) - 1
741 endif ; enddo
742 else
743 do i=ni,2,-1 ; if (int_sum(i) > 0) then
744 int_sum(i) = int_sum(i) - prec
745 int_sum(i-1) = int_sum(i-1) + 1
746 endif ; enddo
747 endif
748
749end subroutine regularize_ints
750
751!> Returns the status of the module's error flag
753 logical :: query_efp_overflow_error
754 query_efp_overflow_error = overflow_error
755end function query_efp_overflow_error
756
757!> Reset the module's error flag to false
758subroutine reset_efp_overflow_error()
759 overflow_error = .false.
760end subroutine reset_efp_overflow_error
761
762!> Add two extended-fixed-point numbers
763function efp_plus(EFP1, EFP2)
764 type(efp_type) :: efp_plus !< The result in extended fixed point format
765 type(efp_type), intent(in) :: efp1 !< The first extended fixed point number
766 type(efp_type), intent(in) :: efp2 !< The second extended fixed point number
767
768 efp_plus = efp1
769
770 call increment_ints(efp_plus%v(:), efp2%v(:))
771end function efp_plus
772
773!> Subract one extended-fixed-point number from another
774function efp_minus(EFP1, EFP2)
775 type(efp_type) :: efp_minus !< The result in extended fixed point format
776 type(efp_type), intent(in) :: efp1 !< The first extended fixed point number
777 type(efp_type), intent(in) :: efp2 !< The extended fixed point number being
778 !! subtracted from the first extended fixed point number
779 integer :: i
780
781 do i=1,ni ; efp_minus%v(i) = -1*efp2%v(i) ; enddo
782
783 call increment_ints(efp_minus%v(:), efp1%v(:))
784end function efp_minus
785
786!> Copy one extended-fixed-point number into another
787subroutine efp_assign(EFP1, EFP2)
788 type(efp_type), intent(out) :: EFP1 !< The recipient extended fixed point number
789 type(efp_type), intent(in) :: EFP2 !< The source extended fixed point number
790 integer i
791 ! This subroutine assigns all components of the extended fixed point type
792 ! variable on the RHS (EFP2) to the components of the variable on the LHS
793 ! (EFP1).
794
795 do i=1,ni ; efp1%v(i) = efp2%v(i) ; enddo
796end subroutine efp_assign
797
798!> Return the real number that an extended-fixed-point number corresponds with
799function efp_to_real(EFP1)
800 type(efp_type), intent(inout) :: efp1 !< The extended fixed point number being converted
801 real :: efp_to_real !< The real version of the number in arbitrary units [a]
802
803 call regularize_ints(efp1%v)
804 efp_to_real = ints_to_real(efp1%v)
805end function efp_to_real
806
807!> Take the difference between two extended-fixed-point numbers (EFP1 - EFP2)
808!! and return the result as a real number
809function efp_real_diff(EFP1, EFP2)
810 type(efp_type), intent(in) :: efp1 !< The first extended fixed point number
811 type(efp_type), intent(in) :: efp2 !< The extended fixed point number being
812 !! subtracted from the first extended fixed point number
813 real :: efp_real_diff !< The real result in arbitrary units [a]
814
815 type(efp_type) :: efp_diff
816
817 efp_diff = efp1 - efp2
818 efp_real_diff = efp_to_real(efp_diff)
819
820end function efp_real_diff
821
822!> Return the extended-fixed-point number that a real number corresponds with
823function real_to_efp(val, overflow)
824 real, intent(in) :: val !< The real number being converted in arbitrary units [a]
825 logical, optional, intent(inout) :: overflow !< Returns true if the conversion is being
826 !! done on a value that is too large to be represented
827 type(efp_type) :: real_to_efp
828
829 logical :: over
830 character(len=80) :: mesg
831
832 if (present(overflow)) then
833 real_to_efp%v(:) = real_to_ints(val, overflow=overflow)
834 else
835 over = .false.
836 real_to_efp%v(:) = real_to_ints(val, overflow=over)
837 if (over) then
838 write(mesg, '(ES13.5)') val
839 call mom_error(fatal,"Overflow in real_to_EFP conversion of "//trim(mesg))
840 endif
841 endif
842
843end function real_to_efp
844
845!> This subroutine does a sum across PEs of a list of EFP variables,
846!! returning the sums in place, with all overflows carried.
847subroutine efp_list_sum_across_pes(EFPs, nval, errors)
848 type(efp_type), dimension(:), &
849 intent(inout) :: EFPs !< The list of extended fixed point numbers
850 !! being summed across PEs.
851 integer, intent(in) :: nval !< The number of values being summed.
852 logical, dimension(:), &
853 optional, intent(out) :: errors !< A list of error flags for each sum
854
855 ! This subroutine does a sum across PEs of a list of EFP variables,
856 ! returning the sums in place, with all overflows carried.
857
858 integer(kind=int64), dimension(ni,nval) :: ints
859 integer(kind=int64) :: prec_error
860 logical :: error_found
861 character(len=256) :: mesg
862 integer :: i, n
863
864 if (num_pes() > max_count_prec) call mom_error(fatal, &
865 "reproducing_sum: Too many processors are being used for the value of "//&
866 "prec. Reduce prec to (2^63-1)/num_PEs.")
867
868 prec_error = ((2_int64)**62 + ((2_int64)**62 - 1)) / num_pes()
869 ! overflow_error is an overflow error flag for the whole module.
870 overflow_error = .false. ; error_found = .false.
871
872 do i=1,nval ; do n=1,ni ; ints(n,i) = efps(i)%v(n) ; enddo ; enddo
873
874 call sum_across_pes(ints(:,:), ni*nval)
875
876 if (present(errors)) errors(:) = .false.
877 do i=1,nval
878 overflow_error = .false.
879 call carry_overflow(ints(:,i), prec_error)
880 do n=1,ni ; efps(i)%v(n) = ints(n,i) ; enddo
881 if (present(errors)) errors(i) = overflow_error
882 if (overflow_error) then
883 write (mesg,'("EFP_list_sum_across_PEs error at ",i0," val was ",ES12.6, ", prec_error = ",ES12.6)') &
884 i, efp_to_real(efps(i)), real(prec_error)
885 call mom_error(warning, mesg)
886 endif
887 error_found = error_found .or. overflow_error
888 enddo
889 if (error_found .and. .not.(present(errors))) then
890 call mom_error(fatal, "Overflow in EFP_list_sum_across_PEs.")
891 endif
892
893end subroutine efp_list_sum_across_pes
894
895!> This subroutine does a sum across PEs of an EFP variable,
896!! returning the sums in place, with all overflows carried.
897subroutine efp_val_sum_across_pes(EFP, error)
898 type(efp_type), intent(inout) :: EFP !< The extended fixed point numbers
899 !! being summed across PEs.
900 logical, optional, intent(out) :: error !< An error flag for this sum
901
902 ! This subroutine does a sum across PEs of a list of EFP variables,
903 ! returning the sums in place, with all overflows carried.
904
905 integer(kind=int64), dimension(ni) :: ints
906 integer(kind=int64) :: prec_error
907 logical :: error_found
908 character(len=256) :: mesg
909 integer :: n
910
911 if (num_pes() > max_count_prec) call mom_error(fatal, &
912 "reproducing_sum: Too many processors are being used for the value of "//&
913 "prec. Reduce prec to (2^63-1)/num_PEs.")
914
915 prec_error = ((2_int64)**62 + ((2_int64)**62 - 1)) / num_pes()
916 ! overflow_error is an overflow error flag for the whole module.
917 overflow_error = .false. ; error_found = .false.
918
919 do n=1,ni ; ints(n) = efp%v(n) ; enddo
920
921 call sum_across_pes(ints(:), ni)
922
923 if (present(error)) error = .false.
924
925 overflow_error = .false.
926 call carry_overflow(ints(:), prec_error)
927 do n=1,ni ; efp%v(n) = ints(n) ; enddo
928 if (present(error)) error = overflow_error
929 if (overflow_error) then
930 write (mesg,'("EFP_val_sum_across_PEs error val was ",ES12.6, ", prec_error = ",ES12.6)') &
931 efp_to_real(efp), real(prec_error)
932 call mom_error(warning, mesg)
933 endif
934 error_found = error_found .or. overflow_error
935
936 if (error_found .and. .not.(present(error))) then
937 call mom_error(fatal, "Overflow in EFP_val_sum_across_PEs.")
938 endif
939
940end subroutine efp_val_sum_across_pes
941
942end module mom_coms