ReductionJobs.cpp 10.6 KB
Newer Older
1
2
3
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4
5
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
// SPDX - License - Identifier: GPL - 3.0 +
7
#include "ReductionJobs.h"
8
9
#include "Common/IndexOf.h"
#include "Common/Map.h"
Gemma Guest's avatar
Gemma Guest committed
10
#include "MantidQtWidgets/Common/Batch/AssertOrThrow.h"
11
#include "RowLocation.h"
12
#include <iostream>
Gemma Guest's avatar
Gemma Guest committed
13
14
#include <numeric>

15
namespace MantidQt::CustomInterfaces::ISISReflectometry {
16

17
namespace {
Samuel Jones's avatar
Samuel Jones committed
18
Group &findOrMakeGroupWithName(ReductionJobs &jobs, std::string const &groupName) {
Gemma Guest's avatar
Gemma Guest committed
19
20
  auto maybeGroupIndex = jobs.indexOfGroupWithName(groupName);
  if (maybeGroupIndex.is_initialized())
21
    return jobs.mutableGroups()[maybeGroupIndex.get()];
Gemma Guest's avatar
Gemma Guest committed
22
23
  else
    return jobs.appendGroup(Group(groupName));
Gemma Guest's avatar
Gemma Guest committed
24
25
26
27
}

/* Return the number of rows and groups that have processing or
 * postprocessing associated with them */
Samuel Jones's avatar
Samuel Jones committed
28
int countItems(ReductionJobs const &jobs, Item::ItemCountFunction countFunction) {
Gemma Guest's avatar
Gemma Guest committed
29
30
  auto const &groups = jobs.groups();
  return std::accumulate(groups.cbegin(), groups.cend(), 0,
Samuel Jones's avatar
Samuel Jones committed
31
                         [countFunction](int &count, Group const &group) { return count + (group.*countFunction)(); });
Gemma Guest's avatar
Gemma Guest committed
32
}
Gemma Guest's avatar
Gemma Guest committed
33
} // namespace
34

35
ReductionJobs::ReductionJobs(std::vector<Group> groups) : m_groups(std::move(groups)), m_groupNameSuffix(1) {}
36

Gemma Guest's avatar
Gemma Guest committed
37
ReductionJobs::ReductionJobs() : m_groupNameSuffix(1) {}
Gemma Guest's avatar
Gemma Guest committed
38

Gemma Guest's avatar
Gemma Guest committed
39
Group &ReductionJobs::appendGroup(Group group) {
40
41
  assertOrThrow(group.name().empty() || !hasGroupWithName(group.name()),
                "Cannot have multiple groups with a matching non-empty name.");
42
43
  m_groups.emplace_back(std::move(group));
  return m_groups.back();
44
45
}

Samuel Jones's avatar
Samuel Jones committed
46
47
boost::optional<int> ReductionJobs::indexOfGroupWithName(std::string const &groupName) const {
  return indexOf(m_groups, [&groupName](Group const &group) -> bool { return group.name() == groupName; });
48
49
}

Gemma Guest's avatar
Gemma Guest committed
50
Group &ReductionJobs::insertGroup(Group group, int beforeIndex) {
51
52
  assertOrThrow(group.name().empty() || !hasGroupWithName(group.name()),
                "Cannot have multiple groups with a matching non-empty name.");
53
  return *m_groups.insert(m_groups.begin() + beforeIndex, std::move(group));
54
55
}

Gemma Guest's avatar
Gemma Guest committed
56
bool ReductionJobs::hasGroupWithName(std::string const &groupName) const {
57
  return std::any_of(m_groups.crbegin(), m_groups.crend(),
Samuel Jones's avatar
Samuel Jones committed
58
                     [&groupName](Group const &group) -> bool { return group.name() == groupName; });
59
60
}

61
bool ReductionJobs::containsSingleEmptyGroup() const {
Samuel Jones's avatar
Samuel Jones committed
62
  return groups().size() == 1 && groups()[0].rows().size() == 0 && groups()[0].name().empty();
63
64
}

Gemma Guest's avatar
Gemma Guest committed
65
66
void ReductionJobs::removeGroup(int index) {
  m_groups.erase(m_groups.begin() + index);
67
68
69
70
71
72
  ensureAtLeastOneGroupExists(*this);
}

void ReductionJobs::removeAllGroups() {
  m_groups.clear();
  ensureAtLeastOneGroupExists(*this);
Gemma Guest's avatar
Gemma Guest committed
73
}
74

75
void ReductionJobs::resetState() {
Samuel Jones's avatar
Samuel Jones committed
76
  std::for_each(m_groups.begin(), m_groups.end(), [](Group &group) { group.resetState(); });
77
78
}

79
void ReductionJobs::resetSkippedItems() {
Samuel Jones's avatar
Samuel Jones committed
80
  std::for_each(m_groups.begin(), m_groups.end(), [](Group &group) { group.resetSkipped(); });
81
82
}

83
std::vector<Group> &ReductionJobs::mutableGroups() { return m_groups; }
84

Gemma Guest's avatar
Gemma Guest committed
85
std::vector<Group> const &ReductionJobs::groups() const { return m_groups; }
86

Gemma Guest's avatar
Gemma Guest committed
87
std::string ReductionJobs::nextEmptyGroupName() {
88
  std::string name = "HiddenGroupName" + std::to_string(m_groupNameSuffix);
89
90
91
92
  m_groupNameSuffix++;
  return name;
}

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/* Return true if the reduction table has content. This excludes the
 * case where we have a single empty group that is usually a convenience
 * group that we've added to avoid an empty table so does not count as
 * user-entered content
 */
bool hasGroupsWithContent(ReductionJobs const &jobs) {
  if (jobs.groups().size() == 0)
    return false;

  if (jobs.containsSingleEmptyGroup())
    return false;

  return true;
}

108
109
110
111
112
113
114
115
116
117
/* This function is called after deleting groups to ensure that the model
 * always contains at least one group - it adds an empty group if
 * required. This is to mimic behaviour of the JobTreeView, which cannot delete
 * the last group/row so it always leaves at least one empty group.
 */
void ensureAtLeastOneGroupExists(ReductionJobs &jobs) {
  if (jobs.groups().size() == 0)
    appendEmptyGroup(jobs);
}

Samuel Jones's avatar
Samuel Jones committed
118
void removeGroup(ReductionJobs &jobs, int groupIndex) { jobs.removeGroup(groupIndex); }
119

120
121
void removeAllRowsAndGroups(ReductionJobs &jobs) { jobs.removeAllGroups(); }

Samuel Jones's avatar
Samuel Jones committed
122
void appendEmptyRow(ReductionJobs &jobs, int groupIndex) { jobs.mutableGroups()[groupIndex].appendEmptyRow(); }
123

Samuel Jones's avatar
Samuel Jones committed
124
void appendEmptyGroup(ReductionJobs &jobs) { jobs.appendGroup(Group(jobs.nextEmptyGroupName())); }
125

Gemma Guest's avatar
Gemma Guest committed
126
void insertEmptyGroup(ReductionJobs &jobs, int beforeGroup) {
127
  jobs.insertGroup(Group(jobs.nextEmptyGroupName()), beforeGroup);
128
129
}

Gemma Guest's avatar
Gemma Guest committed
130
void insertEmptyRow(ReductionJobs &jobs, int groupIndex, int beforeRow) {
131
  jobs.mutableGroups()[groupIndex].insertRow(boost::none, beforeRow);
132
133
}

Samuel Jones's avatar
Samuel Jones committed
134
void updateRow(ReductionJobs &jobs, int groupIndex, int rowIndex, boost::optional<Row> const &newValue) {
Gemma Guest's avatar
Gemma Guest committed
135
  if (newValue.is_initialized()) {
136
    jobs.mutableGroups()[groupIndex].updateRow(rowIndex, newValue);
Gemma Guest's avatar
Gemma Guest committed
137
  } else {
138
    jobs.mutableGroups()[groupIndex].updateRow(rowIndex, boost::none);
139
140
141
  }
}

Samuel Jones's avatar
Samuel Jones committed
142
void mergeRowIntoGroup(ReductionJobs &jobs, Row const &row, double thetaTolerance, std::string const &groupName) {
Gemma Guest's avatar
Gemma Guest committed
143
  auto &group = findOrMakeGroupWithName(jobs, groupName);
Samuel Jones's avatar
Samuel Jones committed
144
  auto indexOfRowToUpdate = group.indexOfRowWithTheta(row.theta(), thetaTolerance);
145

Gemma Guest's avatar
Gemma Guest committed
146
  if (indexOfRowToUpdate.is_initialized()) {
147
148
    auto rowToUpdate = group[indexOfRowToUpdate.get()].get();
    auto newRowValue = mergedRow(rowToUpdate, row);
149
    if (newRowValue.runNumbers() != rowToUpdate.runNumbers())
150
      group.updateRow(indexOfRowToUpdate.get(), newRowValue);
Gemma Guest's avatar
Gemma Guest committed
151
  } else {
152
    group.insertRowSortedByAngle(row);
153
  }
Gemma Guest's avatar
Gemma Guest committed
154
}
155

Gemma Guest's avatar
Gemma Guest committed
156
void removeRow(ReductionJobs &jobs, int groupIndex, int rowIndex) {
157
  jobs.mutableGroups()[groupIndex].removeRow(rowIndex);
158
159
}

Samuel Jones's avatar
Samuel Jones committed
160
bool setGroupName(ReductionJobs &jobs, int groupIndex, std::string const &newValue) {
161
  auto &group = jobs.mutableGroups()[groupIndex];
Gemma Guest's avatar
Gemma Guest committed
162
163
164
165
166
  if (group.name() != newValue) {
    if (newValue.empty() || !jobs.hasGroupWithName(newValue)) {
      group.setName(newValue);
    } else {
      return false;
167
168
    }
  }
Gemma Guest's avatar
Gemma Guest committed
169
  return true;
170
171
}

Samuel Jones's avatar
Samuel Jones committed
172
std::string groupName(ReductionJobs const &jobs, int groupIndex) { return jobs[groupIndex].name(); }
173

174
175
176
/* Return the percentage of items that have been completed */
int percentComplete(ReductionJobs const &jobs) {
  // If there's nothing to process we're 100% complete
Gemma Guest's avatar
Gemma Guest committed
177
  auto const total = countItems(jobs, &Item::totalItems);
178
179
180
  if (total == 0)
    return 100;

Gemma Guest's avatar
Gemma Guest committed
181
182
  auto const completed = countItems(jobs, &Item::completedItems);
  return static_cast<int>(completed * 100 / total);
183
184
}

Samuel Jones's avatar
Samuel Jones committed
185
Group const &ReductionJobs::operator[](int index) const { return m_groups[index]; }
186

187
188
189
190
191
192
193
MantidWidgets::Batch::RowPath ReductionJobs::getPath(Item const &item) const {
  if (item.isGroup())
    return getPath(dynamic_cast<Group const &>(item));
  else
    return getPath(dynamic_cast<Row const &>(item));
}

194
195
196
MantidWidgets::Batch::RowPath ReductionJobs::getPath(Group const &group) const {
  // Find this group in the groups list
  auto groupIter = std::find_if(m_groups.cbegin(), m_groups.cend(),
Samuel Jones's avatar
Samuel Jones committed
197
                                [&group](Group const &currentGroup) -> bool { return &currentGroup == &group; });
198
199
  // Calling this function with a group that's not in the list is an error
  if (groupIter == m_groups.cend()) {
Samuel Jones's avatar
Samuel Jones committed
200
    throw std::runtime_error(std::string("Internal error: could not find table location for group ") + group.name());
201
202
203
204
205
206
207
208
209
210
211
  }
  // Found the group so return its index as the path
  auto const groupIndex = static_cast<int>(groupIter - m_groups.cbegin());
  return {groupIndex};
}

MantidWidgets::Batch::RowPath ReductionJobs::getPath(Row const &row) const {
  auto groupIndex = 0;
  for (auto const &group : m_groups) {
    // See if the row is in this group
    auto const &rows = group.rows();
Samuel Jones's avatar
Samuel Jones committed
212
213
214
    auto rowIter = std::find_if(rows.cbegin(), rows.cend(), [&row](boost::optional<Row> const &currentRow) -> bool {
      return currentRow && &currentRow.get() == &row;
    });
215
216
217
218
219
220
221
222
223
224
225
    if (rowIter == rows.cend()) {
      // Try the next group
      ++groupIndex;
      continue;
    }

    // Found the row, so return its group and row indices as the path
    auto const rowIndex = static_cast<int>(rowIter - rows.cbegin());
    return {groupIndex, rowIndex};
  }

Samuel Jones's avatar
Samuel Jones committed
226
  throw std::runtime_error("Internal error: could not find table location for row");
227
}
228
229
230
231

Group const &ReductionJobs::getParentGroup(Row const &row) const {
  auto const path = getPath(row);
  if (path.size() < 1)
Samuel Jones's avatar
Samuel Jones committed
232
    throw std::runtime_error("Internal error: could not find parent group for row");
233
234
235
  auto const groupIndex = path[0];
  return m_groups[groupIndex];
}
236

Samuel Jones's avatar
Samuel Jones committed
237
boost::optional<Item &> ReductionJobs::getItemWithOutputWorkspaceOrNone(std::string const &wsName) {
238
  for (auto &group : m_groups) {
239
240
241
    // Return this group if it has the output we're looking for
    if (group.postprocessedWorkspaceName() == wsName)
      return group;
242
243
244
    // If it has a child row with this workspace output, return it
    auto maybeRow = group.getItemWithOutputWorkspaceOrNone(wsName);
    if (maybeRow)
245
      return boost::optional<Item &>(maybeRow.get());
246
247
248
  }
  return boost::none;
}
249

Samuel Jones's avatar
Samuel Jones committed
250
Group const &ReductionJobs::getGroupFromPath(const MantidWidgets::Batch::RowLocation &rowLocation) const {
251
  if (isGroupLocation(rowLocation)) {
252
    return groups()[groupOf(rowLocation)];
Samuel Jones's avatar
Samuel Jones committed
253
254
255
256
257
  } else {
    throw std::invalid_argument("Path given does not point to a group.");
  }
}

Samuel Jones's avatar
Samuel Jones committed
258
boost::optional<Row> const &ReductionJobs::getRowFromPath(const MantidWidgets::Batch::RowLocation &rowLocation) const {
259
260
  if (isRowLocation(rowLocation)) {
    return groups()[groupOf(rowLocation)].rows()[rowOf(rowLocation)];
Samuel Jones's avatar
Samuel Jones committed
261
262
  } else {
    throw std::invalid_argument("Path given does not point to a row.");
263
264
  }
}
265

Samuel Jones's avatar
Samuel Jones committed
266
bool ReductionJobs::validItemAtPath(const MantidWidgets::Batch::RowLocation &rowLocation) const {
Gemma Guest's avatar
Gemma Guest committed
267
268
269
270
271
272
  if (isGroupLocation(rowLocation))
    return true;

  return getRowFromPath(rowLocation).is_initialized();
}

Samuel Jones's avatar
Samuel Jones committed
273
Item const &ReductionJobs::getItemFromPath(const MantidWidgets::Batch::RowLocation &rowLocation) const {
Gemma Guest's avatar
Gemma Guest committed
274
275
276
277
278
279
280
281
282
283
  if (isGroupLocation(rowLocation)) {
    return getGroupFromPath(rowLocation);
  } else {
    auto &maybeRow = getRowFromPath(rowLocation);
    if (!maybeRow.is_initialized())
      throw std::invalid_argument("Attempted to access invalid row");
    return maybeRow.get();
  }
}

Samuel Jones's avatar
Samuel Jones committed
284
bool operator!=(ReductionJobs const &lhs, ReductionJobs const &rhs) { return !(lhs == rhs); }
285

Samuel Jones's avatar
Samuel Jones committed
286
bool operator==(ReductionJobs const &lhs, ReductionJobs const &rhs) { return lhs.groups() == rhs.groups(); }
287
} // namespace MantidQt::CustomInterfaces::ISISReflectometry