Compatibility and reproducibility
Supported Python and numpy versions
We follow the approach outlined in numpy’s deprecation policy. This means that Brian supports:
All minor versions of Python released 42 months prior to Brian, and at minimum the two latest minor versions.
All minor versions of numpy released in the 24 months prior to Brian, and at minimum the last three minor versions.
Note that we do not have control about the versions that are supported by the conda-forge
infrastructure. Therefore, brian2
conda packages might not be provided for all of the supported versions. In this
case, affected users can chose to either update the Python/numpy version in their conda environment to a version with a
conda package or to install brian2
via pip.
General policy
We try to keep backwards-incompatible changes to a minimum. In general, brian2
scripts should continue to work with
newer versions and should give the same results.
As an exception to the above rule, we will always correct clearly identified bugs that lead to incorrect simulation
results (i.e., not just an matter of interpretation). Since we do not want to require new users to take any action
to get correct results, we will change the default behaviour in such cases. If possible, we will give the user an
option to restore the old, incorrect behaviour to reproduce the previous results with newer Brian versions. This would
typically be a preference in the legacy
category, see legacy.refractory_timing for an example.
Note
The order of terms when evaluating equations is not fixed and can change with the version of sympy
, the symbolic
mathematics library used in Brian. Similarly, Brian performs a number of optimizations by default and asks the
compiler to perform further ones which might introduce subtle changes depending on the compiler and its version.
Finally, code generation can lead to either Python or C++ code (with a single or multiple threads) executing the
actual simulation which again may affect the numerical results. Therefore, we cannot guarantee exact, “bitwise”
reproducibility of results.
Syntax deprecations
We sometimes realize that the names of arguments or other syntax elements are confusing and therefore decide to change
them. In such cases, we start to use the new syntax everywhere in the documentation and examples, but leave the former
syntax available for compatiblity with previously written code. For example, earlier versions of Brian used
method='linear'
to describe the exact solution of differential equations via sympy (that most importantly applies
to “linear” equations, i.e. linear differential equations with constant coefficients). However, some users interpreted
method='linear'
as a “linear approximation” like the forward Euler method. In newer versions of Brian the
recommended syntax is therefore to use method='exact'
, but the old syntax remains valid.
If the changed syntax is very prominent, its continued use in Brian scripts (published by others) could be confusing to
new users. In these cases, we might decide to give a warning when the deprecated syntax is used (e.g. for the pre
and post
arguments in Synapses
which have been replaced by on_pre
and on_post
). Such warnings will contain
all the information necessary to rewrite the code so that the warning is no longer raised (in line with our general
policy for warnings).
Random numbers
Streams of random numbers in Brian simulations (including the generation of synapses, etc.) are reproducible when a
seed is set via Brian’s seed()
function. Note that there is a difference with regard to random numbers between
runtime and standalone mode: in runtime mode, numpy’s random number generator is always
used – even from generated Cython code. Therefore, the call to seed()
will set numpy’s random number generator seed
which then applies to all random numbers. Regardless of whether initial values of a variable are set via an explicit
call to numpy.random.randn
, or via a Brian expression such as 'randn()'
, both are affected by this seed. In
contrast, random numbers in standalone simulations will be generated by an independent random number generator (but
based on the same algorithm as numpy’s) and the call to seed()
will only affect these numbers, not numbers resulting
from explicit calls to numpy.random
. To make standalone scripts mixing both sources of randomness reproducible, either
set numpy’s random generator seed manually in addition to calling seed()
, or reformulate the model to use code
generation everywhere (e.g. replace group.v = -70*mV + 10*mV*np.random.randn(len(group))
by
group.v = '-70*mv + 10*mV*randn()'
).
Changing the code generation target can imply a change in the order in which random numbers are drawn from the reproducible random number stream. In general, we therefore only guarantee the use of the same numbers if the code generation target and the number of threads (for C++ standalone simulations) is the same.
Note
If there are several sources of randomness (e.g. multiple PoissonGroup
objects) in a simulation, then the order
in which these elements are executed matters. The order of execution is deterministic, but if it is not
unambiguously determined by the when
and order
attributes (see Scheduling for details), then it will
depend on the names of objects. When not explicitly given via the name
argument during the object’s creation,
names are automatically generated by Brian as e.g. poissongroup
, poissongroup_1
, etc. When you repeatedly
run simulations within the same process, these names might change and therefore the order in which the elements are
simulated. Random numbers will then be differently distributed to the objects. To avoid this and get reproducible
random number streams you can either fix the order of elements by specifying the order
or name
argument,
or make sure that each simulation gets run in a fresh Python process.
Python errors
While we try to guarantee the reproducibility of simulations (within the limits stated above), we do so only for code
that does not raise any error. We constantly try to improve the error handling in Brian, and these improvements can
lead to errors raised at a different time (e.g. when creating an object as opposed to when running the simulation),
different types of errors being raised (e.g. DimensionMismatchError
instead of TypeError
), or simply a different
error message text. Therefore, Brian scripts should never use try
/except
blocks to implement program logic.