# Overview of spatially-structured networks¶

All topology functions are now part of

`nest`

and not`nest.topology`

You can use the

`Create`

and`Connect`

functions for spatial networks, the same as you would for non-spatial networksAll former topology functions that used to take a layer ID, now take a NodeCollection

All former topology functions that used to return node/layer IDs now return a NodeCollection

Note

See the reference section Functions related to spatially distributed nodes in our conversion guide for all changes made to functions

## Create spatially distributed nodes¶

Spatially distributed nodes can now be created using the standard `nest.Create()`

function.
The arguments of this function have been changed to unify the creation of populations with and without spatial
information. To create nodes with spatial positions, `nest.Create()`

must be provided with the
`positions`

argument

```
spatial_nodes = nest.Create(model, positions=spatial_data)
```

where `spatial_data`

can be one of the following

`nest.spatial.grid()`

This creates nodes on a grid, with a prescribed number of rows and columns, and, if specified, an extent and center. It can be easier to think of the grid as being defined by number of elements in x-direction and y-direction instead of thinking of rows and columns. Some example grid spatial nodes specifications:

nest.spatial.grid(shape=[5, 4], extent=[2., 3.]) # 5x4 grid in a 2x3 square nest.spatial.grid(shape=[4, 5], center=[1., 1.]) # 4x5 grid in the default 1x1 square, with shifted center nest.spatial.grid(shape=[4, 5], edge_wrap=True) # 4x5 grid with periodic boundary conditions nest.spatial.grid(shape=[2, 3, 4]) # 3D 2x3x4 grid

`nest.spatial.free()`

This creates nodes positioned freely in space. The first argument to

`nest.spatial.free()`

can either be a NEST parameter that generates the positions, or an explicit list of positions. Some example free spatial nodes specifications:nest.spatial.free([[5., 1.], [4., 2.], [3., 3.]]) # Three nodes with explicit positions nest.spatial.free(nest.random.lognormal(), # Positions generated from a lognormal distribution num_dimensions=2) # in 2D nest.spatial.free(nest.random.uniform(), # Positions generated from a uniform distribution num_dimensions=3, # in 3D edge_wrap=True) # with periodic boundary conditions

Note the following

For positions generated from NEST parameters, the number of neurons has to be provided in

`nest.Create()`

with the`n`

argument.The extent is calculated from the positions of the nodes, but can be set explicitly.

If possible, NEST tries to deduce the number of dimensions. But if the positions are generated from NEST parameters, and there is no extent defined, the number of dimensions has to be provided.

spatial_nodes = nest.Create('iaf_psc_alpha', n=5, positions=nest.spatial.free(nest.random.uniform(), num_dimensions=3))

Spatially positioned nodes are no longer subnets, as subnets have been removed, but are rather NodeCollections with metadata. These NodeCollections behave as normal NodeCollections with two exceptions:

They cannot be merged, as concatenating NodeCollections with metadata is not supported.

When setting the status of nodes and connecting spatially distributed NodeCollections you can use spatial information as parameters.

The second point means that we can use masks and position dependent parameters when connecting, and it is possible to set parameters of nodes based on their positions. We can for example set the membrane potential to a value based on the nodes’ position on the x-axis:

```
snodes = nest.Create('iaf_psc_alpha', 10
positions=nest.spatial.free(
nest.random.uniform(min=-10., max=10.), num_dimensions=2))
snodes.V_m=-60. + nest.spatial.pos.x
```

- Composite layers:
It is no longer possible to create composite layers, i.e. layers with multiple nodes in each position. To reproduce this, we now create multiple spatially distributed NodeCollections.

NEST 2.x

NEST 3.0

l = tp.CreateLayer( {'rows': 1, 'columns': 2, 'elements': ['iaf_cond_alpha', 'poisson_generator']}) Use l when connecting, setting parameters etc.

sn_iaf = nest.Create('iaf_psc_alpha' positions=nest.spatial.grid( shape=[2, 1])) sn_poi = nest.Create('poisson_generator', positions=nest.spatial.grid( shape=[3, 1])) Use sn_iaf and sn_poi when connecting, setting parameters etc.

## Retrieving spatial information¶

To retrieve the spatial information from your nodes, spatially structured NodeCollections have
a `.spatial`

parameter that will retrieve all spatial information as a dictionary.

```
>>> spatial_nodes.spatial
{'center': (0.41717460937798023, 0.3541409997269511, 0.5058779059909284),
'edge_wrap': False,
'extent': (0.6786768797785043, 0.4196595948189497, 0.8852582329884171),
'network_size': 5,
'positions': ((0.1951471883803606, 0.24431120231747627, 0.5770208276808262),
(0.34431440755724907, 0.46397079713642597, 0.8201442817226052),
(0.17783616948872805, 0.4038907829672098, 0.16324878949671984),
(0.3796140942722559, 0.2643292499706149, 0.848507022485137),
(0.6565130492672324, 0.38219101540744305, 0.4020354822278023))}
```

Note that if you have specified your positions as a NEST parameter, NEST will convert that
to a list with lists, and this is what you will get when calling `.spatial`

.

## Connect spatially distributed nodes¶

Similar to creating nodes with spatial distributions, such nodes are now connected using the
standard `nest.Connect()`

function. Connecting NodeCollections with
spatial data is no different from connecting NodeCollections without
metadata. In a layer-connection context, moving to the standard
`Connect()`

function brings with it some notable changes:

Convergent and divergent specification of connection is removed, or rather renamed. See table below.

NEST 2.x

NEST 3.0

`convergent`

`pairwise_bernoulli`

with`use_on_source=True`

`convergent`

with`num_connections`

`fixed_indegree`

`divergent`

`pairwise_bernoulli`

`divergent`

with`num_connections`

`fixed_outdegree`

`use_on_source`

here refers to whether the mask and connection probability should be applied to the source neuron or the target neuron. This is only required for`pairwise_bernoulli`

, as`fixed_indegree`

and`fixed_outdegree`

implicitly only apply to either the source or target nodes.The connection probability specification

`kernel`

is renamed to`p`

to fit with`pairwise_bernoulli`

, and is only possible for the connection rules in the table above.Using a

`mask`

is only possible with the connection rules in the table above.

## Usage examples¶

A grid layer connected with Gaussian distance dependent connection probability and rectangular mask on the target layer:

NEST 2.x

NEST 3.0

l = tp.CreateLayer( {'columns': nc, 'rows': nr, 'elements': 'iaf_psc_alpha', 'extent': [2., 2.]}) conn_dict = {'connection_type': 'divergent', 'kernel': {'gaussian': {'p_center': 1., 'sigma': 1.}}, 'mask': {'rectangular': {'lower_left': [-0.5, -0.5], 'upper_right': [0.5, 0.5]}}} nest.ConnectLayers(l, l, conn_dict) l = nest.Create('iaf_psc_alpha', positions=nest.spatial.grid( shape=[nc, nr], extent=[2., 2.])) conn_dict = {'rule': 'pairwise_bernoulli', 'p': nest.spatial_distributions.gaussian( nest.spatial.distance, std=1.), 'mask': {'rectangular': {'lower_left': [-0.5, -0.5], 'upper_right': [0.5, 0.5]}}} nest.Connect(l, l, conn_dict)

A free layer with uniformly distributed positions, connected with fixed number of outgoing connections, linear distance dependent connection probability and delay, and random weights from a normal distribution:

NEST 2.x

NEST 3.0

import numpy as np pos = [[np.random.uniform(-1., 1.), np.random.uniform(-1., 1.)] for j in range(1000)] l = tp.CreateLayer({'positions': pos, 'extent': [2., 2.], 'elements': 'iaf_psc_alpha'}) conn_dict = {'connection_type': 'divergent', 'number_of_connections': 50, 'kernel': {'linear': {'a': -0.5, 'c': 1.}}, 'weights': {'normal': {'mean': 0.0, 'sigma': 1.0}}, 'delays': {'linear': {'a': 1.5, 'c': 0.}}, 'allow_multapses': True, 'allow_autapses': False} tp.ConnectLayers(l, l, conn_dict) pos = nest.spatial.free(nest.random.uniform(-1., 1.), num_dimensions=2) l = nest.Create('iaf_psc_alpha', 1000, positions=pos) conn_dict = {'rule': 'fixed_outdegree', 'outdegree': 50, 'p': 1. - 0.5*nest.spatial.distance, 'weight': nest.random.normal(mean=0., std=1.), 'delay': 1.5*nest.spatial.distance, 'allow_multapses': True, 'allow_autapses': False} nest.Connect(l, l, conn_dict)

## Masks¶

In NEST 3.0, the mask `volume`

got removed, as the same mask was already available under the name `box`

.
The former was only an alias available for backward compatibility.

## Retrieving distance information¶

If you have a SynapseCollection with connections from a spatially distributed network, you can retrieve the
*distance* between the source-target pairs by calling `.distance`

on the SynapseCollection.

s_nodes = nest.Create('iaf_psc_alpha', positions=nest.spatial.grid(shape=[3, 1])) t_nodes = nest.Create('iaf_psc_alpha', positions=nest.spatial.grid(shape=[1, 3])) nest.Connect(s_nodes, t_nodes) conns = nest.GetConnections() dist = conns.distance

`.distance`

will be a tuple of the same length as your SynapseCollection, where `dist[indx]`

will be the distance
between the source-target pair at *indx*.

Calling `.distance`

on a SynapseCollection where either the source or target, or both, are not spatially
distributed also works, and you will receive nan whenever one of the nodes is non-spatial.