Groups, structures, and neuronal populations#

One notable feature of NNGT is to enable users to group nodes (neurons) into groups sharing common properties in order to facilitate the generation of a network, the analysis of its properties, or complex simulations with NEST.

The complete example file containing the code discussed here, as well as additional information on how to access NeuralGroup and NeuralPop properties can be found there: docs/examples/introduction_to_groups.py.

Contents

Neuronal groups#

Neuronal groups are entities containing neurons which share common properties. Inside a population, a single neuron belongs to a single NeuralGroup object. Conversely the union of all groups contains all neurons in the network once and only once.

When creating a group, it is therefore important to make sure that it forms a coherent set of neurons, as this will make network handling easier.

For more versatile grouping, where neurons can belong to multiple ensembles, see the section about meta-groups below: Complex populations and metagroups.

Creating simple groups#

Groups can be created easily through calls to Group or NeuralGroup.

>>> group  = nngt.Group()
>>> ngroup = nngt.NeuralGroup()

create empty groups (nothing very interesting).

Minimally, any useful group requires at least neuron ids and, for a neuronal group, a type (excitatory or inhibitory) to be useful.

To create a useful group, one can therefore either just tell how many nodes/neurons it should contain:

group1 = Group(500)  # a group with 500 nodes

or directly pass it a list of ids (to avoid typing nngt. all the time, we do from nngt import Group, NeuralGroup at the beginning)

group2 = NeuralGroup(range(10, 20))  # neurons with ids from 10 to 19

Note that if you set ids directly you will be responsible for their consistency.

Creating a structured graph#

To create a structured graph, the groups are gathered into a Structure which can then be used to create a graph and connect the nodes.

room1 = nngt.Group(25)
room2 = nngt.Group(50)
room3 = nngt.Group(40)
room4 = nngt.Group(35)

names = ["R1", "R2", "R3", "R4"]

struct = nngt.Structure.from_groups((room1, room2, room3, room4), names)

g = nngt.Graph(structure=struct)

for room in struct:
    nngt.generation.connect_groups(g, room, room, "all_to_all")

nngt.generation.connect_groups(g, (room1, room2), struct, "erdos_renyi",
                               avg_deg=10, ignore_invalid=True)

nngt.generation.connect_groups(g, room3, room1, "erdos_renyi", avg_deg=20)

nngt.generation.connect_groups(g, room4, room3, "erdos_renyi", avg_deg=10)

More realistic neuronal groups#

When designing neuronal networks, one usually cares about their type (excitatory or inhibitory for instance), their properties, etc.

By default, neural groups are created excitatory and the following lines are therefore equivalent:

exc   = NeuralGroup(800, neuron_type=1)   # excitatory group
exc2  = NeuralGroup(800, neuron_type=1)   # also excitatory

To create an inhibitory group, the neural type must be set to -1:

inhib = NeuralGroup(200, neuron_type=-1)  # inhibitory group

Moving towards really realistic groups to run simulation on NEST afterwards, the last step is to associate a neuronal model and set the properties of these neurons (and optionally give them names):

pyr = NeuralGroup(800, neuron_type=1, neuron_model="iaf_psc_alpha",
                  neuron_param={"tau_m": 50.}, name="pyramidal_cells")

fsi = NeuralGroup(200, neuron_type=-1, neuron_model="iaf_psc_alpha",
                  neuron_param={"tau_m": 20.},
                  name="fast_spiking_interneurons")

Populations#

Populations are ensembles of neuronal groups which describe all neurons in a corresponding network. They are usually created before the network and then used to generate connections, but the can also be generated after the network creation, then associated to it.

Simple populations#

To create a population, you can start from scratch by creating an empty population, then adding groups to it:

# making populations from scratch
pop = nngt.NeuralPop(with_models=False)              # empty population
pop.create_group(200, "first_group")                 # create excitatory group
pop.create_group(5, "second_group", neuron_type=-1)  # create inhibitory group

NNGT also provides a two default routine to create simple populations:

  • uniform(), to generate a single population where all neurons belong to the same group,

  • exc_and_inhib(), to generate a mixed excitatory and inhibitory population.

As before, we do from nngt import NeuralPop to avoid typing nngt. all the time.

To create such populations, just use:

# the two default populations
unif_pop = NeuralPop.uniform(1000)                     # only excitatory
ei_pop   = NeuralPop.exc_and_inhib(1000, iratio=0.25)  # 25% inhibitory

Eventually, a population can be created from exiting groups using from_groups():

ei_pop2 = NeuralPop.from_groups([exc, exc2, inhib], ["e1", "e2", "i"],
                                with_models=False)

Note that, here, we pass with_models=False to the population because these groups were created without the information necessary to create a network in NEST (a valid neuron model).

NEST-enabled populations#

To create a NEST-enabled population, one can use one of the standard classmethods (uniform() and exc_and_inhib()) and pass it valid parameters for the neuronal models (optionally also a synaptic model and neuronal/synaptic parameters).

Otherwise, one can build the population from groups that already contain these properties, e.g. the previous pyr and fsi groups:

# optional synaptic properties
syn_spec = {
    'default': {"synaptic_model": "tsodyks2_synapse"},  # default connections
    ("pyramidal_cells", "pyramidal_cells"): {"U": 0.6}  # change a parameter
}

nest_pop = NeuralPop.from_groups([pyr, fsi], syn_spec=syn_spec)

Warning

syn_spec can contain any synaptic model and parameters associated to the NEST model; however, neither the synaptic weight nor the synaptic delay can be set there. For details on how to set synaptic weight and delays between groups, see connect_groups().

To see how to use a population to create a Network and send it to NEST, see Use with NEST.

Complex populations and metagroups#

When building complex neuronal networks, it may be useful to have neurons belong to multiple groups at the same time. Because standard groups can contain a neuron only once, meta-groups were introduced to provide this additional functionality.

Contrary to normal groups, a neuron can belong to any number of metagroups, which allow to make various sub- or super-groups. For instance, when modeling a part of cortex, neurons will belong to a layer, and to a given cell class whithin that layer. In that case, you may want to create specific groups for cell classes, like L3Py, L5Py, L3I, L5I for layer 4 and 5 pyramidal cells as well as interneurons, but you can then also group neurons in a same layer together, and same with pyramidal neurons or interneurons.

First create the normal groups:

nmod = "iaf_psc_exp"

idsL2gc = range(100)
idsL3py, idsL3i = range(100, 200), range(200, 300)
idsL4gc = range(300, 400)
idsL5py, idsL5i = range(400, 500), range(500, 600)
idsL6 = range(600, 700)

L2GC = NeuralGroup(idsL2gc, neuron_model=nmod, name="L2GC", neuron_type=1)
L3Py = NeuralGroup(idsL3py, neuron_model=nmod, name="L3Py", neuron_type=1)
L3I  = NeuralGroup(idsL3i,  neuron_model=nmod, name="L3I",  neuron_type=-1)
L4GC = NeuralGroup(idsL4gc, neuron_model=nmod, name="L4GC", neuron_type=1)
L5Py = NeuralGroup(idsL5py, neuron_model=nmod, name="L5Py", neuron_type=1)
L5I  = NeuralGroup(idsL5i,  neuron_model=nmod, name="L5I",  neuron_type=-1)
L6c  = NeuralGroup(idsL6,   neuron_model=nmod, name="L6c",  neuron_type=1)

Then make the metagroups for the layers:

L2 = MetaGroup(idsL2gc, name="L2")
L3 = MetaNeuralGroup(L3Py.ids + L3I.ids, name="L3")
L4 = MetaGroup(idsL4gc, name="L4")
L5 = MetaNeuralGroup(L5Py.ids + L5I.ids, name="L5")
L6 = MetaGroup(idsL6, name="L6")

Note that I used MetaNeuralGroup for layers 3 and 5 because it enables to differenciate inhibitory and excitatory neurons using inhibitory and excitatory. Otherwise normal MetaGroup are equivalent and sufficient.

Create the population:

pop_column = NeuralPop.from_groups(
    [L2GC, L3Py, L3I, L4GC, L5Py, L5I, L6c], meta_groups=[L2, L3, L4, L5, L6])

Then add additional metagroups for cell types:

pyr = MetaGroup(L3Py.ids + L5Py.ids, name="pyramidal")
pop_column.add_meta_group(pyr)  # add from existing meta-group

pop_column.create_meta_group(L3I.ids + L5I.ids, "interneurons")  # single line

pop_column.create_meta_group(L2GC.ids + L4GC.ids, "granule")

Go to other tutorials: