Skip to main content

Random

At a glance

Generating random numbers is a common task in scientific computing. Aleph's Random module provides users with access to all the common distributions, as well as a high performance random number generator. You can start generating random numbers right away with the random function,

var r_real = random() // a random real number on the interval [0, 1].
var r_integer = random(as_integer) // a random integer between 0 and max(as_integer) (inclusive).
var r_bool = random(as_bool) // a random boolean (true or false) with 50-50 probability.

We also support generating random complex numbers,

var r_complex = random(as_complex) // a random complex number with real and imaginary components on the interval [0, 1].

To get a List of random numbers one can use

var r_list = random(10) // 10 real numbers on the interval [0,1].
var r_int_list = random(10, as_integer) // 10 integers between 0 and max(as_integer) (inclusive).

We can also generate arrays and matrices of real numbers by passing the shape,

var r_mat = random([3,3], as_real | as_matrix) // a real 3 X 3 matrix with entries on the interval [0,1].
var r_vec = random([10]), as_array | as _real) // a real length 10 array with entries on the interval [0,1].
warning

Aleph currently does not support generating arrays and matrices of integer and boolean type using the random method. For this purpose see the set_random and random_like methods.

At the moment Aleph doesn't support generating arrays

The random function is enough to get you started. It uses built in distributions and therefore is memory efficient. If you want to run the random number generator using your own seed, you can refer to the relevant section below.

Distributions

Aleph supports all the parameterized probability distributions defined in the C++ standard template library's random header. All distributions extract entropy from the default Aleph RandomGenerator, an instance of the Mersenne twister algorithm which offers a good balance between speed and entropy which is seeded at compile time. To generate your own seed refer to Devices and Generators. A quick reference list of some common distributions is provided below.

Distribution NameReturn TypeDescription
BernoulliboolTrue or false with a given probability.
PoissonintegerNumber of occurences over a fixed interval given the mean number of occurences.
ChiSquaredrealSum of squares of n independent standard normal variables.
UniformIntegerintegerA uniform distribution of integers on the interval [a,b][a, b].
UniformRealrealA uniform distribution of real numbers on the interval [a,b][a, b].
BinomialrealThe number of successes in a given number of true/false trials of given probability.
NegativeBinomialrealThe number of failures before a given number of successes occur.
GammarealGamma distribution parameterized by shape and scale.
NormalrealThe normal distribution.
LognormalrealThe log-normal distribution.
DiscreteintegerReturns an discrete values with probabilities determined by user given weights.
PiecewiseConstantrealReturns a real number uniformly distributed within givens intervals with the given weights.
PiecewiseLinearrealReturns a real number linearly distributed within given interval with the given weights.

Constructing a distribution

The above distributions can be generated using the random_distribution method. The general convention is that the random_distribution function accepts a string corresponding to the camel cases name of the distribution, along with an optional map of named parameters. For example, the FisherF distribution takes two parameters as input, n and m. It can be constructed via,

var fisher = random_distribution("fisher_f", ["n" : 1.0, "m" : 2.0])

Sampling

If the optional parameters are omitted, the default parameters are used as given in the description of each distribution type. Once a distribution has been created it can be sampled via the sample() method,

var normal = random_distribution("normal", ["mean" : 10.0, "stddev", 5.2])
normal.sample() // A normally distributed random number.

All random distributions have a common interface. The Distribution typename can be used in functions definitions to indicate that this interface is expected, allowing users to define functions that are agnostic to the type of distribution passed. For example,

def greater_than(x, Distribution dist) {
if(dist.sample() > x) {
return true
}
return false
}

greater_than(0.4, random_distribution("normal"))
greater_than(0.6, random_distribution("uniform_real", ["a" : -0.4, "b" : 10.2]))

Accessing parameters

The member functions of a random distribution match the names of the parameters. For example,

var normal = random_distribution("normal", ["mean" : -1002.2, "stddev" : 2])
normal.mean() //-> -1002.2
normal.stddev() //-> 2

Additionally, all distributions have a max and min. For example,

var uniform_int = random_distribution("uniform_int", ["a" : -10, "b" : 12])
uniform_int.min() //-> -10
uniform_int.max() //-> 12

Random matrices and arrays

Generating a random matrix or array can be accomplished in three ways. The first, which we saw above us, involves the random function,

var r_mat = random([10], as_real | as_matrix) // A random 10 element vector with values on the interval [0.0, 1.0].
var r_array = random([10,10], as_array) // A random 10 X 10 complex array with real and imaginary parts on the intervals [0.0, 1.0].
tip

In quantum computing and quantum physics we often want to produce a random state or operator that is uniform either in the complex projective space or in the space of unitaries. The correct way to do this is using the haar_vector and haar_matrix functions.

With a specific distribution

To generate random matrices and arrays using a specific Distribution there are two methods.

Using set_random

One can create a matrix and then populate it with the target Distribution,

var m = zeros([10,10], as_real)
m.set_random(random_distribution("normal")) // Sets the values of m real to normally distributed random values.
m.set_random(random_distribution("bernoulli")) // Sets the values m to be randomly be ones or zeros.

When generating a random complex matrix, both the real and imaginary components are independently sampled,

var m = zeros([10,10])
m.set_random(random_distribution("normal")) // Sets the real and imaginary components of m to be normally distributed.

The benefit of set_random is that it works efficiently to populate the matrix in place.

Using random_like

Sometimes we don't want to overwrite the values of the matrix but rather produce a random matrix with the same dimensions and type as a target matrix. This is where random_like becomes useful,

var original = zeros([10,10])
var new = random_like(original, random_distribution("normal")) // A new 10 X 10 matrix with normally distributed values.

Devices and generators

RandomDevice

On classical computers, the closest we can get to something truly random is to use some hardware specific device that is unpredictable by some standard. Such a source is provided by the RandomDevice type, an instance of which can be produced using,

var random_dev = random_device()

Querying the random device can be done via the sample() method,

random_dev.sample() // A random integer.

Producing random numbers in this way is extremely slow. It is for this reason that the random device should only be used to seed the higher performance RandomGenerator.

Seeding a generator

Seeding a random number generator can be done as follows,

var rng = Generator(random_device().sample())

A generator can also be seeded with a known seed so results can be reproduced. For example,

var rng = Generator(123432)

Sampling from a given Seed

To produce random numbers using the fixed seed, the RandomGenerator can be passed to the distribution's sample method,

var rng = Generator(123432)
var normal = random_distribution("normal")
normal.sample(rng) // Normally distributed random number based on user defined seed.

Documentation Contributors

James Lambert