===================================
Model parameters and initial values
===================================

As was discussed in :doc:`building_networks`, PyNN deals with neurons, and with
the synaptic connections between them, principally at the level of groups:
with :class:`Population` and :class:`Assembly` for neurons and
:class:`Projection` for connections.

Setting the parameters of neurons and connections is also done principally at
the group level, either when creating the group, or after creation using the
:meth:`set()` method. Sometimes, all the neurons in a :class:`Population` or all
the connections in a :class:`Projection` should have the same value. Other
times, different individual cells or connections should have different parameter
values. To handle both of these situations, parameter values may be of four
different types:

* a single number - sets the same value for all cells in the :class:`Population`
  or connections in the :class:`Projection`

* a :class:`RandomDistribution` object (see :doc:`random_numbers`) - each item
  in the group will have the parameter set to a value drawn from the
  distribution

* a list or 1D NumPy array - of the same size as the :class:`Population` or the
  number of connections in the :class:`Projection`

* a function - for a :class:`Population` or :class:`Assembly` the function
  should take a single integer argument, and will be called with the index of
  every neuron in the :class:`Population` to return the parameter value for that
  neuron. For a :class:`Projection`, the function should take two integer
  arguments, and for every connection will be called with the indices of the
  pre- and post-synaptic neurons.

Examples
========

.. testsetup::

   from pyNN.mock import Population, IF_cond_exp, HH_cond_exp, SpikeSourcePoisson, IF_cond_alpha


Setting the same value for all neurons in a population
------------------------------------------------------

.. doctest::

    >>> p = Population(5, IF_cond_exp(tau_m=15.0))

or, equivalently:

.. doctest::

    >>> p = Population(5, IF_cond_exp())
    >>> p.set(tau_m=15.0)

.. note: for some backend simulators it is more efficient to set parameters on
         :class:`population` creation, rather than using the :meth:`set()` method.

To set values for a subset of the population, use a view:

.. doctest::

    >>> p[0,2,4].set(tau_m=10.0)
    >>> p.get('tau_m')
    array([ 10.,  15.,  10.,  15.,  10.])


Setting parameters to random values
-----------------------------------

.. doctest::

    >>> from pyNN.random import RandomDistribution, NumpyRNG
    >>> gbar_na_distr = RandomDistribution('normal', (20.0, 2.0), rng=NumpyRNG(seed=85524))
    >>> p = Population(7, HH_cond_exp(gbar_Na=gbar_na_distr))
    >>> p.get('gbar_Na')
    array([ 20.03132455,  20.09777627,  16.97079318,  17.44786923,
            19.4928947 ,  20.80321881,  19.97246906])
    >>> p[0].gbar_Na
    20.031324546935146

Setting parameters from an array
--------------------------------

.. doctest::

    >>> import numpy as np
    >>> p = Population(6, SpikeSourcePoisson(rate=np.linspace(10.0, 20.0, num=6)))
    >>> p.get('rate')
    array([ 10.,  12.,  14.,  16.,  18.,  20.])

The array of course has to have the same size as the population::

    >>> p = Population(6, SpikeSourcePoisson(rate=np.linspace(10.0, 20.0, num=7)))
    ValueError

Using a function to calculate parameter values
----------------------------------------------

.. doctest::

    >>> from numpy import sin, pi
    >>> p = Population(8, IF_cond_exp(i_offset=lambda i: sin(i*pi/8)))
    >>> p.get('i_offset')
    array([ 0.        ,  0.38268343,  0.70710678,  0.92387953,  1.        ,
            0.92387953,  0.70710678,  0.38268343])

Setting parameters as a function of spatial position
----------------------------------------------------

.. doctest::

    >>> from pyNN.space import Grid2D
    >>> grid = Grid2D(dx=10.0, dy=10.0)
    >>> p = Population(16, IF_cond_alpha(), structure=grid)
    >>> def f_v_thresh(pos):
    ...     x, y, z = pos.T
    ...     return -50 + 0.5*x - 0.2*y
    >>> p.set(v_thresh=lambda i: f_v_thresh(p.position_generator(i)))
    >>> p.get('v_thresh').reshape((4,4))
    array([[-50., -52., -54., -56.],
           [-45., -47., -49., -51.],
           [-40., -42., -44., -46.],
           [-35., -37., -39., -41.]])

.. todo: Another example, using Space

For more on spatial structure, see :doc:`space`.

Using multiple parameter types
------------------------------

It is perfectly possible to use multiple different types of parameter value at
the same time:

.. doctest::

    >>> n = 1000
    >>> parameters = {
    ...     'tau_m': RandomDistribution('uniform', (10.0, 15.0)),
    ...     'cm':    0.85,
    ...     'v_rest': lambda i: np.cos(i*pi*10/n),
    ...     'v_reset': np.linspace(-75.0, -65.0, num=n)}
    >>> p = Population(n, IF_cond_alpha(**parameters))
    >>> p.set(v_thresh=lambda i: -65 + i/n, tau_refrac=5.0)

.. todo:: in the above, give current source examples, and Projection examples

Time series and array-valued parameters
=======================================

For certain neuron models (:class:`SpikeSourceArray`, :class:`GIF_cond_exp`)
and current sources, the individual parameter values are not single numbers
(with physical units), but arrays, e.g.:

.. code-block:: python

    celltype = SpikeSourceArray(np.array([5.0, 15.0, 45.0, 99.0]))

to set the same spike times for the entire population.
To set different spike times for each cell in the population requires an array of arrays.
To avoid ambiguities in this situation, the inner arrays should be wrapped by the
:class:`Sequence` class, e.g.:

.. code-block:: python

    celltype = SpikeSourceArray([Sequence([5.0, 15.0, 45.0, 99.0]),
                                 Sequence([2.0, 5.3, 18.9]),
                                 Sequence([17.8, 88.2, 100.1])
                                ])

Such an array-of-Sequences can also be provided by a generator function, e.g.:

.. code-block:: python

    number = int(2 * simtime * input_rate / 1000.0)

    def generate_spike_times(i):
        gen = lambda: Sequence(numpy.add.accumulate(numpy.random.exponential(1000.0 / input_rate, size=number)))
        if hasattr(i, "__len__"):
            return [gen() for j in i]
        else:
            return gen()

    celltype = SpikeSourceArray(spike_times=generate_spike_times)


As a generalization of :class:`Sequence`, some models require array-valued parameters,
expressed as tuples or :class:`ArrayParameter` instances, e.g.:

.. code-block:: python

    cell_type = GIF_cond_exp(
        ...
        # this parameter has the same value in all neurons in the population
        tau_gamma=(1.0, 10.0, 100.0),  # Time constants for spike-frequency adaptation in ms.
        # the following parameter has different values for each neuron
        a_eta=[(0.1, 0.1, 0.1),        # Post-spike increments for spike-triggered current in nA
               (0.0, 0.0, 0.0),
               (0.0, 0.0, 0.0),
               (0.0, 0.0, 0.0)]
        ...)

.. note:: The reason for defining :class:`Sequence` and :class:`ArrayParameter`
          rather than just using a plain NumPy array is to avoid the ambiguity of
          "is a given array a single parameter value (e.g. a spike train for one cell)
          or an array of parameter values (e.g. one number per cell)?".


Setting initial values
======================

.. todo:: complete

.. note:: For most neuron types, the default initial value for the membrane
          potential is the same as the default value for the resting membrane
          potential parameter.
          However, be aware that changing the value of the resting membrane
          potential will *not* automatically change the initial value.
