Random¶
The flc_random
module contains advanced pseudo-random number generation
from the random C++ header.
The C++11 way of generating random numbers takes some getting used to. Rather than having a single global function that returns a random real number in the range \([0,1)\), C++11 has independent engine objects that generate streams of random bits. Different distribution objects convert those bits into samples of a distribution.
Although C++11 defines a dizzying variety of random number engines,
flc_random
wraps just two: the 32- and 64-bit Mersenne Twister
algorithms. The 32-bit version (MersenneEngine4
) is currently the only
engine type that can be used with distributions and algorithms.
Flibcpp wraps distribution objects as independent subroutines. Each subroutine accepts the constructor parameters of the distribution, the random engine, and a target Fortran array to be filled with random numbers.
Generating an array with 10 normally-distributed reals with a mean of 8 and a standard deviation of 2 is done as such:
use flc_random, only : Engine => MersenneEngine4, normal_distribution
real(C_DOUBLE), dimension(20) :: arr
type(Engine) :: rng
rng = Engine()
call normal_distribution(8.0d0, 2.0d0, rng, arr)
call rng%release()
Engines¶
The two Mersenne twister engines in flc_random
return different-sized
integers per call:
MersenneEngine4
: each invocation returns a 32-bitinteger(4)
MersenneEngine8
: each invocation returns a 64-bitinteger(8)
Engines can be constructed using one of two interface functions: the
argument-free MersenneEngine4()
uses the default seed, and the engine takes
a single argument MersenneEngine4(1234567)
with the seed. Alternatively,
the seed can be set (or reset) using the seed()
type-bound procedure.
Generally, engines are used with distributions (described below). However, if
necessary, individual randomly generated values can be obtained by calling
the next()
type-bound procedure.
Warning
C++ generates unsigned integers with entropy in every bit. This
means that the integers obtained from engine%next()
, reinterpreted as
signed Fortran integers, may be negative.
In most cases, the default distribution-compatible MersenneEngine4
should
be used, since the distributions described below require it.
Distributions¶
Distributions produce numerical values from the random bitstream provided by an RNG engine. For efficiency, each distribution subroutine accepts an array of values that are filled with samples of the distribution.
normal_distribution¶
Each element of the sampled array is distributed according to a Gaussian function with the given mean and standard deviation.
uniform_int_distribution¶
Each element is uniformly sampled between the two provided bounds, inclusive on both sides.
uniform_real_distribution¶
Each element is a sample of a uniform distribution between the two bounds, inclusive on left side only.
discrete_distribution¶
The discrete distribution is constructed with an array of \(N\) weights: the probability that an index in the range \([1, N]\) will be selected.
real(C_DOUBLE), dimension(4), parameter :: weights &
= [.125d0, .125d0, .25d0, .5d0]
integer(C_INT), dimension(1024) :: sampled
call discrete_distribution(weights, Engine(), sampled)
In the above example, 1
and 2
will are expected to each occupy an
eighth of the sampled
array, approximately a quarter of the sampled
array’s values will be 3
, and about a half will be 4
.
Note
The C++ distribution returns values in \([0, N)\), so in accordance with Flibcpp’s indexing convention the result is transformed when provided to Fortran users.