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

## 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:
Create graph class

Connect

Set connection weights (optional)

Spatialize (optional)

Set types (optional: to use with NEST)

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

Connect (can depend on positions)

Set connection weights (optional, can depend on positions)

Set types (optional)

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

Connect (can depend on types and positions)

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#

```
}
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)
print(
"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,
show=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.
nest.Simulate(simtime)
if nngt.get_config('with_plot'):
ns.plot_activity(
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
try:
# 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,
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:**