CAMP 1.0.0
Chemistry Across Multiple Phases
rand.F90
Go to the documentation of this file.
1! Copyright (C) 2007-2021 Barcelona Supercomputing Center and University of
2! Illinois at Urbana-Champaign
3! SPDX-License-Identifier: MIT
4
5!> \file
6!> The camp_rand module.
7
8!> Random number generators.
10
11 use camp_util
13 use camp_mpi
14
15 !> Length of a UUID string.
16 integer, parameter :: camp_uuid_len = 36
17
18 !> Next sequential id
19 integer, private :: next_id = 100
20
21contains
22
23!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
24
25 !> Initializes the random number generator to the state defined by
26 !> the given seed plus offset. If the seed is 0 then a seed is
27 !> auto-generated from the current time plus offset.
28 subroutine camp_srand(seed, offset)
29
30 !> Random number generator seed.
31 integer, intent(in) :: seed
32 !> Random number generator offset.
33 integer, intent(in) :: offset
34
35 integer :: i, n, clock
36 integer, allocatable :: seed_vec(:)
37
38 if (seed == 0) then
39 if (camp_mpi_rank() == 0) then
40 call system_clock(count = clock)
41 end if
42 ! ensure all nodes use exactly the same seed base, to avoid
43 ! accidental correlations
44 call camp_mpi_bcast_integer(clock)
45 else
46 clock = seed
47 end if
48 clock = clock + 67 * offset
49 call random_seed(size = n)
50 allocate(seed_vec(n))
51 i = 0 ! HACK to shut up gfortran warning
52 seed_vec = clock + 37 * (/ (i - 1, i = 1, n) /)
53 call random_seed(put = seed_vec)
54 deallocate(seed_vec)
55
56 end subroutine camp_srand
57
58!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
59
60 !> Cleanup the random number generator.
61 subroutine camp_rand_finalize()
62 end subroutine camp_rand_finalize
63
64!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
65
66 !> Returns a random number between 0 and 1.
67 real(kind=dp) function camp_random()
68
69 real(kind=dp) :: rnd
70
71 call random_number(rnd)
72 camp_random = rnd
73
74 end function camp_random
75
76!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
77
78 !> Returns a random integer between 1 and n.
79 integer function camp_rand_int(n)
80
81 !> Maximum random number to generate.
82 integer, intent(in) :: n
83
84 call assert(669532625, n >= 1)
85 camp_rand_int = mod(int(camp_random() * real(n, kind=dp)), n) + 1
86 call assert(515838689, camp_rand_int >= 1)
87 call assert(802560153, camp_rand_int <= n)
88
89 end function camp_rand_int
90
91!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
92
93 !> Round val to \c floor(val) or \c ceiling(val) with probability
94 !> proportional to the relative distance from \c val. That is,
95 !> Prob(prob_round(val) == floor(val)) = ceil(val) - val.
96 integer function prob_round(val)
97
98 !> Value to round.
99 real(kind=dp), intent(in) :: val
100
101 ! FIXME: can replace this with:
102 ! prob_round = floor(val + camp_random())
103 if (camp_random() < real(ceiling(val), kind=dp) - val) then
104 prob_round = floor(val)
105 else
106 prob_round = ceiling(val)
107 endif
108
109 end function prob_round
110
111!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
112
113 !> Generate a Poisson-distributed random number with the given
114 !> mean.
115 !!
116 !! See http://en.wikipedia.org/wiki/Poisson_distribution for
117 !! information on the method. The method used at present is rather
118 !! inefficient and inaccurate (brute force for mean below 10 and
119 !! normal approximation above that point).
120 !!
121 !! The best known method appears to be due to Ahrens and Dieter (ACM
122 !! Trans. Math. Software, 8(2), 163-179, 1982) and is available (in
123 !! various forms) from:
124 !! - http://www.netlib.org/toms/599
125 !! - http://www.netlib.org/random/ranlib.f.tar.gz
126 !! - http://users.bigpond.net.au/amiller/random/random.f90
127 !! - http://www.netlib.org/random/random.f90
128 !!
129 !! Unfortunately the above code is under the non-free license:
130 !! - http://www.acm.org/pubs/copyright_policy/softwareCRnotice.html
131 !!
132 !! For other reasonable methods see L. Devroye, "Non-Uniform Random
133 !! Variate Generation", Springer-Verlag, 1986.
134 integer function rand_poisson(mean)
135
136 !> Mean of the distribution.
137 real(kind=dp), intent(in) :: mean
138 real(kind=dp) :: l, p
139 integer :: k
140
141 call assert(368397056, mean >= 0d0)
142 if (mean <= 10d0) then
143 ! exact method due to Knuth
144 l = exp(-mean)
145 k = 0
146 p = 1d0
147 do
148 k = k + 1
149 p = p * camp_random()
150 if (p < l) exit
151 end do
152 rand_poisson = k - 1
153 else
154 ! normal approximation with a continuity correction
155 k = nint(rand_normal(mean, sqrt(mean)))
156 rand_poisson = max(k, 0)
157 end if
158
159 end function rand_poisson
160
161!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
162
163 !> Generate a Binomial-distributed random number with the given
164 !> parameters.
165 !!
166 !! See http://en.wikipedia.org/wiki/Binomial_distribution for
167 !! information on the method. The method used at present is rather
168 !! inefficient and inaccurate (brute force for \f$np \le 10\f$ and
169 !! \f$n(1-p) \le 10\f$, otherwise normal approximation).
170 !!
171 !! For better methods, see L. Devroye, "Non-Uniform Random Variate
172 !! Generation", Springer-Verlag, 1986.
173 integer function rand_binomial(n, p)
174
175 !> Sample size.
176 integer, intent(in) :: n
177 !> Sample probability.
178 real(kind=dp), intent(in) :: p
179 real(kind=dp) :: np, nomp, q, g_real
180 integer :: k, g, sum
181
182 call assert(130699849, n >= 0)
183 call assert(754379796, p >= 0d0)
184 call assert(678506029, p <= 1d0)
185 np = real(n, kind=dp) * p
186 nomp = real(n, kind=dp) * (1d0 - p)
187 if ((np >= 10d0) .and. (nomp >= 10d0)) then
188 ! normal approximation with continuity correction
189 k = nint(rand_normal(np, sqrt(np * (1d0 - p))))
190 rand_binomial = min(max(k, 0), n)
191 elseif (np < 1d-200) then
192 rand_binomial = 0
193 elseif (nomp < 1d-200) then
194 rand_binomial = n
195 else
196 ! First waiting time method of Devroye (p. 525).
197 ! We want p small, so if p > 1/2 then we compute with 1 - p and
198 ! take n - k at the end.
199 if (p <= 0.5d0) then
200 q = p
201 else
202 q = 1d0 - p
203 end if
204 k = 0
205 sum = 0
206 do
207 ! G is geometric(q)
208 g_real = log(camp_random()) / log(1d0 - q)
209 ! early bailout for cases to avoid integer overflow
210 if (g_real > real(n - sum, kind=dp)) exit
211 g = ceiling(g_real)
212 sum = sum + g
213 if (sum > n) exit
214 k = k + 1
215 end do
216 if (p <= 0.5d0) then
217 rand_binomial = k
218 else
219 rand_binomial = n - k
220 end if
221 call assert(359087410, rand_binomial <= n)
222 end if
223
224 end function rand_binomial
225
226!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
227
228 !> Generates a normally distributed random number with the given
229 !> mean and standard deviation.
230 real(kind=dp) function rand_normal(mean, stddev)
231
232 !> Mean of distribution.
233 real(kind=dp), intent(in) :: mean
234 !> Standard deviation of distribution.
235 real(kind=dp), intent(in) :: stddev
236 real(kind=dp) :: u1, u2, r, theta, z0, z1
237
238 call assert(898978929, stddev >= 0d0)
239 ! Uses the Box-Muller transform
240 ! http://en.wikipedia.org/wiki/Box-Muller_transform
241 u1 = camp_random()
242 u2 = camp_random()
243 r = sqrt(-2d0 * log(u1))
244 theta = 2d0 * const%pi * u2
245 z0 = r * cos(theta)
246 z1 = r * sin(theta)
247 ! z0 and z1 are now independent N(0,1) random variables
248 ! We throw away z1, but we could use a SAVE variable to only do
249 ! the computation on every second call of this function.
250 rand_normal = stddev * z0 + mean
251
252 end function rand_normal
253
254!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
255
256 !> Generates a vector of normally distributed random numbers with
257 !> the given means and standard deviations. This is a set of
258 !> normally distributed scalars, not a normally distributed vector.
259 subroutine rand_normal_array_1d(mean, stddev, val)
260
261 !> Array of means.
262 real(kind=dp), intent(in) :: mean(:)
263 !> Array of standard deviations.
264 real(kind=dp), intent(in) :: stddev(size(mean))
265 !> Array of sampled normal random variables.
266 real(kind=dp), intent(out) :: val(size(mean))
267
268 integer :: i
269
270 do i = 1,size(mean)
271 val(i) = rand_normal(mean(i), stddev(i))
272 end do
273
274 end subroutine rand_normal_array_1d
275
276!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
277
278 !> Sample the given continuous probability density function.
279 !!
280 !! That is, return a number k = 1,...,n such that prob(k) = pdf(k) /
281 !! sum(pdf). Uses accept-reject.
282 integer function sample_cts_pdf(pdf)
283
284 !> Probability density function (not normalized).
285 real(kind=dp), intent(in) :: pdf(:)
286
287 real(kind=dp) :: pdf_max
288 integer :: k
289 logical :: found
290
291 ! use accept-reject
292 pdf_max = maxval(pdf)
293 if (minval(pdf) < 0d0) then
294 call die_msg(121838078, 'pdf contains negative values')
295 end if
296 if (pdf_max <= 0d0) then
297 call die_msg(119208863, 'pdf is not positive')
298 end if
299 found = .false.
300 do while (.not. found)
301 k = camp_rand_int(size(pdf))
302 if (camp_random() < pdf(k) / pdf_max) then
303 found = .true.
304 end if
305 end do
307
308 end function sample_cts_pdf
309
310!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
311
312 !> Sample the given discrete probability density function.
313 !!
314 !! That is, return a number k = 1,...,n such that prob(k) = pdf(k) /
315 !! sum(pdf). Uses accept-reject.
316 integer function sample_disc_pdf(pdf)
317
318 !> Probability density function.
319 integer, intent(in) :: pdf(:)
320
321 integer :: pdf_max, k
322 logical :: found
323
324 ! use accept-reject
325 pdf_max = maxval(pdf)
326 if (minval(pdf) < 0) then
327 call die_msg(598024763, 'pdf contains negative values')
328 end if
329 if (pdf_max <= 0) then
330 call die_msg(109961454, 'pdf is not positive')
331 end if
332 found = .false.
333 do while (.not. found)
334 k = camp_rand_int(size(pdf))
335 if (camp_random() < real(pdf(k), kind=dp) / real(pdf_max, kind=dp)) then
336 found = .true.
337 end if
338 end do
340
341 end function sample_disc_pdf
342
343!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
344
345 !> Convert a real-valued vector into an integer-valued vector by
346 !> sampling.
347 !!
348 !! Use n_samp samples. Each discrete entry is sampled with a PDF
349 !! given by vec_cts. This is very slow for large n_samp or large n.
350 subroutine sample_vec_cts_to_disc(vec_cts, n_samp, vec_disc)
351
352 !> Continuous vector.
353 real(kind=dp), intent(in) :: vec_cts(:)
354 !> Number of discrete samples to use.
355 integer, intent(in) :: n_samp
356 !> Discretized vector.
357 integer, intent(out) :: vec_disc(size(vec_cts))
358
359 integer :: i_samp, k
360
361 call assert(617770167, n_samp >= 0)
362 vec_disc = 0
363 do i_samp = 1,n_samp
364 k = sample_cts_pdf(vec_cts)
365 vec_disc(k) = vec_disc(k) + 1
366 end do
367
368 end subroutine sample_vec_cts_to_disc
369
370!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
371
372 !> Generate a random hexadecimal character.
373 character function rand_hex_char()
374
375 integer :: i
376
377 i = camp_rand_int(16)
378 if (i <= 10) then
379 rand_hex_char = achar(iachar('0') + i - 1)
380 else
381 rand_hex_char = achar(iachar('A') + i - 11)
382 end if
383
384 end function rand_hex_char
385
386!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
387
388 !> Generate a version 4 UUID as a string.
389 !!
390 !! See http://en.wikipedia.org/wiki/Universally_Unique_Identifier
391 !! for format details.
392 subroutine uuid4_str(uuid)
393
394 !> The newly generated UUID string.
395 character(len=CAMP_UUID_LEN), intent(out) :: uuid
396
397 integer :: i
398
399 do i = 1,8
400 uuid(i:i) = rand_hex_char()
401 end do
402 uuid(9:9) = '-'
403 do i = 1,4
404 uuid((i + 9):(i + 9)) = rand_hex_char()
405 end do
406 uuid(14:14) = '-'
407 do i = 1,4
408 uuid((i + 14):(i + 14)) = rand_hex_char()
409 end do
410 uuid(19:19) = '-'
411 do i = 1,4
412 uuid((i + 19):(i + 19)) = rand_hex_char()
413 end do
414 uuid(24:24) = '-'
415 do i = 1,12
416 uuid((i + 24):(i + 24)) = rand_hex_char()
417 end do
418
419 uuid(15:15) = '4'
420
421 i = camp_rand_int(4)
422 if (i <= 2) then
423 uuid(20:20) = achar(iachar('8') + i - 1)
424 else
425 uuid(20:20) = achar(iachar('A') + i - 3)
426 end if
427
428 end subroutine uuid4_str
429
430!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
431
432 !> Generate an integer id
433 !! Ids will be sequential, and can only be generated by the primary process
434 function generate_int_id() result(id)
435
436 !> The generated id
437 integer(kind=i_kind) :: id
438
439 call assert_msg(226665912, camp_mpi_rank().eq.0, &
440 "Sequential ids can only be generated on the primary "//&
441 "process.")
442
443 id = next_id
444 next_id = next_id + 1
445
446 end function generate_int_id
447
448!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
449
450end module camp_rand
Physical constants.
Definition constants.F90:9
integer, parameter dp
Kind of a double precision real number.
Definition constants.F90:16
type(const_t), save const
Fixed variable for accessing the constant's values.
Definition constants.F90:77
Wrapper functions for MPI.
Definition mpi.F90:13
integer function camp_mpi_rank(comm)
Returns the rank of the current process.
Definition mpi.F90:128
subroutine camp_mpi_bcast_integer(val, comm)
Broadcast the given value from process 0 to all other processes.
Definition mpi.F90:317
Random number generators.
Definition rand.F90:9
character function rand_hex_char()
Generate a random hexadecimal character.
Definition rand.F90:374
integer function sample_cts_pdf(pdf)
Sample the given continuous probability density function.
Definition rand.F90:283
subroutine sample_vec_cts_to_disc(vec_cts, n_samp, vec_disc)
Convert a real-valued vector into an integer-valued vector by sampling.
Definition rand.F90:351
integer function prob_round(val)
Round val to floor(val) or ceiling(val) with probability proportional to the relative distance from v...
Definition rand.F90:97
integer, parameter camp_uuid_len
Length of a UUID string.
Definition rand.F90:16
integer function rand_binomial(n, p)
Generate a Binomial-distributed random number with the given parameters.
Definition rand.F90:174
subroutine camp_rand_finalize()
Cleanup the random number generator.
Definition rand.F90:62
integer function camp_rand_int(n)
Returns a random integer between 1 and n.
Definition rand.F90:80
integer function sample_disc_pdf(pdf)
Sample the given discrete probability density function.
Definition rand.F90:317
subroutine camp_srand(seed, offset)
Initializes the random number generator to the state defined by the given seed plus offset....
Definition rand.F90:29
integer(kind=i_kind) function generate_int_id()
Generate an integer id Ids will be sequential, and can only be generated by the primary process.
Definition rand.F90:435
integer, private next_id
Next sequential id.
Definition rand.F90:19
real(kind=dp) function rand_normal(mean, stddev)
Generates a normally distributed random number with the given mean and standard deviation.
Definition rand.F90:231
subroutine rand_normal_array_1d(mean, stddev, val)
Generates a vector of normally distributed random numbers with the given means and standard deviation...
Definition rand.F90:260
subroutine uuid4_str(uuid)
Generate a version 4 UUID as a string.
Definition rand.F90:393
integer function rand_poisson(mean)
Generate a Poisson-distributed random number with the given mean.
Definition rand.F90:135
real(kind=dp) function camp_random()
Returns a random number between 0 and 1.
Definition rand.F90:68
Common utility subroutines.
Definition util.F90:9
subroutine assert(code, condition_ok)
Errors unless condition_ok is true.
Definition util.F90:165
subroutine die_msg(code, error_msg)
Error immediately.
Definition util.F90:196
subroutine assert_msg(code, condition_ok, error_msg)
Errors unless condition_ok is true.
Definition util.F90:130