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