GeNN  4.9.0
GPU enhanced Neuronal Networks (GeNN)
Tutorial 2 (Python)

In this tutorial we will learn to add synapsePopulations to connect neurons in neuron groups to each other with synaptic models. As an example we will connect the ten Hodgkin-Huxley neurons from tutorial 1 in a ring of excitatory synapses.

First, copy the files from Tutorial 1 into a new directory and rename the tenHH.py to tenHHRing.py, e.g. on Linux or Mac:

>> cp -r tenHH_project tenHHRing_project
>> cd tenHHRing_project
>> mv tenHH.py tenHHRing.py

Finally, to reduce confusion we should rename the model itself. Open tenHHRing.py, change the model name inside,

model = GeNNModel("float", "tenHHRing")

You will also need to import some additional PyGeNN functions and constants:

Defining the Detailed Synaptic Connections

We want to connect our ten neurons into a ring where each neuron connects to its neighbours. In order to initialise this connectivity we need to add a sparse connectivity initialisation snippet at the top of tenHHRing.cc:

"ring",
row_build_code=
"""
$(addSynapse, ($(id_pre) + 1) % $(num_post));
$(endRow);
""",
calc_max_row_len_func=create_cmlf_class(
lambda num_pre, num_post, pars: 1)())

The row_build_code code string will be called to generate each row of the synaptic matrix (connections coming from a single presynaptic neuron) and, in this case, each row consists of a single synapses from the presynaptic neuron $(id_pre) to $(id_pre) + 1 (the modulus operator is used to ensure that the final connection between neuron 9 and 0 is made correctly). In order to allow GeNN to better optimise the generated code we also provide a maximum row length. In this case each row always contains only one synapse but, when more complex connectivity is used, the number of neurons in the pre and postsynaptic population as well as any parameters used to configure the snippet can be accessed from this function.

Note
When defining GeNN code strings, the $(VariableName) syntax is used to refer to variables provided by GeNN and the $(FunctionName, Parameter1,...) syntax is used to call functions provided by GeNN.

Adding Synaptic connections

Now we need additional initial values and parameters for the synapse and post-synaptic models. We will use the standard WeightUpdateModels::StaticPulse weight update model and PostsynapticModels::ExpCond post-synaptic model. They need the following initial variables and parameters:

s_ini = {"g": -0.2}
ps_p = {"tau": 1.0, # Decay time constant [ms]
"E": -80.0} # Reversal potential [mV]
Note
the WeightUpdateModels::StaticPulse weight update model has no parameters and the PostsynapticModels::ExpCond post-synaptic model has no state variables.

We can then add a synapse population at the end of the modelDefinition(...) function,

model.add_synapse_population("Pop1self", "SPARSE_GLOBALG", 10,
pop1, pop1,
"StaticPulse", {}, s_ini, {}, {},
"ExpCond", ps_p, {},
init_connectivity(ring_model, {}))

The pygenn.GeNNModel.add_synapse_population parameters are

  • pop_name: The name of the synapse population
  • matrix_type: String specifying how the synaptic matrix is stored. See Synaptic matrix types for available options.
  • delay_steps: Homogeneous (axonal) delay for synapse population (in terms of the simulation time step DT).
  • source: pygenn.NeuronGroup or name of the (existing!) presynaptic neuron population.
  • target: pygenn.NeuronGroup or name of the (existing!) postsynaptic neuron population.
  • w_update_model: The type of weight update model. This should either be a string containing the name of a built in model or user-defined neuron type returned by pygenn.genn_model.create_custom_weight_update_class (see Weight update models).
  • wu_param_space: Dictionary containing the parameter values (common to all synapses of the population) for the weight update model.
  • wu_var_space: Dictionary containing the initial values or initialisation snippets for the weight update model's state variables
  • wu_pre_var_space: Dictionary containing the initial values or initialisation snippets for the weight update model's presynaptic state variables
  • wu_post_var_space: Dictionary containg the initial values or initialisation snippets for the weight update model's postsynaptic state variables
  • postsyn_model: The type of postsynaptic model. This should either be a string containing the name of a built in model or user-defined neuron type returned by pygenn.genn_model.create_custom_postsynaptic_class (see Postsynaptic integration methods).
  • ps_param_space: Dictionary containing the parameter values (common to all postsynaptic neurons) for the postsynaptic model.
  • ps_var_space: Dictionary containing the initial values or initialisation snippets for variables for the postsynaptic model's state variables
  • connectivity_initialiser: Optional argument, specifying the initialisation snippet for synapse population's sparse connectivity (see Sparse connectivity initialisation).

Adding the pygenn.GeNNModel.add_synapse_population command to the model definition informs GeNN that there will be synapses between the named neuron populations, here between population Pop1 and itself with a delay of 10 (0.1 ms) timesteps. At this point our script tenHHRing.py should look like this

import matplotlib.pyplot as plt
import numpy as np
from pygenn.genn_wrapper import NO_DELAY
"ring",
row_build_code=
"""
$(addSynapse, ($(id_pre) + 1) % $(num_post));
$(endRow);
""",
calc_max_row_len_func=create_cmlf_class(
lambda num_pre, num_post, pars: 1)())
model = GeNNModel("float", "tenHHRing")
model.dT = 0.1
p = {"gNa": 7.15, # Na conductance in [muS]
"ENa": 50.0, # Na equi potential [mV]
"gK": 1.43, # K conductance in [muS]
"EK": -95.0, # K equi potential [mV]
"gl": 0.02672, # leak conductance [muS]
"El": -63.563, # El: leak equi potential in mV,
"C": 0.143} # membr. capacity density in nF
ini = {"V": -60.0, # membrane potential
"m": 0.0529324, # prob. for Na channel activation
"h": 0.3176767, # prob. for not Na channel blocking
"n": 0.5961207} # prob. for K channel activation
s_ini = {"g": -0.2}
ps_p = {"tau": 1.0, # Decay time constant [ms]
"E": -80.0} # Reversal potential [mV]
pop1 = model.add_neuron_population("Pop1", 10, "TraubMiles", p, ini)
model.add_synapse_population("Pop1self", "SPARSE_GLOBALG", 10,
pop1, pop1,
"StaticPulse", {}, s_ini, {}, {},
"ExpCond", ps_p, {},
init_connectivity(ring_model, {}))
model.build()
model.load()
v = np.empty((2000, 10))
v_view = pop1.vars["V"].view
while model.t < 200.0:
model.step_time()
pop1.pull_var_from_device("V")
v[model.timestep - 1,:]=v_view[:]
fig, axis = plt.subplots()
axis.plot(v)
plt.show()

We can now we can build and run our new model in the same way we did in Tutorial 1 (Python). However, even after all our hard work, the result looks very similar to the plot we obtained at the end of Tutorial 1 (Python).

tenHHRingexample1.png

This is because none of the neurons are spiking so there are no spikes to propagate around the ring.

Providing initial stimuli

We can use a NeuronModels::SpikeSourceArray to inject an initial spike into the first neuron in the ring during the first timestep to start spikes propagating. We then need to add it to the network by adding the following before we call pygenn.GeNNModel.build:

stim_ini = {"startSpike": [0], "endSpike": [1]}
stim = model.add_neuron_population("Stim", 1, "SpikeSourceArray", {}, stim_ini)
stim.set_extra_global_param("spikeTimes", [0.0])
model.add_synapse_population("StimPop1", "SPARSE_GLOBALG", NO_DELAY,
stim, pop1,
"StaticPulse", {}, s_ini, {}, {},
"ExpCond", ps_p, {},
init_connectivity("OneToOne", {}))

Each neuron in the spike source array population has its own list of spikes which are concatenated together and stored in an array made accesible to all neurons in the population using an extra global parameter (see Extra global parameters). The startSpike and endSpike variables are then initialised with the indices of each neuron's section of the spikeTimes extra global parameter array. We then connect the spike source array to Pop1 using another synapse population with sparse connectivity, initialised using the built in InitSparseConnectivitySnippet::OneToOne model so the single neuron in the Stim population is connected to the first neuron in the ring. At this point our user code tenHHRing.py should look like this

import matplotlib.pyplot as plt
import numpy as np
from pygenn.genn_wrapper import NO_DELAY
"ring",
row_build_code=
"""
$(addSynapse, ($(id_pre) + 1) % $(num_post));
$(endRow);
""",
calc_max_row_len_func=create_cmlf_class(
lambda num_pre, num_post, pars: 1)())
model = GeNNModel("float", "tenHHRing")
model.dT = 0.1
p = {"gNa": 7.15, # Na conductance in [muS]
"ENa": 50.0, # Na equi potential [mV]
"gK": 1.43, # K conductance in [muS]
"EK": -95.0, # K equi potential [mV]
"gl": 0.02672, # leak conductance [muS]
"El": -63.563, # El: leak equi potential in mV,
"C": 0.143} # membr. capacity density in nF
ini = {"V": -60.0, # membrane potential
"m": 0.0529324, # prob. for Na channel activation
"h": 0.3176767, # prob. for not Na channel blocking
"n": 0.5961207} # prob. for K channel activation
s_ini = {"g": -0.2}
ps_p = {"tau": 1.0, # Decay time constant [ms]
"E": -80.0} # Reversal potential [mV]
stim_ini = {"startSpike": [0], "endSpike": [1]}
pop1 = model.add_neuron_population("Pop1", 10, "TraubMiles", p, ini)
stim = model.add_neuron_population("Stim", 1, "SpikeSourceArray", {}, stim_ini)
model.add_synapse_population("Pop1self", "SPARSE_GLOBALG", 10,
pop1, pop1,
"StaticPulse", {}, s_ini, {}, {},
"ExpCond", ps_p, {},
init_connectivity(ring_model, {}))
model.add_synapse_population("StimPop1", "SPARSE_GLOBALG", NO_DELAY,
stim, pop1,
"StaticPulse", {}, s_ini, {}, {},
"ExpCond", ps_p, {},
init_connectivity("OneToOne", {}))
stim.set_extra_global_param("spikeTimes", [0.0])
model.build()
model.load()
v = np.empty((2000, 10))
v_view = pop1.vars["V"].view
while model.t < 200.0:
model.step_time()
pop1.pull_var_from_device("V")
v[model.timestep - 1,:]=v_view[:]
fig, axis = plt.subplots()
axis.plot(v)
plt.show()

Finally if we build, make and run this model; and plot the first 200 ms of the ten neurons' membrane voltages - they now looks like this:

tenHHRingexample2.png

Previous | Top | Next