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].
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 Name | Return Type | Description |
|---|---|---|
| Bernoulli | bool | True or false with a given probability. |
| Poisson | integer | Number of occurences over a fixed interval given the mean number of occurences. |
| ChiSquared | real | Sum of squares of n independent standard normal variables. |
| UniformInteger | integer | A uniform distribution of integers on the interval . |
| UniformReal | real | A uniform distribution of real numbers on the interval . |
| Binomial | real | The number of successes in a given number of true/false trials of given probability. |
| NegativeBinomial | real | The number of failures before a given number of successes occur. |
| Gamma | real | Gamma distribution parameterized by shape and scale. |
| Normal | real | The normal distribution. |
| Lognormal | real | The log-normal distribution. |
| Discrete | integer | Returns an discrete values with probabilities determined by user given weights. |
| PiecewiseConstant | real | Returns a real number uniformly distributed within givens intervals with the given weights. |
| PiecewiseLinear | real | Returns 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].
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.