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

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

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

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

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

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

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

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)
Exemplo n.º 2
0
    def __init__(self, network, dt=0.001, seed=None, model=None):
        self.closed = False

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

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

        self.model.decoder_cache.shrink()

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

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

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

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

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)
Exemplo n.º 3
0
    def __init__(self, model, dt=0.001, seed=None, builder=Builder()):
        # Call the builder to build the model
        self.model = builder(model, dt)
        self.dt = dt

        # Use model seed as simulator seed if the seed is not provided
        # Note: seed is not used right now, but one day...
        self.seed = self.model.seed if seed is None else seed

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

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

        self.n_steps = 0

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

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)
Exemplo n.º 4
0
def exact_dependency_graph(operators):
    from nengo.utils.simulator import operator_depencency_graph
    edges = operator_depencency_graph(operators)

    dg = nx.DiGraph()
    dg.add_nodes_from(operators)  # add all operators
    for source, dests in edges.items():
        dg.add_edges_from((source, dest) for dest in dests)

    return dg
Exemplo n.º 5
0
    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

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

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

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

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

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

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

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

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

        self.model.decoder_cache.shrink()

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

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

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

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

        seed = np.random.randint(npext.maxint) if seed is None else seed
        self.reset(seed=seed)
Exemplo n.º 6
0
    def __init__(self, network, dt=0.001, seed=None, model=None):
        """Initialize the simulator with a network and (optionally) a model.

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

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

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

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

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

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

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

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

        # Use model seed as simulator seed if the seed is not provided
        # Note: seed is not used right now, but one day...
        self.seed = self.model.seed if seed is None else seed

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

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

        self.n_steps = 0

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

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)
Exemplo n.º 7
0
def greedy_planner(operators):
    from nengo.utils.simulator import operator_depencency_graph
    edges = operator_depencency_graph(operators)
    for op, dests in iteritems(edges):
        assert is_op(op) and all(is_op(op2) for op2 in dests)

    # map unscheduled ops to their direct predecessors and successors
    predecessors_of = {}
    successors_of = {}
    for op in operators:
        predecessors_of[op] = set()
        successors_of[op] = set()
    for op, dests in iteritems(edges):
        for op2 in dests:
            predecessors_of[op2].add(op)
        successors_of[op].update(dests)

    # available ops are ready to be scheduled (all predecessors scheduled)
    available = defaultdict(set)
    for op in (op for op, dep in iteritems(predecessors_of) if not dep):
        available[type(op)].add(op)

    rval = []
    while len(predecessors_of) > 0:
        if len(available) == 0:
            raise ValueError("Cycles in the op graph")

        chosen_type = sorted(available.items(), key=lambda x: len(x[1]))[-1][0]
        candidates = available[chosen_type]

        # --- greedily pick non-overlapping ops
        chosen = []
        base_sets = defaultdict(set)
        base_incs = defaultdict(set)
        base_updates = defaultdict(set)

        def overlaps(op):
            for s in op.sets:
                if any(s.may_share_memory(s2) for s2 in base_sets[s.base]):
                    return True
            for s in op.incs:
                if any(s.may_share_memory(s2) for s2 in base_incs[s.base]):
                    return True
            for s in op.updates:
                if any(s.may_share_memory(s2) for s2 in base_updates[s.base]):
                    return True
            return False

        for op in candidates:
            if not overlaps(op):
                # add op
                chosen.append(op)
                for s in op.sets:
                    base_sets[s.base].add(s)
                for s in op.incs:
                    base_incs[s.base].add(s)
                for s in op.updates:
                    base_updates[s.base].add(s)

        # --- schedule ops
        assert chosen
        rval.append((chosen_type, chosen))

        # --- update predecessors and successors of unsheduled ops
        available[chosen_type].difference_update(chosen)
        if not available[chosen_type]:
            del available[chosen_type]

        for op in chosen:
            for op2 in successors_of[op]:
                preds = predecessors_of[op2]
                preds.remove(op)
                if len(preds) == 0:
                    available[type(op2)].add(op2)
            del predecessors_of[op]
            del successors_of[op]

    assert len(operators) == sum(len(p[1]) for p in rval)
    # print('greedy_planner: Program len:', len(rval))
    return rval
Exemplo n.º 8
0
def greedy_planner(operators):
    from nengo.utils.simulator import operator_depencency_graph
    edges = operator_depencency_graph(operators)
    for op, dests in iteritems(edges):
        assert is_op(op) and all(is_op(op2) for op2 in dests)

    # map unscheduled ops to their direct predecessors and successors
    predecessors_of = {}
    successors_of = {}
    for op in operators:
        predecessors_of[op] = set()
        successors_of[op] = set()
    for op, dests in iteritems(edges):
        for op2 in dests:
            predecessors_of[op2].add(op)
        successors_of[op].update(dests)

    # available ops are ready to be scheduled (all predecessors scheduled)
    available = defaultdict(set)
    for op in (op for op, dep in iteritems(predecessors_of) if not dep):
        available[type(op)].add(op)

    rval = []
    while len(predecessors_of) > 0:
        if len(available) == 0:
            raise ValueError("Cycles in the op graph")

        chosen_type = sorted(available.items(), key=lambda x: len(x[1]))[-1][0]
        candidates = available[chosen_type]

        # --- greedily pick non-overlapping ops
        chosen = []
        base_sets = defaultdict(set)
        base_incs = defaultdict(set)
        base_updates = defaultdict(set)

        def overlaps(op):
            for s in op.sets:
                if any(s.may_share_memory(s2) for s2 in base_sets[s.base]):
                    return True
            for s in op.incs:
                if any(s.may_share_memory(s2) for s2 in base_incs[s.base]):
                    return True
            for s in op.updates:
                if any(s.may_share_memory(s2) for s2 in base_updates[s.base]):
                    return True
            return False

        for op in candidates:
            if not overlaps(op):
                # add op
                chosen.append(op)
                for s in op.sets:
                    base_sets[s.base].add(s)
                for s in op.incs:
                    base_incs[s.base].add(s)
                for s in op.updates:
                    base_updates[s.base].add(s)

        # --- schedule ops
        assert chosen
        rval.append((chosen_type, chosen))

        # --- update predecessors and successors of unsheduled ops
        available[chosen_type].difference_update(chosen)
        if not available[chosen_type]:
            del available[chosen_type]

        for op in chosen:
            for op2 in successors_of[op]:
                preds = predecessors_of[op2]
                preds.remove(op)
                if len(preds) == 0:
                    available[type(op2)].add(op2)
            del predecessors_of[op]
            del successors_of[op]

    assert len(operators) == sum(len(p[1]) for p in rval)
    # print('greedy_planner: Program len:', len(rval))
    return rval
Exemplo n.º 9
0
    def finalize_build(self):
        """ Finalize the build step.

        Called once the MpiBuilder has finished running. Finalizes
        operators and probes, converting them to strings. Then writes
        all relevant information (signals, ops and probes for each component)
        to an HDF5 file. Then, if self.mpi_sim is not None (so we want to
        create a runnable MPI simulator), calls self.mpi_sim.load_file, which
        tells the C++ code to load the HDF5 file we have just written and
        create a working simulator.

        """
        all_ops = list(chain(*[self.component_ops[component] for component in range(self.n_components)]))

        dg = operator_depencency_graph(all_ops)
        global_ordering = [op for op in toposort(dg) if hasattr(op, "make_step")]
        self.global_ordering = {op: i for i, op in enumerate(global_ordering)}

        self._finalize_ops()
        self._finalize_probes()

        with h5.File(self.save_file_name, "w") as save_file:
            save_file.attrs["dt"] = self.dt
            save_file.attrs["n_components"] = self.n_components

            for component in range(self.n_components):
                component_group = save_file.create_group(str(component))

                # signals
                signals = self.signals[component]
                signal_dset = component_group.create_dataset(
                    "signals", (self.total_signal_size[component],), dtype="float64", compression=self.h5_compression
                )

                offset = 0
                for key, sig in signals:
                    A = sig.base._value

                    if A.ndim == 0:
                        A = np.reshape(A, (1, 1))

                    if A.dtype != np.float64:
                        A = A.astype(np.float64)

                    signal_dset[offset : offset + A.size] = A.flatten()
                    offset += A.size

                # signal keys
                component_group.create_dataset(
                    "signal_keys",
                    data=[long(key) for key, sig in signals],
                    dtype="int64",
                    compression=self.h5_compression,
                )

                # signal shapes
                def pad(x):
                    return (1, 1) if len(x) == 0 else ((x[0], 1) if len(x) == 1 else x)

                component_group.create_dataset(
                    "signal_shapes",
                    data=np.array([pad(sig.shape) for key, sig in signals]),
                    dtype="u2",
                    compression=self.h5_compression,
                )

                # signal_labels
                signal_labels = [str(p[1]) for p in signals]
                store_string_list(component_group, "signal_labels", signal_labels, compression=self.h5_compression)

                # operators
                op_strings = self.op_strings[component]
                store_string_list(component_group, "operators", op_strings, compression=self.h5_compression)

                # probes
                probe_strings = self.probe_strings[component]
                store_string_list(component_group, "probes", probe_strings, compression=self.h5_compression)

            probe_strings = self.probe_strings[component]
            store_string_list(save_file, "probe_info", self.all_probe_strings, compression=self.h5_compression)

        if self.mpi_sim is not None:
            self.mpi_sim.load_network(self.save_file_name)
            os.remove(self.save_file_name)

            for args in self.pyfunc_args:
                f = {
                    "N": self.mpi_sim.create_PyFunc,
                    "I": self.mpi_sim.create_PyFuncI,
                    "O": self.mpi_sim.create_PyFuncO,
                    "IO": self.mpi_sim.create_PyFuncIO,
                }[args[0]]
                f(*args[1:])

            self.mpi_sim.finalize_build()
Exemplo n.º 10
0
    def __init__(self,
                 network,
                 dt=0.001,
                 seed=None,
                 model=None,
                 dtype=rc.get('precision', 'dtype')):
        """Initialize the simulator with a network and (optionally) a model.

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

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

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

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

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

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

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

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

        self.model.decoder_cache.shrink()

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Use model seed as simulator seed if the seed is not provided
        # Note: seed is not used right now, but one day...
        self.seed = self.model.seed if seed is None else seed

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

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

        self.n_steps = 0

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

        # Provide a nicer interface to probe outputs
        self.data = ProbeDict(self._probe_outputs)
Exemplo n.º 12
0
    def finalize_build(self):
        """ Finalize the build step.

        Called once the MpiBuilder has finished running. Finalizes
        operators and probes, converting them to strings. Then writes
        all relevant information (signals, ops and probes for each component)
        to an HDF5 file. Then, if self.native_sim is not None (so we want to
        create a runnable MPI simulator), calls self.native_sim.load_file which
        tells the C++ code to load the HDF5 file we have just written and
        create a working simulator.

        """
        all_ops = list(chain(
            *[self.component_ops[component]
              for component in range(self.n_components)]))
        dg = operator_depencency_graph(all_ops)
        global_ordering = [
            op for op in toposort(dg) if hasattr(op, 'make_step')]
        self.global_ordering = {op: i for i, op in enumerate(global_ordering)}
        self.global_ordering[self.time_update] = -1

        # Needs to be done after calling operator_depencency_graph.
        # operator_depencency_graph will detect an error caused
        # by multiple sets of the time signal
        for component in range(self.n_components):
            self.assign_ops(component, [self.time_update])

        self._finalize_ops()
        self._finalize_probes()

        with h5.File(self.save_file, 'w') as save_file:
            save_file.attrs['dt'] = self.dt
            save_file.attrs['n_components'] = self.n_components

            for component in range(self.n_components):
                component_group = save_file.create_group(str(component))

                # base signals
                base_signals = self.base_signals[component]
                signal_dset = component_group.create_dataset(
                    'signals', (self.total_base_signal_size[component],),
                    dtype='float64', compression=self.h5_compression)

                offset = 0
                for base in base_signals.values():
                    shape = base.shape
                    stride = base.elemstrides

                    if base.ndim == 2:
                        # assert that the signal is contiguous
                        assert ((stride[1] == 1 and shape[1] == stride[0]) or
                                (stride[0] == 1 and shape[0] == stride[1]))

                        if base.elemstrides[1] == 1:
                            values = base.initial_value.flatten()
                        elif base.elemstrides[0] == 1:
                            values = base.initial_value.T.flatten()
                        else:
                            raise ValueError(
                                "Received a signal with strides that "
                                "nengo_mpi cannot handle. Signal "
                                "was %s, stride is %s." % (
                                    base, base.elemstrides))

                        signal_dset[offset:offset+base.size] = values
                    else:
                        # assert that the signal is contiguous
                        assert base.ndim == 0 or stride[0] == 1
                        signal_dset[
                            offset:offset+base.size] = base.initial_value

                    offset += base.size

                # base signal keys
                base_signal_keys = np.array([
                    long(key) for key in base_signals.keys()])

                component_group.create_dataset(
                    'signal_keys', data=base_signal_keys,
                    dtype='int64', compression=self.h5_compression)

                # base signal shapes
                base_signal_shapes = np.array([
                    pad(sig.shape) for sig in base_signals.values()])

                component_group.create_dataset(
                    'signal_shapes', data=base_signal_shapes,
                    dtype='int64', compression=self.h5_compression)

                # base signal strides
                base_signal_strides = np.array([
                    pad(sig.elemstrides) for sig in base_signals.values()])

                component_group.create_dataset(
                    'signal_strides', data=base_signal_strides,
                    dtype='int64', compression=self.h5_compression)

                # base signal labels
                if self.debug:
                    signal_labels = [sig.name for sig in base_signals.values()]
                else:
                    signal_labels = ['' for sig in base_signals.values()]

                store_string_list(
                    component_group, 'signal_labels', signal_labels,
                    compression=self.h5_compression)

                # operators
                op_strings = self.op_strings[component]
                store_string_list(
                    component_group, 'operators', op_strings,
                    compression=self.h5_compression)

                # probes
                probe_strings = self.probe_strings[component]
                store_string_list(
                    component_group, 'probes', probe_strings,
                    compression=self.h5_compression)

            store_string_list(
                save_file, 'probe_info', self.all_probe_strings,
                compression=self.h5_compression)

        if self.native_sim is not None:
            self.native_sim.load_network(self.save_file)
            os.remove(self.save_file)

            for op in self.pyfunc_ops:
                self.native_sim.create_PyFunc(op, self.global_ordering[op])

            self.native_sim.finalize_build()