Mantid
Loading...
Searching...
No Matches
ConvertSpectrumAxis2.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 +
10#include "MantidAPI/Run.h"
18#include "MantidHistogramData/Histogram.h"
19#include "MantidHistogramData/HistogramBuilder.h"
23#include "MantidKernel/Unit.h"
26#include "MantidTypes/SpectrumDefinition.h"
27
28#include <cfloat>
29
30constexpr double rad2deg = 180.0 / M_PI;
31
32namespace Mantid::Algorithms {
33// Register the algorithm into the AlgorithmFactory
34DECLARE_ALGORITHM(ConvertSpectrumAxis2)
35using namespace Kernel;
36using namespace API;
37using namespace Geometry;
38using namespace DataObjects;
39using namespace HistogramData;
40
42 // Validator for Input Workspace
43 auto wsVal = std::make_shared<CompositeValidator>();
44 wsVal->add<SpectraAxisValidator>();
45 wsVal->add<InstrumentValidator>();
46 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, wsVal),
47 "The name of the input workspace.");
48 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
49 "The name to use for the output workspace.");
50 std::vector<std::string> targetOptions{
51 "Theta", "SignedTheta", "InPlaneTwoTheta", "SignedInPlaneTwoTheta", "ElasticQ",
52 "ElasticQSquared", "theta", "signed_theta", "ElasticDSpacing"};
53 declareProperty("Target", "", std::make_shared<StringListValidator>(targetOptions),
54 "The unit to which spectrum axis is converted to - \"theta\" (for the "
55 "angle in degrees), Q or Q^2, where elastic Q is evaluated at EFixed. "
56 "InPlaneTwoTheta and SignedInPlaneTwoTheta are the angle when each point "
57 "is projected on the horizontal plane."
58 "Note that 'theta' and 'signed_theta' are there for compatibility "
59 "purposes; they are the same as 'Theta' and 'SignedTheta' respectively");
60 std::vector<std::string> eModeOptions;
61 eModeOptions.emplace_back("Direct");
62 eModeOptions.emplace_back("Indirect");
63 declareProperty("EMode", "Direct", std::make_shared<StringListValidator>(eModeOptions),
64 "Some unit conversions require this value to be set "
65 "(\"Direct\" or \"Indirect\")");
66 auto mustBePositive = std::make_shared<BoundedValidator<double>>();
67 mustBePositive->setLower(0.0);
68 declareProperty("EFixed", EMPTY_DBL(), mustBePositive,
69 "Value of fixed energy in meV : EI (EMode=Direct) or EF "
70 "(EMode=Indirect))");
71
72 declareProperty("OrderAxis", true,
73 "Whether or not to sort the resulting"
74 " spectrum axis.");
75}
76
78 // Get the input workspace.
79 API::MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
80 // Assign value to the member variable storing the number of histograms.
81 const size_t nHist = inputWS->getNumberHistograms();
82
83 // The unit to convert to.
84 const std::string unitTarget = getProperty("Target");
85
86 // Whether needs to be ordered
87 m_toOrder = getProperty("OrderAxis");
88 size_t nProgress = nHist;
89 if (m_toOrder) {
90 // we will need to loop twice, once to build the indexMap,
91 // once to copy over the spectra and set the output
92 nProgress *= 2;
93 } else {
94 m_axis.reserve(nHist);
95 }
96
97 Progress progress(this, 0.0, 1.0, nProgress);
98 // Call the functions to convert to the different forms of theta or Q.
99 if (unitTarget == "theta" || unitTarget == "Theta" || unitTarget == "signed_theta" || unitTarget == "SignedTheta" ||
100 unitTarget == "InPlaneTwoTheta" || unitTarget == "SignedInPlaneTwoTheta") {
101 createThetaMap(progress, unitTarget, inputWS);
102 } else if (unitTarget == "ElasticQ" || unitTarget == "ElasticQSquared" || unitTarget == "ElasticDSpacing") {
103 createElasticQMap(progress, unitTarget, inputWS);
104 }
105
106 // Create an output workspace and set the property for it.
107 MatrixWorkspace_sptr outputWS = createOutputWorkspace(progress, unitTarget, inputWS);
108 setProperty("OutputWorkspace", outputWS);
109}
110
116void ConvertSpectrumAxis2::createThetaMap(API::Progress &progress, const std::string &targetUnit,
117 API::MatrixWorkspace_sptr &inputWS) {
118 // Not sure about default, previously there was a call to a null function?
119 enum thetaTypes { theta, signedTheta, inPlaneTheta, signedInPlaneTheta };
120 thetaTypes thetaType = theta;
121 if (targetUnit == "signed_theta" || targetUnit == "SignedTheta") {
122 thetaType = signedTheta;
123 } else if (targetUnit == "theta" || targetUnit == "Theta") {
124 thetaType = theta;
125 } else if (targetUnit == "InPlaneTwoTheta") {
126 thetaType = inPlaneTheta;
127 } else if (targetUnit == "SignedInPlaneTwoTheta") {
128 thetaType = signedInPlaneTheta;
129 }
130 bool warningGiven = false;
131
132 const auto &spectrumInfo = inputWS->spectrumInfo();
133 for (size_t i = 0; i < spectrumInfo.size(); ++i) {
134 if (!spectrumInfo.hasDetectors(i)) {
135 if (!warningGiven)
136 g_log.warning("The instrument definition is incomplete - spectra "
137 "dropped from output");
138 warningGiven = true;
139 continue;
140 }
141 if (!spectrumInfo.isMonitor(i)) {
142 switch (thetaType) {
143 case signedTheta:
144 emplaceIndexMap(spectrumInfo.signedTwoTheta(i) * rad2deg, i);
145 break;
146 case theta:
147 emplaceIndexMap(spectrumInfo.twoTheta(i) * rad2deg, i);
148 break;
149 case inPlaneTheta:
150 emplaceIndexMap(inPlaneTwoTheta(i, inputWS) * rad2deg, i);
151 break;
152 case signedInPlaneTheta:
154 break;
155 }
156 } else {
157 emplaceIndexMap(0.0, i);
158 }
159
160 progress.report("Converting to theta...");
161 }
162}
163
172 const auto spectrumInfo = inputWS->spectrumInfo();
173 const auto refFrame = inputWS->getInstrument()->getReferenceFrame();
174 const V3D position = spectrumInfo.position(index) - spectrumInfo.samplePosition();
175
176 double angle =
177 std::atan2(std::abs(position[refFrame->pointingHorizontal()]), position[refFrame->pointingAlongBeam()]);
178 return angle;
179}
180
189 const auto spectrumInfo = inputWS->spectrumInfo();
190 const auto refFrame = inputWS->getInstrument()->getReferenceFrame();
191
192 const auto samplePos = spectrumInfo.samplePosition();
193 const auto beamLine = samplePos - spectrumInfo.sourcePosition();
194
195 if (beamLine.nullVector()) {
196 throw Kernel::Exception::InstrumentDefinitionError("Source and sample are at same position!");
197 }
198
199 const V3D sampleDetVec = spectrumInfo.position(index) - samplePos;
200
201 return std::atan2(sampleDetVec[refFrame->pointingHorizontal()], sampleDetVec[refFrame->pointingAlongBeam()]);
202}
203
209void ConvertSpectrumAxis2::createElasticQMap(API::Progress &progress, const std::string &targetUnit,
210 API::MatrixWorkspace_sptr &inputWS) {
211
212 const std::string emodeStr = getProperty("EMode");
213 int emode = 0;
214 if (emodeStr == "Direct")
215 emode = 1;
216 else if (emodeStr == "Indirect")
217 emode = 2;
218
219 const auto &spectrumInfo = inputWS->spectrumInfo();
220 const auto &detectorInfo = inputWS->detectorInfo();
221 const size_t nHist = spectrumInfo.size();
222 for (size_t i = 0; i < nHist; i++) {
223 double theta(0.0), efixed(0.0);
224 if (!spectrumInfo.isMonitor(i)) {
225 theta = 0.5 * spectrumInfo.twoTheta(i);
226 /*
227 * Two assumptions made in the following code.
228 * 1. Getting the detector index of the first detector in the spectrum
229 * definition is enough (this should be completely safe).
230 * 2. That the time index is not important (first element of pair only
231 * accessed). i.e we are not performing scanning. Step scanning is not
232 * supported at the time of writing.
233 */
234 const auto detectorIndex = spectrumInfo.spectrumDefinition(i)[0].first;
235 efixed = getEfixed(detectorIndex, detectorInfo, *inputWS,
236 emode); // get efixed
237 } else {
238 theta = DBL_MIN;
239 efixed = DBL_MIN;
240 }
241
242 // Convert to MomentumTransfer
243 double elasticQInAngstroms = Kernel::UnitConversion::convertToElasticQ(theta, efixed);
244
245 if (targetUnit == "ElasticQ") {
246 emplaceIndexMap(elasticQInAngstroms, i);
247 } else if (targetUnit == "ElasticQSquared") {
248 // The QSquared value.
249 double elasticQSquaredInAngstroms = elasticQInAngstroms * elasticQInAngstroms;
250
251 emplaceIndexMap(elasticQSquaredInAngstroms, i);
252 } else if (targetUnit == "ElasticDSpacing") {
253 double elasticDSpacing = 2 * M_PI / elasticQInAngstroms;
254 emplaceIndexMap(elasticDSpacing, i);
255 }
256
257 progress.report("Converting to " + targetUnit);
258 }
259}
260
269 API::MatrixWorkspace_sptr &inputWS) {
270
271 MatrixWorkspace_sptr outputWorkspace = nullptr;
272 std::unique_ptr<NumericAxis> newAxis = nullptr;
273 EventWorkspace_sptr eventWS = std::dynamic_pointer_cast<EventWorkspace>(inputWS);
274 if (m_toOrder) {
275 // Can not re-use the input one because the spectra are re-ordered.
276 const Histogram hist = eventWS ? Histogram(inputWS->binEdges(0)) : inputWS->histogram(0);
277 outputWorkspace = create<MatrixWorkspace>(*inputWS, m_indexMap.size(), hist);
278 std::vector<double> axis;
279 axis.reserve(m_indexMap.size());
280 std::transform(m_indexMap.begin(), m_indexMap.end(), std::back_inserter(axis),
281 [](const auto &it) { return it.first; });
282
283 newAxis = std::make_unique<NumericAxis>(std::move(axis));
284 } else {
285 // If there is no reordering we can simply clone.
286 outputWorkspace = inputWS->clone();
287 newAxis = std::make_unique<NumericAxis>(m_axis);
288 }
289
290 // Set the units of the axis.
291 if (targetUnit == "theta" || targetUnit == "Theta" || targetUnit == "signed_theta" || targetUnit == "SignedTheta" ||
292 targetUnit == "InPlaneTwoTheta" || targetUnit == "SignedInPlaneTwoTheta") {
293 newAxis->unit() = std::make_shared<Units::Degrees>();
294 } else if (targetUnit == "ElasticQ") {
295 newAxis->unit() = UnitFactory::Instance().create("MomentumTransfer");
296 } else if (targetUnit == "ElasticQSquared") {
297 newAxis->unit() = UnitFactory::Instance().create("QSquared");
298 } else if (targetUnit == "ElasticDSpacing") {
299 newAxis->unit() = UnitFactory::Instance().create("dSpacing");
300 }
301 outputWorkspace->replaceAxis(1, std::move(newAxis));
302 // Note that this is needed only for ordered case
303 if (m_toOrder) {
304 size_t currentIndex = 0;
305 std::multimap<double, size_t>::const_iterator it;
306 for (it = m_indexMap.begin(); it != m_indexMap.end(); ++it) {
307 // Copy over the data.
308 outputWorkspace->getSpectrum(currentIndex).copyDataFrom(inputWS->getSpectrum(it->second));
309 // We can keep the spectrum numbers etc.
310 outputWorkspace->getSpectrum(currentIndex).copyInfoFrom(inputWS->getSpectrum(it->second));
311 ++currentIndex;
312 progress.report("Setting output spectrum #" + std::to_string(currentIndex));
313 }
314 }
315
316 return outputWorkspace;
317}
318
320 const Mantid::API::MatrixWorkspace &inputWS, const int emode) const {
321 double efixed(0);
322 double efixedProp = getProperty("Efixed");
323 Mantid::detid_t detectorID = detectorInfo.detectorIDs()[detectorIndex];
324 if (efixedProp != EMPTY_DBL()) {
325 efixed = efixedProp;
326 g_log.debug() << "Detector: " << detectorID << " Efixed: " << efixed << "\n";
327 } else {
328 if (emode == 1) {
329 if (inputWS.run().hasProperty("Ei")) {
330 efixed = inputWS.run().getLogAsSingleValue("Ei");
331 } else {
332 throw std::invalid_argument("Could not retrieve Efixed from the "
333 "workspace. Please provide a value.");
334 }
335 } else if (emode == 2) {
336
337 const auto &detectorSingle = detectorInfo.detector(detectorIndex);
338
339 std::vector<double> efixedVec = detectorSingle.getNumberParameter("Efixed");
340
341 if (!efixedVec.empty()) {
342 efixed = efixedVec.at(0);
343 g_log.debug() << "Detector: " << detectorID << " EFixed: " << efixed << "\n";
344 } else {
345 g_log.warning() << "Efixed could not be found for detector " << detectorID << ", please provide a value\n";
346 throw std::invalid_argument("Could not retrieve Efixed from the "
347 "detector. Please provide a value.");
348 }
349 }
350 }
351 return efixed;
352}
353
358void ConvertSpectrumAxis2::emplaceIndexMap(double value, size_t wsIndex) {
359 if (m_toOrder) {
360 m_indexMap.emplace(value, wsIndex);
361 } else {
362 m_axis.emplace_back(value);
363 }
364}
365
366} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
constexpr double rad2deg
double value
The value of the point.
Definition: FitMW.cpp:51
double position
Definition: GetAllEi.cpp:154
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
IntArray detectorIndex
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
Kernel::Logger & g_log
Definition: Algorithm.h:451
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
const Run & run() const
Run details object access.
A validator which checks that a workspace has a valid instrument.
bool hasProperty(const std::string &name) const
Does the property exist on the object.
Definition: LogManager.cpp:265
double getLogAsSingleValue(const std::string &name, Kernel::Math::StatisticType statistic=Kernel::Math::Mean) const
Definition: LogManager.h:149
Base MatrixWorkspace Abstract Class.
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
A validator which checks whether the input workspace has the Spectra number in the axis.
A property class for workspaces.
std::vector< double > m_axis
Vector of axis in case ordering is not asked.
double getEfixed(const size_t detectorIndex, const Mantid::Geometry::DetectorInfo &detectorInfo, const API::MatrixWorkspace &inputWS, const int emode) const
Getting Efixed.
bool m_toOrder
Flag whether ordering is needed.
double signedInPlaneTwoTheta(const size_t index, const API::MatrixWorkspace_sptr &inputWS) const
Compute signed in plane two theta.
void createElasticQMap(API::Progress &progress, const std::string &targetUnit, API::MatrixWorkspace_sptr &inputWS)
Converting to Q and QSquared.
double inPlaneTwoTheta(const size_t index, const API::MatrixWorkspace_sptr &inputWS) const
Compute inPlaneTwoTheta.
void emplaceIndexMap(double value, size_t wsIndex)
Emplaces to value and the index pair into the map.
API::MatrixWorkspace_sptr createOutputWorkspace(API::Progress &progress, const std::string &targetUnit, API::MatrixWorkspace_sptr &inputWS)
Creates an output workspace.
std::multimap< double, size_t > m_indexMap
Map to which the conversion to the unit is stored.
void createThetaMap(API::Progress &progress, const std::string &targetUnit, API::MatrixWorkspace_sptr &inputWS)
Converting to theta.
void init() override
Initialisation code.
Geometry::DetectorInfo is an intermediate step towards a DetectorInfo that is part of Instrument-2....
Definition: DetectorInfo.h:49
const std::vector< detid_t > & detectorIDs() const
Returns a sorted vector of all detector IDs.
const Geometry::IDetector & detector(const size_t index) const
Return a const reference to the detector with given index.
virtual std::vector< double > getNumberParameter(const std::string &pname, bool recursive=true) const =0
Get a parameter defined as a double.
Exception for errors associated with the instrument definition.
Definition: Exception.h:220
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
static double convertToElasticQ(const double theta, const double efixed)
Convert to ElasticQ from Energy.
Class for 3D vectors.
Definition: V3D.h:34
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< EventWorkspace > EventWorkspace_sptr
shared pointer to the EventWorkspace class
constexpr double rad2deg
Radians to degrees conversion factor.
Definition: AngleUnits.h:23
int32_t detid_t
Typedef for a detector ID.
Definition: SpectrumInfo.h:21
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition: EmptyValues.h:43
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54