From a406908a698518cccd8a9761d755903cdd6ac4a0 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 7 Sep 2023 19:52:53 +0200 Subject: [PATCH] catch errors in fit preparation --- src/gui_qt/fit/fit_parameter.py | 3 +- src/gui_qt/main/mainwindow.py | 10 +-- src/gui_qt/main/management.py | 119 ++++++++++++++++++-------------- src/nmreval/fit/minimizer.py | 7 +- 4 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/gui_qt/fit/fit_parameter.py b/src/gui_qt/fit/fit_parameter.py index 95bf9db..3229f10 100644 --- a/src/gui_qt/fit/fit_parameter.py +++ b/src/gui_qt/fit/fit_parameter.py @@ -298,7 +298,8 @@ class ParameterSingleWidget(QtWidgets.QWidget): self._name = name self.label.setText(convert(name)) - self.label.setToolTip('If this is bold then this parameter is only for this data. otherwise the general parameter is used and displayed') + self.label.setToolTip('If this is bold then this parameter is only for this data. ' + 'Otherwise, the general parameter is used and displayed') self.value_line.setValidator(QtGui.QDoubleValidator()) self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0) diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index f5e9433..6f1c47b 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -916,10 +916,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.action_odr_fit: 'odr' }[self.ac_group.checkedAction()] - self.fit_dialog.fit_button.setEnabled(False) - self.management.start_fit(parameter, links, fit_options) - self.status.setText('Fit running...'.format(self.management.fitter.step)) - self.fit_timer.start(500) + fit_is_ready = self.management.prepare_fit(parameter, links, fit_options) + if fit_is_ready: + self.management.start_fit() + self.fit_dialog.fit_button.setEnabled(False) + self.status.setText('Fit running...'.format(self.management.fitter.step)) + self.fit_timer.start(500) @QtCore.pyqtSlot(dict, int, bool) def show_fit_preview(self, funcs: dict, num: int, show: bool): diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index 86d1b41..5470c44 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -424,9 +424,9 @@ class UpperManagement(QtCore.QObject): for d in self.data.values(): d.mask = np.ones_like(d.mask, dtype=bool) - def start_fit(self, parameter: dict, links: list, fit_options: dict): + def prepare_fit(self, parameter: dict, links: list, fit_options: dict) -> bool: if self._fit_active: - return + return False self.__fit_options = (parameter, links, fit_options) @@ -436,67 +436,80 @@ class UpperManagement(QtCore.QObject): fit_mode = fit_options['fit_mode'] we_option = fit_options['we'] - for model_id, model_p in parameter.items(): - m = Model(model_p['func']) - models[model_id] = m + self.fitter.fitmethod = fit_mode - m_complex = model_p['complex'] + # all-encompassing error catch + try: + for model_id, model_p in parameter.items(): + m = Model(model_p['func']) + models[model_id] = m - # sets are not in active order but in order they first appeared in fit dialog - # iterate over order of set id in active order and access parameter inside loop - # instead of directly looping - list_ids = list(model_p['parameter'].keys()) - set_order = [self.active_id.index(i) for i in list_ids] - for pos in set_order: - set_id = list_ids[pos] + m_complex = model_p['complex'] - data_i = self.data[set_id] - set_params = model_p['parameter'][set_id] + # sets are not in active order but in order they first appeared in fit dialog + # iterate over order of set id in active order and access parameter inside loop + # instead of directly looping + list_ids = list(model_p['parameter'].keys()) + set_order = [self.active_id.index(i) for i in list_ids] + for pos in set_order: + set_id = list_ids[pos] - if we_option.lower() == 'deltay': - we = data_i.y_err**2 - else: - we = we_option + data_i = self.data[set_id] + set_params = model_p['parameter'][set_id] - if m_complex is None or m_complex == 1: - _y = data_i.y.real - elif m_complex == 2 and np.iscomplexobj(data_i.y): - _y = data_i.y.imag - else: - _y = data_i.y + if we_option.lower() == 'deltay': + we = data_i.y_err**2 + else: + we = we_option - _x = data_i.x + if m_complex is None or m_complex == 1: + _y = data_i.y.real + elif m_complex == 2 and np.iscomplexobj(data_i.y): + _y = data_i.y.imag + else: + _y = data_i.y - if fit_limits == 'none': - inside = slice(None) - elif fit_limits == 'x': - x_lim, _ = self.graphs[self.current_graph].ranges - inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1])) - else: - inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1])) + _x = data_i.x - if isinstance(we, str): - d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id) - else: - d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id) + if fit_limits == 'none': + inside = slice(None) + elif fit_limits == 'x': + x_lim, _ = self.graphs[self.current_graph].ranges + inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1])) + else: + inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1])) - d.set_model(m) - d.set_parameter(set_params[0], var=model_p['var'], - lb=model_p['lb'], ub=model_p['ub'], - fun_kwargs=set_params[1]) + if isinstance(we, str): + d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id) + else: + d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id) - self.fitter.add_data(d) + d.set_model(m) + d.set_parameter(set_params[0], var=model_p['var'], + lb=model_p['lb'], ub=model_p['ub'], + fun_kwargs=set_params[1]) - model_globs = model_p['glob'] - if model_globs: - m.set_global_parameter(**model_p['glob']) + self.fitter.add_data(d) - for links_i in links: - self.fitter.set_link_parameter((models[links_i[0]], links_i[1]), - (models[links_i[2]], links_i[3])) + model_globs = model_p['glob'] + if model_globs: + m.set_global_parameter(**model_p['glob']) + for links_i in links: + self.fitter.set_link_parameter((models[links_i[0]], links_i[1]), + (models[links_i[2]], links_i[3])) + return True + + except Exception as e: + logger.error('Fit preparation failed', *e.args) + QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), + 'Fit prep failed', + f'Fit preparation failed with message\n{e.args}') + return False + + def start_fit(self): with busy_cursor(): - self.fit_worker = FitWorker(self.fitter, fit_mode) + self.fit_worker = FitWorker(self.fitter) self.fit_thread = QtCore.QThread() self.fit_worker.moveToThread(self.fit_thread) @@ -532,7 +545,8 @@ class UpperManagement(QtCore.QObject): for set_id, set_parameter in parameter.items(): new_values = [v.value for v in res[set_id].parameter.values()] parameter[set_id] = (new_values, set_parameter[1]) - self.start_fit(*self.__fit_options) + if self.prepare_fit(*self.__fit_options): + self.start_fit() def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None: """ @@ -1270,16 +1284,15 @@ class UpperManagement(QtCore.QObject): class FitWorker(QtCore.QObject): finished = QtCore.pyqtSignal(list, bool) - def __init__(self, fitter, mode): + def __init__(self, fitter): super().__init__() self.fitter = fitter - self.mode = mode @QtCore.pyqtSlot() def run(self): try: - res = self.fitter.run(mode=self.mode) + res = self.fitter.run() success = True except Exception as e: res = [e] diff --git a/src/nmreval/fit/minimizer.py b/src/nmreval/fit/minimizer.py index 57e1b56..d66db74 100644 --- a/src/nmreval/fit/minimizer.py +++ b/src/nmreval/fit/minimizer.py @@ -23,7 +23,7 @@ class FitAbortException(Exception): class FitRoutine(object): def __init__(self, mode='lsq'): - self._fitmethod = mode + self.fitmethod = mode self.data = [] self.fit_model = None self._no_own_model = [] @@ -169,10 +169,13 @@ class FitRoutine(object): logger.info('Fit aborted by user') self._abort = True - def run(self, mode='lsq'): + def run(self, mode: str=None): self._abort = False self.parameter = Parameters() + if mode is None: + mode = self.fitmethod + fit_groups, linked_parameter = self.prepare_links() for data_groups in fit_groups: