Mantid
Loading...
Searching...
No Matches
DetectorEfficiencyCor.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"
11#include "MantidAPI/Run.h"
25#include "MantidTypes/SpectrumDefinition.h"
26
27#include <algorithm>
28#include <cmath>
29#include <functional>
30
31namespace Mantid::Algorithms {
32// Register the class into the algorithm factory
33DECLARE_ALGORITHM(DetectorEfficiencyCor)
34
35using namespace Kernel;
36using namespace API;
37using namespace Geometry;
38using namespace DataObjects;
39
40namespace {
41
42// E = KSquaredToE*K^2 KSquaredToE = (hbar^2)/(2*NeutronMass)
43const double KSquaredToE = 2.07212466; // units of meV Angstrom^-2
44
45const short NUMCOEFS = 25;
46// series expansion coefficients copied from a fortran source code file
47const double c_eff_f[] = {
48 0.7648360390553052, -0.3700950778935237, 0.1582704090813516, -6.0170218669705407E-02,
49 2.0465515957968953E-02, -6.2690181465706840E-03, 1.7408667184745830E-03, -4.4101378999425122E-04,
50 1.0252117967127217E-04, -2.1988904738111659E-05, 4.3729347905629990E-06, -8.0998753944849788E-07,
51 1.4031240949230472E-07, -2.2815971698619819E-08, 3.4943984983382137E-09, -5.0562696807254781E-10,
52 6.9315483353094009E-11, -9.0261598195695569E-12, 1.1192324844699897E-12, -1.3204992654891612E-13,
53 1.4100387524251801E-14, -8.6430862467068437E-16, -1.1129985821867194E-16, -4.5505266221823604E-16,
54 3.8885561437496108E-16};
55
56const double c_eff_g[] = {
57 2.033429926215546, -2.3123407369310212E-02, 7.0671915734894875E-03, -7.5970017538257162E-04,
58 7.4848652541832373E-05, 4.5642679186460588E-05, -2.3097291253000307E-05, 1.9697221715275770E-06,
59 2.4115259271262346E-06, -7.1302220919333692E-07, -2.5124427621592282E-07, 1.3246884875139919E-07,
60 3.4364196805913849E-08, -2.2891359549026546E-08, -6.7281240212491156E-09, 3.8292458615085678E-09,
61 1.6451021034313840E-09, -5.5868962123284405E-10, -4.2052310689211225E-10, 4.3217612266666094E-11,
62 9.9547699528024225E-11, 1.2882834243832519E-11, -1.9103066351000564E-11, -7.6805495297094239E-12,
63 1.8568853399347773E-12};
64
65// constants from the fortran code multiplied together sigref=143.23d0,
66// wref=3.49416d0, atmref=10.0d0 const = 2.0*sigref*wref/atmref
67const double g_helium_prefactor = 2.0 * 143.23 * 3.49416 / 10.0;
68
69// this should be a big number but not so big that there are rounding errors
70const double DIST_TO_UNIVERSE_EDGE = 1e3;
71
72// Name of pressure parameter
73const std::string PRESSURE_PARAM = "TubePressure";
74// Name of wall thickness parameter
75const std::string THICKNESS_PARAM = "TubeThickness";
76} // namespace
77
78// this default constructor calls default constructors and sets other member
79// data to impossible (flag) values
81 : Algorithm(), m_inputWS(), m_outputWS(), m_Ei(-1.0), m_ki(-1.0), m_shapeCache(), m_samplePos(),
82 m_spectraSkipped() {
84}
85
90 auto val = std::make_shared<CompositeValidator>();
91 val->add<WorkspaceUnitValidator>("DeltaE");
92 val->add<HistogramValidator>();
93 val->add<InstrumentValidator>();
94 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, val),
95 "The workspace to correct for detector efficiency");
96 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
97 "The name of the workspace in which to store the result. Each histogram "
98 "from the input workspace maps to a histogram in this workspace that has "
99 "just one value which indicates if there was a bad detector.");
100 auto checkEi = std::make_shared<BoundedValidator<double>>();
101 checkEi->setLower(0.0);
102 declareProperty("IncidentEnergy", EMPTY_DBL(), checkEi,
103 "The energy of neutrons leaving the source as can be "
104 "calculated by :ref:`algm-GetEi`. If this value is provided, "
105 "uses property value, if it is not present, needs Ei log "
106 "value set on the workspace.");
107}
108
116 // gets and checks the values passed to the algorithm
118
119 // wave number that the neutrons originally had
120 m_ki = std::sqrt(m_Ei / KSquaredToE);
121
122 // Store some information about the instrument setup that will not change
123 m_samplePos = m_inputWS->getInstrument()->getSample()->getPos();
124
125 int64_t numHists = m_inputWS->getNumberHistograms();
126 auto numHists_d = static_cast<double>(numHists);
127 const auto progStep = static_cast<int64_t>(ceil(numHists_d / 100.0));
128 auto const &spectrumInfo = m_inputWS->spectrumInfo();
129
131 for (int64_t i = 0; i < numHists; ++i) {
133
134 m_outputWS->setSharedX(i, m_inputWS->sharedX(i));
135 try {
136 correctForEfficiency(i, spectrumInfo);
137 } catch (Exception::NotFoundError &) {
138 // zero the Y data that can't be corrected
139 m_outputWS->mutableY(i) *= 0.0;
140 PARALLEL_CRITICAL(deteff_invalid) {
141 m_spectraSkipped.insert(m_spectraSkipped.end(), m_inputWS->getAxis(1)->spectraNo(i));
142 }
143 }
144 // make regular progress reports and check for canceling the algorithm
145 if (i % progStep == 0) {
146 progress(static_cast<double>(i) / numHists_d);
148 }
149
151 }
153
154 logErrors(numHists);
155 setProperty("OutputWorkspace", m_outputWS);
156}
163 // these first three properties are fully checked by validators
164 m_inputWS = getProperty("InputWorkspace");
165
166 m_Ei = getProperty("IncidentEnergy");
167 // If we're not given an Ei, see if one has been set.
168 if (m_Ei == EMPTY_DBL()) {
169 if (m_inputWS->run().hasProperty("Ei")) {
170 m_Ei = m_inputWS->run().getPropertyValueAsType<double>("Ei");
171 g_log.debug() << "Using stored Ei value " << m_Ei << "\n";
172 } else {
173 throw std::invalid_argument("No Ei value has been set or stored within the run information.");
174 }
175 }
176
177 m_outputWS = getProperty("OutputWorkspace");
178 // If input and output workspaces are not the same, create a new workspace for
179 // the output
180 if (m_outputWS != m_inputWS) {
181 m_outputWS = create<MatrixWorkspace>(*m_inputWS);
182 }
183}
184
196void DetectorEfficiencyCor::correctForEfficiency(int64_t spectraIn, const SpectrumInfo &spectrumInfo) {
197 if (!spectrumInfo.hasDetectors(spectraIn))
198 throw Exception::NotFoundError("No detectors found", spectraIn);
199
200 if (spectrumInfo.isMonitor(spectraIn) || spectrumInfo.isMasked(spectraIn)) {
201 return;
202 }
203
204 auto &yout = m_outputWS->mutableY(spectraIn);
205 auto &eout = m_outputWS->mutableE(spectraIn);
206 // Need the original values so this is not a reference
207 const auto yValues = m_inputWS->y(spectraIn);
208 const auto eValues = m_inputWS->e(spectraIn);
209
210 // Storage for the reciprocal wave vectors that are calculated as the
211 // correction proceeds
212 std::vector<double> oneOverWaveVectors(yValues.size());
213 const auto &detectorInfo = m_inputWS->detectorInfo();
214 const auto &componentInfo = m_inputWS->componentInfo();
215 const auto &spectrumDefinition = spectrumInfo.spectrumDefinition(spectraIn);
216
217 for (const auto &index : spectrumDefinition) {
218 const auto detIndex = index.first;
219 const auto pressure = componentInfo.getNumberParameter(detIndex, PRESSURE_PARAM);
220 if (pressure.empty()) {
221 throw Exception::NotFoundError(PRESSURE_PARAM, spectraIn);
222 }
223 const double atms = pressure.front();
224 const auto thickness = componentInfo.getNumberParameter(detIndex, THICKNESS_PARAM);
225 if (thickness.empty()) {
226 throw Exception::NotFoundError(THICKNESS_PARAM, spectraIn);
227 }
228 const double wallThickness = thickness.front();
229 double detRadius(0.0);
230 V3D detAxis;
231 if (!componentInfo.hasValidShape(detIndex)) {
232 throw Exception::NotFoundError("Shape", "Detector has no shape");
233 }
234 getDetectorGeometry(componentInfo.shape(detIndex), detRadius, detAxis);
235
236 // now get the sin of the angle, it's the magnitude of the cross product of
237 // unit vector along the detector tube axis and a unit vector directed from
238 // the sample to the detector centre
239 const V3D vectorFromSample = normalize(detectorInfo.position(detIndex) - m_samplePos);
240 Quat rot = detectorInfo.rotation(detIndex);
241 // rotate the original cylinder object axis to get the detector axis in the
242 // actual instrument
243 rot.rotate(detAxis);
244 detAxis.normalize();
245 // Scalar product is quicker than cross product
246 double cosTheta = detAxis.scalar_prod(vectorFromSample);
247 double sinTheta = std::sqrt(1.0 - cosTheta * cosTheta);
248 // Detector constant
249 const double det_const = g_helium_prefactor * (detRadius - wallThickness) * atms / sinTheta;
250
251 auto yinItr = yValues.cbegin();
252 auto einItr = eValues.cbegin();
253 auto youtItr = yout.begin();
254 auto eoutItr = eout.begin();
255 auto xItr = m_inputWS->x(spectraIn).cbegin();
256 auto wavItr = oneOverWaveVectors.begin();
257
258 for (; youtItr != yout.end(); ++youtItr, ++eoutItr) {
259 if (index == spectrumDefinition[0]) {
260 *youtItr = 0.0;
261 *eoutItr = 0.0;
262 *wavItr = calculateOneOverK(*xItr, *(xItr + 1));
263 }
264 const double oneOverWave = *wavItr;
265 const auto nDets(static_cast<double>(spectrumDefinition.size()));
266 const double factor = 1.0 / nDets / detectorEfficiency(det_const * oneOverWave);
267 *youtItr += (*yinItr) * factor;
268 *eoutItr += (*einItr) * factor;
269 ++yinItr;
270 ++einItr;
271 ++xItr;
272 ++wavItr;
273 }
274 }
275}
276
286double DetectorEfficiencyCor::calculateOneOverK(double loBinBound, double uppBinBound) const {
287 double energy = m_Ei - 0.5 * (uppBinBound + loBinBound);
288 double oneOverKSquared = KSquaredToE / energy;
289 return std::sqrt(oneOverKSquared);
290}
291
297void DetectorEfficiencyCor::getDetectorGeometry(const Geometry::IObject &shape, double &detRadius, V3D &detAxis) {
298 const IObject *shape_ptr = &shape;
299
300 std::map<const Geometry::IObject *, std::pair<double, Kernel::V3D>>::const_iterator it = m_shapeCache.find(shape_ptr);
301 if (it == m_shapeCache.end()) {
302 double xDist = distToSurface(V3D(DIST_TO_UNIVERSE_EDGE, 0, 0), shape_ptr);
303 double zDist = distToSurface(V3D(0, 0, DIST_TO_UNIVERSE_EDGE), shape_ptr);
304 if (withinAbsoluteDifference(zDist, xDist, 1e-6)) {
305 detRadius = zDist / 2.0;
306 detAxis = V3D(0, 1, 0);
307 // assume radi in z and x and the axis is in the y
308 PARALLEL_CRITICAL(deteff_shapecachea) { m_shapeCache.emplace(shape_ptr, std::make_pair(detRadius, detAxis)); }
309 return;
310 }
311 double yDist = distToSurface(V3D(0, DIST_TO_UNIVERSE_EDGE, 0), shape_ptr);
312 if (withinAbsoluteDifference(yDist, zDist, 1e-8)) {
313 detRadius = yDist / 2.0;
314 detAxis = V3D(1, 0, 0);
315 // assume that y and z are radi of the cylinder's circular cross-section
316 // and the axis is perpendicular, in the x direction
317 PARALLEL_CRITICAL(deteff_shapecacheb) { m_shapeCache.emplace(shape_ptr, std::make_pair(detRadius, detAxis)); }
318 return;
319 }
320
321 if (withinAbsoluteDifference(xDist, yDist, 1e-8)) {
322 detRadius = xDist / 2.0;
323 detAxis = V3D(0, 0, 1);
324 PARALLEL_CRITICAL(deteff_shapecachec) { m_shapeCache.emplace(shape_ptr, std::make_pair(detRadius, detAxis)); }
325 return;
326 }
327 } else {
328 std::pair<double, V3D> geometry = it->second;
329 detRadius = geometry.first;
330 detAxis = geometry.second;
331 }
332}
333
345double DetectorEfficiencyCor::distToSurface(const V3D &start, const IObject *shape) const {
346 // get a vector from the point that was passed to the origin
347 const V3D direction = normalize(-start);
348 // put the point and the vector (direction) together to get a line, here
349 // called a track
350 Track track(start, direction);
351 // split the track (line) up into the part that is inside the shape and the
352 // part that is outside
353 shape->interceptSurface(track);
354
355 if (track.count() != 1) { // the track missed the shape, probably the shape is
356 // not centered on the origin
357 throw std::invalid_argument("Fatal error interpreting the shape of a detector");
358 }
359 // the first part of the track will be the part inside the shape, return its
360 // length
361 return track.cbegin()->distInsideObject;
362}
363
370double DetectorEfficiencyCor::detectorEfficiency(const double alpha) const {
371 if (alpha < 9.0) {
372 return 0.25 * M_PI * alpha * chebevApprox(0.0, 10.0, c_eff_f, alpha);
373 }
374 if (alpha > 10.0) {
375 double y = 1.0 - 18.0 / alpha;
376 return 1.0 - chebevApprox(-1.0, 1.0, c_eff_g, y) / (alpha * alpha);
377 }
378 double eff_f = 0.25 * M_PI * alpha * chebevApprox(0.0, 10.0, c_eff_f, alpha);
379 double y = 1.0 - 18.0 / alpha;
380 double eff_g = 1.0 - chebevApprox(-1.0, 1.0, c_eff_g, y) / (alpha * alpha);
381 return (10.0 - alpha) * eff_f + (alpha - 9.0) * eff_g;
382}
383
395double DetectorEfficiencyCor::chebevApprox(double a, double b, const double exspansionCoefs[], double x) const {
396 double d = 0.0;
397 double dd = 0.0;
398 double y = (2.0 * x - a - b) / (b - a);
399 double y2 = 2.0 * y;
400 for (int j = NUMCOEFS - 1; j > 0; j -= 1) {
401 double sv = d;
402 d = y2 * d - dd + exspansionCoefs[j];
403 dd = sv;
404 }
405 return y * d - dd + 0.5 * exspansionCoefs[0];
406}
407
413void DetectorEfficiencyCor::logErrors(size_t totalNDetectors) const {
414 std::vector<int>::size_type nspecs = m_spectraSkipped.size();
415 if (!m_spectraSkipped.empty()) {
416 g_log.warning() << "There were " << nspecs
417 << " spectra that could not be corrected out of total: " << totalNDetectors << '\n';
418 g_log.warning() << "Their spectra were nullified\n";
419 g_log.debug() << " Nullified spectra numbers: ";
420 auto itend = m_spectraSkipped.end();
421 for (auto it = m_spectraSkipped.begin(); it != itend; ++it) {
422 g_log.debug() << *it << " ";
423 }
424 g_log.debug() << "\n";
425 }
426}
427
428} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:547
double energy
Definition GetAllEi.cpp:157
const double DIST_TO_UNIVERSE_EDGE
std::map< DeltaEMode::Type, std::string > index
#define PARALLEL_START_INTERRUPT_REGION
Begins a block to skip processing is the algorithm has been interupted Note the end of the block if n...
#define PARALLEL_CRITICAL(name)
#define PARALLEL_END_INTERRUPT_REGION
Ends a block to skip processing is the algorithm has been interupted Note the start of the block if n...
#define PARALLEL_FOR_IF(condition)
Empty definitions - to enable set your complier to enable openMP.
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
Base class from which all concrete algorithm classes should be derived.
Definition Algorithm.h:76
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
void clear() override
Clears all properties under management.
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.
void interruption_point()
This is called during long-running operations, and check if the algorithm has requested that it be ca...
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.
API::SpectrumInfo is an intermediate step towards a SpectrumInfo that is part of Instrument-2....
bool isMonitor(const size_t index) const
Returns true if the detector(s) associated with the spectrum are monitors.
bool hasDetectors(const size_t index) const
Returns true if the spectrum is associated with detectors in the instrument.
bool isMasked(const size_t index) const
Returns true if the detector(s) associated with the spectrum are masked.
const SpectrumDefinition & spectrumDefinition(const size_t index) const
Returns a const reference to the SpectrumDefinition of the spectrum.
A property class for workspaces.
A validator which checks that the unit of the workspace referred to by a WorkspaceProperty is the exp...
double calculateOneOverK(double loBinBound, double uppBinBound) const
Calculate one over the wave vector for 2 bin bounds.
double distToSurface(const Kernel::V3D &start, const Geometry::IObject *shape) const
Computes the distance to the given shape from a starting point.
double m_ki
stores the wave number of incidient neutrons, calculated from the energy
void exec() override
Executes the algorithm.
std::list< int64_t > m_spectraSkipped
The spectra numbers that were skipped.
std::map< const Geometry::IObject *, std::pair< double, Kernel::V3D > > m_shapeCache
A lookup of previously seen shape objects used to save calculation time as most detectors have the sa...
API::MatrixWorkspace_const_sptr m_inputWS
the user selected workspace
API::MatrixWorkspace_sptr m_outputWS
output workspace, maybe the same as the input one
double m_Ei
stores the user selected value for incidient energy of the neutrons
void init() override
Declare algorithm properties.
void retrieveProperties()
Retrieve algorithm properties.
void getDetectorGeometry(const Geometry::IObject &shape, double &detRadius, Kernel::V3D &detAxis)
Sets the detector geometry cache if necessary.
double detectorEfficiency(const double alpha) const
Computes the detector efficiency for a given paramater.
double chebevApprox(double a, double b, const double exspansionCoefs[], double x) const
Computes an approximate expansion of a Chebysev polynomial.
void correctForEfficiency(int64_t spectraIn, const API::SpectrumInfo &spectrumInfo)
Correct the given spectra index for efficiency.
void logErrors(size_t totalNDetectors) const
Log any errors with spectra that occurred.
IObject : Interface for geometry objects.
Definition IObject.h:43
virtual int interceptSurface(Geometry::Track &) const =0
Defines a track as a start point and a direction.
Definition Track.h:165
int count() const
Returns the number of links.
Definition Track.h:219
LType::const_iterator cbegin() const
Returns an interator to the start of the set of links (const version)
Definition Track.h:206
Exception for when an item is not found in a collection.
Definition Exception.h:145
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
Class for quaternions.
Definition Quat.h:38
void rotate(V3D &) const
Rotate a vector.
Definition Quat.cpp:397
Class for 3D vectors.
Definition V3D.h:35
constexpr double scalar_prod(const V3D &v) const noexcept
Calculates the cross product.
Definition V3D.h:281
double normalize()
Make a normalized vector (return norm value)
Definition V3D.cpp:129
MANTID_KERNEL_DLL bool withinAbsoluteDifference(T const x, T const y, S const tolerance)
Test whether x, y are within absolute tolerance tol.
std::enable_if< std::is_pointer< Arg >::value, bool >::type threadSafe(Arg workspace)
Thread-safety check Checks the workspace to ensure it is suitable for multithreaded access.
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition V3D.h:353
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
@ Input
An input workspace.
Definition Property.h:54
@ Output
An output workspace.
Definition Property.h:55