MultiPeriodGroupWorker.cpp 12.2 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 "MantidAPI/MultiPeriodGroupWorker.h"
8
#include "MantidAPI/AlgorithmManager.h"
LamarMoore's avatar
LamarMoore committed
9
10
#include "MantidAPI/IAlgorithm.h"
#include "MantidAPI/MatrixWorkspace.h"
11
#include "MantidAPI/Run.h"
12
#include "MantidAPI/WorkspaceGroup.h"
LamarMoore's avatar
LamarMoore committed
13
14
15
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/Property.h"
#include "MantidKernel/Strings.h"
16
17

using namespace Mantid::Kernel;
18

19
20
namespace Mantid {
namespace API {
21

22
23
24
25
26
27
28
29
/**
 * Constructor
 * @param workspacePropertyName : Property name to treat as source of
 * multiperiod workspaces.
 */
MultiPeriodGroupWorker::MultiPeriodGroupWorker(
    const std::string &workspacePropertyName)
    : m_workspacePropertyName(workspacePropertyName) {}
30

31
32
33
/**
 * Try to add the input workspace to the multiperiod input group list.
 * @param ws: candidate workspace
Ian Bush's avatar
Ian Bush committed
34
35
36
 * @param vecMultiPeriodWorkspaceGroups: Vector of multi period workspace
 * groups.
 * @param vecWorkspaceGroups: Vector of non-multi period workspace groups.
37
38
 */
void MultiPeriodGroupWorker::tryAddInputWorkspaceToInputGroups(
David Fairbrother's avatar
David Fairbrother committed
39
    const Workspace_sptr &ws,
40
    MultiPeriodGroupWorker::VecWSGroupType &vecMultiPeriodWorkspaceGroups,
41
42
43
44
45
    MultiPeriodGroupWorker::VecWSGroupType &vecWorkspaceGroups) const {
  WorkspaceGroup_sptr inputGroup =
      boost::dynamic_pointer_cast<WorkspaceGroup>(ws);
  if (inputGroup) {
    if (inputGroup->isMultiperiod()) {
46
      vecMultiPeriodWorkspaceGroups.emplace_back(inputGroup);
47
    } else {
48
      vecWorkspaceGroups.emplace_back(inputGroup);
49
    }
50
51
  }
}
52

53
54
55
56
57
58
MultiPeriodGroupWorker::VecWSGroupType
MultiPeriodGroupWorker::findMultiPeriodGroups(
    Algorithm const *const sourceAlg) const {
  if (!sourceAlg->isInitialized()) {
    throw std::invalid_argument("Algorithm must be initialized");
  }
59
  VecWSGroupType vecMultiPeriodWorkspaceGroups;
60
  VecWSGroupType vecWorkspaceGroups;
61

62
63
64
65
66
  // Handles the case in which the algorithm is providing a non-workspace
  // property as an input.
  // This is currenly the case for algorithms that take an array of strings as
  // an input where each entry is the name of a workspace.
  if (this->useCustomWorkspaceProperty()) {
67
    using WorkspaceNameType = std::vector<std::string>;
68

69
70
71
    // Perform a check that the input property is the correct type.
    Property *inputProperty =
        sourceAlg->getProperty(this->m_workspacePropertyName);
72

73
74
75
76
77
78
79
    if (!dynamic_cast<ArrayProperty<std::string> *>(inputProperty)) {
      throw std::runtime_error("Support for custom input workspaces that are "
                               "not string Arrays are not currently "
                               "supported.");
      /*Note that we could extend this algorithm to cover other input property
       * types if required, but we don't need that funtionality now.*/
    }
80

81
82
    WorkspaceNameType workspaces =
        sourceAlg->getProperty(this->m_workspacePropertyName);
83

84
    // Inspect all the input workspaces in the ArrayProperty input.
85
86
    for (auto &workspace : workspaces) {
      Workspace_sptr ws = AnalysisDataService::Instance().retrieve(workspace);
87
      if (!ws) {
88
        throw Kernel::Exception::NotFoundError("Workspace", workspace);
89
      }
Ian Bush's avatar
Ian Bush committed
90
91
      tryAddInputWorkspaceToInputGroups(ws, vecMultiPeriodWorkspaceGroups,
                                        vecWorkspaceGroups);
92
93
    }
  } else {
94
    using WorkspaceVector = std::vector<boost::shared_ptr<Workspace>>;
95
    WorkspaceVector inWorkspaces;
96
    sourceAlg->findWorkspaces(inWorkspaces, Direction::Input);
97
    for (auto &inWorkspace : inWorkspaces) {
Ian Bush's avatar
Ian Bush committed
98
99
      tryAddInputWorkspaceToInputGroups(
          inWorkspace, vecMultiPeriodWorkspaceGroups, vecWorkspaceGroups);
100
101
    }
  }
102

Hahn, Steven's avatar
Hahn, Steven committed
103
  if (!vecMultiPeriodWorkspaceGroups.empty() && !vecWorkspaceGroups.empty()) {
Ian Bush's avatar
Ian Bush committed
104
105
    throw std::invalid_argument(
        "The input contains a mix of multi-period and other workspaces.");
106
107
108
  }

  validateMultiPeriodGroupInputs(vecMultiPeriodWorkspaceGroups);
109

110
  return vecMultiPeriodWorkspaceGroups;
111
}
112

113
114
115
bool MultiPeriodGroupWorker::useCustomWorkspaceProperty() const {
  return !this->m_workspacePropertyName.empty();
}
116

117
118
/**
 * Creates a list of input workspaces as a string for a given period using all
119
120
 * nested workspaces at that period within all group workspaces.
 *
121
 * This requires a little explanation, because this is the reason that this
122
 * algorithm needs a customised overriden checkGroups and processGroups
123
 * method:
124
 *
125
 * Say you have two multiperiod group workspaces A and B and an output workspace
126
127
128
129
130
131
132
 * C. A contains matrix workspaces A_1 and A_2, and B contains matrix workspaces
 * B_1 and B2. Because this is multiperiod data. A_1 and B_1 share the same
 * period, as do A_2 and B_2. So merging must be with respect to workspaces of
 * equivalent periods. Therefore, merging must be A_1 + B_1 = C_1 and
 * A_2 + B_2 = C_2. This method constructs the inputs for a nested call to
 * MultiPeriodGroupAlgorithm in this manner.
 *
133
134
135
136
137
138
 * @param periodIndex : zero based index denoting the period.
 * @param vecWorkspaceGroups : Vector of workspace groups
 * @return comma separated string of input workspaces.
 */
std::string MultiPeriodGroupWorker::createFormattedInputWorkspaceNames(
    const size_t &periodIndex, const VecWSGroupType &vecWorkspaceGroups) const {
139
140
  std::string prefix;
  std::string inputWorkspaces;
141
  for (const auto &vecWorkspaceGroup : vecWorkspaceGroups) {
142
143
    inputWorkspaces +=
        prefix + vecWorkspaceGroup->getItem(periodIndex)->getName();
144
145
146
147
    prefix = ",";
  }
  return inputWorkspaces;
}
148

149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/**
 * 1) Looks for input workspace properties that are of type WorkspaceGroup.
 * 2) If a multiperiod workspace has been set to that property then ..
 * 3) Extracts the individual period workspace from that WorkspaceGroup
 * 4) Manually sets that individual period workspace as the corresponding
 * property on the targetAlgorithm.
 * Copy input workspaces assuming we are working with multi-period groups
 * workspace inputs.
 * @param targetAlg: The spawned algorithm to set the properties on.
 * @param sourceAlg: Algorithm being executed with multiperiod group workspaces.
 * @param periodNumber: The relevant period number used to index into the group
 * workspaces
 */
void MultiPeriodGroupWorker::copyInputWorkspaceProperties(
    IAlgorithm *targetAlg, IAlgorithm *sourceAlg,
    const int &periodNumber) const {
  std::vector<Property *> props = sourceAlg->getProperties();
166
  for (auto prop : props) {
167
168
169
170
171
172
173
174
175
176
    if (prop) {
      if (prop->direction() == Direction::Input) {
        if (const IWorkspaceProperty *wsProp =
                dynamic_cast<IWorkspaceProperty *>(prop)) {
          if (WorkspaceGroup_sptr inputws =
                  boost::dynamic_pointer_cast<WorkspaceGroup>(
                      wsProp->getWorkspace())) {
            if (inputws->isMultiperiod()) {
              targetAlg->setProperty(prop->name(),
                                     inputws->getItem(periodNumber - 1));
177
178
179
180
181
            }
          }
        }
      }
    }
182
183
  }
}
184

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//--------------------------------------------------------------------------------------------
/** Process WorkspaceGroup inputs.
 *
 * Overriden from Algorithm base class.
 *
 * This should be called after checkGroups(), which sets up required members.
 * It goes through each member of the group(s), creates and sets an algorithm
 * for each and executes them one by one.
 *
 * If there are several group input workspaces, then the member of each group
 * is executed pair-wise.
 *
 * @param sourceAlg : Source algorithm
 * @param vecMultiPeriodGroups : Vector of pre-identified multiperiod groups.
 * @return true - if all the workspace members are executed.
 */
bool MultiPeriodGroupWorker::processGroups(
    Algorithm *const sourceAlg,
    const VecWSGroupType &vecMultiPeriodGroups) const {
  // If we are not processing multiperiod groups, use the base behaviour.
205
  if (vecMultiPeriodGroups.empty()) {
206
207
208
209
    return false; // Indicates that this is not a multiperiod group workspace.
  }
  Property *outputWorkspaceProperty = sourceAlg->getProperty("OutputWorkspace");
  const std::string outName = outputWorkspaceProperty->value();
210

211
  const size_t nPeriods = vecMultiPeriodGroups[0]->size();
212
213
  WorkspaceGroup_sptr outputWS = boost::make_shared<WorkspaceGroup>();
  AnalysisDataService::Instance().addOrReplace(outName, outputWS);
214

215
  double progress_proportion = 1.0 / static_cast<double>(nPeriods);
216
217
218
  // Loop through all the periods. Create spawned algorithms of the same type as
  // this to process pairs from the input groups.
  for (size_t i = 0; i < nPeriods; ++i) {
219
    const auto periodNumber = static_cast<int>(i + 1);
220
    // use create Child Algorithm that look like this one
221
    Algorithm_sptr alg = sourceAlg->createChildAlgorithm(
Nick Draper's avatar
Nick Draper committed
222
223
224
        sourceAlg->name(), progress_proportion * periodNumber,
        progress_proportion * (1 + periodNumber), sourceAlg->isLogging(),
        sourceAlg->version());
225
226
227
    if (!alg) {
      throw std::runtime_error("Algorithm creation failed.");
    }
228
229
230
231
    // Don't make the new algorithm a child so that it's workspaces are stored
    // correctly
    alg->setChild(false);
    alg->setRethrows(true);
232
233
    alg->initialize();
    // Copy properties that aren't workspaces properties.
234
    sourceAlg->copyNonWorkspaceProperties(alg.get(), periodNumber);
235

236
237
238
239
240
241
242
    if (this->useCustomWorkspaceProperty()) {
      const std::string inputWorkspaces =
          createFormattedInputWorkspaceNames(i, vecMultiPeriodGroups);
      // Set the input workspace property.
      alg->setPropertyValue(this->m_workspacePropertyName, inputWorkspaces);
    } else {
      // Configure input properties that are group workspaces.
243
      copyInputWorkspaceProperties(alg.get(), sourceAlg, periodNumber);
244
245
246
247
248
249
250
251
252
253
254
255
    }
    const std::string outName_i = outName + "_" + Strings::toString(i + 1);
    alg->setPropertyValue("OutputWorkspace", outName_i);
    // Run the spawned algorithm.
    if (!alg->execute()) {
      throw std::runtime_error("Execution of " + sourceAlg->name() +
                               " for group entry " + Strings::toString(i + 1) +
                               " failed.");
    }
    // Add the output workpace from the spawned algorithm to the group.
    outputWS->add(outName_i);
  }
256

257
  sourceAlg->setProperty("OutputWorkspace", outputWS);
258

259
260
  return true;
}
261

262
263
264
265
266
267
268
269
/**
 * Validate the multiperiods workspace groups. Gives the opportunity to exit
 * processing if things don't look right.
 * @param vecMultiPeriodGroups : vector of multiperiod groups.
 */
void MultiPeriodGroupWorker::validateMultiPeriodGroupInputs(
    const VecWSGroupType &vecMultiPeriodGroups) const {
  const size_t multiPeriodGroupsSize = vecMultiPeriodGroups.size();
270

271
272
273
274
275
276
277
278
279
280
281
282
283
284
  if (multiPeriodGroupsSize > 0) {
    const size_t benchMarkGroupSize = vecMultiPeriodGroups[0]->size();
    for (size_t i = 0; i < multiPeriodGroupsSize; ++i) {
      WorkspaceGroup_sptr currentGroup = vecMultiPeriodGroups[i];
      if (currentGroup->size() != benchMarkGroupSize) {
        throw std::runtime_error("Not all the input Multi-period-group input "
                                 "workspaces are the same size.");
      }
      for (size_t j = 0; j < currentGroup->size(); ++j) {
        MatrixWorkspace_const_sptr currentNestedWS =
            boost::dynamic_pointer_cast<const MatrixWorkspace>(
                currentGroup->getItem(j));
        Property *nPeriodsProperty =
            currentNestedWS->run().getLogData("nperiods");
Peterson, Peter's avatar
Peterson, Peter committed
285
        size_t nPeriods = std::stoul(nPeriodsProperty->value());
286
287
288
        if (nPeriods != benchMarkGroupSize) {
          throw std::runtime_error("Missmatch between nperiods log and the "
                                   "number of workspaces in the input group: " +
289
                                   vecMultiPeriodGroups[i]->getName());
290
291
292
        }
        Property *currentPeriodProperty =
            currentNestedWS->run().getLogData("current_period");
Peterson, Peter's avatar
Peterson, Peter committed
293
        size_t currentPeriod = std::stoul(currentPeriodProperty->value());
294
295
296
        if (currentPeriod != (j + 1)) {
          throw std::runtime_error("Multiperiod group workspaces must be "
                                   "ordered by current_period. Correct: " +
297
                                   currentNestedWS->getName());
298
299
300
        }
      }
    }
301
302
  }
}
303

304
} // namespace API
305
} // namespace Mantid