Populations
Once the Neuron
objects have been defined, the populations can be created. Let’s suppose we have defined the following rate-coded neuron:
= ann.Neuron(
LeakyIntegratorNeuron = """
parameters tau = 10.0
baseline = -0.2
""",
= """
equations tau * dv/dt + v = baseline + sum(exc)
r = pos(v)
"""
)
Creating populations
Populations of neurons are created using the Population
class:
= ann.Population(geometry=100, neuron=LeakyIntegratorNeuron)
pop1 = ann.Population(geometry=(8, 8), neuron=LeakyIntegratorNeuron, name="pop2") pop2
The rate-coded or spiking nature of the Neuron
instance is irrelevant when creating the Population
object.
It takes different parameters:
geometry
defines the number of neurons in the population, as well as its spatial structure (1D/2D/3D or more). For example, a two-dimensional population with 15*10 neurons takes the argument(15, 10)
, while a one-dimensional array of 100 neurons would take(100,)
or simply100
.neuron
indicates the neuron type to use for this population (which must have been defined before). It requires aNeuron
instance.name
is an unique string for each population in the network. Ifname
is omitted, an internal name such aspop0
will be given (the number is incremented every time a new population is defined). Although this argument is optional, it is strongly recommended to give an understandable name to each population: if you somehow “lose” the reference to thePopulation
object in some portion of your code, you can always retrieve it using theget_population(name)
method.
After creation, each population has several attributes defined (corresponding to the parameters and variables of the Neuron
type) and is assigned a fixed size (pop.size
corresponding to the total number of neurons, here 100 for pop1
and 64 for pop2
) and geometry (pop1.geometry
, here (100, )
and (8, 8)
).
Geometry and ranks
Each neuron in the population has therefore a set of coordinates (expressed relative to pop1.geometry
) and a rank (from 0 to pop1.size -1
). The reason is that spatial coordinates are useful for visualization, or when defining a distance-dependent connection pattern, but that ANNarchy internally uses flat arrays for performance reasons.
The coordinates use the matrix notation for multi-dimensional arrays, which is also used by Numpy (for a 2D matrix, the first index represents the row, the second the column). You can therefore use safely the reshape()
method of Numpy to switch between coordinates-based and rank-based representations of an array.
To convert the rank of a neuron to its coordinates (and vice-versa), you can use the ravel_multi_index and unravel_index methods of Numpy, but they can be quite slow. The Population
class provides two more efficient methods to do this conversion:
coordinates_from_rank
returns a tuple representing the coordinates of neuron based on its rank (between 0 andsize -1
, otherwise an error is thrown).rank_from_coordinates
returns the rank corresponding to the coordinates.
For example, with pop2
having a geometry (8, 8)
:
>>> pop2.coordinates_from_rank(15)
1, 7)
(>>> pop2.rank_from_coordinates((4, 6))
38
Population attributes
The value of the parameters and variables of all neurons in a population can be accessed and modified through population attributes.
With the previously defined populations, you can list all their parameters and variables with:
>>> pop2.attributes
'tau', 'baseline', 'v', 'r']
[>>> pop2.parameters
'tau', 'baseline']
[>>> pop2.variables
'r', 'v'] [
Reading their value is straightforward:
>>> pop2.tau
10.0
>>> pop2.r
0., 0., 0., 0., 0., 0., 0., 0.],
array([[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]) [
Population-wise parameters/variables have a single value for the population, while neuron-specific ones return a NumPy array with the same geometry has the population.
Setting their value is also simple:
>>> pop2.tau = 20.0
>>> pop2.tau
20.0
>>> pop2.r = 1.0
>>> pop2.r
1., 1., 1., 1., 1., 1., 1., 1.],
array([[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1., 1., 1., 1.]])
[ >>> pop2.v = 0.5 * np.ones(pop2.geometry)
0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
array([[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]])
[ >>> pop2.r = Uniform(0.0, 1.0)
0.97931939, 0.64865327, 0.29740417, 0.49352664, 0.36511704,
array([[ 0.59879869, 0.10835491, 0.38481751],
0.07664157, 0.77532887, 0.04773084, 0.75395453, 0.56072342,
[ 0.54139054, 0.28553319, 0.96159595],
0.01811468, 0.30214921, 0.45321071, 0.56728733, 0.24577655,
[ 0.32798484, 0.84929103, 0.63025331],
0.34168482, 0.07411291, 0.6510492 , 0.89025337, 0.31192464,
[ 0.59834719, 0.77102494, 0.88537967],
0.41813573, 0.47395247, 0.46603402, 0.45863676, 0.76628989,
[ 0.42256749, 0.18527079, 0.8322103 ],
0.70616692, 0.73210377, 0.05255718, 0.01939817, 0.24659769,
[ 0.50349528, 0.79201573, 0.19159611],
0.21246111, 0.93570727, 0.68544108, 0.61158741, 0.17954022,
[ 0.90084004, 0.41286698, 0.45550662],
0.14720568, 0.51426136, 0.36225438, 0.06096426, 0.77209455,
[ 0.07348683, 0.43178591, 0.32451531]])
For population-wide attributes, you can only specify a single value (float, int or bool depending on the type of the parameter/variable). For neuron-specific attributes, you can provide either:
- a single value which will be applied to all neurons of the population.
- a list or a one-dimensional Numpy array of the same length as the number of neurons in the population. This information is provided by
pop1.size
. - a Numpy array of the same shape as the geometry of the population. This information is provided by
pop1.geometry
. - a random number generator object (Uniform, Normal...).
If you do not want to use the attributes of Python (for example when doing a loop over unknown attributes), you can also use the get(name)
and set(values)
methods of Population:
'tau')
pop1.get(set({'v': 1.0, 'r': Uniform(0.0, 1.0)}) pop1.
Accessing individual neurons
There exists a purely semantic access to individual neurons of a population. The IndividualNeuron
class wraps population data for a specific neuron. It can be accessed through the Population.neuron()
method using either the rank of the neuron (from 0 to pop1.size - 1
) or its coordinates in the population’s geometry:
>>> print pop2.neuron(2, 2)
with rank 18 (coordinates (2, 2)).
Neuron of the population pop2
Parameters:= 10.0
tau = -0.2
baseline
Variables:= 0.0
v = 0.0 r
The individual neurons can be manipulated individually:
>>> my_neuron = pop2.neuron(2, 2)
>>> my_neuron.r = 1.0
>>> print my_neuron
with rank 18 (coordinates (2, 2)).
Neuron of the population pop2
Parameters:= 10.0
tau = -0.2
baseline
Variables:= 0.0
v = 1.0 r
IndividualNeuron
is only a wrapper for ease of use, the real data is stored in arrays for the whole population, so accessing individual neurons is much slower and should be reserved to specific cases (i.e. only from time to time and for a limited set of neurons).
Accessing groups of neurons
Individual neurons can be grouped into PopulationView
objects, which hold references to different neurons of the same population. One can create population views by “adding” several neurons together:
>>> popview = pop2.neuron(2,2) + pop2.neuron(3,3) + pop2.neuron(4,4)
>>> popview
PopulationView of pop218, 27, 36]
Ranks: [* Neuron of the population pop2 with rank 18 (coordinates (2, 2)).
Parameters:= 10.0
tau = -0.2
baseline
Variables:= 0.0
v = 0.0
r
* Neuron of the population pop2 with rank 27 (coordinates (3, 3)).
Parameters:= 10.0
tau = -0.2
baseline
Variables:= 0.0
v = 0.0
r
* Neuron of the population pop2 with rank 36 (coordinates (4, 4)).
Parameters:= 10.0
tau = -0.2
baseline
Variables:= 0.0
v = 0.0
r >>> popview.r = 1.0
>>> pop2.r
0., 0., 0., 0., 0., 0., 0., 0.],
array([[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]) [
One can also use the slice operators to create PopulationViews:
>>> popview = pop2[3, :]
>>> popview.r = 1.0
>>> pop2.r
0., 0., 0., 0., 0., 0., 0., 0.],
array([[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 1., 1., 1., 1., 1., 1., 1., 1.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]) [
or:
>>> popview = pop2[2:5, 4]
>>> popview.r = 1.0
>>> pop1.r
0., 0., 0., 0., 0., 0., 0., 0.],
array([[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]) [
PopulationView
objects can be used to create projections.
Contrary to the equivalent in PyNN, PopulationViews in ANNarchy can only group neurons from the same population.
Functions
If you have defined a function inside a Neuron
definition:
= ann.Neuron(
LeakyIntegratorNeuron ="""
parameters tau = 10.0
slope = 1.0
baseline = -0.2
""",
= """
equations tau * dv/dt + v = baseline + sum(exc)
r = sigmoid(v, slope)
""",
= """
functions sigmoid(x, k) = 1.0 / (1.0 + exp(-x*k))
"""
)
you can use this function in Python as if it were a method of the corresponding object:
= ann.Population(1000, LeakyIntegratorNeuron)
pop
= np.linspace(-1., 1., 100)
x = np.ones(100)
k = pop.sigmoid(x, k) r
You can pass either a list or a 1D Numpy array to each argument (not a single value, nor a multidimensional array!).
The size of the arrays passed for each argument is arbitrary (it must not match the population’s size) but you have to make sure that they all have the same size. Errors are not catched, so be careful.