How to control parameters of functions used for neural connections of ensembles

Dear all

I have to control a complex neural connection between a syllable oscillator, i.e.:

``````    SYLL01_oscillator = nengo.Ensemble(n_neurons=m, dimensions=2, intercepts=nengo.dists.Uniform(0.3,1))
``````

and a ramp function integrator, i.e.:

``````    BAP_syll01_conn1 = nengo.Ensemble(n_neurons=Nc, dimensions=1, intercepts=nengo.dists.Uniform(0.3, 1))
``````

The function is defined as follows:

``````BAP_func_sa_lab_constr1 = piecewise({0:[0], 0.2:[1.0], 0.5:[0]}) # C initial: /b/

def ramp_t(x):
theta = math.atan2(x[1], x[0])
t = theta/ (2*np.pi) + 0.5
return t

def BAP_signal_1(x):
return BAP_func_sa_lab_constr1(ramp_t(x))
``````

and the neural connection between oscillator and integrator is:

``````nengo.Connection(SYLL01_oscillator, BAP_syll01_conn1, function=BAP_signal_1)
``````

My question is:

How can I modify my model for

``````piecewise({0:[0], 0.2:[1.0], 0.5:[0]}
``````

because the. values `t1 = 0.2` and `ampl1 = 1.0`, and `t2 = 0.5` need to be a result of
decoding neuron ensembles like

``````val_t1 = nengo.Ensemble (n_neurons=Nc, dimensions=1)
val_t2 = Nengo Ensemble (n_neurons=Nc, dimensions=1)
ampl1 = Nengo Ensemble (n_neurons=Nc, dimensions=1)
``````

and how can I now decode ensemble activity in values (without probing; probing is later!)
and how can I feed the stepwise function during run of the simulation ?

Kind regards
Bernd

Hi @bernd,

I’m not entirely sure what you are trying to accomplish. From your code, it seems like you have constructed a connection between an oscillator and an integrator with a specific function applied to the connection? And it looks like this function takes the 2D coordinate point of the oscillator, and turns it into a ramp signal, which is then used to determine what the output of the piecewise function should be?
So… in essence, what you are looking to do is to have something generate a customizable piecewise function that you can feed into the integrator? Are you looking to have this entirely in neurons?

If you are okay with using a `nengo.Node` to make a customizable piecewise function, then something like this should suffice:

``````def custom_piecewise(t, x):
t1, t2, ampl = x
if t < t1 or t >= t2:
return 0
else:
return ampl

pw_node = nengo.Node(size_in=3, output=custom_piecewise)
nengo.Connection(val_t1, pw_node[0])
nengo.Connection(val_t2, pw_node[1])
nengo.Connection(ampl1, pw_node[2])
nengo.Connection(pw_node, BAP_syll01_conn1)
``````

If, however, you want to implement it in neurons, there are a few ways to do it. I gave it some thought, and I think the most straightforward way is to leverage what you already have implemented in your network. Right now, you have the oscillator connected to the integrator with a connection function that turns the oscillator output into the piecewise signal:

``````oscillator --> connection function --> integrator
``````

My thought is to introduce an intermediary ensemble to modify the output of the oscillator, like so:

``````val_t1 ------------+
val_t2 ----------+ |
ampl ----------+ | |
V V V
oscillator --> modulator --> connection function --> integrator
``````

The goal of this “modulator” ensemble is to adjust the output of the oscillator based off the values of `val_t1`, `val_t2`, and `ampl`. For example, let’s say the oscillator takes 1s to do one cycle. Let’s also say that in the connection function, you configure it so that the output is like this `piecewise({0:[0], 0.5:[1.0], 1.0:[0]}` (so, default values are `val_t1=0.5`, `val_t2=1`, `ampl=1`). Let’s also assume the oscillator starts at [0, 1] and cycles counter clockwise.

Now, let’s say you changed the output of the `val` and `ampl` ensembles to something different. Suppose you wanted `val_t1=0.25`, `val_t2=0.5`, `ampl=1`. What the “modulator” ensemble should be tasked to compute is the mapping between the input oscillator values, and the output oscillator values. So, if the modulator ensemble maps an input of [1, 0] to an output of [0, -1], and an input of [0, -1] to an output of [0, 1], we should observe that as the oscillator cycles, it will produce the modified piecewise function that we wanted. Do I make any sense?

An alternate approach to this is to connect several ensembles (some with thresholds, some that are integrators, etc.) to form the piecewise function. The dynamics of these ensembles can then be controlled by the outputs of `val_t1`, `val_t2`, and `ampl`. As an example, we could use the output of `ampl` to get the amplitude of the piecewise function. Then, use a fast-decaying integrator (where the decay time is controlled by `val_t1`, which should be doable if `val_t1` determines the initial value in the integrator) to inhibit `ampl`. This sets up the initial “0” value for the time until `val_t1`. For `val_t2`, we could have a regular integrator integrate to a specific threshold. Once it reaches this threshold, it triggers an inhibition on `ampl` to then bring the function back to 0.

I’m not entirely sure what you are asking here… In Nengo, if you use “standard” connections (i.e., not connecting to the `.neurons` object), the values being transmitted between ensembles is already decoded.

Hi Xuan
May be you did not understand my problem because it was a kind of trivial.
Yes, you understood my question correctly.
The solution which is offered by you will help me:
the main point is: to reformulate the piecewise function in a way that I can input parameter values to that function.
I can do that now, by using your custom_piecewise function and by forwarding ensemble activities into the pw_node construct.

The new idea or take-away message for me is, that I can use nengo.Node not just as an artificial construct to prompt input in a neuron model (into a neuron ensemble at the perceptual input part of a model) but I as well can use nengo.Node “in the flow of transformations” appearing in a complex model.

In my model: the oscillator is already started by the action selection component (BG-thal) and now values are added from a memory component in order to activate the correct ramp signals. These values are the input to the piecewise function.

One last question:
Is it biologically realistic to include nengo.Node , i.e., to include “NEF-nodes” at any processing stage in a complex model and not just at the sensory input part of a model?
For me it is still unclear how the nengo.Node can be interpreted from a neurobiologically point of view. I understand the notion of neurons and connections building up all components of a brain model like neuron ensembles and neural connections but I am not sure how to interpret a Node in a neurobiologically way in terms of neurons and connections.

Best
Bernd

From a biological standpoint, the `nengo.Node` is quite artificial. It’s like dropping a computer chip inside the brain to do additional computation, so, no, a `nengo.Node` is not biologically realistic.
There is one instance where you can consider the `nengo.Node` biologically realistic though, and that’s when:

• The `nengo.Node` has no output function assigned to it
• And the output connections from the node only do a `transform` and not a `function`.

In the above instance, since transforms are just matrix multiplications, the matrix multiplication can be “folded” into the connection weights, essentially removing the `nengo.Node`, and embedding all of the information within the weights itself. Some of the Nengo backends (e.g., NengoLoihi) do this to optimize the network.

Hi Xuan
thank you for that response.
I so far thought that a connection (.A, B, transform= …, function=…) in both cases (transform and function) resolves the connection statement in a biologically realistic way by just adapting connection weights from ensemble A towards ensemble B?
Regards
Bernd

Yes, @bernd, but I should clarify that the statement you made (i.e., using both `transform` and `function` will result in a biologically realistic connection) is only applicable if `A` is a `nengo.Ensemble`.

If `A` is a `nengo.Node` (as is the case in my example `custom_piecewise` code, where the last connection is from the node), then this is no longer the case. Having a node compute a function in a connection makes the connection not biologically plausible.