Simulating Networks¶
The BSB offers adapters that enable you to simulate your network using widely-used neural simulation software. Consequently, once the model is created, it can be simulated across different software platforms without requiring modifications or adjustments. Currently, adapters are available for NEST, NEURON, and ARBOR, although support for ARBOR is not yet fully developed.
- All simulation details are specified within the simulation block, which includes:
a
simulator
: the software chosen for the simulations.set of
cell models
: the simulator specific representations of the network’s CellTypesset of
connection models
: that instruct the simulator on how to handle the ConnectivityStrategies of the networkset of
devices
: define the experimental setup (such as input stimuli and recorders).
All of the above is simulation backend specific and is covered in the corresponding sections:
Running Simulations¶
Simulations can be run through the CLI or through the bsb
library for more
control:
bsb simulate my_network.hdf5 my_sim_name
from bsb import from_storage
network = from_storage("my_network.hdf5")
network.run_simulation("my_sim")
When using the CLI, the framework sets up a “hands off” simulation workflow:
Read the network file
Read the simulation configuration
Translate the simulation configuration to the simulator
Create all cells, connections and devices
Run the simulation
Collect all the output
When you use the library, you can set up more complex workflows, such as parameter sweeps:
# A module to read HDF5 data
# A module to run NEURON simulations in isolation
import nrnsub
from bsb import from_storage
# This decorator runs each call to the function in isolation
# on a separate process. Does not work with MPI.
@nrnsub.isolate
def sweep(param):
# Open the network file
network = from_storage("my_network.hdf5")
# Here you can set whatever simulation parameter you want.
network.simulations.my_sim.devices.my_stim.rate = param
# Run the simulation
results = network.run_simulation("my_sim")
# Tjese are the recorded spiketrains and signals
print(results.spiketrains)
print(results.analogsignals)
for i in range(11):
# Sweep parameter from 0 to 1 in 0.1 increments
sweep(i / 10)
Parallel simulations
To parallelize any BSB task prepend the MPI command in front of the BSB CLI command, or the Python script command:
mpirun -n 4 bsb simulate my_network.hdf5 my_sim_name
mpirun -n 4 python my_simulation_script.py
Where n
is the number of parallel nodes you’d like to use.
Targetting¶
To customize our experimental setup, devices can be arranged to target specific cell populations. In the BSB, several methods are available to filter the populations of interest. These methods can be based on various criteria, including cell characteristics, labels, and geometric constraints within the network volume.
The target population can be defined when a device block is created in the configuration:
"my_new_device": {
"device": "device_type",
"targetting": {
"strategy": "my_target_strategy",
}
}
config.simulations["my_simulation_name"].devices=dict(
my_new_device={
"device": "device_type",
"targetting": {
"strategy": "my_target_strategy",
}
}
)
Strategies based on cell¶
strategy name
: all . This is a basic strategy that targets all the cells in our network
Target by cell model¶
strategy name
: cell_model . This strategy targets only the cells of the specified models.
Users must provide a list of cell models to target using the attribute cell_models .
Target by id¶
strategy name
: by_id . Each cell model is assigned a numerical identifier
that can be used to select the target cells.
It is necessary to provide a list of integers representing the cell IDs with the attribute ids .
Geometric strategies¶
Instead of targeting cells based on characteristics or labels, it is possible to target a defined region using geometric constraints.
Target a Cylinder¶
strategy name
: cylinder. This strategy targets all the cells contained within a cylinder along the defined axis.
The user must provide three attributes:
origin
: A list of coordinates representing the base of the cylinder for each non-main axis.axis
: A character is used to specify the main axis of the cylinder. Accepted values are “x,” “y,” and “z,” with the default set to “y.”radius
: A float representing the radius of the cylinder.
Target a Sphere¶
strategy name
: sphere. This strategy targets all the cells contained within a sphere.
The user must provide two attributes:
origin
: A list of float that defines the center of the sphere.radius
: A float representing the radius of the sphere.
Simulation results¶
The results of a simulation are stored in .nio
files.
These files are read and written using the Neo Python package, which is specifically
designed for managing electrophysiology data. The files utilize the HDF5 (Hierarchical Data Format version 5)
structure, which organizes data in a hierarchical manner, similar to a dictionary.
The data is organized into blocks, where each block represents a recording session (i.e., a simulation block).
Each block is further subdivided into segments, with each segment representing a specific timeframe within the session.
To retrieve the blocks from a .nio
file:
from neo import io
neo_obj = io.NixIO("NAME_OF_YOUR_NEO_FILE.nio", mode="ro")
blocks = neo_obj.read_all_blocks()
for block in blocks:
list_of_segments = blocks.segments
For more information, please refer to the Neo documentation.
Spike Trains¶
Within a segment, you can access all the SpikeTrain
objects recorded during
that particular timeframe. A SpikeTrain
represents the collection of spikes emitted by the target population,
and it includes metadata about the device name, the size of the cell population, and the IDs of the spiking cells.
This information is stored in the annotations attribute:
for spiketrain in segment.spiketrains:
spiketrain_array = spiketrain.magnitude
unit_of_measure = spiketrain.units
device_name = spiketrain.annotations["device"]
list_of_spiking_cell_ids = spiketrain.annotations["senders"]
end_time_of_the_simulation = spiketrain.annotations["t_stop"]
population_size = spiketrain.annotations["pop_size"]
Note
To retrieve the spike train for a specific cell, the spike time at index i in the spiketrain_array[i]
corresponds to the cell with ID list_of_spiking_cell_ids[i]
.
Analog Signals¶
Each segment also contains an analogsignals attribute, which holds a list of Neo AnalogSignal
objects.
These objects contain the trace of the recorded property, along with the associated time points.
They are also annotated with information such as the device name, the type of cell recorded, and the cell ID,
which can be accessed through the annotations attribute:
for signal in segment.analogsignals:
trace_of_the_signal = signal.magnitude
unit_of_measure = signal.units
time_signature = signal.times
time_unit = signal.times.units
device_name = signal.annotations['name']
cell_type = signal.annotations['cell_type']
cell_id = signal.annotations['cell_id']
Note
Unlike the spike train case, the analogsignals attribute contains a separate AnalogSignal
object for each target of the device.