import numpy
import nengo
from nengo.utils.ensemble import tuning_curves
from nengo.dists import Uniform

N = 100

model = nengo.Network(label='Neurons')
with model:
    neurons = nengo.Ensemble(N, dimensions=1,
                             max_rates=Uniform(100,200)) #Defaults to LIF neurons,
                                        #with random gains and biases for
                                        #neurons between 100-200hz over -1,1

    connection = nengo.Connection(neurons, neurons, #This is just to generate the decoders
                                  solver=nengo.solvers.LstsqNoise(noise=0.2)) #Add noise ###NEW

sim = nengo.Simulator(model)

d = sim.data[connection].weights.T

x, A = tuning_curves(neurons, sim)

A_noisy = A + numpy.random.normal(scale=0.2*numpy.max(A), size=A.shape)

xhat = numpy.dot(A_noisy, d)

pyplot.plot(x, A_noisy)
xlabel('x')
ylabel('firing rate (Hz)')

figure()
plot(x, x)
plot(x, xhat)
xlabel('$x$')
ylabel('$\hat{x}$')
ylim(-1, 1)
xlim(-1, 1)

print('RMSE', np.sqrt(np.average((x-xhat)**2)))


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:

- Can we still decode $x$ using a sum of activities?
    - $\hat{x}(t)=\sum_i{a_i(t) d_i}$

Let's use exactly the same technique we did before to find $d$
$ d = \Gamma^{-1} \Upsilon $
$ \Upsilon_i = \sum_x a_i x dx$
$ \Gamma_{ij} = \sum_x a_i a_j dx $
Can you write Python code for it?

import numpy as np
import nengo

model = nengo.Network(label='Decoding Neurons')

with model:
    stim = nengo.Node(lambda t: np.sin(10*t))
    ens = nengo.Ensemble(2, dimensions=1,
                         encoders = [[1],[-1]],
                         intercepts = [-.5, -.5],
                         max_rates = [100, 100])
    temp = nengo.Ensemble(10, dimensions=1)

    nengo.Connection(stim, ens)
    connection = nengo.Connection(ens, temp) #This is just to generate the decoders

    stim_p = nengo.Probe(stim)
    spikes_p = nengo.Probe(ens.neurons, 'spikes')

sim = nengo.Simulator(model)
sim.run(.6)

t = sim.trange()

x = sim.data[stim_p][:,0]

A = sim.data[spikes_p]

gamma=np.dot(A.T,A)
upsilon=np.dot(A.T,x)
d = np.dot(np.linalg.pinv(gamma),upsilon)

xhat = np.dot(A, d)

figure(figsize=(8,4))
plot(t, x, label='x')
plot(t, xhat)
ylabel('$x$')
#ylim(-1,1)
xlabel('Time');


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
Better, but how can we improve this?
Different shapes for $h(t)$?
Different $\sigma$?

Is there a general solution?

Optimal filters

Let's try to find the optimal filter for a particular special case
Two neurons
Same $\alpha$ and $J^{bias}$ but different $e$ (-1 and +1)

$$\hat{x}(t)=\sum_i^N{a_i(t) * h(t) d_i}$$

Since there are two neurons and they are opposites of each other, we can assume that $d_1=-d_2$ (i.e. they both contribute equally, but oppositely)

$\hat{x}(t)= \sum_i{a_i(t) * h(t) d_i} $
$\hat{x}(t)= a_1(t)h(t)d_1 + a_2(t)h(t)d_2$
$\hat{x}(t) = (a_1(t)-a_2(t))*h(t) d_1$
$\hat{x}(t) = (a_1(t)-a_2(t))*h(t)$ (since we can roll the constant into $h(t)$

Let's call $a_1(t)-a_2(t)=r(t)$, for response

In this case, it's a difference of spike trains

$\hat{x}(t)=r(t) * h(t)$
Random inputs

Can't just optimize for decoding a sine wave

Needs to work for any random input

What do we mean by a random input?
Can you write Python code for it?

import numpy as np
x = np.random.uniform(-1,1,500)
plot(np.arange(500)*0.001, x);


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
How well does this work?
Can you write Python code for it?

in_stim.output.high=10

sim = nengo.Simulator(model)
sim.run(T)
sig1 = sim.data[stim_p][:,0]

r = sim.data[dec_p][:,0] #properly scaled r

X = np.fft.fftshift(np.fft.fft(sig1))
R = np.fft.fftshift(np.fft.fft(r))

sigma_t = 0.06
W2 = np.exp(-omega**2*sigma_t**2)
Rmag = R*R.conjugate()
Rmag[(Rmag.shape[0]-1)/2 + 1] = .1 #remove zero DC term
H = (np.convolve(X*R.conjugate(),W2, 'same')) / (np.convolve(Rmag,W2, 'same'))
h = np.fft.fftshift(np.fft.ifft(np.fft.ifftshift(H))).real


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
If we're analyzing the data after the fact, this is fine
But what if we're just sitting here observing the neural activity and wanting to know what's being represented right now?
Notice that the blue line (the filtered value we're using for decoding) starts going up before the spike happens
The filtered value is using information from the future.

This means that when we look at $\hat{x}$ this way, we're getting a better estimate of $x$ than it is possible for other neurons to get
(assuming neurons aren't psychic)

What could we do instead?

Biologically plausible filter

We're looking for something that takes a spike and turns it into some smooth signal
Instead of coming up with the optimal filter, is there anything from neuroscience we could use instead?
What actually happens when a spike occurs?
What is the input to the next neuron that occurs when a spike happens?

A spike causes a vesicle to release neurotransmitter
The amount of neurotransmitter causes current to flow into the next neuron
The neurotransmitter is slowly reabsorbed
What is the current that goes into the next neuron thanks to a single spike?

(image stolen from here)

So other neurons don't get spikes as inputs
They get these post-synaptic currents

$$
h(t) = \begin{cases}
    e^{-t/\tau} &\mbox{if } t > 0 \
    0 &\mbox{otherwise}
    \end{cases}
$$

This is the standard simple model

$\tau$ is different for different neurotransmitters and different neurotransmitter receptors
GABA-A-R: $\tau=10.41 \pm 6.16$ ms
AMPA-type GluR: $\tau=2.2 \pm 0.2$ ms
NMDA-type GluR: $\tau=146 \pm 9.1$ ms

For more complex models, some people use

$$
h(t) = \begin{cases}
    t^n e^{-t/\tau} &\mbox{if } t > 0 \
    0 &\mbox{otherwise}
    \end{cases}
$$
Can you write Python code for it?

dt = 0.001
tau = 0.05
t_h = np.arange(1000)*dt-0.5
h = np.exp(-t_h/tau)
h[np.where(t_h<0)]=0
h = h/norm(h,1)

figure()
plot(t_h, h)
xlabel('t')
ylabel('h(t)');

in_stim.output.high=10

sim = nengo.Simulator(model)
sim.run(T)
sig = sim.data[stim_p][:,0]

fspikes1 = np.convolve(sim.data[spikes_p][:,0], h, mode='same')
fspikes2 = np.convolve(sim.data[spikes_p][:,1], h, mode='same')

A = np.array([fspikes1, fspikes2]).T

d = sim.data[connection].weights.T

xhat = np.dot(A, d)

t = sim.trange()

from nengo.utils.matplotlib import rasterplot
figure(figsize=(8, 4))
ax = gca()
plot(t, sig,'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[spikes_p], ax=ax.twinx(), use_eventplot=True, color='k')
ylabel("Neuron")

figure(figsize=(8,4))
plot(t, sig, label='x')
plot(t, fspikes1*d[0])
plot(t, fspikes2*d[1])
ylabel('$x$')
ylim(-1,1)
xlabel('time (s)')

figure(figsize=(8,4))
plot(t, sig, label='x')
plot(t, xhat, label='x')
ylabel('$x$')
ylim(-1,1)
xlabel('time (s)');


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
It's a bit better on the original signal
And is 'less worse' when tested on a different signal
What about higher frequency inputs?  Or lower frequency?
Can you write Python code for it?

in_stim.output.high=2
sim = nengo.Simulator(model)
sim.run(T)
sig3 = sim.data[stim_p][:,0]

fspikes1_3 = np.convolve(sim.data[spikes_p][:,0], h, mode='same')
fspikes2_3 = np.convolve(sim.data[spikes_p][:,1], h, mode='same')
spikes_3 = (sim.data[spikes_p][:,0]-sim.data[spikes_p][:,1])*sim.dt

A3 = np.array([fspikes1_3, fspikes2_3]).T

d = sim.data[connection].weights.T

x3hat = np.dot(A3, d)[:,0]

t = sim.trange()
figure()
plot(t, sig3)
plot(t, x3hat)
plot(t, spikes_3, 'k.')
ylim(-1.5,1.5)
print 'RMSE:', np.average((sig3-x3hat)**2)


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
Let's try a sine wave input
Can you write Python code for it?

#have to run previous cells

with model:
    stimulus.output = lambda t: np.sin(10*t)

sim = nengo.Simulator(model)
sim.run(.6)

nengo.utils.matplotlib.plot_tuning_curves(ens, sim)

t = sim.trange()
figure(figsize=(10,4))
plot(t, sim.data[stim_p], label='x')
ax = gca()
ax.plot(t, sim.data[voltage_p],'g', label='v')
ylim((-1,2))
ylabel('$x$')
xlabel('time (s)')
legend(loc='lower left');

rasterplot(t, sim.data[spikes_p], ax=ax.twinx(), use_eventplot=True)
title('Voltage and spikes over time')
ylabel("Voltage");
xlabel("Time");
ylabel('$neurons$')
ylim((-1,2))


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
Maybe that's a bit too random
Many signals don't change that quickly
How do we get a random signal that doesn't change so quickly?
I.e. a signal with a limit on its maximum frequency?
Can you write Python code for it?

from nengo.processes import WhiteSignal

dt = 0.001
max_freq = 10

model = nengo.Network('White Noise')
with model:
    sig = nengo.Node(output=WhiteSignal(1, high=10, rms=0.5))

    sig_p = nengo.Probe(sig)

sim = nengo.Simulator(model)
sim.run(1.0)

t = sim.trange()
x = sim.data[sig_p]

figure(figsize=(8, 4))
ax = gca()
plot(t, sim.data[sig_p])
ylabel("Output")
xlabel("Time");


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
So this is how we're going to model neurons over time
Weaknesses of this approach
These are "point" neurons (i.e. we're assuming the body of the neuron is just one infinitessimal point, rather than having lots and lots of compartments, each with its own RC circuit)
Assumes R is a constant
Assumes $V_{th}$ is a constant
Assumes no adaptation (it's harder for a neuron to fire after it has recently fired)
Doesn't consider nonlinearities at the input

Each of these could be added
Adding these increases computational cost
Not going to worry about it now
But we should make sure everything we do would also work with more detailed neuron models

The time vs. rate-coding debate

There is a standard ongoing debate in the neuroscience literature about whether neurons use a "rate" code or a "timing" code
Rate code
The important thing is the firing rate over some window of time (~100ms)

Time code
The precise pattern of spike generation carries information

The NEF gets rid of this distinction
Some people call what we do rate coding, some call it temporal coding

The important thing is how to do the decoding...essentially
'Slow' decoders will seem like a rate code
'Fast' decoders will seem like a time code

Temporal Decoding

So that's what happens when we allow $x(t)$ to vary over time

We keep $J(t)=\alpha e \cdot x(t) + J^{bias}$
Feed that into the neuron model $G[J(t)]$
We get out a sequence of spikes

How do decode this?

How do we get an estimate of $x$ given that sequence of spikes?

Can we stick with linear decoding?

Let's consider a case with 2 neurons
For simplicity, use the same $\alpha$ and $J^{bias}$, but use $e_1=1$ and $e_2=-1$
Can you write Python code for it?

#assumes has done %pylab inline
import numpy as np
import nengo
from nengo.utils.matplotlib import rasterplot

model = nengo.Network(label='Two Neurons')

with model:
    stim = nengo.Node(lambda t: np.sin(10*t))
    ens = nengo.Ensemble(2, dimensions=1,
                         encoders = [[1],[-1]],
                         intercepts = [-.5, -.5],
                         max_rates= [100, 100])
    nengo.Connection(stim, ens)

    stim_p = nengo.Probe(stim)
    spikes_p = nengo.Probe(ens.neurons, 'spikes')

sim = nengo.Simulator(model)
sim.run(.6)

nengo.utils.matplotlib.plot_tuning_curves(ens, sim)

t = sim.trange()
figure(figsize=(12, 6))
ax = gca()
plot(t, sim.data[stim_p],'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[spikes_p], ax=ax.twinx(), use_eventplot=True)
axis('tight')
ylabel("Neuron");


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
Testing how well it generalizes

If it's a good decoder, it should work for other inputs too
Can you write Python code for it?

sim = nengo.Simulator(model)
sim.run(T)
sig = sim.data[stim_p][:,0]

fspikes1 = np.convolve(sim.data[spikes_p][:,0], h, mode='same')
fspikes2 = np.convolve(sim.data[spikes_p][:,1], h, mode='same')

A = np.array([fspikes1, fspikes2]).T
d = sim.data[connection].weights.T

xhat = np.dot(A, d)[:,0]

t = sim.trange()
figure()
plot(t, sig)
plot(t, xhat)
show()
print 'RMSE:', np.average((sig-xhat)**2)


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
The decoder works better if it is made for the right range of frequencies
What does this tell us about rate vs timing codes?
Doesn't make much sense without thinking about the signal properties
These methods can account for both kinds of codes and smoothly vary between

Optimal filters reconsidered

So we have now found the optimal filter to apply to the spike data to decode out information
Note a limitation: we've only done this for the case of 2 neurons!
But we could use the same filter for many more neurons

Recall what this windowed filter looks like
Can you write Python code for it?

figure()
plot(t-T/2, h)
xlabel('t')
ylabel('h(t)')
xlim(-0.2, 0.2)


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
Things to remember
$X(\omega)$ values are complex numbers
When we plot $X(\omega)$ we actually plot $|X(\omega)|$
Since $x(t)$ should be real (i.e. no imaginary component), we need to constrain $X(\omega)$
This constraint is that $X(\omega)=X(-\omega)^*$
(i.e., the complex conjugate: the real parts are equal, and the imaginary parts switch sign)

Finding an optimal decoder

$\hat{x}(t)=r(t) * h(t)$
$r(t)$ is the response of the two neurons combined together
$r(t)=a_1(t)-a_2(t)$
$h(t)$ is the filter we are trying to find

How do we find $h(t)$?
Convolution is annoying; let's get rid of it
One nice thing about convolution is that it turns into multiplication when you do a Fourier transform

$\hat{X}(\omega) = R(\omega)H(\omega)$

We want to find $H(\omega)$ that minimizes the error

$E = (X(\omega)-\hat{X}(\omega))^2$ (added up over all time)
$E = (X(\omega)-R(\omega)H(\omega))^2$ (added up over all time)

But, we know that our signal $x(t)$ can be written in the frequency domain as a sum of sine waves (that's the Fourier transform)

We've already computed those for our signal $X$; that's how we made it in the first place

So, we can write our error in terms of different frequency values $\omega$

$E(\omega) = (X(\omega)-R(\omega)H(\omega))^2$

Now we can take the derivative and set it equal to zero to do the minimization

$H(\omega)= {{X(\omega)R^*(\omega)} \over {|R(\omega)|^2}} $

Note the complex conjugate that appears there

So now we can find $H(\omega)$ given the Fourier transform of our signal $X(\omega)$ and the Fourier transform of the spiking response $R(\omega)$

Notes for comparison to the textbook

Here we're using the convention that lower-case letters are for time-domain functions and upper-case letters are used for Fourier transfomr
$x(t) \rightarrow X(\omega)$
$r(t) \rightarrow R(\omega)$
$h(t) \rightarrow H(\omega)$

The textbook uses $A(\omega)$ instead of $X(\omega)$
$A$ for amplitude
This has caused confusion with $A$ as the matrix for activities of neurons, so I've switched it here
Can you write Python code for it?

import nengo
from nengo.processes import WhiteSignal

dt = 0.001
max_freq = 10
T = 1.0

model = nengo.Network('White Noise')
with model:
    in_stim = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.5))
    ens = nengo.Ensemble(2, dimensions=1,
                         encoders = [[1],[-1]],
                         intercepts = [-.3, -.3],
                         max_rates= [100, 100])
    temp = nengo.Ensemble(1, dimensions=1)

    nengo.Connection(in_stim, ens)
    connection = nengo.Connection(ens, temp) #This is just to generate the decoders

    stim_p = nengo.Probe(in_stim)
    spikes_p = nengo.Probe(ens.neurons, 'spikes')
    dec_p = nengo.Probe(ens)

sim = nengo.Simulator(model)
sim.run(T)

r_plot = (sim.data[spikes_p][:,0]-sim.data[spikes_p][:,1])*dt
r = sim.data[dec_p][:,0] #properly scaled r
signal = sim.data[stim_p][:,0]

t = sim.trange()
figure(figsize=(8, 6))
plot(t, sim.data[stim_p],'g')
plot(t, r_plot,'r')
ylabel("Output")
xlabel("Time")
axis('tight');

freq = np.arange(int(T/dt)) - (T/dt)/2
omega = freq*2*numpy.pi

X = np.fft.fftshift(np.fft.fft(signal))
R = np.fft.fftshift(np.fft.fft(r))

figure()
plot(omega, np.abs(X))
xlabel('$\omega$')
ylabel('$|X(\omega)|$')
xlim(-200,200)

figure()
plot(omega, np.abs(R))
xlabel('$\omega$')
ylabel('$|R(\omega)|$');


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
Unfortunately, that formula for $G[J(t)]$ is an average firing rate over a long period of time.
What does it mean to be firing at 10Hz for 10ms?

We need to model what's actually happening to that neuron over time

Leaky Integrate-and-Fire neurons

Pretty much the standard computationally efficient neuron model
Simple
Produces spikes
Limiting case of other more complex neuron models
All parameters map onto known properties of real neurons

Bilipid cell membrane acts as a capacitor

Flow of ions through the cell membrane ion channels is a resistor

This is the "leak" current

Resistance and capacitance together give $\tau_{RC}$
$\tau_{RC}=R C$

When voltage passes some threshold, neuron emits a "spike"
Very fast, sharp response that's pretty much the same each time
Spike at time $t_n$ modelled as $\delta(t-t_n)$

Neuron recovers over a short period of time
Refractory time $\tau_{ref}$
During this time the voltage is reset to some resting level

As an RC circuit with a nonlinearity:

Typical values

$\tau_{RC}$: 0.02 seconds
$\tau_{ref}$: 0.002 seconds
Reset voltage (a.k.a. resting potential): -70mV
Firing threshold (a.k.a. threshold potential): -55mV
NOTE: we normalize this so that reset is at 0 and firing is at 1
This does not affect anything about the behaviour of the model

For lots more info, see Unit 2 in this online course

The LIF RC circuit

Let's ignore the spiking elements for now, so here's the circuit

Useful physical laws

Kirchhoff's Current Law

Currents at a point have to add up to zeros
Current coming into the neuron has two paths
$J_{M} = J_R+J_C$

Ohm's Law

Formula for current through a resistor
$ J_R = {V \over R}$

Capacitor

$ C = {Q \over V}$
$ Q = C V $
Change in these quantities over time gives
$ {{dQ} \over {dt}} = C {{dV} \over {dt}} $

Rate of change of charge is current
$ J_C = C {{dV} \over {dt}}...
Can you write Python code for it?

import nengo
from nengo.utils.matplotlib import rasterplot

model = nengo.Network(label='Spiking Neurons')

with model:
    stimulus = nengo.Node(0)
    ens = nengo.Ensemble(1, dimensions=1,
                         encoders = [[1]],
                         intercepts = [-.5],
                         max_rates= [100])

    nengo.Connection(stimulus, ens)

    spikes_p = nengo.Probe(ens.neurons, 'spikes')
    voltage_p = nengo.Probe(ens.neurons, 'voltage')
    stim_p = nengo.Probe(stimulus)

sim = nengo.Simulator(model)
sim.run(.5)

t = sim.trange()
figure(figsize=(6, 3))
ax = gca()
ax.plot(t, sim.data[voltage_p],'g')
rasterplot(t, sim.data[spikes_p], ax=ax)
ylim((0,2))
ylabel("Voltage")
xlabel("Time");

import numpy as np
from nengo.utils.functions import piecewise

x = dict(zip(np.linspace(0,.1,10), np.random.uniform(-1,1,10)))

with model:
    stimulus.output = piecewise(x)

sim = nengo.Simulator(model)
sim.run(.1)

t = sim.trange()

figure()
title('$x$ value changing over time')
plot(t, sim.data[stim_p])
ylabel('$x$')
ylim(-1,1)
xlabel('Time (s)')

figure()
title('Firing rate changing over time')
plot(ens.neuron_type.rates(sim.data[stim_p], gain=sim.data[ens].gain, bias=sim.data[ens].bias))
ylabel('Firing rate (Hz)')
xlabel('Time (ms)')

figure(figsize=(6, 3))
ax = gca()
rasterplot(t, sim.data[spikes_p], ax=ax)
ax.plot(t, sim.data[voltage_p],'g') #, drawstyle='steps-post') #if want to see exact timing
ylim((0,2))
title('Voltage and spikes over time')
ylabel("Voltage")
xlabel("Time (s)");


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
What about with more neurons?
Can you write Python code for it?

from nengo.dists import Uniform

with model:
    ens = nengo.Ensemble(40, dimensions=1, max_rates=Uniform(100,200))

    nengo.Connection(in_stim, ens)

    dec_p = nengo.Probe(ens, synapse = 0.005)

sim = nengo.Simulator(model)
sim.run(T)
sig = sim.data[stim_p][:,0]

t = sim.trange()
figure(figsize=(8,4))
plot(t,sim.data[dec_p])
plot(t,sig);


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
What does this new filter look like?
How does it compare without windowing?
Can you write Python code for it?

figure()
plot(omega, np.abs(H))
xlabel('$\omega$')
ylabel('$|H(\omega)|$')
xlim(-500, 500)

figure()
plot(t-T/2, h)
xlabel('t')
ylabel('h(t)')

figure()
plot(t-T/2, h)
xlabel('t')
ylabel('h(t)')
xlim(-0.2, 0.2)

spikes1 = sim.data[spikes_p][:,0]
fspikes1_1 = np.convolve(sim.data[spikes_p][:,0], h, mode='same')
fspikes2_1 = np.convolve(sim.data[spikes_p][:,1], h, mode='same')
spikes_1 = (sim.data[spikes_p][:,0]-sim.data[spikes_p][:,1])*sim.dt

sim = nengo.Simulator(model)
sim.run(T)
sig2 = sim.data[stim_p][:,0]

fspikes1_2 = np.convolve(sim.data[spikes_p][:,0], h, mode='same')
fspikes2_2 = np.convolve(sim.data[spikes_p][:,1], h, mode='same')
spikes_2 = (sim.data[spikes_p][:,0]-sim.data[spikes_p][:,1])*sim.dt

A1 = numpy.array([fspikes1_1, fspikes2_1]).T
A2 = numpy.array([fspikes1_2, fspikes2_2]).T

d = sim.data[connection].weights.T

x1hat = np.dot(A1, d)[:,0]
x2hat = np.dot(A2, d)[:,0]

t = sim.trange()
figure()
plot(t, sig1)
plot(t, x1hat)
plot(t, spikes_1, 'k.')
ylim(-1.5,1.5)
show()
print 'RMSE:', np.average((sig1-x1hat)**2)

figure()
plot(t, sig2)
plot(t, x2hat)
plot(t, spikes_2, 'k.')
ylim(-1.5,1.5)
print 'RMSE:', np.average((sig2-x2hat)**2)


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
What if we add more neurons?
Can you write Python code for it?

import numpy as np
import nengo
from nengo.utils.matplotlib import rasterplot
from nengo.dists import Uniform

model = nengo.Network(label='Decoding Neurons')

N = 50

with model:
    stim = nengo.Node(lambda t: np.sin(10*t))
    ens = nengo.Ensemble(N, dimensions=1,
                         max_rates=Uniform(100,200))
    temp = nengo.Ensemble(10, dimensions=1)

    nengo.Connection(stim, ens)
    connection = nengo.Connection(ens, temp) #This is just to generate the decoders

    stim_p = nengo.Probe(stim)
    spikes_p = nengo.Probe(ens.neurons, 'spikes')

sim = nengo.Simulator(model)
sim.run(.6)

x = sim.data[stim_p][:,0]

A = sim.data[spikes_p]

gamma=np.dot(A.T,A)
upsilon=np.dot(A.T,x)
d = np.dot(np.linalg.pinv(gamma),upsilon)

xhat = np.dot(A, d)

t = sim.trange()
figure(figsize=(12, 6))
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, xhat,'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[spikes_p], ax=ax.twinx(), use_eventplot=True, color='k')
axis('tight')
ylabel("Neuron");


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
What is this saying?
Every time a spike occurs, when we interpret the data we should replace the spike with this shape
What does this mean?
Can you write Python code for it?

figure(figsize=(8,4))
vlines(dt*np.where(spikes1>0)[0], -1, 1)
plot(t, fspikes1_1*d[0], label='filtered spikes')
ylabel('$x$')
ylim(-1,1)
xlim(0.2, 0.4)
xlabel('time (s)')
legend();


I am working on the file "SYDE 556 Lecture 3 Temporal Representation.ipynb".
The first task is:
What's happening here?
At a given instant in time $t$, only a very small number of neurons are firing
If no neurons are firing at that instant, $\hat{x}=0$
There are usually only one or two neurons firing at any given time
As $dt$ gets smaller, the problem gets even worse

What can we do?

Temporal filtering

We want a spike to contribute to other points in time
So we want to convert a train of spikes into some continuous function

Convolution:

$\hat{x}(t)=\sum_i{a_i(t) * h(t) d_i}$

What should we use for $h(t)$?

Maybe a gaussian?
$h(t) = e^{-t^2 / {2\sigma^2}}$
Can you write Python code for it?

import numpy as np

dt = 0.001
sigma = 0.007
t_h = np.arange(200)*dt-0.1
h = np.exp(-t_h**2/(2*sigma**2))
h = h/norm(h,1)

figure()
plot(t_h, h)
xlabel('t')
ylabel('h(t)');

import nengo
from nengo.utils.matplotlib import rasterplot

model = nengo.Network(label='Decoding Neurons')

with model:
    stim = nengo.Node(lambda t: np.sin(10*t))
    ens = nengo.Ensemble(2, dimensions=1,
                         encoders = [[1],[-1]],
                         intercepts = [-.5, -.5],
                         max_rates = [100, 100])
    temp = nengo.Ensemble(10, dimensions=1)

    nengo.Connection(stim, ens)
    connection = nengo.Connection(ens, temp) #This is just to generate the decoders

    stim_p = nengo.Probe(stim)
    spikes_p = nengo.Probe(ens.neurons, 'spikes')

sim = nengo.Simulator(model)
sim.run(.6)

t = sim.trange()
x = sim.data[stim_p]
d = sim.data[connection].weights.T

fspikes1 = np.convolve(sim.data[spikes_p][:,0], h, mode='same')
fspikes2 = np.convolve(sim.data[spikes_p][:,1], h, mode='same')

A = np.array([fspikes1, fspikes2]).T

xhat = np.dot(A, d)

figure(figsize=(8, 4))
ax = gca()
plot(t, sim.data[stim_p],'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[spikes_p], ax=ax.twinx(), use_eventplot=True, color='k')
ylabel("Neuron")

figure(figsize=(8,4))
plot(t, x, label='x')
plot(t, fspikes1*d[0])
plot(t, fspikes2*d[1])
ylabel('$x$')
xlabel('time (s)')

figure(figsize=(8,4))
plot(t, x, label='x')
plot(t, xhat, label='x')
ylabel('$x$')
xlabel('time (s)');


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
In fact, instead of computing $\omega_{ij}$ at all, it is (usually) more efficient to just do the encoding/decoding
Saves a lot of memory space, since you don't have to store a giant weight matrix
Also, you have NxM multiplies for weights, but only do ~N+M multiplies for encode/decode
Can you write Python code for it?

J_j = numpy.outer(numpy.dot(A_i, d_i), alpha_j*encoders_j)+bias_j


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
Looks pretty much the same! (just delayed, maybe)
So now we've passed one value to the other, but it's implausible
The brain doesn't decode and then re-encode
Can we skip those steps?  Or combine them?

A shortcut

Let's write down what we've done:

Encode into $a$: $a_i = G_i[\alpha_i e_i x + J^{bias}_i]$
Decode from $a$: $\hat{x} = \sum_i a_i d_i$
Set $y = \hat{x}$
Encode into $b$: $b_j = G_j[\alpha_j e_j y + J^{bias}_j]$
Decode from $b$: $\hat{y} = \sum_j b_j d_j$

Now let's just do the substitutions:

I.e. substitute $y = \hat{x} = \sum_i a_i d_i$ into $b$
$b_j = G_j[\alpha_j e_j \sum_i a_i d_i + J^{bias}_j]$
$b_j = G_j[\sum_i \alpha_j e_j d_i a_i  + J^{bias}_j]$
$b_j = G_j[\sum_i \omega_{ij}a_i + J^{bias}_j]$
where $\omega_{ij} = \alpha_j e_j \cdot d_i$ (an outer product)

In other words, we can get the entire weight matrix just by multiplying the decoders from the first population with the encoders from the second population
Can you write Python code for it?

#Have to run previous cells first
n = nengo.neurons.LIFRate()

alpha_j = sim.data[ensB].gain
bias_j = sim.data[ensB].bias
encoders_j = sim.data[ensB].encoders.T

connection_weights = np.outer(alpha_j*encoders_j, d_i)

J_j = np.dot(connection_weights, sim.data[a_rates].T).T + bias_j

B_j = n.rates(J_j, gain=1, bias=0) #Gain and bias already in the previous line

B_j = B_j + numpy.random.normal(scale=0.2*numpy.max(B_j), size=B_j.shape)

xhat_j = numpy.dot(B_j, d_j)

figure(figsize=(8,4))
subplot(1,2,1)
plot(t, xhat_i, 'g', label='$\hat{x}$')
plot(t, x, 'b', label='$x$')
legend()
xlabel('Time (s)')
ylabel('Value')
title('Decode from A')
ylim(-1,1)

subplot(1,2,2)
plot(t, xhat_j, 'g', label='$\hat{y}$')
plot(t, x, 'b', label='$y$')
legend()
xlabel('Time (s)')
title('Decode from B');
ylim(-1,1);


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
Multiplication is quite powerful, and has lots of uses

Gating of signals
Attention effects
Binding
Statistical inference

Here's a simple gating example using the same network
Can you write Python code for it?

with model:
    stimB.output = lambda t: 0 if (t<.5) else .5

sim = nengo.Simulator(model)
sim.run(T)

figure()
plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p]*sim.data[stimA_p],'r', label="$x*y$")
plot(t, sim.data[ensD_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
So everything works fine if we drive each population with the same $x$, let's switch to $\hat{x}$ in the middle
Can you write Python code for it?

#Have to run previous cells first
model.connections.remove(stim_B)
del stim_B

def xhat_fcn(t):
    idx = int(t/sim.dt)
    if idx>=1000: idx=999
    return xhat_i[idx]

with model:
    xhat = nengo.Node(xhat_fcn)

    nengo.Connection(xhat, ensB)

    xhat_p = nengo.Probe(xhat)

sim = nengo.Simulator(model, seed=3)
sim.run(T)

d_j = sim.data[connectionB].weights.T
A_j = sim.data[b_rates]

A_j = A_j + numpy.random.normal(scale=0.2*numpy.max(A_j), size=A_j.shape)

yhat_j = numpy.dot(A_j, d_j)

t = sim.trange()
figure(figsize=(8,4))
subplot(1,2,1)
plot(t, xhat_i, 'g', label='$\hat{x}$')
plot(t, x, 'b', label='$x$')
legend()
xlabel('time (seconds)')
ylabel('value')
title('$\hat{x}$')
ylim(-1,1)

subplot(1,2,2)
plot(t, yhat_j, 'g', label='$\hat{y}$')
plot(t, x, 'b', label='$x$')
legend()
xlabel('time (seconds)')
ylabel('value')
title('$\hat{y}$ (second population)')
ylim(-1,1);


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
Summary

We can use the decoders to find connection weights between groups of neurons
$\omega_{ij}=\alpha_j e_j \cdot d_i$

Using connection weights is numerically identical to decoding and then encoding again
Which can be much more efficient to implement

Feeding two inputs into the same population results in addition
These shortcuts rely on two assumptions:
The input to a neuron is a weighted sum of its synaptic inputs
$J_j = \sum_i a_i \omega_{ij}$

The mapping from $x$ to $J$ is of the form $J_j=\alpha_j e_j \cdot x + J_j^{bias}$

If these assumptions don't hold, you have to do some other form of optimization

If you already have a decoder for $x$, you can quickly find a decoder for any linear function of $x$

If the decoder for $x$ is $d$, the decoder for $Mx$ is $Md$

For some other function of $x$, substitute in that function $f(x)$ when finding $\Upsilon$
Taking all of this into account, the most general form of the weights is:
$\omega_{ij} = \alpha_j e_j M d_i^{f(x)}$

A recipe

To find weights for any linear transformation

Define the repn (enc/dec) for all variables involved in the operation.
Write the transformation in terms of these variables.
Write the transformation using the decoding expressions for all variables except the output variable.
Substitute this expression into the encoding expression of the output variable.

Volunteer for:

$z = x+y$

$z = Rx$

R is a 2D rotation matrix: $$\left[ \begin{array}{cc}
      \cos \theta & \sin \theta \
      -\sin \theta & \cos \theta
      \end{array} \right]$$

$z = x \times y$

General nonlinear functions

What if we want to combine to compute a nonlinear function of two inputs?
E.g., $z=x \times y$

We know how to compute nonlinear functions of a vector space
E.g., $x^2$
If $x$ is a vector, you get a bunch of cross terms
E.g. if $x$ is 2D this gives $x_1^2 + 2 x_1 x_2 + x_2^2$

This means that if you combine two inputs into a 2D space, you can get out their product
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stimA = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.5, seed=3))
    stimB = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.5, seed=5))

    ensA = nengo.Ensemble(55, dimensions=1)
    ensB = nengo.Ensemble(53, dimensions=1)
    ensC = nengo.Ensemble(200, dimensions=2)
    ensD = nengo.Ensemble(54, dimensions=1)

    nengo.Connection(stimA, ensA)
    nengo.Connection(stimB, ensB)
    nengo.Connection(ensA, ensC, transform=[[1],[0]])
    nengo.Connection(ensB, ensC, transform=[[0],[1]])
    nengo.Connection(ensC, ensD, function=lambda x: x[0]*x[1])

    stimA_p = nengo.Probe(stimA)
    stimB_p = nengo.Probe(stimB)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensC_p = nengo.Probe(ensC, synapse=.01)
    ensD_p = nengo.Probe(ensD, synapse=.01)

sim = nengo.Simulator(model)
sim.run(T)

figure()
plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p]*sim.data[stimA_p],'r', label="$x * y$")
plot(t, sim.data[ensD_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");

from nengo_gui.ipython import IPythonViz
IPythonViz(model)#, "configs/mult.py.cfg")


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
Summary

We can use the decoders to find connection weights between groups of neurons
$\omega_{ij}=\alpha_j e_j \cdot d_i$

Using connection weights is numerically identical to decoding and then encoding again
Which can be much more efficient to implement

Feeding two inputs into the same population results in addition
These shortcuts rely on two assumptions:
The input to a neuron is a weighted sum of its synaptic inputs
$J_j = \sum_i a_i \omega_{ij}$

The mapping from $x$ to $J$ is of the form $J_j=\alpha_j e_j \cdot x + J_j^{bias}$

If these assumptions don't hold, you have to do some other form of optimization

If you already have a decoder for $x$, you can quickly find a decoder for any linear function of $x$

If the decoder for $x$ is $d$, the decoder for $Mx$ is $Md$

For some other function of $x$, substitute in that function $f(x)$ when finding $\Upsilon$
Taking all of this into account, the most general form of the weights is:
$\omega_{ij} = e_j M d_i^{f(x)}$

A recipe

To find weights for any linear transformation

Define the repn (enc/dec) for all variables involved in the operation.
Write the transformation in terms of these variables.
Write the transformation using the decoding expressions for all variables except the output variable.
Substitute this expression into the encoding expression of the output variable.

Volunteer for:

$z = x+y$

$z = Rx$

R is a 2D rotation matrix: $$\left[ \begin{array}{cc}
      \cos \theta & \sin \theta \
      -\sin \theta & \cos \theta
      \end{array} \right]$$

$z = x \times y$

General nonlinear functions

What if we want to combine to compute a nonlinear function of two inputs?
E.g., $z=x \times y$

We know how to compute nonlinear functions of a vector space
E.g., $x^2$
If $x$ is a vector, you get a bunch of cross terms
E.g. if $x$ is 2D this gives $x_1^2 + 2 x_1 x_2 + x_2^2$

This means that if you combine two inputs into a 2D space, you can get out their product
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stimA = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.5, seed=3))
    stimB = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.5, seed=5))

    ensA = nengo.Ensemble(55, dimensions=1)
    ensB = nengo.Ensemble(53, dimensions=1)
    ensC = nengo.Ensemble(200, dimensions=2)
    ensD = nengo.Ensemble(54, dimensions=1)

    nengo.Connection(stimA, ensA)
    nengo.Connection(stimB, ensB)
    nengo.Connection(ensA, ensC, transform=[[1],[0]])
    nengo.Connection(ensB, ensC, transform=[[0],[1]])
    nengo.Connection(ensC, ensD, function=lambda x: x[0]*x[1])

    stimA_p = nengo.Probe(stimA)
    stimB_p = nengo.Probe(stimB)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensC_p = nengo.Probe(ensC, synapse=.01)
    ensD_p = nengo.Probe(ensD, synapse=.01)

sim = nengo.Simulator(model)
sim.run(T)

figure()
plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p]*sim.data[stimA_p],'r', label="$x * y$")
plot(t, sim.data[ensD_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
This means we get the exact same effect as having a weight matrix $\omega_{ij}$ if we just take the decoded value from one population and feed that into the next population using the normal encoding method
These are numerically identical processes, since $\omega_{ij} = \alpha_j e_j \cdot d_i$

Spiking neurons

The same approach works for spiking neurons
Do exactly the same as before
The $a_i(t)$ values are spikes, and we convolve with $h(t)$

Other transformations

So this lets us take an $x$ value and feed it into another population
Passing information from one group of neurons to the next
We call this a 'Communication Channel' as you're just sending the information

What about transforming that information in some way?
Instead of $y=x$, can we do $y=f(x)$?

Let's try $y=2x$ to start
We already have a decoder for $\hat{x}$, so how do we get a decoder for $\hat{2x}$?
Two ways
Either use $2x$ when computing $\Upsilon$
Or just multiply your 'representational' decoder by $2$
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stim = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3))
    ensA = nengo.Ensemble(25, dimensions=1)
    ensB = nengo.Ensemble(23, dimensions=1)

    nengo.Connection(stim, ensA)
    nengo.Connection(ensA, ensB, transform=2) #function=lambda x: 2*x)

    stim_p = nengo.Probe(stim)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensA_spikes_p = nengo.Probe(ensA.neurons, 'spikes')
    ensB_spikes_p = nengo.Probe(ensB.neurons, 'spikes')

sim = nengo.Simulator(model, seed=4)
sim.run(T)

t = sim.trange()
figure(figsize=(8, 6))
subplot(2,1,1)
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, sim.data[ensA_p],'g')
ylabel("Output EnsA")
rasterplot(t, sim.data[ensA_spikes_p], ax=ax.twinx(), colors=['k']*25, use_eventplot=True)
#axis('tight')
ylabel("Neuron")

subplot(2,1,2)
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, sim.data[ensB_p],'g')
ylabel("Output EnsB")
xlabel("Time");
rasterplot(t, sim.data[ensB_spikes_p], ax=ax.twinx(), colors=['k']*23, use_eventplot=True)
#axis('tight')
ylabel("Neuron");


import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stim = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3))
    ensA = nengo.Ensemble(25, dimensions=1)
    ensB = nengo.Ensemble(23, dimensions=1)

    nengo.Connection(stim, ensA)
    nengo.Connection(ensA, ensB, transform=2) #function=lambda x: 2*x)

    stim_p = nengo.Probe(stim)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensA_spikes_p = nengo.Probe(ensA.neurons, 'spikes')
    ensB_spikes_p = nengo.Probe(ensB.neurons, 'spikes')

sim = nengo.Simulator(model, seed=4)
sim.run(T)

t = sim.trange()
figure(figsize=(8, 6))
subplot(2,1,1)
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, sim.data[ensA_p],'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[ensA_spikes_p], ax=ax.twinx(), color='k', use_eventplot=True)
#axis('tight')
ylabel("Neuron")

subplot(2,1,2)
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, sim.data[ensB_p],'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[ensB_spikes_p], ax=ax.twinx(), color='k', use_eventplot=True)
#axis('tight')
ylabel("Neuron");


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
Vectors

Almost nothing changes
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stimA = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=3), size_out=2)
    stimB = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=5), size_out=2)

    stimA = nengo.Node([.3,.5])
    stimB = nengo.Node([.3,-.5])

    ensA = nengo.Ensemble(55, dimensions=2)
    ensB = nengo.Ensemble(53, dimensions=2)
    ensC = nengo.Ensemble(54, dimensions=2)

    nengo.Connection(stimA, ensA)
    nengo.Connection(stimB, ensB)
    nengo.Connection(ensA, ensC)
    nengo.Connection(ensB, ensC)

    stimA_p = nengo.Probe(stimA)
    stimB_p = nengo.Probe(stimB)
    ensA_p = nengo.Probe(ensA, synapse=.02)
    ensB_p = nengo.Probe(ensB, synapse=.02)
    ensC_p = nengo.Probe(ensC, synapse=.02)

sim = nengo.Simulator(model)
sim.run(T)

figure()
plot(sim.data[ensA_p][:,0], sim.data[ensA_p][:,1], 'g', label="$\hat{x}$")
plot(sim.data[ensB_p][:,0], sim.data[ensB_p][:,1], 'm', label="$\hat{y}$")
plot(sim.data[ensC_p][:,0], sim.data[ensC_p][:,1], 'k', label="$\hat{z}$")
legend(loc='best')

figure()
plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p]+sim.data[stimA_p],'r', label="$x+y$")
plot(t, sim.data[ensC_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");


import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stimA = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=3), size_out=2)
    stimB = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=5), size_out=2)

    #stimA = nengo.Node([.3,.5])
    #stimB = nengo.Node([.3,-.5])

    ensA = nengo.Ensemble(55, dimensions=2)
    ensB = nengo.Ensemble(53, dimensions=2)
    ensC = nengo.Ensemble(54, dimensions=2)

    nengo.Connection(stimA, ensA)
    nengo.Connection(stimB, ensB)
    nengo.Connection(ensA, ensC)
    nengo.Connection(ensB, ensC)

    stimA_p = nengo.Probe(stimA)
    stimB_p = nengo.Probe(stimB)
    ensA_p = nengo.Probe(ensA, synapse=.02)
    ensB_p = nengo.Probe(ensB, synapse=.02)
    ensC_p = nengo.Probe(ensC, synapse=.02)

sim = nengo.Simulator(model)
sim.run(T)

figure()
plot(sim.data[ensA_p][:,0], sim.data[ensA_p][:,1], 'g', label="$\hat{x}$")
plot(sim.data[ensB_p][:,0], sim.data[ensB_p][:,1], 'm', label="$\hat{y}$")
plot(sim.data[ensC_p][:,0], sim.data[ensC_p][:,1], 'k', label="$\hat{z}$")
legend(loc='best')

figure()
plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
legend(loc='best')
ylabel("Output")
xlabel("Time")

figure()
plot(t, sim.data[stimB_p]+sim.data[stimA_p],'r', label="$x+y$")
plot(t, sim.data[ensC_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
We call standard $d_i$ "representational decoders"
We call $d_i^{f(x)}$ "transformational decoders" (or "decoders for $f(x)$")

Adding

What if we want to combine the inputs from two different populations?
Linear case: $z=x+y$

<img src="files/lecture4/adding1.png">

We want the total current going into a $z$ neuron to be $J=\alpha e \cdot (x+y) + J^{bias}$
How can we achieve this?
Again, substitute into the equation, where $z = x+y \approx \hat{x}+\hat{y}$
$J_k=\alpha_k e \cdot (\hat{x}+\hat{y}) + J_k^{bias}$
$\hat{x} = \sum_i a_i d_i$
$\hat{y} = \sum_j a_j d_j$
$J_k=\alpha_k e_k \cdot (\sum_i a_i d_i+\sum_j a_j d_j) + J_k^{bias}$
$J_k=\sum_i(\alpha_k e_k \cdot d_i a_i) + \sum_j(\alpha_k e_k \cdot d_j a_j) + J_k^{bias}$
$J_k=\sum_i(\omega_{ik} a_i) + \sum_j(\omega_{jk} a_j) + J_k^{bias}$
$\omega_{ik}=\alpha_k e_k \cdot d_i$ and $\omega_{jk}=\alpha_k e_k \cdot d_j$

Putting multiple inputs into a neuron automatically gives us addition!
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stimA = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=3))
    stimB = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=5))

    ensA = nengo.Ensemble(25, dimensions=1)
    ensB = nengo.Ensemble(23, dimensions=1)
    ensC = nengo.Ensemble(24, dimensions=1)

    nengo.Connection(stimA, ensA)
    nengo.Connection(stimB, ensB)
    nengo.Connection(ensA, ensC)
    nengo.Connection(ensB, ensC)

    stimA_p = nengo.Probe(stimA)
    stimB_p = nengo.Probe(stimB)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensC_p = nengo.Probe(ensC, synapse=.01)

sim = nengo.Simulator(model)
sim.run(T)

figure(figsize=(8,6))
plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
plot(t, sim.data[stimB_p]+sim.data[stimA_p],'r', label="$x+y$")
plot(t, sim.data[ensC_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
What about a nonlinear function?
$y = x^2$
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stim = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3))
    ensA = nengo.Ensemble(25, dimensions=1)
    ensB = nengo.Ensemble(23, dimensions=1)

    nengo.Connection(stim, ensA)
    nengo.Connection(ensA, ensB, function=lambda x: x**2)

    stim_p = nengo.Probe(stim)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensA_spikes_p = nengo.Probe(ensA.neurons, 'spikes')
    ensB_spikes_p = nengo.Probe(ensB.neurons, 'spikes')

sim = nengo.Simulator(model, seed=4)
sim.run(T)

t = sim.trange()
figure(figsize=(8, 6))
subplot(2,1,1)
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, sim.data[ensA_p],'g')
ylabel("Output")
rasterplot(t, sim.data[ensA_spikes_p], ax=ax.twinx(), colors=['k']*25, use_eventplot=True)
ylabel("Neuron")

subplot(2,1,2)
ax = gca()
plot(t, sim.data[stim_p],'b')
plot(t, sim.data[ensB_p],'g')
ylabel("Output")
xlabel("Time");
rasterplot(t, sim.data[ensB_spikes_p], ax=ax.twinx(), colors=['k']*23, use_eventplot=True)
ylabel("Neuron");


I am working on the file "SYDE 556 Lecture 4 Transformation.ipynb".
The first task is:
When you set the connection 'function' in Nengo, it solves the same decoding equation as before, but for a function.

In equations:

$ d^{f(x)} = \Gamma^{-1} \Upsilon^{f(x)} $
$ \Upsilon_i^{f(x)} = \sum_x a_i f(x) \;dx$
$ \Gamma_{ij} = \sum_x a_i a_j \;dx $
$ \hat{f}(x) =\sum_i a_i d_i^{f(x)}$

In code:

f_x = myfunction(x)
gamma=np.dot(A.T,A)

upsilon_f=np.dot(A.T, f_x)

d_f = np.dot(np.linalg.pinv(gamma),upsilon)
f_xhat = np.dot(A, d_f)

We call standard $d_i$ "representational decoders"
We call $d_i^{f(x)}$ "transformational decoders" (or "decoders for $f(x)$")

Adding

What if we want to combine the inputs from two different populations?
Linear case: $z=x+y$

We want the total current going into a $z$ neuron to be $J=\alpha e \cdot (x+y) + J^{bias}$
How can we achieve this?
Again, substitute into the equation, where $z = x+y \approx \hat{x}+\hat{y}$
$J_k=\alpha_k e \cdot (\hat{x}+\hat{y}) + J_k^{bias}$
$\hat{x} = \sum_i a_i d_i$
$\hat{y} = \sum_j a_j d_j$
$J_k=\alpha_k e_k \cdot (\sum_i a_i d_i+\sum_j a_j d_j) + J_k^{bias}$
$J_k=\sum_i(\alpha_k e_k \cdot d_i a_i) + \sum_j(\alpha_k e_k \cdot d_j a_j) + J_k^{bias}$
$J_k=\sum_i(\omega_{ik} a_i) + \sum_j(\omega_{jk} a_j) + J_k^{bias}$
$\omega_{ik}=\alpha_k e_k \cdot d_i$ and $\omega_{jk}=\alpha_k e_k \cdot d_j$

Putting multiple inputs into a neuron automatically gives us addition!
Can you write Python code for it?

import nengo
from nengo.processes import WhiteNoise
from nengo.utils.matplotlib import rasterplot

T = 1.0
max_freq = 5

model = nengo.Network()

with model:
    stimA = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=3))
    stimB = nengo.Node(output=WhiteSignal(T, high=max_freq, rms=0.3, seed=5))

    ensA = nengo.Ensemble(25, dimensions=1)
    ensB = nengo.Ensemble(23, dimensions=1)
    ensC = nengo.Ensemble(24, dimensions=1)

    nengo.Connection(stimA, ensA)
    nengo.Connection(stimB, ensB)
    nengo.Connection(ensA, ensC)
    nengo.Connection(ensB, ensC)

    stimA_p = nengo.Probe(stimA)
    stimB_p = nengo.Probe(stimB)
    ensA_p = nengo.Probe(ensA, synapse=.01)
    ensB_p = nengo.Probe(ensB, synapse=.01)
    ensC_p = nengo.Probe(ensC, synapse=.01)

sim = nengo.Simulator(model)
sim.run(T)

figure(figsize=(8,6))
#plot(t, sim.data[stimA_p],'g', label="$x$")
plot(t, sim.data[ensA_p],'b', label="$\hat{x}$")
#plot(t, sim.data[stimB_p],'c', label="$y$")
plot(t, sim.data[ensB_p],'m--', label="$\hat{y}$")
#plot(t, sim.data[stimB_p]+sim.data[stimA_p],'r', label="$x+y$")
plot(t, sim.data[ensC_p],'k--', label="$\hat{z}$")
legend(loc='best')
ylabel("Output")
xlabel("Time");


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
Does this do what you expect?

How is it affected by:

Number of neurons?
Post-synaptic time constant?
Decoding filter time constant?
Speed of oscillation (r)?

What about this shape?
Can you write Python code for it?

import nengo

model = nengo.Network('Heart Oscillator')

tau = 0.02
r=4

def feedback(x):
    return [-tau*r*x[1]+x[0], tau*r*x[0]+x[1]]

def heart_shape(x):
    theta = np.arctan2(x[1], x[0])
    r = 2 - 2 * np.sin(theta) + np.sin(theta)*np.sqrt(np.abs(np.cos(theta)))/(np.sin(theta)+1.4)
    return -r*np.cos(theta), r*np.sin(theta)

with model:
    stim = nengo.Node(lambda t: [.5,.5] if t<.02 else [0,0])

    heart_osc = nengo.Ensemble(1000, dimensions=2)
    heart = nengo.Ensemble(100, dimensions=2, radius=4)

    nengo.Connection(stim, heart_osc)
    nengo.Connection(heart_osc, heart_osc, function=feedback, synapse=tau)
    nengo.Connection(heart_osc, heart, function=heart_shape, synapse=tau)

    heart_p = nengo.Probe(heart, synapse=tau)

sim = nengo.Simulator(model)
sim.run(4)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[heart_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[heart_p][:,0],sim.data[heart_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');


import nengo

model = nengo.Network('Heart Oscillator')

tau = 0.02
r=4

def feedback(x):
    return [-tau*r*x[1]+x[0], tau*r*x[0]+x[1]]

def heart_shape(x):
    theta = np.arctan2(x[1], x[0])
    r = 2 - 2 * np.sin(theta) + np.sin(theta)*np.sqrt(np.abs(np.cos(theta)))/(np.sin(theta)+1.4)
    return -r*np.cos(theta), r*np.sin(theta)

with model:
    stim = nengo.Node(lambda t: [.5,.5] if t<.02 else [0,0])

    heart_osc = nengo.Ensemble(1000, dimensions=2)
    heart = nengo.Ensemble(100, dimensions=2, radius=4)

    nengo.Connection(stim, heart_osc)
    nengo.Connection(heart_osc, heart_osc, function=feedback, synapse=tau)
    nengo.Connection(heart_osc, heart, function=heart_shape, synapse=tau)

    heart_p = nengo.Probe(heart, synapse=tau)

sim = nengo.Simulator(model)
sim.run(4)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[heart_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[heart_p][:,0],sim.data[heart_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');

from nengo_gui.ipython import IPythonViz
IPythonViz(model) #do config


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
Lorenz Attractor (a chaotic attractor)

$\dot{x}=[10x_1-10x_0, -x_0 x_2-x_1, x_0 x_1 - {8 \over 3}(x_2+28)-28]$
Can you write Python code for it?

import nengo

model = nengo.Network('Lorenz Attractor', seed=3)

tau = 0.1
sigma = 10
beta = 8.0/3
rho = 28

def feedback(x):
    dx0 = -sigma * x[0] + sigma * x[1]
    dx1 = -x[0] * x[2] - x[1]
    dx2 = x[0] * x[1] - beta * (x[2] + rho) - rho
    return [dx0 * tau + x[0],
            dx1 * tau + x[1],
            dx2 * tau + x[2]]

with model:
    lorenz = nengo.Ensemble(2000, dimensions=3, radius=60)

    nengo.Connection(lorenz, lorenz, function=feedback, synapse=tau)

    lorenz_p = nengo.Probe(lorenz, synapse=tau)

sim = nengo.Simulator(model)
sim.run(14)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[lorenz_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[lorenz_p][:,0],sim.data[lorenz_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "lorenz.py.cfg")


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
Note: This is not the original Lorenz attractor.
The original is $\dot{x}=[10x_1-10x_0, x_0 (28-x_2)-x_1, x_0 x_1 - {8 \over 3}(x_2)]$
Why change it to $\dot{x}=[10x_1-10x_0, -x_0 x_2-x_1, x_0 x_1 - {8 \over 3}(x_2+28)-28]$?
What's being changed here?

Oscillators with different paths

Since we can implement any function, we're not limited to linear oscillators
What about a "square" oscillator?
Instead of the value going in a circle, it traces out a square

$$
{\dot{x}} = \begin{cases}
    [r, 0] &\mbox{if } |x_1|>|x_0| \wedge x_1>0 \
    [-r, 0] &\mbox{if } |x_1|>|x_0| \wedge x_1<0 \
    [0, -r] &\mbox{if } |x_1|<|x_0| \wedge x_0>0 \
    [0, r] &\mbox{if } |x_1|<|x_0| \wedge x_0<0 \
    \end{cases}
$$
Can you write Python code for it?

import nengo

model = nengo.Network('Square Oscillator')

tau = 0.02
r=6

def feedback(x):
    if abs(x[1])>abs(x[0]):
        if x[1]>0: dx=[r, 0]
        else: dx=[-r, 0]
    else:
        if x[0]>0: dx=[0, -r]
        else: dx=[0, r]
    return [tau*dx[0]+x[0], tau*dx[1]+x[1]]

with model:
    stim = nengo.Node(lambda t: [.5,.5] if t<.02 else [0,0])

    square_osc = nengo.Ensemble(1000, dimensions=2)

    nengo.Connection(square_osc, square_osc, function=feedback, synapse=tau)
    nengo.Connection(stim, square_osc)

    square_osc_p = nengo.Probe(square_osc, synapse=tau)

sim = nengo.Simulator(model)
sim.run(2)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[square_osc_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[square_osc_p][:,0],sim.data[square_osc_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');

from nengo_gui.ipython import IPythonViz
IPythonViz(model) #do config


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
Other fun functions

Oscillator
$F = -kx = m \ddot{x}$ let $\omega = \sqrt{\frac{k}{m}}$
$\frac{d}{dt} \begin{bmatrix}
\omega x \
\dot{x}
\end{bmatrix}
=
\begin{bmatrix}
0 & \omega \
-\omega & 0
\end{bmatrix}
\begin{bmatrix}
x_0 \
x_1
\end{bmatrix}$
Therefore, with the above, $\dot{x}=[x_1, -x_0]$
Can you write Python code for it?

import nengo

model = nengo.Network('Oscillator')

freq = -.5

with model:
    stim = nengo.Node(lambda t: [.5,.5] if t<.02 else [0,0])

    osc = nengo.Ensemble(200, dimensions=2)

    def feedback(x):
        return x[0]+freq*x[1], -freq*x[0]+x[1]

    nengo.Connection(osc, osc, function=feedback, synapse=.01)
    nengo.Connection(stim, osc)

    osc_p = nengo.Probe(osc, synapse=.01)

sim = nengo.Simulator(model)
sim.run(.5)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[osc_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[osc_p][:,0],sim.data[osc_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/oscillator.py.cfg")


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
Other fun functions

Oscillator
$F = -kx = m \ddot{x}$ let $\omega = \sqrt{\frac{k}{m}}$
$\frac{d}{dt} \begin{bmatrix}
\omega x \
\dot{x}
\end{bmatrix}
=
\begin{bmatrix}
0 & \omega \
-\omega & 0
\end{bmatrix}$
$\dot{x}=[x_1, -x_0]$
Can you write Python code for it?

import nengo

model = nengo.Network('Oscillator')

freq = 1

with model:
    stim = nengo.Node(lambda t: [.5,.5] if t<.02 else [0,0])

    osc = nengo.Ensemble(200, dimensions=2)

    def feedback(x):
        return x[0]+freq*x[1], -freq*x[0]+x[1]

    nengo.Connection(osc, osc, function=feedback, synapse=.01)
    nengo.Connection(stim, osc)

    osc_p = nengo.Probe(osc, synapse=.01)

sim = nengo.Simulator(model)
sim.run(.5)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[osc_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[osc_p][:,0],sim.data[osc_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "oscillator.py.cfg")


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
So there are dynamics and filtering going on, since there is always a synaptic filter on a connection
Recurrent connections are dynamic as well (i.e. passing past information to future state of the population)
Let's take a look more carefully

Recurrent connections

So a connection actually approximates $f(x(t))*h(t)$

So what does a recurrent connection do?

Also $x(t) = f(x(t))*h(t)$

where $$
h(t) = \begin{cases}
    e^{-t/\tau} &\mbox{if } t > 0 \
    0 &\mbox{otherwise}
    \end{cases}
$$

How can we work with this?

General rule of thumb: convolutions are annoying, so let's get rid of them

We could do a Fourier transform
$X(\omega)=F(\omega)H(\omega)$
But, since we are studying the response of a system (rather than a continuous signal), there's a more general and appropriate transform that makes life even easier:
Laplace transform (it is more general because $s = a + j\omega$)
The Laplace transform of our equations are:
$X(s)=F(s)H(s)$
$H(s)={1 \over {1+s\tau}}$
Rearranging:

$X(s)=F(s){1 \over {1+s\tau}}$
$X(s)(1+s\tau) = F(s)$
$X(s) + X(s)s\tau = F(s)$
$sX(s) = {1 \over \tau} (F(s)-X(s))$

Convert back into the time domain (inverse Laplace):

${dx \over dt} = {1 \over \tau} (f(x(t))-x(t))$
Dynamics

This says that if we introduce a recurrent connection, we end up implementing a differential equation

So what happened with $f(x)=x+1$?

$\dot{x} = {1 \over \tau} (x+1-x)$
$\dot{x} = {1 \over \tau}$

What about $f(x)=-x$?
$\dot{x} = {1 \over \tau} (-x-x)$
$\dot{x} = {-2x \over \tau}$

And $f(x)=x^2$?

$\dot{x} = {1 \over \tau} (x^2-x)$

What if there's some differential equation we really want to implement?

We want $\dot{x} = f(x)$
So we do a recurrent connection of $f'(x)=\tau f(x)+x$
The resulting model will end up implementing $\dot{x} = {1 \over \tau} (\tau f(x)+x-x)=f(x)$

Inputs

What happens if there's an input as well?

We'll call the input $u$ from another population, and it is also computing some function $g(u)$
$x(t) = f(x(t))h(t)+g(u(t))h(t)$

Follow the same derivation...
Can you write Python code for it?

import nengo
from nengo.utils.functions import piecewise

tau = 0.01

model = nengo.Network('Eye control', seed=4)

with model:
    stim = nengo.Node(piecewise({.3:1, .6:0 }))
    velocity = nengo.Ensemble(100, dimensions=1)
    position = nengo.Ensemble(100, dimensions=1)

    def feedback(x):
        return 1*x

    conn = nengo.Connection(stim, velocity)
    conn = nengo.Connection(velocity, position, transform=tau, synapse=tau)
    conn = nengo.Connection(position, position, function=feedback, synapse=tau)

    stim_p = nengo.Probe(stim)
    position_p = nengo.Probe(position, synapse=.01)
    velocity_p = nengo.Probe(velocity, synapse=.01)

sim = nengo.Simulator(model)
sim.run(1)

plot(sim.trange(), sim.data[stim_p], label = "stim")
plot(sim.trange(), sim.data[position_p], label = "position")
plot(sim.trange(), sim.data[velocity_p], label = "velocity")
legend(loc="best");


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
So we can think of the distortion error as introducing a bunch of local attractors into the representation
Any 'downward' x-crossing will be a stable point ('upwards' is unstable).
There will be a tendency to drift towards one of these even if the input is zero.

Noise error

What will random noise do?
Push the representation back and forth
What if it is small?
What if it is large?

What will changing the post-synaptic time constant $\tau$ do?

How does that interact with noise?

But real eyes aren't perfect integrators

If you get someone to look at someting, then turn off the lights but tell them to keep looking in the same direction, their eye will drift back to centre
How do we implement that?

$\dot{x}=-{1 \over \tau_c}x + v$

$\tau_c$ is the time constant of that return to centre

$A'=\tau {-1 \over \tau_c}+1$

$B' = \tau$
Can you write Python code for it?

import nengo
from nengo.utils.functions import piecewise

tau = 0.1
tau_c = 2.0

model = nengo.Network('Eye control', seed=5)

with model:
    stim = nengo.Node(piecewise({.3:1, .6:0 }))
    velocity = nengo.Ensemble(100, dimensions=1)
    position = nengo.Ensemble(200, dimensions=1)

    def feedback(x):
        return (-tau/tau_c + 1)*x

    conn = nengo.Connection(stim, velocity)
    conn = nengo.Connection(velocity, position, transform=tau, synapse=tau)
    conn = nengo.Connection(position, position, function=feedback, synapse=tau)

    stim_p = nengo.Probe(stim)
    position_p = nengo.Probe(position, synapse=.01)
    velocity_p = nengo.Probe(velocity, synapse=.01)

sim = nengo.Simulator(model)
sim.run(5)

plot(sim.trange(), sim.data[stim_p], label = "stim")
plot(sim.trange(), sim.data[position_p], label = "position")
plot(sim.trange(), sim.data[velocity_p], label = "velocity")
legend(loc="best");


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
That also looks right.  Note that as $\tau_c \rightarrow \infty$ this will approach the integrator.
Humans (a) and Goldfish (b)
Humans have more neurons doing this than goldfish (~1000 vs ~40)
They also have slower decay (70 s vs. 10 s).
Why do these fit together?

Controlled Integrator

What if we want an integrator where we can adjust the decay on-the-fly?
Separate input telling us what the decay constant $d$ should be

$\dot{x} = -d x + v$

So there are two inputs: $v$ and $d$

This is no longer in the standard $Ax + Bu$ form. Sort of...

Let $A = -d(t)$, so it's not a matrix
But it is of the more general form: ${dx \over dt}=f(x)+g(u)$

We need to compute a nonlinear function of an input ($d$) and the state variable ($x$)

How can we do this?

Going to 2D so we can compute the nonlinear function

Let's have the state variable be $[x, d]$

Can you write Python code for it?

import nengo
from nengo.utils.functions import piecewise

tau = 0.1

model = nengo.Network('Controlled integrator', seed=1)

with model:
    vel = nengo.Node(piecewise({.2:1.5, .5:0 }))
    dec = nengo.Node(piecewise({.7:.2, .9:0 }))

    velocity = nengo.Ensemble(100, dimensions=1)
    decay = nengo.Ensemble(100, dimensions=1)
    position = nengo.Ensemble(400, dimensions=2)

    def feedback(x):
        return -x[1]*x[0]+x[0], 0

    conn = nengo.Connection(vel, velocity)
    conn = nengo.Connection(dec, decay)
    conn = nengo.Connection(velocity, position[0], transform=tau, synapse=tau)
    conn = nengo.Connection(decay, position[1], synapse=0.01)
    conn = nengo.Connection(position, position, function=feedback, synapse=tau)

    position_p = nengo.Probe(position, synapse=.01)
    velocity_p = nengo.Probe(velocity, synapse=.01)
    decay_p = nengo.Probe(decay, synapse=.01)

sim = nengo.Simulator(model)
sim.run(1)

plot(sim.trange(), sim.data[decay_p])
lineObjects = plot(sim.trange(), sim.data[position_p])
plot(sim.trange(), sim.data[velocity_p])
legend(('decay','position','decay','velocity'),loc="best");

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/controlled_integrator.py.cfg")


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
That also looks right.  Note that as $\tau_c \rightarrow \infty$ this will approach the integrator.
Humans (a) and Goldfish (b)
Humans have more neurons doing this than goldfish (~1000 vs ~40)
They also have slower decay (70 s vs. 10 s).
Why do these fit together?

Controlled Integrator

What if we want an integrator where we can adjust the decay on-the-fly?
Separate input telling us what the decay constant $d$ should be

$\dot{x} = -d x + v$

So there are two inputs: $v$ and $d$

This is no longer in the standard $Ax + Bu$ form. Sort of...

Let $A = -d(t)$, so it's not a matrix
But it is of the more general form: ${dx \over dt}=f(x)+g(u)$

We need to compute a nonlinear function of an input ($d$) and the state variable ($x$)

How can we do this?
Going to 2D so we can compute the nonlinear function
Let's have the state variable be $[x, d]$

Can you write Python code for it?

import nengo
from nengo.utils.functions import piecewise

tau = 0.1

model = nengo.Network('Controlled integrator', seed=1)

with model:
    vel = nengo.Node(piecewise({.2:1.5, .5:0 }))
    dec = nengo.Node(piecewise({.7:.2, .9:0 }))

    velocity = nengo.Ensemble(100, dimensions=1)
    decay = nengo.Ensemble(100, dimensions=1)
    position = nengo.Ensemble(400, dimensions=2)

    def feedback(x):
        return -x[1]*x[0]+x[0], 0

    conn = nengo.Connection(vel, velocity)
    conn = nengo.Connection(dec, decay)
    conn = nengo.Connection(velocity, position[0], transform=tau, synapse=tau)
    conn = nengo.Connection(decay, position[1], synapse=0.01)
    conn = nengo.Connection(position, position, function=feedback, synapse=tau)

    position_p = nengo.Probe(position, synapse=.01)
    velocity_p = nengo.Probe(velocity, synapse=.01)
    decay_p = nengo.Probe(decay, synapse=.01)

sim = nengo.Simulator(model)
sim.run(1)

plot(sim.trange(), sim.data[decay_p])
lineObjects = plot(sim.trange(), sim.data[position_p])
plot(sim.trange(), sim.data[velocity_p])
legend(('decay','position','decay','velocity'),loc="best");

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "controlled_integrator.py.cfg")


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
That also makes sense.  What if we nudge it away from zero?
Can you write Python code for it?

from nengo.utils.functions import piecewise

with model:
    stim = nengo.Node(piecewise({0:1, .2:-1, .4:0}))
    nengo.Connection(stim, ensA)

sim = nengo.Simulator(model)
sim.run(.6)

plot(sim.trange(), sim.data[ensA_p])
ylim(-1.5,1.5);


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
That sort of makes sense
$x$ increases quickly, then hits an upper bound

How quickly?
What parameters of the system affect this?

What are the precise dynamics?

What about $f(x)=-x$?
Can you write Python code for it?

with model:
    def feedback(x):
        return -x

    conn.function = feedback

sim = nengo.Simulator(model)
sim.run(.5)

plot(sim.trange(), sim.data[ensA_p])
ylim(-1.5,1.5);


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
That's pretty good... the area under the input is about equal to the magnitude of the output.
But, in order to be a perfect integrator, we'd need exactly $x=1\times x$
We won't get exactly that
Neural implementations are always approximations

Two forms of error:
$E_{distortion}$, the decoding error
$E_{noise}$, the random noise error

What will they do?

Distortion error
<img src="files/lecture5/integrator_error.png">

What affects this?
Can you write Python code for it?

import nengo
from nengo.dists import Uniform
from nengo.utils.ensemble import tuning_curves

model = nengo.Network(label='Neurons')
with model:
    neurons = nengo.Ensemble(100, dimensions=1, max_rates=Uniform(100,200))

    connection = nengo.Connection(neurons, neurons)

sim = nengo.Simulator(model)

d = sim.data[connection].weights.T
x, A = tuning_curves(neurons, sim)
xhat = numpy.dot(A, d)

x, A = tuning_curves(neurons, sim)
plot(x,A)

figure()
plot(x, xhat-x)
axhline(0, color='k')
xlabel('$x$')
ylabel('$\hat{x}-x$');


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
The time constant of the post-synaptic filter
We're not getting $f(x)=x$
Instead we're getting $f(x(t))=x(t)*h(t)$
Can you write Python code for it?

tau = 0.03
with model:
    ensA_p = nengo.Probe(ensA, synapse=tau)

sim = nengo.Simulator(model)
sim.run(1)

stim_filt = nengo.Lowpass(tau).filt(sim.data[stim_p], dt=sim.dt)

plot(sim.trange(), sim.data[ensA_p], label="$\hat{x}$")
plot(sim.trange(), sim.data[stim_p], label="$x$")
plot(sim.trange(), stim_filt, label="$h(t)*x(t)$")
legend()
ylim(-.2,1.5);


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
This was supposed to compute $f(x)=x$
For a constant input, that works
But we get something else when there's a change in the input

What is this difference?
What affects it?
Can you write Python code for it?

with model:
    ensA_p = nengo.Probe(ensA, synapse=0.03)

sim = nengo.Simulator(model)
sim.run(1)

plot(sim.trange(), sim.data[ensA_p], label="$\hat{x}$")
plot(sim.trange(), sim.data[stim_p], label="$x$")
legend()
ylim(-.2,1.5);


with model:
    ensA_p = nengo.Probe(ensA, synapse=.03)

sim = nengo.Simulator(model)
sim.run(1)

plot(sim.trange(), sim.data[ensA_p], label="$\hat{x}$")
plot(sim.trange(), sim.data[stim_p], label="$x$")
legend()
ylim(-.2,1.5);


I am working on the file "SYDE 556 Lecture 5 Dynamics.ipynb".
The first task is:
We are doing things differently here
The actual $x$ value is a normal circle oscillator
The heart shape is a function of $x$
But that's just a different decoder

Would it be possible to do an oscillator where $x$ followed this shape?
How could we tell them apart in terms of neural behaviour?

Controlled Oscillator

Change the frequency of the oscillator on-the-fly

$\dot{x}=[x_1 x_2, -x_0 x_2]$
Can you write Python code for it?

import nengo
from nengo.utils.functions import piecewise

model = nengo.Network('Controlled Oscillator')

tau = 0.1
freq = 20

def feedback(x):
    return x[1]*x[2]*freq*tau+1.1*x[0], -x[0]*x[2]*freq*tau+1.1*x[1], 0

with model:
    stim = nengo.Node(lambda t: [20,20] if t<.02 else [0,0])
    freq = nengo.Node(piecewise({0:1, 2:.5, 6:-1}))

    ctrl_osc = nengo.Ensemble(500, dimensions=3)

    nengo.Connection(ctrl_osc, ctrl_osc, function=feedback, synapse=tau)
    nengo.Connection(stim, ctrl_osc[0:2])
    nengo.Connection(freq, ctrl_osc[2])

    ctrl_osc_p = nengo.Probe(ctrl_osc, synapse=0.01)

sim = nengo.Simulator(model)
sim.run(8)

figure(figsize=(12,4))
subplot(1,2,1)
plot(sim.trange(), sim.data[ctrl_osc_p]);
xlabel('Time (s)')
ylabel('State value')

subplot(1,2,2)
plot(sim.data[ctrl_osc_p][:,0],sim.data[ctrl_osc_p][:,1])
xlabel('$x_0$')
ylabel('$x_1$');

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "controlled_oscillator.py.cfg")


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
And saturation effects are quite different
Can you write Python code for it?

#Note that the representation saturates at the radius
with model:
    stim.output = piecewise({.2:[1, -1], 1.2:[0, 0] })

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
By making tau negative, we forced the recurrent connection to be positive
So, whatever is in memory 'grows' over time

This gives a 'primacy' effect
i.e. Whatever you ran into first is what you remember
You could also get this by changing connection weights

Together, primacy and recency capture the most salient properties of human working memory
You see these effects in both 'free recall' and 'serial recall'

So far, the networks we've seen would work well for free recall presumably.
How to include order information?

Serial Working Memory

An ordered list is a very simple kind of structure

To represent structures, we can use what we learned last time: vector binding

Specifically, Semantic Pointers

In this case, we can do something like:

$Pos_0\circledast Item_0 + Pos_1\circledast Item_1 + ...$

The $Item$ can just whatever vector we want to remember
How should we generate $Pos$?  What features does it need?
It would be good if we could generate them on the fly, forever
It would be good if $Pos_0$ "came before" $Pos_1$ in some sense

There is a kind of vector that's ideal for this called a 'unitary vector'
A unitary vector does not change length when convolved
So, you can convolve forever

The convolution of a unitary vector with itself is 'unlike' the original vector
So, you can go fwd/backward with convolution/correlation

This gives a way of 'counting' positions:

$Pos_0 \circledast PlusOne = Pos_1$
$Pos_1 \circledast PlusOne = Pos_2$
$Pos_2 \circledast PlusOne = Pos_3$
etc.
And, $Pos_3 \circledast PlusOne' = Pos_2$

We can put this representation together with primacy and recency, and we get something like this

To do 'recall' from this representation, we want to remember what we've already recalled, so we can add a circuit like this

This does a pretty good job of capturing lots of working memory data

<img...
Can you write Python code for it?

import nengo
from nengo import spa

seed=1
np.random.seed(seed)
model = spa.SPA("Associative Memory", seed=seed)

D = 32
vocab = spa.Vocabulary(D)
vocab.parse('BLUE+GREEN+RED')

noise_RED = vocab.parse("RED").v + .2*np.random.randn(D)
noise_RED = noise_RED/np.linalg.norm(noise_RED)

noise_GREEN = vocab.parse("GREEN").v + .2*np.random.randn(D)
noise_GREEN = noise_GREEN/np.linalg.norm(noise_GREEN)

def memory_input(t):
    if t < 0.2:
        return vocab.parse("BLUE").v
    elif .2 < t < .5:
        return noise_RED
    elif .5 < t < .8:
        return vocab.parse("RED").v
    elif .8 < t < 1:
        return noise_GREEN
    else:
        return vocab.parse("0").v

with model:
    stim = nengo.Node(output=memory_input, label='input')
    model.am = spa.AssociativeMemory(vocab)
    nengo.Connection(stim, model.am.input)

    in_p = nengo.Probe(stim)
    out_p = nengo.Probe(model.am.output, synapse=0.03)

sim = nengo.Simulator(model)
sim.run(1)
t = sim.trange()

figure(figsize=(10,10))
plt.subplot(2, 1, 1)
plt.plot(t, spa.similarity(sim.data[in_p], vocab))
plt.ylabel("Input")
plt.ylim(top=1.1)
plt.legend(vocab.keys, loc='best')
plt.subplot(2, 1, 2)
plt.plot(t, nengo.spa.similarity(sim.data[out_p], vocab))
plt.ylabel("Output")
plt.legend(vocab.keys, loc='best');


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
By making the recurrent connection greater than one we made whatever is in memory 'grow' over time

This gives a 'primacy' effect

i.e. Whatever you ran into first is what you remember
You could also get this by changing connection weights

Together, primacy and recency capture the most salient properties of human working memory

You see these effects in both 'free recall' and 'serial recall'

So far, the networks we've seen would work well for free recall presumably.
How to include order information?

Serial Working Memory

An ordered list is a very simple kind of structure

To represent structures, we can use what we learned last time: vector binding

Specifically, Semantic Pointers

In this case, we can do something like:

$Pos_0\circledast Item_0 + Pos_1\circledast Item_1 + ...$

The $Item$ can just whatever vector we want to remember

How should we generate $Pos$?  What features does it need?

It would be good if we could generate them on the fly, forever
It would be good if $Pos_0$ "came before" $Pos_1$ in some sense

There is a kind of vector that's ideal for this called a 'unitary vector'

A unitary vector does not change length when convolved
So, you can convolve forever

The convolution of a unitary vector with itself is 'unlike' the original vector
So, you can go fwd/backward with convolution/correlation

This gives a way of 'counting' positions:

$Pos_0 \circledast PlusOne = Pos_1$
$Pos_1 \circledast PlusOne = Pos_2$
$Pos_2 \circledast PlusOne = Pos_3$
etc.
And, $Pos_3 \circledast PlusOne' = Pos_2$

We can put this representation together with primacy and recency, and we get something like this

To do 'recall' from this representation, we want to remember what we've already recalled, so we can add a circuit like this

This does a pretty good job of capturing lots of working memory data

Order...
Can you write Python code for it?

import nengo
import nengo_spa as spa

D = 32
seed = 1
model = spa.Network("Associative Memory", seed=seed)

vocab = spa.Vocabulary(dimensions=D,
                       pointer_gen=np.random.RandomState(seed + 1))

words = ['RED', 'GREEN', 'BLUE']
vocab.populate(';'.join(words))

#noise_RED = vocab.parse("RED").v + .4*np.random.randn(D)
noise_RED = vocab.parse("RED").v + .2*vocab.parse("GREEN").v + .2*np.random.randn(D)
noise_RED = noise_RED/np.linalg.norm(noise_RED)

noise_GREEN = vocab.parse("GREEN").v + .2*np.random.randn(D)
noise_GREEN = noise_GREEN/np.linalg.norm(noise_GREEN)

def memory_input(t):
    if t < 0.2:
        return vocab.parse("BLUE").v
    elif .2 < t < .5:
        return noise_RED
    elif .5 < t < .8:
        return vocab.parse("RED").v
    elif .8 < t < 1:
        return noise_GREEN
    else:
        return vocab.parse("0").v

with model:
    stim = spa.Transcode(memory_input, label='input', output_vocab=vocab)
    am = spa.ThresholdingAssocMem(threshold=0.3, input_vocab=vocab,
                                    mapping=vocab.keys(),
                                    function=lambda x: x>.2)

    stim >> am

    in_p = nengo.Probe(stim.output)
    out_p = nengo.Probe(am.output, synapse=0.03)

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/simple_cleanup.py.cfg")

# Notice that the magnitude of the cleaned element is preserved (without 'function='),
# and others are all made closer to or less than zero. The output is thus
# more 'purely' the target vector.
# 'function=' puts another threshold after to boost to one.
sim = nengo.Simulator(model)
sim.run(1.2)
t = sim.trange()

figure(figsize=(10,10))
plt.subplot(2, 1, 1)
plt.plot(t, spa.similarity(sim.data[in_p], vocab))
plt.ylabel("Input")
plt.ylim(top=1.1)
plt.legend(vocab.keys(), loc='best')
plt.subplot(2, 1, 2)
plt.plot(t, spa.similarity(sim.data[out_p], vocab))
plt.ylabel("Output")
plt.legend(vocab.keys(), loc='best');


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
So for higher dimensional memories, you may want the dimensions to be independent
To make this easy, Nengo has 'ensemble arrays'
Can you write Python code for it?

model = nengo.Network(label='Ensemble Array', seed=123)

N = 300 #neurons per sub_ensemble
tau = 0.01

with model:
    stim = nengo.Node(piecewise({.3:[1, -1], .5:[0, 0] }))

    neurons = nengo.networks.EnsembleArray(N, n_ensembles=2)

    nengo.Connection(stim, neurons.input, transform=tau, synapse=tau)
    nengo.Connection(neurons.output, neurons.input, synapse=tau)

    stim_p = nengo.Probe(stim)
    neurons_p = nengo.Probe(neurons.output, synapse=.01)

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/ensemble_array.py.cfg")


model = nengo.Network(label='Ensemble Array', seed=123)

N = 300
tau = 0.01

with model:
    stim = nengo.Node(piecewise({.3:[1, -1], .5:[0, 0] }))

    neurons = nengo.networks.EnsembleArray(N, n_ensembles=2)

    nengo.Connection(stim, neurons.input, transform=tau, synapse=tau)
    nengo.Connection(neurons.output, neurons.input, synapse=tau)

    stim_p = nengo.Probe(stim)
    neurons_p = nengo.Probe(neurons.output, synapse=.01)

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "ensemble_array.py.cfg")


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
This also generalizes to chaotic attractors and many other fun dynamic networks (see Lecture 5).
Can also generalize representation

Generalizing representation

Generalizing a line attractor gives something much more interesting
A plane attractor

<img src="lecture_memory/plane_attractor.png" width="600">

The attractor space is much more complicated, so you need a lot more neurons to do as good a job ($N^D$)
Can you write Python code for it?

%pylab inline
#A 1D integrator with a few hundred neurons works great
import nengo
from nengo.utils.functions import piecewise

model = nengo.Network(label='1D Line Attractor', seed=5)

N = 300
tau = 0.01

with model:
    stim = nengo.Node(piecewise({.3:[1], .5:[0] }))
    neurons = nengo.Ensemble(N, dimensions=1)

    nengo.Connection(stim, neurons, transform=tau, synapse=tau)
    nengo.Connection(neurons, neurons, synapse=tau)

    stim_p = nengo.Probe(stim)
    neurons_p = nengo.Probe(neurons, synapse=.01)

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position")
legend(loc="best");

#Need lots of neurons to get reasonable performance in higher D
import nengo
from nengo.utils.functions import piecewise

model = nengo.Network(label='2D Plane Attractor', seed=4)

N = 2000 #600
tau = 0.01

with model:
    stim = nengo.Node(piecewise({.3:[1, -1], .5:[0, 0] }))
    neurons = nengo.Ensemble(N, dimensions=2)

    nengo.Connection(stim, neurons, transform=tau, synapse=tau)
    nengo.Connection(neurons, neurons, synapse=tau)

    stim_p = nengo.Probe(stim)
    neurons_p = nengo.Probe(neurons, synapse=.01)

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position")
legend(loc="best");

#Note that the representation saturates at the radius
with model:
    stim.output = piecewise({.2:[1, -1], 1.2:[0, 0] })

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
This also generalizes to chaotic attractors and many other fun dynamic networks (see Lecture 5).
Can also generalize representation

Generalizing representation

Generalizing a line attractor gives something much more interesting
A plane attractor

<img src="lecture_memory/plane_attractor.png">

The attractor space is much more complicated, so you need a lot more neurons to do as good a job ($N^D$)
Can you write Python code for it?

#A 1D integrator with a few hundred neurons works great
import nengo
from nengo.utils.functions import piecewise

model = nengo.Network(label='1D Line Attractor', seed=5)

N = 300
tau = 0.01

with model:
    stim = nengo.Node(piecewise({.3:[1], .5:[0] }))
    neurons = nengo.Ensemble(N, dimensions=1)

    nengo.Connection(stim, neurons, transform=tau, synapse=tau)
    nengo.Connection(neurons, neurons, synapse=tau)

    stim_p = nengo.Probe(stim)
    neurons_p = nengo.Probe(neurons, synapse=.01)

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position")
legend(loc="best");

#Need lots of neurons to get reasonable performance in higher D
import nengo
from nengo.utils.functions import piecewise

model = nengo.Network(label='2D Plane Attractor', seed=4)

N = 2000
tau = 0.01

with model:
    stim = nengo.Node(piecewise({.3:[1, -1], .5:[0, 0] }))
    neurons = nengo.Ensemble(N, dimensions=2)

    nengo.Connection(stim, neurons, transform=tau, synapse=tau)
    nengo.Connection(neurons, neurons, synapse=tau)

    stim_p = nengo.Probe(stim)
    neurons_p = nengo.Probe(neurons, synapse=.01)

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position")
legend(loc="best");

#Note that the representation saturates at the radius
with model:
    stim.output = piecewise({.2:[1, -1], 1.2:[0, 0] })

sim = nengo.Simulator(model)
sim.run(4)

t=sim.trange()

plot(t, sim.data[stim_p], label = "stim")
plot(t, sim.data[neurons_p], label = "position");


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
Working memory

We can build high dimensional working memories using plane attractors
In general we want some saturation, and some independence between dimensions
Gives both a 'soft normalization'
And good temporal stability for fewer neurons
Can you write Python code for it?

import nengo
from nengo import spa

def color_input(t):
    if t < 0.15:
        return 'BLUE'
    elif 1.0 < t < 1.15:
        return 'GREEN'
    elif 1.7 < t < 1.85:
        return 'RED'
    else:
        return '0'

model = spa.SPA(label="HighD Working Memory", seed=5)

dimensions = 32

with model:
    model.color_in = spa.Buffer(dimensions=dimensions)

    model.mem = spa.Memory(dimensions=dimensions, subdimensions=4,
                           synapse=0.1, neurons_per_dimension=50)

    # Connect the buffers
    cortical_actions = spa.Actions(
        'mem = color_in'
    )

    model.cortical = spa.Cortical(cortical_actions)

    model.inp = spa.Input(color_in=color_input)

    model.config[nengo.Probe].synapse = nengo.Lowpass(0.03)
    color_in = nengo.Probe(model.color_in.state.output)
    mem = nengo.Probe(model.mem.state.output)

sim = nengo.Simulator(model)
sim.run(3.)

plt.figure(figsize=(10, 10))
vocab = model.get_default_vocab(dimensions)

plt.subplot(2, 1, 1)
plt.plot(sim.trange(), model.similarity(sim.data, color_in))
plt.legend(model.get_output_vocab('color_in').keys, fontsize='x-small')
plt.ylabel("color")

plt.subplot(2, 1, 2)
plt.plot(sim.trange(), model.similarity(sim.data, mem))
plt.legend(fontsize='x-small')
plt.legend(model.get_output_vocab('color_in').keys, fontsize='x-small')
plt.ylabel("memory")
plt.xlabel("time [s]");

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "simple_spa_wm.py.cfg")

model.all_ensembles

model.all_ensembles[2].n_neurons


I am working on the file "SYDE 556 Lecture 8 Memory.ipynb".
The first task is:
You can see interference effects here

If you recalled things from this memory, you'd probably have a 'recency effect'

i.e. The things most recently put in memory would be best recalled

This is seen in human memory, but so is primacy

How could we get primacy?
Can you write Python code for it?

import nengo
import nengo_spa as spa

def colour_input(t):
    if t < 0.15:
        return 'BLUE'
    elif 1.0 < t < 1.15:
        return 'GREEN'
    elif 1.7 < t < 1.85:
        return 'RED'
    else:
        return '0'

model = spa.Network(label="HighD Working Memory", seed=3)

dimensions = 32

with model:
    colour_in = spa.Transcode(colour_input, output_vocab=dimensions)

    mem = spa.State(dimensions, subdimensions=4, feedback=1.2,
                    feedback_synapse=0.1,
                    neurons_per_dimension=50)

    # Connect the ensembles
    colour_in >> mem

    model.config[nengo.Probe].synapse = nengo.Lowpass(0.03)
    colour_in_p = nengo.Probe(colour_in.output)
    mem_p = nengo.Probe(mem.output)

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/simple_spa_wm_primacy.py.cfg")

sim = nengo.Simulator(model)
sim.run(3.)

plt.figure(figsize=(10, 10))
vocab = model.vocabs[dimensions]

plt.subplot(2, 1, 1)
plt.plot(sim.trange(), spa.similarity(sim.data[colour_in_p], vocab))
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("colour")

plt.subplot(2, 1, 2)
plt.plot(sim.trange(), spa.similarity(sim.data[mem_p], vocab))
plt.legend(fontsize='x-small')
plt.legend(vocab.keys(), fontsize='x-small')
plt.ylabel("memory")
plt.xlabel("time [s]");


import nengo
from nengo import spa

def color_input(t):
    if t < 0.15:
        return 'BLUE'
    elif 1.0 < t < 1.15:
        return 'GREEN'
    elif 1.7 < t < 1.85:
        return 'RED'
    else:
        return '0'

model = spa.SPA(label="HighD Working Memory", seed=5)

dimensions = 32

with model:
    model.color_in = spa.Buffer(dimensions=dimensions)

    model.mem = spa.Memory(dimensions=dimensions, subdimensions=4,
                           synapse=0.1, neurons_per_dimension=50, tau=-.2)

    # Connect the buffers
    cortical_actions = spa.Actions(
        'mem = color_in'
    )

    model.cortical = spa.Cortical(cortical_actions)

    model.inp = spa.Input(color_in=color_input)

    model.config[nengo.Probe].synapse = nengo.Lowpass(0.03)
    color_in = nengo.Probe(model.color_in.state.output)
    mem = nengo.Probe(model.mem.state.output)

sim = nengo.Simulator(model)
sim.run(3.)

plt.figure(figsize=(10, 10))
vocab = model.get_default_vocab(dimensions)

plt.subplot(2, 1, 1)
plt.plot(sim.trange(), model.similarity(sim.data, color_in))
plt.legend(model.get_output_vocab('color_in').keys, fontsize='x-small')
plt.ylabel("color")

plt.subplot(2, 1, 2)
plt.plot(sim.trange(), model.similarity(sim.data, mem))
plt.legend(fontsize='x-small')
plt.legend(model.get_output_vocab('color_in').keys, fontsize='x-small')
plt.ylabel("memory")
plt.xlabel("time [s]");


I am working on the file "SYDE 556 Lecture 9 Action Selection.ipynb".
The first task is:
Behavioural Evidence

Is there any evidence that this is the way it works in brains?
Consistent with anatomy/connectivity

What about behavioural evidence?

A few sources of support

Timing data

How long does it take to do an action?
There are lots of existing computational (non-neural) cognitive models that have something like this action selection loop
Usually all-symbolic
A set of IF-THEN rules

e.g. ACT-R
Used to model mental arithmetic, driving a car, using a GUI, air-traffic control, staffing a battleship, etc etc

Best fit across all these situations is to set the loop time to 50ms

How long does this model take?

Notice that all the timing is based on neural properties, not the algorithm
Dominated by the longer neurotransmitter time constants in the basal ganglia

Simple actions

Complex actions (routing)

This is in the right ballpark

But what about this distinction between the two types of actions?

Not a distinction made in the literature
But once we start looking for it, there is evidence
Resolves an outstanding weirdness where some actions seem to take twice as long as others
Starting to be lots of citations for 40ms for simple tasks
Task artifacts and strategic adaptation in the change signal task

This is a nice example of the usefulness of making neural models!
This distinction wasn't obvious from computational implementations

More complex tasks

Lots of complex tasks can be modelled this way
Some basic cognitive components (cortex)
action selection system (basal ganglia and thalamus)

The tricky part is figuring out the actions

Example: the Tower of Hanoi task

3 pegs
N disks of different sizes on the pegs
move from one configuration to another
can only move one disk at a time
no larger disk can be on a smaller disk

can we build rules to do this?
Can you write Python code for it?

from IPython.display import YouTubeVideo
YouTubeVideo('sUvHCs5y0o8', width=640, height=390, loop=1, autoplay=0)


I am working on the file "SYDE 556 Lecture 9 Action Selection.ipynb".
The first task is:
Better behaviour
But there's still situations where there's too much memory (see the visualizer)
We can reduce this by reducing e
Can you write Python code for it?

%pylab inline
import nengo

def stimulus(t):
    if t<.3:
        return [.5,.4]
    elif .3<t<.5:
        return [.3,.5]
    else:
        return [0,0]

model = nengo.Network('Selection')

with model:
    stim = nengo.Node(stimulus)

    s = nengo.Ensemble(200, dimensions=2)
    Qs = nengo.networks.EnsembleArray(50, n_ensembles=4)
    Action = nengo.networks.EnsembleArray(50, n_ensembles=4)

    nengo.Connection(s, Qs.input, transform=[[1,0],[-1,0],[0,1],[0,-1]])
    nengo.Connection(Qs.output, Action.input)

    e = 0.1
    i = -1

    recur = [[e, i, i, i], [i, e, i, i], [i, i, e, i], [i, i, i, e]]

    def positive(x):
        if x[0]<0: return [0]
        else: return x
    pos = Action.add_output('positive', positive)

    nengo.Connection(pos, Action.input, transform=recur)

    def select(x):
        if x[0]>=0: return [1]
        else: return [0]
    sel = Action.add_output('select', select)

    aValues = nengo.networks.EnsembleArray(50, n_ensembles=4)

    nengo.Connection(sel, aValues.input)
    nengo.Connection(stim, s)

    model.config[nengo.Probe].synapse = nengo.Lowpass(0.01)
    qs_p = nengo.Probe(Qs.output)
    action_p = nengo.Probe(Action.output)
    aValues_p = nengo.Probe(aValues.output)
    s_p = nengo.Probe(s)

#sim = nengo.Simulator(model)
#sim.run(1.)

from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/bg_simple1.py.cfg")


I am working on the file "SYDE 556 Lecture 9 Action Selection.ipynb".
The first task is:
Much less memory, but it's still there
And slower to respond to changes
Note that this speed is dependent on $e$, $i$, and the time constant of the neurotransmitter used

Can be hard to find good values

And this gets harder to balance as the number of actions increases

Also hard to balance for a wide range of $Q$ values
(Does it work for $Q$=[0.9, 0.9, 0.95, 0.9] and $Q$=[0.2, 0.2, 0.25, 0.2]?)

But this is still a pretty standard approach

Nice and easy to get working for special cases
Don't really need the NEF (if you're willing to assume non-realistic non-spiking neurons)
(Although really, if you're not looking for biological realism, why not just compute the max function?)

Example: OReilly, R.C. (2006). Biologically Based Computational Models of High-Level Cognition. Science, 314, 91-94.

Leabra

They tend to use a "kWTA" (k-Winners Take All) approach in their models

Set up inhibition so that only $k$ neurons will be active
But since that's complex to do, just do the math instead of doing the inhibition
We think that doing it their way means that the dynamics of the model will be wrong (i.e. all the effects we saw above are being ignored).

Any other options?

Biology

Let's look at the biology
Where is this action selection in the brain?
General consensus: the basal ganglia

<img src="files/lecture_selection/basal_ganglia.jpg" width="500">

Pretty much all of cortex connects in to this area (via the striatum)

Output goes to the thalamus, the central routing system of the brain

Disorders of this area of the brain cause problems controlling actions:

Parkinson's disease
Neurons in the substantia nigra die off
Extremely difficult to trigger actions to start
Usually physical actions; as disease progresses and more of the SNc is gone, can get cognitive effects too

Huntington's disease
Neurons in the striatum die off
Actions are triggered inappropriately (disinhibition)
Small uncontrollable movements
Trouble sequencing cognitive actions too

Also heavily implicated in reinforcement...
Can you write Python code for it?

%pylab inline
mm=1
mp=1
me=1
mg=1

#connection strengths from original model
ws=1
wt=1
wm=1
wg=1
wp=0.9
we=0.3

#neuron lower thresholds for various populations
e=0.2
ep=-0.25
ee=-0.2
eg=-0.2

le=0.2
lg=0.2

D = 10
tau_ampa=0.002
tau_gaba=0.008
N = 50
radius = 1.5

import nengo
from nengo.dists import Uniform

model = nengo.Network('Basal Ganglia', seed=4)

with model:
    stim = nengo.Node([0]*D)

    StrD1 = nengo.networks.EnsembleArray(N, n_ensembles=D, intercepts=Uniform(e,1),
                                 encoders=Uniform(1,1), radius=radius)
    StrD2 = nengo.networks.EnsembleArray(N, n_ensembles=D, intercepts=Uniform(e,1),
                                 encoders=Uniform(1,1), radius=radius)
    STN = nengo.networks.EnsembleArray(N, n_ensembles=D, intercepts=Uniform(ep,1),
                                 encoders=Uniform(1,1), radius=radius)
    GPi = nengo.networks.EnsembleArray(N, n_ensembles=D, intercepts=Uniform(eg,1),
                                 encoders=Uniform(1,1), radius=radius)
    GPe = nengo.networks.EnsembleArray(N, n_ensembles=D, intercepts=Uniform(ee,1),
                                 encoders=Uniform(1,1), radius=radius)

    nengo.Connection(stim, StrD1.input, transform=ws*(1+lg), synapse=tau_ampa)
    nengo.Connection(stim, StrD2.input, transform=ws*(1-le), synapse=tau_ampa)
    nengo.Connection(stim, STN.input, transform=wt, synapse=tau_ampa)

    def func_str(x): #relu-like function
        if x[0]<e: return 0
        return mm*(x[0]-e)
    strd1_out = StrD1.add_output('func_str', func_str)
    strd2_out = StrD2.add_output('func_str', func_str)

    nengo.Connection(strd1_out, GPi.input, transform=-wm, synapse=tau_gaba)
    nengo.Connection(strd2_out, GPe.input, transform=-wm, synapse=tau_gaba)

    def func_stn(x):
        if x[0]<ep: return 0
        return mp*(x[0]-ep)
    stn_out = STN.add_output('func_stn', func_stn)

    tr=[[wp]*D for i in range(D)]
    nengo.Connection(stn_out, GPi.input, transform=tr, synapse=tau_ampa)
   ...