GeNN  3.3.0
GPU enhanced Neuronal Networks (GeNN)
Tutorial 1

In this tutorial we will go through step by step instructions how to create and run your first GeNN simulation from scratch.

The Model Definition

In this tutorial we will use a pre-defined Hodgkin-Huxley neuron model (NeuronModels::TraubMiles) and create a simulation consisting of ten such neurons without any synaptic connections. We will run this simulation on a GPU and save the results - firstly to stdout and then to file.

The first step is to write a model definition function in a model definition file. Create a new directory and, within that, create a new empty file called tenHHModel.cc using your favourite text editor, e.g.

>> emacs tenHHModel.cc &
Note
The ">>" in the example code snippets refers to a shell prompt in a unix shell, do not enter them as part of your shell commands.

The model definition file contains the definition of the network model we want to simulate. First, we need to include the GeNN model specification code modelSpec.h. Then the model definition takes the form of a function named modelDefinition that takes one argument, passed by reference, of type NNmodel. Type in your tenHHModel.cc file:

// Model definintion file tenHHModel.cc
#include "modelSpec.h"
void modelDefinition(NNmodel &model)
{
// definition of tenHHModel
}

First we should set some options:

The defaultVarMode option controls how model state variables will be initialised. The VarMode::LOC_HOST_DEVICE_INIT_DEVICE setting means that initialisation will be done on the GPU, but memory will be allocated on both the host and GPU so the values can be copied back into host memory so they can be recorded. This setting should generally be the default for new models, but section Variable initialisation modes outlines the full range of options as well as how you can control this option on a per-variable level. Now we need to fill the actual model definition. Three standard elements to the `modelDefinition function are initialising GeNN, setting the simulation step size and setting the name of the model:

model.setDT(0.1);
model.setName("tenHHModel");
Note
With this we have fixed the integration time step to 0.1 in the usual time units. The typical units in GeNN are ms, mV, nF, and μS. Therefore, this defines DT= 0.1 ms.

Making the actual model definition makes use of the NNmodel::addNeuronPopulation and NNmodel::addSynapsePopulation member functions of the NNmodel object. The arguments to a call to NNmodel::addNeuronPopulation are

  • NeuronModel: template parameter specifying the neuron model class to use
  • const std::string &name: the name of the population
  • unsigned int size: The number of neurons in the population
  • const NeuronModel::ParamValues &paramValues: Parameter values for the neurons in the population
  • const NeuronModel::VarValues &varInitialisers: Initial values or initialisation snippets for variables of this neuron type

We first create the parameter and initial variable arrays,

// definition of tenHHModel
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
-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
Note
The comments are obviously only for clarity, they can in principle be omitted. To avoid any confusion about the meaning of parameters and variables, however, we recommend strongly to always include comments of this type.

Having defined the parameter values and initial values we can now create the neuron population,

model.addNeuronPopulation<NeuronModels::TraubMiles>("Pop1", 10, p, ini);

The model definition then needs to end on calling

model.finalize();

This completes the model definition in this example. The complete tenHHModel.cc file now should look like this:

// Model definintion file tenHHModel.cc
#include "modelSpec.h"
void modelDefinition(NNmodel &model)
{
// Settings
// definition of tenHHModel
model.setDT(0.1);
model.setName("tenHHModel");
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
-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.finalize();
}

This model definition suffices to generate code for simulating the ten Hodgkin-Huxley neurons on the a GPU or CPU. The second part of a GeNN simulation is the user code that sets up the simulation, does the data handling for input and output and generally defines the numerical experiment to be run.

Building the model

To use GeNN to build your model description into simulation code, use a terminal to navigate to the directory containing your tenHHModel.cc file and, on Linux or Mac, type:

>> genn-buildmodel.sh tenHHModel.cc

Alternatively, on Windows, type:

>> genn-buildmodel.bat tenHHModel.cc

If you don't have an NVIDIA GPU and are running GeNN in CPU_ONLY mode, you can invoke genn-buildmodel with a -c option so, on Linux or Mac:

>> genn-buildmodel.sh -c tenHHModel.cc

or on Windows:

>> genn-buildmodel.bat -c tenHHModel.cc

If your environment variables GENN_PATH and CUDA_PATH are correctly configured, you should see some compile output ending in Model build complete ....

User Code

GeNN will now have generated the code to simulate the model for one timestep using a function stepTimeCPU() (execution on CPU only) or stepTimeGPU() (execution on a GPU). To make use of this code, we need to define a minimal C/C++ main function. For the purposes of this tutorial we will initially simply run the model for one simulated second and record the final neuron variables into a file. Open a new empty file tenHHSimulation.cc in an editor and type

// tenHHModel simulation code
#include "tenHHModel_CODE/definitions.h"
int main()
{
allocateMem();
initialize();
return 0;
}

This boiler plate code includes the header file for the generated code definitions.h in the subdirectory tenHHModel_CODE where GeNN deposits all generated code (this corresponds to the name passed to the NNmodel::setName function). Calling allocateMem() allocates the memory structures for all neuron variables and initialize() launches a GPU kernel which initialise all state variables to their initial values. Now we can use the generated code to integrate the neuron equations provided by GeNN for 1000ms using the GPU unless we are running CPU_ONLY mode. To do so, we add after initialize();

Note
The t variable is provided by GeNN to keep track of the current simulation time in milliseconds.
while (t < 1000.0f) {
#ifdef CPU_ONLY
stepTimeCPU();
#else
stepTimeGPU();
#endif
}

and, if we are running the model on the GPU, we need to copy the result back to the host before outputting it to stdout,

#ifndef CPU_ONLY
pullPop1StateFromDevice();
#endif
for (int j= 0; j < 10; j++) {
cout << VPop1[j] << " ";
cout << mPop1[j] << " ";
cout << hPop1[j] << " ";
cout << nPop1[j] << endl;
}

pullPop1StateFromDevice() copies all relevant state variables of the Pop1 neuron group from the GPU to the CPU main memory. Then we can output the results to stdout by looping through all 10 neurons and outputting the state variables VPop1, mPop1, hPop1, nPop1.

Note
The naming convention for variables in GeNN is the variable name defined by the neuron type, here TraubMiles defining V, m, h, and n, followed by the population name, here Pop1.

This completes the user code. The complete tenHHSimulation.cc file should now look like

// tenHHModel simulation code
#include "tenHHModel_CODE/definitions.h"
int main()
{
allocateMem();
initialize();
while (t < 1000.0f) {
#ifdef CPU_ONLY
stepTimeCPU();
#else
stepTimeGPU();
#endif
}
#ifndef CPU_ONLY
pullPop1StateFromDevice();
#endif
for (int j= 0; j < 10; j++) {
cout << VPop1[j] << " ";
cout << mPop1[j] << " ";
cout << hPop1[j] << " ";
cout << nPop1[j] << endl;
}
return 0;
}

Building the simulator (Linux or Mac)

On Linux and Mac, GeNN simulations are typically built using a simple Makefile. By convention we typically call this GNUmakefile. Create this file and enter

EXECUTABLE :=tenHHSimulation
SOURCES :=tenHHSimulation.cc
include $(GENN_PATH)/userproject/include/makefile_common_gnu.mk

This defines that the final executable of this simulation is named tenHHSimulation and the simulation code is given in the file tenHHSimulation.cc that we completed above. Now type

make

or, if you don't have an NVIDIA GPU and are running GeNN in CPU_ONLY mode, type:

make CPU_ONLY=1

Building the simulator (Windows)

So that projects can be easily debugged within the Visual Studio IDE (see section Debugging suggestions for more details), Windows projects are built using an MSBuild script typically with the same title as the final executable. Therefore create tenHHSimulation.vcxproj and type:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="tenHHModel_CODE\generated_code.props" />
<ItemGroup>
<ClCompile Include="tenHHSimulation.cc" />
</ItemGroup>
</Project>

Now type

msbuild tenHHSimulation.vcxproj /p:Configuration=Release

or, if you don't have an NVIDIA GPU and are running GeNN in CPU_ONLY mode, type:

msbuild tenHHSimulation.vcxproj /p:Configuration=Release_CPU_ONLY

Running the Simulation

You can now execute your newly-built simulator on Linux or Mac with

./tenHHSimulation

Or on Windows with

tenHHSimulation

The output you obtain should look like

-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243
-63.7838 0.0350042 0.336314 0.563243

Reading

This is not particularly interesting as we are just observing the final value of the membrane potentials. To see what is going on in the meantime, we need to copy intermediate values from the device and save them into a file. This can be done in many ways but one sensible way of doing this is to replace the calls to stepTimeGPU in tenHHSimulation.cc with something like this:

ofstream os("tenHH_output.V.dat");
while (t < 1000.0f) {
#ifdef CPU_ONLY
stepTimeCPU();
#else
stepTimeGPU();
#endif
#ifndef CPU_ONLY
pullPop1StateFromDevice();
#endif
os << t << " ";
for (int j= 0; j < 10; j++) {
os << VPop1[j] << " ";
}
os << endl;
}
os.close();
Note
t is a global variable updated by the GeNN code to keep track of elapsed simulation time in ms.

You will also need to add:

#include <fstream>

to the top of tenHHSimulation.cc. After building the model; and building and running the simulator as described above there should be a file tenHH_output.V.dat in the same directory. If you plot column one (time) against the subsequent 10 columns (voltage of the 10 neurons), you should observe dynamics like this:

tenHHexample.png

However so far, the neurons are not connected and do not receive input. As the NeuronModels::TraubMiles model is silent in such conditions, the membrane voltages of the 10 neurons will simply drift from the -60mV they were initialised at to their resting potential.


Previous | Top | Next