Brian2 Documentation
repository·master·Indexed 22 days ago
https://github.com/brian-team/brian2A Python-based, clock-driven simulator for spiking neural networks designed for scientific research. Brian2 provides a flexible and extensible framework for modeling neural systems, featuring a standalone C++ mode for high-performance simulation, support for custom events in NeuronGroups, and a wide range of built-in mathematical functions for equations and expressions.
What's inside Brian2
- Brian2 is a free, open-source, clock-driven simulator for spiking neural networks written in Python. It is designed to be highly flexible, extensible, and easy to use for scientists. It is available on most platforms and released under the CeCILL 2.1 license.
Overview of the code generation package structure
masterThe code generation system is organized into several key modules:
brian2.codegen: The core code generation logic.brian2.codegen.generators: Snippet generation, includingCodeGeneratorclasses and default function implementations.brian2.codegen.runtime: Templates, compilation, and running of code, includingCodeObjectand runtime-specific functions.brian2.parsing: General tools for parsing expressions.brian2.parsing.rendering: AST tools for rendering Python expressions into other languages.brian2.core.functions&brian2.core.variables: Definitions of variable values and core functions.
Achieve reproducible random numbers in Brian
masterTo ensure reproducible simulations, use the
seed()function. However, the behavior of randomness depends on your execution mode:Runtime Mode
In runtime mode, Brian uses
numpy's random number generator for everything, including generated Cython code. Callingseed()sets thenumpyseed, affecting both explicitnumpy.randomcalls and Brian expressions like'randn()'.Standalone Mode
In standalone mode, random numbers are generated by an independent generator.
seed()only affects these internal numbers, not explicitnumpy.randomcalls.To ensure reproducibility in standalone mode when mixing sources:
- Manually set the
numpyrandom seed in addition to callingseed(). - OR Reformulate the model to use code generation for all randomness (e.g., replace
np.random.randn(...)with the string expression'randn()').
Important Caveats for Reproducibility
- Code Generation Target: Changing the target (e.g., Python vs C++) or the number of threads in C++ simulations can change the order in which random numbers are drawn.
- Execution Order: If the execution order of random sources (like multiple
PoissonGroupobjects) is not explicitly defined viaorderorwhen, it may depend on object names. Since names likepoissongroup_1can change between runs in the same process, the random number stream may shift. - Best Practice: To guarantee order, explicitly specify the
orderornameargument during object creation, or run each simulation in a fresh Python process.
# Recommended: Use Brian expressions for all randomness to ensure # compatibility between runtime and standalone modes. group.v = '-70*mv + 10*mV*randn()' # Avoid mixing: # group.v = -70*mV + 10*mV*np.random.randn(len(group))- Manually set the
How 'Magic' networks work in Brian 2
masterFor simple, flat scripts, calling
run()directly creates a "magic" network. Brian 2 uses a "visibility" logic to determine which objects are included:- Visibility Rule: The magic network contains all Brian objects (e.g.,
NeuronGroup,Synapses,SpikeMonitor) that are visible in the same execution frame as therun()call. - No Manual Cleanup: Unlike Brian 1, you do not need to call
clear()orreinit_default_clock()in a loop. Eachrun()call only simulates the objects it "sees" and starts at 0s. - Function Returns: If a function returns a Brian object and that object is stored in a variable at the level of the
run()call, it is considered visible and will be included. The Brian 1 functionsmagic_returnandmagic_registerare no longer necessary. - Limitation: Objects inside containers (e.g.,
groups = {'exc': NeuronGroup(...)}) are not visible to the magic network. For these cases, use an explicitNetworkobject.
# Example of a clean loop in Brian 2 for r in range(100): group1 = NeuronGroup(...) group2 = NeuronGroup(...) syn = Synapses(group1, group2, ...) mon = SpikeMonitor(group2) run(1*second) # Automatically sees group1, group2, syn, mon and starts at 0s- Visibility Rule: The magic network contains all Brian objects (e.g.,
Control model behavior during refractoriness using (unless refractory)
masterBy default, refractoriness only prevents threshold crossings from triggering new spikes. All differential equations continue to update normally.
To stop specific state variables from updating during the refractory period, append the
(unless refractory)flag to the variable declaration in the model string. Variables marked this way are 'clamped' (held constant) during refractoriness. Additionally, variables in a refractory neuron are treated as read-only; incoming synaptic inputs will not affect their values until the refractory period ends.# 'v' is clamped during refractoriness, but 'w' continues to update G = NeuronGroup(N, '''dv/dt = -(v + w)/ tau_v : 1 (unless refractory) dw/dt = -w / tau_w : 1''', threshold='v > 1', reset='v=0; w+=0.1', refractory=2*ms)Use TimedArray for non-continuous stimulation
masterA
TimedArrayacts as a function of time where values are explicitly defined at specific time points. This is useful for non-continuous or block-based stimulation.- 1D Array: Returns the same value for all neurons at a given time.
- 2D Array: The first dimension is time, and the second dimension is the neuron/synapse index. This allows for different values (e.g., shared noise) per neuron.
Semantics Note: For
TimedArray([x1, x2, ...], dt=my_dt), the valuex1is returned for0 <= t < my_dt,x2formy_dt <= t < 2*my_dt, etc.# Example: 2D TimedArray for shared noise between even/odd neurons runtime = 1*second stimulus = TimedArray(np.random.rand(int(runtime/defaultclock.dt), 2), dt=defaultclock.dt) G = NeuronGroup(100, 'dv/dt = (-v + stimulus(t, i % 2))/(10*ms) : 1', threshold='v>1', reset='v=0')How the preferences hierarchy works
masterBrian2 loads preferences from a hierarchy of files. Each subsequent level overrides the values from the previous level. If a file is missing, no error is raised.
- Global defaults: Stored in the installation directory.
- User defaults: Stored in
~/.brian/preferences(works on Windows and Linux). - Local preferences: The file
brian_preferenceslocated in the current working directory.
Define shared variables
masterA shared variable is a variable that has a common value for all neurons in a group. This is useful for modeling external stimuli that change over time.
- Declaration: Use the
(shared)flag in the model description. - Restrictions: Shared variables cannot be written to in contexts that apply to only a subset of neurons (like
resetstatements). If mixing shared and vector writes, shared statements must come first. - Subexpressions: By default, subexpressions are re-evaluated. Use the
(constant over dt)flag for subexpressions that should only be evaluated once per time step (e.g., when usingrand()).
G = NeuronGroup(10, '''shared_input : volt (shared) dv/dt = (-v + shared_input)/tau : volt tau : second''', name='neurons') # Setting the shared value G.shared_input = '(4.0/N)*mV'- Declaration: Use the
Build a morphology tree structure
masterMorphologies are built by attaching new
Morphologyobjects to existing ones as attributes. This creates a tree structure where the original object acts as the parent.Navigation Syntax
- Dot notation: Use
morpho.nameto access a child section. - Indexing syntax: Use
morpho['name']to access a child section. - Abbreviated syntax: If you name child sections
L(left),R(right), or digits1-9, you can navigate the tree using a single string (e.g.,morpho.LRLLRis equivalent tomorpho.L.R.L.L.R).
Warning: Avoid naming sections with names like
L1if you intend to use the abbreviated syntax, asL1will be interpreted as the first child of sectionL.Tree Properties
morpho.n: Number of compartments in the current section.morpho.total_sections: Total number of sections in the entire subtree.morpho.total_compartments: Total number of compartments in the entire subtree.morpho.topology(): Returns a visual representation of the tree structure.
# Building a tree morpho = Soma(diameter=30*um) morpho.axon = Cylinder(length=100*um, diameter=1*um, n=10) morpho.dendrite = Cylinder(length=50*um, diameter=2*um, n=5) # Adding branches to the dendrite morpho.dendrite.branch1 = Cylinder(length=50*um, diameter=1*um, n=3) # Using abbreviated syntax for quick creation morpho = Soma(diameter=30*um) morpho.L = Cylinder(length=10*um, diameter=1*um, n=3) morpho.L1 = Cylinder(length=5*um, diameter=1*um, n=3) # Verifying structure print(morpho.topology())- Dot notation: Use
Introduce noise using stochastic differential equations
masterBrian2 supports stochastic differential equations using the term
xi. This represents Gaussian noise with mean 0 and standard deviation 1, scaling with units of1/sqrt(second).Note: If you use noise in multiple equations within the same
NeuronGrouporSynapses, you must use suffixed names to distinguish them.# Example: Ornstein-Uhlenbeck process for a leaky integrate-and-fire neuron G = NeuronGroup(10, 'dv/dt = -v/tau + sigma*sqrt(2/tau)*xi : volt')Choose between Runtime and Standalone C++ execution modes
masterBrian offers two primary modes for running simulations, balancing flexibility and speed:
- Runtime mode (Default): The simulation loop runs in Python, but the heavy numerical work (integration, propagation) is compiled into a target language. This mode allows you to combine Brian's computations with arbitrary Python code using
NetworkOperation. - Standalone C++ mode: The entire simulation is compiled to C++. This is generally much faster for many simulation types but is less flexible and cannot be used for all simulations.
To enable Standalone C++ mode, call
set_device('cpp_standalone')after importing Brian but before defining your simulation code.from brian2 import * set_device('cpp_standalone') # ... rest of your simulation code- Runtime mode (Default): The simulation loop runs in Python, but the heavy numerical work (integration, propagation) is compiled into a target language. This mode allows you to combine Brian's computations with arbitrary Python code using
How Templates are used for code generation
masterBrian 2 uses Jinja2 templates to transform code snippets into runnable blocks. Templates come in two forms:
- Single Template: Used when code generation only requires a single block of code.
- Macro Templates: Define multiple Jinja macros, where each macro represents a separate code block.
A
CodeObjectspecifies which template to use and which macros to define. Runtime templates can be found in thebrian2/codegen/runtimedirectory.