Nengo

Nengo#

Nengo is a Python package for building, testing, and deploying neural networks. The examples below shows how to import and export from Nengo to NIR and vice-versa.

Examples:

  • Lorentz oscillator: nir-lorentz.py

    • This script creates a Nengo model that simulates the Lorentz oscillator, maps it to NIR, and then back again into Nengo

  • Leaky integrate-and-fire (LIF) tests nir-test.py

    • Creates a NIR model for an Affine map and LIF population and map it into Nengo

import nengo
import numpy as np

import nir

n = nir.NIRGraph(
    nodes=[
        nir.Input(shape=np.array([3])),
        nir.Affine(weight=np.array([[8, 2, 10], [14, 3, 14]]).T, bias=np.array([1, 2])),
        nir.LIF(
            tau=np.array([1] * 2),
            r=np.array([1] * 2),
            v_leak=np.array([0] * 2),
            v_threshold=np.array([1] * 2),
        ),
        nir.Output(shape=np.array([3])),
    ],
    edges=[(0, 1), (1, 2), (2, 3)],
)


def nir_to_nengo(n, swap_linear_order=False):
    nengo_map = []

    model = nengo.Network()
    with model:
        filters = {}
        for i, obj in enumerate(n.nodes):
            if isinstance(obj, nir.Input):
                node = nengo.Node(np.zeros(obj.shape), label=f"Input {i} {obj.shape}")
                nengo_map.append(node)
            elif isinstance(obj, nir.LIF):
                N = obj.tau.flatten().shape[0]
                ens = nengo.Ensemble(
                    n_neurons=N,
                    dimensions=1,
                    label=f"LIF {i}",
                    neuron_type=nengo.RegularSpiking(
                        nengo.LIFRate(tau_rc=obj.tau[0], tau_ref=0)
                    ),
                    # neuron_type=nengo.LIF(tau_rc=obj.tau[0], tau_ref=0),
                    gain=np.ones(N),
                    bias=np.zeros(N),
                )
                nengo_map.append(ens.neurons)
            elif isinstance(obj, nir.LI):
                filt = nengo.Node(
                    lambda t, x: x,
                    size_in=obj.tau.flatten().shape[0],
                    label=f"LI {i} {obj.tau.shape}",
                )
                filters[filt] = nengo.synapses.Lowpass(obj.tau[0])
                nengo_map.append(filt)
            elif isinstance(obj, nir.Affine):
                weights = obj.weight
                if swap_linear_order:
                    weights = weights.T
                w = nengo.Node(
                    lambda t, x, obj=obj: weights @ x + obj.bias,
                    size_in=weights.shape[1],
                    size_out=weights.shape[0],
                    label=f"({weights.shape[0]}x{weights.shape[1]})",
                )
                nengo_map.append(w)
            elif isinstance(obj, nir.Output):
                nengo_map.append(
                    None
                )  # because NIR spec doesn't tell me the size, I can't create this yet
            else:
                raise Exception(f"Unknown NIR object: {obj}")
        for pre, post in n.edges:
            if nengo_map[post] is None:
                output = nengo.Node(
                    lambda t, x: x,
                    size_in=nengo_map[pre].size_out,
                    label=f"Output {post}",
                )
                nengo_map[post] = output
            synapse = filters.get(nengo_map[post], None)

            if nengo_map[pre].size_out != nengo_map[post].size_in:
                print("Error")
                print("pre", nengo_map[pre])
                print("post", nengo_map[post])
                1 / 0

            else:
                nengo.Connection(nengo_map[pre], nengo_map[post], synapse=synapse)

    return model


model = nir_to_nengo(n, swap_linear_order=True)