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.
120pure subroutine rescale_cbrt(a, x, e_r, s_a)
121 real, intent(in) :: a
122 !< The real parameter to be rescaled for cube root in arbitrary units cubed [A3]
123 real, intent(out) :: x
124 !< The rescaled value of a in the range from 0.125 < asx <= 1.0, in ambiguous units cubed [B3]
125 integer(kind=int64), intent(out) :: e_r
126 !< Cube root of the exponent of the rescaling of `a`
127 integer(kind=int64), intent(out) :: s_a
128 !< The sign bit of a
129
130 integer(kind=int64) :: xb
131 ! Floating point value of a, bit-packed as an integer
132 integer(kind=int64) :: e_a
133 ! Unscaled exponent of a
134 integer(kind=int64) :: e_x
135 ! Exponent of x
136 integer(kind=int64) :: e_div, e_mod
137 ! Quotient and remainder of e in e = 3*(e/3) + modulo(e,3).
138
139 ! Pack bits of a into xb and extract its exponent and sign.
140 xb = transfer(a, 1_int64)
141 s_a = ibits(xb, signbit, 1)
142 e_a = ibits(xb, expbit, explen) - bias
143
144 ! Compute terms of exponent decomposition e = 3*(e/3) + modulo(e,3).
145 ! (Fortran division is round-to-zero, so we must emulate floor division.)
146 e_mod = modulo(e_a, 3_int64)
147 e_div = (e_a - e_mod)/3
148
149 ! Our scaling decomposes e_a into e = {3*(e/3) + 3} + {modulo(e,3) - 3}.
150
151 ! The first term is a perfect cube, whose cube root is computed below.
152 e_r = e_div + 1
153
154 ! The second term ensures that x is shifted to [0.125, 1).
155 e_x = e_mod - 3
156
157 ! Insert the new 11-bit exponent into xb and write to x and extend the
158 ! bitcount to 12, so that the sign bit is zero and x is always positive.
159 call mvbits(e_x + bias, 0, explen + 1, xb, fraclen)
160 x = transfer(xb, 1.)
161end subroutine rescale_cbrt
162
163
164!> Undo the rescaling of a real number back to its original base.
165pure function descale(x, e_a, s_a) result(a)
166 real, intent(in) :: x
167 !< The rescaled value which is to be restored in ambiguous units [B]
168 integer(kind=int64), intent(in) :: e_a
169 !< Exponent of the unscaled value
170 integer(kind=int64), intent(in) :: s_a
171 !< Sign bit of the unscaled value
172 real :: a
173 !< Restored value with the corrected exponent and sign in arbitrary units [A]
174
175 integer(kind=int64) :: xb
176 ! Bit-packed real number into integer form
177 integer(kind=int64) :: e_x
178 ! Biased exponent of x
179
180 ! Apply the corrected exponent and sign to x.
181 xb = transfer(x, 1_int64)
182 e_x = ibits(xb, expbit, explen)
183 call mvbits(e_a + e_x, 0, explen, xb, expbit)
184 call mvbits(s_a, 0, 1, xb, signbit)
185 a = transfer(xb, 1.)
186end function descale
187
188
189!> Returns true if any unit test of intrinsic_functions fails, or false if they all pass.
190function intrinsic_functions_unit_tests(verbose) result(fail)
191 logical, intent(in) :: verbose !< If true, write results to stdout
192 logical :: fail !< True if any of the unit tests fail
193
194 ! Local variables
195 real :: testval ! A test value for self-consistency testing [nondim]
196 logical :: v
197 integer :: n
198
199 fail = .false.
200 v = verbose
201 write(stdout,*) '==== MOM_intrinsic_functions: intrinsic_functions_unit_tests ==='
202
203 fail = fail .or. test_cuberoot(v, 1.2345678901234e9)
204 fail = fail .or. test_cuberoot(v, -9.8765432109876e-21)
205 fail = fail .or. test_cuberoot(v, 64.0)
206 fail = fail .or. test_cuberoot(v, -0.5000000000001)
207 fail = fail .or. test_cuberoot(v, 0.0)
208 fail = fail .or. test_cuberoot(v, 1.0)
209 fail = fail .or. test_cuberoot(v, 0.125)
210 fail = fail .or. test_cuberoot(v, 0.965)
211 fail = fail .or. test_cuberoot(v, 1.0 - epsilon(1.0))
212 fail = fail .or. test_cuberoot(v, 1.0 - 0.5*epsilon(1.0))
213
214 testval = 1.0e-99
215 v = .false.
216 do n=-160,160
217 fail = fail .or. test_cuberoot(v, testval)
218 testval = (-2.908 * (1.414213562373 + 1.2345678901234e-5*n)) * testval
219 enddo
221
222!> True if the cube of cuberoot(val) does not closely match val. False otherwise.
223logical function test_cuberoot(verbose, val)
224 logical, intent(in) :: verbose !< If true, write results to stdout
225 real, intent(in) :: val !< The real value to test, in arbitrary units [A]
226 ! Local variables
227 real :: diff ! The difference between val and the cube root of its cube [A].
228
229 diff = val - cuberoot(val)**3
230 test_cuberoot = (abs(diff) > 2.0e-15*abs(val))
231
232 if (test_cuberoot) then
233 write(stdout, '("For val = ",ES22.15,", (val - cuberoot(val**3))) = ",ES9.2," <-- FAIL")') val, diff
234 elseif (verbose) then
235 write(stdout, '("For val = ",ES22.15,", (val - cuberoot(val**3))) = ",ES9.2)') val, diff
236
237 endif
238end function test_cuberoot
239