-
Notifications
You must be signed in to change notification settings - Fork 122
/
AbsorptionCorrection.cpp
407 lines (354 loc) · 15.6 KB
/
AbsorptionCorrection.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
#include "MantidAlgorithms/AbsorptionCorrection.h"
#include "MantidAPI/InstrumentValidator.h"
#include "MantidAPI/Sample.h"
#include "MantidAPI/SpectrumInfo.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidAPI/WorkspaceUnitValidator.h"
#include "MantidGeometry/IDetector.h"
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Objects/ShapeFactory.h"
#include "MantidGeometry/Objects/Track.h"
#include "MantidHistogramData/Interpolate.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/CompositeValidator.h"
#include "MantidKernel/Fast_Exponential.h"
#include "MantidKernel/Material.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/Unit.h"
#include "MantidKernel/UnitFactory.h"
namespace Mantid {
namespace Algorithms {
using namespace API;
using namespace Geometry;
using HistogramData::interpolateLinearInplace;
using namespace Kernel;
using namespace Mantid::PhysicalConstants;
AbsorptionCorrection::AbsorptionCorrection()
: API::Algorithm(), m_inputWS(), m_sampleObject(nullptr), m_L1s(),
m_elementVolumes(), m_elementPositions(), m_numVolumeElements(0),
m_sampleVolume(0.0), m_refAtten(0.0), m_scattering(0), n_lambda(0),
m_xStep(0), m_emode(0), m_lambdaFixed(0.), EXPONENTIAL() {}
void AbsorptionCorrection::init() {
// The input workspace must have an instrument and units of wavelength
auto wsValidator = boost::make_shared<CompositeValidator>();
wsValidator->add<WorkspaceUnitValidator>("Wavelength");
wsValidator->add<InstrumentValidator>();
declareProperty(
make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input,
wsValidator),
"The X values for the input workspace must be in units of wavelength");
declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
Direction::Output),
"Output workspace name");
auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
mustBePositive->setLower(0.0);
declareProperty("AttenuationXSection", EMPTY_DBL(), mustBePositive,
"The ABSORPTION cross-section, at 1.8 Angstroms, for the "
"sample material in barns. Column 8 of a table generated "
"from http://www.ncnr.nist.gov/resources/n-lengths/.");
declareProperty("ScatteringXSection", EMPTY_DBL(), mustBePositive,
"The (coherent + incoherent) scattering cross-section for "
"the sample material in barns. Column 7 of a table generated "
"from http://www.ncnr.nist.gov/resources/n-lengths/.");
declareProperty("SampleNumberDensity", EMPTY_DBL(), mustBePositive,
"The number density of the sample in number of atoms per "
"cubic angstrom if not set with SetSampleMaterial");
auto positiveInt = boost::make_shared<BoundedValidator<int64_t>>();
positiveInt->setLower(1);
declareProperty(
"NumberOfWavelengthPoints", static_cast<int64_t>(EMPTY_INT()),
positiveInt,
"The number of wavelength points for which the numerical integral is\n"
"calculated (default: all points)");
std::vector<std::string> exp_options{"Normal", "FastApprox"};
declareProperty(
"ExpMethod", "Normal",
boost::make_shared<StringListValidator>(exp_options),
"Select the method to use to calculate exponentials, normal or a\n"
"fast approximation (default: Normal)");
std::vector<std::string> propOptions{"Elastic", "Direct", "Indirect"};
declareProperty("EMode", "Elastic",
boost::make_shared<StringListValidator>(propOptions),
"The energy mode (default: elastic)");
declareProperty(
"EFixed", 0.0, mustBePositive,
"The value of the initial or final energy, as appropriate, in meV.\n"
"Will be taken from the instrument definition file, if available.");
// Call the virtual method for concrete algorithm to define any other
// properties
defineProperties();
}
void AbsorptionCorrection::exec() {
// Retrieve the input workspace
m_inputWS = getProperty("InputWorkspace");
// Cache the beam direction
m_beamDirection = m_inputWS->getInstrument()->getBeamDirection();
// Get a reference to the parameter map (used for indirect instruments)
const ParameterMap &pmap = m_inputWS->constInstrumentParameters();
// Get the input parameters
retrieveBaseProperties();
// Create the output workspace
MatrixWorkspace_sptr correctionFactors =
WorkspaceFactory::Instance().create(m_inputWS);
correctionFactors->setDistribution(
true); // The output of this is a distribution
correctionFactors->setYUnit(""); // Need to explicitly set YUnit to nothing
correctionFactors->setYUnitLabel("Attenuation factor");
constructSample(correctionFactors->mutableSample());
const int64_t numHists =
static_cast<int64_t>(m_inputWS->getNumberHistograms());
const int64_t specSize = static_cast<int64_t>(m_inputWS->blocksize());
// If the number of wavelength points has not been given, use them all
if (isEmpty(n_lambda))
n_lambda = specSize;
m_xStep = specSize / n_lambda; // Bin step between points to calculate
if (m_xStep == 0) // Number of wavelength points >number of histogram points
m_xStep = 1;
std::ostringstream message;
message << "Numerical integration performed every " << m_xStep
<< " wavelength points\n";
g_log.information(message.str());
message.str("");
// Calculate the cached values of L1 and element volumes.
initialiseCachedDistances();
// If sample not at origin, shift cached positions.
const auto &spectrumInfo = m_inputWS->spectrumInfo();
const V3D samplePos = spectrumInfo.samplePosition();
if (samplePos != V3D(0, 0, 0)) {
for (auto &elementPosition : m_elementPositions) {
elementPosition += samplePos;
}
}
Progress prog(this, 0.0, 1.0, numHists);
// Loop over the spectra
PARALLEL_FOR_IF(Kernel::threadSafe(*m_inputWS, *correctionFactors))
for (int64_t i = 0; i < int64_t(numHists); ++i) {
PARALLEL_START_INTERUPT_REGION
// Copy over bins
correctionFactors->setSharedX(i, m_inputWS->sharedX(i));
if (!spectrumInfo.hasDetectors(i))
continue;
const auto &det = spectrumInfo.detector(i);
std::vector<double> L2s(m_numVolumeElements);
calculateDistances(det, L2s);
// If an indirect instrument, see if there's an efixed in the parameter map
double lambda_f = m_lambdaFixed;
if (m_emode == 2) {
try {
Parameter_sptr par = pmap.get(&det, "Efixed");
if (par) {
Unit_const_sptr energy = UnitFactory::Instance().create("Energy");
double factor, power;
energy->quickConversion(*UnitFactory::Instance().create("Wavelength"),
factor, power);
lambda_f = factor * std::pow(par->value<double>(), power);
}
} catch (std::runtime_error &) { /* Throws if a DetectorGroup, use single
provided value */
}
}
const auto lambdas = m_inputWS->points(i);
// Get a reference to the Y's in the output WS for storing the factors
auto &Y = correctionFactors->mutableY(i);
// Loop through the bins in the current spectrum every m_xStep
for (int64_t j = 0; j < specSize; j = j + m_xStep) {
const double lambda = lambdas[j];
if (m_emode == 0) // Elastic
{
Y[j] = this->doIntegration(lambda, L2s);
} else if (m_emode == 1) // Direct
{
Y[j] = this->doIntegration(m_lambdaFixed, lambda, L2s);
} else if (m_emode == 2) // Indirect
{
Y[j] = this->doIntegration(lambda, lambda_f, L2s);
}
Y[j] /= m_sampleVolume; // Divide by total volume of the cylinder
// Make certain that last point is calculates
if (m_xStep > 1 && j + m_xStep >= specSize && j + 1 != specSize) {
j = specSize - m_xStep - 1;
}
}
// Interpolate linearly between points separated by m_xStep,
// last point required
if (m_xStep > 1) {
auto histnew = correctionFactors->histogram(i);
interpolateLinearInplace(histnew, m_xStep);
correctionFactors->setHistogram(i, histnew);
}
prog.report();
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
g_log.information() << "Total number of elements in the integration was "
<< m_L1s.size() << '\n';
setProperty("OutputWorkspace", correctionFactors);
// Now do some cleaning-up since destructor may not be called immediately
m_L1s.clear();
m_elementVolumes.clear();
m_elementPositions.clear();
}
/// Fetch the properties and set the appropriate member variables
void AbsorptionCorrection::retrieveBaseProperties() {
double sigma_atten = getProperty("AttenuationXSection"); // in barns
double sigma_s = getProperty("ScatteringXSection"); // in barns
double rho = getProperty("SampleNumberDensity"); // in Angstroms-3
const Material &sampleMaterial = m_inputWS->sample().getShape().material();
if (sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) !=
0.0) {
if (rho == EMPTY_DBL())
rho = sampleMaterial.numberDensity();
if (sigma_s == EMPTY_DBL())
sigma_s =
sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda);
if (sigma_atten == EMPTY_DBL())
sigma_atten = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda);
} else // Save input in Sample with wrong atomic number and name
{
NeutronAtom neutron(static_cast<uint16_t>(EMPTY_DBL()),
static_cast<uint16_t>(0), 0.0, 0.0, sigma_s, 0.0,
sigma_s, sigma_atten);
Object shape = m_inputWS->sample().getShape(); // copy
shape.setMaterial(Material("SetInAbsorptionCorrection", neutron, rho));
m_inputWS->mutableSample().setShape(shape);
}
rho *= 100; // Needed to get the units right
m_refAtten = -sigma_atten * rho / 1.798;
m_scattering = -sigma_s * rho;
n_lambda = getProperty("NumberOfWavelengthPoints");
std::string exp_string = getProperty("ExpMethod");
if (exp_string == "Normal") // Use the system exp function
EXPONENTIAL = exp;
else if (exp_string == "FastApprox") // Use the compact approximation
EXPONENTIAL = fast_exp;
// Get the energy mode
const std::string emodeStr = getProperty("EMode");
// Convert back to an integer representation
m_emode = 0;
if (emodeStr == "Direct")
m_emode = 1;
else if (emodeStr == "Indirect")
m_emode = 2;
// If inelastic, get the fixed energy and convert it to a wavelength
if (m_emode) {
const double efixed = getProperty("Efixed");
Unit_const_sptr energy = UnitFactory::Instance().create("Energy");
double factor, power;
energy->quickConversion(*UnitFactory::Instance().create("Wavelength"),
factor, power);
m_lambdaFixed = factor * std::pow(efixed, power);
}
// Call the virtual function for any further properties
retrieveProperties();
}
/// Create the sample object using the Geometry classes, or use the existing one
void AbsorptionCorrection::constructSample(API::Sample &sample) {
const std::string xmlstring = sampleXML();
if (xmlstring.empty()) {
// This means that we should use the shape already defined on the sample.
m_sampleObject = &sample.getShape();
// Check there is one, and fail if not
if (!m_sampleObject->hasValidShape()) {
const std::string mess(
"No shape has been defined for the sample in the input workspace");
g_log.error(mess);
throw std::invalid_argument(mess);
}
} else {
boost::shared_ptr<Object> shape = ShapeFactory().createShape(xmlstring);
sample.setShape(*shape);
m_sampleObject = &sample.getShape();
g_log.information("Successfully constructed the sample object");
}
}
/// Calculate the distances traversed by the neutrons within the sample
/// @param detector :: The detector we are working on
/// @param L2s :: A vector of the sample-detector distance for each segment of
/// the sample
void AbsorptionCorrection::calculateDistances(const IDetector &detector,
std::vector<double> &L2s) const {
V3D detectorPos(detector.getPos());
if (detector.nDets() > 1) {
// We need to make sure this is right for grouped detectors - should use
// average theta & phi
detectorPos.spherical(detectorPos.norm(),
detector.getTwoTheta(V3D(), V3D(0, 0, 1)) * 180.0 /
M_PI,
detector.getPhi() * 180.0 / M_PI);
}
for (size_t i = 0; i < m_numVolumeElements; ++i) {
// Create track for distance in cylinder between scattering point and
// detector
V3D direction = detectorPos - m_elementPositions[i];
direction.normalize();
Track outgoing(m_elementPositions[i], direction);
int temp = m_sampleObject->interceptSurface(outgoing);
/* Most of the time, the number of hits is 1. Sometime, we have more than
* one intersection due to
* arithmetic imprecision. If it is the case, then selecting the first
* intersection is valid.
* In principle, one could check the consistency of all distances if hits is
* larger than one by doing:
* Mantid::Geometry::Track::LType::const_iterator it=outgoing.begin();
* and looping until outgoing.end() checking the distances with it->Dist
*/
// Not hitting the cylinder from inside, usually means detector is badly
// defined,
// i.e, position is (0,0,0).
if (temp < 1) {
// FOR NOW AT LEAST, JUST IGNORE THIS ERROR AND USE A ZERO PATH LENGTH,
// WHICH I RECKON WILL MAKE A
// NEGLIGIBLE DIFFERENCE ANYWAY (ALWAYS SEEMS TO HAPPEN WITH ELEMENT RIGHT
// AT EDGE OF SAMPLE)
L2s[i] = 0.0;
// std::ostringstream message;
// message << "Problem with detector at " << detectorPos << " ID:" <<
// detector->getID() << '\n';
// message << "This usually means that this detector is defined inside the
// sample cylinder";
// g_log.error(message.str());
// throw std::runtime_error("Problem in
// AbsorptionCorrection::calculateDistances");
} else // The normal situation
{
L2s[i] = outgoing.cbegin()->distFromStart;
}
}
}
/// Carries out the numerical integration over the sample for elastic
/// instruments
double
AbsorptionCorrection::doIntegration(const double &lambda,
const std::vector<double> &L2s) const {
double integral = 0.0;
size_t el = L2s.size();
// Iterate over all the elements, summing up the integral
for (size_t i = 0; i < el; ++i) {
// Equation is exponent * element volume
// where exponent is e^(-mu * wavelength/1.8 * (L1+L2) )
const double exponent =
((m_refAtten * lambda) + m_scattering) * (m_L1s[i] + L2s[i]);
integral += (EXPONENTIAL(exponent) * (m_elementVolumes[i]));
}
return integral;
}
/// Carries out the numerical integration over the sample for inelastic
/// instruments
double
AbsorptionCorrection::doIntegration(const double &lambda_i,
const double &lambda_f,
const std::vector<double> &L2s) const {
double integral = 0.0;
size_t el = L2s.size();
// Iterate over all the elements, summing up the integral
for (size_t i = 0; i < el; ++i) {
// Equation is exponent * element volume
// where exponent is e^(-mu * wavelength/1.8 * (L1+L2) )
double exponent = ((m_refAtten * lambda_i) + m_scattering) * m_L1s[i];
exponent += ((m_refAtten * lambda_f) + m_scattering) * L2s[i];
integral += (EXPONENTIAL(exponent) * (m_elementVolumes[i]));
}
return integral;
}
} // namespace Algorithms
} // namespace Mantid