Mantid
Loading...
Searching...
No Matches
SlicingAlgorithm.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8#include "MantidAPI/Run.h"
17#include "MantidKernel/Memory.h"
20
21#include <boost/regex.hpp>
22#include <utility>
23
24using namespace Mantid::Kernel;
25using namespace Mantid::API;
26using namespace Mantid::Geometry;
28
29namespace Mantid::MDAlgorithms {
30
34 : m_transform(), m_transformFromOriginal(), m_transformToOriginal(), m_transformFromIntermediate(),
35 m_transformToIntermediate(), m_axisAligned(true), m_outD(0), // unititialized and should be invalid
36 m_NormalizeBasisVectors(false) {}
37
41 std::string dimChars = getDimensionChars();
42
43 // --------------- Axis-aligned properties
44 // ---------------------------------------
45 declareProperty("AxisAligned", true, "Perform binning aligned with the axes of the input MDEventWorkspace?");
46 setPropertyGroup("AxisAligned", "Axis-Aligned Binning");
47 for (size_t i = 0; i < dimChars.size(); i++) {
48 std::string dim(" ");
49 dim[0] = dimChars[i];
50 std::string propName = "AlignedDim" + dim;
52 "Binning parameters for the " + Strings::toString(i) +
53 "th dimension.\n"
54 "Enter it as a comma-separated list of values with the format: "
55 "'name,minimum,maximum,number_of_bins'. Leave blank for NONE.");
56 setPropertySettings(propName, std::make_unique<VisibleWhenProperty>("AxisAligned", IS_EQUAL_TO, "1"));
57 setPropertyGroup(propName, "Axis-Aligned Binning");
58 }
59
60 // --------------- NON-Axis-aligned properties
61 // ---------------------------------------
62 std::string grpName = "Non-Aligned Binning";
63
64 auto ps = [] {
65 std::unique_ptr<IPropertySettings> settings =
66 std::make_unique<VisibleWhenProperty>("AxisAligned", IS_EQUAL_TO, "0");
67 return settings;
68 };
69 for (size_t i = 0; i < dimChars.size(); i++) {
70 std::string dim(" ");
71 dim[0] = dimChars[i];
72 std::string propName = "BasisVector" + dim;
74 "Description of the basis vector of the " + Strings::toString(i) +
75 "th output dimension."
76 "Format: 'name, units, x,y,z,..'.\n"
77 " name : string for the name of the output dimension.\n"
78 " units : string for the units of the output dimension.\n"
79 " x,y,z,...: vector definining the basis in the input dimensions "
80 "space.\n"
81 "Leave blank for NONE.");
82 setPropertySettings(propName, ps());
83 setPropertyGroup(propName, grpName);
84 }
85 declareProperty(std::make_unique<ArrayProperty<double>>("Translation", Direction::Input),
86 "Coordinates in the INPUT workspace that corresponds to "
87 "(0,0,0) in the OUTPUT workspace.\n"
88 "Enter as a comma-separated string.\n"
89 "Default: 0 in all dimensions (no translation).");
90
91 declareProperty(std::make_unique<ArrayProperty<double>>("OutputExtents", Direction::Input),
92 "The minimum, maximum edges of space of each dimension of "
93 "the OUTPUT workspace, as a comma-separated list");
94
95 declareProperty(std::make_unique<ArrayProperty<int>>("OutputBins", Direction::Input),
96 "The number of bins for each dimension of the OUTPUT workspace.");
97
98 declareProperty(std::make_unique<PropertyWithValue<bool>>("NormalizeBasisVectors", true, Direction::Input),
99 "Normalize the given basis vectors to unity. \n"
100 "If true, then a distance of 1 in the INPUT dimensions = 1 "
101 "in the OUTPUT dimensions.\n"
102 "If false, then a distance of norm(basis_vector) in the "
103 "INPUT dimension = 1 in the OUTPUT dimensions.");
104
105 declareProperty(std::make_unique<PropertyWithValue<bool>>("ForceOrthogonal", false, Direction::Input),
106 "Force the input basis vectors to form an orthogonal coordinate system. "
107 "Only works in 3 dimension!");
108
109 // For GUI niceness
110 setPropertyGroup("Translation", grpName);
111 setPropertyGroup("OutputExtents", grpName);
112 setPropertyGroup("OutputBins", grpName);
113 setPropertyGroup("NormalizeBasisVectors", grpName);
114 setPropertyGroup("ForceOrthogonal", grpName);
115 setPropertySettings("Translation", ps());
116 setPropertySettings("OutputExtents", ps());
117 setPropertySettings("OutputBins", ps());
118 setPropertySettings("NormalizeBasisVectors", ps());
119 setPropertySettings("ForceOrthogonal", ps());
120}
121
122//----------------------------------------------------------------------------------------------
136 std::string input = Strings::strip(str);
137 if (input.empty())
138 return;
139 if (input.size() < 3)
140 throw std::invalid_argument("Dimension string is too short to be valid: " + str);
141
142 // The current dimension index.
143 size_t dim = m_binDimensions.size();
144
145 size_t n_first_comma;
146
147 // Special case: accept dimension names [x,y,z]
148 if (input[0] == '[') {
149 // Find the name at the closing []
150 size_t n = input.find_first_of(']', 1);
151 if (n == std::string::npos)
152 throw std::invalid_argument("No closing ] character in the dimension name of : " + str);
153 // Find the comma after the name
154 n_first_comma = input.find_first_of(',', n);
155 if (n_first_comma == std::string::npos)
156 throw std::invalid_argument("No comma after the closing ] character in the dimension string: " + str);
157 } else
158 // Find the comma after the name
159 n_first_comma = input.find_first_of(',');
160
161 if (n_first_comma == std::string::npos)
162 throw std::invalid_argument("No comma in the dimension string: " + str);
163 if (n_first_comma == input.size() - 1)
164 throw std::invalid_argument("Dimension string ends in a comma: " + str);
165
166 // Get the entire name
167 std::string name = Strings::strip(input.substr(0, n_first_comma));
168 if (name.empty())
169 throw std::invalid_argument("name should not be blank.");
170
171 // Now remove the name and comma
172 // And split the rest of it
173 input = input.substr(n_first_comma + 1);
174 std::vector<std::string> strs;
175 boost::split(strs, input, boost::is_any_of(","));
176 if (strs.size() != this->m_inWS->getNumDims() + 1)
177 throw std::invalid_argument("Wrong number of values (expected 2 + # of "
178 "input dimensions) in the dimensions string: " +
179 str);
180
181 // Get the number of bins from
182 int numBins = m_numBins[dim];
183 if (numBins < 1)
184 throw std::invalid_argument("Number of bins for output dimension " + Strings::toString(dim) + " should be >= 1.");
185
186 // Get the min/max extents in this OUTPUT dimension
187 double min = m_minExtents[dim];
188 double max = m_maxExtents[dim];
189 double lengthInOutput = max - min;
190 if (lengthInOutput <= 0)
191 throw std::invalid_argument("The maximum extents for dimension " + Strings::toString(dim) + " should be > 0.");
192
193 // Create the basis vector with the right # of dimensions
194 VMD basis(this->m_inWS->getNumDims());
195 for (size_t d = 0; d < this->m_inWS->getNumDims(); d++)
196 if (!Strings::convert(strs[d + 1], basis[d]))
197 throw std::invalid_argument("Error converting argument '" + strs[d + 1] + "' in the dimensions string '" + str +
198 "' to a number.");
199
200 // If B was binned from A (m_originalWS), and we are binning C from B,
201 // convert the basis vector from B space -> A space
202 if (m_originalWS) {
203 // Turn basis vector into two points
204 VMD basis0(this->m_inWS->getNumDims());
205 VMD basis1 = basis;
206 // Convert the points to the original coordinates (from inWS to originalWS)
207 CoordTransform const *toOrig = m_inWS->getTransformToOriginal();
208 VMD origBasis0 = toOrig->applyVMD(basis0);
209 VMD origBasis1 = toOrig->applyVMD(basis1);
210 // New basis vector, now in the original workspace
211 basis = origBasis1 - origBasis0;
212 }
213
214 // Check on the length of the basis vector
215 double basisLength = basis.norm();
216 if (basisLength <= 0)
217 throw std::invalid_argument("direction should not be 0-length.");
218
219 // Normalize it to unity, if desized
220 double transformScaling = 1.0;
222 // A distance of 1 in the INPUT space = a distance of 1.0 in the OUTPUT
223 // space
224 basis.normalize();
225 transformScaling = 1.0;
226 } else
227 // A distance of |basisVector| in the INPUT space = a distance of 1.0 in the
228 // OUTPUT space
229 transformScaling = (1.0 / basisLength);
230
231 // This is the length of this dimension as measured in the INPUT space
232 double lengthInInput = lengthInOutput / transformScaling;
233
234 // Scaling factor, to convert from units in the INPUT dimensions to the output
235 // BIN number
236 double binningScaling = double(numBins) / (lengthInInput);
237
238 // Extract the arguments
239 std::string units = Strings::strip(strs[0]);
240
241 // Create the appropriate frame
242 auto frame = createMDFrameForNonAxisAligned(units, basis);
243
244 // Create the output dimension
245 auto out = std::make_shared<MDHistoDimension>(name, name, *frame, static_cast<coord_t>(min),
246 static_cast<coord_t>(max), numBins);
247
248 // Put both in the algo for future use
249 m_bases.emplace_back(basis);
250 m_binDimensions.emplace_back(std::move(out));
251 m_binningScaling.emplace_back(binningScaling);
252 m_transformScaling.emplace_back(transformScaling);
253}
254
255//----------------------------------------------------------------------------------------------
262 // Count the number of output dimensions
263 m_outD = 0;
264 std::string dimChars = this->getDimensionChars();
265 for (char dimChar : dimChars) {
266 std::string propName = "BasisVector0";
267 propName[11] = dimChar;
268 if (!Strings::strip(this->getPropertyValue(propName)).empty())
269 m_outD++;
270 }
271
272 std::vector<double> extents = this->getProperty("OutputExtents");
273 if (extents.size() != m_outD * 2)
274 throw std::invalid_argument("The OutputExtents parameter must have " + Strings::toString(m_outD * 2) +
275 " entries "
276 "(2 for each dimension in the OUTPUT workspace).");
277
278 m_minExtents.clear();
279 m_maxExtents.clear();
280 for (size_t d = 0; d < m_outD; d++) {
281 m_minExtents.emplace_back(extents[d * 2]);
282 m_maxExtents.emplace_back(extents[d * 2 + 1]);
283 }
284
285 m_numBins = this->getProperty("OutputBins");
286 if (m_numBins.size() != m_outD)
287 throw std::invalid_argument("The OutputBins parameter must have 1 entry "
288 "for each dimension in the OUTPUT workspace.");
289
290 m_NormalizeBasisVectors = this->getProperty("NormalizeBasisVectors");
291 m_transformScaling.clear();
292
293 // Create the dimensions based on the strings from the user
294 m_binDimensions.clear();
295 m_bases.clear();
296 m_binningScaling.clear();
297 m_transformScaling.clear();
298 for (char dimChar : dimChars) {
299 std::string propName = "BasisVector0";
300 propName[11] = dimChar;
301 try {
303 } catch (std::exception &e) {
304 throw std::invalid_argument("Error parsing the " + propName + " parameter: " + std::string(e.what()));
305 }
306 }
307
308 // Number of output binning dimensions found
309 m_outD = m_binDimensions.size();
310 if (m_outD == 0)
311 throw std::runtime_error("No output dimensions were found in the MDEventWorkspace. Cannot bin!");
312
313 // ensure we are not asking for more total output bins than can fit into memory
314 // the total number of bins in an N-D workspace is the product (not sum) of bins per dimension
315 std::size_t totalBins =
316 std::accumulate(m_numBins.cbegin(), m_numBins.cend(), std::size_t(1),
317 [](std::size_t acc, int val) -> std::size_t { return acc * static_cast<std::size_t>(val); });
318 // MDHistoWorkspace allocates signal, error squared, and numEvents (each a double) plus a bool mask per bin
319 constexpr std::size_t bytesPerBin = 3 * sizeof(double) + sizeof(bool);
320 std::string memStr = Kernel::MemoryStats().checkAvailableMemory(totalBins * bytesPerBin);
321 if (!memStr.empty()) {
322 memStr = "You requested a total of " + std::to_string(totalBins) + " bins. " + memStr +
323 " Please reduce the number of bins or output dimensions.";
324 throw std::runtime_error(memStr);
325 }
326
327 // Get the Translation parameter
328 std::vector<double> translVector;
329 try {
330 translVector = getProperty("Translation");
331 } catch (std::exception &e) {
332 throw std::invalid_argument("Error parsing the Translation parameter: " + std::string(e.what()));
333 }
334
335 // Default to 0,0,0 when not specified
336 if (translVector.empty())
337 translVector.resize(m_inWS->getNumDims(), 0);
338 m_translation = VMD(translVector);
339
340 if (m_translation.getNumDims() != m_inWS->getNumDims())
341 throw std::invalid_argument("The number of dimensions in the Translation parameter is "
342 "not consistent with the number of dimensions in the input workspace.");
343
344 // Validate
345 if (m_outD > m_inWS->getNumDims())
346 throw std::runtime_error("More output dimensions were specified than input dimensions "
347 "exist in the MDEventWorkspace. Cannot bin!");
348 if (m_binningScaling.size() != m_outD)
349 throw std::runtime_error("Inconsistent number of entries in scaling vector.");
350}
351
352//----------------------------------------------------------------------------------------------
357 // Process all the input properties
359
360 // Number of input dimensions
361 size_t inD = m_inWS->getNumDims();
362
363 // ----- Make the basis vectors orthogonal -------------------------
364 bool ForceOrthogonal = getProperty("ForceOrthogonal");
365 if (ForceOrthogonal && m_bases[0].getNumDims() == 3 && m_bases.size() >= 2) {
366 std::vector<VMD> firstTwo = m_bases;
367 firstTwo.resize(2, VMD(3));
368 std::vector<VMD> ortho = VMD::makeVectorsOrthogonal(firstTwo);
369 // Set the bases back
370 ortho.resize(m_bases.size(), VMD(3));
371 m_bases = ortho;
372 g_log.information() << "Basis vectors forced to be orthogonal: ";
373 for (auto &base : m_bases)
374 g_log.information() << base.toString(",") << "; ";
375 g_log.information() << '\n';
376 }
377
378 // Now, convert the original vector to the coordinates of the ORIGNAL ws, if
379 // any
380 if (m_originalWS) {
381 CoordTransform const *toOrig = m_inWS->getTransformToOriginal();
383 }
384
385 // OK now find the min/max coordinates of the edges in the INPUT workspace
387 for (size_t d = 0; d < m_outD; d++) {
388 // Translate from the outCoords=(0,0,0) to outCoords=(min,min,min)
389 m_inputMinPoint += (m_bases[d] * m_binDimensions[d]->getMinimum());
390 }
391
392 // Create the CoordTransformAffine for BINNING with these basis vectors
393 auto ct = std::make_unique<DataObjects::CoordTransformAffine>(inD, m_outD);
394 // Note: the scaling makes the coordinate correspond to a bin index
395 ct->buildNonOrthogonal(m_inputMinPoint, this->m_bases, VMD(this->m_binningScaling) / VMD(m_transformScaling));
396 this->m_transform = std::move(ct);
397
398 // Transformation original->binned
399 auto ctFrom = std::make_unique<DataObjects::CoordTransformAffine>(inD, m_outD);
400 ctFrom->buildNonOrthogonal(m_translation, this->m_bases, VMD(m_transformScaling) / VMD(m_transformScaling));
401 m_transformFromOriginal = std::move(ctFrom);
402
403 // Validate
404 if (m_transform->getInD() != inD)
405 throw std::invalid_argument("The number of input dimensions in the CoordinateTransform "
406 "object is not consistent with the number of dimensions in the input "
407 "workspace.");
408 if (m_transform->getOutD() != m_outD)
409 throw std::invalid_argument("The number of output dimensions in the CoordinateTransform "
410 "object is not consistent with the number of dimensions specified in "
411 "the OutDimX, etc. properties.");
412
413 // Now the reverse transformation
414 m_transformToOriginal = nullptr;
415 if (m_outD == inD) {
416 // Can't reverse transform if you lost dimensions.
417 auto ctTo = std::make_unique<DataObjects::CoordTransformAffine>(inD, m_outD);
418 auto toMatrix = static_cast<DataObjects::CoordTransformAffine *>(m_transformFromOriginal.get())->getMatrix();
419 // Invert the affine matrix to get the reverse transformation
420 toMatrix.Invert();
421 ctTo->setMatrix(toMatrix);
422 m_transformToOriginal = std::move(ctTo);
423 }
424}
425
426//----------------------------------------------------------------------------------------------
434 if (str.empty()) {
435 throw std::runtime_error("Empty string passed to one of the AlignedDim0 parameters.");
436 } else {
437 // Strip spaces
438 std::string input = Strings::strip(str);
439 if (input.size() < 4)
440 throw std::invalid_argument("Dimensions string is too short to be valid: " + str);
441
442 // Find the 3rd comma from the end
443 size_t n = std::string::npos;
444 for (size_t i = 0; i < 3; i++) {
445 n = input.find_last_of(',', n);
446 if (n == std::string::npos)
447 throw std::invalid_argument("Wrong number of values (4 are expected) "
448 "in the dimensions string: " +
449 str);
450 if (n == 0)
451 throw std::invalid_argument("Dimension string starts with a comma: " + str);
452 n--;
453 }
454 // The name is everything before the 3rd comma from the end
455 std::string name = input.substr(0, n + 1);
457
458 // And split the rest of it
459 input = input.substr(n + 2);
460 std::vector<std::string> strs;
461 boost::split(strs, input, boost::is_any_of(","));
462 if (strs.size() != 3)
463 throw std::invalid_argument("Wrong number of values (3 are expected) after the name "
464 "in the dimensions string: " +
465 str);
466
467 // Extract the arguments
468 coord_t min, max;
469 int numBins = 0;
470 Strings::convert(strs[0], min);
471 Strings::convert(strs[1], max);
472 Strings::convert(strs[2], numBins);
473 if (name.empty())
474 throw std::invalid_argument("Name should not be blank.");
475 if (min >= max)
476 throw std::invalid_argument("Min should be > max.");
477 if (numBins < 1)
478 throw std::invalid_argument("Number of bins should be >= 1.");
479
480 // Find the named axis in the input workspace
481 size_t dim_index = 0;
482 try {
483 dim_index = m_inWS->getDimensionIndexByName(name);
484 } catch (std::runtime_error &) {
485 // The dimension was not found by name. Try using the ID
486 try {
487 dim_index = m_inWS->getDimensionIndexById(name);
488 } catch (std::runtime_error &) {
489 throw std::runtime_error("Dimension " + name +
490 " was not found in the "
491 "MDEventWorkspace! Cannot continue.");
492 }
493 }
494
495 // Copy the dimension name, ID and units
496 IMDDimension_const_sptr inputDim = m_inWS->getDimension(dim_index);
497 const auto &frame = inputDim->getMDFrame();
498 m_binDimensions.emplace_back(
499 new MDHistoDimension(inputDim->getName(), inputDim->getDimensionId(), frame, min, max, numBins));
500
501 // Add the index from which we're binning to the vector
502 this->m_dimensionToBinFrom.emplace_back(dim_index);
503 }
504}
505//----------------------------------------------------------------------------------------------
510 std::string dimChars = this->getDimensionChars();
511
512 // Validate inputs
513 bool previousWasEmpty = false;
514 size_t numDims = 0;
515 for (char dimChar : dimChars) {
516 std::string propName = "AlignedDim0";
517 propName[10] = dimChar;
518 std::string prop = Strings::strip(getPropertyValue(propName));
519 if (!prop.empty())
520 numDims++;
521 if (!prop.empty() && previousWasEmpty)
522 throw std::invalid_argument("Please enter the AlignedDim parameters in the order 0,1,2, etc.,"
523 "without skipping any entries.");
524 previousWasEmpty = prop.empty();
525 }
526
527 // Number of input dimension
528 size_t inD = m_inWS->getNumDims();
529 // Validate
530 if (numDims == 0)
531 throw std::runtime_error("No output dimensions specified.");
532 if (numDims > inD)
533 throw std::runtime_error("More output dimensions were specified than input dimensions "
534 "exist in the MDEventWorkspace.");
535
536 // Create the dimensions based on the strings from the user
537 m_binDimensions.clear();
538 m_dimensionToBinFrom.clear();
539 for (size_t i = 0; i < numDims; i++) {
540 std::string propName = "AlignedDim0";
541 propName[10] = dimChars[i];
543 }
544
545 // Number of output binning dimensions found
546 m_outD = m_binDimensions.size();
547
548 // ensure we are not asking for more total output bins than can fit into memory
549 // the total number of bins in an N-D workspace is the product (not sum) of bins per dimension
550 std::size_t totalBins =
551 std::accumulate(m_binDimensions.cbegin(), m_binDimensions.cend(), std::size_t(1),
552 [](std::size_t acc, const std::shared_ptr<MDHistoDimension> &dim) -> std::size_t {
553 return acc * dim->getNBins();
554 });
555 // MDHistoWorkspace allocates signal, error squared, and numEvents (each a double) plus a bool mask per bin
556 constexpr std::size_t bytesPerBin = 3 * sizeof(double) + sizeof(bool);
557 std::string memStr = Kernel::MemoryStats().checkAvailableMemory(totalBins * bytesPerBin);
558 if (!memStr.empty()) {
559 memStr = "You requested a total of " + std::to_string(totalBins) + " bins. " + memStr +
560 " Please reduce the number of bins or output dimensions.";
561 throw std::runtime_error(memStr);
562 }
563
564 // Now we build the coordinate transformation object
565 m_translation = VMD(inD);
566 m_bases.clear();
567 std::vector<coord_t> origin(m_outD), scaling(m_outD);
568 for (size_t d = 0; d < m_outD; d++) {
569 origin[d] = m_binDimensions[d]->getMinimum();
570 scaling[d] = 1.0f / m_binDimensions[d]->getBinWidth();
571 // Origin in the input
573 // Create a unit basis vector that corresponds to this
574 VMD basis(inD);
575 basis[m_dimensionToBinFrom[d]] = 1.0;
576 m_bases.emplace_back(basis);
577 }
578
579 // Transform for binning
580 m_transform = std::make_unique<DataObjects::CoordTransformAligned>(m_inWS->getNumDims(), m_outD, m_dimensionToBinFrom,
581 origin, scaling);
582
583 // Transformation original->binned. There is no offset or scaling!
584 std::vector<coord_t> unitScaling(m_outD, 1.0);
585 std::vector<coord_t> zeroOrigin(m_outD, 0.0);
587 std::make_unique<DataObjects::CoordTransformAligned>(inD, m_outD, m_dimensionToBinFrom, zeroOrigin, unitScaling);
588
589 // Now the reverse transformation.
590 if (m_outD == inD) {
591 // Make the reverse map = if you're in the output dimension "od", what INPUT
592 // dimension index is that?
593 Matrix<coord_t> mat = m_transformFromOriginal->makeAffineMatrix();
594 mat.Invert();
595 auto tmp = std::make_unique<DataObjects::CoordTransformAffine>(inD, m_outD);
596 tmp->setMatrix(mat);
597 m_transformToOriginal = std::move(tmp);
598 } else {
599 // Changed # of dimensions - can't reverse the transform
600 m_transformToOriginal = nullptr;
601 g_log.warning("SlicingAlgorithm: Your slice will cause the output "
602 "workspace to have fewer dimensions than the input. This will "
603 "affect your ability to create subsequent slices.");
604 }
605}
606
607//-----------------------------------------------------------------------------------------------
620 if (!m_inWS)
621 throw std::runtime_error("SlicingAlgorithm::createTransform(): input "
622 "MDWorkspace must be set first!");
623 if (std::dynamic_pointer_cast<MatrixWorkspace>(m_inWS))
624 throw std::runtime_error(this->name() + " cannot be run on a MatrixWorkspace!");
625
626 // Is the transformation aligned with axes?
627 m_axisAligned = getProperty("AxisAligned");
628
629 // Refer to the original workspace. Make sure that is possible
630 if (m_inWS->numOriginalWorkspaces() > 0)
631 m_originalWS = std::dynamic_pointer_cast<IMDWorkspace>(m_inWS->getOriginalWorkspace());
632 if (m_originalWS) {
633 if (m_axisAligned)
634 throw std::runtime_error("Cannot perform axis-aligned binning on a MDHistoWorkspace. "
635 "Please use non-axis aligned binning.");
636
637 if (m_originalWS->getNumDims() != m_inWS->getNumDims())
638 throw std::runtime_error("SlicingAlgorithm::createTransform(): Cannot propagate "
639 "a transformation if the number of dimensions has changed.");
640
641 if (!m_inWS->getTransformToOriginal())
642 throw std::runtime_error("SlicingAlgorithm::createTransform(): Cannot propagate "
643 "a transformation. There is no transformation saved from " +
644 m_inWS->getName() + " back to " + m_originalWS->getName() + ".");
645
646 // Fail if the MDHistoWorkspace was modified by binary operation
647 DataObjects::MDHistoWorkspace_sptr inHisto = std::dynamic_pointer_cast<DataObjects::MDHistoWorkspace>(m_inWS);
648 if (inHisto) {
649 if (inHisto->getNumExperimentInfo() > 0) {
650 const Run &run = inHisto->getExperimentInfo(0)->run();
651 if (run.hasProperty("mdhisto_was_modified")) {
652 Property *prop = run.getProperty("mdhisto_was_modified");
653 if (prop) {
654 if (prop->value() == "1") {
655 throw std::runtime_error("This MDHistoWorkspace was modified by a binary operation "
656 "(e.g. Plus, Minus). "
657 "It is not currently possible to rebin a modified "
658 "MDHistoWorkspace because that requires returning to the "
659 "original "
660 "(unmodified) MDEventWorkspace, and so would give incorrect "
661 "results. "
662 "Instead, you can use SliceMD and perform operations on the "
663 "resulting "
664 "MDEventWorkspaces, which preserve all events. "
665 "You can override this check by removing the "
666 "'mdhisto_was_modified' sample log.");
667 }
668 }
669 }
670 }
671 }
672
673 g_log.notice() << "Performing " << this->name() << " on the original workspace, '" << m_originalWS->getName()
674 << "'\n";
675 }
676
677 // Create the coordinate transformation
678 m_transform = nullptr;
679 if (m_axisAligned) {
681 } else {
683 }
684
685 // Finalize, for binnign MDHistoWorkspace
686 if (m_originalWS) {
687 // The intermediate workspace is the MDHistoWorkspace being BINNED
689 CoordTransform const *originalToIntermediate = m_intermediateWS->getTransformFromOriginal();
690 if (originalToIntermediate && (m_originalWS->getNumDims() == m_intermediateWS->getNumDims())) {
691 try {
692 // The transform from the INPUT to the INTERMEDIATE ws
693 // intermediate_coords = [OriginalToIntermediate] * [thisToOriginal] *
694 // these_coords
695 Matrix<coord_t> matToOriginal = m_transformToOriginal->makeAffineMatrix();
696 Matrix<coord_t> matOriginalToIntermediate = originalToIntermediate->makeAffineMatrix();
697 Matrix<coord_t> matToIntermediate = matOriginalToIntermediate * matToOriginal;
698
699 m_transformToIntermediate = std::make_unique<DataObjects::CoordTransformAffine>(m_originalWS->getNumDims(),
700 m_intermediateWS->getNumDims());
701 m_transformToIntermediate->setMatrix(matToIntermediate);
702 // And now the reverse
703 matToIntermediate.Invert();
704 m_transformFromIntermediate = std::make_unique<DataObjects::CoordTransformAffine>(
705 m_intermediateWS->getNumDims(), m_originalWS->getNumDims());
706 m_transformFromIntermediate->setMatrix(matToIntermediate);
707 } catch (std::runtime_error &) {
708 // Ignore error. Have no transform.
709 }
710 }
711
712 // Replace the input workspace with the original MDEventWorkspace
713 // for future binning
715 }
716}
717
718//----------------------------------------------------------------------------------------------
735std::unique_ptr<MDImplicitFunction> SlicingAlgorithm::getGeneralImplicitFunction(const size_t *const chunkMin,
736 const size_t *const chunkMax) {
737 size_t nd = m_inWS->getNumDims();
738
739 // General implicit function
740 auto func = std::make_unique<MDImplicitFunction>();
741
742 // First origin = min of each basis vector
743 VMD o1 = m_translation;
744 // Second origin = max of each basis vector
745 VMD o2 = m_translation;
746 // And this the list of basis vectors. Each vertex is given by o1+bases[i].
747 std::vector<VMD> bases;
748 VMD x;
749
750 for (size_t d = 0; d < m_bases.size(); d++) {
751 double xMin = m_binDimensions[d]->getMinimum();
752 double xMax = m_binDimensions[d]->getMaximum();
753 // Move the position if you're using a chunk
754 if (chunkMin != nullptr)
755 xMin = m_binDimensions[d]->getX(chunkMin[d]);
756 if (chunkMax != nullptr)
757 xMax = m_binDimensions[d]->getX(chunkMax[d]);
758 // Offset the origin by the position along the basis vector
759 o1 += (m_bases[d] * xMin);
760 o2 += (m_bases[d] * xMax);
761
762 VMD thisBase = m_bases[d] * (xMax - xMin);
763 bases.emplace_back(thisBase);
764 if (d == 0)
765 x = thisBase;
766 }
767
768 // Dimensionality of the box
769 size_t boxDim = bases.size();
770
771 // Point that is sure to be inside the volume of interest
772 VMD insidePoint = (o1 + o2) / 2.0;
773
774 if (boxDim == 1) {
775 // 2 planes defined by 1 basis vector
776 // Your normal = the x vector
777 func->addPlane(MDPlane(x, o1));
778 func->addPlane(MDPlane(x * -1.0, o2));
779 } else if (boxDim == nd || boxDim == nd - 1) {
780 // Create a pair of planes for each base supplied. This is general to non-
781 // orthogonal bases. If we have bases (x y z t) then we create the planes
782 //
783 // y z t
784 // x z t
785 // x y t
786 // x y z
787 //
788 // Note: the last plane may or may not be created depending on the number
789 // of bases supplied to the slicing algorithm relative to the number
790 // of dimensions. i.e. if 3 bases were supplied and we have 4 dimensions
791 // then 6 planes are created instead of 8.
792 std::vector<VMD> vectors;
793
794 for (size_t ignoreIndex = 0; ignoreIndex < boxDim; ++ignoreIndex) {
795 vectors.clear();
796 // Create a list of vectors that excludes the "current" basis
797 for (size_t baseIndex = 0; baseIndex < boxDim; ++baseIndex) {
798 if (baseIndex != ignoreIndex)
799 vectors.emplace_back(bases[baseIndex]);
800 }
801
802 // if we have fewer basis vectors than dimensions
803 // create a normal for the final dimension
804 if (boxDim == nd - 1)
805 vectors.emplace_back(VMD::getNormalVector(bases));
806
807 // Add two planes for each set of vectors
808 func->addPlane(MDPlane(vectors, o1, insidePoint));
809 func->addPlane(MDPlane(vectors, o2, insidePoint));
810 }
811 } else {
812 // Last-resort, totally general case
813 // 2*N planes defined by N basis vectors, in any dimensionality workspace.
814 // Assumes orthogonality!
815 g_log.warning("SlicingAlgorithm given " + std::to_string(boxDim) + " bases and " + std::to_string(nd) +
816 " dimensions and " + "therefore will assume orthogonality");
817 for (auto &base : bases) {
818 // For each basis vector, make two planes, perpendicular to it and facing
819 // inwards
820 func->addPlane(MDPlane(base, o1));
821 func->addPlane(MDPlane(base * -1.0, o2));
822 }
823 }
824
825 return func;
826}
827
828//----------------------------------------------------------------------------------------------
841std::unique_ptr<MDImplicitFunction> SlicingAlgorithm::getImplicitFunctionForChunk(const size_t *const chunkMin,
842 const size_t *const chunkMax) {
843 size_t nd = m_inWS->getNumDims();
844 if (m_axisAligned) {
845 std::vector<coord_t> function_min(nd, -1e30f); // default to all space if the dimension is not specified
846 std::vector<coord_t> function_max(nd, +1e30f); // default to all space if the dimension is not specified
847 for (size_t bd = 0; bd < m_outD; bd++) {
848 // Dimension in the MDEventWorkspace
849 size_t d = m_dimensionToBinFrom[bd];
850 if (chunkMin)
851 function_min[d] = m_binDimensions[bd]->getX(chunkMin[bd]);
852 else
853 function_min[d] = m_binDimensions[bd]->getX(0);
854 if (chunkMax)
855 function_max[d] = m_binDimensions[bd]->getX(chunkMax[bd]);
856 else
857 function_max[d] = m_binDimensions[bd]->getX(m_binDimensions[bd]->getNBins());
858 }
859 return std::make_unique<MDBoxImplicitFunction>(function_min, function_max);
860 } else {
861 // General implicit function
862 return getGeneralImplicitFunction(chunkMin, chunkMax);
863 }
864}
865
876 const Mantid::Kernel::VMD &basisVector) const {
877 // Get set of basis vectors
878 auto oldBasis = getOldBasis(m_inWS->getNumDims());
879
880 // Get indices onto which the vector projects
881 auto indicesWithProjection = getIndicesWithProjection(basisVector, oldBasis);
882
883 // Extract MDFrame
884 return extractMDFrameForNonAxisAligned(indicesWithProjection, units);
885}
886
887std::vector<Mantid::Kernel::VMD> SlicingAlgorithm::getOldBasis(size_t dimension) const {
888 std::vector<Mantid::Kernel::VMD> oldBasis;
889 for (size_t i = 0; i < dimension; ++i) {
890 Mantid::Kernel::VMD basisVector(dimension);
891 basisVector[i] = 1.0;
892 oldBasis.emplace_back(basisVector);
893 }
894 return oldBasis;
895}
896
904 const Mantid::Kernel::VMD &basisVector) const {
905 return std::fabs(oldVector.scalar_prod(basisVector)) > 0.0;
906}
907
915 const std::vector<Mantid::Kernel::VMD> &oldBasis) const {
916 std::vector<size_t> indexWithProjection;
917 for (size_t index = 0; index < oldBasis.size(); ++index) {
918 if (isProjectingOnFrame(oldBasis[index], basisVector)) {
919 indexWithProjection.emplace_back(index);
920 }
921 }
922 return indexWithProjection;
923}
924
933SlicingAlgorithm::extractMDFrameForNonAxisAligned(std::vector<size_t> indicesWithProjection,
934 const std::string &units) const {
935 if (indicesWithProjection.empty()) {
936 g_log.warning() << "Slicing Algorithm: Chosen vector does not "
937 "project on any vector of the old basis.";
938 }
939 // Get a reference frame to perform pairwise comparison
940 const auto &referenceMDFrame = m_inWS->getDimension(indicesWithProjection[0])->getMDFrame();
941
942 for (auto &index : indicesWithProjection) {
943 const auto &toCheckMDFrame = m_inWS->getDimension(index)->getMDFrame();
944 if (!referenceMDFrame.isSameType(toCheckMDFrame)) {
945 g_log.warning() << "Slicing Algorithm: New basis vector tries to "
946 "mix un-mixable MDFrame types.";
947 }
948 }
949
950 Mantid::Geometry::MDFrame_uptr mdFrame(referenceMDFrame.clone());
951 setTargetUnits(mdFrame, units);
952
953 return mdFrame;
954}
955
956/*
957 * Set units of the output workspace
958 * @param mdFrame: MDFrame to be added to the output workspace
959 * @param unit: the unit to use in mdFrame
960 */
961void SlicingAlgorithm::setTargetUnits(Mantid::Geometry::MDFrame_uptr &mdFrame, const std::string &unit) const {
962 boost::regex pattern("in.*A.*\\^-1");
963
964 if (boost::regex_match(unit, pattern)) {
965 // RLU with special label
966 auto md_unit = ReciprocalLatticeUnit(unit);
967 mdFrame->setMDUnit(md_unit);
968 } else if (unit == "r") {
969 // RLU
970 auto md_unit = ReciprocalLatticeUnit();
971 mdFrame->setMDUnit(md_unit);
972 } else if (unit == "a") {
973 // Inverse angstroms
974 auto md_unit = InverseAngstromsUnit();
975 mdFrame->setMDUnit(md_unit);
976 }
977 // else leave the unit the same as the input workspace
978}
979
980} // namespace Mantid::MDAlgorithms
gsl_vector * tmp
std::map< DeltaEMode::Type, std::string > index
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:422
const std::string name() const override=0
function to return a name of the algorithm, must be overridden in all algorithms
Unique SingleValueParameter Declaration for InputNDimensions.
Mantid::Kernel::VMD applyVMD(const Mantid::Kernel::VMD &inputVector) const
Wrapper for VMD.
virtual Mantid::Kernel::Matrix< coord_t > makeAffineMatrix() const
bool hasProperty(const std::string &name) const
Does the property exist on the object.
Kernel::Property * getProperty(const std::string &name) const
Returns the named property as a pointer.
This class stores information regarding an experimental run as a series of log entries.
Definition Run.h:35
Generic class to transform from M input dimensions to N output dimensions.
void setMatrix(const Mantid::Kernel::Matrix< coord_t > &newMatrix)
Directly set the affine matrix to use.
A generalized description of a N-dimensional hyperplane.
Definition MDPlane.h:41
Support for a property that holds an array of values.
void setPropertySettings(const std::string &name, std::unique_ptr< IPropertySettings const > settings)
Add a PropertySettings instance to the chain of settings for a given property.
void setPropertyGroup(const std::string &name, const std::string &group)
Set the group for a given property.
Inverse Angstroms unit.
Definition MDUnit.h:51
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
Numerical Matrix class.
Definition Matrix.h:42
T Invert()
LU inversion routine.
Definition Matrix.cpp:924
This class is responsible for memory statistics.
Definition Memory.h:28
std::string checkAvailableMemory(std::size_t const requestedMemoryBytes) const
Check if there is enough space in memory to hold the requested amount of memory.
Definition Memory.cpp:571
The concrete, templated class for properties.
Base class for properties.
Definition Property.h:94
virtual std::string value() const =0
Returns the value of the property as a string.
static std::vector< VMDBase > makeVectorsOrthogonal(std::vector< VMDBase > &vectors)
Make an orthogonal system with 2 input 3D vectors.
Definition VMD.cpp:450
TYPE normalize()
Normalize this vector to unity length.
Definition VMD.cpp:427
TYPE scalar_prod(const VMDBase &v) const
Scalar product of two vectors.
Definition VMD.cpp:391
size_t size() const
Definition VMD.cpp:239
size_t getNumDims() const
Definition VMD.cpp:236
static VMDBase getNormalVector(const std::vector< VMDBase > &vectors)
Given N-1 vectors defining a N-1 dimensional hyperplane in N dimensions, returns a vector that is nor...
Definition VMD.cpp:494
TYPE norm() const
Definition VMD.cpp:420
std::unique_ptr< API::CoordTransform > m_transformFromOriginal
Coordinate transformation to save in the output workspace (original->binned)
Mantid::API::IMDWorkspace_sptr m_intermediateWS
Intermediate original workspace.
std::vector< size_t > m_dimensionToBinFrom
Index of the dimension in the MDEW for the dimension in the output.
std::unique_ptr< Mantid::Geometry::MDImplicitFunction > getImplicitFunctionForChunk(const size_t *const chunkMin, const size_t *const chunkMax)
Create an implicit function for picking boxes, based on the indexes in the output MDHistoWorkspace.
bool isProjectingOnFrame(const Mantid::Kernel::VMD &oldVector, const Mantid::Kernel::VMD &basisVector) const
Check if the two vectors are orthogonal or not.
std::unique_ptr< API::CoordTransform > m_transform
Coordinate transformation to apply.
std::vector< size_t > getIndicesWithProjection(const Mantid::Kernel::VMD &basisVector, const std::vector< Mantid::Kernel::VMD > &oldBasis) const
Get indices which have a projection contribution.
Mantid::API::IMDWorkspace_sptr m_originalWS
Original (MDEventWorkspace) that inWS was based on.
void createGeneralTransform()
Loads the dimensions and create the coordinate transform, using the inputs.
void createTransform()
Read the algorithm properties and creates the appropriate transforms for slicing the MDEventWorkspace...
void createAlignedTransform()
Using the parameters, create a coordinate transformation for aligned cuts.
void setTargetUnits(Mantid::Geometry::MDFrame_uptr &frame, const std::string &units) const
std::vector< Mantid::Kernel::VMD > getOldBasis(size_t dimension) const
Mantid::Kernel::VMD m_inputMinPoint
Coordinates in the INPUT workspace corresponding to the minimum edge in all dimensions.
std::vector< double > m_maxExtents
For non-aligned, the maximum coordinate extents in each OUTPUT dimension.
Mantid::API::IMDWorkspace_sptr m_inWS
Input workspace.
std::unique_ptr< API::CoordTransform > m_transformToOriginal
Coordinate transformation to save in the output workspace (binned->original)
std::unique_ptr< DataObjects::CoordTransformAffine > m_transformFromIntermediate
Coordinate transformation to save in the output WS, from the intermediate WS.
std::vector< double > m_binningScaling
Scaling factor to apply for each basis vector (to map to the bins).
size_t m_outD
Number of dimensions in the output (binned) workspace.
void makeBasisVectorFromString(const std::string &str)
Generate the MDHistoDimension and basis vector for a given string from BasisVector0 etc.
Mantid::Geometry::MDFrame_uptr createMDFrameForNonAxisAligned(const std::string &units, const Mantid::Kernel::VMD &basisVector) const
Create an MDFrame for the Non-Axis-Aligned case.
bool m_NormalizeBasisVectors
The NormalizeBasisVectors option.
void processGeneralTransformProperties()
Reads the various Properties for the general (non-aligned) case and fills in members on the Algorithm...
std::vector< Mantid::Kernel::VMD > m_bases
Basis vectors of the output dimensions, normalized to unity length.
std::unique_ptr< Mantid::Geometry::MDImplicitFunction > getGeneralImplicitFunction(const size_t *const chunkMin, const size_t *const chunkMax)
Create an implicit function for picking boxes, based on the indexes in the output MDHistoWorkspace.
Mantid::Geometry::MDFrame_uptr extractMDFrameForNonAxisAligned(std::vector< size_t > indicesWithProjection, const std::string &units) const
Extract the MDFrame.
bool m_axisAligned
Set to true if the cut is aligned with the axes.
void initSlicingProps()
Initialise the properties.
std::vector< int > m_numBins
For non-aligned, the number of bins in each OUTPUT dimension.
Mantid::Kernel::VMD m_translation
Translation from the OUTPUT to the INPUT workspace i.e.
std::vector< double > m_transformScaling
Scaling factor to apply for each basis vector to transfor to the output dimensions.
std::vector< double > m_minExtents
For non-aligned, the minimum coordinate extents in each OUTPUT dimension.
std::unique_ptr< DataObjects::CoordTransformAffine > m_transformToIntermediate
Coordinate transformation to save in the intermediate WS.
void makeAlignedDimensionFromString(const std::string &str)
Generate a MDHistoDimension_sptr from a comma-sep string (for AlignedDim0, etc.) Must be called in or...
std::vector< Mantid::Geometry::MDHistoDimension_sptr > m_binDimensions
Bin dimensions to actually use.
std::shared_ptr< MDHistoWorkspace > MDHistoWorkspace_sptr
A shared pointer to a MDHistoWorkspace.
std::unique_ptr< MDFrame > MDFrame_uptr
Definition MDFrame.h:36
std::shared_ptr< const IMDDimension > IMDDimension_const_sptr
Shared Pointer to const IMDDimension.
MANTID_KERNEL_DLL std::string strip(const std::string &A)
strip pre/post spaces
Definition Strings.cpp:419
int convert(const std::string &A, T &out)
Convert a string into a number.
Definition Strings.cpp:696
std::string toString(const T &value)
Convert a number to a string.
Definition Strings.cpp:734
VMDBase< VMD_t > VMD
Define the VMD as using the double or float data type.
Definition VMD.h:86
float coord_t
Typedef for the data type to use for coordinate axes in MD objects such as MDBox, MDEventWorkspace,...
Definition MDTypes.h:27
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition Property.h:53