.. _spatial_networks: Spatially-structured networks ============================= NEST provides a convenient interface for creating neurons placed in space and connecting those neurons with probabilities and properties depending on the relative placement of neurons. This permits the creation of complex networks with spatial structure. This user manual provides an introduction to the functionalities provided for defining spatial networks in NEST. It is based exclusively on the PyNEST, the Python interface to NEST. NEST users using the SLI interface should be able to map instructions to corresponding SLI code. This manual is not meant as a comprehensive reference manual. Please consult the online documentation in PyNEST for details; where appropriate, that documentation also points to relevant SLI documentation. This manual describes the spatial functionalities included with NEST 3.0. In the next sections of this manual, we introduce the commands and concepts required to work with spatially distributed nodes. In particular, * section :ref:`sec_connections` describes how to connect spatial nodes with each other * section :ref:`sec_inspection` explains how you can inspect and visualize spatial networks. * section :ref:`sec_custom_masks` deals with creating connection boundaries using parameters, and the more advanced topic of extending the functionalities with custom masks provided by C++ classes in an extension module. The Python scripts used throughout this manual are found in the NEST sources under ``doc/htmldoc/networks/scripts``. There may be a number of undocumented features, which you may discover by browsing the code. These features are highly experimental and should *not be used for simulations*, as they have not been validated. Spatially distributed nodes --------------------------- Neuronal networks can have an organized spatial distribution, which we call *layers*. Layers are NodeCollections with spatial metadata. We will first illustrate how to place elements in simple grid-like layers, where each element is a single model neuron, before describing how the elements can be placed freely in space. We will illustrate the definition and use of spatially distributed NodeCollections using examples. NEST distinguishes between two classes of layers: grid-based layers in which each element is placed at a location in a regular grid; free layers in which elements can be placed arbitrarily in the plane. Grid-based layers allow for more efficient connection-generation under certain circumstances. Grid-based NodeCollections ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. _sec_spatial_verysimple: A very simple example ^^^^^^^^^^^^^^^^^^^^^ We create a first, grid-based simple NodeCollection with the following command: .. literalinclude:: scripts/layers.py :start-after: # { layer1 #} :end-before: # { end #} .. _fig_layer1: .. figure:: figures/layer1.png :name: fig:layer1 Simple grid-based NodeCollection centered about the origin. Blue circles mark the elements, the thin square the extent of the layer. Row and column indices are shown in the right and top margins, respectively. The layer is shown in :numref:`fig_layer1`. Note the following properties: - We are using the standard :py:func:`.Create` function, but in addition to model type, we are also passing a ``nest.spatial.grid`` object as the ``positions`` argument. - The layer has five *rows* and five *columns*, as given by ``shape=[columns, rows]``. Note that a more intuitive way of thinking of *shape* is to think of it as given by ``shape=[num_x, num_y]``. - The *center* of the NodeCollection is at the origin of the coordinate system, :math:`(0,0)`. - The *extent* or size of the layer is :math:`1\times 1`. This is the default size for grid-based layers. The extent is marked by the thin square in :numref:`fig_layer1`. - The *grid spacing* of the layer is .. math:: :label: dx_dy_extent \begin{split} dx &= \frac{\text{x-extent}}{\text{number of columns}} \\ dy &= \frac{\text{y-extent}}{\text{number of rows}} \end{split} In the layer shown, we have :math:`dx=dy=0.2`, but the grid spacing may differ in x- and y-direction. - Layer elements are spaced by the grid spacing and are arranged symmetrically about the center. - The outermost elements are placed :math:`dx/2` and :math:`dy/2` from the borders of the extent. - Element *positions* in the coordinate system are given by :math:`(x,y)` pairs. The *coordinate system* follows that standard mathematical convention that the :math:`x`-axis runs from left to right and the :math:`y`-axis from bottom to top. - Each element of a grid-based NodeCollection has a *row- and column-index* in addition to its :math:`(x,y)`-coordinates. Indices are shown in the top and right margin of :numref:`fig_layer1`. Note that row-indices follow matrix convention, i.e., run from top to bottom. Following pythonic conventions, indices run from 0. .. _sec_setextent: Setting the extent ^^^^^^^^^^^^^^^^^^ Grid-based layers have a default extent of :math:`1\times 1`. You can specify a different extent of a layer, i.e., its size in :math:`x`- and :math:`y`-direction by passing the ``extent`` argument to ``nest.spatial.grid()``: .. literalinclude:: scripts/layers.py :start-after: # { layer2 #} :end-before: # { end #} .. _fig_layer2: .. figure:: figures/layer2.png :name: fig:layer2 Same layer as in :numref:`fig_layer1`, but with different extent. The resulting NodeCollection is shown in :numref:`fig_layer2`. The extent is always a two-element list of floats. In this example, we have grid spacings :math:`dx=0.4` and :math:`dy=0.1`. Changing the extent does not affect grid indices. The size of ``extent`` in :math:`x`- and :math:`y`-directions should be numbers that can be expressed exactly as binary fractions. This is automatically ensured for integer values. Otherwise, under rare circumstances, subtle rounding errors may occur and trigger an assertion, thus stopping NEST. Setting the center ^^^^^^^^^^^^^^^^^^ Layers are centered about the origin :math:`(0,0)` by default. This can be changed by passing the ``center`` argument to ``nest.spatial.grid()``. The following code creates layers centered about :math:`(0,0)`, :math:`(-1,1)`, and :math:`(1.5,0.5)`, respectively: .. literalinclude:: scripts/layers.py :start-after: # { layer3 #} :end-before: # { end #} .. _fig_layer3: .. figure:: figures/layer3.png :name: fig:layer3 Three layers centered, respectively, about :math:`(0,0)` (blue), :math:`(-1,-1)` (green), and :math:`(1.5,0.5)` (red). The center is given as a two-element list of floats. Changing the center does not affect grid indices: For each of the three layers in :numref:`fig_layer3`, grid indices run from 0 to 4 through columns and rows, respectively, even though elements in these three layers have different positions in the global coordinate system. The ``center`` coordinates should be numbers that can be expressed exactly as binary fractions. For more information, see the section :ref:`sec_setextent`. Constructing a layer: an example ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To see how to construct a layer, consider the following example: - a layer with :math:`n_y` rows and :math:`n_x` columns; - spacing between nodes is :math:`d` in :math:`x`- and :math:`y`-directions; - the left edge of the extent shall be at :math:`x=0`; - the extent shall be centered about :math:`y=0`. From Eq. :eq:`dx_dy_extent`, we see that the extent of the NodeCollection must be :math:`(n_x d, n_y d)`. We now need to find the coordinates :math:`(c_x, c_y)` of the center of the layer. To place the left edge of the extent at :math:`x=0`, we must place the center of the layer at :math:`c_x=n_x d / 2` along the :math:`x`-axis, i.e., half the extent width to the right of :math:`x=0`. Since the layer is to be centered about :math:`y=0`, we have :math:`c_y=0`. Thus, the center coordinates are :math:`(n_x d/2, 0)`. The layer is created with the following code and shown in :numref:`fig_layer3a`: .. literalinclude:: scripts/layers.py :start-after: # { layer3a #} :end-before: # { end #} .. _fig_layer3a: .. figure:: figures/layer3a.png :name: fig:layer3a NodeCollection with :math:`n_x=5` columns and :math:`n_y=3` rows, spacing :math:`d=0.1` and the left edge of the extent at :math:`x=0`, centered about the :math:`y`-axis. The cross marks the point on the extent placed at the origin :math:`(0,0)`, the circle the center of the layer. Free layers ~~~~~~~~~~~ *Free layers* do not restrict node positions to a grid, but allow free placement within the extent. To this end, the user can specify the positions of all nodes explicitly, or pass a random distribution parameter to ``nest.spatial.free()``. The following code creates a NodeCollection of 50 ``iaf_psc_alpha`` neurons uniformly distributed in a layer with extent :math:`1\times 1`, i.e., spanning the square :math:`[-0.5,0.5]\times[-0.5,0.5]`: .. literalinclude:: scripts/layers.py :start-after: # { layer4 #} :end-before: # { end #} .. _fig_layer4: .. figure:: figures/layer4.png :name: fig:layer4 A free layer with 50 elements uniformly distributed in an extent of size :math:`1\times 1`. Note the following points: - For free layers, element *positions* are specified by the ``nest.spatial.free`` object. - The ``pos`` entry must either be a Python ``list`` (or ``tuple``) of element coordinates, i.e., of two-element tuples of floats giving the (:math:`x`, :math:`y`)-coordinates of the elements, or a ``Parameter`` object. - When using a parameter object for the positions, the number of dimensions have to be specified by the ``num_dimensions`` variable. num_dimensions can either be 2 or 3. - When using a parameter object you also need to specify how many elements you want to create by specifying ``'n'`` in the :py:func:`.Create` call. This is **not** the case when you pass a list to the ``nest.spatial.free`` object. - The extent is automatically set when using ``nest.spatial.free``, however, it is still possible to set the extent yourself by passing the ``extent`` variable to the object. - All element positions must be *within* the layer's extent. Elements may be placed on the perimeter of the extent as long as no periodic boundary conditions are used; see the section :ref:`sec_periodic`. To create a spatially distributed NodeCollection from a list, do the following: .. literalinclude:: scripts/layers.py :start-after: # { layer4b #} :end-before: # { end #} .. _fig_layer4b: .. figure:: figures/layer4b.png :name: fig:layer4b A free layer with 3 elements freely distributed space. The extent is given by the gray lines. Note that when using a list to specify the positions, neither ``'n'`` nor ``num_dimenstions`` are specified. Furthermore, the extent is calculated from the node positions, and is here :math:`1.45\times 1.45`. The extent could also be set by passing the ``extent`` argument. 3D layers ~~~~~~~~~ Although the term "layer" suggests a 2-dimensional structure, the layers in NEST may in fact be 3-dimensional. The example from the previous section may be easily extended by updating number of dimensions for the positions: .. literalinclude:: scripts/layers.py :start-after: # { layer4_3d #} :end-before: # { end #} .. _fig_layer4_3d: .. figure:: figures/layer4_3d.png :name: fig:layer4_3d A free 3D layer with 200 elements uniformly distributed in an extent of size :math:`1\times 1\times 1`. Again it is also possible to specify a list of list to create nodes in a 3-dimensional space. Another possibility is to create a 3D grid-layer, with 3 elements passed to the shape argument, ``shape=[nx, ny, nz]``: .. literalinclude:: scripts/layers.py :start-after: # { layer4_3d_b #} :end-before: # { end #} .. _fig_layer4_3d_b: .. figure:: figures/layer4_3d_b.png :name: fig:layer4_3d_b A grid 3D NodeCollection with 120 elements distributed on a grid with 4 elements in the x-direction, 5 elements in the y-direction and 6 elements in the z-direction, with an extent of size :math:`1\times 1\times 1`. .. _sec_periodic: Periodic boundary conditions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Simulations usually model systems much smaller than the biological networks we want to study. One problem this entails is that a significant proportion of neurons in a model network is close to the edges of the network with fewer neighbors than nodes properly inside the network. In the :math:`5\times 5`-layer in :numref:`fig_layer1` for instance., 16 out of 25 nodes form the border of the layer. One common approach to reducing the effect of boundaries on simulations is to introduce *periodic boundary conditions*, so that the rightmost elements on a grid are considered nearest neighbors to the leftmost elements, and the topmost to the bottommost. The flat layer becomes the surface of a torus. :numref:`fig_player` illustrates this for a one-dimensional layer, which turns from a line to a ring upon introduction of periodic boundary conditions. You specify periodic boundary conditions for a NodeCollection using the entry ``edge_wrap``: .. literalinclude:: scripts/layers.py :start-after: # { player #} :end-before: # { end #} .. _fig_player: .. figure:: figures/player.png :name: fig:player Top left: Layer with single row and five columns without periodic boundary conditions. Numbers above elements show element coordinates. Colors shifting from blue to magenta mark increasing distance from the element at :math:`(-2,0)`. Bottom left: Same layer, but with periodic boundary conditions. Note that the element at :math:`(2,0)` now is a nearest neighbor to the element at :math:`(-2,0)`. Right: Layer with periodic boundary condition arranged on a circle to illustrate neighborhood relationships. Note that the longest possible distance between two elements in a layer without periodic boundary conditions is .. math:: \sqrt{x_{\text{ext}}^2 + y_{\text{ext}}^2} but only .. math:: \left.\sqrt{x_{\text{ext}}^2 + y_{\text{ext}}^2}\right/ 2 for a layer with periodic boundary conditions; :math:`x_{\text{ext}}` and :math:`y_{\text{ext}}` are the components of the extent size. We will discuss the consequences of periodic boundary conditions more in the section on :ref:`sec_connections`. Layers as NodeCollection ~~~~~~~~~~~~~~~~~~~~~~~~ From the perspective of NEST, a layer is a special type of *NodeCollection*. From the user perspective, the following points may be of interest: - The NodeCollection has a ``spatial`` property describing the spatial properties of the NodeCollection (``l`` is the layer created at the beginning of this guide): .. literalinclude:: scripts/layers.py :start-after: # { layer1s #} :end-before: # { end #} The ``spatial`` property is read-only; changing any value will not change properties of the spatially distributed NodeCollection. - NEST sees the elements of the layer in the same way as the elements of any other NodeCollection. NodeCollections created as layers can therefore be used in the same ways as any standard NodeCollection. However, operations requiring a NodeCollection with spatial data (e.g. :py:func:`.Connect` with spatial dependence, or visualization of layers) can only be used on NodeCollections created with spatial distribution. .. literalinclude:: scripts/layers.py :start-after: # { layer1p #} :end-before: # { end #} .. _sec_connections: Connections ----------- The most important feature of the spatially-structured networks is the ability to create connections between NodeCollections with quite some flexibility. In this chapter, we will illustrate how to specify and create connections. All connections are created using the :py:func:`.Connect` function. Basic principles ~~~~~~~~~~~~~~~~ .. _sec_spatial_terminology: Terminology ^^^^^^^^^^^ We begin by introducing important terminology: Connection In the context of connections between the elements of NodeCollections with spatial distributions, we often call the set of all connections between pairs of network nodes created by a single call to :py:func:`.Connect` a *connection*. Connection dictionary A dictionary specifying the properties of a connection between two NodeCollections in a call to :py:func:`.Connect`. Source The *source* of a single connection is the node sending signals (usually spikes). In a projection, the source layer is the layer from which source nodes are chosen. Target The *target* of a single connection is the node receiving signals (usually spikes). In a projection, the target layer is the layer from which target nodes are chosen. Driver When connecting two layers, the *driver* layer is the one in which each node is considered in turn. Pool | When connecting two layers, the *pool* layer is the one from which nodes are chosen for each node in the driver layer. I.e., we have +--------------------------------+--------------+--------------+ | Connection parameters | Driver | Pool | +================================+==============+==============+ | ``rule='fixed_indegree'`` | target layer | source layer | +--------------------------------+--------------+--------------+ | ``rule='fixed_outdegree'`` | source layer | target layer | +--------------------------------+--------------+--------------+ | ``rule='pairwise_bernoulli'`` | source layer | target layer | +--------------------------------+--------------+--------------+ | ``rule='pairwise_bernoulli'`` | target layer | source layer | | and ``use_on_source=True`` | | | +--------------------------------+--------------+--------------+ | ``rule='pairwise_poisson'`` | source layer | target layer | +--------------------------------+--------------+--------------+ Displacement The *displacement* between a driver and a pool node is the shortest vector connecting the driver to the pool node, taking boundary conditions into account. Distance The *distance* between a driver and a pool node is the length of their displacement. Mask The *mask* defines which pool nodes are at all considered as potential targets for each driver node. See section :ref:`sec_conn_masks` for details. Connection probability or ``p`` The *connection probability*, specified as ``p`` in the connection specifications, is either a value, or a parameter which specifies the probability for creating a connection between a driver and a pool node. The default probability is :math:`1`, i.e., connections are created with certainty. See section :ref:`sec_conn_kernels` for details. Pairwise average number of connections or ``pairwise_avg_num_conns`` The *pairwise average number of connections* between a driver and a pool node. It is used in the ``pairwise_poisson`` connection rule and determines the mean value of the Poisson distribution from which the number of connections between the nodes is sampled. See section :ref:`sec_conn_kernels` for details. Autapse An *autapse* is a synapse (connection) from a node onto itself. Autapses are permitted by default, but can be disabled by adding ``'allow_autapses': False`` to the connection dictionary. Multapse Node A is connected to node B by a *multapse* if there are synapses (connections) from A to B. Multapses are permitted by default, but can be disabled by adding ``'allow_multapses': False`` to the connection dictionary. Connecting spatially distributed nodes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with "normal" NodeCollections, connections between spatially distributed nodes are created by calling :py:func:`.Connect`. However, having spatial information about the nodes makes position-based options available, and so in addition to the usual connection schemes there exists additional connection parameters for spatially distributed NodeCollections. In many cases when connecting spatially distributed NodeCollections, a mask will be specified. Mask specifications are described in the section :ref:`sec_conn_masks`. Only neurons within the mask are considered as potential sources or targets. If no mask is given, all neurons in the respective NodeCollection are considered sources or targets. Here is a simple example, cf. :numref:`fig_conn1` .. literalinclude:: scripts/connections.py :start-after: # { conn1 #} :end-before: # { end #} .. _fig_conn1: .. figure:: figures/conn1.png :name: fig:conn1 Left: Minimal connection example from a layer onto itself using a rectangular mask shown as red for the node at :math:`(0,0)` (marked light red). The targets of this node are marked with red dots. The targets for the node at :math:`(4,5)` are marked with yellow dots. This node has fewer targets since it is at the corner and many potential targets are beyond the layer. Right: The effect of periodic boundary conditions is seen here. Source and target layer and connection dictionary were identical, except that periodic boundary conditions were used. The node at :math:`(4,5)` now has 15 targets, too, but they are spread across the corners of the layer. If we wrapped the layer to a torus, they would form a :math:`5\times 3` rectangle centered on the node at :math:`(4,5)`. In this example, layer ``l`` is both source and target layer. For each node in the NodeCollection we choose targets according to the rectangular mask centered about each source node. Since the connection probability is 1.0, we connect to all nodes within the mask. Note the effect of normal and periodic boundary conditions on the connections created for different nodes in the NodeCollection, as illustrated in :numref:`fig_conn1`. Mapping source and target layers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The application of masks and other functions depending on the distance or even the displacement between nodes in the source and target layers requires a mapping of coordinate systems between source and target layers. NEST applies the following *coordinate mapping rules*: 1. All layers have two-dimensional Euclidean coordinate systems. 2. No scaling or coordinate transformation can be applied between NodeCollections with spatial distribution. 3. The displacement :math:`d(D,P)` from node :math:`D` in the driver layer to node :math:`P` in the pool layer is measured by first mapping the position of :math:`D` in the driver layer to the identical position in the pool layer and then computing the displacement from that position to :math:`P`. If the pool layer has periodic boundary conditions, they are taken into account. It does not matter for displacement computations whether the driver layer has periodic boundary conditions. .. _sec_conn_masks: Masks ~~~~~ A mask describes which area of the pool layer shall be searched for nodes when connecting for any given node in the driver layer. We will first describe geometrical masks defined for all layer types and then consider grid-based masks for grid-based NodeCollections. If no mask is specified, all nodes in the pool layer will be searched. Note that the mask size should not exceed the size of the layer when using periodic boundary conditions, since the mask would "wrap around" in that case and pool nodes would be considered multiple times as targets. If none of the mask types provided in the library meet your need, you may define custom masks, either by introducing a cut-off to the connection probability using parameters, or by adding more mask types in a NEST extension module. This is covered in the section on :ref:`sec_custom_masks`. .. _sec_free_masks: Masks for 2D layers ^^^^^^^^^^^^^^^^^^^ NEST currently provides four types of masks usable for 2-dimensional free and grid-based NodeCollections. They are illustrated in :numref:`fig_conn2_a`. The masks are Rectangular All nodes within a rectangular area are connected. The area is specified by its lower left and upper right corners, measured in the same unit as element coordinates. Example: .. literalinclude:: scripts/connections.py :start-after: # { conn2r #} :end-before: # { end #} Circular All nodes within a circle are connected. The area is specified by its radius. .. literalinclude:: scripts/connections.py :start-after: # { conn2c #} :end-before: # { end #} Doughnut All nodes between an inner and outer circle are connected. Note that nodes *on* the inner circle are not connected. The area is specified by the radii of the inner and outer circles. .. literalinclude:: scripts/connections.py :start-after: # { conn2d #} :end-before: # { end #} Elliptical All nodes within an ellipsis are connected. The area is specified by its major and minor axis. .. literalinclude:: scripts/connections.py :start-after: # { conn2e #} :end-before: # { end #} .. _fig_conn2_a: .. figure:: figures/conn2_a.png :name: fig:conn2_a Masks for 2D layers. For all mask types, the driver node is marked by a wide light-red circle, the selected pool nodes by red dots and the masks are red. From left to right, top to bottom: rectangular, circular, doughnut and elliptical masks centered about the driver node. By default, the masks are centered about the position of the driver node, mapped into the pool layer. You can change the location of the mask relative to the driver node by specifying an ``'anchor'`` entry in the mask dictionary. The anchor is a 2D vector specifying the location of the mask center relative to the driver node, as in the following examples (cf. :numref:`fig_conn2_b`). .. literalinclude:: scripts/connections.py :start-after: # { conn2ro #} :end-before: # { end #} .. literalinclude:: scripts/connections.py :start-after: # { conn2co #} :end-before: # { end #} .. literalinclude:: scripts/connections.py :start-after: # { conn2do #} :end-before: # { end #} .. literalinclude:: scripts/connections.py :start-after: # { conn2eo #} :end-before: # { end #} .. _fig_conn2_b: .. figure:: figures/conn2_b.png :name: fig:conn2_b The same masks as in :numref:`fig_conn2_a`, but centered about :math:`(-1.5,-1.5)`, :math:`(-2,0)`, :math:`(1.5,1.5)` and :math:`(2, -1)`, respectively, using the ``'anchor'`` parameter. It is, as of NEST 2.16, possible to rotate the :math:`\textbf{rectangular}` and :math:`\textbf{elliptical}` masks, see Fig :numref:`fig_conn2_b`. To do so, add an ``'azimuth_angle'`` entry in the specific mask dictionary. The ``azimuth_angle`` is measured in degrees and is the rotational angle from the x-axis to the y-axis. .. literalinclude:: scripts/connections.py :start-after: # { conn2rr #} :end-before: # { end #} .. literalinclude:: scripts/connections.py :start-after: # { conn2er #} :end-before: # { end #} .. _fig_conn2_c: .. figure:: figures/conn2_c.png :name: fig:conn2_c Rotated rectangular and elliptical mask from :numref:`fig_conn2_a` and :numref:`fig_conn2_b`, where the rectangular mask is rotated :math:`120^\circ` and the elliptical mask is rotated :math:`45^\circ`. .. _sec_3d_masks: Masks for 3D layers ^^^^^^^^^^^^^^^^^^^ Similarly, there are three mask types that can be used for 3D NodeCollections, Box All nodes within a cuboid volume are connected. The area is specified by its lower left and upper right corners, measured in the same unit as element coordinates. Example: .. literalinclude:: scripts/connections.py :start-after: # { conn_3d_a #} :end-before: # { end #} Spherical All nodes within a sphere are connected. The area is specified by its radius. .. literalinclude:: scripts/connections.py :start-after: # { conn_3d_b #} :end-before: # { end #} Ellipsoidal All nodes within an ellipsoid are connected. The area is specified by its major, minor, and polar axis. .. literalinclude:: scripts/connections.py :start-after: # { conn_3d_c #} :end-before: # { end #} As in the 2D case, you can change the location of the mask relative to the driver node by specifying a 3D vector in the ``'anchor'`` entry in the mask dictionary. If you want to rotate the box or ellipsoidal masks, you can add an ``'azimuth_angle'`` entry in the specific mask dictionary for rotation from the x-axis towards the y-axis about the z-axis, or a ``'polar_angle'`` entry, specifying the rotation angle in degrees from the z-axis about the (possibly rotated) x axis, from the (possibly rotated) y-axis. You can specify both at once of course. If both are specified, we first rotate about the z-axis and then about the new x-axis. NEST currently does not support rotation in all three directions, the rotation from the y-axis about the (possibly rotated) z-axis, from the (possibly rotated) x-axis is missing. .. _fig_conn_3d: .. figure:: figures/conn_3d.png :name: fig:conn3d Masks for 3D NodeCollections. For all mask types, the driver node is marked by a wide light-red circle, the selected pool nodes by red dots and the masks are red. From left to right: box and spherical masks centered about the driver node. Masks for grid-based layers ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Grid-based layers can be connected using rectangular *grid masks*. For these, you specify the size of the mask not by lower left and upper right corner coordinates, but give their size in x and y direction, as in this example: .. literalinclude:: scripts/connections.py :start-after: # { conn3 #} :end-before: # { end #} The resulting connections are shown in :numref:`fig_conn3`. By default the top-left corner of a grid mask, i.e., the grid mask element with grid index :math:`[0,0]`\ (see :ref:`2.1.1 `), is aligned with the driver node. You can change this alignment by specifying an *anchor* for the mask: .. literalinclude:: scripts/connections.py :start-after: # { conn3c #} :end-before: # { end #} You can even place the anchor outside the mask: .. literalinclude:: scripts/connections.py :start-after: # { conn3x #} :end-before: # { end #} The resulting connection patterns are shown in :numref:`fig_conn3`. .. _fig_conn3: .. figure:: figures/conn3.png :name: fig:conn3 Grid masks for connections between grid-based layers. Left: :math:`5\times 3` mask with default alignment at upper left corner. Center: Same mask, but anchored to center node at grid index :math:`[1,2]`. Right: Same mask, but anchor to the upper left of the mask at grid index :math:`[-1,2]`. Connections specified using grid masks are generated more efficiently than connections specified using other mask types. Note the following: - Grid-based masks are applied by considering grid indices. The position of nodes in physical coordinates is ignored. - In consequence, grid-based masks should only be used between NodeCollections with identical grid spacings. - The semantics of the ``'anchor'`` property for grid-based masks differ significantly for general masks described in the section :ref:`sec_free_masks`. For general masks, the anchor is the center of the mask relative to the driver node. For grid-based nodes, the anchor determines which mask element is aligned with the driver element. .. _sec_conn_kernels: Probabilistic connection rules ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Many neuronal network models employ probabilistic connection rules. NEST supports probabilistic connections through the ``pairwise_bernoulli`` and the ``pairwise_poisson`` connection rule. For the ``pairwise_bernoulli`` rule, the probability can then be a constant, depend on the position of the source or the target neuron, or on the distance between a driver and a pool node to a connection probability. For the ``pairwise_poisson`` rule, the pairwise average number of connections can depend on the position of the source or the target neuron, or on the distance between a driver and a pool node to a connection probability. To create dependencies on neuron positions, NEST :py:class:`.Parameter` objects are used. NEST then generates a connection according to this probability. Probabilistic connections between layers can be generated in two different ways: Free probabilistic connections using ``pairwise_bernoulli`` In this case, :py:func:`.Connect` considers each driver node :math:`D` in turn. For each :math:`D`, it evaluates the parameter value for each pool node :math:`P` within the mask and creates a connection according to the resulting probability. This means in particular that *each possible driver-pool pair is inspected exactly once* and that there will be *at most one connection between each driver-pool pair*. Free probabilistic connections using ``pairwise_poisson`` In this case, :py:func:`.Connect` considers each driver node :math:`D` in turn. For each :math:`D`, it evaluates the parameter value for each pool node :math:`P` within the mask and creates a number of connections that are sampled from a Poisson distribution with mean of the parameter value. This means in particular that *each possible driver-pool pair is inspected exactly once* and that *more than one connection between each driver-pool pair is possible*. Additionally, the parameter may be larger than :math:`1`. Prescribed number of connections can be obtained by using ``fixed_indegree`` or ``fixed_outdegree`` connection rule, and specifying the number of connections to create per driver node. See the section :ref:`sec_prescribed_numbers` for details. A selection of specific NEST parameters pertaining to spatially structured networks are shown the table below. .. _tbl_parameters: NEST parameters specific to spatially-structured networks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The parameters in the table below represent positions of neurons or distances between two neurons. To set node parameters, only the node position can be used. The others can only be used when connecting. +-----------------------------------------+-------------------------------------------------------------------------+ | Parameter | Description | +=========================================+=========================================================================+ | | ``nest.spatial.pos.x`` | | Position of a neuron, on the x, y, and z axis. | | | ``nest.spatial.pos.y`` | | Can be used to set node properties, but not for connecting. | | | ``nest.spatial.pos.z`` | | +-----------------------------------------+-------------------------------------------------------------------------+ | | ``nest.spatial.source_pos.x`` | | Position of the source neuron, on the x, y, and z axis. | | | ``nest.spatial.source_pos.y`` | | Can only be used when connecting. | | | ``nest.spatial.source_pos.z`` | | +-----------------------------------------+-------------------------------------------------------------------------+ | | ``nest.spatial.target_pos.x`` | | | | ``nest.spatial.target_pos.y`` | | Position of the target neuron, on the x, y, and z axis. | | | ``nest.spatial.target_pos.z`` | | Can only be used when connecting. | +-----------------------------------------+-------------------------------------------------------------------------+ | | ``nest.spatial.distance`` | | Distance between two nodes. Can only be used when connecting. | +-----------------------------------------+-------------------------------------------------------------------------+ | | ``nest.spatial.distance.x`` | | | | ``nest.spatial.distance.y`` | | Distance on the x, y and z axis between the source and target neuron. | | | ``nest.spatial.distance.z`` | | Can only be used when connecting. | +-----------------------------------------+-------------------------------------------------------------------------+ NEST provides some functions to help create distributions based on the position of the nodes, for instance the distance between two neurons, shown in the table below. The table also includes parameters drawing values from random distributions. +----------------------------------------------+--------------------+------------------------------------------------------+ | Distribution function | Arguments | Function | +==============================================+====================+======================================================+ | | | .. math:: p(x) = e^{-\frac{x}{\beta}} | | ``nest.spatial_distributions.exponential()`` | | x, | | | | | beta | | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | x, | .. math:: | | ``nest.spatial_distributions.gaussian()`` | | mean, | p(x) = e^{-\frac{(x-\text{mean})^2} | | | | std | {2\text{std}^2}} | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | .. math:: | | | | x, | | | | | y, | p(x) = e^{-\frac{\frac{(x-\text{mean_x})^2} | | | | mean_x, | {\text{std_x}^2}+\frac{ | | ``nest.spatial_distributions.gaussian2D()`` | | mean_y, | (y-\text{mean_y})^2}{\text{std_y}^2}-2 | | | | std_x, | \rho\frac{(x-\text{mean_x})(y-\text{mean_y})} | | | | std_y, | {\text{std_x}\text{std_y}}} | | | | rho | {2(1-\rho^2)}} | | | | | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | .. math:: | | | | x, | | | | | y, | p(x) = \big[\cos(360^{\circ} | | | | theta, | \frac{y^{\prime}}{\lambda} | | ``nest.spatial_distributions.gabor()`` | | gamma, | + \psi)\big]^{+} e^{-\frac{ | | | | std, | \gamma^{2}x^{\prime 2}+y^{\prime 2}}{ | | | | lam, | 2\text{std}^{2}}} | | | | psi | \\ x^{\prime} = x\cos\theta + y\sin\theta | | | | \\ y^{\prime} = -x\sin\theta + y\cos\theta | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | .. math:: p(x) = \frac{x^{\kappa-1}e^{-\frac{x} | | ``nest.spatial_distributions.gamma()`` | | x, | {\theta}}}{\theta^\kappa\Gamma(\kappa)} | | | | kappa | | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | :math:`p\in [\text{min},\text{max})` uniformly | | ``nest.random.uniform()`` | | min, | | | | | max | | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | | | ``nest.random.normal()`` | | mean, | normal with given mean and standard deviation | | | | std | | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | | | ``nest.random.exponential()`` | | beta | exponential with a given scale, :math:`\beta` | | | | | +----------------------------------------------+--------------------+------------------------------------------------------+ | | | lognormal with given mean and standard | | ``nest.random.lognormal()`` | | mean, | deviation, std | | | | std | | +----------------------------------------------+--------------------+------------------------------------------------------+ .. _fig_conn4: .. figure:: figures/conn4.png :name: fig:conn4 Illustration of various connection probabilities. Top left: constant probability, :math:`p=0.5`. Top right: Distance dependent Gaussian probability, green distribution show :math:`\sigma`. Bottom left: Same distance dependent Gaussian probability, but all :math:`p<0.5` treated as :math:`p=0`. Bottom right: 2D-Gaussian. Several examples follow. They are illustrated in :numref:`fig_conn4`. Constant Fixed connection probability: .. literalinclude:: scripts/connections.py :start-after: # { conn4cp #} :end-before: # { end #} Gaussian The connection probability is a Gaussian distribution based on the distance between neurons. In the example, connection probability is 1 for :math:`d=0` and falls off with a "standard deviation" of :math:`\sigma=1`: .. literalinclude:: scripts/connections.py :start-after: # { conn4g #} :end-before: # { end #} Cut-off Gaussian In this example we have a distance-dependent Gaussian distributon, where all probabilities less than :math:`0.5` are set to zero: .. TODO: Reference to full Parameter table with nest.logic.conditional(). .. literalinclude:: scripts/connections.py :start-after: # { conn4cut #} :end-before: # { end #} 2D Gaussian Here we use a two-dimensional Gaussian distribution, i.e., a Gaussian with different widths in :math:`x`- and :math:`y`- directions. This probability depends on displacement, not only on distance: .. literalinclude:: scripts/connections.py :start-after: # { conn42d #} :end-before: # { end #} Rectified Gabor Function We conclude with an example of a rectified Gabor distribution, i.e., a two-dimensional Gaussian distribution modulated with a spatial oscillation perpendicular to :math:`\theta`. This probability depends on the displacement along the coordinates axes, not the distance: .. literalinclude:: scripts/connections.py :start-after: # { conn4gab #} :end-before: # { end #} Note that for pool layers with periodic boundary conditions, NEST always uses the shortest possible displacement vector from driver to pool neuron as ``nest.spatial.distance``. Weights and delays ~~~~~~~~~~~~~~~~~~ Parameters, such as those presented in Table :ref:`tbl_parameters`, can also be used to specify distance-dependent or randomized weights and delays for the connections created by :py:func:`.Connect`. Weight and delays are in NEST passed along in a synapse dictionary to the ``Connect()`` call. :: nest.Connect(nodes, nodes, conn_dict, syn_dict) Figure :numref:`fig_conn5` illustrates weights and delays generated using these parameters. The code examples used to generate the figures are shown below. All examples use a spatially distributed NodeCollection of 51 nodes placed on a line; the line is centered about :math:`(25,0)`, so that the leftmost node has coordinates :math:`(0,0)`. The distance between neighboring elements is 1. The mask is rectangular, spans the entire NodeCollection and is centered about the driver node. Linear example .. literalinclude:: scripts/connections.py :start-after: # { conn5lin #} :end-before: # { end #} Results are shown in the top panel of :numref:`fig_conn5`. Connection weights and delays are shown for the leftmost neuron as driver. Weights drop linearly from :math:`1`. From the node at :math:`(20,0)` on, the cutoff sets weights to 0. There are no connections to nodes beyond :math:`(25,0)`, since the mask extends only 25 units to the right of the driver. Delays increase in a stepwise linear fashion, as NEST requires delays to be multiples of the simulation resolution. Linear example with periodic boundary conditions .. literalinclude:: scripts/connections.py :start-after: # { conn5linpbc #} :end-before: # { end #} Results are shown in the middle panel of :numref:`fig_conn5`. This example is identical to the previous, except that the (pool) layer has periodic boundary conditions. Therefore, the left half of the mask about the node at :math:`(0,0)` wraps back to the right half of the layer and that node connects to all nodes in the layer. Various spatially dependent distributions .. literalinclude:: scripts/connections.py :start-after: # { conn5exp #} :end-before: # { end #} .. literalinclude:: scripts/connections.py :start-after: # { conn5gauss #} :end-before: # { end #} Results are shown in the bottom panel of :numref:`fig_conn5`. It shows linear, exponential and Gaussian distributions of the distance between connected nodes, used with weight functions for the node at :math:`(25,0)`. Randomized weights and delays .. literalinclude:: scripts/connections.py :start-after: # { conn5uniform #} :end-before: # { end #} By using the ``nest.random.uniform()`` parameter for weights or delays, one can obtain randomized values for weights and delays, as shown by the red circles in the bottom panel of :numref:`fig_conn5`. .. _fig_conn5: .. figure:: figures/conn5.png :name: fig:conn5 Distance-dependent and randomized weights and delays. See text for details. Designing distance-dependent parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Although NEST comes with some pre-defined functions that can be used to create distributions of distance-dependent parameters, there is no limit to how parameters can be combined. .. TODO: reference to parameter documentation As an example, we will now combine some parameters to create a new parameter that is linear (actually affine) with respect to the displacement between the nodes, of the form .. math:: p = 0.5 + d_x + 2 d_y. \ where :math:`d_x` and :math:`d_y` are the displacements between the source and target neuron on the x and y axis, respectively. The parameter is then simply: .. literalinclude:: scripts/connections.py :start-after: # { conn_param_design #} :end-before: # { end #} This can be directly plugged into the :py:func:`.Connect` function: .. literalinclude:: scripts/connections.py :start-after: # { conn_param_design_ex #} :end-before: # { end #} Periodic boundary conditions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Connections between layers with periodic boundary conditions are based on the following principles: - Periodic boundary conditions are always applied in the pool layer. It is irrelevant whether the driver layer has periodic boundary conditions or not. - By default, NEST does not accept masks that are wider than the pool layer when using periodic boundary conditions. Otherwise, one pool node could appear as multiple targets to the same driver node as the masks wraps several times around the layer. For layers with different extents in :math:`x`- and :math:`y`-directions this means that the maximum layer size is determined by the smaller extension. - ``nest.spatial.distance`` and its single dimension variants always consider the shortest distance (displacement) between driver and pool node. In most physical systems simulated using periodic boundary conditions, interactions between entities are short-range. Periodic boundary conditions are well-defined in such cases. In neuronal network models with long-range interactions, periodic boundary conditions may not make sense. In general, we recommend to use periodic boundary conditions only when connection masks are significantly smaller than the NodeCollections they are applied to. .. _sec_prescribed_numbers: Prescribed number of connections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We have so far described how to connect spatially distributed NodeCollections by either connecting to all nodes inside the mask or by considering each pool node in turn and connecting it according to a given probability function. In both cases, the number of connections generated depends on mask and connection probability. Many neuron models in the literature, in contrast, prescribe a certain *fan in* (number of incoming connections) or *fan out* (number of outgoing connections) for each node. You can achieve this in NEST by prescribing the number of connections for each driver node by using ``fixed_indegree`` or ``fixed_outdegree`` as connection rule. Connection generation now proceeds in a different way than before: 1. For each driver node, :py:func:`.Connect` randomly selects a node from the mask region in the pool layer, and creates a connection with the probability prescribed. This is repeated until the requested number of connections has been created. 2. Thus, if all nodes in the mask shall be connected with equal probability, you should not specify a connection probability. 3. If you specify a probability with a distance-dependent distribution (e.g., Gaussian, linear, exponential), the connections will be distributed within the mask with the spatial profile given by the probability. 4. If you prohibit multapses (see section :ref:`sec_spatial_terminology`) and prescribe a number of connections greater than the number of pool nodes in the mask, ``Connect()`` may get stuck in an infinite loop and NEST will hang. Keep in mind that the number of nodes within the mask may vary considerably for free layers with randomly placed nodes. 5. If you use the connection rule ``'rule': fixed_indegree`` in the connection dictionary, you also have to specify ``'indegree'``, the number of connections per target node. 6. Similarly, if you use the connection rule ``'rule': fixed_outdegree`` in the connection dictionary, you have to use ``'outdegree'`` to specify the number of connections per source node. The following code generates a network of 1000 randomly placed nodes and connects them with a fixed fan out, of 50 outgoing connections per node distributed with a profile linearly decaying from unit probability to zero probability at distance :math:`0.5`. Multiple connections (multapses) between pairs of nodes are allowed, self-connections (autapses) prohibited. The probability of finding a connection at a certain distance is then given by the product of the probabilities for finding nodes at a certain distance with the probability value for this distance. For the connection probability and parameter values below we have .. math:: :label: eq_ptheo p_{\text{conn}}(d) = \frac{12}{\pi} \times 2\pi r \times (1-2r) = 24 r (1-2r) \qquad \text{for} \quad 0\le r < \frac{1}{2}\;.\qquad The resulting distribution of distances between connected nodes is shown in :numref:`fig_conn6`. .. literalinclude:: scripts/connections.py :start-after: # { conn6 #} :end-before: # { end #} .. _fig_conn6: .. figure:: figures/conn6.png :name: fig:conn6 Distribution of distances between source and target for a network of 1000 randomly placed nodes, a fixed fan out of 50 connections and a connection probability decaying linearly from 1 to 0 at :math:`d=0.5`. The red line is the expected distribution from Eq. :eq:`eq_ptheo`. Functions determining weight and delay as function of distance/displacement work in just the same way as before when the number of connections is prescribed. It is also possible to use a random parameter or spatially dependent parameter to set the number of incoming or outgoing connections. .. literalinclude:: scripts/connections.py :start-after: # { conn7 #} :end-before: # { end #} Synapse models and properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, :py:func:`.Connect` creates connections using the default synapse model in NEST, :hxt_ref:`static_synapse`. You can specify a different model by adding a ``'synapse_model'`` entry to the synapse specification dictionary, as in this example: .. literalinclude:: scripts/connections.py :start-after: # { conn8 #} :end-before: # { end #} You have to use synapse models if you want to set, e.g., the receptor type of connections or parameters for plastic synapse models. These can not be set in distance-dependent ways at present. .. _sec_dev_subregions: Connecting devices to subregions of NodeCollections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is possible to connect stimulation and recording devices only to specific subregions of the layers. A simple way to achieve this is to create a layer which contains only the device placed typically in its center. When connecting the device layer to a neuron layer, an appropriate mask needs to be specified and optionally also an anchor for shifting the center of the mask. As demonstrated in the following example, stimulation devices have to be connected as the source layer. .. literalinclude:: scripts/connections.py :start-after: # { conn9 #} :end-before: # { end #} While recording devices, on the other hand, have to be connected as the target layer (see also the following section): .. literalinclude:: scripts/connections.py :start-after: # { conn10 #} :end-before: # { end #} Spatially distributed NodeCollections and recording devices ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Generally, one should not create a layer of recording devices to record from another NodeCollection with spatial extent. This is especially true for spike recorders. Instead, create a single spike recorder and connect all neurons in the spatially distributed NodeCollection to that spike recorder: .. literalinclude:: scripts/connections.py :start-after: # { conn11 #} :end-before: # { end #} Connecting a layer of neurons to a layer of recording devices as described in the section on :ref:`sec_dev_subregions`, is only possible using the ``pairwise_bernoulli`` rule. Note that voltmeter and :hxt_ref:`multimeter` do not suffer from this restriction, since they are connected as sources, not as targets. .. _sec_inspection: Inspecting spatially distributed NodeCollections ------------------------------------------------ We strongly recommend that you inspect the NodeConnections created to be sure that node placement and connectivity indeed turned out as expected. In this section, we describe some functions that NEST provide to query and visualize networks, NodeCollections, and connectivity. Query functions ~~~~~~~~~~~~~~~ The following table presents some query functions provided by NEST. +---------------------------------+-----------------------------------------------------+ | ``nest.PrintNodes()`` | Print the node ID ranges and model names of | | | the nodes in the network. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetConnections()`` | Retrieve connections (all or for a given | | | source or target); see also | | | :ref:`connectivity_concepts`. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetNodes()`` | Returns a NodeCollection of all elements with given | | | properties. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetPosition()`` | Return the spatial locations of nodes. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetSourceNodes()`` | Obtain sources of targets in a | | | given source layer. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetTargetNodes()`` | Obtain targets of sources in a | | | given target layer. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetSourcePositions()`` | Obtain positions of sources of | | | targets in a given source layer. | +---------------------------------+-----------------------------------------------------+ | ``nest.GetTargetPositions()`` | Obtain positions of targets of | | | sources in a given target layer. | +---------------------------------+-----------------------------------------------------+ | ``nest.FindNearestElement()`` | Return the node(s) closest to the | | | location(s) in the given NodeCollection. | +---------------------------------+-----------------------------------------------------+ | ``nest.FindCenterElement()`` | Return NodeCollection of node closest to center | | | of layer. | +---------------------------------+-----------------------------------------------------+ | ``nest.Displacement()`` | Obtain vector of lateral displacement | | | between nodes, taking periodic boundary | | | conditions into account. | +---------------------------------+-----------------------------------------------------+ | ``nest.Distance()`` | Obtain vector of lateral distances between | | | nodes, taking periodic boundary conditions | | | into account. | +---------------------------------+-----------------------------------------------------+ | ``nest.DumpLayerNodes()`` | Write layer element positions to file. | | | | +---------------------------------+-----------------------------------------------------+ | ``nest.DumpLayerConnections()`` | Write connectivity information to file. | | | This function may be very useful to check | | | that NEST created the correct | | | connection structure. | +---------------------------------+-----------------------------------------------------+ | ``nest.SelectNodesByMask()`` | Obtain NodeCollection of elements inside a | | | masked area of a NodeCollection. | | | | +---------------------------------+-----------------------------------------------------+ Visualization functions ~~~~~~~~~~~~~~~~~~~~~~~ NEST provides three functions to visualize networks: +--------------------------------------+------------------------------------------+ | :py:func:`.PlotLayer` | Plot nodes in a spatially distributed | | | NodeCollection. | +--------------------------------------+------------------------------------------+ | :py:func:`.PlotSources` | Plot all sources of a node in a given | | | NodeCollection. | +--------------------------------------+------------------------------------------+ | :py:func:`.PlotTargets` | Plot all targets of a node in a given | | | NodeCollection. | +--------------------------------------+------------------------------------------+ | :py:func:`.PlotProbabilityParameter` | Add indication of mask and probability | | | ``p`` to plot of NodeCollection. This | | | function is usually called by | | | :py:func:`.PlotTargets`. | +--------------------------------------+------------------------------------------+ .. _fig_vislayer: .. figure:: figures/vislayer.png :name: fig:vislayer :math:`21\times 21` grid with divergent Gaussian projections onto itself. Blue circles mark layer elements, red circles connection targets of the center neuron. The large red circle is the mask, the green distribution mark the Gaussian probability distribution. The following code shows a practical example: A :math:`21\times21` network which connects to itself with Gaussian connections. The resulting graphics is shown in :numref:`fig_vislayer`. All elements and the targets of the center neuron are shown, as well as mask and connection probability. .. literalinclude:: scripts/layers.py :start-after: # { vislayer #} :end-before: # { end #} .. _sec_custom_masks: Creating custom masks --------------------- In some cases, the built-in masks may not meet your needs, and you want to create a custom mask. There are two ways to do this: 1. To use parameters to introduce a cut-off to the connection probability. 2. To implement a custom mask in C++ as a module. Using parameters is the most accessible option; the entire implementation is done on the PyNEST level. However, the price for this flexibility is reduced connection efficiency compared to masks implemented in C++. Combining parameters to give the wanted behaviour may also be difficult if the mask specifications are complex. Implementing a custom mask in C++ gives much higher connection performance and greater freedom in implementation, but requires some knowledge of the C++ language. As the mask in this case is implemented in an extension module, which is dynamically loaded into NEST, it also requires some additional steps for installation. Using parameters to specify connection boundaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can use parameters that represent spatial distances between nodes to create a connection probability with mask behaviour. For this, you need to create a condition parameter that describes the boundary of the mask. As condition parameters evaluate to either ``0`` or ``1``, it can be used alone, with the ``nest.logic.conditional()`` parameter, or multiplied with another parameter or value, before passing it as the connection probability. As an example, suppose we want to create connections to 50 % of the target nodes, but only to those within an elliptical area around each source node. Using ``nest.spatial.distance`` parameters, we can define a parameter that creates an elliptical connection boundary. First, we define variables controlling the shape of the ellipse. :: rx = 0.5 # radius in the x-direction ry = 0.25 # radius in the y-direction Next, we define the connection boundary. We only want to connect to targets inside an ellipse, so the condition is .. math:: \frac{x^2}{r_x^2}+\frac{y^2}{r_y^2} \leq 1, where :math:`x` and :math:`y` are the distances between the source and target neuron, in x- and y-directions, respectively. We use this expression to define the boundary using parameters. :: x = nest.spatial.distance.x y = nest.spatial.distance.y lhs = x * x / rx**2 + y * y / ry**2 mask_param = nest.logic.conditional(lhs <= 1.0, 0.5, 0.0) # Because the probability outside the ellipse is zero, # we could also have defined the parameter as # mask_param = 0.5*(lhs <= 1.0) Then, we can use the parameter as connection probability when connecting populations with spatial information. :: l = nest.Create('iaf_psc_alpha', positions=nest.spatial.grid(shape=[11, 11], extent=[1., 1.])) nest.Connect(l, l, {'rule': 'pairwise_bernoulli', 'p': mask_param}) Adding masks in a module ~~~~~~~~~~~~~~~~~~~~~~~~ If using parameters to define a connection boundary is not efficient enough, or if you need more flexibility in defining the mask, you can add a custom mask, written in C++, and add it to NEST via an extension module. For more information on writing such modules, see the :doc:`NEST extension module repository `. To add a mask, a subclass of ``nest::Mask`` must be defined, where ``D`` is the dimension (2 or 3). In this case we will define a 2-dimensional elliptic mask by creating a class called ``EllipticMask``. Please note that elliptical masks are already part of NEST (see :ref:`sec_conn_masks`). However, that elliptical mask is defined in a different way than what we will do here though, so this can still be used as an introductory example. First, we must include the header files for the ``Mask`` parent class: .. code:: c #include "mask.h" #include "mask_impl.h" The ``Mask`` class has a few methods that must be overridden: .. code:: c class EllipticMask : public nest::Mask< 2 > { public: EllipticMask( const DictionaryDatum& d ) : rx_( 1.0 ) , ry_( 1.0 ) { updateValue< double >( d, "r_x", rx_ ); updateValue< double >( d, "r_y", ry_ ); } using Mask< 2 >::inside; // returns true if point is inside the ellipse bool inside( const nest::Position< 2 >& p ) const { return p[ 0 ] * p[ 0 ] / rx_ / rx_ + p[ 1 ] * p[ 1 ] / ry_ / ry_ <= 1.0; } // returns true if the whole box is inside the ellipse bool inside( const nest::Box< 2 >& b ) const { nest::Position< 2 > p = b.lower_left; // Test if all corners are inside mask if ( not inside( p ) ) return false; // (0,0) p[ 0 ] = b.upper_right[ 0 ]; if ( not inside( p ) ) return false; // (0,1) p[ 1 ] = b.upper_right[ 1 ]; if ( not inside( p ) ) return false; // (1,1) p[ 0 ] = b.lower_left[ 0 ]; if ( not inside( p ) ) return false; // (1,0) return true; } // returns bounding box of ellipse nest::Box< 2 > get_bbox() const { nest::Position< 2 > ll( -rx_, -ry_ ); nest::Position< 2 > ur( rx_, ry_ ); return nest::Box< 2 >( ll, ur ); } nest::Mask< 2 >* clone() const { return new EllipticMask( *this ); } protected: double rx_, ry_; }; The overridden methods include a test if a point is inside the mask, and for efficiency reasons also a test if a box is fully inside the mask. We implement the latter by testing if all the corners are inside, since our elliptic mask is convex. We must also define a function which returns a bounding box for the mask, i.e. a box completely surrounding the mask. The mask class must then be registered in NEST. This is done by adding a line to the function ``MyModule::init()`` in the file ``mymodule.cpp``: .. code:: c nest::NestModule::register_mask< EllipticMask >( "elliptic" ); After compiling and installing your module, the mask is available to be used in connections, e.g. :: nest.Install('mymodule') l = nest.Create('iaf_psc_alpha', positions=nest.spatial.grid(shape=[11, 11], extent=[1., 1.])) nest.Connect(l, l, {'rule': 'pairwise_bernoulli', 'p': 0.5, 'mask': {'elliptic': {'r_x': 0.5, 'r_y': 0.25}}})