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 &componentInfo = inputWS->componentInfo();
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, componentInfo, *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 if (efixedProp != EMPTY_DBL()) {
324 efixed = efixedProp;
325 g_log.debug() << "Detector index: " << detectorIndex << " Efixed: " << efixed << "\n";
326 } else {
327 if (emode == 1) {
328 if (inputWS.run().hasProperty("Ei")) {
329 efixed = inputWS.run().getLogAsSingleValue("Ei");
330 } else {
331 throw std::invalid_argument("Could not retrieve Efixed from the "
332 "workspace. Please provide a value.");
333 }
334 } else if (emode == 2) {
335 std::vector<double> efixedVec = componentInfo.getNumberParameter(detectorIndex, "Efixed");
336
337 if (!efixedVec.empty()) {
338 efixed = efixedVec.at(0);
339 g_log.debug() << "Detector index: " << detectorIndex << " EFixed: " << efixed << "\n";
340 } else {
341 g_log.warning() << "Efixed could not be found for detector with index " << detectorIndex
342 << ", please provide a value\n";
343 throw std::invalid_argument("Could not retrieve Efixed from the "
344 "detector. Please provide a value.");
345 }
346 }
347 }
348 return efixed;
349}
350
355void ConvertSpectrumAxis2::emplaceIndexMap(double value, size_t wsIndex) {
356 if (m_toOrder) {
357 m_indexMap.emplace(value, wsIndex);
358 } else {
359 m_axis.emplace_back(value);
360 }
361}
362
363} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:547
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
IntArray detectorIndex
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:426
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
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.
double getLogAsSingleValue(const std::string &name, Kernel::Math::StatisticType statistic=Kernel::Math::Mean) const
Definition LogManager.h:160
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.
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.
double getEfixed(const size_t detectorIndex, const Mantid::Geometry::ComponentInfo &componentInfo, const API::MatrixWorkspace &inputWS, const int emode) const
Getting Efixed.
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.
ComponentInfo : Provides a component centric view on to the instrument.
std::vector< double > getNumberParameter(const size_t componentIndex, const std::string &name, bool recursive=true) const
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:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
static double convertToElasticQ(const double theta, const double efixed)
Convert to ElasticQ from Energy.
Class for 3D vectors.
Definition V3D.h:35
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
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition Property.h:54
@ Output
An output workspace.
Definition Property.h:55