def run_simulation(self): """ entry hook for timestep simulation - use settings to create modules in simulation loop - move them into an extra thread - start simulation """ # setup simulation modules self._setup_sim_modules(self.target_model) # setup simulator self._sim = Simulator(self._sim_settings, self._sim_modules) self._sim.moveToThread(self.simThread) # setup simulation modules for module in self._sim_modules.values(): module.moveToThread(self.simThread) # setup signal connections self.simThread.started.connect(self._sim.run) self._sim.state_changed.connect(self.simulation_state_changed) self._sim.finished.connect(self.simThread.quit) self.simThread.finished.connect(self.sim_finished) self.end_time = self._sim_settings.end_time # run self.simThread.start()
class SimulatorInteractor(QtCore.QObject): """ Class that interacts between the gui which controls the programs execution and the Simulator which handles the time step simulation """ # signals simulation_finished = QtCore.pyqtSignal(dict) simulation_failed = QtCore.pyqtSignal(dict) simulationProgressChanged = QtCore.pyqtSignal(int) def __init__(self, parent=None): QtCore.QObject.__init__(self, parent) self._logger = logging.getLogger(self.__class__.__name__) self.last_progress = 0 self.end_time = 0 self._setup_model() self._sim = None self._sim_settings = None self.simThread = QtCore.QThread() self._sim_modules = {} self._sim_data = {'modules': {}} self._sim_state = None def _setup_model(self): # create model self.target_model = SimulatorModel(self) self.target_model.itemChanged.connect(self.item_changed) # insert header self.target_model.setHorizontalHeaderLabels(['Property', 'Value']) # insert items self._setup_model_items() def _setup_model_items(self): """ fill model with items corresponding to all predefined SimulationModules """ # build initialisation list setup_list = Simulator.module_list # TODO remove the next lines # complete sorting therefore add observer, sensor and so on again setup_list.append("Trajectory") setup_list.remove("Trajectory") # insert main items for module in setup_list: name = QStandardItem(module) value = QStandardItem('None') new_items = [name, value] self.target_model.appendRow(new_items) # insert settings for row in range(self.target_model.rowCount()): index = self.target_model.index(row, 0) self._add_settings(index) def _add_settings(self, index): parent = index.model().itemFromIndex(index) module_name = str(parent.text()) sub_module_name = str(index.model().item(index.row(), 1).text()) if sub_module_name == 'None': return settings = self._read_settings(module_name, sub_module_name) for key, val in settings.iteritems(): setting_name = QStandardItem(key) setting_value = QStandardItem(str(val)) parent.appendRow([setting_name, setting_value]) def _read_settings(self, module_name, sub_module_name): """ reads the public settings from a simulation module """ module_cls = getattr(simulation_modules, module_name) sub_module_cls = get_simulation_module_class_by_name(module_cls, sub_module_name) return sub_module_cls.public_settings def item_changed(self, item): if item.parent(): return idx = item.index() module_item = idx.model().item(idx.row()) # delete all old settings module_item.removeRows(0, module_item.rowCount()) # insert new settings self._add_settings(module_item.index()) return def _get_settings(self, model, module_name): item = model.findItems(module_name).pop(0) settings = OrderedDict() for row in range(item.rowCount()): property_name = str(item.child(row, 0).text()) # TODO this is not the good way --> switch to pyqtgraphs implementation if property_name == 'Method': prop_val = str(item.child(row, 1).text()) else: val_str = str(item.child(row, 1).text()) if '[' in val_str: # parse vector # if the vector contains complex numbers the command text(), see above, # build a string with ' in it, we have to delete this val_str = val_str.replace("'", "") prop_val = np.array([complex(x) if "j" in x else float(x) for x in val_str[1:-1].split(',')]) else: # parse scalar try: prop_val = np.array(float(val_str)) except ValueError: # well then it is probably no float prop_val = val_str settings.update({property_name: prop_val}) return settings def _setup_sim_modules(self, model): """ setup simulation Modules :param model: model holding the public settings of each module """ for row in range(model.rowCount()): # build correct object and add it to the simulator module_item = model.item(row, 0) module_name = str(module_item.text()) sub_module_item = model.item(row, 1) sub_module_name = str(sub_module_item.text()) if sub_module_name == 'None': continue # get class module_cls = getattr(simulation_modules, module_name) sub_module_cls = get_simulation_module_class_by_name(module_cls, sub_module_name) # get public settings for module settings = self._get_settings(self.target_model, module_item.text()) settings.update({"type": sub_module_name}) settings.update({"modules": self._sim_modules}) # append special settings if module_name == "Solver": self._sim_settings = SimulationSettings(settings["start time"], settings["end time"], settings["measure rate"]) # build object slot = sub_module_cls(settings) # add to simulation modules self._sim_modules.update({module_name: slot}) # store settings self._sim_data['modules'].update({module_name: settings}) def set_regime(self, reg): if reg is None: return if isinstance(reg, list): self._logger.error("setRegime(): only scalar input allowed!") return self._apply_regime(reg) def _apply_regime(self, reg): """ sets all module settings to those provided in the regime """ if reg["clear previous"]: # delete all items self.target_model.removeRows(0, self.target_model.rowCount()) # load module defaults self._setup_model_items() # overwrite all settings with the provided ones for module_name, value in reg.iteritems(): if module_name == "Name" or module_name == "clear previous": continue # sanity check module_cls = getattr(simulation_modules, module_name) if not module_cls: raise AttributeError("_apply_regime(): No module called {0}".format(module_name)) items = self.target_model.findItems(module_name) # items = self.target_model.findItems(string.capwords(module_name)) if not len(items): raise ValueError("_apply_regime(): No item in List called {0}".format(module_name)) module_item = items.pop(0) module_type = value["type"] # sanity check sub_module_cls = get_simulation_module_class_by_name(module_cls, module_type) if not sub_module_cls: raise AttributeError("_apply_regime(): No sub-module called {0}".format(module_type)) module_index = module_item.index() module_type_index = module_index.model().index(module_index.row(), 1) module_index.model().setData(module_type_index, module_type) # due to signal connections, default settings are loaded automatically in the back # overwrite specific settings for key, val in value.iteritems(): if key == "type": continue found = False for row in range(module_item.rowCount()): if str(module_item.child(row, 0).text()) == key: value_idx = self.target_model.index(row, 1, module_index) self.target_model.setData(value_idx, str(val)) found = True break if not found: self._logger.error("_applyRegime(): setting {0} not available for {1}".format(key, module_type)) continue def run_simulation(self): """ entry hook for timestep simulation - use settings to create modules in simulation loop - move them into an extra thread - start simulation """ # setup simulation modules self._setup_sim_modules(self.target_model) # setup simulator self._sim = Simulator(self._sim_settings, self._sim_modules) self._sim.moveToThread(self.simThread) # setup simulation modules for module in self._sim_modules.values(): module.moveToThread(self.simThread) # setup signal connections self.simThread.started.connect(self._sim.run) self._sim.state_changed.connect(self.simulation_state_changed) self._sim.finished.connect(self.simThread.quit) self.simThread.finished.connect(self.sim_finished) self.end_time = self._sim_settings.end_time # run self.simThread.start() @QtCore.pyqtSlot(SimulationStateChange) def simulation_state_changed(self, state_change): """ slot for simulation state changes :param state_change: see :cls:SimulationStateChange """ self._logger.debug("simulation state change '{}'".format(state_change.type)) if state_change.type == "start": self._sim_state = "running" elif state_change.type == "time": self._logger.debug("reached simulation time {0}".format(state_change.t)) progress = int(state_change.t / self._sim_settings.end_time * 100) if progress != self.last_progress: self._logger.info("simulation reached {0}%".format(progress)) self.simulationProgressChanged.emit(progress) self.last_progress = progress elif state_change.type == "abort": self._sim_state = "aborted" self._sim_data.update({'results': copy.deepcopy(state_change.data)}) elif state_change.type == "finish": self._sim_state = "finished" self._sim_data.update({'results': copy.deepcopy(state_change.data)}) else: self._logger.error("simulation_state_changed(): ERROR Unknown state {0}".format(state_change.type)) def _sim_aftercare(self): # delete modules for module in self._sim_modules.keys(): del module self._sim_modules = {} # don't disconnect signals in debug-mode if sys.gettrace() is None: self.simThread.started.disconnect(self._sim.run) self._sim.state_changed.disconnect(self.simulation_state_changed) self._sim.finished.disconnect(self.simThread.quit) self.simThread.finished.disconnect(self.sim_finished) # delete simulator del self._sim def _postprocessing(self): """ calculation of some basic metrics for quick judging of simulation results """ # TODO make this able to calc error for vector-like model output # control and observer error if "Trajectory" in self._sim_data["results"]: # take only the first column c_error = self._get_result_by_name("Trajectory")[:, :, 0] - self._get_result_by_name("Model")[:, :, 0] self._sim_data['results'].update(control_error=c_error) if "Observer" in self._sim_data["results"]: o_error = self._get_result_by_name("Solver") - self._get_result_by_name("Observer") self._sim_data['results'].update(observer_error=o_error) def _get_result_by_name(self, name): return self._sim_data["results"][name] def sim_finished(self): """ slot to be called when the simulator reached the target time or aborted simulation due to an error """ self._sim_aftercare() self._postprocessing() assert self._sim_state == "finished" or self._sim_state == "aborted" if self._sim_state == "finished": self.simulation_finished.emit(self._sim_data) else: self.simulation_failed.emit(self._sim_data)