Brian2 Documentation

repository·master·Indexed 22 days ago

https://github.com/brian-team/brian2

A 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.

Tokens
82.6K
Snippets
252
Records
335
Agent score
78%

What's inside Brian2

  1. Overview of Brian2

    master
    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.
  2. Overview of the code generation package structure

    master

    The code generation system is organized into several key modules:

    • brian2.codegen: The core code generation logic.
    • brian2.codegen.generators: Snippet generation, including CodeGenerator classes and default function implementations.
    • brian2.codegen.runtime: Templates, compilation, and running of code, including CodeObject and 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.
  3. Achieve reproducible random numbers in Brian

    master

    To 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. Calling seed() sets the numpy seed, affecting both explicit numpy.random calls 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 explicit numpy.random calls.

    To ensure reproducibility in standalone mode when mixing sources:

    1. Manually set the numpy random seed in addition to calling seed().
    2. 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 PoissonGroup objects) is not explicitly defined via order or when, it may depend on object names. Since names like poissongroup_1 can change between runs in the same process, the random number stream may shift.
    • Best Practice: To guarantee order, explicitly specify the order or name argument 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))
  4. How 'Magic' networks work in Brian 2

    master

    For 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 the run() call.
    • No Manual Cleanup: Unlike Brian 1, you do not need to call clear() or reinit_default_clock() in a loop. Each run() 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 functions magic_return and magic_register are no longer necessary.
    • Limitation: Objects inside containers (e.g., groups = {'exc': NeuronGroup(...)}) are not visible to the magic network. For these cases, use an explicit Network object.
    # 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
  5. Control model behavior during refractoriness using (unless refractory)

    master

    By 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)
  6. Use TimedArray for non-continuous stimulation

    master

    A TimedArray acts 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 value x1 is returned for 0 <= t < my_dt, x2 for my_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')
  7. How the preferences hierarchy works

    master

    Brian2 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.

    1. Global defaults: Stored in the installation directory.
    2. User defaults: Stored in ~/.brian/preferences (works on Windows and Linux).
    3. Local preferences: The file brian_preferences located in the current working directory.
  8. Define shared variables

    master

    A 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 reset statements). 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 using rand()).
    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'
  9. Build a morphology tree structure

    master

    Morphologies are built by attaching new Morphology objects to existing ones as attributes. This creates a tree structure where the original object acts as the parent.

    • Dot notation: Use morpho.name to 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 digits 1-9, you can navigate the tree using a single string (e.g., morpho.LRLLR is equivalent to morpho.L.R.L.L.R).

    Warning: Avoid naming sections with names like L1 if you intend to use the abbreviated syntax, as L1 will be interpreted as the first child of section L.

    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())
  10. Introduce noise using stochastic differential equations

    master

    Brian2 supports stochastic differential equations using the term xi. This represents Gaussian noise with mean 0 and standard deviation 1, scaling with units of 1/sqrt(second).

    Note: If you use noise in multiple equations within the same NeuronGroup or Synapses, 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')
  11. Choose between Runtime and Standalone C++ execution modes

    master

    Brian offers two primary modes for running simulations, balancing flexibility and speed:

    1. 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.
    2. 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
  12. How Templates are used for code generation

    master

    Brian 2 uses Jinja2 templates to transform code snippets into runnable blocks. Templates come in two forms:

    1. Single Template: Used when code generation only requires a single block of code.
    2. Macro Templates: Define multiple Jinja macros, where each macro represents a separate code block.

    A CodeObject specifies which template to use and which macros to define. Runtime templates can be found in the brian2/codegen/runtime directory.