def add_op(self, op): # print(op) self.operators.append(op) # Fail fast by trying make_step with a temporary sigdict signals = SignalDict(__time__=np.asarray(0.0, dtype=self.dtype)) op.init_signals(signals) op.make_step(signals, self.dt, np.random)
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_signaldict(allclose): """Tests SignalDict's dict overrides.""" signaldict = SignalDict() scalar = Signal(1.0) # Both __getitem__ and __setitem__ raise KeyError with pytest.raises(KeyError): signaldict[scalar] with pytest.raises(KeyError): signaldict[scalar] = np.array(1.0) signaldict.init(scalar) # tests repeat init with pytest.raises(SignalError, match="Cannot add signal twice"): signaldict.init(scalar) assert allclose(signaldict[scalar], np.array(1.0)) # __getitem__ handles scalars assert signaldict[scalar].shape == () one_d = Signal([1.0]) signaldict.init(one_d) assert allclose(signaldict[one_d], np.array([1.0])) assert signaldict[one_d].shape == (1, ) two_d = Signal([[1.0], [1.0]]) signaldict.init(two_d) assert allclose(signaldict[two_d], np.array([[1.0], [1.0]])) assert signaldict[two_d].shape == (2, 1) # __getitem__ handles views implicitly (note no .init) two_d_view = two_d[0, :] assert allclose(signaldict[two_d_view], np.array([1.0])) assert signaldict[two_d_view].shape == (1, ) # __setitem__ ensures memory location stays the same memloc = signaldict[scalar].__array_interface__["data"][0] signaldict[scalar] = np.array(0.0) assert allclose(signaldict[scalar], np.array(0.0)) assert signaldict[scalar].__array_interface__["data"][0] == memloc memloc = signaldict[one_d].__array_interface__["data"][0] signaldict[one_d] = np.array([0.0]) assert allclose(signaldict[one_d], np.array([0.0])) assert signaldict[one_d].__array_interface__["data"][0] == memloc memloc = signaldict[two_d].__array_interface__["data"][0] signaldict[two_d] = np.array([[0.0], [0.0]]) assert allclose(signaldict[two_d], np.array([[0.0], [0.0]])) assert signaldict[two_d].__array_interface__["data"][0] == memloc # __str__ pretty-prints signals and current values # Order not guaranteed for dicts, so we have to loop for k in signaldict: assert "%s %s" % (repr(k), repr(signaldict[k])) in str(signaldict)
def build_temporal_solver(model, solver, conn, rng, transform=None): # Unpack the relevant variables from the connection. assert isinstance(conn.pre_obj, Ensemble) ensemble = conn.pre_obj neurons = ensemble.neurons neuron_type = ensemble.neuron_type # Find the operator that simulates the neurons. # We do it this way (instead of using the step_math method) # because we don't know the number of state parameters or their shapes. ops = list( filter( lambda op: (isinstance(op, SimNeurons) and op.J is model.sig[neurons]['in']), model.operators)) if not len(ops) == 1: # pragma: no cover raise RuntimeError("Expected exactly one operator for simulating " "neurons (%s), found: %s" % (neurons, ops)) op = ops[0] # Create stepper for the neuron model. signals = SignalDict() op.init_signals(signals) step_simneurons = op.make_step(signals, model.dt, rng) # Create custom rates method that uses the built neurons. def override_rates_method(x, gain, bias): n_eval_points, n_neurons = x.shape assert ensemble.n_neurons == n_neurons a = np.empty((n_eval_points, n_neurons)) for i, x_t in enumerate(x): signals[op.J][...] = neuron_type.current(x_t, gain, bias) step_simneurons() a[i, :] = signals[op.output] if solver.synapse is None: return a return solver.synapse.filt(a, axis=0, y0=0, dt=model.dt) # Hot-swap the rates method while calling the underlying solver. # The solver will then call this temporarily created rates method # to process each evaluation point. save_rates_method = neuron_type.rates neuron_type.rates = override_rates_method try: # Note: passing solver.solver doesn't actually cause solver.solver # to be built. It will still use conn.solver. This is because # the function decorated with @Builder.register(Solver) actually # ignores the solver and considers only the conn. The only point of # passing solver.solver here is to invoke its corresponding builder # function in case something custom happens to be registered. # Note: in nengo>2.8.0 the transform parameter is dropped return model.build(solver.solver, conn, rng, transform) finally: neuron_type.rates = save_rates_method
def test_signal_init(sig_type): if sig_type == "sparse_scipy": pytest.importorskip("scipy.sparse") sig, dense = make_signal( sig_type, shape=(3, 3), indices=np.asarray([[0, 0], [0, 2], [1, 1], [2, 2]]), data=[1.0, 2.0, 1.0, 1.5], ) signals = SignalDict() signals.init(sig) assert np.all(signals[sig] == dense) sig.readonly = True signals = SignalDict() signals.init(sig) with pytest.raises((ValueError, RuntimeError, TypeError)): signals[sig].data[0] = -1
def test_signaldict(): """Tests SignalDict's dict overrides.""" signaldict = SignalDict() scalar = Signal(1.) # Both __getitem__ and __setitem__ raise KeyError with pytest.raises(KeyError): signaldict[scalar] with pytest.raises(KeyError): signaldict[scalar] = np.array(1.) signaldict.init(scalar) assert np.allclose(signaldict[scalar], np.array(1.)) # __getitem__ handles scalars assert signaldict[scalar].shape == () one_d = Signal([1.]) signaldict.init(one_d) assert np.allclose(signaldict[one_d], np.array([1.])) assert signaldict[one_d].shape == (1, ) two_d = Signal([[1.], [1.]]) signaldict.init(two_d) assert np.allclose(signaldict[two_d], np.array([[1.], [1.]])) assert signaldict[two_d].shape == (2, 1) # __getitem__ handles views two_d_view = two_d[0, :] signaldict.init(two_d_view) assert np.allclose(signaldict[two_d_view], np.array([1.])) assert signaldict[two_d_view].shape == (1, ) # __setitem__ ensures memory location stays the same memloc = signaldict[scalar].__array_interface__['data'][0] signaldict[scalar] = np.array(0.) assert np.allclose(signaldict[scalar], np.array(0.)) assert signaldict[scalar].__array_interface__['data'][0] == memloc memloc = signaldict[one_d].__array_interface__['data'][0] signaldict[one_d] = np.array([0.]) assert np.allclose(signaldict[one_d], np.array([0.])) assert signaldict[one_d].__array_interface__['data'][0] == memloc memloc = signaldict[two_d].__array_interface__['data'][0] signaldict[two_d] = np.array([[0.], [0.]]) assert np.allclose(signaldict[two_d], np.array([[0.], [0.]])) assert signaldict[two_d].__array_interface__['data'][0] == memloc # __str__ pretty-prints signals and current values # Order not guaranteed for dicts, so we have to loop for k in signaldict: assert "%s %s" % (repr(k), repr(signaldict[k])) in str(signaldict)
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 add_op(self, op): """Add an operator to the model. In addition to adding the operator, this method performs additional error checking by calling the operator's ``make_step`` function. Calling ``make_step`` catches errors early, such as when signals are not properly initialized, which aids debugging. For that reason, we recommend calling this method over directly accessing the ``operators`` attribute. """ self.operators.append(op) # Fail fast by trying make_step with a temporary sigdict signals = SignalDict() op.init_signals(signals) op.make_step(signals, self.dt, np.random)
def test_signal_reshape(): """Tests Signal.reshape""" # check proper shape after reshape three_d = Signal(np.ones((2, 2, 2))) assert three_d.reshape((8, )).shape == (8, ) assert three_d.reshape((4, 2)).shape == (4, 2) assert three_d.reshape((2, 4)).shape == (2, 4) assert three_d.reshape(-1).shape == (8, ) assert three_d.reshape((4, -1)).shape == (4, 2) assert three_d.reshape((-1, 4)).shape == (2, 4) assert three_d.reshape((2, -1, 2)).shape == (2, 2, 2) assert three_d.reshape((1, 2, 1, 2, 2, 1)).shape == (1, 2, 1, 2, 2, 1) # check with non-contiguous arrays (and with offset) value = np.arange(20).reshape(5, 4) s = Signal(np.array(value), name='s') s0slice = slice(0, 3), slice(None, None, 2) s0shape = 2, 3 s0 = s[s0slice].reshape(*s0shape) assert s.offset == 0 assert np.array_equal(s0.initial_value, value[s0slice].reshape(*s0shape)) s1slice = slice(1, None), slice(None, None, 2) s1shape = 2, 4 s1 = s[s1slice].reshape(s1shape) assert s1.offset == 4 * s1.dtype.itemsize assert np.array_equal(s1.initial_value, value[s1slice].reshape(s1shape)) # check error if non-contiguous array cannot be reshaped without copy s2slice = slice(None, None, 2), slice(None, None, 2) s2shape = 2, 3 s2 = s[s2slice] with pytest.raises(SignalError): s2.reshape(s2shape) # check that views are working properly (incrementing `s` effects views) values = SignalDict() values.init(s) values.init(s0) values.init(s1) values[s] += 1 assert np.array_equal(values[s0], value[s0slice].reshape(s0shape) + 1) assert np.array_equal(values[s1], value[s1slice].reshape(s1shape) + 1)
def test_signaldict_reset(): """Tests SignalDict's reset function.""" signaldict = SignalDict() two_d = Signal([[1], [1]]) signaldict.init(two_d) two_d_view = two_d[0, :] signaldict[two_d_view] = -0.5 assert np.allclose(signaldict[two_d], np.array([[-0.5], [1]])) signaldict[two_d] = np.array([[-1], [-1]]) assert np.allclose(signaldict[two_d], np.array([[-1], [-1]])) assert np.allclose(signaldict[two_d_view], np.array([-1])) signaldict.reset(two_d_view) assert np.allclose(signaldict[two_d_view], np.array([1])) assert np.allclose(signaldict[two_d], np.array([[1], [-1]])) signaldict.reset(two_d) assert np.allclose(signaldict[two_d], np.array([[1], [1]]))
def __init__(self, network, dt=0.001, seed=None, model=None, context=None, n_prealloc_probes=32, profiling=None, if_python_code='none', planner=greedy_planner, progress_bar=True): # --- check version if nengo.version.version_info in bad_nengo_versions: raise ValueError( "This simulator does not support Nengo version %s. Upgrade " "with 'pip install --upgrade --no-deps nengo'." % nengo.__version__) elif nengo.version.version_info > latest_nengo_version_info: warnings.warn("This version of `nengo_ocl` has not been tested " "with your `nengo` version (%s). The latest fully " "supported version is %s" % (nengo.__version__, latest_nengo_version)) # --- create these first since they are used in __del__ self.closed = False self.model = None # --- arguments/attributes if context is None and Simulator.some_context is None: print('No context argument was provided to nengo_ocl.Simulator') print("Calling pyopencl.create_some_context() for you now:") Simulator.some_context = cl.create_some_context() if profiling is None: profiling = int(os.getenv("NENGO_OCL_PROFILING", 0)) self.context = Simulator.some_context if context is None else context self.profiling = profiling self.queue = cl.CommandQueue( self.context, properties=PROFILING_ENABLE if self.profiling else 0) if if_python_code not in ['none', 'warn', 'error']: raise ValueError("%r not a valid value for `if_python_code`" % if_python_code) self.if_python_code = if_python_code self.n_prealloc_probes = n_prealloc_probes self.progress_bar = progress_bar # --- Nengo build with Timer() as nengo_timer: if model is None: self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt), decoder_cache=get_default_decoder_cache()) else: self.model = model if network is not None: # Build the network into the model self.model.build(network) logger.info("Nengo build in %0.3f s" % nengo_timer.duration) # --- operators with Timer() as planner_timer: operators = list(self.model.operators) # convert DotInc and Copy to MultiDotInc operators = list(map(MultiDotInc.convert_to, operators)) operators = MultiDotInc.compress(operators) # plan the order of operations, combining where appropriate op_groups = planner(operators) assert len([typ for typ, _ in op_groups if typ is Reset ]) < 2, ("All resets not planned together") self.operators = operators self.op_groups = op_groups logger.info("Planning in %0.3f s" % planner_timer.duration) with Timer() as signals_timer: # Initialize signals all_signals = stable_unique(sig for op in operators for sig in op.all_signals) all_bases = stable_unique(sig.base for sig in all_signals) sigdict = SignalDict() # map from Signal.base -> ndarray for op in operators: op.init_signals(sigdict) # Add built states to the probe dictionary self._probe_outputs = dict(self.model.params) # Provide a nicer interface to probe outputs self.data = ProbeDict(self._probe_outputs) # Create data on host and add views self.all_data = RaggedArray( [sigdict[sb] for sb in all_bases], names=[getattr(sb, 'name', '') for sb in all_bases], dtype=np.float32) view_builder = ViewBuilder(all_bases, self.all_data) view_builder.setup_views(operators) for probe in self.model.probes: view_builder.append_view(self.model.sig[probe]['in']) view_builder.add_views_to(self.all_data) self.all_bases = all_bases self.sidx = { k: np.int32(v) for k, v in iteritems(view_builder.sidx) } self._A_views = view_builder._A_views self._X_views = view_builder._X_views self._YYB_views = view_builder._YYB_views del view_builder # Copy data to device self.all_data = CLRaggedArray(self.queue, self.all_data) logger.info("Signals in %0.3f s" % signals_timer.duration) # --- set seed self.seed = np.random.randint(npext.maxint) if seed is None else seed self.rng = np.random.RandomState(self.seed) # --- create list of plans self._raggedarrays_to_reset = {} self._cl_rngs = {} self._python_rngs = {} plans = [] with Timer() as plans_timer: for op_type, op_list in op_groups: plans.extend(self.plan_op_group(op_type, op_list)) plans.extend(self.plan_probes()) logger.info("Plans in %0.3f s" % plans_timer.duration) # -- create object to execute list of plans self._plans = Plans(plans, self.profiling) self.rng = None # all randomness set, should no longer be used self._reset_probes() # clears probes from previous model builds
def __init__(self, network, dt=0.001, seed=None, model=None, planner=greedy_planner): with Timer() as nengo_timer: if model is None: self.model = Model(dt=float(dt), label="%s, dt=%f" % (network, dt), decoder_cache=get_default_decoder_cache()) else: self.model = model if network is not None: # Build the network into the model self.model.build(network) logger.info("Nengo build in %0.3f s" % nengo_timer.duration) # --- set seed seed = np.random.randint(npext.maxint) if seed is None else seed self.seed = seed self.rng = np.random.RandomState(self.seed) self._step = Signal(np.array(0.0, dtype=np.float64), name='step') self._time = Signal(np.array(0.0, dtype=np.float64), name='time') # --- operators with Timer() as planner_timer: operators = list(self.model.operators) # convert DotInc, Reset, Copy, and ProdUpdate to MultiProdUpdate operators = list(map(MultiProdUpdate.convert_to, operators)) operators = MultiProdUpdate.compress(operators) # plan the order of operations, combining where appropriate op_groups = planner(operators) assert len([typ for typ, _ in op_groups if typ is Reset ]) < 2, ("All resets not planned together") # add time operator after planning, to ensure it goes first time_op = TimeUpdate(self._step, self._time) operators.insert(0, time_op) op_groups.insert(0, (type(time_op), [time_op])) self.operators = operators self.op_groups = op_groups logger.info("Planning in %0.3f s" % planner_timer.duration) with Timer() as signals_timer: # Initialize signals all_signals = signals_from_operators(operators) all_bases = stable_unique([sig.base for sig in all_signals]) sigdict = SignalDict() # map from Signal.base -> ndarray for op in operators: op.init_signals(sigdict) # Add built states to the probe dictionary self._probe_outputs = self.model.params # Provide a nicer interface to probe outputs self.data = ProbeDict(self._probe_outputs) self.all_data = RaggedArray( [sigdict[sb] for sb in all_bases], [getattr(sb, 'name', '') for sb in all_bases], dtype=np.float32) builder = ViewBuilder(all_bases, self.all_data) self._AX_views = {} self._YYB_views = {} for op_type, op_list in op_groups: self.setup_views(builder, op_type, op_list) for probe in self.model.probes: builder.append_view(self.model.sig[probe]['in']) builder.add_views_to(self.all_data) self.all_bases = all_bases self.sidx = builder.sidx self._prep_all_data() logger.info("Signals in %0.3f s" % signals_timer.duration) # --- create list of plans with Timer() as plans_timer: self._plan = [] for op_type, op_list in op_groups: self._plan.extend(self.plan_op_group(op_type, op_list)) self._plan.extend(self.plan_probes()) logger.info("Plans in %0.3f s" % plans_timer.duration) self.n_steps = 0
def add_op(self, op): self.operators.append(op) # Fail fast by trying make_step with a temporary sigdict signals = SignalDict() op.init_signals(signals) op.make_step(signals, self.dt, np.random)
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()
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()