MPI-AMRVAC  3.0
The MPI - Adaptive Mesh Refinement - Versatile Advection Code
mod_random.t
Go to the documentation of this file.
1 !> Module for pseudo random number generation. The internal pseudo random
2 !> generator is the xoroshiro128plus method.
3 module mod_random
4 
5  implicit none
6  private
7 
8  ! A 64 bit floating point type
9  integer, parameter :: dp = kind(0.0d0)
10 
11  ! A 32 bit integer type
12  integer, parameter :: i4 = selected_int_kind(9)
13 
14  ! A 64 bit integer type
15  integer, parameter :: i8 = selected_int_kind(18)
16 
17  !> Random number generator type, which contains the state
18  type rng_t
19  !> The rng state (always use your own seed)
20  integer(i8), private :: s(2) = [123456789_i8, 987654321_i8]
21  contains
22  procedure, non_overridable :: set_seed ! Seed the generator
23  procedure, non_overridable :: jump ! Jump function (see below)
24  procedure, non_overridable :: int_4 ! 4-byte random integer
25  procedure, non_overridable :: int_8 ! 8-byte random integer
26  procedure, non_overridable :: unif_01 ! Uniform (0,1] real
27  procedure, non_overridable :: unif_01_vec ! Uniform (0,1] real vector
28  procedure, non_overridable :: normal ! One normal(0,1) number
29  procedure, non_overridable :: two_normals ! Two normal(0,1) samples
30  procedure, non_overridable :: poisson ! Sample from Poisson-dist.
31  procedure, non_overridable :: circle ! Sample on a circle
32  procedure, non_overridable :: sphere ! Sample on a sphere
33  procedure, non_overridable :: next ! Internal method
34  end type rng_t
35 
36  !> Parallel random number generator type
37  type prng_t
38  type(rng_t), allocatable :: rngs(:)
39  contains
40  procedure, non_overridable :: init_parallel
41  end type prng_t
42 
43  public :: rng_t
44  public :: prng_t
45 
46 contains
47 
48  !> Initialize a collection of rng's for parallel use
49  subroutine init_parallel(self, n_proc, rng)
50  class(prng_t), intent(inout) :: self
51  type(rng_t), intent(inout) :: rng
52  integer, intent(in) :: n_proc
53  integer :: n
54 
55  allocate(self%rngs(n_proc))
56  self%rngs(1) = rng
57 
58  do n = 2, n_proc
59  self%rngs(n) = self%rngs(n-1)
60  call self%rngs(n)%jump()
61  end do
62  end subroutine init_parallel
63 
64  !> Set a seed for the rng
65  subroutine set_seed(self, the_seed)
66  class(rng_t), intent(inout) :: self
67  integer(i8), intent(in) :: the_seed(2)
68 
69  self%s = the_seed
70 
71  ! Simulate calls to next() to improve randomness of first number
72  call self%jump()
73  end subroutine set_seed
74 
75  ! This is the jump function for the generator. It is equivalent
76  ! to 2^64 calls to next(); it can be used to generate 2^64
77  ! non-overlapping subsequences for parallel computations.
78  subroutine jump(self)
79  class(rng_t), intent(inout) :: self
80  integer :: i, b
81  integer(i8) :: global_time(2), dummy
82 
83  ! The signed equivalent of the unsigned constants
84  integer(i8), parameter :: jmp_c(2) = &
85  (/-4707382666127344949_i8, -2852180941702784734_i8/)
86 
87  global_time = 0
88  do i = 1, 2
89  do b = 0, 63
90  if (iand(jmp_c(i), shiftl(1_i8, b)) /= 0) then
91  global_time = ieor(global_time, self%s)
92  end if
93  dummy = self%next()
94  end do
95  end do
96 
97  self%s = global_time
98  end subroutine jump
99 
100  !> Return 4-byte integer
101  integer(i4) function int_4(self)
102  class(rng_t), intent(inout) :: self
103  int_4 = int(self%next(), i4)
104  end function int_4
105 
106  !> Return 8-byte integer
107  integer(i8) function int_8(self)
108  class(rng_t), intent(inout) :: self
109  int_8 = self%next()
110  end function int_8
111 
112  !> Get a uniform [0,1) random real (double precision)
113  real(dp) function unif_01(self)
114  class(rng_t), intent(inout) :: self
115  integer(i8) :: x
116  real(dp) :: tmp
117 
118  x = self%next()
119  x = ior(shiftl(1023_i8, 52), shiftr(x, 12))
120  unif_01 = transfer(x, tmp) - 1.0_dp
121  end function unif_01
122 
123  !> Fill array with uniform random numbers
124  subroutine unif_01_vec(self, rr)
125  class(rng_t), intent(inout) :: self
126  real(dp), intent(out) :: rr(:)
127  integer :: i
128 
129  do i = 1, size(rr)
130  rr(i) = self%unif_01()
131  end do
132  end subroutine unif_01_vec
133 
134  !> Return two normal random variates with mean 0 and variance 1.
135  !> http://en.wikipedia.org/wiki/Marsaglia_polar_method
136  function two_normals(self) result(rands)
137  class(rng_t), intent(inout) :: self
138  real(dp) :: rands(2), sum_sq
139 
140  do
141  rands(1) = 2 * self%unif_01() - 1
142  rands(2) = 2 * self%unif_01() - 1
143  sum_sq = sum(rands**2)
144  if (sum_sq < 1.0_dp .and. sum_sq > 0.0_dp) exit
145  end do
146  rands = rands * sqrt(-2 * log(sum_sq) / sum_sq)
147  end function two_normals
148 
149  !> Single normal random number
150  real(dp) function normal(self)
151  class(rng_t), intent(inout) :: self
152  real(dp) :: rands(2)
153 
154  rands = self%two_normals()
155  normal = rands(1)
156  end function normal
157 
158  !> Return Poisson random variate with rate lambda. Works well for lambda < 30
159  !> or so. For lambda >> 1 it can produce wrong results due to roundoff error.
160  function poisson(self, lambda) result(rr)
161  class(rng_t), intent(inout) :: self
162  real(dp), intent(in) :: lambda
163  integer(i4) :: rr
164  real(dp) :: expl, p
165 
166  expl = exp(-lambda)
167  rr = 0
168  p = self%unif_01()
169 
170  do while (p > expl)
171  rr = rr + 1
172  p = p * self%unif_01()
173  end do
174  end function poisson
175 
176  !> Sample point on a circle with given radius
177  function circle(self, radius) result(xy)
178  class(rng_t), intent(inout) :: self
179  real(dp), intent(in) :: radius
180  real(dp) :: rands(2), xy(2)
181  real(dp) :: sum_sq
182 
183  ! Method for uniform sampling on circle
184  do
185  rands(1) = 2 * self%unif_01() - 1
186  rands(2) = 2 * self%unif_01() - 1
187  sum_sq = sum(rands**2)
188  if (sum_sq <= 1) exit
189  end do
190 
191  xy(1) = (rands(1)**2 - rands(2)**2) / sum_sq
192  xy(2) = 2 * rands(1) * rands(2) / sum_sq
193  xy = xy * radius
194  end function circle
195 
196  !> Sample point on a sphere with given radius
197  function sphere(self, radius) result(xyz)
198  class(rng_t), intent(inout) :: self
199  real(dp), intent(in) :: radius
200  real(dp) :: rands(2), xyz(3)
201  real(dp) :: sum_sq, tmp_sqrt
202 
203  ! Marsaglia method for uniform sampling on sphere
204  do
205  rands(1) = 2 * self%unif_01() - 1
206  rands(2) = 2 * self%unif_01() - 1
207  sum_sq = sum(rands**2)
208  if (sum_sq <= 1) exit
209  end do
210 
211  tmp_sqrt = sqrt(1 - sum_sq)
212  xyz(1:2) = 2 * rands(1:2) * tmp_sqrt
213  xyz(3) = 1 - 2 * sum_sq
214  xyz = xyz * radius
215  end function sphere
216 
217  !> Interal routine: get the next value (returned as 64 bit signed integer)
218  function next(self) result(res)
219  class(rng_t), intent(inout) :: self
220  integer(i8) :: res
221  integer(i8) :: global_time(2)
222 
223  global_time = self%s
224  res = global_time(1) + global_time(2)
225  global_time(2) = ieor(global_time(1), global_time(2))
226  self%s(1) = ieor(ieor(rotl(global_time(1), 55), global_time(2)), shiftl(global_time(2), 14))
227  self%s(2) = rotl(global_time(2), 36)
228  end function next
229 
230  !> Helper function for next()
231  pure function rotl(x, k) result(res)
232  integer(i8), intent(in) :: x
233  integer, intent(in) :: k
234  integer(i8) :: res
235 
236  res = ior(shiftl(x, k), shiftr(x, 64 - k))
237  end function rotl
238 
239 end module mod_random
Module for pseudo random number generation. The internal pseudo random generator is the xoroshiro128p...
Definition: mod_random.t:3