Parameter handling¶
Note
these classes are not part of the PyNN API. They should not be used in PyNN scripts, they are intended for implementing backends. You are not required to use them when implementing your own backend, however, as long as your backend conforms to the API.
The main abstractions in PyNN are the population of neurons, and the set of connections (a ‘projection’) between two populations. Setting the parameters of individual neuron and synapse models, therefore, mainly takes place at the level of populations and projections.
Note
it is also possible to set the parameters of neurons and synapses individually, but this is generally less efficient.
Any model parameter in PyNN can be expressed as:
a single value - all neurons in a population or synapses in a projection get the same value
a
RandomDistribution
object - each element gets a value drawn from the random distributiona list/array of values of the same size as the population/projection
a mapping function, where a mapping function accepts a either a single argument
i
(for a population) or two arguments(i, j)
(for a projection) and returns a single value.
A “single value” is usually a single number, but for some parameters (e.g. for spike times) it may be a list/array of numbers.
To handle all these possibilities in a uniform way, and at the same time allow
for efficient parallelization, in the ‘common’ implementation of the PyNN API
all parameter values are converted into LazyArray
objects, and the
set of parameters for a model is contained in a dict
-like object,
ParameterSpace
.
The LazyArray
class¶
LazyArray
is a PyNN-specific sub-class of a more general class,
larray
, and most of its functionality comes from the parent class. Full
documentation for larray
is available in the lazyarray package, but we
give here a quick overview.
LazyArray
has three important features in the context of PyNN:
any operations on the array (potentially including array construction) are not performed immediately, but are delayed until evaluation is specifically requested.
evaluation of only parts of the array is possible, which means that in a parallel simulation with MPI, all processes have the same
LazyArray
for a parameter, but on a given process, only the part of the array which is needed for the neurons/synapses that exist on that process need be evaluated.single often all neurons in a population or synapses in a projection have the same value for a given parameter, a
LazyArray
created from a single value evaluates to that value: the full array is never created unless this is requested.
For example, suppose we have two parameters, tau_m, which is constant, and v_thresh which varies according to the position of the neuron in the population.
>>> from pyNN.parameters import LazyArray
>>> tau_m = 2 * LazyArray(10.0, shape=(20,))
>>> v_thresh = -55 + LazyArray(lambda i: 0.1*i, shape=(20,))
If we evaluate tau_m we get a full, homogeneous array:
>>> tau_m.evaluate()
array([ 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20.,
20., 20., 20., 20., 20., 20., 20., 20., 20.])
but we could also have asked just for the single number, in which case the full array would never be created:
>>> tau_m.evaluate(simplify=True)
20.0
Similarly, we can evaluate v_thresh to get a normal NumPy array:
>>> v_thresh.evaluate()
array([-55. , -54.9, -54.8, -54.7, -54.6, -54.5, -54.4, -54.3, -54.2,
-54.1, -54. , -53.9, -53.8, -53.7, -53.6, -53.5, -53.4, -53.3,
-53.2, -53.1])
but we can also take, for example, only every fifth value, in which case the operation “add -55” only gets performed for those elements.
>>> v_thresh[::5]
array([-55. , -54.5, -54. , -53.5])
In this example the operation is very fast, but with slower operations (e.g. distance calculations) and large arrays, the time savings can be considerable (see lazyarray performance).
In summary, by using LazyArray
, we can pass parameters around in an
optimised way without having to worry about exactly what form the parameter
value takes, hence avoiding a lot of logic at multiple points in the code.
Reference¶
- class LazyArray(value, shape=None, dtype=None)[source]¶
Bases:
larray
- Optimises storage of arrays in various ways:
stores only a single value if all the values in the array are the same
if the array is created from a
RandomDistribution
or a function f(i,j), then elements are only evaluated when they are accessed. Any operations performed on the array are also queued up to be executed on access.
The main intention of the latter is to save memory for very large arrays by accessing them one row or column at a time: the entire array need never be in memory.
- Arguments:
- value:
may be an int, float, bool, NumPy array, iterator, generator or a function, f(i) or f(i,j), depending on the dimensions of the array. f(i,j) should return a single number when i and j are integers, and a 1D array when either i or j or both is a NumPy array (in the latter case the two arrays must have equal lengths).
- shape:
a tuple giving the shape of the array, or None
- dtype:
the NumPy dtype.
- __getitem__(addr: Any) Union[ndarray, int64, float64, int, float] ¶
Return one or more items from the array, as for NumPy arrays.
addr may be a single integer, a slice, a NumPy boolean array or a NumPy integer array.
- by_column(mask=None)[source]¶
Iterate over the columns of the array. Columns will be yielded either as a 1D array or as a single value (for a flat array).
mask: either None or a boolean array indicating which columns should be included.
- apply(f: Union[Callable, ufunc]) None ¶
Add the function f(x) to the list of the operations to be performed, where x will be a scalar or a numpy array.
>>> m = larray(4, shape=(2,2)) >>> m.apply(np.sqrt) >>> m.evaluate() array([[ 2., 2.], [ 2., 2.]])
- check_bounds(addr: Any) None ¶
Check whether the given address is within the array bounds.
- evaluate(simplify: bool = False, empty_val: int = 0) Union[ndarray, float64, bool, int, float] ¶
Return the lazy array as a real NumPy array.
If the array is homogeneous and
simplify
isTrue
, return a single numerical value.
- property is_homogeneous: bool¶
True if all the elements of the array are the same.
- property ncols: int¶
Size of the second dimension (if it exists) of the array.
- property nrows: int¶
Size of the first dimension of the array.
- property shape¶
Shape of the array
- property size: int¶
Total number of elements in the array.
The ParameterSpace
class¶
ParameterSpace
is a dict-like class that contains LazyArray
objects.
In addition to the usual dict
methods, it has several methods
that allow operations on all the lazy arrays within it at once. For example:
>>> from pyNN.parameters import ParameterSpace
>>> ps = ParameterSpace({'a': [2, 3, 5, 8], 'b': 7, 'c': lambda i: 3*i+2}, shape=(4,))
>>> ps['c']
<larray: base_value=<function <lambda> at ...> shape=(4,) dtype=None, operations=[]>
>>> ps.evaluate()
>>> ps['c']
array([ 2, 5, 8, 11])
the evaluate()
method also accepts a mask, in order to evaluate only
part of the lazy arrays:
>>> ps = ParameterSpace({'a': [2, 3, 5, 8, 13], 'b': 7, 'c': lambda i: 3*i+2}, shape=(5,))
>>> ps.evaluate(mask=[1, 3, 4])
>>> ps.as_dict()
{'a': array([ 3, 8, 13]), 'c': array([ 5, 11, 14]), 'b': array([7, 7, 7])}
An example with two-dimensional arrays:
>>> ps2d = ParameterSpace({'a': [[2, 3, 5, 8, 13], [21, 34, 55, 89, 144]],
... 'b': 7,
... 'c': lambda i, j: 3*i-2*j}, shape=(2, 5))
>>> ps2d.evaluate(mask=(slice(None), [1, 3, 4]))
>>> print(ps2d['a'])
[[ 3 8 13]
[ 34 89 144]]
>>> print(ps2d['c'])
[[-2 -6 -8]
[ 1 -3 -5]]
There are also several methods to allow iterating over the parameter space in
different ways. A ParameterSpace
can be viewed as both a dict
contaning arrays and as an array of dicts. Iterating over a parameter space
gives the latter view:
>>> for D in ps:
... print(D)
...
{'a': 3, 'c': 5, 'b': 7}
{'a': 8, 'c': 11, 'b': 7}
{'a': 13, 'c': 14, 'b': 7}
unlike for a dict
, where iterating over it gives the keys.
items()
works as for a normal dict
:
>>> for key, value in ps.items():
... print(key, "=", value)
a = [ 3 8 13]
c = [ 5 11 14]
b = [7 7 7]
Reference¶
- class ParameterSpace(parameters, schema=None, shape=None, component=None)[source]¶
Representation of one or more points in a parameter space.
i.e. represents one or more parameter sets, where each parameter set has the same parameter names and types but the parameters may have different values.
- Arguments:
- parameters:
a dict containing values of any type that may be used to construct a lazy array, i.e. int, float, NumPy array,
RandomDistribution
, function that accepts a single argument.- schema:
a dict whose keys are the expected parameter names and whose values are the expected parameter types
- component:
optional - class for which the parameters are destined. Used in error messages.
- shape:
the shape of the lazy arrays that will be constructed.
- __iter__()[source]¶
Return an array-element-wise iterator over the parameter space.
Each item in the iterator is a dict, containing the same keys as the
ParameterSpace
. For the ith dict returned by the iterator, each value is the ith element of the corresponding lazy array in the parameter space.Example:
>>> ps = ParameterSpace({'a': [2, 3, 5, 8], 'b': 7, 'c': lambda i: 3*i+2}, shape=(4,)) >>> ps.evaluate() >>> for D in ps: ... print(D) ... {'a': 2, 'c': 2, 'b': 7} {'a': 3, 'c': 5, 'b': 7} {'a': 5, 'c': 8, 'b': 7} {'a': 8, 'c': 11, 'b': 7}
- property shape¶
Size of the lazy arrays contained within the parameter space
- items() an iterator over the (key, value) items of PS. [source]¶
Note that the values will all be
LazyArray
objects.
- update(**parameters)[source]¶
Update the contents of the parameter space according to the (key, value) pairs in
**parameters
. All values will be turned into lazy arrays.If the
ParameterSpace
has a schema, the keys and the data types of the values will be checked against the schema.
- pop(name, d=None)[source]¶
Remove the given parameter from the parameter set and from its schema, and return its value.
- property is_homogeneous¶
True if all of the lazy arrays within are homogeneous.
- evaluate(mask=None, simplify=False)[source]¶
Evaluate all lazy arrays contained in the parameter space, using the given mask.
- as_dict()[source]¶
Return a plain dict containing the same keys and values as the parameter space. The values must first have been evaluated.
- property parallel_safe¶
- property has_native_rngs¶
Return True if the parameter set contains any NativeRNGs
The Sequence
class¶
- class Sequence(value)[source]¶
Represents a sequence of numerical values.
- Arguments:
- value:
anything which can be converted to a NumPy array, or another
Sequence
object.
- __mul__(val)¶
Return a new
ArrayParameter
in which all values in the originalArrayParameter
have been multiplied by val.If val is itself an array, return an array of
ArrayParameter
objects, where ArrayParameter i is the original ArrayParameter multiplied by element i of val.
- __div__(val)¶
Return a new
ArrayParameter
in which all values in the originalArrayParameter
have been divided by val.If val is itself an array, return an array of
ArrayParameter
objects, where ArrayParameter i is the original ArrayParameter divided by element i of val.