Brian provides two basic functions to generate random numbers that can be used in model code and equations: rand()
,
to generate uniformly generated random numbers between 0 and 1, and randn()
, to generate random numbers from a
standard normal distribution (i.e. normally distributed numbers with a mean of 0 and a standard deviation of 1). All
other stochastic elements of a simulation (probabilistic connections, Poisson-distributed input generated by
PoissonGroup or PoissonInput, differential equations using the noise term xi
, ...) will internally make use of
these two basic functions.
For :ref:`runtime`, random numbers are generated by numpy.random.rand and numpy.random.randn respectively, which
uses a Mersenne-Twister pseudorandom number generator. When the
numpy
code generation target is used, these functions are called directly, but for cython
, Brian
uses a internal buffers for uniformly and normally distributed random numbers and calls the numpy functions whenever
all numbers from this buffer have been used. This avoids the overhead of switching between C code and Python code for
each random number. For :ref:`cpp_standalone`, the random number generation is based on "randomkit", the same
Mersenne-Twister implementation that is used by numpy. The source code of this implementation will be included in every
generated standalone project.
As explained above, :ref:`runtime` makes use of numpy's random number generator. In principle, using numpy.random.seed
therefore permits reproducing a stream of random numbers. However, for cython
, Brian's buffer
complicates the matter a bit: if a simulation sets numpy's seed, uses 10000 random numbers, and then resets the seed,
the following 10000 random numbers (assuming the current size of the buffer) will come out of the pre-generated buffer
before numpy's random number generation functions are called again and take into account the seed set by the user.
Instead, users should use the seed function provided by Brian 2 itself, this will take care of setting numpy's random
seed and empty Brian's internal buffers. This function also has the advantage that it will continue to work when the
simulation is switched to standalone code generation (see below). Note that random numbers are not guaranteed to be
reproducible across different code generation targets or different versions of Brian, especially if several sources of
randomness are used in the same CodeObject (e.g. two noise variables in the equations of a NeuronGroup). This is
because Brian does not guarantee the order of certain operations (e.g. should it first generate all random numbers for
the first noise variable for all neurons, followed by the random numbers for the second noise variable for all neurons
or rather first the random numbers for all noice variables of the first neuron, then for the second neuron, etc.) Since
all random numbers are coming from the same stream of random numbers, the order of getting the numbers out of this
stream matter.
For :ref:`cpp_standalone`, Brian's seed function will insert code to set the random number generator seed into the
generated code. The code will be generated at the position where the seed call was made, allowing detailed control
over the seeding. For example the following code would generate identical initial conditions every time it is run, but
the noise generated by the xi
variable would differ:
G = NeuronGroup(10, 'dv/dt = -v/(10*ms) + 0.1*xi/sqrt(ms) : 1') seed(4321) G.v = 'rand()' seed() run(100*ms)
Note
In standalone mode, seed will not set numpy's random number generator. If you use random numbers in the Python script itself (e.g. to generate a list of synaptic connections that will be passed to the standalone code as a pre-calculated array), then you have to explicitly call numpy.random.seed yourself to make these random numbers reproducible.
Note
Seeding should lead to reproducible random numbers even when using OpenMP with multiple threads (for repeated simulations with the same number of threads), but this has not been rigorously tested. Use at your own risk.