Changeset 727

Show
Ignore:
Timestamp:
03/11/10 15:25:14 (2 years ago)
Author:
pierre
Message:

New implementation of the connector class, which is fully operationnal. All tests are passed, and now, a generic implementation allows to use distance dependent expressions for all the connectors (except OneToOne? ?). They are also much faster, computing only local computations, and all provides therefore a good scaling with MPI. In addition, a safe flag has been added to all the Connector: on by default, user can turn it off to avoid tests on weights and delays. Again, if really someone need to do a big network and knows what he is bulding, then all those superfical tests can be turned off

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/src/connectors2.py

    r713 r727  
    11import numpy 
    2 from pyNN import common, core 
     2from pyNN import errors, common, core, random 
    33from pyNN.space import Space 
    44from pyNN.random import RandomDistribution 
     
    77class ConnectionAttributeGenerator(object): 
    88     
    9     def __init__(self, source, local_mask): 
    10         self.source = source 
     9    def __init__(self, source, local_mask, safe=True): 
     10        self.source     = source 
    1111        self.local_mask = local_mask 
     12        self.safe       = safe 
    1213        if isinstance(self.source, numpy.ndarray): 
    1314            self.source_iterator = iter(self.source) 
    1415     
    15     def get(self, connectivity_matrix, distance_matrix): 
    16         local_value_mask = self.local_mask[connectivity_matrix] 
    17         if numpy.isscalar(self.source): 
    18             return numpy.ones((local_value_mask.sum(),))*self.source 
     16    def get(self, N, distance_matrix=None, sub_mask=None): 
     17        if isinstance(self.source, basestring): 
     18            assert distance_matrix is not None 
     19            d      = distance_matrix.as_array(sub_mask) 
     20            values = eval(self.source) 
     21            return values 
     22        elif numpy.isscalar(self.source): 
     23            if sub_mask is None: 
     24                values = numpy.ones((self.local_mask.sum(),))*self.source 
     25            else: 
     26                values = numpy.ones((len(sub_mask),))*self.source 
     27            return values 
    1928        elif isinstance(self.source, RandomDistribution): 
    20             all_values = self.source.next(connectivity_matrix.sum(), 
    21                                           mask_local=False) 
    22             local_values = all_values[local_value_mask] 
    23             return local_values 
     29            values = self.source.next(N, mask_local=self.local_mask) 
     30            if sub_mask is not None: 
     31                values = values[sub_mask] 
     32            return values 
    2433        elif isinstance(self.source, numpy.ndarray): 
    2534            source_row = self.source_iterator.next() 
    26             assert source_row.shape == connectivity_matrix.shape 
    27             return source_row[local_value_mask] 
    28         elif isinstance(self.source, basestring): 
    29             d = distance_matrix.as_array(mask=local_value_mask) 
    30             values = eval(self.source) 
     35            values     = source_row[self.local_mask] 
     36            if sub_mask: 
     37                values = values[sub_mask] 
    3138            return values 
    3239 
    3340 
    3441class WeightGenerator(ConnectionAttributeGenerator): 
     42     
     43    def __init__(self, source, local_mask, projection, safe=True): 
     44      ConnectionAttributeGenerator.__init__(self, source, local_mask, safe) 
     45      self.projection     = projection 
     46      self.is_conductance = common.is_conductance(projection.post.local_cells[0]) 
     47 
     48    def check(self, weight): 
     49        if weight is None: 
     50            weight = DEFAULT_WEIGHT 
     51        if core.is_listlike(weight): 
     52            weight     = numpy.array(weight) 
     53            nan_filter = (1-numpy.isnan(weight)).astype(bool) # weight arrays may contain NaN, which should be ignored 
     54            filtered_weight = weight[nan_filter] 
     55            all_negative = (filtered_weight<=0).all() 
     56            all_positive = (filtered_weight>=0).all() 
     57            if not (all_negative or all_positive): 
     58                raise errors.InvalidWeightError("Weights must be either all positive or all negative") 
     59        elif numpy.isscalar(weight): 
     60            all_positive = weight >= 0 
     61            all_negative = weight < 0 
     62        else: 
     63            raise Exception("Weight must be a number or a list/array of numbers.") 
     64        if self.is_conductance or self.projection.synapse_type == 'excitatory': 
     65            if not all_positive: 
     66                raise errors.InvalidWeightError("Weights must be positive for conductance-based and/or excitatory synapses") 
     67        elif self.is_conductance==False and self.projection.synapse_type == 'inhibitory': 
     68            if not all_negative: 
     69                raise errors.InvalidWeightError("Weights must be negative for current-based, inhibitory synapses") 
     70        else: # is_conductance is None. This happens if the cell does not exist on the current node. 
     71            logger.debug("Can't check weight, conductance status unknown.") 
     72        return weight 
     73 
     74    def get(self, N, distance_matrix=None, sub_mask=None): 
     75      values = ConnectionAttributeGenerator.get(self, N, distance_matrix, sub_mask)       
     76      if self.safe: 
     77          values = self.check(values) 
     78      return values 
     79 
     80 
     81class DelayGenerator(ConnectionAttributeGenerator): 
     82 
     83    def __init__(self, source, local_mask, safe=True): 
     84        ConnectionAttributeGenerator.__init__(self, source, local_mask, safe) 
     85        self.min_delay = common.get_min_delay() 
     86        self.max_delay = common.get_max_delay() 
     87 
     88    def check(self, delay): 
     89        all_negative = (delay<=self.max_delay).all() 
     90        all_positive = (delay>=self.min_delay).all()# If the delay is too small , we have to throw an error 
     91        if not (all_negative and all_positive): 
     92            raise errors.ConnectionError("delay (%s) is out of range [%s,%s]" % (delay, common.get_min_delay(), common.get_max_delay())) 
     93        return delay 
     94 
     95    def get(self, N, distance_matrix=None, sub_mask=None): 
     96        values = ConnectionAttributeGenerator.get(self, N, distance_matrix, sub_mask) 
     97        if self.safe: 
     98            values = self.check(values) 
     99        return values 
     100 
     101 
     102class ProbaGenerator(ConnectionAttributeGenerator): 
    35103    pass 
    36104 
    37 class DelayGenerator(ConnectionAttributeGenerator): 
    38     pass 
    39  
    40105 
    41106class DistanceMatrix(object): 
    42107     
    43     def __init__(self, A, B, space): 
    44         assert A.shape == (3,) 
     108    def __init__(self, B, space, mask=None): 
    45109        assert B.shape[0] == 3 
    46110        self.space = space 
     111        if mask is not None: 
     112            self.B = ((B.T)[mask]).T 
     113        else: 
     114            self.B = B 
     115         
     116    def as_array(self, sub_mask=None): 
     117        if self._distance_matrix is None and self.A is not None: 
     118            if sub_mask is None: 
     119                self._distance_matrix = self.space.distances(self.A, self.B)[0] 
     120            else: 
     121                self._distance_matrix = self.space.distances(self.A, self.B[:,sub_mask])[0] 
     122        return self._distance_matrix 
     123         
     124    def set_source(self, A): 
     125        assert A.shape == (3,) 
    47126        self.A = A 
    48         self.B = B 
    49         self._distance_matrix = None 
    50         self._last_mask = None 
    51          
    52     def as_array(self, mask=None): 
    53         if self._distance_matrix is None or mask != self._last_mask: 
    54             if mask is not None and mask.sum() < mask.size: 
    55                 local_B = ((self.B.T)[mask]).T 
    56             else: 
    57                 local_B = self.B 
    58             self._distance_matrix = self.space.distances(self.A, local_B)[0] 
    59             self._last_mask = mask 
    60         return self._distance_matrix 
    61          
     127        self._distance_matrix = None         
     128 
    62129 
    63130class Connector(object): 
    64131     
    65     def __init__(self, weights=0.0, delays=None): 
     132    def __init__(self, weights=0.0, delays=None, space=Space(), safe=True): 
    66133        self.weights = weights 
    67         min_delay = common.get_min_delay() 
     134        self.space   = space 
     135        self.safe    = safe 
     136        min_delay    = common.get_min_delay() 
    68137        if delays is None: 
    69138            self.delays = min_delay 
     
    78147        pass 
    79148     
    80      
     149 
     150 
     151class ProbabilisticConnector(Connector): 
     152     
     153    def __init__(self, projection, weights=0.0, delays=None, allow_self_connections=True, space=Space(), safe=True): 
     154 
     155        Connector.__init__(self, weights, delays, space, safe) 
     156        if isinstance(projection.rng, random.NativeRNG): 
     157            raise Exception("Use of NativeRNG not implemented.") 
     158        else: 
     159            self.rng = projection.rng 
     160         
     161        self.local             = projection.post._mask_local.flatten() 
     162        self.N                 = projection.post.size 
     163        self.weights_generator = WeightGenerator(weights, self.local, projection, safe) 
     164        self.delays_generator  = DelayGenerator(delays, self.local, safe) 
     165        self.probas_generator  = ProbaGenerator(RandomDistribution('uniform',(0,1), rng=self.rng), self.local) 
     166        self.distance_matrix   = DistanceMatrix(projection.post.positions, self.space, self.local) 
     167        self.projection        = projection 
     168        self.allow_self_connections = allow_self_connections 
     169 
     170    def _probabilistic_connect(self, src, p): 
     171        """ 
     172        Connect-up a Projection with connection probability p, where p may be either 
     173        a float 0<=p<=1, or a dict containing a float array for each pre-synaptic 
     174        cell, the array containing the connection probabilities for all the local 
     175        targets of that pre-synaptic cell. 
     176        """ 
     177        if numpy.isscalar(p) and p == 1: 
     178            create = numpy.arange(self.local.sum()) 
     179        else: 
     180            rarr   = self.probas_generator.get(self.N, self.distance_matrix) 
     181            create = numpy.where(rarr < p)[0] 
     182         
     183        if isinstance(self.weights, basestring) or isinstance(self.delays, basestring):       
     184            self.distance_matrix.set_source(src.position) 
     185         
     186        targets = self.projection.post.local_cells[create] 
     187        weights = self.weights_generator.get(self.N, self.distance_matrix, create) 
     188        delays  = self.delays_generator.get(self.N, self.distance_matrix, create) 
     189             
     190        if not self.allow_self_connections and self.projection.pre == self.projection.post and src in targets: 
     191            i       = numpy.where(targets == src)[0] 
     192            weights = numpy.delete(weights, i) 
     193            delays  = numpy.delete(delays, i) 
     194            targets = numpy.delete(targets, i) 
     195         
     196        if len(targets) > 0: 
     197            self.projection.connection_manager.connect(src, targets.tolist(), weights, delays) 
     198 
    81199     
    82200     
     
    87205    """ 
    88206     
    89     def __init__(self, allow_self_connections=True, 
    90                  weights=0.0, delays=None, space=Space()): 
     207    def __init__(self, allow_self_connections=True, weights=0.0, delays=None, space=Space(), safe=True): 
    91208        """ 
    92209        Create a new connector. 
     
    104221                   dependent weights or delays 
    105222        """ 
    106         Connector.__init__(self, weights, delays) 
     223        Connector.__init__(self, weights, delays, space, safe) 
    107224        assert isinstance(allow_self_connections, bool) 
    108225        self.allow_self_connections = allow_self_connections 
    109         self.space = space 
    110          
    111          
    112     def connect(self, projection):        
    113         local = projection.post._mask_local.flatten() 
    114         weight_generator = WeightGenerator(self.weights, local) 
    115         delay_generator = DelayGenerator(self.delays, local) 
    116          
    117         global_target_mask = numpy.ones((projection.post.size,), dtype=bool) 
    118         targets = projection.post.local_cells.tolist() 
    119         if len(targets) > 0: 
    120             for src in projection.pre.all(): 
    121                 distance_matrix = DistanceMatrix(src.position, 
    122                                                  projection.post.positions, 
    123                                                  space=self.space) 
    124                 weights = weight_generator.get(global_target_mask, distance_matrix) 
    125                 delays = delay_generator.get(global_target_mask, distance_matrix) 
    126                 projection.connection_manager.connect(src, targets, weights, delays) 
     226         
     227    def connect(self, projection): 
     228        connector = ProbabilisticConnector(projection, self.weights, self.delays, self.allow_self_connections, self.space, safe=self.safe) 
     229        for src in projection.pre.all(): 
     230            connector._probabilistic_connect(src, 1) 
    127231 
    128232     
     
    133237    """ 
    134238     
    135     def __init__(self, p_connect, allow_self_connections=True, 
    136                  weights=0.0, delays=None, space=Space()): 
     239    def __init__(self, p_connect, allow_self_connections=True, weights=0.0, delays=None, space=Space(), safe=True): 
    137240        """ 
    138241        Create a new connector. 
     
    152255                   dependent weights or delays 
    153256        """ 
    154         Connector.__init__(self, weights, delays) 
     257        Connector.__init__(self, weights, delays, space, safe) 
    155258        assert isinstance(allow_self_connections, bool) 
    156259        self.allow_self_connections = allow_self_connections 
    157260        self.p_connect = float(p_connect) 
    158         self.space = space 
    159261        assert 0 <= self.p_connect 
    160262         
    161263    def connect(self, projection): 
    162         assert projection.rng.parallel_safe 
    163         p = self.p_connect 
    164         local = projection.post._mask_local.flatten() 
    165         weight_generator = WeightGenerator(self.weights, local) 
    166         delay_generator = DelayGenerator(self.delays, local) 
     264        #assert projection.rng.parallel_safe 
     265        connector = ProbabilisticConnector(projection, self.weights, self.delays, self.allow_self_connections, self.space, safe=self.safe) 
    167266        for src in projection.pre.all(): 
    168              
    169             N = projection.post.size 
    170             rarr = projection.rng.next(N, 'uniform', (0,1), mask_local=False) 
    171             if not core.is_listlike(rarr) and numpy.isscalar(rarr): # if N=1, rarr will be a single number 
    172                 rarr = numpy.array([rarr]) 
    173             global_target_mask = rarr < p 
    174             local_target_mask = global_target_mask[local] 
    175             targets = projection.post.local_cells[local_target_mask].tolist() 
    176              
    177             if len(targets) > 0: 
    178                 distance_matrix = DistanceMatrix(src.position, 
    179                                                  projection.post.positions, 
    180                                                  space=self.space) 
    181                 weights = weight_generator.get(global_target_mask, distance_matrix) 
    182                 delays = delay_generator.get(global_target_mask, distance_matrix) 
    183                 projection.connection_manager.connect(src, targets, weights, delays) 
    184  
     267            connector._probabilistic_connect(src, self.p_connect) 
     268 
     269 
     270class DistanceDependentProbabilityConnector(ProbabilisticConnector): 
     271    """ 
     272    For each pair of pre-post cells, the connection probability depends on distance. 
     273    """ 
     274     
     275    def __init__(self, d_expression, allow_self_connections=True, 
     276                 weights=0.0, delays=None, space=Space(), safe=True): 
     277        """ 
     278        Create a new connector. 
     279        , projection 
     280        `d_expression` -- the right-hand side of a valid python expression for 
     281            probability, involving 'd', e.g. "exp(-abs(d))", or "d<3" 
     282        `space` -- a Space object. 
     283        `weights` -- may either be a float, a RandomDistribution object, a list/ 
     284                     1D array with at least as many items as connections to be 
     285                     created, or a DistanceDependence object. Units nA. 
     286        `delays`  -- as `weights`. If `None`, all synaptic delays will be set 
     287                     to the global minimum delay. 
     288        """ 
     289        Connector.__init__(self, weights, delays, space, safe) 
     290        assert isinstance(d_expression, str) 
     291        try: 
     292            d = 0; assert 0 <= eval(d_expression), eval(d_expression) 
     293            d = 1e12; assert 0 <= eval(d_expression), eval(d_expression) 
     294        except ZeroDivisionError, err: 
     295            raise ZeroDivisionError("Error in the distance expression %s. %s" % (d_expression, err)) 
     296        self.d_expression = d_expression 
     297        assert isinstance(allow_self_connections, bool) 
     298        self.allow_self_connections = allow_self_connections 
     299         
     300    def connect(self, projection): 
     301        """Connect-up a Projection.""" 
     302        connector       = ProbabilisticConnector(projection, self.weights, self.delays, self.allow_self_connections, self.space, safe=self.safe) 
     303        proba_generator = ProbaGenerator(self.d_expression, connector.local) 
     304 
     305        for src in projection.pre.all():      
     306            connector.distance_matrix.set_source(src.position) 
     307            proba  = proba_generator.get(connector.N, connector.distance_matrix) 
     308            if proba.dtype == 'bool': 
     309                proba = proba.astype(float) 
     310            connector._probabilistic_connect(src, proba) 
     311 
     312 
     313class FromListConnector(Connector): 
     314    """ 
     315    Make connections according to a list. 
     316    """ 
     317     
     318    def __init__(self, conn_list): 
     319        """ 
     320        Create a new connector. 
     321         
     322        `conn_list` -- a list of tuples, one tuple for each connection. Each 
     323                       tuple should contain: 
     324                          (pre_addr, post_addr, weight, delay) 
     325                       where pre_addr is the address (a tuple) of the presynaptic 
     326                       neuron, and post_addr is the address of the postsynaptic 
     327                       neuron. 
     328        """ 
     329        # needs extending for dynamic synapses. 
     330        Connector.__init__(self, 0., common.get_min_delay()) 
     331        self.conn_list = conn_list         
     332         
     333    def connect(self, projection): 
     334        """Connect-up a Projection.""" 
     335        # slow: should maybe sort by pre 
     336        for i in xrange(len(self.conn_list)): 
     337            src, tgt, weight, delay = self.conn_list[i][:] 
     338            src = projection.pre[tuple(src)]            
     339            tgt = projection.post[tuple(tgt)] 
     340            projection.connection_manager.connect(src, [tgt], weight, delay) 
     341 
     342 
     343class FromFileConnector(FromListConnector): 
     344    """ 
     345    Make connections according to a list read from a file. 
     346    """ 
     347     
     348    def __init__(self, filename, distributed=False): 
     349        """ 
     350        Create a new connector. 
     351         
     352        `filename` -- name of a text file containing a list of connections, in 
     353                      the format required by `FromListConnector`. 
     354        `distributed` -- if this is True, then each node will read connections 
     355                         from a file called `filename.x`, where `x` is the MPI 
     356                         rank. This speeds up loading connections for 
     357                         distributed simulations. 
     358        """ 
     359        Connector.__init__(self, 0., common.get_min_delay()) 
     360        self.filename = filename 
     361        self.distributed = distributed 
     362 
     363    def connect(self, projection): 
     364        """Connect-up a Projection.""" 
     365        if self.distributed: 
     366            self.filename += ".%d" % common.rank() 
     367        # open the file... 
     368        f = open(self.filename, 'r', 10000) 
     369        lines = f.readlines() 
     370        f.close() 
     371        # gather all the data in a list of tuples (one per line) 
     372        input_tuples = [] 
     373        for line in lines: 
     374            single_line = line.rstrip() 
     375            src, tgt, w, d = single_line.split("\t", 4) 
     376            src = "[%s" % src.split("[",1)[1] 
     377            tgt = "[%s" % tgt.split("[",1)[1] 
     378            input_tuples.append((eval(src), eval(tgt), float(w), float(d))) 
     379        self.conn_list = input_tuples 
     380        FromListConnector.connect(self, projection) 
     381 
     382 
     383 
     384class FixedNumberPostConnector(Connector): 
     385    """ 
     386    Each pre-synaptic neuron is connected to exactly n post-synaptic neurons 
     387    chosen at random. 
     388     
     389    If n is less than the size of the post-synaptic population, there are no 
     390    multiple connections, i.e., no instances of the same pair of neurons being 
     391    multiply connected. If n is greater than the size of the post-synaptic 
     392    population, all possible single connections are made before starting to add 
     393    duplicate connections. 
     394    """ 
     395     
     396    def __init__(self, n, allow_self_connections=True, weights=0.0, delays=None, space=Space(), safe=True): 
     397        """ 
     398        Create a new connector. 
     399         
     400        `n` -- either a positive integer, or a `RandomDistribution` that produces 
     401               positive integers. If `n` is a `RandomDistribution`, then the 
     402               number of post-synaptic neurons is drawn from this distribution 
     403               for each pre-synaptic neuron. 
     404        `allow_self_connections` -- if the connector is used to connect a 
     405               Population to itself, this flag determines whether a neuron is 
     406               allowed to connect to itself, or only to other neurons in the 
     407               Population. 
     408        `weights` -- may either be a float, a RandomDistribution object, a list/ 
     409                     1D array with at least as many items as connections to be 
     410                     created. Units nA. 
     411        `delays`  -- as `weights`. If `None`, all synaptic delays will be set 
     412                     to the global minimum delay. 
     413        """ 
     414        Connector.__init__(self, weights, delays, space, safe) 
     415        assert isinstance(allow_self_connections, bool) 
     416        self.allow_self_connections = allow_self_connections 
     417        if isinstance(n, int): 
     418            self.n = n 
     419            assert n >= 0 
     420        elif isinstance(n, random.RandomDistribution): 
     421            self.rand_distr = n 
     422            # weak check that the random distribution is ok 
     423            assert numpy.all(numpy.array(n.next(100)) >= 0), "the random distribution produces negative numbers" 
     424        else: 
     425            raise Exception("n must be an integer or a RandomDistribution object") 
     426         
     427    def connect(self, projection): 
     428        """Connect-up a Projection."""         
     429        local             = projection.post._mask_local.flatten() 
     430        weights_generator = WeightGenerator(self.weights, local, projection, self.safe) 
     431        delays_generator  = DelayGenerator(self.delays, local, self.safe) 
     432        distance_matrix   = DistanceMatrix(projection.post.positions, self.space, local) 
     433        candidates        = projection.post.all_cells.flatten()             
     434         
     435        if isinstance(projection.rng, random.NativeRNG): 
     436            raise Exception("Use of NativeRNG not implemented.")         
     437         
     438        for src in projection.pre.all(): 
     439            # pick n neurons at random 
     440            if hasattr(self, 'rand_distr'): 
     441                n = self.rand_distr.next() 
     442            else: 
     443                n = self.n 
     444             
     445            targets     = numpy.zeros(0, int) 
     446            loc_targets = [] 
     447            create      = [] 
     448             
     449            while len(targets) < n: # if the number of requested cells is larger than the size of the 
     450                                    # postsynaptic population, we allow multiple connections for a given cell                 
     451                ids     = projection.rng.permutation(candidates)[0:n] - projection.post.first_id 
     452                targets = numpy.concatenate((targets, candidates[ids.astype(int)]))                  
     453                if not self.allow_self_connections and projection.pre == projection.post:                 
     454                    idx     = numpy.where(targets == src)[0] 
     455                    targets = numpy.delete(targets, idx) 
     456             
     457            targets = targets[:n]             
     458            if isinstance(self.weights, basestring) or isinstance(self.delays, basestring):       
     459                distance_matrix.set_source(src.position) 
     460             
     461            # We need to keep only the local cells, because then distances will be computed only 
     462            # by the nodes that will established the connections. create is just a mask with all  
     463            # the id of the cells that are local and that need to be used. Same id could be repeted 
     464            # but this is not a problem. 
     465            for id in targets: 
     466                pos = numpy.where(id == projection.post.local_cells)[0] 
     467                N   = len(pos) 
     468                if N > 0: 
     469                    loc_targets += N*[id] 
     470                    create      += [pos[0]] 
     471             
     472            weights = weights_generator.get(n, distance_matrix, create) 
     473            delays  = delays_generator.get(n, distance_matrix, create)             
     474                         
     475            if len(loc_targets) > 0: 
     476                projection.connection_manager.connect(src, loc_targets, weights, delays) 
     477                     
     478 
     479class FixedNumberPreConnector(Connector): 
     480    """ 
     481    Each post-synaptic neuron is connected to exactly n pre-synaptic neurons 
     482    chosen at random. 
     483     
     484    If n is less than the size of the pre-synaptic population, there are no 
     485    multiple connections, i.e., no instances of the same pair of neurons being 
     486    multiply connected. If n is greater than the size of the pre-synaptic 
     487    population, all possible single connections are made before starting to add 
     488    duplicate connections. 
     489    """ 
     490     
     491    def __init__(self, n, allow_self_connections=True, weights=0.0, delays=None, space=Space(), safe=True): 
     492        """ 
     493        Create a new connector. 
     494         
     495        `n` -- either a positive integer, or a `RandomDistribution` that produces 
     496               positive integers. If `n` is a `RandomDistribution`, then the 
     497               number of pre-synaptic neurons is drawn from this distribution 
     498               for each post-synaptic neuron. 
     499        `allow_self_connections` -- if the connector is used to connect a 
     500            Population to itself, this flag determines whether a neuron is 
     501            allowed to connect to itself, or only to other neurons in the 
     502            Population. 
     503        `weights` -- may either be a float, a RandomDistribution object, a list/ 
     504                     1D array with at least as many items as connections to be 
     505                     created. Units nA. 
     506        `delays`  -- as `weights`. If `None`, all synaptic delays will be set 
     507                     to the global minimum delay. 
     508        """ 
     509        Connector.__init__(self, weights, delays, space, safe) 
     510        assert isinstance(allow_self_connections, bool) 
     511        self.allow_self_connections = allow_self_connections 
     512        if isinstance(n, int): 
     513            self.n = n 
     514            assert n >= 0 
     515        elif isinstance(n, random.RandomDistribution): 
     516            self.rand_distr = n 
     517            # weak check that the random distribution is ok 
     518            assert numpy.all(numpy.array(n.next(100)) >= 0), "the random distribution produces negative numbers" 
     519        else: 
     520            raise Exception("n must be an integer or a RandomDistribution object") 
     521 
     522    def connect(self, projection): 
     523        """Connect-up a Projection.""" 
     524        local             = numpy.arange(len(projection.post))         
     525        weights_generator = WeightGenerator(self.weights, local, projection, self.safe) 
     526        delays_generator  = DelayGenerator(self.delays, local, self.safe) 
     527        distance_matrix   = DistanceMatrix(projection.pre.positions, self.space) 
     528        candidates        = projection.pre.all_cells.flatten()                         
     529         
     530        if isinstance(projection.rng, random.NativeRNG): 
     531            raise Exception("Warning: use of NativeRNG not implemented.") 
     532             
     533        for tgt in projection.post.local_cells.flat: 
     534            # pick n neurons at random 
     535            if hasattr(self, 'rand_distr'): 
     536                n = self.rand_distr.next() 
     537            else: 
     538                n = self.n 
     539             
     540            sources = [] 
     541            while len(sources) < n: # if the number of requested cells is larger than the size of the 
     542                                    # presynaptic population, we allow multiple connections for a given cell 
     543                ids     = projection.rng.permutation(candidates)[0:n] - projection.pre.first_id 
     544                sources = numpy.concatenate((sources, candidates[ids.astype(int)]))  
     545                if not self.allow_self_connections and projection.pre == projection.post: 
     546                    i       = numpy.where(sources == tgt)[0] 
     547                    sources = numpy.delete(sources, i) 
     548             
     549            sources = sources[:n].astype(int) 
     550             
     551            if isinstance(self.weights, basestring) or isinstance(self.delays, basestring):       
     552                distance_matrix.set_source(tgt.position) 
     553             
     554            create  = sources - projection.pre.first_id 
     555            weights = weights_generator.get(n, distance_matrix, create) 
     556            delays  = delays_generator.get(n, distance_matrix, create)             
     557                                             
     558            for src, w, d in zip(sources, weights, delays): 
     559                projection.connection_manager.connect(src, [tgt], w, d) 
     560                     
     561 
     562class OneToOneConnector(Connector): 
     563    """ 
     564    Where the pre- and postsynaptic populations have the same size, connect 
     565    cell i in the presynaptic population to cell i in the postsynaptic 
     566    population for all i. 
     567    """ 
     568    #In fact, despite the name, this should probably be generalised to the 
     569    #case where the pre and post populations have different dimensions, e.g., 
     570    #cell i in a 1D pre population of size n should connect to all cells 
     571    #in row i of a 2D post population of size (n,m). 
     572     
     573     
     574    def __init__(self, weights=0.0, delays=None, space=Space()): 
     575        """ 
     576        Create a new connector. 
     577         
     578        `weights` -- may either be a float, a RandomDistribution object, a list/ 
     579                     1D array with at least as many items as connections to be 
     580                     created. Units nA. 
     581        `delays`  -- as `weights`. If `None`, all synaptic delays will be set 
     582                     to the global minimum delay. 
     583        """ 
     584        Connector.__init__(self, weights, delays, space) 
     585     
     586    def connect(self, projection): 
     587        """Connect-up a Projection."""         
     588        if projection.pre.dim == projection.post.dim: 
     589            N                 = projection.post.size 
     590            local             = projection.post._mask_local.flatten() 
     591            weights_generator = WeightGenerator(self.weights, local, projection) 
     592            delays_generator  = DelayGenerator(self.delays, local)                 
     593            weights           = weights_generator.get(N) 
     594            delays            = delays_generator.get(N) 
     595             
     596            for tgt, w, d in zip(projection.post.local_cells, weights, delays): 
     597                src = projection.pre.index(projection.post.id_to_index(tgt)) 
     598                 
     599                # the float is in case the values are of type numpy.float64, which NEST chokes on 
     600                projection.connection_manager.connect(src, [tgt], float(w), float(d)) 
     601        else: 
     602            raise errors.InvalidDimensionsError("OneToOneConnector does not support presynaptic and postsynaptic Populations of different sizes.")