From 70fcfcbff1896c23ce38b15eb9b2734882a15685 Mon Sep 17 00:00:00 2001 From: Paxon Date: Tue, 19 Sep 2023 11:08:03 -0700 Subject: [PATCH 1/5] resfire process and fixed process model --- src/lava/proc/resfire/models.py | 61 +++++++++++++++++++++ src/lava/proc/resfire/process.py | 92 ++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 src/lava/proc/resfire/models.py create mode 100644 src/lava/proc/resfire/process.py diff --git a/src/lava/proc/resfire/models.py b/src/lava/proc/resfire/models.py new file mode 100644 index 000000000..c22ce8398 --- /dev/null +++ b/src/lava/proc/resfire/models.py @@ -0,0 +1,61 @@ +# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY +# +# Copyright © 2022-2023 Intel Corporation. +# +# This software and the related documents are Intel copyrighted +# materials, and your use of them is governed by the express +# license under which they were provided to you (License). Unless +# the License provides otherwise, you may not use, modify, copy, +# publish, distribute, disclose or transmit this software or the +# related documents without Intel's prior written permission. +# +# This software and the related documents are provided as is, with +# no express or implied warranties, other than those that are +# expressly stated in the License. +# See: https://spdx.org/licenses/ + + +import numpy as np +from lava.proc.resfire.process import RFZero + +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol +from lava.magma.core.model.py.ports import PyInPort, PyOutPort +from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.resources import CPU +from lava.magma.core.decorator import implements, requires, tag +from lava.magma.core.model.py.model import PyLoihiProcessModel + + +@implements(proc=RFZero, protocol=LoihiProtocol) +@requires(CPU) +@tag('fixed_pt') +class PyRFZeroModelFixed(PyLoihiProcessModel): + """ Floating point implementation of RF""" + u_in = LavaPyType(PyInPort.VEC_DENSE, np.int32, precision=24) + v_in = LavaPyType(PyInPort.VEC_DENSE, np.int32, precision=24) + s_out = LavaPyType(PyOutPort.VEC_DENSE, np.int32, precision=24) + + vth: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) + + u: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) + v: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) + + lst: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) + lct: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) + + def run_spk(self) -> None: + u_in = self.u_in.recv() + v_in = self.v_in.recv() + + new_u = ((self.u * self.lct) // (2**15) + - (self.v * self.lst) // (2**15) + u_in) + + new_v = ((self.v * self.lct) // (2**15) + + (self.u * self.lst) // (2**15) + v_in) + + s_out = new_u * (new_u > self.vth) * (new_v >= 0) * (self.v < 0) + + self.u = new_u + self.v = new_v + + self.s_out.send(s_out) diff --git a/src/lava/proc/resfire/process.py b/src/lava/proc/resfire/process.py new file mode 100644 index 000000000..3e0e8aa8c --- /dev/null +++ b/src/lava/proc/resfire/process.py @@ -0,0 +1,92 @@ +# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY +# +# Copyright © 2022-2023 Intel Corporation. +# +# This software and the related documents are Intel copyrighted +# materials, and your use of them is governed by the express +# license under which they were provided to you (License). Unless +# the License provides otherwise, you may not use, modify, copy, +# publish, distribute, disclose or transmit this software or the +# related documents without Intel's prior written permission. +# +# This software and the related documents are provided as is, with +# no express or implied warranties, other than those that are +# expressly stated in the License. +# See: https://spdx.org/licenses/ + +import os +import numpy as np + +import typing as ty +from typing import Any, Dict +from enum import IntEnum, unique + +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.process.ports.ports import InPort, OutPort + + +class RFZero(AbstractProcess): + def __init__(self, + shape: ty.Tuple[int, ...], + freqs: np.ndarray, # Hz + decay_tau: np.ndarray, # seconds + dt: ty.Optional[float] = 0.001, # seconds/timestep + vth: ty.Optional[int] = 1) -> None: + """ + RFZero + Resonate and fire neuron with spike trigger of threshold and + 0-phase crossing. Graded spikes carry amplitude of oscillation. + + Parameters + ---------- + shape : tuple(int) + Number and topology of RF neurons. + freqs : numpy.ndarray + Frequency for each neuron (Hz). + decay_tau : numpy.ndarray + Decay time constant (s). + dt : float, optional + Time per timestep. Default is 0.001 seconds. + vth : float, optional + Neuron threshold voltage. + Currently, only a single threshold can be set for the entire + population of neurons. + """ + super().__init__(shape=shape) + + self.u_in = InPort(shape=shape) + self.v_in = InPort(shape=shape) + + self.s_out = OutPort(shape=shape) + + self.u = Var(shape=shape, init=0) + self.v = Var(shape=shape, init=0) + + ll = -1/decay_tau + + lct = np.array(np.exp(dt * ll) * np.cos(dt * freqs * np.pi * 2)) + lst = np.array(np.exp(dt * ll) * np.sin(dt * freqs * np.pi * 2)) + lct = (lct * 2**15).astype(np.int32) + lst = (lst * 2**15).astype(np.int32) + + # might need to right shift? + #lct = np.array(np.exp(dt * ll) * 2**14).astype(np.int32) + #lct = np.array(2**15-2**10-1).astype(np.int32) + + self.lct = Var(shape=shape, init=lct) + + #self.lct = 1 + dt * ll + #lst = np.array(dt * freqs * np.pi * 2 * 2**16).astype(np.int32) + self.lst = Var(shape=shape, init=lst) + + #print(lct, lst) + + self.vth = Var(shape=(1,), init=vth) + + + @property + def shape(self) -> ty.Tuple[int, ...]: + """Return shape of the Process.""" + return self.proc_params['shape'] + \ No newline at end of file From 2732ae2af30bfddae9ad7fe4930a4b8048b09552 Mon Sep 17 00:00:00 2001 From: Paxon Date: Tue, 19 Sep 2023 11:42:32 -0700 Subject: [PATCH 2/5] changed vth->uth in RFZero. Added tests. --- src/lava/proc/resfire/models.py | 26 ++++---- src/lava/proc/resfire/process.py | 42 +++++-------- tests/lava/proc/resfire/__init__.py | 0 tests/lava/proc/resfire/test_resfire.py | 84 +++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 tests/lava/proc/resfire/__init__.py create mode 100644 tests/lava/proc/resfire/test_resfire.py diff --git a/src/lava/proc/resfire/models.py b/src/lava/proc/resfire/models.py index c22ce8398..29cf3afe2 100644 --- a/src/lava/proc/resfire/models.py +++ b/src/lava/proc/resfire/models.py @@ -25,37 +25,37 @@ from lava.magma.core.decorator import implements, requires, tag from lava.magma.core.model.py.model import PyLoihiProcessModel - + @implements(proc=RFZero, protocol=LoihiProtocol) @requires(CPU) @tag('fixed_pt') class PyRFZeroModelFixed(PyLoihiProcessModel): - """ Floating point implementation of RF""" + """Fixed point implementation of RFZero""" u_in = LavaPyType(PyInPort.VEC_DENSE, np.int32, precision=24) v_in = LavaPyType(PyInPort.VEC_DENSE, np.int32, precision=24) s_out = LavaPyType(PyOutPort.VEC_DENSE, np.int32, precision=24) - - vth: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) - + + uth: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) + u: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) v: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) - + lst: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) lct: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=24) - + def run_spk(self) -> None: u_in = self.u_in.recv() v_in = self.v_in.recv() - + new_u = ((self.u * self.lct) // (2**15) - (self.v * self.lst) // (2**15) + u_in) - + new_v = ((self.v * self.lct) // (2**15) + (self.u * self.lst) // (2**15) + v_in) - - s_out = new_u * (new_u > self.vth) * (new_v >= 0) * (self.v < 0) - + + s_out = new_u * (new_u > self.uth) * (new_v >= 0) * (self.v < 0) + self.u = new_u self.v = new_v - + self.s_out.send(s_out) diff --git a/src/lava/proc/resfire/process.py b/src/lava/proc/resfire/process.py index 3e0e8aa8c..3f7a8e8ca 100644 --- a/src/lava/proc/resfire/process.py +++ b/src/lava/proc/resfire/process.py @@ -28,16 +28,16 @@ class RFZero(AbstractProcess): def __init__(self, - shape: ty.Tuple[int, ...], - freqs: np.ndarray, # Hz - decay_tau: np.ndarray, # seconds - dt: ty.Optional[float] = 0.001, # seconds/timestep - vth: ty.Optional[int] = 1) -> None: + shape: ty.Tuple[int, ...], + freqs: np.ndarray, # Hz + decay_tau: np.ndarray, # seconds + dt: ty.Optional[float] = 0.001, # seconds/timestep + uth: ty.Optional[int] = 1) -> None: """ RFZero Resonate and fire neuron with spike trigger of threshold and 0-phase crossing. Graded spikes carry amplitude of oscillation. - + Parameters ---------- shape : tuple(int) @@ -48,7 +48,7 @@ def __init__(self, Decay time constant (s). dt : float, optional Time per timestep. Default is 0.001 seconds. - vth : float, optional + uth : float, optional Neuron threshold voltage. Currently, only a single threshold can be set for the entire population of neurons. @@ -57,36 +57,24 @@ def __init__(self, self.u_in = InPort(shape=shape) self.v_in = InPort(shape=shape) - + self.s_out = OutPort(shape=shape) - + self.u = Var(shape=shape, init=0) self.v = Var(shape=shape, init=0) - - ll = -1/decay_tau - + + ll = -1 / decay_tau + lct = np.array(np.exp(dt * ll) * np.cos(dt * freqs * np.pi * 2)) lst = np.array(np.exp(dt * ll) * np.sin(dt * freqs * np.pi * 2)) lct = (lct * 2**15).astype(np.int32) lst = (lst * 2**15).astype(np.int32) - - # might need to right shift? - #lct = np.array(np.exp(dt * ll) * 2**14).astype(np.int32) - #lct = np.array(2**15-2**10-1).astype(np.int32) - + self.lct = Var(shape=shape, init=lct) - - #self.lct = 1 + dt * ll - #lst = np.array(dt * freqs * np.pi * 2 * 2**16).astype(np.int32) self.lst = Var(shape=shape, init=lst) - - #print(lct, lst) - - self.vth = Var(shape=(1,), init=vth) - - + self.uth = Var(shape=(1,), init=uth) + @property def shape(self) -> ty.Tuple[int, ...]: """Return shape of the Process.""" return self.proc_params['shape'] - \ No newline at end of file diff --git a/tests/lava/proc/resfire/__init__.py b/tests/lava/proc/resfire/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lava/proc/resfire/test_resfire.py b/tests/lava/proc/resfire/test_resfire.py new file mode 100644 index 000000000..f1abeff43 --- /dev/null +++ b/tests/lava/proc/resfire/test_resfire.py @@ -0,0 +1,84 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import unittest +import numpy as np + +from lava.proc.resfire.process import RFZero +from lava.proc.dense.process import Dense +from lava.proc import io + +from lava.magma.core.run_conditions import RunSteps +from lava.magma.core.run_configs import Loihi2SimCfg + + +class TestRFZeroProc(unittest.TestCase): + """Tests for RFZero""" + + def test_rfzero_impulse(self): + """Tests for correct behavior of RFZero neurons from impulse input""" + + num_steps = 50 + num_neurons = 4 + num_inputs = 1 + + # Create some weights + weightr = np.zeros((num_neurons, num_inputs)) + weighti = np.zeros((num_neurons, num_inputs)) + + weightr[0, 0] = 50 + weighti[0, 0] = -50 + + weightr[1, 0] = -70 + weighti[1, 0] = -70 + + weightr[2, 0] = -90 + weighti[2, 0] = 90 + + weightr[3, 0] = 110 + weighti[3, 0] = 110 + + # Create inputs + inp_shape = (num_inputs,) + out_shape = (num_neurons,) + + inp_data = np.zeros((inp_shape[0], num_steps)) + inp_data[:, 3] = 10 + + # Create the procs + denser = Dense(weights=weightr, num_message_bits=24) + densei = Dense(weights=weighti, num_message_bits=24) + + vec = RFZero(shape=out_shape, uth=1, + decay_tau=0.1, freqs=20) + + generator1 = io.source.RingBuffer(data=inp_data) + generator2 = io.source.RingBuffer(data=inp_data) + logger = io.sink.RingBuffer(shape=out_shape, buffer=num_steps) + + # Connect the procs + generator1.s_out.connect(denser.s_in) + generator2.s_out.connect(densei.s_in) + + denser.a_out.connect(vec.u_in) + densei.a_out.connect(vec.v_in) + + vec.s_out.connect(logger.a_in) + + # Run + try: + vec.run(condition=RunSteps(num_steps=num_steps), + run_cfg=Loihi2SimCfg()) + out_data = logger.data.get().astype(np.int32) + finally: + vec.stop() + + expected_out = np.array([661, 833, 932, 1007]) + + self.assertTrue( + np.all(expected_out == out_data[[0, 1, 2, 3], [11, 23, 36, 48]])) + + +if __name__ == '__main__': + unittest.main() From df7bc3dcec972e0a4e8cd61bcf4a7ed020a0e880 Mon Sep 17 00:00:00 2001 From: Paxon Date: Tue, 3 Oct 2023 11:32:56 -0700 Subject: [PATCH 3/5] removed unused imports --- src/lava/proc/resfire/process.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lava/proc/resfire/process.py b/src/lava/proc/resfire/process.py index 3f7a8e8ca..bcd93818c 100644 --- a/src/lava/proc/resfire/process.py +++ b/src/lava/proc/resfire/process.py @@ -14,12 +14,11 @@ # expressly stated in the License. # See: https://spdx.org/licenses/ -import os import numpy as np import typing as ty -from typing import Any, Dict -from enum import IntEnum, unique +from typing import Dict +from enum import unique from lava.magma.core.process.process import AbstractProcess from lava.magma.core.process.variable import Var From a995386d37f5e6cb2a0fc4e1b7b98003fb77d8f5 Mon Sep 17 00:00:00 2001 From: Paxon Date: Thu, 5 Oct 2023 08:49:00 -0700 Subject: [PATCH 4/5] unused imports, copyright statement. --- src/lava/proc/resfire/process.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/lava/proc/resfire/process.py b/src/lava/proc/resfire/process.py index bcd93818c..44591ddb7 100644 --- a/src/lava/proc/resfire/process.py +++ b/src/lava/proc/resfire/process.py @@ -1,24 +1,10 @@ -# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY -# -# Copyright © 2022-2023 Intel Corporation. -# -# This software and the related documents are Intel copyrighted -# materials, and your use of them is governed by the express -# license under which they were provided to you (License). Unless -# the License provides otherwise, you may not use, modify, copy, -# publish, distribute, disclose or transmit this software or the -# related documents without Intel's prior written permission. -# -# This software and the related documents are provided as is, with -# no express or implied warranties, other than those that are -# expressly stated in the License. +# Copyright (C) 2022-23 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ import numpy as np import typing as ty -from typing import Dict -from enum import unique from lava.magma.core.process.process import AbstractProcess from lava.magma.core.process.variable import Var From e2f8f3693eb84b5f41be1586d15681227dd87201 Mon Sep 17 00:00:00 2001 From: Paxon Date: Fri, 6 Oct 2023 09:02:39 -0700 Subject: [PATCH 5/5] bsd license on resfire models.py --- src/lava/proc/resfire/models.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/lava/proc/resfire/models.py b/src/lava/proc/resfire/models.py index 29cf3afe2..2c47208f9 100644 --- a/src/lava/proc/resfire/models.py +++ b/src/lava/proc/resfire/models.py @@ -1,17 +1,5 @@ -# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY -# -# Copyright © 2022-2023 Intel Corporation. -# -# This software and the related documents are Intel copyrighted -# materials, and your use of them is governed by the express -# license under which they were provided to you (License). Unless -# the License provides otherwise, you may not use, modify, copy, -# publish, distribute, disclose or transmit this software or the -# related documents without Intel's prior written permission. -# -# This software and the related documents are provided as is, with -# no express or implied warranties, other than those that are -# expressly stated in the License. +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/