Graph generation

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 (MAKE A REF). 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 weight_prop 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 Connections class to set the weights of a graph explicitely by using:

>>> nngt.Connections.weights(graph, elist=edges_to_weigh, distrib="distrib_of_choice", ...)

Examples

import nngt
import nngt.generation as ng

Generating simple graphs:

# random graphs
g1 = ng.erdos_renyi(1000, avg_deg=25)
g2 = ng.erdos_renyi(1000, avg_deg=25, directed=False) # the same graph but undirected
# scale-free with Gaussian weight distribution
g3 = nngt.Graph(1000, weight_prop={"distrib":"gaussian", "distrib_prop":{"avg": 60., "std":5.}})
ng.random_scale_free(2.2, 2.9, from_graph=g3)

Generating a network with excitatory and inhibitory neurons:

# 800 excitatory neurons, 200 inhibitory
net = nngt.Network.ei_network(1000, ei_ratio=0.2)
# connect the populations
ng.connect_neural_types(net, 1, -1, "erdos_renyi", {"density": 0.035}) # exc -> inhib
ng.connect_neural_types(net, 1, 1, "newman_watts", {"coord_nb":10, "proba_shortcut": 0.1}) # exc -> exc
ng.connect_neural_types(net, -1, 1, "random_scale_free", {"in_exp": 2.1, "out_exp": 2.6, "density": 0.2}) # inhib -> exc
ng.connect_neural_types(net, -1, -1, "erdos_renyi", {"density": 0.04}) # inhib -> inhib