==========================
Abstraction layer tutorial
==========================

Note that at present the Python API for the abstraction layer only supports
neuron and synapse models (including models of synaptic plasticity), but not
connectivity rules.

Here we will use the Izhikevich neuron model as an example.

Start by importing the abstraction layer module::
    
    >>> from nineml.abstraction_layer import *

A neuron model in NineML consists of a number of dynamical regimes connected
by conditional transitions. Regimes may contain ordinary differential equations,
expressions, and other regimes, and have a finite duration. Transitions occur
when a specified condition is met, and are instantaneous. During a transition,
the values of variables may be modified, and signals (e.g. spikes) may be sent.

For the Izhikevich model there is no absolute refractory period, so we need only
a single regime::

    >>> subthreshold_regime = Regime(
    ...                           "dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn",
    ...                           "dU/dt = a*(b*V - U)",
    ...                           name="subthreshold_regime")

The syntax for writing equations (which may be either ordinary differential
equations or assignments) is basically the same as Python's expression syntax
(and very similar to that of C, etc.), except for the ``dV/dt`` part. The ``name``
argument is not required, but makes for more readable XML.

When the membrane potential passes a threshold, the membrane potential ``V`` is
reset and the adaptation variable ``U`` is incremented::
    
    >>> spike_transition = Transition(
    ...                        "V = c",
    ...                        "U += d",
    ...                        SpikeOutputEvent,
    ...                        from_=subthreshold_regime,
    ...                        to=subthreshold_regime,
    ...                        condition="V > theta",
    ...                        name="spike_transition")
    
Here the transition goes from and returns to the same regime.

As an alternative to creating the Transition object separately, it can be
created at the same time as creating the Regime, by using the ``On`` function::

    >>> subthreshold_regime = Regime(
    ...                           "dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn",
    ...                           "dU/dt = a*(b*V - U)",
    ...                           transitions=On("V > theta",
    ...                                          do=["V = c", "U += d", SpikeOutputEvent],
    ...                                          name="spike_transition"),
    ...                           name="subthreshold_regime")

If the "to" regime is not specified in ``On``, it is taken to be the same as
the "from" regime.

A neuron model is not very useful on its own. We need to combine it at least
with a synapse model, which will set the value of the current ``Isyn`` and may
need to know the value of the membrane potential ``V``. To handle this, we need
to define "ports"::

    >>> ports = [SendPort("V"), ReducePort("Isyn", op="+")]
    
This states that the value of ``V`` will be made available to other components,
and that the value of ``Isyn`` will be obtained by summing the currents made
available by other components.

To create the neuron component, we need to specify either a list of regimes
or a list of transitions, together with the list of ports,
i.e. the following are equivalent::
    
    >>> c = Component("Izhikevich", transitions=[spike_transition], ports=ports)
    >>> c = Component("Izhikevich", regimes=[subthreshold_regime], ports=ports)

Now we can export the component as XML (normally to a file, but here we print
it to the screen)::

    >>> import sys
    >>> c.write(sys.stdout)                                                     #doctest: +REPORT_UDIFF
    <?xml version='1.0' encoding='UTF-8'?>
    <nineml xmlns="http://nineml.org/9ML/0.1">
      <component name="Izhikevich">
        <parameter name="a"/>
        <parameter name="theta"/>
        <parameter name="c"/>
        <parameter name="b"/>
        <parameter name="d"/>
        <analog-port symbol="V" mode="send"/>
        <analog-port symbol="Isyn" mode="reduce" op="+"/>
        <analog-port symbol="U" mode="send"/>
        <analog-port symbol="t" mode="send"/>
        <regime name="subthreshold_regime">
          <ode independent_variable="t" name="ODE2" dependent_variable="V">
            <math-inline>0.04*V*V + 5*V + 140.0 - U + Isyn</math-inline>
          </ode>
          <ode independent_variable="t" name="ODE3" dependent_variable="U">
            <math-inline>a*(b*V - U)</math-inline>
          </ode>
        </regime>
        <transition to="subthreshold_regime" from="subthreshold_regime" name="spike_transition" condition="V &gt; theta">
          <assignment to="V" name="Assignment1">
            <math-inline>c</math-inline>
          </assignment>
          <inplace to="U" name="InplaceAdd1" op="+=">
            <math-inline>d</math-inline>
          </inplace>
          <event-port symbol="spike_output" mode="send"/>
        </transition>
      </component>
    </nineml>
    
Note that there is no specification yet for an XML representation of the
abstraction layer, so the syntax above will almost certainly change in future.
If the model is expressed in Python, however, it will be easy to export new XML
files every time the syntax changes. Also note that for now we are using a
simple "inline" maths syntax, but (optional?) use of MathML is planned.

Note also that send ports for ``U`` and ``t`` were automatically created. In fact,
we need not have created the ``V`` port either, since the system assumes that the
values of state variables should be made available to other components.


Reading abstraction layer Components from file
----------------------------------------------

Now let's write our Izhikevich component to a file::

    >>> c.write("test.xml")

and then read it back in and check it is indeed the same component::

    >>> c1 = parse("test.xml")
    >>> c == c1
    True
    

Exploring an abstraction layer Component
----------------------------------------

Once you have a NineML ``Component`` object, it can be introspected in various
ways. To get a list of the various methods and attributes of the component
object, use the Python ``help()`` function::

    >>> help(c)                                                                 #doctest: +ELLIPSIS
    Help on Component in module nineml.abstraction_layer object:
    ...

We can also inspect the ``Component`` at various levels of detail::

    >>> c.regimes
    set([Regime(subthreshold_regime)])
    >>> c.transitions                                                           #doctest: +NORMALIZE_WHITESPACE
    set([Transition(from Regime(subthreshold_regime) to Regime(subthreshold_regime) if Condition('V > theta'))])
    >>> list(c.equations)                                                       #doctest: +NORMALIZE_WHITESPACE
    [Assignment('V', 'c'),
     Inplace('U', '+=', 'd'),
     ODE(dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn),
     ODE(dU/dt = a*(b*V - U))]
    >>> c.variables
    set(['U', 't', 'V'])
    >>> c.independent_variables
    set(['t'])
    >>> c.integrated_variables
    set(['U', 'V'])
    >>> c.parameters
    set(['a', 'theta', 'c', 'b', 'd'])
    >>> list(c.ports)                                                           #doctest: +NORMALIZE_WHITESPACE
    [AnalogPort('V', mode='send'),
     AnalogPort('Isyn', mode='reduce', op='+'),
     AnalogPort('U', mode='send'),
     AnalogPort('t', mode='send'),
     EventPort('spike_output', mode='send')]
    
To visualize the component, we can export it in dot format (http://en.wikipedia.org/wiki/DOT_language)::

    >>> c.to_dot("test.dot")
    
Various tools can convert a dot file to an image, for example GraphViz (http://graphviz.org/).
If you have GraphViz installed, then on the command-line you can convert the dot
files into various image formats, e.g.::

    $ dot -Tsvg test.dot -o test.svg
    $ dot -Tpng test.dot -o test.png


Regimes also have various useful methods and attributes, e.g.::

    >>> subthresh = c.regime_map['subthreshold_regime']
    >>> help(subthresh)                                                         #doctest: +ELLIPSIS
    Help on Regime in module nineml.abstraction_layer object:
    ...
    >>> list(subthresh.equations)
    [ODE(dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn), ODE(dU/dt = a*(b*V - U))]
    >>> list(subthresh.odes)
    [ODE(dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn), ODE(dU/dt = a*(b*V - U))]
    >>> subthresh.neighbors
    [Regime(subthreshold_regime)]
    >>> list(subthresh.transitions)
    [Transition(from Regime(subthreshold_regime) to Regime(subthreshold_regime) if Condition('V > theta'))]
    
as do equations::

    >>> dv, du = subthresh.equations
    >>> print dv
    ODE(dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn)
    >>> dv.indep_variable
    't'
    >>> dv.dependent_variable
    'V'
    >>> dv.rhs
    '0.04*V*V + 5*V + 140.0 - U + Isyn'
    >>> dv.as_expr()
    'dV/dt = 0.04*V*V + 5*V + 140.0 - U + Isyn'
    >>> from lxml.etree import tostring
    >>> print tostring(dv.to_xml(), pretty_print=True)                          #doctest: +NORMALIZE_WHITESPACE
    <ode independent_variable="t" name="ODE2" dependent_variable="V">
      <math-inline>0.04*V*V + 5*V + 140.0 - U + Isyn</math-inline>
    </ode>

A more complex example
----------------------

The Izhikevich model is fairly simple, with a single regime and only two ODEs.
As a more complex example, consider the Hodgkin-Huxley model. This has four ODEs,
with complex expressions on the right-hand sides. It is necessary to break these
expressions down into parts. For this we use "bindings", i.e. we associate a
name with an expression, and can then use the name in place of the expression in
building up more complex expressions. These bindings may also take the form of
functions::

    >>> bindings = [
    ...     "q10 := 3.0**((celsius - 6.3)/10.0)",  # temperature correction factor
    ...     "alpha_m(V) := -0.1*(V+40.0)/(exp(-(V+40.0)/10.0) - 1.0)",  # m
    ...     "beta_m(V) := 4.0*exp(-(V+65.0)/18.0)",
    ...     "mtau(V) := 1/(q10*(alpha_m(V) + beta_m(V)))",
    ...     "minf(V) := alpha_m(V)/(alpha_m(V) + beta_m(V))",
    ...     "alpha_h(V) := 0.07*exp(-(V+65.0)/20.0)",                   # h
    ...     "beta_h(V) := 1.0/(exp(-(V+35)/10.0) + 1.0)",
    ...     "htau(V) := 1.0/(q10*(alpha_h(V) + beta_h(V)))",
    ...     "hinf(V) := alpha_h(V)/(alpha_h(V) + beta_h(V))",
    ...     "alpha_n(V) := -0.01*(V+55.0)/(exp(-(V+55.0)/10.0) - 1.0)", # n
    ...     "beta_n(V) := 0.125*exp(-(V+65.0)/80.0)",
    ...     "ntau(V) := 1.0/(q10*(alpha_n(V) + beta_n(V)))",
    ...     "ninf(V) := alpha_n(V)/(alpha_n(V) + beta_n(V))",
    ...     "gna(m,h) := gnabar*m*m*m*h",                           # conductances 
    ...     "gk(n) := gkbar*n*n*n*n",
    ...     "ina(m,h,V) := gna(m,h)*(ena - V)",                     # currents
    ...     "ik(n,V) := gk(n)*(ek - V)",
    ...     "il(V) := gl*(el - V )"]
    >>> hh_regime = Regime(
    ...                 "dn/dt = (ninf(V)-n)/ntau(V)",
    ...                 "dm/dt = (minf(V)-m)/mtau(V)",
    ...                 "dh/dt = (hinf(V)-h)/htau(V)",
    ...                 "dV/dt = (ina(m,h,V) + ik(n,V) + il(V) + Isyn)/C",
    ...                 name="hh_regime",
    ...                 transitions=On("V > theta",do=[SpikeOutputEvent]))
    >>> ports = [ReducePort("Isyn", op="+")]
    >>> c = Component("Hodgkin-Huxley", regimes=[hh_regime],
    ...               bindings=bindings, ports=ports)
    >>> c.write("hh.xml")

An example with two regimes
---------------------------

A leaky integrate-and-fire model with refractory period has two dynamical
regimes - the subthreshold regime and the refractory regime. Just for fun, we'll
define the component in a single step::

    >>> leaky_iaf = Component("LeakyIAF",
    ...                       regimes=[
    ...                           Regime("dV/dt = (-gL*(V-vL) + Isyn)/C",
    ...                                  transitions=On("V>Vth",
    ...                                                 do=["tspike = t",
    ...                                                     "V = V_reset",
    ...                                                     SpikeOutputEvent],
    ...                                                 to="refractory-regime"),
    ...                                  name="sub-threshold-regime"),
    ...                           Regime(transitions=On("t >= tspike + trefractory",
    ...                                                 to="sub-threshold-regime"),
    ...                                  name="refractory-regime")
    ...                       ],
    ...                       ports=[ReducePort("Isyn", op="+")])
    >>> leaky_iaf.write("leaky_iaf.xml")

Note that here we used the *name* of the regime in the ``to`` argument to the
``On`` transition constructor, rather than a ``Regime`` object. These references
are resolved automatically.

A synapse example
-----------------

A very commonly-used synapse model is a step increase in conductance, which
decays exponentially::

    >>> cond_decay = Regime("dg/dt = -g/tau",
    ...                     transitions=On(SpikeInputEvent, do="g += q"))
    >>> ports = [RecvPort("V"), SendPort("Isyn = g*(E-V)")]
    >>> coba_syn = Component("CoBaSynapse", regimes=[cond_decay], ports=ports)
    
The main points of interest here are (i) that the transition condition is an
external spike event and (ii) that the ``SendPort`` may include an
assignment statement rather than a variable name. For comparison, a synapse
with an exponentially decaying current, rather than conductance, would have
Isyn as the state variable::

    >>> curr_decay = Regime("dIsyn/dt = -Isyn/tau",
    ...                     transitions=On(SpikeInputEvent, do="Isyn += q"))
    >>> ports = [RecvPort("V"), SendPort("Isyn")]
    >>> cuba_syn = Component("CuBaSynapse", regimes=[curr_decay], ports=ports)

A synaptic plasticity example
-----------------------------

Here is an implementation of the triplet STDP rule described in Pfister and
Gerstner (2006) J. Neuroscience 26: 9673-9682. There is a single dynamical
regime, with transitions triggered by both pre- and post-synaptic spikes::

    >>> decay_regime = Regime("dr1/dt = -r1/tau_plus",
    ...                       "dr2/dt = -r2/tau_x",
    ...                       "do1/dt = -o1/tau_minus",
    ...                       "do2/dt = -o2/tau_y")
    >>> on_pre = Transition("W -= o1*(A2_minus + A3_minus*r2)",
    ...                     "r1 += 1.0",
    ...                     "r2 += 1.0",
    ...                     condition=PreEvent,
    ...                     from_=decay_regime)
    >>> on_post = Transition("W  += r1*(A2_plus + A3_plus*o2)",
    ...                      "o1 += 1.0",
    ...                      "o2 += 1.0",
    ...                      condition=PostEvent,
    ...                      from_=decay_regime)
    >>> triplet_stdp = Component("PfisterTripletSTDP",
    ...                          transitions=(on_pre, on_post),
    ...                          ports=[SendPort("W")])


For further examples, see `lib9ml/python/examples/AL/`. 
