Mantid
Loading...
Searching...
No Matches
IndexPeaks.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 +
9#include "MantidAPI/Sample.h"
18
19#include <optional>
20
21#include <algorithm>
22
23namespace Mantid::Crystal {
24// Register the algorithm into the AlgorithmFactory
25DECLARE_ALGORITHM(IndexPeaks)
26
27using API::IPeaksWorkspace;
29using Geometry::IndexingUtils;
30using Geometry::IPeak;
31using Geometry::OrientedLattice;
33using Kernel::Logger;
34using Kernel::V3D;
35
36namespace {
37const auto OPTIMIZE_UB_ATTEMPTS{4};
38
39namespace Prop {
40const std::string PEAKSWORKSPACE{"PeaksWorkspace"};
41const std::string TOLERANCE{"Tolerance"};
42const std::string SATE_TOLERANCE{"ToleranceForSatellite"};
43const std::string ROUNDHKLS{"RoundHKLs"};
44const std::string COMMONUB{"CommonUBForAll"};
45const std::string SAVEMODINFO{"SaveModulationInfo"};
46const std::string AVERAGE_ERR{"AverageError"};
47const std::string NUM_INDEXED{"NumIndexed"};
48const std::string MAIN_NUM_INDEXED{"MainNumIndexed"};
49const std::string SATE_NUM_INDEXED{"SateNumIndexed"};
50const std::string MAIN_ERR{"MainError"};
51const std::string SATE_ERR{"SatelliteError"};
52const std::string UPDATE_UB{"UpdateUB"};
53
54struct SatelliteIndexingArgs {
55 const double tolerance;
56 const int maxOrder;
57 const std::vector<V3D> modVectors;
58 const bool crossTerms;
59};
60
61struct IndexPeaksArgs {
67 static IndexPeaksArgs parse(const API::Algorithm &alg) {
68 const IPeaksWorkspace_sptr peaksWS = alg.getProperty(PEAKSWORKSPACE);
69 const int maxOrderFromAlg = alg.getProperty(ModulationProperties::MaxOrder);
70
71 // Init variables
72 int maxOrderToUse{0};
73 std::vector<V3D> modVectorsToUse;
74 modVectorsToUse.reserve(3);
75 bool crossTermToUse{false};
76
77 // Parse mod vectors
78 modVectorsToUse = addModulationVectors(alg.getProperty(ModulationProperties::ModVector1),
79 alg.getProperty(ModulationProperties::ModVector2),
80 alg.getProperty(ModulationProperties::ModVector3));
81 // check the 3 mod vectors added from properties
82 modVectorsToUse = validModulationVectors(modVectorsToUse[0], modVectorsToUse[1], modVectorsToUse[2]);
83
84 // deal with case: max order > 0 and no mod vector is specified
85 if (maxOrderFromAlg > 0 && modVectorsToUse.size() == 0) {
86 // Max Order is larger than zero but no modulated vector specified
87 // Assume that the caller method will handle this
88 maxOrderToUse = maxOrderFromAlg;
89 } else if (maxOrderFromAlg == 0 && modVectorsToUse.size() == 0) {
90 // Use lattice definitions if they exist
91 const auto &lattice = peaksWS->sample().getOrientedLattice();
92 crossTermToUse = lattice.getCrossTerm();
93 maxOrderToUse = lattice.getMaxOrder(); // the lattice can return a 0 here
94
95 // if lattice has maxOrder, we will use the modVec from it, otherwise
96 // stick to the input got from previous assignment
97 if (maxOrderToUse > 0) {
98 modVectorsToUse = validModulationVectors(lattice.getModVec(0), lattice.getModVec(1), lattice.getModVec(2));
99 }
100 } else {
101 // Use user specified
102 // default behavior: map everything automatically
103 maxOrderToUse = maxOrderFromAlg;
104 crossTermToUse = alg.getProperty(ModulationProperties::CrossTerms);
105 }
106
107 return {peaksWS,
108 alg.getProperty(TOLERANCE),
109 alg.getProperty(ROUNDHKLS),
110 alg.getProperty(COMMONUB),
111 alg.getProperty(SAVEMODINFO),
112 SatelliteIndexingArgs{alg.getProperty(SATE_TOLERANCE), maxOrderToUse, modVectorsToUse, crossTermToUse},
113 alg.getProperty(UPDATE_UB)};
114 }
115
117 const double mainTolerance;
118 const bool roundHKLs;
119 const bool commonUB;
121 const SatelliteIndexingArgs satellites;
122 const bool updateUB;
123};
124} // namespace Prop
125
129struct PeakIndexingStats {
130 PeakIndexingStats &operator+=(const PeakIndexingStats &rhs) {
131 numIndexed += rhs.numIndexed;
132 error += rhs.error;
133 return *this;
134 }
136 double error{0.0};
137};
138
143struct CombinedIndexingStats {
144 CombinedIndexingStats &operator+=(const CombinedIndexingStats &rhs) {
145 main += rhs.main;
146 satellites += rhs.satellites;
147 return *this;
148 }
149
151 inline int totalNumIndexed() const { return main.numIndexed + satellites.numIndexed; }
153 inline double mainError() const {
154 if (main.numIndexed == 0)
155 return 0.0;
156 return main.error / main.numIndexed;
157 }
159 inline double satelliteError() const {
160 if (satellites.numIndexed == 0)
161 return 0.0;
162 return satellites.error / satellites.numIndexed;
163 }
165 inline double averageError() const { return (main.error + satellites.error) / totalNumIndexed(); }
166
167 PeakIndexingStats main;
168 PeakIndexingStats satellites;
169};
170
177DblMatrix optimizeUBMatrix(const DblMatrix &ubOrig, const std::vector<V3D> &qSample, const double tolerance) {
178 DblMatrix optimizedUB(ubOrig);
179
180 double errorAtStart{0.0};
181 std::vector<V3D> millerIndices;
182 millerIndices.reserve(qSample.size());
183 const int numIndexedAtStart =
184 IndexingUtils::CalculateMillerIndices(optimizedUB, qSample, tolerance, millerIndices, errorAtStart);
185
186 if (numIndexedAtStart < 3) {
187 // can't optimize without at least 3 indexed peaks
188 return optimizedUB;
189 }
190
191 for (auto i = 0; i < OPTIMIZE_UB_ATTEMPTS; ++i) {
192 try {
193 // optimization requires rounded indices
194 IndexingUtils::RoundHKLs(millerIndices);
195 IndexingUtils::Optimize_UB(optimizedUB, millerIndices, qSample);
196 } catch (...) {
197 // If there is any problem, such as too few
198 // independent peaks, just use the original UB
199 optimizedUB = ubOrig;
200 break;
201 }
202 double errorInLoop{0.0};
203 const int numIndexedInLoop =
204 IndexingUtils::CalculateMillerIndices(optimizedUB, qSample, tolerance, millerIndices, errorInLoop);
205 if (numIndexedInLoop < numIndexedAtStart) // use the original UB
206 break;
207 }
208 return optimizedUB;
209}
210
212using IndexedSatelliteInfo = std::tuple<V3D, V3D, V3D, double>;
213
237std::optional<IndexedSatelliteInfo> indexSatellite(const V3D &mainHKL, const int maxOrder,
238 const std::vector<V3D> &modVectors, const double tolerance,
239 const bool crossTerms) {
240 const auto offsets = generateOffsetVectors(modVectors, maxOrder, crossTerms);
241 bool foundSatellite{false};
242 V3D indexedIntHKL, indexedMNP, fractionalOffset;
243 for (const auto &mnpOffset : offsets) {
244 const auto candidateIntHKL = mainHKL - std::get<3>(mnpOffset);
245 const V3D candidateMNP{std::get<0>(mnpOffset), std::get<1>(mnpOffset), std::get<2>(mnpOffset)};
246 if (IndexingUtils::ValidIndex(candidateIntHKL, tolerance)) {
247 indexedIntHKL = candidateIntHKL;
248 indexedMNP = candidateMNP;
249 fractionalOffset = std::get<3>(mnpOffset);
250 foundSatellite = true;
251 // we deliberately don't break and use the last valid
252 // reflection we find.
253 }
254 }
255 if (foundSatellite)
256 return std::make_tuple(fractionalOffset, indexedIntHKL, indexedMNP, indexedIntHKL.hklError());
257 else
258 return std::nullopt;
259}
260
272CombinedIndexingStats indexPeaks(const std::vector<IPeak *> &peaks, DblMatrix ub, const double mainTolerance,
273 const bool roundHKLs, const bool optimizeUB,
274 const Prop::SatelliteIndexingArgs &satelliteArgs) {
275 const auto nPeaks = peaks.size();
276 std::vector<V3D> qSample(nPeaks);
277 std::generate(std::begin(qSample), std::end(qSample),
278 [&peaks, i = 0u]() mutable { return peaks[i++]->getQSampleFrame(); });
279 if (optimizeUB) {
280 ub = optimizeUBMatrix(ub, qSample, mainTolerance);
281 }
282
283 CombinedIndexingStats stats;
284 ub.Invert();
285 for (auto i = 0u; i < peaks.size(); ++i) {
286 const auto peak = peaks[i];
287 V3D nominalHKL = IndexingUtils::CalculateMillerIndices(ub, qSample[i]);
288 if (IndexingUtils::ValidIndex(nominalHKL, mainTolerance)) {
289 stats.main.numIndexed++;
290 stats.main.error += nominalHKL.hklError() / 3.0;
291 if (roundHKLs) {
292 IndexingUtils::RoundHKL(nominalHKL);
293 }
294 peak->setHKL(nominalHKL);
295 peak->setIntHKL(nominalHKL);
296 peak->setIntMNP(V3D(0, 0, 0));
297 } else if (satelliteArgs.maxOrder > 0) {
298 auto result = indexSatellite(nominalHKL, satelliteArgs.maxOrder, satelliteArgs.modVectors,
299 satelliteArgs.tolerance, satelliteArgs.crossTerms);
300 if (result) {
301 const auto &satelliteInfo = result.value();
302 V3D hkl;
303 ;
304 if (roundHKLs) {
305 hkl = std::get<1>(satelliteInfo);
307 hkl += std::get<0>(satelliteInfo);
308 } else {
309 hkl = nominalHKL;
310 }
311 peak->setHKL(hkl);
312 peak->setIntHKL(std::get<1>(satelliteInfo));
313 peak->setIntMNP(std::get<2>(satelliteInfo));
314 stats.satellites.numIndexed++;
315 stats.satellites.error += std::get<3>(satelliteInfo) / 3.;
316 } else {
317 // clear these to make sure leftover values from previous index peaks
318 // run are not used
319 peak->setHKL(V3D(0, 0, 0));
320 peak->setIntHKL(V3D(0, 0, 0));
321 peak->setIntMNP(V3D(0, 0, 0));
322 }
323 } else {
324 peak->setHKL(V3D(0, 0, 0));
325 peak->setIntHKL(V3D(0, 0, 0));
326 peak->setIntMNP(V3D(0, 0, 0));
327 }
328 }
329 return stats;
330}
331
341void logIndexingResults(std::ostream &out, const CombinedIndexingStats &indexingInfo, const int runNo,
342 const size_t nPeaksTotal, const Prop::IndexPeaksArgs &args) {
343 if (runNo >= 0)
344 out << "Run " << runNo;
345 else
346 out << "All runs";
347 out << " indexed " << indexingInfo.totalNumIndexed() << " peaks out of " << nPeaksTotal;
348 if (args.satellites.maxOrder > 0) {
349 out << " of which, " << indexingInfo.main.numIndexed << " main Bragg peaks are indexed with tolerance of "
350 << args.mainTolerance << ", " << indexingInfo.satellites.numIndexed
351 << " satellite peaks are indexed with tolerance of " << args.satellites.tolerance << '\n';
352 out << " Average error in h,k,l for indexed peaks = " << indexingInfo.averageError() << '\n';
353 out << " Average error in h,k,l for indexed main peaks = " << indexingInfo.main.error << '\n';
354 out << " Average error in h,k,l for indexed satellite peaks = " << indexingInfo.satellites.error << '\n';
355 } else {
356 out << " with tolerance of " << args.mainTolerance << '\n';
357 out << " Average error in h,k,l for indexed peaks = " << indexingInfo.mainError() << '\n';
358 }
359}
360
361} // namespace
362
370 using Kernel::Direction;
371
372 // -- inputs --
373 this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace_sptr::element_type>>(Prop::PEAKSWORKSPACE,
374 "", Direction::InOut),
375 "Input Peaks Workspace");
376
377 auto mustBePositiveDbl = std::make_shared<BoundedValidator<double>>();
378 mustBePositiveDbl->setLower(0.0);
379 this->declareProperty(Prop::TOLERANCE, 0.15, mustBePositiveDbl, "Main peak indexing tolerance", Direction::Input);
380 this->declareProperty(Prop::SATE_TOLERANCE, 0.15, mustBePositiveDbl, "Satellite peak indexing tolerance",
382 this->declareProperty(Prop::ROUNDHKLS, true, "Round H, K and L values to integers");
383 this->declareProperty(Prop::COMMONUB, false, "Index all orientations with a common UB");
385 this->declareProperty(Prop::SAVEMODINFO, false,
386 "If true, update the OrientedLattice with the maxOrder, "
387 "modulation vectors & cross terms values input to the algorithm");
388 this->declareProperty(Prop::UPDATE_UB, false,
389 "Saves the optimized UB matrix to the workspace if CommonUBForAll=False. This option works "
390 "only when the peak workspace contains a single run.");
391
392 // -- outputs --
393 this->declareProperty(Prop::NUM_INDEXED, 0, "Gets set with the number of indexed peaks.", Direction::Output);
394 this->declareProperty(Prop::AVERAGE_ERR, 0.0, "Gets set with the average HKL indexing error.", Direction::Output);
395 this->declareProperty(Prop::MAIN_NUM_INDEXED, 0, "Gets set with the number of indexed main peaks.",
397 this->declareProperty(Prop::SATE_NUM_INDEXED, 0, "Gets set with the number of indexed main peaks.",
399 this->declareProperty(Prop::MAIN_ERR, 0.0, "Gets set with the average HKL indexing error of Main Peaks.",
401 this->declareProperty(Prop::SATE_ERR, 0.0, "Gets set with the average HKL indexing error of Satellite Peaks.",
403}
404
409std::map<std::string, std::string> IndexPeaks::validateInputs() {
410 std::map<std::string, std::string> helpMsgs;
411
412 IPeaksWorkspace_sptr ws = this->getProperty(Prop::PEAKSWORKSPACE);
413 try {
414 ws->sample().getOrientedLattice();
415 } catch (std::runtime_error &) {
416 helpMsgs[Prop::PEAKSWORKSPACE] = "No UB Matrix defined in the lattice.";
417 return helpMsgs;
418 }
419
420 const auto args = Prop::IndexPeaksArgs::parse(*this);
421 const bool isSave = this->getProperty(Prop::SAVEMODINFO);
422 const bool isMOZero = (args.satellites.maxOrder == 0);
423 bool isAllVecZero = true;
424 // parse() validates all the mod vectors. There should not be any modulated
425 // vector in modVectors is equal to (0, 0, 0)
426 for (size_t vecNo = 0; vecNo < args.satellites.modVectors.size(); vecNo++) {
427 if (args.satellites.modVectors[vecNo] != V3D(0.0, 0.0, 0.0)) {
428 isAllVecZero = false;
429 } else {
430 g_log.warning() << "Mod vector " << vecNo << " is invalid (0, 0, 0)"
431 << "\n";
432 }
433 }
434 if (isMOZero && !isAllVecZero) {
435 helpMsgs["MaxOrder"] = "Max Order cannot be zero if a Modulation Vector has been supplied.";
436 }
437 if (!isMOZero && isAllVecZero) {
438 helpMsgs["ModVector1"] = "At least one Modulation Vector must be supplied if Max Order set.";
439 }
440 if (isSave && isAllVecZero) {
441 helpMsgs[Prop::SAVEMODINFO] = "Modulation info cannot be saved with no "
442 "valid Modulation Vectors supplied.";
443 }
444 if (isSave && isMOZero) {
445 helpMsgs["MaxOrder"] = "Modulation info cannot be saved with Max Order = 0.";
446 }
447 return helpMsgs;
448}
449
454 const auto args = Prop::IndexPeaksArgs::parse(*this);
455
456 // quick exit
457 if (args.workspace->getNumberPeaks() == 0) {
458 g_log.warning("Empty peaks workspace. Nothing to index");
459 return;
460 }
461
462 // save modulation input if asked
463 if (args.storeModulationInfo) {
464 auto &lattice = args.workspace->mutableSample().getOrientedLattice();
465 lattice.setMaxOrder(args.satellites.maxOrder);
466 lattice.setCrossTerm(args.satellites.crossTerms);
467
468 if (args.satellites.modVectors.size() >= 1) {
469 lattice.setModVec1(args.satellites.modVectors[0]);
470 } else {
471 g_log.warning("empty modVector 1, skipping saving");
472 lattice.setModVec1(V3D(0.0, 0.0, 0.0));
473 }
474
475 if (args.satellites.modVectors.size() >= 2) {
476 lattice.setModVec2(args.satellites.modVectors[1]);
477 } else {
478 g_log.warning("empty modVector 2, skipping saving");
479 lattice.setModVec2(V3D(0.0, 0.0, 0.0));
480 }
481
482 if (args.satellites.modVectors.size() >= 3) {
483 lattice.setModVec3(args.satellites.modVectors[2]);
484 } else {
485 g_log.warning("empty modVector 3, skipping saving");
486 lattice.setModVec3(V3D(0.0, 0.0, 0.0));
487 }
488 // set modUB now mod vectors populated
489 lattice.setModUB(lattice.getUB() * lattice.getModHKL());
490 }
491
492 CombinedIndexingStats indexingInfo;
493 const auto &lattice = args.workspace->sample().getOrientedLattice();
494 const auto &sampleUB = lattice.getUB();
495 if (args.commonUB) {
496 // Use sample UB an all peaks regardless of run
497 std::vector<IPeak *> allPeaksRef;
498 allPeaksRef.reserve(args.workspace->getNumberPeaks());
499 for (int i = 0; i < args.workspace->getNumberPeaks(); i++) {
500 allPeaksRef.emplace_back(args.workspace->getPeakPtr(i));
501 }
502 const bool optimizeUB{false};
503 indexingInfo = indexPeaks(allPeaksRef, sampleUB, args.mainTolerance, args.roundHKLs, optimizeUB, args.satellites);
504 } else {
505 // Use a UB optimized for each run
506 std::unordered_map<int, std::vector<IPeak *>> peaksPerRun;
507 for (int i = 0; i < args.workspace->getNumberPeaks(); i++)
508 peaksPerRun[args.workspace->getPeak(i).getRunNumber()].emplace_back(args.workspace->getPeakPtr(i));
509 if (peaksPerRun.size() == 1) {
510 if (args.updateUB) {
511 // Save the optimized UB to the workspace
512 auto peaks = peaksPerRun.begin()->second;
513 std::vector<V3D> qSample(peaks.size());
514 std::generate(std::begin(qSample), std::end(qSample),
515 [&peaks, i = 0u]() mutable { return peaks[i++]->getQSampleFrame(); });
516
517 DblMatrix optimizedUB = optimizeUBMatrix(sampleUB, qSample, args.mainTolerance);
518 if (optimizedUB != sampleUB) {
519 args.workspace->mutableSample().getOrientedLattice().setUB(optimizedUB);
520 g_log.notice() << "Updating the UB matrix with an improved optimized version.\n";
521 } else {
522 g_log.notice() << "No improved UB matrix was found, so retaining the original UB.\n";
523 }
524 } else {
526 "Peaks from only one run exist but CommonUBForAll=False so peaks will be indexed with an optimised "
527 "UB which will not be saved in the workspace. Use UpdateUB=True to save the optimized UB matrix.\n");
528 }
529 }
530 const bool optimizeUB{true};
531 for (const auto &runPeaks : peaksPerRun) {
532 const auto &peaks = runPeaks.second;
533 const auto indexedInRun =
534 indexPeaks(peaks, sampleUB, args.mainTolerance, args.roundHKLs, optimizeUB, args.satellites);
535 logIndexingResults(g_log.notice(), indexedInRun, runPeaks.first, peaks.size(), args);
536 indexingInfo += indexedInRun;
537 }
538 }
539
540 setProperty("NumIndexed", indexingInfo.totalNumIndexed());
541 setProperty("MainNumIndexed", indexingInfo.main.numIndexed);
542 setProperty("SateNumIndexed", indexingInfo.satellites.numIndexed);
543 setProperty("AverageError", indexingInfo.averageError());
544 setProperty("MainError", indexingInfo.mainError());
545 setProperty("SatelliteError", indexingInfo.satelliteError());
546
547 // Final results
548 logIndexingResults(g_log.notice(), indexingInfo, -1, args.workspace->getNumberPeaks(), args);
549 // Show the lattice parameters
550 g_log.notice() << args.workspace->sample().getOrientedLattice() << "\n";
551}
552
553} // namespace Mantid::Crystal
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
const std::vector< double > & rhs
const bool commonUB
double error
PeakIndexingStats main
int numIndexed
const std::vector< V3D > modVectors
const bool crossTerms
const bool storeModulationInfo
const bool updateUB
const bool roundHKLs
const int maxOrder
const SatelliteIndexingArgs satellites
IPeaksWorkspace_sptr workspace
const double mainTolerance
double tolerance
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:422
A property class for workspaces.
void init() override
Initialize the algorithm's properties.
std::map< std::string, std::string > validateInputs() override
Validate all inputs once set.
void exec() override
Execute the algorithm.
static void RoundHKL(Kernel::V3D &hkl)
Round all the components of a HKL objects to the nearest integer.
static void RoundHKLs(std::vector< Kernel::V3D > &hkl_list)
Round all the components of a list of V3D objects, to the nearest integer.
static double Optimize_UB(Kernel::DblMatrix &UB, const std::vector< Kernel::V3D > &hkl_vectors, const std::vector< Kernel::V3D > &q_vectors, std::vector< double > &sigabc)
Find the UB matrix that most nearly maps hkl to qxyz for 3 or more peaks.
static bool ValidIndex(const Kernel::V3D &hkl, double tolerance)
Check is hkl is within tolerance of integer (h,k,l) non-zero values.
static int CalculateMillerIndices(const Kernel::DblMatrix &UB, const std::vector< Kernel::V3D > &q_vectors, double tolerance, std::vector< Kernel::V3D > &miller_indices, double &ave_error)
Given a UB, get list of Miller indices for specifed Qs and tolerance.
ArrayLenghtValidator : Validate length of an array property.
Support for a property that holds an array of values.
BoundedValidator is a validator that requires the values to be between upper or lower bounds,...
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
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
Class for 3D vectors.
Definition V3D.h:34
std::shared_ptr< IPeaksWorkspace > IPeaksWorkspace_sptr
shared pointer to Mantid::API::IPeaksWorkspace
MatrixWorkspace_sptr MANTID_API_DLL operator+=(const MatrixWorkspace_sptr &lhs, const MatrixWorkspace_sptr &rhs)
Adds two workspaces.
std::vector< Kernel::V3D > addModulationVectors(const std::vector< double > &modVector1, const std::vector< double > &modVector2, const std::vector< double > &modVector3)
Create a list of valid modulation vectors from the input.
std::vector< MNPOffset > generateOffsetVectors(const std::vector< Kernel::V3D > &modVectors, const int maxOrder, const bool crossTerms)
Calculate a list of HKL offsets from the given modulation vectors.
std::vector< Kernel::V3D > validModulationVectors(const std::vector< double > &modVector1, const std::vector< double > &modVector2, const std::vector< double > &modVector3)
Create a list of valid modulation vectors from the input.
Mantid::Kernel::Matrix< double > DblMatrix
Definition Matrix.h:206
String constants for algorithm's properties.
static void appendTo(API::IAlgorithm *alg)
Append the common set of properties that relate to modulation vectors to the given algorithm.
Describes the direction (within an algorithm) of a Property.
Definition Property.h:50
@ InOut
Both an input & output workspace.
Definition Property.h:55
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54