deNEST¶
deNEST is a Python library for specifying networks and running simulations using the NEST simulator (https://nest-simulator.org).
deNEST allows the user to concisely specify large-scale networks and simulations in hierarchically-organized declarative parameter files.
From these parameter files, a network is instantiated in NEST (neurons & their projections), and a simulation is run in sequential steps (“sessions”), during which the network parameters can be modified and the network can be stimulated, recorded, etc.
Some advantages of the declarative approach:
- Parameters and code are separated
- Simulations are easier to reason about, reuse, and modify
- Parameters are more readable and succinct
- Parameter files can be easily version controlled and diffs are smaller and more interpretable
- Clean separation between the specification of the “network” (the simulated neuronal system) and the “simulation” (structured stimulation and recording of the network), which facilitates running different experiments using the same network
- Parameter exploration is more easily automated
To learn how to use deNEST, please see the Overview section and the Jupyter notebook tutorials:
Installation¶
Local¶
Install NEST >= v2.14.0, <3.0 by following the instructions at http://www.nest-simulator.org/installation/.
Set up a Python 3 environment and install deNEST with:
pip install denest
Docker¶
A Docker image is provided with NEST 2.20 installed, based on nest-docker.
From within the repo, build the image:
docker build --tag denest .
Run an interactive container:
docker run \ -it \ --name denest_simulation \ --volume $(pwd):/opt/data \ --publish 8080:8080 \ denest \ /bin/bash
Install deNEST within the container:
pip install -e .
Use deNEST from within the container.
For more information on how to use the NEST Docker image, see nest-docker.
Overview¶
Definitions¶
Network¶
- We use the term network to mean a full network in NEST, consisting of layers of units with specific models, projections of specific types with specific synapse models amongst these layers, population recorders (multimeters, spike detectors) and projection recorders (weight recorder).
- The full network is represented in deNEST by the
Network
class.- New NEST models (neuron and generator model, synapse model or recorder model) can be specified with arbitrary parameters. During network creation, models with specific parameters are created in NEST using a
nest.CopyModel()
or anest.SetDefaults()
call.
- Synapse models are represented by the
SynapseModel
class in deNEST. All other models are represented by theModel
class.- Neuron and generator models are specified as leaves of the
network/neuron_models
parameter subtree (see section below)- Synapse models are specified as leaves of the
network/synapse_models
parameter subtree (see “Network parameters” section below)- Recorder models are specified as leaves of the
network/recorder_models
parameter subtree (see “Network parameters” section below)- A layer is a NEST topological layer, created with a
tp.CreateLayer()
call in NEST. A population is all the nodes of the same model within a layer.
- Layers are represented by the
Layer
orInputLayer
class in deNEST.- Layers can be of the type
InputLayer
when they are composed of generators. An extra population of parrot neurons can be automatically created and connected one-to-one to the generators, such that recording of generators’ activity is possible. Additionally,InputLayer
supports shifting theorigin
flag of stimulators at the start of aSession
.- Layers are specified as leaves of the
network/layers
parameter subtree (see “Network parameters” section below).- A projection model is a template specifying parameters passed to
tp.ConnectLayers
, and that individual projections amongst populations can inherit from.
- Projection models are represented by the
ProjectionModel
class in deNEST.- Projection models are specified as leaves of the
network/projection_models
parameter subtree (see “Network parameters” section below).- A projection is an individual projection between layers or populations, created with a
tp.ConnectLayers()
call. The parameters passed totp.ConnectLayers()
are those of the “projection model” of the specific projection.
- The list of all individual projections within the network is specified in the
'projections'
parameter of thenetwork/topology
parameter subtree (see “Network parameters” section below).- A population recorder is a recorder connected to all the nodes of a given population. A projection recorder is a recorder connected to all the synapses of a given projection. Recorders with arbitrary parameters can be defined by creating “recorder models”. However, currently only recorders based on the ‘multimeter’, ‘spike_detector’ and ‘weight_recorder’ NEST models are supported.
- population and projection recorders are represented by the
PopulationRecorder
andProjectionRecorder
classes in deNEST.- The list of all population recorders and projection recorders are specified in the
'population_recorders'
and'projection_recorders'
parameters of thenetwork/recorders
parameter subtree (See “Network parameters” section below).
Simulation¶
- A session model is a template specifying parameters inherited by
individual sessions.
- session models are specified as leaves of the
session_models
parameter subtree (see “Simulation parameters” section below)
- session models are specified as leaves of the
- A session is a period of simulation of a network with specific inputs
and parameters, and corresponds to a single
nest.Simulate()
call. The parameters used by a given session are inherited from its session model.- A session’s parameters define the operations that may be performed before
running it:
- Modifying the state of some units (using the
Network.set_state()
method) - (Possibly) shift the
origin
flag for theInputLayer
stimulators - (Possibly) deactivate the recorders for that session by setting their
start
flag to the end of the session
- Modifying the state of some units (using the
- Individual sessions are represented by the
Session
object in deNEST. (see “Simulation parameters” section below)
- A session’s parameters define the operations that may be performed before
running it:
- A simulation is a full experiment. It is represented by the
Simulation
object in deNEST, which contains aNetwork
object and a list ofSession
objects.- The list of sessions run during a simulation is specified by the
sessions
parameter of thesimulation
parameter subtree (eg:sessions: ['warmup', 'noise', 'grating', 'noise', 'grating']
) (see “Simulation parameters” section below).
- The list of sessions run during a simulation is specified by the
Overview of a full simulation¶
A full deNEST simulation consists of the following steps:
- Initialize simulation (
Simulation.__init__(<params_tree>)()
)- Initialize kernel: (
Simulation.init_kernel(<kernel_subtree>)()
)- Set NEST kernel parameters
- Set seed for NEST’s random generator.
- Create network (
Simulation.create_network(<network_subtree>)()
):- Initialize the network objects (
Network.__init__(<network_subtree)()
) - Create the objects in NEST (
Network.create()
)
- Initialize the network objects (
- Initialize the sessions (
Session.__init__()
) - Save the simulation’s metadata
- Create and clean the output directory
- Save the full simulation parameter tree
- Save deNEST and NEST version information
- Save session times
- Save network metadata
- Save session metadata
- Initialize kernel: (
- Run the simulation (
Simulation.run()
). This runs each session in turn:- Initialize session (
Session.initialize()
)- (Possibly) reset the network
- (Possibly) inactivate recorders for the duration of the session
- (Possibly) shift the origin of stimulator devices to the start of the session
- (Possibly) Change some of the network’s parameters using the
Network.set_state()
method- Change neuron parameters
- Change synapse parameters
- Call
nest.Simulate()
.
- Initialize session (
Specifying the simulation parameters¶
All parameters used by deNEST are specified in tree-like YAML files which are
converted to ParamsTree
objects.
In this section, we describe the ParamsTree
objects, the expected structure
of the full parameter tree interpreted by deNEST, and the expected formats and
parameters of each of the subtrees that define the various aspects of the
network and simulation.
Main parameter file¶
To facilitate defining parameters in separate files, denest.run()
and
denest.load_trees()
take as input a path to a YAML file containing the
relative paths of the tree-like YAML files to merge so as to define the full
parameter tree (for examples, see the Example declarative specification or the
params/tree_paths.yml
file in the repository.).
The ParamsTree
class¶
The ParamsTree
class is instantiated from tree-like nested dictionaries. At
each node, two reserved keys contain the node’s data (called 'params'
and
'nest_params'
). All the other keys are interpreted as named children nodes.
The 'params'
key contains data interpreted by deNEST, while the
'nest_params'
key contains data passed to NEST without modification.
The ParamsTree
class offers a tree structure with two useful
characteristics:
- Hierarchical inheritance of ancestor’s data: This provides a concise way
of defining data for nested scopes. Data common to all leaves may be specified
once in the root node, while more specific data may be specified further down
the tree. Data lower within the tree overrides data higher in the tree.
Ancestor nodes’
params
andnest_params
are inherited independently. - (Horizontal) merging of trees:
ParamsTree
objects can be merged horizontally withParamsTree.merge()
. During the merging of multiple params trees, the contents of theparams
andnest_params
data keys of nodes at the same relative position are combined. This allows splitting the deNEST parameter trees in separate files for convenience, and overriding the data of a node anywhere in the tree while preserving hierarchical inheritance.
An example parameter tree¶
Below is an example of a YAML file with a tree-like structure that can be loaded
and represented by the ParamsTree
class:
network:
neuron_models:
ht_neuron:
params: # params common to all leaves
nest_model: ht_neuron
nest_params: # nest_params common to all leaves
g_KL: 1.0
cortical_excitatory:
nest_params:
tau_spike: 1.75
tau_m: 16.0
l1_exc: # leaf
l2_exc: # leaf
nest_params:
g_KL: 2.0 # Overrides ancestor's value
cortical_inhibitory:
nest_params:
tau_m: 8.0
l1_inh: # leaf
This file can be loaded into a ParamsTree
object. The leaves of the
resulting ParamsTree
and their respective data (params
and
nest_params
) are as follows. Note the inheritance and override of
ancestor data. The nested format above is more compact and less error prone
when there are a lot of shared parameters between leaves.
l1_exc:
params:
nest_model: ht_neuron
nest_params:
g_KL: 1.0
tau_spike: 1.75
tau_m: 16.0
l2_exc:
params:
nest_model: ht_neuron
nest_params:
g_KL: 2.0
tau_spike: 1.75
tau_m: 16.0
l1_inh:
params:
nest_model: ht_neuron
nest_params:
g_KL: 1.0
tau_m: 8.0
Full parameter tree: expected structure¶
All the aspects of the overall simulation are specified in specific named subtrees.
The overall ParamsTree
passed to denest.Simulation()
is expected to have
no data and the following children:
simulation
(ParamsTree
). Defines input and output paths, and the simulation steps performed. The following parameters (params
field) are recognized:
output_dir
(str): Path to the output directory. (Default:'output'
)input_dir
(str): Path to the directory in which input files are searched for for each session. (Default:'input'
)sessions
(list(str)): Order in which sessions are run. Elements of the list should be the name of session models defined in thesession_models
parameter subtree (Default:[]
)
kernel
(ParamsTree
): Used for NEST kernel initialization. Refer toSimulation.init_kernel()
for a description of kernel parameters.
session_models
(ParamsTree
): Parameter tree, the leaves of which define session models. Refer toSessions()
for a description of session parameters.
network
(ParamsTree
): Parameter tree defining the network in NEST. Refer toNetwork
for a full description of network parameters.
"network"
parameter tree: expected structure¶
All network parameters are specified in the network
subtree, used to
initialize the Network
object.
The network
subtree should have no data, and the following children are expected:
neuron_models
(ParamsTree
). Parameter tree, the leaves of which define neuron models. Each leaf is used to initialize aModel
objectsynapse_models
(ParamsTree
). Parameter tree, the leaves of which define synapse models. Each leaf is used to initialize aSynapseModel
objectlayers
(ParamsTree
). Parameter tree, the leaves of which define layers. Each leaf is used to initialize aLayer
orInputLayer
object depending on the value of theirtype
params
parameter.projection_models
(ParamsTree
). Parameter tree, the leaves of which define projection models. Each leaf is used to initialize aProjectionModel
object.recorder_models
(ParamsTree
). Parameter tree, the leaves of which define recorder models. Each leaf is used to initialize aModel
object.topology
(ParamsTree
).ParamsTree
object without children, theparams
of which may contain aprojections
key specifying all the individual population-to-population projections within the network as a list.Projection
objects are created from thetopology
ParamsTree
object by theNetwork.build_projections
method. Refer to this method for a description of thetopology
parameter.recorders
(ParamsTree
).ParamsTree
object without children, theparams
of which may contain apopulation_recorders
and aprojection_recorders
key specifying all the network recorders.PopulationRecorder
andProjectionRecorder
objects are created from therecorders
ParamsTree
object by theNetwork.build_recorders
method. Refer to this method for a description of therecorders
parameter.
Running a deNEST Simulation¶
From Python (e.g. in a Jupyter notebook):
Using the
Simulation
object to run the simulation step by step:import denest # Path to the parameter files to use params_path = 'params/tree_paths.yml' # Override some parameters loaded from the file overrides = [ # Maybe change the nest kernel's settings ? {'kernel': {'nest_params': {'local_num_threads': 20}}}, # Maybe change a parameter for all the projections at once ? {'network': {'projection_models': {'nest_params': { 'allow_autapses': true }}}}, ] # Load the parameters params = denest.load_trees(params_path, *overrides) # Initialize the simulation sim = denest.Simulation(params, output_dir='output') # Run the simulation (runs all the sessions) sim.run()
Using the
denest.run()
function to run the full simulation at once:import denest # Path to the parameter files to use params_path = 'params/tree_paths.yml' # Override parameters overrides = [] denest.run(params_path, *overrides, output_dir=None)
From the command line:
python -m denest <tree_paths.yml> [-o <output_dir>]
Example declarative specification¶
Here is an example parameter file (in YAML) that specifies a full simulation:
params: {}
nest_params: {}
session_models:
params:
reset_network: false
record: true
shift_origin: false
nest_params: {}
even_rate:
params:
simulation_time: 50.0
unit_changes:
- layers:
- input_layer
population_name: input_exc
change_type: constant
from_array: false
nest_params:
rate: 100.0
nest_params: {}
warmup:
params:
reset_network: true
record: false
simulation_time: 50.0
unit_changes:
- layers:
- l1
population_name: null
change_type: constant
from_array: false
nest_params:
V_m: -70.0
- layers:
- input_layer
population_name: input_exc
change_type: constant
from_array: false
nest_params:
rate: 100.0
nest_params: {}
arbitrary_rate:
params:
simulation_time: 50.0
unit_changes:
- layers:
- input_layer
population_name: input_exc
change_type: constant
from_array: true
nest_params:
rate: ./input_layer_rates_5x5x1.npy
nest_params: {}
simulation:
params:
sessions:
- warmup
- even_rate
- arbitrary_rate
output_dir: ./output
input_dir: ./params/input
nest_params: {}
kernel:
params:
extension_modules: []
nest_seed: 94
nest_params:
local_num_threads: 20
resolution: 1.0
print_time: true
overwrite_files: true
network:
params: {}
nest_params: {}
neuron_models:
params: {}
nest_params: {}
ht_neuron:
params:
nest_model: ht_neuron
nest_params:
g_peak_NaP: 0.5
g_peak_h: 0.0
g_peak_T: 0.0
g_peak_KNa: 0.5
g_KL: 1.0
E_rev_NaP: 55.0
g_peak_AMPA: 0.1
g_peak_NMDA: 0.15
g_peak_GABA_A: 0.33
g_peak_GABA_B: 0.0132
instant_unblock_NMDA: true
S_act_NMDA: 0.4
V_act_NMDA: -58.0
cortical_inhibitory:
params: {}
nest_params:
theta_eq: -53.0
tau_theta: 1.0
tau_spike: 0.5
tau_m: 8.0
l1_inh:
params: {}
nest_params: {}
l2_inh:
params: {}
nest_params: {}
cortical_excitatory:
params: {}
nest_params:
theta_eq: -51.0
tau_theta: 2.0
tau_spike: 1.75
tau_m: 16.0
l1_exc:
params: {}
nest_params: {}
l2_exc:
params: {}
nest_params: {}
input_exc:
params:
nest_model: poisson_generator
nest_params: {}
layers:
params:
type: null
nest_params:
rows: 5
columns: 5
extent:
- 8.0
- 8.0
edge_wrap: true
input_area:
params:
type: InputLayer
add_parrots: true
nest_params: {}
input_layer:
params:
populations:
input_exc: 1
nest_params: {}
l1_area:
params: {}
nest_params: {}
l1:
params:
populations:
l1_exc: 2
l1_inh: 1
nest_params: {}
l2_area:
params: {}
nest_params: {}
l2:
params:
populations:
l2_exc: 2
l2_inh: 1
nest_params: {}
synapse_models:
params: {}
nest_params: {}
static_synapse:
params:
nest_model: static_synapse_lbl
nest_params: {}
input_synapse_NMDA:
params:
target_neuron: ht_neuron
receptor_type: NMDA
nest_params: {}
input_synapse_AMPA:
params:
target_neuron: ht_neuron
receptor_type: AMPA
nest_params: {}
ht_synapse:
params:
nest_model: ht_synapse
target_neuron: ht_neuron
nest_params: {}
GABA_B_syn:
params:
receptor_type: GABA_B
nest_params: {}
AMPA_syn:
params:
receptor_type: AMPA
nest_params: {}
GABA_A_syn:
params:
receptor_type: GABA_A
nest_params: {}
NMDA_syn:
params:
receptor_type: NMDA
nest_params: {}
topology:
params:
projections:
- source_layers:
- input_layer
source_population: parrot_neuron
target_layers:
- l1
target_population: l1_exc
projection_model: input_projection_AMPA
- source_layers:
- input_layer
source_population: parrot_neuron
target_layers:
- l1
target_population: l1_inh
projection_model: input_projection_AMPA
- source_layers:
- input_layer
source_population: parrot_neuron
target_layers:
- l1
target_population: l1_inh
projection_model: input_projection_NMDA
- source_layers:
- l1
source_population: l1_exc
target_layers:
- l1
target_population: l1_exc
projection_model: horizontal_exc
- source_layers:
- l1
source_population: l1_exc
target_layers:
- l1
target_population: l1_inh
projection_model: horizontal_exc
- source_layers:
- l1
source_population: l1_exc
target_layers:
- l2
target_population: l2_exc
projection_model: FF_exc
- source_layers:
- l1
source_population: l1_exc
target_layers:
- l2
target_population: l2_inh
projection_model: FF_exc
nest_params: {}
recorder_models:
params: {}
nest_params:
record_to:
- file
- memory
withgid: true
withtime: true
spike_detector:
params:
nest_model: spike_detector
nest_params: {}
weight_recorder:
params:
nest_model: weight_recorder
nest_params:
record_to:
- file
- memory
withport: false
withrport: true
multimeter:
params:
nest_model: multimeter
nest_params:
interval: 1.0
record_from:
- V_m
recorders:
params:
population_recorders:
- layers: []
populations: []
model: multimeter
- layers:
- l2
populations:
- l2_inh
model: multimeter
- layers: null
populations:
- l2_exc
model: multimeter
- layers:
- l1
populations: null
model: multimeter
- layers: null
populations: null
model: spike_detector
projection_recorders:
- source_layers:
- input_layer
source_population: parrot_neuron
target_layers:
- l1
target_population: l1_exc
projection_model: input_projection_AMPA
model: weight_recorder
- source_layers:
- l1
source_population: l1_exc
target_layers:
- l1
target_population: l1_exc
projection_model: horizontal_exc
model: weight_recorder
nest_params: {}
projection_models:
params:
type: topological
nest_params:
allow_autapses: false
allow_multapses: false
allow_oversized_mask: true
horizontal_inh:
params: {}
nest_params:
connection_type: divergent
synapse_model: GABA_A_syn
mask:
circular:
radius: 7.0
kernel:
gaussian:
p_center: 0.25
sigma: 7.5
weights: 1.0
delays:
uniform:
min: 1.75
max: 2.25
input_projection:
params: {}
nest_params:
connection_type: convergent
mask:
circular:
radius: 12.0
kernel: 0.8
weights: 1.0
delays:
uniform:
min: 1.75
max: 2.25
input_projection_AMPA:
params: {}
nest_params:
synapse_model: input_synapse_AMPA
input_projection_NMDA:
params: {}
nest_params:
synapse_model: input_synapse_NMDA
horizontal_exc:
params: {}
nest_params:
connection_type: divergent
synapse_model: AMPA_syn
mask:
circular:
radius: 12.0
kernel:
gaussian:
p_center: 0.05
sigma: 7.5
weights: 1.0
delays:
uniform:
min: 1.75
max: 2.25
FF_exc:
params: {}
nest_params:
connection_type: convergent
synapse_model: AMPA_syn
mask:
circular:
radius: 12.0
kernel: 0.8
weights: 1.0
delays:
uniform:
min: 1.75
max: 2.25