Newer
Older
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
//------------------------------------------------------------------------------
// Includes
//------------------------------------------------------------------------------
#include "MantidAlgorithms/MonteCarloAbsorption.h"
#include "MantidAPI/WorkspaceProperty.h"
#include "MantidAPI/WorkspaceValidators.h"
#include "MantidAPI/SampleEnvironment.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidGeometry/IDetector.h"
#include "MantidGeometry/Objects/Track.h"
#include "MantidKernel/VectorHelper.h"
#include "MantidKernel/NeutronAtom.h"
// @todo: This needs a factory
#include "MantidKernel/MersenneTwister.h"
/// @cond
namespace
{
/// Number of attempts to choose a random point within the object before it gives up
const int MaxRandPointAttempts(20);
}
/// @endcond
namespace Mantid
{
namespace Algorithms
{
DECLARE_ALGORITHM(MonteCarloAbsorption)
using API::WorkspaceProperty;
using API::WorkspaceUnitValidator;
using API::MatrixWorkspace_sptr;
using API::WorkspaceFactory;
using API::Progress;
using namespace Geometry;
using Kernel::Direction;
//------------------------------------------------------------------------------
// Public methods
//------------------------------------------------------------------------------
/**
* Constructor
*/
MonteCarloAbsorption::MonteCarloAbsorption() :
m_inputWS(), m_sampleShape(NULL), m_container(NULL), m_numberOfPoints(0),
m_xStepSize(0), m_numberOfEvents(1), m_samplePos(), m_sourcePos(),
m_bbox_length(0.0), m_bbox_halflength(0.0), m_bbox_width(0.0),
m_bbox_halfwidth(0.0), m_bbox_height(0.0), m_bbox_halfheight(0.0),
m_randGen(NULL)
{
}
/**
* Destructor
*/
MonteCarloAbsorption::~MonteCarloAbsorption()
{
delete m_randGen;
}
//------------------------------------------------------------------------------
// Private methods
//------------------------------------------------------------------------------
/**
* Initialize the algorithm
*/
void MonteCarloAbsorption::init()
{
declareProperty(new WorkspaceProperty<>("InputWorkspace", "", Direction::Input,
new WorkspaceUnitValidator<> ("Wavelength")),
"The X values for the input workspace must be in units of wavelength");
declareProperty(new WorkspaceProperty<> ("OutputWorkspace", "", Direction::Output),
"Output workspace name");
Kernel::BoundedValidator<int> *positiveInt = new Kernel::BoundedValidator<int> ();
positiveInt->setLower(1);
declareProperty("NumberOfWavelengthPoints", EMPTY_INT(), positiveInt,
"The number of wavelength points for which a simulation is\n"
"performed (default: all points)");
declareProperty("EventsPerPoint", 300, positiveInt->clone(),
"The number of events to simulate per wavelength point used.");
declareProperty("SeedValue", 123456789, positiveInt->clone(),
"A seed for the random number generator");
}
/**
* Execution code
*/
void MonteCarloAbsorption::exec()
{
retrieveInput();
initCaches();
MatrixWorkspace_sptr correctionFactors = WorkspaceFactory::Instance().create(m_inputWS);
correctionFactors->isDistribution(true); // The output of this is a distribution
correctionFactors->setYUnit(""); // Need to explicitly set YUnit to nothing
correctionFactors->setYUnitLabel("Attenuation factor");
const bool isHistogram = m_inputWS->isHistogramData();
const int numHists(m_inputWS->getNumberHistograms());
const int numBins = m_inputWS->blocksize();
// Compute the step size
m_xStepSize = numBins/m_numberOfPoints;
std::ostringstream message;
message << "Simulation performed every " << m_xStepSize << " wavelength points" << std::endl;
g_log.information(message.str());
message.str("");
Progress prog(this,0.0,1.0,numHists);
PARALLEL_FOR2(m_inputWS, correctionFactors)
for( int i = 0; i < numHists; ++i )
{
PARALLEL_START_INTERUPT_REGION
// Copy over the X-values
const MantidVec & xValues = m_inputWS->readX(i);
correctionFactors->dataX(i) = xValues;
// Final detector position
IDetector_const_sptr detector;
try
{
detector = m_inputWS->getDetector(i);
}
catch(Kernel::Exception::NotFoundError&)
{
continue;
}
MantidVec & yValues = correctionFactors->dataY(i);
MantidVec & eValues = correctionFactors->dataE(i);
// Simulation for each requested wavelength point
for( int bin = 0; bin < numBins; bin += m_xStepSize )
{
const double lambda = isHistogram ?
(0.5 * (xValues[bin] + xValues[bin + 1]) ) : xValues[bin];
doSimulation(detector.get(), lambda, yValues[bin], eValues[bin]);
// Ensure we have the last point for the interpolation
if ( m_xStepSize > 1 && bin + m_xStepSize >= numBins && bin+1 != numBins)
{
bin = numBins - m_xStepSize - 1;
}
}
// Interpolate through points not simulated
if( m_xStepSize > 1 )
{
Kernel::VectorHelper::linearlyInterpolateY(xValues, yValues, m_xStepSize);
}
prog.report();
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
// Save the results
setProperty("OutputWorkspace", correctionFactors);
}
/**
* Perform the simulation
* @param detector A pointer to the current detector
* @param lambda The chosen wavelength
* @param attenFactor [Output] The calculated attenuation factor for this wavelength
* @param error [Output] The value of the error on the factor
*/
void MonteCarloAbsorption::doSimulation(const IDetector *const detector, const double lambda,
double & attenFactor, double & error)
{
/**
Currently, assuming square beam profile to pick start position then randomly selecting
a point within the sample using it's bounding box.
This point defines the single scattering point and hence the attenuation path lengths and final
directional vector to the detector
*/
// Absolute detector position
const V3D detectorPos(detector->getPos());
int numDetected(0);
attenFactor = 0.0;
error = 0.0;
while( numDetected < m_numberOfEvents )
{
V3D startPos = sampleBeamProfile();
V3D scatterPoint = selectScatterPoint();
attenFactor += attenuationFactor(startPos, scatterPoint, detectorPos, lambda);
++numDetected;
}
// Attenuation factor is simply the average value
attenFactor /= numDetected;
// Error is 1/sqrt(nevents)
error = 1./sqrt((double)numDetected);
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
}
/**
* Sample the beam profile for a random start location, assigning a weight
* to the given point
* @returns The starting location
*/
V3D MonteCarloAbsorption::sampleBeamProfile() const
{
return m_sourcePos;
}
/**
* Selects a random location within the sample + container environment.
* @returns Selected position as V3D object
*/
V3D MonteCarloAbsorption::selectScatterPoint() const
{
// Generate 3 random numbers, use them to calculate a random location
// within the bounding box of the sample environment + sample
// and then check if this position is within the whole environment.
// If it is return it, if not try again.
V3D scatterPoint;
int nattempts(0);
while( nattempts < MaxRandPointAttempts )
{
scatterPoint = V3D(m_bbox_halflength*(2.0*m_randGen->next() - 1.0),
m_bbox_halfwidth*(2.0*m_randGen->next() - 1.0),
m_bbox_halfheight*(2.0*m_randGen->next() - 1.0) );
++nattempts;
if( m_sampleShape->isValid(scatterPoint) ||
(m_container && m_container->isValid(scatterPoint)) )
{
scatterPoint += m_samplePos;
return scatterPoint;
}
}
// If we got here then the shape is too strange for the bounding box to be of any use.
g_log.error() << "Attempts to generat a random point with the sample/can "
<< "have exceeded the allowed number of tries.\n";
throw std::runtime_error("Attempts to produce random scatter point failed. Check sample shape.");
}
/**
* Return the attenuation factor for the given track
* @param startPos The origin of the track
* @param scatterPoint The point of scatter
* @param finalPos The end point of the track
* @param lambda The wavelength of the neutron
* @returns The attenuation factor for this neutron's track
*/
double
MonteCarloAbsorption::attenuationFactor(const V3D & startPos, const V3D & scatterPoint,
const V3D & finalPos, const double lambda)
{
double factor(1.0);
// Define two tracks, before and after scatter, and trace check their
// intersections with the the environment and sample
Track beforeScatter(scatterPoint, (startPos - scatterPoint));
Track afterScatter(scatterPoint, (finalPos - scatterPoint));
// Theoretically this should never happen as there should always be an intersection
// but do to precision limitations points very close to the surface give
// zero intersection, so just reject
if( m_sampleShape->interceptSurface(beforeScatter) == 0 ||
m_sampleShape->interceptSurface(afterScatter) == 0 )
{
return 0.0;
}
double length = beforeScatter.begin()->distInsideObject;
factor *= attenuation(length, *m_sampleMaterial, lambda);
beforeScatter.clearIntersectionResults();
if( m_container )
{
m_container->interceptSurfaces(beforeScatter);
}
// Attenuation factor is product of factor for each material
Track::LType::const_iterator cend = beforeScatter.end();
for(Track::LType::const_iterator citr = beforeScatter.begin();
citr != cend; ++citr)
{
length = citr->distInsideObject;
IObjComponent *objComp = dynamic_cast<IObjComponent*>(citr->componentID);
Material_const_sptr mat = objComp->material();
factor *= attenuation(length, *mat, lambda);
}
length = afterScatter.begin()->distInsideObject;
factor *= attenuation(length, *m_sampleMaterial, lambda);
afterScatter.clearIntersectionResults();
if( m_container )
{
m_container->interceptSurfaces(afterScatter);
}
// Attenuation factor is product of factor for each material
cend = afterScatter.end();
for(Track::LType::const_iterator citr = afterScatter.begin();
citr != cend; ++citr)
{
length = citr->distInsideObject;
IObjComponent *objComp = dynamic_cast<IObjComponent*>(citr->componentID);
Material_const_sptr mat = objComp->material();
factor *= attenuation(length, *mat, lambda);
}
return factor;
}
/**
* Calculate the attenuation for a given length, material and wavelength
* @param length Distance through the material
* @param material A reference to the Material
* @param lambda The wavelength
* @returns The attenuation factor
*/
double
MonteCarloAbsorption::attenuation(const double length, const Geometry::Material& material,
const double lambda) const
{
const double rho = material.numberDensity() * 100.0;
const double sigma_s = material.totalScatterXSection(lambda);
const double sigma_t = sigma_s + material.absorbXSection(lambda);
return exp(-rho*sigma_t*length);
}
/**
* Gather the input values and check validity
* @throws std::invalid_argument If the input is invalid. Currently if there is
* no defined sample shape
*/
void MonteCarloAbsorption::retrieveInput()
{
m_inputWS = getProperty("InputWorkspace");
// // Define a test sample material
// Material *vanadium = new Material("Vanadium", PhysicalConstants::getNeutronAtom(23,0), 0.072);
// m_inputWS->mutableSample().setMaterial(*vanadium);
// // Define test environment
// std::ostringstream xml;
// xml << "<sphere id=\"" << "sp" << "\">"
// << "<centre x=\"" << 0.0 << "\" y=\"" << 0.0 << "\" z=\"" << 0.0 << "\" />"
// << "<radius val=\"" << 0.05 << "\" />"
// << "</sphere>";
// Geometry::ShapeFactory shapeMaker;
// Object_sptr p = shapeMaker.createShape(xml.str());
// API::SampleEnvironment * kit = new API::SampleEnvironment("TestEnv");
// kit->add(new ObjComponent("one", p, NULL, boost::shared_ptr<Material>(vanadium)));
// m_inputWS->mutableSample().setEnvironment(kit);
m_sampleShape = &(m_inputWS->sample().getShape());
m_sampleMaterial = &(m_inputWS->sample().getMaterial());
if( !m_sampleShape->hasValidShape() )
{
g_log.debug() << "Invalid shape defined on workspace. TopRule = " << m_sampleShape->topRule()
<< ", No. of surfaces: " << m_sampleShape->getSurfacePtr().size() << "\n";
throw std::invalid_argument("Input workspace has an invalid sample shape.");
}
if( m_sampleMaterial->totalScatterXSection(1.0) == 0.0 )
{
g_log.warning() << "The sample material appears to have zero scattering cross section.\n"
<< "Result will most likely be nonsensical.\n";
}
try
{
m_container = &(m_inputWS->sample().getEnvironment());
}
catch(std::runtime_error&)
{
m_container = NULL;
g_log.information() << "No environment has been defined, continuing with only sample.\n";
}
m_numberOfPoints = getProperty("NumberOfWavelengthPoints");
if( isEmpty(m_numberOfPoints) || m_numberOfPoints > m_inputWS->blocksize() )
{
m_numberOfPoints = m_inputWS->blocksize();
if( !isEmpty(m_numberOfPoints) )
{
g_log.warning() << "The requested number of wavelength points is larger than the spectra size. "
<< "Defaulting to spectra size.\n";
}
}
m_numberOfEvents = getProperty("EventsPerPoint");
}
/**
* Initialise the caches used here including setting up the random
* number generator
*/
void MonteCarloAbsorption::initCaches()
{
if( !m_randGen )
{
m_randGen = new Kernel::MersenneTwister;
int seedValue = getProperty("SeedValue");
m_randGen->setSeed(seedValue);
}
m_samplePos = m_inputWS->getInstrument()->getSample()->getPos();
m_sourcePos = m_inputWS->getInstrument()->getSource()->getPos();
BoundingBox box(m_sampleShape->getBoundingBox());
if( m_container )
{
BoundingBox envBox;
m_container->getBoundingBox(envBox);
box.grow(envBox);
}
//Save the dimensions for quicker calculations later
m_bbox_length = box.xMax() - box.xMin();
m_bbox_halflength = 0.5 * m_bbox_length;
m_bbox_width = box.yMax() - box.yMin();
m_bbox_halfwidth = 0.5 * m_bbox_width;
m_bbox_height = box.zMax() - box.zMin();
m_bbox_halfheight = 0.5 * m_bbox_height;
}
}
}