libSUFR
a LIBrary of Some Useful Fortran Routines
All Classes Namespaces Files Functions Variables Pages
random_numbers.f90
Go to the documentation of this file.
1!> \file random_numbers.f90 Procedures to generate random numbers
2
3
4! Copyright (c) 2002-2025 Marc van der Sluys - Nikhef/Utrecht University - marc.vandersluys.nl
5!
6! This file is part of the libSUFR package,
7! see: http://libsufr.sourceforge.net/
8!
9! This is free software: you can redistribute it and/or modify it under the terms of the European Union
10! Public Licence 1.2 (EUPL 1.2). This software is distributed in the hope that it will be useful, but
11! WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12! PURPOSE. See the EU Public Licence for more details. You should have received a copy of the European
13! Union Public Licence along with this code. If not, see <https://www.eupl.eu/1.2/en/>.
14!
15!
16
17
18
19
20!***********************************************************************************************************************************
21!> \brief Procedures to generate random numbers
22
24 implicit none
25 save
26
27contains
28
29
30 !*********************************************************************************************************************************
31 !> \brief Use the system clock to get a random initialisation seed (i.e., negative integer) for a random-numbed generator.
32 !!
33 !! \param degree Degree of randomness: 0-completely (same result for a ms), 1-same result during a clock hour,
34 !! 2-same result during a calendar day (int)
35 !! \param date_array Provide a date/time array as provided by date_and_time() to generate a non-random seed, e.g. for
36 !! reproduction purposes ([year,month,day, (tz), hour,minute,second,millisecond]; optional)
37 !!
38 !! \retval get_ran_seed Randon-number seed: -1e6 < seed < 0 (int)
39
40 function get_ran_seed(degree, date_array)
41 implicit none
42 integer, intent(in) :: degree
43 integer, intent(in), optional :: date_array(8)
44 integer :: get_ran_seed
45
46 integer :: seed,dt(8)
47 character :: tmpstr*(10)
48
49
50 call date_and_time(tmpstr,tmpstr,tmpstr,dt) ! dt: 1-year, 2-month, 3-day, 5-hour, 6-minute, 7-second, 8-millisecond
51 if(present(date_array)) dt = date_array ! Use a user-provided date/time, rather than the system clock
52
53 select case (degree)
54 case(1)
55 seed = dt(1)*1000 + dt(2)*10000 + dt(3)*10101 + dt(5) ! Constant result during a clock hour
56 case(2)
57 seed = dt(1)*1000 + dt(2)*10000 + dt(3)*10101 ! Constant result during a calendar day
58 case default
59 seed = dt(6)*1010 + dt(7)*10101 + dt(8)*1001 ! Different result every millisecond
60 end select
61
62 get_ran_seed = -abs(mod(seed+1,999999)) ! Return a negative number, -1e6 < get_ran_seed < 0
63
64 end function get_ran_seed
65 !*********************************************************************************************************************************
66
67
68
69 !*********************************************************************************************************************************
70 !> \brief Generate a pseudo-random number from a uniform distribution 0 < r < 1.
71 !!
72 !! \param seed The seed to generate the random number from.
73 !! Set seed<0 to initialise the generator; seed is updated between calls (int).
74 !! \retval ran_unif The random number, uniformely generated between 0 < r < 1 (double).
75 !!
76 !! - Use two L'Ecuyer generators, period is \f$ \sim 10^{18}\f$
77 !! - tab is a Bays-Durham shuffle table of length Ntab
78 !! \see Numerical Recipes in Fortran 77, Sect.7.1.
79
80 function ran_unif(seed)
81 use sufr_kinds, only: double, dbl
82
83 implicit none
84 integer, intent(inout) :: seed
85 real(double) :: ran_unif
86
87 integer, parameter :: im1=2147483563, ia1=40014, iq1=53668, ir1=12211
88 integer, parameter :: im2=2147483399, ia2=40692, iq2=52774, ir2= 3791
89 integer, parameter :: ntab=32, im1m1=im1-1, ndtab=67108862 ! ndtab=1+im1m1/Ntab
90
91 ! rnmx should be the largest number < 1 and != 1:
92 real(double), parameter :: am1 = 1.0_dbl/im1
93 real(double), parameter :: eps = epsilon(0.0_dbl)
94 real(double), parameter :: rnmx = 1.0_dbl - eps
95
96 integer, save :: seed2=123456789, tab(ntab+8)=0, iy=0 ! +8 needed to avoid gfortran-8 out-of-bounds warnings
97 integer :: j,k
98
99 if(seed.le.0) then ! 'Initialise' generator
100 seed = max(-seed,1) ! Don't allow seed=0
101 seed2 = seed
102 do j = ntab+8,1,-1 ! Shuffle the table, don't save the first 8 iterations
103 k = seed/iq1
104 seed = ia1*(seed-k*iq1) - k*ir1
105 if(seed.lt.0) seed = seed + im1
106 if(j.le.ntab) tab(j) = seed
107 end do
108 iy = tab(1)
109 end if
110
111 ! Produce the random number 1:
112 k = seed/iq1
113 seed = ia1*(seed-k*iq1) - k*ir1 ! Use Schrage's method to compute mod(). Update seed for next draw
114 if(seed.lt.0) seed = seed + im1
115
116 ! Produce the random number 2:
117 k = seed2/iq2
118 seed2 = ia2*(seed2-k*iq2) - k*ir2 ! Use Schrage's method to compute mod(). Update seed for next draw
119 if(seed2.lt.0) seed2 = seed2 + im2
120
121 j = 1 + iy/ndtab ! Result: 1 <= j <= Ntab
122 iy = tab(j) - seed2 ! tab contains information about seed
123 tab(j) = seed
124 if(iy.lt.1) iy = iy + im1m1
125
126 ran_unif = min(am1*iy,rnmx) ! Make sure r<1
127
128 end function ran_unif
129 !*********************************************************************************************************************************
130
131
132
133 !*********************************************************************************************************************************
134 !> \brief Generate a pseudo-random number from a Gaussian distribution with mean 0 and standard deviation 1
135 !!
136 !! \param seed The seed to generate the random number from
137 !! \retval ran_gauss The random number, using a Gaussian distribution
138 !!
139 !! - Two pseudo-random numbers are drawn from a uniform distribution, using ran_unif().
140 !! - These are converted into two pseudo-random numbers from a Gaussian distribution using the Box-Muller transform
141 !! - Odd-numbered calls return the first, even-numbered calls the second number
142 !! - using the polar form is ~38% faster than using the basic form
143 !!
144 !! \see e.g.:
145 !! - Numerical Recipes in Fortran 77, Sect.7.2.
146 !! - http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
147
148 function ran_gauss(seed)
149 use sufr_kinds, only: double
150
151 implicit none
152 integer, intent(inout) :: seed
153 real(double) :: ran_gauss, ru1,ru2, rusq, fac, rg1,rg2
154 logical :: saved = .false.
155 save :: rg2, saved
156
157 if(.not.saved) then ! Compute
158 rusq = -1.d0
159 do while(rusq.le.0.d0 .or. rusq.ge.1.d0)
160 ru1 = ran_unif(seed)*2 - 1.d0
161 ru2 = ran_unif(seed)*2 - 1.d0
162
163 rusq = ru1**2 + ru2**2
164 end do
165
166 fac = sqrt(-2*log(rusq)/rusq)
167 rg1 = fac * ru1
168 rg2 = fac * ru2
169
170 ran_gauss = rg1
171 saved = .true.
172 else
173 ran_gauss = rg2
174 saved = .false.
175 end if
176
177 end function ran_gauss
178 !*********************************************************************************************************************************
179
180
181 !*********************************************************************************************************************************
182 !> \brief Generate a pseudo-random number from a uniform distribution 0 < r < 1.
183 !!
184 !! \param seed The seed to generate the random number from.
185 !! Set seed<0 to initialise the generator; seed is updated between calls (int).
186 !! \param str The random string (I/O: output str has length of input str, filled with random characters)
187
188 subroutine ran_str(seed, str)
189 implicit none
190 integer, intent(inout) :: seed
191 character, intent(inout) :: str*(*)
192
193 integer, parameter :: ascii_start=33, ascii_end=126 ! First and last ASCII characters to use
194 integer :: dascii, ich, chari
195
196
197 dascii = ascii_end - ascii_start + 1
198 do ich=1,len(str)
199 chari = nint(ascii_start + ran_unif(seed)*dascii)
200 str(ich:ich) = char(chari)
201 end do
202
203 end subroutine ran_str
204 !*********************************************************************************************************************************
205
206end module sufr_random_numbers
207!***********************************************************************************************************************************
208
Provides kinds and related constants/routines.
Definition kinds.f90:26
integer, parameter double
Double-precision float. Precision = 15, range = 307.
Definition kinds.f90:35
integer, parameter dbl
Double-precision float. Precision = 15, range = 307.
Definition kinds.f90:36
Procedures to generate random numbers.
integer function get_ran_seed(degree, date_array)
Use the system clock to get a random initialisation seed (i.e., negative integer) for a random-numbed...
subroutine ran_str(seed, str)
Generate a pseudo-random number from a uniform distribution 0 < r < 1.
real(double) function ran_gauss(seed)
Generate a pseudo-random number from a Gaussian distribution with mean 0 and standard deviation 1.
real(double) function ran_unif(seed)
Generate a pseudo-random number from a uniform distribution 0 < r < 1.