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>
38 const double aa_start;
41 Labelor(
double t_step,
double a_step = 0.0,
double a_start = 0.0)
44 virtual ~Labelor() {};
45 virtual size_t operator()(
SpectrumInfo const &spectrumInfo,
size_t i) = 0;
48class CircularSectorLabelor :
public Labelor {
56 const double inv_tt_step;
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;
65class SphericalSectorLabelor :
public Labelor {
73 const double inv_tt_step;
74 const double inv_aa_step;
75 const int num_aa_step;
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);
88class CircularOrderedLabelor :
public Labelor {
98 std::unordered_map<size_t, double> divs;
99 std::unordered_map<size_t, std::vector<size_t>> groups;
101 const double inv_tt_step;
104 inline size_t getBand(
const double x) {
return static_cast<int>(
x * inv_tt_step); };
107 CircularOrderedLabelor(
double t_step,
SpectrumInfo const &spectrumInfo)
108 : Labelor(t_step), inv_tt_step(1.0 / tt_step), currentGroup(0) {
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());
114 auto stopit = std::remove(tt.begin(), tt.end(), -1.0);
115 tt.erase(stopit, tt.end());
116 std::sort(tt.begin(), tt.end());
118 auto it = tt.begin();
119 while (it != tt.end()) {
120 size_t band = getBand(*it);
122 groups[band].push_back(++currentGroup);
123 groups[band + 1].push_back(currentGroup);
124 double next = *it + tt_step;
126 while ((*it) <= next && it != tt.end())
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();
136 index = groups[band].front();
141class SplitCircularOrderedLabelor :
public Labelor {
145 const double inv_tt_step;
147 std::unordered_map<size_t, double> divs;
148 std::unordered_map<size_t, std::vector<size_t>> groups;
151 inline size_t getBand(
const double tt) {
return static_cast<size_t>(tt * inv_tt_step); };
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());
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());
165 auto it = xx.begin();
166 while (it != xx.end()) {
167 size_t band = getBand(*it);
169 groups[band].push_back(++currentGroup);
170 groups[band + 1].push_back(currentGroup);
171 double next = (*it) + tt_step;
173 while ((*it) <= next && it != xx.end())
178 size_t operator()(
SpectrumInfo const &spectrumInfo,
size_t i)
override {
180 tt = (tt < 0 ? 2. * M_PI + tt : M_PI - tt);
181 size_t band = getBand(tt);
182 size_t index = groups[band].back();
184 index = groups[band].front();
204 return R
"(DataHandling\Grouping;Transforms\Grouping;Diffraction\Utility)";
211 std::make_shared<InstrumentValidator>()),
212 "A workspace from which to generate the grouping.");
215 "The grouping workspace created");
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.");
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.");
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.");
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.");
245 std::vector<std::string>{
"xml",
"nxs",
"nx5"}),
246 "A grouping file that will be created.");
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.");
259 std::map<std::string, std::string> issues;
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;
270 std::string err =
"At least one of {'GroupingWorkspace', 'GroupingFilename'} must be passed.";
271 issues[
"GroupingWorkspace"] = err;
272 issues[
"GroupingFilename"] = err;
287 const auto &spectrumInfo = input_ws->spectrumInfo();
288 const auto &detectorInfo = input_ws->detectorInfo();
289 const auto &detectorIDs = detectorInfo.detectorIDs();
291 if (detectorIDs.empty())
292 throw std::invalid_argument(
"Workspace contains no detectors.");
294 const auto inst = input_ws->getInstrument();
296 throw std::invalid_argument(
"Input Workspace has invalid Instrument\n");
299 this->
m_groupWS = std::make_shared<GroupingWorkspace>(inst);
306 std::unique_ptr<Labelor> label;
308 double azimuthalStep =
getProperty(
"AzimuthalStep");
309 double azimuthalStart =
getProperty(
"AzimuthalStart");
311 if (azimuthalStep == 360.0)
312 label = std::make_unique<CircularSectorLabelor>(step);
314 label = std::make_unique<SphericalSectorLabelor>(step, azimuthalStep, azimuthalStart);
316 if (azimuthalStep == 360.0)
317 label = std::make_unique<CircularOrderedLabelor>(step, spectrumInfo);
319 label = std::make_unique<SplitCircularOrderedLabelor>(step, spectrumInfo);
322 for (
size_t i = 0; i < spectrumInfo.
size(); ++i) {
327 const auto &det = spectrumInfo.
detector(i);
328 const double groupId = (double)(*label)(spectrumInfo, i);
331 this->
m_groupWS->setValue(det.getID(), groupId);
335 const auto ids = std::set<detid_t>(idv.begin(), idv.end());
349 }
else if (ext ==
"nxs" || ext ==
"nx5") {
352 throw std::invalid_argument(
"that file format doesn't exist: must be xml, nxs, nx5\n");
363 const std::string XMLfilename = this->
getProperty(
"GroupingFilename");
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());
371 const auto numSteps = int(180. / step + 1);
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();
377 std::stringstream spID, textvalue;
379 bool numberByAngle = this->
getProperty(
"NumberByAngle");
385 AutoPtr<Element> pChildGroup = pDoc->createElement(
"group");
386 pChildGroup->setAttribute(
"ID", spID.str());
387 pRoot->appendChild(pChildGroup);
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);
396 AutoPtr<Element> pDetid = pDoc->createElement(
"detids");
397 AutoPtr<Text> pText1 = pDoc->createTextNode(text);
398 pDetid->appendChild(pText1);
399 pChildGroup->appendChild(pDetid);
404 writer.setNewLine(
"\n");
405 writer.setOptions(XMLWriter::PRETTY_PRINT);
407 std::ofstream ofs(XMLfilename.c_str());
409 g_log.
error(
"Unable to create file: " + XMLfilename);
412 ofs <<
"<?xml version=\"1.0\"?>\n";
414 writer.writeNode(ofs, pDoc);
420 const std::string filename = this->
getProperty(
"GroupingFilename");
422 saveNexus->setProperty(
"InputWorkspace", this->
m_groupWS);
423 saveNexus->setProperty(
"Filename", filename);
424 saveNexus->executeAsChildAlg();
433 std::ofstream outPAR_file(PARfilename.c_str());
435 g_log.
error(
"Unable to create file: " + PARfilename);
439 const auto &spectrumInfo = input_ws->spectrumInfo();
442 const auto numSteps =
static_cast<size_t>(180. / step + 1);
444 std::vector<std::vector<detid_t>> groups(numSteps);
445 std::vector<double> twoThetaAverage(numSteps, 0.);
446 std::vector<double> rAverage(numSteps, 0.);
448 for (
size_t i = 0; i < spectrumInfo.
size(); ++i) {
453 const auto &det = spectrumInfo.
detector(i);
456 const auto where =
static_cast<size_t>(tt / step);
458 twoThetaAverage[where] += tt;
459 rAverage[where] += spectrumInfo.
l2(i);
461 groups[where].emplace_back(det.getID());
465 groups[where].insert(groups[where].end(), idv.begin(), idv.end());
469 size_t goodGroups(0);
470 for (
size_t i = 0; i < numSteps; ++i) {
471 size_t gSize = groups.at(i).size();
477 outPAR_file <<
" " << goodGroups <<
'\n';
479 for (
size_t i = 0; i < numSteps; ++i) {
480 const size_t gSize = groups.at(i).size();
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);
489 outPAR_file.width(10);
490 outPAR_file << step *
Geometry::deg2rad * rAverage.at(i) /
static_cast<double>(gSize);
491 outPAR_file.width(10);
493 outPAR_file.width(10);
494 outPAR_file << (groups.at(i)).at(0) <<
'\n';
506 const std::size_t EXT_SIZE{3};
507 std::string result(filename);
508 result.replace(result.end() - EXT_SIZE, result.end(),
"par");
#define DECLARE_ALGORITHM(classname)
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.
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.
void exec() override
Execute the algorithm.
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.
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.
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.
constexpr double rad2deg
Radians to degrees conversion factor.
Helper class which provides the Collimation Length for SANS instruments.
@ Input
An input workspace.
@ Output
An output workspace.