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/

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:

room2 = nngt.Group(50)

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)

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

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.

                               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)

if nngt.get_config("with_plot"):
    # chord diagram
    sg = g.get_structure_graph()

    nngt.plot.chord_diagram(sg, names="name", sort="distance",
                            use_gradient=True, show=True)

    # spring-block layout    
    nngt.plot.library_draw(g, node_cmap="viridis", show=True)

''' ------------------- #
# More group properties #
# ------------------- '''

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:

                  neuron_param={"tau_m": 50.}, name="pyramidal_cells")

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

fsi = NeuralGroup(200, neuron_type=-1, neuron_model="iaf_psc_alpha",

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):

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

print("E/I population has size", pop.size, "and contains",
      len(pop), "groups:", pop.keys(), "\n")


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:

print("E/I population has size", ei_pop.size, "and contains",
      len(ei_pop), "groups:", ei_pop.keys(), "\n")

# A population can also be created from existing groups.

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:

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

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

# add synaptic properties to the connections that will be made.
# (because the group already have names, we don't need to specify them again)

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:

# - pyramidal cells and interneurons in layers 3 and 5
# - indiscriminate cells in layer 6

nmod = "iaf_psc_exp"

idsL2gc = range(100)
idsL3py, idsL3i = range(100, 200), range(200, 300)


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:

# We can also group them by layers using metagroups (for L2/L3/L6 it is not
# really useful but it gives a coherent notation)
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")

# Then we create the population from the groups
pop_column = NeuralPop.from_groups(
    [L2GC, L3Py, L3I, L4GC, L5Py, L5I, L6c], meta_groups=[L2, L3, L4, L5, L6])

# We can also add additional meta-groups for pyramidal, granule, and
# interneurons

Then make the metagroups for the layers:

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

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

print("Column has meta-groups:", pop_column.meta_groups.keys(), "\n")

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:

# excitatory and inhibitory neurons
print("Excitatory L3 neurons:", L3.excitatory, "\n")

Then add additional metagroups for cell types:

Go to other tutorials: