Skip to content

SNN API

from spinnaker2 import snn, hardware

Populations

A population describes a group of neurons with the same neuron model.

stim = snn.Population(
    size=10,
    neuron_model="spike_list",
    params={0: [1, 2, 3], 5: [20, 30]},
    name="stim")

neuron_params = {
    "alpha_decay": 0.8,
    "threshold"  : 1.0,
    "i_offset"   : 0.0,
    "v_init"     : 0.0,
    "reset"      : "reset_by_subtraction"
    }

pop1 = snn.Population(
    size=20,
    neuron_model="lif",
    params=neuron_params,
    name="pop1",
    record=["spikes", "v"])

A population is initialized with the following parameters:

  • size: number of neurons (int)
  • neuron_model: neuron model (str), see section Neuron models further below
  • params: parameters for the population (dict), depends on the neuron model
  • name: a name for the population (str), optional.
  • record: list of variables to be recorded (list). Defaults to ['spikes'], which means that spikes are recorded. Some neuron models allow to record also the voltage of the entire simulation ('v') or just the last timestep ('v_last').

Neuron parameters can be set individually for each neuron, see Customizing your simulation

Projections

A projection is a list of synapses between two populations. Example:

w = 2 # weight
d = 1  # delay
conns = []
conns.append([0,1,w,d])
conns.append([0,0,-w,d])

proj = snn.Projection(pre=stim, post=pop1, connections=conns)
parameters of a Projection:

  • pre: pre-synaptic population (snn.Population)
  • post: post-synaptic population (snn.Population)
  • connections: a list of synaptic connections (list of lists). Each connection is defined by a list or tuple with 4 elements. The format is [pre_index, post_index, weight, delay].

Example for connection:

conns.append([2, 4, 5, 1])
This a creates a synapse from the neuron 2 in the pre population to neuron 4 in the post population with weight of 5 and delay of 1 time step.

Network and Simulation

# add all populations and projects to a snn.Network()
net = snn.Network("my network")
net.add(stim, pop1, proj)

# run on spiNNaker2 for 50 time steps
hw = hardware.SpiNNaker2Chip(eth_ip='192.168.1.48')
timesteps = 50
hw.run(net, timesteps)

Obtaining results

Both spikes and voltages can be recorded for most of the neuron models.

Obtaining and plotting spikes

# get results and plot
from spinnaker2 import helpers
import matplotlib.pyplot as plt

# get_spikes() returns a dictionary with:
#  keys: neuron indices
#  values: lists of spike times per neurons
spike_times = pop1.get_spikes()
print(spike_times)

indices, times = helpers.spike_times_dict_to_arrays(spike_times)
plt.plot(times, indices, ".")
plt.xlim(0,timesteps)
plt.ylim(0,pop1.size)
plt.xlabel("time step")
plt.ylabel("neuron")
plt.show()

Obtaining and plotting voltages

The feature to record neuron voltages is mainly for debugging. If voltages are recorded for many neurons, no long simulation times will be possible due to memory limitations.

pop1 = snn.Population(size=1, neuron_model="lif", params=neuron_params, name="pop1", record=["v"])

# create network ...
# run experiment on hardware ...

# get_voltages() returns a dictionary with:
#  - keys: neuron indices
#  - values: numpy arrays with 1 float value per timestep per neuron
voltages = pop1.get_voltages()

An example for voltage recording can be found in examples/snn/lif_neuron.py

Obtaining and plotting execution times (time_done)

py-spinnaker2 uses a real-time simulation, where the discrete time update of the neuron model is triggered in a regular interval (1 ms per default). Within this interval, the previously received incoming spikes are processed, the neurons are updated and generated spikes are sent out. When processing has finished, the ARM core goes to sleep.

One can record the execution times for each timestep and core, i.e., the relative time within the interval when the processing has finished. This information is helpful to see whether the simulation can be further accelerated (shorter interval) or needs to be enlarged to avoid that processing cannot finish within the interval.

Recording of the execuation times is enabled by adding time_done to the record variable of the population:

pop1 = snn.Population(size=1, neuron_model="lif", params=neuron_params, name="pop1", record=["spikes", "time_done"])

Results can be obtained as follows:

# get results and plot
from spinnaker2 import helpers
import matplotlib.pyplot as plt

# get_time_done_times() returns a dictonary with
#  keys: PE ids
#  values: lists of execution times(time_done_times) per PE
time_done_times = pop1.get_time_done_times()
print(time_done_times) # prints out the dictonary

# saves the dictionary to .npz file under a given name, here: time_done_times
helpers.save_dict_to_npz(time_done_times, "/your-explicit-filepath/")

# delivers a plot similar to Fig. 5 in https://www.frontiersin.org/articles/10.3389/fnins.2018.00816/full, showing time on the x-axis with red lines showing every new discrete timestep and blue boxes representing the execution time needed
helpers.plot_times_done_multiple_pes_one_plot_horizontal("/your-explicit-filepath/time_done_times.npz")

# delivers a plot showing the discrete simulation timesteps on the x-axis and execution times on the y-axis with a red line showing the maximum allowed execution time
helpers.plot_times_done_multiple_pes_one_plot_vertical("/your-explicit-filepath/time_done_times.npz")

Units of time and other variables

  • time is represented as discrete time steps (no unit). Input spikes and recorded spikes are provided as the time steps they occur. The first time step is 0.
  • all other variables are transferred as they are to SpiNNaker2, e.g.:

    • neuron variables are represented as float (single precision)
    • weights are signed integers. Note that the number of bits per weight is limited and depends on the neuron model of the post-synaptic population. Weights are clipped to the weight range and discretized by rounding-to-zero.
    • delays are unsigned integers. Range: 0-7 for lif and lif_curr_exp.

      Note that for each projection there is a minimum delay of 1 time step: spikes are generated in one time step on one core, and are processed as synaptic events in other cores one time step later. Accordingly, the effective delay is delay + 1.

      The neuron models with _no_delay suffix have no configurable delays, spikes are processed in the subsequent time step.