What do the connection inputs and outputs mean?


I’m trying to understand the very basics of how connections work, but am having trouble finding exactly how a connection affects the voltage and current of the pre and post neurons. Is it in step_math of the neuron_type where the input from the previous layer is being used, or somewhere else? For example, if I make the following network, I am expecting that the input to b.neurons is the voltage array of a.neurons multiplied by the weights, which in this case, should be identical to the voltage of a.neurons. However, I don’t know where the inputs and outputs from probing the connection are coming from/what they mean:

weights = np.array([[1, 1, 1],
                    [1, 1, 1],
                    [1, 1, 1]])

model = nengo.Network()

with model:
    a = nengo.Ensemble(3, dimensions=1, neuron_type=nengo.LIF())
    b = nengo.Ensemble(3, dimensions=1, neuron_type=nengo.LIF())

    conn = nengo.Connection(a.neurons, b.neurons, transform = weights)
    spikes_probe = nengo.Probe(a.neurons, 'spikes')
    voltage_probe = nengo.Probe(a.neurons, 'voltage')
    b_spikes = nengo.Probe(b.neurons, 'spikes')
    b_voltage = nengo.Probe(b.neurons, 'voltage')
    conn_out = nengo.Probe(conn, 'output')
    conn_in = nengo.Probe(conn, 'input')
with nengo.Simulator(model) as sim:

plt.figure(figsize=(12, 6))
plt.plot(sim.trange(), sim.data[b_voltage][:,0], label = "b")
plt.plot(sim.trange(), sim.data[voltage_probe][:,0], label = 'a')
plt.xlabel('time [s]')
plt.ylabel('b voltage')

plt.plot(sim.trange(), sim.data[b_voltage][:,1], label = "b")
plt.plot(sim.trange(), sim.data[voltage_probe][:,1], label = 'a')
plt.xlabel('time [s]')
plt.ylabel('b voltage')

plt.plot(sim.trange(), sim.data[b_voltage][:,2], label = "b")
plt.plot(sim.trange(), sim.data[voltage_probe][:,2], label = 'a')
plt.xlabel('time [s]')
plt.ylabel('b voltage')


I get the following graphs and values for conn_out and conn_in.

Regarding the question about probing, in general for a connection c = nengo.Connection(pre, post) in general, nengo.Probe(c, "input") is equivalent to nengo.Probe(pre, "output") , and nengo.Probe(c, "output") is equivalent to nengo.Probe(post, "input"). Given that pre and post are neurons in your example, the inputs to the connection are the spikes generated by ensemble a, which are scaled by 1/dt and hence appear as 1000s if dt is one millisecond as per default, while the outputs from the connection are the result of multiplying input spikes by their corresponding connection weights and then passing them through a synaptic filter.

These outputs are then provided as inputs to the neurons in post or ensemble b in your example. Because the LIF neuron model has internal parameters (e.g., a bias), the voltage of each neuron is not determined solely by its input, and won’t be identical to that of a corresponding neuron in a (to do this kind of voltage comparison you’d also want to use an identity matrix instead of a full array of ones for the connection weights in order to ensure that spikes from a neuron in a only are received by a corresponding neuron in b).

Anyway, I hope this helps to clear things up, but please feel free to let us know if you have further questions.

Thank you, this is very helpful! I see now that when I make synapse = None and have an identity matrix as the weights, the connection input and output are identical. How do I know where this output is being used in the voltage update for the LIF neuron? I can’t seem to tell where this information is in the documentation. My ultimate goal is to create a custom neuron type where I can control where the input from the previous layer is being used.

A good starting point might be to look at the implementation for the LIF neuron type that you are currently using: https://www.nengo.ai/nengo/frontend-api.html#nengo.LIF

In general, a neuron type implementation has to have a step method that runs at every tilmestep during a simulation. Neurons also typically have a gain and bias associated with them, where the gain has a multiplicative effect on the input while the bias has an additive effect (see e.g: https://www.nengo.ai/nengo/_modules/nengo/neurons.html#NeuronType). If you set the gains to 1 and biases to 0, then the input J in the existing LIF step implementation will be the output of the connection object (the gains may be rolled into the connection weights, but for now, I’d start with setting them to 1 and go from here).