NeuroML exporter¶
Overview
The main work of the exporter is done in the lemsexport
module.
It consists of two main classes:
NMLExporter
- responsible for building the NeuroML2/LEMS model.LEMSDevice
- responsible for code generation. It gathers all variables needed to describe the model and callsNMLExporter
with well-prepared parameters.
NMLExporter¶
The whole process of building NeuroML model starts with calling the
create_lems_model
method. It selects crucial Brian 2 objects to further
parse and pass them to respective methods.
if network is None:
net = Network(collect(level=1))
else:
net = network
if not constants_file:
self._model.add(lems.Include(LEMS_CONSTANTS_XML))
else:
self._model.add(lems.Include(constants_file))
includes = set(includes)
for incl in INCLUDES:
includes.add(incl)
neuron_groups = [o for o in net.objects if type(o) is NeuronGroup]
state_monitors = [o for o in net.objects if type(o) is StateMonitor]
spike_monitors = [o for o in net.objects if type(o) is SpikeMonitor]
for o in net.objects:
if type(o) not in [NeuronGroup, StateMonitor, SpikeMonitor,
Thresholder, Resetter, StateUpdater]:
logger.warn("""{} export functionality
is not implemented yet.""".format(type(o).__name__))
# Thresholder, Resetter, StateUpdater are not interesting from our perspective
if len(netinputs)>0:
includes.add(LEMS_INPUTS)
for incl in includes:
self.add_include(incl)
# First step is to add individual neuron deifinitions and initialize
# them by MultiInstantiate
for e, obj in enumerate(neuron_groups):
self.add_neurongroup(obj, e, namespace, initializers)
Neuron Group¶
A method add_neurongroup
requires more attention. This is the method
responsible for building cell model in LEMS (as so-called ComponentType
)
and initializing it when necessary.
In order to build a whole network of cells with different initial values,
we need to use the MultiInstantiate
LEMS tag. A method make_multiinstantiate
does this job. It iterates over all parameters and analyses equation
to find those with iterator variable i
. Such variables are initialized
in a MultiInstantiate
loop at the beginning of a simulation.
More details about the methods described above can be found in the code comments.
DOM structure¶
Until this point the whole model is stored in NMLExporter._model
, because
the method add_neurongroup
takes advantage of pylems
module to create
a XML structure. After that we export it to self._dommodel
and rather
use NeuroML2 specific content. To extend it one may use
self._extend_dommodel()
method, giving as parameter a proper DOM structure
(built for instance using python xml.dom.minidom
).
# DOM structure of the whole model is constructed below
self._dommodel = self._model.export_to_dom()
# input support - currently only Poisson Inputs
for e, obj in enumerate(netinputs):
self.add_input(obj, counter=e)
# A population should be created in *make_multiinstantiate*
# so we can add it to our DOM structure.
if self._population:
self._extend_dommodel(self._population)
# if some State or Spike Monitors occur we support them by
# Simulation tag
self._model_namespace['simulname'] = "sim1"
self._simulation = NeuroMLSimulation(self._model_namespace['simulname'],
self._model_namespace['networkname'])
for e, obj in enumerate(state_monitors):
self.add_statemonitor(obj, filename=recordingsname, outputfile=True)
for e, obj in enumerate(spike_monitors):
self.add_spikemonitor(obj, filename=recordingsname)
Some of the NeuroML structures are already implemented in supporting.py
. For example:
NeuroMLSimulation
- describes Simulation, adds plot and lines, adds outputfiles for spikes and voltage recordings;NeuroMLSimpleNetwork
- creates a network of cells given some ComponentType;NeuroMLTarget
- picks target for simulation runner.
At the end of the model parsing, a simulation tag is built and added with a target pointing to it.
simulation = self._simulation.build()
self._extend_dommodel(simulation)
target = NeuroMLTarget(self._model_namespace['simulname'])
target = target.build()
self._extend_dommodel(target)
You may access the final DOM structure by accessing the model`
property or
export it to a XML file by calling the export_to_file()
method of the
NMLExporter
object.
Model namespace¶
In many places of the code a dictionary self._model_namespace
is used.
As LEMS used identifiers id
to name almost all of its components, we
want to be consistent in naming them. The dictionary stores names of
model’s components and allows to refer it later in the code.
LEMSDevice¶
LEMSDevice
allows you to take advantage of Brian 2’s code generation mechanism.
It makes usage of the module easier, as it means for user that they just
need to import brian2tools.nmlexport
and set the device
neuroml2
like this:
import brian2lems.nmlexport
set_device('neuroml2', filename="ifcgmtest.xml")
In the class init a flag self.build_on_run
was set to True
which
means that exporter starts working immediately after encountering the run
statement.
def __init__(self):
super(LEMSDevice, self).__init__()
self.runs = []
self.assignments = []
self.build_on_run = True
self.build_options = None
self.has_been_run = False
First of all method network_run
is called which gathers of necessary
variables from the script or function namespaces and passes it to build
method. In build
we select all needed variables to separate dictionaries,
create a name of the recording files and eventually build the exporter.
initializers = {}
for descriptions, duration, namespace, assignments in self.runs:
for assignment in assignments:
if not assignment[2] in initializers:
initializers[assignment[2]] = assignment[-1]
if len(self.runs) > 1:
raise NotImplementedError("Currently only single run is supported.")
if len(filename.split("."))!=1:
filename_ = 'recording_' + filename.split(".")[0]
else:
filename_ = 'recording_' + filename
exporter = NMLExporter()
exporter.create_lems_model(self.network, namespace=namespace,
initializers=initializers,
recordingsname=filename_)
exporter.export_to_file(filename)
LEMS Unit Constants¶
Last lines of the method are saving LemsConstantUnit.xml
file
alongside with our model file. This is due to the fact that in some places
of mathematical expressions LEMS requires unitless variables, e.g. instead of
1 mm
it wants 0.001
. So we store most popular units transformed to
constants in a separate file which is included in the model file header.
if lems_const_save:
with open(os.path.join(nmlcdpath, LEMS_CONSTANTS_XML), 'r') as f:
with open(LEMS_CONSTANTS_XML, 'w') as fout:
fout.write(f.read())
Other modules¶
If you want to know more about other scripts included in package
( lemsrendering
, supporting
,
cgmhelper
), please read their docstrings or comments
included in the code.
TODO¶
- synapses support;
First attempt to make synapses export work was made during GSOC period. The problem with that feature is related to the fact that NeuroML and brian2 internal synapses implementation differs substantially. For instance, in NeuroML there are no predefined rules for connections, but user needs to explicitly define a synapse. Moreover, in Brian 2, for efficiency reasons, postsynaptic potentials are normally modeled in the post-synaptic cell (for linearly summating synapses, this is equivalent but much more efficient), whereas in NeuroML they are modeled as part of the synapse (simulation speed is not an issue here).
- network input support;
Although there are some classes supporting PoissonInput
in the supporting.py
, full functionality
of input is still not provided, as it is stongly linked with above synapses problems.