Ejemplo n.º 1
0
    def __init__(
            self, network, dt=0.001, seed=None, model=None, progress_bar=True):
        self.closed = False
        self.progress_bar = progress_bar

        if model is None:
            self._model = Model(dt=float(dt),
                                label="%s, dt=%f" % (network, dt),
                                decoder_cache=get_default_decoder_cache())
        else:
            self._model = model

        if network is not None:
            # Build the network into the model
            self._model.build(network, progress_bar=self.progress_bar)

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self._model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self._model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self._model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)
Ejemplo n.º 2
0
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 progress_bar=True,
                 optimize=True):
        self.closed = True  # Start closed in case constructor raises exception
        self.progress_bar = progress_bar
        self.optimize = optimize

        if model is None:
            self.model = Model(
                dt=float(dt),
                label="%s, dt=%f" % (network, dt),
                decoder_cache=get_default_decoder_cache(),
            )
        else:
            self.model = model

        pt = ProgressTracker(progress_bar, Progress("Building", "Build"))
        with pt:
            if network is not None:
                # Build the network into the model
                self.model.build(network,
                                 progress=pt.next_stage("Building", "Build"))

            # Order the steps (they are made in `Simulator.reset`)
            self.dg = operator_dependency_graph(self.model.operators)

            if optimize:
                with pt.next_stage("Building (running optimizer)",
                                   "Optimization"):
                    opmerge_optimize(self.model, self.dg)

        self._step_order = [
            op for op in toposort(self.dg) if hasattr(op, "make_step")
        ]

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Add built states to the raw simulation data dictionary
        self._sim_data = self.model.params

        # Provide a nicer interface to simulation data
        self.data = SimulationData(self._sim_data)

        if seed is None:
            if network is not None and network.seed is not None:
                seed = network.seed + 1
            else:
                seed = np.random.randint(npext.maxint)

        self.closed = False
        self.reset(seed=seed)
Ejemplo n.º 3
0
def test_signaldict(allclose):
    """Tests SignalDict's dict overrides."""
    signaldict = SignalDict()

    scalar = Signal(1.0)

    # Both __getitem__ and __setitem__ raise KeyError
    with pytest.raises(KeyError):
        signaldict[scalar]
    with pytest.raises(KeyError):
        signaldict[scalar] = np.array(1.0)

    signaldict.init(scalar)

    # tests repeat init
    with pytest.raises(SignalError, match="Cannot add signal twice"):
        signaldict.init(scalar)

    assert allclose(signaldict[scalar], np.array(1.0))
    # __getitem__ handles scalars
    assert signaldict[scalar].shape == ()

    one_d = Signal([1.0])
    signaldict.init(one_d)
    assert allclose(signaldict[one_d], np.array([1.0]))
    assert signaldict[one_d].shape == (1, )

    two_d = Signal([[1.0], [1.0]])
    signaldict.init(two_d)
    assert allclose(signaldict[two_d], np.array([[1.0], [1.0]]))
    assert signaldict[two_d].shape == (2, 1)

    # __getitem__ handles views implicitly (note no .init)
    two_d_view = two_d[0, :]
    assert allclose(signaldict[two_d_view], np.array([1.0]))
    assert signaldict[two_d_view].shape == (1, )

    # __setitem__ ensures memory location stays the same
    memloc = signaldict[scalar].__array_interface__["data"][0]
    signaldict[scalar] = np.array(0.0)
    assert allclose(signaldict[scalar], np.array(0.0))
    assert signaldict[scalar].__array_interface__["data"][0] == memloc

    memloc = signaldict[one_d].__array_interface__["data"][0]
    signaldict[one_d] = np.array([0.0])
    assert allclose(signaldict[one_d], np.array([0.0]))
    assert signaldict[one_d].__array_interface__["data"][0] == memloc

    memloc = signaldict[two_d].__array_interface__["data"][0]
    signaldict[two_d] = np.array([[0.0], [0.0]])
    assert allclose(signaldict[two_d], np.array([[0.0], [0.0]]))
    assert signaldict[two_d].__array_interface__["data"][0] == memloc

    # __str__ pretty-prints signals and current values
    # Order not guaranteed for dicts, so we have to loop
    for k in signaldict:
        assert "%s %s" % (repr(k), repr(signaldict[k])) in str(signaldict)
Ejemplo n.º 4
0
def test_signaldict():
    """Tests SignalDict's dict overrides."""
    signaldict = SignalDict()

    scalar = Signal(1.)

    # Both __getitem__ and __setitem__ raise KeyError
    with pytest.raises(KeyError):
        signaldict[scalar]
    with pytest.raises(KeyError):
        signaldict[scalar] = np.array(1.)

    signaldict.init(scalar)
    assert np.allclose(signaldict[scalar], np.array(1.))
    # __getitem__ handles scalars
    assert signaldict[scalar].shape == ()

    one_d = Signal([1.])
    signaldict.init(one_d)
    assert np.allclose(signaldict[one_d], np.array([1.]))
    assert signaldict[one_d].shape == (1, )

    two_d = Signal([[1.], [1.]])
    signaldict.init(two_d)
    assert np.allclose(signaldict[two_d], np.array([[1.], [1.]]))
    assert signaldict[two_d].shape == (2, 1)

    # __getitem__ handles views
    two_d_view = two_d[0, :]
    signaldict.init(two_d_view)
    assert np.allclose(signaldict[two_d_view], np.array([1.]))
    assert signaldict[two_d_view].shape == (1, )

    # __setitem__ ensures memory location stays the same
    memloc = signaldict[scalar].__array_interface__['data'][0]
    signaldict[scalar] = np.array(0.)
    assert np.allclose(signaldict[scalar], np.array(0.))
    assert signaldict[scalar].__array_interface__['data'][0] == memloc

    memloc = signaldict[one_d].__array_interface__['data'][0]
    signaldict[one_d] = np.array([0.])
    assert np.allclose(signaldict[one_d], np.array([0.]))
    assert signaldict[one_d].__array_interface__['data'][0] == memloc

    memloc = signaldict[two_d].__array_interface__['data'][0]
    signaldict[two_d] = np.array([[0.], [0.]])
    assert np.allclose(signaldict[two_d], np.array([[0.], [0.]]))
    assert signaldict[two_d].__array_interface__['data'][0] == memloc

    # __str__ pretty-prints signals and current values
    # Order not guaranteed for dicts, so we have to loop
    for k in signaldict:
        assert "%s %s" % (repr(k), repr(signaldict[k])) in str(signaldict)
Ejemplo n.º 5
0
def test_commonsig_readonly():
    """Test that the common signals cannot be modified."""
    net = nengo.Network(label="test_commonsig")
    model = Model()
    model.build(net)
    signals = SignalDict()

    for sig in itervalues(model.sig['common']):
        signals.init(sig)
        with pytest.raises((ValueError, RuntimeError)):
            signals[sig] = np.array([-1])
        with pytest.raises((ValueError, RuntimeError)):
            signals[sig][...] = np.array([-1])
Ejemplo n.º 6
0
def test_commonsig_readonly():
    """Test that the common signals cannot be modified."""
    net = nengo.Network(label="test_commonsig")
    model = Model()
    model.build(net)
    signals = SignalDict()

    for sig in itervalues(model.sig['common']):
        signals.init(sig)
        with pytest.raises((ValueError, RuntimeError)):
            signals[sig] = np.array([-1])
        with pytest.raises((ValueError, RuntimeError)):
            signals[sig][...] = np.array([-1])
Ejemplo n.º 7
0
def test_signaldict():
    """Tests SignalDict's dict overrides."""
    signaldict = SignalDict()

    scalar = Signal(1.)

    # Both __getitem__ and __setitem__ raise KeyError
    with pytest.raises(KeyError):
        signaldict[scalar]
    with pytest.raises(KeyError):
        signaldict[scalar] = np.array(1.)

    signaldict.init(scalar)
    assert np.allclose(signaldict[scalar], np.array(1.))
    # __getitem__ handles scalars
    assert signaldict[scalar].shape == ()

    one_d = Signal([1.])
    signaldict.init(one_d)
    assert np.allclose(signaldict[one_d], np.array([1.]))
    assert signaldict[one_d].shape == (1,)

    two_d = Signal([[1.], [1.]])
    signaldict.init(two_d)
    assert np.allclose(signaldict[two_d], np.array([[1.], [1.]]))
    assert signaldict[two_d].shape == (2, 1)

    # __getitem__ handles views
    two_d_view = two_d[0, :]
    signaldict.init(two_d_view)
    assert np.allclose(signaldict[two_d_view], np.array([1.]))
    assert signaldict[two_d_view].shape == (1,)

    # __setitem__ ensures memory location stays the same
    memloc = signaldict[scalar].__array_interface__['data'][0]
    signaldict[scalar] = np.array(0.)
    assert np.allclose(signaldict[scalar], np.array(0.))
    assert signaldict[scalar].__array_interface__['data'][0] == memloc

    memloc = signaldict[one_d].__array_interface__['data'][0]
    signaldict[one_d] = np.array([0.])
    assert np.allclose(signaldict[one_d], np.array([0.]))
    assert signaldict[one_d].__array_interface__['data'][0] == memloc

    memloc = signaldict[two_d].__array_interface__['data'][0]
    signaldict[two_d] = np.array([[0.], [0.]])
    assert np.allclose(signaldict[two_d], np.array([[0.], [0.]]))
    assert signaldict[two_d].__array_interface__['data'][0] == memloc

    # __str__ pretty-prints signals and current values
    # Order not guaranteed for dicts, so we have to loop
    for k in signaldict:
        assert "%s %s" % (repr(k), repr(signaldict[k])) in str(signaldict)
Ejemplo n.º 8
0
    def __init__(self, network, dt=0.001, seed=None, model=None):
        self.closed = False

        if model is None:
            dt = float(dt)  # make sure it's a float (for division purposes)
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)
Ejemplo n.º 9
0
 def add_op(self, op):
     #        print(op)
     self.operators.append(op)
     # Fail fast by trying make_step with a temporary sigdict
     signals = SignalDict(__time__=np.asarray(0.0, dtype=self.dtype))
     op.init_signals(signals)
     op.make_step(signals, self.dt, np.random)
Ejemplo n.º 10
0
def test_signal_reshape():
    """Tests Signal.reshape"""
    # check proper shape after reshape
    three_d = Signal(np.ones((2, 2, 2)))
    assert three_d.reshape((8,)).shape == (8,)
    assert three_d.reshape((4, 2)).shape == (4, 2)
    assert three_d.reshape((2, 4)).shape == (2, 4)
    assert three_d.reshape(-1).shape == (8,)
    assert three_d.reshape((4, -1)).shape == (4, 2)
    assert three_d.reshape((-1, 4)).shape == (2, 4)
    assert three_d.reshape((2, -1, 2)).shape == (2, 2, 2)
    assert three_d.reshape((1, 2, 1, 2, 2, 1)).shape == (1, 2, 1, 2, 2, 1)

    # check with non-contiguous arrays (and with offset)
    value = np.arange(20).reshape(5, 4)
    s = Signal(np.array(value), name='s')

    s0slice = slice(0, 3), slice(None, None, 2)
    s0shape = 2, 3
    s0 = s[s0slice].reshape(*s0shape)
    assert s.offset == 0
    assert np.array_equal(s0.initial_value, value[s0slice].reshape(*s0shape))

    s1slice = slice(1, None), slice(None, None, 2)
    s1shape = 2, 4
    s1 = s[s1slice].reshape(s1shape)
    assert s1.offset == 4 * s1.dtype.itemsize
    assert np.array_equal(s1.initial_value, value[s1slice].reshape(s1shape))

    # check error if non-contiguous array cannot be reshaped without copy
    s2slice = slice(None, None, 2), slice(None, None, 2)
    s2shape = 2, 3
    s2 = s[s2slice]
    with pytest.raises(SignalError):
        s2.reshape(s2shape)

    # check that views are working properly (incrementing `s` effects views)
    values = SignalDict()
    values.init(s)
    values.init(s0)
    values.init(s1)

    values[s] += 1
    assert np.array_equal(values[s0], value[s0slice].reshape(s0shape) + 1)
    assert np.array_equal(values[s1], value[s1slice].reshape(s1shape) + 1)
Ejemplo n.º 11
0
def build_temporal_solver(model, solver, conn, rng, transform=None):
    # Unpack the relevant variables from the connection.
    assert isinstance(conn.pre_obj, Ensemble)
    ensemble = conn.pre_obj
    neurons = ensemble.neurons
    neuron_type = ensemble.neuron_type

    # Find the operator that simulates the neurons.
    # We do it this way (instead of using the step_math method)
    # because we don't know the number of state parameters or their shapes.
    ops = list(
        filter(
            lambda op:
            (isinstance(op, SimNeurons) and op.J is model.sig[neurons]['in']),
            model.operators))
    if not len(ops) == 1:  # pragma: no cover
        raise RuntimeError("Expected exactly one operator for simulating "
                           "neurons (%s), found: %s" % (neurons, ops))
    op = ops[0]

    # Create stepper for the neuron model.
    signals = SignalDict()
    op.init_signals(signals)
    step_simneurons = op.make_step(signals, model.dt, rng)

    # Create custom rates method that uses the built neurons.
    def override_rates_method(x, gain, bias):
        n_eval_points, n_neurons = x.shape
        assert ensemble.n_neurons == n_neurons

        a = np.empty((n_eval_points, n_neurons))
        for i, x_t in enumerate(x):
            signals[op.J][...] = neuron_type.current(x_t, gain, bias)
            step_simneurons()
            a[i, :] = signals[op.output]

        if solver.synapse is None:
            return a
        return solver.synapse.filt(a, axis=0, y0=0, dt=model.dt)

    # Hot-swap the rates method while calling the underlying solver.
    # The solver will then call this temporarily created rates method
    # to process each evaluation point.
    save_rates_method = neuron_type.rates
    neuron_type.rates = override_rates_method
    try:
        # Note: passing solver.solver doesn't actually cause solver.solver
        # to be built. It will still use conn.solver. This is because
        # the function decorated with @Builder.register(Solver) actually
        # ignores the solver and considers only the conn. The only point of
        # passing solver.solver here is to invoke its corresponding builder
        # function in case something custom happens to be registered.
        # Note: in nengo>2.8.0 the transform parameter is dropped
        return model.build(solver.solver, conn, rng, transform)

    finally:
        neuron_type.rates = save_rates_method
Ejemplo n.º 12
0
def test_signaldict_reset():
    """Tests SignalDict's reset function."""
    signaldict = SignalDict()
    two_d = Signal([[1.], [1.]])
    signaldict.init(two_d)

    two_d_view = two_d[0, :]
    signaldict.init(two_d_view)

    signaldict[two_d_view] = -0.5
    assert np.allclose(signaldict[two_d], np.array([[-0.5], [1]]))

    signaldict[two_d] = np.array([[-1], [-1]])
    assert np.allclose(signaldict[two_d], np.array([[-1], [-1]]))
    assert np.allclose(signaldict[two_d_view], np.array([-1]))

    signaldict.reset(two_d_view)
    assert np.allclose(signaldict[two_d_view], np.array([1]))
    assert np.allclose(signaldict[two_d], np.array([[1], [-1]]))

    signaldict.reset(two_d)
    assert np.allclose(signaldict[two_d], np.array([[1], [1]]))
Ejemplo n.º 13
0
    def add_op(self, op):
        """Add an operator to the model.

        In addition to adding the operator, this method performs additional
        error checking by calling the operator's ``make_step`` function.
        Calling ``make_step`` catches errors early, such as when signals are
        not properly initialized, which aids debugging. For that reason,
        we recommend calling this method over directly accessing
        the ``operators`` attribute.
        """
        self.operators.append(op)
        # Fail fast by trying make_step with a temporary sigdict
        signals = SignalDict()
        op.init_signals(signals)
        op.make_step(signals, self.dt, np.random)
Ejemplo n.º 14
0
def test_signaldict_reset():
    """Tests SignalDict's reset function."""
    signaldict = SignalDict()
    two_d = Signal([[1.], [1.]])
    signaldict.init(two_d)

    two_d_view = two_d[0, :]
    signaldict.init(two_d_view)

    signaldict[two_d_view] = -0.5
    assert np.allclose(signaldict[two_d], np.array([[-0.5], [1]]))

    signaldict[two_d] = np.array([[-1], [-1]])
    assert np.allclose(signaldict[two_d], np.array([[-1], [-1]]))
    assert np.allclose(signaldict[two_d_view], np.array([-1]))

    signaldict.reset(two_d_view)
    assert np.allclose(signaldict[two_d_view], np.array([1]))
    assert np.allclose(signaldict[two_d], np.array([[1], [-1]]))

    signaldict.reset(two_d)
    assert np.allclose(signaldict[two_d], np.array([[1], [1]]))
Ejemplo n.º 15
0
def test_signal_init(sig_type):
    if sig_type == "sparse_scipy":
        pytest.importorskip("scipy.sparse")

    sig, dense = make_signal(
        sig_type,
        shape=(3, 3),
        indices=np.asarray([[0, 0], [0, 2], [1, 1], [2, 2]]),
        data=[1.0, 2.0, 1.0, 1.5],
    )
    signals = SignalDict()
    signals.init(sig)
    assert np.all(signals[sig] == dense)

    sig.readonly = True
    signals = SignalDict()
    signals.init(sig)
    with pytest.raises((ValueError, RuntimeError, TypeError)):
        signals[sig].data[0] = -1
Ejemplo n.º 16
0
def test_signal_reshape():
    """Tests Signal.reshape"""
    # check proper shape after reshape
    three_d = Signal(np.ones((2, 2, 2)))
    assert three_d.reshape((8, )).shape == (8, )
    assert three_d.reshape((4, 2)).shape == (4, 2)
    assert three_d.reshape((2, 4)).shape == (2, 4)
    assert three_d.reshape(-1).shape == (8, )
    assert three_d.reshape((4, -1)).shape == (4, 2)
    assert three_d.reshape((-1, 4)).shape == (2, 4)
    assert three_d.reshape((2, -1, 2)).shape == (2, 2, 2)
    assert three_d.reshape((1, 2, 1, 2, 2, 1)).shape == (1, 2, 1, 2, 2, 1)

    # check with non-contiguous arrays (and with offset)
    value = np.arange(20).reshape(5, 4)
    s = Signal(np.array(value), name='s')

    s0slice = slice(0, 3), slice(None, None, 2)
    s0shape = 2, 3
    s0 = s[s0slice].reshape(*s0shape)
    assert s.offset == 0
    assert np.array_equal(s0.initial_value, value[s0slice].reshape(*s0shape))

    s1slice = slice(1, None), slice(None, None, 2)
    s1shape = 2, 4
    s1 = s[s1slice].reshape(s1shape)
    assert s1.offset == 4 * s1.dtype.itemsize
    assert np.array_equal(s1.initial_value, value[s1slice].reshape(s1shape))

    # check error if non-contiguous array cannot be reshaped without copy
    s2slice = slice(None, None, 2), slice(None, None, 2)
    s2shape = 2, 3
    s2 = s[s2slice]
    with pytest.raises(SignalError):
        s2.reshape(s2shape)

    # check that views are working properly (incrementing `s` effects views)
    values = SignalDict()
    values.init(s)
    values.init(s0)
    values.init(s1)

    values[s] += 1
    assert np.array_equal(values[s0], value[s0slice].reshape(s0shape) + 1)
    assert np.array_equal(values[s1], value[s1slice].reshape(s1shape) + 1)
Ejemplo n.º 17
0
class Simulator(object):
    """Reference simulator for Nengo models."""

    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float, optional
            The length of a simulator timestep, in seconds.
        seed : int, optional
            A seed for all stochastic operators used in this simulator.
        model : nengo.builder.Model instance or None, optional
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        if model is None:
            dt = float(dt)  # make sure it's a float (for division purposes)
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict(__time__=np.asarray(0.0, dtype=np.float64))
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)

    @property
    def dt(self):
        """The time step of the simulator"""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise AttributeError("Cannot change simulator 'dt'. Please file "
                             "an issue at http://github.com/nengo/nengo"
                             "/issues and describe your use case.")

    @property
    def time(self):
        """The current time of the simulator"""
        return self.signals['__time__'].copy()

    def trange(self, dt=None):
        """Create a range of times matching probe data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., dt).

        Parameters
        ----------
        dt : float (optional)
            The sampling period of the probe to create a range for. If empty,
            will use the default probe sampling period.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)

    def _probe(self):
        """Copy all probed signals to buffers"""
        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else
                      probe.sample_every / self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def step(self):
        """Advance the simulator by `self.dt` seconds.
        """
        self.n_steps += 1
        self.signals['__time__'][...] = self.n_steps * self.dt

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def run(self, time_in_seconds, progress_bar=True):
        """Simulate for the given length of time.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        steps = int(np.round(float(time_in_seconds) / self.dt))
        logger.debug("Running %s for %f seconds, or %d steps",
                     self.model.label, time_in_seconds, steps)
        self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=True):
        """Simulate for the given number of `dt` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        with ProgressTracker(steps, progress_bar) as progress:
            for i in range(steps):
                self.step()
                progress.step()

    def reset(self, seed=None):
        """Reset the simulator state.

        Parameters
        ----------
        seed : int, optional
            A seed for all stochastic operators used in the simulator.
            This will change the random sequences generated for noise
            or inputs (e.g. from Processes), but not the built objects
            (e.g. ensembles, connections).
        """
        if seed is not None:
            self.seed = seed

        self.n_steps = 0
        self.signals['__time__'][...] = 0

        # reset signals
        for key in self.signals:
            if key != '__time__':
                self.signals.reset(key)

        # rebuild steps (resets ops with their own state, like Processes)
        self.rng = np.random.RandomState(self.seed)
        self._steps = [op.make_step(self.signals, self.dt, self.rng)
                       for op in self._step_order]

        # clear probe data
        for probe in self.model.probes:
            self._probe_outputs[probe] = []
Ejemplo n.º 18
0
class Simulator(object):
    """Reference simulator for Nengo models."""

    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float, optional
            The length of a simulator timestep, in seconds.
        seed : int, optional
            A seed for all stochastic operators used in this simulator.
        model : nengo.builder.Model instance or None, optional
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        self.closed = False

        if model is None:
            dt = float(dt)  # make sure it's a float (for division purposes)
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    @property
    def dt(self):
        """The time step of the simulator"""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise ReadonlyError(attr='dt', obj=self)

    def _probe_step_time(self):
        self._n_steps = self.signals[self.model.step].copy()
        self._time = self.signals[self.model.time].copy()

    @property
    def n_steps(self):
        """The current time step of the simulator"""
        return self._n_steps

    @property
    def time(self):
        """The current time of the simulator"""
        return self._time

    def trange(self, dt=None):
        """Create a range of times matching probe data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., dt).

        Parameters
        ----------
        dt : float (optional)
            The sampling period of the probe to create a range for. If empty,
            will use the default probe sampling period.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)

    def _probe(self):
        """Copy all probed signals to buffers"""
        self._probe_step_time()

        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else
                      probe.sample_every / self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def step(self):
        """Advance the simulator by `self.dt` seconds.
        """
        if self.closed:
            raise SimulatorClosed("Simulator cannot run because it is closed.")

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def run(self, time_in_seconds, progress_bar=True):
        """Simulate for the given length of time.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        steps = int(np.round(float(time_in_seconds) / self.dt))
        logger.info("Running %s for %f seconds, or %d steps",
                    self.model.label, time_in_seconds, steps)
        self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=True):
        """Simulate for the given number of `dt` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        with ProgressTracker(steps, progress_bar) as progress:
            for i in range(steps):
                self.step()
                progress.step()

    def reset(self, seed=None):
        """Reset the simulator state.

        Parameters
        ----------
        seed : int, optional
            A seed for all stochastic operators used in the simulator.
            This will change the random sequences generated for noise
            or inputs (e.g. from Processes), but not the built objects
            (e.g. ensembles, connections).
        """
        if self.closed:
            raise SimulatorClosed("Cannot reset closed Simulator.")

        if seed is not None:
            self.seed = seed

        # reset signals
        for key in self.signals:
            self.signals.reset(key)

        # rebuild steps (resets ops with their own state, like Processes)
        self.rng = np.random.RandomState(self.seed)
        self._steps = [op.make_step(self.signals, self.dt, self.rng)
                       for op in self._step_order]

        # clear probe data
        for probe in self.model.probes:
            self._probe_outputs[probe] = []

        self._probe_step_time()

    def close(self):
        """Closes the simulator.

        Any call to ``run``, ``run_steps``, ``step``, and ``reset`` on a closed
        simulator will raise ``SimulatorClosed``.
        """
        self.closed = True
        self.signals = None  # signals may no longer exist on some backends
Ejemplo n.º 19
0
class Simulator(object):
    """Reference simulator for Nengo models."""

    # 'unsupported' defines features unsupported by a simulator.
    # The format is a list of tuples of the form `(test, reason)` with `test`
    # being a string with wildcards (*, ?, [abc], [!abc]) matched against Nengo
    # test paths and names, and `reason` is a string describing why the feature
    # is not supported by the backend. For example:
    #     unsupported = [('test_pes*', 'PES rule not implemented')]
    # would skip all test whose names start with 'test_pes'.
    unsupported = []

    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float, optional
            The length of a simulator timestep, in seconds.
        seed : int, optional
            A seed for all stochastic operators used in this simulator.
        model : nengo.builder.Model instance or None, optional
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        self.closed = False

        if model is None:
            dt = float(dt)  # make sure it's a float (for division purposes)
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)

    def __del__(self):
        """Raise a ResourceWarning if we are deallocated while open."""
        if not self.closed:
            warnings.warn(
                "Simulator with model=%s was deallocated while open. Please "
                "close simulators manually to ensure resources are properly "
                "freed." % self.model, ResourceWarning)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    @property
    def dt(self):
        """The time step of the simulator"""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise ReadonlyError(attr='dt', obj=self)

    def _probe_step_time(self):
        self._n_steps = self.signals[self.model.step].copy()
        self._time = self.signals[self.model.time].copy()

    @property
    def n_steps(self):
        """The current time step of the simulator"""
        return self._n_steps

    @property
    def time(self):
        """The current time of the simulator"""
        return self._time

    def trange(self, dt=None):
        """Create a range of times matching probe data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., dt).

        Parameters
        ----------
        dt : float (optional)
            The sampling period of the probe to create a range for. If empty,
            will use the default probe sampling period.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)

    def _probe(self):
        """Copy all probed signals to buffers"""
        self._probe_step_time()

        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else
                      probe.sample_every / self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def step(self):
        """Advance the simulator by `self.dt` seconds.
        """
        if self.closed:
            raise SimulatorClosed("Simulator cannot run because it is closed.")

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def run(self, time_in_seconds, progress_bar=True):
        """Simulate for the given length of time.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        steps = int(np.round(float(time_in_seconds) / self.dt))
        logger.info("Running %s for %f seconds, or %d steps",
                    self.model.label, time_in_seconds, steps)
        self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=True):
        """Simulate for the given number of `dt` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        with ProgressTracker(steps, progress_bar) as progress:
            for i in range(steps):
                self.step()
                progress.step()

    def reset(self, seed=None):
        """Reset the simulator state.

        Parameters
        ----------
        seed : int, optional
            A seed for all stochastic operators used in the simulator.
            This will change the random sequences generated for noise
            or inputs (e.g. from Processes), but not the built objects
            (e.g. ensembles, connections).
        """
        if self.closed:
            raise SimulatorClosed("Cannot reset closed Simulator.")

        if seed is not None:
            self.seed = seed

        # reset signals
        for key in self.signals:
            self.signals.reset(key)

        # rebuild steps (resets ops with their own state, like Processes)
        self.rng = np.random.RandomState(self.seed)
        self._steps = [op.make_step(self.signals, self.dt, self.rng)
                       for op in self._step_order]

        # clear probe data
        for probe in self.model.probes:
            self._probe_outputs[probe] = []

        self._probe_step_time()

    def close(self):
        """Closes the simulator.

        Any call to ``run``, ``run_steps``, ``step``, and ``reset`` on a closed
        simulator will raise ``SimulatorClosed``.
        """
        self.closed = True
        self.signals = None  # signals may no longer exist on some backends
Ejemplo n.º 20
0
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 context=None,
                 n_prealloc_probes=32,
                 profiling=None,
                 if_python_code='none',
                 planner=greedy_planner,
                 progress_bar=True):
        # --- check version
        if nengo.version.version_info in bad_nengo_versions:
            raise ValueError(
                "This simulator does not support Nengo version %s. Upgrade "
                "with 'pip install --upgrade --no-deps nengo'." %
                nengo.__version__)
        elif nengo.version.version_info > latest_nengo_version_info:
            warnings.warn("This version of `nengo_ocl` has not been tested "
                          "with your `nengo` version (%s). The latest fully "
                          "supported version is %s" %
                          (nengo.__version__, latest_nengo_version))

        # --- create these first since they are used in __del__
        self.closed = False
        self.model = None

        # --- arguments/attributes
        if context is None and Simulator.some_context is None:
            print('No context argument was provided to nengo_ocl.Simulator')
            print("Calling pyopencl.create_some_context() for you now:")
            Simulator.some_context = cl.create_some_context()
        if profiling is None:
            profiling = int(os.getenv("NENGO_OCL_PROFILING", 0))
        self.context = Simulator.some_context if context is None else context
        self.profiling = profiling
        self.queue = cl.CommandQueue(
            self.context, properties=PROFILING_ENABLE if self.profiling else 0)

        if if_python_code not in ['none', 'warn', 'error']:
            raise ValueError("%r not a valid value for `if_python_code`" %
                             if_python_code)
        self.if_python_code = if_python_code
        self.n_prealloc_probes = n_prealloc_probes
        self.progress_bar = progress_bar

        # --- Nengo build
        with Timer() as nengo_timer:
            if model is None:
                self.model = Model(dt=float(dt),
                                   label="%s, dt=%f" % (network, dt),
                                   decoder_cache=get_default_decoder_cache())
            else:
                self.model = model

            if network is not None:
                # Build the network into the model
                self.model.build(network)

        logger.info("Nengo build in %0.3f s" % nengo_timer.duration)

        # --- operators
        with Timer() as planner_timer:
            operators = list(self.model.operators)

            # convert DotInc and Copy to MultiDotInc
            operators = list(map(MultiDotInc.convert_to, operators))
            operators = MultiDotInc.compress(operators)

            # plan the order of operations, combining where appropriate
            op_groups = planner(operators)
            assert len([typ for typ, _ in op_groups if typ is Reset
                        ]) < 2, ("All resets not planned together")

            self.operators = operators
            self.op_groups = op_groups

        logger.info("Planning in %0.3f s" % planner_timer.duration)

        with Timer() as signals_timer:
            # Initialize signals
            all_signals = stable_unique(sig for op in operators
                                        for sig in op.all_signals)
            all_bases = stable_unique(sig.base for sig in all_signals)

            sigdict = SignalDict()  # map from Signal.base -> ndarray
            for op in operators:
                op.init_signals(sigdict)

            # Add built states to the probe dictionary
            self._probe_outputs = dict(self.model.params)

            # Provide a nicer interface to probe outputs
            self.data = ProbeDict(self._probe_outputs)

            # Create data on host and add views
            self.all_data = RaggedArray(
                [sigdict[sb] for sb in all_bases],
                names=[getattr(sb, 'name', '') for sb in all_bases],
                dtype=np.float32)

            view_builder = ViewBuilder(all_bases, self.all_data)
            view_builder.setup_views(operators)
            for probe in self.model.probes:
                view_builder.append_view(self.model.sig[probe]['in'])
            view_builder.add_views_to(self.all_data)

            self.all_bases = all_bases
            self.sidx = {
                k: np.int32(v)
                for k, v in iteritems(view_builder.sidx)
            }
            self._A_views = view_builder._A_views
            self._X_views = view_builder._X_views
            self._YYB_views = view_builder._YYB_views
            del view_builder

            # Copy data to device
            self.all_data = CLRaggedArray(self.queue, self.all_data)

        logger.info("Signals in %0.3f s" % signals_timer.duration)

        # --- set seed
        self.seed = np.random.randint(npext.maxint) if seed is None else seed
        self.rng = np.random.RandomState(self.seed)

        # --- create list of plans
        self._raggedarrays_to_reset = {}
        self._cl_rngs = {}
        self._python_rngs = {}

        plans = []
        with Timer() as plans_timer:
            for op_type, op_list in op_groups:
                plans.extend(self.plan_op_group(op_type, op_list))
            plans.extend(self.plan_probes())

        logger.info("Plans in %0.3f s" % plans_timer.duration)

        # -- create object to execute list of plans
        self._plans = Plans(plans, self.profiling)

        self.rng = None  # all randomness set, should no longer be used
        self._reset_probes()  # clears probes from previous model builds
Ejemplo n.º 21
0
class Simulator(object):
    """Reference simulator for Nengo models.

    The simulator takes a `.Network` and builds internal data structures to
    run the model defined by that network. Run the simulator with the
    `~.Simulator.run` method, and access probed data through the
    ``data`` attribute.

    Building and running the simulation may allocate resources like files
    and sockets. To properly free these resources, call the `.Simulator.close`
    method. Alternatively, `.Simulator.close` will automatically be called
    if you use the ``with`` syntax::

        with nengo.Simulator(my_network) as sim:
            sim.run(0.1)
        print(sim.data[my_probe])

    Note that the ``data`` attribute is still accessible even when a simulator
    has been closed. Running the simulator, however, will raise an error.

    Parameters
    ----------
    network : Network or None
        A network object to be built and then simulated. If None,
        then a `.Model` with the build model must be provided instead.
    dt : float, optional (Default: 0.001)
        The length of a simulator timestep, in seconds.
    seed : int, optional (Default: None)
        A seed for all stochastic operators used in this simulator.
        Will be set to ``network.seed + 1`` if not given.
    model : Model, optional (Default: None)
        A `.Model` that contains build artifacts to be simulated.
        Usually the simulator will build this model for you; however, if you
        want to build the network manually, or you want to inject build
        artifacts in the model before building the network, then you can
        pass in a `.Model` instance.
    progress_bar : bool or `.ProgressBar` or `.ProgressUpdater`, optional \
                   (Default: True)
        Progress bar for displaying build and simulation progress.

        If ``True``, the default progress bar will be used.
        If ``False``, the progress bar will be disabled.
        For more control over the progress bar, pass in a `.ProgressBar`
        or `.ProgressUpdater` instance.
    optimize : bool, optional (Default: True)
        If ``True``, the builder will run an additional optimization step
        that can speed up simulations signficantly at the cost of slower
        builds. If running models for very small amounts of time,
        pass ``False`` to disable the optimizer.

    Attributes
    ----------
    closed : bool
        Whether the simulator has been closed.
        Once closed, it cannot be reopened.
    data : ProbeDict
        The `.ProbeDict` mapping from Nengo objects to the data associated
        with those objects. In particular, each `.Probe` maps to the data
        probed while running the simulation.
    dg : dict
        A dependency graph mapping from each `.Operator` to the operators
        that depend on that operator.
    model : Model
        The `.Model` containing the signals and operators necessary to
        simulate the network.
    signals : SignalDict
        The `.SignalDict` mapping from `.Signal` instances to NumPy arrays.

    """

    # 'unsupported' defines features unsupported by a simulator.
    # The format is a list of tuples of the form `(test, reason)` with `test`
    # being a string with wildcards (*, ?, [abc], [!abc]) matched against Nengo
    # test paths and names, and `reason` is a string describing why the feature
    # is not supported by the backend. For example:
    #     unsupported = [('test_pes*', 'PES rule not implemented')]
    # would skip all test whose names start with 'test_pes'.
    unsupported = []

    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 progress_bar=True,
                 optimize=True):
        self.closed = True  # Start closed in case constructor raises exception
        self.progress_bar = progress_bar

        if model is None:
            self.model = Model(dt=float(dt),
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network, progress_bar=self.progress_bar)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_dependency_graph(self.model.operators)

        if optimize:
            opmerge_optimize(self.model, self.dg)

        self._step_order = [
            op for op in toposort(self.dg) if hasattr(op, 'make_step')
        ]

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        if seed is None:
            if network is not None and network.seed is not None:
                seed = network.seed + 1
            else:
                seed = np.random.randint(npext.maxint)

        self.closed = False
        self.reset(seed=seed)

    def __del__(self):
        """Raise a ResourceWarning if we are deallocated while open."""
        if not self.closed:
            warnings.warn(
                "Simulator with model=%s was deallocated while open. Please "
                "close simulators manually to ensure resources are properly "
                "freed." % self.model, ResourceWarning)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    @property
    def dt(self):
        """(float) The time step of the simulator."""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise ReadonlyError(attr='dt', obj=self)

    @property
    def n_steps(self):
        """(int) The current time step of the simulator."""
        return self._n_steps

    @property
    def time(self):
        """(float) The current time of the simulator."""
        return self._time

    def close(self):
        """Closes the simulator.

        Any call to `.Simulator.run`, `.Simulator.run_steps`,
        `.Simulator.step`, and `.Simulator.reset` on a closed simulator raises
        a `.SimulatorClosed` exception.
        """
        self.closed = True
        self.signals = None  # signals may no longer exist on some backends

    def _probe(self):
        """Copy all probed signals to buffers."""
        self._probe_step_time()

        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else probe.sample_every /
                      self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def _probe_step_time(self):
        self._n_steps = self.signals[self.model.step].item()
        self._time = self.signals[self.model.time].item()

    def reset(self, seed=None):
        """Reset the simulator state.

        Parameters
        ----------
        seed : int, optional
            A seed for all stochastic operators used in the simulator.
            This will change the random sequences generated for noise
            or inputs (e.g. from processes), but not the built objects
            (e.g. ensembles, connections).
        """
        if self.closed:
            raise SimulatorClosed("Cannot reset closed Simulator.")

        if seed is not None:
            self.seed = seed

        # reset signals
        for key in self.signals:
            self.signals.reset(key)

        # rebuild steps (resets ops with their own state, like Processes)
        self.rng = np.random.RandomState(self.seed)
        self._steps = [
            op.make_step(self.signals, self.dt, self.rng)
            for op in self._step_order
        ]

        # clear probe data
        for probe in self.model.probes:
            self._probe_outputs[probe] = []
        self.data.reset()

        self._probe_step_time()

    def run(self, time_in_seconds, progress_bar=None):
        """Simulate for the given length of time.

        If the given length of time is not a multiple of ``dt``,
        it will be rounded to the nearest ``dt``. For example, if ``dt``
        is 0.001 and ``run`` is called with ``time_in_seconds=0.0006``,
        the simulator will advance one timestep, resulting in the actual
        simulator time being 0.001.

        The given length of time must be positive. The simulator cannot
        be run backwards.

        Parameters
        ----------
        time_in_seconds : float
            Amount of time to run the simulation for. Must be positive.
        progress_bar : bool or `.ProgressBar` or `.ProgressUpdater`, optional \
                       (Default: True)
            Progress bar for displaying the progress of the simulation run.

            If True, the default progress bar will be used.
            If False, the progress bar will be disabled.
            For more control over the progress bar, pass in a `.ProgressBar`
            or `.ProgressUpdater` instance.
        """
        if time_in_seconds < 0:
            raise ValidationError("Must be positive (got %g)" %
                                  (time_in_seconds, ),
                                  attr="time_in_seconds")

        steps = int(np.round(float(time_in_seconds) / self.dt))

        if steps == 0:
            warnings.warn("%g results in running for 0 timesteps. Simulator "
                          "still at time %g." % (time_in_seconds, self.time))
        else:
            logger.info("Running %s for %f seconds, or %d steps",
                        self.model.label, time_in_seconds, steps)
            self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=None):
        """Simulate for the given number of ``dt`` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or `.ProgressBar` or `.ProgressUpdater`, optional \
                       (Default: True)
            Progress bar for displaying the progress of the simulation run.

            If True, the default progress bar will be used.
            If False, the progress bar will be disabled.
            For more control over the progress bar, pass in a `.ProgressBar`
            or `.ProgressUpdater` instance.
        """
        if progress_bar is None:
            progress_bar = self.progress_bar
        with ProgressTracker(steps, progress_bar, "Simulating") as progress:
            for i in range(steps):
                self.step()
                progress.step()

    def step(self):
        """Advance the simulator by 1 step (``dt`` seconds)."""
        if self.closed:
            raise SimulatorClosed("Simulator cannot run because it is closed.")

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def trange(self, dt=None):
        """Create a vector of times matching probed data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., ``dt``).

        Parameters
        ----------
        dt : float, optional (Default: None)
            The sampling period of the probe to create a range for.
            If None, the simulator's ``dt`` will be used.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)
Ejemplo n.º 22
0
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 dtype=rc.get('precision', 'dtype')):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float
            The length of a simulator timestep, in seconds.
        seed : int
            A seed for all stochastic operators used in this simulator.
            Note that there are not stochastic operators implemented
            currently, so this parameters does nothing.
        model : nengo.builder.Model instance or None
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        dt = float(dt)  # make sure it's a float (for division purposes)
        if model is None:
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache(),
                               dtype=dtype)
        else:
            self.model = model

        #print(network)
        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        self.seed = np.random.randint(npext.maxint) if seed is None else seed
        self.rng = np.random.RandomState(self.seed)

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict(
            __time__=np.asarray(npext.castDecimal(0), dtype=self.dtype))
        #print(self.model)
        #print(self.model.operators)
        for op in self.model.operators:
            op.init_signals(self.signals)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [
            node for node in toposort(self.dg) if hasattr(node, 'make_step')
        ]
        self._steps = [
            node.make_step(self.signals, dt, self.rng)
            for node in self._step_order
        ]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        self.reset()
Ejemplo n.º 23
0
class Simulator(object):
    """Reference simulator for Nengo models."""

    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float
            The length of a simulator timestep, in seconds.
        seed : int
            A seed for all stochastic operators used in this simulator.
            Note that there are not stochastic operators implemented
            currently, so this parameters does nothing.
        model : nengo.builder.Model instance or None
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        dt = float(dt)  # make sure it's a float (for division purposes)

        if model is None:
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        self.seed = np.random.randint(npext.maxint) if seed is None else seed
        self.rng = np.random.RandomState(self.seed)

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict(__time__=np.asarray(0.0, dtype=np.float64))
        for op in self.model.operators:
            op.init_signals(self.signals)

        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [node for node in toposort(self.dg)
                            if hasattr(node, 'make_step')]
        self._steps = [node.make_step(self.signals, dt, self.rng)
                       for node in self._step_order]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        self.reset()

    @property
    def dt(self):
        """The time step of the simulator"""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise AttributeError("Cannot change simulator 'dt'. Please file "
                             "an issue at http://github.com/ctn-waterloo/nengo"
                             "/issues and describe your use case.")

    @property
    def time(self):
        """The current time of the simulator"""
        return self.signals['__time__'].copy()

    def trange(self, dt=None):
        """Create a range of times matching probe data.

        Parameters
        ----------
        dt : float (optional)
            The sampling period of the probe to create a range for. If empty,
            will use the default probe sampling period.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)

    def _probe(self):
        """Copy all probed signals to buffers"""
        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else
                      probe.sample_every / self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def step(self):
        """Advance the simulator by `self.dt` seconds.
        """
        self.n_steps += 1
        self.signals['__time__'][...] = self.n_steps * self.dt

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def run(self, time_in_seconds):
        """Simulate for the given length of time."""
        steps = int(np.round(float(time_in_seconds) / self.dt))
        logger.debug("Running %s for %f seconds, or %d steps",
                     self.model.label, time_in_seconds, steps)
        self.run_steps(steps)

    def run_steps(self, steps):
        """Simulate for the given number of `dt` steps."""
        for i in range(steps):
            if i % 1000 == 0:
                logger.debug("Step %d", i)
            self.step()

    def reset(self):
        """Reset the simulator state."""
        self.n_steps = 0
        self.signals['__time__'][...] = 0

        for key in self.signals:
            if key != '__time__':
                self.signals.reset(key)

        for probe in self.model.probes:
            self._probe_outputs[probe] = []
Ejemplo n.º 24
0
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 planner=greedy_planner):

        with Timer() as nengo_timer:
            if model is None:
                self.model = Model(dt=float(dt),
                                   label="%s, dt=%f" % (network, dt),
                                   decoder_cache=get_default_decoder_cache())
            else:
                self.model = model

            if network is not None:
                # Build the network into the model
                self.model.build(network)

        logger.info("Nengo build in %0.3f s" % nengo_timer.duration)

        # --- set seed
        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.seed = seed
        self.rng = np.random.RandomState(self.seed)

        self._step = Signal(np.array(0.0, dtype=np.float64), name='step')
        self._time = Signal(np.array(0.0, dtype=np.float64), name='time')

        # --- operators
        with Timer() as planner_timer:
            operators = list(self.model.operators)

            # convert DotInc, Reset, Copy, and ProdUpdate to MultiProdUpdate
            operators = list(map(MultiProdUpdate.convert_to, operators))
            operators = MultiProdUpdate.compress(operators)

            # plan the order of operations, combining where appropriate
            op_groups = planner(operators)
            assert len([typ for typ, _ in op_groups if typ is Reset
                        ]) < 2, ("All resets not planned together")

            # add time operator after planning, to ensure it goes first
            time_op = TimeUpdate(self._step, self._time)
            operators.insert(0, time_op)
            op_groups.insert(0, (type(time_op), [time_op]))

            self.operators = operators
            self.op_groups = op_groups

        logger.info("Planning in %0.3f s" % planner_timer.duration)

        with Timer() as signals_timer:
            # Initialize signals
            all_signals = signals_from_operators(operators)
            all_bases = stable_unique([sig.base for sig in all_signals])

            sigdict = SignalDict()  # map from Signal.base -> ndarray
            for op in operators:
                op.init_signals(sigdict)

            # Add built states to the probe dictionary
            self._probe_outputs = self.model.params

            # Provide a nicer interface to probe outputs
            self.data = ProbeDict(self._probe_outputs)

            self.all_data = RaggedArray(
                [sigdict[sb] for sb in all_bases],
                [getattr(sb, 'name', '') for sb in all_bases],
                dtype=np.float32)

            builder = ViewBuilder(all_bases, self.all_data)
            self._AX_views = {}
            self._YYB_views = {}
            for op_type, op_list in op_groups:
                self.setup_views(builder, op_type, op_list)
            for probe in self.model.probes:
                builder.append_view(self.model.sig[probe]['in'])
            builder.add_views_to(self.all_data)

            self.all_bases = all_bases
            self.sidx = builder.sidx

            self._prep_all_data()

        logger.info("Signals in %0.3f s" % signals_timer.duration)

        # --- create list of plans
        with Timer() as plans_timer:
            self._plan = []
            for op_type, op_list in op_groups:
                self._plan.extend(self.plan_op_group(op_type, op_list))
            self._plan.extend(self.plan_probes())

        logger.info("Plans in %0.3f s" % plans_timer.duration)

        self.n_steps = 0
Ejemplo n.º 25
0
class Simulator(object):
    """Reference simulator for Nengo models.

    The simulator takes a `.Network` and builds internal data structures to
    run the model defined by that network. Run the simulator with the
    `~.Simulator.run` method, and access probed data through the
    ``data`` attribute.

    Building and running the simulation may allocate resources like files
    and sockets. To properly free these resources, call the `.Simulator.close`
    method. Alternatively, `.Simulator.close` will automatically be called
    if you use the ``with`` syntax::

        with nengo.Simulator(my_network) as sim:
            sim.run(0.1)
        print(sim.data[my_probe])

    Note that the ``data`` attribute is still accessible even when a simulator
    has been closed. Running the simulator, however, will raise an error.

    Parameters
    ----------
    network : Network or None
        A network object to be built and then simulated. If None,
        then a `.Model` with the build model must be provided instead.
    dt : float, optional (Default: 0.001)
        The length of a simulator timestep, in seconds.
    seed : int, optional (Default: None)
        A seed for all stochastic operators used in this simulator.
    model : Model, optional (Default: None)
        A `.Model` that contains build artifacts to be simulated.
        Usually the simulator will build this model for you; however, if you
        want to build the network manually, or you want to inject build
        artifacts in the model before building the network, then you can
        pass in a `.Model` instance.

    Attributes
    ----------
    closed : bool
        Whether the simulator has been closed.
        Once closed, it cannot be reopened.
    data : ProbeDict
        The `.ProbeDict` mapping from Nengo objects to the data associated
        with those objects. In particular, each `.Probe` maps to the data
        probed while running the simulation.
    dg : dict
        A dependency graph mapping from each `.Operator` to the operators
        that depend on that operator.
    model : Model
        The `.Model` containing the signals and operators necessary to
        simulate the network.
    signals : SignalDict
        The `.SignalDict` mapping from `.Signal` instances to NumPy arrays.

    """

    # 'unsupported' defines features unsupported by a simulator.
    # The format is a list of tuples of the form `(test, reason)` with `test`
    # being a string with wildcards (*, ?, [abc], [!abc]) matched against Nengo
    # test paths and names, and `reason` is a string describing why the feature
    # is not supported by the backend. For example:
    #     unsupported = [('test_pes*', 'PES rule not implemented')]
    # would skip all test whose names start with 'test_pes'.
    unsupported = []

    def __init__(self, network, dt=0.001, seed=None, model=None):
        self.closed = False

        if model is None:
            dt = float(dt)  # make sure it's a float (for division purposes)
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)

    def __del__(self):
        """Raise a ResourceWarning if we are deallocated while open."""
        if not self.closed:
            warnings.warn(
                "Simulator with model=%s was deallocated while open. Please "
                "close simulators manually to ensure resources are properly "
                "freed." % self.model, ResourceWarning)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    @property
    def dt(self):
        """(float) The time step of the simulator."""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise ReadonlyError(attr='dt', obj=self)

    @property
    def n_steps(self):
        """(int) The current time step of the simulator."""
        return self._n_steps

    @property
    def time(self):
        """(float) The current time of the simulator."""
        return self._time

    def close(self):
        """Closes the simulator.

        Any call to `.Simulator.run`, `.Simulator.run_steps`,
        `.Simulator.step`, and `.Simulator.reset` on a closed simulator raises
        a `.SimulatorClosed` exception.
        """
        self.closed = True
        self.signals = None  # signals may no longer exist on some backends

    def _probe(self):
        """Copy all probed signals to buffers."""
        self._probe_step_time()

        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else
                      probe.sample_every / self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def _probe_step_time(self):
        self._n_steps = self.signals[self.model.step].copy()
        self._time = self.signals[self.model.time].copy()

    def reset(self, seed=None):
        """Reset the simulator state.

        Parameters
        ----------
        seed : int, optional
            A seed for all stochastic operators used in the simulator.
            This will change the random sequences generated for noise
            or inputs (e.g. from processes), but not the built objects
            (e.g. ensembles, connections).
        """
        if self.closed:
            raise SimulatorClosed("Cannot reset closed Simulator.")

        if seed is not None:
            self.seed = seed

        # reset signals
        for key in self.signals:
            self.signals.reset(key)

        # rebuild steps (resets ops with their own state, like Processes)
        self.rng = np.random.RandomState(self.seed)
        self._steps = [op.make_step(self.signals, self.dt, self.rng)
                       for op in self._step_order]

        # clear probe data
        for probe in self.model.probes:
            self._probe_outputs[probe] = []

        self._probe_step_time()

    def run(self, time_in_seconds, progress_bar=True):
        """Simulate for the given length of time.

        Parameters
        ----------
        time_in_seconds : float
            Amount of time to run the simulation for.
        progress_bar : bool or `.ProgressBar` or `.ProgressUpdater`, optional \
                       (Default: True)
            Progress bar for displaying the progress of the simulation run.

            If True, the default progress bar will be used.
            If False, the progress bar will be disabled.
            For more control over the progress bar, pass in a `.ProgressBar`
            or `.ProgressUpdater` instance.
        """
        steps = int(np.round(float(time_in_seconds) / self.dt))
        logger.info("Running %s for %f seconds, or %d steps",
                    self.model.label, time_in_seconds, steps)
        self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=True):
        """Simulate for the given number of ``dt`` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or `.ProgressBar` or `.ProgressUpdater`, optional \
                       (Default: True)
            Progress bar for displaying the progress of the simulation run.

            If True, the default progress bar will be used.
            If False, the progress bar will be disabled.
            For more control over the progress bar, pass in a `.ProgressBar`
            or `.ProgressUpdater` instance.
        """
        with ProgressTracker(steps, progress_bar) as progress:
            for i in range(steps):
                self.step()
                progress.step()

    def step(self):
        """Advance the simulator by 1 step (``dt`` seconds)."""
        if self.closed:
            raise SimulatorClosed("Simulator cannot run because it is closed.")

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def trange(self, dt=None):
        """Create a vector of times matching probed data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., ``dt``).

        Parameters
        ----------
        dt : float, optional (Default: None)
            The sampling period of the probe to create a range for.
            If None, the simulator's ``dt`` will be used.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)
Ejemplo n.º 26
0
    def __init__(self, network, dt=0.001, seed=None):
        self.model = Model(
            dt=float(dt),
            label="Nengo RS model",
            decoder_cache=get_default_decoder_cache(),
        )
        self.model.build(network)

        signal_to_engine_id = {}
        for signal_dict in self.model.sig.values():
            for signal in signal_dict.values():
                self.add_sig(signal_to_engine_id, signal)
        x = SignalU64("step", 0)
        signal_to_engine_id[self.model.step] = x
        signal_to_engine_id[self.model.time] = SignalF64("time", 0.0)
        self._sig_to_ngine_id = signal_to_engine_id

        dg = BidirectionalDAG(operator_dependency_graph(self.model.operators))
        toposorted_dg = toposort(dg.forward)
        node_indices = {node: idx for idx, node in enumerate(toposorted_dg)}

        ops = []
        for op in toposorted_dg:
            dependencies = [node_indices[node] for node in dg.backward[op]]
            if isinstance(op, core_op.Reset):
                ops.append(
                    Reset(
                        np.asarray(op.value, dtype=np.float64),
                        self.get_sig(signal_to_engine_id, op.dst),
                        dependencies,
                    ))
            elif isinstance(op, core_op.TimeUpdate):
                ops.append(
                    TimeUpdate(
                        dt,
                        self.get_sig(signal_to_engine_id, self.model.step),
                        self.get_sig(signal_to_engine_id, self.model.time),
                        dependencies,
                    ))
            elif isinstance(op, core_op.ElementwiseInc):
                ops.append(
                    ElementwiseInc(
                        self.get_sig(signal_to_engine_id, op.Y),
                        self.get_sig(signal_to_engine_id, op.A),
                        self.get_sig(signal_to_engine_id, op.X),
                        dependencies,
                    ))
            elif isinstance(op, core_op.Copy):
                assert op.src_slice is None and op.dst_slice is None
                ops.append(
                    Copy(
                        op.inc,
                        self.get_sig(signal_to_engine_id, op.src),
                        self.get_sig(signal_to_engine_id, op.dst),
                        dependencies,
                    ))
            elif isinstance(op, core_op.DotInc):
                ops.append(
                    DotInc(
                        self.get_sig(signal_to_engine_id, op.Y),
                        self.get_sig(signal_to_engine_id, op.A),
                        self.get_sig(signal_to_engine_id, op.X),
                        dependencies,
                    ))
            elif isinstance(op, neurons.SimNeurons):
                signals = SignalDict()
                op.init_signals(signals)
                ops.append(
                    SimNeurons(
                        self.dt,
                        op.neurons.step_math,
                        [signals[s]
                         for s in op.states] if hasattr(op, "states") else [],
                        self.get_sig(signal_to_engine_id, op.J),
                        self.get_sig(signal_to_engine_id, op.output),
                        dependencies,
                    ))
            elif isinstance(op, processes.SimProcess):
                signals = SignalDict()
                op.init_signals(signals)
                shape_in = (0, ) if op.input is None else op.input.shape
                shape_out = op.output.shape
                rng = None
                state = {k: signals[s] for k, s in op.state.items()}
                step_fn = op.process.make_step(shape_in, shape_out, self.dt,
                                               rng, state)
                ops.append(
                    SimProcess(
                        op.mode == "inc",
                        lambda *args, step_fn=step_fn: np.asarray(
                            step_fn(*args), dtype=float),
                        self.get_sig(signal_to_engine_id, op.t),
                        self.get_sig(signal_to_engine_id, op.output),
                        None if op.input is None else self.get_sig(
                            signal_to_engine_id, op.input),
                        dependencies,
                    ))
            elif isinstance(op, core_op.SimPyFunc):
                ops.append(
                    SimPyFunc(
                        lambda *args, op=op: np.asarray(op.fn(*args),
                                                        dtype=float),
                        self.get_sig(signal_to_engine_id, op.output),
                        None if op.t is None else self.get_sig(
                            signal_to_engine_id, op.t),
                        None if op.x is None else self.get_sig(
                            signal_to_engine_id, op.x),
                        dependencies,
                    ))
            else:
                raise Exception(f"missing: {op}")

        self.probe_mapping = {}
        for probe in self.model.probes:
            self.probe_mapping[probe] = Probe(
                signal_to_engine_id[self.model.sig[probe]["in"]])

        self._engine = Engine(list(signal_to_engine_id.values()), ops,
                              list(self.probe_mapping.values()))
        self.data = SimData(self)
        print("initialized")

        self._engine.reset()
Ejemplo n.º 27
0
    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float, optional
            The length of a simulator timestep, in seconds.
        seed : int, optional
            A seed for all stochastic operators used in this simulator.
        model : nengo.builder.Model instance or None, optional
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        if model is None:
            dt = float(dt)  # make sure it's a float (for division purposes)
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache())
        else:
            self.model = model

        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict(__time__=np.asarray(0.0, dtype=np.float64))
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Order the steps (they are made in `Simulator.reset`)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [op for op in toposort(self.dg)
                            if hasattr(op, 'make_step')]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)
Ejemplo n.º 28
0
class Simulator:
    r"""Reference simulator for Nengo models.

    The simulator takes a `.Network` and builds internal data structures to
    run the model defined by that network. Run the simulator with the
    `~.Simulator.run` method, and access probed data through the
    ``data`` attribute.

    Building and running the simulation may allocate resources like files
    and sockets. To properly free these resources, call the `.Simulator.close`
    method. Alternatively, `.Simulator.close` will automatically be called
    if you use the ``with`` syntax:

    .. testcode::

       with nengo.Network() as net:
           ensemble = nengo.Ensemble(10, 1)
       with nengo.Simulator(net, progress_bar=False) as sim:
           sim.run(0.1)

    Note that the ``data`` attribute is still accessible even when a simulator
    has been closed. Running the simulator, however, will raise an error.

    When debugging or comparing models, it can be helpful to see the full ordered
    list of operators that the simulator will execute on each timestep.

    .. testcode::

       with nengo.Simulator(nengo.Network(), progress_bar=False) as sim:
           print('\n'.join("* %s" % op for op in sim.step_order))

    .. testoutput::

       * TimeUpdate{}

    The diff of two simulators' sorted ops tells us how two built models differ.
    We can use ``difflib`` in the Python standard library to see the differences.

    .. testcode::

       # Original model
       with nengo.Network() as net:
           ensemble = nengo.Ensemble(10, 1, label="Ensemble")
       sim1 = nengo.Simulator(net, progress_bar=False)

       # Add a node
       with net:
           node = nengo.Node(output=0, label="Node")
           nengo.Connection(node, ensemble)
       sim2 = nengo.Simulator(net, progress_bar=False)

       import difflib

       print("".join(difflib.unified_diff(
           sorted("%s: %s\n" % (type(op).__name__, op.tag) for op in sim1.step_order),
           sorted("%s: %s\n" % (type(op).__name__, op.tag) for op in sim2.step_order),
           fromfile="sim1",
           tofile="sim2",
           n=0,
       )).strip())

       sim1.close()
       sim2.close()

    .. testoutput::
       :options:

       --- sim1
       +++ sim2
       @@ -0,0 +1 @@
       +Copy: <Connection from <Node "Node"> to <Ensemble "Ensemble">>
       @@ -4,0 +6 @@
       +SimProcess: Lowpass(tau=0.005)

    Parameters
    ----------
    network : Network or None
        A network object to be built and then simulated. If None,
        then a `.Model` with the build model must be provided instead.
    dt : float, optional
        The length of a simulator timestep, in seconds.
    seed : int, optional
        A seed for all stochastic operators used in this simulator.
        Will be set to ``network.seed + 1`` if not given.
    model : Model, optional
        A `.Model` that contains build artifacts to be simulated.
        Usually the simulator will build this model for you; however, if you
        want to build the network manually, or you want to inject build
        artifacts in the model before building the network, then you can
        pass in a `.Model` instance.
    progress_bar : bool or ProgressBar, optional
        Progress bar for displaying build and simulation progress.

        If ``True``, the default progress bar will be used.
        If ``False``, the progress bar will be disabled.
        For more control over the progress bar, pass in a ``ProgressBar``
        instance.
    optimize : bool, optional
        If ``True``, the builder will run an additional optimization step
        that can speed up simulations significantly at the cost of slower
        builds. If running models for very small amounts of time,
        pass ``False`` to disable the optimizer.

    Attributes
    ----------
    closed : bool
        Whether the simulator has been closed.
        Once closed, it cannot be reopened.
    data : SimulationData
        The `.SimulationData` mapping from Nengo objects to the data associated
        with those objects. In particular, each `.Probe` maps to the data
        probed while running the simulation.
    dg : dict
        A dependency graph mapping from each `.Operator` to the operators
        that depend on that operator.
    model : Model
        The `.Model` containing the signals and operators necessary to
        simulate the network.
    signals : SignalDict
        The `.SignalDict` mapping from `.Signal` instances to NumPy arrays.

    """
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 progress_bar=True,
                 optimize=True):
        self.closed = True  # Start closed in case constructor raises exception
        self.progress_bar = progress_bar
        self.optimize = optimize

        if model is None:
            self.model = Model(
                dt=float(dt),
                label="%s, dt=%f" % (network, dt),
                decoder_cache=get_default_decoder_cache(),
            )
        else:
            self.model = model

        pt = ProgressTracker(progress_bar, Progress("Building", "Build"))
        with pt:
            if network is not None:
                # Build the network into the model
                self.model.build(network,
                                 progress=pt.next_stage("Building", "Build"))

            # Order the steps (they are made in `Simulator.reset`)
            self.dg = operator_dependency_graph(self.model.operators)

            if optimize:
                with pt.next_stage("Building (running optimizer)",
                                   "Optimization"):
                    opmerge_optimize(self.model, self.dg)

        self._step_order = [
            op for op in toposort(self.dg) if hasattr(op, "make_step")
        ]

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict()
        for op in self.model.operators:
            op.init_signals(self.signals)

        # Add built states to the raw simulation data dictionary
        self._sim_data = self.model.params

        # Provide a nicer interface to simulation data
        self.data = SimulationData(self._sim_data)

        if seed is None:
            if network is not None and network.seed is not None:
                seed = network.seed + 1
            else:
                seed = np.random.randint(npext.maxint)

        self.closed = False
        self.reset(seed=seed)

    def __del__(self):
        """Raise a ResourceWarning if we are deallocated while open."""
        if not self.closed:
            warnings.warn(
                "Simulator with model=%s was deallocated while open. Please "
                "close simulators manually to ensure resources are properly "
                "freed." % self.model,
                ResourceWarning,
            )

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def __getstate__(self):
        signals = ({k: v
                    for k, v in self.signals.items()
                    if not k.readonly} if self.signals is not None else {})
        probe_outputs = {
            probe: self._sim_data[probe]
            for probe in self.model.probes
        }
        return dict(
            model=self.model,
            signals=signals,
            probe_outputs=probe_outputs,
            dt=self.dt,
            seed=self.seed,
            progress_bar=self.progress_bar,
            optimize=self.optimize,
            closed=self.closed,
        )

    def __setstate__(self, state):
        self.__init__(
            network=None,
            model=state["model"],
            dt=state["dt"],
            seed=state["seed"],
            progress_bar=state["progress_bar"],
            optimize=False,  # The pickled Sim will have already been optimized
        )
        for key, value in state["signals"].items():
            self.signals[key] = value
        for key, value in state["probe_outputs"].items():
            self._sim_data[key].extend(value)
        # Set whether it had originally been optimized
        self.optimize = state["optimize"]
        if state["closed"]:
            self.close()

    @property
    def dt(self):
        """(float) The time step of the simulator."""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise ReadonlyError(attr="dt", obj=self)

    @property
    def n_steps(self):
        """(int) The current time step of the simulator."""
        return self._n_steps

    @property
    def step_order(self):
        """(list) The ordered list of step functions run by this simulator."""
        return self._step_order

    @property
    def time(self):
        """(float) The current time of the simulator."""
        return self._time

    def clear_probes(self):
        """Clear all probe histories.

        .. versionadded:: 3.0.0
        """
        for probe in self.model.probes:
            self._sim_data[probe] = []
        self.data.reset()  # clear probe cache

    def close(self):
        """Closes the simulator.

        Any call to `.Simulator.run`, `.Simulator.run_steps`,
        `.Simulator.step`, and `.Simulator.reset` on a closed simulator raises
        a `.SimulatorClosed` exception.
        """
        self.closed = True
        self.signals = None  # signals may no longer exist on some backends

    def _probe(self):
        """Copy all probed signals to buffers."""
        self._probe_step_time()

        for probe in self.model.probes:
            period = 1 if probe.sample_every is None else probe.sample_every / self.dt
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]["in"]].copy()
                self._sim_data[probe].append(tmp)

    def _probe_step_time(self):
        self._n_steps = self.signals[self.model.step].item()
        self._time = self.signals[self.model.time].item()

    def reset(self, seed=None):
        """Reset the simulator state.

        Parameters
        ----------
        seed : int, optional
            A seed for all stochastic operators used in the simulator.
            This will change the random sequences generated for noise
            or inputs (e.g. from processes), but not the built objects
            (e.g. ensembles, connections).
        """
        if self.closed:
            raise SimulatorClosed("Cannot reset closed Simulator.")

        if seed is not None:
            self.seed = seed

        # reset signals
        for key in self.signals:
            self.signals.reset(key)

        # rebuild steps (resets ops with their own state, like Processes)
        self.rng = np.random.RandomState(self.seed)
        self._steps = [
            op.make_step(self.signals, self.dt, self.rng)
            for op in self._step_order
        ]

        self.clear_probes()

        self._probe_step_time()

    def run(self, time_in_seconds, progress_bar=None):
        """Simulate for the given length of time.

        If the given length of time is not a multiple of ``dt``,
        it will be rounded to the nearest ``dt``. For example, if ``dt``
        is 0.001 and ``run`` is called with ``time_in_seconds=0.0006``,
        the simulator will advance one timestep, resulting in the actual
        simulator time being 0.001.

        The given length of time must be positive. The simulator cannot
        be run backwards.

        Parameters
        ----------
        time_in_seconds : float
            Amount of time to run the simulation for. Must be positive.
        progress_bar : bool or ProgressBar, optional
            Progress bar for displaying the progress of the simulation run.

            If True, the default progress bar will be used.
            If False, the progress bar will be disabled.
            For more control over the progress bar, pass in a ``ProgressBar``
            instance.
        """
        if time_in_seconds < 0:
            raise ValidationError("Must be positive (got %g)" %
                                  (time_in_seconds, ),
                                  attr="time_in_seconds")

        steps = int(np.round(float(time_in_seconds) / self.dt))

        if steps == 0:
            warnings.warn("%g results in running for 0 timesteps. Simulator "
                          "still at time %g." % (time_in_seconds, self.time))
        else:
            logger.info(
                "Running %s for %f seconds, or %d steps",
                self.model.label,
                time_in_seconds,
                steps,
            )
            self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=None):
        """Simulate for the given number of ``dt`` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ProgressBar, optional
            Progress bar for displaying the progress of the simulation run.

            If True, the default progress bar will be used.
            If False, the progress bar will be disabled.
            For more control over the progress bar, pass in a ``ProgressBar``
            instance.
        """
        if progress_bar is None:
            progress_bar = self.progress_bar

        with ProgressTracker(progress_bar,
                             Progress("Simulating", "Simulation",
                                      steps)) as pt:
            for i in range(steps):
                self.step()
                pt.total_progress.step()

    def step(self):
        """Advance the simulator by 1 step (``dt`` seconds)."""
        if self.closed:
            raise SimulatorClosed("Simulator cannot run because it is closed.")

        old_err = np.seterr(invalid="raise", divide="ignore")
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def trange(self, dt=None, sample_every=None):
        """Create a vector of times matching probed data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., ``dt``).

        Parameters
        ----------
        sample_every : float, optional
            The sampling period of the probe to create a range for.
            If None, a time value for every ``dt`` will be produced.

            .. versionchanged:: 3.0.0
               Renamed from dt to sample_every
        """
        if dt is not None:
            if sample_every is not None:
                raise ValidationError(
                    "Cannot specify both `dt` and `sample_every`. "
                    "Use `sample_every` only.",
                    attr="dt",
                    obj=self,
                )
            warnings.warn("`dt` is deprecated. Use `sample_every` instead.",
                          DeprecationWarning)
            sample_every = dt
        period = 1 if sample_every is None else sample_every / self.dt
        steps = np.arange(1, self.n_steps + 1)
        return self.dt * steps[steps % period < 1]
Ejemplo n.º 29
0
 def add_op(self, op):
     self.operators.append(op)
     # Fail fast by trying make_step with a temporary sigdict
     signals = SignalDict()
     op.init_signals(signals)
     op.make_step(signals, self.dt, np.random)
Ejemplo n.º 30
0
class Simulator(object):
    """Reference simulator for Nengo models."""
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 dtype=rc.get('precision', 'dtype')):
        """Initialize the simulator with a network and (optionally) a model.

        Most of the time, you will pass in a network and sometimes a dt::

            sim1 = nengo.Simulator(my_network)  # Uses default 0.001s dt
            sim2 = nengo.Simulator(my_network, dt=0.01)  # Uses 0.01s dt

        For more advanced use cases, you can initialize the model yourself,
        and also pass in a network that will be built into the same model
        that you pass in::

            sim = nengo.Simulator(my_network, model=my_model)

        If you want full control over the build process, then you can build
        your network into the model manually. If you do this, then you must
        explicitly pass in ``None`` for the network::

            sim = nengo.Simulator(None, model=my_model)

        Parameters
        ----------
        network : nengo.Network instance or None
            A network object to the built and then simulated.
            If a fully built ``model`` is passed in, then you can skip
            building the network by passing in network=None.
        dt : float
            The length of a simulator timestep, in seconds.
        seed : int
            A seed for all stochastic operators used in this simulator.
            Note that there are not stochastic operators implemented
            currently, so this parameters does nothing.
        model : nengo.builder.Model instance or None
            A model object that contains build artifacts to be simulated.
            Usually the simulator will build this model for you; however,
            if you want to build the network manually, or to inject some
            build artifacts in the Model before building the network,
            then you can pass in a ``nengo.builder.Model`` instance.
        """
        dt = float(dt)  # make sure it's a float (for division purposes)
        if model is None:
            self.model = Model(dt=dt,
                               label="%s, dt=%f" % (network, dt),
                               decoder_cache=get_default_decoder_cache(),
                               dtype=dtype)
        else:
            self.model = model

        #print(network)
        if network is not None:
            # Build the network into the model
            self.model.build(network)

        self.model.decoder_cache.shrink()

        self.seed = np.random.randint(npext.maxint) if seed is None else seed
        self.rng = np.random.RandomState(self.seed)

        # -- map from Signal.base -> ndarray
        self.signals = SignalDict(
            __time__=np.asarray(npext.castDecimal(0), dtype=self.dtype))
        #print(self.model)
        #print(self.model.operators)
        for op in self.model.operators:
            op.init_signals(self.signals)
        self.dg = operator_depencency_graph(self.model.operators)
        self._step_order = [
            node for node in toposort(self.dg) if hasattr(node, 'make_step')
        ]
        self._steps = [
            node.make_step(self.signals, dt, self.rng)
            for node in self._step_order
        ]

        # Add built states to the probe dictionary
        self._probe_outputs = self.model.params

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)

        self.reset()

    @property
    def dt(self):
        """The time step of the simulator"""
        return self.model.dt

    @dt.setter
    def dt(self, dummy):
        raise AttributeError("Cannot change simulator 'dt'. Please file "
                             "an issue at http://github.com/nengo/nengo"
                             "/issues and describe your use case.")

    @property
    def dtype(self):
        return self.model.dtype

    @property
    def time(self):
        """The current time of the simulator"""
        return self.signals['__time__'].copy()

    def trange(self, dt=None):
        """Create a range of times matching probe data.

        Note that the range does not start at 0 as one might expect, but at
        the first timestep (i.e., dt).

        Parameters
        ----------
        dt : float (optional)
            The sampling period of the probe to create a range for. If empty,
            will use the default probe sampling period.
        """
        dt = self.dt if dt is None else dt
        n_steps = int(self.n_steps * (self.dt / dt))
        return dt * np.arange(1, n_steps + 1)

    def _probe(self):
        """Copy all probed signals to buffers"""
        for probe in self.model.probes:
            period = (1 if probe.sample_every is None else probe.sample_every /
                      self.dt)
            if self.n_steps % period < 1:
                tmp = self.signals[self.model.sig[probe]['in']].copy()
                self._probe_outputs[probe].append(tmp)

    def step(self):
        """Advance the simulator by `self.dt` seconds.
        """
        self.n_steps += 1
        self.signals['__time__'][...] = self.n_steps * self.dt

        old_err = np.seterr(invalid='raise', divide='ignore')
        try:
            for step_fn in self._steps:
                step_fn()
        finally:
            np.seterr(**old_err)

        self._probe()

    def run(self, time_in_seconds, progress_bar=True):
        """Simulate for the given length of time.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        steps = int(np.round(float(time_in_seconds) / self.dt))
        logger.debug("Running %s for %f seconds, or %d steps",
                     self.model.label, time_in_seconds, steps)
        self.run_steps(steps, progress_bar=progress_bar)

    def run_steps(self, steps, progress_bar=True):
        """Simulate for the given number of `dt` steps.

        Parameters
        ----------
        steps : int
            Number of steps to run the simulation for.
        progress_bar : bool or ``ProgressBar`` or ``ProgressUpdater``, optional
            Progress bar for displaying the progress.

            By default, ``progress_bar=True``, which uses the default progress
            bar (text in most situations, or an HTML version in recent IPython
            notebooks).

            To disable the progress bar, use ``progress_bar=False``.

            For more control over the progress bar, pass in a
            :class:`nengo.utils.progress.ProgressBar`,
            or :class:`nengo.utils.progress.ProgressUpdater` instance.
        """
        with ProgressTracker(steps, progress_bar) as progress:
            for i in range(steps):
                self.step()
                progress.step()

    def reset(self):
        """Reset the simulator state."""
        self.n_steps = 0
        self.signals['__time__'][...] = 0

        for key in self.signals:
            if key != '__time__':
                self.signals.reset(key)

        for probe in self.model.probes:
            self._probe_outputs[probe] = []