=======================
Neurons and Connections
=======================

Importing PyNN
==============

The simulator used by a PyNN script is determined by which module is imported from the PyNN package, e.g.::

    >>> from pyNN.neuron import * #doctest: +SKIP
    >>> from pyNN.nest import *  #doctest: +SKIP
    >>> from pyNN.pcsim import *  #doctest: +SKIP
    >>> from pyNN.brian import *  #doctest: +SKIP
    
After this line, all PyNN code is independent of the simulator used, although it is possible to include
simulator-specific code in the script as well (if simulator-independence is not important to you, or
if you are in the process of porting simulator-specific code to pure PyNN code).


Initialising the simulator
==========================

Before using any other functions or classes from PyNN, the user must call the ``setup()`` function::

    >>> setup()
    
``setup()`` takes various optional arguments: setting the simulation timestep (there is currently no support in the API for variable timestep methods although native simulator code can be used to select this option where the simulator supports it), setting the minimum and maximum synaptic delays, and turning debugging output on and off, e.g.::

    >>> setup(timestep=0.1, min_delay=0.1, max_delay=0.5, debug=False)
    
Debugging information is written to a log file in the working directory.

Creating neurons
================

Neurons are created with the ``create()`` function. To create a single integrate-and-fire neuron, type::

    >>> create(IF_curr_alpha)
    
Here, ``IF_curr_alpha`` is a particular class of IF neuron with alpha-function shaped synaptic currents, that will work with any PyNN simulation engine, whether NEURON, NEST, PCSIM or Brian.
``IF_curr_alpha`` is a so-called 'standard cell', implemented as a Python class.
For more information, see the section StandardCells.

You don't have to use standard cells.
You can also use any neuron model that is available in an individual simulator, although of course your simulation will then only run with that simulator, for example::

    >>> create('iaf_neuron') #doctest: +SKIP
    
``iaf_neuron`` is a neuron model available in the NEST simulator.

To create many neurons at once, add the ``n`` argument, e.g.::

    >>> create(IF_curr_alpha, n=10)
    
The neurons we have created so far have all had default parameter values, 
stored in the ``default_values`` of the standard cell class, e.g.::

    >>> IF_curr_alpha.default_parameters #doctest" +NORMALIZE_WHITESPACE
    {'tau_refrac': 0.0, 'tau_m': 20.0, 'i_offset': 0.0, 'cm': 1.0, 'v_init': -65.0,
     'v_thresh': -50.0, 'tau_syn_E': 0.5, 'v_rest': -65.0, 'tau_syn_I': 0.5,
     'v_reset': -65.0}

To use different parameter values, use the ``cellparams`` argument, e.g.::

    >>> create(IF_curr_alpha, cellparams={'tau_m': 15.0, 'cm': 0.9}, n=10)

If you try to set a non-existent parameter, or pass an invalid value, PyNN will raise an Exception, e.g.::

    >>> create(IF_curr_alpha, cellparams={'foo': 15.0})
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      File "/usr/lib/python/site-packages/pyNN/nest1.py", line 228, in create
        celltype = cellclass(cellparams)
      File "/usr/lib/python/site-packages/pyNN/nest1.py", line 68, in __init__
        common.IF_curr_alpha.__init__(self,parameters) # checks supplied parameters and adds default
      File "/usr/lib/python/site-packages/pyNN/common.py", line 113, in __init__
        self.parameters = self.checkParameters(parameters, with_defaults=True)
      File "/usr/lib/python/site-packages/pyNN/common.py", line 99, in checkParameters
        raise NonExistentParameterError(k)
    NonExistentParameterError: foo
    >>> create(IF_curr_alpha, cellparams={'tau_m': 'bar'})
    Traceback (most recent call last):
      File "/usr/lib/python/site-packages/pyNN/nest1.py", line 228, in create
        celltype = cellclass(cellparams)
      File "/usr/lib/python/site-packages/pyNN/nest1.py", line 68, in __init__
        common.IF_curr_alpha.__init__(self,parameters) # checks supplied parameters and adds default
      File "/usr/lib/python/site-packages/pyNN/common.py", line 113, in __init__
        self.parameters = self.checkParameters(parameters, with_defaults=True)
      File "/usr/lib/python/site-packages/pyNN/common.py", line 90, in checkParameters
        raise InvalidParameterValueError, (type(supplied_parameters[k]), type(default_parameters[k]))
    InvalidParameterValueError: (<type 'str'>, <type 'float'>)

    
If you wish to do something with the cell after creating it: record from it, change a parameter, connect it to another cell, you should assign the return value of the function to a variable, e.g.::

    >>> cell = create(IF_curr_alpha)
    >>> cell_list = create(IF_curr_alpha, n=10)

The ``create()`` function returns either a cell id object or a list of id objects.

Connecting neurons
==================

Any neuron that emits spikes can be connected to any neuron with at least one synapse using the ``connect()`` function, e.g.::

    >>> spike_source = create(SpikeSourceArray, cellparams={'spike_times': [10.0, 20.0, 30.0]})
    >>> cell_list2 = create(IF_curr_exp, n=10)
    >>> connect(spike_source, cell_list2)
    
In this case we connect a spike-generating mechanism (``SpikeSourceArray`` is a 'standard cell' model that emits spikes at times specified by the ``spike_times`` parameter) to each cell in the list ``cells``, i.e. we create 10 connections at once.
For clarity, we could also have specified the argument names::

    >>> connect(source=spike_source, target=cell_list2)
    
Either ``source`` or ``target`` or both may be individual cell ids or lists of ids.
In the latter case, each source (presynaptic) cell is connected to every target (postsynaptic) cell with probability given by the optional argument `p`, which defaults to 1, e.g.::

    >>> source_list = cell_list
    >>> target_list = cell_list2
    >>> connect(source_list, target_list, p=0.5)
    
When specifying connections as above, default values are given to the synaptic weight and delay.
These values are seldom very useful, and it is better to specify the ``weight`` and ``delay`` arguments of ``connect()``, e.g.::

    >>> connect(source_list, target_list, weight=1.5, delay=0.5)
    
Weights are specified in nA for 'current-based' synapses or µS for 'conductance-based' synapses.
Delays are in ms.
For current-based synapses, weights should be negative for inhibitory synapses.
For conductance-based synapses, weights should always be positive, since the effect of a synapse is determined by its reversal potential.

If the neuron model has more than one synapse mechanism, or more than one synaptic location, the particular synapse to which the connection should be made is specified with the ``synapse_type`` argument, e.g.::

    >>> connect(source_list, target_list, weight=-1.5, delay=0.5, synapse_type='inhibitory')

(the attribute ``synapse_types`` of all standard cell objects contains a list of the synapse types for that cell type).

Setting neuron parameters
=========================

There are many ways to change the parameters for individual neurons and post-synaptic mechanisms after creation of the neuron.
To change a single parameter of a single neuron, just set the relevant attribute of the neuron ID object, e.g.::

    >>> cells = create(IF_curr_exp, cellparams={'v_init': -70.0}, n=10)
    >>> cells[0].tau_m
    20.0
    >>> cells[0].tau_m = 15
    >>> cells[0].tau_m
    15.0
    
To change several parameters at once for a single neuron, use the ``set_parameters()`` method of the neuron ID, e.g.::

    >>> cells[1].set_parameters(tau_m=10.0, cm=0.5)
    >>> cells[1].tau_m
    10.0
    >>> cells[1].cm
    0.5

To change parameters for several cells at once, use the ``set()`` function, e.g.::

    >>> set(cells[0:5], param='v_init', val=-65.0)
    >>> print cells[0].v_init
    -65.0
    >>> print cells[5].v_init
    -70.0
    
Individual parameters can be set using the ``param`` and ``val`` arguments, as above, or multiple parameters can be set at once by passing a dictionary of name:value pairs as the ``param`` argument, with ``val`` empty, e.g.::

    >>> set(cells, param={'tau_refrac': 2.0, 'tau_syn_E': 5.0})

Setting position in space
=========================

In some cases it is important to know the position of a neuron in space. This information
can be set and retrieved using the `position` attribute of the neuron ID::

    >>> cells[0].position = (75, 456, 56)
    >>> cells[0].position
    array([ 75, 456, 56])
    
Positions must always be in 3D, and may be given as integers or floating-point values, and as tuples or as numpy arrays.
No specific scale of units is assumed, although many parts of PyNN do assume a Euclidean coordinate system.

Injecting current
=================

Most standard cells have an `i_offset` parameter, which allows a constant current to be
injected into a neuron. If you want to have a time varying current, you may create a
``CurrentSource`` object and connect it to the neuron either using the ``inject()`` method of the
neuron ID or using the ``inject_into()`` method of the ``CurrentSource``::

    >>> pulse = DCSource(amplitude=0.5, start=20.0, stop=80.0)
    >>> steps = StepCurrentSource(times=[0.0, 50.0, 100.0], amplitudes=[0.1, 0.2, 0.3])
    >>> pulse.inject_into(cells[3:7])
    >>> cells[9].inject(steps)

Recording spikes and membrane potential
=======================================

To record action potentials use the ``record()`` function, to record membrane potential use the ``record_v()`` function and to record synaptic conductances use the ``record_gsyn()`` function.
The arguments for all three functions are a cell id or list of ids, and a filename, e.g.::

    >>> record(cell, "spikes.dat")
    >>> record_v(cell_list, "Vm.dat")
    
By default, all simulators write data files in the same format.

The beginning of a typical spike file looks like::

    # dt = 0.1
    # n = 1000
    0.0     2
    0.3     5
    0.4     3
    0.9     2
    1.0     1
    . . .
    
The beginning of a typical membrane potential file looks like::

    # dt = 0.1
    # n = 1000
    -65.0   0
    -64.9   0
    -64.7   0
    -64.5   0
    . . .

Both file types begin with header lines giving the timestep (there is currently no support for variable-time step recording) and the number of data points in the file.
Each line of the spike file then gives the occurence time of a spike (in ms) and the id of the neuron in which it was recorded.
Each line of the membrane potential file gives the membrane potential (in mV) followed by the id of the neuron in which it was recorded.
In both cases, whether the file is sorted by cell id or by time depends on the simulator: it is not standardised.

In some cases it is more efficient to write files in the simulator's native format, rather than the standard PyNN format.
In this case, use the ``compatible_output=False`` argument to the ``end()`` function.

Considerable enhancements to file formats are planned for future releases of PyNN, including recording to binary (HDF5) rather than text files, support for variable time-step recording, and user-specified output formats.

Running a simulation
====================

The ``run()`` function runs the simulation for a given number of milliseconds, e.g.::

    >>> run(1000.0)
    
Finishing up
============

Just as a simulation must be begun with a call to ``setup()``, it must be ended with a call to ``end()``.

Examples
========

There are several example scripts in the ``examples`` directory of the source distribution.
    
