-
Notifications
You must be signed in to change notification settings - Fork 122
/
He3TubeEfficiency.cpp
462 lines (401 loc) · 18 KB
/
He3TubeEfficiency.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidAlgorithms/He3TubeEfficiency.h"
#include "MantidAPI/Axis.h"
#include "MantidAPI/HistogramValidator.h"
#include "MantidAPI/InstrumentValidator.h"
#include "MantidAPI/SpectrumInfo.h"
#include "MantidAPI/WorkspaceUnitValidator.h"
#include "MantidDataObjects/EventWorkspace.h"
#include "MantidDataObjects/Workspace2D.h"
#include "MantidDataObjects/WorkspaceCreation.h"
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Instrument/ParameterMap.h"
#include "MantidGeometry/Objects/IObject.h"
#include "MantidGeometry/Objects/Track.h"
#include "MantidKernel/ArrayBoundedValidator.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/CompositeValidator.h"
#include <cmath>
#include <stdexcept>
// this should be a big number but not so big that there are rounding errors
const double DIST_TO_UNIVERSE_EDGE = 1e3;
// Scalar constant for exponential in units of K / (m Angstroms atm)
const double EXP_SCALAR_CONST = 2175.486863864;
// Tolerance for diameter/thickness comparison
const double TOL = 1.0e-8;
namespace Mantid {
namespace Algorithms {
using namespace DataObjects;
using namespace HistogramData;
// Register the class into the algorithm factory
DECLARE_ALGORITHM(He3TubeEfficiency)
/// Default constructor
He3TubeEfficiency::He3TubeEfficiency()
: Algorithm(), m_inputWS(), m_outputWS(), m_paraMap(nullptr), m_shapeCache(), m_samplePos(), m_spectraSkipped(),
m_progress(nullptr) {
m_shapeCache.clear();
}
/**
* Declare algorithm properties
*/
void He3TubeEfficiency::init() {
using namespace Mantid::Kernel;
auto wsValidator = std::make_shared<CompositeValidator>();
wsValidator->add<API::WorkspaceUnitValidator>("Wavelength");
wsValidator->add<API::HistogramValidator>();
wsValidator->add<API::InstrumentValidator>();
this->declareProperty(std::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>(
"InputWorkspace", "", Kernel::Direction::Input, wsValidator),
"Name of the input workspace");
this->declareProperty(
std::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>("OutputWorkspace", "", Kernel::Direction::Output),
"Name of the output workspace, can be the same as the input");
auto mustBePositive = std::make_shared<Kernel::BoundedValidator<double>>();
mustBePositive->setLower(0.0);
this->declareProperty(std::make_unique<Kernel::PropertyWithValue<double>>("ScaleFactor", 1.0, mustBePositive),
"Constant factor with which to scale the calculated"
"detector efficiency. Same factor applies to all efficiencies.");
auto mustBePosArr = std::make_shared<Kernel::ArrayBoundedValidator<double>>();
mustBePosArr->setLower(0.0);
this->declareProperty(std::make_unique<Kernel::ArrayProperty<double>>("TubePressure", mustBePosArr),
"Provide overriding the default tube pressure. The pressure must "
"be specified in atm.");
this->declareProperty(std::make_unique<Kernel::ArrayProperty<double>>("TubeThickness", mustBePosArr),
"Provide overriding the default tube thickness. The thickness must "
"be specified in metres.");
this->declareProperty(std::make_unique<Kernel::ArrayProperty<double>>("TubeTemperature", mustBePosArr),
"Provide overriding the default tube temperature. The temperature must "
"be specified in Kelvin.");
}
/**
* Executes the algorithm
*/
void He3TubeEfficiency::exec() {
// Get the workspaces
m_inputWS = this->getProperty("InputWorkspace");
m_outputWS = this->getProperty("OutputWorkspace");
if (m_outputWS != m_inputWS) {
m_outputWS = create<API::MatrixWorkspace>(*m_inputWS);
}
// Get the detector parameters
m_paraMap = &(m_inputWS->constInstrumentParameters());
// Store some information about the instrument setup that will not change
m_samplePos = m_inputWS->getInstrument()->getSample()->getPos();
// Check if it is an event workspace
DataObjects::EventWorkspace_const_sptr eventW =
std::dynamic_pointer_cast<const DataObjects::EventWorkspace>(m_inputWS);
if (eventW != nullptr) {
this->execEvent();
return;
}
std::size_t numHists = m_inputWS->getNumberHistograms();
m_progress = std::make_unique<API::Progress>(this, 0.0, 1.0, numHists);
const auto &spectrumInfo = m_inputWS->spectrumInfo();
PARALLEL_FOR_IF(Kernel::threadSafe(*m_inputWS, *m_outputWS))
for (int i = 0; i < static_cast<int>(numHists); ++i) {
PARALLEL_START_INTERUPT_REGION
m_outputWS->setSharedX(i, m_inputWS->sharedX(i));
try {
this->correctForEfficiency(i, spectrumInfo);
} catch (std::out_of_range &) {
// if we don't have all the data there will be spectra we can't correct,
// avoid leaving the workspace part corrected
m_outputWS->mutableY(i) = 0;
PARALLEL_CRITICAL(deteff_invalid) { m_spectraSkipped.emplace_back(m_inputWS->getAxis(1)->spectraNo(i)); }
}
// make regular progress reports
m_progress->report();
// check for canceling the algorithm
if (i % 1000 == 0) {
interruption_point();
}
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
this->logErrors();
this->setProperty("OutputWorkspace", m_outputWS);
}
/**
* Corrects a spectra for the detector efficiency calculated from detector
* information. Gets the detector information and uses this to calculate its
* efficiency
* @param spectraIndex :: index of the spectrum to get the efficiency for
* @param spectrumInfo :: the SpectrumInfo object for the input workspace
* @throw invalid_argument if the shape of a detector is isn't a cylinder
* aligned along one axis
* @throw NotFoundError if the detector or its gas pressure or wall thickness
* were not found
*/
void He3TubeEfficiency::correctForEfficiency(std::size_t spectraIndex, const API::SpectrumInfo &spectrumInfo) {
if (spectrumInfo.isMonitor(spectraIndex) || spectrumInfo.isMasked(spectraIndex)) {
return;
}
const auto &det = spectrumInfo.detector(spectraIndex);
const double exp_constant = this->calculateExponential(spectraIndex, det);
const double scale = this->getProperty("ScaleFactor");
const auto &yValues = m_inputWS->y(spectraIndex);
auto &yOut = m_outputWS->mutableY(spectraIndex);
const auto wavelength = m_inputWS->points(spectraIndex);
// todo is it going to die? returning local var by reference
std::vector<double> effCorrection(wavelength.size());
computeEfficiencyCorrection(effCorrection, wavelength, exp_constant, scale);
std::transform(yValues.cbegin(), yValues.cend(), effCorrection.cbegin(), yOut.begin(),
[&](const double y, const double effcorr) { return y * effcorr; });
const auto &eValues = m_inputWS->e(spectraIndex);
auto &eOut = m_outputWS->mutableE(spectraIndex);
// reset wavelength iterator
std::transform(eValues.cbegin(), eValues.cend(), effCorrection.cbegin(), eOut.begin(),
[&](const double e, const double effcorr) { return e * effcorr; });
}
/**
* This function calculates the exponential contribution to the He3 tube
* efficiency.
* @param spectraIndex :: the current index to calculate
* @param idet :: the current detector pointer
* @throw out_of_range if twice tube thickness is greater than tube diameter
* @return the exponential contribution for the given detector
*/
double He3TubeEfficiency::calculateExponential(std::size_t spectraIndex, const Geometry::IDetector &idet) {
// Get the parameters for the current associated tube
double pressure = this->getParameter("TubePressure", spectraIndex, "tube_pressure", idet);
double tubethickness = this->getParameter("TubeThickness", spectraIndex, "tube_thickness", idet);
double temperature = this->getParameter("TubeTemperature", spectraIndex, "tube_temperature", idet);
double detRadius(0.0);
Kernel::V3D detAxis;
this->getDetectorGeometry(idet, detRadius, detAxis);
double detDiameter = 2.0 * detRadius;
double twiceTubeThickness = 2.0 * tubethickness;
// now get the sin of the angle, it's the magnitude of the cross product of
// unit vector along the detector tube axis and a unit vector directed from
// the sample to the detector center
const Kernel::V3D vectorFromSample = normalize(idet.getPos() - m_samplePos);
Kernel::Quat rot = idet.getRotation();
// rotate the original cylinder object axis to get the detector axis in the
// actual instrument
rot.rotate(detAxis);
detAxis.normalize();
// Scalar product is quicker than cross product
double cosTheta = detAxis.scalar_prod(vectorFromSample);
double sinTheta = std::sqrt(1.0 - cosTheta * cosTheta);
const double straight_path = detDiameter - twiceTubeThickness;
if (std::fabs(straight_path - 0.0) < TOL) {
throw std::out_of_range("Twice tube thickness cannot be greater than "
"or equal to the tube diameter");
}
const double pathlength = straight_path / sinTheta;
return EXP_SCALAR_CONST * (pressure / temperature) * pathlength;
}
/**
* Update the shape cache if necessary
* @param det :: a pointer to the detector to query
* @param detRadius :: An output parameter that contains the detector radius
* @param detAxis :: An output parameter that contains the detector axis vector
*/
void He3TubeEfficiency::getDetectorGeometry(const Geometry::IDetector &det, double &detRadius, Kernel::V3D &detAxis) {
std::shared_ptr<const Geometry::IObject> shape_sptr = det.shape();
if (!shape_sptr) {
throw std::runtime_error("Detector geometry error: detector with id: " + std::to_string(det.getID()) +
" does not have shape. Is this a detectors group?\n"
"The algorithm works for instruments with one-to-one "
"spectra-to-detector maps only!");
}
// std::map<const Geometry::Object *, std::pair<double,
// Kernel::V3D>>::const_iterator
auto it = m_shapeCache.find(shape_sptr.get());
if (it == m_shapeCache.end()) {
double xDist = distToSurface(Kernel::V3D(DIST_TO_UNIVERSE_EDGE, 0, 0), shape_sptr.get());
double zDist = distToSurface(Kernel::V3D(0, 0, DIST_TO_UNIVERSE_EDGE), shape_sptr.get());
if (std::abs(zDist - xDist) < 1e-8) {
detRadius = zDist / 2.0;
detAxis = Kernel::V3D(0, 1, 0);
// assume radii in z and x and the axis is in the y
PARALLEL_CRITICAL(deteff_shapecachea) { m_shapeCache.insert({shape_sptr.get(), {detRadius, detAxis}}); }
return;
}
double yDist = distToSurface(Kernel::V3D(0, DIST_TO_UNIVERSE_EDGE, 0), shape_sptr.get());
if (std::abs(yDist - zDist) < 1e-8) {
detRadius = yDist / 2.0;
detAxis = Kernel::V3D(1, 0, 0);
// assume that y and z are radii of the cylinder's circular cross-section
// and the axis is perpendicular, in the x direction
PARALLEL_CRITICAL(deteff_shapecacheb) { m_shapeCache.insert({shape_sptr.get(), {detRadius, detAxis}}); }
return;
}
if (std::abs(xDist - yDist) < 1e-8) {
detRadius = xDist / 2.0;
detAxis = Kernel::V3D(0, 0, 1);
PARALLEL_CRITICAL(deteff_shapecachec) { m_shapeCache.insert({shape_sptr.get(), {detRadius, detAxis}}); }
return;
}
} else {
std::pair<double, Kernel::V3D> geometry = it->second;
detRadius = geometry.first;
detAxis = geometry.second;
}
}
/**
* For basic shapes centered on the origin (0,0,0) this returns the distance to
* the surface in the direction of the point given
* @param start :: the distance calculated from origin to the surface in a line
* towards this point. It should be outside the shape
* @param shape :: the object to calculate for, should be centered on the
* origin
* @return the distance to the surface in the direction of the point given
* @throw invalid_argument if there is any error finding the distance
* @returns The distance to the surface in metres
*/
double He3TubeEfficiency::distToSurface(const Kernel::V3D start, const Geometry::IObject *shape) const {
// get a vector from the point that was passed to the origin
const Kernel::V3D direction = normalize(-start);
// put the point and the vector (direction) together to get a line,
// here called a track
Geometry::Track track(start, direction);
// split the track (line) up into the part that is inside the shape and the
// part that is outside
shape->interceptSurface(track);
if (track.count() != 1) {
// the track missed the shape, probably the shape is not centered on
// the origin
throw std::invalid_argument("Fatal error interpreting the shape of a detector");
}
// the first part of the track will be the part inside the shape,
// return its length
return track.cbegin()->distInsideObject;
}
/**
* Calculate the detector efficiency from the detector parameters and the
* spectrum's x-axis.
* @param alpha :: the value to feed to the exponential
* @param scale_factor :: an overall value for scaling the efficiency
* @return the calculated efficiency
*/
double He3TubeEfficiency::detectorEfficiency(const double alpha, const double scale_factor) const {
return (scale_factor / (1.0 - std::exp(-alpha)));
}
/**
* Logs if there were any problems locating spectra.
*/
void He3TubeEfficiency::logErrors() const {
std::vector<int>::size_type nspecs = m_spectraSkipped.size();
if (nspecs > 0) {
this->g_log.warning() << "There were " << nspecs << " spectra that could not be corrected. ";
this->g_log.debug() << "Unaffected spectra numbers: ";
for (size_t i = 0; i < nspecs; ++i) {
this->g_log.debug() << m_spectraSkipped[i] << " ";
}
this->g_log.debug() << '\n';
}
}
/**
* Retrieve the detector parameter either from the workspace property or from
* the associated detector property.
* @param wsPropName :: the workspace property name for the detector parameter
* @param currentIndex :: the currently requested spectra index
* @param detPropName :: the detector property name for the detector parameter
* @param idet :: the current detector
* @return the value of the detector property
*/
double He3TubeEfficiency::getParameter(const std::string &wsPropName, std::size_t currentIndex,
const std::string &detPropName, const Geometry::IDetector &idet) {
std::vector<double> wsProp = this->getProperty(wsPropName);
if (wsProp.empty()) {
return idet.getNumberParameter(detPropName).at(0);
} else {
if (wsProp.size() == 1) {
return wsProp.at(0);
} else {
return wsProp.at(currentIndex);
}
}
}
/**
* Execute for events
*/
void He3TubeEfficiency::execEvent() {
this->g_log.information("Processing event workspace");
const API::MatrixWorkspace_const_sptr matrixInputWS = getProperty("InputWorkspace");
// generate the output workspace pointer
API::MatrixWorkspace_sptr matrixOutputWS = getProperty("OutputWorkspace");
if (matrixOutputWS != matrixInputWS) {
matrixOutputWS = matrixInputWS->clone();
setProperty("OutputWorkspace", matrixOutputWS);
}
auto m_outputWS = std::dynamic_pointer_cast<DataObjects::EventWorkspace>(matrixOutputWS);
std::size_t numHistograms = m_outputWS->getNumberHistograms();
auto &spectrumInfo = m_outputWS->mutableSpectrumInfo();
m_progress = std::make_unique<API::Progress>(this, 0.0, 1.0, numHistograms);
PARALLEL_FOR_IF(Kernel::threadSafe(*m_outputWS))
for (int i = 0; i < static_cast<int>(numHistograms); ++i) {
PARALLEL_START_INTERUPT_REGION
const auto &det = spectrumInfo.detector(i);
if (spectrumInfo.isMonitor(i) || spectrumInfo.isMasked(i)) {
continue;
}
double exp_constant = 0.0;
try {
exp_constant = this->calculateExponential(i, det);
} catch (std::out_of_range &) {
// Parameters are bad so skip correction
PARALLEL_CRITICAL(deteff_invalid) {
m_spectraSkipped.emplace_back(m_outputWS->getAxis(1)->spectraNo(i));
m_outputWS->getSpectrum(i).clearData();
spectrumInfo.setMasked(i, true);
}
}
// Do the correction
auto &evlist = m_outputWS->getSpectrum(i);
switch (evlist.getEventType()) {
case API::TOF:
// Switch to weights if needed.
evlist.switchTo(API::WEIGHTED);
// Fall through
case API::WEIGHTED:
eventHelper(evlist.getWeightedEvents(), exp_constant);
break;
case API::WEIGHTED_NOTIME:
eventHelper(evlist.getWeightedEventsNoTime(), exp_constant);
break;
}
m_progress->report();
// check for canceling the algorithm
if (i % 1000 == 0) {
interruption_point();
}
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
m_outputWS->clearMRU();
this->logErrors();
}
/** Private function to calculate the effeciency correction from
* points(wavelength)
* @param effCorrection :: Vector that will hold the values for each wavelength
* @param wavelength :: The points calculated from the histogram
* @param expConstant :: The exponential constant
* @param scale :: The scale factor
*/
void He3TubeEfficiency::computeEfficiencyCorrection(std::vector<double> &effCorrection, const Points &xes,
const double expConstant, const double scale) const {
std::transform(xes.cbegin(), xes.cend(), effCorrection.begin(),
[&](double wavelen) { return detectorEfficiency(expConstant * wavelen, scale); });
}
/**
* Private function for doing the event correction.
* @param events :: the list of events to correct
* @param expval :: the value of the exponent for the detector efficiency
*/
template <class T> void He3TubeEfficiency::eventHelper(std::vector<T> &events, double expval) {
const double scale = this->getProperty("ScaleFactor");
for (auto &event : events) {
auto de = static_cast<float>(this->detectorEfficiency(expval * event.tof(), scale));
event.m_weight *= de;
event.m_errorSquared *= de * de;
}
}
} // namespace Algorithms
} // namespace Mantid