This page was generated from docs/advanced/graph_mapping.ipynb. Interactive online version:
Graph mapping
You can extend the computational graphing capabilities of Rockpool, by adding new graph.GraphModule
subclasses. These classes can be converted between eachother and the graph can be analysed in order to map networks on to hardware.
Subclassing GraphModule
All GraphModule
classes are dataclass
es, and use the @dataclass
decorator. As below, you must use @dataclass(eq = False, repr = False)
to decorate your subclass, in order to be compatible with the graph mapping subsystem.
The subsystem requires that equality is defined by object ID (hence eq = False
), and provides a human-readable __repr__()
method (hence repr = False
, to avoid using the dataclass
__repr__()
method).
[1]:
# - Switch off warnings
import warnings
warnings.filterwarnings("ignore")
# - Rockpool imports
import rockpool.graph as rg
from dataclasses import dataclass
@dataclass(eq=False, repr=False)
class MyGraphModule(rg.GraphModule):
# - Define parameters as for any dataclass
param1: float
param2: int
param3: list
GraphModule
provides a __post_init__()
method that can be used to perform any validity checks after initialisation. __post_init__()
also ensures that the input_nodes
and output_nodes
are correctly connected to the module being created.
If you override __post_init__()
, you must call super().__post_init__
.
[2]:
class MyGraphModule(MyGraphModule):
# - Any initialisation checks can be performed a __post_init__ method
def __post_init__(self, *args, **kwargs):
# - You *must* call super().__post_init__()
super().__post_init__(*args, **kwargs)
if param1 < param2:
raise ValueError("param1 must be > param2")
GraphModule
provides several methods:
Method |
Purpose |
---|---|
Factory method to instantiate an object with self-created input and output nodes |
|
|
Perform any post-initialisation checks on the module |
Add a |
|
Add a |
|
Remove a |
|
Remove a |
|
Class method: Try to convert a different a |
Transforming GraphModule
s
GraphModule
provides a method _convert_from()
, which is used to transform GraphModule
objects between various subclasses. These conversion rules must be specifically defined — there is no real automatic conversion between classes. If you do not override _convert_from()
then you will not be able to convert other GraphModule
subclasses to objects of your class.
Below is an example implementation of _convert_from()
.
[3]:
import rockpool.graph as rg
from typing import List
from dataclasses import dataclass
@dataclass(eq=False, repr=False)
class MyNeurons(rg.GenericNeurons):
thresholds: List[int]
dt: float
@classmethod
def _convert_from(cls, mod: rg.GraphModule) -> rg.GraphModule:
if isinstance(mod, cls):
# - No need to do anything
return mod
elif isinstance(mod, LIFNeuronWithSynsRealValue):
# - Convert from a real-valued LIF neuron
# - Get a value for `dt` to use in the conversion
if mod.dt is None:
raise ValueError(
f"Graph module of type {type(mod).__name__} has no `dt` set, so cannot convert time constants when converting to {cls.__name__}."
)
# - Get thresholds from source module
thresholds = np.round(np.array(mod.threshold)).astype(int).tolist()
# - Build a new self module to insert into the graph
neurons = cls._factory(
len(mod.input_nodes),
len(mod.output_nodes),
mod.name,
thresholds,
mod.dt,
)
# - Replace the target module and return
rg.replace_module(mod, neurons)
return neurons
else:
raise ValueError(
f"Graph module of type {type(mod).__name__} cannot be converted to a {cls.__name__}"
)
In the example above, the rules match specific subclasses of GraphModule
, and convert them explicitly.
Creating a mapper
The steps in mapping a graph onto some target hardware are generally
Check DRC rules (this permits you to make many assumptions about the graph structure once DRC rules pass)
Convert neuron graph module types to types that match the hardware
Assign hardware IDs to neurons, weights, inputs, outputs
Pull required data from the graph and build an equivalent hardware configuration
Currently there is a mapper for the Xylo architecture in devices.xylo.mapper()
. Look through the code there for an example of building a mapper.
DRC checks
The suggested way to perform DRC checks is to write a set of functions, each of which defines a single design rule, as an check over a graph. If the design rule is violated, then you should raise an error.
Below are examples of a few design rules.
[4]:
import rockpool.graph as rg
from typing import List, Callable
# - Define an error class for DRC violations
class DRCError(ValueError):
pass
def output_nodes_have_neurons_as_source(graph: rg.GraphModule):
# - All output nodes must have a source that is a neuron
for n in graph.output_nodes:
for s in n.source_modules:
if not isinstance(s, rg.GenericNeurons):
raise DRCError(
f"All network outputs must be directly from neurons.\nA network output node {n} has a source {s} which is not a neuron."
)
def first_module_is_a_weight(graph: rg.GraphModule):
# - The first module after the input must be a set of weights
for inp in graph.input_nodes:
for sink in inp.sink_modules:
if not isinstance(sink, rg.LinearWeights):
raise DRCError(
f"The network input must go first through a weight.\nA network input node {inp} has a sink module {sink} which is not a LinearWeight."
)
def le_16_input_channels(graph: rg.GraphModule):
# - Only 16 input channels are supported
if len(graph.input_nodes) > 16:
raise DRCError(
f"Xylo only supports up to 16 input channels. The network requires {len(graph.input_nodes)} input channels."
)
Now we show a suggested way to collect the rules and perform a DRC.
[5]:
# - Collect a list of DRC functions
xylo_drc = [
output_nodes_have_neurons_as_source,
first_module_is_a_weight,
le_16_input_channels,
]
def check_drc(graph, design_rules: List[Callable[[rg.GraphModule], None]]):
"""
Perform a design rule check
"""
for dr in design_rules:
try:
dr(graph)
except DRCError as e:
raise DRCError(
f"Design rule {dr.__name__} triggered an error:\n"
+ "".join([f"{msg}" for msg in e.args])
)
# - To perform the DRC check, use the function like so:
# check_drc(graph, xylo_drc)