'''
@author: <NAME>

Tests spatially structured networks generated by the CSA implementation in NEST.
'''

import numpy as np
import numpy.random as rnd
import random
import nest
import nest.topology as topo
import csa

from testsuite.spatial_test import SpatialTester


class NEST_CSA_SpatialTester(SpatialTester):
    '''
    Tests spatially structured networks generated by the CSA implementation
    in NEST.
    '''

    def __init__(self, L, N, kernel_name, kernel_params=None):
        '''
        Construct a test object.

        Parameters
        ----------
            L            : Side length of area / volume.
            N            : Number of nodes.
            kernel_name  : Name of kernel to use.
            kernel_params: Dict with params to update.
        '''

        nest.set_verbosity('M_FATAL')

        SpatialTester.__init__(self, L=L, N=N)

        assert kernel_name == 'gaussian', 'Currently, only a Gaussian kernel' \
                              'is supported by this implementation of the CSA.'
        self._kernel = lambda D: (self._params['c'] + self._params['p_center'] *
                                  np.e ** -((D - self._params['mean']) ** 2 /
                                            (2. * self._params['sigma'] ** 2)))

        default_params = {'p_center': 1., 'sigma': self._L / 4.,
                          'mean': 0., 'c': 0.}
        self._params = default_params
        if kernel_params is not None:
            assert kernel_params.keys() == ['sigma'], \
                'Only valid kernel parameter is "sigma".'
            self._params.update(kernel_params)

    def _geometryFunction(self, topologyLayer):
        '''
        This factory returns a CSA-style geometry function for the given layer.
    
        The function returned will return for each CSA-index the position in
        space of the given neuron as a 2- or 3-element list.
    
        Note: This function stores a copy of the neuron positions internally,
              entailing memory overhead.
        '''

        positions = topo.GetPosition(nest.GetLeaves(topologyLayer)[0])

        def geometry_function(idx):
            '''Return position of neuron with given CSA-index.'''

            return positions[idx]

        return geometry_function

    def _reset(self, seed):
        '''
        Seed the PRNG.
        
        Parameters
        ----------
            seed: PRNG seed value. 
        '''

        if seed is None:
            seed = random.randint(0, 10 ** 10)
        seed = 4 * seed # Reduces probability of overlapping seed values.
        random.seed(seed) # CSA uses random.
        rnd.seed(seed + 1) # _build used numpy.ranom.
        nest.SetKernelStatus({'rng_seeds': [seed + 2],
                              'grng_seed': seed + 3})

    def _build(self):
        '''Create populations.'''

        ldict_s = {'elements': 'iaf_neuron', 'positions': [(0., 0.)],
                   'extent': [self._L] * 2, 'edge_wrap': True}
        x = rnd.uniform(-self._L / 2., self._L / 2., self._N)
        y = rnd.uniform(-self._L / 2., self._L / 2., self._N)
        ldict_t = {'elements': 'iaf_neuron', 'positions': zip(x, y),
                   'extent': [self._L] * 2, 'edge_wrap': True}
        self._ls = topo.CreateLayer(ldict_s)
        self._lt = topo.CreateLayer(ldict_t)
        self._driver = topo.FindCenterElement(self._ls)

    def _connect(self):
        '''Connect populations.'''

        g1 = self._geometryFunction(self._ls)
        g2 = self._geometryFunction(self._lt)
        d = csa.euclidMetric2d(g1, g2)
        sigma = self._params['sigma']
        cutoff = self._max_dist
        cs = csa.cset(csa.cross([0], xrange(self._N - 1)) *
                      (csa.random * (csa.gaussian(sigma, cutoff) * d)),
                      1.0, 1.0)
        nest.CGConnect(nest.GetLeaves(self._ls)[0], nest.GetLeaves(self._lt)[0],
                       cs, {'weight': 0, 'delay': 1})

    def _distances(self):
        '''Return distances to all nodes in target population.'''

        return topo.Distance(self._driver, nest.GetLeaves(self._lt)[0])

    def _target_distances(self):
        '''Return distances from source node to connected nodes.'''

        connections = nest.GetConnections(source=self._driver)
        target_nodes = [conn[1] for conn in connections]
        return topo.Distance(self._driver, target_nodes)

    def _positions(self):
        '''Return positions of all nodes.'''

        return [tuple(pos) for pos in
                topo.GetPosition(nest.GetLeaves(self._lt)[0])]

    def _target_positions(self):
        '''Return positions of all connected target nodes.'''

        return [tuple(pos) for pos in
                topo.GetTargetPositions(self._driver, self._lt)[0]]


class Spatial2DTester(NEST_CSA_SpatialTester):
    '''
    Tests for 2D spatially structured networks generated by the
    CSA implementation in NEST.
    '''

    def __init__(self, L, N, kernel_name, kernel_params=None):
        '''
        Construct a test object.

        Parameters
        ----------
            L            : Side length of area / volume.
            N            : Number of nodes.
            kernel_name  : Name of kernel to use.
            kernel_params: Dict with params to update.
            '''

        self._dimensions = 2
        NEST_CSA_SpatialTester.__init__(self, L=L, N=N,
                                        kernel_name=kernel_name,
                                        kernel_params=kernel_params)


class Spatial3DTester(NEST_CSA_SpatialTester):
    '''
    Tests for 3D spatially structured networks generated by the
    CSA implementation in NEST.
    '''

    def __init__(self, *args, **kwargs):
        raise NotImplementedError('CSA does currently not support ' \
                                  'construction of 3D spatial networks.')


if __name__ == '__main__':
    test = Spatial2DTester(L=1.0, N=10000, kernel_name='gaussian')
    ks, p = test.ks_test(control=False, seed=0)
    print 'p-value of KS-test:', p
    z, p = test.z_test(control=False, seed=0)
    print 'p-value of Z-test:', p
    test.show_network()
    test.show_PDF()
    test.show_CDF()

'''
@author: <NAME>

Tests spatially structured networks generated by the CSA implementation in NEST.
'''

import numpy as np
import numpy.random as rnd
import random
import nest
import nest.topology as topo
import csa

from testsuite.spatial_test import SpatialTester


class NEST_CSA_SpatialTester(SpatialTester):
    '''
    Tests spatially structured networks generated by the CSA implementation
    in NEST.
    '''

    def __init__(self, L, N, kernel_name, kernel_params=None):
        '''
        Construct a test object.

        Parameters
        ----------
            L            : Side length of area / volume.
            N            : Number of nodes.
            kernel_name  : Name of kernel to use.
            kernel_params: Dict with params to update.
        '''

        nest.set_verbosity('M_FATAL')

        SpatialTester.__init__(self, L=L, N=N)

        assert kernel_name == 'gaussian', 'Currently, only a Gaussian kernel' \
                              'is supported by this implementation of the CSA.'
        self._kernel = lambda D: (self._params['c'] + self._params['p_center'] *
                                  np.e ** -((D - self._params['mean']) ** 2 /
                                            (2. * self._params['sigma'] ** 2)))

        default_params = {'p_center': 1., 'sigma': self._L / 4.,
                          'mean': 0., 'c': 0.}
        self._params = default_params
        if kernel_params is not None:
            assert kernel_params.keys() == ['sigma'], \
                'Only valid kernel parameter is "sigma".'
            self._params.update(kernel_params)

    def _geometryFunction(self, topologyLayer):
        '''
        This factory returns a CSA-style geometry function for the given layer.
    
        The function returned will return for each CSA-index the position in
        space of the given neuron as a 2- or 3-element list.
    
        Note: This function stores a copy of the neuron positions internally,
              entailing memory overhead.
        '''

        positions = topo.GetPosition(nest.GetLeaves(topologyLayer)[0])

        def geometry_function(idx):
            '''Return position of neuron with given CSA-index.'''

            return positions[idx]

        return geometry_function

    def _reset(self, seed):
        '''
        Seed the PRNG.
        
        Parameters
        ----------
            seed: PRNG seed value. 
        '''

        if seed is None:
            seed = random.randint(0, 10 ** 10)
        seed = 4 * seed # Reduces probability of overlapping seed values.
        random.seed(seed) # CSA uses random.
        rnd.seed(seed + 1) # _build used numpy.ranom.
        nest.SetKernelStatus({'rng_seeds': [seed + 2],
                              'grng_seed': seed + 3})

    def _build(self):
        '''Create populations.'''

        ldict_s = {'elements': 'iaf_neuron', 'positions': [(0., 0.)],
                   'extent': [self._L] * 2, 'edge_wrap': True}
        x = rnd.uniform(-self._L / 2., self._L / 2., self._N)
        y = rnd.uniform(-self._L / 2., self._L / 2., self._N)
        ldict_t = {'elements': 'iaf_neuron', 'positions': zip(x, y),
                   'extent': [self._L] * 2, 'edge_wrap': True}
        self._ls = topo.CreateLayer(ldict_s)
        self._lt = topo.CreateLayer(ldict_t)
        self._driver = topo.FindCenterElement(self._ls)

    def _connect(self):
        '''Connect populations.'''

        g1 = self._geometryFunction(self._ls)
        g2 = self._geometryFunction(self._lt)
        d = csa.euclidMetric2d(g1, g2)
        sigma = self._params['sigma']
        cutoff = self._max_dist
        cs = csa.cset(csa.cross([0], xrange(self._N - 1)) *
                      (csa.random * (csa.gaussian(sigma, cutoff) * d)),
                      1.0, 1.0)
        nest.CGConnect(nest.GetLeaves(self._ls)[0], nest.GetLeaves(self._lt)[0],
                       cs, {'weight': 0, 'delay': 1})

    def _distances(self):
        '''Return distances to all nodes in target population.'''

        return topo.Distance(self._driver, nest.GetLeaves(self._lt)[0])

    def _target_distances(self):
        '''Return distances from source node to connected nodes.'''

        connections = nest.GetConnections(source=self._driver)
        target_nodes = [conn[1] for conn in connections]
        return topo.Distance(self._driver, target_nodes)

    def _positions(self):
        '''Return positions of all nodes.'''

        return [tuple(pos) for pos in
                topo.GetPosition(nest.GetLeaves(self._lt)[0])]

    def _target_positions(self):
        '''Return positions of all connected target nodes.'''

        return [tuple(pos) for pos in
                topo.GetTargetPositions(self._driver, self._lt)[0]]


class Spatial2DTester(NEST_CSA_SpatialTester):
    '''
    Tests for 2D spatially structured networks generated by the
    CSA implementation in NEST.
    '''

    def __init__(self, L, N, kernel_name, kernel_params=None):
        '''
        Construct a test object.

        Parameters
        ----------
            L            : Side length of area / volume.
            N            : Number of nodes.
            kernel_name  : Name of kernel to use.
            kernel_params: Dict with params to update.
            '''

        self._dimensions = 2
        NEST_CSA_SpatialTester.__init__(self, L=L, N=N,
                                        kernel_name=kernel_name,
                                        kernel_params=kernel_params)


class Spatial3DTester(NEST_CSA_SpatialTester):
    '''
    Tests for 3D spatially structured networks generated by the
    CSA implementation in NEST.
    '''

    def __init__(self, *args, **kwargs):
        raise NotImplementedError('CSA does currently not support ' \
                                  'construction of 3D spatial networks.')


if __name__ == '__main__':
    test = Spatial2DTester(L=1.0, N=10000, kernel_name='gaussian')
    ks, p = test.ks_test(control=False, seed=0)
    print 'p-value of KS-test:', p
    z, p = test.z_test(control=False, seed=0)
    print 'p-value of Z-test:', p
    test.show_network()
    test.show_PDF()
    test.show_CDF()

@author: <NAME>  Tests spatially structured networks generated by the CSA implementation in NEST. Tests spatially structured networks generated by the CSA implementation     in NEST. Construct a test object.          Parameters         ----------             L            : Side length of area / volume.             N            : Number of nodes.             kernel_name  : Name of kernel to use.             kernel_params: Dict with params to update. This factory returns a CSA-style geometry function for the given layer.              The function returned will return for each CSA-index the position in         space of the given neuron as a 2- or 3-element list.              Note: This function stores a copy of the neuron positions internally,               entailing memory overhead. Return position of neuron with given CSA-index. Seed the PRNG.                  Parameters         ----------             seed: PRNG seed value. # Reduces probability of overlapping seed values. # CSA uses random. # _build used numpy.ranom. Create populations. Connect populations. Return distances to all nodes in target population. Return distances from source node to connected nodes. Return positions of all nodes. Return positions of all connected target nodes. Tests for 2D spatially structured networks generated by the     CSA implementation in NEST. Construct a test object.          Parameters         ----------             L            : Side length of area / volume.             N            : Number of nodes.             kernel_name  : Name of kernel to use.             kernel_params: Dict with params to update. Tests for 3D spatially structured networks generated by the     CSA implementation in NEST.