Mantid
Loading...
Searching...
No Matches
GenerateGroupingPowder.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 +
18
19#include <Poco/DOM/AutoPtr.h>
20#include <Poco/DOM/DOMWriter.h>
21#include <Poco/DOM/Document.h>
22#include <Poco/DOM/Text.h>
23#include <Poco/XML/XMLWriter.h>
24
25#include <fstream>
26
27using namespace Mantid::API;
28using namespace Mantid::DataObjects;
29using namespace Mantid::Kernel;
30using namespace Mantid::Geometry;
31using namespace Poco::XML;
32
33namespace {
34class Labelor {
35protected:
36 const double tt_step;
37 const double aa_step;
38 const double aa_start;
39
40public:
41 Labelor(double t_step, double a_step = 0.0, double a_start = 0.0)
42 : tt_step(t_step * Mantid::Geometry::deg2rad), aa_step(a_step * Mantid::Geometry::deg2rad),
43 aa_start(a_start * Mantid::Geometry::deg2rad) {};
44 virtual ~Labelor() {};
45 virtual size_t operator()(SpectrumInfo const &spectrumInfo, size_t i) = 0;
46};
47
48class CircularSectorLabelor : public Labelor {
55 // reduce flops by saving as mult. constant
56 const double inv_tt_step;
57
58public:
59 CircularSectorLabelor(double t_step) : Labelor(t_step), inv_tt_step(1.0 / tt_step) {};
60 size_t operator()(SpectrumInfo const &spectrumInfo, size_t i) override {
61 return static_cast<size_t>(spectrumInfo.twoTheta(i) * inv_tt_step) + 1;
62 };
63};
64
65class SphericalSectorLabelor : public Labelor {
72 // reduce flops by saving as mult. constant
73 const double inv_tt_step;
74 const double inv_aa_step;
75 const int num_aa_step;
76
77public:
78 SphericalSectorLabelor(double t_step, double a_step, double a_start)
79 : Labelor(t_step, a_step, a_start), inv_tt_step(1.0 / tt_step), inv_aa_step(1.0 / aa_step),
80 num_aa_step(int(std::ceil(360.0 / a_step))) {};
81 size_t operator()(SpectrumInfo const &spectrumInfo, size_t i) override {
82 return static_cast<size_t>(
83 static_cast<int64_t>(spectrumInfo.twoTheta(i) * inv_tt_step * num_aa_step) +
84 static_cast<int64_t>(std::floor((spectrumInfo.azimuthal(i) - aa_start) * inv_aa_step)) % num_aa_step + 1);
85 };
86};
87
88class CircularOrderedLabelor : public Labelor {
98 std::unordered_map<size_t, double> divs;
99 std::unordered_map<size_t, std::vector<size_t>> groups;
100
101 const double inv_tt_step;
102 size_t currentGroup;
103
104 inline size_t getBand(const double x) { return static_cast<int>(x * inv_tt_step); };
105
106public:
107 CircularOrderedLabelor(double t_step, SpectrumInfo const &spectrumInfo)
108 : Labelor(t_step), inv_tt_step(1.0 / tt_step), currentGroup(0) {
109
110 std::vector<double> tt(spectrumInfo.size());
111 std::transform(spectrumInfo.cbegin(), spectrumInfo.cend(), tt.begin(), [](auto x) {
112 return (x.isMonitor() || x.isMasked() || !x.hasDetectors() ? -1.0 : M_PI - x.twoTheta());
113 });
114 auto stopit = std::remove(tt.begin(), tt.end(), -1.0);
115 tt.erase(stopit, tt.end());
116 std::sort(tt.begin(), tt.end());
117
118 auto it = tt.begin();
119 while (it != tt.end()) {
120 size_t band = getBand(*it);
121 divs[band] = *it;
122 groups[band].push_back(++currentGroup);
123 groups[band + 1].push_back(currentGroup);
124 double next = *it + tt_step;
125 // cppcheck-suppress derefInvalidIterator
126 while ((*it) <= next && it != tt.end())
127 it++;
128 }
129 };
130
131 size_t operator()(SpectrumInfo const &spectrumInfo, size_t i) override {
132 double tt = M_PI - spectrumInfo.twoTheta(i);
133 size_t band = getBand(tt);
134 size_t index = groups[band].back();
135 if (tt < divs[band])
136 index = groups[band].front();
137 return index;
138 }
139};
140
141class SplitCircularOrderedLabelor : public Labelor {
145 const double inv_tt_step;
146
147 std::unordered_map<size_t, double> divs;
148 std::unordered_map<size_t, std::vector<size_t>> groups;
149 size_t currentGroup;
150
151 inline size_t getBand(const double tt) { return static_cast<size_t>(tt * inv_tt_step); };
152
153public:
154 SplitCircularOrderedLabelor(double t_step, SpectrumInfo const &spectrumInfo)
155 : Labelor(t_step), inv_tt_step(1.0 / tt_step), currentGroup(0) {
156 std::vector<double> xx(spectrumInfo.size());
157 std::transform(spectrumInfo.cbegin(), spectrumInfo.cend(), xx.begin(), [](auto x) {
158 return (x.isMonitor() || x.isMasked() || !x.hasDetectors() ? -1.0 : M_PI - x.signedTwoTheta());
159 });
160 auto stopit = std::remove(xx.begin(), xx.end(), -1.0);
161 xx.erase(stopit, xx.end());
162 std::transform(xx.cbegin(), xx.cend(), xx.begin(), [](double x) { return (x <= M_PI ? x : 3. * M_PI - x); });
163 std::sort(xx.begin(), xx.end());
164
165 auto it = xx.begin();
166 while (it != xx.end()) {
167 size_t band = getBand(*it);
168 divs[band] = *it;
169 groups[band].push_back(++currentGroup);
170 groups[band + 1].push_back(currentGroup);
171 double next = (*it) + tt_step;
172 // cppcheck-suppress derefInvalidIterator
173 while ((*it) <= next && it != xx.end())
174 it++;
175 }
176 };
177
178 size_t operator()(SpectrumInfo const &spectrumInfo, size_t i) override {
179 double tt = spectrumInfo.signedTwoTheta(i);
180 tt = (tt < 0 ? 2. * M_PI + tt : M_PI - tt);
181 size_t band = getBand(tt);
182 size_t index = groups[band].back();
183 if (tt < divs[band])
184 index = groups[band].front();
185 return (index);
186 }
187};
188
189} // anonymous namespace
190
191namespace Mantid::DataHandling {
192
193// Register the algorithm into the AlgorithmFactory
195
196
197const std::string GenerateGroupingPowder::name() const { return "GenerateGroupingPowder"; }
198
200int GenerateGroupingPowder::version() const { return 1; }
201
203const std::string GenerateGroupingPowder::category() const {
204 return R"(DataHandling\Grouping;Transforms\Grouping;Diffraction\Utility)";
205}
206
210 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input,
211 std::make_shared<InstrumentValidator>()),
212 "A workspace from which to generate the grouping.");
213 declareProperty(std::make_unique<WorkspaceProperty<GroupingWorkspace>>("GroupingWorkspace", "", Direction::Output,
215 "The grouping workspace created");
216
217 auto positiveDouble = std::make_shared<BoundedValidator<double>>();
218 positiveDouble->setLower(0.0);
219 declareProperty("AngleStep", -1.0, positiveDouble, "The polar angle step for grouping, in degrees.");
220
221 auto withinCircle = std::make_shared<BoundedValidator<double>>();
222 withinCircle->setLower(0.0);
223 withinCircle->setUpper(360.0);
224 withinCircle->setLowerExclusive(true);
225 declareProperty("AzimuthalStep", 360.0, withinCircle, "The azimuthal angle step for grouping, in degrees.");
226 auto withinDoubleCircle = std::make_shared<BoundedValidator<double>>();
227 withinDoubleCircle->setLower(-360.0);
228 withinDoubleCircle->setUpper(360.0);
229 withinDoubleCircle->setExclusive(true);
230 declareProperty("AzimuthalStart", 0.0, withinDoubleCircle, "The azimuthal angle start location, in degrees.");
231
232 declareProperty("NumberByAngle", true,
233 "If true, divide sphere into groups labeled by angular sector."
234 "Empty parts of the instrument will effectively have group numbers that do not exist in the output."
235 "If false, labels will be assigned in order of highest angle.");
236
237 // allow saving as either xml or hdf5 formats
238 auto fileExtensionXMLorHDF5 = std::make_shared<ListValidator<std::string>>();
239 fileExtensionXMLorHDF5->addAllowedValue(std::string("xml"));
240 fileExtensionXMLorHDF5->addAllowedValue(std::string("nxs"));
241 fileExtensionXMLorHDF5->addAllowedValue(std::string("nx5"));
242 declareProperty("FileFormat", std::string("xml"), fileExtensionXMLorHDF5,
243 "File extension/format for saving output: either xml (default) or nxs/nx5.");
244 declareProperty(std::make_unique<FileProperty>("GroupingFilename", "", FileProperty::OptionalSave,
245 std::vector<std::string>{"xml", "nxs", "nx5"}),
246 "A grouping file that will be created.");
247 declareProperty("GenerateParFile", true,
248 "If true, a par file with a corresponding name to the "
249 "grouping file will be generated. Ignored if grouping file is not specified.");
250}
251
252//----------------------------------------------------------------------------------------------
258std::map<std::string, std::string> GenerateGroupingPowder::validateInputs() {
259 std::map<std::string, std::string> issues;
260
261 const bool useAzimuthal = (double(getProperty("AzimuthalStep")) < 360.0);
262 const bool generateParFile = getProperty("GenerateParFile");
263 if (useAzimuthal && generateParFile) {
264 std::string noAzimuthInPar = "Cannot save a PAR file while using azimuthal grouping.";
265 issues["AzimuthalStep"] = noAzimuthInPar;
266 issues["GenerateParFile"] = noAzimuthInPar;
267 }
268
269 if (isDefault("GroupingWorkspace") && isDefault("GroupingFilename")) {
270 std::string err = "At least one of {'GroupingWorkspace', 'GroupingFilename'} must be passed.";
271 issues["GroupingWorkspace"] = err;
272 issues["GroupingFilename"] = err;
273 }
274
275 return issues;
276}
277
284
286 MatrixWorkspace_const_sptr input_ws = getProperty("InputWorkspace");
287 const auto &spectrumInfo = input_ws->spectrumInfo();
288 const auto &detectorInfo = input_ws->detectorInfo();
289 const auto &detectorIDs = detectorInfo.detectorIDs();
290
291 if (detectorIDs.empty())
292 throw std::invalid_argument("Workspace contains no detectors.");
293
294 const auto inst = input_ws->getInstrument();
295 if (!inst) { // validator should make this impossible
296 throw std::invalid_argument("Input Workspace has invalid Instrument\n");
297 }
298
299 this->m_groupWS = std::make_shared<GroupingWorkspace>(inst);
300 if (!isDefault("GroupingWorkspace")) {
301 this->setProperty("GroupingWorkspace", this->m_groupWS);
302 }
303
304 const double step = getProperty("AngleStep");
305
306 std::unique_ptr<Labelor> label;
307 bool numberByAngle = getProperty("NumberByAngle");
308 double azimuthalStep = getProperty("AzimuthalStep");
309 double azimuthalStart = getProperty("AzimuthalStart");
310 if (numberByAngle) {
311 if (azimuthalStep == 360.0)
312 label = std::make_unique<CircularSectorLabelor>(step);
313 else
314 label = std::make_unique<SphericalSectorLabelor>(step, azimuthalStep, azimuthalStart);
315 } else {
316 if (azimuthalStep == 360.0)
317 label = std::make_unique<CircularOrderedLabelor>(step, spectrumInfo);
318 else
319 label = std::make_unique<SplitCircularOrderedLabelor>(step, spectrumInfo);
320 }
321
322 for (size_t i = 0; i < spectrumInfo.size(); ++i) {
323 if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMasked(i) || spectrumInfo.isMonitor(i)) {
324 continue;
325 }
326
327 const auto &det = spectrumInfo.detector(i);
328 const double groupId = (double)(*label)(spectrumInfo, i);
329
330 if (spectrumInfo.hasUniqueDetector(i)) {
331 this->m_groupWS->setValue(det.getID(), groupId);
332 } else {
333 const auto &group = dynamic_cast<const DetectorGroup &>(det);
334 const auto idv = group.getDetectorIDs();
335 const auto ids = std::set<detid_t>(idv.begin(), idv.end());
336 this->m_groupWS->setValue(ids, groupId);
337 }
338 }
339}
340
342
343 // save if a filename was specified
344 if (!isDefault("GroupingFilename")) {
345
346 std::string ext = this->getProperty("FileFormat");
347 if (ext == "xml") {
348 this->saveAsXML();
349 } else if (ext == "nxs" || ext == "nx5") {
350 this->saveAsNexus();
351 } else {
352 throw std::invalid_argument("that file format doesn't exist: must be xml, nxs, nx5\n");
353 }
354
355 if (getProperty("GenerateParFile")) {
356 this->saveAsPAR();
357 }
358 }
359}
360
361// XML file
363 const std::string XMLfilename = this->getProperty("GroupingFilename");
364 // XML
365 AutoPtr<Document> pDoc = new Document;
366 AutoPtr<Element> pRoot = pDoc->createElement("detector-grouping");
367 pDoc->appendChild(pRoot);
368 pRoot->setAttribute("instrument", this->m_groupWS->getInstrument()->getName());
369
370 const double step = getProperty("AngleStep");
371 const auto numSteps = int(180. / step + 1);
372
373 for (int i = 0; i < numSteps; ++i) {
374 std::vector<detid_t> group = this->m_groupWS->getDetectorIDsOfGroup(i);
375 size_t gSize = group.size();
376 if (gSize > 0) {
377 std::stringstream spID, textvalue;
378 // try to preserve original behavior of previous method, labeling starting at 0
379 bool numberByAngle = this->getProperty("NumberByAngle");
380 if (numberByAngle)
381 spID << i - 1;
382 else
383 spID << i;
384
385 AutoPtr<Element> pChildGroup = pDoc->createElement("group");
386 pChildGroup->setAttribute("ID", spID.str());
387 pRoot->appendChild(pChildGroup);
388
389 std::copy(group.begin(), group.end(), std::ostream_iterator<size_t>(textvalue, ","));
390 std::string text = textvalue.str();
391 const size_t found = text.rfind(',');
392 if (found != std::string::npos) {
393 text.erase(found, 1); // erase the last comma
394 }
395
396 AutoPtr<Element> pDetid = pDoc->createElement("detids");
397 AutoPtr<Text> pText1 = pDoc->createTextNode(text);
398 pDetid->appendChild(pText1);
399 pChildGroup->appendChild(pDetid);
400 }
401 }
402
403 DOMWriter writer;
404 writer.setNewLine("\n");
405 writer.setOptions(XMLWriter::PRETTY_PRINT);
406
407 std::ofstream ofs(XMLfilename.c_str());
408 if (!ofs) {
409 g_log.error("Unable to create file: " + XMLfilename);
410 throw Exception::FileError("Unable to create file: ", XMLfilename);
411 }
412 ofs << "<?xml version=\"1.0\"?>\n";
413
414 writer.writeNode(ofs, pDoc);
415 ofs.close();
416}
417
418// HDF5 file
420 const std::string filename = this->getProperty("GroupingFilename");
421 auto saveNexus = createChildAlgorithm("SaveNexusProcessed");
422 saveNexus->setProperty("InputWorkspace", this->m_groupWS);
423 saveNexus->setProperty("Filename", filename);
424 saveNexus->executeAsChildAlg();
425}
426
427// PAR file
429 std::string PARfilename = getPropertyValue("GroupingFilename");
430 std::string ext = getProperty("FileFormat");
431 PARfilename = parFilenameFromXmlFilename(PARfilename);
432
433 std::ofstream outPAR_file(PARfilename.c_str());
434 if (!outPAR_file) {
435 g_log.error("Unable to create file: " + PARfilename);
436 throw Exception::FileError("Unable to create file: ", PARfilename);
437 }
438 MatrixWorkspace_const_sptr input_ws = getProperty("InputWorkspace");
439 const auto &spectrumInfo = input_ws->spectrumInfo();
440
441 const double step = getProperty("AngleStep");
442 const auto numSteps = static_cast<size_t>(180. / step + 1);
443
444 std::vector<std::vector<detid_t>> groups(numSteps);
445 std::vector<double> twoThetaAverage(numSteps, 0.);
446 std::vector<double> rAverage(numSteps, 0.);
447 // run through spectrums
448 for (size_t i = 0; i < spectrumInfo.size(); ++i) {
449 // skip invalid cases
450 if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMasked(i) || spectrumInfo.isMonitor(i)) {
451 continue;
452 }
453 const auto &det = spectrumInfo.detector(i);
454 // integer count angle slice
455 const double tt = spectrumInfo.twoTheta(i) * Geometry::rad2deg;
456 const auto where = static_cast<size_t>(tt / step);
457 // create these averages at this slice?
458 twoThetaAverage[where] += tt;
459 rAverage[where] += spectrumInfo.l2(i);
460 if (spectrumInfo.hasUniqueDetector(i)) {
461 groups[where].emplace_back(det.getID());
462 } else {
463 const auto &group = dynamic_cast<const DetectorGroup &>(det);
464 const auto idv = group.getDetectorIDs();
465 groups[where].insert(groups[where].end(), idv.begin(), idv.end());
466 }
467 }
468
469 size_t goodGroups(0);
470 for (size_t i = 0; i < numSteps; ++i) {
471 size_t gSize = groups.at(i).size();
472 if (gSize > 0)
473 ++goodGroups;
474 }
475
476 // Write the number of detectors to the file.
477 outPAR_file << " " << goodGroups << '\n';
478
479 for (size_t i = 0; i < numSteps; ++i) {
480 const size_t gSize = groups.at(i).size();
481 if (gSize > 0) {
482 outPAR_file << std::fixed << std::setprecision(3);
483 outPAR_file.width(10);
484 outPAR_file << rAverage.at(i) / static_cast<double>(gSize);
485 outPAR_file.width(10);
486 outPAR_file << twoThetaAverage.at(i) / static_cast<double>(gSize);
487 outPAR_file.width(10);
488 outPAR_file << 0.;
489 outPAR_file.width(10);
490 outPAR_file << step * Geometry::deg2rad * rAverage.at(i) / static_cast<double>(gSize);
491 outPAR_file.width(10);
492 outPAR_file << 0.01;
493 outPAR_file.width(10);
494 outPAR_file << (groups.at(i)).at(0) << '\n';
495 }
496 }
497
498 // Close the file
499 outPAR_file.close();
500}
501
502// static function to convert output filename to parameter filename
503// this assumes the file has a 3-letter extension so it works for
504// nexus files as well
505std::string GenerateGroupingPowder::parFilenameFromXmlFilename(const std::string &filename) {
506 const std::size_t EXT_SIZE{3};
507 std::string result(filename);
508 result.replace(result.end() - EXT_SIZE, result.end(), "par");
509 return result;
510}
511
512} // namespace Mantid::DataHandling
std::string name
Definition Run.cpp:60
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
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.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Kernel::Logger & g_log
Definition Algorithm.h:422
bool isDefault(const std::string &name) const
@ OptionalSave
to specify a file to write to but an empty string is
API::SpectrumInfo is an intermediate step towards a SpectrumInfo that is part of Instrument-2....
double signedTwoTheta(const size_t index) const
Returns the signed scattering angle 2 theta in radians (angle w.r.t.
bool isMonitor(const size_t index) const
Returns true if the detector(s) associated with the spectrum are monitors.
const SpectrumInfoIterator< const SpectrumInfo > cbegin() const
bool hasDetectors(const size_t index) const
Returns true if the spectrum is associated with detectors in the instrument.
double twoTheta(const size_t index) const
Returns the scattering angle 2 theta in radians (angle w.r.t.
double l2(const size_t index) const
Returns L2 (distance from sample to spectrum).
double azimuthal(const size_t index) const
Returns the out-of-plane angle in radians (angle w.r.t.
bool isMasked(const size_t index) const
Returns true if the detector(s) associated with the spectrum are masked.
bool hasUniqueDetector(const size_t index) const
Returns true if the spectrum is associated with exactly one detector.
const Geometry::IDetector & detector(const size_t index) const
Return a const reference to the detector or detector group of the spectrum with given index.
size_t size() const
Returns the size of the SpectrumInfo, i.e., the number of spectra.
const SpectrumInfoIterator< const SpectrumInfo > cend() const
A property class for workspaces.
GenerateGroupingPowder : Generate grouping file and par file, for powder scattering.
int version() const override
Algorithm's version for identification.
const std::string category() const override
Algorithm's category for identification.
std::map< std::string, std::string > validateInputs() override
validate inputs
void init() override
Initialize the algorithm's properties.
static std::string parFilenameFromXmlFilename(const std::string &filename)
DataObjects::GroupingWorkspace_sptr m_groupWS
Holds a collection of detectors.
std::vector< detid_t > getDetectorIDs() const
What detectors are contained in the group?
Records the filename and the description of failure.
Definition Exception.h:98
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
constexpr double deg2rad
Defines units/enum for Crystal work.
Definition AngleUnits.h:20
constexpr double rad2deg
Radians to degrees conversion factor.
Definition AngleUnits.h:23
Helper class which provides the Collimation Length for SANS instruments.
STL namespace.
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54