Implementation of the SPA syntax¶
The SPA syntax is implemented with Python operator overloading at its core. There is a hierarchy of certain sets to consider in this.
An operation on two
SemanticPointer
instances will always produce anotherSemanticPointer
instance (or scalar in case of a dot product). Thus, the set ofSemanticPointer
is closed under most operations.Next is the set of symbols created with
nengo_spa.sym
. These will be instances ofPointerSymbol
and any operation on two of these instance will give anotherPointerSymbol
. For example,PointerSymbol('A') * PointerSymbol('B')
will givePointerSymbol('A*B')
. In addition, some operations are defined betweenFixedScalar
andPointerSymbol
. When using a number in such an operation it will be converted automatically to aFixedScalar
instance.The final set are operations based on module outputs that will change over the course of a simulation. All of these fall into the set of
DynamicNode
instances. The implementation of the operators forDynamicNode
is a little bit more involved as the operands may not just be otherDynamicNode
instances, but alsoPointerSymbol
andSemanticPointer
instances. The result will usually be also aDynamicNode
, but certain specializations exist.Summed
is used for the sum of multiple dynamic outputs,Transformed
is used for a dynamic output to which a constant transformation matrix is applied, andModuleOutput
is the bare output of a SPA module.
NengoSPA used to construct a complete abstract syntax tree (AST) before
constructing any network, but this has since been changed to greedily construct
networks as soon as all the required information has been obtained. For this
historical reason, most of the classes mentioned still live in nengo_spa.ast
.
Furthermore, all of this classes derive from nengo_spa.ast.base.Node
which
specifies the minimal interface of all these classes:
A type attribute used to ensure type safety within the SPA syntax and provides the type a node evaluates to (see below).
A connect_to(sink, **kwargs) method that creates all the network components to implement the node and connects the output to the Nengo object sink. The keyword arguments are to be passed to the
nengo.Connection
.A construct() method that creates all the network components required to implement the node and returns a Nengo object providing the output (instead of connecting into a receiving Nengo object).
For fixed values nodes of the type Fixed
are used that need to implement an
additional evaluate() method that evaluates the fixed expression and returns
the result.
Types¶
Types in NengoSPA are defined by deriving from nengo_spa.types.Type
. Two
types will be considered equal if their name attributes match (as long as
__eq__ is not overwritten). For more complex relationships, the __gt__
method may be overwritten to specify a partial ordering. A call to
a.__gt__(b)
should return True, if (and only if) b can be cast to the
type a. For example, the type for an unspecified vocabulary can be cast to
the type for a specific vocabulary.
Currently, the types TScalar
, TAnyVocab
, TAnyVocabOfDim
, and
TVocabulary
are defined, with partial ordering TScalar < TAnyVocab
,
TAnyVocab < TAnyVocabOfDim
, TAnyVocab < TVocabulary
,
TAnyVocabOfDim(vocab.dimensions) < TVocabulary(vocab)
.
The nengo_spa.types.coerce_types
can be used to determine the smallest type
in the partial ordering enclosing all given types.
Actions and the >>
operator¶
The >>
(right shift) operator for creating connections is also implemented
through operator overloading on the ModuleInput
class. Depending on the
context, it either creates the connection immediately or creates
a RoutedConnection
instance in the context of an nengo_spa.Actions
object.
nengo_spa.actions.ifmax
will then trigger the actual creation of the gated
connection with the appropriate gating.
Application of the operator overloading to nengo_spa.Network
¶
So far the operator overloading has only been done on specific classes deriving
from nengo_spa.ast.base.Node
. However, the operators need to work with SPA
networks. For this, nengo_spa.Network
derives from SpaOperatorMixin
which:
Defines the overloaded operators
Converts the operands to the appropriate
nengo_spa.ast.base.Node
(orModuleInput
) classDelegates to the class’s implementation of the operator.
Furthermore, the operators need to be overloaded for the input and outputs of
a SPA network. These will be basic Nengo objects and should continue to be
usable as such. Thus, the Network.declare_input
and
Network.declare_output
methods will dynamically insert SpaOperatorMixin
into the inheritance list of a single instance. They also register the
associated vocabulary for the type checking.