========================================================
Representing spatial structure and calculating distances
========================================================

.. currentmodule: pyNN.space

The :mod:`space` module contains classes for specifying the locations of neurons
in space and for calculating the distances between them.

Neuron positions can be defined either manually, using the :attr:`positions`
attribute of a :class:`Population` or using a :class:`Structure` instance
which is passed to the :class:`Population` constructor.

A number of different structures are available in :mod:`space`. It is simple to
define your own :class:`Structure` sub-class if you need something that is not
already provided.

The simplest structure is a grid, whether 1D, 2D or 3D, e.g.:

.. testsetup::

    from pyNN.random import NumpyRNG

.. doctest::
   :options: +NORMALIZE_WHITESPACE

    >>> from pyNN.space import *
    >>> line = Line(dx=100.0, x0=0.0, y=200.0, z=500.0)
    >>> line.generate_positions(7)
    array([[   0.,  100.,  200.,  300.,  400.,  500.,  600.],
           [ 200.,  200.,  200.,  200.,  200.,  200.,  200.],
           [ 500.,  500.,  500.,  500.,  500.,  500.,  500.]])
    >>> grid = Grid2D(aspect_ratio=3, dx=10.0, dy=25.0, z=-3.0)
    >>> grid.generate_positions(3)
    array([[  0.,  10.,  20.],
           [  0.,   0.,   0.],
           [ -3.,  -3.,  -3.]])
    >>> grid.generate_positions(12)
    array([[  0.,   0.,  10.,  10.,  20.,  20.,  30.,  30.,  40.,  40.,  50.,  50.],
           [  0.,  25.,   0.,  25.,   0.,  25.,   0.,  25.,   0.,  25.,   0.,  25.],
           [ -3.,  -3.,  -3.,  -3.,  -3.,  -3.,  -3.,  -3.,  -3.,  -3.,  -3.,  -3.]])

Here we have specified an *x*:*y* ratio of 3, so if we ask the grid to
generate positions for 3 neurons, we get a 3x1 grid, 12 neurons a 6x2 grid,
27 neurons 9x3, etc.

BY default, grid positions are filled sequentially, iterating first over the *z*
dimension, then *y*, then *x*, but we can also fill the grid randomly:

.. doctest::

    >>> rgrid = Grid2D(aspect_ratio=1, dx=10.0, dy=10.0, fill_order='random', rng=NumpyRNG(seed=13886))
    >>> rgrid.generate_positions(9)
    array([[ 10.,  10.,  10.,   0.,   0.,   0.,  20.,  20.,  20.],
           [  0.,  10.,  20.,  10.,  20.,   0.,   0.,  10.,  20.],
           [  0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.]])

The :mod:`space` module also provides the :class:`RandomStructure` class, which
distributes neurons randomly and uniformly within a given volume:

.. doctest::
   :options: +NORMALIZE_WHITESPACE

    >>> glomerulus = RandomStructure(boundary=Sphere(radius=200.0), rng=NumpyRNG(seed=34534))
    >>> glomerulus.generate_positions(5)
    array([[ -19.78455022,   33.21412264,  -79.4314059 ,  143.39033263,  -63.18242977],
           [  56.17281502,  -23.15159309,  131.89071845,  -73.73583484,  -8.86422999],
           [ -78.88348228,   -3.97408513,  -95.03056844,   45.13969087,  -111.67070498]])

The volume classes currently available are :class:`Sphere` and :class:`Cuboid`.

Defining your own :class:`Structure` classes is straightforward, just inherit
from :class:`BaseStructure` and implement a :meth:`generate_positions` method::


    class MyStructure(BaseStructure):
        parameter_names = ("spam", "eggs")

        def __init__(self, spam=3, eggs=1):
            ...

        def generate_positions(self, n):
            ...
            # must return a 3xn numpy array

To define your own :class:`Shape` class for use with :class:`RandomStructure`,
subclass :class:`Shape` and implement a :meth:`sample` method::

    class Tetrahedron(Shape):

        def __init__(self, side_length):
            ...

        def sample(self, n, rng):
           ...
           # return a nx3 numpy array.

.. note:: rotation of structures is currently missing, but is planned for a
          future release.
