This page was generated from docs/extension_io.ipynb. Interactive online version: Colab badge Deepnote badge. Binder badge Download notebook.

Extended input-output optionsยถ

Neural structures are fascinating and very interesting to observe even in isolation, but they are most useful when there is a way to externally influence them and observe the resulting differences in behaviour. To cover this need, NeuroML offers an extensive set of <input>s that can be modelled and applied on a <network>, and the <OutputFile> specification format to record and observe the simulated quantities.

However, the scientifically interesting set of input sources can often exceed what NeuroML can model through its dynamical equations (for example, experimental captures of real phenomena), and the <OutputFile> specification is also impractical for large-scale recording and real-time processing. Furthermore, experimental setups may include some sort of external environment (be it another brain region, organ or the organismโ€™s surroundings) that the simulated model interacts with. There are cases where the full system and interaction can be captured inside a NeuroML model (see the Pong example) but again all kinds of interaction cannot be expressed within NeuroML plus LEMS.

This article introduces EDENโ€™s extended capabilities for getting data in and out of the simulation, beyond what NeuroML already offers.

The descriptions and usage examples for each of EDENโ€™s extended input/output capabilities follows:

General conceptsยถ

EDEN receives information from and provides information to other parts of the computer itโ€™s running on, in the form of streams. These streams are sequences of bits of information (of possibly unknown length), that flow in strict (simulation) time order. Different types of transmitted information may always be cast to streams (for example, independent packets can be serialised into time series, with information imputed for the lost packets as appropriate). This is done with auxiliary programming thatโ€™s specific to each experimental apparatus, and is thus not part of EDEN but part of the experiment.

These streams of information within EDEN may take one of two forms: they can be either:

  • Time series, which are observed as quantities changing values over time (for example, an electrical potential or dopamine eligibility trace); or

  • Event series which are instances of an event that occur at specific points in time (for example, action potentials from outside the simulated network, other instantaneous signals).

EDENโ€™s extended I/O tags commonly use two attributes, href and format to specify where (in computer access space) and how (in form of expression) to exchange data. Due to the technologies involved, these two parameters are not entirely independent. The values of these attributes are as explained below:

Data URLโ€™sยถ

The URI scheme (the <scheme>:// part of a URL) determines how an information resource should be found and accessed (common examples are http, tel and zoomโ„ข). The rest of the URL that follows is decoded and retrieved following that scheme, so we have to specify it first.

The available options for source/destination URIโ€™s and their meanings are:

  • no scheme: A regular file, with a path relative to (i.e. starts from the same point as) the file the URL is written on. The meaning is the same as the fileName in classic NeuroML.

  • file://: A regular file, with a path relative to the current working directory (e.g. the notebook thatโ€™s being used, the folder open when clicking on a program, or what the command line starts with, try os.getcwd() if in doubt).

The URI schemes tcp://, pipe://(named pipes for Windows), unix://(Unix domain pipes), and mpi:// are planned to be supported soon.

Data formatยถ

The options for the format of data transmitted to/from the specified URL are:

  • ascii_v0: The first text-based format type, that closely resembles the de facto NeuroML output format. Itโ€™s human-readable text files with each sample of the recording on a separate line, that (typically) starts with the simulated time that the sample refers to.

More data formats may be introduced as needed.


Extended outputยถ

Time series with EdenOutputFileยถ

In NeuroML, the evolution over time of quantities in the simulated <network> can be saved to files and retrieved thanks to <OutputFile>s in the <Simulation>. Each <OutputFile> writes down a time series of one or more physical quantitys specified by the <OutputColumn>s, throughout the simulation.

A problem that arises is that <OutputFile> records the quantitiesโ€™ values for every single timestep of the simulation, which very quickly makes it impractical to record many time series over a long simulation duration - even if only few of the points in time that are recorded are actually needed. Another minor issue (which nonetheless adds unnecessary friction) is that quantities are always recorded in SI units.

To address these limitations, EDEN offers an alternative form of <OutputFile> fittingly called <EdenOutputFile>. Along with the general href and format mentioned above, this tag also accepts more parameters to get a nicer looking output.

When using this tag, each <OutputColumn> that records a dimensioned quantity must also specify the output_units to record the quantity in. This helps with ambiguity and can also save some unit conversion when the experimental pipeline uses non-SI units already.

More importantly, one can specify when to record using this tag. There are two ways to specify the sampling points in time:

  • either periodically over an interval,

  • or at some explicitly specified points in time.

The first way involves specifying the following attributes on the EdenOutputfile tag:

  • starting_from (default: \(0\)): The point in (simulation) time to start recording from.

  • up_to_excluding (default: \(\infty\) ): The point in time when recording ends.

  • sampling_interval (default: simulation step) The sampling period (interval between samples).

As an alternative to specifying a recording interval and sampling period (or going ahead with the default values), one may instead add the child tag <SamplePoints>. This tag must contain one or more tags in the form <s t="time to record">, and the time values must be in strictly increasing order.

Example: Reducing the size of simulation outputยถ

Letโ€™s take the first single-neuron model from Introduction and record it as usual:


%%writefile SingleCell_Model.nml
<neuroml>
<iafCell id="MyFirstCellType" C="200 pF" leakConductance="10 nS" leakReversal="-70 mV" reset="-70mV" thresh="-50mV" />
<pulseGenerator id="MyFirstInputSource" delay="100ms" duration="500ms" amplitude="0.21nA"/>
<network id="MyNetwork" type="networkWithTemperature" temperature="37degC" >
    <population id="Pop" component="MyFirstCellType" size="1"/>
    <inputList id="MyFirstInputList" population="Pop" component="MyFirstInputSource">
        <input id="0" target="Pop[0]" destination="synapses"/>
    </inputList>
</network>
</neuroml>
Writing SingleCell_Model.nml

%%writefile LEMS_RecordLots_Sim.xml
<?xml version="1.0" encoding ="UTF-8" ?>
<Lems>
<include file="SingleCell_Model.nml" />
<Simulation id="MySim" length="1 s" step="10 us" target="MyNetwork" >
    <OutputFile id="MyFirstOutputFile" fileName="results_more.gen.txt">
        <OutputColumn id="my_first_vm" quantity="Pop[0]/v"/>
    </OutputFile>
</Simulation>
<Target component="MySim"/>
</Lems>
Writing LEMS_RecordLots_Sim.xml

from eden_simulator import runEden; from matplotlib import pyplot as plt
def plot_single_cell(results, units='V'):
    plt.figure(figsize=(8,4))
    plt.plot(results['t'],results['Pop[0]/v'])
    plt.xlabel('Time (sec)'); plt.ylabel('Membrane voltage');

results = runEden('LEMS_RecordLots_Sim.xml')
plot_single_cell(results)
_images/extension_io_4_0.png

Now, letโ€™s use <EdenOutputFile> to record on just every millisecond:


%%writefile LEMS_RecordLess_Sim.xml
<?xml version="1.0" encoding ="UTF-8" ?>
<Lems>
<include file="SingleCell_Model.nml" />
<Simulation id="MySim" length="1 s" step="10 us" target="MyNetwork" >
    <!-- Add an EdenOutputFile โ— -->
    <EdenOutputFile id="MyOutputFile" href="results_less.gen.txt" format="ascii_v0" sampling_interval="1 msec">
        <OutputColumn id="my_first_vm" quantity="Pop[0]/v" output_units="mV"/> <!-- With units โ— -->
    </EdenOutputFile>
</Simulation>
<Target component="MySim"/>
</Lems>
Writing LEMS_RecordLess_Sim.xml

results = runEden('LEMS_RecordLess_Sim.xml')
plot_single_cell(results, units='mV')
_images/extension_io_7_0.png

And compare the two recordings in size:


import os; from si_prefix import si_format # or humanize or whatever
for x in [ 'results_more.gen.txt', 'results_less.gen.txt' ]:
    print(f'{x}: {si_format(os.path.getsize(x))}bytes')
results_more.gen.txt: 3.4 Mbytes
results_less.gen.txt: 34.0 kbytes

And naturally, this difference is multiplied by the number of quantities that are being recorded.

Example: Recording a specific range in timeยถ

Another use case is when someone is not that interested in the whole course of the simulation, but rather on the behaviour that the network converges to (typically near the end of the simulation). In that case, the time range to record over can be specified with starting_from and up_to_excluding.

Note: This is not recommended to do while the model is still being developed. When one is still not sure about how the network starts off, there is a concern it might have settled to a different (and possibly unrealistic!) state than whatโ€™s expected. But when thereโ€™s no such concern any more and only the final phase needs to be recorded, it can also help save space further.

Hereโ€™s how to record a specific range then, on the above model:


%%writefile LEMS_RecordRange_Sim.xml
<?xml version="1.0" encoding ="UTF-8" ?>
<Lems> <include file="SingleCell_Model.nml" />
<Simulation id="MySim" length="1 s" step="10 us" target="MyNetwork" >
    <!-- Add an EdenOutputFile with a specific range โ— -->
    <EdenOutputFile id="MyOutputFile" href="results_less.gen.txt" format="ascii_v0"
       starting_from="0.5 s" up_to_excluding="0.6 sec"  sampling_interval="1 msec">
        <OutputColumn id="my_first_vm" quantity="Pop[0]/v" output_units="mV"/>
    </EdenOutputFile>
</Simulation>
<Target component="MySim"/> </Lems>
Writing LEMS_RecordRange_Sim.xml

results = runEden('LEMS_RecordRange_Sim.xml')
plot_single_cell(results, units='mV')
_images/extension_io_13_0.png

Event series with EdenEventOutputFileยถ

An alternative that NeuroML offers to recording the trajectories of quantities with <OutputFile> is to record discrete events that occur (in practice thatโ€™s basically spikes from neurons, to capture the activity raster). This is done with <EventOutputfile> tags in the <Simulation>. Each <EventOutputFile> writes down a sequence of when and which spikes occurred, so that each occurrence appears as a text line with the time reference and spike source number. Different spike sources are listed by <EventSelection>s inside the tag, and identified through their numerical idโ€™s.

Although clear and concise, this format has some limitations when the outputs are not just ordinary files, but actually continuous streams that get fed to a post-processing pipeline (and possibly fed back to the simulation through EDENโ€™s input extensions):

  • When reading a stream line by line, it is not clear whether this is the last spike to occur on the timebase, or more spikes on the same timebase follow. If the stream recipient waits to receive more information, they may have to wait until whenever the next spike appears, and if it doesnโ€™t wait and proceeds with processing up to the time base, it may miss more events that happened on the same timestep.

  • In the same way, it is not known when the next event follows, and there can be any distance between events. This can be a problem for the spike stream recipient when there are real-time constraints or feedback with the simulator, as post-processing cannot know up to how far it can process the timebase.

To address these limitations, EDEN offers an alternative form of <EventOutputFile> fittingly called <EdenEventOutputFile>. Along with the general href and format mentioned above, this tag also accepts more attributes to control output:

  • starting_from (default: \(0\) ): The point in (simulation) time to start recording from.

  • up_to_excluding (default: \(\infty\) ): The point in time when recording ends.

  • maximum_interval (default: \(\infty\) ): The maximum time interval between lines.

Note that the format parameter is also used in NeuroML, with allowed values ID_TIME or TIME_ID. For this tag, the format can only be ascii_v0 (more similar to TIME_ID).

The ascii_v0 format expresses the event stream as a sequence of lines, with the difference that all spikes that happened on the same timestep are listed on a single line, and extra lines with no events may be inserted when maximum_interval requires it. Each line is therefore in this format:

<simulation time> <whitespace-separated list of zero or more ID's>

Example: Adding โ€œheartbeatsโ€ to an event streamยถ

Letโ€™s take the first single-neuron model from the previous example and record its spikes, but also add a maximum_interval this time:


%%writefile LEMS_RecordSpikes_Sim.xml
<?xml version="1.0" encoding ="UTF-8" ?>
<Lems> <include file="SingleCell_Model.nml" />
<Simulation id="MySim" length="1 s" step="10 us" target="MyNetwork" >
    <!-- Add an EdenOutputFile with a specific range โ— -->
    <EventOutputFile id="SpikesOut" fileName="spikes_out.gen.txt" format="TIME_ID">
        <EventSelection id="0" select="Pop[0]" eventPort="spike"/>
    </EventOutputFile>
    <EdenEventOutputFile id="DeLuxe" href="spikes_out_deluxe.gen.txt" format="ascii_v0"
        maximum_interval="100 ms">
        <EventSelection id="0" select="Pop[0]" eventPort="spike"/>
    </EdenEventOutputFile>
</Simulation>
<Target component="MySim"/> </Lems>
Writing LEMS_RecordSpikes_Sim.xml

runEden('LEMS_RecordSpikes_Sim.xml')
with open('spikes_out.gen.txt') as f: lines_before = list(f.readlines())
with open('spikes_out_deluxe.gen.txt') as f: lines_after = list(f.readlines())
from difflib import HtmlDiff; from IPython.display import display, HTML
mydiff = HtmlDiff(tabsize=4)
display(HTML(mydiff.make_file(lines_before, lines_after, fromdesc='EventOutputFile', todesc='EdenEventOutputFile')))
# see also: !diff --side-by-side spikes_out.gen.txt spikes_out_deluxe.gen.txt

EventOutputFile
EdenEventOutputFile
nn1ย ย ย ย ย ย ย ย ย ย ย ย ย ย +0
2ย ย ย ย ย ย ย ย ย ย ย ย +0.1
1ย ย ย ย ย ย ย ย +0.16089ย 03ย ย ย ย ย ย ย ย +0.16089ย 0
2ย ย ย ย ย ย ย ย +0.22178ย 04ย ย ย ย ย ย ย ย +0.22178ย 0
3ย ย ย ย ย ย ย ย +0.28267ย 05ย ย ย ย ย ย ย ย +0.28267ย 0
4ย ย ย ย ย ย ย ย +0.34356ย 06ย ย ย ย ย ย ย ย +0.34356ย 0
5ย ย ย ย ย ย ย ย +0.40445ย 07ย ย ย ย ย ย ย ย +0.40445ย 0
6ย ย ย ย ย ย ย ย +0.46534ย 08ย ย ย ย ย ย ย ย +0.46534ย 0
7ย ย ย ย ย ย ย ย +0.52623ย 09ย ย ย ย ย ย ย ย +0.52623ย 0
8ย ย ย ย ย ย ย ย +0.58712ย 010ย ย ย ย ย ย ย ย +0.58712ย 0
tt11ย ย ย ย ย ย ย ย +0.68712
12ย ย ย ย ย ย ย ย +0.78712
13ย ย ย ย ย ย ย ย +0.88712
14ย ย ย ย ย ย ย ย +0.98712
Legends
Colors
ย Addedย 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

These timestamp-only lines may look superfluous, but they may be necessary to go forward in processing pipelines as explained above. Arguably, one could also add a regular <๐šœ๐š™๐š’๐š”๐šŽ๐™ถ๐šŽ๐š—๐šŽ๐š›๐šŠ๐š๐š˜๐š›> to that effect. But only EdenEventOutputFile can pack events the by timestep, which is also very important.

Exercise: Play with the starting_from and up_to_excluding attributes and see their effect.

Exercise: Set up a sizeable network thatโ€™s buzzing with activity and watch how each EventOutputFile handles spikes on the same timestep.

Extended inputยถ

EDEN is able to provide a simulation with information streams coming from outside it. This greatly extends the modelling possibilities, since:

  • input signals can be created by any method and data pipeline, beyond what can be expressed with differential equations;

  • the SNN model can be plugged into a closed loop experiment, and receive feedback from processes working in a completely different way.

The external input streams to add to a simulation are specified through the <EdenTimeSeriesReader> and <EdenEventSetReader> tags. Unlike the OutputWriters, these are located inside the <network> since they effectively are part of it alongside the neurons and synapses.

The form that the information streams take is similar to that of point neurons, in that they can have one or more scalar Exposures and <OutputPort>s. This is intentional, as they are meant to interact with parts of the model as if they are regular (albeit read-only) objects of the model.

Time series with EdenTimeSeriesReaderยถ

A <EdenTimeSeriesReader> is a source of one or more time-evolving quantities, whose values are retrieved from a URL. These quantities are presented to the model as a collection of elements, where each element is uniformly structured as a collection of named scalars. One can think of elements as pseudo-point-neurons, and of their named scalars as LEMS <Exposure>s.

Along with the general href and format mentioned above, this tag also has the attribute instances which represents the number of identical elements that make up the <EdenTimeSeriesReader>. Inside the tag, <InputColumn> tags list the different named scalars, with attributes:

  • id: The name of the scalar quantity.

  • dimension: The dimensionality of the quantity, none for dimensionless as usual for LEMS.

  • units (if dimensional): The units for the numerical values of the quantity.

Since input streams are an extension to NeuroML (and they offer many more ways of interaction than, say, <gapJunction>s allow), they cannot be interacted with through core NeuroML. Instead, mechanisms can rely on these quantities to adjust their behaviour through <๐š…๐šŠ๐š›๐š’๐šŠ๐š‹๐š•๐šŽ๐š๐šŽ๐šš๐šž๐š’๐š›๐šŽ๐š–๐šŽ๐š—๐š>s, which is another extension that EDEN adds to NeuroML. What this means is that if a mechanismโ€™s dynamics depend on an external quantity, then this quantity is specified as a <๐š…๐šŠ๐š›๐š’๐šŠ๐š‹๐š•๐šŽ๐š๐šŽ๐šš๐šž๐š’๐š›๐šŽ๐š–๐šŽ๐š—๐š> (instead of a local <Requirement>) within the LEMS-specified mechanism.

The ascii_v0 data format to supply <EdenTimeSeriesReader>s with is similar to what <OutputWriter> already generates:

  • There is one line for each sample point in time, followed by all the mentioned time series. Sample points are laid out in strict ascending order. A difference is that the time points for samples donโ€™t have to be regularly spaced (just like with <EdenOutputFile>โ€™s SamplePoints): between sample points, the quantities will retain the values given from the last sample (they will be piecewise constant over time).

  • The quantity values for each element are laid along the line in element order. When there is more than one <InputColumn> per element, the new value for each of its named scalars is laid out in the order the InputColumns were declared. Hence there are elements * scalars quantity values per line (plus the timestamp at the start of the line) and the value order is element-major, scalar-minor.

Example: An arbitrary-pulse current probeยถ

The simplest (and popular) example to demonstrate the power of <EdenTimeSeriesReader> with is a current probe of a user-specified waveform.
First, letโ€™s see how to set up stuff model-wise:

%%writefile ReadTimeSeries.nml
<neuroml>
<!-- A simple yet versatile current source โ— -->
<ComponentType name="RefCurrentSource" extends="basePointCurrent"
    description="Generates instantaneous current sourced from a VariableReference.">
    <!-- Here comes the VariableRequirement โ— -->
    <VariableRequirement name="iPointed" dimension="current" />
    <Dynamics>
        <DerivedVariable name="i" dimension="current" exposure="i" value="iPointed"/>
    </Dynamics>
</ComponentType>

<iafCell id="MyFirstCellType" C="200 pF" leakConductance="10 nS" leakReversal="-70 mV" reset="-70mV" thresh="-50mV" />
<RefCurrentSource id="MyRefInputSource"/> <!-- We'll have to set up a VariableRequirement for all instances โ— -->

<network id="MyNetwork" type="networkWithTemperature" temperature="37degC" >
    <population id="Pop" component="MyFirstCellType" size="1"/>
    <inputList id="MyInputList" population="Pop" component="MyRefInputSource">
        <input id="0" target="Pop[0]" destination="synapses"/>
    </inputList>

    <!-- Add an input stream โ— -->
    <EdenTimeSeriesReader id="MyFirstInputStream"  href="file://timeseries.gen.txt" format="ascii_v0" instances="1" >
        <InputColumn id="i" dimension="current" units="nA"/>
    </EdenTimeSeriesReader>
</network>

<Simulation id="MySim" length="1 s" step="10 us" target="MyNetwork" >
    <EdenOutputFile id="MyOutputFile" href="results_less.gen.txt" format="ascii_v0" sampling_interval="1 msec">
        <OutputColumn id="my_first_vm" quantity="Pop[0]/v" output_units="mV"/>
    </EdenOutputFile>

    <!-- Add EdenCustomSetup to set up the VariableReferences โ— -->
    <EdenCustomSetup filename="ReadTimeSeries_CustomSetup.gen.txt"/>
</Simulation>
<Target component="MySim"/>
</neuroml>
Writing ReadTimeSeries.nml

%%writefile ReadTimeSeries_CustomSetup.gen.txt
set input MyInputList all iPointed MyFirstInputStream[0]/i # add more inputs and see what happens :)
Writing ReadTimeSeries_CustomSetup.gen.txt

Then, cook up a signal to pass to the simulation and save it in the ascii_v0 format:


import numpy as np;
N_samples = 1000; signal_dt_sec = 0.001;
times = np.array(range(N_samples)) * signal_dt_sec
# and then add stuff
signal = 0*times
signal[150:526] = 0.3 * np.sin( (times[150:526]- times[150]) / 0.04 )
signal[650:850] = 0.5 * ( (times[650:850]- times[650]) / 0.2 ) ** 2
# and write it down
with open('timeseries.gen.txt', 'wt') as f:
    for (t,i) in zip(times,signal):
        f.write(f'{t} {i}\n')

And run it all together:


results = runEden('ReadTimeSeries.nml')
def halfaxes(ax, color, label):
    ax.set_ylabel(label, color=color)
    ax.tick_params(axis='y', labelcolor=color)
plot_single_cell(results); halfaxes(plt.gca(), 'tab:blue', 'Membrane voltage (mV)')
ax2 = plt.gca().twinx()
ax2.plot(times, signal, color='tab:orange'); halfaxes(ax2, color='tab:orange', label='Current (nA)')
_images/extension_io_24_0.png

Exercise: Try writing a (short) timeseries.gen.txt by hand, or set up your favourite waveform and see what happens to the cell.

Event series with EdenEventSetReaderยถ

An <EdenEventSetReader> is a set of one or more spike sources, whose occurrences are retrieved from a URL. The spike sources are presented to the model as a collection of elements, where each element is uniformly structured as a collection of named ports. One can think of elements as pseudo-point-neurons, and of their named ports as LEMS <EventPort>s.

Along with the general href and format mentioned above, this tag also has the attribute instances which represents the number of identical elements that make up the <EdenTimeSeriesReader>. Inside the tag, <Port> tags list the different named ports, with no specific attributes.

Even though they are an extension to NeuroML, since the elements resemble neurons with one or more spike emitting sites, they can appear to the model as pseudo-neuron populations (just like ordinary spike sources are) and be directly used in <projection>s as pre-synaptic sites!

  • When connecting them to post-synapses on real neurons with a <projection>, the interpretation of the usual <connection> parameters preCellId and preSegment changes:

    • preCellId will stand for the numbered element of the EventSet;

    • preSegment will stand for the named port of said element. If unspecified when there are multiple ports, itโ€™s assumed to be the typical port spike;

    • preFractionAlong is not applicable, as is the case with point neurons.

  • Of course there may be no synaptic component on their side; hence they can only be the presynapticPopulation in a simple, AP based <projection>.

The ascii_v0 data format to supply <EdenEventSetReader>s with is similar to what <EdenEventOutputFile> already generates:

  • There is one line for each distinct sample point in time, followed by all the event identifiers that occur on it. Sample points are laid out in strict ascending order.

  • A difference with <EdenEventOutputFile> is how the multiple ports of elements are identified, when present: instead of a simple integer (that identifies the elements, when each has only one port) there are two integers separated by a ! character.
    In this case:
    • the first number is that of the element in the event set;

    • the second number is that of the event <Port>, in the order they were listed;

    • both are needed to determine which port of which element emitted an event.

    Do note, however, that <(Eden)EventOutputFile> with its simple list of <EventSelection>s canโ€™t write streams in this โ€œmulti-portโ€ format.

Example: An arbitrary-train multi-port spike sourceยถ

A simple example to show how <EdenEventSetReader> works is with a multi-port event stream that projects to different neurons.

First, letโ€™s see how to set up stuff model-wise:


%%writefile ReadEventSet.nml
<neuroml>
<iafCell id="MyFirstCellType" C="200 pF" leakConductance="10 nS" leakReversal="-70 mV" reset="-70mV" thresh="-50mV" />
<expOneSynapse id="MySyn" gbase="11nS" erev="0V" tauDecay="5ms"/>
<network id="MyNetwork" type="networkWithTemperature" temperature="37degC" >

    <!-- Add a population with two cells -->
    <population id="Pop" component="MyFirstCellType" size="2"/>

    <!-- Add an event stream โ— -->
    <EdenEventSetReader id="MyFirstEventStream" href="eventset.gen.txt" format="ascii_v0" instances="2">
        <Port id="zero"/>
        <Port id="one"/>
    </EdenEventSetReader>

    <!-- Apply a synaptic projection from the event stream to the cells โ— -->
    <projection id="MyFirstStreamProjection" presynapticPopulation="MyFirstEventStream" postsynapticPopulation="Pop" synapse="MySyn">
        <connectionWD id="0" preCellId="0" preSegmentId="zero" postCellId="0" weight="1" delay="10 ms"/>
        <connectionWD id="1" preCellId="0" preSegmentId="one"  postCellId="1" weight="1" delay="10 ms"/>
        <connectionWD id="2" preCellId="1" preSegmentId="zero" postCellId="1" weight="1" delay="10 ms"/>
        <connectionWD id="3" preCellId="1" preSegmentId="one"  postCellId="0" weight="1" delay="10 ms"/>
    </projection>

</network>
<Simulation id="MySim" length="1 s" step="100 us" target="MyNetwork" >
    <OutputFile id="MyFirstOutputFile" fileName="results.gen.txt">
        <OutputColumn id="my_first_vm"  quantity="Pop[0]/v"/>
        <OutputColumn id="my_second_vm" quantity="Pop[1]/v"/>
    </OutputFile>
</Simulation>
<Target component="MySim"/>
</neuroml>
Writing ReadEventSet.nml

And then write up some โ€œeventsโ€:


%%writefile eventset.gen.txt
0.1 0!0
0.3 0!1 1!0 1!1
0.4 1!0 1!1
0.6 0!1
Writing eventset.gen.txt

And run the model plus injected spikes:


results = runEden('ReadEventSet.nml')
plt.figure(figsize=(8,4))
plt.plot(results['t'],results['Pop[0]/v'], label='My first cell')
plt.plot(results['t'],results['Pop[1]/v'], label='My second cell')
plt.xlabel('Time (sec)'); plt.ylabel('Membrane voltage (V)'); plt.legend();
_images/extension_io_31_0.png

Notice the delay between when the spikes happen and when the synapses are activated, just as <connectionWD> specifies.

Exercise: Repeat the example, with elements of a single port with this time, and hence simple integers as spike identifiers. In that case, the format is the same as what <EdenEventOutputFile> outputs.

In this simple case (of just a few pre-calculated spikes) one could have simply used the classic NeuroML <๐šœ๐š™๐š’๐š”๐šŽ๐™ฐ๐š›๐š›๐šŠ๐šข>s, but this gets unwieldy with population-wide spike trains and long durations. And, of course, this precludes dynamic live-streaming from another part of the experimental rig.

On shorting readers and writersยถ

Note that directly recording data that comes in from readers is not officially supported, because of the instantaneous input-to-output requirement they imply in conjunction with the problematic cases that can occur when running on a MPI cluster if input and output streams are distributed among cluster nodes in arbitrary ways. It is permitted without support, but there may be a delay of one timestep between the information fed to the <Eden...Reader>s and that emitted by an (Event)OutputWriter that samples these input streams.