Overview

The Process you've created can now be controlled, generating a dynamic behaviour dependent on the parameters we pass to it. Processes of this sort can be described as "source" processes, because their output is derived entirely from their initial state - that is, they don't take any inputs. Most processes you write will want to take some input, at least. Let's try this, now.

(Aside) Adding a second process to the system

This sort of stuff is really covered elsewhere (in Developing Systems), so we're not going to dwell on it. We're just going to add a source of numeric data to the system, that we can use as an input to our process. We're then going to link its output to the input of our process, calling the input Port "drive".

Modify your test script as follows, to add the source.

M Source Code (against 995)
... cls = 'dev/quickstart/1262'; sys = sys.addprocess('neuron', cls, fS, state);
% add source
state = [];
state.data = 1;
state.repeat = true;
state.ndims = 1;
sys = sys.addprocess('source', cls, fS, state);
 
% link source to our process
sys = sys.link('source>out', 'neuron<drive');
% execution exe = brahms_execution; ...

You can go ahead and run this immediately, without modifying your process. Nothing will be different - the process has been presented this input, but it has ignored it, and this is quite acceptable in BRAHMS.

Validating an input is present

Modify your source file as follows to check that an input named "drive" was presented, and store it amongst our state. Since we don't need to see our inputs to create our outputs, we can safely do all our input validation on the last call to EVENT_INIT_CONNECT. Run your test script afterwards to check it all works OK.

Py Source Code (against 1262)
# on last call if input['event']['flags'] & F_LAST_CALL:
# check we've an input called "drive"
index = input['iif']['default']['index']
if not 'drive' in index:
output['error'] = 'expected an input called "drive"'
return (persist, output)
 
# store a reference to it
persist['input'] = input['iif']['default']['ports'][index['drive']]

Once this is working, you might try temporarily changing the test script to call the input something else (e.g. neuron<other), and you'll see what happens if the correct input isn't presented.

Next, we should validate that the input is of the expected BRAHMS class, std/data/numeric...

Py Source Code (against 1262)
# store a reference to it ...
# validate class of input
if not (persist['input']['class'] == 'std/2009/data/numeric'):
output['error'] = 'input "drive" should be std/data/numeric class'
return (persist, output)

Validating input Structure

Often, a process will handle inputs with many different structures (number of dimensions, dimensions themselves, even different Numeric Types). Our process, however, is going to insist on a simple real, scalar, DOUBLE-type input as the current drive to its cell membrane. Modify your source file as shown.

Py Source Code (against 1262)
# validate class of input ...
# validate structure of input
if not (persist['input']['structure'] == 'DOUBLE/REAL/1'):
output['error'] = 'input "drive" should be real scalar double'
return (persist, output)

Reading input

OK, so we've validated that (a) we got an input called "drive", (b) it was a std/data/numeric input, and (c) it had the expected structure (real scalar double, in this case). Let's now use the content of the input to drive our membrane. Like writing output, we read input on each call to EVENT_RUN_SERVICE, and we have to resolve the port to a data object to read it. Modify your source file as follows, and run the test script.

Py Source Code (against 1262)
# run dynamics ... persist['currentMembranePotential'] = persist['currentMembranePotential'] * lambd
# use the input data to drive the membrane
persist['currentMembranePotential'] += persist['input']['data'] / fS

With the input driving the membrane, you should get a plot that looks something like this.

Whoops. We're not driving this neuron very hard, I don't think (though, notice it's running a little hotter than before)...

New dynamics

Let's finish the job on the dynamics, by detecting threshold-crossing (membrane > 1.0), resetting the membrane, and generating a spike on the second output channel. Modify your source code as follows.

Py Source Code (against 1262)
# use the input data to drive the membrane persist['currentMembranePotential'] += persist['input']['data'] / fS
# handle threshold crossing
spike = 0.0
if persist['currentMembranePotential'] > 1.0:
persist['currentMembranePotential'] = 0.0
spike = 1.0
# write output brahms.operation(persist['self'], OPERATION_SET_CONTENT, \ persist['hOutput'], \
numpy.array([persist['currentMembranePotential'], spike], numpy.float64))

Let's also make a modification to the parameters in our test script so that the behaviour is a bit more interesting.

M Source Code (against 995)
% add source state = [];
state.data = 25;
state.repeat = true; ...

And let's update our plot commands so we can see the spiking output as well as the membrane potential.

M Source Code (against 995)
% show output figure(1) t = ((1:length(out.neuron.out))-1)/fS;
clf
hold on
plot(t, out.neuron.out(1, :))
plot(t, out.neuron.out(2, :) + 1.2, 'r')
axis([0 exe.stop -0.5 2.5])
xlabel('time (seconds)'); ylabel('membrane potential (unitless)')

Ta-da! Our neuron is doing its thing.

Now - try changing the sample rate of the execution. You should find that sample rates all the way down to 25Hz still result in oscillatory behaviour, and, of course, you can raise the sample rate as high as you like and just increase the accuracy of the integration, though you'll notice things starting to slow down a bit if you go much above 100kHz.

More than one input

Finally, we're going to add a second input, just to illustrate how it's done. This neurons a bit unrealistic at the moment, so let's add some current injection noise. There's a random number generator in the Standard Library, at std/random/numeric, so we'll add an instance of that to the system, parametrize it appropriately, and link it to our neuron.

M Source Code (against 995)
... % link source to our process sys = sys.link('source>out', 'neuron<drive');
% add RNG
state = [];
state.dims = 1;
state.dist = 'normal';
state.pars = [0 50];
state.complex = false;
seed = 42;
sys = sys.addprocess('rng', cls, fS, state, seed);
 
% link RNG to our process
sys = sys.link('rng>out', 'neuron<noise');
% execution exe = brahms_execution; ...

Next, we'll have to modify our source file to validate this input, read it, and use it as an additional input to the membrane.

Py Source Code (against 1262)
# validate structure of input if not (persist['input']['structure'] == 'DOUBLE/REAL/1'): output['error'] = 'input "drive" should be real scalar double' return (persist, output)
# validate input "noise"
index = input['iif']['default']['index']
if not 'noise' in index:
output['error'] = 'expected an input called "noise"'
return (persist, output)
persist['noise'] = input['iif']['default']['ports'][index['noise']]
if not (persist['noise']['class'] == 'std/2009/data/numeric'):
output['error'] = 'input "noise" should be std/data/numeric class'
return (persist, output)
if not (persist['noise']['structure'] == 'DOUBLE/REAL/1'):
output['error'] = 'input "noise" should be real scalar double'
return (persist, output)
Py Source Code (against 1262)
# use the input data to drive the membrane
persist['currentMembranePotential'] += persist['input']['data'] / fS \
+ persist['noise']['data'] / fS

Nice. We've a realistic (ahem...) neuron, taking two inputs (current drive and membrane noise), maintaining internal dynamics, and outputting a two-element output for other Processes, or the user, to view.

Notes
  • We've used the Standard Library RNG, and hard-coded the seed, so your plot should actually look identical to the one displayed here.
  • In addition, it should look identical regardless of which language you have authored in. Perhaps unsurprisingly, but hey.

Where do I go from here?

You should note that it is not necessary to name inputs. You can, rather, review what inputs you were passed by index instead. In addition, you can offer input Sets, if required, but this is not something you should adopt unless there is a real need - most processes don't need Sets. See Using Sets under Advanced Topics.

To find out more about how you can interact with your inputs, review the documentation for SystemMLInterface.