In order to facilitate generality, we don't require our neuroprocessors to store and maintain specific values in their nodes, edges and networks. For example, a processor might support neurons with leak, or different modalities of STDP on edges. Or it might not.
For this reason, we have defined the Property and PropertyPack classes.
To summarize:
- A
Propertydefines one or more values that may be stored with nodes, edges or networks. - A
PropertyPackis a collection of three maps -- one for nodes, one for edges and one for networks. The maps are keyed by property name, and their values are properties.
Processors define PropertyPacks, and networks store them. Each node, edge and network
contains a vector of doubles called values. This vector matches the approprate
map in the PropertyPack.
If you are writing a Processor, then you'll need to define your PropertyPack.
There are methods in the PropertyPack class to help you do that.
A Property has the following values, which are all defined in its JSON. This description
is to help you read Property and PropertyPack JSON. Don't bother trying to create this
JSON yourself -- the Property and PropertyPack methods will do it for you.
| Name | Type | Value |
|---|---|---|
"name" |
string | The name of the property (e.g. "leak", "threshold" ) |
"type" |
char | 'I' (73) for integer, 'D' (68) for double, 'B' (66) for boolean } |
"min_value" |
double | The minimum value for data (for boolean, this should be 0) |
"max_value" |
double | The maximum value for data (for boolean, this should be 1) |
"size" |
int | The number of data values that compose this property |
| `"index" | int | The index where this starts in the values vector. |
For example, the GNP neuroprocessor can have the following Property defined on its nodes:
{ "name":"Threshold", "type":68, "index":0, "size":1, "min_value":-1.0, "max_value":1.0 }
And the following properties defined on its edges:
{ "name":"Weight", "type":68, "index":0, "size":1, "min_value":0.0, "max_value":1.0 }
{ "name":"Inhibitory", "type":66, "index":1, "size":1, "min_value":0.0, "max_value":1.0 }
{ "name":"Delay", "type":73, "index":2, "size":1, "min_value":0.0, "max_value":4.0 }
This means that the values vector in the nodes contains one value, the Threshold (double).
Edges contain three values -- Weight (double), Inhibitory (boolean) and Delay (integer)
A PropertyPack is a bundling of Properties. A neuroprocessor must define three sets of
Properties:
- Properties for nodes -- these are data values that each node will store.
- Properties for edges -- these are data values that each edge will store.
- Properties for networks -- these are data values that each network will store.
Once again, this is to help you read JSON -- don't bother creating it -- instead use the methods.
The JSON for a PropertyPack has three keys:
"node_properties": The properties for nodes."edge_properties": The properties for edges."network_properties": The properties for networks.
Each of these is an array containing the JSON for the properties. The properties may be specified in any order. However, their indices and sizes must define a contiguous sequence of values. Specifically:
- There must be one property whose
indexis zero. - For each property whose
indexis not zero, there must be a property whoseindex+sizeequals the property'sindex.
For example, here is the default PropertyPack JSON for GNP.
{ "node_properties": [
{ "name":"Threshold", "type":68, "index":0, "size":1, "min_value":-1.0, "max_value":1.0 }],
"edge_properties": [
{ "name":"Delay", "type":73, "index":2, "size":1, "min_value":0.0, "max_value":4.0 },
{ "name":"Inhibitory", "type":66, "index":1, "size":1, "min_value":0.0, "max_value":1.0 },
{ "name":"Weight", "type":68, "index":0, "size":1, "min_value":0.0, "max_value":1.0 }],
"network_properties": [
{ "name":"Enable_Inhibitory_Synapse", "type":73, "index":0, "size":1, "min_value":0.0, "max_value":0.0 }] }
Don't bother with methods from the Property class, except for perhaps as_json() or to_json().
You can access all of the fields of the Property (e.g. type, index) directly. Although
they are not const, you shouldn't modify them. This is also true with the Propery_Pack
fields.
add_node_property(),add_edge_property()andadd_network_property()create properties and add them to the proper map in thePropertyPack. Their arguments are all of the fields in theProperty, with the exception ofindex, because that is calculated automatically. For example, the first property in a map will have anindexof zero. The next one will have anindexequal to the first property'scount
That's all there is to it. There are a few more things:
PropertyPackhas aclear()method, which clears out the three maps.- Both classes have
from_json(),to_json()andas_json()methods, so that you can store and retrieve them using JSON. - Both classes have a
pretty_json()method, which produces a string that is easier to read than using thedump()method of the JSON.
I know that this is a little repetitive, but sometimes repetition is good. If you are exploring or developing within TENNLab, here's when you may want to care about these data structures:
-
You are writing a neuroprocssor. You will have to define the PropertyPack(s) for your neuroprocessor's nodes, edges and networks. This will be in your processor's
get_properties()method, and will be stored with networks trained with the neuroprocessor. -
Anyone who wants to understand what's in a network. Nodes, edges and networks all store a vector of doubles called
values. The definitions of these values are in thePropertyPack. For example, in GNP, each edge has three values in its vector. If that vector is[0.1817,1.0,2.0], then you use thePropertyPackto interpret it:- The edge has a weight of 0.1817. This is because in the
PropertyPack,"Weight"has anindexof 0, so it corresponds to the first value: 0.1817. - The edge is inhibitory (since that is a boolean - one corresponds to true).
- The edge has a delay of 2.
If you are simply exploring networks, then the utility bin/network_tool is a good place to start. It allows you do print out nodes, edges, PropertyPacks, etc on network files. If you are doing something more advanced (for example, writing code to visualize a network), then you'll want to use relevant methods in the
Networkclass, plus of course thePropertyandPropertyPackclasses. - The edge has a weight of 0.1817. This is because in the
-
The learning or training engine (e.g. EONS). Let's take EONS as an example. EONS reads the
PropertyPackso that it knows what parameters it needs to optimize, and what values those parameters can take on.
To compile the property_tool and property_pack_tool, do make utils in the main framework
directory.
The program bin/property_tool lets you test and use all of the methods in the Property class.
It maintains one instance of a Property, which you can manipulate on the command line. It's
pretty bare bones:
For example:
UNIX> bin/property_tool 'PT>' # Its optional command line argument is a prompt.
PT> ?
For commands that take a json either put a filename on the same line,
or the json can be multiple lines, starting on the next line.
FJ json - Read an instance of the property class from json.
TJ [file] - Create JSON from the property.
C n i s t min max - Create an instance from name index size type min max.
STUFF - Test the copy constructor, move constructor, assignment.
? - Print commands.
Q - Quit.
PT> C Fred 4 2 D -14 200 # Create a property from the command line.
PT> TJ # Print its JSON
{ "name":"Fred", "type":68, "index":4, "size":2, "min_value":-14.0, "max_value":200.0 }
PT> FJ # Create a property from JSON
{"index":1,"max_value":1.0,"min_value":0.0,"name":"Inhibitory","size":1,"type":66}
PT> TJ # Print its JSON
{ "name":"Inhibitory", "type":66, "index":1, "size":1, "min_value":0.0, "max_value":1.0 }
PT> TJ tmp.txt # Print its JSON to a file.
PT> STUFF # Test various constructors, etc.
Passed Test 1
Passed Test 2
Test 3 Done (If there's no other output, that means it passed.
PT> Q
UNIX> # You'll note that the JSON in the file is different from the one printed.
UNIX> cat tmp.txt
{"index":1,"max_value":1.0,"min_value":0.0,"name":"Inhibitory","size":1,"type":66}
UNIX>
The reason that the JSON on the screen is different from the JSON printed to the file, is that
the method pretty_json() is used to the screen, whereas to_json.dump() is used for the file.
That way, the file is more efficiently stored, but you can still read it more clearly.
This is analogous to property_tool:
UNIX> bin/property_pack_tool 'PPT>'
PPT> ?
For commands that take a json either put a filename on the same line,
or the json can be multiple lines, starting on the next line.
FJ json - Read a PropertyPack from json.
TJ [file] - Create JSON from the property pack.
C - Clear.
N name type min max cnt - Add a node property (add_node_property).
E name type min max cnt - Add a edge property (add_edge_property).
W name type min max cnt - Add a network property (add_network_property).
? - Print commands.
Q - Quit.
PPT>
PPT> TJ # The program starts with an empty property pack.
{ "node_properties": [],
"edge_properties": [],
"network_properties": [] }
PPT> N threshold D -10 10 1 # Add a node property called "threshold"
Added: index = 0
PPT> TJ
{ "node_properties": [
{ "name":"threshold", "type":68, "index":0, "size":1, "min_value":-10.0, "max_value":10.0 }],
"edge_properties": [],
"network_properties": [] }
PPT> N coordinates D -100 100 3 # Add another node property "coordinates", with three values:
Added: index = 1
PPT> TJ
{ "node_properties": [
{ "name":"coordinates", "type":68, "index":1, "size":3, "min_value":-100.0, "max_value":100.0 },
{ "name":"threshold", "type":68, "index":0, "size":1, "min_value":-10.0, "max_value":10.0 }],
"edge_properties": [],
"network_properties": [] }
PPT> TJ tmp.txt # When we write the JSON to a file, it will look different from the above printout.
PPT> FJ # I did a cut-and-paste from a GNP network file, using bin/network_tool
{ "node_properties": [
{ "name":"Threshold", "type":68, "index":0, "size":1, "min_value":-1.0, "max_value":1.0 }],
"edge_properties": [
{ "name":"Delay", "type":73, "index":2, "size":1, "min_value":0.0, "max_value":4.0 },
{ "name":"Inhibitory", "type":66, "index":1, "size":1, "min_value":0.0, "max_value":1.0 },
{ "name":"Weight", "type":68, "index":0, "size":1, "min_value":0.0, "max_value":1.0 }],
"network_properties": [
{ "name":"Enable_Inhibitory_Synapse", "type":73, "index":0, "size":1, "min_value":0.0, "max_value":0.0 }] }
PPT> TJ
{ "node_properties": [
{ "name":"Threshold", "type":68, "index":0, "size":1, "min_value":-1.0, "max_value":1.0 }],
"edge_properties": [
{ "name":"Delay", "type":73, "index":2, "size":1, "min_value":0.0, "max_value":4.0 },
{ "name":"Inhibitory", "type":66, "index":1, "size":1, "min_value":0.0, "max_value":1.0 },
{ "name":"Weight", "type":68, "index":0, "size":1, "min_value":0.0, "max_value":1.0 }],
"network_properties": [
{ "name":"Enable_Inhibitory_Synapse", "type":73, "index":0, "size":1, "min_value":0.0, "max_value":0.0 }] }
PPT> E Leak_Rate D 0 10 1 # I'm adding an edge property called "Leak_Rate"
Added: index = 3
PPT> TJ
{ "node_properties": [
{ "name":"Threshold", "type":68, "index":0, "size":1, "min_value":-1.0, "max_value":1.0 }],
"edge_properties": [
{ "name":"Delay", "type":73, "index":2, "size":1, "min_value":0.0, "max_value":4.0 },
{ "name":"Inhibitory", "type":66, "index":1, "size":1, "min_value":0.0, "max_value":1.0 },
{ "name":"Leak_Rate", "type":68, "index":3, "size":1, "min_value":0.0, "max_value":10.0 },
{ "name":"Weight", "type":68, "index":0, "size":1, "min_value":0.0, "max_value":1.0 }],
"network_properties": [
{ "name":"Enable_Inhibitory_Synapse", "type":73, "index":0, "size":1, "min_value":0.0, "max_value":0.0 }] }
PPT> Q
UNIX> # Here you see how the file JSON is different.
UNIX> cat tmp.txt
{"edge_properties":[],"network_properties":[],"node_properties":[{"index":1,"max_value":100.0,"min_value":-100.0,"name":"coordinates","size":3,"type":68},{"index":0,"max_value":10.0,"min_value":-10.0,"name":"threshold","size":1,"type":68}]}
UNIX>