Using non-standard/native cell and synapse models

Standard models are neuron/synapse models that are available in at least two of the simulation engines supported by PyNN. Non-standard models, then, work only with a single simulator. We also call these “native” models.

With native models, we lose full simulator-independence. However, for people who work mainly with a single simulator, native models allow them to use PyNN’s high-level API for building and instrumenting networks but with a broader range of neuron and synapse models. This is also often a useful intermediate step in converting a native network model to PyNN.

Using native NEST models

To use a NEST neuron model with PyNN, we wrap the NEST model with a PyNN NativeCellType class, e.g.:

>>> from pyNN.nest import native_cell_type, Population, run, setup
>>> setup()
>>> ht_neuron = native_cell_type('ht_neuron')
>>> poisson = native_cell_type('poisson_generator')
>>> p1 = Population(10, ht_neuron, {'Tau_m': 20.0})
>>> p2 = Population(1, poisson, {'rate': 200.0})

We can now initialize state variables, set/get parameter values, and record from these neurons as from standard cells:

>>> p1.get('Tau_m')
[20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0]
>>> p1.get('Tau_theta')
[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0]
>>> p1.get('C_m')
NonExistentParameterError: C_m (valid parameters for ht_neuron are:
  AMPA_E_rev, AMPA_Tau_1, AMPA_Tau_2, AMPA_g_peak, E_K, E_Na, GABA_A_E_rev,
  GABA_A_Tau_1, GABA_A_Tau_2, GABA_A_g_peak, GABA_B_E_rev, GABA_B_Tau_1,
  GABA_B_Tau_2, GABA_B_g_peak, KNa_E_rev, KNa_g_peak, NMDA_E_rev, NMDA_Sact,
  NMDA_Tau_1, NMDA_Tau_2, NMDA_Vact, NMDA_g_peak, NaP_E_rev, NaP_g_peak,
  T_E_rev, T_g_peak, Tau_m, Tau_spike, Tau_theta, Theta_eq, g_KL, g_NaL,
  h_E_rev, h_g_peak, spike_duration)

>>> p1.initialize('V_m', -70.0)
>>> p1.initialize('Theta', -50.0)

Note that the API for recording is somewhat clumsy for native models, and will be improved in the next PyNN release:

>>> p1._record('V_m')
>>> run(250.0)
>>> id, t, v = p1.recorders['V_m'].get().T

To connect populations of native cells, you need to know the available synaptic receptor types:

>>> ht_neuron.synapse_types
{'AMPA': 1, 'GABA_A': 3, 'GABA_B': 4, 'NMDA': 2}
>>> from pyNN.nest import Projection, AllToAllConnector
>>> connector = AllToAllConnector()
>>> prj_ampa = Projection(p2, p1, connector, target='AMPA')
>>> prj_nmda = Projection(p2, p1, connector, target='NMDA')

To use a NEST STDP model with PyNN, we use the NativeSynapseDynamics class:

>>> from pyNN.nest import NativeSynapseDynamics
>>> stdp = NativeSynapseDynamics("stdp_synapse", {'Wmax': 50.0, 'lambda': 0.015})
>>> prj_plastic = Projection(p1, p1, connector, target='AMPA', synapse_dynamics=stdp)

Using native NEURON models

A native NEURON cell model is described using a Python class (which may wrap a Hoc template). For this class to work with PyNN, there are a small number of requirements:

  • the __init__() method should take just **parameters as its argument.

  • instances should have attributes:

    • source: a reference to the membrane potential which will be

      monitored for spike emission, e.g. self.soma(0.5)._ref_v

    • source_section: the Hoc Section in which source is located.

    • parameter_names: a tuple of the names of attributes/properties of

      the class that correspond to parameters of the model.

    • traces: an empty dict, used for recording.

    • recording_time: should be False initially.

  • there must be memb_init() method, taking no arguments.

Here is an example, which uses the nrnutils package for conciseness:

from nrnutils import Mechanism, Section

class SimpleNeuron(object):

    def __init__(self, **parameters):
        hh = Mechanism('hh', gl=parameters['g_leak'], el=-65,
                       gnabar=parameters['gnabar'], gkbar=parameters['gkbar'])
        self.soma = Section(L=30, diam=30, mechanisms=[hh])
        self.soma.add_synapse('ampa', 'Exp2Syn', e=0.0, tau1=0.1, tau2=5.0)

        # needed for PyNN
        self.source_section = self.soma
        self.source = self.soma(0.5)._ref_v
        self.parameter_names = ('g_leak', 'gnabar', 'gkbar')
        self.traces = {}
        self.recording_time = False

    def _set_gnabar(self, value):
        for seg in self.soma:
            seg.hh.gnabar = value
    def _get_gnabar(self):
        return self.soma(0.5).hh.gnabar
    gnabar = property(fget=_get_gnabar, fset=_set_gnabar)

    # ... gkbar and g_leak properties defined similarly

    def memb_init(self):
        for seg in self.soma:
            seg.v = self.v_init

For each cell model, you must also define a cell type:

class SimpleNeuronType(NativeCellType):
    default_parameters = {'g_leak': 0.0002, 'gkbar': 0.036, 'gnabar': 0.12}
    default_initial_values = {'v': -65.0}
    recordable = ['soma(0.5).v', 'soma(0.5).ina']
    model = SimpleNeuron

The requirement to explicitly list all variables you might wish to record in the recordable attribute is a temporary inconvenience, which will be removed in a future version.

It is now straightforward to use this cell type in PyNN:

>>> from pyNN.neuron import setup, run, Population, Projection, AllToAllConnector
>>> setup()
>>> p1 = Population(10, SimpleNeuronType, {'g_leak': 0.0003})
>>> p1._record('soma(0.5).ina')
>>> prj = Projection(p1, p1, AllToAllConnector(), target='soma.ampa')
>>> run(100.0)
>>> id, t, ina = p1.recorders['soma(0.5).ina'].get().T