For further information, the slides that accompany this tutorial are also here.
Part Two: Linear Transformations
Creating Terminations
- Connections between ensembles are built using Origins and
Terminations. The Origin from one ensemble can be connected to the
Termination on the next ensemble
- Create two ensembles. They can have different neural properties and
different numbers of neurons, but for now make sure they are both
one-dimensional.
- Right-click on the second ensemble and select Add Decoded Termination
- Provide a name (for example, “input”)
- Set the input dimension to 1 and use Set Weights to set the connection weight to 1
- Set tauPSC to 0.01 (this synaptic time constant differs according
to which neurotransmitter is involved. 10ms is the time constant for
AMPA (5-10ms).


Creating Projections
- We can now connect the two neural ensembles.
- Every ensemble automatically has an origin called X. This is an
origin suitable for building any linear transformation. In Part Three
we will show how to create origins for non-linear transformations.

- Click and drag from the origin to the termination. This will create the desired projection.

Adding Inputs
- In order to test that this projection works, we need to set the
value encoded by the first neural ensemble. We do this by creating an
input to the system. This is how all external inputs to Nengo models
are specified.
- Right-click inside the Network and choose Create New->Function Input.
- Give it a name (for example, “external input”)
- Set its output dimensions to 1

- Press Set Function to define the behaviour of this input
- Select Constant Function from the drop-down list and then press Set to define the value itself. For this model, set it to 0.5.



- Add a termination on the first neural ensemble and create a projection from the new input to that ensemble.

Interactive Plots
- To observe the performance of this model, we now switch over to
Interactive Plots. This allows us to both graph the performance of the
model and adjust its inputs on-the-fly to see how this affects
behaviour.
- Start Interactive Plots by right-clicking inside the Network and selecting Interactive Plots

- The text shows the various components of your model, and the arrows indicate the synaptic connections between them.
- You can move the components by left-click dragging them, and you can move all the components by dragging the background.
- You can hide a component by right-clicking on it and selecting “hide”
- To show a hidden component, right click on the background and select the component by name
- The bottom of the window shows the controls for running the simulation.
- The simulation can be started and stopped by pressing the Play
or Pause button at the bottom right. Doing this right now will run the
simulation, but no data will be displayed since we don’t have any
graphs open yet!
- The reset button on the far left clears all the data from the simulation and puts it back to the beginning.
- In the middle is a slider that shows the current time in the
simulation. Once a simulation has been run, we can slide this back and
forth to observe data from different times in the simulation.
- Right-clicking on a component also allows us to select a type of data to show about that component.
- Right-click on A and select “value”. This creates a graph that
shows the value being represented by the neuron in ensemble A. You can
move the graph by left-click dragging it, and you can resize it by
dragging near the corners or using a mouse scroll wheel.
- Press the Play button at the bottom-right or the window and confirm
that this group of neurons successfully represents its input value,
which we previously set to be 0.5.

- Now let us see what happens if we change the input. Right-click on
the input and select “control”. This lets us vary the input while the
simulation is running.
- Drag the slider up and down while the simulation is running (press
Play again if it is paused). The neurons in ensemble A should be able
to successfully represent the changing values.

- We can also view what the individual neurons are doing during the
simulation. Right-click on A and choose “spike raster”. This shows the
individual spikes coming from the neurons. Since there are 100 neurons
in ensemble A, the spikes from only a sub-set of these are shown. You
can right-click on the spike raster graph and adjust the proportion of
spikes shown. Change it to 50%.
- Run the simulation and change the input. This will affect the neuron firing patterns.

- We can also see the voltage levels of all the individual neurons.
Right-click on A and choose “voltage grid”. Each neuron is shown as a
square and the shading of that square indicates the voltage of that
neuron’s cell membrane, from black (resting potential) to white (firing
threshold). Yellow indicates a spike.
- The neurons are initially randomly ordered. You can change this by
right-clicking on the voltage grid and selecting “improve layout”. This
will attempt to re-order the neurons so that neurons with similar
firing patterns are near each other, as they are in the brain. This
does not otherwise affect the simulation in any way.
- Run the simulation and change the input. This will affect the neuron voltage.

- So far, we have just been graphing information about neural
ensemble A. We have shown that these 100 neurons can accurately
represent a value that is directly input to them.
- For this to be useful for constructing cognitive models, we need to
also show that the spiking output from this group of neurons can be
used to transfer this information from one neural group to another.
- In other words, we want to show that B can represent the same
thing as A, where B’s only input is the neural firing from group A. For
this to happen, the correct synaptic connection weights between A and B
(as per the Neural Engineering Framework) must be calculated.
- Nengo automatically calculates these weights whenever an origin is created.
- We can see that this communication is successful by creating graphs for ensemble B.
- Do this by right-clicking on B and selecting “value”, and then right-clicking on B again and selecting “voltage grid”.
- To aid in identifying which graph goes with which ensemble, right click on a graph and select “label”.
- Graphs can be moved (by dragging) and resized (by dragging near the edges and corners or by the mouse scroll wheel) as desired.

- Notice that the neural ensembles can be representing the same value, but have a different firing pattern.
- Close the Interactive Plots when you are finished.
Adding Scalars
- If we want to add two values, we can simply add another termination to the final ensemble and project to it as well.
- Create a termination on the second ensemble called “input 2”
- Create a new ensemble
- Create a projection from the X origin to input 2

- Create a new Function input and set its value to -0.7
- Add the required termination and projection to connect it to the new ensemble

- Switch to Interactive Plots.
- Show the controls for the two inputs
- Create value graphs for the three neural ensembles
- Press Play to start the simulation. The value for the final ensemble should be 0.5-0.7=-0.2
- Use the control sliders to adjust the input. The output should still be the sum of the inputs.

- This will be true for most values. However, if the sum is outside
of the radius that was set when the neural group was formed (in this
case, from -1 to 1), then the neurons may not be able to fire fast
enough to represent that value (i.e. they will saturate). Try this by
computing 1+1. The result will only be around 1.3.
- To accurately represent values outside of the range -1 to 1, we
need to change the radius of the output ensemble. Return to the
standard black editing mode and right-click on ensemble B. Select
“Configure” and change its radii to 2. Now return to the Interactive
Plots. The network should now accurately compute that 1+1=2.
Adjusting Transformations
- So far, we have only considered projections that do not adjust the
values being represented in any way. However, due to the NEF derivation
of the synaptic weights between neurons, we can adjust these to create
arbitrary linear transformations (i.e. we can multiply any represented
value by a matrix).
- Each termination in Nengo has an associated transformation matrix.
This can be adjusted as desired. In this case, we will double the
weight of the original value, so instead of computing x+y, the network
will compute 2x+y.
- Right-click on the first termination in the ensemble that has two
projections coming into it. Select Configure. Double-click on transform.
- Double-click on the 1.0 and change it to 2.0

- Click on OK and then Done
- Now run the simulation. The final result should be 2(0.5)-0.7=0.3
Multiple Dimensions
- Everything discussed above also applies to ensembles that represent more than one dimension.
- To create these, set the number of dimensions to 2 when creating the ensemble

- When adding a termination, the input dimension can be adjusted.
This defines the shape of the transformation matrix for the
termination, allowing for projections that change the dimension of the
data

For example, two 1-dimensional values can be combined into a
single two-dimensional ensemble. This would be done with two
terminations: one with a transformation (or coupling) matrix of [1 0]
and the other with [0 1]. If the two inputs are called a and b, this
will result in the following calculation:
- a*[1 0] + b*[0 1] = [a 0] + [0 b] = [a b]
- This will be useful for creating non-linear transformations, as discussed further in the next section.
There are additional ways to view 2D representations in the interactive plots
- Including plotting the activity of the neurons along their preferred direction vectors
- Plotting the 2D decoded value of the representation

Scripting
- Along with the ability to construct models using this
point-and-click interface, Nengo also provides a Python scripting
language interface for model creation. These examples can be seen in
the “demo” directory.
- To create the communication channel through the scripting interface, go to the Script Console (Ctrl-P) and type
run demo/communication.py
- The actual code for this can be seen by opening the communication.py file in the demo directory.
import nef
net=nef.Network('Communications Channel')
input=net.make_input('input',[0.5])
A=net.make('A',100,1)
B=net.make('B',100,1)
net.connect(input,A)
net.connect(A,B)
net.add_to(world)
- The following demo scripts create models similar to those seen in this part of the tutorial:
demo/singleneuron.py
shows what happens with an ensemble with only a single neuron on it (poor representation)
demo/twoneurons.py
shows two neurons working together to represent
demo/manyneurons.py
shows a standard ensemble of 100 neurons representing a value
demo/communication.py
shows a communication channel
demo/addition.py
shows adding two numbers
demo/2drepresentation.py
shows 100 neurons representing a 2-D vector
demo/combining.py
shows two separate values being combined into a 2-D vector
Part Four: Feedback and Dynamics
Storing Information Over Time: Constructing an Integrator
- The basis of many of our cognitive models is the integrator.
Mathematically, the output of this network should be the integral of
the inputs to this network.
- Practically speaking, this means that if the input to the
network is zero, then its output will stay at whatever value it is
currently at. This makes it the basis of a neural memory system, as a
representation can be stored over time.
- Integrators are also often used in sensorimotor systems, such as eye control
- For an integrator, a neural ensemble needs to connect to itself
with a transformation weight of 1, and have an input with a weight of
τ, which is the same as the synaptic time constant of the
neurotransmitter used.
- Create a one-dimensional ensemble called Integrator. Use 100 neurons and a radius of 1.
- Add two terminations with synaptic time constants of 0.1s. Call the
first one “input” and give it a weight of 0.1. Call the second one
“feedback” and give it a weight of 1.
- Create a new Function input using a Constant Function with a value of 1.
- Connect the Function input to the input termination
- Connect the X origin of the ensemble back to its own feedback termination.

- Go to Interactive Plots. Create a graph for the value of the ensemble (right-click on the ensemble and select “value”).
- Press Play to run the simulation. The value stored in the ensemble
should linearly increase, reaching a value of 1 after approximately 1
second.
- You can increase the amount of time shown on the graphs in
Interactive Plots. Do this by clicking on the small downwards-pointing
arrow at the bottom of the window. This will reveal a variety of
settings for Interactive Plots. Change the “time shown” to 1.

Representation Range
- What happens if the previous simulation runs for longer than one second?
- The value stored in the ensemble does not increase after a certain
point. This is because all neural ensembles have a range of values they
can represent (the radius), and cannot accurately represent outside of
that range.

- Adjust the radius of the ensemble to 1.5 using either the Configure
interface or the script console (that.radii=[1.5]). Run the model
again. It should now accurately integrate up to a maximum of 1.5.

Complex Input
- We can also run the model with a more complex input. Change the
Function input using the following command from the script console
(after clicking on it in the black model editing mode interface). Press
Ctrl-P to show the script console.
that.functions=[ca.nengo.math.impl.PiecewiseConstantFunction([0.2,0.3,0.44,0.54,0.8,0.9],[0,5,0,-10,0,5,0])]
- You can see what this function looks like by right-clicking on it in the editing interface and selecting “Plot”.

- Return to Interactive Plots and run the simulation.

Adjusting Synaptic Time Constants
- You can adjust the accuracy of an integrator by using different neurotransmitters.
- Change the input termination to have a tau of 0.01 (10ms: GABA) and
a transform to be 0.01. Also change the feedback termination to have a
tau of 0.01 (but leave its transform at 1).

- By using a shorter time constant, the network dynamics are more sensitive to small-scale variation (i.e. noise).
This indicates how important the use of a particular
neurotransmitter is, and why there are so many different types with
vastly differing time constants.
- AMPA: 2-10ms
- GABAA: 10-20ms
- NMDA: 20-150ms
- The actual details of these time constants vary across the brain as
well. We are collecting empirical data on these from various sources at
http://ctn.uwaterloo.ca/~cnrglab/?q=node/505
You can also run this example using scripting
Controlled Integrator
- We can also build an integrator where the feedback transformation (1 in the previous model) can be controlled.
- This allows us to build a tunable filter.
- This requires the use of multiplication, since we need to multiply
two stored values together. This was covered in the previous part of
the tutorial.
- We can efficiently implement this by using a two-dimensional
ensemble. One dimension will hold the value being represented, and the
other dimension will hold the transformation weight.
- Create a two-dimensional neural ensemble with 225 neurons and a radius of 1.5.
- Create the following three terminations:
- “input”: time constant of 0.1, 1 dimensional, with a
transformation matrix of [0.1 0]. This acts the same as the input in
the previous model, but only affects the first dimension.
- “control”: time constant of 0.1, 1 dimensional, with a
transformation matrix of [0 1]. This stores the input control signal
into the second dimension of the ensemble.
- “feedback”: time constant of 0.1, 1 dimensional, with a
transformation matrix of [1 0]. This will be used in the same manner as
the feedback termination in the previous model.
- Create a new origin that multiplies the values in the vector together
- This is exactly the same as the multiplier in the previous part of this tutorial
- This is a 1 dimensional output, with a User-defined Function of x0*x1
- Create two function inputs called “input” and “control”. Start with Constant functions with a value of 1
- Use the script console to set the “input” function by clicking on it and entering the same input function as used above.
that.functions=[ca.nengo.math.impl.PiecewiseConstantFunction([0.2,0.3,0.44,0.54,0.8,0.9],[0,5,0,-10,0,5,0])]
- Connect the input function to the input termination, the control
function to the control termination, and the product origin to the
feedback termination.

- Go to Interactive Plots and show a graph for the value of the
ensemble (right-click->X->value). If you run the simulation, this
graph will show the values of both variables stored in this ensemble
(the integrated value and the control signal). For clarity, turn off
the display of the cotrol signal by right-clicking on the graph and
removing the checkmark beside “v[1]”.
- The performance of this model should be similar to that of the non-controlled integrator.

- Now adjust the control input to be 0.3 instead of 1. This will make
the integrator into a leaky integrator. This value adjusts how quickly
the integrator forgets over time.

- You can also run this example using scripting
run demo/controlledintegrator.py