diff --git a/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp index ab9102c16a2d5894a3ceac47ceeadb5205d48d42..7d0975a91ea3c6953ef13ecb5e5a6b55fd86e538 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp @@ -24,6 +24,8 @@ void export_MultiDomainFunction() { (arg("self"), arg("i")), "Get the i-th function.") .def("add", &MultiDomainFunction::addFunction, (arg("self"), arg("function")), "Add a member function.") + .def("replaceFunction", &MultiDomainFunction::replaceFunction, + (arg("self"), arg("function")), "Replace a member function.") .def("setDomainIndex", &MultiDomainFunction::setDomainIndex, (arg("self"), arg("funIndex"), arg("domainIndex")), "Associate a function and a domain."); diff --git a/qt/python/mantidqt/_common.sip b/qt/python/mantidqt/_common.sip index 41d7f3cd66839219696b372fa367e70e645fcd50..08da6391588a347db2d84019d1d085f3c5bf15d8 100644 --- a/qt/python/mantidqt/_common.sip +++ b/qt/python/mantidqt/_common.sip @@ -804,6 +804,9 @@ public: void removeDatasets(QList<int> indices); QStringList getDatasetNames() const; void setErrorsEnabled(bool enabled); + void hideGlobalCheckbox(); + void showGlobalCheckbox(); + QStringList getGlobalParameters() const; void setGlobalParameters(const QStringList &globals); void clear(); @@ -855,6 +858,10 @@ public: %MethodCode sipRes = boost::python::to_python_value<const Mantid::API::IFunction_sptr &>()(sipCpp->getFunctionByIndex(*a0)); %End + SIP_PYOBJECT getFunctionWithIndex(const int index); + %MethodCode + sipRes = boost::python::to_python_value<const Mantid::API::IFunction_sptr &>()(sipCpp->getFunctionWithIndex(a0)); + %End signals: void functionStructureChanged(); void parameterChanged(const QString &funcIndex, const QString ¶mName); diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionBrowser.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionBrowser.h index 6eb3f93c97aadfea58eacdf37a7964b78f5abaaf..fd15dc44d07d039732b0185851ac64be331a57ca 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionBrowser.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionBrowser.h @@ -63,6 +63,8 @@ public: bool hasFunction() const; /// Return a function with specified index IFunction_sptr getFunctionByIndex(const QString &index); + /// Return a function with specified index + IFunction_sptr getFunctionWithIndex(const int index); /// Return index of the current function, if one is selected boost::optional<QString> currentFunctionIndex(); /// Update the function parameter value @@ -119,6 +121,10 @@ public: void clearErrors() override; /// Set a parameter that is responsible for the background level void setBackgroundA0(double value); + // hide the global options + void hideGlobalCheckbox(); + // show the global options + void showGlobalCheckbox(); signals: void parameterChanged(const QString &funcIndex, const QString ¶mName); diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionMultiDomainPresenter.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionMultiDomainPresenter.h index d8dbb8b035441ce78dcf9a390a6d320b6f4830dc..77ad3087c68ee546f31f3c5401e3c34c9099bfa2 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionMultiDomainPresenter.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionMultiDomainPresenter.h @@ -36,6 +36,7 @@ public: QString getFunctionString() const; IFunction_sptr getFunction() const; IFunction_sptr getFunctionByIndex(const QString &index); + IFunction_sptr getFunctionWithIndex(const int index); IFunction_sptr getFitFunction() const; QString getFitFunctionString() const; bool hasFunction() const; @@ -73,6 +74,8 @@ public: void setColumnSizes(int s0, int s1, int s2); void setErrorsEnabled(bool enabled); + void hideGlobals(); + void showGlobals(); signals: void functionStructureChanged(); void parameterChanged(const QString &funcIndex, const QString ¶mName); diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionTreeView.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionTreeView.h index b65ffb3999383b0735c98db6d9e83279a4208af1..719ebbbe4b18422721e98d1ee9c7a54b79aca096 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionTreeView.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FunctionTreeView.h @@ -122,6 +122,12 @@ public: /// Resize the browser's columns void setColumnSizes(int s0, int s1, int s2 = -1); + // Hide global boxes + void hideGlobals(); + + // Show global boxes + void showGlobals(); + protected: /// Create the Qt property browser void createBrowser(); diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/qttreepropertybrowser.h b/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/qttreepropertybrowser.h index 1cdd0d849cf9a5e0a1cc3208b7343d92415395fd..26f669ffee991ae8b40f97c3f4fb2d39e74bc707 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/qttreepropertybrowser.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/qttreepropertybrowser.h @@ -151,6 +151,9 @@ public: bool isItemVisible(QtBrowserItem *item) const; void setItemVisible(QtBrowserItem *item, bool visible); + void hideColumn(int col); + void showColumn(int col); + void setBackgroundColor(QtBrowserItem *item, const QColor &color); QColor backgroundColor(QtBrowserItem *item) const; QColor calculatedBackgroundColor(QtBrowserItem *item) const; @@ -275,6 +278,8 @@ public: void disableItem(QTreeWidgetItem *item) const; void enableItem(QTreeWidgetItem *item) const; bool hasValue(QTreeWidgetItem *item) const; + void hideColumn(int col); + void showColumn(int col); void slotCollapsed(const QModelIndex &index); void slotExpanded(const QModelIndex &index); diff --git a/qt/widgets/common/src/FunctionBrowser.cpp b/qt/widgets/common/src/FunctionBrowser.cpp index a2c781a10151e224432876bf71f8f8ee2bff6079..7fcd1754d2224e44105850847fbc8110d3381af7 100644 --- a/qt/widgets/common/src/FunctionBrowser.cpp +++ b/qt/widgets/common/src/FunctionBrowser.cpp @@ -93,6 +93,14 @@ IFunction_sptr FunctionBrowser::getFunctionByIndex(const QString &index) { return m_presenter->getFunctionByIndex(index); } +/** + * Return function at specified function index (e.g. f0.) + * @param index :: Index of the function, or empty string for top-level function + * @return Function at index, or null pointer if not found + */ +IFunction_sptr FunctionBrowser::getFunctionWithIndex(const int index) { + return m_presenter->getFunctionWithIndex(index); +} /** * Updates the function parameter value * @param paramName :: Fully qualified parameter name (includes function index) @@ -344,5 +352,9 @@ void FunctionBrowser::setBackgroundA0(double value) { m_presenter->setBackgroundA0(value); } +void FunctionBrowser::hideGlobalCheckbox() { m_presenter->hideGlobals(); } + +void FunctionBrowser::showGlobalCheckbox() { m_presenter->showGlobals(); } + } // namespace MantidWidgets } // namespace MantidQt diff --git a/qt/widgets/common/src/FunctionMultiDomainPresenter.cpp b/qt/widgets/common/src/FunctionMultiDomainPresenter.cpp index 273678c56203ab3e6f4aa5e0e4580931b34d7f78..6543fb3b858550c3dc2dbc1315d771eb99c32235 100644 --- a/qt/widgets/common/src/FunctionMultiDomainPresenter.cpp +++ b/qt/widgets/common/src/FunctionMultiDomainPresenter.cpp @@ -79,6 +79,11 @@ FunctionMultiDomainPresenter::getFunctionByIndex(const QString &index) { return getFunctionWithPrefix(index, m_model->getCurrentFunction()); } +IFunction_sptr +FunctionMultiDomainPresenter::getFunctionWithIndex(const int index) { + return m_model->getSingleFunction(index); +} + void FunctionMultiDomainPresenter::setParameter(const QString ¶mName, double value) { m_model->setParameter(paramName, value); @@ -409,5 +414,19 @@ void FunctionMultiDomainPresenter::updateViewFromModel() { } } +void FunctionMultiDomainPresenter::hideGlobals() { + auto treeView = dynamic_cast<FunctionTreeView *>(m_view); + if (treeView) { + treeView->hideGlobals(); + } +} + +void FunctionMultiDomainPresenter::showGlobals() { + auto treeView = dynamic_cast<FunctionTreeView *>(m_view); + if (treeView) { + treeView->showGlobals(); + } +} + } // namespace MantidWidgets } // namespace MantidQt diff --git a/qt/widgets/common/src/FunctionTreeView.cpp b/qt/widgets/common/src/FunctionTreeView.cpp index df5efa6c406da63fb940fec37e24fbb41b124240..6d063152bd1ff71bd44895b3bd146bf23bf15715 100644 --- a/qt/widgets/common/src/FunctionTreeView.cpp +++ b/qt/widgets/common/src/FunctionTreeView.cpp @@ -1793,6 +1793,11 @@ void FunctionTreeView::setColumnSizes(int s0, int s1, int s2) { m_browser->setColumnSizes(s0, s1, s2); } +/// Show global column +void FunctionTreeView::hideGlobals() { m_browser->hideColumn(2); } +// Hide global column +void FunctionTreeView::showGlobals() { m_browser->showColumn(2); } + /** * Emit a signal when any of the Global options change. */ diff --git a/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp b/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp index a0b85250e787498e5dc92b0e0cffe12202696a44..ede13f42570eba791d6ecdef5dec5535ef2a0405 100644 --- a/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp +++ b/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp @@ -728,6 +728,14 @@ void QtTreePropertyBrowserPrivate::setColumnSizes(int s0, int s1, int s2) { } } +void QtTreePropertyBrowserPrivate::hideColumn(int col) { + m_treeWidget->header()->hideSection(col); +} + +void QtTreePropertyBrowserPrivate::showColumn(int col) { + m_treeWidget->header()->showSection(col); +} + /** \class QtTreePropertyBrowser @@ -1091,6 +1099,10 @@ void QtTreePropertyBrowser::setColumnSizes(int s0, int s1, int s2) { d_ptr->setColumnSizes(s0, s1, s2); } +void QtTreePropertyBrowser::hideColumn(int col) { d_ptr->hideColumn(col); } + +void QtTreePropertyBrowser::showColumn(int col) { d_ptr->showColumn(col); } + QTreeWidgetItem *QtTreePropertyBrowser::getItemWidget(QtBrowserItem *item) { return d_ptr->getItemWidget(item); } diff --git a/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_presenter.py b/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_presenter.py index f14dee9123530cc972bb620597842b6f34e0f8b5..badd95aa6bfdd4e482e82b57ea03e3750e737e90 100644 --- a/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_presenter.py +++ b/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_presenter.py @@ -34,6 +34,7 @@ class FittingTabPresenter(object): self.manual_selection_made = False self.automatically_update_fit_name = True self.thread_success = True + self.fitting_calculation_model = None self.update_selected_workspace_guess() self.gui_context_observer = GenericObserverWithArgPassing( self.handle_gui_changes_made) @@ -51,6 +52,26 @@ class FittingTabPresenter(object): self.update_view_from_model_observer = GenericObserverWithArgPassing( self.update_view_from_model) + @property + def selected_data(self): + return self._selected_data + + @selected_data.setter + def selected_data(self, selected_data): + if self._selected_data == selected_data: + return + + self._selected_data = selected_data + self.clear_and_reset_gui_state() + + @property + def start_x(self): + return self._start_x + + @property + def end_x(self): + return self._end_x + def handle_select_fit_data_clicked(self): selected_data, dialog_return = WorkspaceSelectorView.get_selected_data( self.context.data_context.current_runs, @@ -76,49 +97,12 @@ class FittingTabPresenter(object): def handle_selected_plot_type_changed(self): self.update_selected_workspace_guess() - def update_selected_workspace_guess(self): - if self.view.fit_type == self.view.simultaneous_fit: - self.update_fit_specifier_list() - if self.manual_selection_made: - guess_selection = self.selected_data - self.selected_data = guess_selection - else: - self.selected_data = self.get_workspace_selected_list() - - def get_workspace_selected_list(self): - if self.context._frequency_context is not None: - freq = self.context._frequency_context.plot_type - else: - freq = 'None' - - runs = 'All' - groups_and_pairs = self._get_selected_groups_and_pairs() - if self.view.fit_type == self.view.simultaneous_fit: - if self.view.simultaneous_fit_by == "Run": - runs = self.view.simultaneous_fit_by_specifier - elif self.view.simultaneous_fit_by == "Group/Pair": - groups_and_pairs = [self.view.simultaneous_fit_by_specifier] - - guess_selection = [] - for grppair in groups_and_pairs: - guess_selection += self.context.get_names_of_workspaces_to_fit( - runs=runs, - group_and_pair=grppair, - phasequad=False, - rebin=not self.view.fit_to_raw, freq=freq) - - guess_selection = list(set(self._check_data_exists(guess_selection))) - return guess_selection - def handle_display_workspace_changed(self): current_index = self.view.get_index_for_start_end_times() self.view.start_time = self.start_x[current_index] self.view.end_time = self.end_x[current_index] - - self.view.set_datasets_in_function_browser( - [self.view.display_workspace]) - self.view.function_browser_multi.setCurrentDataset(current_index) - + self.view.function_browser.setCurrentDataset(current_index) + self._update_stored_fit_functions() self.update_fit_status_information_in_view() def handle_use_rebin_changed(self): @@ -138,10 +122,14 @@ class FittingTabPresenter(object): self.view.workspace_combo_box_label.setText( 'Display parameters for') self.view.simul_fit_by_specifier.setEnabled(True) + self.view.switch_to_simultaneous() + self._update_stored_fit_functions() self.update_fit_specifier_list() else: self.update_selected_workspace_guess() self.view.workspace_combo_box_label.setText('Select Workspace') + self.view.switch_to_single() + self._update_stored_fit_functions() self.view.simul_fit_by_specifier.setEnabled(False) def handle_plot_guess_changed(self): @@ -159,33 +147,6 @@ class FittingTabPresenter(object): self.model.change_plot_guess(self.view.plot_guess, parameters) - def fitting_domain_type_changed(self): - # If the fit function has been removed we should remove clear both browsers and the fit information - # and return. - if self._fit_function[0] is None: - self.clear_fit_information() - self.view.function_browser.clear() - self.view.function_browser_multi.clear() - - if self.view.fit_type == self.view.simultaneous_fit: - self.view.switch_to_simultaneous() - else: - self.view.switch_to_single() - - def sync_single_domain_browser_with_multi_domain_browser(self): - multi_domain_function = self.create_multi_domain_function(self._fit_function) - if multi_domain_function: - self.view.function_browser_multi.blockSignals(True) - self.view.function_browser_multi.setFunction(str(multi_domain_function)) - self.view.function_browser_multi.blockSignals(False) - - def sync_multi_domain_browser_with_single_domain_browser(self): - single_domain_function = self._fit_function[0] - if single_domain_function: - self.view.function_browser.blockSignals(True) - self.view.function_browser.setFunction(str(single_domain_function)) - self.view.function_browser.blockSignals(False) - def handle_fit_clicked(self): self.context.fitting_context.number_of_fits = 0 if self._tf_asymmetry_mode: @@ -193,90 +154,6 @@ class FittingTabPresenter(object): else: self.perform_standard_fit() - def perform_standard_fit(self): - self._fit_function_cache = [item.clone() for item in self._fit_function if item] - fit_type = self.view.fit_type - - try: - if fit_type == self.view.simultaneous_fit: - self._number_of_fits_cached = 1 - simultaneous_fit_parameters = self.get_multi_domain_fit_parameters() - global_parameters = self.view.get_global_parameters() - print(simultaneous_fit_parameters) - calculation_function = functools.partial( - self.model.do_simultaneous_fit, - simultaneous_fit_parameters, global_parameters) - self.calculation_thread = self.create_thread( - calculation_function) - else: - self._number_of_fits_cached = 1 - single_fit_parameters = self.get_parameters_for_single_fit() - calculation_function = functools.partial( - self.model.do_single_fit, single_fit_parameters) - self.calculation_thread = self.create_thread( - calculation_function) - - self.calculation_thread.threadWrapperSetUp(self.handle_started, - self.handle_finished, - self.handle_error) - self.calculation_thread.start() - except ValueError as error: - self.view.warning_popup(error) - - def perform_tf_asymmetry_fit(self): - self._fit_function_cache = [item.clone() for item in self._fit_function if item] - fit_type = self.view.fit_type - - try: - if fit_type == self.view.simultaneous_fit: - simultaneous_fit_parameters = self.get_multi_domain_tf_fit_parameters() - global_parameters = self.view.get_global_parameters() - calculation_function = functools.partial( - self.model.do_simultaneous_tf_fit, - simultaneous_fit_parameters, global_parameters) - self.calculation_thread = self.create_thread(calculation_function) - else: - single_fit_parameters = self.get_parameters_for_tf_single_fit_calculation() - calculation_function = functools.partial( - self.model.do_single_tf_fit, single_fit_parameters) - self.calculation_thread = self.create_thread(calculation_function) - - self.calculation_thread.threadWrapperSetUp(self.handle_started, - self.handle_finished, - self.handle_error) - self.calculation_thread.start() - except ValueError as error: - self.view.warning_popup(error) - - def get_parameters_for_tf_single_fit_calculation(self): - workspace, workspace_directory = self.model.create_fitted_workspace_name(self.view.display_workspace, - self.view.fit_object) - - return { - 'InputFunction': self.view.fit_object, - 'ReNormalizedWorkspaceList': self.view.display_workspace, - 'UnNormalizedWorkspaceList': self.context.group_pair_context.get_unormalisised_workspace_list( - [self.view.display_workspace])[0], - 'OutputFitWorkspace': workspace, - 'StartX': self.start_x[0], - 'EndX': self.end_x[0], - 'Minimizer': self.view.minimizer - } - - def get_multi_domain_tf_fit_parameters(self): - workspace, workspace_directory = self.model.create_multi_domain_fitted_workspace_name( - self.view.display_workspace, self.view.fit_object) - return { - 'InputFunction': self.view.fit_object, - 'ReNormalizedWorkspaceList': self.selected_data, - 'UnNormalizedWorkspaceList': self.context.group_pair_context.get_unormalisised_workspace_list( - self.selected_data), - 'OutputFitWorkspace': workspace, - 'StartX': self.start_x[self.view.get_index_for_start_end_times()], - 'EndX': self.end_x[self.view.get_index_for_start_end_times()], - 'Minimizer': self.view.minimizer - } - def handle_started(self): self.view.setEnabled(False) self.thread_success = True @@ -289,12 +166,16 @@ class FittingTabPresenter(object): fit_function, fit_status, fit_chi_squared = self.fitting_calculation_model.result if any([not fit_function, not fit_status, not fit_chi_squared]): return - index = self.view.get_index_for_start_end_times() - if self.view.fit_type == self.view.simultaneous_fit: - self._fit_function = [fit_function] * len(self.start_x) + if self.view.is_simul_fit(): + self._fit_function[0] = fit_function self._fit_status = [fit_status] * len(self.start_x) self._fit_chi_squared = [fit_chi_squared] * len(self.start_x) + else: + current_index = self.view.get_index_for_start_end_times() + self._fit_function[current_index] = fit_function + self._fit_status[current_index] = fit_status + self._fit_chi_squared[current_index] = fit_chi_squared self.update_fit_status_information_in_view() self.view.undo_fit_button.setEnabled(True) @@ -326,24 +207,18 @@ class FittingTabPresenter(object): self.view.function_browser.setFunction(str(self._fit_function[self.view.get_index_for_start_end_times()])) self.view.function_browser.blockSignals(False) return - if not self.view.fit_object: - self._fit_function = [None] * len(self.selected_data) if self.selected_data else [None] + self._fit_function = [None] else: - self._fit_function = [self.view.fit_object.clone() for _ in self.selected_data] \ - if self.selected_data else [self.view.fit_object.clone()] + self._fit_function = [func.clone() for func in self._get_fit_function()] + + self.clear_fit_information() if self.automatically_update_fit_name: - self.view.function_name = self.model.get_function_name( - self.view.fit_object) + name = self._get_fit_function()[0] + self.view.function_name = self.model.get_function_name(name) self.model.function_name = self.view.function_name - # resync browsers - if self.view.fit_type == self.view.simultaneous_fit: - self.sync_multi_domain_browser_with_single_domain_browser() - else: - self.sync_single_domain_browser_with_multi_domain_browser() - def handle_tf_asymmetry_mode_changed(self): def calculate_tf_fit_function(original_fit_function): tf_asymmetry_parameters = self.get_parameters_for_tf_function_calculation(original_fit_function) @@ -383,7 +258,7 @@ class FittingTabPresenter(object): self.view.function_name = self.view.function_name.replace(',TFAsymmetry', '') self.model.function_name = self.view.function_name - if self.view.fit_type != self.view.simultaneous_fit: + if not self.view.is_simul_fit(): for index, fit_function in enumerate(self._fit_function): fit_function = fit_function if fit_function else self.view.fit_object.clone() new_function = calculate_tf_fit_function(fit_function) @@ -397,22 +272,22 @@ class FittingTabPresenter(object): else: new_function = calculate_tf_fit_function(self.view.fit_object) self._fit_function = [new_function.clone()] * len(self.selected_data) - self.view.function_browser_multi.blockSignals(True) - self.view.function_browser_multi.clear() - self.view.function_browser_multi.setFunction( + self.view.function_browser.blockSignals(True) + self.view.function_browser.clear() + self.view.function_browser.setFunction( str(self._fit_function[self.view.get_index_for_start_end_times()])) - self.view.function_browser_multi.setGlobalParameters(new_global_parameters) - self.view.function_browser_multi.blockSignals(False) + self.view.function_browser.setGlobalParameters(new_global_parameters) + self.view.function_browser.blockSignals(False) self.update_fit_status_information_in_view() self.handle_display_workspace_changed() def handle_function_parameter_changed(self): - if self.view.fit_type != self.view.simultaneous_fit: + if not self.view.is_simul_fit(): index = self.view.get_index_for_start_end_times() - self._fit_function[index] = self.view.fit_object.clone() + self._fit_function[index] = self._get_fit_function()[index] else: - self._fit_function = [self.view.fit_object] * len(self.selected_data) + self._fit_function = self._get_fit_function() def handle_undo_fit_clicked(self): self._fit_function = self._fit_function_cache @@ -436,6 +311,152 @@ class FittingTabPresenter(object): def handle_fit_specifier_changed(self): self.selected_data = self.get_workspace_selected_list() + def perform_standard_fit(self): + if not self.view.fit_object: + return + + self._fit_function_cache = [func.clone() for func in self._fit_function] + fit_type = self.view.fit_type + + try: + if fit_type == self.view.simultaneous_fit: + self._number_of_fits_cached = 1 + simultaneous_fit_parameters = self.get_multi_domain_fit_parameters() + global_parameters = self.view.get_global_parameters() + calculation_function = functools.partial( + self.model.do_simultaneous_fit, + simultaneous_fit_parameters, global_parameters) + self.calculation_thread = self.create_thread( + calculation_function) + else: + self._number_of_fits_cached = 1 + single_fit_parameters = self.get_parameters_for_single_fit() + calculation_function = functools.partial( + self.model.do_single_fit, single_fit_parameters) + self.calculation_thread = self.create_thread( + calculation_function) + + self.calculation_thread.threadWrapperSetUp(self.handle_started, + self.handle_finished, + self.handle_error) + self.calculation_thread.start() + except ValueError as error: + self.view.warning_popup(error) + + def perform_tf_asymmetry_fit(self): + self._fit_function_cache = [item.clone() for item in self._fit_function if item] + fit_type = self.view.fit_type + + try: + if fit_type == self.view.simultaneous_fit: + simultaneous_fit_parameters = self.get_multi_domain_tf_fit_parameters() + global_parameters = self.view.get_global_parameters() + calculation_function = functools.partial( + self.model.do_simultaneous_tf_fit, + simultaneous_fit_parameters, global_parameters) + self.calculation_thread = self.create_thread(calculation_function) + else: + single_fit_parameters = self.get_parameters_for_tf_single_fit_calculation() + calculation_function = functools.partial( + self.model.do_single_tf_fit, single_fit_parameters) + self.calculation_thread = self.create_thread(calculation_function) + + self.calculation_thread.threadWrapperSetUp(self.handle_started, + self.handle_finished, + self.handle_error) + self.calculation_thread.start() + except ValueError as error: + self.view.warning_popup(error) + + def get_parameters_for_tf_single_fit_calculation(self): + workspace, workspace_directory = self.model.create_fitted_workspace_name(self.view.display_workspace, + self.view.fit_object) + + return { + 'InputFunction': self.view.fit_object, + 'ReNormalizedWorkspaceList': self.view.display_workspace, + 'UnNormalizedWorkspaceList': self.context.group_pair_context.get_unormalisised_workspace_list( + [self.view.display_workspace])[0], + 'OutputFitWorkspace': workspace, + 'StartX': self.start_x[0], + 'EndX': self.end_x[0], + 'Minimizer': self.view.minimizer + } + + def get_multi_domain_tf_fit_parameters(self): + workspace, workspace_directory = self.model.create_multi_domain_fitted_workspace_name( + self.view.display_workspace, self.view.fit_object) + return { + 'InputFunction': self.view.fit_object, + 'ReNormalizedWorkspaceList': self.selected_data, + 'UnNormalizedWorkspaceList': self.context.group_pair_context.get_unormalisised_workspace_list( + self.selected_data), + 'OutputFitWorkspace': workspace, + 'StartX': self.start_x[self.view.get_index_for_start_end_times()], + 'EndX': self.end_x[self.view.get_index_for_start_end_times()], + 'Minimizer': self.view.minimizer + } + + def clear_and_reset_gui_state(self): + self.view.set_datasets_in_function_browser(self.selected_data) + + self._fit_status = [None] * len( + self.selected_data) if self.selected_data else [None] + self._fit_chi_squared = [0.0] * len( + self.selected_data) if self.selected_data else [0.0] + if self.view.fit_object: + self._fit_function = [func.clone() for func in self._get_fit_function()] + else: + self._fit_function = [None] + + self.view.undo_fit_button.setEnabled(False) + + self.reset_start_time_to_first_good_data_value() + self.view.update_displayed_data_combo_box(self.selected_data) + self.update_fit_status_information_in_view() + + def clear_fit_information(self): + self._fit_status = [None] * len( + self.selected_data) if self.selected_data else [None] + self._fit_chi_squared = [0.0] * len( + self.selected_data) if self.selected_data else [0.0] + self.update_fit_status_information_in_view() + self.view.undo_fit_button.setEnabled(False) + + def update_selected_workspace_guess(self): + if self.view.fit_type == self.view.simultaneous_fit: + self.update_fit_specifier_list() + if self.manual_selection_made: + guess_selection = self.selected_data + self.selected_data = guess_selection + else: + self.selected_data = self.get_workspace_selected_list() + + def get_workspace_selected_list(self): + if self.context._frequency_context is not None: + freq = self.context._frequency_context.plot_type + else: + freq = 'None' + + runs = 'All' + groups_and_pairs = self._get_selected_groups_and_pairs() + if self.view.fit_type == self.view.simultaneous_fit: + if self.view.simultaneous_fit_by == "Run": + runs = self.view.simultaneous_fit_by_specifier + elif self.view.simultaneous_fit_by == "Group/Pair": + groups_and_pairs = [self.view.simultaneous_fit_by_specifier] + + guess_selection = [] + for grppair in groups_and_pairs: + guess_selection += self.context.get_names_of_workspaces_to_fit( + runs=runs, + group_and_pair=grppair, + phasequad=False, + rebin=not self.view.fit_to_raw, freq=freq) + + guess_selection = list(set(self._check_data_exists(guess_selection))) + return guess_selection + def update_fit_specifier_list(self): if self.view.simultaneous_fit_by == "Run": flattened_run_list = [str(item) for sublist in self.context.data_context.current_runs for item in sublist] @@ -449,7 +470,6 @@ class FittingTabPresenter(object): def get_parameters_for_single_fit(self): params = self._get_shared_parameters() - params['InputWorkspace'] = self.view.display_workspace params['StartX'] = self.start_x[0] params['EndX'] = self.end_x[0] @@ -481,55 +501,50 @@ class FittingTabPresenter(object): :return: The set of attributes common to all fit types """ return { - 'Function': self.view.fit_object, + 'Function': self._current_fit_function(), 'Minimizer': self.view.minimizer, 'EvaluationType': self.view.evaluation_type } - @property - def selected_data(self): - return self._selected_data - - @selected_data.setter - def selected_data(self, selected_data): - if self._selected_data == selected_data: - return - - self._selected_data = selected_data - self.clear_and_reset_gui_state() - - def clear_and_reset_gui_state(self): - single_data = [self.selected_data[0]] if self.selected_data else [] - self.view.set_datasets_in_function_browser(single_data) - self.view.set_datasets_in_function_browser_multi(self.selected_data) - - self._fit_status = [None] * len( - self.selected_data) if self.selected_data else [None] - self._fit_chi_squared = [0.0] * len( - self.selected_data) if self.selected_data else [0.0] - self._fit_function = [self.view.fit_object] * len( - self.selected_data) if self.selected_data else [self.view.fit_object] - self.view.undo_fit_button.setEnabled(False) - - self.reset_start_time_to_first_good_data_value() - self.view.update_displayed_data_combo_box(self.selected_data) - self.update_fit_status_information_in_view() + def _update_stored_fit_functions(self): + if self.view.is_simul_fit(): + if self.view.fit_object: # make sure there is a fit function in the browser + self._fit_function = [self.view.fit_object.clone()] # return the fit function stored in the browser + else: + self._fit_function = [None] + else: # we need to convert stored function into equiv + if self.view.fit_object: # make sure there is a fit function in the browser + if isinstance(self.view.fit_object, MultiDomainFunction): + equiv_fit_function = self.view.fit_object.createEquivalentFunctions() + single_domain_fit_functions = [func.clone() for func in equiv_fit_function] + else: + single_domain_fit_functions = [self.view.fit_object.clone()] + self._fit_function = single_domain_fit_functions + else: + self._fit_function = [None] * len(self._start_x) + + def _get_fit_function(self): + if self.view.is_simul_fit(): + return [self.view.fit_object] # return the fit function stored in the browser + else: # we need to convert stored function into equiv + if self.view.fit_object: # make sure thers a fit function in the browser + if isinstance(self.view.fit_object, MultiDomainFunction): + equiv_fit_funtion = self.view.fit_object.createEquivalentFunctions() + single_domain_fit_function = equiv_fit_funtion + else: + single_domain_fit_function = [self.view.fit_object] + return single_domain_fit_function + else: + return [None] * len(self._start_x) - def clear_fit_information(self): - self._fit_status = [None] * len( - self.selected_data) if self.selected_data else [None] - self._fit_chi_squared = [0.0] * len( - self.selected_data) if self.selected_data else [0.0] - self.update_fit_status_information_in_view() - self.view.undo_fit_button.setEnabled(False) + def _current_fit_function(self): + return self._fit_function[self._fit_function_index()] - @property - def start_x(self): - return self._start_x - - @property - def end_x(self): - return self._end_x + def _fit_function_index(self): + if self.view.is_simul_fit(): + return 0 # if we are doing a single simulatenous fit, return index 0 + else: + return self.view.get_index_for_start_end_times() # else doing a single fit on displayed workspaces def update_start_x(self, index, value): self._start_x[index] = value @@ -559,7 +574,8 @@ class FittingTabPresenter(object): self.view.end_time = self.end_x[0] if 0 < len(self.end_x) else 15.0 def update_fit_status_information_in_view(self): - current_index = self.view.get_index_for_start_end_times() + current_index = self._fit_function_index() + self.view.update_with_fit_outputs(self._fit_function[current_index], self._fit_status[current_index], self._fit_chi_squared[current_index]) @@ -572,10 +588,6 @@ class FittingTabPresenter(object): else: self.selected_data = [] - def check_workspaces_are_tf_asymmetry_compliant(self, workspace_list): - non_compliant_workspaces = [item for item in workspace_list if 'Group' not in item] - return False if non_compliant_workspaces else True - def get_parameters_for_tf_function_calculation(self, fit_function): mode = 'Construct' if self.view.tf_asymmetry_mode else 'Extract' workspace_list = self.selected_data if self.view.fit_type == self.view.simultaneous_fit else [ @@ -584,22 +596,9 @@ class FittingTabPresenter(object): 'WorkspaceList': workspace_list, 'Mode': mode} - def create_multi_domain_function(self, function_list): - if not any(function_list): - return None - multi_domain_function = MultiDomainFunction() - for index, func in enumerate(function_list): - multi_domain_function.add(func) - multi_domain_function.setDomainIndex(index, index) - - return multi_domain_function - def _get_selected_groups_and_pairs(self): return self.context.group_pair_context.selected_groups + self.context.group_pair_context.selected_pairs - def _check_data_exists(self, guess_selection): - return [item for item in guess_selection if AnalysisDataService.doesExist(item)] - def _get_run_number_from_workspace(self, workspace_name): instrument = self.context.data_context.instrument run = re.findall(r'%s(\d+)' % instrument, workspace_name) @@ -611,3 +610,13 @@ class FittingTabPresenter(object): grp = re.findall(r'%s' % grppair, workspace_name) if len(grp) > 0: return grp[0] + + @staticmethod + def check_workspaces_are_tf_asymmetry_compliant(workspace_list): + non_compliant_workspaces = [item for item in workspace_list if 'Group' not in item] + return False if non_compliant_workspaces else True + + @staticmethod + def _check_data_exists(guess_selection): + return [item for item in guess_selection if AnalysisDataService.doesExist(item)] + diff --git a/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_view.py b/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_view.py index 458beb77d34fc140fce9b6d5a9567e852a489312..df4e97c339596428fe3d36b1e5d355dc45f443e2 100644 --- a/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_view.py +++ b/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_view.py @@ -27,13 +27,10 @@ class FittingTabView(QtWidgets.QWidget, ui_fitting_tab): self.setup_simul_fit_combo_box() self.undo_fit_button.setEnabled(False) - self.function_browser = FunctionBrowser(self, False) - self.function_browser_multi = FunctionBrowser(self, True) - self.function_browser_multi.hide() + self.function_browser = FunctionBrowser(self, True) self.function_browser_layout.addWidget(self.function_browser) - self.function_browser_layout.addWidget(self.function_browser_multi) self.function_browser.setErrorsEnabled(True) - self.function_browser_multi.setErrorsEnabled(True) + self.function_browser.hideGlobalCheckbox() self.increment_parameter_display_button.clicked.connect(self.increment_display_combo_box) self.decrement_parameter_display_button.clicked.connect(self.decrement_display_combo_box) @@ -85,12 +82,6 @@ class FittingTabView(QtWidgets.QWidget, ui_fitting_tab): self.function_browser.removeDatasets(index_list) self.function_browser.addDatasets(data_set_name_list) - def set_datasets_in_function_browser_multi(self, data_set_name_list): - number_of_data_sets = self.function_browser_multi.getNumberOfDatasets() - index_list = range(number_of_data_sets) - self.function_browser_multi.removeDatasets(index_list) - self.function_browser_multi.addDatasets(data_set_name_list) - def update_with_fit_outputs(self, fit_function, output_status, output_chi_squared): if not fit_function: self.fit_status_success_failure.setText('No Fit') @@ -98,14 +89,14 @@ class FittingTabView(QtWidgets.QWidget, ui_fitting_tab): self.fit_status_chi_squared.setText('Chi squared: {}'.format(output_chi_squared)) return - if self.fit_type != self.simultaneous_fit: + if self.is_simul_fit(): self.function_browser.blockSignals(True) self.function_browser.updateMultiDatasetParameters(fit_function) self.function_browser.blockSignals(False) else: - self.function_browser_multi.blockSignals(True) - self.function_browser_multi.updateMultiDatasetParameters(fit_function) - self.function_browser_multi.blockSignals(False) + self.function_browser.blockSignals(True) + self.function_browser.updateParameters(fit_function) + self.function_browser.blockSignals(False) if output_status == 'success': self.fit_status_success_failure.setText('Success') @@ -172,10 +163,7 @@ class FittingTabView(QtWidgets.QWidget, ui_fitting_tab): @property def fit_object(self): - if self.fit_type != self.simultaneous_fit: - return self.function_browser.getGlobalFunction() - else: - return self.function_browser_multi.getGlobalFunction() + return self.function_browser.getGlobalFunction() @property def minimizer(self): @@ -263,15 +251,16 @@ class FittingTabView(QtWidgets.QWidget, ui_fitting_tab): return current_index if current_index != -1 else 0 def get_global_parameters(self): - return self.function_browser_multi.getGlobalParameters() + return self.function_browser.getGlobalParameters() def switch_to_simultaneous(self): - self.function_browser_multi.show() - self.function_browser.hide() + self.function_browser.showGlobalCheckbox() def switch_to_single(self): - self.function_browser_multi.hide() - self.function_browser.show() + self.function_browser.hideGlobalCheckbox() + + def is_simul_fit(self): + return self.simul_fit_checkbox.isChecked() def setup_fit_by_specifier(self, choices): self.simul_fit_by_specifier.blockSignals(True) diff --git a/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_widget.py b/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_widget.py index 630a529fbe1d5d594819501e892381a6d054c0c6..e93395cd2bea45dfb8895b764b4d72eb783ac319 100644 --- a/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_widget.py +++ b/scripts/Muon/GUI/Common/fitting_tab_widget/fitting_tab_widget.py @@ -31,10 +31,6 @@ class FittingTabWidget(object): self.fitting_tab_presenter.handle_function_structure_changed) self.fitting_tab_view.function_browser.functionStructureChanged.connect( self.fitting_tab_presenter.handle_plot_guess_changed) - self.fitting_tab_view.function_browser_multi.functionStructureChanged.connect( - self.fitting_tab_presenter.handle_function_structure_changed) - self.fitting_tab_view.function_browser_multi.functionStructureChanged.connect( - self.fitting_tab_presenter.handle_plot_guess_changed) self.fitting_tab_view.function_name_line_edit.textChanged.connect( self.fitting_tab_presenter.handle_fit_name_changed_by_user) self.fitting_tab_view.undo_fit_button.clicked.connect(self.fitting_tab_presenter.handle_undo_fit_clicked) @@ -43,6 +39,3 @@ class FittingTabWidget(object): self.fitting_tab_view.plot_guess_checkbox.stateChanged.connect(self.fitting_tab_presenter.handle_plot_guess_changed) self.fitting_tab_view.function_browser.parameterChanged.connect(self.fitting_tab_presenter.handle_function_parameter_changed) self.fitting_tab_view.function_browser.parameterChanged.connect(self.fitting_tab_presenter.handle_plot_guess_changed) - self.fitting_tab_view.function_browser_multi.parameterChanged.connect(self.fitting_tab_presenter.handle_function_parameter_changed) - self.fitting_tab_view.function_browser_multi.parameterChanged.connect(self.fitting_tab_presenter.handle_plot_guess_changed) - self.fitting_tab_view.simul_fit_checkbox.toggled.connect(self.fitting_tab_presenter.fitting_domain_type_changed) diff --git a/scripts/test/Muon/fitting_tab_widget/fitting_tab_presenter_test.py b/scripts/test/Muon/fitting_tab_widget/fitting_tab_presenter_test.py index 6a58c69367f193a93ac40b7435ce08db8c6f8307..a6468caa5d7574951288d3164cdc37ca24e76737 100644 --- a/scripts/test/Muon/fitting_tab_widget/fitting_tab_presenter_test.py +++ b/scripts/test/Muon/fitting_tab_widget/fitting_tab_presenter_test.py @@ -6,7 +6,7 @@ # SPDX - License - Identifier: GPL - 3.0 + import unittest -from mantid.api import FunctionFactory +from mantid.api import FunctionFactory, MultiDomainFunction from mantid.py3compat import mock from mantidqt.utils.qt.testing import start_qapplication from qtpy import QtWidgets @@ -35,6 +35,17 @@ def wait_for_thread(thread_model): QtWidgets.QApplication.instance().processEvents() +def create_multi_domain_function(function_list): + if not any(function_list): + return None + multi_domain_function = MultiDomainFunction() + for index, func in enumerate(function_list): + multi_domain_function.add(func) + multi_domain_function.setDomainIndex(index, index) + + return multi_domain_function + + @start_qapplication class FittingTabPresenterTest(unittest.TestCase): def setUp(self): @@ -73,7 +84,7 @@ class FittingTabPresenterTest(unittest.TestCase): self.assertEqual(self.view.workspace_combo_box_label.text(), 'Display parameters for') - def test_that_changeing_fit_type_to_single_fit_updates_label(self): + def test_that_changing_fit_type_to_single_fit_updates_label(self): self.view.simul_fit_checkbox.setChecked(True) self.view.simul_fit_checkbox.setChecked(False) @@ -99,6 +110,7 @@ class FittingTabPresenterTest(unittest.TestCase): def test_get_parameters_for_single_fit_returns_correctly(self): self.presenter.selected_data = ['Input Workspace Name'] self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + result = self.presenter.get_parameters_for_single_fit() self.assertEqual(result, {'Function': mock.ANY, @@ -107,6 +119,18 @@ class FittingTabPresenterTest(unittest.TestCase): 'EvaluationType': 'CentrePoint'} ) + def test_get_parameters_for_simul_fit_returns_correctly(self): + self.presenter.selected_data = ['Input Workspace (1)', 'Input Workspace (2)'] + self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + + result = self.presenter.get_multi_domain_fit_parameters() + + self.assertEqual(result['InputWorkspace'], ['Input Workspace (1)', 'Input Workspace (2)']) + self.assertEqual(result['Minimizer'], 'Levenberg-Marquardt') + self.assertEqual(result['StartX'], [0.0, 0.0]) + self.assertEqual(result['EndX'], [15.0, 15.0]) + self.assertEqual(result['EvaluationType'], 'CentrePoint') + def test_for_single_fit_mode_when_display_workspace_changes_updates_fitting_browser_with_new_name(self): self.presenter.selected_data = ['Input Workspace Name'] @@ -120,8 +144,7 @@ class FittingTabPresenterTest(unittest.TestCase): return_value=['Input Workspace Name_1', 'Input Workspace Name 2']) self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') self.view.simul_fit_checkbox.setChecked(True) - print("FIT FUNCTION", self.view.function_browser_multi.getGlobalFunction()) - self.presenter.model.do_simultaneous_fit.return_value = (self.view.function_browser_multi.getGlobalFunction(), + self.presenter.model.do_simultaneous_fit.return_value = (self.view.function_browser.getGlobalFunction(), 'Fit Suceeded', 0.5) self.view.fit_button.clicked.emit(True) @@ -144,8 +167,8 @@ class FittingTabPresenterTest(unittest.TestCase): return_value=['Input Workspace Name_1', 'Input Workspace Name 2']) self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') self.view.simul_fit_checkbox.setChecked(True) - self.view.function_browser_multi.setGlobalParameters(['A']) - self.presenter.model.do_simultaneous_fit.return_value = (self.view.function_browser_multi.getGlobalFunction(), + self.view.function_browser.setGlobalParameters(['A']) + self.presenter.model.do_simultaneous_fit.return_value = (self.view.function_browser.getGlobalFunction(), 'Fit Suceeded', 0.5) self.view.fit_button.clicked.emit(True) @@ -162,113 +185,70 @@ class FittingTabPresenterTest(unittest.TestCase): call_args_globals = simultaneous_call_args[0][1] self.assertEqual(call_args_globals, ['A']) - def test_when_new_data_is_selected_clear_out_old_fits_and_information(self): - self.presenter._fit_status = ['success', 'success', 'success'] - self.presenter._fit_chi_squared = [12.3, 3.4, 0.35] - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.presenter_fit_function = [fit_function, fit_function, fit_function] - self.presenter.manual_selection_made = True - self.presenter._start_x = [0.15, 0.45, 0.67] - self.presenter._end_x = [0.56, 0.78, 0.34] - self.view.end_time = 0.56 - self.view.start_time = 0.15 - self.presenter.retrieve_first_good_data_from_run_name = mock.MagicMock(return_value=0.15) - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] - - self.presenter.selected_data = new_workspace_list - - self.assertEqual(self.presenter._fit_status, [None, None, None]) - self.assertEqual(self.presenter._fit_chi_squared, [0.0, 0.0, 0.0]) - self.assertEqual(self.presenter._fit_function, [None, None, None]) - self.assertEqual(self.presenter._selected_data, new_workspace_list) - self.assertEqual(self.presenter.manual_selection_made, True) - self.assertEqual(self.presenter.start_x, [0.15, 0.15, 0.15]) - self.assertEqual(self.presenter.end_x, [0.56, 0.56, 0.56]) - - def test_when_new_data_is_selected_updates_combo_box_on_view(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] + def test_fit_function_updates_correctly_and_resets_gui_state_if_new_data_is_selected_and_single_fit(self): + self.presenter._fit_status = ['success'] + self.presenter._fit_chi_squared = [12.3] + fit_function_string = 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0' + self.view.function_browser.setFunction(fit_function_string) + self.view.simul_fit_checkbox.setChecked(False) + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry'] + self.presenter.get_workspace_selected_list = mock.MagicMock(return_value=new_workspace_list) - self.presenter.selected_data = new_workspace_list + self.presenter.handle_selected_group_pair_changed() - self.assertEqual(retrieve_combobox_info(self.view.parameter_display_combo), new_workspace_list) + self.assertEqual(self.presenter._fit_status, [None, None]) + self.assertEqual(self.presenter._fit_chi_squared, [0.0, 0.0]) + self.assertEqual([str(item) for item in self.presenter._fit_function], + [fit_function_string] * 2) - def test_when_new_data_is_selected_updates_fit_property_browser_appropriately_for_simultaneous(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] + def test_stored_fit_function_updates_correctly_and_resets_gui_state_if_new_data_is_selected_and_simul_fit(self): + self.presenter._fit_status = ['success'] + self.presenter._fit_chi_squared = [12.3] + fit_function_string = 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0' + self.view.function_browser.setFunction(fit_function_string) self.view.simul_fit_checkbox.setChecked(True) + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry'] + self.presenter.get_workspace_selected_list = mock.MagicMock(return_value=new_workspace_list) - self.presenter.selected_data = new_workspace_list + self.presenter.handle_selected_group_pair_changed() - self.assertEqual(self.view.function_browser_multi.getDatasetNames(), new_workspace_list) - self.assertEqual(self.view.function_browser_multi.getNumberOfDatasets(), 3) + self.assertEqual(self.presenter._fit_status, [None, None]) + self.assertEqual(self.presenter._fit_chi_squared, [0.0, 0.0]) + self.assertEqual(str(self.presenter._fit_function[0]), + 'composite=MultiDomainFunction,NumDeriv=true;name=GausOsc,' + 'A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i;name=GausOsc,' + 'A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i') - def test_when_switching_to_simultaneous_function_browser_setup_correctly(self): + def test_when_new_data_is_selected_updates_combo_box_on_view(self): new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] - self.presenter.selected_data = new_workspace_list - self.presenter.context.get_names_of_workspaces_to_fit = mock.MagicMock( - return_value=['MUSR22725; Group; top; Asymmetry']) - - self.view.simul_fit_checkbox.setChecked(True) - - self.assertEqual(self.view.function_browser.getDatasetNames(), ['MUSR22725; Group; top; Asymmetry']) - self.assertEqual(self.view.function_browser.getNumberOfDatasets(), 1) - def test_when_switching_to_simultaneous_with_manual_selection_on_function_browser_setup_correctly(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] self.presenter.selected_data = new_workspace_list - self.presenter.manual_selection_made = True - - self.view.simul_fit_checkbox.setChecked(True) - self.assertEqual(self.view.function_browser_multi.getDatasetNames(), new_workspace_list) - self.assertEqual(self.view.function_browser_multi.getNumberOfDatasets(), 3) + self.assertEqual(retrieve_combobox_info(self.view.parameter_display_combo), new_workspace_list) - def test_when_switching_to_single_fit_from_simultaneous_with_manual_selection_on_function_browser_setup_correctly( - self): + def test_when_new_data_is_selected_updates_fit_property_browser_appropriately_for_simultaneous(self): new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] - self.presenter.selected_data = new_workspace_list - self.presenter.manual_selection_made = True - self.view.simul_fit_checkbox.setChecked(True) - self.view.simul_fit_checkbox.setChecked(False) - self.assertEqual(self.view.function_browser.getDatasetNames(), ['MUSR22725; Group; top; Asymmetry']) - self.assertEqual(self.view.function_browser.getNumberOfDatasets(), 1) - - def test_display_workspace_changed_for_single_fit_updates_function_browser(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] self.presenter.selected_data = new_workspace_list - self.presenter.manual_selection_made = True - self.presenter._start_x = [0.15, 0.45, 0.67] - self.presenter._end_x = [0.56, 0.78, 0.34] - self.view.parameter_display_combo.setCurrentIndex(1) - - self.assertEqual(self.view.function_browser.getDatasetNames(), ['MUSR22725; Group; bottom; Asymmetry']) - self.assertEqual(self.view.function_browser.getNumberOfDatasets(), 1) - self.assertEqual(self.view.end_time, 0.78) - self.assertEqual(self.view.start_time, 0.45) + self.assertEqual(self.view.function_browser.getDatasetNames(), new_workspace_list) + self.assertEqual(self.view.function_browser.getNumberOfDatasets(), 3) def test_display_workspace_changed_for_simultaneous_fit_updates_function_browser(self): self.view.simul_fit_checkbox.setChecked(True) new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] self.presenter.selected_data = new_workspace_list - self.presenter.manual_selection_made = True self.presenter._start_x = [0.15, 0.45, 0.67] self.presenter._end_x = [0.56, 0.78, 0.34] self.view.parameter_display_combo.setCurrentIndex(1) - self.assertEqual(self.view.function_browser_multi.getDatasetNames(), new_workspace_list) - self.assertEqual(self.view.function_browser_multi.getNumberOfDatasets(), 3) - # self.assertEqual(self.view.function_browser.getCurrentDataset(), 1) TODO FunctionBrowser seems to have an issue here + self.assertEqual(self.view.function_browser.getDatasetNames(), new_workspace_list) + self.assertEqual(self.view.function_browser.getNumberOfDatasets(), 3) self.assertEqual(self.view.end_time, 0.78) self.assertEqual(self.view.start_time, 0.45) @@ -302,176 +282,65 @@ class FittingTabPresenterTest(unittest.TestCase): self.assertEqual(self.view.function_name, 'test function') - def test_check_workspaces_are_tf_asymmetry_compliant(self): - list_of_lists_to_test = [ - ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'], - ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Pair; long; Asymmetry'], - ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; PhaseQuad'] - ] - - expected_results = [True, False, False] - - for workspace_list, expected_result in zip(list_of_lists_to_test, expected_results): - result = self.presenter.check_workspaces_are_tf_asymmetry_compliant(workspace_list) - self.assertEqual(result, expected_result) - - def test_get_parameters_for_tf_function_calculation_for_turning_mode_on(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] - self.view.tf_asymmetry_mode_checkbox.blockSignals(True) - self.view.tf_asymmetry_mode = True - self.view.tf_asymmetry_mode_checkbox.blockSignals(False) - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.presenter.selected_data = new_workspace_list - - result = self.presenter.get_parameters_for_tf_function_calculation(fit_function) - - self.assertEqual(result, {'InputFunction': fit_function, 'WorkspaceList': [new_workspace_list[0]], - 'Mode': 'Construct'}) - - def test_get_parameters_for_tf_function_calculation_for_turning_mode_off(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry'] - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.presenter.selected_data = new_workspace_list - - result = self.presenter.get_parameters_for_tf_function_calculation(fit_function) - - self.assertEqual(result, {'InputFunction': fit_function, 'WorkspaceList': [new_workspace_list[0]], - 'Mode': 'Extract'}) - - def test_handle_asymmetry_mode_changed_reverts_changed_and_shows_error_if_non_group_selected(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Pair; fwd; Long'] - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.view.function_browser.setFunction(str(fit_function)) - self.presenter.selected_data = new_workspace_list - - self.view.tf_asymmetry_mode = True - - self.view.warning_popup.assert_called_once_with( - 'Can only fit groups in tf asymmetry mode and need a function defined') - - def test_handle_asymmetry_mode_correctly_updates_function_for_a_single_fit(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry'] - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - fit_function_2 = FunctionFactory.createInitialized('name=GausOsc,A=1.0,Sigma=2.5,Frequency=0.1,Phi=0') - self.view.function_browser.setFunction(str(fit_function)) - self.presenter.selected_data = new_workspace_list - self.presenter.model.calculate_tf_function.return_value = fit_function_2 - - self.view.tf_asymmetry_mode = True - - self.assertEqual(str(self.view.fit_object), str(fit_function_2)) - self.assertEqual([str(item) for item in self.presenter._fit_function], [str(fit_function_2)]) - - def test_handle_asymmetry_mode_correctly_updates_function_for_sumultaneous_fit(self): - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.view.function_browser.setFunction(str(fit_function)) - self.view.simul_fit_checkbox.setChecked(True) - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; fwd'] - self.presenter.selected_data = new_workspace_list - fit_function_2 = self.view.fit_object.clone() - self.presenter.model.calculate_tf_function.return_value = fit_function_2 - - self.view.tf_asymmetry_mode = True - - self.assertEqual([str(item) for item in self.presenter._fit_function], [str(fit_function_2)] * 3) - self.assertEqual(str(self.view.fit_object), str(fit_function_2)) - - def test_handle_asymmetry_mode_correctly_keeps_globals_for_simultaneous_fit(self): - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.view.function_browser.setFunction(str(fit_function)) - self.view.simul_fit_checkbox.setChecked(True) - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; fwd'] - self.presenter.selected_data = new_workspace_list - fit_function_2 = self.view.fit_object.clone() - self.presenter.model.calculate_tf_function.return_value = fit_function_2 - self.view.function_browser_multi.setGlobalParameters(['A']) - - self.view.tf_asymmetry_mode = True - - self.assertEqual(self.view.get_global_parameters(), ['f0.f1.f1.A']) - - def test_handle_tf_asymmetry_mode_succesfully_converts_back(self): - new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; fwd'] - self.presenter.selected_data = new_workspace_list - self.view.function_browser.setFunction(EXAMPLE_TF_ASYMMETRY_FUNCTION) - self.view.tf_asymmetry_mode_checkbox.blockSignals(True) - self.view.tf_asymmetry_mode = True - self.presenter._tf_asymmetry_mode = True - self.view.tf_asymmetry_mode_checkbox.blockSignals(False) - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - self.presenter.model.calculate_tf_function.return_value = fit_function + def test_on_function_structure_changed_updates_stored_fit_function_for_single_fit(self): + self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; Asymmetry'] - self.view.tf_asymmetry_mode = False + self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') self.assertEqual([str(item) for item in self.presenter._fit_function], - ['name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0', - 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0', - 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0']) - self.assertEqual(str(self.view.fit_object), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - - def test_handle_fit_with_tf_asymmetry_mode_calls_CalculateMuonAsymmetry(self): - self.presenter.model.get_function_name.return_value = 'GausOsc' - self.presenter.model.create_fitted_workspace_name.return_value = ('workspace', 'workspace directory') - self.presenter.handle_finished = mock.MagicMock() - new_workspace_list = ['MUSR22725; Group; top; Asymmetry'] - fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') - fit_function_2 = FunctionFactory.createInitialized(EXAMPLE_TF_ASYMMETRY_FUNCTION) - self.view.function_browser.setFunction(str(fit_function)) - self.presenter.selected_data = new_workspace_list - self.presenter.model.calculate_tf_function.return_value = fit_function_2 - self.view.tf_asymmetry_mode = True - self.presenter.handle_fit_clicked() - wait_for_thread(self.presenter.calculation_thread) - - self.assertEqual(self.presenter.handle_finished.call_count, 1) + ['name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0'] * 3) - def test_get_parameters_for_tf_single_fit_calculation(self): - self.presenter.model.create_fitted_workspace_name.return_value = ('workspace', 'workspace directory') - self.context.group_pair_context.get_unormalisised_workspace_list = mock.MagicMock(return_value= - [ - '__MUSR22725; Group; top; Asymmetry_unnorm', - '__MUSR22725; Group; bottom; Asymmetry_unnorm', - '__MUSR22725; Group; fwd; Asymmetry_unnorm']) + def test_on_function_structure_changed_updates_stored_fit_function_for_simultaneous_fit(self): + self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry'] - result = self.presenter.get_parameters_for_tf_single_fit_calculation() + self.view.is_simul_fit = mock.MagicMock(return_value=True) + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + multi_domain_function = create_multi_domain_function([fit_function] * 2) + self.view.function_browser.setFunction(str(multi_domain_function)) - self.assertEqual(result, {'EndX': 15.0, - 'InputFunction': None, - 'Minimizer': 'Levenberg-Marquardt', - 'OutputFitWorkspace': 'workspace', - 'ReNormalizedWorkspaceList': '', - 'StartX': 0.0, - 'UnNormalizedWorkspaceList': '__MUSR22725; Group; top; Asymmetry_unnorm'} - ) + self.assertEqual(str(self.presenter._fit_function[0]), + 'composite=MultiDomainFunction,NumDeriv=true;name=GausOsc,' + 'A=0.2,Sigma=0.2,' + 'Frequency=0.1,Phi=0,$domains=i;' + 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i') - def test_on_function_structure_changed_stores_current_fit_state_in_relevant_presenter(self): + def test_updating_function_parameters_updates_relevant_stored_function_for_single_fit(self): self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] + self.view.is_simul_fit = mock.MagicMock(return_value=False) + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + multi_domain_function = create_multi_domain_function([fit_function] * 3) + self.view.function_browser.setFunction(str(multi_domain_function)) + self.view.parameter_display_combo.setCurrentIndex(2) - self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.view.function_browser.setParameter('A', 3) - self.assertEqual([str(item) for item in self.presenter._fit_function], - ['name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0'] * 3) + self.presenter.handle_function_parameter_changed() + + self.assertEqual(str(self.presenter._fit_function[0]), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.assertEqual(str(self.presenter._fit_function[1]), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.assertEqual(str(self.presenter._fit_function[2]), 'name=GausOsc,A=3,Sigma=0.2,Frequency=0.1,Phi=0') - def test_updating_function_parameters_updates_relevant_stored_function(self): + def test_updating_function_parameters_updates_relevant_stored_function_for_simul_fit(self): self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] - self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.view.is_simul_fit = mock.MagicMock(return_value=True) + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + multi_domain_function = create_multi_domain_function([fit_function] * 3) + self.view.function_browser.setFunction(str(multi_domain_function)) + self.view.parameter_display_combo.setCurrentIndex(1) + self.view.function_browser.setParameter('A', 3) - self.view.function_browser.setParameter('A', 1.5) + self.presenter.handle_function_parameter_changed() - self.assertEqual(str(self.view.fit_object), 'name=GausOsc,A=1.5,Sigma=0.2,Frequency=0.1,Phi=0') + self.assertEqual(str(self.presenter._fit_function[0]), 'composite=MultiDomainFunction,NumDeriv=true;' + 'name=GausOsc,''A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,' + '$domains=i;name=GausOsc,A=3,Sigma=0.2,Frequency=0.1,' + 'Phi=0,$domains=i;name=GausOsc,A=0.2,Sigma=0.2,' + 'Frequency=0.1,Phi=0,$domains=i') - def test_handle_display_workspace_changed_updates_displayed_single_function(self): + def test_handle_display_workspace_changed_updates_displayed_function(self): self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') @@ -479,17 +348,16 @@ class FittingTabPresenterTest(unittest.TestCase): self.view.parameter_display_combo.setCurrentIndex(1) - self.assertEqual(str(self.view.fit_object), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.assertEqual(str(self.presenter._fit_function[1]), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') def test_setting_selected_data_resets_function_browser_datasets(self): self.assertEqual(self.view.function_browser.getDatasetNames(), []) self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', 'MUSR22725; Group; fwd; Asymmetry'] - self.assertEqual(self.view.function_browser.getDatasetNames(), ['MUSR22725; Group; top; Asymmetry']) - self.assertEqual(self.view.function_browser_multi.getDatasetNames(), ['MUSR22725; Group; top; Asymmetry', - 'MUSR22725; Group; bottom; Asymmetry', - 'MUSR22725; Group; fwd; Asymmetry']) + self.assertEqual(self.view.function_browser.getDatasetNames(), ['MUSR22725; Group; top; Asymmetry', + 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; Asymmetry']) def test_switching_to_simultaneous_keeps_stored_fit_functions_same_length(self): self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', @@ -501,10 +369,10 @@ class FittingTabPresenterTest(unittest.TestCase): self.view.simul_fit_checkbox.setChecked(True) - self.assertEqual([str(item) for item in self.presenter._fit_function], [ - 'composite=MultiDomainFunction,NumDeriv=true;name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i' - ';name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i;' - 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i'] * 3) + self.assertEqual(str(self.view.fit_object), + 'composite=MultiDomainFunction,NumDeriv=true;name=GausOsc,A=0.2,Sigma=0.2,' + 'Frequency=0.1,Phi=0,$domains=i'';name=GausOsc,A=0.2,Sigma=0.2,' + 'Frequency=0.1,Phi=0,$domains=i;''name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0,$domains=i') def test_switching_from_simultaneous_to_single_fit_updates_fit_functions_appropriately(self): self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', @@ -548,13 +416,17 @@ class FittingTabPresenterTest(unittest.TestCase): self.view.function_browser.setFunction('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.5,Sigma=0.5,Frequency=1,Phi=0') self.presenter.fitting_calculation_model = mock.MagicMock() - self.presenter.model.do_single_fit.return_value = (fit_function, 'Fit Suceeded', 0.5) + self.presenter.model.do_single_fit.return_value = (fit_function, 'Fit Succeeded', 0.5) self.presenter.handle_fit_clicked() wait_for_thread(self.presenter.calculation_thread) + # test fit has updated the function + self.assertEqual(str(self.presenter._fit_function[0]), 'name=GausOsc,A=0.5,Sigma=0.5,Frequency=1,Phi=0') + self.view.undo_fit_button.clicked.emit(True) - self.assertEqual(str(self.view.fit_object), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + # test undo fit has worked + self.assertEqual(str(self.presenter._fit_function[0]), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') def test_removing_fit_and_then_switching_displayed_workspace_does_not_lead_to_error(self): self.presenter.selected_data = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', @@ -578,7 +450,7 @@ class FittingTabPresenterTest(unittest.TestCase): self.presenter.get_workspace_selected_list = mock.Mock() self.presenter.clear_and_reset_gui_state = mock.Mock() self.presenter.update_selected_workspace_guess() - self.assertEquals(self.presenter.get_workspace_selected_list.call_count, 1) + self.assertEqual(self.presenter.get_workspace_selected_list.call_count, 1) def test_update_selected_ws_guess_non(self): self.presenter.manual_selection_made = True @@ -590,9 +462,9 @@ class FittingTabPresenterTest(unittest.TestCase): self.presenter.selected_data = before self.presenter.update_selected_workspace_guess() - self.assertEquals(self.presenter.update_selected_frequency_workspace_guess.call_count, 0) - self.assertEquals(self.presenter.update_selected_time_workspace_guess.call_count, 0) - self.assertEquals(self.presenter.selected_data, before) + self.assertEqual(self.presenter.update_selected_frequency_workspace_guess.call_count, 0) + self.assertEqual(self.presenter.update_selected_time_workspace_guess.call_count, 0) + self.assertEqual(self.presenter.selected_data, before) def test_update_time_guess(self): self.context.get_names_of_workspaces_to_fit = mock.Mock(return_value="test") @@ -606,8 +478,8 @@ class FittingTabPresenterTest(unittest.TestCase): rebin=False, freq='None') self.context.get_names_of_workspaces_to_fit.assert_any_call(runs="All", group_and_pair="fwd", phasequad=False, rebin=False, freq='None') - self.assertEquals(self.presenter.context.get_names_of_workspaces_to_fit.call_count, 2) - self.assertEquals(output, ["test"]) + self.assertEqual(self.presenter.context.get_names_of_workspaces_to_fit.call_count, 2) + self.assertEqual(output, ["test"]) def test_handle_plot_guess_changed_calls_correct_function(self): self.presenter.get_parameters_for_single_fit = mock.Mock(return_value={}) @@ -623,7 +495,6 @@ class FittingTabPresenterTest(unittest.TestCase): self.assertEqual(-1, self.view.minimizer_combo.findText('FABADA')) def test_simul_fit_by_specifier_updates_correctly_when_fit_change_to_simultanenous(self): - # test simul fit by Run self.view.simul_fit_by_combo.setCurrentIndex(SIMUL_FIT_BY_COMBO_MAP["Run"]) self.view.simul_fit_checkbox.setChecked(True) @@ -675,8 +546,156 @@ class FittingTabPresenterTest(unittest.TestCase): self.assertEqual(str(self.view.parameter_display_combo.itemText(0)), "WS1") self.assertEqual(str(self.view.parameter_display_combo.itemText(1)), "WS2") - def test_simul_fit_by_specifier_corretcly_selects_workspaces_for_fit(self): - pass + def test_check_workspaces_are_tf_asymmetry_compliant(self): + list_of_lists_to_test = [ + ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; Asymmetry'], + ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Pair; long; Asymmetry'], + ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; PhaseQuad'] + ] + + expected_results = [True, False, False] + + for workspace_list, expected_result in zip(list_of_lists_to_test, expected_results): + result = self.presenter.check_workspaces_are_tf_asymmetry_compliant(workspace_list) + self.assertEqual(result, expected_result) + + def test_get_parameters_for_tf_function_calculation_for_turning_mode_on(self): + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; Asymmetry'] + self.view.tf_asymmetry_mode_checkbox.blockSignals(True) + self.view.tf_asymmetry_mode = True + self.view.tf_asymmetry_mode_checkbox.blockSignals(False) + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.presenter.selected_data = new_workspace_list + + result = self.presenter.get_parameters_for_tf_function_calculation(fit_function) + + self.assertEqual(result, {'InputFunction': fit_function, 'WorkspaceList': [new_workspace_list[0]], + 'Mode': 'Construct'}) + + def test_get_parameters_for_tf_function_calculation_for_turning_mode_off(self): + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; Asymmetry'] + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.presenter.selected_data = new_workspace_list + + result = self.presenter.get_parameters_for_tf_function_calculation(fit_function) + + self.assertEqual(result, {'InputFunction': fit_function, 'WorkspaceList': [new_workspace_list[0]], + 'Mode': 'Extract'}) + + def test_handle_asymmetry_mode_changed_reverts_changed_and_shows_error_if_non_group_selected(self): + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Pair; fwd; Long'] + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.view.function_browser.setFunction(str(fit_function)) + self.presenter.selected_data = new_workspace_list + + self.view.tf_asymmetry_mode = True + + self.view.warning_popup.assert_called_once_with( + 'Can only fit groups in tf asymmetry mode and need a function defined') + + def test_handle_asymmetry_mode_correctly_updates_function_for_a_single_fit(self): + new_workspace_list = ['MUSR22725; Group; top; Asymmetry'] + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + fit_function_2 = FunctionFactory.createInitialized('name=GausOsc,A=1.0,Sigma=2.5,Frequency=0.1,Phi=0') + self.view.function_browser.setFunction(str(fit_function)) + self.presenter.selected_data = new_workspace_list + self.presenter.model.calculate_tf_function.return_value = fit_function_2 + + self.view.tf_asymmetry_mode = True + + self.assertEqual(str(self.view.fit_object), str(fit_function_2)) + self.assertEqual([str(item) for item in self.presenter._fit_function], [str(fit_function_2)]) + + def test_handle_asymmetry_mode_correctly_updates_function_for_simultaneous_fit(self): + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.view.function_browser.setFunction(str(fit_function)) + self.view.simul_fit_checkbox.setChecked(True) + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; fwd'] + self.presenter.selected_data = new_workspace_list + fit_function_2 = self.view.fit_object.clone() + self.presenter.model.calculate_tf_function.return_value = fit_function_2 + + self.view.tf_asymmetry_mode = True + + self.assertEqual([str(item) for item in self.presenter._fit_function], [str(fit_function_2)] * 3) + self.assertEqual(str(self.view.fit_object), str(fit_function_2)) + + def test_handle_asymmetry_mode_correctly_keeps_globals_for_simultaneous_fit(self): + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.view.function_browser.setFunction(str(fit_function)) + self.view.simul_fit_checkbox.setChecked(True) + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; fwd'] + self.presenter.selected_data = new_workspace_list + fit_function_2 = self.view.fit_object.clone() + self.presenter.model.calculate_tf_function.return_value = fit_function_2 + self.view.function_browser.setGlobalParameters(['A']) + + self.view.tf_asymmetry_mode = True + + self.assertEqual(self.view.get_global_parameters(), ['f0.f1.f1.A']) + + def test_handle_tf_asymmetry_mode_succesfully_converts_back(self): + new_workspace_list = ['MUSR22725; Group; top; Asymmetry', 'MUSR22725; Group; bottom; Asymmetry', + 'MUSR22725; Group; fwd; fwd'] + self.presenter.selected_data = new_workspace_list + self.view.function_browser.setFunction(EXAMPLE_TF_ASYMMETRY_FUNCTION) + self.view.tf_asymmetry_mode_checkbox.blockSignals(True) + self.view.tf_asymmetry_mode = True + self.presenter._tf_asymmetry_mode = True + self.view.tf_asymmetry_mode_checkbox.blockSignals(False) + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + self.presenter.model.calculate_tf_function.return_value = fit_function + + self.view.tf_asymmetry_mode = False + + self.assertEqual([str(item) for item in self.presenter._fit_function], + ['name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0', + 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0', + 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0']) + self.assertEqual(str(self.view.fit_object), 'name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + + def test_handle_fit_with_tf_asymmetry_mode_calls_CalculateMuonAsymmetry(self): + self.presenter.model.get_function_name.return_value = 'GausOsc' + self.presenter.model.create_fitted_workspace_name.return_value = ('workspace', 'workspace directory') + self.presenter.handle_finished = mock.MagicMock() + new_workspace_list = ['MUSR22725; Group; top; Asymmetry'] + fit_function = FunctionFactory.createInitialized('name=GausOsc,A=0.2,Sigma=0.2,Frequency=0.1,Phi=0') + fit_function_2 = FunctionFactory.createInitialized(EXAMPLE_TF_ASYMMETRY_FUNCTION) + self.view.function_browser.setFunction(str(fit_function)) + self.presenter.selected_data = new_workspace_list + self.presenter.model.calculate_tf_function.return_value = fit_function_2 + self.view.tf_asymmetry_mode = True + self.presenter.handle_fit_clicked() + wait_for_thread(self.presenter.calculation_thread) + + self.assertEqual(self.presenter.handle_finished.call_count, 1) + + def test_get_parameters_for_tf_single_fit_calculation(self): + self.presenter.model.create_fitted_workspace_name.return_value = ('workspace', 'workspace directory') + self.context.group_pair_context.get_unormalisised_workspace_list = mock.MagicMock(return_value= + [ + '__MUSR22725; Group; top; Asymmetry_unnorm', + '__MUSR22725; Group; bottom; Asymmetry_unnorm', + '__MUSR22725; Group; fwd; Asymmetry_unnorm']) + + result = self.presenter.get_parameters_for_tf_single_fit_calculation() + + self.assertEqual(result, {'EndX': 15.0, + 'InputFunction': None, + 'Minimizer': 'Levenberg-Marquardt', + 'OutputFitWorkspace': 'workspace', + 'ReNormalizedWorkspaceList': '', + 'StartX': 0.0, + 'UnNormalizedWorkspaceList': '__MUSR22725; Group; top; Asymmetry_unnorm'} + ) if __name__ == '__main__':