The Lava Software Framework Documentation describes a Leaky-Integrate-And-Fire neuron as:
class LIF(AbstractProcess):
"""Leaky-Integrate-and-Fire neural process with activation input and spike
output ports a_in and s_out.
Realizes the following abstract behavior:
u[t] = u[t-1] * (1-du) + a_in
v[t] = v[t-1] * (1-dv) + u[t] + bias
s_out = v[t] > vth
v[t] = v[t] - s_out*vth
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
shape = kwargs.get("shape", (1,))
self.a_in = InPort(shape=shape)
self.s_out = OutPort(shape=shape)
self.u = Var(shape=shape, init=0)
self.v = Var(shape=shape, init=0)
self.du = Var(shape=(1,), init=kwargs.pop("du", 0))
self.dv = Var(shape=(1,), init=kwargs.pop("dv", 0))
self.bias = Var(shape=shape, init=kwargs.pop("b", 0))
self.vth = Var(shape=(1,), init=kwargs.pop("vth", 10))
and one can create a neuron that spikes at t=6
using:
def two_lif_neurons():
# Instantiate Lava processes to build network
from lava.proc.dense.process import Dense
from lava.proc.lif.process import LIF
lif1 = LIF(u=0, du=3, dv=0, bias=2)
dense = Dense()
lif2 = LIF()
# Connect processes via their directional input and output ports
lif1.out_ports.s_out.connect(dense.in_ports.s_in)
dense.out_ports.a_out.connect(lif2.in_ports.a_in)
# Execute process lif1 and all processes connected to it for fixed number of steps
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg
for t in range(1, 10):
lif1.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg())
if t == 6:
# print the output spike
print(lif1.s_out)
lif1.stop()
The relevant line is at t=6
which is when the neuron spikes. I would like to verify the spike is indeed sent. However, print(lif1.s_out)
outputs:
<lava.magma.core.process.ports.ports.OutPort object at 0x7fb5a6c8a3d0>
So I thought I'd print the attributes of the lif1.s_out
object:
'__abstractmethods__',
'__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__slots__',
'__str__',
'__subclasshook__',
'__weakref__',
'_abc_impl',
'_add_inputs',
'_add_outputs',
'_connect_backward',
'_connect_forward',
'_name',
'_process',
'_validate_ports',
'concat_with',
'connect',
'connect_from',
'flatten',
'get_dst_ports',
'get_incoming_virtual_ports',
'get_outgoing_virtual_ports',
'get_src_ports',
'in_connections',
'name',
'out_connections',
'process',
'reshape',
'shape',
'size',
'transpose']
And I looked at the port_in
object of the dense()
object: https://github.com/lava-nc/lava/blob/81336e63783edf6b27f2f019797728b208458cb8/src/lava/magma/core/process/ports/ports.py However, I did not yet find how to print the boolean value of the spike of the lif1
neuron. Hence, I would like to ask:
How can one print the boolean value of the outgoing spike of the lif neuron in the Intel Lava Neuromorphic Computing Framework?
An example is found here
One can add a monitor that monitors and stores the neuron behaviour, before starting to simulate the neurons. Then afterwards, you can read out what the neurons did.
# Instantiate Lava processes to build network
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg
from lava.proc.dense.process import Dense
from lava.proc.lif.process import LIF
from lava.proc.monitor.process import Monitor
from pprint import pprint
import numpy as np
lif1 = LIF(bias=2, vth=1)
dense = Dense(shape=(1, 1), weights=np.ones((1, 1)))
lif2 = LIF(vth=np.inf, dv=0, du=1)
mon_lif_1_v = Monitor()
mon_lif_2_v = Monitor()
mon_spike = Monitor()
mon_lif_1_v.probe(lif1.v, 20)
mon_lif_2_v.probe(lif2.v, 20)
mon_spike.probe(lif1.s_out, 20)
# This is used to get the name of the process that is used for monitoring.
mon_lif_1_v_process = list(mon_lif_1_v.get_data())[0]
mon_lif_2_v_process = list(mon_lif_2_v.get_data())[0]
mon_spike_process = list(mon_spike.get_data())[0]
# Note it follows the order of declaration/initialisation of the neuron/dense it follows.
print(f"mon_lif_1_v_process={mon_lif_1_v_process}")
print(f"mon_lif_2_v_process={mon_lif_2_v_process}")
print(f"mon_spike_process={mon_spike_process}")
# Connect processes via their directional input and output ports
lif1.out_ports.s_out.connect(dense.in_ports.s_in)
dense.out_ports.a_out.connect(lif2.in_ports.a_in)
for run in range(10):
t = run
# Execute process lif1 and all processes connected to it for fixed number of steps
lif1.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg())
# Print the currents that have accumulated in the post synaptic neuron (lif2)
print(
f'lif1.v={mon_lif_1_v.get_data()[mon_lif_1_v_process]["v"][t]},lif1.s_out={mon_spike.get_data()[mon_spike_process]["s_out"][t]}, lif2.v={mon_lif_2_v.get_data()[mon_lif_2_v_process]["v"][t]}'
)
# Change and Print the weights of the synapse (dense) to 2 from its initial state of 1
dense.weights.set(np.ones((1, 1)) * 2)
print(dense.weights)
for run in range(10):
t = run + 10
# Run the simulation for 10 more timesteps
lif1.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg())
# Show that the voltage increase reflects the increase in the synaptic weights
print(
f'lif1.v={mon_lif_1_v.get_data()[mon_lif_1_v_process]["v"][t]},lif1.s_out={mon_spike.get_data()[mon_spike_process]["s_out"][t]}, lif2.v={mon_lif_2_v.get_data()[mon_lif_2_v_process]["v"][t]}'
)
print(f"voltage_list after={voltage_list}")
lif1.stop()