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.