def test_build_linear_system(seed, rng, plt, allclose): func = lambda x: x**2 with nengo.Network(seed=seed) as net: conn = nengo.Connection(nengo.Ensemble(60, 1), nengo.Ensemble(50, 1), function=func) model = Model() model.build(net) eval_points, activities, targets = build_linear_system(model, conn, rng) assert eval_points.shape[1] == 1 assert targets.shape[1] == 1 assert activities.shape[1] == 60 assert eval_points.shape[0] == activities.shape[0] == targets.shape[0] X = eval_points AA = activities.T.dot(activities) AX = activities.T.dot(eval_points) AY = activities.T.dot(targets) WX = np.linalg.solve(AA, AX) WY = np.linalg.solve(AA, AY) Xhat = activities.dot(WX) Yhat = activities.dot(WY) i = np.argsort(eval_points.ravel()) plt.plot(X[i], Xhat[i]) plt.plot(X[i], Yhat[i]) assert allclose(Xhat, X, atol=1e-1) assert allclose(Yhat, func(X), atol=1e-1)
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)
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 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])
def test_multidotinc_compress(monkeypatch): if nengo.version.version_info < (2, 3, 1): # LEGACY # Nengo versions <= 2.3.0 have more stringent op validation which # required PreserveValue. That's been removed, so the strict # validation causes this test to fail despite it working. monkeypatch.setattr(nengo.utils.simulator, "validate_ops", lambda *args: None) a = Signal([0, 0]) b = Signal([0, 0]) A = Signal([[1, 2], [0, 1]]) B = Signal([[2, 1], [-1, 1]]) x = Signal([1, 1]) y = Signal([1, -1]) m = Model(dt=0) m.operators += [Reset(a), DotInc(A, x, a), DotInc(B, y, a)] m.operators += [DotInc(A, y, b), DotInc(B, x, b)] with nengo_ocl.Simulator(None, model=m) as sim: sim.step() assert np.allclose(sim.signals[a], [4, -1]) assert np.allclose(sim.signals[b], [2, -1]) sim.step() assert np.allclose(sim.signals[a], [4, -1]) assert np.allclose(sim.signals[b], [4, -2])
def test_encoder_decoder_with_views(RefSimulator): foo = Signal([1.0], name="foo") decoders = np.asarray([.2, .1]) m = Model(dt=0.001) sig_in, sig_out = build_pyfunc(lambda t, x: x + 1, True, 2, 2, None, m) m.operators += [ DotInc(Signal([[1.0], [2.0]]), foo[:], sig_in), ProdUpdate(Signal(decoders * 0.5), sig_out, Signal(0.2), foo[:]) ] def check(sig, target): assert np.allclose(sim.signals[sig], target) sim = RefSimulator(None, model=m) sim.step() # DotInc to pop.input_signal (input=[1.0,2.0]) # produpdate updates foo (foo=[0.2]) # pop updates pop.output_signal (output=[2,3]) check(foo, .2) check(sig_in, [1, 2]) check(sig_out, [2, 3]) sim.step() # DotInc to pop.input_signal (input=[0.2,0.4]) # (note that pop resets its own input signal each timestep) # produpdate updates foo (foo=[0.39]) 0.2*0.5*2+0.1*0.5*3 + 0.2*0.2 # pop updates pop.output_signal (output=[1.2,1.4]) check(foo, .39) check(sig_in, [0.2, 0.4]) check(sig_out, [1.2, 1.4])
def test_noise(RefSimulator, seed): """Make sure that we can generate noise properly.""" n = 1000 mean, std = 0.1, 0.8 noise = Signal(np.zeros(n), name="noise") process = nengo.processes.StochasticProcess(nengo.dists.Gaussian( mean, std)) m = Model(dt=0.001) m.operators += [Reset(noise), SimNoise(noise, process)] sim = RefSimulator(None, model=m, seed=seed) samples = np.zeros((100, n)) for i in range(100): sim.step() samples[i] = sim.signals[noise] h, xedges = np.histogram(samples.flat, bins=51) x = 0.5 * (xedges[:-1] + xedges[1:]) dx = np.diff(xedges) z = 1. / np.sqrt(2 * np.pi * std**2) * np.exp(-0.5 * (x - mean)**2 / std**2) y = h / float(h.sum()) / dx assert np.allclose(y, z, atol=0.02)
def test_signal_indexing_1(RefSimulator): one = Signal(np.zeros(1), name="a") two = Signal(np.zeros(2), name="b") three = Signal(np.zeros(3), name="c") tmp = Signal(np.zeros(3), name="tmp") m = Model(dt=0.001) m.operators += [ Reset(one), Reset(two), Reset(tmp), DotInc(Signal(1, name="A1"), three[:1], one), DotInc(Signal(2.0, name="A2"), three[1:], two), DotInc(Signal([[0, 0, 1], [0, 1, 0], [1, 0, 0]], name="A3"), three, tmp), Copy(src=tmp, dst=three, as_update=True), ] sim = RefSimulator(None, model=m) sim.signals[three] = np.asarray([1, 2, 3]) sim.step() assert np.all(sim.signals[one] == 1) assert np.all(sim.signals[two] == [4, 6]) assert np.all(sim.signals[three] == [3, 2, 1]) sim.step() assert np.all(sim.signals[one] == 3) assert np.all(sim.signals[two] == [4, 2]) assert np.all(sim.signals[three] == [1, 2, 3])
def test_build_linear_system_zeroact(seed, rng): eval_points = np.linspace(-0.1, 0.1, 100)[:, None] with nengo.Network(seed=seed) as net: a = nengo.Ensemble(5, 1, intercepts=nengo.dists.Choice([0.9])) b = nengo.Ensemble(5, 1, intercepts=nengo.dists.Choice([0.9])) model = Model() model.build(net) conn = nengo.Connection(a, b, eval_points=eval_points, add_to_container=False) with pytest.raises(BuildError, match="'activities' matrix is all zero"): build_linear_system(model, conn, rng)
def test_signal_init_values(RefSimulator): """Tests that initial values are not overwritten.""" zero = Signal([0]) one = Signal([1]) five = Signal([5.0]) zeroarray = Signal([[0], [0], [0]]) array = Signal([1, 2, 3]) m = Model(dt=0) m.operators += [ PreserveValue(five), PreserveValue(array), DotInc(zero, zero, five), DotInc(zeroarray, one, array) ] sim = RefSimulator(None, model=m) assert sim.signals[zero][0] == 0 assert sim.signals[one][0] == 1 assert sim.signals[five][0] == 5.0 assert np.all(np.array([1, 2, 3]) == sim.signals[array]) sim.step() assert sim.signals[zero][0] == 0 assert sim.signals[one][0] == 1 assert sim.signals[five][0] == 5.0 assert np.all(np.array([1, 2, 3]) == sim.signals[array])
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 test_session_config(Simulator, as_model): with Network() as net: config.configure_settings(session_config={ "graph_options.optimizer_options.opt_level": 21, "gpu_options.allow_growth": True}) if as_model: # checking that config settings work when we pass in a model instead of # network model = Model(dt=0.001, builder=builder.NengoBuilder()) model.build(net) net = None else: model = None with Simulator(net, model=model) as sim: assert sim.sess._config.graph_options.optimizer_options.opt_level == 21 assert sim.sess._config.gpu_options.allow_growth
def test_session_config(Simulator, as_model): with Network() as net: config.configure_settings( session_config={ "graph_options.optimizer_options.opt_level": 21, "gpu_options.allow_growth": True }) if as_model: # checking that config settings work when we pass in a model instead of # network model = Model(dt=0.001, builder=builder.NengoBuilder()) model.build(net) net = None else: model = None with Simulator(net, model=model) as sim: assert sim.sess._config.graph_options.optimizer_options.opt_level == 21 assert sim.sess._config.gpu_options.allow_growth
def test_hierarchical_seeding(): """Changes to subnetworks shouldn't affect seeds in top-level network""" def create(make_extra, seed): objs = [] with nengo.Network(seed=seed, label='n1') as model: objs.append(nengo.Ensemble(10, 1, label='e1')) with nengo.Network(label='n2'): objs.append(nengo.Ensemble(10, 1, label='e2')) if make_extra: # This shouldn't affect any seeds objs.append(nengo.Ensemble(10, 1, label='e3')) objs.append(nengo.Ensemble(10, 1, label='e4')) return model, objs same1, same1objs = create(False, 9) same2, same2objs = create(True, 9) diff, diffobjs = create(True, 10) m1 = Model() m1.build(same1) same1seeds = m1.seeds m2 = Model() m2.build(same2) same2seeds = m2.seeds m3 = Model() m3.build(diff) diffseeds = m3.seeds for diffobj, same2obj in zip(diffobjs, same2objs): # These seeds should all be different assert diffseeds[diffobj] != same2seeds[same2obj] # Skip the extra ensemble same2objs = same2objs[:2] + same2objs[3:] for same1obj, same2obj in zip(same1objs, same2objs): # These seeds should all be the same assert same1seeds[same1obj] == same2seeds[same2obj]
def test_encoder_decoder_pathway(RefSimulator): """Verifies (like by hand) that the simulator does the right things in the right order.""" foo = Signal([1.0], name="foo") decoders = np.asarray([.2, .1]) decs = Signal(decoders * 0.5) m = Model(dt=0.001) sig_in, sig_out = build_pyfunc(lambda t, x: x + 1, True, 2, 2, None, m) m.operators += [ DotInc(Signal([[1.0], [2.0]]), foo, sig_in), ProdUpdate(decs, sig_out, Signal(0.2), foo) ] def check(sig, target): assert np.allclose(sim.signals[sig], target) sim = RefSimulator(None, model=m) check(foo, 1.0) check(sig_in, 0) check(sig_out, 0) sim.step() # DotInc to pop.input_signal (input=[1.0,2.0]) # produpdate updates foo (foo=[0.2]) # pop updates pop.output_signal (output=[2,3]) check(sig_in, [1, 2]) check(sig_out, [2, 3]) check(foo, .2) check(decs, [.1, .05]) sim.step() # DotInc to pop.input_signal (input=[0.2,0.4]) # (note that pop resets its own input signal each timestep) # produpdate updates foo (foo=[0.39]) 0.2*0.5*2+0.1*0.5*3 + 0.2*0.2 # pop updates pop.output_signal (output=[1.2,1.4]) check(decs, [.1, .05]) check(sig_in, [0.2, 0.4]) check(sig_out, [1.2, 1.4]) # -- foo is computed as a prodUpdate of the *previous* output signal # foo <- .2 * foo + dot(decoders * .5, output_signal) # .2 * .2 + dot([.2, .1] * .5, [2, 3]) # .04 + (.2 + .15) # <- .39 check(foo, .39)
def test_simple_pyfunc(RefSimulator): dt = 0.001 time = Signal(np.zeros(1), name="time") sig = Signal(np.zeros(1), name="sig") m = Model(dt=dt) sig_in, sig_out = build_pyfunc(lambda t, x: np.sin(x), True, 1, 1, None, m) m.operators += [ ProdUpdate(Signal(dt), Signal(1), Signal(1), time), DotInc(Signal([[1.0]]), time, sig_in), ProdUpdate(Signal([[1.0]]), sig_out, Signal(0), sig), ] sim = RefSimulator(None, model=m) sim.step() for i in range(5): sim.step() t = (i + 2) * dt assert np.allclose(sim.signals[time], t) assert np.allclose(sim.signals[sig], np.sin(t - dt * 2))
def test_simple_pyfunc(RefSimulator): dt = 0.001 time = Signal(np.zeros(1), name="time") sig = Signal(np.zeros(1), name="sig") m = Model(dt=dt) sig_in, sig_out = build_pyfunc(m, lambda t, x: np.sin(x), True, 1, 1, None) m.operators += [ Reset(sig), DotInc(Signal([[1.0]]), time, sig_in), DotInc(Signal([[1.0]]), sig_out, sig), DotInc(Signal(dt), Signal(1), time, as_update=True), ] sim = RefSimulator(None, model=m) for i in range(5): sim.step() t = i * dt assert np.allclose(sim.signals[sig], np.sin(t)) assert np.allclose(sim.signals[time], t + dt)
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)
def test_hierarchical_seeding(): """Changes to subnetworks shouldn't affect seeds in top-level network""" def create(make_extra, seed): objs = [] with nengo.Network(seed=seed, label="n1") as model: objs.append(nengo.Ensemble(10, 1, label="e1")) with nengo.Network(label="n2"): objs.append(nengo.Ensemble(10, 1, label="e2")) if make_extra: # This shouldn't affect any seeds objs.append(nengo.Ensemble(10, 1, label="e3")) objs.append(nengo.Ensemble(10, 1, label="e4")) return model, objs same1, same1objs = create(False, 9) same2, same2objs = create(True, 9) diff, diffobjs = create(True, 10) m1 = Model() m1.build(same1) same1seeds = m1.seeds m2 = Model() m2.build(same2) same2seeds = m2.seeds m3 = Model() m3.build(diff) diffseeds = m3.seeds for diffobj, same2obj in zip(diffobjs, same2objs): # These seeds should all be different assert diffseeds[diffobj] != same2seeds[same2obj] # Skip the extra ensemble same2objs = same2objs[:2] + same2objs[3:] for same1obj, same2obj in zip(same1objs, same2objs): # These seeds should all be the same assert same1seeds[same1obj] == same2seeds[same2obj]
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] = []
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. """ self.dt = dt if model is None: self.model = Model(dt=self.dt, label="%s, dt=%f" % (network.label, dt), seed=network.seed) else: self.model = model if network is not None: # Build the network into the model Builder.build(network, model=self.model) # Use model seed as simulator seed if the seed is not provided # Note: seed is not used right now, but one day... self.seed = self.model.seed if seed is None else 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.dt) 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, self.dt) for node in self._step_order] self.n_steps = 0 # 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)
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
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]
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)
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] = []
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
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)
class Simulator: def add_sig(self, signal_to_engine_id, signal): if signal is None or signal in signal_to_engine_id: pass elif signal.base is None or signal is signal.base: signal_to_engine_id[signal] = SignalArrayF64(signal) else: current = signal sliceinfo = slices_from_signal(signal) while (current.base.base is not None and current.base.base is not current.base): current = current.base sliceinfo = tuple( slice( b.start + b.step * a.start, b.start + b.step * a.stop, a.step * b.step, ) for a, b in zip(sliceinfo, slices_from_signal(current))) self.add_sig(signal_to_engine_id, current.base) try: signal_to_engine_id[signal] = SignalArrayViewF64( signal.name, sliceinfo, signal_to_engine_id[current.base]) except TypeError: print( f"TypeError: {signal.name} {sliceinfo} {current.base} {signal_to_engine_id[current.base]}" ) raise def get_sig(self, signal_to_engine_id, signal): self.add_sig(signal_to_engine_id, signal) return signal_to_engine_id[signal] 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() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def close(self): pass @property def dt(self): return self.model.dt def run(self, time_in_seconds): print("run") n_steps = int(time_in_seconds / self.dt) self._engine.run_steps(n_steps) def run_step(self): self._engine.run_step() def trange(self): step = self._sig_to_ngine_id[self.model.step].get() return np.arange(1, step + 1) * self.dt
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()
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] = []
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()