Specification of an LPU in NeuroDriver
In this hackpad, we will describe how to construct a graph that can be read by the NeuroDriver LPU API. Based on such graph, the LPU API can construct and execute an LPU if all types of components specified in the graph have been implemented (as subclasses of +NDComponent). Note that since NeuroArch is a graph-based database, it is straightforward to generate a graph specification of LPU from NeuroArch database directly.
Currently, we only consider using the MultiDiGraph class in the networkx module for specifying the graph. Additional parsers can be implemented.
To create a new MultiDiGraph
import networkx
G = networkx.MultiDiGraph()
Graph Structure
A graph consists of nodes and edges. Since neurons are connected via synapses, it is a common approach to map neurons to nodes and synapses to edges in a graph. In NeuroDriver LPU specification, however, we consider all components to be nodes, and connection between components to be edges. The following figure illustrate a simple example of this structure.
This design has several advantages:
  1. More types of components can be defined, not restricted to neurons and synapses.
  1. Since neurons, synapses, dendrites and other components may each have a extensive set of parameters, they can be treated more universally as nodes.
  1. Arbitrary connections between components is possible. For example, it is easier to pass some states from multiple components to a synapse using this definition of graph.
Defining Nodes in the Graph
Each component in an LPU should be given a unique identification, and we refer to this as UID. The UID can be specified when adding a node to the graph G.
Here, a new component is added to the LPU specification with UID 'neuron1'. We recommend using a unique string for the UIDs.
We have not yet defined what the component is and its parameters. They can be specified when creating the node,
           {'class': 'LeakyIAF',
            'name': 'OSN',
            'resting_potential': -70.0,
            'threshold': -50.0,
            'capacitance': 0.07,
            'resistance': 200.0
or updated after the node is created
G.node['neuron1'].update({'class': 'LeakyIAF',
                          'name': 'OSN',
                          'resting_potential': -70.0,
                          'threshold': -50.0,
                          'capacitance': 0.07,
                          'resistance': 200.0
Here, the attribute dictionary should provide several information:
  1. 'class': this specifies the subclass to be used for this component.
  1. 'name': the name is not necessary, but can be useful when inspecting a GEXF file generated from the graph.
  1. 'resting_potential', 'threshold', 'capacitance' and 'resistance' are the parameters used in the LeakyIAF implementation. Here, the name of the parameters must exactly be the same as used in the LeakyIAF subclass implementation.
It is also possible to define UID in the attribute dictionary. For example, the dictionary key for UID can be 'a_different_uid_key'. However, when processing the graph to create the LPU, this must be explicitly expressed in the code, by calling
comp_dict, conns = graph_to_dicts(G, uid_key = 'a_different_uid_key')
NeuroDriver API defines a special type of component, called 'Ports', to be the interface with Neurokernel communication API. States transported from other LPUs will be stored in the 'in' ports. States of neurons that projects to other LPUs will be copied to 'out' ports and provided to Neurokernel API.