In [1]:
%pylab inline
In [2]:
import nengo
model = nengo.Network()
with model:
ensA = nengo.Ensemble(100, dimensions=1)
def feedback(x):
return x+1
conn = nengo.Connection(ensA, ensA, function=feedback, synapse = 0.1)
ensA_p = nengo.Probe(ensA, synapse=.01)
sim = nengo.Simulator(model)
sim.run(.5)
plot(sim.trange(), sim.data[ensA_p])
ylim(-1.5,1.5);
What are the precise dynamics?
What about $f(x)=-x$?
In [3]:
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);
In [4]:
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);
Does this make sense?
What about $f(x)=x^2$?
In [5]:
with model:
stim.output = piecewise({.1:.2, .2:.4, .5:0})
def feedback(x):
return x*x
conn.function = feedback
sim = nengo.Simulator(model)
sim.run(.6)
plot(sim.trange(), sim.data[ensA_p])
ylim(-1.5,1.5);
In [6]:
import nengo
from nengo.utils.functions import piecewise
model = nengo.Network(seed=4)
with model:
stim = nengo.Node(piecewise({.3:1}))
ensA = nengo.Ensemble(100, dimensions=1)
def feedback(x):
return x
nengo.Connection(stim, ensA)
#conn = nengo.Connection(ensA, ensA, function=feedback)
stim_p = nengo.Probe(stim)
ensA_p = nengo.Probe(ensA, synapse=0.01)
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);
In [7]:
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);
In [8]:
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);
$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))$
${dx \over dt} = {1 \over \tau} (f(x(t))-x(t))$
This says that if we introduce a recurrent connection, we end up implementing a differential equation
So what happened with $f(x)=x+1$?
What happens if there's an input as well?
Follow the same derivation steps
So if you have some input that you want added to $\dot{x}$, you need to scale it by $\tau$
This lets us do any differential equation of the form $\dot{x}=f(x)+g(u)$
$\dot{x}(t) = A x(t) + B u(t)$
$sX(s) = A X(s) + B U(s)$
$X(s) = {1 \over {1 + s\tau}} (A'X(s) + B'U(s))$
$X(s) + \tau sX(s) = (A'X(s) + B'U(s))$
$sX(s) = {1 \over \tau} (A'X(s) + B'U(s) - X(s))$
$sX(s) = {1 \over \tau} ((A' - I) X(s) + B'U(s))$
$A' = \tau A + I$ and
$B' = \tau B$
where $I$ is the identity matrix
This is nice because lots of engineers think of the systems they build in these terms (i.e. as linear control systems).
$\dot{x}(t) = f(x(t),u(t),t)$
$X(s) = H(s)F'(X(s),U(s),s)$
$X(s) = {1 \over {1 + s\tau}} F'(X(s),U(s),s)$
$sX(s) = {1 \over \tau} (F'(X(s),U(s),s) - X(s))$
$F'(X(s),U(s),s) = \tau(F(X(s),U(s),s)) + X(s)$
In [31]:
import nengo
from nengo.utils.functions import piecewise
from nengo.utils.ensemble import tuning_curves
tau = 0.01
model = nengo.Network('Eye control', seed=8)
with model:
stim = nengo.Node(piecewise({.3:1, .6:0 }))
velocity = nengo.Ensemble(100, dimensions=1)
position = nengo.Ensemble(20, 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)
x, A = tuning_curves(position, sim)
plot(x,A)
figure()
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");
In [27]:
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$');
$\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$
In [37]:
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");
$\dot{x} = -d x + v$
This is no longer in the standard $Ax + Bu$ form. Sort of...
We need to compute a nonlinear function of an input ($d$) and the state variable ($x$)
In [48]:
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");
In [49]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/controlled_integrator.py.cfg")
In [52]:
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$');
In [53]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/oscillator.py.cfg")
Lorenz Attractor (a chaotic attractor)
In [54]:
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$');
In [55]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/lorenz.py.cfg")
In [58]:
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$');
In [59]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model) #do config
r
)?
In [60]:
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$');
In [61]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model) #do config
In [62]:
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_ctrl = 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, 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$');
In [63]:
from nengo_gui.ipython import IPythonViz
IPythonViz(model, "configs/controlled_oscillator.py.cfg")
In [ ]: