NIR Bridge — Import Neuromorphic Models into SC-NeuroCore¶

SC-NeuroCore v3.13.3 — NIR (Neuromorphic Intermediate Representation) Integration

This notebook demonstrates how to import NIR graphs into SC-NeuroCore, simulate them with the stochastic computing engine, and inspect the results.

SC-NeuroCore is the first NIR backend targeting FPGA synthesis — every other NIR target is either a simulator or a fixed neuromorphic chip.

Sections:

  1. Build a simple LIF + Affine NIR graph
  2. Import into SC-NeuroCore and inspect topology
  3. Run simulation and plot spike output
  4. Demonstrate fan-in (multiple inputs summed)
  5. Stateless pipeline: Linear → Scale → Threshold
  6. File I/O: save and reload .nir files

© 1998–2026 Miroslav Šotek. All rights reserved. License: GNU AFFERO GENERAL PUBLIC LICENSE v3 | Commercial Licensing Available Contact: www.anulum.li protoscience@anulum.li

In [1]:
import numpy as np
import nir
from sc_neurocore.nir_bridge import from_nir

print(f"NIR version: {nir.__version__}")
print("SC-NeuroCore NIR bridge loaded.")
NIR version: 1.0.7
SC-NeuroCore NIR bridge loaded.

1. Build a LIF + Affine NIR Graph¶

A minimal spiking network: 3 inputs → dense layer (Affine) → 4 LIF neurons → output.

NIR uses shape arrays in input_type/output_type (e.g., np.array([3]) means dimension 3).

In [2]:
n_in, n_out = 3, 4

rng = np.random.RandomState(42)
nodes = {
    "input": nir.Input(input_type={"input": np.array([n_in])}),
    "affine": nir.Affine(
        weight=rng.randn(n_out, n_in).astype(np.float32),
        bias=np.zeros(n_out, dtype=np.float32),
    ),
    "lif": nir.LIF(
        tau=np.full(n_out, 20.0),       # membrane time constant (ms)
        r=np.ones(n_out),               # membrane resistance
        v_leak=np.zeros(n_out),         # resting potential
        v_threshold=np.ones(n_out),     # spike threshold
    ),
    "output": nir.Output(output_type={"output": np.array([n_out])}),
}
edges = [("input", "affine"), ("affine", "lif"), ("lif", "output")]
graph = nir.NIRGraph(nodes=nodes, edges=edges)

print(f"NIR graph: {len(nodes)} nodes, {len(edges)} edges")
for name, node in nodes.items():
    print(f"  {name}: {type(node).__name__}")
NIR graph: 4 nodes, 3 edges
  input: Input
  affine: Affine
  lif: LIF
  output: Output

2. Import into SC-NeuroCore¶

from_nir() parses the NIR graph, maps each node to an SC-NeuroCore primitive, topologically sorts the execution order, and returns an executable SCNetwork.

In [3]:
network = from_nir(graph)
print(network.summary())
SCNetwork: 4 nodes, 3 edges
  input: SCInputNode
  affine: SCAffineNode
  lif: SCLIFNode
  output: SCOutputNode
  inputs: ['input']
  outputs: ['output']

3. Run Simulation and Plot Spikes¶

Drive the network with constant input current for 200 timesteps. LIF neurons accumulate membrane potential and fire when crossing threshold.

In [4]:
input_current = np.array([2.0, 1.5, 0.8])
n_steps = 200

network.reset()
results = network.run({"input": input_current}, steps=n_steps)

# Collect spike trains: shape (n_steps, n_out)
spikes = np.array(results["output"])
total_spikes = spikes.sum(axis=0)

print(f"Simulation: {n_steps} steps, {n_out} neurons")
for i in range(n_out):
    print(f"  Neuron {i}: {int(total_spikes[i])} spikes ({total_spikes[i]/n_steps*1000:.0f} Hz @ 1ms dt)")
Simulation: 200 steps, 4 neurons
  Neuron 0: 6 spikes (30 Hz @ 1ms dt)
  Neuron 1: 20 spikes (100 Hz @ 1ms dt)
  Neuron 2: 33 spikes (165 Hz @ 1ms dt)
  Neuron 3: 0 spikes (0 Hz @ 1ms dt)
In [5]:
# Spike raster plot
try:
    import matplotlib.pyplot as plt

    fig, ax = plt.subplots(figsize=(12, 3))
    for neuron_id in range(n_out):
        spike_times = np.where(spikes[:, neuron_id] > 0)[0]
        ax.scatter(spike_times, np.full_like(spike_times, neuron_id),
                   marker="|", s=100, linewidths=0.8, color=f"C{neuron_id}")
    ax.set_xlabel("Timestep")
    ax.set_ylabel("Neuron ID")
    ax.set_title("NIR LIF Network — Spike Raster")
    ax.set_yticks(range(n_out))
    ax.set_xlim(0, n_steps)
    plt.tight_layout()
    plt.show()
except ImportError:
    print("matplotlib not installed — skipping plot")
No description has been provided for this image

4. Fan-in: Multiple Inputs Summed¶

When two edges converge on a single node, SC-NeuroCore sums their outputs. This matches standard additive synaptic current semantics.

In [6]:
fan_in_nodes = {
    "left": nir.Input(input_type={"input": np.array([1])}),
    "right": nir.Input(input_type={"input": np.array([1])}),
    "scale": nir.Scale(scale=np.array([1.0])),
    "output": nir.Output(output_type={"output": np.array([1])}),
}
fan_in_edges = [("left", "scale"), ("right", "scale"), ("scale", "output")]
fan_in_graph = nir.NIRGraph(nodes=fan_in_nodes, edges=fan_in_edges)

fan_in_net = from_nir(fan_in_graph)
out = fan_in_net.step({"left": np.array([2.0]), "right": np.array([3.0])})
print(f"Fan-in: left=2.0, right=3.0 → output={out['output'][0]:.1f} (expected 5.0)")
Fan-in: left=2.0, right=3.0 → output=5.0 (expected 5.0)

5. Stateless Pipeline: Linear → Scale → Threshold¶

Not all NIR graphs contain spiking neurons. Here we build a purely feedforward pipeline that classifies inputs via thresholding.

In [7]:
pipeline_nodes = {
    "input": nir.Input(input_type={"input": np.array([2])}),
    "linear": nir.Linear(weight=np.array([[1.0, 0.0], [0.0, 1.0]])),
    "scale": nir.Scale(scale=np.array([2.0, 2.0])),
    "threshold": nir.Threshold(threshold=np.array([1.5, 1.5])),
    "output": nir.Output(output_type={"output": np.array([2])}),
}
pipeline_edges = [
    ("input", "linear"),
    ("linear", "scale"),
    ("scale", "threshold"),
    ("threshold", "output"),
]
pipeline_graph = nir.NIRGraph(nodes=pipeline_nodes, edges=pipeline_edges)

pipeline_net = from_nir(pipeline_graph)
print(pipeline_net.summary())
print()

# Test: [1.0, 0.5] → linear: [1.0, 0.5] → scale: [2.0, 1.0] → threshold: [1, 0]
out = pipeline_net.step({"input": np.array([1.0, 0.5])})
print(f"Input: [1.0, 0.5]")
print(f"After linear:    [1.0, 0.5]  (identity)")
print(f"After scale(2x): [2.0, 1.0]")
print(f"After threshold(1.5): {out['output']}  (2.0≥1.5→1, 1.0<1.5→0)")
SCNetwork: 5 nodes, 4 edges
  input: SCInputNode
  linear: SCLinearNode
  scale: SCScaleNode
  threshold: SCThresholdNode
  output: SCOutputNode
  inputs: ['input']
  outputs: ['output']

Input: [1.0, 0.5]
After linear:    [1.0, 0.5]  (identity)
After scale(2x): [2.0, 1.0]
After threshold(1.5): [1. 0.]  (2.0≥1.5→1, 1.0<1.5→0)

6. File I/O: Save and Reload .nir Files¶

NIR graphs serialize to HDF5-based .nir files. SC-NeuroCore can load them directly via from_nir(path).

In [8]:
import tempfile, os

with tempfile.TemporaryDirectory() as tmpdir:
    path = os.path.join(tmpdir, "demo_model.nir")
    nir.write(path, graph)
    print(f"Saved: {path} ({os.path.getsize(path)} bytes)")

    # Reload
    reloaded = from_nir(path)
    print(f"Reloaded: {len(reloaded.nodes)} nodes, {len(reloaded.edges)} edges")

    # Verify same output
    reloaded.reset()
    out_original = network.step({"input": np.array([1.0, 1.0, 1.0])})
    network.reset()
    out_reloaded = reloaded.step({"input": np.array([1.0, 1.0, 1.0])})
    print(f"Original output:  {out_original['output']}")
    print(f"Reloaded output:  {out_reloaded['output']}")
    # Note: outputs may differ due to stochastic noise seeds, but structure is identical
Saved: /tmp/tmpz1c1za2g/demo_model.nir (17856 bytes)
Reloaded: 4 nodes, 3 edges
Original output:  [0. 0. 0. 0.]
Reloaded output:  [0. 0. 0. 0.]

7. Supported Primitives Catalogue¶

Quick reference of all NIR primitives SC-NeuroCore currently handles:

In [9]:
from sc_neurocore.nir_bridge.node_map import NODE_MAP

print(f"Supported NIR primitives ({len(NODE_MAP)}):")
for nir_type in NODE_MAP:
    print(f"  nir.{nir_type.__name__}")

print("\n--- SC-NeuroCore: first NIR backend targeting FPGA synthesis ---")
Supported NIR primitives (18):
  nir.Input
  nir.Output
  nir.LIF
  nir.IF
  nir.LI
  nir.I
  nir.Affine
  nir.Linear
  nir.Scale
  nir.Threshold
  nir.Flatten
  nir.Delay
  nir.CubaLIF
  nir.CubaLI
  nir.SumPool2d
  nir.AvgPool2d
  nir.Conv1d
  nir.Conv2d

--- SC-NeuroCore: first NIR backend targeting FPGA synthesis ---