def test_node_neurons(decode_neurons, tolerance, Simulator, seed, plt): sim_time = 0.2 stim_fn = lambda t: 0.9 * np.sin(2 * np.pi * t / sim_time) out_synapse = nengo.Alpha(0.03) stim_synapse = out_synapse.combine(nengo.Alpha(0.005)) with nengo.Network(seed=seed) as model: stim = nengo.Node(stim_fn) a = nengo.Ensemble(n_neurons=100, dimensions=1) nengo.Connection(stim, a) p_stim = nengo.Probe(stim, synapse=stim_synapse) p_a = nengo.Probe(a, synapse=out_synapse) build_model = Model() build_model.node_neurons = decode_neurons with Simulator(model, model=build_model) as sim: sim.run(sim_time) t = sim.trange() target = sim.data[p_stim] error = np.abs(sim.data[p_a] - target).mean() plt.plot(t, target) plt.plot(t, sim.data[p_a]) plt.ylim([-1.1, 1.1]) plt.title("error = %0.2e" % error) assert error < tolerance
def test_bad_gain_error(Simulator): with nengo.Network() as net: nengo.Ensemble(5, 1, intercepts=nengo.dists.Choice([2.0])) model = Model() model.intercept_limit = 10.0 with pytest.raises(BuildError, match="negative.*gain"): with Simulator(net, model=model): pass
def test_add_inputs(decode_neurons, tolerance, Simulator, seed, plt): sim_time = 2.0 pres_time = sim_time / 4 eval_time = sim_time / 8 stim_values = [[0.5, 0.5], [0.5, -0.9], [-0.7, -0.3], [-0.3, 1.0]] stim_times = np.arange(0, sim_time, pres_time) stim_fn_a = nengo.processes.Piecewise( {t: stim_values[i][0] for i, t in enumerate(stim_times)}) stim_fn_b = nengo.processes.Piecewise( {t: stim_values[i][1] for i, t in enumerate(stim_times)}) with nengo.Network(seed=seed) as model: stim_a = nengo.Node(stim_fn_a) stim_b = nengo.Node(stim_fn_b) a = nengo.Ensemble(n_neurons=100, dimensions=1) b = nengo.Ensemble(n_neurons=100, dimensions=1) nengo.Connection(stim_a, a) nengo.Connection(stim_b, b) c = nengo.Ensemble(n_neurons=100, dimensions=1) nengo.Connection(a, c) nengo.Connection(b, c) out_synapse = nengo.Alpha(0.03) stim_synapse = out_synapse.combine(nengo.Alpha(0.005)).combine( nengo.Alpha(0.005)) p_stim_a = nengo.Probe(stim_a, synapse=stim_synapse) p_stim_b = nengo.Probe(stim_b, synapse=stim_synapse) p_c = nengo.Probe(c, synapse=out_synapse) build_model = Model() build_model.decode_neurons = decode_neurons with Simulator(model, model=build_model) as sim: sim.run(sim_time) t = sim.trange() tmask = np.zeros(t.shape, dtype=bool) for pres_t in np.arange(0, sim_time, pres_time): t0 = pres_t + pres_time - eval_time t1 = pres_t + pres_time tmask |= (t >= t0) & (t <= t1) target = sim.data[p_stim_a] + sim.data[p_stim_b] error = np.abs(sim.data[p_c][tmask] - target[tmask]).mean() plt.plot(t, target) plt.plot(t, sim.data[p_c]) plt.ylim([-1.1, 1.1]) plt.title("error = %0.2e" % error) assert error < tolerance
def test_pes_overflow(plt, seed, Simulator): dims = 3 n_per_dim = 300 tau = 0.01 simtime = 0.6 model, probes = pes_network( n_per_dim, dims, seed, learn_synapse=tau, input_scale=np.linspace(1, 0.7, dims), learning_rule_type=nengo.PES(learning_rate=1e-2), period=simtime, ) loihi_model = Model() # set learning_wgt_exp low to create overflow in weight values loihi_model.pes_wgt_exp = -2 with Simulator( model, model=loihi_model, hardware_options={"allocator": RoundRobin()}, ) as loihi_sim: loihi_sim.run(simtime) t = loihi_sim.trange() post_tmask = t > simtime - 0.1 dec_tau = loihi_sim.model.decode_tau y = loihi_sim.data[probes["stim"]] y_dpre = nengo.Lowpass(dec_tau).filt(y) y_dpost = nengo.Lowpass(tau).combine(nengo.Lowpass(dec_tau)).filt(y_dpre) y_loihi = loihi_sim.data[probes["post"]] plt.plot(t, y_dpost, "k", label="target") plt.plot(t, y_loihi, "g", label="loihi") # --- fit output to scaled version of target output z_ref0 = y_dpost[post_tmask][:, 0] z_loihi = y_loihi[post_tmask] scale = np.linspace(0, 1, 50) E = np.abs(z_loihi - scale[:, None, None] * z_ref0[:, None]) errors = E.mean(axis=1) # average over time (errors is: scales x dims) for j in range(dims): errors_j = errors[:, j] i = np.argmin(errors_j) assert errors_j[i] < 0.1, ("Learning output for dim %d did not match " "any scaled version of the target output" % j) assert scale[i] > 0.25, "Learning output for dim %d is too small" % j assert scale[i] < 0.9, ( "Learning output for dim %d is too large " "(weights or traces not clipping as expected)" % j)
def _basic_model(): model = Model() block0 = LoihiBlock(1) block0.compartment.configure_lif() model.add_block(block0) block1 = LoihiBlock(1) block1.compartment.configure_lif() model.add_block(block1) axon1 = Axon(1) block0.add_axon(axon1) synapse1 = Synapse(1) synapse1.set_full_weights([[1]]) axon1.target = synapse1 block1.add_synapse(synapse1) axon0 = Axon(1) input = LoihiInput() input.add_axon(axon0) model.add_input(input) synapse0 = Synapse(1) synapse0.set_full_weights([[1]]) axon0.target = synapse0 block0.add_synapse(synapse0) discretize_model(model) return model
def test_intercept_limit(passed_intercepts, rng): model = Model() assert model.intercept_limit == 0.95 ens = nengo.Ensemble(10000, 1, intercepts=passed_intercepts, add_to_container=False) with pytest.warns(UserWarning): _, _, _, intercepts = get_gain_bias(ens, rng, model.intercept_limit) assert np.all(intercepts <= model.intercept_limit)
def test_builder_poptype_errors(): pytest.importorskip("nxsdk") # Test error in build_synapse model = Model() block = LoihiBlock(1) block.compartment.configure_lif() model.add_block(block) synapse = Synapse(1) synapse.set_weights([[1]]) synapse.pop_type = 8 block.add_synapse(synapse) discretize_model(model) allocator = Greedy() # one core per ensemble board = allocator(model, n_chips=1) with pytest.raises(ValueError, match="[Ss]ynapse.*[Uu]nrec.*pop.*type"): build_board(board) # Test error in collect_axons model = Model() block0 = LoihiBlock(1) block0.compartment.configure_lif() model.add_block(block0) block1 = LoihiBlock(1) block1.compartment.configure_lif() model.add_block(block1) axon = Axon(1) block0.add_axon(axon) synapse = Synapse(1) synapse.set_weights([[1]]) synapse.pop_type = 8 axon.target = synapse block1.add_synapse(synapse) discretize_model(model) board = allocator(model, n_chips=1) with pytest.raises(ValueError, match="[Aa]xon.*[Uu]nrec.*pop.*type"): build_board(board)
def test_strict_mode(strict, monkeypatch): # Tests should be run in strict mode assert EmulatorInterface.strict model = Model() model.add_block(LoihiBlock(1)) monkeypatch.setattr(EmulatorInterface, "strict", strict) emu = EmulatorInterface(model) assert emu.strict == strict if strict: check = pytest.raises(SimulationError, match="Error in emulator") else: check = pytest.warns(UserWarning) with check: emu.compartment.error("Error in emulator")
def test_build_callback(Simulator): with nengo.Network() as net: a = nengo.Ensemble(3, 1) b = nengo.Ensemble(3, 1) c = nengo.Connection(a, b) objs = [] def build_callback(obj): objs.append(obj) model = Model() model.build_callback = build_callback with Simulator(net, model=model): pass for obj in (a, b, c): assert obj in objs, "%s not in objs" % obj
def test_utilization(): comp_fracs = [0.9, 0.2, 0.35] model = Model() for comp_frac in comp_fracs: n_compartments = int(round(comp_frac * MAX_COMPARTMENTS)) block = LoihiBlock(n_compartments) block.compartment.configure_relu() model.add_block(block) util = block.utilization() assert np.allclose( util["compartments"], (n_compartments, MAX_COMPARTMENTS), rtol=0, atol=0.001 ) lines = model.utilization_summary() assert len(lines) == len(comp_fracs) + 1 assert lines[-1].startswith("Average")
def _basic_model(n_blocks=2): model = Model() blocks = [] for _ in range(n_blocks): block = LoihiBlock(1) block.compartment.configure_lif() model.add_block(block) blocks.append(block) for i in range(n_blocks - 1): axon = Axon(1) blocks[i].add_axon(axon) synapse = Synapse(1) synapse.set_weights([[1]]) axon.target = synapse blocks[i + 1].add_synapse(synapse) axon0 = Axon(1) input = LoihiInput() input.add_axon(axon0) model.add_input(input) synapse0 = Synapse(1) synapse0.set_weights([[1]]) axon0.target = synapse0 blocks[0].add_synapse(synapse0) discretize_model(model) return model
def test_negative_base(request, seed): n_axons = 3 model = Model() input = SpikeInput(n_axons) input.add_spikes(1, list(range(n_axons)), permanent=True) model.add_input(input) axon = Axon(n_axons) input.add_axon(axon) block = LoihiBlock(3) block.compartment.configure_relu() model.add_block(block) synapse = Synapse(n_axons) weights = [0.1, 0.1, 0.1] indices = [0, 1, 2] axon_to_weight_map = list(range(n_axons)) bases = [0, 1, -1] synapse.set_population_weights( weights, indices, axon_to_weight_map, bases, pop_type=32 ) axon.target = synapse block.add_synapse(synapse) probe = LoihiProbe(target=block, key="voltage") model.add_probe(probe) discretize_model(model) n_steps = 2 if request.config.getoption("--target") == "loihi": with HardwareInterface(model, use_snips=False, seed=seed) as sim: sim.run_steps(n_steps) y = sim.collect_probe_output(probe) else: with EmulatorInterface(model, seed=seed) as sim: sim.run_steps(n_steps) y = sim.collect_probe_output(probe) # Compartments 0 and 2 should change from axons 0 and 1. # Axon 2 should have no effect, and not change compartment 1 (the sum of # its base and index), or other compartments (e.g. 2 if base ignored) assert np.allclose(y[1, 1], 0), "Third axon not ignored" assert np.allclose(y[1, 0], y[1, 2]), "Third axon targeting another" assert not np.allclose(y[1], y[0]), "Voltage not changing"
def test_noise_amplitude_warnings(Simulator, seed): with nengo.Network(seed=seed) as net: a = nengo.Ensemble(5, 1) b = nengo.Ensemble(5, 1) nengo.Connection(a, b) model = Model() model.decode_neurons = NoisyDecodeNeurons(10, noise_exp=5) with pytest.warns(UserWarning, match="[Nn]oise.*exceeds.*upper"): with Simulator(net, model=model): pass model = Model() model.decode_neurons = NoisyDecodeNeurons(10, noise_exp=-7) with pytest.warns(UserWarning, match="[Nn]oise.*below.*lower"): with Simulator(net, model=model): pass
class Simulator(object): """Nengo Loihi simulator for Loihi hardware and emulator. The simulator takes a `nengo.Network` and builds internal data structures to run the model defined by that network on Loihi emulator or hardware. Run the simulator with the `.Simulator.run` method, and access probed data through the ``data`` attribute. Building and running the simulation allocates resources. To properly free these resources, call the `.Simulator.close` method. Alternatively, `.Simulator.close` will automatically be called if you use ``with`` syntax:: with nengo_loihi.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 the *model* parameter 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. precompute : bool, optional (Default: True) Whether model inputs should be precomputed to speed up simulation. When *precompute* is False, the simulator will be run one step at a time in order to use model outputs as inputs in other parts of the model. target : str, optional (Default: None) Whether the simulator should target the emulator (``'sim'``) or Loihi hardware (``'loihi'``). If None, *target* will default to ``'loihi'`` if NxSDK is installed, and the emulator if it is not. Attributes ---------- closed : bool Whether the simulator has been closed. Once closed, it cannot be reopened. data : ProbeDict The dictionary mapping from Nengo objects to the data associated with those objects. In particular, each `nengo.Probe` maps to the data probed while running the simulation. model : Model The `.Model` containing the data structures necessary for simulating the network. precompute : bool Whether model inputs should be precomputed to speed up simulation. When *precompute* is False, the simulator will be run one step at a time in order to use model outputs as inputs in other parts of the model. """ # '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 tests whose names start with 'test_pes'. unsupported = [ # no ensembles on chip ('test_circularconv.py:*', "no ensembles onchip"), ('test_product.py:test_direct_mode_with_single_neuron', "no ensembles onchip"), ('test_connection.py:test_neuron_slicing', "no ensembles onchip"), ('test_connection.py:test_boolean_indexing', "no ensembles onchip"), ('test_learning_rules.py:test_pes_synapse*', "no ensembles onchip"), ('test_learning_rules.py:test_pes_recurrent_slice*', "no ensembles onchip"), ('test_neurons.py:test_amplitude[[]LIFRate[]]', "no ensembles onchip"), ('test_neurons.py:test_amplitude[[]RectifiedLinear[]]', "no ensembles onchip"), ('test_neurons.py:test_alif_rate', "no ensembles onchip"), ('test_neurons.py:test_izhikevich', "no ensembles onchip"), ('test_neurons.py:test_sigmoid_response_curves*', "no ensembles onchip"), ('test_node.py:test_[!i]*', "no ensembles onchip"), ('test_probe.py:test_multirun', "no ensembles onchip"), ('test_probe.py:test_dts', "no ensembles onchip"), ('test_probe.py:test_large', "no ensembles onchip"), ('test_probe.py:test_conn_output', "no ensembles onchip"), ('test_processes.py:test_time', "no ensembles onchip"), ('test_processes.py:test_brownnoise', "no ensembles onchip"), ('test_processes.py:test_gaussian_white*', "no ensembles onchip"), ('test_processes.py:test_whitesignal*', "no ensembles onchip"), ('test_processes.py:test_reset', "no ensembles onchip"), ('test_processes.py:test_seed', "no ensembles onchip"), ('test_processes.py:test_present_input', "no ensembles onchip"), ('test_processes.py:TestPiecewise*', "no ensembles onchip"), ('test_simulator.py:test_steps', "no ensembles onchip"), ('test_simulator.py:test_time_absolute', "no ensembles onchip"), ('test_simulator.py:test_trange*', "no ensembles onchip"), ('test_simulator.py:test_probe_cache', "no ensembles onchip"), ('test_simulator.py:test_invalid_run_time', "no ensembles onchip"), ('test_simulator.py:test_sample_every*', "no ensembles onchip"), ('test_synapses.py:test_lowpass', "no ensembles onchip"), ('test_synapses.py:test_alpha', "no ensembles onchip"), ('test_synapses.py:test_triangle', "no ensembles onchip"), ('test_synapses.py:test_linearfilter', "no ensembles onchip"), ('utils/*test_ensemble.py:test_*_curves_direct_mode*', "no ensembles onchip"), ('utils/*test_network.py:*direct_mode_learning[learning_rule[123]*', "no ensembles onchip"), ('utils/*test_neurons.py:test_rates_*', "no ensembles onchip"), # accuracy ('test_actionselection.py:test_basic', "inaccurate"), ('test_am_[!s]*', "integrator instability"), ('test_ensemblearray.py:test_matrix_mul', "inaccurate"), ('test_product.py:test_sine_waves', "inaccurate"), ('test_workingmemory.py:test_inputgatedmemory', "inaccurate"), ('test_cortical.py:test_convolution', "inaccurate"), ('test_thalamus.py:test_routing', "inaccurate"), ('test_thalamus.py:test_nondefault_routing', "inaccurate"), ('test_connection.py:test_node_to_ensemble*', "inaccurate"), ('test_connection.py:test_neurons_to_node*', "inaccurate"), ('test_connection.py:test_function_and_transform', "inaccurate"), ('test_connection.py:test_weights*', "inaccurate"), ('test_connection.py:test_vector*', "inaccurate"), ('test_connection.py:test_slicing*', "inaccurate"), ('test_connection.py:test_function_output_size', "inaccurate"), ('test_connection.py:test_function_points', "inaccurate"), ('test_ensemble.py:test_scalar*', "inaccurate"), ('test_ensemble.py:test_vector*', "inaccurate"), ('test_learning_rules.py:test_pes_transform', "inaccurate"), ('test_learning_rules.py:test_slicing', "inaccurate"), ('test_neurons.py:test_alif', "inaccurate"), ('test_neurons.py:test_amplitude[[]LIF[]]', "inaccurate"), ('test_neurons.py:test_amplitude[[]SpikingRectifiedLinear[]]', "inaccurate"), ('test_presets.py:test_thresholding_preset', "inaccurate"), ('test_synapses.py:test_decoders', "inaccurate"), ('test_actionselection.py:test_basic', "inaccurate"), ('test_actionselection.py:test_thalamus', "inaccurate"), # builder inconsistencies ('test_connection.py:test_neurons_to_ensemble*', "transform shape not implemented"), ('test_connection.py:test_transform_probe', "transform shape not implemented"), ('test_connection.py:test_list_indexing*', "indexing bug?"), ('test_connection.py:test_prepost_errors', "learning bug?"), ('test_ensemble.py:test_gain_bias_warning', "warning not raised"), ('test_ensemble.py:*invalid_intercepts*', "BuildError not raised"), ('test_learning_rules.py:test_pes_ens_*', "learning bug?"), ('test_learning_rules.py:test_pes_weight_solver', "learning bug?"), ('test_learning_rules.py:test_pes_neuron_*', "learning bug?"), ('test_learning_rules.py:test_pes_multidim_error', "dict of learning rules not handled"), ('test_learning_rules.py:test_reset*', "learning bug?"), ('test_neurons.py:test_lif_min_voltage*', "lif.min_voltage ignored"), ('test_neurons.py:test_lif_zero_tau_ref', "lif.tau_ref ignored"), ('test_probe.py:test_input_probe', "shape mismatch"), ('test_probe.py:test_slice', "ObjView not handled properly"), ('test_probe.py:test_update_timing', "probe bug?"), ('test_solvers.py:test_nosolver*', "NoSolver bug"), # reset bugs ('test_neurons.py:test_reset*', "sim.reset not working correctly"), # non-PES learning rules ('test_learning_rules.py:test_unsupervised*', "non-PES learning rules not implemented"), ('test_learning_rules.py:test_dt_dependence*', "non-PES learning rules not implemented"), ('*voja*', "voja not implemented"), ('test_learning_rules.py:test_custom_type', "non-PES learning rules not implemented"), # Nengo bug ('test_simulator.py:test_entry_point', "logic should be more flexible"), # ensemble noise ('test_ensemble.py:test_noise*', "ensemble.noise not implemented"), # probe types ('test_connection.py:test_dist_transform', "probe type not implemented"), ('test_connection.py:test_decoder_probe', "probe type not implemented"), ('test_probe.py:test_defaults', "probe type not implemented"), ('test_probe.py:test_ensemble_encoders', "probe type not implemented"), # probe.sample_every ('test_integrator.py:test_integrator', "probe.sample_every not implemented"), ('test_oscillator.py:test_oscillator', "probe.sample_every not implemented"), ('test_ensemble.py:test_product*', "probe.sample_every not implemented"), ('test_neurons.py:test_dt_dependence*', "probe.sample_every not implemented"), ('test_probe.py:test_multiple_probes', "probe.sample_every not implemented"), # needs better place and route ('test_ensemble.py:test_eval_points_heuristic*', "max number of compartments exceeded"), ('test_neurons.py:test_lif*', "idxBits out of range"), ('test_basalganglia.py:test_basal_ganglia', "output_axons exceedecd max"), ('test_cortical.py:test_connect', "total synapse bits exceeded max"), ('test_cortical.py:test_transform', "total synapse bits exceeded max"), ('test_cortical.py:test_translate', "total synapse bits exceeded max"), ('test_memory.py:test_run', "total synapse bits exceeded max"), ('test_memory.py:test_run_decay', "total synapse bits exceeded max"), ('test_state.py:test_memory_run', "total synapse bits exceeded max"), ('test_state.py:test_memory_run_decay', "total synapse bits exceeded max"), ('test_bind.py:test_run', "exceeded max cores per chip on loihi"), # serialization / deserialization ('test_cache.py:*', "model pickling not implemented"), ('test_copy.py:test_pickle_model', "model pickling not implemented"), ('test_simulator.py:test_signal_init_values', "nengo.builder.Model instances not handled"), # progress bars ('test_simulator.py:test_simulator_progress_bars', "progress bars not implemented"), # utils.connection.target_function (deprecated) ('utils/tests/test_connection.py*', "target_function (deprecated) not working"), # removing passthroughs changes test behaviour ('test_connection.py:test_zero_activities_error', "decoded connection optimized away"), ('test_connection.py:test_function_returns_none_error', "decoded connection optimized away"), ] def __init__( # noqa: C901 self, network, dt=0.001, seed=None, model=None, precompute=False, target=None, progress_bar=None, remove_passthrough=True ): self.closed = True # Start closed in case constructor raises exception if progress_bar is not None: raise NotImplementedError("progress bars not implemented") if model is None: # Call the builder to make a model self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt)) else: assert isinstance(model, Model), ( "model is not type 'nengo_loihi.builder.Model'") self.model = model assert self.model.dt == dt max_rate = self.model.inter_rate * self.model.inter_n rtol = 1e-8 # allow for floating point inaccuracies if max_rate > (1. / self.dt) * (1 + rtol): raise BuildError("Simulator `dt` must be <= %s (got %s)" % (1. / max_rate, self.dt)) self.precompute = precompute self.networks = None self.sims = OrderedDict() self._run_steps = None if network is not None: nengo.rc.set("decoder_cache", "enabled", "False") config.add_params(network) # split the host into one, two or three networks self.networks = split( network, precompute, max_rate, self.model.inter_tau, remove_passthrough=remove_passthrough, ) network = self.networks.chip self.model.chip2host_params = self.networks.chip2host_params self.chip = self.networks.chip self.host = self.networks.host self.host_pre = self.networks.host_pre if len(self.host_pre.all_objects) > 0: self.sims["host_pre"] = nengo.Simulator(self.host_pre, dt=self.dt, progress_bar=False, optimize=False) if len(self.host.all_objects) > 0: self.sims["host"] = nengo.Simulator( self.host, dt=self.dt, progress_bar=False, optimize=False) elif not precompute: # If there is no host and precompute=False, then all objects # must be on the chip, which is precomputable in the sense that # no communication has to happen with the host. # We could warn about this, but we want to avoid people having # to specify `precompute` unless they absolutely have to. self.precompute = True # Build the network into the model self.model.build(network) self._probe_outputs = self.model.params self.data = ProbeDict(self._probe_outputs) for sim in self.sims.values(): self.data.add_fallback(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) if target is None: try: import nxsdk target = 'loihi' except ImportError: target = 'sim' self.target = target logger.info("Simulator target is %r", target) logger.info("Simulator precompute is %r", self.precompute) if target != "simreal": self.model.discretize() if target in ("simreal", "sim"): self.sims["emulator"] = CxSimulator(self.model, seed=seed) elif target == 'loihi': self.sims["loihi"] = LoihiSimulator( self.model, use_snips=not self.precompute, seed=seed) else: raise ValidationError("Must be 'simreal', 'sim', or 'loihi'", attr="target") assert "emulator" in self.sims or "loihi" in self.sims 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): for sim in self.sims.values(): sim.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): for sim in self.sims.values(): sim.__exit__(exc_type, exc_value, traceback) self.close() @property def dt(self): """(float) The step time 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. """ for sim in self.sims.values(): if not sim.closed: sim.close() self.closed = True def _probe(self): """Copy all probed signals to buffers.""" self._probe_step_time() for probe in self.model.probes: if probe in self.networks.chip2host_params: continue assert probe.sample_every is None, ( "probe.sample_every not implemented") assert ("loihi" not in self.sims or "emulator" not in self.sims) cx_probe = self.model.objs[probe]['out'] if "loihi" in self.sims: data = self.sims["loihi"].get_probe_output(cx_probe) elif "emulator" in self.sims: data = self.sims["emulator"].get_probe_output(cx_probe) # TODO: stop recomputing this all the time del self._probe_outputs[probe][:] self._probe_outputs[probe].extend(data) assert len(self._probe_outputs[probe]) == self.n_steps, ( len(self._probe_outputs[probe]), self.n_steps) def _probe_step_time(self): self._time = self._n_steps * self.dt 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 self._n_steps = 0 self._time = 0 # clear probe data for probe in self.model.probes: self._probe_outputs[probe] = [] self.data.reset() def run(self, time_in_seconds): """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. """ 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) def step(self): """Advance the simulator by 1 step (``dt`` seconds).""" self.run_steps(1) def _collect_receiver_info(self): spikes = [] errors = {} for sender, receiver in self.networks.host2chip_senders.items(): receiver.clear() for t, x in sender.queue: receiver.receive(t, x) del sender.queue[:] if hasattr(receiver, 'collect_spikes'): for cx_spike_input, t, spike_idxs in receiver.collect_spikes(): ti = round(t / self.model.dt) spikes.append((cx_spike_input, ti, spike_idxs)) if hasattr(receiver, 'collect_errors'): for probe, t, e in receiver.collect_errors(): conn = self.model.probe_conns[probe] synapses = self.model.objs[conn]['decoders'] assert synapses.tracing ti = round(t / self.model.dt) errors_ti = errors.setdefault(ti, {}) if synapses in errors_ti: errors_ti[synapses] += e else: errors_ti[synapses] = e.copy() errors = [(synapses, ti, e) for ti, ee in errors.items() for synapses, e in ee.items()] return spikes, errors def _host2chip(self, sim): spikes, errors = self._collect_receiver_info() sim.host2chip(spikes, errors) def _chip2host(self, sim): probes_receivers = { # map cx_probes to receivers self.model.objs[probe]['out']: receiver for probe, receiver in self.networks.chip2host_receivers.items()} sim.chip2host(probes_receivers) def _make_run_steps(self): if self._run_steps is not None: return assert "emulator" not in self.sims or "loihi" not in self.sims if "emulator" in self.sims: self._make_emu_run_steps() else: self._make_loihi_run_steps() def _make_emu_run_steps(self): host_pre = self.sims.get("host_pre", None) emulator = self.sims["emulator"] host = self.sims.get("host", None) if self.precompute: if host_pre is not None and host is not None: def emu_precomputed_host_pre_and_host(steps): host_pre.run_steps(steps) self._host2chip(emulator) emulator.run_steps(steps) self._chip2host(emulator) host.run_steps(steps) self._run_steps = emu_precomputed_host_pre_and_host elif host_pre is not None: def emu_precomputed_host_pre_only(steps): host_pre.run_steps(steps) self._host2chip(emulator) emulator.run_steps(steps) self._run_steps = emu_precomputed_host_pre_only elif host is not None: def emu_precomputed_host_only(steps): emulator.run_steps(steps) self._chip2host(emulator) host.run_steps(steps) self._run_steps = emu_precomputed_host_only else: self._run_steps = emulator.run_steps else: assert host is not None, "Model is precomputable" def emu_bidirectional_with_host(steps): for _ in range(steps): host.step() self._host2chip(emulator) emulator.step() self._chip2host(emulator) self._run_steps = emu_bidirectional_with_host def _make_loihi_run_steps(self): host_pre = self.sims.get("host_pre", None) loihi = self.sims["loihi"] host = self.sims.get("host", None) if self.precompute: if host_pre is not None and host is not None: def loihi_precomputed_host_pre_and_host(steps): host_pre.run_steps(steps) self._host2chip(loihi) loihi.run_steps(steps, blocking=True) self._chip2host(loihi) host.run_steps(steps) self._run_steps = loihi_precomputed_host_pre_and_host elif host_pre is not None: def loihi_precomputed_host_pre_only(steps): host_pre.run_steps(steps) self._host2chip(loihi) loihi.run_steps(steps, blocking=True) self._run_steps = loihi_precomputed_host_pre_only elif host is not None: def loihi_precomputed_host_only(steps): loihi.run_steps(steps, blocking=True) self._chip2host(loihi) host.run_steps(steps) self._run_steps = loihi_precomputed_host_only else: self._run_steps = loihi.run_steps else: assert host is not None, "Model is precomputable" def loihi_bidirectional_with_host(steps): loihi.run_steps(steps, blocking=False) for _ in range(steps): host.step() self._host2chip(loihi) self._chip2host(loihi) logger.info("Waiting for run_steps to complete...") loihi.wait_for_completion() logger.info("run_steps completed") self._run_steps = loihi_bidirectional_with_host def run_steps(self, steps): """Simulate for the given number of ``dt`` steps. Parameters ---------- steps : int Number of steps to run the simulation for. """ if self.closed: raise SimulatorClosed("Simulator cannot run because it is closed.") if self._run_steps is None: self._make_run_steps() try: self._run_steps(steps) except Exception: if "loihi" in self.sims and self.sims["loihi"].use_snips: # Need to write to board, otherwise it will wait indefinitely h2c = self.sims["loihi"].nengo_io_h2c c2h = self.sims["loihi"].nengo_io_c2h print(traceback.format_exc()) print("\nAttempting to end simulation...") for _ in range(steps): h2c.write(h2c.numElements, [0] * h2c.numElements) c2h.read(c2h.numElements) self.sims["loihi"].wait_for_completion() self.sims["loihi"].n2board.nxDriver.stopExecution() self.sims["loihi"].n2board.nxDriver.stopDriver() raise self._n_steps += steps logger.info("Finished running for %d steps", steps) self._probe() def trange(self, sample_every=None, 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 ---------- sample_every : float, optional (Default: None) The sampling period of the probe to create a range for. If None, a time value for every ``dt`` will be produced. """ 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]
def __init__( # noqa: C901 self, network, dt=0.001, seed=None, model=None, precompute=False, target=None, progress_bar=None, remove_passthrough=True ): self.closed = True # Start closed in case constructor raises exception if progress_bar is not None: raise NotImplementedError("progress bars not implemented") if model is None: # Call the builder to make a model self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt)) else: assert isinstance(model, Model), ( "model is not type 'nengo_loihi.builder.Model'") self.model = model assert self.model.dt == dt max_rate = self.model.inter_rate * self.model.inter_n rtol = 1e-8 # allow for floating point inaccuracies if max_rate > (1. / self.dt) * (1 + rtol): raise BuildError("Simulator `dt` must be <= %s (got %s)" % (1. / max_rate, self.dt)) self.precompute = precompute self.networks = None self.sims = OrderedDict() self._run_steps = None if network is not None: nengo.rc.set("decoder_cache", "enabled", "False") config.add_params(network) # split the host into one, two or three networks self.networks = split( network, precompute, max_rate, self.model.inter_tau, remove_passthrough=remove_passthrough, ) network = self.networks.chip self.model.chip2host_params = self.networks.chip2host_params self.chip = self.networks.chip self.host = self.networks.host self.host_pre = self.networks.host_pre if len(self.host_pre.all_objects) > 0: self.sims["host_pre"] = nengo.Simulator(self.host_pre, dt=self.dt, progress_bar=False, optimize=False) if len(self.host.all_objects) > 0: self.sims["host"] = nengo.Simulator( self.host, dt=self.dt, progress_bar=False, optimize=False) elif not precompute: # If there is no host and precompute=False, then all objects # must be on the chip, which is precomputable in the sense that # no communication has to happen with the host. # We could warn about this, but we want to avoid people having # to specify `precompute` unless they absolutely have to. self.precompute = True # Build the network into the model self.model.build(network) self._probe_outputs = self.model.params self.data = ProbeDict(self._probe_outputs) for sim in self.sims.values(): self.data.add_fallback(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) if target is None: try: import nxsdk target = 'loihi' except ImportError: target = 'sim' self.target = target logger.info("Simulator target is %r", target) logger.info("Simulator precompute is %r", self.precompute) if target != "simreal": self.model.discretize() if target in ("simreal", "sim"): self.sims["emulator"] = CxSimulator(self.model, seed=seed) elif target == 'loihi': self.sims["loihi"] = LoihiSimulator( self.model, use_snips=not self.precompute, seed=seed) else: raise ValidationError("Must be 'simreal', 'sim', or 'loihi'", attr="target") assert "emulator" in self.sims or "loihi" in self.sims self.closed = False self.reset(seed=seed)
def test_pop_tiny(pop_type, channels_last, nc, request, plt, seed, allclose): tau_rc = 0.02 tau_ref = 0.001 tau_s = 0.0 dt = 0.001 neuron_bias = 1. pres_time = 0.4 sti, stj = 1, 1 if nc == 1: filters = np.array([[-0.5, 2., -0.25], [-0.75, 2., -1.0], [-0.5, 3., -0.5], [-1.0, 6., -0.25]]).reshape(1, 4, 1, 3) inp_biases = np.array([[1, 5, 1], [2, 1, 2]]) inp_biases = inp_biases[:, :, None] elif nc == 2: filters = np.array([[[-0.5, 2., -0.2], [-0.7, 2., -1.0], [-0.5, 3., -0.5], [-1.0, 6., -0.2]], [[-1.0, 2., -1.0], [-0.5, 2., -0.5], [-0.8, 3., -0.2], [-1.0, 4., -0.2]]]).reshape(2, 4, 1, 3) inp_biases = np.array([[[1, 5, 1], [2, 1, 2]], [[0, 3, 1], [4, 2, 1]]]) inp_biases = np.transpose(inp_biases, (1, 2, 0)) # rearrange to (kernel_rows, kernel_cols, in_channels, out_channels) filters = np.transpose(filters, (2, 3, 0, 1)) inp_biases = inp_biases / (inp_biases.max() + 0.001) # --- compute nengo_loihi outputs ni, nj, nk = inp_biases.shape si, sj, nc, nf = filters.shape nij = ni * nj nyi = 1 + (ni - si) // sti nyj = 1 + (nj - sj) // stj out_size = nyi * nyj * nf assert out_size <= 1024 model = Model() # input block inp = LoihiBlock(ni * nj * nk, label='inp') assert inp.n_neurons <= 1024 inp.compartment.configure_relu() inp.compartment.bias[:] = inp_biases.ravel() inp_ax = Axon(nij, label='inp_ax') # we always compute the pixel/channel idxs with channels_last=True # (not sure why?), and then set it to the correct value afterwards inp_shape = nengo_transforms.ChannelShape((ni, nj, nk), channels_last=True) inp_ax.set_compartment_axon_map(target_axons=conv.pixel_idxs(inp_shape), atoms=conv.channel_idxs(inp_shape)) inp_shape.shape = (ni, nj, nk) if channels_last else (nk, ni, nj) inp_shape.channels_last = channels_last inp.add_axon(inp_ax) model.add_block(inp) # conv block neurons = LoihiBlock(out_size, label='neurons') assert neurons.n_neurons <= 1024 neurons.compartment.configure_lif(tau_rc=tau_rc, tau_ref=tau_ref, dt=dt) neurons.compartment.configure_filter(tau_s, dt=dt) neurons.compartment.bias[:] = neuron_bias synapse = Synapse(np.prod(inp_shape.spatial_shape), label='synapse') conv2d_transform = nengo_transforms.Convolution( nf, inp_shape, strides=(sti, stj), channels_last=channels_last, init=filters, kernel_size=(1, 3)) weights, indices, axon_to_weight_map, bases = conv.conv2d_loihi_weights( conv2d_transform) synapse.set_population_weights(weights, indices, axon_to_weight_map, bases, pop_type=pop_type) neurons.add_synapse(synapse) out_probe = Probe(target=neurons, key='spiked') neurons.add_probe(out_probe) inp_ax.target = synapse model.add_block(neurons) # simulation discretize_model(model) n_steps = int(pres_time / dt) target = request.config.getoption("--target") if target == 'loihi': with HardwareInterface(model, use_snips=False, seed=seed) as sim: sim.run_steps(n_steps) sim_out = sim.get_probe_output(out_probe) else: with EmulatorInterface(model, seed=seed) as sim: sim.run_steps(n_steps) sim_out = sim.get_probe_output(out_probe) sim_out = np.sum(sim_out, axis=0) * (dt / pres_time) if channels_last: sim_out.shape = (nyi, nyj, nf) sim_out = np.transpose(sim_out, (2, 0, 1)) else: sim_out.shape = (nf, nyi, nyj) out_max = sim_out.max() # --- plot results rows = 1 cols = 2 ax = plt.subplot(rows, cols, 1) plt.hist(sim_out.ravel(), bins=11) ax = plt.subplot(rows, cols, 2) tile(sim_out, vmin=0, vmax=out_max, grid=True, ax=ax) # ref_out determined by emulator running code known to work if nc == 1: ref_out = np.array([[0.06, 0.02], [0.055, 0.], [0.0825, 0.0225], [0.125, 0.04]]) elif nc == 2: ref_out = np.array([[0.0975, 0.02], [0.0825, 0.02], [0.125, 0.055], [0.2475, 0.0825]]) assert allclose(sim_out[:, :, 0], ref_out, rtol=0, atol=1e-7)
class Simulator: """Nengo Loihi simulator for Loihi hardware and emulator. The simulator takes a `nengo.Network` and builds internal data structures to run the model defined by that network on Loihi emulator or hardware. Run the simulator with the `.Simulator.run` method, and access probed data through the ``data`` attribute. Building and running the simulation allocates resources. To properly free these resources, call the `.Simulator.close` method. Alternatively, `.Simulator.close` will automatically be called if you use ``with`` syntax:: with nengo_loihi.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 the *model* parameter 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. precompute : bool, optional (Default: True) Whether model inputs should be precomputed to speed up simulation. When *precompute* is False, the simulator will be run one step at a time in order to use model outputs as inputs in other parts of the model. target : str, optional (Default: None) Whether the simulator should target the emulator (``'sim'``) or Loihi hardware (``'loihi'``). If None, *target* will default to ``'loihi'`` if NxSDK is installed, and the emulator if it is not. hardware_options : dict, optional (Default: {}) Dictionary of additional configuration for the hardware. See `.hardware.HardwareInterface` for possible parameters. Attributes ---------- closed : bool Whether the simulator has been closed. Once closed, it cannot be reopened. data : ProbeDict The dictionary mapping from Nengo objects to the data associated with those objects. In particular, each `nengo.Probe` maps to the data probed while running the simulation. model : Model The `.Model` containing the data structures necessary for simulating the network. precompute : bool Whether model inputs should be precomputed to speed up simulation. When *precompute* is False, the simulator will be run one step at a time in order to use model outputs as inputs in other parts of the model. """ def __init__( # noqa: C901 self, network, dt=0.001, seed=None, model=None, precompute=False, target=None, progress_bar=None, remove_passthrough=True, hardware_options=None, ): # initialize values used in __del__ and close() first self.closed = True self.precompute = precompute self.networks = None self.sims = OrderedDict() self._run_steps = None hardware_options = {} if hardware_options is None else hardware_options if progress_bar: warnings.warn("nengo-loihi does not support progress bars") if HAS_DL: install_dl_builders() if model is None: # Call the builder to make a model self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt)) else: assert isinstance(model, Model), ( "model is not type 'nengo_loihi.builder.Model'") self.model = model assert self.model.dt == dt if network is not None: nengo.rc.set("decoder_cache", "enabled", "False") config.add_params(network) # ensure seeds are identical to nengo seed_network(network, seeds=self.model.seeds, seeded=self.model.seeded) # split the host into one, two or three networks self.networks = split( network, precompute=precompute, node_neurons=self.model.node_neurons, node_tau=self.model.decode_tau, remove_passthrough=remove_passthrough, ) network = self.networks.chip self.model.chip2host_params = self.networks.chip2host_params self.chip = self.networks.chip self.host = self.networks.host self.host_pre = self.networks.host_pre if len(self.host_pre.all_objects) > 0: host_pre_model = self._get_host_model( self.host_pre, dt=dt, seeds=self.model.seeds, seeded=self.model.seeded) self.sims["host_pre"] = nengo.Simulator(self.host_pre, dt=self.dt, model=host_pre_model, progress_bar=False, optimize=False) if len(self.host.all_objects) > 0: host_model = self._get_host_model( self.host, dt=dt, seeds=self.model.seeds, seeded=self.model.seeded) self.sims["host"] = nengo.Simulator( self.host, dt=self.dt, model=host_model, progress_bar=False, optimize=False) elif not precompute: # If there is no host and precompute=False, then all objects # must be on the chip, which is precomputable in the sense that # no communication has to happen with the host. # We could warn about this, but we want to avoid people having # to specify `precompute` unless they absolutely have to. self.precompute = True # Build the network into the model self.model.build(network) self._probe_outputs = self.model.params self.data = ProbeDict(self._probe_outputs) for sim in self.sims.values(): self.data.add_fallback(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) if target is None: target = 'loihi' if HAS_NXSDK else 'sim' self.target = target logger.info("Simulator target is %r", target) logger.info("Simulator precompute is %r", self.precompute) if target != "simreal": discretize_model(self.model) if target in ("simreal", "sim"): self.sims["emulator"] = EmulatorInterface(self.model, seed=seed) elif target == 'loihi': assert HAS_NXSDK, "Must have NxSDK installed to use Loihi hardware" self.sims["loihi"] = HardwareInterface( self.model, use_snips=not self.precompute, seed=seed, **hardware_options) else: raise ValidationError("Must be 'simreal', 'sim', or 'loihi'", attr="target") assert "emulator" in self.sims or "loihi" in self.sims self.closed = False self.reset(seed=seed) @staticmethod def _get_host_model(network, dt, seeds, seeded): model = nengo.builder.Model( dt=float(dt), label="%s, dt=%f" % (network, dt), decoder_cache=get_default_decoder_cache()) model.seeds.update(seeds) model.seeded.update(seeded) return model 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): for sim in self.sims.values(): sim.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): for sim in self.sims.values(): sim.__exit__(exc_type, exc_value, traceback) self.close() @property def dt(self): """(float) The step time 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. """ for sim in self.sims.values(): if not sim.closed: sim.close() self.closed = True def _probe(self): """Copy all probed signals to buffers.""" self._probe_step_time() for probe in self.model.probes: if probe in self.networks.chip2host_params: continue assert probe.sample_every is None, ( "probe.sample_every not implemented") assert ("loihi" not in self.sims or "emulator" not in self.sims) loihi_probe = self.model.objs[probe]['out'] if "loihi" in self.sims: data = self.sims["loihi"].get_probe_output(loihi_probe) elif "emulator" in self.sims: data = self.sims["emulator"].get_probe_output(loihi_probe) # TODO: stop recomputing this all the time del self._probe_outputs[probe][:] self._probe_outputs[probe].extend(data) assert len(self._probe_outputs[probe]) == self.n_steps, ( len(self._probe_outputs[probe]), self.n_steps) def _probe_step_time(self): self._time = self._n_steps * self.dt 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 self._n_steps = 0 self._time = 0 # clear probe data for probe in self.model.probes: self._probe_outputs[probe] = [] self.data.reset() def run(self, time_in_seconds): """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. """ 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) def step(self): """Advance the simulator by 1 step (``dt`` seconds).""" self.run_steps(1) def _collect_receiver_info(self): spikes = [] errors = OrderedDict() for sender, receiver in self.networks.host2chip_senders.items(): receiver.clear() for t, x in sender.queue: receiver.receive(t, x) del sender.queue[:] if hasattr(receiver, 'collect_spikes'): for spike_input, t, spike_idxs in receiver.collect_spikes(): ti = round(t / self.model.dt) spikes.append((spike_input, ti, spike_idxs)) if hasattr(receiver, 'collect_errors'): for probe, t, e in receiver.collect_errors(): conn = self.model.probe_conns[probe] synapse = self.model.objs[conn]['decoders'] assert synapse.learning ti = round(t / self.model.dt) errors_ti = errors.setdefault(ti, OrderedDict()) if synapse in errors_ti: errors_ti[synapse] += e else: errors_ti[synapse] = e.copy() errors = [(synapse, ti, e) for ti, ee in errors.items() for synapse, e in ee.items()] return spikes, errors def _host2chip(self, sim): spikes, errors = self._collect_receiver_info() sim.host2chip(spikes, errors) def _chip2host(self, sim): probes_receivers = OrderedDict( # map probes to receivers (self.model.objs[probe]['out'], receiver) for probe, receiver in self.networks.chip2host_receivers.items()) sim.chip2host(probes_receivers) def _make_run_steps(self): if self._run_steps is not None: return assert "emulator" not in self.sims or "loihi" not in self.sims if "emulator" in self.sims: self._make_emu_run_steps() else: self._make_loihi_run_steps() def _make_emu_run_steps(self): host_pre = self.sims.get("host_pre", None) emulator = self.sims["emulator"] host = self.sims.get("host", None) if self.precompute: if host_pre is not None and host is not None: def emu_precomputed_host_pre_and_host(steps): host_pre.run_steps(steps) self._host2chip(emulator) emulator.run_steps(steps) self._chip2host(emulator) host.run_steps(steps) self._run_steps = emu_precomputed_host_pre_and_host elif host_pre is not None: def emu_precomputed_host_pre_only(steps): host_pre.run_steps(steps) self._host2chip(emulator) emulator.run_steps(steps) self._run_steps = emu_precomputed_host_pre_only elif host is not None: def emu_precomputed_host_only(steps): emulator.run_steps(steps) self._chip2host(emulator) host.run_steps(steps) self._run_steps = emu_precomputed_host_only else: self._run_steps = emulator.run_steps else: assert host is not None, "Model is precomputable" def emu_bidirectional_with_host(steps): for _ in range(steps): host.step() self._host2chip(emulator) emulator.step() self._chip2host(emulator) self._run_steps = emu_bidirectional_with_host def _make_loihi_run_steps(self): host_pre = self.sims.get("host_pre", None) loihi = self.sims["loihi"] host = self.sims.get("host", None) if self.precompute: if host_pre is not None and host is not None: def loihi_precomputed_host_pre_and_host(steps): host_pre.run_steps(steps) self._host2chip(loihi) loihi.run_steps(steps, blocking=True) self._chip2host(loihi) host.run_steps(steps) self._run_steps = loihi_precomputed_host_pre_and_host elif host_pre is not None: def loihi_precomputed_host_pre_only(steps): host_pre.run_steps(steps) self._host2chip(loihi) loihi.run_steps(steps, blocking=True) self._run_steps = loihi_precomputed_host_pre_only elif host is not None: def loihi_precomputed_host_only(steps): loihi.run_steps(steps, blocking=True) self._chip2host(loihi) host.run_steps(steps) self._run_steps = loihi_precomputed_host_only else: self._run_steps = loihi.run_steps else: assert host is not None, "Model is precomputable" def loihi_bidirectional_with_host(steps): loihi.run_steps(steps, blocking=False) for _ in range(steps): host.step() self._host2chip(loihi) self._chip2host(loihi) logger.info("Waiting for run_steps to complete...") loihi.wait_for_completion() logger.info("run_steps completed") self._run_steps = loihi_bidirectional_with_host def run_steps(self, steps): """Simulate for the given number of ``dt`` steps. Parameters ---------- steps : int Number of steps to run the simulation for. """ if self.closed: raise SimulatorClosed("Simulator cannot run because it is closed.") self._make_run_steps() try: self._run_steps(steps) except Exception: if "loihi" in self.sims and self.sims["loihi"].use_snips: # Need to write to board, otherwise it will wait indefinitely h2c = self.sims["loihi"].nengo_io_h2c c2h = self.sims["loihi"].nengo_io_c2h print(traceback.format_exc()) print("\nAttempting to end simulation...") for _ in range(steps): h2c.write(h2c.numElements, [0] * h2c.numElements) c2h.read(c2h.numElements) self.sims["loihi"].wait_for_completion() self.sims["loihi"].n2board.nxDriver.stopExecution() self.sims["loihi"].n2board.nxDriver.stopDriver() raise self._n_steps += steps logger.info("Finished running for %d steps", steps) self._probe() def trange(self, sample_every=None, 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 ---------- sample_every : float, optional (Default: None) The sampling period of the probe to create a range for. If None, a time value for every ``dt`` will be produced. """ 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]
def test_add_inputs(decode_neurons, tolerance, Simulator, seed, plt): """Test the addition of two inputs with DecodeNeurons. This test forms the basis for the scale factors for Preset5DecodeNeurons and Preset10DecodeNeurons. It is unclear exactly why these scale factors help. The best values depend on the exact inputs below, as well as the seed used for this test. More testing is needed to find optimal scale factors, or (ideally) get rid of them completely if we can better understand the underlying mechanics. """ sim_time = 2.0 pres_time = sim_time / 4 eval_time = sim_time / 8 stim_values = [[0.5, 0.5], [0.5, -0.9], [-0.7, -0.3], [-0.3, 1.0]] stim_times = np.arange(0, sim_time, pres_time) stim_fn_a = nengo.processes.Piecewise( {t: stim_values[i][0] for i, t in enumerate(stim_times)}) stim_fn_b = nengo.processes.Piecewise( {t: stim_values[i][1] for i, t in enumerate(stim_times)}) probe_solver = nengo.solvers.LstsqL2nz(reg=0.01) with nengo.Network(seed=seed) as model: stim_a = nengo.Node(stim_fn_a) stim_b = nengo.Node(stim_fn_b) a = nengo.Ensemble(n_neurons=100, dimensions=1) b = nengo.Ensemble(n_neurons=100, dimensions=1) nengo.Connection(stim_a, a) nengo.Connection(stim_b, b) c = nengo.Ensemble(n_neurons=100, dimensions=1) nengo.Connection(a, c) nengo.Connection(b, c) out_synapse = nengo.Alpha(0.03) stim_synapse = out_synapse.combine(nengo.Alpha(0.005)).combine( nengo.Alpha(0.005)) p_stim_a = nengo.Probe(stim_a, synapse=stim_synapse, solver=probe_solver) p_stim_b = nengo.Probe(stim_b, synapse=stim_synapse, solver=probe_solver) p_c = nengo.Probe(c, synapse=out_synapse, solver=probe_solver) build_model = Model() build_model.decode_neurons = decode_neurons with Simulator(model, model=build_model) as sim: sim.run(sim_time) t = sim.trange() tmask = np.zeros(t.shape, dtype=bool) for pres_t in np.arange(0, sim_time, pres_time): t0 = pres_t + pres_time - eval_time t1 = pres_t + pres_time tmask |= (t >= t0) & (t <= t1) target = sim.data[p_stim_a] + sim.data[p_stim_b] error = np.abs(sim.data[p_c][tmask] - target[tmask]).mean() plt.plot(t, target) plt.plot(t, sim.data[p_c]) plt.ylim([-1.1, 1.1]) plt.title("error = %0.2e" % error) assert error < tolerance
def test_one_to_one_allocator_big_block_error(): model = Model() model.add_block(LoihiBlock(1050)) with pytest.raises(ValidationError, match="Segment does not fit"): OneToOne()(model)
def __init__( # noqa: C901 self, network, dt=0.001, seed=None, model=None, precompute=None, target=None, progress_bar=None, remove_passthrough=True, hardware_options=None, ): # initialize values used in __del__ and close() first self.closed = True self.network = network self.sims = OrderedDict() self.timers = Timers() self.timers.start("build") self.seed = seed self._n_steps = 0 self._time = 0 hardware_options = {} if hardware_options is None else hardware_options if progress_bar: warnings.warn("nengo-loihi does not support progress bars") if model is None: self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt)) else: assert isinstance( model, Model), "model is not type 'nengo_loihi.builder.Model'" self.model = model assert self.model.dt == dt if network is None: raise ValidationError("network parameter must not be None", attr="network") if target is None: target = "loihi" if HAS_NXSDK else "sim" self.target = target logger.info("Simulator target is %r", target) # Build the network into the model self.model.build( network, precompute=precompute, remove_passthrough=remove_passthrough, discretize=target != "simreal", ) # Create host_pre and host simulators if necessary self.precompute = self.model.split.precompute logger.info("Simulator precompute is %r", self.precompute) assert precompute is None or precompute == self.precompute if self.model.split.precomputable() and not self.precompute: warnings.warn( "Model is precomputable. Setting precompute=False may slow execution." ) if len(self.model.host_pre.params) > 0: assert self.precompute self.sims["host_pre"] = nengo.Simulator( network=None, dt=self.dt, model=self.model.host_pre, progress_bar=False, optimize=False, ) if len(self.model.host.params) > 0: self.sims["host"] = nengo.Simulator( network=None, dt=self.dt, model=self.model.host, progress_bar=False, optimize=False, ) self._probe_outputs = self.model.params self.data = SimulationData(self._probe_outputs) for sim in self.sims.values(): self.data.add_fallback(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) if target in ("simreal", "sim"): self.sims["emulator"] = EmulatorInterface(self.model, seed=seed) elif target == "loihi": assert HAS_NXSDK, "Must have NxSDK installed to use Loihi hardware" use_snips = not self.precompute and self.sims.get("host", None) is not None self.sims["loihi"] = HardwareInterface(self.model, use_snips=use_snips, seed=seed, **hardware_options) else: raise ValidationError("Must be 'simreal', 'sim', or 'loihi'", attr="target") assert "emulator" in self.sims or "loihi" in self.sims self._runner = StepRunner(self.model, self.sims, self.precompute, self.timers) self.closed = False self.timers.stop("build")
class Simulator: """Nengo Loihi simulator for Loihi hardware and emulator. The simulator takes a `nengo.Network` and builds internal data structures to run the model defined by that network on Loihi emulator or hardware. Run the simulator with the `.Simulator.run` method, and access probed data through the ``data`` attribute. Building and running the simulation allocates resources. To properly free these resources, call the `.Simulator.close` method. Alternatively, `.Simulator.close` will automatically be called if you use ``with`` syntax:: with nengo_loihi.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 A network object to be built and then simulated. If None, then the *model* parameter 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. precompute : bool, optional (Default: None) Whether model inputs should be precomputed to speed up simulation. When *precompute* is False, the simulator will be run one step at a time in order to use model outputs as inputs in other parts of the model. By default, the simulator will choose ``True`` if it works for your model, and ``False`` otherwise. target : str, optional (Default: None) Whether the simulator should target the emulator (``'sim'``) or Loihi hardware (``'loihi'``). If None, *target* will default to ``'loihi'`` if NxSDK is installed, and the emulator if it is not. hardware_options : dict, optional (Default: {}) Dictionary of additional configuration for the hardware. See `.hardware.HardwareInterface` for possible parameters. Attributes ---------- closed : bool Whether the simulator has been closed. Once closed, it cannot be reopened. data : ProbeDict The dictionary mapping from Nengo objects to the data associated with those objects. In particular, each `nengo.Probe` maps to the data probed while running the simulation. model : Model The `.Model` containing the data structures necessary for simulating the network. precompute : bool Whether model inputs should be precomputed to speed up simulation. When *precompute* is False, the simulator will be run one step at a time in order to use model outputs as inputs in other parts of the model. """ def __init__( # noqa: C901 self, network, dt=0.001, seed=None, model=None, precompute=None, target=None, progress_bar=None, remove_passthrough=True, hardware_options=None, ): # initialize values used in __del__ and close() first self.closed = True self.network = network self.sims = OrderedDict() self.timers = Timers() self.timers.start("build") self.seed = seed self._n_steps = 0 self._time = 0 hardware_options = {} if hardware_options is None else hardware_options if progress_bar: warnings.warn("nengo-loihi does not support progress bars") if model is None: self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt)) else: assert isinstance( model, Model), "model is not type 'nengo_loihi.builder.Model'" self.model = model assert self.model.dt == dt if network is None: raise ValidationError("network parameter must not be None", attr="network") if target is None: target = "loihi" if HAS_NXSDK else "sim" self.target = target logger.info("Simulator target is %r", target) # Build the network into the model self.model.build( network, precompute=precompute, remove_passthrough=remove_passthrough, discretize=target != "simreal", ) # Create host_pre and host simulators if necessary self.precompute = self.model.split.precompute logger.info("Simulator precompute is %r", self.precompute) assert precompute is None or precompute == self.precompute if self.model.split.precomputable() and not self.precompute: warnings.warn( "Model is precomputable. Setting precompute=False may slow execution." ) if len(self.model.host_pre.params) > 0: assert self.precompute self.sims["host_pre"] = nengo.Simulator( network=None, dt=self.dt, model=self.model.host_pre, progress_bar=False, optimize=False, ) if len(self.model.host.params) > 0: self.sims["host"] = nengo.Simulator( network=None, dt=self.dt, model=self.model.host, progress_bar=False, optimize=False, ) self._probe_outputs = self.model.params self.data = SimulationData(self._probe_outputs) for sim in self.sims.values(): self.data.add_fallback(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) if target in ("simreal", "sim"): self.sims["emulator"] = EmulatorInterface(self.model, seed=seed) elif target == "loihi": assert HAS_NXSDK, "Must have NxSDK installed to use Loihi hardware" use_snips = not self.precompute and self.sims.get("host", None) is not None self.sims["loihi"] = HardwareInterface(self.model, use_snips=use_snips, seed=seed, **hardware_options) else: raise ValidationError("Must be 'simreal', 'sim', or 'loihi'", attr="target") assert "emulator" in self.sims or "loihi" in self.sims self._runner = StepRunner(self.model, self.sims, self.precompute, self.timers) self.closed = False self.timers.stop("build") 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): self.timers.start("connect") for sim in self.sims.values(): sim.__enter__() self.timers.stop("connect") return self def __exit__(self, exc_type, exc_value, traceback): for sim in self.sims.values(): sim.__exit__(exc_type, exc_value, traceback) self.close() @property def dt(self): """(float) The step time 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. """ for sim in self.sims.values(): if not sim.closed: sim.close() self._runner = None self.closed = True def _probe(self): """Copy all probed signals to buffers.""" self._probe_step_time() for probe in self.model.nengo_probes: if probe in self.model.chip2host_params: continue assert probe.sample_every is None, "probe.sample_every not implemented" assert "loihi" not in self.sims or "emulator" not in self.sims loihi_probe = self.model.objs[probe]["out"] if "loihi" in self.sims: data = self.sims["loihi"].get_probe_output(loihi_probe) elif "emulator" in self.sims: data = self.sims["emulator"].get_probe_output(loihi_probe) # TODO: stop recomputing this all the time del self._probe_outputs[probe][:] self._probe_outputs[probe].extend(data) assert len(self._probe_outputs[probe]) == self.n_steps, ( len(self._probe_outputs[probe]), self.n_steps, ) def _probe_step_time(self): self._time = self._n_steps * self.dt 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.") raise NotImplementedError() def run(self, time_in_seconds): """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. """ 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) def run_steps(self, steps): """Simulate for the given number of ``dt`` steps. Parameters ---------- steps : int Number of steps to run the simulation for. """ if self.closed: raise SimulatorClosed("Simulator cannot run because it is closed.") self._runner.run_steps(steps) self._n_steps += steps logger.info("Finished running for %d steps", steps) self._probe() def step(self): """Advance the simulator by 1 step (``dt`` seconds).""" self.run_steps(1) def trange(self, sample_every=None, 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 ---------- sample_every : float, optional (Default: None) The sampling period of the probe to create a range for. If None, a time value for every ``dt`` will be produced. """ 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]
def test_uv_overflow(n_axons, plt, allclose, monkeypatch): # TODO: Currently this is not testing the V overflow, since it is higher # and I haven't been able to figure out a way to make it overflow. nt = 15 model = Model() # n_axons controls number of input spikes and thus amount of overflow input = SpikeInput(n_axons) for t in np.arange(1, nt + 1): # send spikes to all axons input.add_spikes(t, np.arange(n_axons), permanent=True) model.add_input(input) block = LoihiBlock(1) block.compartment.configure_relu() block.compartment.configure_filter(0.1) model.add_block(block) synapse = Synapse(n_axons) synapse.set_weights(np.ones((n_axons, 1))) block.add_synapse(synapse) axon = Axon(n_axons) axon.target = synapse input.add_axon(axon) probe_u = LoihiProbe(target=block, key="current") model.add_probe(probe_u) probe_v = LoihiProbe(target=block, key="voltage") model.add_probe(probe_v) probe_s = LoihiProbe(target=block, key="spiked") model.add_probe(probe_s) discretize_model(model) # must set these after `discretize` to specify discretized values block.compartment.vmin = -(2**22) + 1 block.compartment.vth[:] = VTH_MAX assert EmulatorInterface.strict # Tests should be run in strict mode monkeypatch.setattr(EmulatorInterface, "strict", False) overflow_var = "q0" if n_axons == 1000 else "current" with EmulatorInterface(model) as emu: with pytest.warns(UserWarning, match=f"Overflow in {overflow_var}"): emu.run_steps(nt) emu_u = emu.collect_probe_output(probe_u) emu_v = emu.collect_probe_output(probe_v) emu_s = emu.collect_probe_output(probe_s) with HardwareInterface(model, use_snips=False) as sim: sim.run_steps(nt) sim_u = sim.collect_probe_output(probe_u) sim_v = sim.collect_probe_output(probe_v) sim_s = sim.collect_probe_output(probe_s) sim_v[sim_s > 0] = 0 # since Loihi has placeholder voltage after spike plt.subplot(311) plt.plot(emu_u) plt.plot(sim_u) plt.subplot(312) plt.plot(emu_v) plt.plot(sim_v) plt.subplot(313) plt.plot(emu_s) plt.plot(sim_s) assert allclose(emu_u, sim_u) assert allclose(emu_v, sim_v)
def test_simulator_noise(exp, request, plt, seed, allclose): # TODO: test that the mean falls within a number of standard errors # of the expected mean, and that non-zero offsets work correctly. # Currently, there is an unexpected negative bias for small noise # exponents, apparently because there is a probability of generating # the shifted equivalent of -128, whereas with e.g. exp = 7 all the # generated numbers fall in [-127, 127]. offset = 0 target = request.config.getoption("--target") n_cx = 1000 model = Model() block = LoihiBlock(n_cx) block.compartment.configure_relu() block.compartment.vmin = -1 block.compartment.enableNoise[:] = 1 block.compartment.noiseExp0 = exp block.compartment.noiseMantOffset0 = offset block.compartment.noiseAtDendOrVm = 1 probe = Probe(target=block, key='voltage') block.add_probe(probe) model.add_block(block) discretize_model(model) exp2 = block.compartment.noiseExp0 offset2 = block.compartment.noiseMantOffset0 n_steps = 100 if target == 'loihi': with HardwareInterface(model, use_snips=False, seed=seed) as sim: sim.run_steps(n_steps) y = sim.get_probe_output(probe) else: with EmulatorInterface(model, seed=seed) as sim: sim.run_steps(n_steps) y = sim.get_probe_output(probe) t = np.arange(1, n_steps + 1) bias = offset2 * 2.**(exp2 - 1) std = 2.**exp2 / np.sqrt(3) # divide by sqrt(3) for std of uniform -1..1 rmean = t * bias rstd = np.sqrt(t) * std rerr = rstd / np.sqrt(n_cx) ymean = y.mean(axis=1) ystd = y.std(axis=1) diffs = np.diff(np.vstack([np.zeros_like(y[0]), y]), axis=0) plt.subplot(311) plt.hist(diffs.ravel(), bins=256) plt.subplot(312) plt.plot(rmean, 'k') plt.plot(rmean + 3 * rerr, 'k--') plt.plot(rmean - 3 * rerr, 'k--') plt.plot(ymean) plt.title('mean') plt.subplot(313) plt.plot(rstd, 'k') plt.plot(ystd) plt.title('std') assert allclose(ystd, rstd, rtol=0.1, atol=1)
def test_big_block_error(): model = Model() model.add_block(LoihiBlock(1050)) with pytest.raises(ValidationError, match="Segment does not fit"): Greedy()(model, n_chips=1)
def test_builder_strings(): model = Model(label="myModel") assert str(model) == "Model(myModel)"
def test_population_input(request, allclose): target = request.config.getoption("--target") dt = 0.001 n_inputs = 3 n_axons = 1 n_cx = 2 steps = 6 spike_times_inds = [(1, [0]), (3, [1]), (5, [2])] model = Model() input = SpikeInput(n_inputs) model.add_input(input) spikes = [(input, ti, inds) for ti, inds in spike_times_inds] input_axon = Axon(n_axons) axon_map = np.zeros(n_inputs, dtype=int) atoms = np.arange(n_inputs) input_axon.set_axon_map(axon_map, atoms) input.add_axon(input_axon) block = LoihiBlock(n_cx) block.compartment.configure_lif(tau_rc=0., tau_ref=0., dt=dt) block.compartment.configure_filter(0, dt=dt) model.add_block(block) synapse = Synapse(n_axons) weights = 0.1 * np.array([[[1, 2], [2, 3], [4, 5]]], dtype=float) indices = np.array([[[0, 1], [0, 1], [0, 1]]], dtype=int) axon_to_weight_map = np.zeros(n_axons, dtype=int) cx_bases = np.zeros(n_axons, dtype=int) synapse.set_population_weights(weights, indices, axon_to_weight_map, cx_bases, pop_type=32) block.add_synapse(synapse) input_axon.target = synapse probe = Probe(target=block, key='voltage') block.add_probe(probe) discretize_model(model) if target == 'loihi': with HardwareInterface(model, use_snips=True) as sim: sim.run_steps(steps, blocking=False) for ti in range(1, steps + 1): spikes_i = [spike for spike in spikes if spike[1] == ti] sim.host2chip(spikes=spikes_i, errors=[]) sim.chip2host(probes_receivers={}) y = sim.get_probe_output(probe) else: for inp, ti, inds in spikes: inp.add_spikes(ti, inds) with EmulatorInterface(model) as sim: sim.run_steps(steps) y = sim.get_probe_output(probe) vth = block.compartment.vth[0] assert (block.compartment.vth == vth).all() z = y / vth assert allclose(z[[1, 3, 5]], weights[0], atol=4e-2, rtol=0)
def __init__( # noqa: C901 self, network, dt=0.001, seed=None, model=None, precompute=False, target=None, progress_bar=None, remove_passthrough=True, hardware_options=None, ): # initialize values used in __del__ and close() first self.closed = True self.precompute = precompute self.networks = None self.sims = OrderedDict() self._run_steps = None hardware_options = {} if hardware_options is None else hardware_options if progress_bar: warnings.warn("nengo-loihi does not support progress bars") if HAS_DL: install_dl_builders() if model is None: # Call the builder to make a model self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt)) else: assert isinstance(model, Model), ( "model is not type 'nengo_loihi.builder.Model'") self.model = model assert self.model.dt == dt if network is not None: nengo.rc.set("decoder_cache", "enabled", "False") config.add_params(network) # ensure seeds are identical to nengo seed_network(network, seeds=self.model.seeds, seeded=self.model.seeded) # split the host into one, two or three networks self.networks = split( network, precompute=precompute, node_neurons=self.model.node_neurons, node_tau=self.model.decode_tau, remove_passthrough=remove_passthrough, ) network = self.networks.chip self.model.chip2host_params = self.networks.chip2host_params self.chip = self.networks.chip self.host = self.networks.host self.host_pre = self.networks.host_pre if len(self.host_pre.all_objects) > 0: host_pre_model = self._get_host_model( self.host_pre, dt=dt, seeds=self.model.seeds, seeded=self.model.seeded) self.sims["host_pre"] = nengo.Simulator(self.host_pre, dt=self.dt, model=host_pre_model, progress_bar=False, optimize=False) if len(self.host.all_objects) > 0: host_model = self._get_host_model( self.host, dt=dt, seeds=self.model.seeds, seeded=self.model.seeded) self.sims["host"] = nengo.Simulator( self.host, dt=self.dt, model=host_model, progress_bar=False, optimize=False) elif not precompute: # If there is no host and precompute=False, then all objects # must be on the chip, which is precomputable in the sense that # no communication has to happen with the host. # We could warn about this, but we want to avoid people having # to specify `precompute` unless they absolutely have to. self.precompute = True # Build the network into the model self.model.build(network) self._probe_outputs = self.model.params self.data = ProbeDict(self._probe_outputs) for sim in self.sims.values(): self.data.add_fallback(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) if target is None: target = 'loihi' if HAS_NXSDK else 'sim' self.target = target logger.info("Simulator target is %r", target) logger.info("Simulator precompute is %r", self.precompute) if target != "simreal": discretize_model(self.model) if target in ("simreal", "sim"): self.sims["emulator"] = EmulatorInterface(self.model, seed=seed) elif target == 'loihi': assert HAS_NXSDK, "Must have NxSDK installed to use Loihi hardware" self.sims["loihi"] = HardwareInterface( self.model, use_snips=not self.precompute, seed=seed, **hardware_options) else: raise ValidationError("Must be 'simreal', 'sim', or 'loihi'", attr="target") assert "emulator" in self.sims or "loihi" in self.sims self.closed = False self.reset(seed=seed)
def test_multiple_get_probe_output(): n_steps = 15 n_axons = 3 model = Model() # n_axons controls number of input spikes and thus amount of overflow input = SpikeInput(n_axons) for t in np.arange(1, n_steps + 1): input.add_spikes(t, np.arange(n_axons)) # send spikes to all axons model.add_input(input) block = LoihiBlock(1) block.compartment.configure_relu() block.compartment.configure_filter(0.1) model.add_block(block) synapse = Synapse(n_axons) synapse.set_weights(np.ones((n_axons, 1))) block.add_synapse(synapse) axon = Axon(n_axons) axon.target = synapse input.add_axon(axon) probe_u = LoihiProbe(target=block, key="current", synapse=Lowpass(0.005)) model.add_probe(probe_u) probe_v = LoihiProbe(target=block, key="voltage", synapse=Lowpass(0.005)) model.add_probe(probe_v) probe_s = LoihiProbe(target=block, key="spiked", synapse=Lowpass(0.005)) model.add_probe(probe_s) discretize_model(model) # must set these after `discretize` to specify discretized values block.compartment.vmin = -(2**22) + 1 block.compartment.vth[:] = VTH_MAX with EmulatorInterface(model) as emu: emu.run_steps(n_steps) first_u = emu.get_probe_output(probe_u) first_v = emu.get_probe_output(probe_v) first_s = emu.get_probe_output(probe_s) second_u = emu.get_probe_output(probe_u) second_v = emu.get_probe_output(probe_v) second_s = emu.get_probe_output(probe_s) assert np.all(first_u == second_u) assert np.all(first_v == second_v) assert np.all(first_s == second_s)
def test_conv2d_weights(channels_last, hw_opts, request, plt, seed, rng, allclose): def loihi_rates_n(neuron_type, x, gain, bias, dt): """Compute Loihi rates on higher dimensional inputs""" y = x.reshape(-1, x.shape[-1]) gain = np.asarray(gain) bias = np.asarray(bias) if gain.ndim == 0: gain = gain * np.ones(x.shape[-1]) if bias.ndim == 0: bias = bias * np.ones(x.shape[-1]) rates = loihi_rates(neuron_type, y, gain, bias, dt) return rates.reshape(*x.shape) if channels_last: plt.saveas = None pytest.xfail("Blocked by CxBase cannot be > 256 bug") target = request.config.getoption("--target") if target != 'loihi' and len(hw_opts) > 0: pytest.skip("Hardware options only available on hardware") pop_type = 32 # load data with open(os.path.join(test_dir, 'mnist10.pkl'), 'rb') as f: test10 = pickle.load(f) test_x = test10[0][0].reshape(28, 28) test_x = test_x[3:24, 3:24] test_x = 1.999 * test_x - 0.999 filters = Gabor(freq=Uniform(0.5, 1)).generate(8, (7, 7), rng=rng) sti, stj = 2, 2 tau_rc = 0.02 tau_ref = 0.002 tau_s = 0.005 dt = 0.001 encode_type = nengo.SpikingRectifiedLinear() encode_gain = 1. / dt encode_bias = 0. neuron_type = nengo.LIF(tau_rc=tau_rc, tau_ref=tau_ref) neuron_gain = 1. neuron_bias = 1. pres_time = 0.2 # --- compute ideal outputs def conv_pm(x, kernel): y0 = scipy.signal.correlate2d(x[0], kernel, mode='valid')[::sti, ::stj] y1 = scipy.signal.correlate2d(x[1], kernel, mode='valid')[::sti, ::stj] return [y0, -y1] ref_out = np.array([test_x, -test_x]) ref_out = loihi_rates_n(encode_type, ref_out, encode_gain, encode_bias, dt) ref_out = ref_out / encode_gain ref_out = np.array([conv_pm(ref_out, kernel) for kernel in filters]) ref_out = ref_out.sum(axis=1) # sum positive and negative parts ref_out = loihi_rates_n(neuron_type, ref_out, neuron_gain, neuron_bias, dt) # --- compute nengo_loihi outputs inp_biases = np.stack([test_x, -test_x], axis=-1 if channels_last else 0) inp_shape = nengo_transforms.ChannelShape(inp_biases.shape, channels_last=channels_last) kernel = np.array([filters, -filters]) # two channels, pos and neg kernel = np.transpose(kernel, (2, 3, 0, 1)) conv2d_transform = nengo_transforms.Convolution( 8, inp_shape, strides=(sti, stj), channels_last=channels_last, kernel_size=(7, 7), init=kernel) out_size = ref_out.size nf, nyi, nyj = ref_out.shape assert out_size <= 1024 model = Model() # input block inp = LoihiBlock(inp_shape.size, label='inp') assert inp.n_neurons <= 1024 inp.compartment.configure_relu() inp.compartment.bias[:] = inp_biases.ravel() inp_ax = Axon(np.prod(inp_shape.spatial_shape), label='inp_ax') inp_ax.set_compartment_axon_map(target_axons=conv.pixel_idxs(inp_shape), atoms=conv.channel_idxs(inp_shape)) inp.add_axon(inp_ax) model.add_block(inp) # conv block neurons = LoihiBlock(out_size, label='neurons') assert neurons.n_neurons <= 1024 neurons.compartment.configure_lif(tau_rc=tau_rc, tau_ref=tau_ref, dt=dt) neurons.compartment.configure_filter(tau_s, dt=dt) neurons.compartment.bias[:] = neuron_bias synapse = Synapse(np.prod(inp_shape.spatial_shape), label='synapse') weights, indices, axon_to_weight_map, bases = conv.conv2d_loihi_weights( conv2d_transform) synapse.set_population_weights(weights, indices, axon_to_weight_map, bases, pop_type=pop_type) neurons.add_synapse(synapse) out_probe = Probe(target=neurons, key='spiked') neurons.add_probe(out_probe) inp_ax.target = synapse model.add_block(neurons) # simulation discretize_model(model) n_steps = int(pres_time / dt) if target == 'loihi': with HardwareInterface(model, use_snips=False, seed=seed, **hw_opts) as sim: sim.run_steps(n_steps) sim_out = sim.get_probe_output(out_probe) else: with EmulatorInterface(model, seed=seed) as sim: sim.run_steps(n_steps) sim_out = sim.get_probe_output(out_probe) sim_out = np.sum(sim_out, axis=0) / pres_time if channels_last: sim_out.shape = (nyi, nyj, nf) sim_out = np.transpose(sim_out, (2, 0, 1)) else: sim_out.shape = (nf, nyi, nyj) out_max = max(ref_out.max(), sim_out.max()) # --- plot results rows = 2 cols = 2 ax = plt.subplot(rows, cols, 1) tile(filters, cols=8, ax=ax) ax = plt.subplot(rows, cols, 2) tile(ref_out, vmin=0, vmax=out_max, cols=8, ax=ax) ax = plt.subplot(rows, cols, 3) plt.hist(ref_out.ravel(), bins=31) plt.hist(sim_out.ravel(), bins=31) ax = plt.subplot(rows, cols, 4) # tile(sim_out, vmin=0, vmax=1, cols=8, ax=ax) tile(sim_out, vmin=0, vmax=out_max, cols=8, ax=ax) assert allclose(sim_out, ref_out, atol=10, rtol=1e-3)
def test_one_to_one_allocator_big_block_error(): model = Model() model.add_block(LoihiBlock(1050)) with pytest.raises(ValidationError): OneToOne()(model)