Search code examples
pythonintel

Detecting input and output spikes of neurons in lava-nc?


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?


Solution

  • 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()