Questions about nengo.Connection

I read the nengo.Connection documentation, but unfortunately I still do not understand what is the role of transform, solver, .neurons and synapses?

  1. What does transform do in nengo.Connection?

In the documentation it is written:

transform : (size_out, size_mid) array_like, optional (Default: np.array(1.0))
Linear transform mapping the pre output to the post input.

Case 1: The default value of transform is NoTransform(size_in=1) when I make a connection between a node and ensemble or ensemble and ensemble.

a = nengo.Connection(input, ens1)

Case 2: But, when I make a connection between a node and ens1.neurons or ens1.neurons and ens2.neurons I must specify the transform parameter, otherwise I get size error.

a = nengo.Connection(input, ens1.neurons)

gives me a size error, but

a = nengo.Connection(input, ens1.neurons, transform=[[1]] * n_neurons)

runs without an error. I understand how the shape of transform works.

Case 3: I can also do something like this.

weights = np.random.normal(size=(ens2.n_neurons, ens1.n_neurons))
conn = nengo.Connection(pre=ens1.neurons,
                        post=ens2.neurons,
                        transform=weights) # transform=nengo_dl.dists.Glorot())

In case 3, does this mean that weights are linearly mapped from pre output to the post input? How to choose transform? Is transform always linearly mapped irrespective of the connection type(decoded connection or direct connection)?

So, NoTransform means it does not do any linear mapping between the pre output to the post input. Signals are directly passed between pre- and post. Correct? But then, if the value of transform is given there is a linear mapping. How is this linear mapping done?

I believe when conn = nengo.Connection(pre=ens1.neurons, post=ens2.neurons) there are no weights because of the direct connection, and when conn = nengo.Connection(pre=ens1, post=ens2) there are weights that are solved (solver is LstsqL2() by default) because it is decoded connection. Is my understanding correct?

  1. This link and this link talks about different types of connection (decoded connection and direct connection)

The examples of decoded connection given in the link is

with nengo.Network() as net:
    ens1 = nengo.Ensemble(10, dimensions=2)
    node = nengo.Node(size_in=1)
    ens2 = nengo.Ensemble(4, dimensions=2)

    # Ensemble to ensemble
    nengo.Connection(ens1, ens2)
    # Ensemble slice to node
    nengo.Connection(ens1[0], node)
    # Ensemble to neurons slice
    nengo.Connection(ens1, ens2.neurons[:2])

Example of Direct connection given in the link is:

with nengo.Network() as net:
    ens1 = nengo.Ensemble(10, dimensions=1)
    ens2 = nengo.Ensemble(20, dimensions=2)

    # Neuron to neuron
    weights = np.random.normal(size=(ens2.n_neurons, ens1.n_neurons))
    nengo.Connection(ens1.neurons, ens2.neurons, transform=weights)

Then, we have this example in the link, one using a decoded connection and one using a direct connection:

with nengo.Network() as net:
    ens1 = nengo.Ensemble(20, dimensions=1, seed=0)
    ens2 = nengo.Ensemble(15, dimensions=1)

    # Decoded ensemble to ensemble connection
    conn1 = nengo.Connection(ens1, ens2, function=lambda x: x + 0.5)

with nengo.Simulator(net) as sim:
    decoders = sim.data[conn1].weights

with net:
    # Direct neurons to ensemble connection
    conn2 = nengo.Connection(ens1.neurons, ens2, transform=decoders) 

Why is conn2 = nengo.Connection(ens1.neurons, ens2, transform=decoders) a direct connection, but nengo.Connection(ens1, ens2.neurons[:2]) a decoded connection? Is it because neurons slice is used? I understand what equation is solved by decoded connection, but I don’t understand what decoded connection means?

  1. How are spikes (or any other parameter) affected when a connection is made between 2 ensembles or 2 ensembles.neuron? When to make a connection like in case 1 or case 2?

Case 1

conn = nengo.Connection(pre=ens1,post=ens2)

Case 2

conn = nengo.Connection(pre=ens1.neurons,
                        post=ens2.neurons,
                        transform=weights)
  1. What is a solver? In the documentation, it is written

solver : Solver, optional (Default: nengo.solvers.LstsqL2())

Solver instance to compute decoders or weights (see Solver). If solver.weights is True, a full connection weight matrix is computed instead of decoders.

4a. My understanding is when solver.weights is True, it forms a neuron-to-neuron weight matrices (but how? random, uniform, normal?) and when it is false, it solves for decoders (what is solved in decoder?), as stated in here.

4b. Does solver optimize the connection weights?

  • If yes, then how? Is it based on the learning rule? If it is based on the learning rule, then what does nengo.solvers.LstsqL2() do? I thought nengo.Connection just creates a connection between node and ensemble or ensemble and ensemble, and weights are calculated during sim.run.
  • if no, then what does the solver really do?
  1. Why is synapse passed through a low pass filter? I know that the synaptic filter is not the same as synaptic weights.

So, say synapse=0.5 is a shorthand for synapse=nengo.Lowpass(0.5), i.e. a Lowpass synaptic filter with a time-constant of 0.5

So, the role of synapse is just to smooth the output?

Hi,
I have similar curiosities… did you ever find clarity to these questions?
Would appreciate if you could share further insight with someone just getting started with the framework :slight_smile:
Thanks!