diff --git a/src/lava/frameworks/loihi2.py b/src/lava/frameworks/loihi2.py new file mode 100644 index 000000000..07007d767 --- /dev/null +++ b/src/lava/frameworks/loihi2.py @@ -0,0 +1,14 @@ +# Copyright (C) 2022-23 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +from lava.networks.gradedvecnetwork import (InputVec, OutputVec, GradedVec, + GradedDense, GradedSparse, + ProductVec, + LIFVec, + NormalizeNet) + +from lava.networks.resfire import ResFireVec + +from lava.magma.core.run_conditions import RunSteps, RunContinuous +from lava.magma.core.run_configs import Loihi2SimCfg, Loihi2HwCfg diff --git a/src/lava/networks/gradedvecnetwork.py b/src/lava/networks/gradedvecnetwork.py new file mode 100644 index 000000000..abf163b7d --- /dev/null +++ b/src/lava/networks/gradedvecnetwork.py @@ -0,0 +1,324 @@ +# 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 lava.proc.graded.process import InvSqrt +from lava.proc.graded.process import NormVecDelay +from lava.proc.sparse.process import Sparse +from lava.proc.dense.process import Dense +from lava.proc.prodneuron.process import ProdNeuron +from lava.proc.graded.process import GradedVec as GradedVecProc +from lava.proc.lif.process import LIF +from lava.proc.io import sink, source + +from .network import Network, AlgebraicVector, AlgebraicMatrix + + +class InputVec(AlgebraicVector): + """InputVec + Simple input vector. Adds algebraic syntax to RingBuffer + + Parameters + ---------- + vec : np.ndarray + NxM array of input values. Input will repeat every M steps. + exp : int, optional + Set the fixed point base value + loihi2 : bool, optional + Flag to create the adapters for loihi 2. + """ + + def __init__(self, + vec: np.ndarray, + loihi2: ty.Optional[bool] = False, + exp: ty.Optional[int] = 0, + **kwargs) -> None: + + self.loihi2 = loihi2 + self.shape = np.atleast_2d(vec).shape + self.exp = exp + + # Convert it to fixed point base + vec *= 2**self.exp + + self.inport_plug = source.RingBuffer(data=np.atleast_2d(vec)) + + if self.loihi2: + from lava.proc import embedded_io as eio + self.inport_adapter = eio.spike.PyToNxAdapter( + shape=(self.shape[0],), + num_message_bits=24) + self.inport_plug.s_out.connect(self.inport_adapter.inp) + self.out_port = self.inport_adapter.out + + else: + self.out_port = self.inport_plug.s_out + + def __lshift__(self, other): + # Maybe this could be done with a numpy array and call set_data? + return NotImplemented + + +class OutputVec(Network): + """OutputVec + Records spike output. Adds algebraic syntax to RingBuffer + + Parameters + ---------- + shape : tuple(int) + shape of the output to record + buffer : int, optional + length of the recording. + (buffer is overwritten if shorter than sim time). + loihi2 : bool, optional + Flag to create the adapters for loihi 2. + num_message_bits : int + size of output message. ("0" is for unary spike event). + """ + + def __init__(self, + shape: ty.Tuple[int, ...], + buffer: int = 1, + loihi2: ty.Optional[bool] = False, + num_message_bits: ty.Optional[int] = 24, + **kwargs) -> None: + + self.shape = shape + self.buffer = buffer + self.loihi2 = loihi2 + self.num_message_bits = num_message_bits + + self.outport_plug = sink.RingBuffer( + shape=self.shape, buffer=self.buffer, **kwargs) + + if self.loihi2: + from lava.proc import embedded_io as eio + self.outport_adapter = eio.spike.NxToPyAdapter( + shape=self.shape, num_message_bits=self.num_message_bits) + self.outport_adapter.out.connect(self.outport_plug.a_in) + self.in_port = self.outport_adapter.inp + else: + self.in_port = self.outport_plug.a_in + + def get_data(self): + return (self.outport_plug.data.get().astype(np.int32) << 8) >> 8 + + +class LIFVec(AlgebraicVector): + """LIFVec + Network wrapper to LIF neuron. + + Parameters + ---------- + See lava.proc.lif.process.LIF + """ + + def __init__(self, **kwargs): + self.main = LIF(**kwargs) + + self.in_port = self.main.a_in + self.out_port = self.main.s_out + + +class GradedVec(AlgebraicVector): + """GradedVec + Simple graded threshold vector with no dynamics. + + Parameters + ---------- + shape : tuple(int) + Number and topology of neurons. + vth : int, optional + Threshold for spiking. + exp : int, optional + Fixed point base of the vector. + """ + + def __init__(self, + shape: ty.Tuple[int, ...], + vth: int = 10, + exp: int = 0, + **kwargs): + + self.shape = shape + self.vth = vth + self.exp = exp + + self.main = GradedVecProc(shape=self.shape, vth=self.vth, exp=self.exp) + self.in_port = self.main.a_in + self.out_port = self.main.s_out + + super().__init__() + + def __mul__(self, other): + if isinstance(other, GradedVec): + # Create the product network + prod_layer = ProductVec(shape=self.shape, vth=1, exp=self.exp) + + weightsI = np.eye(self.shape[0]) + + weights_A = GradedSparse(weights=weightsI) + weights_B = GradedSparse(weights=weightsI) + weights_out = GradedSparse(weights=weightsI) + + prod_layer << (weights_A @ self, weights_B @ other) + weights_out @ prod_layer + return weights_out + else: + return NotImplemented + + +class ProductVec(AlgebraicVector): + """ProductVec + + Neuron that will multiply values on two input channels. + + Parameters + ---------- + shape : tuple(int) + Number and topology of neurons. + vth : int + Threshold for spiking. + exp : int + Fixed point base of the vector. + """ + + def __init__(self, + shape: ty.Tuple[int, ...], + vth: ty.Optional[int] = 10, + exp: ty.Optional[int] = 0, + **kwargs): + self.shape = shape + self.vth = vth + self.exp = exp + + self.main = ProdNeuron(shape=self.shape, vth=self.vth, exp=self.exp) + + self.in_port = self.main.a_in1 + self.in_port2 = self.main.a_in2 + + self.out_port = self.main.s_out + + def __lshift__(self, other): + # We're going to override the behavior here, + # since there are two ports the API idea is: + # prod_layer << (conn1, conn2) + if isinstance(other, (list, tuple)): + # It should be only length 2, and a Network object, + # TODO: add checks + other[0].out_port.connect(self.in_port) + other[1].out_port.connect(self.in_port2) + else: + return NotImplemented + + +class GradedDense(AlgebraicMatrix): + """GradedDense + Network wrapper for Dense. Adds algebraic syntax to Dense. + + Parameters + ---------- + See lava.proc.dense.process.Dense + + weights : numpy.ndarray + Weight matrix expressed as floating point. Weights will be automatically + reconfigured to fixed point (may lead to changes due to rounding). + exp : int, optional + Fixed point base of the weight (reconfigures weights/weight_exp). + """ + + def __init__(self, + weights: np.ndarray, + exp: int = 7, + **kwargs): + self.exp = exp + + # Adjust the weights to the fixed point + w = weights * 2 ** self.exp + + self.main = Dense(weights=w, + num_message_bits=24, + num_weight_bits=8, + weight_exp=-self.exp) + + self.in_port = self.main.s_in + self.out_port = self.main.a_out + + +class GradedSparse(AlgebraicMatrix): + """GradedSparse + Network wrapper for Sparse. Adds algebraic syntax to Sparse. + + Parameters + ---------- + See lava.proc.sparse.process.Sparse + + weights : numpy.ndarray + Weight matrix expressed as floating point. Weights will be automatically + reconfigured to fixed point (may lead to changes due to rounding). + exp : int, optional + Fixed point base of the weight (reconfigures weights/weight_exp). + """ + + def __init__(self, + weights: np.ndarray, + exp: int = 7, + **kwargs): + + self.exp = exp + + # Adjust the weights to the fixed point + w = weights * 2 ** self.exp + self.main = Sparse(weights=w, + num_message_bits=24, + num_weight_bits=8, + weight_exp=-self.exp) + + self.in_port = self.main.s_in + self.out_port = self.main.a_out + + +class NormalizeNet(AlgebraicVector): + """NormalizeNet + Creates a layer for normalizing vector inputs + + Parameters + ---------- + shape : tuple(int) + Number and topology of neurons. + exp : int + Fixed point base of the vector. + """ + + def __init__(self, + shape: ty.Tuple[int, ...], + exp: ty.Optional[int] = 12, + **kwargs): + self.shape = shape + self.fpb = exp + + vec_to_fpinv_w = np.ones((1, self.shape[0])) + fpinv_to_vec_w = np.ones((self.shape[0], 1)) + weight_exp = 0 + + self.vfp_dense = Dense(weights=vec_to_fpinv_w, + num_message_bits=24, + weight_exp=-weight_exp) + self.fpv_dense = Dense(weights=fpinv_to_vec_w, + num_message_bits=24, + weight_exp=-weight_exp) + + self.main = NormVecDelay(shape=self.shape, vth=1, + exp=self.fpb) + self.fp_inv_neuron = InvSqrt(shape=(1,), fp_base=self.fpb) + + self.main.s2_out.connect(self.vfp_dense.s_in) + self.vfp_dense.a_out.connect(self.fp_inv_neuron.a_in) + self.fp_inv_neuron.s_out.connect(self.fpv_dense.s_in) + self.fpv_dense.a_out.connect(self.main.a_in2) + + self.in_port = self.main.a_in1 + self.out_port = self.main.s_out diff --git a/src/lava/networks/network.py b/src/lava/networks/network.py new file mode 100644 index 000000000..8e9d1e5e1 --- /dev/null +++ b/src/lava/networks/network.py @@ -0,0 +1,154 @@ +# 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 scipy.sparse import csr_matrix + +from lava.magma.core.process.ports.ports import InPort, OutPort +from lava.magma.core.process.process import AbstractProcess + + +class NetworkList(list): + """NetworkList + This is a list subclass to keep track of Network objects that + are added using the '+' operator. + """ + + def __init__(self, iterable): + super().__init__(iterable) + + +class Network: + """Network + Abstract Network object. + + Networks contain other networks and lava processes. + """ + + in_port: InPort + out_port: OutPort + main: AbstractProcess + + def run(self, **kwargs): + self.main.run(**kwargs) + + def stop(self, **kwargs): + self.main.stop(**kwargs) + + def __lshift__(self, + other): + # Self-referential type hint is causing a NameError + # other: ty.Union[Network, NetworkList]): + """ + Operator overload of "<<" to connect Network objects. + + EPF: note that the precedence could matter if we include more + operators. We want this assignment operator to have lowest + precedence, which "<<" is lower than "+", so it works. However, it + is higher than i.e. "^" which would not work. Comparisons have even + lower precedence, "<=" could be better. + """ + if isinstance(other, Network): + other.out_port.connect(self.in_port) + return self + elif isinstance(other, NetworkList): + for o in other: + self << o + return self + else: + return NotImplemented + + def __add__(self, + other): + # Self-referential typing is causing a NameError + # other: ty.Union[Network, NetworkList]): + """ + Operator overload of "+" to act as summation in algebraic syntax. + """ + if isinstance(other, Network): + return NetworkList([self, other]) + elif isinstance(other, NetworkList): + other.append(self) + return other + else: + return NotImplemented + # When chaining operations this is used for [weights1, weights2] + weights3 + __radd__ = __add__ + + +class AlgebraicVector(Network): + """AlgebraicVector + Provides vector operator syntax for Networks. + """ + + def __lshift__(self, + other): + # Self-referential typing is causing a NameError + # other: ty.Union[AlgebraicVector, Network, NetworkList]): + """ + Operator overload of "<<" to connect AlgebraicVector objects. + """ + + if isinstance(other, AlgebraicVector): + # If a vector is connected to another vector, an Identity + # connection is generated and the two procs are connected. + + # This import statement needs to be here to avoid a circular + # import error + from lava.networks.gradedvecnetwork import GradedSparse + weightsI = csr_matrix(np.eye(np.prod(self.shape))) + I_syn = GradedSparse(weights=weightsI) + other.out_port.connect(I_syn.in_port) + I_syn.out_port.connect(self.in_port) + return self + + elif isinstance(other, Network): + # This will take care of the standard weights to neurons. + other.out_port.connect(self.in_port) + return self + elif isinstance(other, NetworkList): + # When using the plus operator to add + for o in other: + self << o + return self + else: + return NotImplemented + + +class AlgebraicMatrix(Network): + """AlgebraicMatrix + Provides matrix operator syntax for Networks. + """ + + def __matmul__(self, + other): + # Self-referential typing is causing a NameError + # other: AlgebraicVector): + """ + Operator overload of "@" to form matrix-vector product. + """ + if isinstance(other, AlgebraicVector): + other.out_port.connect(self.in_port) + return self + else: + return NotImplemented + + def __mul__(self, + other): + # Self-referential typing is causing a NameError + # other: AlgebraicMatrix): + """ + Operator overload of "*" to for multiplication. + """ + if isinstance(other, AlgebraicMatrix): + from lava.networks.gradedvecnetwork import ProductVec + # How to pass in exp? + prod_layer = ProductVec(shape=self.shape, vth=1, exp=0) + + prod_layer << (self, other) + + return prod_layer + else: + return NotImplemented diff --git a/src/lava/networks/resfire.py b/src/lava/networks/resfire.py new file mode 100644 index 000000000..7712a4649 --- /dev/null +++ b/src/lava/networks/resfire.py @@ -0,0 +1,45 @@ +# Copyright (C) 2022-23 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import numpy as np +from .network import AlgebraicVector +from lava.proc.resfire.process import RFZero + + +class ResFireVec(AlgebraicVector): + """ + Network wrapper for resonate-and-fire neurons + """ + + def __init__(self, **kwargs): + self.uth = kwargs.pop('uth', 10) + self.shape = kwargs.pop('shape', (1,)) + self.freqs = kwargs.pop('freqs', np.array([10])) + self.decay_tau = kwargs.pop('decay_tau', np.array([1])) + self.dt = kwargs.pop('dt', 0.001) + + self.freqs = np.array(self.freqs) + self.decay_tau = np.array(self.decay_tau) + + self.main = RFZero(shape=self.shape, uth=self.uth, + freqs=self.freqs, decay_tau=self.decay_tau, + dt=self.dt) + + self.in_port = self.main.u_in + self.in_port2 = self.main.v_in + + self.out_port = self.main.s_out + + def __lshift__(self, other): + # We're going to override the behavior here + # since theres two ports the API idea is: + # rf_layer << (conn1, conn2) + if isinstance(other, (list, tuple)): + # it should be only length 2, and a Network object, + # add checks + other[0].out_port.connect(self.in_port) + other[1].out_port.connect(self.in_port2) + else: + # in this case we will just connect to in_port + super().__lshift__(other) diff --git a/src/lava/proc/graded/models.py b/src/lava/proc/graded/models.py index b87f925a9..969caa1d4 100644 --- a/src/lava/proc/graded/models.py +++ b/src/lava/proc/graded/models.py @@ -92,7 +92,7 @@ def run_spk(self) -> None: @implements(proc=InvSqrt, protocol=LoihiProtocol) @requires(CPU) -@tag('float') +@tag('floating_pt') class InvSqrtModelFloat(PyLoihiProcessModel): """Implementation of InvSqrt in floating point""" a_in = LavaPyType(PyInPort.VEC_DENSE, float) @@ -111,9 +111,19 @@ def run_spk(self) -> None: self.s_out.send(sp_out) -def make_fpinv_table(fp_base): +def make_fpinv_table(fp_base: int) -> np.ndarray: """ - Creates the table for fp inverse algorithm. + Creates the table for fp inverse square root algorithm. + + Parameters + ---------- + fp_base : int + Base of the fixed point. + + Returns + ------- + Y_est : np.ndarray + Initialization look-up table for fp inverse square root. """ n_bits = 24 B = 2**fp_base @@ -121,22 +131,49 @@ def make_fpinv_table(fp_base): Y_est = np.zeros((n_bits), dtype='int') n_adj = 1.238982962 - for m in range(n_bits): # span the 24 bits, negate the decimal base + for m in range(n_bits): # Span the 24 bits, negate the decimal base Y_est[n_bits - m - 1] = 2 * int(B / (2**((m - fp_base) / 2) * n_adj)) return Y_est -def clz(val): +def clz(val: int) -> int: """ Count lead zeros. + + Parameters + ---------- + val : int + Integer value for counting lead zeros. + + Returns + ------- + out_val : int + Number of leading zeros. """ - return (24 - (int(np.log2(val)) + 1)) + out_val = (24 - (int(np.log2(val)) + 1)) + return out_val -def inv_sqrt(s_fp, n_iters=5, b_fraction=12): +def inv_sqrt(s_fp: int, + n_iters: int = 5, + b_fraction: int = 12) -> int: """ - Runs the fixed point inverse square root algorithm + Runs the fixed point inverse square root algorithm. + + Parameters + ---------- + s_fp : int + Fixed point value to calulate inverse square root. + n_iters : int, optional + Number of iterations for fixed point inverse square root algorithm. + b_fraction : int, optional + Fixed point base. + + Returns + ------- + y_i : int + Approximate inverse square root in fixed point. """ Y_est = make_fpinv_table(b_fraction) m = clz(s_fp) diff --git a/src/lava/proc/graded/process.py b/src/lava/proc/graded/process.py index 6c5943a46..ed7c41b3f 100644 --- a/src/lava/proc/graded/process.py +++ b/src/lava/proc/graded/process.py @@ -10,12 +10,23 @@ from lava.magma.core.process.ports.ports import InPort, OutPort -def loihi2round(vv): +def loihi2round(vv: np.ndarray) -> np.ndarray: """ Round values in numpy array the way loihi 2 performs rounding/truncation. + + Parameters + ---------- + vv : np.ndarray + Input values to be rounded consistent with loihi2 rouding. + + Returns + ------- + vv_r : np.ndarray + Output values rounded consistent with loihi2 rouding. """ - return np.fix(vv + (vv > 0) - 0.5).astype('int') + vv_r = np.fix(vv + (vv > 0) - 0.5).astype('int') + return vv_r class GradedVec(AbstractProcess): @@ -28,12 +39,12 @@ class GradedVec(AbstractProcess): Parameters ---------- - shape: tuple(int) - number and topology of neurons - vth: int - threshold for spiking - exp: int - fixed point base + shape : tuple(int) + Number and topology of neurons. + vth : int + Threshold for spiking. + exp : int + Fixed point base. """ def __init__( @@ -78,12 +89,12 @@ class NormVecDelay(AbstractProcess): Parameters ---------- - shape: tuple(int) - number and topology of neurons - vth: int - threshold for spiking - exp: int - fixed point base + shape : tuple(int) + Number and topology of neurons. + vth : int + Threshold for spiking. + exp : int + Fixed point base. """ def __init__( @@ -123,8 +134,11 @@ class InvSqrt(AbstractProcess): Parameters ---------- + shape : tuple(int) + Number and topology of neurons. + fp_base : int - Base of the fixed-point representation + Base of the fixed-point representation. """ def __init__( @@ -133,7 +147,7 @@ def __init__( fp_base: ty.Optional[int] = 12) -> None: super().__init__(shape=shape) - # base of the decimal point + # Base of the decimal point self.fp_base = Var(shape=(1,), init=fp_base) self.a_in = InPort(shape=shape) self.s_out = OutPort(shape=shape) diff --git a/src/lava/proc/prodneuron/process.py b/src/lava/proc/prodneuron/process.py index 417ed5490..94d1a10c9 100644 --- a/src/lava/proc/prodneuron/process.py +++ b/src/lava/proc/prodneuron/process.py @@ -20,7 +20,7 @@ def __init__( Multiplies two graded inputs and outputs result as graded spike. v[t] = (a_in1 * a_in2) >> exp - s_out = v[t] * (v[t] > vth) + s_out = v[t] * (|v[t]| > vth) Parameters ---------- diff --git a/tests/lava/frameworks/__init__.py b/tests/lava/frameworks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lava/frameworks/test_frameworks.py b/tests/lava/frameworks/test_frameworks.py new file mode 100644 index 000000000..e833995c3 --- /dev/null +++ b/tests/lava/frameworks/test_frameworks.py @@ -0,0 +1,17 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import unittest + + +class TestFrameworks(unittest.TestCase): + """Tests for framework import.""" + + def test_frameworks_loihi2_import(self): + """Tests if framework import fails.""" + import lava.frameworks.loihi2 as lv + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/networks/__init__.py b/tests/lava/networks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lava/networks/test_networks.py b/tests/lava/networks/test_networks.py new file mode 100644 index 000000000..317977c92 --- /dev/null +++ b/tests/lava/networks/test_networks.py @@ -0,0 +1,28 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import unittest +import numpy as np +from scipy.sparse import csr_matrix + +import lava.frameworks.loihi2 as lv + + +class TestNetworks(unittest.TestCase): + """Tests for LVA Networks.""" + + def test_networks_instantiate(self): + """Tests if LVA Networks can be instantiated.""" + inputvec = lv.InputVec(np.ones((1,)), shape=(1,)) + outputvec = lv.OutputVec(shape=(1,), buffer=1) + threshvec = lv.GradedVec(shape=(1,)) + gradeddense = lv.GradedDense(weights=np.ones((1, 1))) + gradedsparse = lv.GradedSparse(weights=csr_matrix(np.ones((1, 1)))) + productvec = lv.ProductVec(shape=(1,)) + lifvec = lv.LIFVec(shape=(1,)) + normnet = lv.NormalizeNet(shape=(1,)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/proc/graded/test_graded.py b/tests/lava/proc/graded/test_graded.py index b4c4b5027..b2acbe882 100644 --- a/tests/lava/proc/graded/test_graded.py +++ b/tests/lava/proc/graded/test_graded.py @@ -17,10 +17,10 @@ class TestGradedVecProc(unittest.TestCase): - """Tests for GradedVec""" + """Tests for GradedVec.""" def test_gradedvec_dot_dense(self): - """Tests that GradedVec and Dense computes dot product""" + """Tests that GradedVec and Dense computes dot product.""" num_steps = 10 v_thresh = 1 @@ -59,7 +59,7 @@ def test_gradedvec_dot_dense(self): self.assertTrue(np.all(out_data[:, (3, 7)] == expected_out[:, (2, 6)])) def test_gradedvec_dot_sparse(self): - """Tests that GradedVec and Dense computes dot product""" + """Tests that GradedVec and Dense computes dot product.""" num_steps = 10 v_thresh = 1 @@ -103,8 +103,8 @@ class TestInvSqrtProc(unittest.TestCase): """Tests for inverse square process.""" def test_invsqrt_calc(self): - """Checks the InvSqrt calculation""" - fp_base = 12 # base of the decimal point + """Checks the InvSqrt calculation.""" + fp_base = 12 # Base of the decimal point num_steps = 25 weights1 = np.zeros((1, 1)) @@ -147,10 +147,10 @@ def test_invsqrt_calc(self): class TestNormVecDelayProc(unittest.TestCase): - """Tests for NormVecDelay""" + """Tests for NormVecDelay.""" def test_norm_vec_delay_out1(self): - """Checks the first channel output of NormVecDelay""" + """Checks the first channel output of NormVecDelay.""" weight_exp = 7 num_steps = 10 @@ -203,17 +203,19 @@ def test_norm_vec_delay_out1(self): ch1 = (weights1 @ inp_data1) / 2**weight_exp ch2 = (weights2 @ inp_data2) / 2**weight_exp - # I'm using roll to account for the two step delay but hacky - # be careful if inputs change - # hmm.. there seems to be a delay step missing compared to + # I'm using roll to account for the two step delay in NormVecDelay. + # However, this is a hack, as the inputs need to be 0 at the end + # of the simulation, since roll wraps the values. + # Be wary that this potentially won't be correct with different inputs. + # There seems to be a delay step missing compared to # ncmodel, not sure where the delay should go... expected_out = np.roll(ch1, 1) * ch2 - # Then there is one extra timestep from hardware + # Then there is one extra delay timestep from hardware self.assertTrue(np.all(expected_out[:, :-1] == out_data[:, 1:])) def test_norm_vec_delay_out2(self): - """Checks the second channel output of NormVecDelay""" + """Checks the second channel output of NormVecDelay.""" weight_exp = 7 num_steps = 10 @@ -265,7 +267,7 @@ def test_norm_vec_delay_out2(self): ch1 = (weights1 @ inp_data1) / 2**weight_exp expected_out = ch1 ** 2 - # then there is one extra timestep from hardware + # Then there is one extra timestep from hardware self.assertTrue(np.all(expected_out[:, :-1] == out_data[:, 1:])) diff --git a/tests/lava/proc/prodneuron/test_prod_neuron.py b/tests/lava/proc/prodneuron/test_prod_neuron.py index 13919d3b9..65eb2bf2e 100644 --- a/tests/lava/proc/prodneuron/test_prod_neuron.py +++ b/tests/lava/proc/prodneuron/test_prod_neuron.py @@ -14,7 +14,10 @@ class TestProdNeuronProc(unittest.TestCase): + """Tests for ProdNeuron.""" + def test_prod_neuron_out(self): + """Tests prod neuron calcultion is correct.""" weight_exp = 7 num_steps = 10 @@ -68,7 +71,7 @@ def test_prod_neuron_out(self): ch2 = (weights2 @ inp_data2) / 2**weight_exp expected_out = ch1 * ch2 - # then there is one extra timestep from hardware + # Then there is one extra timestep from hardware self.assertTrue(np.all(expected_out[:, :-1] == out_data[:, 1:])) diff --git a/tests/lava/tutorials/test_tutorials-lva.py b/tests/lava/tutorials/test_tutorials-lva.py new file mode 100644 index 000000000..7234c5de4 --- /dev/null +++ b/tests/lava/tutorials/test_tutorials-lva.py @@ -0,0 +1,222 @@ +# Copyright (C) 2022-2024 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import glob +import os +import platform +import subprocess # noqa S404 +import sys +import tempfile +import typing as ty +import unittest +from test import support + +import lava +import nbformat + +import tutorials + + +class TestTutorials(unittest.TestCase): + """Export notebook, execute to check for errors.""" + + system_name = platform.system().lower() + + def _execute_notebook( + self, base_dir: str, path: str + ) -> ty.Tuple[ty.Type[nbformat.NotebookNode], ty.List[str]]: + """Execute a notebook via nbconvert and collect output. + + Parameters + ---------- + base_dir : str + notebook search directory + path : str + path to notebook + + Returns + ------- + Tuple + (parsed nbformat.NotebookNode object, list of execution errors) + """ + + cwd = os.getcwd() + dir_name, notebook = os.path.split(path) + try: + env = self._update_pythonpath(base_dir, dir_name) + nb = self._convert_and_execute_notebook(notebook, env) + errors = self._collect_errors_from_all_cells(nb) + except Exception as e: + nb = None + errors = str(e) + finally: + os.chdir(cwd) + + return nb, errors + + def _update_pythonpath( + self, base_dir: str, dir_name: str + ) -> ty.Dict[str, str]: + """Update PYTHONPATH with notebook location. + + Parameters + ---------- + base_dir : str + Parent directory to use + dir_name : str + Directory containing notebook + + Returns + ------- + env : dict + Updated dictionary of environment variables + """ + os.chdir(base_dir + "/" + dir_name) + + env = os.environ.copy() + module_path = [lava.__path__.__dict__["_path"][0]] + + module_path.extend( + [os.path.dirname(module_path[0]), env.get("PYTHONPATH", "")] + ) + + sys_path = ":".join(map(str, sys.path)) + env_path = env.get("PYTHONPATH", "") + mod_path = ":".join(map(str, module_path)) + + env["PYTHONPATH"] = env_path + ":" + mod_path + ":" + sys_path + + return env + + def _convert_and_execute_notebook( + self, notebook: str, env: ty.Dict[str, str] + ) -> ty.Type[nbformat.NotebookNode]: + """Covert notebook and execute it. + + Parameters + ---------- + notebook : str + Notebook name + env : dict + Dictionary of environment variables + + Returns + ------- + nb : nbformat.NotebookNode + Notebook dict-like node with attribute-access + """ + with tempfile.NamedTemporaryFile(mode="w+t", suffix=".ipynb") as fout: + args = [ + "jupyter", + "nbconvert", + "--to", + "notebook", + "--execute", + "--ExecutePreprocessor.timeout=-1", + "--output", + fout.name, + notebook, + ] + subprocess.check_call(args, env=env) # nosec # noqa: S603 + + fout.seek(0) + return nbformat.read(fout, nbformat.current_nbformat) + + def _collect_errors_from_all_cells( + self, nb: nbformat.NotebookNode + ) -> ty.List[str]: + """Collect errors from executed notebook. + + Parameters + ---------- + nb : nbformat.NotebookNode + Notebook to search for errors + + Returns + ------- + List + Collection of errors + """ + errors = [] + for cell in nb.cells: + if "outputs" in cell: + for output in cell["outputs"]: + if output.output_type == "error": + errors.append(output) + return errors + + def _run_notebook(self, notebook: str): + """Run a specific notebook + + Parameters + ---------- + notebook : str + name of notebook to run + """ + cwd = os.getcwd() + tutorials_temp_directory = tutorials.__path__.__dict__["_path"][0] + tutorials_directory = "" + + tutorials_directory = os.path.realpath(tutorials_temp_directory) + os.chdir(tutorials_directory) + + errors_record = {} + + try: + glob_pattern = "**/{}".format(notebook) + discovered_notebooks = sorted( + glob.glob(glob_pattern, recursive=True) + ) + + self.assertTrue( + len(discovered_notebooks) != 0, + "Notebook not found. Input to function {}".format(notebook), + ) + + # If the notebook is found execute it and store any errors + for notebook_name in discovered_notebooks: + nb, errors = self._execute_notebook( + str(tutorials_directory), notebook_name + ) + errors_joined = ( + "\n".join(errors) if isinstance(errors, list) else errors + ) + if errors: + errors_record[notebook_name] = (errors_joined, nb) + + self.assertFalse( + errors_record, + "Failed to execute Jupyter Notebooks \ + with errors: \n {}".format( + errors_record + ), + ) + finally: + os.chdir(cwd) + + @unittest.skipIf(system_name != "linux", "Tests work on linux") + def test_lava_va_01(self): + """Test tutorial lava va 01, Fixed point dot product.""" + self._run_notebook("lava_va/Tutorial01-Fixed_point_dot_product.ipynb") + + @unittest.skipIf(system_name != "linux", "Tests work on linux") + def test_lava_va_02(self): + """Test tutorial lava va 02, Fixed point element-wise product.""" + self._run_notebook( + "lava_va/" + + "Tutorial02-Fixed_point_elementwise_product.ipynb") + + @unittest.skipIf(system_name != "linux", "Tests work on linux") + def test_lava_va_03(self): + """Test tutorial lava va 03, Normalization Network.""" + self._run_notebook("lava_va/Tutorial03-Normalization_network.ipynb") + + @unittest.skipIf(system_name != "linux", "Tests work on linux") + def test_lava_va_04(self): + """Test tutorial lava va 04, Creating network motifs.""" + self._run_notebook("lava_va/Tutorial04-Creating_network_motifs.ipynb") + + +if __name__ == "__main__": + support.run_unittest(TestTutorials) diff --git a/tutorials/lava_va/Tutorial01-Fixed_point_dot_product.ipynb b/tutorials/lava_va/Tutorial01-Fixed_point_dot_product.ipynb new file mode 100644 index 000000000..fbed7bd78 --- /dev/null +++ b/tutorials/lava_va/Tutorial01-Fixed_point_dot_product.ipynb @@ -0,0 +1,672 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3cd2e32f", + "metadata": {}, + "source": [ + "*Copyright (C) 2022-23 Intel Corporation*
\n", + "*SPDX-License-Identifier: BSD-3-Clause*
\n", + "*See: https://spdx.org/licenses/*\n", + "\n", + "---\n", + "\n", + "# Tutorial 1: An Introduction to Graded Spikes and Fixed-point computations\n", + "\n", + "**Motivation:** In this tutorial, we will discuss the basics of Lava vector algebra API and computing with graded spikes on Loihi 2. This tutorial will demonstrate simple dot-product matrix operations using graded spikes.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "72b82564", + "metadata": {}, + "outputs": [], + "source": [ + "from pylab import *" + ] + }, + { + "cell_type": "markdown", + "id": "816aceb0", + "metadata": {}, + "source": [ + "Lava-VA includes a new set of processes that are compatible with Loihi 2. \n", + "\n", + "First, we can import some of the standard library using an import package. These are designed to make importing the standard libraries more simple and accessible.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "af469544", + "metadata": {}, + "outputs": [], + "source": [ + "import lava.frameworks.loihi2 as lv" + ] + }, + { + "cell_type": "markdown", + "id": "3a39c84c", + "metadata": {}, + "source": [ + "Next, we'll get access to Loihi 2, or we can use the CPU backend." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cf6e92dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on Loihi 2\n" + ] + } + ], + "source": [ + "from lava.utils import loihi\n", + "\n", + "loihi.use_slurm_host(loihi_gen=loihi.ChipGeneration.N3B3)\n", + "use_loihi2 = loihi.is_installed()\n", + "\n", + "if use_loihi2:\n", + " run_cfg = lv.Loihi2HwCfg()\n", + " print(\"Running on Loihi 2\")\n", + "else:\n", + " run_cfg = lv.Loihi2SimCfg(select_tag='fixed_pt')\n", + " print(\"Loihi2 compiler is not available in this system. \"\n", + " \"This tutorial will execute on CPU backend.\")" + ] + }, + { + "cell_type": "markdown", + "id": "d2816267", + "metadata": {}, + "source": [ + "Now, lets setup some inputs, and create the structure for our Loihi 2 algorithm. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9c3c5a76", + "metadata": {}, + "outputs": [], + "source": [ + "vec = np.array([40, 30, 20, 10])\n", + "weights = np.zeros((3,4))\n", + "weights[:, 0] = [8, 9, -7]\n", + "weights[:, 1] = [9, 8, -5]\n", + "weights[:, 2] = [8, -10, -4]\n", + "weights[:, 3] = [8, -10, -3]\n", + "\n", + "# Note: we define the weights using floating points,\n", + "# this will create the equivalent fixed-point \n", + "# representation on Loihi 2. We use the weight_exp to \n", + "# set the dynamic range. The dynamic range is:\n", + "# weight_exp = 8 -- [-1, 1)\n", + "# weight_exp = 7 -- [-2, 2)\n", + "# weight_exp = 6 -- [-4, 4)\n", + "# ...\n", + "# weight_exp = 1 -- [-128, 128)\n", + "weights /= 10\n", + "weight_exp = 7" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "48b567d1", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps=16" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "09c97cca", + "metadata": {}, + "outputs": [], + "source": [ + "inp_data = np.zeros((vec.shape[0], num_steps))\n", + "inp_data[:, 1] = vec.ravel()\n", + "inp_data[:, 3] = 4*vec.ravel()\n", + "inp_data[:, 5] = 16*vec.ravel()\n", + "inp_data[:, 7] = 64*vec.ravel()\n", + "inp_data[:, 9] = 256*vec.ravel()" + ] + }, + { + "cell_type": "markdown", + "id": "41d911d8", + "metadata": {}, + "source": [ + "In this case, I have created an input vector and some weights, and then I will send the input vector in with different magnitudes at different timesteps." + ] + }, + { + "cell_type": "markdown", + "id": "e88436b4", + "metadata": {}, + "source": [ + "Next, we use the standard library to create the input layer, the synaptic weights, the neuron layer, and the readout layer." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7590a9fc", + "metadata": {}, + "outputs": [], + "source": [ + "invec = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "\n", + "in_out_syn = lv.GradedDense(weights=weights, exp=weight_exp)\n", + "\n", + "outvec = lv.GradedVec(shape=(weights.shape[0],), vth=1)\n", + "\n", + "out_monitor = lv.OutputVec(shape=outvec.shape, buffer=num_steps, loihi2=use_loihi2)\n" + ] + }, + { + "cell_type": "markdown", + "id": "129d701b", + "metadata": {}, + "source": [ + "There is a new interface that includes the ability to incorporate operator overloading. This allows constructions of Networks based on an algebraic syntax.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "97e62916", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "outvec << in_out_syn @ invec\n", + "out_monitor << outvec" + ] + }, + { + "cell_type": "markdown", + "id": "a7b70c79", + "metadata": {}, + "source": [ + "Now we can run the network." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6b2c0862", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " outvec.run(condition=lv.RunSteps(num_steps=num_steps),\n", + " run_cfg=run_cfg)\n", + " out_data = out_monitor.get_data()\n", + "finally:\n", + " outvec.stop()" + ] + }, + { + "cell_type": "markdown", + "id": "0668d3be", + "metadata": {}, + "source": [ + "What we should see is the dot product of the input vector. Since we incremented the input strength, the entire vector output will also grow proportionally." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c3cad817", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 83, 331, 1321, 5280, 21120],\n", + " [ 30, 119, 473, 1890, 7560],\n", + " [ -54, -218, -868, -3470, -13880]], dtype=int32)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out_data[:,2:11:2]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "552f017a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 83., 30., -54.])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "weights @ vec" + ] + }, + { + "cell_type": "markdown", + "id": "c53991fb", + "metadata": {}, + "source": [ + "There may be some rounding differences due to the rounding of the values, but we see the correct values compared to the numpy calculation." + ] + }, + { + "cell_type": "markdown", + "id": "a93b4a44", + "metadata": {}, + "source": [ + "## Addition operator overload\n", + "\n", + "As a second example we will create two weight matrices and show how the additionn operator overload can be used.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "dd826827", + "metadata": {}, + "outputs": [], + "source": [ + "# Defining two input streams\n", + "vec = np.array([40, 30, 20, 10])\n", + "weights = np.zeros((3,4))\n", + "weights[:, 0] = [8, 9, -7]\n", + "weights[:, 1] = [9, 8, -5]\n", + "weights[:, 2] = [8, -10, -4]\n", + "weights[:, 3] = [8, -10, -3]\n", + "\n", + "vec2 = np.array([50, -50, 20, -20])\n", + "weights2 = np.zeros((3,4))\n", + "weights2[:, 0] = [3, -5, 4]\n", + "weights2[:, 1] = [0, -2, -10]\n", + "weights2[:, 2] = [6, 8, -4]\n", + "weights2[:, 3] = [-5, 7, -7]\n", + "\n", + "weights /= 10\n", + "weights2 /= 10\n", + "\n", + "weight_exp = 7\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1802b046", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps=16\n", + "\n", + "inp_data = np.zeros((vec.shape[0], num_steps))\n", + "inp_data[:, 1] = vec.ravel()\n", + "inp_data[:, 3] = 4*vec.ravel()\n", + "inp_data[:, 5] = 16*vec.ravel()\n", + "inp_data[:, 7] = 64*vec.ravel()\n", + "inp_data[:, 9] = 256*vec.ravel()\n", + "\n", + "inp_data2 = np.zeros((vec2.shape[0], num_steps))\n", + "inp_data2[:, 1] = vec2.ravel()\n", + "inp_data2[:, 3] = 4*vec2.ravel()\n", + "inp_data2[:, 5] = 16*vec2.ravel()\n", + "inp_data2[:, 7] = 64*vec2.ravel()\n", + "inp_data2[:, 9] = 256*vec2.ravel()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a108c232", + "metadata": {}, + "outputs": [], + "source": [ + "# instantiate the objects\n", + "invec1 = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "invec2 = lv.InputVec(inp_data2, loihi2=use_loihi2)\n", + "\n", + "in_out_syn1 = lv.GradedDense(weights=weights, exp=weight_exp)\n", + "in_out_syn2 = lv.GradedDense(weights=weights2, exp=weight_exp)\n", + "\n", + "outvec = lv.GradedVec(shape=(weights.shape[0],), vth=1)\n", + "\n", + "out_monitor = lv.OutputVec(shape=outvec.shape, buffer=num_steps, loihi2=use_loihi2)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "5c7e9258", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# compute the dot product of both input streams and add together\n", + "outvec << in_out_syn1 @ invec1 + in_out_syn2 @ invec2\n", + "out_monitor << outvec" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "e44b8e6f", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " outvec.run(condition=lv.RunSteps(num_steps=num_steps),\n", + " run_cfg=run_cfg) # Loihi2SimCfg(select_tag='fixed_pt')\n", + " out_data = out_monitor.get_data()\n", + "finally:\n", + " outvec.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b0c7abf8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 120, 478, 1909, 7630, 30520],\n", + " [ 17, 69, 271, 1080, 4320],\n", + " [ 22, 83, 340, 1360, 5440]], dtype=int32)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out_data[:,2:11:2]" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "97106421", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([120., 17., 22.])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "weights @ vec + weights2 @ vec2" + ] + }, + { + "cell_type": "markdown", + "id": "3ebf3be9", + "metadata": {}, + "source": [ + "Again we see the output results matching the numpy calculations, perhaps with some differences due to rounding." + ] + }, + { + "cell_type": "markdown", + "id": "4f773ae8", + "metadata": {}, + "source": [ + "## More algebra syntax\n", + "\n", + "Another function that occurs under-the-hood is the creation of Identity connections when connecting vectors. \n", + "\n", + "This can also be supported with the addition operator.\n", + "\n", + "Just have to make sure the vector shapes are correct!" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "aa7f0c48", + "metadata": {}, + "outputs": [], + "source": [ + "# Defining two input streams\n", + "vec = np.array([40, 30, 20, 10])\n", + "weights = np.zeros((4,4))\n", + "weights[:, 0] = [8, 9, -7, -2]\n", + "weights[:, 1] = [9, 8, -5, 2]\n", + "weights[:, 2] = [8, -10, -4, 5]\n", + "weights[:, 3] = [8, -10, -3, -9]\n", + "\n", + "vec2 = np.array([50, -50, 20, -20])\n", + "weights2 = np.zeros((4,4))\n", + "weights2[:, 0] = [3, -5, 4, -6]\n", + "weights2[:, 1] = [0, -2, -10, 0]\n", + "weights2[:, 2] = [6, 8, -4, 4]\n", + "weights2[:, 3] = [-5, 7, -7, 8]\n", + "\n", + "weights3 = np.random.randint(20, size=(4,4)) - 10\n", + "weights3 = weights3 / 10\n", + "\n", + "weights /= 10\n", + "weights2 /= 10\n", + "\n", + "weight_exp = 7" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "7d7aaff8", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps=16\n", + "\n", + "inp_data = np.zeros((vec.shape[0], num_steps))\n", + "inp_data[:, 1] = vec.ravel()\n", + "inp_data[:, 3] = 4*vec.ravel()\n", + "inp_data[:, 5] = 16*vec.ravel()\n", + "inp_data[:, 7] = 64*vec.ravel()\n", + "inp_data[:, 9] = 256*vec.ravel()\n", + "\n", + "inp_data2 = np.zeros((vec2.shape[0], num_steps))\n", + "inp_data2[:, 1] = vec2.ravel()\n", + "inp_data2[:, 3] = 4*vec2.ravel()\n", + "inp_data2[:, 5] = 16*vec2.ravel()\n", + "inp_data2[:, 7] = 64*vec2.ravel()\n", + "inp_data2[:, 9] = 256*vec2.ravel()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "237a8090", + "metadata": {}, + "outputs": [], + "source": [ + "# instantiate the objects\n", + "invec1 = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "invec2 = lv.InputVec(inp_data2, loihi2=use_loihi2)\n", + "\n", + "in_out_syn1 = lv.GradedDense(weights=weights, exp=weight_exp)\n", + "in_out_syn2 = lv.GradedDense(weights=weights2, exp=weight_exp)\n", + "\n", + "extra_syn = lv.GradedDense(weights=weights3, exp=weight_exp)\n", + "\n", + "intvec1 = lv.GradedVec(shape=(weights.shape[0],), vth=1)\n", + "intvec2 = lv.GradedVec(shape=(weights.shape[0],), vth=1)\n", + "\n", + "outvec = lv.GradedVec(shape=(weights.shape[0],), vth=1)\n", + "out_monitor = lv.OutputVec(shape=outvec.shape, buffer=num_steps, loihi2=use_loihi2)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "20055ec5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "intvec1 << in_out_syn1 @ invec1\n", + "intvec2 << in_out_syn2 @ invec2\n", + "\n", + "outvec << intvec1 + intvec2 + extra_syn @ intvec1\n", + "out_monitor << outvec\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "7d9a8b98", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " outvec.run(condition=lv.RunSteps(num_steps=num_steps),\n", + " run_cfg=run_cfg) # Loihi2SimCfg(select_tag='fixed_pt')\n", + " out_data = out_monitor.get_data()\n", + "finally:\n", + " outvec.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "c45ca11b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 168, 670, 2678, 10705, 42817],\n", + " [ -7, -28, -118, -476, -1900],\n", + " [ 123, 484, 1941, 7757, 31030],\n", + " [ -68, -276, -1103, -4415, -17659]], dtype=int32)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out_data[:,3:12:2]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "c7c61f8b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([168.8, -7.4, 123.8, -69.6])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "weights @ vec + weights2 @ vec2 + weights3 @ weights @ vec" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4217586c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/lava_va/Tutorial02-Fixed_point_elementwise_product.ipynb b/tutorials/lava_va/Tutorial02-Fixed_point_elementwise_product.ipynb new file mode 100644 index 000000000..e438802d0 --- /dev/null +++ b/tutorials/lava_va/Tutorial02-Fixed_point_elementwise_product.ipynb @@ -0,0 +1,361 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2e52884c", + "metadata": {}, + "source": [ + "*Copyright (C) 2022-23 Intel Corporation*
\n", + "*SPDX-License-Identifier: BSD-3-Clause*
\n", + "*See: https://spdx.org/licenses/*\n", + "\n", + "---\n", + "\n", + "# Tutorial 2: Elementwise products\n", + "\n", + "**Motivation:** In this tutorial, we will highlight more of the standard library included with Lava-VA. Here we demonstrate the element-wise product of vectors using ProductVec.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "64990f4f", + "metadata": {}, + "source": [ + "First, we make the imports and connect to Loihi 2." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "72b82564", + "metadata": {}, + "outputs": [], + "source": [ + "from pylab import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "af33e13b", + "metadata": {}, + "outputs": [], + "source": [ + "import lava.frameworks.loihi2 as lv" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2a68a562", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on Loihi 2\n" + ] + } + ], + "source": [ + "from lava.utils import loihi\n", + "\n", + "loihi.use_slurm_host(loihi_gen=loihi.ChipGeneration.N3B3)\n", + "use_loihi2 = loihi.is_installed()\n", + "\n", + "if use_loihi2:\n", + " run_cfg = lv.Loihi2HwCfg()\n", + " print(\"Running on Loihi 2\")\n", + "else:\n", + " run_cfg = lv.Loihi2SimCfg(select_tag='fixed_pt')\n", + " print(\"Loihi2 compiler is not available in this system. \"\n", + " \"This tutorial will execute on CPU backend.\")" + ] + }, + { + "cell_type": "markdown", + "id": "c1ff4fcd", + "metadata": {}, + "source": [ + "Next, we will setup the inputs and initialize the input weights." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d0065b52", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps = 10\n", + "weights1 = np.zeros((5,1))\n", + "weights2 = np.zeros((5,1))\n", + "\n", + "weights1[:,0] = [2, 6, 10, -2, -6]\n", + "weights2[:,0] = [4, 8, 12, -4, 8]\n", + "\n", + "weights1 /= 16\n", + "weights2 /= 16\n", + "\n", + "inp_shape = (weights1.shape[1],)\n", + "out_shape = (weights1.shape[0],)\n", + "\n", + "inp_data = np.zeros((inp_shape[0], num_steps))\n", + "inp_data[:, 2] = 16\n", + "inp_data[:, 6] = 32" + ] + }, + { + "cell_type": "markdown", + "id": "5c40fe2e", + "metadata": {}, + "source": [ + "Then we instantiate the objects in the network." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b6f6a23c", + "metadata": {}, + "outputs": [], + "source": [ + "dense1 = lv.GradedDense(weights=weights1)\n", + "dense2 = lv.GradedDense(weights=weights2)\n", + "\n", + "vec = lv.ProductVec(shape=out_shape, vth=1, exp=0)\n", + "\n", + "generator1 = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "generator2 = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "monitor = lv.OutputVec(shape=out_shape, buffer=num_steps,\n", + " loihi2=True)" + ] + }, + { + "cell_type": "markdown", + "id": "5bddeb53", + "metadata": {}, + "source": [ + "In this case, ProductVec is an object that has two input channels. We can access those input channels by concatenating the objects and \"piping\" them into the ProductVec layer. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "73392fbe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec << (dense1 @ generator1, dense2 @ generator2)\n", + "monitor << vec" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "aaff1295", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " vec.run(condition=lv.RunSteps(num_steps=num_steps),\n", + " run_cfg=run_cfg)\n", + " out_data = monitor.get_data()\n", + "finally:\n", + " vec.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "154e2cb5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 8, 32],\n", + " [ 48, 192],\n", + " [ 120, 480],\n", + " [ 8, 32],\n", + " [ -48, -192]], dtype=int32)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out_data[:, (3,7)]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "af0e7ae4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 8., 48., 120., 8., -48.])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(weights1 @ inp_data[:,2]) * (weights2 @ inp_data[:,2])" + ] + }, + { + "cell_type": "markdown", + "id": "37462a5e", + "metadata": {}, + "source": [ + "We can see that this matches the numpy calculation." + ] + }, + { + "cell_type": "markdown", + "id": "8b442585", + "metadata": {}, + "source": [ + "## Multiplication operator overload\n", + "\n", + "Similar to addition, the multiplication operator is overloaded inside of GradedVec to enable the use of algebraic syntax to compute the elementwise product.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cfaeb662", + "metadata": {}, + "outputs": [], + "source": [ + "dense1 = lv.GradedDense(weights=weights1)\n", + "dense2 = lv.GradedDense(weights=weights2)\n", + "\n", + "vec1 = lv.GradedVec(shape=out_shape, vth=1, exp=0)\n", + "vec2 = lv.GradedVec(shape=out_shape, vth=1, exp=0)\n", + "\n", + "outvec = lv.GradedVec(shape=out_shape, vth=1, exp=0)\n", + "\n", + "generator1 = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "generator2 = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "monitor = lv.OutputVec(shape=out_shape, buffer=num_steps,\n", + " loihi2=True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "709a42d5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec1 << dense1 @ generator1\n", + "vec2 << dense2 @ generator2\n", + "outvec << vec1 * vec2\n", + "monitor << outvec" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3a0fc3a4", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " vec1.run(condition=lv.RunSteps(num_steps=num_steps),\n", + " run_cfg=run_cfg)\n", + " out_data = monitor.get_data()\n", + "finally:\n", + " vec1.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "95ca186e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 8],\n", + " [ 48],\n", + " [120],\n", + " [ 8],\n", + " [-48]], dtype=int32)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out_data[:, (5,)] " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e80b63c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/lava_va/Tutorial03-Normalization_network.ipynb b/tutorials/lava_va/Tutorial03-Normalization_network.ipynb new file mode 100644 index 000000000..3651ee8c7 --- /dev/null +++ b/tutorials/lava_va/Tutorial03-Normalization_network.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "37ee11cd", + "metadata": {}, + "source": [ + "*Copyright (C) 2022-23 Intel Corporation*
\n", + "*SPDX-License-Identifier: BSD-3-Clause*
\n", + "*See: https://spdx.org/licenses/*\n", + "\n", + "---\n", + "\n", + "# Tutorial 3: Normalization Network\n", + "\n", + "**Motivation:** In this tutorial, we will highlight more of the standard library included with Lava-VA. Here we demonstrate the Normalization network for computing a normalized vector output from a dot product.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5822757f", + "metadata": {}, + "outputs": [], + "source": [ + "from pylab import *" + ] + }, + { + "cell_type": "markdown", + "id": "2dc9a58e", + "metadata": {}, + "source": [ + "Here again we setup the imports and connect to Loihi 2." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "086079fb", + "metadata": {}, + "outputs": [], + "source": [ + "import lava.frameworks.loihi2 as lv" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "67b4603d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on Loihi 2\n" + ] + } + ], + "source": [ + "from lava.utils import loihi\n", + "\n", + "loihi.use_slurm_host(loihi_gen=loihi.ChipGeneration.N3B3)\n", + "use_loihi2 = loihi.is_installed()\n", + "\n", + "if use_loihi2:\n", + " run_cfg = lv.Loihi2HwCfg()\n", + " print(\"Running on Loihi 2\")\n", + "else:\n", + " run_cfg = lv.Loihi2SimCfg(select_tag='fixed_pt')\n", + " print(\"Loihi2 compiler is not available in this system. \"\n", + " \"This tutorial will execute on CPU backend.\")" + ] + }, + { + "cell_type": "markdown", + "id": "fb525826", + "metadata": {}, + "source": [ + "Now we will create some inputs and initialize the weight matrix." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "82d4446c", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps=20\n", + "weights = np.zeros((10,1))\n", + "\n", + "weights[:,0] = [2, 4, -2, -4, 8, 10, -8, -10, 5, -5]\n", + "weights /= 10\n", + "\n", + "inp_data = np.zeros((weights.shape[1], num_steps))\n", + "\n", + "inp_data[:, 2] = 160\n", + "inp_data[:, 5] = 320\n", + "inp_data[:, 8] = 640\n", + "inp_data[:, 11] = 1280\n" + ] + }, + { + "cell_type": "markdown", + "id": "f77b1746", + "metadata": {}, + "source": [ + "In this demo, we set the input stimulus to be of different magnitudes. The inputs will multiply with the weight values, producing a vector output. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "151073fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0.2],\n", + " [ 0.4],\n", + " [-0.2],\n", + " [-0.4],\n", + " [ 0.8],\n", + " [ 1. ],\n", + " [-0.8],\n", + " [-1. ],\n", + " [ 0.5],\n", + " [-0.5]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "weights" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "678f6873", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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IFZUVMBut8NgMLg8bzAYYDabO3yN2Wz2qL0vOBwOLFiwALfddhuuv/56t+2SkpKwYcMGfPTRR3jrrbfgcDhw66234qeffvJZX1NSUrBp0ybs3LkT69atw7lz53DHHXdItSvbU+L1BoBt27ahuroajz76qNs2Srje7YnXzZNr2pOfE29rbGzEU089hYceeqjTiguefr95y+TJk/Hmm28iNzcXL730Evbu3YspU6bAbrd32F6J13zz5s2IiIjA/fff32k7pVzznrpqyz9dKzIzM3H8+PEu5+lTU1ORmpoqfX3rrbdi5MiReO211/D88897u5sAgClTpkj/Hjt2LFJSUjBo0CC899573frLUyneeOMNTJkyBSaTyW0bJVzvq5HNZsMDDzwAQRCwbt26Ttsq5ftt9uzZ0r/HjBmDsWPHYujQodizZw8mTpzos370xoYNG5CRkdFlYphSrnlPXbMjxNjYWGg0GpSVlbk8X1ZWBqPR2OFrjEajR+29LSsrCzt27MDu3bs9rv0YHByMG264Ad99952Xete1qKgoDB8+3G0flHa9AeD8+fP44osv8B//8R8evU4J11u8bp5c0578nHiLGAzPnz+PnJwcj+vxdfX95itDhgxBbGys234o6ZoDwJdffomioiKPv+cB5Vzz7rpmA6JWq8X48eORm5srPedwOJCbm+vyl31bqampLu0BICcnx217bxEEAVlZWfjwww+xa9cuJCYmenwOu92OY8eOIT4+3gs97J66ujp8//33bvuglOvd1saNGxEXF4dp06Z59DolXO/ExEQYjUaXa2qxWJCfn+/2mvbk58QbxGB49uxZfPHFF4iJifH4HF19v/nKTz/9hMrKSrf9UMo1F73xxhsYP348kpOTPX6tUq55t/k7q8ef3n33XSEkJETYtGmTcPLkSWH+/PlCVFSUYDabBUEQhIcfflh4+umnpfZff/21EBQUJKxYsUI4deqUsHTpUiE4OFg4duyYT/v92GOPCXq9XtizZ49w4cIF6dHQ0CC1ad/35557Tvjss8+E77//XigoKBBmz54t6HQ64cSJEz7r95///Gdhz549wrlz54Svv/5aSEtLE2JjY4Xy8vIO+6yU6y2y2+3CwIEDhaeeeuqKY0q53rW1tcKRI0eEI0eOCACElStXCkeOHJGyMV988UUhKipK+Oijj4Rvv/1WmDFjhpCYmChcvnxZOsc999wjrFmzRvq6q58Tb/fbarUK9913nzBgwAChsLDQ5Xu+qanJbb+7+n7zRd9ra2uFv/zlL0JeXp5w7tw54YsvvhBuvPFGYdiwYUJjY6Pbvvv7motqamqEsLAwYd26dR2ew1/X3Fuu6YAoCIKwZs0aYeDAgYJWqxVuvvlm4cCBA9KxX/ziF8KcOXNc2r/33nvC8OHDBa1WK4wePVr45JNPfNxjZ4p0R4+NGzdKbdr3fcGCBdLnNBgMwtSpU4XDhw/7tN8PPvigEB8fL2i1WqF///7Cgw8+KHz33Xdu+ywIyrjeos8++0wAIBQVFV1xTCnXe/fu3R1+b4h9czgcwuLFiwWDwSCEhIQIEydOvOLzDBo0SFi6dKnLc539nHi73+fOnXP7Pb979263/e7q+80XfW9oaBAmTZok9OvXTwgODhYGDRokzJs374rAprRrLnrttdeE0NBQobq6usNz+OuaewvrIRIREeEavodIRETUFgMiERERGBCJiIgAMCASEREBYEAkIiICwIBIREQEgAGRiIgIAAMiERERAAZEIiIiAAyIREREABgQiYiIAAD/H64bkHOSCymtAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "figure(figsize=(5,3))\n", + "plot(inp_data.T);" + ] + }, + { + "cell_type": "markdown", + "id": "6796f559", + "metadata": {}, + "source": [ + "Note, the increasing amplitude of the input values. Also note the exact timesteps of the input stimulus." + ] + }, + { + "cell_type": "markdown", + "id": "1d8740c8", + "metadata": {}, + "source": [ + "Next, we instantiate the objects from the standard library. The NormalizeNet Network object will act like GradedVec, but its outputs will be normalized. This might be useful for various computations, like the cosine similarity.\n", + "\n", + "Under the hood, NormalizeNet uses a feedback inhibition loop to compute the normalization factor. The sum of squares is sent to an \"inhibitory neuron\", which computes the inverse square root. The inverse square root value is conveyed back to the neuron layer and multiplied with the original input values. The output is then emitted on the primary channel, which is a normalized result of the dot product computation.\n", + "\n", + "Note: because the network receives a recurrent inhibition, each stage takes a timestep. This means that the output is delayed by two timesteps." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ae30a451", + "metadata": {}, + "outputs": [], + "source": [ + "invec = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "in_w = lv.GradedDense(weights=weights)\n", + "norm_layer = lv.NormalizeNet(shape=(weights.shape[0],))\n", + "monitor = lv.OutputVec(shape=(weights.shape[0],), \n", + " buffer=num_steps,\n", + " loihi2=use_loihi2)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "593678ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "norm_layer << in_w @ invec\n", + "monitor << norm_layer" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b54757ad", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " norm_layer.run(condition=lv.RunSteps(num_steps=num_steps),\n", + " run_cfg=run_cfg)\n", + " out_data = monitor.get_data()\n", + "finally:\n", + " norm_layer.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9a083710", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Normalized output channel\n", + "figure(figsize=(5,3))\n", + "plot(out_data.T)" + ] + }, + { + "cell_type": "markdown", + "id": "1450dae6", + "metadata": {}, + "source": [ + "This results in the same vector being output, with a consistent total magnitude, regardless of the input magnitude. Notice that there is some slight perturbation due to rounding and some approximations with the fixed point square root algorithm. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e5a1945", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/lava_va/Tutorial04-Creating_network_motifs.ipynb b/tutorials/lava_va/Tutorial04-Creating_network_motifs.ipynb new file mode 100644 index 000000000..087759fc4 --- /dev/null +++ b/tutorials/lava_va/Tutorial04-Creating_network_motifs.ipynb @@ -0,0 +1,293 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b420cc24", + "metadata": {}, + "source": [ + "*Copyright (C) 2022-23 Intel Corporation*
\n", + "*SPDX-License-Identifier: BSD-3-Clause*
\n", + "*See: https://spdx.org/licenses/*\n", + "\n", + "---\n", + "\n", + "# Tutorial: Creating network motifs\n", + "\n", + "**Motivation:** In this tutorial, we will provide a walkthrough on how to create custom network motifs with Lava-VA. The Lava-VA Network is a recursive hierarchical container for creating reusable components. Custom motifs can be created with basic python syntax and links to standard components of the Lava-VA Network.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "72b82564", + "metadata": {}, + "outputs": [], + "source": [ + "from pylab import *" + ] + }, + { + "cell_type": "markdown", + "id": "41f00cf8", + "metadata": {}, + "source": [ + "First, we will import the objects into the lv namespace and connect to Loihi 2." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4d85a200", + "metadata": {}, + "outputs": [], + "source": [ + "import lava.frameworks.loihi2 as lv" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c7047153", + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.sparse import csr_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2a68a562", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on Loihi 2\n" + ] + } + ], + "source": [ + "from lava.utils import loihi\n", + "\n", + "loihi.use_slurm_host(loihi_gen=loihi.ChipGeneration.N3B3)\n", + "use_loihi2 = loihi.is_installed()\n", + "#use_loihi2 = False\n", + "\n", + "if use_loihi2:\n", + " run_cfg = lv.Loihi2HwCfg()\n", + " print(\"Running on Loihi 2\")\n", + "else:\n", + " run_cfg = lv.Loihi2SimCfg(select_tag='fixed_pt')\n", + " print(\"Loihi2 compiler is not available in this system. \"\n", + " \"This tutorial will execute on CPU backend.\")" + ] + }, + { + "cell_type": "markdown", + "id": "8f9ad5c7", + "metadata": {}, + "source": [ + "Our goal for this tutorial is to create a simple memory buffer network. This is also often called a shift register in standard digital electronics. \n", + "\n", + "In particular, our design will consist of a population of GradedVec neurons, which transmit graded spike values. We want to connect the population with a recurrent matrix, so that each value is transferred to the neighboring neuron on the next timestep. \n", + "\n", + "Further, we want to incorporate the operator overloading. To do so we can inherit from the AlgebraicVector class that includes the overloading function. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4b250b2d", + "metadata": {}, + "outputs": [], + "source": [ + "from lava.networks.network import AlgebraicVector\n", + "\n", + "class MemoryBuffer(AlgebraicVector):\n", + " def __init__(self, shape):\n", + " self.shape = shape\n", + " \n", + " # Create the weight matrix \n", + " rec_weights = np.roll(np.eye(self.shape[0]), 1, axis=0)\n", + " \n", + " # Instantiate the core Network objects\n", + " self.main = lv.GradedVec(shape=shape, vth=1)\n", + " self.buf_weights = lv.GradedSparse(weights=rec_weights)\n", + " \n", + " # Create the network motif by connecting the recurrent \n", + " # weights to the neural population\n", + " self.main << self.buf_weights @ self.main\n", + "\n", + " # Connect the standard ports\n", + " self.in_port = self.main.in_port\n", + " self.out_port = self.main.out_port\n", + " \n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "cf9bc778", + "metadata": {}, + "source": [ + "Now that we've created our custom network motif, we can use it in a simulation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "521eb200", + "metadata": {}, + "outputs": [], + "source": [ + "num_steps = 20\n", + "mem_buffer_size = (50,)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0881a1ef", + "metadata": {}, + "outputs": [], + "source": [ + "inp_data = np.zeros((1, num_steps))\n", + "\n", + "inp_data[:, 1] = 2\n", + "inp_data[:, 3] = 4\n", + "inp_data[:, 5] = 8\n", + "inp_data[:, 7] = 16\n", + "inp_data[:, 9] = 32\n", + "inp_data[:, 11] = 64\n", + "\n", + "in_weights = np.zeros((mem_buffer_size[0], 1))\n", + "in_weights[0] = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "51d9a920", + "metadata": {}, + "outputs": [], + "source": [ + "invec = lv.InputVec(inp_data, loihi2=use_loihi2)\n", + "\n", + "in_out_syn = lv.GradedDense(weights=in_weights)\n", + "\n", + "memvec = MemoryBuffer(shape=mem_buffer_size)\n", + "\n", + "out_monitor = lv.OutputVec(shape=mem_buffer_size, buffer=num_steps, loihi2=use_loihi2)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f861a87f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "memvec << in_out_syn @ invec\n", + "out_monitor << memvec" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "fcc17544", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " memvec.run(condition=lv.RunSteps(num_steps=num_steps), \n", + " run_cfg=run_cfg)\n", + " out_spike_data = out_monitor.get_data()\n", + "finally:\n", + " memvec.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ac427ab4", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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8wlUW1urM844Q/v27Zvwf9+eDx+lpaVatGiRtm7daj0WiUS0detWtbS05PHKCkNzc7Pq6+vj7n9fX5927NjB/U+DYRi65ZZbtHnzZr344otqbm6Oe37RokUqKSmJu9979uzRgQMHuN9ZEolEFAwGudcT4PLLL9dbb72l3bt3W18XXnihrrvuOuvv3POJc+zYMX3wwQdqaGiY+H/fGbesngQ2btxoBAIBY8OGDca7775r3HTTTUZtba3R1dWV70vzhP7+fuONN94w3njjDUOScf/99xtvvPGG8eGHHxqGYRhr1641amtrjWeeecZ48803jauuuspobm42Tpw4kecrP/ncfPPNRk1NjfHyyy8bhw8ftr6OHz9uveZ73/ueMWfOHOPFF180du3aZbS0tBgtLS15vOqT1w9+8AOjo6PD2L9/v/Hmm28aP/jBDwyfz2f89re/NQyDe50L9t0uhsE9z6a/+Zu/MV5++WVj//79xu9+9zujtbXVmDFjhnHkyBHDMCb2XhdE+DAMw/inf/onY86cOUZpaalx0UUXGdu3b8/3JXnGSy+9ZEga87VixQrDMKLbbe+8806jrq7OCAQCxuWXX27s2bMnvxd9kkp0nyUZ69evt15z4sQJ46/+6q+MqVOnGlOmTDG+9rWvGYcPH87fRZ/EvvOd7xhz5841SktLjZkzZxqXX365FTwMg3udC6PDB/c8e6699lqjoaHBKC0tNU455RTj2muvNfbt22c9P5H32mcYhpF5/QQAAMAZz/d8AACAyYXwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcorwAQAAcur/B57V7ElhMu6FAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot(out_spike_data[:, -1])\n" + ] + }, + { + "cell_type": "markdown", + "id": "92b21619", + "metadata": {}, + "source": [ + "The above network shows the last timestep of the memory buffer in the simulation. Here, we turned a temporal pattern into a spatial pattern using a simple permutation motif. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55b4c89d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}