diff --git a/.gitignore b/.gitignore index d0a1ba4e..36ad3c27 100644 --- a/.gitignore +++ b/.gitignore @@ -274,6 +274,9 @@ Thumbs.db #thumbnail cache on Windows /examples/PyTorch/simple_pytorch_to_mdf.1 /examples/MDF/NLP/TestNLP_3 /examples/MDF/NLP/TestNLP +/examples/MDF/networks/Net_acyclical +/examples/MDF/net/Net_acyclical +/examples/MDF/net/weight.py /examples/MDF/conditions/composite_example /examples/NeuroML/arm64 /examples/MDF/RNN/iaf.net diff --git a/docs/MDF_specification.json b/docs/MDF_specification.json index 202a92c7..eeaca631 100644 --- a/docs/MDF_specification.json +++ b/docs/MDF_specification.json @@ -106,6 +106,10 @@ "type": "str", "description": "The unique (for this Node) id of the input port," }, + "default_value": { + "type": "Union[EvaluableExpression, List, Dict, ndarray, int, float, str, NoneType]", + "description": "Value to set at this input port if no edge connected to it." + }, "shape": { "type": "Union[Tuple[int, ...], NoneType]", "description": "The shape of the input port. This uses the same syntax as numpy ndarray shapes\n(e.g., :code:`numpy.zeros(shape)` would produce an array with the correct shape" @@ -113,6 +117,10 @@ "type": { "type": "Union[str, NoneType]", "description": "The data type of the input received at a port." + }, + "reduce": { + "type": "Union[str, NoneType]", + "description": "Specifies how to deal with multiple inputs to one port during a single timestep: add: add up all the values; multiply: multiply the values, overwrite: just use the last value supplied (default)" } } }, diff --git a/docs/MDF_specification.yaml b/docs/MDF_specification.yaml index aa520e8d..c1d4fc2f 100644 --- a/docs/MDF_specification.yaml +++ b/docs/MDF_specification.yaml @@ -91,6 +91,11 @@ specification: id: type: str description: The unique (for this Node) id of the input port, + default_value: + type: Union[EvaluableExpression, List, Dict, ndarray, int, float, + str, NoneType] + description: Value to set at this input port if no edge connected + to it. shape: type: Union[Tuple[int, ...], NoneType] description: 'The shape of the input port. This uses the same syntax @@ -101,6 +106,12 @@ specification: type: type: Union[str, NoneType] description: The data type of the input received at a port. + reduce: + type: Union[str, NoneType] + description: 'Specifies how to deal with multiple inputs to one port + during a single timestep: add: add up all the values; multiply: + multiply the values, overwrite: just use the last value supplied + (default)' Function: definition: A single value which is evaluated as a function of values on :class:`InputPort`(s) and other Functions diff --git a/docs/README.md b/docs/README.md index 0db2109c..8b2365af 100644 --- a/docs/README.md +++ b/docs/README.md @@ -179,6 +179,13 @@ The InputPort is an attribute of a Node which allows ex + + default_value + Union[EvaluableExpression, List, Dict, ndarray, int, float, str, NoneType] + Value to set at this input port if no edge connected to it. + + + shape Union[Tuple[int, ...], NoneType] @@ -194,6 +201,13 @@ The InputPort is an attribute of a Node which allows ex + + reduce + Union[str, NoneType] + Specifies how to deal with multiple inputs to one port during a single timestep: add: add up all the values; multiply: multiply the values, overwrite: just use the last value supplied (default) + + + ## Function diff --git a/docs/sphinx/source/api/Specification.rst b/docs/sphinx/source/api/Specification.rst index 18761b00..6e5f1cf0 100644 --- a/docs/sphinx/source/api/Specification.rst +++ b/docs/sphinx/source/api/Specification.rst @@ -87,15 +87,17 @@ The `InputPort <#inputport>`__ is an attribute of a Node which allows external i **Allowed parameters** -=============== ================================ ============================================================================================= -Allowed field Data Type Description -=============== ================================ ============================================================================================= -**metadata** Union[Any, NoneType] Optional metadata field, an arbitrary dictionary of string keys and JSON serializable values. -**id** str The unique (for this Node) id of the input port, -**shape** Union[Tuple[int, ...], NoneType] The shape of the input port. This uses the same syntax as numpy ndarray shapes - (e.g., **numpy.zeros(shape)** would produce an array with the correct shape -**type** Union[str, NoneType] The data type of the input received at a port. -=============== ================================ ============================================================================================= +================= ========================================================================== ================================================================================================================================================================================================= +Allowed field Data Type Description +================= ========================================================================== ================================================================================================================================================================================================= +**metadata** Union[Any, NoneType] Optional metadata field, an arbitrary dictionary of string keys and JSON serializable values. +**id** str The unique (for this Node) id of the input port, +**default_value** Union[EvaluableExpression, List, Dict, ndarray, int, float, str, NoneType] Value to set at this input port if no edge connected to it. +**shape** Union[Tuple[int, ...], NoneType] The shape of the input port. This uses the same syntax as numpy ndarray shapes + (e.g., **numpy.zeros(shape)** would produce an array with the correct shape +**type** Union[str, NoneType] The data type of the input received at a port. +**reduce** Union[str, NoneType] Specifies how to deal with multiple inputs to one port during a single timestep: add: add up all the values; multiply: multiply the values, overwrite: just use the last value supplied (default) +================= ========================================================================== ================================================================================================================================================================================================= ======== Function diff --git a/docs/sphinx/source/api/export_format/NeuroML/Izh.png b/docs/sphinx/source/api/export_format/NeuroML/Izh.png index 0ee1a7b9..077a4381 100644 Binary files a/docs/sphinx/source/api/export_format/NeuroML/Izh.png and b/docs/sphinx/source/api/export_format/NeuroML/Izh.png differ diff --git a/docs/sphinx/source/api/export_format/NeuroML/IzhikevichTest.gv.png b/docs/sphinx/source/api/export_format/NeuroML/IzhikevichTest.gv.png index 120eef86..9334cdb1 100644 Binary files a/docs/sphinx/source/api/export_format/NeuroML/IzhikevichTest.gv.png and b/docs/sphinx/source/api/export_format/NeuroML/IzhikevichTest.gv.png differ diff --git a/docs/sphinx/source/api/export_format/ONNX/abc.png b/docs/sphinx/source/api/export_format/ONNX/abc.png index e6cdc653..22c62cf2 100644 Binary files a/docs/sphinx/source/api/export_format/ONNX/abc.png and b/docs/sphinx/source/api/export_format/ONNX/abc.png differ diff --git a/examples/MDF/RNN/IAF_net.json b/examples/MDF/RNN/IAF_net.json index cf60851e..e93ef7e5 100644 --- a/examples/MDF/RNN/IAF_net.json +++ b/examples/MDF/RNN/IAF_net.json @@ -56,7 +56,8 @@ "current_input": { "shape": [ 8 - ] + ], + "reduce": "add" } }, "parameters": { @@ -140,7 +141,8 @@ "current_input": { "shape": [ 8 - ] + ], + "reduce": "add" } }, "parameters": { @@ -224,7 +226,8 @@ "spike_input": { "shape": [ 8 - ] + ], + "reduce": "add" } }, "parameters": { diff --git a/examples/MDF/RNN/IAF_net.yaml b/examples/MDF/RNN/IAF_net.yaml index a6556eb4..a3e0fea3 100644 --- a/examples/MDF/RNN/IAF_net.yaml +++ b/examples/MDF/RNN/IAF_net.yaml @@ -40,6 +40,7 @@ IAF_net: current_input: shape: - 8 + reduce: add parameters: v0: value: @@ -99,6 +100,7 @@ IAF_net: current_input: shape: - 8 + reduce: add parameters: v0: value: @@ -158,6 +160,7 @@ IAF_net: spike_input: shape: - 8 + reduce: add parameters: syn_tau: value: 10 diff --git a/examples/MDF/RNN/IAF_net2.json b/examples/MDF/RNN/IAF_net2.json index 3e29332b..44d49a5f 100644 --- a/examples/MDF/RNN/IAF_net2.json +++ b/examples/MDF/RNN/IAF_net2.json @@ -65,7 +65,8 @@ "current_input": { "shape": [ 8 - ] + ], + "reduce": "add" } }, "parameters": { @@ -149,7 +150,8 @@ "current_input": { "shape": [ 8 - ] + ], + "reduce": "add" } }, "parameters": { @@ -233,7 +235,8 @@ "spike_input": { "shape": [ 8 - ] + ], + "reduce": "add" } }, "parameters": { diff --git a/examples/MDF/RNN/IAF_net2.yaml b/examples/MDF/RNN/IAF_net2.yaml index 3b170ea3..1c64aa20 100644 --- a/examples/MDF/RNN/IAF_net2.yaml +++ b/examples/MDF/RNN/IAF_net2.yaml @@ -48,6 +48,7 @@ IAF_net2: current_input: shape: - 8 + reduce: add parameters: v0: value: @@ -107,6 +108,7 @@ IAF_net2: current_input: shape: - 8 + reduce: add parameters: v0: value: @@ -166,6 +168,7 @@ IAF_net2: spike_input: shape: - 8 + reduce: add parameters: syn_tau: value: 10 diff --git a/examples/MDF/RNN/IAF_net3.json b/examples/MDF/RNN/IAF_net3.json new file mode 100644 index 00000000..cc2b342d --- /dev/null +++ b/examples/MDF/RNN/IAF_net3.json @@ -0,0 +1,283 @@ +{ + "IAF_net3": { + "format": "ModECI MDF v0.4", + "generating_application": "Python modeci-mdf v0.4.12", + "graphs": { + "iaf_example": { + "nodes": { + "current_input_node": { + "parameters": { + "time": { + "default_initial_value": 0, + "time_derivative": "1" + }, + "start": { + "value": 20 + }, + "duration": { + "value": 10 + }, + "amplitude": { + "value": 10 + }, + "level": { + "value": 0, + "conditions": [ + { + "id": "on", + "test": "time > start", + "value": "amplitude" + }, + { + "id": "off", + "test": "time > start + duration", + "value": "amplitude*0" + } + ] + } + }, + "output_ports": { + "current_output": { + "value": "level" + } + } + }, + "current_input_node2": { + "parameters": { + "time": { + "default_initial_value": 0, + "time_derivative": "1" + }, + "start": { + "value": 60 + }, + "duration": { + "value": 10 + }, + "amplitude": { + "value": 3 + }, + "level": { + "value": 0, + "conditions": [ + { + "id": "on", + "test": "time > start", + "value": "amplitude" + }, + { + "id": "off", + "test": "time > start + duration", + "value": "amplitude*0" + } + ] + } + }, + "output_ports": { + "current_output": { + "value": "level" + } + } + }, + "pre": { + "input_ports": { + "current_input": { + "shape": [ + 1 + ], + "reduce": "add" + } + }, + "parameters": { + "v0": { + "value": [ + -60 + ] + }, + "erev": { + "value": [ + -70 + ] + }, + "tau": { + "value": 10.0 + }, + "thresh": { + "value": [ + -20 + ] + }, + "spiking": { + "default_initial_value": "0", + "conditions": [ + { + "id": "is_spiking", + "test": "v >= thresh", + "value": "1" + }, + { + "id": "not_spiking", + "test": "v < thresh", + "value": "0" + } + ] + }, + "v": { + "default_initial_value": "v0", + "time_derivative": "-1 * (v-erev)/tau + current_input", + "conditions": [ + { + "id": "reset", + "test": "v > thresh", + "value": "erev" + } + ] + } + }, + "output_ports": { + "v_output": { + "value": "v" + }, + "spiking_output": { + "value": "spiking" + } + } + }, + "post": { + "input_ports": { + "current_input": { + "shape": [ + 1 + ], + "reduce": "add" + } + }, + "parameters": { + "v0": { + "value": [ + -60 + ] + }, + "erev": { + "value": [ + -70 + ] + }, + "tau": { + "value": 10.0 + }, + "thresh": { + "value": [ + -20 + ] + }, + "spiking": { + "default_initial_value": "0", + "conditions": [ + { + "id": "is_spiking", + "test": "v >= thresh", + "value": "1" + }, + { + "id": "not_spiking", + "test": "v < thresh", + "value": "0" + } + ] + }, + "v": { + "default_initial_value": "v0", + "time_derivative": "-1 * (v-erev)/tau + current_input", + "conditions": [ + { + "id": "reset", + "test": "v > thresh", + "value": "erev" + } + ] + } + }, + "output_ports": { + "v_output": { + "value": "v" + }, + "spiking_output": { + "value": "spiking" + } + } + }, + "syn_post": { + "input_ports": { + "spike_input": { + "shape": [ + 1 + ], + "reduce": "add" + } + }, + "parameters": { + "syn_tau": { + "value": 10 + }, + "spike_weights": { + "value": [ + 40 + ] + }, + "weighted_spike": { + "function": "MatMul", + "args": { + "A": "spike_weights", + "B": "spike_input" + } + }, + "syn_i": { + "default_initial_value": "0", + "time_derivative": "-1 * syn_i", + "conditions": [ + { + "id": "spike_detected", + "test": "spike_input > 0", + "value": "weighted_spike" + } + ] + } + }, + "output_ports": { + "current_output": { + "value": "syn_i" + } + } + } + }, + "edges": { + "input_edge": { + "sender": "current_input_node", + "receiver": "pre", + "sender_port": "current_output", + "receiver_port": "current_input" + }, + "input_edge2": { + "sender": "current_input_node2", + "receiver": "pre", + "sender_port": "current_output", + "receiver_port": "current_input" + }, + "post_internal_edge": { + "sender": "syn_post", + "receiver": "post", + "sender_port": "current_output", + "receiver_port": "current_input" + }, + "syn_edge": { + "sender": "pre", + "receiver": "syn_post", + "sender_port": "spiking_output", + "receiver_port": "spike_input" + } + } + } + } + } +} diff --git a/examples/MDF/RNN/IAF_net3.yaml b/examples/MDF/RNN/IAF_net3.yaml new file mode 100644 index 00000000..06758477 --- /dev/null +++ b/examples/MDF/RNN/IAF_net3.yaml @@ -0,0 +1,178 @@ +IAF_net3: + format: ModECI MDF v0.4 + generating_application: Python modeci-mdf v0.4.12 + graphs: + iaf_example: + nodes: + current_input_node: + parameters: + time: + default_initial_value: 0 + time_derivative: '1' + start: + value: 20 + duration: + value: 10 + amplitude: + value: 10 + level: + value: 0 + conditions: + - id: 'on' + test: time > start + value: amplitude + - id: 'off' + test: time > start + duration + value: amplitude*0 + output_ports: + current_output: + value: level + current_input_node2: + parameters: + time: + default_initial_value: 0 + time_derivative: '1' + start: + value: 60 + duration: + value: 10 + amplitude: + value: 3 + level: + value: 0 + conditions: + - id: 'on' + test: time > start + value: amplitude + - id: 'off' + test: time > start + duration + value: amplitude*0 + output_ports: + current_output: + value: level + pre: + input_ports: + current_input: + shape: + - 1 + reduce: add + parameters: + v0: + value: + - -60 + erev: + value: + - -70 + tau: + value: 10.0 + thresh: + value: + - -20 + spiking: + default_initial_value: '0' + conditions: + - id: is_spiking + test: v >= thresh + value: '1' + - id: not_spiking + test: v < thresh + value: '0' + v: + default_initial_value: v0 + time_derivative: -1 * (v-erev)/tau + current_input + conditions: + - id: reset + test: v > thresh + value: erev + output_ports: + v_output: + value: v + spiking_output: + value: spiking + post: + input_ports: + current_input: + shape: + - 1 + reduce: add + parameters: + v0: + value: + - -60 + erev: + value: + - -70 + tau: + value: 10.0 + thresh: + value: + - -20 + spiking: + default_initial_value: '0' + conditions: + - id: is_spiking + test: v >= thresh + value: '1' + - id: not_spiking + test: v < thresh + value: '0' + v: + default_initial_value: v0 + time_derivative: -1 * (v-erev)/tau + current_input + conditions: + - id: reset + test: v > thresh + value: erev + output_ports: + v_output: + value: v + spiking_output: + value: spiking + syn_post: + input_ports: + spike_input: + shape: + - 1 + reduce: add + parameters: + syn_tau: + value: 10 + spike_weights: + value: + - 40 + weighted_spike: + function: MatMul + args: + A: spike_weights + B: spike_input + syn_i: + default_initial_value: '0' + time_derivative: -1 * syn_i + conditions: + - id: spike_detected + test: spike_input > 0 + value: weighted_spike + output_ports: + current_output: + value: syn_i + edges: + input_edge: + sender: current_input_node + receiver: pre + sender_port: current_output + receiver_port: current_input + input_edge2: + sender: current_input_node2 + receiver: pre + sender_port: current_output + receiver_port: current_input + post_internal_edge: + sender: syn_post + receiver: post + sender_port: current_output + receiver_port: current_input + syn_edge: + sender: pre + receiver: syn_post + sender_port: spiking_output + receiver_port: spike_input diff --git a/examples/MDF/RNN/IAFs.json b/examples/MDF/RNN/IAFs.json index efcc2a85..7e8ebd9f 100644 --- a/examples/MDF/RNN/IAFs.json +++ b/examples/MDF/RNN/IAFs.json @@ -47,7 +47,8 @@ "current_input": { "shape": [ 1 - ] + ], + "reduce": "add" } }, "parameters": { @@ -110,7 +111,8 @@ "current_input": { "shape": [ 1 - ] + ], + "reduce": "add" } }, "parameters": { @@ -173,7 +175,8 @@ "spike_input": { "shape": [ 1 - ] + ], + "reduce": "add" } }, "parameters": { diff --git a/examples/MDF/RNN/IAFs.yaml b/examples/MDF/RNN/IAFs.yaml index 7c479cc8..c14b276f 100644 --- a/examples/MDF/RNN/IAFs.yaml +++ b/examples/MDF/RNN/IAFs.yaml @@ -32,6 +32,7 @@ IAFs: current_input: shape: - 1 + reduce: add parameters: v0: value: @@ -70,6 +71,7 @@ IAFs: current_input: shape: - 1 + reduce: add parameters: v0: value: @@ -108,6 +110,7 @@ IAFs: spike_input: shape: - 1 + reduce: add parameters: syn_tau: value: 10 diff --git a/examples/MDF/RNN/IaF.net3.run.png b/examples/MDF/RNN/IaF.net3.run.png new file mode 100644 index 00000000..4ffbb7e9 Binary files /dev/null and b/examples/MDF/RNN/IaF.net3.run.png differ diff --git a/examples/MDF/RNN/RNN.run.png b/examples/MDF/RNN/RNN.run.png index f793e1da..fd86a095 100644 Binary files a/examples/MDF/RNN/RNN.run.png and b/examples/MDF/RNN/RNN.run.png differ diff --git a/examples/MDF/RNN/RNNs.json b/examples/MDF/RNN/RNNs.json index 67b6697b..9722517f 100644 --- a/examples/MDF/RNN/RNNs.json +++ b/examples/MDF/RNN/RNNs.json @@ -46,6 +46,7 @@ ] }, "fb_input": { + "default_value": 0, "shape": [ 5 ] @@ -102,7 +103,7 @@ 0.10262953816578246, 0.43893793957112615 ], - "time_derivative": "-x + g*int_fb + ext_input" + "time_derivative": "-x + g*int_fb + ext_input + fb_input" }, "r": { "function": "tanh", @@ -185,6 +186,15 @@ "parameters": { "weight": 1 } + }, + "feedback_edge": { + "sender": "readout_node", + "receiver": "rnn_node", + "sender_port": "z", + "receiver_port": "fb_input", + "parameters": { + "weight": 0.3 + } } } } diff --git a/examples/MDF/RNN/RNNs.yaml b/examples/MDF/RNN/RNNs.yaml index 3589bf9a..03b25311 100644 --- a/examples/MDF/RNN/RNNs.yaml +++ b/examples/MDF/RNN/RNNs.yaml @@ -31,6 +31,7 @@ RNNs: shape: - 5 fb_input: + default_value: 0 shape: - 5 parameters: @@ -70,7 +71,7 @@ RNNs: - -0.5462970928715938 - 0.10262953816578246 - 0.43893793957112615 - time_derivative: -x + g*int_fb + ext_input + time_derivative: -x + g*int_fb + ext_input + fb_input r: function: tanh args: @@ -127,3 +128,10 @@ RNNs: receiver_port: input parameters: weight: 1 + feedback_edge: + sender: readout_node + receiver: rnn_node + sender_port: z + receiver_port: fb_input + parameters: + weight: 0.3 diff --git a/examples/MDF/RNN/generate_iaf.py b/examples/MDF/RNN/generate_iaf.py index 42b4ec8f..cf4e3ef3 100644 --- a/examples/MDF/RNN/generate_iaf.py +++ b/examples/MDF/RNN/generate_iaf.py @@ -58,7 +58,7 @@ def create_iaf_syn_node( syn_tau = Parameter(id="syn_tau", value=syn_tau) syn_node.parameters.append(syn_tau) - ip_spike = InputPort(id="spike_input", shape="(%i,)" % num_cells) + ip_spike = InputPort(id="spike_input", shape="(%i,)" % num_cells, reduce="add") syn_node.input_ports.append(ip_spike) spike_weights = Parameter(id="spike_weights", value=numpy.identity(num_cells)) @@ -91,7 +91,7 @@ def create_iaf_syn_node( ## IAF node... iaf_node = Node(id) - ip_current = InputPort(id="current_input", shape="(%i,)" % num_cells) + ip_current = InputPort(id="current_input", shape="(%i,)" % num_cells, reduce="add") iaf_node.input_ports.append(ip_current) v0 = Parameter( @@ -293,7 +293,8 @@ def main(): recorded = {} times = [] t = [] - i = [] + input1 = [] + input2 = [] s1 = [] sp1 = [] s2 = [] @@ -313,9 +314,15 @@ def main(): % eg.enodes["post"].evaluable_outputs["v_output"].curr_value ) - i.append( + input1.append( eg.enodes[input_node.id].evaluable_outputs["current_output"].curr_value ) + if net3: + input2.append( + eg.enodes[input_node2.id] + .evaluable_outputs["current_output"] + .curr_value + ) t.append(eg.enodes[input_node.id].evaluable_parameters["time"].curr_value) s1.append(eg.enodes["pre"].evaluable_outputs["v_output"].curr_value) sp1.append(eg.enodes["pre"].evaluable_parameters["spiking"].curr_value) @@ -331,16 +338,18 @@ def main(): markersize = 2 if num_cells < 20 else 0.5 - if type(i[0]) == numpy.ndarray and i[0].size > 1: - for ii in range(len(i[0])): + if type(input1[0]) == numpy.ndarray and input1[0].size > 1: + for ii in range(len(input1[0])): iii = [] for ti in range(len(t)): - iii.append(i[ti][ii]) + iii.append(input1[ti][ii]) axis[0].plot( times, iii, label="Input node %s current" % ii, linewidth="0.5" ) else: - axis[0].plot(times, i, label="Input node current", color="k") + axis[0].plot(times, input1, label="Input node current", color="k") + if net3: + axis[0].plot(times, input2, label="Input node current 2", color="g") if not some_net: axis[0].legend() @@ -443,7 +452,7 @@ def main(): engine="dot", output_format="png", view_on_render=False, - level=2, + level=1 if net3 else 2, filename_root="iaf%s" % ( ".net" diff --git a/examples/MDF/RNN/generate_rnn.py b/examples/MDF/RNN/generate_rnn.py index 2ce00a50..29cdbfa4 100644 --- a/examples/MDF/RNN/generate_rnn.py +++ b/examples/MDF/RNN/generate_rnn.py @@ -87,16 +87,15 @@ def main(): ) mod_graph.edges.append(e2) - """ e3 = Edge( id="feedback_edge", - parameters={"weight": 0.1}, + parameters={"weight": 0.3}, sender=readout_node.id, sender_port=readout_node.get_output_port("z").id, receiver=rnn_node.id, receiver_port=rnn_node.get_input_port("fb_input").id, ) - mod_graph.edges.append(e3)""" + mod_graph.edges.append(e3) if N < 100: new_file = mod.to_json_file("%s.json" % mod.id) diff --git a/examples/MDF/RNN/iaf.net.png b/examples/MDF/RNN/iaf.net.png index 30db8f94..1a8a3bd1 100644 Binary files a/examples/MDF/RNN/iaf.net.png and b/examples/MDF/RNN/iaf.net.png differ diff --git a/examples/MDF/RNN/iaf.net2.png b/examples/MDF/RNN/iaf.net2.png index 22784bc0..ceec52d2 100644 Binary files a/examples/MDF/RNN/iaf.net2.png and b/examples/MDF/RNN/iaf.net2.png differ diff --git a/examples/MDF/RNN/iaf.png b/examples/MDF/RNN/iaf.png index acd54abb..e2785d20 100644 Binary files a/examples/MDF/RNN/iaf.png and b/examples/MDF/RNN/iaf.png differ diff --git a/examples/MDF/RNN/regenerate.sh b/examples/MDF/RNN/regenerate.sh index 6a6b140e..9911be35 100755 --- a/examples/MDF/RNN/regenerate.sh +++ b/examples/MDF/RNN/regenerate.sh @@ -10,6 +10,7 @@ python generate_iaf.py -net2 -graph python generate_iaf.py -run -nogui python generate_iaf.py -run -net -nogui python generate_iaf.py -run -net2 -nogui +python generate_iaf.py -run -net3 -nogui #Fix dimensions! #python generate_iaf.py -neuroml #pynml LEMS_Simiaf_example.xml -lems-graph diff --git a/examples/MDF/RNN/rnn.png b/examples/MDF/RNN/rnn.png index ea726f10..963bdbc8 100644 Binary files a/examples/MDF/RNN/rnn.png and b/examples/MDF/RNN/rnn.png differ diff --git a/examples/MDF/RNN/utils.py b/examples/MDF/RNN/utils.py index 6688add9..1899769f 100644 --- a/examples/MDF/RNN/utils.py +++ b/examples/MDF/RNN/utils.py @@ -8,11 +8,11 @@ def create_rnn_node(id, N, g, seed=1234): ## RNN node... rnn_node = Node(id=id) - ipr1 = InputPort(id="ext_input", shape="(%i,)" % N) - rnn_node.input_ports.append(ipr1) + ext_ip = InputPort(id="ext_input", shape="(%i,)" % N) + rnn_node.input_ports.append(ext_ip) - ipr2 = InputPort(id="fb_input", shape="(%i,)" % N) - rnn_node.input_ports.append(ipr2) + fb_ip = InputPort(id="fb_input", default_value=0, shape="(%i,)" % N) + rnn_node.input_ports.append(fb_ip) default_initial_value = np.zeros(N) default_initial_value = 2 * np.random.random(N) - 1 @@ -26,7 +26,7 @@ def create_rnn_node(id, N, g, seed=1234): x = Parameter( id="x", default_initial_value=default_initial_value, - time_derivative="-x + g*int_fb + %s" % ipr1.id, + time_derivative=f"-x + g*int_fb + {ext_ip.id} + {fb_ip.id}", ) rnn_node.parameters.append(x) diff --git a/examples/MDF/networks/Net_acyclical.json b/examples/MDF/networks/Net_acyclical.json new file mode 100644 index 00000000..4cbac758 --- /dev/null +++ b/examples/MDF/networks/Net_acyclical.json @@ -0,0 +1,64 @@ +{ + "Net_acyclical": { + "format": "ModECI MDF v0.4", + "generating_application": "Python modeci-mdf v0.4.12", + "graphs": { + "Net_acyclical": { + "nodes": { + "A": { + "input_ports": { + "input_port": { + "default_value": 0 + } + }, + "parameters": { + "passed_token": { + "value": "input_port + 1" + }, + "internal_count": { + "value": "internal_count + 1" + } + }, + "output_ports": { + "out_port": { + "value": "passed_token" + } + } + }, + "B": { + "input_ports": { + "input_port": {} + }, + "parameters": { + "passed_token": { + "value": "input_port + 1" + }, + "internal_count": { + "value": "internal_count + 1" + } + }, + "output_ports": { + "out_port": { + "value": "passed_token" + } + } + } + }, + "edges": { + "edge_A_B": { + "sender": "A", + "receiver": "B", + "sender_port": "out_port", + "receiver_port": "input_port" + }, + "edge_B_A": { + "sender": "B", + "receiver": "A", + "sender_port": "out_port", + "receiver_port": "input_port" + } + } + } + } + } +} diff --git a/examples/MDF/networks/Net_acyclical.png b/examples/MDF/networks/Net_acyclical.png new file mode 100644 index 00000000..22386968 Binary files /dev/null and b/examples/MDF/networks/Net_acyclical.png differ diff --git a/examples/MDF/networks/Net_acyclical.yaml b/examples/MDF/networks/Net_acyclical.yaml new file mode 100644 index 00000000..f578ad33 --- /dev/null +++ b/examples/MDF/networks/Net_acyclical.yaml @@ -0,0 +1,40 @@ +Net_acyclical: + format: ModECI MDF v0.4 + generating_application: Python modeci-mdf v0.4.12 + graphs: + Net_acyclical: + nodes: + A: + input_ports: + input_port: + default_value: 0 + parameters: + passed_token: + value: input_port + 1 + internal_count: + value: internal_count + 1 + output_ports: + out_port: + value: passed_token + B: + input_ports: + input_port: {} + parameters: + passed_token: + value: input_port + 1 + internal_count: + value: internal_count + 1 + output_ports: + out_port: + value: passed_token + edges: + edge_A_B: + sender: A + receiver: B + sender_port: out_port + receiver_port: input_port + edge_B_A: + sender: B + receiver: A + sender_port: out_port + receiver_port: input_port diff --git a/examples/MDF/networks/network.py b/examples/MDF/networks/network.py new file mode 100644 index 00000000..4f72f718 --- /dev/null +++ b/examples/MDF/networks/network.py @@ -0,0 +1,105 @@ +""" + Example of ModECI MDF - Testing networks +""" + +from modeci_mdf.mdf import * +import sys +import os +from modeci_mdf.utils import simple_connect + + +def main(ref="acyclical"): + + mod = Model(id="Net_%s" % ref) + + mod_graph = Graph(id="Net_%s" % ref) + mod.graphs.append(mod_graph) + + for id in ["A", "B"]: + + node = Node(id=id) + + if id == "A": + ip1 = InputPort(id="input_port", default_value=0) + node.input_ports.append(ip1) + + if id == "B": + ip1 = InputPort(id="input_port") + node.input_ports.append(ip1) + + p1 = Parameter(id="passed_token", value="input_port + 1") + node.parameters.append(p1) + + p2 = Parameter(id="internal_count", value="internal_count + 1") + node.parameters.append(p2) + + op1 = OutputPort(id="out_port", value=p1.id) + node.output_ports.append(op1) + + mod_graph.nodes.append(node) + + simple_connect(mod_graph.get_node("A"), mod_graph.get_node("B"), mod_graph) + simple_connect(mod_graph.get_node("B"), mod_graph.get_node("A"), mod_graph) + + new_file = mod.to_json_file("%s.json" % mod.id) + new_file = mod.to_yaml_file("%s.yaml" % mod.id) + + if "-run" in sys.argv: + verbose = True + # verbose = False + from modeci_mdf.utils import load_mdf, print_summary + + from modeci_mdf.execution_engine import EvaluableGraph + + eg = EvaluableGraph(mod_graph, verbose) + dt = 1 + + duration = 2 + t = 0 + recorded = {} + times = [] + ai = [] + bi = [] + ao = [] + bo = [] + while t <= duration: + times.append(t) + print("====== Evaluating at t = %s ======" % (t)) + if t == 0: + eg.evaluate() # replace with initialize? + else: + eg.evaluate(time_increment=dt) + + ai.append(eg.enodes["A"].evaluable_parameters["internal_count"].curr_value) + bi.append(eg.enodes["B"].evaluable_parameters["internal_count"].curr_value) + ao.append(eg.enodes["A"].evaluable_outputs["out_port"].curr_value) + bo.append(eg.enodes["B"].evaluable_outputs["out_port"].curr_value) + t += dt + + if "-nogui" not in sys.argv: + import matplotlib.pyplot as plt + + plt.plot(times, ai, marker="x", label="A count") + plt.plot(times, bi, label="B count") + plt.plot(times, ao, marker="o", label="A token") + plt.plot(times, bo, label="B token") + plt.legend() + plt.show() + + if "-graph" in sys.argv: + mod.to_graph_image( + engine="dot", + output_format="png", + view_on_render=False, + level=3, + filename_root=mod_graph.id, + only_warn_on_fail=( + os.name == "nt" + ), # Makes sure test of this doesn't fail on Windows on GitHub Actions + ) + + return mod_graph + + +if __name__ == "__main__": + main() diff --git a/examples/MDF/networks/regenerate.sh b/examples/MDF/networks/regenerate.sh new file mode 100755 index 00000000..acb22dd6 --- /dev/null +++ b/examples/MDF/networks/regenerate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -ex + +python network.py -run -graph -nogui diff --git a/examples/NeuroML/Izh.png b/examples/NeuroML/Izh.png index 0ee1a7b9..077a4381 100644 Binary files a/examples/NeuroML/Izh.png and b/examples/NeuroML/Izh.png differ diff --git a/examples/NeuroML/IzhikevichTest.gv.png b/examples/NeuroML/IzhikevichTest.gv.png index 120eef86..9334cdb1 100644 Binary files a/examples/NeuroML/IzhikevichTest.gv.png and b/examples/NeuroML/IzhikevichTest.gv.png differ diff --git a/examples/NeuroML/PyNN/HH.mdf.png b/examples/NeuroML/PyNN/HH.mdf.png index 7cb45a8b..42975df9 100644 Binary files a/examples/NeuroML/PyNN/HH.mdf.png and b/examples/NeuroML/PyNN/HH.mdf.png differ diff --git a/examples/NeuroML/PyNN/InputWeights.mdf.png b/examples/NeuroML/PyNN/InputWeights.mdf.png index a34f50a1..f91fa93c 100644 Binary files a/examples/NeuroML/PyNN/InputWeights.mdf.png and b/examples/NeuroML/PyNN/InputWeights.mdf.png differ diff --git a/examples/NeuroML/PyNN/Net1.mdf.png b/examples/NeuroML/PyNN/Net1.mdf.png index 8d21027b..3cbf34f6 100644 Binary files a/examples/NeuroML/PyNN/Net1.mdf.png and b/examples/NeuroML/PyNN/Net1.mdf.png differ diff --git a/examples/NeuroML/PyNN/OneCell.mdf.png b/examples/NeuroML/PyNN/OneCell.mdf.png index d67c5aff..74fb0b69 100644 Binary files a/examples/NeuroML/PyNN/OneCell.mdf.png and b/examples/NeuroML/PyNN/OneCell.mdf.png differ diff --git a/examples/NeuroML/PyNN/SimpleNet.mdf.png b/examples/NeuroML/PyNN/SimpleNet.mdf.png index 52d272aa..f94a631f 100644 Binary files a/examples/NeuroML/PyNN/SimpleNet.mdf.png and b/examples/NeuroML/PyNN/SimpleNet.mdf.png differ diff --git a/examples/ONNX/abc.png b/examples/ONNX/abc.png index e6cdc653..22c62cf2 100644 Binary files a/examples/ONNX/abc.png and b/examples/ONNX/abc.png differ diff --git a/examples/TensorFlow/Keras/IRIS/model_on_iris_plot.png b/examples/TensorFlow/Keras/IRIS/model_on_iris_plot.png index 9b0cde3d..5bfe195f 100644 Binary files a/examples/TensorFlow/Keras/IRIS/model_on_iris_plot.png and b/examples/TensorFlow/Keras/IRIS/model_on_iris_plot.png differ diff --git a/examples/TensorFlow/Keras/MNIST/model_plot.png b/examples/TensorFlow/Keras/MNIST/model_plot.png index f4142fab..445bbb4c 100644 Binary files a/examples/TensorFlow/Keras/MNIST/model_plot.png and b/examples/TensorFlow/Keras/MNIST/model_plot.png differ diff --git a/src/modeci_mdf/execution_engine.py b/src/modeci_mdf/execution_engine.py index 2922a23b..ff71dd86 100644 --- a/src/modeci_mdf/execution_engine.py +++ b/src/modeci_mdf/execution_engine.py @@ -692,10 +692,17 @@ class EvaluableInput: def __init__(self, input_port: InputPort, verbose: Optional[bool] = False): self.verbose = verbose self.input_port = input_port - default = 0 - if input_port.type and "float" in input_port.type: - default = 0.0 - self.curr_value = np.full(input_port.shape, default) + + if self.input_port.reduce == "overwrite": + if self.input_port.default_value is not None: + self.curr_value = self.input_port.default_value + else: + default = 0 + if input_port.type and "float" in input_port.type: + default = 0.0 + self.curr_value = np.full(input_port.shape, default) + else: + self.curr_value = None def set_input_value(self, value: Union[str, int, np.ndarray]): """Set a new value at input port @@ -703,9 +710,30 @@ def set_input_value(self, value: Union[str, int, np.ndarray]): Args: value: Value to be set at Input Port """ - if self.verbose: - print(f" Input value in {self.input_port.id} set to {_val_info(value)}") - self.curr_value = value + + if self.curr_value is None: + ## No value set yet, so just set to value being input now + print( + f" Input value in {self.input_port.id} being set to {_val_info(value)}" + ) + self.curr_value = value + else: + ## A previous value has been set during this time step. So reduce it... + if self.input_port.reduce == "overwrite": + print( + f" Input value in {self.input_port.id} being set to {_val_info(value)}" + ) + self.curr_value = value + elif self.input_port.reduce == "add": + print( + f" Input value in {self.input_port.id} was {self.curr_value}, increasing by {_val_info(value)}" + ) + self.curr_value += value + elif self.input_port.reduce == "multiply": + print( + f" Input value in {self.input_port.id} was {self.curr_value}, increasing by {_val_info(value)}" + ) + self.curr_value *= value def evaluate( self, parameters: Dict[str, Any] = None, array_format: str = FORMAT_DEFAULT @@ -719,16 +747,27 @@ def evaluate( Returns: value at Input port """ + + if self.curr_value is None: + if self.input_port.default_value is not None: + self.curr_value = self.input_port.default_value + else: + self.curr_value = np.full(self.input_port.shape, 0.0) + + final_val = self.curr_value + if self.verbose: print( - " Evaluated %s with params %s =\t%s" + " Evaluated the %s with params %s \n =\t%s" % ( self.input_port, _params_info(parameters), - _val_info(self.curr_value), + _val_info(final_val), ) ) - return self.curr_value + + self.curr_value = None + return final_val class EvaluableNode: @@ -1034,6 +1073,14 @@ def __init__(self, graph: Graph, verbose: Optional[bool] = False): ): # It could have been already removed... self.root_nodes.remove(edge.receiver) + if len(self.root_nodes) == 0: + for node in graph.nodes: + for ip in node.input_ports: + if ip.default_value is not None: + self.root_nodes.append(node.id) + + print("Root nodes evaluated as: %s" % (self.root_nodes)) + self.ordered_edges = [] evaluated_nodes = [] for rn in self.root_nodes: @@ -1049,6 +1096,8 @@ def __init__(self, graph: Graph, verbose: Optional[bool] = False): self.ordered_edges.append(edge) evaluated_nodes.append(edge.receiver) + print("Ordered_edges as: %s" % (self.ordered_edges)) + if self.graph.conditions is not None: if self.graph.conditions.node_specific is None: conditions = {} @@ -1076,6 +1125,7 @@ def __init__(self, graph: Graph, verbose: Optional[bool] = False): else: conditions = {} termination_conds = {} + self.scheduler = graph_scheduler.Scheduler( graph=self.graph.dependency_dict, conditions=conditions, @@ -1147,9 +1197,16 @@ def evaluate( for node in ts: self.order_of_execution.append(node.id) for edge in incoming_edges[node]: - self.evaluate_edge( - edge, time_increment=time_increment, array_format=array_format - ) + if edge.sender in self.order_of_execution: + self.evaluate_edge( + edge, + time_increment=time_increment, + array_format=array_format, + ) + else: + print( + "> Not evaluating edge: %s, as sender not run yet..." % edge + ) self.enodes[node.id].evaluate( time_increment=time_increment, array_format=array_format ) diff --git a/src/modeci_mdf/interfaces/graphviz/exporter.py b/src/modeci_mdf/interfaces/graphviz/exporter.py index e34c32ac..833a9786 100644 --- a/src/modeci_mdf/interfaces/graphviz/exporter.py +++ b/src/modeci_mdf/interfaces/graphviz/exporter.py @@ -285,9 +285,9 @@ def mdf_to_graphviz( additional = "" if ip.shape is not None and ip.shape != (): additional += "shape: %s, " % str(ip.shape) - """if ip.reduce is not None: + if ip.reduce is not None: if ip.reduce != "overwrite": # since this is the default... - additional += "reduce: %s, " % str(ip.reduce)""" + additional += "reduce: %s, " % str(ip.reduce) if ip.type is not None: additional += "type: %s, " % str(ip.type) diff --git a/src/modeci_mdf/mdf.py b/src/modeci_mdf/mdf.py index 327240be..d6f908e6 100644 --- a/src/modeci_mdf/mdf.py +++ b/src/modeci_mdf/mdf.py @@ -99,19 +99,25 @@ class InputPort(MdfBase): Attributes: id: The unique (for this Node) id of the input port, + default_value: Value to set at this input port if no edge connected to it. shape: The shape of the input port. This uses the same syntax as numpy ndarray shapes (e.g., :code:`numpy.zeros(shape)` would produce an array with the correct shape type: The data type of the input received at a port. + reduce: Specifies how to deal with multiple inputs to one port during a single timestep: add: add up all the values; multiply: multiply the values, overwrite: just use the last value supplied (default) """ id: str = field(validator=instance_of(str)) + default_value: Optional[ValueExprType] = field(default=None) shape: Optional[Tuple[int, ...]] = field( validator=optional(instance_of(tuple)), default=(), converter=lambda x: make_tuple(x) if type(x) is str else x, ) type: Optional[str] = field(validator=optional(instance_of(str)), default=None) + reduce: Optional[str] = field( + validator=optional(instance_of(str)), default="overwrite" + ) @modelspec.define(eq=False) @@ -488,7 +494,10 @@ def dependency_dict(self) -> Dict[Node, Set[Node]]: sender = self.get_node(edge.sender) receiver = self.get_node(edge.receiver) - dependencies[receiver].add(sender) + if receiver.get_input_port(edge.receiver_port).default_value is not None: + pass + else: + dependencies[receiver].add(sender) return dependencies diff --git a/test_all.sh b/test_all.sh index 4d09a4d1..2d6c79cb 100755 --- a/test_all.sh +++ b/test_all.sh @@ -72,6 +72,11 @@ cd .. cd RNN ./regenerate.sh +## Test regenerating network example + +cd ../networks +./regenerate.sh + ## Test regenerating NeuroML diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index 45b5356a..f8f9984c 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -15,7 +15,7 @@ def test_execution_engine_main(tmpdir): mdf_formats = ["json", "yaml"] array_formats = [FORMAT_NUMPY, FORMAT_TENSORFLOW] - # For now, don't make tensorflow a requiremnt... + # For now, don't make tensorflow a requirement... try: import tensorflow except: @@ -221,7 +221,7 @@ def test_threshold(create_model): [ ("A", "A_output"), ("A", "A_param_2"), - ("B", "B_input"), + ("B", "B_output"), ], ) @pytest.mark.parametrize(