Introduction to PyNN

The neuron circuits have a lot of tunable parameters. But how do we set their values? For this the module PyNN can be used. It is a language that allows you to form groups of neurons and to connect them in different ways to each other. Also, the parameters of individual neurons can be varied and the resulting dynamic can be observed.

In the following, we want to build a simple network in which a neuron is stimulated by a group of five neurons.

../_images/pynn_simple_network.png
# import the module PyNN
import pynn_brainscales.brainscales2 as pynn

# set the environment
from _static.common.helpers import setup_hardware_client, get_nightly_calibration

setup_hardware_client()
calib = get_nightly_calibration()
pynn.setup(initial_config=calib)

Groups of neurons are called populations. Such a population is instantiated by setting the number of neurons it should contain, the cell type of these neurons and the values of the cell parameters. The cell type of our artificial neurons is called HXNeuron. Its parameters are not expressed in the same units as for biological neurons, but in hardware units. These two systems of units are not directly related. Also within the hardware units there is no general translation to physical voltages and currents. These values can have different meanings for the different parameters on a chip, for example, a threshold voltage of 300 may be higher than a leakage voltage of 400. But also compared to other chips that are designed completely identical the actual measured values can vary slightly.

# define the neuron parameters of the population
numb_neurons = 1
neuron_parameters = {                          # range
    "leak_v_leak": 400,                        # (300-1000)
    "leak_i_bias": 200,                        # (0-1022)
    "threshold_v_threshold": 400,              # (0-600)
    "threshold_enable": True,                  #
    "refractory_period_refractory_time": 100,  # (0-255)
    "reset_v_reset": 300,                      # (300-1000)
    "reset_i_bias": 1000,                      # (0-1022)
    "membrane_capacitance_capacitance": 63     # (0-63)
}

neuron_type = pynn.cells.HXNeuron(**neuron_parameters)

# save the configured neuron in the population 'pop'
pop = pynn.Population(numb_neurons, neuron_type)

The spikes of all neurons that have been stored in populations can be recorded. Furthermore, it is also possible to record the membrane potential of a single neuron. Consequently, for this purpose the population must have a size of one.

Warning

We can only record one neuron at the time. Make sure to use populations of size one or use views to select a single neuron, see. PyNN documentation.

# record spikes and membrane potential 'v' of the neuron in the
# population 'pop'
pop.record(["spikes", "v"])

Different populations can be connected by so-called projections. For this, firstly it must be specified which is the pre-synaptic (source) and which the post-synaptic (receiver) population. Furthermore, the way in which the neurons within the populations are exactly connected to each other is specified, e.g. all neurons are connected or only a certain percentage of the neurons are connected to each other. In addition, the synaptic weight which describes the strength of the connection and the synapse type are specified. This can either be excitatory, meaning that the membrane voltage increases in case of stimulation, or it is inhibitory, which causes the membrane voltage to decrease.

# create a source population that generates spikes at given times
spike_times = [0.01, 0.03, 0.05, 0.07, 0.09]
src = pynn.Population(5, pynn.cells.SpikeSourceArray(spike_times=spike_times))

# define a synapse and its weight
synapse_weight = 63
synapse = pynn.synapses.StaticSynapse(weight=synapse_weight)

# connect the pre-synaptic population 'src' to the post-synaptic
# neuron in 'pop'
pynn.Projection(src, pop, pynn.AllToAllConnector(),
                synapse_type=synapse, receptor_type="excitatory")

The created network of populations and projections can now be emulated for a selected time.

# the duration is given in ms,
# this is in the hardware domain, not in the biological
# (the hardware is faster by a factor of approx. 1000
# compared to biology)
duration = 0.1
pynn.run(duration)

Thereafter, the recorded behavior of the neurons can be read out.

# read out the spikes of the neuron in 'pop'
spiketrain = pop.get_data("spikes").segments[0].spiketrains[0]
print(f"The neuron spiked {len(spiketrain)} times.")
print(f"The spike times were: {spiketrain}")

# plot its membrane potential
mem_v = pop.get_data("v").segments[0].irregularlysampledsignals[0]

import matplotlib.pyplot as plt
%matplotlib inline
plt.figure()
plt.plot(mem_v.times, mem_v)
plt.xlabel("time [ms]")
plt.ylabel("membrane potential [LSB]")
plt.show()