briar
Large-Scale SNN framework with dendrites, built on Brian2
Research by Zubin Kane -- Makin Lab at Purdue University
Overview
briar provides pre-built architectures for studying how orientation selectivity and phase diversity emerge in visual cortex through spike-timing-dependent plasticity (STDP). It wraps Brian2 with a declarative layer for defining neuron pools, synapse pools, and learning rules, then handles device setup, compilation, monitoring, and result serialization automatically.
Key features:
- Declarative architecture definition -- define pools and synapses as dataclasses, briar generates the Brian2 equations
- Built-in architectures for common V1 models (feedforward, two-layer simple/complex, efficient coding)
- Custom architecture for building networks from scratch
- cpp_standalone and CUDA support with incremental compilation for fast repeated runs
- Parameter sweeps with automatic grid search
- Rich result objects with summary dashboards, diffs, and architecture-aware plotting
Installation
pip install briar
Quick Start
The simplest way to run an experiment is to pick an architecture and a task, then call buildrun():
from briar import SimpleComplex, NaturalImageTask
task = NaturalImageTask(n_patterns=500, image_size=32)
arch = SimpleComplex(task)
results = arch.buildrun()
This builds the full Brian2 network, runs the simulation, and returns a Results object containing all parameters, spike monitors, weight histories, and connectivity.
Inspecting results
# In Jupyter, just evaluate `results` to see the rich dashboard
results
# In a script, print() gives the same dashboard in plain text
print(results)
# results.summary() also prints the dashboard (same as print)
results.summary()
# Show only parameters that differ from defaults
results.diff()
# Architecture-aware default plots
results.plot()
results.plot(full=True) # additional diagnostics
results.plot(debug=True) # low-level debug panels
Modifying parameters
Every architecture parameter can be overridden at construction. The diff() method shows exactly what changed:
arch = SimpleComplex(
task,
eta_ff_simple=1e-3, # increase simple cell learning rate
ff_complex_radius=6.0, # widen complex cell receptive fields
)
results = arch.buildrun()
# diff() highlights only the non-default values
results.diff()
Adding pools to an existing architecture
Any architecture can be extended with additional pools after construction. Added pools are automatically discovered and built:
from briar import SimpleComplex, NaturalImageTask, NeuronPool, SynapsePool
task = NaturalImageTask(n_patterns=500, image_size=32)
arch = SimpleComplex(task)
# Add a new neuron pool and connect it
arch.add(NeuronPool(name='readout', n_neurons=8))
arch.add(SynapsePool(
name='ff_readout',
source=arch.simple_layer,
target=arch.readout,
))
results = arch.buildrun()
Device Modes
briar supports four device modes:
from briar import SimpleComplex, NaturalImageTask, SimConfig
task = NaturalImageTask(n_patterns=500, image_size=32)
# C++ standalone (default) -- compiled to native code
arch = SimpleComplex(task)
results = arch.buildrun()
# Runtime -- interpreted, no compilation, useful for quick tests
sim = SimConfig(use_cpp=False)
arch = SimpleComplex(task, sim=sim)
results = arch.buildrun()
# CUDA standalone -- compiled to GPU code (requires brian2cuda on Linux)
sim = SimConfig(use_cuda=True)
arch = SimpleComplex(task, sim=sim)
results = arch.buildrun()
# CUDA without reduce -- skip .cu file combining (for debugging)
sim = SimConfig(use_cuda=True, cuda_reduce=False)
arch = SimpleComplex(task, sim=sim)
results = arch.buildrun()
C++ and CUDA modes use incremental compilation -- the first build is slow, but subsequent runs reuse the compiled binary and finish in seconds.
Architectures
Simple
Feedforward-only: LGN ON/OFF inputs connect to simple cells via STDP. Tests whether feedforward learning alone can produce orientation selectivity with phase diversity.
input_pool (LGN ON/OFF) -> simple_layer (STDP)
from briar import Simple, RetinalWaveTask
task = RetinalWaveTask(n_waves=200, image_size=32)
arch = Simple(task, eta_ff_simple=5e-4)
results = arch.buildrun()
SimpleComplex
Two-layer architecture inspired by Antolik & Bednar (2011) with dendritic predictive coding from Mikulasch et al. (2021). Simple cells learn from LGN input via somatic STDP; complex cells learn from simple cells via dendritic predictive coding. Includes Mexican hat recurrent connections and feedback.
input_pool (LGN ON/OFF) -> simple_layer (somatic, STDP)
simple_layer -> complex_layer (dendritic, predictive coding)
complex_layer -> complex_layer (Mexican hat recurrent)
complex_layer -> simple_layer (Mexican hat feedback, fixed)
from briar import SimpleComplex, NaturalImageTask
task = NaturalImageTask(n_patterns=500, image_size=32)
arch = SimpleComplex(task)
results = arch.buildrun()
EfficientEncoder
Single-layer efficient coding model with feedforward predictive coding, Hebbian recurrent connections, and a decoder for reconstruction loss monitoring.
input_pool -> layer1 (dendritic, predictive coding)
layer1 -> layer1 (dendritic, Hebbian recurrent)
layer1 -> decoder (reconstruction loss)
from briar import EfficientEncoder, BarTask
task = BarTask(image_size=8, n_patterns=1000)
arch = EfficientEncoder(task)
results = arch.buildrun()
Custom
A blank architecture with no pre-defined pools. Build any network from scratch by adding pools and wiring them manually:
from briar import (
Custom, NeuronPool, SynapsePool, PoissonPool,
DecoderPool, SimConfig, PlasticityRule,
)
from briar.tasks import BarTask
from briar.datastructures import Compartment, DendriticRule
task = BarTask(image_size=8, n_patterns=500)
arch = Custom(task)
# Input layer
arch.add(PoissonPool(name='input', task=task))
# Hidden layer
arch.add(NeuronPool(name='hidden', n_neurons=16))
# Feedforward synapses with STDP
arch.add(SynapsePool(
name='ff',
source=arch.input,
target=arch.hidden,
eta=5e-4,
plasticity_rule=PlasticityRule.STDP,
))
# Spike monitor
arch.hidden.add_monitor('spikes')
results = arch.buildrun()
Parameter Sweeps
Run multiple experiments varying one or more parameters:
from briar import SimpleComplex, NaturalImageTask, sweep
task = NaturalImageTask(n_patterns=500)
# Sweep a single parameter
results = sweep(
SimpleComplex, task, use_cpp=False,
eta_ff_simple=[5e-5, 5e-4, 5e-3, 5e-2, 5e-1],
)
# Mix fixed overrides with swept parameters
results = sweep(
SimpleComplex, task,
ff_complex_radius=6.0, # scalar -> fixed
eta_ff_simple=[5e-5, 5e-4, 5e-3, 5e-2, 5e-1], # list -> swept
)
# Multi-parameter grid (Cartesian product)
results = sweep(
SimpleComplex, task,
eta_ff_simple=[1e-4, 1e-3],
eta_ff_complex=[5e-5, 5e-4],
) # 4 runs total
Comparison Plots
Compare results from a sweep (or manually loaded pickles) side-by-side:
from briar import plot_raster_comparison, plot_rate_ridgeline, Results
# Stacked raster plots
plot_raster_comparison(results, layer='simple')
plot_raster_comparison(results, layer='complex')
# Ridgeline firing rate distributions
plot_rate_ridgeline(results, layer='simple')
plot_rate_ridgeline(results, layer='complex')
# Works with manually loaded results too
r1 = Results.load('dumps/SimpleComplex/NaturalImageTask/20260304_091654.pkl')
r2 = Results.load('dumps/SimpleComplex/NaturalImageTask/20260304_131307.pkl')
plot_raster_comparison([r1, r2], labels=['eta=1e-4', 'eta=1e-3'])
Testing
# All tests
pytest
# Fast tests only - runtime mode, fastest in sequential (not parallel)
pytest -m "not slow" --override-ini="addopts="
# Slow tests only - cpp_standalone compile+run
pytest -m slow
# Slow test *examples* for a specific architecture
pytest -m encoder
pytest -m "slow and simple_complex"
Metadata
Release files for briar 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| briar-0.2.0.tar.gz | 163.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| briar-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 295.6 kB
Release files / briar-0.2.0.tar.gz
| Download URL | briar-0.2.0.tar.gz |
|---|---|
| Size | 163.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f8b36888e4fd9d1e2c53a3801670995bd225a4c78520cb96caf72f749cabd8ec
|
|
BLAKE2b-256 checksum How to use checksums |
562caceab330e9e71c5c0dc9ac148cce90d0e4b3c6f57a160aaade9c41b67d5f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / briar-0.2.0-py3-none-any.whl
| Download URL | briar-0.2.0-py3-none-any.whl |
|---|---|
| Size | 132.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
805944ccfac037cfca1055dcfa69641d452126948f60337d5d07c62f0a1df914
|
|
BLAKE2b-256 checksum How to use checksums |
7479418287d69361beb2e89b0e0a5910eb206872fa47ac6a9cab9a5cc0476e4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Security for briar 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.