On this page, we’ll go over the changes between Nengo 1.4 and 2.0. They will first be reviewed heuristically in the section Big Changes, before being broken down practically in Changes to Common Functions
In the old API each object had to be assigned it’s own unique string.
In the new Nengo, you can use strings to identify objects called labels
,
but they are not unique. Instead, if you want to identify an object, you just
make sure to assign it a variable in your network
Previously, each object had a set of origins and terminations, which determined how the object produced output and accepted input, respectively. These two things have been collapsed into a single Connection object, which contains the logic of the origin and termination in one place.
Because the model is defined separately from when it’s built, the performance advantages of having origins and terminations can be accomplished during the build phase of the model instead.
Many other objects have been removed, in order to start with a very minimal set of objects allowing a new user to get up and running without having to spend all the effort of memorizing a large API.
Basically:
A power user can easily divide his code and stop from repeating themselves by encapsulating code that appears in multiple places in a Network.
There is now a clear separation between model definition and model creation/simulation. The motivation behind this is to allow for testing models as they are being created. For example, you can create a model, add a node and an ensemble, and the create a simulator based on that model and run it to make sure that your node and ensemble are doing what you think they’re doing. Then, you can continue adding new objects to your model—this will not be reflected in the simulator that you’ve already created, but you can create a new simulator with this updated model and run it without having to rerun your script from the top. Basically, it allows for a more iterative and interactive modelling process, and makes it more explicit which decisions are made manually and which are automatically determined when the simulator is created. Additionally, this means that the simulator timestep (dt) is not defined until the simulator is created, meaning that you can run the same model with different timesteps to see if there is a marked functional difference.
Many commonly used functions have been simplified or changed to be more explicit.
Old API signature:
nef.Network.make(name, neurons, dimensions, tau_rc, tau_ref, max_rate, intercept, radius, encoders, decoder_noise, eval_points, noise, noise_frequency, mode, add_to_network, node_factory, decoder_sign, seed, quick, storage_code)
A simple example:
nef.Network.make('A', 40, 1, mode='spike')
New API signature:
nengo.Ensemble(neurons, dimensions, radius, encoders, intercepts, max_rates, eval_points, neuron_type, seed, label)
A simple example:
A = nengo.Ensemble(40, 1, neuron_type=nengo.LIF(), label='A')
See nengo.Ensemble
for
a list of properties that can be manipulated.
Network arrays were very tightly coupled with the old API. In the new API, they have been decoupled and are just dumb containers, which you can easily import. The functionality should still be identical, though the syntax has changed.
Old API:
nef.Network.make_array(name, neurons, length, dimensions, **args)
New API:
nengo.networks.EnsembleArray(name, neurons, n_ensembles, dimensions_per_ensemble, **ens_args)
See nengo.networks.EnsembleArray
for more information.
Previously, there were several different ways
to provide input to a Nengo model:
SimpleNode
, FunctionInput
, and others.
All of these use cases should be covered
by nengo.Node
.
In the old API, you could create your own
SimpleNode
, or create a FunctionInput
with:
nef.Network.make_input(name, values, zero_after_time)
In the new API, you create a node with:
nengo.Node(output)
where output
is either a constant value
(float, list, NumPy array), a function, or
None
when passing through values unchanged.
See nengo.Node
for more information.
In the old API, inputs were defined as:
# Piecewise example
net.make_input("contextinput", {0.0:[0, 0.1], 0.5:[1, 0], 1.0:[0, 1]})
# Periodic white noise
net.make_fourier_input('fin1', base=0.1, high=10, power=0.5, seed=12)
Inputs are just nodes whose sole function are to output a function.
Many of the Examples use function output nodes.
Practically, to convert from one to the other, consider this table
that uses an example ensemble called ens
who’s input needs to be
transformed by a two-dimensional identity function, [[1,0],[0,1]]
.
Same, thing but instead of a decoded origin, we want one that connects directly to the ensemble’s neurons.
One more time, but with an output and no transform.
A lot of the complexity of the old API has been pushed down to the constructors of the connection object. In general, old API calls of the form:
nef.Network.connect(pre, post)
are now:
nengo.Connection(pre, post)
However, there are some changes in the additional arguments.
The old API used weight
, index_pre
and index_post
as a shortcut to define transform
;
in the new API, only the transform
can be specified.
There are many NumPy functions that make transforms
easier to specify.
Additionally, we now utilize Python’s slice syntax
to route dimensions easily:
nengo.Connection(pre_1d, post_2d[0])
The keyword argument pstc
has been renamed to synapse
.