Downloaded from https://academic.oup.
com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
Appendix E
Random numbers
E.1 Random number generators
Before the develoment of computers, random sequences of numbers had to be generated
by physical methods such as rolling dice, tossing coins, picking numbered balls from an
urn, or analysing noise generated in an electronic valve. To assist workers in this field
large tables of pre-calculated random sequences were published (RAND, 2001).
Many applications, including mc and stochastic dynamics simulation, require random
sequences of millions of numbers, which must be generated on the computer. The field of
pseudo-random number generation (so-called because the sequences are generated deter-
ministically and repeatably) is fairly well developed, and the desirable (and undesirable)
features have been studied in detail.
In the current context of liquid-state computer simulations, the raw speed of random
number generation is almost never critical: other parts of the program are almost always
more time-consuming. The statistical distribution of the numbers should, of course,
conform to the desired one: most commonly, random numbers are generated uniformly
over the unit interval (0, 1), and more complicated distributions are obtained from this.
However, on top of this, the numbers should be uncorrelated, and to investigate this
it is necessary to calculate two-point, three-point, and higher, joint distributions. The
sequence will typically repeat itself after a certain period, and the length of this repeat
cycle should be as long as possible. A battery of tests for random number generators
known as Diehard, due to Marsaglia (1995) has been updated under the name Dieharder
by Brown (2015). A very comprehensive test suite, TestU01 (L’Ecuyer and Simard, 2007),
is also available online (TestU01, 2009).
The dangers of using a poor random number generator should not be underestimated.
Although mc simulations are often (or appear to be) insensitive to the details, occasionally
catastrophic results can result from a poor choice (Ferrenberg et al., 1992; Schmid and
Wilding, 1995; Bauke and Mertens, 2004; Deng et al., 2008). A serious molecular simulator
will want to either research the algorithm used by the built-in generator, or replace it
with one whose behaviour is known, selected from a library (Press et al., 2007; Barash
and Shchur, 2013). Be aware, however, that software library contents can change from
version to version. A collection of random number generators may be found in the Gnu
is not Unix (gnu) Scientific Library. A selection is also provided in the Intel Math Kernel
library.
Computer Simulation of Liquids. Second Edition. M. P. Allen and D. J. Tildesley.
© M. P. Allen and D. J. Tildesley 2017. Published in 2017 by Oxford University Press.
510 Random numbers
E.2 Uniformly distributed random numbers
The generation of uniform (pseudo)random numbers is an enormous field and several
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
books are available to give the reader a proper introduction (Knuth, 1997; Gentle, 2003;
L’Ecuyer, 2007; Kroese et al., 2011). Jones (2010) has authored a very useful online paper
giving advice on good practice.
Typically, a sequence of large positive integers is produced, each obtained from the
last by some operation (e.g. multiplication) conducted in finite modulus arithmetic. This
is typified by linear congruential generators (lcgs), of the form
X i+1 = (aX i + b) mod M (E.1)
where the quantities a, b, and M are large positive integer parameters, and mod is the
modulo operation (as for the MOD function in Fortran). The sequence begins by selecting an
initial seed, X 0 . Thus, each result in the sequence is an integer lying in the range (0, M − 1)
inclusive. The desired random numbers are returned as
ξ i = X i /M.
Note that this excludes the possibility of generating X i = 1, but allows the possibility of
X i = 0; in practice, generators may allow neither, either, or both end values to appear. A
special case of this kind of generator is the simple multiplicative congruential generator,
for which b = 0.
These basic generators are not usually considered good enough for mc simulation
work, and may be extended in several ways. Multiple recursive generators (mrgs) make
the right-hand side of eqn (E.1) a linear function of several previous values X k . A linear
feedback shift register generator uses the same approach, but calculates the output ξ i
in a different way. A variation of this method is used in the popular and widespread
Mersenne twister (Matsumoto and Nishimura, 1998). A drawback of this generator is
that it is rather elaborate and complicated to seed properly. Combining mrgs to give a
single, better-quality, sequence is a productive approach (L’Ecuyer, 1996; 1999); notable,
and widely available, examples of this type are mrg32k3a (L’Ecuyer, 1999) and various
versions of kiss from Marsaglia. The well generators (Panneton et al., 2006) are also
promising. All of these choices pass the overwhelming majority of the statistical tests
mentioned in the introductory section.
Increasingly one wishes to generate independent streams of random numbers on
parallel computers. Here, a key issue is the seeding: if the different processes use the same
seeds, they will generate the same sequences of numbers, which will almost certainly
lead to trouble. Calculating a seed from the system clock carries with it the danger that
several of the processes will still start with the same value; combining the clock value
with the processor i.d. number may avoid the problem. On some operating systems it is
possible to use values from the device /dev/random or /dev/urandom.
Fortran provides built in routines RANDOM_SEED and RANDOM_NUMBER for, respectively,
initializing the random number generator and producing random numbers, uniformly
sampled on the range (0, 1). We use these extensively in the supplied examples. Two
points are worthy of comment. First, the RANDOM_SEED routine takes a set of optional
arguments which may be used to fine-tune the initialization, for example in order to
Generating non-uniform distributions 511
reliably repeat a given sequence of numbers, or alternatively to generate a ‘random’
starting point based on (e.g.) the system clock. If called without these arguments, it simply
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
initializes to a default state. The gnu documentation for gfortran provides a way of
randomly seeding the built-in generator, using /dev/urandom if available, and combining
the clock and processor i.d. values otherwise. We have reproduced this in the function
init_random_seed provided in the file maths_module.f90 of Code A.1. Of course, for a
different compiler, a different method may be needed, and there is no guarantee that
/dev/urandom will be available on a given computer. Second, the Fortran standard does
not actually specify that RANDOM_NUMBER uses any particular random number generator:
it depends on the particular compiler, and possibly on the computer. In our code, for
simplicity, we are ignoring most of the preceding advice, and using this routine instead of
including a particular choice of our own. The gnu documentation for gfortran (at the time
of writing) indicates that one of Marsaglia’s kiss generators is used in RANDOM_NUMBER,
which should be satisfactory.
We often wish to generate uniform random integers k within a specified range,
(k min , k max ) inclusive, for example to choose a particle for an mc move or thermal velocity
randomization. This is simply, and reliably, computed from a uniformly distributed variate
ξ in the range (0, 1) by h i
k = k min + ξ (k max − k min + 1)
where bxc is the largest integer less than or equal to x, that is, the FLOOR function in Fortran.
It is sensible to guard against the (very small) danger of roundoff effects by ensuring,
afterwards, that k min ≤ k ≤ k max . This is implemented in the routine random_integer in
the file maths_module.f90 of Code A.1.
E.3 Generating non-uniform distributions
Using the random number ξ generated uniformly on (0, 1) it is possible to construct
random numbers taken from a variety of distributions. There are many distributions
which are of interest to statisticians, but only a limited number which are required in
liquid-state simulation. In this section we discuss generating random variables on the
normal (Gaussian), and exponential distributions. The interested reader is referred to
the standard texts (Knuth, 1997; Gentle, 2003; L’Ecuyer, 2007; Kroese et al., 2011) for a
comprehensive discussion of other distributions, and proofs of the results quoted in this
section. Many libraries contain routines to generate these distributions automatically.
The normal distribution, with mean µ, and variance σ 2 , is defined as
1 (x − µ) 2
!
ρ (x ) = √ exp − − ∞ < x < ∞.
σ 2π 2σ 2
The expectation values of the first two moments are given by
hxi = µ, hx 2 i = µ 2 + σ 2 , or h(x − µ) 2 i = σ 2 .
A random number ζ 0 chosen from this distribution will be related to a number ζ generated
from the normal distribution with zero mean and unit variance by
ζ 0 = µ + σζ .
512 Random numbers
A typical example is the random selection of velocity components (vx , vy , vz ) from the
Maxwell–Boltzmann
√ distribution at temperature T in the Andersen thermostat, when
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
µ = 0, σ = k BT /m, for each component, where m is the particle mass (see Section 3.8.1).
The problem is reduced to sampling ζ . One way (of many) to do this involves two
steps and the generation of two uniform random numbers (Box and Muller, 1958):
(a) generate independent uniform random numbers ξ 1 and ξ 2 on (0, 1);
(b) calculate ζ 1 = (−2 ln ξ 1 ) −1/2 cos 2πξ 2 and ζ 2 = (−2 ln ξ 1 ) −1/2 sin 2πξ 2 .
This algorithm is provided in the routine random_normal in the file maths_module.f90 of
Code A.1. The routine generates ζ 1 and ζ 2 together, typically returning ζ 1 and saving ζ 2
for the next call.
In some applications (e.g. Brownian dynamics, Chapter 12) we need to generate
correlated pairs of numbers that are normally distributed. Given two independent normal
random numbers ζ 1 and ζ 2 , with zero means and unit variances, obtained as before, the
variables
2 1/2
ζ 10 = σ1ζ 1 , ζ 20 = σ2 c 12ζ 1 + (1 − c 12 ) ζ2 (E.2)
are sampled from the bivariate Gaussian distribution with zero means, variances σ12 and
σ22 , and correlation coefficient c 12 .
In the bd simulations described in Section 12.2, we need to sample a large number, n,
of correlated random numbers from a multivariate Gaussian distribution
1
exp − 12 x · C−1 · x
ρ (x) = p
(2π) n |C|
where for simplicity we consider zero means, hx i i = 0, i = 1, . . . , n, and the (symmetric)
covariance matrix C is defined such that hx i x j i = Ci j . Many libraries include routines
to sample directly from this distribution in an efficient way. Otherwise, the correlated
set of random numbers ζi0, i = 1, . . . , n, may be obtained from an independent set of
normally distributed random numbers, ζi , given the lower-triangular matrix L which
satisfies C = L · LT (see eqn (12.9)). This may be obtained by a Cholesky decomposition,
which is once more a common feature of numerical libraries. The elements of the matrix
are
v
j−1
u
t
X
Lj j = Cj j − L2jk
k=1
j−1
X
Li j = L−1
jj
*Ci j − Lik L jk +, i>j
, k =1 -
√
and the calculation typically begins with L 11 = C 11 , followed by successive rows
{L 21 , L 22 }, {L 31 , L 32 , L 33 }, and so on. The expression inside the square root is guaranteed
to be positive provided the matrix C is positive definite. The desired random variables are
i
X
ζi0 = Li j ζ j .
j=1
Generating non-uniform distributions 513
Equation (E.2) is just a special case of this equation, with C 11 = σ12 , C 22 = σ22 and
C 12 = C 21 = σ1σ2c 12 . In the bd application, n = 3N , C = D, the diffusion coefficient, and
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
the random displacements are given by eqn (12.9)
√ √ i
X
Ri = 2∆t ζi0 = 2∆t Li j ζ j .
j=1
As mentioned in Section 12.2, it cannot be guaranteed that the Oseen tensor is positive
definite, so an alternative form for D is usually employed.
Now we turn to the problem of sampling from the exponential distribution
µ −1 exp(−x/µ)
0<x <∞
ρ (x ) =
0 (E.3)
otherwise
where µ is a positive parameter defining the mean value hxi = µ. A suitable method is
(a) generate a uniform random number ξ on (0, 1);
(b) calculate ζ = −µ ln ξ .
One example of the use of such a distribution is the selection of a random angular
velocity ω for a linear molecule. The direction of ω may be chosen randomly in a plane
perpendicular to the molecular axis (see next section), and then the value of ω 2 selected
from the exponential distribution with mean value µ = hω 2 i = 2k BT /I , where I is the
moment of inertia.
This is an example of the general technique of inverse transforms. It relies on being able
to calculate (either analytically or in tabular form) the cumulative probability function
x
P (x ) = dx 0 ρ (x 0 )
−∞
that is, the integrated probability of occurrence of a value less than or equal to x, given
the probability density ρ (x ). More specifically, it relies on knowing the inverse function
x (P ). If a random value of P is chosen in the range 0 ≤ P ≤ 1, then the variable x (P )
will be distributed with probability density ρ (x ). In this case, P (x ) = 1 − exp(−x/µ), the
inverse function is x = −µ ln(1 − P ), and we sample 1 − P, or equivalently P, uniformly
over the range (0, 1).
This method also works for sampling from a discrete distribution. Consider the problem
of selecting from a set of m alternatives k = 1, 2, . . . , m according to their assigned
probabilities pk . Suppose that we have already normalized these, so that k pk = 1, and
P
that they are stored in an array p. Then the following code snippet is used.
CALL RANDOM_NUMBER ( ran )
k = 1
p_cumul = p (1)
DO
IF ( p_cumul >= ran ) EXIT
k = k +1
p_cumul = p_cumul + p ( k )
END DO
514 Random numbers
The value of k on exit from the loop identifies the ‘winner’ with the correct probability. A
moment’s thought reveals that this is the inverse transform method. The first step is to
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
choose a random number uniformly on (0, 1). Then, the loop determines the first value
of k for which the cumulative probability exceeds this number. It is possible to add a
guard against roundoff errors, to ensure that k never exceeds the dimension of the array.
This method is used in cbmc (see Section 9.3.4), where we typically use un-normalized
weights wk , given by the Boltzmann factors associated with trial moves. In this case, a
trivial modification of the code is used.
w_total = SUM (w)
CALL RANDOM_NUMBER ( ran )
ran = ran * w_total
k = 1
w_cumul = w (1)
DO
IF ( w_cumul >= ran ) EXIT
k = k +1
w_cumul = w_cumul + w ( k )
END DO
E.4 Random vectors on the surface of a sphere
There are a number of suitable methods for generating a vector on the surface of a unit
sphere. The simplest of these is an iterative procedure using the acceptance–rejection
technique of von Neumann (1951).
(a) Generate three uniform random numbers ξ 1 , ξ 2 , and ξ 3 on (0, 1).
(b) Calculate ζi = 2ξ i − 1 for i = 1, . . . , 3 so that the vector ζ = (ζ 1 , ζ 2 , ζ 3 ) is distributed
uniformly in a cube of side 2 centred at the origin.
(c) Form the sum ζ 2 = ζ 12 + ζ 22 + ζ 32 .
(d) If ζ 2 < 1 (i.e. inside the inscribed sphere) take ζˆ = ζ /ζ as the vector.
(e) Otherwise, reject the vector and return to step (a).
This is provided as a routine in the file maths_module.f90 of Code A.1. Marsaglia (1972)
has suggested an improvement.
(a) Generate two uniform random numbers ξ 1 , ξ 2 on (0, 1).
(b) Calculate ζi = 2ξ i − 1 for i = 1, 2.
(c) Form the sum ζ 2 = ζ 12 + ζ 22 .
(d) If ζ 2 < 1 take ζˆ = 2ζ 1 1 − ζ 2 , 2ζ 2 1 − ζ 2 , 1 − 2ζ 2 as the vector.
p p
(e) Otherwise, reject the vector and return to step (a).
This is also provided as an alternative routine. The method can be readily extended to
choosing points on a four-sphere (suitable for quaternion orientations) and Marsaglia
gives an appropriate algorithm. To obtain random vectors in a plane normal to a given unit
vector ê, simply proceed as just described and project out the component ζ 0 = ζˆ − (ζˆ · ê) ê,
then renormalize ζ 0 to unit length.
Choosing randomly and uniformly from complicated regions 515
E.5 Choosing randomly and uniformly from complicated
regions
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
von Neumann (1951) suggested the following algorithm for generating random numbers
from an arbitrary distribution ρ (r). The function is split in the following way
ρ (r) = Ca(r)b (r) (E.4)
where a(r) is a simpler (normalized) distribution function, from which it is easy to sample
a random number, b (r) is a function which lies between zero and one, and C is a constant,
with C ≥ 1. The following steps generate a random vector sampled from ρ (r).
(a) Sample ζ randomly from the distribution a(r).
(b) Generate a uniform random number ξ on (0, 1).
(c) If ξ ≤ b (ζ ) then ζ is sampled from the distribution ρ (r).
(d) Otherwise, reject ζ and return to step (a).
A simple example illustrates the method. Suppose we wish to generate a vector uniformly
inside the unit circle |r| 2 < 1, where r = (x 1 , x 2 ), but we wish to sample uniformly in the
square −1 < x 1 , x 2 < 1. In this case
1/π x 12 + x 22 < 1
1/4 −1 < x 1 , x 2 < 1
ρ (r) =
0 a(r) =
otherwise 0
otherwise
b (r) = Θ(1 − x 12 − x 22 ) C = 4/π
The step function Θ ensures that b (r) = 1 inside the region of interest, and zero outside. A
little reflection shows that in this case the von Neumann algorithm simplifies considerably:
(a) Sample ζ randomly inside the square.
(b) If ζ lies within the circle then ζ is sampled from the distribution ρ (r).
(c) Otherwise, reject ζ and return to step (a).
It is not necessary to know the value of C. It will be noticed that the basic mc sam-
pling method discussed in Section 4.2 is based on this approach, as well as the rejection
techniques for generating random orientations discussed in the previous sections. This
approach can be readily extended to higher dimensions and more complicated regions of
interest.
Mc simulation of molecules relies on generating small random rotations: a new vector
should be sampled uniformly on the surface of the unit sphere, but within a given solid
angle of the old vector. A very simple implementation, using the approach just described,
is given in a routine in the file maths_module.f90 of Code A.1. This is, however, not the
most efficient approach, and several better alternatives are also provided. In general, the
secret of success with the rejection method is to choose a(r) so as to be convenient to
sample the random variates (a hypersphere, hyperellipsoid, etc.) and which covers the
whole of the region that is of interest, but is not too much larger. The closer the match,
the smaller the fraction of rejected tries.
516 Random numbers
E.6 Generating a random permutation
In the Lowe–Andersen thermostat (Lowe, 1999) discussed in Sections 3.8.1 and 12.4, it is
Downloaded from https://academic.oup.com/book/27866/chapter/203878488 by University of Michigan (Brian Conrad) user on 04 September 2025
necessary to examine pairs of particles and, with a certain probability, randomize their
relative velocity. It is essential to do this in a random order; the best way is to prepare
the desired list (which typically contains all pairs within a certain interaction range) in a
systematic way (looping over particle indices) and then shuffle it. The standard in-place
algorithm for doing this is quite simple (Knuth, 1997) and is illustrated by the following
code snippet, which shuffles an array a of quantities (numbered from 1 to m).
! INTEGER :: q, p, m
! REAL , DIMENSION (m) :: a
! REAL :: tmp
DO p = 1, m -1
q = random_integer ( p, m )
tmp = a(p)
a(p) = a(q)
a(q) = tmp
END DO
In words: a single sweep through the array is undertaken, and each element is swapped
with a randomly selected element from further up the array. The function random_integer
appears in the file maths_module.f90 of Code A.1 and returns an integer chosen randomly
in the specified range p . . . m inclusive.