Revision ca55eb09bc94977ef711d13e1622b0dea0777fc3 authored by neworderofjamie on 14 January 2020, 10:33:43 UTC, committed by neworderofjamie on 16 February 2020, 19:17:36 UTC
# Conflicts: # include/genn/genn/code_generator/generateInit.h # include/genn/genn/code_generator/generateNeuronUpdate.h # include/genn/genn/code_generator/generateSynapseUpdate.h # src/genn/genn/code_generator/generateAll.cc # src/genn/genn/code_generator/generateInit.cc # src/genn/genn/code_generator/generateNeuronUpdate.cc # src/genn/genn/code_generator/generateRunner.cc # src/genn/genn/code_generator/generateSynapseUpdate.cc
1 parent 11bd42a
12_Tutorial.dox
//----------------------------------------------------------------------------
/*! \page Tutorial2 Tutorial 2
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 ``tenHHModel.cc`` to ``tenHHRingModel.cc`` and ``tenHHSimulation.cc`` to ``tenHHRingSimulation.cc``, e.g. on Linux or Mac:
\code
>> cp -r tenHH_project tenHHRing_project
>> cd tenHHRing_project
>> mv tenHHModel.cc tenHHRingModel.cc
>> mv tenHHSimulation.cc tenHHRingSimulation.cc
\endcode
Finally, to reduce confusion we should rename the model itself. Open `tenHHRingModel.cc`, change the model name inside,
\code
model.setName("tenHHRing");
\endcode
\section SynapseMatrix 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 ``tenHHRingModel.cc``:
\code
class Ring : public InitSparseConnectivitySnippet::Base
{
public:
DECLARE_SNIPPET(Ring, 0);
SET_ROW_BUILD_CODE(
"$(addSynapse, ($(id_pre) + 1) % $(num_post));\n"
"$(endRow);\n");
SET_MAX_ROW_LENGTH(1);
};
IMPLEMENT_SNIPPET(Ring);
\endcode
The ``SET_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.
\section addSynapse 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:
\code
WeightUpdateModels::StaticPulse::VarValues s_ini(
-0.2); // 0 - g: the synaptic conductance value
PostsynapticModels::ExpCond::ParamValues ps_p(
1.0, // 0 - tau_S: decay time constant for S [ms]
-80.0); // 1 - Erev: Reversal potential
\endcode
\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,
\code
model.addSynapsePopulation<WeightUpdateModels::StaticPulse, PostsynapticModels::ExpCond>(
"Pop1self", SynapseMatrixType::SPARSE_GLOBALG, 10,
"Pop1", "Pop1",
{}, s_ini,
ps_p, {},
initConnectivity<Ring>());
\endcode
The addSynapsePopulation parameters are
\arg WeightUpdateModel: template parameter specifying the type of weight update model (derived from WeightUpdateModels::Base).
\arg PostsynapticModel: template parameter specifying the type of postsynaptic model (derived from PostsynapticModels::Base).
\arg name string containing unique name of synapse population.
\arg mtype how the synaptic matrix associated with this synapse population should be represented. Here SynapseMatrixType::SPARSE_GLOBALG means that there will be sparse connectivity and each connection will have the same weight (-0.2 as specified previously).
\arg delayStep integer specifying number of timesteps of propagation delay that spikes travelling through this synapses population should incur (or NO_DELAY for none)
\arg src string specifying name of presynaptic (source) population
\arg trg string specifying name of postsynaptic (target) population
\arg weightParamValues parameters for weight update model wrapped in WeightUpdateModel::ParamValues object.
\arg weightVarInitialisers initial values or initialisation snippets for the weight update model's state variables wrapped in a WeightUpdateModel::VarValues object.
\arg postsynapticParamValues parameters for postsynaptic model wrapped in PostsynapticModel::ParamValues object.
\arg postsynapticVarInitialisers initial values or initialisation snippets for the postsynaptic model wrapped in PostsynapticModel::VarValues object.
\arg connectivityInitialiser snippet and any paramaters (in this case there are none) used to initialise the synapse population's sparse connectivity.
Adding the addSynapsePopulation command to the model definition informs GeNN that there will be synapses between the named neuron populations, here between population `Pop1` and itself.
At this point our model definition file `tenHHRingModel.cc` should look like this
\code
// Model definition file tenHHRing.cc
#include "modelSpec.h"
class Ring : public InitSparseConnectivitySnippet::Base
{
public:
DECLARE_SNIPPET(Ring, 0);
SET_ROW_BUILD_CODE(
"$(addSynapse, ($(id_pre) + 1) % $(num_post));\n"
"$(endRow);\n");
SET_MAX_ROW_LENGTH(1);
};
IMPLEMENT_SNIPPET(Ring);
void modelDefinition(ModelSpec &model)
{
// definition of tenHHRing
model.setDT(0.1);
model.setName("tenHHRing");
NeuronModels::TraubMiles::ParamValues p(
7.15, // 0 - gNa: Na conductance in muS
50.0, // 1 - ENa: Na equi potential in mV
1.43, // 2 - gK: K conductance in muS
-95.0, // 3 - EK: K equi potential in mV
0.02672, // 4 - gl: leak conductance in muS
-63.563, // 5 - El: leak equi potential in mV
0.143); // 6 - Cmem: membr. capacity density in nF
NeuronModels::TraubMiles::VarValues ini(
-60.0, // 0 - membrane potential V
0.0529324, // 1 - prob. for Na channel activation m
0.3176767, // 2 - prob. for not Na channel blocking h
0.5961207); // 3 - prob. for K channel activation n
model.addNeuronPopulation<NeuronModels::TraubMiles>("Pop1", 10, p, ini);
WeightUpdateModels::StaticPulse::VarValues s_ini(
-0.2); // 0 - g: the synaptic conductance value
PostsynapticModels::ExpCond::ParamValues ps_p(
1.0, // 0 - tau_S: decay time constant for S [ms]
-80.0); // 1 - Erev: Reversal potential
model.addSynapsePopulation<WeightUpdateModels::StaticPulse, PostsynapticModels::ExpCond>(
"Pop1self", SynapseMatrixType::SPARSE_GLOBALG, 100,
"Pop1", "Pop1",
{}, s_ini,
ps_p, {},
initConnectivity<Ring>());
}
\endcode
We can now build our new model:
\code
>> genn-buildmodel.sh tenHHRingModel.cc
\endcode
\note
Again, if you don't have an NVIDIA GPU and are running GeNN in CPU_ONLY mode, you can instead build with the `-c` option as described in \ref Tutorial1.
Now we can open the `tenHHRingSimulation.cc` file and update the file name of the model includes to match the name we set previously:
\code
// tenHHRingModel simulation code
#include "tenHHRing_CODE/definitions.h"
\endcode
Additionally, we need to add a call to a second initialisation function to ``main()`` after we call `initialize()`:
\code
initializeSparse();
\endcode
This initializes any variables associated with the sparse connectivity we have added (and will also copy any manually initialised variables to the GPU).
Then, after using the ``genn-create-user-project`` tool to create a new project with a model name of ``tenHHRing`` and using ``tenHHRingSimulation.cc`` rather than ``tenHHSimulation.cc``, we can build and run our new simulator in the same way we did in \ref Tutorial1.
However, even after all our hard work, if we plot the content of the first column against the subsequent 10 columns of `tenHHexample.V.dat` it looks very similar to the plot we obtained at the end of \ref Tutorial1.
\image html tenHHRingexample1.png
\image latex tenHHRingexample1.png width=10cm
This is because none of the neurons are spiking so there are no spikes to propagate around the ring.
\section initialConditions Providing initial stimuli
We can use a NeuronModels::SpikeSource to inject an initial spike into the first neuron in the ring during the first timestep to start spikes propagating.
Firstly we need to define another sparse connectivity initialisation snippet at the top of ``tenHHRingModel.cc`` which simply creates a single synapse on the first row of the synaptic matrix:
\code
class FirstToFirst : public InitSparseConnectivitySnippet::Base
{
public:
DECLARE_SNIPPET(FirstToFirst, 0);
SET_ROW_BUILD_CODE(
"if($(id_pre) == 0) {\n"
" $(addSynapse, $(id_pre));\n"
"}\n"
"$(endRow);\n");
SET_MAX_ROW_LENGTH(1);
};
IMPLEMENT_SNIPPET(FirstToFirst);
\endcode
We then need to add it to the network by adding the following to the end of the `modelDefinition(...)` function:
\code
model.addNeuronPopulation<NeuronModels::SpikeSource>("Stim", 1, {}, {});
model.addSynapsePopulation<WeightUpdateModels::StaticPulse, PostsynapticModels::ExpCond>(
"StimPop1", SynapseMatrixType::SPARSE_GLOBALG, NO_DELAY,
"Stim", "Pop1",
{}, s_ini,
ps_p, {},
initConnectivity<FirstToFirst>());
\endcode
and finally inject a spike in the first timestep (in the same way that the `t` variable is provided by GeNN to keep track of the current simulation time in milliseconds, `iT` is provided to keep track of it in timesteps):
\code
if(iT == 0) {
spikeCount_Stim = 1;
spike_Stim[0] = 0;
pushStimCurrentSpikesToDevice();
}
\endcode
\note
``spike_Stim[n]`` is used to specify the indices of the neurons in population ``Stim`` spikes which should emit spikes where \f$n \in [0, \mbox{spikeCount\_Stim} )\f$.
At this point our user code `tenHHRingModel.cc` should look like this
\code
// Model definintion file tenHHRing.cc
#include "modelSpec.h"
class Ring : public InitSparseConnectivitySnippet::Base
{
public:
DECLARE_SNIPPET(Ring, 0);
SET_ROW_BUILD_CODE(
"$(addSynapse, ($(id_pre) + 1) % $(num_post));\n"
"$(endRow);\n");
SET_MAX_ROW_LENGTH(1);
};
IMPLEMENT_SNIPPET(Ring);
class FirstToFirst : public InitSparseConnectivitySnippet::Base
{
public:
DECLARE_SNIPPET(FirstToFirst, 0);
SET_ROW_BUILD_CODE(
"if($(id_pre) == 0) {\n"
" $(addSynapse, $(id_pre));\n"
"}\n"
"$(endRow);\n");
SET_MAX_ROW_LENGTH(1);
};
IMPLEMENT_SNIPPET(FirstToFirst);
void modelDefinition(ModelSpec &model)
{
// definition of tenHHRing
model.setDT(0.1);
model.setName("tenHHRing");
NeuronModels::TraubMiles::ParamValues p(
7.15, // 0 - gNa: Na conductance in muS
50.0, // 1 - ENa: Na equi potential in mV
1.43, // 2 - gK: K conductance in muS
-95.0, // 3 - EK: K equi potential in mV
0.02672, // 4 - gl: leak conductance in muS
-63.563, // 5 - El: leak equi potential in mV
0.143); // 6 - Cmem: membr. capacity density in nF
NeuronModels::TraubMiles::VarValues ini(
-60.0, // 0 - membrane potential V
0.0529324, // 1 - prob. for Na channel activation m
0.3176767, // 2 - prob. for not Na channel blocking h
0.5961207); // 3 - prob. for K channel activation n
model.addNeuronPopulation<NeuronModels::TraubMiles>("Pop1", 10, p, ini);
model.addNeuronPopulation<NeuronModels::SpikeSource>("Stim", 1, {}, {});
WeightUpdateModels::StaticPulse::VarValues s_ini(
-0.2); // 0 - g: the synaptic conductance value
PostsynapticModels::ExpCond::ParamValues ps_p(
1.0, // 0 - tau_S: decay time constant for S [ms]
-80.0); // 1 - Erev: Reversal potential
model.addSynapsePopulation<WeightUpdateModels::StaticPulse, PostsynapticModels::ExpCond>(
"Pop1self", SynapseMatrixType::SPARSE_GLOBALG, 100,
"Pop1", "Pop1",
{}, s_ini,
ps_p, {},
initConnectivity<Ring>());
model.addSynapsePopulation<WeightUpdateModels::StaticPulse, PostsynapticModels::ExpCond>(
"StimPop1", SynapseMatrixType::SPARSE_GLOBALG, NO_DELAY,
"Stim", "Pop1",
{}, s_ini,
ps_p, {},
initConnectivity<FirstToFirst>());
}
\endcode
and `tenHHRingSimulation.cc`` should look like this:
\code
// Standard C++ includes
#include <fstream>
// tenHHRing simulation code
#include "tenHHRing_CODE/definitions.h"
int main()
{
allocateMem();
initialize();
initializeSparse();
std::ofstream os("tenHHRing_output.V.dat");
while(t < 200.0f) {
if(iT == 0) {
glbSpkStim[0] = 0;
glbSpkCntStim[0] = 1;
pushStimCurrentSpikesToDevice();
}
stepTimeU();
pullVPop1FromDevice();
os << t << " ";
for (int j= 0; j < 10; j++) {
os << VPop1[j] << " ";
}
os << std::endl;
}
os.close();
return 0;
}
\endcode
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:
\image html tenHHRingexample2.png
\image latex tenHHRingexample2.png width=10cm
-----
\link Tutorial1 Previous\endlink | \link Tutorial2 Top\endlink | \link UserGuide Next\endlink
*/
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...