Graph generation#

This page gives example on how to generate increasingly complex network structures. The example files can be found at: docs/examples/, docs/examples/, docs/examples/, and docs/examples/


In order to keep the code as generic and easy to maintain as possible, the generation of graphs or networks is divided in several steps:

  • Structured connectivity: a simple graph is generated as an assembly of nodes and edges, without any biological properties. This allows us to implement known graph-theoretical algorithms in a straightforward fashion.

  • Populations: detailed properties can be implemented, such as inhibitory synapses and separation of the neurons into inhibitory and excitatory populations – these can be done while respecting user-defined constraints.

  • Synaptic properties: eventually, synaptic properties such as weight/strength and delays can be added to the network.


The library as been designed so that these various operations can be realized in any order!

Juste to get work on a topological graph/network:
  1. Create graph class

  2. Connect

  3. Set connection weights (optional)

  4. Spatialize (optional)

  5. Set types (optional: to use with NEST)

To work on a really spatially embedded graph/network:
  1. Create spatial graph/network

  2. Connect (can depend on positions)

  3. Set connection weights (optional, can depend on positions)

  4. Set types (optional)

Or to model a complex neural network in NEST:
  1. Create spatial network (with space and neuron types)

  2. Connect (can depend on types and positions)

  3. Set connection weights and types (optional, can depend on types and positions)

Setting weights#

The weights can be either user-defined or generated by one of the available distributions (Attributes and distributions). User-defined weights are generated via:

  • a list of edges

  • a list of weights

Pre-defined distributions require the following variables:

  • a distribution name (“constant”, “gaussian”…)

  • a dictionary containing the distribution properties

  • an optional attribute for distributions that are correlated to another (e.g. the distances between neurons)

  • a optional value defining the variance of the Gaussian noise that should be applied on the weights

There are several ways of settings the weights of a graph which depend on the time at which you assign them.

At graph creation

You can define the weights by entering a weights argument to the constructor; this should be a dictionary containing at least the name of the weight distribution: {"distrib": "distribution_name"}. If entered, this will be stored as a graph property and used to assign the weights whenever new edges are created unless you specifically assign rules for those new edges’ weights.

At any given time

You can use the set_weights() function to set the weights of a graph explicitely by using:

graph.set_weights(elist=edges_to_weigh, distrib="distrib_of_choice", ...)

For more details on weights, other attributes, and available distributions, see Properties of graph components.


import nngt
import nngt.generation as ng

Simple generation#


g3 = nngt.Graph(num_nodes, weights=w)
ng.random_scale_free(2.2, 2.9, avg_deg=avg_deg_sf, from_graph=g3)

# same in 1 step
g4 = ng.random_scale_free(
    2.2, 2.9, avg_deg=avg_deg_sf, nodes=num_nodes, weights=w)

# ----------------- #
# Check the results #
# ----------------- #

assert np.isclose(avg_deg_er, np.average(g1.get_degrees('in')), 1e-4)
assert np.isclose(avg_deg_sf, np.average(g3.get_degrees('in')), 1e-4)
assert np.isclose(avg_deg_sf, np.average(g4.get_degrees('in')), 1e-4)

    "Erdos-Renyi: requested average degree of {}; got {} for directed graph "
    "and {} for undirected one.".format(
        avg_deg_er, g1.edge_nb() / float(num_nodes),
        g2.edge_nb() / float(num_nodes))

Networks composed of heterogeneous groups#

Connect the groups

# inter-groups (Erdos-Renyi)
prop_er1 = {"density": 0.005}
ng.connect_groups(net, "left", "right", "erdos_renyi", **prop_er1)

# intra-groups (Newman-Watts)
prop_nw = {
    "coord_nb": 20,
    "proba_shortcut": 0.1,
    "reciprocity_circular": 1.

ng.connect_groups(net, "left", "left", "newman_watts", **prop_nw)
ng.connect_groups(net, "right", "right", "newman_watts", **prop_nw)

Plot the graph

if nngt.get_config("with_plot"):
    nngt.plot.library_draw(net, show=False)

    pop_graph = net.get_structure_graph()

    nngt.plot.chord_diagram(pop_graph, names="name", use_gradient=True,

Use with NEST#

Generating a network with excitatory and inhibitory neurons:

# exc -> exc (Newmann-Watts)
prop_nw = {
    "coord_nb": 10,
    "proba_shortcut": 0.1,
    "reciprocity_circular": 1.
ng.connect_neural_types(net, 1, 1, "newman_watts", **prop_nw)

# inhib -> exc (Random scale-free)
prop_rsf = {
    "in_exp": 2.1,
    "out_exp": 2.6,
    "density": 0.2
ng.connect_neural_types(net, -1, 1, "random_scale_free", **prop_rsf)

# inhib -> inhib (Erdos-Renyi)
ng.connect_neural_types(net, -1, -1, "erdos_renyi", density=0.04)

# ------------------ #
# Simulate with NEST #
# ------------------ #

if nngt.get_config('with_nest'):
    import nngt.simulation as ns
    import nest

    Prepare the network and devices.
    # send to NEST

Send the network to NEST:

    Simulate and plot.
    simtime = 100.

    if nngt.get_config('with_plot'):
            recorder, record, network=net, show=True, limits=(0,simtime))

    A reminder about weights of inhibitory connections

    # sign of NNGT versus NEST inhibitory connections
    igroup = net.population["inhibitory"]

    # in NNGT
    iedges = net.get_edges(source_node=igroup.ids)
    w_nngt = set(net.get_weights(edges=iedges))

    # in NEST
        # nest 2

You can check that connections from neurons that are marked as inhibitory are automatically assigned a negative sign in NEST:

        # nest 3
        import nest
        s = nest.NodeCollection(net.population["inhibitory"].nest_gids)
        t = nest.NodeCollection(net.population.nest_gids)

        iconn  = nest.GetConnections(source=s, target=t)

    w_nest = set(nest.GetStatus(iconn, "weight"))

    # In NNGT, inhibitory weights are positive to work with graph analysis
    # methods; they are automatically converted to negative weights in NEST
    print("NNGT weights:", w_nngt, "versus NEST weights", w_nest)

Returns: NNGT weights: {1.0} versus NEST weights {-1.0}.

Advanced examples#

Receptor ports in NEST#

Some models, such as multisynaptic neurons, or advanced models incorporating various neurotransmitters use an additional information, called "port" to identify the synapse that will be used by the nest.Connect method. These models can also be used with NNGT by telling the NeuralGroup which type of port the neuron should try to bind to.

NB: the port is specified in the source neuron and declares which synapse of the target neuron is concerned.

    (1, 1):   exc_syn,
    (1, -1):  exc_syn,
    (-1, 1):  inh_syn,
    (-1, -1): inh_syn,

pop = nngt.NeuralPop.exc_and_inhib(
    num_neurons, en_model=neuron_model, in_model=neuron_model,

# create the network and send it to NEST
w_prop = {"distribution": "gaussian", "avg": 0.2, "std": .05}
net = nngt.generation.gaussian_degree(
    avg_degree, std_degree, population=pop, weights=w_prop)

Send to NEST and set excitation and recorders
if nngt.get_config('with_nest'):
    import nest
    import nngt.simulation as ns


    gids = net.to_nest()

    # add noise to the excitatory neurons
    excs = list(pop["excitatory"].nest_gids)
    inhs = list(pop["inhibitory"].nest_gids)
    ns.set_noise(excs, 10., 2.)
    ns.set_noise(inhs, 5., 1.)

    # record
    groups = [key for key in net.population]
    recorder, record = ns.monitor_groups(groups, net)

    Simulate and plot.
    simtime = 2000.

    if nngt.get_config('with_plot'):
            recorder, record, network=net, show=True, histogram=False,
            limits=(0, simtime))

Go to other tutorials: