MOM6
MOM_random.F90
1 !> Provides gridded random number capability
2 module mom_random
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
7 use mom_time_manager, only : time_type, set_date, get_date
8 
9 use mersennetwister_mod, only : randomnumbersequence ! Random number class from FMS
10 use mersennetwister_mod, only : new_randomnumbersequence ! Constructor/initializer
11 use mersennetwister_mod, only : getrandomreal ! Generates a random number
12 use mersennetwister_mod, only : getrandompositiveint ! Generates a random positive integer
13 
14 use iso_fortran_env, only : stdout=>output_unit, stderr=>error_unit
15 
16 implicit none ; private
17 
18 public :: random_0d_constructor
19 public :: random_01
20 public :: random_norm
21 public :: random_2d_constructor
22 public :: random_2d_01
23 public :: random_2d_norm
24 public :: random_unit_tests
25 
26 #include <MOM_memory.h>
27 
28 !> Container for pseudo-random number generators
29 type, public :: prng ; private
30 
31  !> Scalar random number generator for whole model
32  type(randomnumbersequence) :: stream0d
33 
34  !> Random number generator for each cell on horizontal grid
35  type(randomnumbersequence), dimension(:,:), allocatable :: stream2d
36 
37 end type prng
38 
39 contains
40 
41 !> Returns a random number between 0 and 1
42 real function random_01(CS)
43  type(prng), intent(inout) :: cs !< Container for pseudo-random number generators
44 
45  random_01 = getrandomreal(cs%stream0d)
46 
47 end function random_01
48 
49 !> Returns an approximately normally distributed random number with mean 0 and variance 1
50 real function random_norm(CS)
51  type(prng), intent(inout) :: cs !< Container for pseudo-random number generators
52  ! Local variables
53  integer :: i
54 
55  random_norm = getrandomreal(cs%stream0d) - 0.5
56  do i = 1,11
57  random_norm = random_norm + ( getrandomreal(cs%stream0d) - 0.5 )
58  enddo
59 
60 end function random_norm
61 
62 !> Generates random numbers between 0 and 1 for each cell of the model grid
63 subroutine random_2d_01(CS, HI, rand)
64  type(prng), intent(inout) :: cs !< Container for pseudo-random number generators
65  type(hor_index_type), intent(in) :: hi !< Horizontal index structure
66  real, dimension(SZI_(HI),SZJ_(HI)), intent(out) :: rand !< Random numbers between 0 and 1
67  ! Local variables
68  integer :: i,j
69 
70  do j = hi%jsd,hi%jed
71  do i = hi%isd,hi%ied
72  rand(i,j) = getrandomreal( cs%stream2d(i,j) )
73  enddo
74  enddo
75 
76 end subroutine random_2d_01
77 
78 !> Returns an approximately normally distributed random number with mean 0 and variance 1
79 !! for each cell of the model grid
80 subroutine random_2d_norm(CS, HI, rand)
81  type(prng), intent(inout) :: cs !< Container for pseudo-random number generators
82  type(hor_index_type), intent(in) :: hi !< Horizontal index structure
83  real, dimension(SZI_(HI),SZJ_(HI)), intent(out) :: rand !< Random numbers between 0 and 1
84  ! Local variables
85  integer :: i,j,n
86 
87  do j = hi%jsd,hi%jed
88  do i = hi%isd,hi%ied
89  rand(i,j) = getrandomreal( cs%stream2d(i,j) ) - 0.5
90  enddo
91  do n = 1,11
92  do i = hi%isd,hi%ied
93  rand(i,j) = rand(i,j) + ( getrandomreal( cs%stream2d(i,j) ) - 0.5 )
94  enddo
95  enddo
96  enddo
97 
98 end subroutine random_2d_norm
99 
100 !> Constructor for scalar PRNG. Can be used to reset the sequence.
101 subroutine random_0d_constructor(CS, Time, seed)
102  type(prng), intent(inout) :: cs !< Container for pseudo-random number generators
103  type(time_type), intent(in) :: time !< Current model time
104  integer, intent(in) :: seed !< Seed for PRNG
105  ! Local variables
106  integer :: tseed
107 
108  tseed = seed_from_time(time)
109  tseed = ieor(tseed, seed)
110  cs%stream0d = new_randomnumbersequence(tseed)
111 
112 end subroutine random_0d_constructor
113 
114 !> Constructor for gridded PRNG. Can be used to reset the sequence.
115 subroutine random_2d_constructor(CS, HI, Time, seed)
116  type(prng), intent(inout) :: cs !< Container for pseudo-random number generators
117  type(hor_index_type), intent(in) :: hi !< Horizontal index structure
118  type(time_type), intent(in) :: time !< Current model time
119  integer, intent(in) :: seed !< Seed for PRNG
120  ! Local variables
121  integer :: i,j,sseed,tseed
122 
123  if (.not. allocated(cs%stream2d)) allocate( cs%stream2d(hi%isd:hi%ied,hi%jsd:hi%jed) )
124 
125  tseed = seed_from_time(time)
126  tseed = ieor(tseed*9007, seed)
127  do j = hi%jsd,hi%jed
128  do i = hi%isd,hi%ied
129  sseed = seed_from_index(hi, i, j)
130  sseed = ieor(tseed, sseed*7993)
131  cs%stream2d(i,j) = new_randomnumbersequence(sseed)
132  enddo
133  enddo
134 
135 end subroutine random_2d_constructor
136 
137 !> Return a seed derived as hash of values in Time
138 integer function seed_from_time(Time)
139  type(time_type), intent(in) :: time !< Current model time
140  ! Local variables
141  integer :: yr,mo,dy,hr,mn,sc,s1,s2
142 
143  call get_date(time,yr,mo,dy,hr,mn,sc)
144  s1 = sc + 61*(mn + 61*hr) + 379 ! Range 379 .. 89620
145  ! Fun fact: 2147483647 is the eighth Mersenne prime.
146  ! This is not the reason for using 2147483647 here. It is the
147  ! largest integer of kind=4.
148  s2 = modulo(dy + 32*(mo + 13*yr), 2147483647_4) ! Range 0 .. 2147483646
149  seed_from_time = ieor(s1*4111, s2)
150 
151 end function seed_from_time
152 
153 !> Create seed from position index
154 integer function seed_from_index(HI, i, j)
155  type(hor_index_type), intent(in) :: hi !< Horizontal index structure
156  integer, intent(in) :: i !< i-index (of h-cell)
157  integer, intent(in) :: j !< j-index (of h-cell)
158  ! Local variables
159  integer :: ig, jg, ni, nj, ij
160 
161  ni = hi%niglobal
162  nj = hi%njglobal
163  ! Periodicity is assumed here but does not break non-periodic models
164  ig = mod(hi%idg_offset + i - 1 + ni, ni)+1
165  jg = max(hi%jdg_offset + j, 0)
166  if (jg>nj) then ! Tri-polar hard-coded until we put needed info in HI **TODO**
167  jg = 2*nj+1-jg
168  ig = ni+1-ig
169  endif
170  seed_from_index = ig + ni*(jg-1)
171 
172 end function seed_from_index
173 
174 !> Destructor for PRNG
175 subroutine random_destruct(CS)
176  type(prng), pointer :: CS !< Container for pseudo-random number generators
177 
178  if (allocated(cs%stream2d)) deallocate(cs%stream2d)
179  !deallocate(CS)
180 end subroutine random_destruct
181 
182 !> Runs some statistical tests on the PRNG
183 logical function random_unit_tests(verbose)
184  logical :: verbose !< True if results should be written to stdout
185  ! Local variables
186  type(prng) :: test_rng ! Generator
187  type(time_type) :: time ! Model time
188  real :: r1, r2, r3 ! Some random numbers and re-used work variables
189  real :: mean, var, ar1, std ! Some statistics
190  integer :: stdunit ! For messages
191  integer, parameter :: n_samples = 800
192  integer :: i, j, ni, nj
193  ! Fake being on a decomposed domain
194  type(hor_index_type), pointer :: hi => null() !< Not the real HI
195  real, dimension(:,:), allocatable :: r2d ! Random numbers
196 
197  ! Fake a decomposed domain
198  ni = 6
199  nj = 9
200  allocate(hi)
201  hi%isd = 0
202  hi%ied = ni+1
203  hi%jsd = 0
204  hi%jed = nj+1
205  hi%niglobal = ni
206  hi%njglobal = nj
207  hi%idg_offset = 0
208  hi%jdg_offset = 0
209 
210  random_unit_tests = .false.
211  stdunit = stdout
212  write(stdunit,'(1x,a)') '==== MOM_random: random_unit_tests ======================='
213 
214  if (verbose) write(stdunit,'(1x,"random: ",a)') '-- Time-based seeds ---------------------'
215  ! Check time-based seed generation
216  time = set_date(1903, 11, 21, 13, 47, 29)
217  i = seed_from_time(time)
218  random_unit_tests = random_unit_tests .or. &
219  test_fn(verbose, i==212584341, 'time seed 1903/11/21 13:47:29', ivalue=i)
220  time = set_date(1903, 11, 22, 13, 47, 29)
221  i = seed_from_time(time)
222  random_unit_tests = random_unit_tests .or.&
223  test_fn(verbose, i==212584342, 'time seed 1903/11/22 13:47:29', ivalue=i)
224  time = set_date(1903, 11, 21, 13, 47, 30)
225  i = seed_from_time(time)
226  random_unit_tests = random_unit_tests .or.&
227  test_fn(verbose, i==212596634, 'time seed 1903/11/21 13:47:30', ivalue=i)
228 
229  if (verbose) write(stdunit,'(1x,"random: ",a)') '-- PRNG tests ---------------------------'
230  ! Generate a random number, r1
231  call random_0d_constructor(test_rng, time, 1)
232  r1 = random_01(test_rng)
233  random_unit_tests = random_unit_tests .or. &
234  test_fn(verbose, abs(r1-4.75310122e-2)<1.e-9, 'first call', r1)
235 
236  ! Check that we get a different number, r2, on a second call
237  r2 = random_01(test_rng)
238  random_unit_tests = random_unit_tests .or. &
239  test_fn(verbose, abs(r2-2.71289742e-1)<1.e-9, 'consecutive test', r2)
240 
241  ! Check that we can reproduce r1 by resetting the seed
242  call random_0d_constructor(test_rng, time, 1)
243  r2 = random_01(test_rng)
244  random_unit_tests = random_unit_tests .or. &
245  test_fn(verbose, abs(r2-r1)==0., 'reproduce test', r2)
246 
247  ! Check that we get a different number, r2, with a different seed but same date
248  call random_0d_constructor(test_rng, time, 2)
249  r2 = random_01(test_rng)
250  random_unit_tests = random_unit_tests .or. &
251  test_fn(verbose, abs(r2-7.15508473e-1)<1.e-9, 'different seed test', r2)
252 
253  ! Check that we get a different number, r2, for a different date but same seed
254  time = set_date(1903, 11, 21, 13, 0, 29)
255  call random_0d_constructor(test_rng, time, 1)
256  r2 = random_01(test_rng)
257  random_unit_tests = random_unit_tests .or. &
258  test_fn(verbose, abs(r2-9.56667163e-1)<1.e-9, 'different date test', r2)
259 
260  if (verbose) write(stdunit,'(1x,"random: ",a)') '-- index-based seeds --------------------'
261  ! Check index-based seed
262  i = seed_from_index(hi,1,1)
263  random_unit_tests = random_unit_tests .or. test_fn(verbose, i==1, 'seed from index (1,1)', ivalue=i)
264  j = seed_from_index(hi,ni+1,1)
265  random_unit_tests = random_unit_tests .or. test_fn(verbose, j==i, 'seed from index (n+1,1)', ivalue=j)
266  i = seed_from_index(hi,ni,1)
267  random_unit_tests = random_unit_tests .or. test_fn(verbose, i==6, 'seed from index (n,1)', ivalue=i)
268  j = seed_from_index(hi,0,1)
269  random_unit_tests = random_unit_tests .or. test_fn(verbose, j==i, 'seed from index (0,1)', ivalue=j)
270  i = seed_from_index(hi,1,nj)
271  random_unit_tests = random_unit_tests .or. test_fn(verbose, i==49, 'seed from index (1,n)', ivalue=i)
272  j = seed_from_index(hi,ni,nj+1)
273  random_unit_tests = random_unit_tests .or. test_fn(verbose, j==i, 'seed from index (n,n+1)', ivalue=j)
274  i = seed_from_index(hi,ni,nj)
275  random_unit_tests = random_unit_tests .or. test_fn(verbose, i==54, 'seed from index (n,n)', ivalue=i)
276  j = seed_from_index(hi,1,nj+1)
277  random_unit_tests = random_unit_tests .or. test_fn(verbose, j==i, 'seed from index (1,n+1)', ivalue=j)
278 
279  if (.not.random_unit_tests) write(stdunit,'(1x,a)') 'Passed unit tests'
280  ! The rest of these are not unit tests but statistical tests and as such
281  ! could fail for different sample sizes but happen to pass here.
282 
283  ! Check statistics of large samples for uniform generator
284  mean = 0. ; var = 0. ; ar1 = 0. ; r2 = 0.
285  do i = 1, n_samples
286  r1 = random_01(test_rng) - 0.5
287  mean = mean + r1
288  var = var + r1**2
289  ar1 = ar1 + r1*r2
290  r2 = r1 ! Keep copy of last value
291  enddo
292  mean = mean / real(n_samples) ! Expected mean is 0
293  var = var / real(n_samples) ! Expected variance is 1/12
294  ar1 = ar1 / real(n_samples-1) ! Autocovariance
295  std = sqrt(var) ! Expected std is sqrt(1/12)
296  r2 = mean*sqrt(real(12*n_samples)) ! Normalized error in mean
297  r3 = std*sqrt(12.) ! Normalized standard deviation
298  r1 = ( ar1 * sqrt(real(n_samples-1)) ) / var
299  if (verbose) then
300  write(stdunit,'(1x,"random: ",a)') '-- Uniform -0.5 .. 0.5 generator --------'
301  write(stdunit,'(1x,"random: ",a,f12.9)') 'mean =',mean,'std =',std,'AR1 =',ar1
302  write(stdunit,'(1x,"random: ",a,f12.9)') 'norm. mean =',r2, &
303  'norm. std =',r3,'norm. AR1 =',r1
304  endif
305  random_unit_tests = random_unit_tests .or. &
306  test_fn(verbose, abs(r2)<2., &
307  'n>>1, mean within 2 sigma [uniform]', r2)
308  random_unit_tests = random_unit_tests .or. &
309  test_fn(verbose, abs(r3-1.)<1./sqrt(real(n_samples)), &
310  'n>>1, std ~ 1/sqrt(12) [uniform]', r3-1.)
311  random_unit_tests = random_unit_tests .or. &
312  test_fn(verbose, abs(r1)<2., &
313  'n>>1, AR1 < std/sqrt(n) [uniform]', r1)
314 
315  ! Check statistics of large samples for normal generator
316  mean = 0. ; var = 0. ; ar1 = 0. ; r2 = 0.
317  do i = 1, n_samples
318  r1 = random_norm(test_rng)
319  mean = mean + r1
320  var = var + r1**2
321  ar1 = ar1 + r1*r2
322  r2 = r1 ! Keep copy of last value for AR calculation
323  enddo
324  mean = mean / real(n_samples)
325  var = var / real(n_samples)
326  ar1 = ar1 / real(n_samples)
327  std = sqrt(var)
328  r3 = 1./sqrt(real(n_samples)) ! Standard error of mean
329  r2 = mean*sqrt(real(n_samples)) ! Normalized error in mean
330  r3 = std ! Normalized standard deviation
331  r1 = ( ar1 * sqrt(real(n_samples-1)) ) / var
332  if (verbose) then
333  write(stdunit,'(1x,"random: ",a)') '-- Normal distribution generator --------'
334  write(stdunit,'(1x,"random: ",a,f12.9)') 'mean =',mean,'std =',std,'AR1 =',ar1
335  write(stdunit,'(1x,"random: ",a,f12.9)') 'norm. error in mean =',r2, &
336  'norm. standard deviation =',r3,'norm. AR1 =',r1
337  endif
338  random_unit_tests = random_unit_tests .or. &
339  test_fn(verbose, abs(r2)<2., &
340  'n>>1, mean within 2 sigma [norm]', r2)
341  random_unit_tests = random_unit_tests .or. &
342  test_fn(verbose, abs(r3-1.)<1./sqrt(real(n_samples)), &
343  'n>>1, std ~ 1 [norm]', r3-1.)
344  random_unit_tests = random_unit_tests .or. &
345  test_fn(verbose, abs(r1)<2., &
346  'n>>1, AR1 < std/sqrt(n) [norm]', r1)
347 
348  if (verbose) write(stdunit,'(1x,"random: ",a)') '-- 2d PRNG ------------------------------'
349  ! Check 2d random number generator 0..1
350  allocate( r2d(hi%isd:hi%ied,hi%jsd:hi%jed) )
351  call random_2d_constructor(test_rng, hi, time, 123)
352  r2d(:,:) = -999. ! Use -9. to detect unset values
353  call random_2d_01(test_rng, hi, r2d)
354  if (any(abs(r2d(:,:)+999.)<=0.)) random_unit_tests=.true.
355  r1 = minval(r2d)
356  r2 = maxval(r2d)
357  random_unit_tests = random_unit_tests .or. test_fn(verbose, r1>=0., '2d all set', r1)
358  random_unit_tests = random_unit_tests .or. test_fn(verbose, r2<=1., '2d all valid', r2)
359  mean = sum( r2d(1:ni,1:nj) - 0.5 )/real(ni*nj)
360  var = sum( (r2d(1:ni,1:nj) - 0.5 - mean)**2 )/real(ni*nj)
361  std = sqrt(var)
362  r3 = 1./sqrt(real(12*ni*nj)) ! Standard error of mean
363  r2 = mean*sqrt(real(12*ni*nj)) ! Normalized error in mean
364  r3 = std*sqrt(12.) ! Normalized standard deviation
365  if (verbose) then
366  write(stdunit,'(1x,"random: ",a)') '2D uniform 0..1 generator'
367  write(stdunit,'(1x,"random: ",a,f12.9)') 'mean =',mean,'std =',std
368  write(stdunit,'(1x,"random: ",a,f12.9)') 'norm. error in mean =',r2
369  write(stdunit,'(1x,"random: ",a,f12.9)') 'norm. standard deviation =',r3
370  endif
371  random_unit_tests = random_unit_tests .or. &
372  test_fn(verbose, abs(r2)<2., &
373  '2d, mean within 2 sigma [uniform]', r2)
374  random_unit_tests = random_unit_tests .or. &
375  test_fn(verbose, abs(r3-1.)<1./sqrt(real(ni*nj)), &
376  '2d, std ~ 1/sqrt(12) [uniform]', r3-1.)
377  if (verbose) then
378  write(stdunit,'(1x,"random:")')
379  write(stdunit,'(1x,"random:",8f8.5)') r2d
380  write(stdunit,'(1x,"random:")')
381  endif
382 
383  ! Check 2d normal random number generator
384  call random_2d_norm(test_rng, hi, r2d)
385  mean = sum( r2d(1:ni,1:nj) )/real(ni*nj)
386  var = sum( r2d(1:ni,1:nj)**2 )/real(ni*nj)
387  std = sqrt(var)
388  r3 = 1./sqrt(real(ni*nj)) ! Standard error of mean
389  r2 = mean*sqrt(real(ni*nj)) ! Normalized error in mean
390  r3 = std ! Normalized standard deviation
391  if (verbose) then
392  write(stdunit,'(1x,"random: ",a)') '2D normal generator'
393  write(stdunit,'(1x,"random: ",a,f12.9)') 'mean =',mean,'std =',std
394  write(stdunit,'(1x,"random: ",a,f12.9)') 'norm. error in mean =',r2
395  write(stdunit,'(1x,"random: ",a,f12.9)') 'norm. standard deviation =',r3
396  endif
397  random_unit_tests = random_unit_tests .or. &
398  test_fn(verbose, abs(r2)<2., &
399  '2d, mean within 2 sigma [norm]', r2)
400  random_unit_tests = random_unit_tests .or. &
401  test_fn(verbose, abs(r3-1.)<1./sqrt(real(ni*nj)), &
402  '2d, std ~ 1/sqrt(12) [norm]', r3-1.)
403 
404  ! Clean up
405  deallocate(r2d)
406  deallocate(hi)
407 
408  if (.not.random_unit_tests) write(stdunit,'(1x,a)') 'Passed statistical tests'
409 
410 end function random_unit_tests
411 
412 !> Convenience function for reporting result of test
413 logical function test_fn(verbose, good, label, rvalue, ivalue)
414  logical, intent(in) :: verbose !< Verbosity
415  logical, intent(in) :: good !< True if pass, false otherwise
416  character(len=*), intent(in) :: label !< Label for messages
417  real, intent(in) :: rvalue !< Result of calculation
418  integer, intent(in) :: ivalue !< Result of calculation
419  optional :: rvalue, ivalue
420 
421  if (present(ivalue)) then
422  if (.not. good) then
423  write(stdout,'(1x,a,i10,1x,a,a)') 'random: result =',ivalue,label,' <------- FAIL!'
424  write(stderr,'(1x,a,i10,1x,a,a)') 'random: result =',ivalue,label,' <------- FAIL!'
425  elseif (verbose) then
426  write(stdout,'(1x,a,i10,1x,a)') 'random: result =',ivalue,label
427  endif
428  else
429  if (.not. good) then
430  write(stdout,'(1x,a,1pe15.8,1x,a,a)') 'random: result =',rvalue,label,' <------- FAIL!'
431  write(stderr,'(1x,a,1pe15.8,1x,a,a)') 'random: result =',rvalue,label,' <------- FAIL!'
432  elseif (verbose) then
433  write(stdout,'(1x,a,1pe15.8,1x,a)') 'random: result =',rvalue,label
434  endif
435  endif
436  test_fn = .not. good
437 
438 end function test_fn
439 
440 end module mom_random
441 
442 !> \namespace mom_random
443 !!
444 !! Provides MOM6 wrappers to the FMS implementation of the Mersenne twister.
445 !!
446 !! Example usage:
447 !! \code
448 !! type(PRNG) :: rng
449 !! real :: rn
450 !! call random_0d_constructor(rng, Time, seed) ! Call this each time-step
451 !! rn = random_01(rng)
452 !! rn = random_norm(rng)
453 !!
454 !! type(PRNG) :: rng
455 !! real, dimension(:,:) :: rn2d
456 !! call random_2d_constructor(rng, HI, Time, seed) ! Call this each time-step
457 !! call random_2d_01(rng, HI, rn2d)
458 !! call random_2d_norm(rng, HI, rn2d)
459 !!
460 !! Note: reproducibility across restarts is implemented by using time-derived
461 !! seeds to pass to the Mersenne twister. It is therefore important that any
462 !! PRNG type be re-initialized each time-step.
463 !! \endcode
Wraps the FMS time manager functions.
Defines the horizontal index type (hor_index_type) used for providing index ranges.
Container for horizontal index ranges for data, computational and global domains.
Container for pseudo-random number generators.
Definition: MOM_random.F90:29
Provides gridded random number capability.
Definition: MOM_random.F90:2