Mantid
Loading...
Searching...
No Matches
Qxy.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 +
11#include "MantidAPI/Run.h"
19#include "MantidHistogramData/LinearGenerator.h"
24
25#include <algorithm>
26#include <cmath>
27
28namespace Mantid::Algorithms {
29
30// Register the algorithm into the AlgorithmFactory
32
33using namespace Kernel;
34using namespace API;
35using namespace Geometry;
36
37void Qxy::init() {
38 auto wsValidator = std::make_shared<CompositeValidator>();
39 wsValidator->add<WorkspaceUnitValidator>("Wavelength");
40 wsValidator->add<HistogramValidator>();
41 wsValidator->add<InstrumentValidator>();
42
43 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, wsValidator),
44 "The corrected data in units of wavelength.");
45 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
46 "The name to use for the corrected workspace.");
47
48 auto mustBePositive = std::make_shared<BoundedValidator<double>>();
49 mustBePositive->setLower(1.0e-12);
50 auto mustBeIntOver2 = std::make_shared<BoundedValidator<int>>();
51 mustBeIntOver2->setLower(3);
52
53 declareProperty("MaxQxy", -1.0, mustBePositive, "The upper limit of the Qx-Qy grid (goes from -MaxQxy to +MaxQxy).");
54 declareProperty("DeltaQ", -1.0, mustBePositive, "The dimension of a Qx-Qy cell.");
55 declareProperty("IQxQyLogBinning", false, "I(qx,qy) log binning when binning is not specified.",
58 "The scaling to apply to each spectrum e.g. for detector "
59 "efficiency, must have just one bin per spectrum and the "
60 "same number of spectra as DetBankWorkspace.");
61 auto wavVal = std::make_shared<CompositeValidator>();
62 wavVal->add<WorkspaceUnitValidator>("Wavelength");
63 wavVal->add<HistogramValidator>();
65 std::make_unique<WorkspaceProperty<>>("WavelengthAdj", "", Direction::Input, PropertyMode::Optional, wavVal),
66 "The scaling to apply to each bin to account for monitor "
67 "counts, transmission fraction, etc. Must be one spectrum "
68 "with the same binning as the InputWorkspace, the same units "
69 "(counts) and the same [[ConvertToDistribution|distribution "
70 "status]].");
71 declareProperty("AccountForGravity", false, "Whether to correct for the effects of gravity.", Direction::Input);
72 declareProperty("SolidAngleWeighting", true, "If true, pixels will be weighted by their solid angle.",
74 auto mustBePositive2 = std::make_shared<BoundedValidator<double>>();
75 mustBePositive2->setLower(0.0);
76 declareProperty("RadiusCut", 0.0, mustBePositive2,
77 "The minimum distance in mm from the beam center at which "
78 "all wavelengths are used in the calculation (see section "
79 "[[Q1D#Resolution and Cutoffs|Resolution and Cutoffs]])");
80 declareProperty("WaveCut", 0.0, mustBePositive2,
81 "The shortest wavelength in angstrom at which counts should "
82 "be summed from all detector pixels (see section "
83 "[[Q1D#Resolution and Cutoffs|Resolution and Cutoffs]])");
84 declareProperty("OutputParts", false,
85 "Set to true to output two additional workspaces which will "
86 "have the names OutputWorkspace_sumOfCounts "
87 "OutputWorkspace_sumOfNormFactors. The division of "
88 "_sumOfCounts and _sumOfNormFactors equals the workspace "
89 "returned by the property OutputWorkspace");
90 declareProperty("ExtraLength", 0.0, mustBePositive2, "Additional length for gravity correction.");
92 "SolidAngleNumberOfCylinderSlices", 10, mustBeIntOver2,
93 "The number of angular slices used when triangulating a cylinder in order to calculate the solid "
94 "angle of a tube detector. The default is 10 to preserve legacy behaviour, but increased accuracy has "
95 "been observed when using values of 11+");
96}
97
98void Qxy::exec() {
99 MatrixWorkspace_const_sptr inputWorkspace = getProperty("InputWorkspace");
100 MatrixWorkspace_const_sptr waveAdj = getProperty("WavelengthAdj");
101 MatrixWorkspace_const_sptr pixelAdj = getProperty("PixelAdj");
102 const bool doGravity = getProperty("AccountForGravity");
103 const bool doSolidAngle = getProperty("SolidAngleWeighting");
104
105 // throws if we don't have common binning or another incompatibility
106 Qhelper helper;
107 helper.examineInput(inputWorkspace, waveAdj, pixelAdj);
108 g_log.debug() << "All input workspaces were found to be valid\n";
109
110 // Create the output Qx-Qy grid
111 MatrixWorkspace_sptr outputWorkspace = this->setUpOutputWorkspace(inputWorkspace);
112
113 // Will also need an identically-sized workspace to hold the solid angle/time
114 // bin masked weight
115 MatrixWorkspace_sptr weights = WorkspaceFactory::Instance().create(outputWorkspace);
116 // Copy the X values from the output workspace to the solidAngles one
117 for (size_t i = 0; i < weights->getNumberHistograms(); ++i)
118 weights->setSharedX(i, outputWorkspace->sharedX(0));
119
120 const size_t numSpec = inputWorkspace->getNumberHistograms();
121 const size_t numBins = inputWorkspace->blocksize();
122
123 // Set the progress bar (1 update for every one percent increase in progress)
124 Progress prog(this, 0.05, 1.0, numSpec);
125
126 const auto &spectrumInfo = inputWorkspace->spectrumInfo();
127 const auto &detectorInfo = inputWorkspace->detectorInfo();
128
129 // the samplePos is often not (0, 0, 0) because the instruments components are
130 // moved to account for the beam centre
131 const V3D samplePos = spectrumInfo.samplePosition();
132
133 for (int64_t i = 0; i < int64_t(numSpec); ++i) {
134 if (!spectrumInfo.hasDetectors(i)) {
135 g_log.warning() << "Workspace index " << i << " has no detector assigned to it - discarding\n";
136 continue;
137 }
138 // If no detector found or if it's masked or a monitor, skip onto the next
139 // spectrum
140 if (spectrumInfo.isMonitor(i) || spectrumInfo.isMasked(i))
141 continue;
142
143 // get the bins that are included inside the RadiusCut/WaveCutcut off, those
144 // to calculate for
145 const size_t wavStart =
146 helper.waveLengthCutOff(inputWorkspace, spectrumInfo, getProperty("RadiusCut"), getProperty("WaveCut"), i);
147 if (wavStart >= inputWorkspace->y(i).size()) {
148 // all the spectra in this detector are out of range
149 continue;
150 }
151
152 V3D detPos = spectrumInfo.position(i) - samplePos;
153
154 // these will be re-calculated if gravity is on but without gravity there is
155 // no need
156 double phi = atan2(detPos.Y(), detPos.X());
157 double a = cos(phi);
158 double b = sin(phi);
159 double sinTheta = sin(spectrumInfo.twoTheta(i) * 0.5);
160
161 // Get references to the data for this spectrum
162 const auto &X = inputWorkspace->x(i);
163 const auto &Y = inputWorkspace->y(i);
164 const auto &E = inputWorkspace->e(i);
165
166 const auto &axis = outputWorkspace->x(0);
167
168 // the solid angle of the detector as seen by the sample is used for
169 // normalisation later on
170 const int numberOfCylinderSlices = getProperty("SolidAngleNumberOfCylinderSlices");
171 double angle = 0.0;
172 for (const auto detID : inputWorkspace->getSpectrum(i).getDetectorIDs()) {
173 const auto index = detectorInfo.indexOf(detID);
174 if (!detectorInfo.isMasked(index))
175 angle += detectorInfo.detector(index).solidAngle({samplePos, numberOfCylinderSlices});
176 }
177
178 // some bins are masked completely or partially, the following vector will
179 // contain the fractions
180 std::vector<double> maskFractions;
181 if (inputWorkspace->hasMaskedBins(i)) {
182 // go through the set and convert it to a vector
183 const MatrixWorkspace::MaskList &mask = inputWorkspace->maskedBins(i);
184 maskFractions.resize(numBins, 1.0);
185 MatrixWorkspace::MaskList::const_iterator it, itEnd(mask.end());
186 for (it = mask.begin(); it != itEnd; ++it) {
187 // The weight for this masked bin is 1 minus the degree to which this
188 // bin is masked
189 maskFractions[it->first] -= it->second;
190 }
191 }
192 double maskFraction(1);
193
194 // this object is not used if gravity correction is off, but it is only
195 // constructed once per spectrum
197 if (doGravity) {
198 grav = GravitySANSHelper(spectrumInfo, i, getProperty("ExtraLength"));
199 }
200
201 for (int j = static_cast<int>(numBins) - 1; j >= static_cast<int>(wavStart); --j) {
202 if (j < 0)
203 break; // Be careful with counting down. Need a better fix but this will
204 // work for now
205 const double binWidth = X[j + 1] - X[j];
206 // Calculate the wavelength at the mid-point of this bin
207 const double wavLength = X[j] + (binWidth) / 2.0;
208
209 if (doGravity) {
210 // SANS instruments must have their y-axis pointing up, show the
211 // detector position as where the neutron would be without gravity
212 sinTheta = grav.calcComponents(wavLength, a, b);
213 }
214
215 // Calculate |Q| for this bin
216 const double Q = 4.0 * M_PI * sinTheta / wavLength;
217
218 // Now get the x & y components of Q.
219 const double Qx = a * Q;
220 // Test whether they're in range, if not go to next spectrum.
221 if (Qx < axis.front() || Qx >= axis.back())
222 break;
223 const double Qy = b * Q;
224 if (Qy < axis.front() || Qy >= axis.back())
225 break;
226 // Find the indices pointing to the place in the 2D array where this bin's
227 // contents should go
228 const auto xIndex = std::upper_bound(axis.begin(), axis.end(), Qx) - axis.begin() - 1;
229 const int yIndex = static_cast<int>(std::upper_bound(axis.begin(), axis.end(), Qy) - axis.begin() - 1);
230 // PARALLEL_CRITICAL(qxy) /* Write to shared memory - must protect
231 // */
232 {
233 // the data will be copied to this bin in the output array
234 double &outputBinY = outputWorkspace->mutableY(yIndex)[xIndex];
235 double &outputBinE = outputWorkspace->mutableE(yIndex)[xIndex];
236
237 if (std::isnan(outputBinY)) {
238 outputBinY = outputBinE = 0;
239 }
240 // Add the contents of the current bin to the 2D array.
241 outputBinY += Y[j];
242 // add the errors in quadranture
243 outputBinE = std::sqrt((outputBinE * outputBinE) + (E[j] * E[j]));
244
245 // account for masked bins
246 if (!maskFractions.empty()) {
247 maskFraction = maskFractions[j];
248 }
249 // add the total weight for this bin in the weights workspace,
250 // in an equivalent bin to where the data was stored
251
252 // first take into account the product of contributions to the weight
253 // which have no errors
254 double weight = 0.0;
255 if (doSolidAngle)
256 weight = maskFraction * angle;
257 else
258 weight = maskFraction;
259
260 // then the product of contributions which have errors, i.e. optional
261 // pixelAdj and waveAdj contributions
262 auto &outWeightsY = weights->mutableY(yIndex);
263 auto &outWeightsE = weights->mutableE(yIndex);
264
265 if (pixelAdj && waveAdj) {
266 auto pixelY = pixelAdj->y(i)[0];
267 auto pixelE = pixelAdj->e(i)[0];
268
269 auto waveY = waveAdj->y(0)[j];
270 auto waveE = waveAdj->e(0)[j];
271
272 outWeightsY[xIndex] += weight * pixelY * waveY;
273 const double pixelYSq = pixelY * pixelY;
274 const double pixelESq = pixelE * pixelE;
275 const double waveYSq = waveY * waveY;
276 const double waveESq = waveE * waveE;
277 // add product of errors from pixelAdj and waveAdj (note no error on
278 // weight is assumed)
279 outWeightsE[xIndex] += weight * weight * (waveESq * pixelYSq + pixelESq * waveYSq);
280 } else if (pixelAdj) {
281 auto pixelY = pixelAdj->y(i)[0];
282 auto pixelE = pixelAdj->e(i)[0];
283
284 outWeightsY[xIndex] += weight * pixelY;
285 const double pixelESq = weight * pixelE;
286 // add error from pixelAdj
287 outWeightsE[xIndex] += pixelESq * pixelESq;
288 } else if (waveAdj) {
289 auto waveY = waveAdj->y(0)[j];
290 auto waveE = waveAdj->e(0)[j];
291
292 outWeightsY[xIndex] += weight * waveY;
293 const double waveESq = weight * waveE;
294 // add error from waveAdj
295 outWeightsE[xIndex] += waveESq * waveESq;
296 } else
297 outWeightsY[xIndex] += weight;
298 }
299 } // loop over single spectrum
300
301 prog.report("Calculating Q");
302
303 } // loop over all spectra
304
305 // take sqrt of error weight values
306 // left to be executed here for computational efficiency
307 size_t numHist = weights->getNumberHistograms();
308 for (size_t i = 0; i < numHist; i++) {
309 auto &weightsE = weights->mutableE(i);
310 std::transform(weightsE.cbegin(), weightsE.cend(), weightsE.begin(), [&](double val) { return sqrt(val); });
311 }
312
313 bool doOutputParts = getProperty("OutputParts");
314 if (doOutputParts) {
315 // copy outputworkspace before it gets further modified
316 MatrixWorkspace_sptr ws_sumOfCounts = WorkspaceFactory::Instance().create(outputWorkspace);
317 for (size_t i = 0; i < ws_sumOfCounts->getNumberHistograms(); i++) {
318 ws_sumOfCounts->setHistogram(i, outputWorkspace->histogram(i));
319 }
320
321 helper.outputParts(this, ws_sumOfCounts, weights);
322 }
323
324 // Divide the output data by the solid angles
325 outputWorkspace /= weights;
326 outputWorkspace->setDistribution(true);
327
328 // Count of the number of empty cells
329 const size_t nhist = outputWorkspace->getNumberHistograms();
330 const size_t nbins = outputWorkspace->blocksize();
331 int emptyBins = 0;
332 for (size_t i = 0; i < nhist; ++i) {
333 const auto &yOut = outputWorkspace->y(i);
334 for (size_t j = 0; j < nbins; ++j) {
335 if (yOut[j] < 1.0e-12)
336 ++emptyBins;
337 }
338 }
339
340 // Log the number of empty bins
341 g_log.notice() << "There are a total of " << emptyBins << " (" << (100 * emptyBins) / (outputWorkspace->size())
342 << "%) empty Q bins.\n";
343}
344
357std::vector<double> Qxy::logBinning(double min, double max, int num) {
358 if (min < 0 || max < 0)
359 std::cerr << "Only positive numbers allowed\n";
360 if (min == 0)
361 min = 1e-3; // This is Qmin default! Might have to change this
362 std::vector<double> outBins(num);
363 min = log10(min);
364 max = log10(max);
365 double binWidth = fabs((max - min) / (num - 1));
366 for (int i = 0; i < num; ++i) {
367 outBins[i] = pow(10, min + i * binWidth);
368 }
369 return outBins;
370}
371
372double Qxy::getQminFromWs(const API::MatrixWorkspace &inputWorkspace) {
373 // get qmin from the run properties
374 double qmin = 0;
375 const API::Run &run = inputWorkspace.run();
376 if (run.hasProperty("qmin")) {
377 qmin = run.getPropertyValueAsType<double>("Qmin");
378 } else {
379 g_log.warning() << "Could not retrieve Qmin from run object.\n";
380 }
381 g_log.notice() << "QxQy: Using logarithm binning with qmin=" << qmin << ".\n";
382 return qmin;
383}
384
390 const double max = getProperty("MaxQxy");
391 const double delta = getProperty("DeltaQ");
392 const bool log_binning = getProperty("IQxQyLogBinning");
393
394 // number of bins
395 auto nBins = static_cast<int>(max / delta);
396
397 HistogramData::BinEdges axis;
398 if (log_binning) {
399 // get qmin from the run properties
400 double qmin = getQminFromWs(*inputWorkspace);
401 std::vector<double> positiveBinning = logBinning(qmin, max, nBins);
402 std::reverse(std::begin(positiveBinning), std::end(positiveBinning));
403 std::vector<double> totalBinning = positiveBinning;
404 std::for_each(std::begin(totalBinning), std::end(totalBinning), [](double &n) { n = -1 * n; });
405 totalBinning.emplace_back(0.0);
406 std::reverse(std::begin(positiveBinning), std::end(positiveBinning));
407 totalBinning.insert(std::end(totalBinning), std::begin(positiveBinning), std::end(positiveBinning));
408 nBins = nBins * 2 + 1;
409 axis = totalBinning;
410
411 } else {
412 if (nBins * delta != max)
413 ++nBins; // Stop at first boundary past MaxQxy if max is not a multiple of
414 // delta
415 double startVal = -1.0 * delta * nBins;
416 nBins *= 2; // go from -max to +max
417 nBins += 1; // Add 1 - this is a histogram
418
419 // Build up the X values
420 HistogramData::BinEdges linearAxis(nBins, HistogramData::LinearGenerator(startVal, delta));
421 axis = linearAxis;
422 }
423
424 // Create an output workspace with the same meta-data as the input
425 MatrixWorkspace_sptr outputWorkspace =
426 WorkspaceFactory::Instance().create(inputWorkspace, nBins - 1, nBins, nBins - 1);
427
428 // Create a numeric axis to replace the vertical one
429 auto verticalAxis = std::make_unique<BinEdgeAxis>(nBins);
430 auto verticalAxisRaw = verticalAxis.get();
431 outputWorkspace->replaceAxis(1, std::move(verticalAxis));
432 for (int i = 0; i < nBins; ++i) {
433 const double currentVal = axis[i];
434 // Set the Y value on the axis
435 verticalAxisRaw->setValue(i, currentVal);
436 }
437
438 // Fill the X vectors in the output workspace
439 for (int i = 0; i < nBins - 1; ++i) {
440 outputWorkspace->setBinEdges(i, axis);
441 auto &y = outputWorkspace->mutableY(i);
442 auto &e = outputWorkspace->mutableE(i);
443
444 for (int j = 0; j < nBins - j; ++j) {
445 y[j] = std::numeric_limits<double>::quiet_NaN();
446 e[j] = std::numeric_limits<double>::quiet_NaN();
447 }
448 }
449
450 // Set the axis units
451 outputWorkspace->getAxis(1)->unit() = outputWorkspace->getAxis(0)->unit() =
452 UnitFactory::Instance().create("MomentumTransfer");
453 // Set the 'Y' unit (gets confusing here...this is probably a Z axis in this
454 // case)
455 outputWorkspace->setYUnitLabel("Cross Section (1/cm)");
456
457 setProperty("OutputWorkspace", outputWorkspace);
458 return outputWorkspace;
459}
460
461} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
std::map< DeltaEMode::Type, std::string > index
#define fabs(x)
Definition Matrix.cpp:22
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:422
const Run & run() const
Run details object access.
A validator which checks that a workspace contains histogram data (the default) or point data as requ...
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.
HeldType getPropertyValueAsType(const std::string &name) const
Get the value of a property as the given TYPE.
Base MatrixWorkspace Abstract Class.
std::map< size_t, double > MaskList
Masked bins for each spectrum are stored as a set of pairs containing <bin index, weight>
Helper class for reporting progress from algorithms.
Definition Progress.h:25
This class stores information regarding an experimental run as a series of log entries.
Definition Run.h:35
A property class for workspaces.
A validator which checks that the unit of the workspace referred to by a WorkspaceProperty is the exp...
A helper class for calculating neutron's gravitional drop.
double calcComponents(const double wavAngstroms, double &xFrac, double &yFrac) const
Calculate the sins and cosins of angles as required to calculate Q is 2 dimensions.
Helper class for the Q1D and Qxy algorithms.
Definition Qhelper.h:20
size_t waveLengthCutOff(const API::MatrixWorkspace_const_sptr &dataWS, const API::SpectrumInfo &spectrumInfo, const double RCut, const double WCut, const size_t wsInd) const
Finds the first index number of the first wavelength bin that should included based on the the calcul...
Definition Qhelper.cpp:150
void outputParts(API::Algorithm *alg, const API::MatrixWorkspace_sptr &sumOfCounts, const API::MatrixWorkspace_sptr &sumOfNormFactors)
This method performs the common work between Qxy and Q1D2 if algorihtm parameter OutputParts=True.
Definition Qhelper.cpp:183
void examineInput(const API::MatrixWorkspace_const_sptr &dataWS, const API::MatrixWorkspace_const_sptr &binAdj, const API::MatrixWorkspace_const_sptr &detectAdj, const API::MatrixWorkspace_const_sptr &qResolution)
Checks if workspaces input to Q1D or Qxy are reasonable.
Definition Qhelper.cpp:29
double getQminFromWs(const API::MatrixWorkspace &inputWorkspace)
Definition Qxy.cpp:372
API::MatrixWorkspace_sptr setUpOutputWorkspace(const API::MatrixWorkspace_const_sptr &inputWorkspace)
Creates the output workspace, setting the X vector to the bins boundaries in Qx.
Definition Qxy.cpp:389
void init() override
Initialisation code.
Definition Qxy.cpp:37
std::vector< double > logBinning(double min, double max, int num)
This function calculates a logarithm binning It gives the same result as scipy:
Definition Qxy.cpp:357
void exec() override
Execution code.
Definition Qxy.cpp:98
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 notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Class for 3D vectors.
Definition V3D.h:34
constexpr double X() const noexcept
Get x.
Definition V3D.h:238
constexpr double Y() const noexcept
Get y.
Definition V3D.h:239
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
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54