Octopus
quickrnd.F90
Go to the documentation of this file.
1!! Copyright (C) 2002-2024 M. Marques, A. Castro, A. Rubio, G. Bertsch, X. Andrade, A. Buccheri, H. Menke
2!!
3!! This program is free software; you can redistribute it and/or modify
4!! it under the terms of the GNU General Public License as published by
5!! the Free Software Foundation; either version 2, or (at your option)
6!! any later version.
7!!
8!! This program is distributed in the hope that it will be useful,
9!! but WITHOUT ANY WARRANTY; without even the implied warranty of
10!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11!! GNU General Public License for more details.
12!!
13!! You should have received a copy of the GNU General Public License
14!! along with this program; if not, write to the Free Software
15!! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16!! 02110-1301, USA.
17!!
18
19#include "global.h"
20
21module quickrnd_oct_m
22 use, intrinsic :: iso_fortran_env
23 use debug_oct_m
24 use global_oct_m
27
28 implicit none
29
30 private :: &
31 xorshift64, &
33 public :: &
34 quickrnd, &
35 shiftseed, &
38
39 ! xorshift64 needs a good seed, in particular it cannot be seeded with 0.
40 ! These seeds are generated with the splitmix64 generator and have a
41 ! reasonable amount of entropy.
42 integer(int64), parameter, public :: &
43 splitmix64_123 = int(z'B4DC9BD462DE412B', int64), & ! splitmix64(123)
44 splitmix64_321 = int(z'E95F1C4EFCF85DEE', int64) ! splitmix64(321)
45
46 interface quickrnd
48 end interface quickrnd
49
50contains
51
56 function xorshift64(x) result(res)
57 integer(int64), intent(inout) :: x
58 integer(int64) :: res
59
60 assert(x /= 0_int64)
61
62 x = ieor(x, shiftl(x, 13))
63 x = ieor(x, shiftr(x, 7))
64 x = ieor(x, shiftl(x, 17))
65
66 res = x
67 end function xorshift64
68
77 function to_double(x) result(d)
78 integer(int64), intent(in) :: x
79 real(real64) :: d
80 real(real64), parameter :: two_pow_minus_53 = real(z'3CA0000000000000', real64)
81
82 d = real(shiftr(x, 11), real64) * two_pow_minus_53
83 end function to_double
84
85 ! ---------------------------------------------------------
86
87 subroutine dquickrnd_single(iseed, rnd)
88 integer(int64), intent(inout) :: iseed
89 real(real64), intent(out) :: rnd
90
91 ! no PUSH_SUB, called too often
92
93 rnd = to_double(xorshift64(iseed))
94
95 end subroutine dquickrnd_single
96
97 ! ---------------------------------------------------------
98
99 subroutine dquickrnd_array(iseed, nn, rnd)
100 integer(int64), intent(inout) :: iseed
101 integer, intent(in) :: nn
102 real(real64), intent(inout) :: rnd(:)
103
104 integer :: ii
105
106 push_sub(quickrnd_array)
107
108 do ii = 1, nn
109 rnd(ii) = to_double(xorshift64(iseed))
110 end do
111
112 pop_sub(quickrnd_array)
113
114 end subroutine dquickrnd_array
115
116 ! ---------------------------------------------------------
117
118 subroutine zquickrnd_array(iseed, nn, rnd)
119 integer(int64), intent(inout) :: iseed
120 integer, intent(in) :: nn
121 complex(real64), intent(inout) :: rnd(:)
122
123 real(real64), parameter :: inv_sqrt2 = 1.0_real64 / sqrt(m_two)
124 real(real64) :: re, im
125 integer :: ii
126
127 push_sub(quickrnd_array)
128
129 do ii = 1, nn
130 re = to_double(xorshift64(iseed))
131 im = to_double(xorshift64(iseed))
132 rnd(ii) = cmplx(re, im, real64) * inv_sqrt2
133 end do
134
135 pop_sub(quickrnd_array)
136
137 end subroutine zquickrnd_array
138
139 ! ---------------------------------------------------------
140
141 subroutine shiftseed(iseed, n)
142 integer(int64), intent(inout) :: iseed
143 integer(int64), intent(in) :: n
144
145 integer(int64) :: discard
146 integer(int64) :: ii
147
148 push_sub(shiftseed)
150 do ii = 1, n
151 discard = xorshift64(iseed)
152 end do
153
154 pop_sub(shiftseed)
155
156 end subroutine shiftseed
157
158
159 ! TODO(Alex) Issue #1074 . When MR !2523 is merged, scrap `xorshift_rnd_32` and `random_real_in_range`
160 ! in favour of functions introduced. Use them to implement `random_number_in_range`, as described in the
161 ! issue.
162 subroutine xorshift_rnd_32_biased(x)
163 integer(int32), intent(inout) :: x
164
165 x = ieor(x, ishft(x, 13_int32)) ! x ^= x << 13
166 x = ieor(x, ishft(x, -17_int32)) ! x ^= x >> 17
167 x = ieor(x, ishft(x, 5_int32)) ! x ^= x << 5
168
169 ! Ensure the highest bit is zero.
170 ! This keeps the result in the range [0, 2^31 - 1] (positive) BUT biases against zero
171 x = iand(x, z'7FFFFFFF')
172
173 end subroutine xorshift_rnd_32_biased
174
175
177 function random_real_in_range(x_min, x_max, seed) result(rand)
178 real(real64), intent(in ) :: x_min, x_max
179 integer(int32), intent(inout) :: seed
180 real(real64) :: rand
181
182 real(real64), parameter :: max_value_xorshift_32 = 2147483647_real64
183 real(real64) :: scale
184
185 call xorshift_rnd_32_biased(seed)
186 scale = real(seed, real64) / max_value_xorshift_32
187 rand = scale * (x_max - x_min) + x_min
188
189 end function random_real_in_range
190
191
192 ! TODO Alex. Issue #1074 Review the use of this function
194 function random_integer_in_range(x_min, x_max, seed) result(rand)
195 integer(int64), intent(in ) :: x_min, x_max
196 integer(int64), intent(inout) :: seed
197 integer(int64) :: rand
198
199 integer(int64) :: range
200
201 seed = xorshift64(seed)
202 ! Is this is required?
203 if (seed < 0_int64) seed = -seed ! Enforce a positive result
204 range = x_max - x_min + 1_int64
205 rand = x_min + mod(seed, range)
206
207 end function random_integer_in_range
208
209
215 subroutine fisher_yates_shuffle(m, n, seed, values)
216 integer(int32), intent(in ) :: m
217 integer(int64), intent(in ) :: n
218 integer(int64), intent(inout) :: seed
219 integer(int64), intent( out) :: values(:)
220
221 integer(int64), allocatable :: indices(:)
222 integer(int64) :: i, j, temp
223
224 push_sub(fisher_yates_shuffle)
225
226 safe_allocate(indices(1:n))
227 do j = 1, n
228 indices(j) = j
229 end do
230
231 do i = 1, m
232 j = random_integer_in_range(i, n, seed)
233 ! Perform the swap
234 temp = indices(i)
235 indices(i) = indices(j)
236 indices(j) = temp
237 values(i) = indices(i)
238 end do
239
240 values(m) = indices(m)
241 safe_deallocate_a(indices)
242
243 pop_sub(fisher_yates_shuffle)
244
245 end subroutine fisher_yates_shuffle
246
247
266 subroutine reservoir_sampling_aexpj(weight, reservoir, seed_value)
267 real(real64), intent(in ) :: weight(:)
268 integer(int32), intent(out) :: reservoir(:)
269 integer(int32), intent(in ), optional :: seed_value
271 integer(int32) :: m
272 integer(int32) :: n
273 real(real64) :: u_i, u_i2
274 real(real64) :: xw
275 real(real64), allocatable :: key(:)
276
277 integer(int32) :: min_key_index, i, seed_size
278 integer(int32), allocatable :: seed(:)
279 real(real64) :: thres_w
280
281 ! Random number range must be (0, 1), as log(0) would result in a key of -inf, and log(1) = 0
282 ! Set range to [a, b], chosen empirically (may not be optimal)
283 real(real64), parameter :: a = 1.e-10_real64
284 real(real64), parameter :: b = 1._real64 - 1.e-10_real64
285
288 m = size(reservoir)
289 n = size(weight)
290 ! Cannot ask for more numbers than are present in the population
291 assert(m <= n)
292
293 if (present(seed_value)) then
294 safe_allocate_source(seed(1:1), seed_value)
295 else
296 ! Fortran intrinsic returns an array of seeds
297 call random_seed(size=seed_size)
298 safe_allocate(seed(1:seed_size))
299 call random_seed(get=seed)
300 end if
301
302 ! Initialise the reservoir with the first m items of the population
303 min_key_index = 1
304 safe_allocate(key(1:m))
305
306 do i = 1, m
307 reservoir(i) = i
308 u_i = random_real_in_range(a, b, seed(1))
309 key(i) = u_i ** (1._real64 / weight(i))
310 if (key(i) < key(min_key_index)) min_key_index = i
311 enddo
312
313 u_i = random_real_in_range(a, b, seed(1))
314 xw = log(u_i) / log(key(min_key_index))
315
316 ! Perform swaps at jump-intervals, until the population is exhausted
317 i = m + 1
318 do while(i <= n)
319 xw = xw - weight(i)
320 if (xw <= 0._real64) then
321 reservoir(min_key_index) = i
322 thres_w = key(min_key_index)**weight(i)
323 ! U__{i2} \in (t_w, 1)
324 u_i2 = random_real_in_range(thres_w + a, b, seed(1))
325 key(min_key_index) = u_i2 ** (1._real64 / weight(i))
326 min_key_index = minloc(key, dim=1)
327 u_i = random_real_in_range(a, b, seed(1))
328 xw = log(u_i) / log(key(min_key_index))
329 endif
330 i = i + 1
331 enddo
332
333 safe_deallocate_a(key)
334 safe_deallocate_a(seed)
335
337
338 end subroutine reservoir_sampling_aexpj
339
340end module quickrnd_oct_m
341
342!! Local Variables:
343!! mode: f90
344!! coding: utf-8
345!! End:
double log(double __x) __attribute__((__nothrow__
double sqrt(double __x) __attribute__((__nothrow__
real(real64), parameter, public m_two
Definition: global.F90:190
subroutine dquickrnd_array(iseed, nn, rnd)
Definition: quickrnd.F90:193
integer(int64), parameter, public splitmix64_321
Definition: quickrnd.F90:135
subroutine, public reservoir_sampling_aexpj(weight, reservoir, seed_value)
Algorithm "A-ExpJ" weighted reservoir sampling without replacement.
Definition: quickrnd.F90:360
subroutine, public shiftseed(iseed, n)
Definition: quickrnd.F90:235
real(real64) function random_real_in_range(x_min, x_max, seed)
Generate a random real64 in the range .
Definition: quickrnd.F90:271
integer(int64) function random_integer_in_range(x_min, x_max, seed)
Generate a random int64 in the range .
Definition: quickrnd.F90:288
real(real64) function, private to_double(x)
Generating uniform doubles in the unit interval.
Definition: quickrnd.F90:171
subroutine xorshift_rnd_32_biased(x)
Definition: quickrnd.F90:256
subroutine, public fisher_yates_shuffle(m, n, seed, values)
Return m random numbers from a range of with no replacement.
Definition: quickrnd.F90:309
subroutine dquickrnd_single(iseed, rnd)
Definition: quickrnd.F90:181
subroutine zquickrnd_array(iseed, nn, rnd)
Definition: quickrnd.F90:212
integer(int64) function, private xorshift64(x)
xorshift64 pseudorandom number generator
Definition: quickrnd.F90:150