AccumulateMD.cpp 19.7 KB
Newer Older
1
2
3
4
5
6
7
8
#include "MantidMDAlgorithms/AccumulateMD.h"
#include "MantidAPI/FrameworkManager.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/ArrayBoundedValidator.h"
#include "MantidKernel/CompositeValidator.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/MandatoryValidator.h"
#include "MantidKernel/PropertyWithValue.h"
9
#include "MantidAPI/FileFinder.h"
10
#include "MantidAPI/HistoryView.h"
11
#include "MantidDataObjects/MDHistoWorkspaceIterator.h"
12
13
#include "MantidAPI/FileProperty.h"
#include "MantidKernel/EnabledWhenProperty.h"
14
#include <Poco/File.h>
15
16
17
18
19
20
21
22

using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace Mantid::DataObjects;

namespace Mantid {
namespace MDAlgorithms {

23
/*
24
25
26
27
28
29
30
31
32
33
 * Reduce the vector of input data to only data files and workspaces which can
 * be found
 * @param input_data :: Vector of input data sources
 * @param psi :: Vector of goniometer angle psi containing a value for each data
 * source
 * @param gl :: Vector of goniometer anglegl containing a value for each data
 * source
 * @param gs :: Vector of goniometer angle gs containing a value for each data
 * source
 * @param efix :: Vector of data source energy values in meV
34
 * @returns names of data sources which cannot be found
35
*/
36
37
38
39
40
41
std::string filterToExistingSources(std::vector<std::string> &input_data,
                                    std::vector<double> &psi,
                                    std::vector<double> &gl,
                                    std::vector<double> &gs,
                                    std::vector<double> &efix) {
  std::ostringstream nonexistent;
42
  for (size_t i = input_data.size(); i > 0; i--) {
43
    if (!dataExists(input_data[i - 1])) {
44
      nonexistent << input_data[i - 1] << ",";
45
46
47
48
49
      input_data.erase(input_data.begin() + i - 1);
      psi.erase(psi.begin() + i - 1);
      gl.erase(gl.begin() + i - 1);
      gs.erase(gs.begin() + i - 1);
      efix.erase(efix.begin() + i - 1);
50
51
    }
  }
52
  return nonexistent.str();
53
}
54

55
/*
56
57
58
 * Return true if dataName is an existing workspace or file
 * @param data_name :: Workspace name or file name
 * @returns true if a workspace or file with given name exists
59
*/
60
bool dataExists(const std::string &data_name) {
61
  const std::string filepath =
62
      Mantid::API::FileFinder::Instance().getFullPath(data_name);
63
  // Calls to the ADS in algorithms like this should ordinarily
64
65
  // be avoided, unfortunately we have little choice in this case.
  // If we gave FileFinder an absolute path it just returns it (whether or not
66
  // the file exists) so we must also check the full path returned with
67
  // fileExists()
68
  return (AnalysisDataService::Instance().doesExist(data_name) ||
69
          fileExists(filepath));
70
71
}

72
/*
73
 * Test if a file with this full path exists
74
 * @param filename :: full path of a file to test existence of
75
 * @returns true if the file exists
76
77
*/
bool fileExists(const std::string &filename) {
78
79
  if (filename.empty())
    return false;
80
  Poco::File test_file(filename);
81
  return test_file.exists();
82
83
}

84
/*
85
86
87
88
89
90
91
92
93
94
95
 * Remove anything from input_data which is already in current_data
 * @param input_data :: Vector of input data sources
 * @param current_data :: Vector of data sources previously appended to
 * workspace
 * @param psi :: Vector of goniometer angle psi containing a value for each data
 * source
 * @param gl :: Vector of goniometer anglegl containing a value for each data
 * source
 * @param gs :: Vector of goniometer angle gs containing a value for each data
 * source
 * @param efix :: Vector of data source energy values in meV
96
 * @returns data sources which are already in the workspace
97
*/
98
99
100
101
102
std::string filterToNew(std::vector<std::string> &input_data,
                        std::vector<std::string> &current_data,
                        std::vector<double> &psi, std::vector<double> &gl,
                        std::vector<double> &gs, std::vector<double> &efix) {
  std::ostringstream old_sources;
103
  for (size_t i = input_data.size(); i > 0; i--) {
104
    if (appearsInCurrentData(input_data[i - 1], current_data)) {
105
      old_sources << input_data[i - 1] << ",";
106
107
108
109
110
111
112
      input_data.erase(input_data.begin() + i - 1);
      psi.erase(psi.begin() + i - 1);
      gl.erase(gl.begin() + i - 1);
      gs.erase(gs.begin() + i - 1);
      efix.erase(efix.begin() + i - 1);
    }
  }
113
  return old_sources.str();
114
115
}

116
117
118
119
120
121
122
123
/*
 * Check if the named data source is in the vector of data currently in the
 * workspace
 * @param data_source :: Name of a data source
 * @param current_data :: Vector of data sources previously appended to
 * workspace
 * @returns true if the named data source appears in the vector of current data
*/
124
125
bool appearsInCurrentData(const std::string &data_source,
                          std::vector<std::string> &current_data) {
126
127
128
  for (auto reverse_iter = current_data.rbegin();
       reverse_iter != current_data.rend(); ++reverse_iter) {
    if (data_source == *reverse_iter) {
129
130
131
132
      return true;
    }
  }
  return false;
133
134
}

135
/*
136
137
138
139
140
 * Return a vector of the names of files and workspaces which have been
 * previously added to the workspace
 * @param ws_history :: History of the workspace
 * @returns a vector of the names of data_sources which have previously been
 * appended to the workspace
141
*/
142
std::vector<std::string>
143
144
145
getHistoricalDataSources(const WorkspaceHistory &ws_history,
                         const std::string &create_alg_name,
                         const std::string &accumulate_alg_name) {
146
  // Using a set so we only insert unique names
147
  std::unordered_set<std::string> historical_data_sources;
148

149
150
  // Get previously added data sources from DataSources property of the original
  // call of CreateMD and any subsequent calls of AccumulateMD
151
152
  auto view = ws_history.createView();
  view->unrollAll();
153
  const std::vector<HistoryItem> history_items = view->getAlgorithmsList();
154
155
  for (const auto &history_item : history_items) {
    auto alg_history = history_item.getAlgorithmHistory();
156
157
    if (alg_history->name() == create_alg_name ||
        alg_history->name() == accumulate_alg_name) {
158
      auto props = alg_history->getProperties();
159
160
      for (auto &prop : props) {
        PropertyHistory_const_sptr prop_history = prop;
161
162
        if (prop_history->name() == "DataSources") {
          insertDataSources(prop_history->value(), historical_data_sources);
163
164
165
166
        }
      }
    }
  }
167

168
169
  std::vector<std::string> result(historical_data_sources.begin(),
                                  historical_data_sources.end());
170
  return result;
171
172
}

173
/*
174
175
176
177
178
 * Split string of data sources from workspace history and insert them into
 * complete set of historical data sources
 * @param data_sources :: string from workspace history containing list of data
 * sources
 * @param historical_data_sources :: set of data sources
179
*/
180
181
182
void insertDataSources(
    const std::string &data_sources,
    std::unordered_set<std::string> &historical_data_sources) {
183
184
  // Split the property string into a vector of data sources
  std::vector<std::string> data_split;
185
  boost::split(data_split, data_sources, boost::is_any_of(","));
186
187
188
189
190
191
192

  // Trim any whitespace from ends of each data source string
  std::for_each(
      data_split.begin(), data_split.end(),
      boost::bind(boost::algorithm::trim<std::string>, _1, std::locale()));

  // Insert each data source into our complete set of historical data sources
Hahn, Steven's avatar
Hahn, Steven committed
193
  historical_data_sources.insert(data_split.begin(), data_split.end());
194
195
}

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(AccumulateMD)

/// Algorithms name for identification. @see Algorithm::name
const std::string AccumulateMD::name() const { return "AccumulateMD"; }

/// Algorithm's version for identification. @see Algorithm::version
int AccumulateMD::version() const { return 1; }

/// Algorithm's category for identification. @see Algorithm::category
const std::string AccumulateMD::category() const { return "MDAlgorithms"; }

/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
const std::string AccumulateMD::summary() const {
  return "Add new data to an existing MDHistoWorkspace";
}

/*
214
215
 * Initialize the algorithm's properties.
*/
216
void AccumulateMD::init() {
217
218
  declareProperty(make_unique<WorkspaceProperty<IMDEventWorkspace>>(
                      "InputWorkspace", "", Direction::Input),
219
                  "An input MDEventWorkspace to append data to.");
220

221
  declareProperty(make_unique<WorkspaceProperty<IMDEventWorkspace>>(
222
                      "OutputWorkspace", "", Direction::Output),
223
                  "MDEventWorkspace with new data appended.");
224
225

  declareProperty(
226
      Kernel::make_unique<ArrayProperty<std::string>>(
227
228
          "DataSources",
          boost::make_shared<MandatoryValidator<std::vector<std::string>>>(),
Matthew D Jones's avatar
Matthew D Jones committed
229
          Direction::Input),
230
231
      "Input workspaces to process, or filenames to load and process");

232
  declareProperty(make_unique<ArrayProperty<double>>("EFix", Direction::Input),
233
234
                  "datasource energy values in meV");

235
  std::vector<std::string> e_mode_options{"Elastic", "Direct", "Indirect"};
236

Matthew D Jones's avatar
Matthew D Jones committed
237
238
  declareProperty("Emode", "Direct",
                  boost::make_shared<StringListValidator>(e_mode_options),
239
                  "Analysis mode ['Elastic', 'Direct', 'Indirect'].");
Matthew D Jones's avatar
Matthew D Jones committed
240

241
  declareProperty(
242
      Kernel::make_unique<ArrayProperty<double>>(
243
244
245
246
247
248
          "Alatt",
          boost::make_shared<MandatoryValidator<std::vector<double>>>(),
          Direction::Input),
      "Lattice parameters");

  declareProperty(
249
      Kernel::make_unique<ArrayProperty<double>>(
250
251
252
253
254
255
          "Angdeg",
          boost::make_shared<MandatoryValidator<std::vector<double>>>(),
          Direction::Input),
      "Lattice angles");

  declareProperty(
256
      Kernel::make_unique<ArrayProperty<double>>(
257
258
259
          "u", boost::make_shared<MandatoryValidator<std::vector<double>>>(),
          Direction::Input),
      "Lattice vector parallel to neutron beam");
260
261

  declareProperty(
262
      Kernel::make_unique<ArrayProperty<double>>(
Matthew D Jones's avatar
Matthew D Jones committed
263
264
          "v", boost::make_shared<MandatoryValidator<std::vector<double>>>(),
          Direction::Input),
265
266
      "Lattice vector perpendicular to neutron beam in the horizontal plane");

267
  declareProperty(make_unique<ArrayProperty<double>>("Psi", Direction::Input),
268
269
                  "Psi rotation in degrees. Optional or one entry per run.");

270
  declareProperty(make_unique<ArrayProperty<double>>("Gl", Direction::Input),
271
272
                  "gl rotation in degrees. Optional or one entry per run.");

273
  declareProperty(make_unique<ArrayProperty<double>>("Gs", Direction::Input),
274
275
                  "gs rotation in degrees. Optional or one entry per run.");

276
  declareProperty(
277
      make_unique<PropertyWithValue<bool>>("InPlace", true, Direction::Input),
278
279
280
      "Execute conversions to MD and Merge in one-step. Less "
      "memory overhead.");

281
282
283
284
  declareProperty(
      make_unique<PropertyWithValue<bool>>("Clean", false, Direction::Input),
      "Create workspace from fresh rather than appending to "
      "existing workspace data.");
285
286
287
288
289
290
291
292
293
294
295
296
297
298

  declareProperty(
      make_unique<FileProperty>("Filename", "", FileProperty::OptionalSave,
                                ".nxs"),
      "The name of the Nexus file to write, as a full or relative path.\n"
      "Only used if FileBackEnd is true.");
  setPropertySettings("Filename", make_unique<EnabledWhenProperty>(
                                      "CreateFileBackEnd", IS_EQUAL_TO, "1"));

  declareProperty("FileBackEnd", false,
                  "If true, Filename must also be specified. The algorithm "
                  "will create the specified file in addition to an output "
                  "workspace. The workspace will load data from the file on "
                  "demand in order to reduce memory use.");
299
300
301
}

/*
302
303
 * Execute the algorithm.
*/
304
305
void AccumulateMD::exec() {

306
  IMDEventWorkspace_sptr input_ws = this->getProperty("InputWorkspace");
307
308
  std::vector<std::string> input_data = this->getProperty("DataSources");

309
310
311
  const std::string out_filename = this->getProperty("Filename");
  const bool filebackend = this->getProperty("FileBackEnd");

312
  std::vector<double> psi = this->getProperty("Psi");
313
  padParameterVector(psi, input_data.size());
314
  std::vector<double> gl = this->getProperty("Gl");
315
  padParameterVector(gl, input_data.size());
316
  std::vector<double> gs = this->getProperty("Gs");
317
  padParameterVector(gs, input_data.size());
318
  std::vector<double> efix = this->getProperty("EFix");
319
  padParameterVector(efix, input_data.size());
320

321
  // Create progress reporting object
Matthew D Jones's avatar
Matthew D Jones committed
322
  // Progress prog = Progress(this, 0.0, 1.0, 2);
323
324
325
326
327
328
  this->progress(0);

  const std::string nonexistent =
      filterToExistingSources(input_data, psi, gl, gs, efix);
  g_log.notice() << "These data sources were not found: " << nonexistent
                 << std::endl;
329
330
331
332
333

  // If we can't find any data, we can't do anything
  if (input_data.empty()) {
    g_log.warning() << "No data found matching input in " << this->name()
                    << std::endl;
334
    this->setProperty("OutputWorkspace", input_ws);
335
336
    return; // POSSIBLE EXIT POINT
  }
337
  this->interruption_point();
338
339
340
341
342

  // If Clean=True then just call CreateMD to create a fresh workspace and
  // delete the old one, note this means we don't retain workspace history...
  bool do_clean = this->getProperty("Clean");
  if (do_clean) {
343
    this->progress(0.5);
344
345
    IMDEventWorkspace_sptr out_ws = createMDWorkspace(
        input_data, psi, gl, gs, efix, out_filename, filebackend);
346
    this->setProperty("OutputWorkspace", out_ws);
347
    g_log.notice() << this->name() << " successfully created a clean workspace"
348
                   << std::endl;
349
    this->progress(1.0);
350
351
    return; // POSSIBLE EXIT POINT
  }
352
  this->interruption_point();
353

354
  // Find what files and workspaces have already been included in the workspace.
355
  const WorkspaceHistory ws_history = input_ws->getHistory();
356
357
358
359
360
  // Get name from algorithm like this so that an error is thrown if the
  // name of the algorithm is changed
  Algorithm_sptr create_alg = createChildAlgorithm("CreateMD");
  std::vector<std::string> current_data =
      getHistoricalDataSources(ws_history, create_alg->name(), this->name());
361

362
  // If there's no new data, we don't have anything to do
363
364
365
366
367
  const std::string old_sources =
      filterToNew(input_data, current_data, psi, gl, gs, efix);
  g_log.notice() << "Data from these sources are already in the workspace: "
                 << old_sources << std::endl;

368
  if (input_data.empty()) {
369
370
371
    g_log.notice() << "No new data to append to workspace in " << this->name()
                   << std::endl;
    this->setProperty("OutputWorkspace", input_ws);
372
373
    return; // POSSIBLE EXIT POINT
  }
374
375
376
  this->interruption_point();

  // If we reach here then new data exists to append to the input workspace
377
378
  // Use CreateMD with the new data to make a temp workspace
  // Merge the temp workspace with the input workspace using MergeMD
379
  IMDEventWorkspace_sptr tmp_ws =
380
      createMDWorkspace(input_data, psi, gl, gs, efix, "", false);
381
  this->interruption_point();
382
  this->progress(0.5); // Report as CreateMD is complete
383

384
  const std::string temp_ws_name = "TEMP_WORKSPACE_ACCUMULATEMD";
385
  // Currently have to use ADS here as list of workspaces can only be passed as
386
  // a list of workspace names as a string
387
388
389
390
  AnalysisDataService::Instance().add(temp_ws_name, tmp_ws);
  std::string ws_names_to_merge = input_ws->getName();
  ws_names_to_merge.append(",");
  ws_names_to_merge.append(temp_ws_name);
391
392

  Algorithm_sptr merge_alg = createChildAlgorithm("MergeMD");
393
  merge_alg->setProperty("InputWorkspaces", ws_names_to_merge);
394
  merge_alg->executeAsChildAlg();
395
396
397

  API::IMDEventWorkspace_sptr out_ws =
      merge_alg->getProperty("OutputWorkspace");
398

399
  this->setProperty("OutputWorkspace", out_ws);
400
401
  g_log.notice() << this->name() << " successfully appended data" << std::endl;

402
403
  this->progress(1.0); // Report as MergeMD is complete

404
405
  // Clean up temporary workspace
  AnalysisDataService::Instance().remove(temp_ws_name);
406
407
408
}

/*
409
410
411
412
413
414
415
416
417
418
419
 * Use the CreateMD algorithm to create an MD workspace
 * @param data_sources :: Vector of input data sources
 * @param psi :: Vector of goniometer angle psi containing a value for each data
 * source
 * @param gl :: Vector of goniometer anglegl containing a value for each data
 * source
 * @param gs :: Vector of goniometer angle gs containing a value for each data
 * source
 * @param efix :: Vector of data source energy values in meV
 * @returns the newly created workspace
*/
420
IMDEventWorkspace_sptr AccumulateMD::createMDWorkspace(
421
422
    const std::vector<std::string> &data_sources,
    const std::vector<double> &psi, const std::vector<double> &gl,
423
424
    const std::vector<double> &gs, const std::vector<double> &efix,
    const std::string &filename, const bool filebackend) {
425
426
427
428
429

  Algorithm_sptr create_alg = createChildAlgorithm("CreateMD");

  create_alg->setProperty("DataSources", data_sources);
  create_alg->setProperty("EFix", efix);
430
431
432
433
434
  create_alg->setPropertyValue("EMode", this->getPropertyValue("EMode"));
  create_alg->setPropertyValue("Alatt", this->getPropertyValue("Alatt"));
  create_alg->setPropertyValue("Angdeg", this->getPropertyValue("Angdeg"));
  create_alg->setPropertyValue("u", this->getPropertyValue("u"));
  create_alg->setPropertyValue("v", this->getPropertyValue("v"));
435
436
437
  create_alg->setProperty("Psi", psi);
  create_alg->setProperty("Gl", gl);
  create_alg->setProperty("Gs", gs);
438
  create_alg->setPropertyValue("InPlace", this->getPropertyValue("InPlace"));
439
440
441
442
  if (filebackend) {
    create_alg->setProperty("Filename", filename);
    create_alg->setProperty("FileBackEnd", filebackend);
  }
443
444
445
  create_alg->executeAsChildAlg();

  return create_alg->getProperty("OutputWorkspace");
446
}
447

448
449
450
451
/*
 * Validate the input properties
 * @returns a map of properties names with errors
 */
452
453
454
455
std::map<std::string, std::string> AccumulateMD::validateInputs() {
  // Create the map
  std::map<std::string, std::string> validation_output;

456
  // Get properties to validate
457
  const std::vector<std::string> data_sources =
458
      this->getProperty("DataSources");
459
460
461
462
463
464
465
466
  const std::vector<double> u = this->getProperty("u");
  const std::vector<double> v = this->getProperty("v");
  const std::vector<double> alatt = this->getProperty("Alatt");
  const std::vector<double> angdeg = this->getProperty("Angdeg");
  const std::vector<double> psi = this->getProperty("Psi");
  const std::vector<double> gl = this->getProperty("Gl");
  const std::vector<double> gs = this->getProperty("Gs");
  const std::vector<double> efix = this->getProperty("Efix");
467
468
469
470
471
472
473
  const std::string filename = this->getProperty("Filename");
  const bool fileBackEnd = this->getProperty("FileBackEnd");

  if (fileBackEnd && filename.empty()) {
    validation_output["Filename"] =
        "Filename must be given if FileBackEnd is required.";
  }
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490

  const size_t ws_entries = data_sources.size();

  if (u.size() < 3) {
    validation_output["u"] = "u must have 3 components";
  }
  if (v.size() < 3) {
    validation_output["v"] = "v must have 3 components";
  }
  if (alatt.size() < 3) {
    validation_output["Alatt"] = "Lattice parameters must have 3 components";
  }
  if (angdeg.size() < 3) {
    validation_output["Angdeg"] = "Angle must have 3 components";
  }
  if (!psi.empty() && psi.size() != ws_entries) {
    validation_output["Psi"] = "If Psi is given an entry "
491
492
                               "should be provided for "
                               "every input datasource";
493
494
495
  }
  if (!gl.empty() && gl.size() != ws_entries) {
    validation_output["Gl"] = "If Gl is given an entry "
496
497
                              "should be provided for "
                              "every input datasource";
498
499
500
  }
  if (!gs.empty() && gs.size() != ws_entries) {
    validation_output["Gs"] = "If Gs is given an entry "
501
502
                              "should be provided for "
                              "every input datasource";
503
504
505
  }
  if (efix.size() > 1 && efix.size() != ws_entries) {
    validation_output["EFix"] =
506
        "Either specify a single EFix value, or as many "
507
508
509
510
511
512
        "as there are input datasources";
  }

  return validation_output;
}

513
514
} // namespace MDAlgorithms
} // namespace Mantid