Graph generation#

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

Content:

Principle#

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.

Modularity#

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.

Examples#

import nngt
import nngt.generation as ng

Simple generation#

num_nodes  = 1000
avg_deg_er = 25
avg_deg_sf = 100

# random graphs
g1 = ng.erdos_renyi(nodes=num_nodes, avg_deg=avg_deg_er)

# the same graph but undirected
g2 = ng.erdos_renyi(nodes=num_nodes, avg_deg=avg_deg_er, directed=False)

# 2-step generation of a scale-free with Gaussian weight distribution
w = {
    "distribution": "gaussian",
    "avg": 60.,
    "std": 5.
}

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)

Networks composed of heterogeneous groups#

'''
Make the population
'''

# two groups
g1 = nngt.Group(500)  # nodes 0 to 499
g2 = nngt.Group(500)  # nodes 500 to 999

# make structure
struct = nngt.Structure.from_groups((g1, g2), ("left", "right"))

# create network from this population
net = nngt.Graph(structure=struct)


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

Use with NEST#

Generating a network with excitatory and inhibitory neurons:

'''
Build a network with two populations:
* excitatory (80%)
* inhibitory (20%)
'''
num_nodes = 1000

# 800 excitatory neurons, 200 inhibitory
net = nngt.Network.exc_and_inhib(num_nodes, ei_ratio=0.2)

'''
Connect the populations.
'''
# exc -> inhib (Erdos-Renyi)
ng.connect_neural_types(net, 1, -1, "erdos_renyi", density=0.035)

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

Send the network to NEST:

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

    '''
    Prepare the network and devices.
    '''
    # send to NEST
    gids = net.to_nest()
    # excite
    ns.set_poisson_input(gids, rate=100000.)
    # record
    groups = [key for key in net.population]
    recorder, record = ns.monitor_groups(groups, net)

    '''
    Simulate and plot.
    '''
    simtime = 100.
    nest.Simulate(simtime)

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

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


    # 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
    try:
        # nest 2
        iconn  = nest.GetConnections(
            source=list(net.population["inhibitory"].nest_gids),

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.

'''
Build a network with two populations:
* excitatory (80%)
* inhibitory (20%)
'''
num_neurons = 50   # number of neurons
avg_degree  = 20   # average number of neighbours
std_degree  = 3    # deviation for the Gaussian graph

# parameters
neuron_model = "ht_neuron"      # hill-tononi model
exc_syn = {'receptor_type': 1}  # 1 is 'AMPA' in this model
inh_syn = {'receptor_type': 3}  # 3 is 'GABA_A' in this model

synapses = {
    (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,
    syn_spec=synapses)

# 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

    nest.ResetKernel()

    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.
    nest.Simulate(simtime)

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

Go to other tutorials: