Context matters, membership doesn’t

When you create a Nengo object and leave the parameters as their default values, we determine the default values dynamically based on Config objects. Every Network has an associated Config object, and you can make new Config objects for additional flexibility.

Nengo keeps track of the current context. The following example works based on the context.

[1]:
import nengo
[2]:
with nengo.Network() as model:
    model.config[nengo.Ensemble].radius = 2.0
    subnet = nengo.Network()

    with subnet:
        a = nengo.Ensemble(10, 1)
        print(a.radius)
2.0

The radius of a is 2.0 because the current context includes both subnet and model. subnet does not change the default value of the radius, but model does, so it uses the model default of 2.0.

Here’s a similar example.

[3]:
with nengo.Network() as model:
    model.config[nengo.Ensemble].radius = 2.0
    subnet = nengo.Network()

with subnet:
    a = nengo.Ensemble(10, 1)
    print(a.radius)
1.0

While this example looks nearly identical, the difference is that a is created in the context of subnet only; model is not part of the config context. Because of that, when a is created, Nengo sees that no default is set in subnet and uses the global default value of 1.0.

This may seem counterintuitive since subnet is a member of model; it’s stored as a sub-network of the model network. However, Nengo objects are not aware of their parents. This allows subnet to be used the same way whether it’s the top-level network or whether it’s nested twenty layers deep, but it also means that we can’t set defaults based on network membership.

Context details

The configuration context is stored in the nengo.Config class as nengo.Config.context. context is a thread-local list. We add new contexts to the end of that list at the start of with blocks, and pop contexts off of that list at the end of with blocks.

[4]:
# No context
len(nengo.Config.context)
[4]:
0
[5]:
# Model context
with model:
    print(len(nengo.Config.context))
    print(nengo.Config.context[0] is model.config)
1
True
[6]:
# Subnet, Model context
with model:
    with subnet:
        print(len(nengo.Config.context))
        print(nengo.Config.context[0] is model.config)
        print(nengo.Config.context[1] is subnet.config)
2
True
True

If you are not sure what context you’re in, but you want to know what the defaults are in the current context, use nengo.Config.all_defaults. You can optionally pass in the type that you’re interested in.

[7]:
with model:
    with subnet:
        print(nengo.Config.all_defaults(nengo.Ensemble))
Current defaults for Ensemble:
  bias: None
  encoders: ScatteredHypersphere(surface=True)
  eval_points: ScatteredHypersphere()
  gain: None
  intercepts: Uniform(low=-1.0, high=0.9)
  label: None
  max_rates: Uniform(low=200, high=400)
  n_eval_points: None
  neuron_type: LIF()
  noise: None
  normalize_encoders: True
  radius: 2.0
  seed: None
[8]:
with subnet:
    print(nengo.Config.all_defaults(nengo.Ensemble))
Current defaults for Ensemble:
  bias: None
  encoders: ScatteredHypersphere(surface=True)
  eval_points: ScatteredHypersphere()
  gain: None
  intercepts: Uniform(low=-1.0, high=0.9)
  label: None
  max_rates: Uniform(low=200, high=400)
  n_eval_points: None
  neuron_type: LIF()
  noise: None
  normalize_encoders: True
  radius: 1.0
  seed: None

Note above that the radius changes despite the fact that both situations occur in the context of subnet! Configuration outside matters if no default has been set in the immediate context.