Mantid
Loading...
Searching...
No Matches
PDFFourierTransform.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 +
8#include "MantidAPI/Axis.h"
9#include "MantidAPI/Run.h"
10#include "MantidAPI/Sample.h"
14#include "MantidHistogramData/Histogram.h"
15#include "MantidHistogramData/LinearGenerator.h"
20#include "MantidKernel/Unit.h"
22
23#include <cmath>
24#include <sstream>
25
26namespace Mantid::Algorithms {
27
28using std::string;
29using namespace HistogramData;
30
31// Register the algorithm into the AlgorithmFactory
32DECLARE_ALGORITHM(PDFFourierTransform)
33
34using namespace Mantid::Kernel;
35using namespace Mantid::API;
36using namespace DataObjects;
37
38namespace { // anonymous namespace
40const string BIG_G_OF_R("G(r)");
42const string LITTLE_G_OF_R("g(r)");
44const string RDF_OF_R("RDF(r)");
45
47const string S_OF_Q("S(Q)");
49const string S_OF_Q_MINUS_ONE("S(Q)-1");
51const string Q_S_OF_Q_MINUS_ONE("Q[S(Q)-1]");
52
53constexpr double TWO_OVER_PI(2. / M_PI);
54} // namespace
55
56const std::string PDFFourierTransform::name() const { return "PDFFourierTransform"; }
57
58int PDFFourierTransform::version() const { return 1; }
59
60const std::string PDFFourierTransform::category() const { return "Diffraction\\Utility"; }
61
65 auto uv = std::make_shared<API::WorkspaceUnitValidator>("MomentumTransfer");
66
67 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, uv),
68 S_OF_Q + ", " + S_OF_Q_MINUS_ONE + ", or " + Q_S_OF_Q_MINUS_ONE);
69 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
70 "Result paired-distribution function");
71
72 // Set up input data type
73 std::vector<std::string> inputTypes;
74 inputTypes.emplace_back(S_OF_Q);
75 inputTypes.emplace_back(S_OF_Q_MINUS_ONE);
76 inputTypes.emplace_back(Q_S_OF_Q_MINUS_ONE);
77 declareProperty("InputSofQType", S_OF_Q, std::make_shared<StringListValidator>(inputTypes),
78 "To identify whether input function");
79
80 auto mustBePositive = std::make_shared<BoundedValidator<double>>();
81 mustBePositive->setLower(0.0);
82
83 declareProperty("Qmin", EMPTY_DBL(), mustBePositive,
84 "Minimum Q in S(Q) to calculate in Fourier transform (optional).");
85 declareProperty("Qmax", EMPTY_DBL(), mustBePositive,
86 "Maximum Q in S(Q) to calculate in Fourier transform. (optional)");
87 declareProperty("Filter", false, "Set to apply Lorch function filter to the input");
88
89 // Set up output data type
90 std::vector<std::string> outputTypes;
91 outputTypes.emplace_back(BIG_G_OF_R);
92 outputTypes.emplace_back(LITTLE_G_OF_R);
93 outputTypes.emplace_back(RDF_OF_R);
94 declareProperty("PDFType", BIG_G_OF_R, std::make_shared<StringListValidator>(outputTypes),
95 "Type of output PDF including G(r)");
96
97 declareProperty("DeltaR", EMPTY_DBL(), mustBePositive,
98 "Step size of r of G(r) to calculate. Default = "
99 ":math:`\\frac{\\pi}{Q_{max}}`.");
100 declareProperty("Rmax", 20., mustBePositive, "Maximum r for G(r) to calculate.");
101 declareProperty("rho0", EMPTY_DBL(), mustBePositive,
102 "Average number density used for g(r) and RDF(r) conversions (optional)");
103
104 string recipGroup("Reciprocal Space");
105 setPropertyGroup("InputSofQType", recipGroup);
106 setPropertyGroup("Qmin", recipGroup);
107 setPropertyGroup("Qmax", recipGroup);
108 setPropertyGroup("Filter", recipGroup);
109
110 string realGroup("Real Space");
111 setPropertyGroup("PDFType", realGroup);
112 setPropertyGroup("DeltaR", realGroup);
113 setPropertyGroup("Rmax", realGroup);
114 setPropertyGroup("rho0", realGroup);
115}
116
117std::map<string, string> PDFFourierTransform::validateInputs() {
118 std::map<string, string> result;
119
120 double Qmin = getProperty("Qmin");
121 double Qmax = getProperty("Qmax");
122 if ((!isEmpty(Qmin)) && (!isEmpty(Qmax)))
123 if (Qmax <= Qmin)
124 result["Qmax"] = "Must be greater than Qmin";
125
126 // check for null pointers - this is to protect against workspace groups
127 API::MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
128 if (!inputWS) {
129 return result;
130 }
131
132 if (inputWS->getNumberHistograms() != 1) {
133 result["InputWorkspace"] = "Input workspace must have only one spectrum";
134 }
135
136 return result;
137}
138
139size_t PDFFourierTransform::determineQminIndex(const std::vector<double> &Q, const std::vector<double> &FofQ) {
140 double qmin = getProperty("Qmin");
141
142 // check against available Q-range
143 if (isEmpty(qmin)) {
144 qmin = Q.front();
145 } else if (qmin < Q.front()) {
146 g_log.information("Specified Qmin < range of data. Adjusting to data range.");
147 qmin = Q.front();
148 }
149
150 // get index for the Qmin from the Q-range
151 auto q_iter = std::upper_bound(Q.begin(), Q.end(), qmin);
152 size_t qmin_index = std::distance(Q.begin(), q_iter);
153 if (qmin_index == 0)
154 qmin_index += 1; // so there doesn't have to be a check in integration loop
155
156 // go to first non-nan value
157 q_iter = std::find_if(std::next(FofQ.begin(), qmin_index), FofQ.end(), static_cast<bool (*)(double)>(std::isnormal));
158 size_t first_normal_index = std::distance(FofQ.begin(), q_iter);
159 if (first_normal_index > qmin_index) {
160 g_log.information("Specified Qmin where data is nan/inf. Adjusting to data range.");
161 qmin_index = first_normal_index;
162 }
163
164 return qmin_index;
165}
166
167size_t PDFFourierTransform::determineQmaxIndex(const std::vector<double> &Q, const std::vector<double> &FofQ) {
168 double qmax = getProperty("Qmax");
169
170 // check against available Q-range
171 if (isEmpty(qmax)) {
172 qmax = Q.back();
173 } else if (qmax > Q.back()) {
174 g_log.information() << "Specified Qmax > range of data. Adjusting to data range.\n";
175 qmax = Q.back();
176 }
177
178 // get pointers for the data range
179 auto q_iter = std::lower_bound(Q.begin(), Q.end(), qmax);
180 size_t qmax_index = std::distance(Q.begin(), q_iter);
181
182 // go to first non-nan value
183 auto q_back_iter = std::find_if(FofQ.rbegin(), FofQ.rend(), static_cast<bool (*)(double)>(std::isnormal));
184 size_t first_normal_index = FofQ.size() - std::distance(FofQ.rbegin(), q_back_iter) - 1;
185 if (first_normal_index < qmax_index) {
186 g_log.information("Specified Qmax where data is nan/inf. Adjusting to data range.");
187 qmax_index = first_normal_index;
188 }
189
190 return qmax_index;
191}
192
194 double rho0 = getProperty("rho0");
195
196 if (isEmpty(rho0)) {
197 API::MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
198
199 const Kernel::Material &material = inputWS->sample().getMaterial();
200 double materialDensity = material.numberDensity();
201
202 if (!isEmpty(materialDensity) && materialDensity > 0)
203 rho0 = materialDensity;
204 else
205 rho0 = 1.;
206 }
207
208 return rho0;
209}
210
211//----------------------------------------------------------------------------------------------
215 // get input data
216 API::MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
217 auto inputQ = inputWS->x(0).rawData(); // x for input
218 HistogramData::HistogramDx inputDQ(inputQ.size(), 0.0); // dx for input
219 if (inputWS->sharedDx(0))
220 inputDQ = inputWS->dx(0);
221 auto inputFOfQ = inputWS->y(0).rawData(); // y for input
222 auto inputDfOfQ = inputWS->e(0).rawData(); // dy for input
223
224 // transform input data into Q/MomentumTransfer
225 const std::string inputXunit = inputWS->getAxis(0)->unit()->unitID();
226 if (inputXunit == "MomentumTransfer") {
227 // nothing to do
228 } else if (inputXunit == "dSpacing") {
229 // convert the x-units to Q/MomentumTransfer
230 const double PI_2(2. * M_PI);
231 std::for_each(inputQ.begin(), inputQ.end(), [&PI_2](double &Q) { Q /= PI_2; });
232 std::transform(inputDQ.begin(), inputDQ.end(), inputQ.begin(), inputDQ.begin(), std::divides<double>());
233 // reverse all of the arrays
234 std::reverse(inputQ.begin(), inputQ.end());
235 std::reverse(inputDQ.begin(), inputDQ.end());
236 std::reverse(inputFOfQ.begin(), inputFOfQ.end());
237 std::reverse(inputDfOfQ.begin(), inputDfOfQ.end());
238 } else {
239 std::stringstream msg;
240 msg << "Input data x-axis with unit \"" << inputXunit
241 << R"(" is not supported (use "MomentumTransfer" or "dSpacing"))";
242 throw std::invalid_argument(msg.str());
243 }
244 g_log.debug() << "Input unit is " << inputXunit << "\n";
245
246 // convert from histogram to density
247 if (!inputWS->isHistogramData()) {
248 g_log.warning() << "This algorithm has not been tested on density data "
249 "(only on histograms)\n";
250 /* Don't do anything for now
251 double deltaQ;
252 for (size_t i = 0; i < inputFOfQ.size(); ++i)
253 {
254 deltaQ = inputQ[i+1] -inputQ[i];
255 inputFOfQ[i] = inputFOfQ[i]*deltaQ;
256 inputDfOfQ[i] = inputDfOfQ[i]*deltaQ; // TODO feels wrong
257 inputQ[i] += -.5*deltaQ;
258 inputDQ[i] += .5*(inputDQ[i] + inputDQ[i+1]); // TODO running average
259 }
260 inputQ.emplace_back(inputQ.back()+deltaQ);
261 inputDQ.emplace_back(inputDQ.back()); // copy last value
262 */
263 }
264
265 // convert to Q[S(Q)-1]
266 string soqType = getProperty("InputSofQType");
267 if (soqType == S_OF_Q) {
268 g_log.information() << "Subtracting one from all values\n";
269 // there is no error propagation for subtracting one
270 std::for_each(inputFOfQ.begin(), inputFOfQ.end(), [](double &F) { F--; });
271 soqType = S_OF_Q_MINUS_ONE;
272 }
273 if (soqType == S_OF_Q_MINUS_ONE) {
274 g_log.information() << "Multiplying all values by Q\n";
275 // error propagation
276 for (size_t i = 0; i < inputDfOfQ.size(); ++i) {
277 inputDfOfQ[i] = inputQ[i] * inputDfOfQ[i] + inputFOfQ[i] * inputDQ[i];
278 }
279 // convert the function
280 std::transform(inputFOfQ.begin(), inputFOfQ.end(), inputQ.begin(), inputFOfQ.begin(), std::multiplies<double>());
281 soqType = Q_S_OF_Q_MINUS_ONE;
282 }
283 if (soqType != Q_S_OF_Q_MINUS_ONE) {
284 // should never get here
285 std::stringstream msg;
286 msg << "Do not understand InputSofQType = " << soqType;
287 throw std::runtime_error(msg.str());
288 }
289
290 // determine Q-range
291 size_t qmin_index = determineQminIndex(inputQ, inputFOfQ);
292 size_t qmax_index = determineQmaxIndex(inputQ, inputFOfQ);
293 g_log.notice() << "Adjusting to data: Qmin = " << inputQ[qmin_index] << " Qmax = " << inputQ[qmax_index] << "\n";
294
295 // determine r axis for result
296 const double rmax = getProperty("RMax");
297 double rdelta = getProperty("DeltaR");
298 if (isEmpty(rdelta))
299 rdelta = M_PI / inputQ[qmax_index];
300 auto sizer = static_cast<size_t>(rmax / rdelta);
301
302 bool filter = getProperty("Filter");
303
304 // create the output workspace
305 API::MatrixWorkspace_sptr outputWS = create<Workspace2D>(1, Points(sizer));
306 outputWS->getAxis(0)->unit() = UnitFactory::Instance().create("AtomicDistance");
307 outputWS->setYUnitLabel("PDF");
308
309 outputWS->mutableRun().addProperty("Qmin", inputQ[qmin_index], "Angstroms^-1", true);
310 outputWS->mutableRun().addProperty("Qmax", inputQ[qmax_index], "Angstroms^-1", true);
311
312 BinEdges edges(sizer + 1, LinearGenerator(rdelta, rdelta));
313 outputWS->setBinEdges(0, edges);
314
315 auto &outputR = outputWS->mutableX(0);
316 g_log.information() << "Using rmin = " << outputR.front() << "Angstroms and rmax = " << outputR.back()
317 << "Angstroms\n";
318 // always calculate G(r) then convert
319 auto &outputY = outputWS->mutableY(0);
320 auto &outputE = outputWS->mutableE(0);
321
322 // do the math
323 for (size_t r_index = 0; r_index < sizer; r_index++) {
324 const double r = outputR[r_index];
325 double fs = 0;
326 double error = 0;
327 for (size_t q_index = qmin_index; q_index < qmax_index; q_index++) {
328 const double q = inputQ[q_index];
329 const double deltaq = inputQ[q_index] - inputQ[q_index - 1];
330 double sinus = sin(q * r) * deltaq;
331
332 // multiply by filter function sin(q*pi/qmax)/(q*pi/qmax)
333 if (filter && q != 0) {
334 const double lorchKernel = q * M_PI / inputQ[qmax_index];
335 sinus *= sin(lorchKernel) / lorchKernel;
336 }
337 fs += sinus * inputFOfQ[q_index];
338 error += (sinus * inputDfOfQ[q_index]) * (sinus * inputDfOfQ[q_index]);
339 // g_log.debug() << "q[" << i << "] = " << q << " dq = " << deltaq << "
340 // S(q) =" << s;
341 // g_log.debug() << " d(gr) = " << temp << " gr = " << gr << '\n';
342 }
343
344 // put the information into the output
345 outputY[r_index] = fs * TWO_OVER_PI;
346 outputE[r_index] = sqrt(error) * TWO_OVER_PI;
347 }
348
349 // convert to the correct form of PDF
350 string pdfType = getProperty("PDFType");
351
352 double rho0 = determineRho0();
353 if (pdfType == LITTLE_G_OF_R || pdfType == RDF_OF_R)
354 g_log.information() << "Using rho0 = " << rho0 << "\n";
355
356 if (pdfType == BIG_G_OF_R) {
357 // nothing to do
358 } else if (pdfType == LITTLE_G_OF_R) {
359 const double factor = 1. / (4. * M_PI * rho0);
360 for (size_t i = 0; i < outputY.size(); ++i) {
361 // error propagation - assuming uncertainty in r = 0
362 outputE[i] = outputE[i] / outputR[i];
363 // transform the data
364 outputY[i] = 1. + factor * outputY[i] / outputR[i];
365 }
366 } else if (pdfType == RDF_OF_R) {
367 const double factor = 4. * M_PI * rho0;
368 for (size_t i = 0; i < outputY.size(); ++i) {
369 // error propagation - assuming uncertainty in r = 0
370 outputE[i] = outputE[i] * outputR[i];
371 // transform the data
372 outputY[i] = outputR[i] * outputY[i] + factor * outputR[i] * outputR[i];
373 }
374 } else {
375 // should never get here
376 std::stringstream msg;
377 msg << "Do not understand PDFType = " << pdfType;
378 throw std::runtime_error(msg.str());
379 }
380
381 // set property
382 setProperty("OutputWorkspace", outputWS);
383}
384
385} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
double error
Definition: IndexPeaks.cpp:133
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
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
A property class for workspaces.
int version() const override
Algorithm's version for identification.
const std::string category() const override
Algorithm's category for identification.
size_t determineQmaxIndex(const std::vector< double > &Q, const std::vector< double > &FofQ)
void exec() override
Run the algorithm.
const std::string name() const override
Algorithm's name for identification.
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
size_t determineQminIndex(const std::vector< double > &Q, const std::vector< double > &FofQ)
void init() override
Initialize the properties.
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.
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void notice(const std::string &msg)
Logs at notice level.
Definition: Logger.cpp:95
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
A material is defined as being composed of a given element, defined as a PhysicalConstants::NeutronAt...
Definition: Material.h:50
double numberDensity() const
Get the number density.
Definition: Material.cpp:189
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition: EmptyValues.h:43
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54