From 261ddcb7954d29a32994e626177e2a8418c5534f Mon Sep 17 00:00:00 2001
From: Robert Applin <robert.applin@stfc.ac.uk>
Date: Tue, 1 Dec 2020 14:49:58 +0000
Subject: [PATCH] Refs #29906. Add empty composites to model.

---
 .../Common/FitScriptGeneratorModel.h          |  69 +++++--
 .../Common/FitScriptGeneratorPresenter.h      |  16 +-
 .../Common/FitScriptGeneratorView.h           |   7 +-
 .../common/src/FitScriptGeneratorModel.cpp    | 192 +++++++++++++++++-
 .../src/FitScriptGeneratorPresenter.cpp       |  53 ++++-
 .../common/src/FitScriptGeneratorView.cpp     |  21 +-
 6 files changed, 306 insertions(+), 52 deletions(-)

diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorModel.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorModel.h
index 4475a136a08..1cff4e44c35 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorModel.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorModel.h
@@ -7,22 +7,36 @@
 #pragma once
 
 #include "DllOption.h"
+#include "MantidAPI/IFunction_fwd.h"
 #include "MantidAPI/MatrixWorkspace_fwd.h"
+#include "MantidAPI/MultiDomainFunction.h"
 #include "MantidQtWidgets/Common/IndexTypes.h"
 
-#include <map>
 #include <string>
-#include <utility>
+#include <vector>
+
+#include <QString>
 
 namespace MantidQt {
 namespace MantidWidgets {
 
-// Workspace name, workspace index
-// using WorkspaceDomain = std::pair<std::string, std::size_t>;
-// StartX, EndX
-// using XRange = std::pair<double, double>;
-// Fit functions
-// using FitFunctions = std::vector<IFunction_sptr>;
+struct FitDomain {
+
+  FitDomain(std::string const &prefix, std::string const &workspaceName,
+            WorkspaceIndex workspaceIndex, double startX, double endX) {
+    m_multiDomainFunctionPrefix = prefix;
+    m_workspaceName = workspaceName;
+    m_workspaceIndex = workspaceIndex;
+    m_startX = startX;
+    m_endX = endX;
+  }
+
+  std::string m_multiDomainFunctionPrefix;
+  std::string m_workspaceName;
+  WorkspaceIndex m_workspaceIndex;
+  double m_startX;
+  double m_endX;
+};
 
 class EXPORT_OPT_MANTIDQT_COMMON FitScriptGeneratorModel {
 public:
@@ -34,13 +48,40 @@ public:
   void addWorkspaceDomain(std::string const &workspaceName,
                           WorkspaceIndex workspaceIndex, double startX,
                           double endX);
-  void addWorkspaceDomains(
-      std::vector<Mantid::API::MatrixWorkspace_const_sptr> const &workspaces,
-      std::vector<WorkspaceIndex> const &workspaceIndices);
 
-  // private:
-  // std::map<WorkspaceDomain, XRange> m_fitRanges;
-  // std::map<WorkspaceDomain, FitFunctions> m_fitFunctions;
+private:
+  void removeWorkspaceDomain(
+      std::size_t const &removeIndex,
+      std::vector<FitDomain>::const_iterator const &removeIter);
+  void addWorkspaceDomain(std::string const &prefix,
+                          std::string const &workspaceName,
+                          WorkspaceIndex workspaceIndex, double startX,
+                          double endX);
+
+  bool hasWorkspaceDomain(std::string const &workspaceName,
+                          WorkspaceIndex workspaceIndex);
+  std::vector<FitDomain>::const_iterator
+  findWorkspaceDomain(std::string const &workspaceName,
+                      WorkspaceIndex workspaceIndex) const;
+
+  void removeCompositeAtPrefix(std::string const &prefix);
+
+  void addEmptyCompositeAtPrefix(std::string const &prefix);
+  void addEmptyCompositeAtPrefix(std::string const &compositePrefix,
+                                 std::size_t const &compositeIndex);
+
+  void removeCompositeAtIndex(std::size_t const &compositeIndex);
+
+  bool hasCompositeAtPrefix(std::string const &prefix) const;
+
+  std::string nextAvailablePrefix() const;
+
+  [[nodiscard]] inline std::size_t numberOfDomains() const noexcept {
+    return m_fitDomains.size();
+  }
+
+  std::vector<FitDomain> m_fitDomains;
+  Mantid::API::MultiDomainFunction_sptr m_function;
 };
 
 } // namespace MantidWidgets
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorPresenter.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorPresenter.h
index 1d45fb0238f..fd80f7fa413 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorPresenter.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorPresenter.h
@@ -10,6 +10,7 @@
 #include "FitScriptGeneratorView.h"
 
 #include "MantidAPI/MatrixWorkspace_fwd.h"
+#include "MantidQtWidgets/Common/IndexTypes.h"
 
 #include <string>
 #include <vector>
@@ -41,10 +42,21 @@ private:
 
   void setWorkspaces(QStringList const &workspaceNames, double startX,
                      double endX);
+  void addWorkspaces(
+      std::vector<Mantid::API::MatrixWorkspace_const_sptr> const &workspaces,
+      std::vector<WorkspaceIndex> const &workspaceIndices);
   void addWorkspace(std::string const &workspaceName, double startX,
                     double endX);
-  void addWorkspace(MatrixWorkspace_const_sptr const &workspace, double startX,
-                    double endX);
+  void addWorkspace(Mantid::API::MatrixWorkspace_const_sptr const &workspace,
+                    double startX, double endX);
+  void addWorkspace(Mantid::API::MatrixWorkspace_const_sptr const &workspace,
+                    WorkspaceIndex workspaceIndex, double startX, double endX);
+  void addWorkspace(std::string const &workspaceName,
+                    WorkspaceIndex workspaceIndex, double startX, double endX);
+
+  void displayWarningMessages();
+
+  std::vector<std::string> m_warnings;
 
   FitScriptGeneratorModel *m_model;
   FitScriptGeneratorView *m_view;
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorView.h b/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorView.h
index 120b82b2ed9..63ec7321a62 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorView.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/FitScriptGeneratorView.h
@@ -62,14 +62,13 @@ public:
   void addWorkspaceDomain(std::string const &workspaceName,
                           WorkspaceIndex workspaceIndex, double startX,
                           double endX);
-  void addWorkspaceDomains(
-      std::vector<Mantid::API::MatrixWorkspace_const_sptr> const &workspaces,
-      std::vector<WorkspaceIndex> const &workspaceIndices);
 
   bool openAddWorkspaceDialog();
   std::vector<Mantid::API::MatrixWorkspace_const_sptr> getDialogWorkspaces();
   std::vector<WorkspaceIndex> getDialogWorkspaceIndices() const;
 
+  void displayWarning(std::string const &message);
+
 private slots:
   void onRemoveClicked();
   void onAddWorkspaceClicked();
@@ -81,8 +80,6 @@ private:
   void setFitBrowserOption(QString const &name, QString const &value);
   void setFittingType(QString const &fitType);
 
-  void displayWarning(QString const &message);
-
   FitScriptGeneratorPresenter *m_presenter;
   AddWorkspaceDialog m_dialog;
   std::unique_ptr<FitScriptGeneratorDataTable> m_dataTable;
diff --git a/qt/widgets/common/src/FitScriptGeneratorModel.cpp b/qt/widgets/common/src/FitScriptGeneratorModel.cpp
index ad8ea17ce7d..162ae3af6d2 100644
--- a/qt/widgets/common/src/FitScriptGeneratorModel.cpp
+++ b/qt/widgets/common/src/FitScriptGeneratorModel.cpp
@@ -6,27 +6,207 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidQtWidgets/Common/FitScriptGeneratorModel.h"
 
+#include "MantidAPI/CompositeFunction.h"
+#include "MantidAPI/FunctionFactory.h"
+#include "MantidAPI/IFunction.h"
 #include "MantidAPI/MatrixWorkspace.h"
 
+#include <boost/algorithm/string.hpp>
+
+#include <algorithm>
+#include <stdexcept>
+
 using namespace Mantid::API;
 
+namespace {
+
+IFunction_sptr createIFunction(std::string const &functionString) {
+  return FunctionFactory::Instance().createInitialized(functionString);
+}
+
+CompositeFunction_sptr toComposite(IFunction_sptr function) {
+  return std::dynamic_pointer_cast<CompositeFunction>(function);
+}
+
+CompositeFunction_sptr createEmptyComposite() {
+  return toComposite(createIFunction("name=CompositeFunction"));
+}
+
+MultiDomainFunction_sptr
+createMultiDomainFunction(std::size_t const &numberOfDomains) {
+  return FunctionFactory::Instance().createInitializedMultiDomainFunction(
+      "name=CompositeFunction", numberOfDomains);
+}
+
+std::vector<std::string> splitStringBy(std::string const &str,
+                                       std::string const &delimiter) {
+  std::vector<std::string> subStrings;
+  boost::split(subStrings, str, boost::is_any_of(delimiter));
+  subStrings.erase(std::remove_if(subStrings.begin(), subStrings.end(),
+                                  [](std::string const &subString) {
+                                    return subString.empty();
+                                  }),
+                   subStrings.cend());
+  return subStrings;
+}
+
+bool isSinglePrefix(std::string const &prefix) {
+  auto const subStrings = splitStringBy(prefix, "f.");
+  return subStrings.size() == 1;
+}
+
+std::size_t getPrefixIndexAt(std::string const &prefix,
+                             std::size_t const &index) {
+  auto const subStrings = splitStringBy(prefix, "f.");
+  return std::stoull(subStrings[index]);
+}
+
+std::string getTopPrefix(std::string const &prefix) {
+  auto topPrefix = prefix;
+  auto const firstDotIndex = topPrefix.find(".");
+  if (firstDotIndex != std::string::npos)
+    topPrefix.erase(firstDotIndex, topPrefix.size() - firstDotIndex);
+  return topPrefix;
+}
+
+IFunction_sptr getFunctionAtPrefix(std::string const &prefix,
+                                   IFunction_sptr const &function,
+                                   bool isLastFunction = false) {
+  if (isLastFunction)
+    return function;
+
+  auto const isLast = isSinglePrefix(prefix);
+  auto const topPrefix = getTopPrefix(prefix);
+  auto const firstIndex = getPrefixIndexAt(prefix, 0);
+
+  try {
+    return getFunctionAtPrefix(topPrefix, function->getFunction(firstIndex),
+                               isLast);
+  } catch (std::exception const &) {
+    return IFunction_sptr();
+  }
+}
+
+} // namespace
+
 namespace MantidQt {
 namespace MantidWidgets {
 
-FitScriptGeneratorModel::FitScriptGeneratorModel() {}
+FitScriptGeneratorModel::FitScriptGeneratorModel()
+    : m_fitDomains(), m_function(nullptr) {}
 
 FitScriptGeneratorModel::~FitScriptGeneratorModel() {}
 
 void FitScriptGeneratorModel::removeWorkspaceDomain(
-    std::string const &workspaceName, WorkspaceIndex workspaceIndex) {}
+    std::string const &workspaceName, WorkspaceIndex workspaceIndex) {
+  auto const removeIter = findWorkspaceDomain(workspaceName, workspaceIndex);
+  auto const removePrefix = removeIter->m_multiDomainFunctionPrefix;
+  auto const removeIndex = getPrefixIndexAt(removePrefix, 0);
+
+  removeWorkspaceDomain(removeIndex, removeIter);
+  removeCompositeAtPrefix(removePrefix);
+}
+
+void FitScriptGeneratorModel::removeWorkspaceDomain(
+    std::size_t const &removeIndex,
+    std::vector<FitDomain>::const_iterator const &removeIter) {
+  if (removeIter != m_fitDomains.cend())
+    m_fitDomains.erase(removeIter);
+
+  auto const decrementPrefixes = [&](FitDomain &fitDomain) {
+    auto thisIndex = getPrefixIndexAt(fitDomain.m_multiDomainFunctionPrefix, 0);
+    if (removeIndex < thisIndex) {
+      --thisIndex;
+      fitDomain.m_multiDomainFunctionPrefix = "f" + std::to_string(thisIndex);
+    }
+    return fitDomain;
+  };
+
+  std::transform(m_fitDomains.begin(), m_fitDomains.end(), m_fitDomains.begin(),
+                 decrementPrefixes);
+}
 
 void FitScriptGeneratorModel::addWorkspaceDomain(
     std::string const &workspaceName, WorkspaceIndex workspaceIndex,
-    double startX, double endX) {}
+    double startX, double endX) {
+  if (hasWorkspaceDomain(workspaceName, workspaceIndex))
+    throw std::invalid_argument("The '" + workspaceName + " (" +
+                                std::to_string(workspaceIndex.value) +
+                                ")' domain already exists.");
+
+  addWorkspaceDomain(nextAvailablePrefix(), workspaceName, workspaceIndex,
+                     startX, endX);
+}
+
+void FitScriptGeneratorModel::addWorkspaceDomain(
+    std::string const &prefix, std::string const &workspaceName,
+    WorkspaceIndex workspaceIndex, double startX, double endX) {
+  addEmptyCompositeAtPrefix(prefix);
+  m_fitDomains.emplace_back(
+      FitDomain(prefix, workspaceName, workspaceIndex, startX, endX));
+}
+
+bool FitScriptGeneratorModel::hasWorkspaceDomain(
+    std::string const &workspaceName, WorkspaceIndex workspaceIndex) {
+  return findWorkspaceDomain(workspaceName, workspaceIndex) !=
+         m_fitDomains.end();
+}
+
+std::vector<FitDomain>::const_iterator
+FitScriptGeneratorModel::findWorkspaceDomain(
+    std::string const &workspaceName, WorkspaceIndex workspaceIndex) const {
+  auto const isMatch = [&](FitDomain const &fitDomain) {
+    return fitDomain.m_workspaceName == workspaceName &&
+           fitDomain.m_workspaceIndex == workspaceIndex;
+  };
+
+  return std::find_if(m_fitDomains.cbegin(), m_fitDomains.cend(), isMatch);
+}
+
+void FitScriptGeneratorModel::removeCompositeAtPrefix(
+    std::string const &prefix) {
+  removeCompositeAtIndex(getPrefixIndexAt(prefix, 0));
+}
+
+void FitScriptGeneratorModel::addEmptyCompositeAtPrefix(
+    std::string const &prefix) {
+  if (!m_function) {
+    m_function = createMultiDomainFunction(1);
+  } else {
+    addEmptyCompositeAtPrefix(getTopPrefix(prefix),
+                              getPrefixIndexAt(prefix, 0));
+  }
+}
+
+void FitScriptGeneratorModel::addEmptyCompositeAtPrefix(
+    std::string const &compositePrefix, std::size_t const &compositeIndex) {
+  if (compositeIndex != numberOfDomains())
+    throw std::runtime_error("The composite index provided is invalid.");
+
+  if (hasCompositeAtPrefix(compositePrefix))
+    throw std::runtime_error("The composite prefix provided already exists.");
+
+  m_function->addFunction(createEmptyComposite());
+  m_function->setDomainIndex(compositeIndex, compositeIndex);
+}
+
+void FitScriptGeneratorModel::removeCompositeAtIndex(
+    std::size_t const &compositeIndex) {
+  if (compositeIndex > numberOfDomains())
+    throw std::runtime_error("The composite prefix provided does not exist");
+
+  m_function->removeFunction(compositeIndex);
+}
+
+bool FitScriptGeneratorModel::hasCompositeAtPrefix(
+    std::string const &compositePrefix) const {
+  return static_cast<bool>(
+      toComposite(getFunctionAtPrefix(compositePrefix, m_function)));
+}
 
-void FitScriptGeneratorModel::addWorkspaceDomains(
-    std::vector<MatrixWorkspace_const_sptr> const &workspaces,
-    std::vector<WorkspaceIndex> const &workspaceIndices) {}
+std::string FitScriptGeneratorModel::nextAvailablePrefix() const {
+  return "f" + std::to_string(numberOfDomains());
+}
 
 } // namespace MantidWidgets
 } // namespace MantidQt
diff --git a/qt/widgets/common/src/FitScriptGeneratorPresenter.cpp b/qt/widgets/common/src/FitScriptGeneratorPresenter.cpp
index 0a53c02c1e3..8496a2f79dd 100644
--- a/qt/widgets/common/src/FitScriptGeneratorPresenter.cpp
+++ b/qt/widgets/common/src/FitScriptGeneratorPresenter.cpp
@@ -10,6 +10,9 @@
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/MatrixWorkspace.h"
 
+#include <algorithm>
+#include <iterator>
+#include <sstream>
 #include <stdexcept>
 
 namespace MantidQt {
@@ -18,7 +21,7 @@ namespace MantidWidgets {
 FitScriptGeneratorPresenter::FitScriptGeneratorPresenter(
     FitScriptGeneratorView *view, FitScriptGeneratorModel *model,
     QStringList const &workspaceNames, double startX, double endX)
-    : m_view(view), m_model(model) {
+    : m_warnings(), m_view(view), m_model(model) {
   m_view->subscribePresenter(this);
   setWorkspaces(workspaceNames, startX, endX);
 }
@@ -59,10 +62,8 @@ void FitScriptGeneratorPresenter::handleAddWorkspaceClicked() {
     auto const workspaces = m_view->getDialogWorkspaces();
     auto const workspaceIndices = m_view->getDialogWorkspaceIndices();
 
-    if (!workspaces.empty() && !workspaceIndices.empty()) {
-      m_view->addWorkspaceDomains(workspaces, workspaceIndices);
-      m_model->addWorkspaceDomains(workspaces, workspaceIndices);
-    }
+    if (!workspaces.empty() && !workspaceIndices.empty())
+      addWorkspaces(workspaces, workspaceIndices);
   }
 }
 
@@ -70,6 +71,17 @@ void FitScriptGeneratorPresenter::setWorkspaces(
     QStringList const &workspaceNames, double startX, double endX) {
   for (auto const &workspaceName : workspaceNames)
     addWorkspace(workspaceName.toStdString(), startX, endX);
+  displayWarningMessages();
+}
+void FitScriptGeneratorPresenter::addWorkspaces(
+    std::vector<MatrixWorkspace_const_sptr> const &workspaces,
+    std::vector<WorkspaceIndex> const &workspaceIndices) {
+  for (auto const &workspace : workspaces)
+    for (auto const &workspaceIndex : workspaceIndices) {
+      auto const xData = workspace->x(workspaceIndex.value);
+      addWorkspace(workspace, workspaceIndex, xData.front(), xData.back());
+    }
+  displayWarningMessages();
 }
 
 void FitScriptGeneratorPresenter::addWorkspace(std::string const &workspaceName,
@@ -81,9 +93,34 @@ void FitScriptGeneratorPresenter::addWorkspace(std::string const &workspaceName,
 
 void FitScriptGeneratorPresenter::addWorkspace(
     MatrixWorkspace_const_sptr const &workspace, double startX, double endX) {
-  for (auto index = 0u; index < workspace->getNumberHistograms(); ++index) {
-    m_view->addWorkspaceDomain(workspace->getName(), index, startX, endX);
-    m_model->addWorkspaceDomain(workspace->getName(), index, startX, endX);
+  for (auto index = 0u; index < workspace->getNumberHistograms(); ++index)
+    addWorkspace(workspace, WorkspaceIndex{index}, startX, endX);
+}
+
+void FitScriptGeneratorPresenter::addWorkspace(
+    MatrixWorkspace_const_sptr const &workspace, WorkspaceIndex workspaceIndex,
+    double startX, double endX) {
+  addWorkspace(workspace->getName(), workspaceIndex, startX, endX);
+}
+
+void FitScriptGeneratorPresenter::addWorkspace(std::string const &workspaceName,
+                                               WorkspaceIndex workspaceIndex,
+                                               double startX, double endX) {
+  try {
+    m_model->addWorkspaceDomain(workspaceName, workspaceIndex, startX, endX);
+    m_view->addWorkspaceDomain(workspaceName, workspaceIndex, startX, endX);
+  } catch (std::invalid_argument const &ex) {
+    m_warnings.emplace_back(ex.what());
+  }
+}
+
+void FitScriptGeneratorPresenter::displayWarningMessages() {
+  if (!m_warnings.empty()) {
+    std::stringstream ss;
+    std::copy(m_warnings.cbegin(), m_warnings.cend(),
+              std::ostream_iterator<std::string>(ss, "\n"));
+    m_view->displayWarning(ss.str());
+    m_warnings.clear();
   }
 }
 
diff --git a/qt/widgets/common/src/FitScriptGeneratorView.cpp b/qt/widgets/common/src/FitScriptGeneratorView.cpp
index a0ad18ad11e..24b5fdfee33 100644
--- a/qt/widgets/common/src/FitScriptGeneratorView.cpp
+++ b/qt/widgets/common/src/FitScriptGeneratorView.cpp
@@ -161,18 +161,6 @@ void FitScriptGeneratorView::addWorkspaceDomain(
                          startX, endX);
 }
 
-void FitScriptGeneratorView::addWorkspaceDomains(
-    std::vector<MatrixWorkspace_const_sptr> const &workspaces,
-    std::vector<WorkspaceIndex> const &workspaceIndices) {
-  for (auto const &workspace : workspaces) {
-    for (auto const &workspaceIndex : workspaceIndices) {
-      auto const xData = workspace->x(workspaceIndex.value);
-      addWorkspaceDomain(workspace->getName(), workspaceIndex, xData.front(),
-                         xData.back());
-    }
-  }
-}
-
 bool FitScriptGeneratorView::openAddWorkspaceDialog() {
   return m_dialog.exec() == QDialog::Accepted;
 }
@@ -185,9 +173,8 @@ FitScriptGeneratorView::getDialogWorkspaces() {
   if (AnalysisDataService::Instance().doesExist(workspaceName))
     workspaces = getWorkspaces(workspaceName);
   else
-    displayWarning("Failed to add workspace '" +
-                   QString::fromStdString(workspaceName) +
-                   "' : workspace doesn't exist.");
+    displayWarning("Failed to add workspace '" + workspaceName +
+                   "': workspace doesn't exist.");
   return workspaces;
 }
 
@@ -196,8 +183,8 @@ FitScriptGeneratorView::getDialogWorkspaceIndices() const {
   return convertToWorkspaceIndex(m_dialog.workspaceIndices());
 }
 
-void FitScriptGeneratorView::displayWarning(QString const &message) {
-  QMessageBox::warning(this, "Warning!", message);
+void FitScriptGeneratorView::displayWarning(std::string const &message) {
+  QMessageBox::warning(this, "Warning!", QString::fromStdString(message));
 }
 
 } // namespace MantidWidgets
-- 
GitLab