Mantid
Loading...
Searching...
No Matches
MultipleScatteringCorrection.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2021 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 +
7
12#include "MantidAPI/Sample.h"
22#include "MantidHistogramData/Interpolate.h"
26
27namespace Mantid::Algorithms {
28
29using namespace API;
30using namespace Geometry;
31using namespace Kernel;
32using namespace Mantid::DataObjects;
33
34using HistogramData::interpolateLinearInplace;
35
36DECLARE_ALGORITHM(MultipleScatteringCorrection)
37
38namespace {
39// the maximum number of elements to combine at once in the pairwise summation
40constexpr int64_t MAX_INTEGRATION_LENGTH{1000};
41
42static constexpr double RAD2DEG = 180.0 / M_PI; // save some flops??
43
44inline int64_t findMiddle(const int64_t start, const int64_t stop) {
45 auto half = static_cast<int64_t>(floor(.5 * (static_cast<double>(stop - start))));
46 return start + half;
47}
48
49inline size_t calcLinearIdxFromUpperTriangular(const size_t N, const size_t row_idx, const size_t col_idx) {
50 // calculate the linear index from the upper triangular matrix from a (N x N) matrix
51 // row_idx < col_idx due to upper triangular matrix
52 assert(row_idx < col_idx); // only relevant during Debug build
53 return N * (N - 1) / 2 - (N - row_idx) * (N - row_idx - 1) / 2 + col_idx - row_idx - 1;
54}
55
56// being added to make it clearer that we are creating a unit vector
57inline const V3D getDirection(const V3D &posInitial, const V3D &posFinal) { return normalize(posFinal - posInitial); }
58
59// make code slightly clearer
60inline double getDistanceInsideObject(const IObject &shape, Track &track) {
61 if (shape.interceptSurface(track) > 0) {
62 return track.totalDistInsideObject();
63 } else {
64 return 0.0;
65 }
66}
67
68inline double checkzero(const double x) { return std::abs(x) < std::numeric_limits<float>::min() ? 0.0 : x; }
69} // namespace
70
76 // 1- The input workspace must have an instrument and units of wavelength
77 auto wsValidator = std::make_shared<CompositeValidator>();
78 wsValidator->add<WorkspaceUnitValidator>("Wavelength");
79 wsValidator->add<InstrumentValidator>();
80 // 2- The input workspace must have a sample defined (shape and material)
81 wsValidator->add<SampleValidator, unsigned int>((SampleValidator::Shape | SampleValidator::Material));
82
83 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, wsValidator),
84 "The X values for the input workspace must be in units of wavelength");
85
86 auto positiveInt = std::make_shared<BoundedValidator<int64_t>>();
87 positiveInt->setLower(1);
88 declareProperty("NumberOfWavelengthPoints", static_cast<int64_t>(EMPTY_INT()), positiveInt,
89 "The number of wavelength points for which the numerical integral is\n"
90 "calculated (default: all points)");
91
92 auto moreThanZero = std::make_shared<BoundedValidator<double>>();
93 moreThanZero->setLower(0.001);
94 declareProperty("ElementSize", 1.0, moreThanZero, "The size of one side of an integration element cube in mm");
95
96 declareProperty("ContainerElementSize", EMPTY_DBL(),
97 "The size of one side of an integration element cube in mm for container."
98 "Default to be the same as ElementSize.");
99
100 std::vector<std::string> methodOptions{"SampleOnly", "SampleAndContainer"};
101 declareProperty("Method", "SampleOnly", std::make_shared<StringListValidator>(methodOptions),
102 "Correction method, use either SampleOnly or SampleAndContainer.");
103
104 declareProperty(std::make_unique<WorkspaceProperty<WorkspaceGroup>>("OutputWorkspace", "", Direction::Output),
105 "Output workspace name. "
106 "A Workspace2D containing the correction matrix that can be directly applied to the corresponding "
107 "Event workspace for multiple scattering correction.");
108}
109
115std::map<std::string, std::string> MultipleScatteringCorrection::validateInputs() {
116 std::map<std::string, std::string> result;
117
118 // check 0: input workspace must have a valida sample
119 // NOTE: technically the workspace validator should be able to catch the undefined sample
120 // error. Keeping this check here in case the validator is changed in the future.
121 m_inputWS = getProperty("InputWorkspace");
122 const auto &sample = m_inputWS->sample();
123 if (!sample.getShape().hasValidShape()) {
124 result["InputWorkspace"] = "The input workspace must have a valid sample shape";
125 }
126
127 // check 1: input workspace must have a valid sample environment
128 std::string method = getProperty("Method");
129 if (method == "SampleAndContainer") {
130 const auto &containerShape = m_inputWS->sample().getEnvironment().getContainer();
131 if (!containerShape.hasValidShape()) {
132 result["Method"] = "SampleAndContainer requires a valid container shape.";
133 }
134 }
135
136 return result;
137}
138
144 // Parse input properties and assign corresponding values to the member
145 // variables
146 parseInputs();
147
148 std::string method = getProperty("Method");
149 if (method == "SampleOnly") {
150 //-- Setup output workspace
151 // set the OutputWorkspace to a workspace group with one workspace:
152 // ${OutputWorkspace}_sampleOnly
153 API::MatrixWorkspace_sptr ws_sampleOnly = create<HistoWorkspace>(*m_inputWS);
154 ws_sampleOnly->setYUnit(""); // Need to explicitly set YUnit to nothing
155 ws_sampleOnly->setDistribution(true); // The output of this is a distribution
156 ws_sampleOnly->setYUnitLabel("Multiple Scattering Correction factor");
157 //-- Fill the workspace with sample only correction factors
158 const auto &sampleShape = m_inputWS->sample().getShape();
159 calculateSingleComponent(ws_sampleOnly, sampleShape, m_sampleElementSize);
160 //-- Package output to workspace group
161 const std::string outWSName = getProperty("OutputWorkspace");
162 std::vector<std::string> names;
163 names.emplace_back(outWSName + "_sampleOnly");
164 API::AnalysisDataService::Instance().addOrReplace(names.back(), ws_sampleOnly);
165 // group
166 auto group = createChildAlgorithm("GroupWorkspaces");
167 group->initialize();
168 group->setProperty("InputWorkspaces", names);
169 group->setProperty("OutputWorkspace", outWSName);
170 group->execute();
171 API::WorkspaceGroup_sptr outWS = group->getProperty("OutputWorkspace");
172 // NOTE:
173 // The output here is a workspace group of one, and it is an intended design as
174 // the MantidTotalScattering would like to have consistent output type regardless
175 // of the correction method.
176 setProperty("OutputWorkspace", outWS);
177 } else if (method == "SampleAndContainer") {
178 //-- Setup output workspace
179 // set the OutputWorkspace to a workspace group with two workspaces:
180 // ${OutputWorkspace}_containerOnly
181 // ${OutputWorkspace}_sampleAndContainer
182 // 1. container only
183 API::MatrixWorkspace_sptr ws_containerOnly = create<HistoWorkspace>(*m_inputWS);
184 ws_containerOnly->setYUnit(""); // Need to explicitly set YUnit to nothing
185 ws_containerOnly->setDistribution(true); // The output of this is a distribution
186 ws_containerOnly->setYUnitLabel("Multiple Scattering Correction factor");
187 const auto &containerShape = m_inputWS->sample().getEnvironment().getContainer();
188 calculateSingleComponent(ws_containerOnly, containerShape, m_containerElementSize);
189 // 2. sample and container
190 API::MatrixWorkspace_sptr ws_sampleAndContainer = create<HistoWorkspace>(*m_inputWS);
191 ws_sampleAndContainer->setYUnit(""); // Need to explicitly set YUnit to nothing
192 ws_sampleAndContainer->setDistribution(true); // The output of this is a distribution
193 ws_sampleAndContainer->setYUnitLabel("Multiple Scattering Correction factor");
194 calculateSampleAndContainer(ws_sampleAndContainer);
195 //-- Package output to workspace group
196 const std::string outWSName = getProperty("OutputWorkspace");
197 std::vector<std::string> names;
198 names.emplace_back(outWSName + "_containerOnly");
199 API::AnalysisDataService::Instance().addOrReplace(names.back(), ws_containerOnly);
200 names.emplace_back(outWSName + "_sampleAndContainer");
201 API::AnalysisDataService::Instance().addOrReplace(names.back(), ws_sampleAndContainer);
202 // group
203 auto group = createChildAlgorithm("GroupWorkspaces");
204 group->initialize();
205 group->setProperty("InputWorkspaces", names);
206 group->setProperty("OutputWorkspace", outWSName);
207 group->execute();
208 API::WorkspaceGroup_sptr outWS = group->getProperty("OutputWorkspace");
209 //
210 setProperty("OutputWorkspace", outWS);
211 } else {
212 // With validator guarding the gate, this should never happen. However, just incase it
213 // does, we should throw an exception.
214 throw std::invalid_argument("Invalid method: " + method);
215 }
216}
217
223 // Get input workspace
224 m_inputWS = getProperty("InputWorkspace");
225
226 // Get the beam direction
227 m_beamDirection = m_inputWS->getInstrument()->getBeamDirection();
228
229 // Get the total number of wavelength points, default to use all if not specified
230 m_num_lambda = isDefault("NumberOfWavelengthPoints") ? static_cast<int64_t>(m_inputWS->blocksize())
231 : getProperty("NumberOfWavelengthPoints");
232 // -- while we're here, compute the step in bin number between two adjacent points
233 const auto specSize = static_cast<int64_t>(m_inputWS->blocksize());
234 m_xStep = std::max(int64_t(1), specSize / m_num_lambda); // Bin step between points to calculate
235 // -- notify the user of the bin step
236 std::ostringstream msg;
237 msg << "Numerical integration performed every " << m_xStep << " wavelength points";
238 g_log.information(msg.str());
239 g_log.information() << msg.str();
240
241 // Get the element size
242 m_sampleElementSize = getProperty("ElementSize"); // in mm
243 m_sampleElementSize = m_sampleElementSize * 1e-3; // convert to m
244 m_containerElementSize = getProperty("ContainerElementSize");
246}
247
256 const Geometry::IObject &shape, const double elementSize) {
257 const auto material = shape.material();
258 // Cache distances
259 // NOTE: cannot use IObject_sprt for sample shape as the getShape() method dereferenced
260 // the shared pointer upon returning.
261 MultipleScatteringCorrectionDistGraber distGraber(shape, elementSize);
262 distGraber.cacheLS1(m_beamDirection);
263
264 const int64_t numVolumeElements = distGraber.m_numVolumeElements;
265
266 // calculate distance within material from source to scattering point
267 std::vector<double> LS1s(numVolumeElements, 0.0);
268 calculateLS1s(distGraber, LS1s, shape);
269
270 // L12 is independent from the detector, therefore can be cached outside
271 // - L12 is a upper off-diagonal matrix
272 // NOTE: if the sample size/volume is too large, we might need to use openMP
273 // to parallelize the calculation
274 const int64_t len_l12 = numVolumeElements * (numVolumeElements - 1) / 2;
275 std::vector<double> L12s(len_l12, 0.0);
276 calculateL12s(distGraber, L12s, shape);
277
278 // L2D needs to be calculated w.r.t the detector
279
280 // compute the prefactor for multiple scattering correction factor Delta
281 // Delta = totScatterCoeff * A2/A1
282 // NOTE: Unit is important
283 // rho in 1/A^3, and sigma_s in 1/barns (1e-8 A^(-2))
284 // so rho * sigma_s = 1e-8 A^(-1) = 100 meters
285 // A2/A1 gives length in meters
286 const double rho = material.numberDensityEffective();
287 const double sigma_s = material.totalScatterXSection();
288 const double unit_scaling = 1e2;
289 const double totScatterCoeff = rho * sigma_s * unit_scaling;
290
291 // Calculate one detector at a time
292 const auto &spectrumInfo = m_inputWS->spectrumInfo();
293 const auto numHists = static_cast<int64_t>(m_inputWS->getNumberHistograms());
294 const auto specSize = static_cast<int64_t>(m_inputWS->blocksize());
295 Progress prog(this, 0.0, 1.0, numHists);
296 // -- loop over the spectra/detectors
298 for (int64_t workspaceIndex = 0; workspaceIndex < numHists; ++workspaceIndex) {
300 // locate the spectrum and its detector
301 if (!spectrumInfo.hasDetectors(workspaceIndex)) {
302 g_log.information() << "Spectrum " << workspaceIndex << " does not have a detector defined for it\n";
303 continue;
304 }
305 if (spectrumInfo.isMasked(workspaceIndex)) {
306 continue;
307 }
308 const auto &det = spectrumInfo.detector(workspaceIndex);
309
310 // compute L2D
311 std::vector<double> L2Ds(numVolumeElements, 0.0);
312 calculateL2Ds(distGraber, det, L2Ds, shape);
313
314 const auto wavelengths = m_inputWS->points(workspaceIndex);
315 // these need to have the minus sign applied still
316
317 const auto LinearCoefAbs = material.linearAbsorpCoef(wavelengths.cbegin(), wavelengths.cend());
318
319 auto &output = outws->mutableY(workspaceIndex);
320 // -- loop over the wavelength points every m_xStep
321 for (int64_t wvBinsIndex = 0; wvBinsIndex < specSize; wvBinsIndex += m_xStep) {
322 double A1 = 0.0;
323 double A2 = 0.0;
324
325 pairWiseSum(A1, A2, -LinearCoefAbs[wvBinsIndex], distGraber, LS1s, L12s, L2Ds, 0, numVolumeElements);
326
327 // compute the correction factor
328 // NOTE: prefactor, totScatterCoeff, is pre-calculated outside the loop (see above)
329 output[wvBinsIndex] = totScatterCoeff / (4 * M_PI) * (A2 / A1);
330
331 // debug output
332#ifndef NDEBUG
333 std::ostringstream msg_debug;
334 msg_debug << "Det_" << workspaceIndex << "@spectrum_" << wvBinsIndex << '\n'
335 << "\trho = " << rho << ", sigma_s = " << sigma_s << '\n'
336 << "\tA1 = " << A1 << '\n'
337 << "\tA2 = " << A2 << '\n'
338 << "\tms_factor = " << output[wvBinsIndex] << '\n';
339 g_log.notice(msg_debug.str());
340#endif
341
342 // Make certain that last point is calculated
343 if (m_xStep > 1 && wvBinsIndex + m_xStep >= specSize && wvBinsIndex + 1 != specSize) {
344 wvBinsIndex = specSize - m_xStep - 1;
345 }
346 }
347
348 // Interpolate linearly between points separated by m_xStep,
349 // last point required
350 if (m_xStep > 1) {
351 auto histNew = outws->histogram(workspaceIndex);
352 interpolateLinearInplace(histNew, m_xStep);
353 outws->setHistogram(workspaceIndex, histNew);
354 }
355
356 prog.report();
357
359 }
361
362 g_log.notice() << "finished integration.\n";
363}
364
372 // retrieve container related properties as they are relevant now
373 const auto &sample = m_inputWS->sample();
374 const auto &sampleMaterial = sample.getShape().material();
375 const auto &containerMaterial = sample.getEnvironment().getContainer().material();
376
377 // get the sample and container shapes
378 const auto &sampleShape = sample.getShape();
379 const auto &containerShape = sample.getEnvironment().getContainer();
380
381 MultipleScatteringCorrectionDistGraber distGraberSample(sampleShape, m_sampleElementSize);
382 distGraberSample.cacheLS1(m_beamDirection);
383 MultipleScatteringCorrectionDistGraber distGraberContainer(containerShape, m_containerElementSize);
384 distGraberContainer.cacheLS1(m_beamDirection);
385
386 // useful info to have
387 const int64_t numVolumeElementsSample = distGraberSample.m_numVolumeElements;
388 const int64_t numVolumeElementsContainer = distGraberContainer.m_numVolumeElements;
389 const int64_t numVolumeElements = numVolumeElementsSample + numVolumeElementsContainer;
390 g_log.information() << "numVolumeElementsSample=" << numVolumeElementsSample
391 << ", numVolumeElementsContainer=" << numVolumeElementsContainer << "\n";
392
393 // Total elements = [container_elements] + [sample_elements]
394 // Schematic for scattering element i (*)
395 // | \ / |
396 // | container \ sample / container |
397 // | \ / |
398 // | ---LS1_container[i] --- \ LS1_sample[i] * L2D_sample[i] / ---L2D_container[i] --- |
399 // | \ / |
400
401 // LS1 can be cached here, but L2D must be calculated within the loop of spectra
402 std::vector<double> LS1_container(numVolumeElements, 0.0);
403 std::vector<double> LS1_sample(numVolumeElements, 0.0);
404 calculateLS1s(distGraberContainer, distGraberSample, LS1_container, LS1_sample, containerShape, sampleShape);
405
406 // cache L12 for both sample and container
407 // L12 is a upper off-diagonal matrix from the hybrid of container and sample.
408 // e.g. container: i, j, k
409 // sample: l, m, n
410 // hybrid: i, j, k, l, m, n
411 // L12:
412 // i j k l m n
413 // -------------------------------------------------
414 // i | x L12[0] L12[1] L12[2] L12[3] L12[4]
415 // j | x x L12[5] L12[6] L12[7] L12[8]
416 // k | x x x L12[9] L12[10] L12[11]
417 // l | x x x x L12[12] L12[13]
418 // m | x x x x x L12[14]
419 // n | x x x x x x
420 const int64_t len_l12 = numVolumeElements * (numVolumeElements - 1) / 2;
421 std::vector<double> L12_container(len_l12, 0.0);
422 std::vector<double> L12_sample(len_l12, 0.0);
423 calculateL12s(distGraberContainer, distGraberSample, L12_container, L12_sample, containerShape, sampleShape);
424#ifndef NDEBUG
425 for (size_t i = 0; i < size_t(numVolumeElements); ++i) {
426 for (size_t j = i + 1; j < size_t(numVolumeElements); ++j) {
427 const auto idx = calcLinearIdxFromUpperTriangular(numVolumeElements, i, j);
428 const auto l12 = L12_container[idx] + L12_sample[idx];
429 if (l12 < 1e-9) {
430 g_log.notice() << "L12_container(" << i << "," << j << ")=" << L12_container[idx] << '\n'
431 << "L12_sample(" << i << "," << j << ")=" << L12_sample[idx] << '\n';
432 }
433 }
434 }
435#endif
436
437 // cache the elementsVolumes
438 std::vector<double> elementVolumes(distGraberContainer.m_elementVolumes.begin(),
439 distGraberContainer.m_elementVolumes.end());
440 elementVolumes.insert(elementVolumes.end(), distGraberSample.m_elementVolumes.begin(),
441 distGraberSample.m_elementVolumes.end());
442#ifndef NDEBUG
443 for (size_t i = 0; i < elementVolumes.size(); ++i) {
444 if (elementVolumes[i] < 1e-16) {
445 g_log.notice() << "Element_" << i << " has near zero volume: " << elementVolumes[i] << '\n';
446 }
447 }
448 g_log.notice() << "V_container = "
449 << std::accumulate(distGraberContainer.m_elementVolumes.begin(),
450 distGraberContainer.m_elementVolumes.end(), 0.0)
451 << '\n'
452 << "V_sample = "
453 << std::accumulate(distGraberSample.m_elementVolumes.begin(), distGraberSample.m_elementVolumes.end(),
454 0.0)
455 << '\n';
456#endif
457
458 // NOTE: Unit is important
459 // rho in 1/A^3, and sigma_s in 1/barns (1e-8 A^(-2))
460 // so rho * sigma_s = 1e-8 A^(-1) = 100 meters
461 // A2/A1 gives length in meters
462 const double unit_scaling = 1e2;
463 const double rho_sample = sampleMaterial.numberDensityEffective();
464 const double sigma_s_sample = sampleMaterial.totalScatterXSection();
465 const double rho_container = containerMaterial.numberDensityEffective();
466 const double sigma_s_container = containerMaterial.totalScatterXSection();
467 const double totScatterCoef_container = rho_container * sigma_s_container * unit_scaling;
468 const double totScatterCoef_sample = rho_sample * sigma_s_sample * unit_scaling;
469
470 // Compute the multiple scattering factor: one detector at a time
471 const auto &spectrumInfo = m_inputWS->spectrumInfo();
472 const auto numHists = static_cast<int64_t>(m_inputWS->getNumberHistograms());
473 const auto specSize = static_cast<int64_t>(m_inputWS->blocksize());
474 Progress prog(this, 0.0, 1.0, numHists);
475 // -- loop over the spectra/detectors
477 for (int64_t workspaceIndex = 0; workspaceIndex < numHists; ++workspaceIndex) {
479 // locate the spectrum and its detector
480 if (!spectrumInfo.hasDetectors(workspaceIndex)) {
481 g_log.information() << "Spectrum " << workspaceIndex << " does not have a detector defined for it\n";
482 continue;
483 }
484 if (spectrumInfo.isMasked(workspaceIndex)) {
485 continue;
486 }
487 const auto &det = spectrumInfo.detector(workspaceIndex);
488
489 // calculate L2D (2_element -> detector)
490 // 1. container
491 std::vector<double> L2D_container(numVolumeElements, 0.0);
492 std::vector<double> L2D_sample(numVolumeElements, 0.0);
493 calculateL2Ds(distGraberContainer, distGraberSample, det, L2D_container, L2D_sample, containerShape, sampleShape);
494
495 // prepare material wise linear coefficient
496 const auto wavelengths = m_inputWS->points(workspaceIndex);
497 const auto sampleLinearCoefAbs = sampleMaterial.linearAbsorpCoef(wavelengths.cbegin(), wavelengths.cend());
498 const auto containerLinearCoefAbs = containerMaterial.linearAbsorpCoef(wavelengths.cbegin(), wavelengths.cend());
499
500 auto &output = outws->mutableY(workspaceIndex);
501 for (int64_t wvBinsIndex = 0; wvBinsIndex < specSize; wvBinsIndex += m_xStep) {
502 double A1 = 0.0;
503 double A2 = 0.0;
504
505 // compute the multiple scattering correction factor Delta
506 pairWiseSum(A1, A2, // output values
507 -containerLinearCoefAbs[wvBinsIndex], -sampleLinearCoefAbs[wvBinsIndex], // absorption coefficient
508 numVolumeElementsContainer, numVolumeElements, // number of elements for checking type
509 totScatterCoef_container, totScatterCoef_sample, // volumes
510 elementVolumes, // source -> 1st scattering element
511 LS1_container, LS1_sample, // 1st -> 2nd scattering element
512 L12_container, L12_sample, // 2nd scattering element -> detector
513 L2D_container, L2D_sample, // starting element idx, ending element idx
514 0, numVolumeElements);
515
516 output[wvBinsIndex] = (A2 / A1) / (4.0 * M_PI);
517
518 // debug output
519#ifndef NDEBUG
520 std::ostringstream msg_debug;
521 msg_debug << "Det_" << workspaceIndex << "@spectrum_" << wvBinsIndex << '\n'
522 << "-containerLinearCoefAbs[wvBinsIndex] = " << -containerLinearCoefAbs[wvBinsIndex] << '\n'
523 << "-sampleLinearCoefAbs[wvBinsIndex] = " << -sampleLinearCoefAbs[wvBinsIndex] << '\n'
524 << "numVolumeElementsContainer = " << numVolumeElementsContainer << '\n'
525 << "numVolumeElements = " << numVolumeElements << '\n'
526 << "totScatterCoef_container = " << totScatterCoef_container << '\n'
527 << "totScatterCoef_sample = " << totScatterCoef_sample << '\n'
528 << "\tA1 = " << A1 << '\n'
529 << "\tA2 = " << A2 << '\n'
530 << "\tms_factor = " << output[wvBinsIndex] << '\n';
531 g_log.notice(msg_debug.str());
532#endif
533
534 // Make certain that last point is calculated
535 if (m_xStep > 1 && wvBinsIndex + m_xStep >= specSize && wvBinsIndex + 1 != specSize) {
536 wvBinsIndex = specSize - m_xStep - 1;
537 }
538 }
539 // Interpolate linearly between points separated by m_xStep,
540 // last point required
541 if (m_xStep > 1) {
542 auto histNew = outws->histogram(workspaceIndex);
543 interpolateLinearInplace(histNew, m_xStep);
544 outws->setHistogram(workspaceIndex, histNew);
545 }
546 prog.report();
548 }
550 g_log.notice() << "finished integration.\n";
551}
552
561 std::vector<double> &LS1s, //
562 const Geometry::IObject &shape) const {
563 const auto &sourcePos = m_inputWS->getInstrument()->getSource()->getPos();
564 const int64_t numVolumeElements = distGraber.m_numVolumeElements;
565 Track trackerLS1(V3D{0, 0, 1}, V3D{0, 0, 1});
566
567 for (int64_t idx = 0; idx < numVolumeElements; ++idx) {
568 const auto pos = distGraber.m_elementPositions[idx];
569 const auto vec = getDirection(pos, sourcePos);
570 //
571 trackerLS1.reset(pos, vec);
572 trackerLS1.clearIntersectionResults();
573 LS1s[idx] = getDistanceInsideObject(shape, trackerLS1);
574 }
575}
576
588 const MultipleScatteringCorrectionDistGraber &distGraberSample, //
589 std::vector<double> &LS1sContainer, //
590 std::vector<double> &LS1sSample, //
591 const Geometry::IObject &shapeContainer, //
592 const Geometry::IObject &shapeSample) const {
593 // Total elements = [container_elements] + [sample_elements]
594 // Schematic for scattering element i (*)
595 // | \ / |
596 // | container \ sample / container |
597 // | \ / |
598 // | ---LS1_container[i] --- \ LS1_sample[i] * L2D_sample[i] / ---L2D_container[i] --- |
599 // | \ / |
600 const int64_t numVolumeElementsSample = distGraberSample.m_numVolumeElements;
601 const int64_t numVolumeElementsContainer = distGraberContainer.m_numVolumeElements;
602 const int64_t numVolumeElements = numVolumeElementsSample + numVolumeElementsContainer;
603 const auto sourcePos = m_inputWS->getInstrument()->getSource()->getPos();
604 Track trackerLS1(V3D{0, 0, 1}, V3D{0, 0, 1}); // reusable tracker for calculating LS1
605 for (int64_t idx = 0; idx < numVolumeElements; ++idx) {
606 const auto pos = idx < numVolumeElementsContainer
607 ? distGraberContainer.m_elementPositions[idx]
608 : distGraberSample.m_elementPositions[idx - numVolumeElementsContainer];
609 const auto vec = getDirection(pos, sourcePos);
610 //
611 trackerLS1.reset(pos, vec);
612 trackerLS1.clearIntersectionResults();
613 LS1sContainer[idx] = getDistanceInsideObject(shapeContainer, trackerLS1);
614 trackerLS1.reset(pos, vec);
615 trackerLS1.clearIntersectionResults();
616 LS1sSample[idx] = getDistanceInsideObject(shapeSample, trackerLS1);
617#ifndef NDEBUG
618 // debug
619 std::ostringstream msg_debug;
620 msg_debug << "idx=" << idx << ", pos=" << pos << ", vec=" << vec << '\n';
621 if (idx < numVolumeElementsContainer) {
622 msg_debug << "Container element " << idx << '\n';
623 } else {
624 msg_debug << "Sample element " << idx - numVolumeElementsContainer << '\n';
625 }
626 msg_debug << "LS1_container=" << LS1sContainer[idx] << ", LS1_sample=" << LS1sSample[idx] << '\n';
627 g_log.notice(msg_debug.str());
628#endif
629 }
630}
631
640 std::vector<double> &L12s, //
641 const Geometry::IObject &shape) {
642 const int64_t numVolumeElements = distGraber.m_numVolumeElements;
643 // L12 is independent from the detector, therefore can be cached outside
644 // - L12 is a upper off-diagonal matrix
645 // NOTE: if the sample size/volume is too large, we might need to use openMP
646 // to parallelize the calculation
647
649 for (int64_t indexTo = 0; indexTo < numVolumeElements; ++indexTo) {
651
652 const auto posTo = distGraber.m_elementPositions[indexTo];
653 Track track(posTo, V3D{0, 0, 1}); // take object creation out of the loop
654 for (int64_t indexFrom = indexTo + 1; indexFrom < numVolumeElements; ++indexFrom) {
655 // where in the final result to update, e.g.
656 // x 1 2 3
657 // x x 4 5
658 // x x x 6
659 // x x x x
660 const int64_t idx = calcLinearIdxFromUpperTriangular(numVolumeElements, indexTo, indexFrom);
661
662 const auto posFrom = distGraber.m_elementPositions[indexFrom];
663 const V3D unitVector = getDirection(posFrom, posTo);
664
665 // reset information in the Track and calculate distance
666 track.reset(posFrom, unitVector);
668 const auto rayLengthOne1 = getDistanceInsideObject(shape, track);
669
670 track.reset(posTo, unitVector);
672 const auto rayLengthOne2 = getDistanceInsideObject(shape, track);
673 // getDistanceInsideObject returns the line segment inside the shape from the given ray (defined by track)
674 // therefore, the distance between the two point (element) can be found by the difference of the two line
675 // segments.
676 L12s[idx] = checkzero(rayLengthOne1 - rayLengthOne2);
677 }
678
680 }
682}
683
685 const MultipleScatteringCorrectionDistGraber &distGraberSample, //
686 std::vector<double> &L12sContainer, //
687 std::vector<double> &L12sSample, //
688 const Geometry::IObject &shapeContainer, //
689 const Geometry::IObject &shapeSample) {
690 const int64_t numVolumeElementsSample = distGraberSample.m_numVolumeElements;
691 const int64_t numVolumeElementsContainer = distGraberContainer.m_numVolumeElements;
692 const int64_t numVolumeElements = numVolumeElementsSample + numVolumeElementsContainer;
693 // L12 is a upper off-diagonal matrix from the hybrid of container and sample.
694 // e.g. container: a, b, c
695 // sample: alpha, beta, gamma
696 // hybrid: a, b, c, alpha, beta, gamma
697 // L12:
698 // a b c alpha beta gamma
699 // -------------------------------------------------
700 // a | x L12[0] L12[1] L12[2] L12[3] L12[4]
701 // b | x x L12[5] L12[6] L12[7] L12[8]
702 // c | x x x L12[9] L12[10] L12[11]
703 // alpha | x x x x L12[12] L12[13]
704 // beta | x x x x x L12[14]
705 // gamma | x x x x x x
706
708 for (int64_t indexTo = 0; indexTo < numVolumeElements; ++indexTo) {
710 // find the position of first scattering element (container or sample)
711 const auto posTo = indexTo < numVolumeElementsContainer
712 ? distGraberContainer.m_elementPositions[indexTo]
713 : distGraberSample.m_elementPositions[indexTo - numVolumeElementsContainer];
714 // NOTE:
715 // We are using Track to ensure that the distance calculated are within the material
716 Track track(posTo, V3D{0, 0, 1}); // reusable track, eco-friendly
717 for (int64_t indexFrom = indexTo + 1; indexFrom < numVolumeElements; ++indexFrom) {
718 const int64_t idx = calcLinearIdxFromUpperTriangular(numVolumeElements, indexTo, indexFrom);
719 const auto posFrom = indexFrom < numVolumeElementsContainer
720 ? distGraberContainer.m_elementPositions[indexFrom]
721 : distGraberSample.m_elementPositions[indexFrom - numVolumeElementsContainer];
722 const V3D unitVector = getDirection(posFrom, posTo);
723
724 // combined
726 track.reset(posFrom, unitVector);
727 const auto rayLen1_container = getDistanceInsideObject(shapeContainer, track);
729 track.reset(posFrom, unitVector);
730 const auto rayLen1_sample = getDistanceInsideObject(shapeSample, track);
732 track.reset(posTo, unitVector);
733 const auto rayLen2_container = getDistanceInsideObject(shapeContainer, track);
735 track.reset(posTo, unitVector);
736 const auto rayLen2_sample = getDistanceInsideObject(shapeSample, track);
737 //
738 L12sContainer[idx] = checkzero(rayLen1_container - rayLen2_container);
739 L12sSample[idx] = checkzero(rayLen1_sample - rayLen2_sample);
740 }
742 }
744}
745
755 const IDetector &detector, std::vector<double> &L2Ds,
756 const Geometry::IObject &shape) const {
757 V3D detectorPos(detector.getPos());
758 if (detector.nDets() > 1) {
759 // We need to make sure this is right for grouped detectors - should use
760 // average theta & phi
761 detectorPos.spherical(detectorPos.norm(), detector.getTwoTheta(V3D(), V3D(0, 0, 1)) * RAD2DEG,
762 detector.getPhi() * RAD2DEG);
763 }
764
765 // calculate the distance between the detector and the shape (sample or container)
766 Track TwoToDetector(distGraber.m_elementPositions[0], V3D{1, 0, 0}); // take object creation out of the loop
767 for (size_t i = 0; i < distGraber.m_elementPositions.size(); ++i) {
768 const auto &elementPos = distGraber.m_elementPositions[i];
769 TwoToDetector.reset(elementPos, getDirection(elementPos, detectorPos));
770 TwoToDetector.clearIntersectionResults();
771 // find distance in sample
772 L2Ds[i] = getDistanceInsideObject(shape, TwoToDetector);
773 }
774}
775
777 const MultipleScatteringCorrectionDistGraber &distGraberSample, //
778 const IDetector &detector, //
779 std::vector<double> &container_L2Ds, //
780 std::vector<double> &sample_L2Ds, //
781 const Geometry::IObject &shapeContainer, //
782 const Geometry::IObject &shapeSample) const {
783 V3D detectorPos(detector.getPos());
784 if (detector.nDets() > 1) {
785 // We need to make sure this is right for grouped detectors - should use
786 // average theta & phi
787 detectorPos.spherical(detectorPos.norm(), detector.getTwoTheta(V3D(), V3D(0, 0, 1)) * RAD2DEG,
788 detector.getPhi() * RAD2DEG);
789 }
790
791 const int64_t numVolumeElementsSample = distGraberSample.m_numVolumeElements;
792 const int64_t numVolumeElementsContainer = distGraberContainer.m_numVolumeElements;
793 const int64_t numVolumeElements = numVolumeElementsSample + numVolumeElementsContainer;
794 // Total elements = [container_elements] + [sample_elements]
795 // Schematic for scattering element i (*)
796 // | \ / |
797 // | container \ sample / container |
798 // | \ / |
799 // | ---LS1_container[i] --- \ LS1_sample[i] * L2D_sample[i] / ---L2D_container[i] --- |
800 // | \ / |
801 // L2D must be calculated within the loop of spectra, so we cannot use OpenMP here
802 Track trackerL2D(V3D{0, 0, 1}, V3D{0, 0, 1}); // reusable tracker for calculating L2D
803 for (int64_t idx = 0; idx < numVolumeElements; ++idx) {
804 const auto pos = idx < numVolumeElementsContainer
805 ? distGraberContainer.m_elementPositions[idx]
806 : distGraberSample.m_elementPositions[idx - numVolumeElementsContainer];
807 const auto vec = getDirection(pos, detectorPos);
808 //
809 trackerL2D.reset(pos, vec);
810 trackerL2D.clearIntersectionResults();
811 container_L2Ds[idx] = getDistanceInsideObject(shapeContainer, trackerL2D);
812 trackerL2D.reset(pos, vec);
813 trackerL2D.clearIntersectionResults();
814 sample_L2Ds[idx] = getDistanceInsideObject(shapeSample, trackerL2D);
815 }
816}
817
818void MultipleScatteringCorrection::pairWiseSum(double &A1, double &A2, //
819 const double linearCoefAbs, //
820 const MultipleScatteringCorrectionDistGraber &distGraber, //
821 const std::vector<double> &LS1s, //
822 const std::vector<double> &L12s, //
823 const std::vector<double> &L2Ds, //
824 const int64_t startIndex, const int64_t endIndex) const {
825 if (endIndex - startIndex > MAX_INTEGRATION_LENGTH) {
826 int64_t middle = findMiddle(startIndex, endIndex);
827
828 // recursive to process upper and lower part
829 pairWiseSum(A1, A2, linearCoefAbs, distGraber, LS1s, L12s, L2Ds, startIndex, middle);
830 pairWiseSum(A1, A2, linearCoefAbs, distGraber, LS1s, L12s, L2Ds, middle, endIndex);
831 } else {
832 // perform the integration
833 const auto &elementVolumes = distGraber.m_elementVolumes;
834 const auto nElements = distGraber.m_numVolumeElements;
835 for (int64_t i = startIndex; i < endIndex; ++i) {
836 // compute A1
837 double exponent = (LS1s[i] + L2Ds[i]) * linearCoefAbs;
838 A1 += exp(exponent) * elementVolumes[i];
839 // compute A2
840 double a2 = 0.0;
841 for (int64_t j = 0; j < int64_t(nElements); ++j) {
842 if (i == j) {
843 // skip self (second order scattering must happen in a different element)
844 continue;
845 }
846 // L12 is a pre-computed vector, therefore we can use the index directly
847 size_t idx_l12 = i < j ? calcLinearIdxFromUpperTriangular(nElements, i, j)
848 : calcLinearIdxFromUpperTriangular(nElements, j, i);
849 // compute a2 component
850 const auto l12 = L12s[idx_l12];
851 if (l12 > 0.0) {
852 exponent = (LS1s[i] + L12s[idx_l12] + L2Ds[j]) * linearCoefAbs;
853 a2 += exp(exponent) * elementVolumes[j] / (L12s[idx_l12] * L12s[idx_l12]);
854 }
855 }
856 A2 += a2 * elementVolumes[i];
857 }
858 }
859}
860
862 double &A1, double &A2, //
863 const double linearCoefAbsContainer, const double linearCoefAbsSample, //
864 const int64_t numVolumeElementsContainer, const int64_t numVolumeElementsTotal, //
865 const double totScatterCoefContainer, // rho * sigma_s * unit_scaling
866 const double totScatterCoefSample, // unit_scaling = 100
867 const std::vector<double> &elementVolumes, //
868 const std::vector<double> &LS1sContainer, const std::vector<double> &LS1sSample, // source -> 1_element
869 const std::vector<double> &L12sContainer, const std::vector<double> &L12sSample, // 1_element -> 2_element
870 const std::vector<double> &L2DsContainer, const std::vector<double> &L2DsSample, // 2_element -> detector
871 const int64_t startIndex, const int64_t endIndex) const {
872 if (endIndex - startIndex > MAX_INTEGRATION_LENGTH) {
873 int64_t middle = findMiddle(startIndex, endIndex);
874 // recursive to process upper and lower part
875 pairWiseSum(A1, A2, linearCoefAbsContainer, linearCoefAbsSample, numVolumeElementsContainer, numVolumeElementsTotal,
876 totScatterCoefContainer, totScatterCoefSample, elementVolumes, LS1sContainer, LS1sSample, L12sContainer,
877 L12sSample, L2DsContainer, L2DsSample, startIndex, middle);
878 pairWiseSum(A1, A2, linearCoefAbsContainer, linearCoefAbsSample, numVolumeElementsContainer, numVolumeElementsTotal,
879 totScatterCoefContainer, totScatterCoefSample, elementVolumes, LS1sContainer, LS1sSample, L12sContainer,
880 L12sSample, L2DsContainer, L2DsSample, middle, endIndex);
881 } else {
882 // perform the integration
883 for (int64_t i = startIndex; i < endIndex; ++i) {
884 const double factor_i = i > numVolumeElementsContainer ? totScatterCoefSample : totScatterCoefContainer;
885 // compute A1
886 double exponent = (LS1sContainer[i] + L2DsContainer[i]) * linearCoefAbsContainer +
887 (LS1sSample[i] + L2DsSample[i]) * linearCoefAbsSample;
888 A1 += exp(exponent) * factor_i * elementVolumes[i];
889 // compute A2
890 double a2 = 0.0;
891 for (int64_t j = 0; j < numVolumeElementsTotal; ++j) {
892 if (i == j) {
893 // skip self (second order scattering must happen in a different element)
894 continue;
895 }
896 const double factor_j = j > numVolumeElementsContainer ? totScatterCoefSample : totScatterCoefContainer;
897 // L12 is a pre-computed vector, therefore we can use the index directly
898 size_t idx_l12 = i < j ? calcLinearIdxFromUpperTriangular(numVolumeElementsTotal, i, j)
899 : calcLinearIdxFromUpperTriangular(numVolumeElementsTotal, j, i);
900 const double l12 = L12sContainer[idx_l12] + L12sSample[idx_l12];
901 if (l12 > 0.0) {
902 exponent = (LS1sContainer[i] + L12sContainer[idx_l12] + L2DsContainer[j]) * linearCoefAbsContainer + //
903 (LS1sSample[i] + L12sSample[idx_l12] + L2DsSample[j]) * linearCoefAbsSample;
904 a2 += exp(exponent) * factor_j * elementVolumes[j] / (l12 * l12);
905 }
906 }
907 A2 += a2 * factor_i * elementVolumes[i];
908 }
909 }
910}
911
912} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
#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_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.
std::vector< T > const * vec
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.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Kernel::Logger & g_log
Definition Algorithm.h:422
bool isDefault(const std::string &name) const
A validator which checks that a workspace has a valid instrument.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
A validator which checks that sample has the required properties.
A property class for workspaces.
A validator which checks that the unit of the workspace referred to by a WorkspaceProperty is the exp...
MultipleScatteringCorrectionDistGraber : This is a helper class to calculate the distance from source...
std::vector< Kernel::V3D > m_elementPositions
Cached element positions.
void cacheLS1(const Mantid::Kernel::V3D &beamDirection)
pre-calculate the distance from source to L1 for all the voxels in the sample
void calculateLS1s(const MultipleScatteringCorrectionDistGraber &distGraber, std::vector< double > &LS1s, const Geometry::IObject &shape) const
compute LS1s within given shape for single component case
void calculateSingleComponent(const API::MatrixWorkspace_sptr &outws, const Geometry::IObject &shape, const double elementSize)
calculate the correction factor per detector for sample only case
void parseInputs()
parse and assign corresponding values from input properties
void pairWiseSum(double &A1, double &A2, const double linearCoefAbs, const MultipleScatteringCorrectionDistGraber &distGraber, const std::vector< double > &LS1s, const std::vector< double > &L12s, const std::vector< double > &L2Ds, const int64_t startIndex, const int64_t endIndex) const
double m_sampleElementSize
The size of the integration element for sample in meters.
void calculateSampleAndContainer(const API::MatrixWorkspace_sptr &outws)
calculate the multiple scattering factor (0, 1) for sample and container case
API::MatrixWorkspace_sptr m_inputWS
A pointer to the input workspace.
void calculateL2Ds(const MultipleScatteringCorrectionDistGraber &distGraber, const IDetector &detector, std::vector< double > &L2Ds, const Geometry::IObject &shape) const
Calculate distance between exiting element to the detector for single component case.
int64_t m_num_lambda
The number of points in wavelength, the rest is interpolated linearly.
int64_t m_xStep
The step in bin number between adjacent points for linear interpolation.
void init() override
interface initialisation method
std::map< std::string, std::string > validateInputs() override
validate the inputs
void calculateL12s(const MultipleScatteringCorrectionDistGraber &distGraber, std::vector< double > &L12s, const Geometry::IObject &shape)
calculate L12 for single component case
double m_containerElementSize
the size of the integration element for container in meters
virtual Kernel::V3D getPos() const =0
Get the position of the IComponent. Tree structure is traverse through the.
Interface class for detector objects.
Definition IDetector.h:43
virtual double getPhi() const =0
Gives the phi of this detector object in radians.
virtual std::size_t nDets() const =0
Get the number of physical detectors this object represents.
virtual double getTwoTheta(const Kernel::V3D &observer, const Kernel::V3D &axis) const =0
Gives the angle of this detector object with respect to an axis.
IObject : Interface for geometry objects.
Definition IObject.h:42
virtual int interceptSurface(Geometry::Track &) const =0
virtual const Kernel::Material & material() const =0
Defines a track as a start point and a direction.
Definition Track.h:165
void clearIntersectionResults()
Clear the current set of intersection results.
Definition Track.cpp:55
double totalDistInsideObject() const
Returns the sum of all the links distInsideObject in the track.
Definition Track.cpp:254
void reset(const Kernel::V3D &startPoint, const Kernel::V3D &direction)
Set a starting point and direction.
Definition Track.cpp:45
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
Class for 3D vectors.
Definition V3D.h:34
void spherical(const double R, const double theta, const double phi) noexcept
Sets the vector position based on spherical coordinates.
Definition V3D.cpp:56
double norm() const noexcept
Definition V3D.h:269
std::shared_ptr< WorkspaceGroup > WorkspaceGroup_sptr
shared pointer to Mantid::API::WorkspaceGroup
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
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:352
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
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:53
@ Output
An output workspace.
Definition Property.h:54