MOM_intrinsic_functions.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!> A module with intrinsic functions that are used by MOM but are not supported
6!! by some compilers.
8
9use iso_fortran_env, only : stdout => output_unit, stderr => error_unit
10use iso_fortran_env, only : int64, real64
11
12implicit none ; private
13
14public :: invcosh, cuberoot
16
17! Floating point model, if bit layout from high to low is (sign, exp, frac)
18
19integer, parameter :: bias = maxexponent(1.) - 1
20 !< The double precision exponent offset
21integer, parameter :: signbit = storage_size(1.) - 1
22 !< Position of sign bit
23integer, parameter :: explen = 1 + ceiling(log(real(bias))/log(2.))
24 !< Bit size of exponent
25integer, parameter :: expbit = signbit - explen
26 !< Position of lowest exponent bit
27integer, parameter :: fraclen = expbit
28 !< Length of fractional part
29
30contains
31
32!> Evaluate the inverse cosh, either using a math library or an
33!! equivalent expression
34function invcosh(x)
35 real, intent(in) :: x !< The argument of the inverse of cosh [nondim]. NaNs will
36 !! occur if x<1, but there is no error checking
37 real :: invcosh ! The inverse of cosh of x [nondim]
38
39#ifdef __INTEL_COMPILER
40 invcosh = acosh(x)
41#else
42 invcosh = log(x+sqrt(x*x-1))
43#endif
44
45end function invcosh
46
47
48!> Returns the cube root of a real argument at roundoff accuracy, in a form that works properly with
49!! rescaling of the argument by integer powers of 8. If the argument is a NaN, a NaN is returned.
50elemental function cuberoot(x) result(root)
51 real, intent(in) :: x !< The argument of cuberoot in arbitrary units cubed [A3]
52 real :: root !< The real cube root of x in arbitrary units [A]
53
54 real :: asx ! The absolute value of x rescaled by an integer power of 8 to put it into
55 ! the range from 0.125 < asx <= 1.0, in ambiguous units cubed [B3]
56 real :: root_asx ! The cube root of asx [B]
57 real :: ra_3 ! root_asx cubed [B3]
58 real :: num ! The numerator of an expression for the evolving estimate of the cube root of asx
59 ! in arbitrary units that can grow or shrink with each iteration [B C]
60 real :: den ! The denominator of an expression for the evolving estimate of the cube root of asx
61 ! in arbitrary units that can grow or shrink with each iteration [C]
62 real :: num_prev ! The numerator of an expression for the previous iteration of the evolving estimate
63 ! of the cube root of asx in arbitrary units that can grow or shrink with each iteration [B D]
64 real :: np_3 ! num_prev cubed [B3 D3]
65 real :: den_prev ! The denominator of an expression for the previous iteration of the evolving estimate of
66 ! the cube root of asx in arbitrary units that can grow or shrink with each iteration [D]
67 real :: dp_3 ! den_prev cubed [C3]
68 real :: r0 ! Initial value of the iterative solver. [B C]
69 real :: r0_3 ! r0 cubed [B3 C3]
70 integer :: itt
71
72 integer(kind=int64) :: e_x, s_x
73
74 if ((x >= 0.0) .eqv. (x <= 0.0)) then
75 ! Return 0 for an input of 0, or NaN for a NaN input.
76 root = x
77 else
78 call rescale_cbrt(x, asx, e_x, s_x)
79
80 ! Iteratively determine root_asx = asx**1/3 using Halley's method and then Newton's method,
81 ! noting that Halley's method onverges monotonically and needs no bounding. Halley's method is
82 ! slightly more complicated that Newton's method, but converges in a third fewer iterations.
83 ! Keeping the estimates in a fractional form Root = num / den allows this calculation with
84 ! no real divisions during the iterations before doing a single real division at the end,
85 ! and it is therefore more computationally efficient.
86
87 ! This first estimate gives the same magnitude of errors for 0.125 and 1.0 after two iterations.
88 ! The first iteration is applied explicitly.
89 r0 = 0.707106
90 r0_3 = r0 * r0 * r0
91 num = r0 * (r0_3 + 2.0 * asx)
92 den = 2.0 * r0_3 + asx
93
94 do itt=1,2
95 ! Halley's method iterates estimates as Root = Root * (Root**3 + 2.*asx) / (2.*Root**3 + asx).
96 num_prev = num ; den_prev = den
97
98 ! Pre-compute these as integer powers, to avoid `pow()`-like intrinsics.
99 np_3 = num_prev * num_prev * num_prev
100 dp_3 = den_prev * den_prev * den_prev
101
102 num = num_prev * (np_3 + 2.0 * asx * dp_3)
103 den = den_prev * (2.0 * np_3 + asx * dp_3)
104 ! Equivalent to: root_asx = root_asx * (root_asx**3 + 2.*asx) / (2.*root_asx**3 + asx)
105 enddo
106 ! At this point the error in root_asx is better than 1 part in 3e14.
107 root_asx = num / den
108
109 ! One final iteration with Newton's method polishes up the root and gives a solution
110 ! that is within the last bit of the true solution.
111 ra_3 = root_asx * root_asx * root_asx
112 root_asx = root_asx - (ra_3 - asx) / (3.0 * (root_asx * root_asx))
113
114 root = descale(root_asx, e_x, s_x)
115 endif
116end function cuberoot
117
118
119!> Rescale `a` to the range [0.125, 1) and compute its cube-root exponent.
120!!
121!! This function decomposes `a` into the form `s * x * 2**e` so that `x` is
122!! in the desired range. This is accomplished by computing the integral cube
123!! root of `e` (as a division) and applying the residual to `x`.
124pure subroutine rescale_cbrt(a, x, e_r, s_a)
125 real, intent(in) :: a
126 !< The number to be rescaled for cube-root computation [A3]
127 real, intent(out) :: x
128 !< The rescaled value of `a` in the range [0.125, 1) [B3]
129 integer(kind=int64), intent(out) :: e_r
130 !< The integral component of the cube-root exponent of `a`.
131 integer(kind=int64), intent(out) :: s_a
132 !< Sign bit of `a`. A nonzero value indicates negative sign.
133
134 integer(kind=int64) :: xb
135 ! Floating point integer representation of `a`
136 integer(kind=int64) :: e_a
137 ! Exponent of `a`
138 integer(kind=int64) :: e_x
139 ! Exponent of `x`
140
141 ! Pack bits of a into xb and extract its exponent and sign.
142 xb = transfer(a, 1_int64)
143 s_a = ibits(xb, signbit, 1)
144 e_a = ibits(xb, expbit, explen) - bias
145
146 ! The floating-point form of `a` with exponent `e` is
147 !
148 ! a = s * (1 + m) * 2**e
149 !
150 ! where (1+m) ∈ [1,2). We want to split 2**e so that (1+m) is rescaled to
151 ! the range [0.125, 1); that is, [2**-3, 2**0).
152 !
153 ! First decompose the exponent `e` into quotient-remainder form:
154 !
155 ! e = 3⌊e/3⌋ + modulo(e,3)
156 !
157 ! Since modulo(e,3) ∈ {0,1,2}, the second term of the following expression is
158 ! in {-3,-2,-1}.
159 !
160 ! e = 3 * (⌊e/3⌋ + 1) + (modulo(e,3) - 3).
161 !
162 ! Here, (modulo(e,3) - 3) is in the range [2**-3, 1) and holds the
163 ! floating-point exponent of `x`.
164 !
165 ! Fortran integer division is round-to-zero. To convert to floor division,
166 ! we use the sign() intrinsic to shift negative values downward.
167 !
168 ! ⌊e/3⌋ = (e + sign(1,e) - 1) / 3
169 !
170 ! ⌊e/3⌋ + 1 reduces to the form below. This is what we call the integral
171 ! cube-root of `a` in the description above.
172
173 e_r = (e_a + sign(1_int64, e_a) + 2) / 3
174
175 ! modulo() is not implemented on all systems, so compute the remainder as
176 ! r = n - 3*q.
177
178 e_x = e_a - e_r * 3
179
180 ! Insert the new 11-bit exponent into xb and write to x and extend the
181 ! bitcount to 12, so that the sign bit is zero and x is always positive.
182 call mvbits(e_x + bias, 0, explen + 1, xb, fraclen)
183 x = transfer(xb, 1.)
184end subroutine rescale_cbrt
185
186
187!> Undo the rescaling of a real number back to its original base.
188pure function descale(x, e_a, s_a) result(a)
189 real, intent(in) :: x
190 !< The rescaled value which is to be restored in ambiguous units [B]
191 integer(kind=int64), intent(in) :: e_a
192 !< Exponent of the unscaled value
193 integer(kind=int64), intent(in) :: s_a
194 !< Sign bit of the unscaled value
195 real :: a
196 !< Restored value with the corrected exponent and sign in arbitrary units [A]
197
198 integer(kind=int64) :: xb
199 ! Bit-packed real number into integer form
200 integer(kind=int64) :: e_x
201 ! Biased exponent of x
202
203 ! Apply the corrected exponent and sign to x.
204 xb = transfer(x, 1_int64)
205 e_x = ibits(xb, expbit, explen)
206 call mvbits(e_a + e_x, 0, explen, xb, expbit)
207 call mvbits(s_a, 0, 1, xb, signbit)
208 a = transfer(xb, 1.)
209end function descale
210
211
212!> Returns true if any unit test of intrinsic_functions fails, or false if they all pass.
213function intrinsic_functions_unit_tests(verbose) result(fail)
214 logical, intent(in) :: verbose !< If true, write results to stdout
215 logical :: fail !< True if any of the unit tests fail
216
217 ! Local variables
218 real :: testval ! A test value for self-consistency testing [nondim]
219 logical :: v
220 integer :: n
221
222 fail = .false.
223 v = verbose
224 write(stdout,*) '==== MOM_intrinsic_functions: intrinsic_functions_unit_tests ==='
225
226 fail = fail .or. test_cuberoot(v, 1.2345678901234e9)
227 fail = fail .or. test_cuberoot(v, -9.8765432109876e-21)
228 fail = fail .or. test_cuberoot(v, 64.0)
229 fail = fail .or. test_cuberoot(v, -0.5000000000001)
230 fail = fail .or. test_cuberoot(v, 0.0)
231 fail = fail .or. test_cuberoot(v, 1.0)
232 fail = fail .or. test_cuberoot(v, 0.125)
233 fail = fail .or. test_cuberoot(v, 0.965)
234 fail = fail .or. test_cuberoot(v, 1.0 - epsilon(1.0))
235 fail = fail .or. test_cuberoot(v, 1.0 - 0.5*epsilon(1.0))
236
237 testval = 1.0e-99
238 v = .false.
239 do n=-160,160
240 fail = fail .or. test_cuberoot(v, testval)
241 testval = (-2.908 * (1.414213562373 + 1.2345678901234e-5*n)) * testval
242 enddo
244
245!> True if the cube of cuberoot(val) does not closely match val. False otherwise.
246logical function test_cuberoot(verbose, val)
247 logical, intent(in) :: verbose !< If true, write results to stdout
248 real, intent(in) :: val !< The real value to test, in arbitrary units [A]
249 ! Local variables
250 real :: diff ! The difference between val and the cube root of its cube [A].
251
252 diff = val - cuberoot(val)**3
253 test_cuberoot = (abs(diff) > 2.0e-15*abs(val))
254
255 if (test_cuberoot) then
256 write(stdout, '("For val = ",ES22.15,", (val - cuberoot(val**3))) = ",ES9.2," <-- FAIL")') val, diff
257 elseif (verbose) then
258 write(stdout, '("For val = ",ES22.15,", (val - cuberoot(val**3))) = ",ES9.2)') val, diff
259
260 endif
261end function test_cuberoot
262