17#include <boost/algorithm/string/detail/classification.hpp>
18#include <boost/algorithm/string/split.hpp>
19#include <boost/algorithm/string/trim.hpp>
31void removeSpacesFromString(std::string &str) { str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end()); }
38void extendVectorBy(std::vector<std::string> &vec,
const std::vector<std::string> &extension) {
39 vec.reserve(vec.size() + std::distance(extension.cbegin(), extension.cend()));
40 vec.insert(vec.end(), extension.cbegin(), extension.cend());
50std::vector<std::string>
splitStringBy(
const std::string &str,
const std::string &delimiter) {
51 std::vector<std::string> subStrings;
52 boost::split(subStrings, str, boost::is_any_of(delimiter));
53 subStrings.erase(std::remove_if(subStrings.begin(), subStrings.end(),
54 [](
const std::string &subString) { return subString.empty(); }),
65bool hasSeparator(
const std::string &str,
const std::string &separator) {
66 return str.find(separator) != std::string::npos;
76std::vector<std::string> getDetectorRangeFromLimits(
int lower,
int upper) {
77 std::vector<std::string> detectorIds;
78 detectorIds.reserve(
static_cast<std::size_t
>(
upper -
lower + 1));
90std::vector<std::string> groupsFromColonRange(
const std::string &groupString) {
92 if (splitByColon.size() > 2)
93 throw std::runtime_error(
"Expected a single colon separator.");
95 if (splitByColon.size() == 2)
96 return getDetectorRangeFromLimits(std::stoi(splitByColon[0]), std::stoi(splitByColon[1]));
107std::vector<std::string> expandGroupsWithColonSeparator(
const std::vector<std::string> &groupsToExpand) {
108 std::vector<std::string> expandedGroupStrings;
109 for (
const auto &groupString : groupsToExpand)
110 extendVectorBy(expandedGroupStrings, groupsFromColonRange(groupString));
111 return expandedGroupStrings;
123void addDetectorToGroup(
const std::vector<detid_t> &allowedDetectorIDs, std::map<detid_t, int> &detectorIDToGroup,
124 int detectorID,
int groupID) {
125 const auto iter = std::find(allowedDetectorIDs.cbegin(), allowedDetectorIDs.cend(), detectorID);
126 if (iter == allowedDetectorIDs.cend())
127 throw std::runtime_error(
"The Detector ID '" +
std::to_string(detectorID) +
128 "' is not valid for this instrument component.");
130 detectorIDToGroup[detectorID] = groupID;
143void addDashSeparatedDetectorIDsToSameGroup(
const std::vector<detid_t> &allowedDetectorIDs,
144 std::map<detid_t, int> &detectorIDToGroup,
const std::string &groupString,
148 if (splitByDash.size() < 2)
149 throw std::runtime_error(
"Expected at least one dash separator.");
150 else if (splitByDash.size() > 2)
151 throw std::runtime_error(
"Expected a single dash separator.");
153 for (
auto i = std::stoi(splitByDash[0]); i <= std::stoi(splitByDash[1]); ++i)
154 addDetectorToGroup(allowedDetectorIDs, detectorIDToGroup, i, groupID);
167void addPlusSeparatedDetectorIDsToSameGroup(
const std::vector<detid_t> &allowedDetectorIDs,
168 std::map<detid_t, int> &detectorIDToGroup,
const std::string &groupString,
171 if (splitByPlus.size() < 2)
172 throw std::runtime_error(
"Expected at least one plus separator.");
174 for (
const auto &
id : splitByPlus)
175 addDetectorToGroup(allowedDetectorIDs, detectorIDToGroup, std::stoi(
id), groupID);
184std::vector<detid_t> getAllowedDetectorIDs(
const Instrument_const_sptr &instrument,
const std::string &componentName) {
185 std::vector<IDetector_const_sptr> detectors;
186 detectors.reserve(instrument->getNumberDetectors());
187 instrument->getDetectorsInBank(detectors, componentName);
189 std::vector<detid_t> detectorIDs;
190 detectorIDs.reserve(detectors.size());
191 std::transform(detectors.cbegin(), detectors.cend(), std::back_inserter(detectorIDs),
205std::map<detid_t, int> mapGroupingStringsToGroupIDs(
const std::vector<detid_t> &allowedDetectorIDs,
206 const std::vector<std::string> &groupingStrings) {
207 std::map<detid_t, int> detectorIDToGroup;
208 for (
auto j = 0; j < static_cast<int>(groupingStrings.size()); ++j) {
209 if (hasSeparator(groupingStrings[j],
"+"))
210 addPlusSeparatedDetectorIDsToSameGroup(allowedDetectorIDs, detectorIDToGroup, groupingStrings[j], j + 1);
211 else if (hasSeparator(groupingStrings[j],
"-"))
212 addDashSeparatedDetectorIDsToSameGroup(allowedDetectorIDs, detectorIDToGroup, groupingStrings[j], j + 1);
214 addDetectorToGroup(allowedDetectorIDs, detectorIDToGroup, std::stoi(groupingStrings[j]), j + 1);
216 return detectorIDToGroup;
228 const std::string &componentName, std::string &customGroupingString) {
229 removeSpacesFromString(customGroupingString);
231 const auto detectorIDs = getAllowedDetectorIDs(instrument, componentName);
232 const auto groupStrings = expandGroupsWithColonSeparator(
splitStringBy(customGroupingString,
","));
234 return mapGroupingStringsToGroupIDs(detectorIDs, groupStrings);
262 "Optional: An input workspace with the instrument we want to use.");
265 "Optional: Name of the instrument to base the "
266 "GroupingWorkspace on which to base the GroupingWorkspace.");
269 "Optional: Path to the instrument definition file on which "
270 "to base the GroupingWorkspace.");
273 "Optional: Path to the old-style .cal grouping/calibration "
274 "file (multi-column ASCII). You must also specify the "
278 "Optional: A string of the instrument component names to use "
279 "as separate groups. "
280 "Use / or , to separate multiple groups. "
281 "If empty, then an empty GroupingWorkspace will be created.");
283 std::vector<std::string> grouping{
"",
"All",
"Group",
"2_4Grouping",
"Column",
"bank"};
284 declareProperty(
"GroupDetectorsBy",
"", std::make_shared<StringListValidator>(grouping),
285 "Only used if GroupNames is empty");
286 declareProperty(
"MaxRecursionDepth", 5,
"Number of levels to search into the instrument (default=5)");
289 "Used to distribute the detectors of a given component into "
290 "a fixed number of groups");
292 "This takes a comma separated list of grouped detector IDs. An example "
293 "of the syntax is 1,2+3,4-6,7:10. The documentation page for this "
294 "algorithm gives a full explanation of this syntax.");
296 "Specify the instrument component to "
297 "group into a fixed number of groups");
300 "An output GroupingWorkspace.");
302 std::string inputs(
"Specify Instrument");
307 std::string groupby(
"Specify Grouping");
321 std::map<std::string, std::string> result;
324 int numInstrument = 0;
331 if (numInstrument == 0) {
332 std::string msg(
"Must supply an instrument");
333 result[
"InputWorkspace"] = msg;
334 result[
"InstrumentName"] = msg;
335 result[
"InstrumentFilename"] = msg;
336 }
else if (numInstrument > 1) {
337 std::string msg(
"Must supply an instrument only one way");
340 result[
"InputWorkspace"] = msg;
342 result[
"InstrumentName"] = msg;
344 result[
"InstrumentFilename"] = msg;
348 int numGroupings = 0;
355 if (numGroupings != 1) {
356 std::string msg(
"Must supply grouping only one way");
358 result[
"GroupNames"] = msg;
360 result[
"GroupDetectorsBy"] = msg;
362 result[
"ComponentName"] = msg;
365 std::string customGroupingString =
getPropertyValue(
"CustomGroupingString");
368 if (!componentName.empty() && !customGroupingString.empty()) {
370 (void)makeGroupingByCustomString(
getInstrument(), componentName, customGroupingString);
371 }
catch (
const std::runtime_error &ex) {
372 result[
"CustomGroupingString"] = ex.what();
387 std::ifstream grFile(groupingFileName.c_str());
388 if (!grFile.is_open()) {
391 std::map<detid_t, int> detIDtoGroup;
393 while (getline(grFile, str)) {
395 if (str.empty() || str[0] ==
'#')
397 std::istringstream istr(str);
398 int n, udet, sel, group;
400 istr >>
n >> udet >> offset >> sel >> group;
401 if ((sel) && (group > 0)) {
402 detIDtoGroup[udet] = group;
421 std::map<detid_t, int> detIDtoGroup;
424 std::vector<IDetector_const_sptr> detectors;
425 inst->getDetectorsInBank(detectors, compName);
426 size_t numDetectors = detectors.size();
429 if (numGroups >
static_cast<int>(numDetectors))
430 throw std::runtime_error(
"Number of groups must be less than or "
431 "equal to number of detectors");
434 int detectorsPerGroup =
static_cast<int>(numDetectors) / numGroups;
437 for (
unsigned int detIndex = 0; detIndex < numDetectors; detIndex++) {
438 int detectorID = detectors[detIndex]->getID();
439 int groupNum = (detIndex / detectorsPerGroup) + 1;
442 if (groupNum <= numGroups)
443 detIDtoGroup[detectorID] = groupNum;
460 std::string groupName = std::move(groupi);
462 groupName.erase(remove_if(groupName.begin(), groupName.end(), std::not_fn(::isdigit)), groupName.end());
465 groupName = std::move(groupj);
467 groupName.erase(remove_if(groupName.begin(), groupName.end(), std::not_fn(::isdigit)), groupName.end());
484 std::map<detid_t, int> detIDtoGroup;
487 std::vector<std::string> vgroups;
488 boost::split(vgroups, GroupNames, boost::algorithm::detail::is_any_ofF<char>(
",/*"));
489 while (vgroups.back().empty()) {
493 std::sort(vgroups.begin(), vgroups.end(),
groupnumber);
497 std::map<std::string, int> group_map;
499 for (
auto &vgroup : vgroups) {
501 group_map[vgroup] = ++
index;
505 if (!group_map.empty()) {
507 using sptr_ICompAss = std::shared_ptr<const Geometry::ICompAssembly>;
508 using sptr_IComp = std::shared_ptr<const Geometry::IComponent>;
509 using sptr_IDet = std::shared_ptr<const Geometry::IDetector>;
510 std::queue<std::pair<sptr_ICompAss, int>> assemblies;
511 sptr_ICompAss current = std::dynamic_pointer_cast<const Geometry::ICompAssembly>(inst);
512 sptr_IDet currentDet;
513 sptr_IComp currentIComp;
514 sptr_ICompAss currentchild;
516 int top_group, child_group;
519 top_group = group_map[current->getName()];
520 assemblies.emplace(current, top_group);
525 while (!assemblies.empty())
527 current = assemblies.front().first;
528 top_group = assemblies.front().second;
530 int nchilds = current->nelements();
532 for (
int i = 0; i < nchilds; ++i) {
533 currentIComp = (*(current.get()))[i];
534 currentDet = std::dynamic_pointer_cast<const Geometry::IDetector>(currentIComp);
535 if (currentDet.get())
538 detIDtoGroup[currentDet->getID()] = top_group;
542 currentchild = std::dynamic_pointer_cast<const Geometry::ICompAssembly>(currentIComp);
543 if (currentchild.get()) {
544 child_group = group_map[currentchild->getName()];
545 if (child_group == 0)
546 child_group = top_group;
547 assemblies.emplace(currentchild, child_group);
569 std::string customGroupingString =
getPropertyValue(
"CustomGroupingString");
576 if (!InstrumentName.empty())
578 if (!InstrumentFilename.empty())
582 throw std::invalid_argument(
"You must specify exactly ONE way to get an "
583 "instrument (workspace, instrument name, or "
584 "IDF file). You specified more than one.");
586 throw std::invalid_argument(
"You must specify exactly ONE way to get an "
587 "instrument (workspace, instrument name, or "
588 "IDF file). You specified none.");
590 if (!OldCalFilename.empty() && !GroupNames.empty())
591 throw std::invalid_argument(
"You must specify either to use the "
592 "OldCalFilename parameter OR GroupNames but "
595 bool sortnames =
false;
600 if (inst->getName() !=
"SNAP" && grouping ==
"2_4Grouping") {
601 const std::string message(
"2_4Grouping only works for SNAP.");
603 throw std::invalid_argument(message);
606 if (GroupNames.empty() && OldCalFilename.empty()) {
607 if (grouping ==
"All") {
608 GroupNames = inst->getName();
609 }
else if (inst->getName() ==
"SNAP" && grouping ==
"Group") {
610 GroupNames =
"East,West";
611 }
else if (inst->getName() ==
"POWGEN" && grouping ==
"Group") {
612 GroupNames =
"South,North";
613 }
else if (inst->getName() ==
"SNAP" && grouping ==
"2_4Grouping") {
614 GroupNames =
"Column1,Column2,Column3,Column4,Column5,Column6,";
618 int maxRecurseDepth = this->
getProperty(
"MaxRecursionDepth");
620 PRAGMA_OMP(parallel
for schedule(dynamic, 1) )
621 for (
int num = 0; num < 300; ++num) {
623 std::ostringstream mess;
624 mess << grouping << num;
628 GroupNames += mess.str() +
",";
636 auto outWS = std::make_shared<GroupingWorkspace>(inst);
640 std::map<detid_t, int> detIDtoGroup;
642 Progress prog(
this, 0.2, 1.0, outWS->getNumberHistograms());
644 if (!GroupNames.empty()) {
646 if (grouping ==
"2_4Grouping") {
647 std::map<detid_t, int>::const_iterator it_end = detIDtoGroup.end();
648 std::map<detid_t, int>::const_iterator it;
649 for (it = detIDtoGroup.begin(); it != it_end; ++it) {
651 detIDtoGroup[it->first] = 1;
653 detIDtoGroup[it->first] = 2;
657 }
else if (!OldCalFilename.empty())
659 else if ((numGroups > 0) && !componentName.empty())
661 else if (!customGroupingString.empty() && !componentName.empty()) {
663 detIDtoGroup = makeGroupingByCustomString(inst, componentName, customGroupingString);
664 }
catch (
const std::runtime_error &ex) {
670 g_log.
information() << detIDtoGroup.size() <<
" entries in the detectorID-to-group map.\n";
671 setProperty(
"NumberGroupedSpectraResult",
static_cast<int>(detIDtoGroup.size()));
673 if (detIDtoGroup.empty()) {
675 setProperty(
"NumberGroupsResult",
static_cast<int>(0));
677 size_t numNotFound = 0;
680 std::map<detid_t, int>::const_iterator it_end = detIDtoGroup.end();
681 std::map<detid_t, int>::const_iterator it;
682 std::unordered_set<int> groupCount;
683 for (it = detIDtoGroup.begin(); it != it_end; ++it) {
684 int detID = it->first;
685 int group = it->second;
686 groupCount.insert(group);
688 outWS->setValue(detID,
double(group));
689 }
catch (std::invalid_argument &) {
693 setProperty(
"NumberGroupsResult",
static_cast<int>(groupCount.size()));
696 g_log.
warning() << numNotFound <<
" detector IDs (out of " << detIDtoGroup.size()
697 <<
") were not found in the instrument\n.";
707 if (inputWorkspace) {
708 instrument = inputWorkspace->getInstrument();
713 childAlg->setPropertyValue(
"Filename", instrumentFilename);
715 childAlg->setPropertyValue(
"InstrumentName", instrumentName);
716 childAlg->executeAsChildAlg();
717 instrument = tempWS->getInstrument();
#define DECLARE_ALGORITHM(classname)
std::map< DeltaEMode::Type, std::string > index
#define PARALLEL_START_INTERRUPT_REGION
Begins a block to skip processing is the algorithm has been interupted Note the end of the block if n...
#define PARALLEL_CRITICAL(name)
#define PARALLEL_END_INTERRUPT_REGION
Ends a block to skip processing is the algorithm has been interupted Note the start of the block if n...
#define PRAGMA_OMP(expression)
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
double lower
lower and upper bounds on the multiplier, if known
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
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
Helper class for reporting progress from algorithms.
A property class for workspaces.
void init() override
Initialise the properties.
const std::string category() const override
Algorithm's category for identification.
std::map< std::string, std::string > validateInputs() override
Cross-check properties with each other.
const std::string name() const override
Algorithm's name for identification.
void exec() override
Run the algorithm.
Mantid::Geometry::Instrument_const_sptr getInstrument()
int version() const override
Algorithm's version for identification.
BoundedValidator is a validator that requires the values to be between upper or lower bounds,...
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 setPropertyGroup(const std::string &name, const std::string &group)
Set the group for a given property.
The Logger class is in charge of the publishing messages from the framework through various channels.
void error(const std::string &msg)
Logs at error level.
void warning(const std::string &msg)
Logs at warning level.
void information(const std::string &msg)
Logs at information level.
OptionalBool : Tri-state bool.
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
void setNumSteps(int64_t nsteps)
Change the number of steps between start/end.
The concrete, templated class for properties.
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< Algorithm > Algorithm_sptr
Typedef for a shared pointer to an Algorithm.
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::map< detid_t, int > makeGroupingByNumGroups(const std::string &compName, int numGroups, const Instrument_const_sptr &inst, Progress &prog)
Creates a mapping based on a fixed number of groups for a given instrument component.
std::map< detid_t, int > readGroupingFile(const std::string &groupingFileName, Progress &prog)
Read old-style .cal file to get the grouping.
std::map< detid_t, int > makeGroupingByNames(std::string GroupNames, const Instrument_const_sptr &inst, Progress &prog, bool sortnames)
Use bank names to build grouping.
bool groupnumber(std::string groupi, std::string groupj)
Use group numbers for sorting.
std::shared_ptr< const IComponent > IComponent_const_sptr
Typdef of a shared pointer to a const IComponent.
std::shared_ptr< const Mantid::Geometry::IDetector > IDetector_const_sptr
Shared pointer to IDetector (const version)
std::shared_ptr< const Instrument > Instrument_const_sptr
Shared pointer to an const instrument object.
int convert(const std::string &A, T &out)
Convert a string into a number.
Helper class which provides the Collimation Length for SANS instruments.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
@ Output
An output workspace.