Mantid
Loading...
Searching...
No Matches
DiscusMultipleScatteringCorrection.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8#include "MantidAPI/Axis.h"
10#include "MantidAPI/ISpectrum.h"
13#include "MantidAPI/Sample.h"
33
34using namespace Mantid::API;
35using namespace Mantid::Kernel;
38
39namespace {
40constexpr int DEFAULT_NPATHS = 1000;
41constexpr int DEFAULT_SEED = 123456789;
42constexpr int DEFAULT_NSCATTERINGS = 2;
43constexpr int DEFAULT_LATITUDINAL_DETS = 5;
44constexpr int DEFAULT_LONGITUDINAL_DETS = 10;
45
49inline double toWaveVector(double energy) { return sqrt(energy / PhysicalConstants::E_mev_toNeutronWavenumberSq); }
50
52inline double fromWaveVector(double wavevector) {
53 return PhysicalConstants::E_mev_toNeutronWavenumberSq * wavevector * wavevector;
54}
55
56struct EFixedProvider {
57 explicit EFixedProvider(const ExperimentInfo &expt) : m_expt(expt), m_emode(expt.getEMode()), m_EFixed(0.0) {
58 if (m_emode == DeltaEMode::Direct) {
59 m_EFixed = m_expt.getEFixed();
60 }
61 }
62 inline DeltaEMode::Type emode() const { return m_emode; }
63 inline double value(const Mantid::detid_t detID) const {
64 if (m_emode != DeltaEMode::Indirect)
65 return m_EFixed;
66 else
67 return m_expt.getEFixed(detID);
68 }
69
70private:
71 const ExperimentInfo &m_expt;
72 const DeltaEMode::Type m_emode;
73 double m_EFixed;
74};
75} // namespace
76
77namespace Mantid::Algorithms {
78
79std::unique_ptr<DiscusData2D> DiscusData2D::createCopy(bool clearY) {
80 auto data2DNew = std::make_unique<DiscusData2D>();
81 data2DNew->m_data.resize(m_data.size());
82 for (size_t i = 0; i < m_data.size(); i++) {
83 data2DNew->m_data[i].X = m_data[i].X;
84 data2DNew->m_data[i].Y = clearY ? std::vector<double>(m_data[i].Y.size(), 0.) : m_data[i].Y;
85 }
86 data2DNew->m_specAxis = m_specAxis;
87 return data2DNew;
88}
89
90const std::vector<double> &DiscusData2D::getSpecAxisValues() {
91 if (!m_specAxis)
92 throw std::runtime_error("DiscusData2D::getSpecAxisValues - No spec axis has been defined.");
93 return *m_specAxis;
94}
95
96// Register the algorithm into the AlgorithmFactory
98
99
103 // The input workspace must have an instrument
104 auto wsValidator = std::make_shared<InstrumentValidator>();
105
106 declareProperty(
107 std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, wsValidator),
108 "The name of the input workspace. The input workspace must have X units of Momentum (k) for elastic "
109 "calculations and units of energy transfer (DeltaE) for inelastic calculations. This is used to "
110 "supply the sample details, the detector positions and the x axis range to calculate corrections for");
111
112 declareProperty(std::make_unique<WorkspaceProperty<Workspace>>("StructureFactorWorkspace", "", Direction::Input),
113 "The name of the workspace containing S'(q) or S'(q, w). For elastic calculations, the input "
114 "workspace must contain a single spectrum and have X units of momentum transfer. A workspace group "
115 "containing one workspace per component can also be supplied if a calculation is being run on a "
116 "workspace with a sample environment specified");
117 declareProperty(std::make_unique<WorkspaceProperty<WorkspaceGroup>>("OutputWorkspace", "", Direction::Output),
118 "Name for the WorkspaceGroup that will be created. Each workspace in the "
119 "group contains a calculated weight for a particular number of "
120 "scattering events. The number of scattering events varies from 1 up to "
121 "the number supplied in the NumberOfScatterings parameter. The group "
122 "will also include an additional workspace for a calculation with a "
123 "single scattering event where the absorption post scattering has been "
124 "set to zero");
125 auto wsKValidator = std::make_shared<WorkspaceUnitValidator>("Momentum");
126 declareProperty(std::make_unique<WorkspaceProperty<>>("ScatteringCrossSection", "", Direction::Input,
127 PropertyMode::Optional, wsKValidator),
128 "A workspace containing the scattering cross section as a function of k, :math:`\\sigma_s(k)`. Note "
129 "- this parameter would normally be left empty which results in the tabulated cross section data "
130 "being used instead which implies no wavelength dependence");
131
132 auto positiveInt = std::make_shared<Kernel::BoundedValidator<int>>();
133 positiveInt->setLower(1);
134 declareProperty("NumberOfSimulationPoints", EMPTY_INT(), positiveInt,
135 "The number of points on the input workspace x axis for which a simulation is attempted");
136
137 declareProperty("NeutronPathsSingle", DEFAULT_NPATHS, positiveInt,
138 "The number of \"neutron\" paths to generate for single scattering");
139 declareProperty("NeutronPathsMultiple", DEFAULT_NPATHS, positiveInt,
140 "The number of \"neutron\" paths to generate for multiple scattering");
141 declareProperty("SeedValue", DEFAULT_SEED, positiveInt, "Seed the random number generator with this value");
142 auto nScatteringsValidator = std::make_shared<Kernel::BoundedValidator<int>>();
143 nScatteringsValidator->setLower(1);
144 nScatteringsValidator->setUpper(5);
145 declareProperty("NumberScatterings", DEFAULT_NSCATTERINGS, nScatteringsValidator, "Number of scatterings");
146
147 auto interpolateOpt = createInterpolateOption();
148 declareProperty(interpolateOpt->property(), interpolateOpt->propertyDoc());
149 declareProperty("SparseInstrument", false,
150 "Enable simulation on special "
151 "instrument with a sparse grid of "
152 "detectors interpolating the "
153 "results to the real instrument.");
154 auto threeOrMore = std::make_shared<Kernel::BoundedValidator<int>>();
155 threeOrMore->setLower(3);
156 declareProperty("NumberOfDetectorRows", DEFAULT_LATITUDINAL_DETS, threeOrMore,
157 "Number of detector rows in the detector grid of the sparse instrument.");
158 setPropertySettings("NumberOfDetectorRows",
159 std::make_unique<EnabledWhenProperty>("SparseInstrument", ePropertyCriterion::IS_NOT_DEFAULT));
160 auto twoOrMore = std::make_shared<Kernel::BoundedValidator<int>>();
161 twoOrMore->setLower(2);
162 declareProperty("NumberOfDetectorColumns", DEFAULT_LONGITUDINAL_DETS, twoOrMore,
163 "Number of detector columns in the detector grid "
164 "of the sparse instrument.");
165 setPropertySettings("NumberOfDetectorColumns",
166 std::make_unique<EnabledWhenProperty>("SparseInstrument", ePropertyCriterion::IS_NOT_DEFAULT));
167 declareProperty("ImportanceSampling", false,
168 "Enable importance sampling on the Q value chosen on multiple scatters based on Q.S(Q)");
169 // Control the number of attempts made to generate a random point in the object
170 declareProperty("MaxScatterPtAttempts", 5000, positiveInt,
171 "Maximum number of tries made to generate a scattering point "
172 "within the sample. Objects with holes in them, e.g. a thin "
173 "annulus can cause problems if this number is too low.\n"
174 "If a scattering point cannot be generated by increasing "
175 "this value then there is most likely a problem with "
176 "the sample geometry.");
177 declareProperty("SimulateEnergiesIndependently", false,
178 "For inelastic calculation, whether the results for adjacent energy transfer bins are simulated "
179 "separately. Currently applies to Direct geometry only");
180 declareProperty("NormalizeStructureFactors", false,
181 "Enable normalization of supplied structure factor(s). May be required when running a calculation "
182 "involving more than one material where the normalization of the default S(Q)=1 structure factor "
183 "doesn't match the normalization of a supplied non-isotropic structure factor");
184}
185
190std::map<std::string, std::string> DiscusMultipleScatteringCorrection::validateInputs() {
191 std::map<std::string, std::string> issues;
192 MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
193 if (inputWS == nullptr) {
194 // Mainly aimed at groups. Group ws pass the property validation on MatrixWorkspace type if all members are
195 // MatrixWorkspaces. We output a WorkspaceGroup for a single input workspace so can't manage input groups
196 issues["InputWorkspace"] = "Input workspace must be a matrix workspace";
197 return issues;
198 }
199 Geometry::IComponent_const_sptr sample = inputWS->getInstrument()->getSample();
200 if (!sample)
201 issues["InputWorkspace"] = "Input workspace does not have a Sample";
202
203 bool atLeastOneValidShape = inputWS->sample().getShape().hasValidShape();
204 if (!atLeastOneValidShape) {
205 if (inputWS->sample().hasEnvironment()) {
206 auto env = &inputWS->sample().getEnvironment();
207 for (size_t i = 0; i < env->nelements(); i++) {
208 if (env->getComponent(i).hasValidShape()) {
209 atLeastOneValidShape = true;
210 break;
211 }
212 }
213 }
214 }
215 if (!atLeastOneValidShape) {
216 issues["InputWorkspace"] = "Either the Sample or one of the environment parts must have a valid shape.";
217 }
218
219 if (inputWS->sample().getShape().hasValidShape())
220 if (inputWS->sample().getMaterial().numberDensity() == 0)
221 issues["InputWorkspace"] = "Sample must have a material set up with a non-zero number density";
222 if (inputWS->sample().hasEnvironment()) {
223 auto env = &inputWS->sample().getEnvironment();
224 for (size_t i = 0; i < env->nelements(); i++)
225 if (env->getComponent(i).hasValidShape())
226 if (env->getComponent(i).material().numberDensity() == 0)
227 issues["InputWorkspace"] = "Sample environment component " + std::to_string(i) +
228 " must have a material set up with a non-zero number density ";
229 }
230
231 std::vector<MatrixWorkspace_sptr> SQWSs;
232 Workspace_sptr SQWSBase = getProperty("StructureFactorWorkspace");
233 auto SQWSGroup = std::dynamic_pointer_cast<WorkspaceGroup>(SQWSBase);
234 if (SQWSGroup) {
235 auto groupMembers = SQWSGroup->getAllItems();
236 std::set<std::string> materialNames;
237 materialNames.insert(inputWS->sample().getMaterial().name());
238 if (inputWS->sample().hasEnvironment()) {
239 auto nEnvComponents = inputWS->sample().getEnvironment().nelements();
240 for (size_t i = 0; i < nEnvComponents; i++)
241 materialNames.insert(inputWS->sample().getEnvironment().getComponent(i).material().name());
242 }
243
244 for (auto &materialName : materialNames) {
245 auto wsIt = std::find_if(groupMembers.begin(), groupMembers.end(),
246 [materialName](Workspace_sptr &ws) { return ws->getName() == materialName; });
247 if (wsIt == groupMembers.end()) {
248 issues["StructureFactorWorkspace"] =
249 "No workspace for material " + materialName + " found in S(Q,w) workspace group";
250 } else
251 SQWSs.push_back(std::dynamic_pointer_cast<MatrixWorkspace>(*wsIt));
252 }
253 } else
254 SQWSs.push_back(std::dynamic_pointer_cast<MatrixWorkspace>(SQWSBase));
255
256 if (inputWS->getEMode() == Kernel::DeltaEMode::Elastic) {
257 if (inputWS->getAxis(0)->unit()->unitID() != "Momentum")
258 issues["InputWorkspace"] += "Input workspace must have units of Momentum (k) for elastic instrument\n";
259 for (auto &SQWS : SQWSs) {
260 if (SQWS->getNumberHistograms() != 1)
261 issues["StructureFactorWorkspace"] += "S(Q) workspace must contain a single spectrum for elastic mode\n";
262
263 if (SQWS->getAxis(0)->unit()->unitID() != "MomentumTransfer")
264 issues["StructureFactorWorkspace"] += "S(Q) workspace must have units of MomentumTransfer\n";
265 }
266 } else {
267 for (auto &SQWS : SQWSs) {
268 if (inputWS->getAxis(0)->unit()->unitID() != "DeltaE")
269 issues["InputWorkspace"] = "Input workspace must have units of DeltaE for inelastic instrument\n";
270 std::set<std::string> axisUnits;
271 axisUnits.insert(SQWS->getAxis(0)->unit()->unitID());
272 axisUnits.insert(SQWS->getAxis(1)->unit()->unitID());
273 if (axisUnits != std::set<std::string>{"DeltaE", "MomentumTransfer"})
274 issues["StructureFactorWorkspace"] +=
275 "S(Q, w) workspace must have units of Energy Transfer and MomentumTransfer\n";
276
277 if (SQWS->getAxis(1)->isSpectra())
278 issues["StructureFactorWorkspace"] += "S(Q, w) must have a numeric spectrum axis\n";
279 std::vector<double> wValues;
280 if (SQWS->getAxis(0)->unit()->unitID() == "DeltaE") {
281 if (!SQWS->isCommonBins())
282 issues["StructureFactorWorkspace"] += "S(Q,w) must have common w values at all Q";
283 }
284
285 auto checkEqualQBins = [&issues](const MantidVec &qValues) {
286 Kernel::EqualBinsChecker checker(qValues, 1.0E-07, -1);
287 if (!checker.validate().empty())
288 issues["StructureFactorWorkspace"] +=
289 "S(Q,w) must have equal size bins in Q in order to support gaussian interpolation";
290 ;
291 };
292
293 if (SQWS->getAxis(0)->unit()->unitID() == "MomentumTransfer") {
294 for (size_t iHist = 0; iHist < SQWS->getNumberHistograms(); iHist++) {
295 auto qValues = SQWS->dataX(iHist);
296 checkEqualQBins(qValues);
297 }
298 } else if (SQWS->getAxis(1)->unit()->unitID() == "MomentumTransfer") {
299 auto qAxis = dynamic_cast<NumericAxis *>(SQWS->getAxis(1));
300 if (qAxis) {
301 auto qValues = qAxis->getValues();
302 checkEqualQBins(qValues);
303 }
304 }
305 }
306 }
307
308 for (auto &SQWS : SQWSs) {
309 for (size_t i = 0; i < SQWS->getNumberHistograms(); i++) {
310 auto &y = SQWS->y(i);
311 if (std::any_of(y.cbegin(), y.cend(), [](const auto yval) { return yval < 0 || std::isnan(yval); }))
312 issues["StructureFactorWorkspace"] += "S(Q) workspace must have all y >= 0";
313 }
314 }
315
316 const int nSimulationPoints = getProperty("NumberOfSimulationPoints");
317 if (!isEmpty(nSimulationPoints)) {
318 InterpolationOption interpOpt;
319 const std::string interpValue = getPropertyValue("Interpolation");
320 interpOpt.set(interpValue, false, false);
321 const auto nSimPointsIssue = interpOpt.validateInputSize(nSimulationPoints);
322 if (!nSimPointsIssue.empty())
323 issues["NumberOfSimulationPoints"] = nSimPointsIssue;
324 }
325
326 const bool simulateEnergiesIndependently = getProperty("SimulateEnergiesIndependently");
327 if (simulateEnergiesIndependently) {
328 if (inputWS->getEMode() == Kernel::DeltaEMode::Elastic)
329 issues["SimulateEnergiesIndependently"] =
330 "SimulateEnergiesIndependently is only applicable to inelastic direct geometry calculations";
331 if (inputWS->getEMode() == Kernel::DeltaEMode::Indirect)
332 issues["SimulateEnergiesIndependently"] =
333 "SimulateEnergiesIndependently is only applicable to inelastic direct geometry calculations. Different "
334 "energy transfer bins are always simulated separately for indirect geometry";
335 }
336
337 return issues;
338}
347 double &xmax) const {
348 // set to crazy values to start
349 xmin = std::numeric_limits<double>::max();
350 xmax = -1.0 * xmin;
351 size_t numberOfSpectra = ws.getNumberHistograms();
352 const auto &spectrumInfo = ws.spectrumInfo();
353
354 // determine the data range - only return min > 0. Bins with x=0 will be skipped later on
355 for (size_t wsIndex = 0; wsIndex < numberOfSpectra; wsIndex++) {
356 if (spectrumInfo.hasDetectors(wsIndex) && !spectrumInfo.isMonitor(wsIndex) && !spectrumInfo.isMasked(wsIndex)) {
357 const auto &dataX = ws.points(wsIndex);
358 const double xfront = dataX.front();
359 const double xback = dataX.back();
360 if (std::isnormal(xfront) && std::isnormal(xback)) {
361 if (xfront < xmin)
362 xmin = xfront;
363 if (xback > xmax)
364 xmax = xback;
365 }
366 }
367 }
368 // workspace not partitioned at this point so don't replicate code using m_indexInfo->communicator
369 if (xmin > xmax)
370 throw std::runtime_error("Unable to determine min and max x values for workspace");
371}
372
374 Workspace_sptr suppliedSQWS = getProperty("StructureFactorWorkspace");
375 auto SQWSGroup = std::dynamic_pointer_cast<WorkspaceGroup>(suppliedSQWS);
376 size_t nEnvComponents = 0;
377 if (m_env)
378 nEnvComponents = m_env->nelements();
379 m_SQWSs.clear();
380 if (SQWSGroup) {
381 std::string matName = m_sampleShape->material().name();
382 auto SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
383 addWorkspaceToDiscus2DData(m_sampleShape, matName, SQWSGroupMember);
384 if (nEnvComponents > 0) {
385 matName = m_env->getContainer().material().name();
386 SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
387 addWorkspaceToDiscus2DData(m_env->getContainer().getShapePtr(), matName, SQWSGroupMember);
388 }
389 for (size_t i = 1; i < nEnvComponents; i++) {
390 matName = m_env->getComponent(i).material().name();
391 SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
392 addWorkspaceToDiscus2DData(m_env->getComponentPtr(i), matName, SQWSGroupMember);
393 }
394 } else {
396 std::dynamic_pointer_cast<MatrixWorkspace>(suppliedSQWS));
397 MatrixWorkspace_sptr isotropicSQ = DataObjects::create<Workspace2D>(
398 *std::dynamic_pointer_cast<MatrixWorkspace>(suppliedSQWS), static_cast<size_t>(1),
399 HistogramData::Histogram(HistogramData::Points{0.}, HistogramData::Frequencies{1.}));
400 if (nEnvComponents > 0) {
401 std::string_view matName = m_env->getContainer().material().name();
402 g_log.information() << "Creating isotropic structure factor for " << matName << std::endl;
403 addWorkspaceToDiscus2DData(m_env->getContainer().getShapePtr(), matName, isotropicSQ);
404 }
405 for (size_t i = 1; i < nEnvComponents; i++) {
406 std::string_view matName = m_env->getComponent(i).material().name();
407 g_log.information() << "Creating isotropic structure factor for " << matName << std::endl;
408 addWorkspaceToDiscus2DData(m_env->getComponentPtr(i), matName, isotropicSQ);
409 }
410 }
411}
412
418 const std::string_view &matName,
420 // avoid repeated conversion of bin edges to points inside loop by converting to point data
422 // if S(Q,w) has been supplied ensure Q is along the x axis of each spectrum (so same as S(Q))
423 if (SQWS->getAxis(1)->unit()->unitID() == "MomentumTransfer") {
424 auto transposeAlgorithm = this->createChildAlgorithm("Transpose");
425 transposeAlgorithm->initialize();
426 transposeAlgorithm->setProperty("InputWorkspace", SQWS);
427 transposeAlgorithm->setProperty("OutputWorkspace", "_");
428 transposeAlgorithm->execute();
429 SQWS = transposeAlgorithm->getProperty("OutputWorkspace");
430 } else if (SQWS->getAxis(1)->isSpectra()) {
431 // for elastic set w=0 on the spectrum axis to align code with inelastic
432 auto newAxis = std::make_unique<NumericAxis>(std::vector<double>{0.});
433 newAxis->setUnit("DeltaE");
434 SQWS->replaceAxis(1, std::move(newAxis));
435 }
436 auto specAxis = dynamic_cast<NumericAxis *>(SQWS->getAxis(1));
437 std::vector<DiscusData1D> data;
438 for (size_t i = 0; i < SQWS->getNumberHistograms(); i++) {
439 data.emplace_back(SQWS->histogram(i).dataX(), SQWS->histogram(i).dataY());
440 }
441 ComponentWorkspaceMapping SQWSMapping{
442 shape, matName,
443 std::make_shared<DiscusData2D>(data, std::make_shared<std::vector<double>>(specAxis->getValues()))};
444 SQWSMapping.logSQ = SQWSMapping.SQ->createCopy();
445 convertToLogWorkspace(SQWSMapping.logSQ);
446 m_SQWSs.push_back(SQWSMapping);
447}
448
455 if (ws->isHistogramData()) {
457 auto pointDataAlgorithm = this->createChildAlgorithm("ConvertToPointData");
458 pointDataAlgorithm->initialize();
459 pointDataAlgorithm->setProperty("InputWorkspace", ws);
460 pointDataAlgorithm->setProperty("OutputWorkspace", "_");
461 pointDataAlgorithm->execute();
462 ws = pointDataAlgorithm->getProperty("OutputWorkspace");
463 } else {
464 // flat interpolation is later used on S(Q) so convert to points by assigning Y value to LH bin edge
465 MatrixWorkspace_sptr SQWSPoints =
466 API::WorkspaceFactory::Instance().create(ws, ws->getNumberHistograms(), ws->blocksize(), ws->blocksize());
467 SQWSPoints->setSharedY(0, ws->sharedY(0));
468 SQWSPoints->setSharedE(0, ws->sharedE(0));
469 std::vector<double> newX = ws->histogram(0).dataX();
470 newX.pop_back();
471 SQWSPoints->setSharedX(0, HistogramData::Points(newX).cowData());
472 ws = SQWSPoints;
473 }
474 }
475 auto binAxis = dynamic_cast<BinEdgeAxis *>(ws->getAxis(1));
476 if (binAxis) {
477 auto edges = binAxis->getValues();
478 std::vector<double> centres;
479 VectorHelper::convertToBinCentre(edges, centres);
480 auto newAxis = std::make_unique<NumericAxis>(centres);
481 newAxis->setUnit(ws->getAxis(1)->unit()->unitID());
482 ws->replaceAxis(1, std::move(newAxis));
483 }
484}
485
491 "DiscusMultipleScatteringCorrection is in the beta stage of development. Its name, properties and behaviour "
492 "may change without warning.");
493 if (!getAlwaysStoreInADS())
494 throw std::runtime_error("This algorithm explicitly stores named output workspaces in the ADS so must be run with "
495 "AlwaysStoreInADS set to true");
496 const MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
497
500
501 MatrixWorkspace_sptr sigmaSSWS = getProperty("ScatteringCrossSection");
502 if (sigmaSSWS)
503 m_sigmaSS = std::make_shared<DiscusData1D>(
504 DiscusData1D{sigmaSSWS->getSpectrum(0).readX(), sigmaSSWS->getSpectrum(0).readY()});
505
506 // for inelastic we could calculate the qmax based on the min\max w in the S(Q,w) but that
507 // would bake as assumption that S(Q,w)=0 beyond the limits of the supplied data
508 double qmax = std::numeric_limits<float>::max();
509 EFixedProvider efixed(*inputWS);
510 m_EMode = efixed.emode();
511 g_log.information("EMode=" + DeltaEMode::asString(m_EMode) + " detected");
513 double kmin, kmax;
514 getXMinMax(*inputWS, kmin, kmax);
515 qmax = 2 * kmax;
516 }
517 prepareQSQ(qmax);
518
519 m_simulateEnergiesIndependently = getProperty("SimulateEnergiesIndependently");
520 // call this function with dummy efixed to determine total possible simulation points
521 const auto inputNbins = generateInputKOutputWList(-1.0, inputWS->points(0).rawData()).size();
522
523 int nSimulationPointsInt = getProperty("NumberOfSimulationPoints");
524 size_t nSimulationPoints = static_cast<size_t>(nSimulationPointsInt);
525
526 if (isEmpty(nSimulationPoints)) {
527 nSimulationPoints = inputNbins;
528 } else if (nSimulationPoints > inputNbins) {
529 g_log.warning() << "The requested number of simulation points is larger "
530 "than the maximum number of simulations per spectra. "
531 "Defaulting to "
532 << inputNbins << ".\n ";
533 nSimulationPoints = inputNbins;
534 }
535
536 m_NormalizeSQ = getProperty("NormalizeStructureFactors");
537
538 const bool useSparseInstrument = getProperty("SparseInstrument");
539 SparseWorkspace_sptr sparseWS;
540 if (useSparseInstrument) {
541 const int latitudinalDets = getProperty("NumberOfDetectorRows");
542 const int longitudinalDets = getProperty("NumberOfDetectorColumns");
543 sparseWS = createSparseWorkspace(*inputWS, nSimulationPoints, latitudinalDets, longitudinalDets);
544 }
545 const int nScatters = getProperty("NumberScatterings");
546 m_maxScatterPtAttempts = getProperty("MaxScatterPtAttempts");
547 std::vector<MatrixWorkspace_sptr> simulationWSs;
548 std::vector<MatrixWorkspace_sptr> outputWSs;
549
550 auto noAbsOutputWS = createOutputWorkspace(*inputWS);
551 auto noAbsSimulationWS = useSparseInstrument ? sparseWS->clone() : noAbsOutputWS;
552 for (int i = 0; i < nScatters; i++) {
553 auto outputWS = createOutputWorkspace(*inputWS);
554 MatrixWorkspace_sptr simulationWS = useSparseInstrument ? sparseWS->clone() : outputWS;
555 simulationWSs.emplace_back(simulationWS);
556 outputWSs.emplace_back(outputWS);
557 }
558 const MatrixWorkspace &instrumentWS = useSparseInstrument ? *sparseWS : *inputWS;
559 const auto nhists = useSparseInstrument ? sparseWS->getNumberHistograms() : inputWS->getNumberHistograms();
560
561 const int nSingleScatterEvents = getProperty("NeutronPathsSingle");
562 const int nMultiScatterEvents = getProperty("NeutronPathsMultiple");
563
564 const int seed = getProperty("SeedValue");
565
566 InterpolationOption interpolateOpt;
567 interpolateOpt.set(getPropertyValue("Interpolation"), false, true);
568
569 m_importanceSampling = getProperty("ImportanceSampling");
570
571 Progress prog(this, 0.0, 1.0, nhists * nSimulationPoints);
572 prog.setNotifyStep(0.1);
573 const std::string reportMsg = "Computing corrections";
574
575 bool enableParallelFor = true;
576 enableParallelFor = std::all_of(simulationWSs.cbegin(), simulationWSs.cend(),
577 [](const MatrixWorkspace_sptr &ws) { return Kernel::threadSafe(*ws); });
578
579 enableParallelFor = enableParallelFor && Kernel::threadSafe(*noAbsOutputWS);
580
581 const auto &spectrumInfo = instrumentWS.spectrumInfo();
582
583 PARALLEL_FOR_IF(enableParallelFor)
584 for (int64_t i = 0; i < static_cast<int64_t>(nhists); ++i) { // signed int for openMP loop
586
587 auto &spectrum = instrumentWS.getSpectrum(i);
588 Mantid::specnum_t specNo = spectrum.getSpectrumNo();
589 MersenneTwister rng(seed + specNo);
590 // no two theta for monitors
591
592 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i) && !spectrumInfo.isMasked(i)) {
593
594 const double eFixedValue = efixed.value(spectrumInfo.detector(i).getID());
595 auto xPoints = instrumentWS.points(i).rawData();
596
597 auto kInW = generateInputKOutputWList(eFixedValue, xPoints);
598
599 const auto nbins = kInW.size();
600 // step size = index range / number of steps requested
601 const size_t nsteps = std::max(static_cast<size_t>(1), nSimulationPoints - 1);
602 const size_t xStepSize = nbins == 1 ? 1 : (nbins - 1) / nsteps;
603
604 const auto detPos = spectrumInfo.position(i);
605
606 // create copy of the SQ workspaces vector and fully copy any members that will be modified
607 auto componentWorkspaces = m_SQWSs;
608
610 // prep invPOfQ outside the bin loop to avoid costly construction\destruction
611 createInvPOfQWorkspaces(componentWorkspaces, 2);
612
613 std::vector<double> kValues;
614 std::transform(kInW.begin(), kInW.end(), std::back_inserter(kValues),
615 [](std::tuple<double, int, double> t) { return std::get<0>(t); });
616 calculateQSQIntegralAsFunctionOfK(componentWorkspaces, kValues);
617
618 for (size_t bin = 0; bin < nbins; bin += xStepSize) {
619 const double kinc = std::get<0>(kInW[bin]);
620 if ((kinc <= 0) || std::isnan(kinc)) {
621 g_log.warning("Skipping calculation for bin with invalid x, workspace index=" + std::to_string(i) +
622 " bin index=" + std::to_string(std::get<1>(kInW[bin])));
623 continue;
624 }
625 std::vector<double> wValues = std::get<1>(kInW[bin]) == -1 ? xPoints : std::vector{std::get<2>(kInW[bin])};
626
628 prepareCumulativeProbForQ(kinc, componentWorkspaces);
629
630 auto weights = simulatePaths(nSingleScatterEvents, 1, rng, componentWorkspaces, kinc, wValues, detPos, true);
631 if (std::get<1>(kInW[bin]) == -1) {
632 noAbsSimulationWS->getSpectrum(i).mutableY() += weights;
633 } else {
634 noAbsSimulationWS->getSpectrum(i).dataY()[std::get<1>(kInW[bin])] = weights[0];
635 }
636
637 for (int ne = 0; ne < nScatters; ne++) {
638 int nEvents = ne == 0 ? nSingleScatterEvents : nMultiScatterEvents;
639
640 weights = simulatePaths(nEvents, ne + 1, rng, componentWorkspaces, kinc, wValues, detPos, false);
641 if (std::get<1>(kInW[bin]) == -1.0) {
642 simulationWSs[ne]->getSpectrum(i).mutableY() += weights;
643 } else {
644 simulationWSs[ne]->getSpectrum(i).dataY()[std::get<1>(kInW[bin])] = weights[0];
645 }
646 }
647
648 prog.report(reportMsg);
649
650 // Ensure we have the last point for the interpolation
651 if (xStepSize > 1 && bin + xStepSize >= nbins && bin + 1 != nbins) {
652 bin = nbins - xStepSize - 1;
653 }
654 } // bins
655
656 // interpolate through points not simulated. Simulation WS only has
657 // reduced X values if using sparse instrument so no interpolation
658 // required
659 if (!useSparseInstrument && xStepSize > 1) {
660 auto histNoAbs = noAbsSimulationWS->histogram(i);
661 if (xStepSize < nbins) {
662 interpolateOpt.applyInplace(histNoAbs, xStepSize);
663 } else {
664 std::fill(histNoAbs.mutableY().begin() + 1, histNoAbs.mutableY().end(), histNoAbs.y()[0]);
665 }
666 noAbsOutputWS->setHistogram(i, histNoAbs);
667
668 for (size_t ne = 0; ne < static_cast<size_t>(nScatters); ne++) {
669 auto histnew = simulationWSs[ne]->histogram(i);
670 if (xStepSize < nbins) {
671 interpolateOpt.applyInplace(histnew, xStepSize);
672 } else {
673 std::fill(histnew.mutableY().begin() + 1, histnew.mutableY().end(), histnew.y()[0]);
674 }
675 outputWSs[ne]->setHistogram(i, histnew);
676 }
677 }
678 }
679
681 }
683
684 if (useSparseInstrument) {
685 Poco::Thread::sleep(200); // to ensure prog message changes
686 const std::string reportMsgSpatialInterpolation = "Spatial Interpolation";
687 prog.report(reportMsgSpatialInterpolation);
688 interpolateFromSparse(*noAbsOutputWS, *std::dynamic_pointer_cast<SparseWorkspace>(noAbsSimulationWS),
689 interpolateOpt);
690 for (size_t ne = 0; ne < static_cast<size_t>(nScatters); ne++) {
691 interpolateFromSparse(*outputWSs[ne], *std::dynamic_pointer_cast<SparseWorkspace>(simulationWSs[ne]),
692 interpolateOpt);
693 }
694 }
695
696 // Create workspace group that holds output workspaces
697 auto wsgroup = std::make_shared<WorkspaceGroup>();
698 auto outputGroupWSName = getPropertyValue("OutputWorkspace");
699 if (AnalysisDataService::Instance().doesExist(outputGroupWSName))
700 API::AnalysisDataService::Instance().deepRemoveGroup(outputGroupWSName);
701
702 const std::string wsNamePrefix = outputGroupWSName + "_Scatter_";
703 std::string wsName = wsNamePrefix + "1_NoAbs";
704 setWorkspaceName(noAbsOutputWS, wsName);
705 wsgroup->addWorkspace(noAbsOutputWS);
706
707 for (size_t i = 0; i < outputWSs.size(); i++) {
708 wsName = wsNamePrefix + std::to_string(i + 1);
709 setWorkspaceName(outputWSs[i], wsName);
710 wsgroup->addWorkspace(outputWSs[i]);
711
712 auto integratedWorkspace = integrateWS(outputWSs[i]);
713 setWorkspaceName(integratedWorkspace, wsName + "_Integrated");
714 wsgroup->addWorkspace(integratedWorkspace);
715 }
716
717 if (outputWSs.size() > 1) {
718 // create sum of multiple scatter workspaces for use in subtraction method
719 auto summedMScatOutput = createOutputWorkspace(*inputWS);
720 for (size_t i = 1; i < outputWSs.size(); i++) {
721 summedMScatOutput = summedMScatOutput + outputWSs[i];
722 }
723 wsName = wsNamePrefix + "2_" + std::to_string(outputWSs.size()) + "_Summed";
724 setWorkspaceName(summedMScatOutput, wsName);
725 wsgroup->addWorkspace(summedMScatOutput);
726 // create sum of all scattering order workspaces for use in ratio method
727 auto summedAllScatOutput = createOutputWorkspace(*inputWS);
728 summedAllScatOutput = summedMScatOutput + outputWSs[0];
729 wsName = wsNamePrefix + "1_" + std::to_string(outputWSs.size()) + "_Summed";
730 setWorkspaceName(summedAllScatOutput, wsName);
731 wsgroup->addWorkspace(summedAllScatOutput);
732 // create ratio of single to all scatter
733 auto ratioOutput = createOutputWorkspace(*inputWS);
734 ratioOutput = outputWSs[0] / summedAllScatOutput;
735 wsName = outputGroupWSName + "_Ratio_Single_To_All";
736 setWorkspaceName(ratioOutput, wsName);
737 wsgroup->addWorkspace(ratioOutput);
738
739 // ConvFit method being investigated by Spencer for inelastic currently uses the opposite ratio
741 auto invRatioOutput = 1 / ratioOutput;
742 auto replaceNans = this->createChildAlgorithm("ReplaceSpecialValues");
743 replaceNans->setChild(true);
744 replaceNans->initialize();
745 replaceNans->setProperty("InputWorkspace", invRatioOutput);
746 replaceNans->setProperty("OutputWorkspace", invRatioOutput);
747 replaceNans->setProperty("NaNValue", 0.0);
748 replaceNans->setProperty("InfinityValue", 0.0);
749 replaceNans->execute();
750 wsName = outputGroupWSName + "_Ratio_All_To_Single";
751 setWorkspaceName(invRatioOutput, wsName);
752 wsgroup->addWorkspace(invRatioOutput);
753 }
754 }
755
756 // set the output property
757 setProperty("OutputWorkspace", wsgroup);
758
759 if (g_log.is(Kernel::Logger::Priority::PRIO_INFORMATION)) {
760 g_log.information() << "Total simulation points=" << nhists * nSimulationPoints << "\n";
761 for (auto &kv : m_attemptsToGenerateInitialTrack)
762 g_log.information() << "Generating initial track required " << kv.first << " attempts on " << kv.second
763 << " occasions.\n";
764 g_log.information() << "Calls to interceptSurface=" << m_callsToInterceptSurface << "\n";
765 g_log.information() << "Total I(k) calculations=" << m_IkCalculations << ", average per simulation point="
766 << static_cast<double>(m_IkCalculations) / static_cast<double>(nhists * nSimulationPoints)
767 << "\n";
768 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG))
769 for (size_t i = 0; i < m_SQWSs.size(); i++)
770 g_log.information() << "Scatters in component " << i << ": " << *(m_SQWSs[i].scatterCount) << "\n";
771 }
772}
773
782std::vector<std::tuple<double, int, double>>
783DiscusMultipleScatteringCorrection::generateInputKOutputWList(const double efixed, const std::vector<double> &xPoints) {
784 std::vector<std::tuple<double, int, double>> kInW;
785 const double kFixed = toWaveVector(efixed);
787 int index = 0;
788 std::transform(xPoints.begin(), xPoints.end(), std::back_inserter(kInW), [&index](double d) {
789 auto t = std::make_tuple(d, index, 0.);
790 index++;
791 return t;
792 });
793 } else {
795 kInW.emplace_back(std::make_tuple(kFixed, -1, 0.));
796 else {
797 for (int i = 0; i < static_cast<int>(xPoints.size()); i++) {
799 kInW.emplace_back(std::make_tuple(kFixed, i, xPoints[i]));
800 else if (m_EMode == DeltaEMode::Indirect) {
801 const double initialE = efixed + xPoints[i];
802 if (initialE > 0) {
803 const double kin = toWaveVector(initialE);
804 kInW.emplace_back(std::make_tuple(kin, i, xPoints[i]));
805 } else
806 // negative kinc is filtered out later
807 kInW.emplace_back(std::make_tuple(-1.0, i, xPoints[i]));
808 }
809 }
810 }
811 }
812 return kInW;
813}
814
822 for (auto &SQWSMapping : m_SQWSs) {
823 auto &SQWS = SQWSMapping.SQ;
824 std::shared_ptr<DiscusData2D> outputWS = SQWS->createCopy(true);
825 std::vector<double> IOfQYFull;
826 // loop through the S(Q) spectra for the different energy transfer values
827 for (size_t iW = 0; iW < SQWS->getNumberHistograms(); iW++) {
828 std::vector<double> qValues = SQWS->histogram(iW).X;
829 std::vector<double> SQValues = SQWS->histogram(iW).Y;
830 // add terminating points at 0 and qmax before multiplying by Q so no extrapolation problems
831 if (qValues.front() > 0.) {
832 qValues.insert(qValues.begin(), 0.);
833 SQValues.insert(SQValues.begin(), SQValues.front());
834 }
835 if (qValues.back() < qmax) {
836 qValues.push_back(qmax);
837 SQValues.push_back(SQValues.back());
838 }
839 // add some extra points to help the Q.S(Q) integral get the right answer
840 for (size_t i = 1; i < qValues.size(); i++) {
841 if (std::abs(SQValues[i] - SQValues[i - 1]) >
842 std::numeric_limits<double>::epsilon() * std::min(SQValues[i - 1], SQValues[i])) {
843 qValues.insert(qValues.begin() + i, std::nextafter(qValues[i], -DBL_MAX));
844 SQValues.insert(SQValues.begin() + i, SQValues[i - 1]);
845 i++;
846 }
847 }
848
849 std::vector<double> QSQValues;
850 std::transform(SQValues.begin(), SQValues.end(), qValues.begin(), std::back_inserter(QSQValues),
851 std::multiplies<double>());
852
853 outputWS->histogram(iW).X.resize(qValues.size());
854 outputWS->histogram(iW).X = qValues;
855 outputWS->histogram(iW).Y.resize(QSQValues.size());
856 outputWS->histogram(iW).Y = QSQValues;
857 }
858 SQWSMapping.QSQ = outputWS;
859 }
860}
861
872std::tuple<std::vector<double>, std::vector<double>, std::vector<double>>
873DiscusMultipleScatteringCorrection::integrateQSQ(const std::shared_ptr<DiscusData2D> &QSQ, double kinc,
874 const bool returnCumulative) {
875 std::vector<double> IOfQYFull, qValuesFull, wIndices;
876 double IOfQMaxPreviousRow = 0.;
877
878 auto &wValues = QSQ->getSpecAxisValues();
879 std::vector<double> wWidths;
880 if (wValues.size() == 1) {
881 // convertToBinBoundary currently gives width of 1 for single point but because this is essential for the maths
882 // set the width to 1 explicitly
883 wWidths.push_back(1.);
884 } else {
885 std::vector<double> wBinEdges;
886 wBinEdges.reserve(wValues.size() + 1);
887 VectorHelper::convertToBinBoundary(wValues, wBinEdges);
888 std::adjacent_difference(wBinEdges.begin(), wBinEdges.end(), std::back_inserter(wWidths));
889 wWidths.erase(wWidths.begin()); // first element returned by adjacent_difference isn't a diff so delete it
890 }
891
892 double wMax = fromWaveVector(kinc);
893 auto it = std::lower_bound(wValues.begin(), wValues.end(), wMax);
894 size_t iFirstInaccessibleW = std::distance(wValues.begin(), it);
895 auto nAccessibleWPoints = iFirstInaccessibleW;
896
897 // loop through the S(Q) spectra for the different energy transfer values
898 std::vector<double> IOfQX, IOfQY;
899 // reserve minimum space required for performance
900 IOfQYFull.reserve(nAccessibleWPoints);
901 qValuesFull.reserve(nAccessibleWPoints);
902 wIndices.reserve(nAccessibleWPoints);
903 //}
904 for (size_t iW = 0; iW < nAccessibleWPoints; iW++) {
905 auto kf = getKf((wValues)[iW], kinc);
906 auto [qmin, qrange] = getKinematicRange(kf, kinc);
907 IOfQX.clear();
908 IOfQY.clear();
909 integrateCumulative(QSQ->histogram(iW), qmin, qmin + qrange, IOfQX, IOfQY, returnCumulative);
910 // w bin width for elastic will equal 1
911 double wBinWidth = wWidths[iW];
912 std::transform(IOfQY.begin(), IOfQY.end(), IOfQY.begin(),
913 [IOfQMaxPreviousRow, wBinWidth](double d) -> double { return d * wBinWidth + IOfQMaxPreviousRow; });
914 IOfQMaxPreviousRow = IOfQY.back();
915 IOfQYFull.insert(IOfQYFull.end(), IOfQY.begin(), IOfQY.end());
916 qValuesFull.insert(qValuesFull.end(), IOfQX.begin(), IOfQX.end());
917 wIndices.insert(wIndices.end(), IOfQX.size(), static_cast<double>(iW));
918 }
920 return {IOfQYFull, qValuesFull, wIndices};
921}
922
931 double kinc, const ComponentWorkspaceMappings &materialWorkspaces) {
932 for (size_t iMat = 0; iMat < materialWorkspaces.size(); iMat++) {
933 auto QSQ = materialWorkspaces[iMat].QSQ;
934 auto [IOfQYFull, qValuesFull, wIndices] = integrateQSQ(QSQ, kinc, true);
935 auto IOfQYAtQMax = IOfQYFull.empty() ? 0. : IOfQYFull.back();
936 if (IOfQYAtQMax == 0.)
937 throw std::runtime_error("Integral of Q * S(Q) is zero so can't generate probability distribution");
938 // normalise probability range to 0-1
939 std::vector<double> IOfQYNorm;
940 std::transform(IOfQYFull.begin(), IOfQYFull.end(), std::back_inserter(IOfQYNorm),
941 [IOfQYAtQMax](double d) -> double { return d / IOfQYAtQMax; });
942 // Store the normalized integral (= cumulative probability) on the x axis
943 // The y values in the two spectra store Q, w (or w index to be precise)
944 auto &InvPOfQ = materialWorkspaces[iMat].InvPOfQ;
945 for (size_t i = 0; i < InvPOfQ->getNumberHistograms(); i++) {
946 InvPOfQ->histogram(i).X.resize(IOfQYNorm.size());
947 InvPOfQ->histogram(i).X = IOfQYNorm;
948 }
949 InvPOfQ->histogram(0).Y.resize(qValuesFull.size());
950 InvPOfQ->histogram(0).Y = qValuesFull;
951 InvPOfQ->histogram(1).Y.resize(wIndices.size());
952 InvPOfQ->histogram(1).Y = wIndices;
953 }
954}
955
956void DiscusMultipleScatteringCorrection::convertToLogWorkspace(const std::shared_ptr<DiscusData2D> &SOfQ) {
957 // generate log of the structure factor to support gaussian interpolation
958
959 for (size_t i = 0; i < SOfQ->getNumberHistograms(); i++) {
960 auto &ySQ = SOfQ->histogram(i).Y;
961
962 std::transform(ySQ.begin(), ySQ.end(), ySQ.begin(), [](double d) -> double {
963 const double exp_that_gives_close_to_zero = -20.0;
964 if (d == 0.)
965 return exp_that_gives_close_to_zero;
966 else
967 return std::log(d);
968 });
969 }
970}
971
981 const std::vector<double> &specialKs) {
982 for (auto &SQWSMapping : matWSs) {
983 std::set<double> kValues(specialKs.begin(), specialKs.end());
984 // Calculate the integral for a range of k values. Not massively important which k values but choose them here
985 // based on the q points in the S(Q) profile and the initial k values incident on the sample
986 const std::vector<double> qValues = SQWSMapping.SQ->histogram(0).X;
987 for (auto q : qValues) {
988 if (q > 0)
989 kValues.insert(q / 2);
990 }
991
992 // add a few extra points beyond supplied q range to ensure capture asymptotic value of integral/2*k*k.
993 // Useful when doing a flat interpolation on m_QSQIntegral during inelastic calculation where k not known up front
995 double maxSuppliedQ = qValues.back();
996 if (maxSuppliedQ > 0.) {
997 kValues.insert(maxSuppliedQ);
998 kValues.insert(2 * maxSuppliedQ);
999 }
1000 }
1001
1002 std::vector<double> finalkValues, QSQIntegrals;
1003 for (auto k : kValues) {
1004 std::vector<double> IOfQYFull;
1005 std::tie(IOfQYFull, std::ignore, std::ignore) = integrateQSQ(SQWSMapping.QSQ, k, false);
1006 auto IOfQYAtQMax = IOfQYFull.empty() ? 0. : IOfQYFull.back();
1007 // going to divide by this so storing zero results not useful - and don't want to interpolate a zero value
1008 // into a k region where the integral is actually non-zero
1009 if (IOfQYAtQMax > 0) {
1010 double normalisedIntegral = IOfQYAtQMax / (2 * k * k);
1011 finalkValues.push_back(k);
1012 QSQIntegrals.push_back(normalisedIntegral);
1013 }
1014 }
1015 auto QSQScaleFactor = std::make_shared<DiscusData1D>(DiscusData1D{finalkValues, QSQIntegrals});
1016 SQWSMapping.QSQScaleFactor = QSQScaleFactor;
1017 }
1018}
1019
1035 const double xmax, std::vector<double> &resultX,
1036 std::vector<double> &resultY,
1037 const bool returnCumulative) {
1038 const std::vector<double> &xValues = h.X;
1039 const std::vector<double> &yValues = h.Y;
1040 bool isPoints = xValues.size() == yValues.size();
1041
1042 // set the integral to zero at xmin
1043 if (returnCumulative) {
1044 resultX.emplace_back(xmin);
1045 resultY.emplace_back(0.);
1046 }
1047 double sum = 0;
1048
1049 // ensure there's a point at xmin
1050 if (xValues.front() > xmin)
1051 throw std::runtime_error("Distribution doesn't extend as far as lower integration limit, x=" +
1052 std::to_string(xmin));
1053 // ...and a terminating point. Q.S(Q) generally not flat so assuming flat extrapolation not v useful
1054 if (xValues.back() < xmax)
1055 throw std::runtime_error("Distribution doesn't extend as far as upper integration limit, x=" +
1056 std::to_string(xmax));
1057
1058 auto iter = std::upper_bound(xValues.cbegin(), xValues.cend(), xmin);
1059 auto iRight = static_cast<size_t>(std::distance(xValues.cbegin(), iter));
1060
1061 auto linearInterp = [&xValues, &yValues](const double x, const size_t lIndex, const size_t rIndex) -> double {
1062 return (yValues[lIndex] * (xValues[rIndex] - x) + yValues[rIndex] * (x - xValues[lIndex])) /
1063 (xValues[rIndex] - xValues[lIndex]);
1064 };
1065 double yToUse;
1066
1067 // deal with partial initial segments
1068 if (xmin > xValues[iRight - 1]) {
1069 if (xmax >= xValues[iRight]) {
1070 if (isPoints) {
1071 double interpY = linearInterp(xmin, iRight - 1, iRight);
1072 yToUse = 0.5 * (interpY + yValues[iRight]);
1073 } else
1074 yToUse = yValues[iRight - 1];
1075 sum += yToUse * (xValues[iRight] - xmin);
1076 if (returnCumulative) {
1077 resultX.push_back(xValues[iRight]);
1078 resultY.push_back(sum);
1079 }
1080 iRight++;
1081 } else {
1082 if (isPoints) {
1083 double interpY1 = linearInterp(xmin, iRight - 1, iRight);
1084 double interpY2 = linearInterp(xmax, iRight - 1, iRight);
1085 yToUse = 0.5 * (interpY1 + interpY2);
1086 } else
1087 yToUse = yValues[iRight - 1];
1088 sum += yToUse * (xmax - xmin);
1089 if (returnCumulative) {
1090 resultX.push_back(xmax);
1091 resultY.push_back(sum);
1092 }
1093 iRight++;
1094 }
1095 }
1096
1097 // integrate the intervals between each pair of points. Do this until right point is at end of vector or > xmax
1098 for (; iRight < xValues.size() && xValues[iRight] <= xmax; iRight++) {
1099 if (isPoints)
1100 yToUse = 0.5 * (yValues[iRight - 1] + yValues[iRight]);
1101 else
1102 yToUse = yValues[iRight - 1];
1103 double xLeft = xValues[iRight - 1];
1104 double xRight = xValues[iRight];
1105 sum += yToUse * (xRight - xLeft);
1106 if (returnCumulative) {
1107 if (xRight > std::nextafter(xLeft, DBL_MAX)) {
1108 resultX.emplace_back(xRight);
1109 resultY.emplace_back(sum);
1110 }
1111 }
1112 }
1113
1114 // integrate a partial final interval if xmax is between points
1115 if ((xmax > xValues[iRight - 1]) && (xmin <= xValues[iRight - 1])) {
1116 if (isPoints) {
1117 double interpY = linearInterp(xmax, iRight - 1, iRight);
1118 yToUse = 0.5 * (yValues[iRight - 1] + interpY);
1119 } else
1120 yToUse = yValues[iRight - 1];
1121 sum += yToUse * (xmax - xValues[iRight - 1]);
1122 if (returnCumulative) {
1123 resultX.emplace_back(xmax);
1124 resultY.emplace_back(sum);
1125 }
1126 }
1127 if (!returnCumulative) {
1128 resultX.emplace_back(xmax);
1129 resultY.emplace_back(sum);
1130 }
1131}
1132
1134 auto wsIntegrals = DataObjects::create<Workspace2D>(*ws, HistogramData::Points{0.});
1135 for (size_t i = 0; i < ws->getNumberHistograms(); i++) {
1136 std::vector<double> IOfQX, IOfQY;
1137 integrateCumulative(DiscusData1D{ws->histogram(i).dataX(), ws->histogram(i).dataY()}, ws->x(i).front(),
1138 ws->x(i).back(), IOfQX, IOfQY, false);
1139 wsIntegrals->mutableY(i) = IOfQY.back();
1140 }
1141 return wsIntegrals;
1142}
1143
1154std::tuple<double, double> DiscusMultipleScatteringCorrection::new_vector(const Material &material, double k,
1155 bool specialSingleScatterCalc) {
1156 double scatteringXSection, absorbXsection;
1157 if (specialSingleScatterCalc) {
1158 absorbXsection = 0;
1159 } else {
1160 const double wavelength = 2 * M_PI / k;
1161 absorbXsection = material.absorbXSection(wavelength);
1162 }
1163 if (m_sigmaSS) {
1164 scatteringXSection = interpolateFlat(*m_sigmaSS, k);
1165 } else {
1166 scatteringXSection = material.totalScatterXSection();
1167 }
1168
1169 const auto sig_total = scatteringXSection + absorbXsection;
1170 return {sig_total, scatteringXSection};
1171}
1172
1180std::tuple<double, int>
1181DiscusMultipleScatteringCorrection::sampleQW(const std::shared_ptr<DiscusData2D> &CumulativeProb, double x) {
1182 return {interpolateSquareRoot(CumulativeProb->histogram(0), x),
1183 static_cast<int>(interpolateFlat(CumulativeProb->histogram(1), x))};
1184}
1185
1192 const auto &histx = histToInterpolate.X;
1193 const auto &histy = histToInterpolate.Y;
1194 assert(histToInterpolate.X.size() == histToInterpolate.Y.size());
1195 if (x > histx.back()) {
1196 return histy.back();
1197 }
1198 if (x < histx.front()) {
1199 return histy.front();
1200 }
1201 const auto iter = std::upper_bound(histx.cbegin(), histx.cend(), x);
1202 const auto idx = static_cast<size_t>(std::distance(histx.cbegin(), iter) - 1);
1203 const double x0 = histx[idx];
1204 const double x1 = histx[idx + 1];
1205 const double asq = (pow(histy[idx + 1], 2) - pow(histy[idx], 2)) / (x1 - x0);
1206 if (asq == 0.) {
1207 throw std::runtime_error("Cannot perform square root interpolation on supplied distribution");
1208 }
1209 const double b = x0 - pow(histy[idx], 2) / asq;
1210 return sqrt(asq * (x - b));
1211}
1212
1220 auto &xHisto = histToInterpolate.X;
1221 auto &yHisto = histToInterpolate.Y;
1222 if (x > xHisto.back()) {
1223 return yHisto.back();
1224 }
1225 if (x < xHisto.front()) {
1226 return yHisto.front();
1227 }
1228 // may be useful at some point to introduce a tolerance here in case x is just below a step change but seems to behave
1229 // OK for now
1230 auto iter = std::upper_bound(xHisto.cbegin(), xHisto.cend(), x);
1231 auto idx = static_cast<size_t>(std::distance(xHisto.cbegin(), iter) - 1);
1232 return yHisto[idx];
1233}
1234
1243 // could have written using points() method so it also worked on histogram data but found that the points
1244 // method was bottleneck on multithreaded code due to cow_ptr atomic_load
1245 assert(histToInterpolate.X.size() == histToInterpolate.Y.size());
1246 if (x > histToInterpolate.X.back()) {
1247 return exp(histToInterpolate.Y.back());
1248 }
1249 if (x < histToInterpolate.X.front()) {
1250 return exp(histToInterpolate.Y.front());
1251 }
1252 // assume log(cross section) is quadratic in k
1253 auto deltax = histToInterpolate.X[1] - histToInterpolate.X[0];
1254
1255 auto iter = std::upper_bound(histToInterpolate.X.cbegin(), histToInterpolate.X.cend(), x);
1256 auto idx = static_cast<size_t>(std::distance(histToInterpolate.X.cbegin(), iter) - 1);
1257
1258 // need at least two points to the right of the x value for the quadratic
1259 // interpolation to work
1260 auto ny = histToInterpolate.Y.size();
1261 if (ny < 3) {
1262 throw std::runtime_error("Need at least 3 y values to perform quadratic interpolation");
1263 }
1264 if (idx > ny - 3) {
1265 idx = ny - 3;
1266 }
1267 // this interpolation assumes the set of 3 bins\point have the same width
1268 // U=0 on point or bin edge to the left of where x lies
1269 const auto U = (x - histToInterpolate.X[idx]) / deltax;
1270 const auto &y = histToInterpolate.Y;
1271 const auto A = (y[idx] - 2 * y[idx + 1] + y[idx + 2]) / 2;
1272 const auto B = (-3 * y[idx] + 4 * y[idx + 1] - y[idx + 2]) / 2;
1273 const auto C = y[idx];
1274 return exp(A * U * U + B * U + C);
1275}
1276
1287 double w) {
1288 double SQ = 0.;
1289 int iW = -1;
1290 auto &wValues = SQWSMapping.SQ->getSpecAxisValues();
1291 if (wValues.size() == 1) {
1292 // don't use indexOfValue here because for single point it invents a bin width of +/-0.5
1293 if (w == (wValues)[0])
1294 iW = 0;
1295 } else
1296 try {
1297 // required w values will often equal the points in the S(Q,w) distribution so pick nearest value
1298 iW = static_cast<int>(Kernel::VectorHelper::indexOfValueFromCentersNoThrow(wValues, w));
1299 } catch (std::out_of_range &) {
1300 }
1301 if (iW >= 0) {
1303 // the square root interpolation used to look up Q, w in InvPOfQ is based on flat interpolation of S(Q) so use
1304 // same interpolation here for consistency
1305 SQ = interpolateFlat(SQWSMapping.SQ->histogram(iW), q);
1306 else
1307 SQ = interpolateGaussian(SQWSMapping.logSQ->histogram(iW), q);
1308 }
1309
1310 return SQ;
1311}
1312
1331 const int nPaths, const int nScatters, Kernel::PseudoRandomNumberGenerator &rng,
1332 ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector<double> &wValues,
1333 const Kernel::V3D &detPos, bool specialSingleScatterCalc) {
1334 std::vector<double> sumOfWeights(wValues.size(), 0.);
1335 std::vector<int> countZeroWeights(wValues.size(),
1336 0); // for debugging and analysis of where importance sampling may help
1337
1338 for (int ie = 0; ie < nPaths; ie++) {
1339 auto [success, weights] =
1340 scatter(nScatters, rng, componentWorkspaces, kinc, wValues, detPos, specialSingleScatterCalc);
1341 if (success) {
1342 std::transform(weights.begin(), weights.end(), sumOfWeights.begin(), sumOfWeights.begin(), std::plus<double>());
1343 std::transform(weights.begin(), weights.end(), countZeroWeights.begin(), countZeroWeights.begin(),
1344 [](double d, int count) { return d > 0. ? count : count + 1; });
1345 } else
1346 ie--;
1347 }
1348 for (size_t i = 0; i < wValues.size(); i++) {
1349 sumOfWeights[i] = sumOfWeights[i] / nPaths;
1350 }
1351
1352 return sumOfWeights;
1353}
1354
1373std::tuple<bool, std::vector<double>>
1375 const ComponentWorkspaceMappings &componentWorkspaces, const double kinc,
1376 const std::vector<double> &wValues, const Kernel::V3D &detPos,
1377 bool specialSingleScatterCalc) {
1378 double weight = 1;
1379
1380 auto track = start_point(rng);
1381 auto shapeObjectWithScatter =
1382 updateWeightAndPosition(track, weight, kinc, rng, specialSingleScatterCalc, componentWorkspaces);
1383 double scatteringXSection;
1384 std::tie(std::ignore, scatteringXSection) =
1385 new_vector(shapeObjectWithScatter->material(), kinc, specialSingleScatterCalc);
1386
1387 auto currentComponentWorkspaces = componentWorkspaces;
1388 double k = kinc;
1389 for (int iScat = 0; iScat < nScatters - 1; iScat++) {
1390 if ((k != kinc)) {
1392 auto newComponentWorkspaces = componentWorkspaces;
1393 for (auto &SQWSMapping : currentComponentWorkspaces)
1394 SQWSMapping.InvPOfQ = SQWSMapping.InvPOfQ->createCopy();
1395 prepareCumulativeProbForQ(k, newComponentWorkspaces);
1396 currentComponentWorkspaces = newComponentWorkspaces;
1397 }
1398 }
1399 auto trackStillAlive =
1400 q_dir(track, shapeObjectWithScatter, currentComponentWorkspaces, k, scatteringXSection, rng, weight);
1401 if (!trackStillAlive)
1402 return {true, std::vector<double>(wValues.size(), 0.)};
1403 int nlinks = m_sampleShape->interceptSurface(track);
1404 if (m_env) {
1405 nlinks += m_env->interceptSurfaces(track);
1407 }
1409 if (nlinks == 0) {
1410 return {false, {0.}};
1411 }
1412 shapeObjectWithScatter =
1413 updateWeightAndPosition(track, weight, k, rng, specialSingleScatterCalc, componentWorkspaces);
1414 std::tie(std::ignore, scatteringXSection) =
1415 new_vector(shapeObjectWithScatter->material(), k, specialSingleScatterCalc);
1416 }
1417
1418 Kernel::V3D directionToDetector = detPos - track.startPoint();
1419 Kernel::V3D prevDirection = track.direction();
1420 directionToDetector.normalize();
1421 track.reset(track.startPoint(), directionToDetector);
1422 int nlinks = m_sampleShape->interceptSurface(track);
1424 if (m_env) {
1425 nlinks += m_env->interceptSurfaces(track);
1427 }
1428 // due to VALID_INTERCEPT_POINT_SHIFT some tracks that skim the surface
1429 // of a CSGObject sample may not generate valid tracks. Start over again
1430 // for this event
1431 if (nlinks == 0) {
1432 return {false, {0.}};
1433 }
1434 std::vector<double> weights;
1435 auto scatteringXSectionFull = shapeObjectWithScatter->material().totalScatterXSection();
1436 // Step through required overall energy transfer (w) values and work out what
1437 // w that means for the final scatter. There will be a single w value for elastic
1438 // Slightly different approach to original DISCUS code. It stepped through the w values
1439 // in the supplied S(Q,w) distribution and applied each one to the final scatter. If
1440 // this resulted in an overall w that equalled one of the required w values it was output.
1441 // That approach implicitly assumed S(Q,w)=0 where not specified and that no interpolation
1442 // on w would be needed - this may be what's required but seems possible it might not always be
1443 for (auto &w : wValues) {
1444 const double finalE = fromWaveVector(kinc) - w;
1445 if (finalE > 0) {
1446 const double kout = toWaveVector(finalE);
1447 const auto qVector = directionToDetector * kout - prevDirection * k;
1448 const double q = qVector.norm();
1449 const double finalW = fromWaveVector(k) - finalE;
1450 auto componentWSIt = findMatchingComponent(componentWorkspaces, shapeObjectWithScatter);
1451 auto componentWSMapping = *componentWSIt; // to help debugging
1452 double SQ = Interpolate2D(componentWSMapping, q, finalW);
1453 scatteringXSection = m_NormalizeSQ ? scatteringXSection / interpolateFlat(*(componentWSMapping.QSQScaleFactor), k)
1454 : scatteringXSectionFull;
1455
1456 double AT2 = 1;
1457 for (auto it = track.cbegin(); it != track.cend(); it++) {
1458 double sigma_total;
1459 auto &materialPassingThrough = it->object->material();
1460 std::tie(sigma_total, std::ignore) = new_vector(materialPassingThrough, kout, specialSingleScatterCalc);
1461 double numberDensity = materialPassingThrough.numberDensityEffective();
1462 double vmu = 100 * numberDensity * sigma_total;
1463 if (specialSingleScatterCalc)
1464 vmu = 0;
1465 const double dl = it->distInsideObject;
1466 AT2 *= exp(-dl * vmu);
1467 }
1468 weights.emplace_back(weight * AT2 * SQ * scatteringXSection / (4 * M_PI));
1469 } else {
1470 weights.emplace_back(0.);
1471 }
1472 }
1473 return {true, weights};
1474}
1475
1476double DiscusMultipleScatteringCorrection::getKf(const double deltaE, const double kinc) {
1477 double kf;
1478 if (deltaE == 0.) {
1479 kf = kinc; // avoid costly sqrt
1480 } else {
1481 // slightly concerned that rounding errors moving between k and E may mean we take the sqrt of
1482 // a negative number in here. deltaE was capped using a threshold calculated using fromWaveVector so
1483 // hopefully any rounding will affect fromWaveVector(kinc) in same direction
1484 kf = toWaveVector(fromWaveVector(kinc) - deltaE);
1485 assert(!std::isnan(kf));
1486 }
1487 return kf;
1488} // namespace Mantid::Algorithms
1489
1502std::tuple<double, double> DiscusMultipleScatteringCorrection::getKinematicRange(double kf, double ki) {
1503 const double qmin = abs(kf - ki);
1504 const double qrange = 2 * std::min(ki, kf);
1505 return {qmin, qrange};
1506}
1514std::tuple<double, double, int, double>
1516 Kernel::PseudoRandomNumberGenerator &rng, const double kinc) {
1517
1518 // in order to keep integration limits constant sample full range of w even if some not kinematically accessible
1519 // Note - Discus took different approach where it sampled q,w from kinematically accessible range only but it
1520 // only calculated for double scattering and easier to normalise in that case
1521 double wRange;
1522 /*
1523 // The rectangular integration region could be restricted further by limiting w range by calculating max possible w
1524 // TO DO: validate the results for this optimisation
1525 // the energy transfer must always be less than the positive value corresponding to energy going from ki to 0
1526 // Note - this is still the case for indirect because on a multiple scatter the kf isn't kfixed
1527 double wMax = fromWaveVector(kinc);
1528 // find largest w bin centre that is < wmax and then sample w up to the next bin edge
1529 auto it = std::lower_bound(wValues.begin(), wValues.end(), wMax);
1530 int iWMax = static_cast<int>(std::distance(wValues.begin(), it) - 1);*/
1531 int iW = 0;
1532 if (wValues.size() == 1) {
1533 iW = 0;
1534 wRange = 1;
1535 } else {
1536 std::vector<double> wBinEdges;
1537 wBinEdges.reserve(wValues.size() + 1);
1538 VectorHelper::convertToBinBoundary(wValues, wBinEdges);
1539 // w bins not necessarily equal so don't just sample w index
1540 wRange = /*std::min(wMax, wBinEdges[iWMax + 1])*/ wBinEdges.back() - wBinEdges.front();
1541 double w = wBinEdges.front() + rng.nextValue() * wRange;
1542 iW = static_cast<int>(Kernel::VectorHelper::indexOfValueFromCentersNoThrow(wValues, w));
1543 }
1544 double maxkf = toWaveVector(fromWaveVector(kinc) - wValues.front());
1545 double qRange = kinc + maxkf;
1546 double q = qRange * rng.nextValue();
1547 return {q, qRange, iW, wRange};
1548}
1549
1559 // the QSQIntegrals were divided by k^2 so in theory they should be ~flat
1560 return interpolateFlat(QSQScaleFactor, k) * 2 * k * k;
1561}
1562
1575 const ComponentWorkspaceMappings &componentWorkspaces, double &k,
1576 const double scatteringXSection,
1577 Kernel::PseudoRandomNumberGenerator &rng, double &weight) {
1578 const double kinc = k;
1579 double QQ;
1580 int iW;
1581 auto componentWSIt = findMatchingComponent(componentWorkspaces, shapePtr);
1583 std::tie(QQ, iW) = sampleQW(componentWSIt->InvPOfQ, rng.nextValue());
1584 k = getKf(componentWSIt->SQ->getSpecAxisValues()[iW], kinc);
1585 weight = weight * scatteringXSection;
1586 } else {
1587 double qrange, wRange;
1588 auto &wValues = componentWSIt->SQ->getSpecAxisValues();
1589 std::tie(QQ, qrange, iW, wRange) = sampleQWUniform(wValues, rng, kinc);
1590 // if w inaccessible return (ie treat as zero weight) rather than retry so that integration stays over full w
1591 // range
1592 if (fromWaveVector(kinc) - wValues[iW] <= 0)
1593 return false;
1594 k = getKf(wValues[iW], kinc);
1595 double SQ = interpolateGaussian(componentWSIt->logSQ->histogram(iW), QQ);
1596 // integrate over rectangular area of qw space
1597 weight = weight * scatteringXSection * SQ * QQ * qrange * wRange;
1598 if (SQ > 0) {
1599 double integralQSQ = getQSQIntegral(*componentWSIt->QSQScaleFactor, kinc);
1600 assert(integralQSQ != 0.);
1601 weight = weight / integralQSQ;
1602 } else
1603 return false;
1604 }
1605 // T = 2theta
1606 const double cosT = (kinc * kinc + k * k - QQ * QQ) / (2 * kinc * k);
1607 // if q not accessible return rather than retry so that integration stays over rectangular area
1608 if (std::abs(cosT) > 1.0)
1609 return false;
1610
1611 updateTrackDirection(track, cosT, rng.nextValue() * 2 * M_PI);
1612 return true;
1613}
1614
1622 const double phi) {
1623 const auto B3 = sqrt(1 - cosT * cosT);
1624 const auto B2 = cosT;
1625 // possible to do this using the Quat class instead??
1626 // Quat(const double _deg, const V3D &_axis);
1627 // Quat(acos(cosT)*180/M_PI,
1628 // Kernel::V3D(track.direction()[],track.direction()[],0))
1629
1630 // Rodrigues formula with final term equal to zero
1631 // v_rot = cosT * v + sinT(k x v)
1632 // with rotation axis k orthogonal to v
1633 // Define k by first creating two vectors orthogonal to v:
1634 // (vy, -vx, 0) by inspection
1635 // and then (-vz * vx, -vy * vz, vx * vx + vy * vy) as cross product
1636 // Then define k as combination of these:
1637 // sin(phi) * (vy, -vx, 0) + cos(phi) * (-vx * vz, -vy * vz, 1 - vz * vz)
1638 // ...with division by normalisation factor of sqrt(vx * vx + vy * vy)
1639 // Note: xyz convention here isn't the standard Mantid one. x=beam, z=up
1640 const auto vy = track.direction()[0];
1641 const auto vz = track.direction()[1];
1642 const auto vx = track.direction()[2];
1643 double UKX, UKY, UKZ;
1644 if (vz * vz < 1.0) {
1645 // calculate A2 from vx^2 + vy^2 rather than 1-vz^2 to reduce floating point rounding error when vz close to
1646 // 1
1647 auto A2 = sqrt(vx * vx + vy * vy);
1648 auto UQTZ = cos(phi) * A2;
1649 auto UQTX = -cos(phi) * vz * vx / A2 + sin(phi) * vy / A2;
1650 auto UQTY = -cos(phi) * vz * vy / A2 - sin(phi) * vx / A2;
1651 UKX = B2 * vx + B3 * UQTX;
1652 UKY = B2 * vy + B3 * UQTY;
1653 UKZ = B2 * vz + B3 * UQTZ;
1654 } else {
1655 // definition of phi in general formula is dependent on v. So may see phi "redefinition" as vx and vy tend
1656 // to zero and you move from general formula to this special case
1657 UKX = B3 * cos(phi);
1658 UKY = B3 * sin(phi);
1659 UKZ = B2 * vz;
1660 }
1661 track.reset(track.startPoint(), Kernel::V3D(UKY, UKZ, UKX));
1662}
1663
1672 for (int i = 0; i < m_maxScatterPtAttempts; i++) {
1673 auto t = generateInitialTrack(rng);
1674 int nlinks = m_sampleShape->interceptSurface(t);
1676 if (m_env) {
1677 nlinks += m_env->interceptSurfaces(t);
1679 }
1680 if (nlinks > 0) {
1681 if (i > 0) {
1682 if (g_log.is(Kernel::Logger::Priority::PRIO_WARNING)) {
1684 }
1685 }
1686 return t;
1687 }
1688 }
1689 throw std::runtime_error(
1690 "DiscusMultipleScatteringCorrection::start_point() - Unable to generate entry point into sample after " +
1691 std::to_string(m_maxScatterPtAttempts) + " attempts. Try increasing MaxScatterPtAttempts");
1692}
1693
1706 Geometry::Track &track, double &weight, const double k, Kernel::PseudoRandomNumberGenerator &rng,
1707 bool specialSingleScatterCalc, const ComponentWorkspaceMappings &componentWorkspaces) {
1708 double totalMuL = 0.;
1709 auto nlinks = track.count();
1710 // Set default size to 5 (same as in LineIntersectVisit.h)
1711 boost::container::small_vector<std::tuple<const Geometry::IObject *, double, double, double>, 5> geometryObjects;
1712 geometryObjects.reserve(nlinks);
1713 // loop through all the track segments calculating some useful quantities for later
1714 for (auto it = track.cbegin(); it != track.cend(); it++) {
1715 const double trackSegLength = it->distInsideObject;
1716 const auto geometryObj = it->object;
1717 double sigma_total;
1718 std::tie(sigma_total, std::ignore) = new_vector(geometryObj->material(), k, specialSingleScatterCalc);
1719 double vmu = 100 * geometryObj->material().numberDensityEffective() * sigma_total;
1720 double muL = trackSegLength * vmu;
1721 totalMuL += muL;
1722 // some overlap between the quantities stored here but since calculated them all may as well store them all
1723 geometryObjects.emplace_back(geometryObj, vmu, muL, sigma_total);
1724 }
1725
1726 // randomly sample distance travelled across a total muL and work out which component this sits in
1727 double b4Overall = (1.0 - exp(-totalMuL));
1728 double muL = -log(1 - rng.nextValue() * b4Overall);
1729 double vl = 0.;
1730 double newWeight = 0.;
1731 double prevExpTerms = 1.;
1732 std::tuple<const Geometry::IObject *, double, double, double> geometryObjectDetails;
1733 for (size_t i = 0; i < geometryObjects.size(); i++) {
1734 geometryObjectDetails = geometryObjects[i];
1735 auto muL_i = std::get<2>(geometryObjectDetails);
1736 auto vmu_i = std::get<1>(geometryObjectDetails);
1737 if (muL - muL_i > 0) {
1738 vl += muL_i / vmu_i;
1739 muL = muL - muL_i;
1740 prevExpTerms *= exp(-muL_i);
1741 } else {
1742 vl += muL / vmu_i;
1743 double b4 = (1.0 - exp(-muL_i)) * prevExpTerms;
1744 auto sigma_total = std::get<3>(geometryObjectDetails);
1745 newWeight = b4 / sigma_total;
1746 break;
1747 }
1748 }
1749 weight = weight * newWeight;
1750 // At the moment this doesn't cope if sample shape is concave eg if track has more than one segment inside the
1751 // sample with segment outside sample in between
1752 // Note - this clears the track intersections but the sample\environment shapes live on
1753 inc_xyz(track, vl);
1754 auto geometryObject = std::get<0>(geometryObjectDetails);
1755 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG)) {
1756 auto componentIt = findMatchingComponent(componentWorkspaces, geometryObject);
1757 (*(componentIt->scatterCount))++;
1758 }
1759 return geometryObject;
1760}
1761
1769 // generate random point on front surface of sample bounding box
1770 // The change of variables from length to t1 means this still samples the points fairly in the integration
1771 // volume even in shapes like cylinders where the depth varies across xy
1772 auto neutron = m_beamProfile->generatePoint(rng, m_activeRegion);
1773 auto ptx = neutron.startPos.X();
1774 auto pty = neutron.startPos.Y();
1775
1776 auto ptOnBeamProfile = Kernel::V3D();
1777 ptOnBeamProfile[m_refframe->pointingHorizontal()] = ptx;
1778 ptOnBeamProfile[m_refframe->pointingUp()] = pty;
1779 ptOnBeamProfile[m_refframe->pointingAlongBeam()] = m_sourcePos[m_refframe->pointingAlongBeam()];
1780 auto toSample = Kernel::V3D();
1781 toSample[m_refframe->pointingAlongBeam()] = 1.;
1782 return Geometry::Track(ptOnBeamProfile, toSample);
1783}
1784
1793 Kernel::V3D position = track.front().entryPoint;
1794 Kernel::V3D direction = track.direction();
1795 const auto x = position[0] + vl * direction[0];
1796 const auto y = position[1] + vl * direction[1];
1797 const auto z = position[2] + vl * direction[2];
1798 const auto startPoint = V3D(x, y, z);
1800 track.reset(startPoint, track.direction());
1801}
1802
1812std::shared_ptr<SparseWorkspace>
1814 const size_t rows, const size_t columns) {
1815 auto sparseWS = std::make_shared<SparseWorkspace>(modelWS, nXPoints, rows, columns);
1816 return sparseWS;
1817}
1818
1820 for (auto &SQWSMapping : matWSs) {
1821 auto &QSQ = SQWSMapping.QSQ;
1822 size_t expectedMaxSize =
1823 std::accumulate(QSQ->histograms().cbegin(), QSQ->histograms().cend(), static_cast<size_t>(0),
1824 [](const size_t value, const DiscusData1D &histo) { return value + histo.Y.size(); });
1825 auto ws = std::make_shared<DiscusData2D>(std::vector<DiscusData1D>(nhists), nullptr);
1826 ws->histogram(0).X.reserve(expectedMaxSize);
1827 for (size_t i = 0; i < nhists; i++)
1828 ws->histogram(i).Y.reserve(expectedMaxSize);
1829 SQWSMapping.InvPOfQ = ws;
1830 }
1831}
1832
1834 MatrixWorkspace_uptr outputWS = DataObjects::create<Workspace2D>(inputWS);
1835 // The algorithm computes the signal values at bin centres so they should
1836 // be treated as a distribution
1837 outputWS->setDistribution(true);
1838 outputWS->setYUnit("");
1839 outputWS->setYUnitLabel("Scattered Weight");
1840 return outputWS;
1841}
1842
1849 auto interpolationOpt = std::make_unique<InterpolationOption>();
1850 return interpolationOpt;
1851}
1852
1854 MatrixWorkspace &targetWS, const SparseWorkspace &sparseWS,
1855 const Mantid::Algorithms::InterpolationOption &interpOpt) {
1856 const auto &spectrumInfo = targetWS.spectrumInfo();
1857 const auto refFrame = targetWS.getInstrument()->getReferenceFrame();
1858 PARALLEL_FOR_IF(Kernel::threadSafe(targetWS, sparseWS))
1859 for (int64_t i = 0; i < static_cast<decltype(i)>(spectrumInfo.size()); ++i) {
1861 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i)) {
1862 double lat, lon;
1863 std::tie(lat, lon) = spectrumInfo.geographicalAngles(i);
1864 const auto spatiallyInterpHisto = sparseWS.bilinearInterpolateFromDetectorGrid(lat, lon);
1865 if (spatiallyInterpHisto.size() > 1) {
1866 auto targetHisto = targetWS.histogram(i);
1867 interpOpt.applyInPlace(spatiallyInterpHisto, targetHisto);
1868 targetWS.setHistogram(i, targetHisto);
1869 } else {
1870 targetWS.mutableY(i) = spatiallyInterpHisto.y().front();
1871 }
1872 }
1874 }
1876}
1877
1885 bool noClash(false);
1886
1887 for (int i = 0; !noClash; ++i) {
1888 std::string wsIndex; // dont use an index if there is no other
1889 // workspace
1890 if (i > 0) {
1891 wsIndex = "_" + std::to_string(i);
1892 }
1893
1894 bool wsExists = AnalysisDataService::Instance().doesExist(wsName + wsIndex);
1895 if (!wsExists) {
1896 wsName += wsIndex;
1897 noClash = true;
1898 }
1899 }
1900}
1901
1910 API::AnalysisDataService::Instance().addOrReplace(wsName, ws);
1911}
1912
1921 const Geometry::IObject *shapeObjectWithScatter) {
1922 // Currently look up based on the raw pointer value. Did consider looking up based on something more human readable
1923 // such as the component id or name but this isn't guaranteed to be set and a string key may be longer than the
1924 // pointer which is probably 8 bytes
1925 auto componentWSIt = std::find_if(componentWorkspaces.begin(), componentWorkspaces.end(),
1926 [shapeObjectWithScatter](const ComponentWorkspaceMapping &SQWS) {
1927 return SQWS.ComponentPtr.get() == shapeObjectWithScatter;
1928 });
1929 assert(componentWSIt != componentWorkspaces.end());
1930 // can't return iterator because boost have moved vec_iterator into a different namespace post v1.65.1 so won't
1931 // build on all platforms
1932 return &(*componentWSIt);
1933}
1934
1936 m_sampleShape = inputWS->sample().getShapePtr();
1937 try {
1938 m_env = &inputWS->sample().getEnvironment();
1939 } catch (std::runtime_error &) {
1940 // swallow this as no defined environment from getEnvironment
1941 }
1942 // generate the bounding box before the multithreaded section
1943 m_activeRegion = m_sampleShape->getBoundingBox();
1944 if (m_env) {
1945 const auto &envBox = m_env->boundingBox();
1946 m_activeRegion.grow(envBox);
1947 }
1948 auto instrument = inputWS->getInstrument();
1949 m_beamProfile = BeamProfileFactory::createBeamProfile(*instrument, inputWS->sample());
1950 m_refframe = instrument->getReferenceFrame();
1951 m_sourcePos = instrument->getSource()->getPos();
1952}
1953
1954} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
double value
The value of the point.
Definition: FitMW.cpp:51
double energy
Definition: GetAllEi.cpp:157
double position
Definition: GetAllEi.cpp:154
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
int count
counter
Definition: Matrix.cpp:37
#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...
Definition: MultiThreaded.h:94
#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::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
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.
Definition: Algorithm.cpp:842
Kernel::Logger & g_log
Definition: Algorithm.h:451
bool getAlwaysStoreInADS() const override
Returns true if we always store in the AnalysisDataService.
Definition: Algorithm.cpp:188
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
Stores numeric values that are assumed to be bin edge values.
Definition: BinEdgeAxis.h:20
This class is shared by a few Workspace types and holds information related to a particular experimen...
const SpectrumInfo & spectrumInfo() const
Return a reference to the SpectrumInfo object.
Geometry::Instrument_const_sptr getInstrument() const
Returns the parameterized instrument.
specnum_t getSpectrumNo() const
Definition: ISpectrum.cpp:123
Base MatrixWorkspace Abstract Class.
virtual ISpectrum & getSpectrum(const size_t index)=0
Return the underlying ISpectrum ptr at the given workspace index.
HistogramData::Points points(const size_t index) const
virtual std::size_t getNumberHistograms() const =0
Returns the number of histograms in the workspace.
void setHistogram(const size_t index, T &&...data) &
HistogramData::Histogram histogram(const size_t index) const
Returns the Histogram at the given workspace index.
HistogramData::HistogramY & mutableY(const size_t index) &
Class to represent a numeric axis of a workspace.
Definition: NumericAxis.h:29
virtual const std::vector< double > & getValues() const
Return a const reference to the values.
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
A property class for workspaces.
static std::unique_ptr< IBeamProfile > createBeamProfile(const Geometry::Instrument &instrument, const API::Sample &sample)
std::shared_ptr< std::vector< double > > m_specAxis
std::unique_ptr< DiscusData2D > createCopy(bool clearY=false)
Calculates a multiple scattering correction Based on Muscat Fortran code provided by Spencer Howells.
void setWorkspaceName(const API::MatrixWorkspace_sptr &ws, std::string wsName)
Set the name on a workspace, adjusting for potential clashes in the ADS.
void addWorkspaceToDiscus2DData(const Geometry::IObject_const_sptr &shape, const std::string_view &matName, API::MatrixWorkspace_sptr ws)
Function to convert between a Matrix workspace and the internal simplified 2D data structure.
void convertWsBothAxesToPoints(API::MatrixWorkspace_sptr &ws)
Convert x axis of a workspace to points if it's bin edges.
void convertToLogWorkspace(const std::shared_ptr< DiscusData2D > &SOfQ)
void createInvPOfQWorkspaces(ComponentWorkspaceMappings &matWSs, size_t nhists)
std::tuple< double, double > getKinematicRange(double kf, double ki)
Get the range of q values accessible for a particular kinc and kf.
std::tuple< double, double, int, double > sampleQWUniform(const std::vector< double > &wValues, Kernel::PseudoRandomNumberGenerator &rng, const double kinc)
Sample the q and w value for a scattering event without importance sampling.
std::vector< double > simulatePaths(const int nEvents, const int nScatters, Kernel::PseudoRandomNumberGenerator &rng, ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector< double > &wValues, const Kernel::V3D &detPos, bool specialSingleScatterCalc)
Simulates a set of neutron paths through the sample to a specific detector position with each path co...
void updateTrackDirection(Geometry::Track &track, const double cosT, const double phi)
Update the track's direction following a scatter event given theta and phi angles.
std::vector< std::tuple< double, int, double > > generateInputKOutputWList(const double efixed, const std::vector< double > &xPoints)
Generate a list of the k and w points where calculation results are required.
std::tuple< double, double > new_vector(const Kernel::Material &material, double k, bool specialSingleScatterCalc)
Calculate a total cross section using a k-specific scattering cross section Note - a separate tabulat...
std::tuple< bool, std::vector< double > > scatter(const int nScatters, Kernel::PseudoRandomNumberGenerator &rng, const ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector< double > &wValues, const Kernel::V3D &detPos, bool specialSingleScatterCalc)
Simulates a single neutron path through the sample to a specific detector position containing the spe...
Geometry::Track generateInitialTrack(Kernel::PseudoRandomNumberGenerator &rng)
Generate an initial track starting at the source and entering the sample/sample environment at a rand...
std::tuple< double, int > sampleQW(const std::shared_ptr< DiscusData2D > &CumulativeProb, double x)
Use importance sampling to choose a Q and w value for the scatter.
std::map< std::string, std::string > validateInputs() override
Validate the input properties.
API::MatrixWorkspace_sptr integrateWS(const API::MatrixWorkspace_sptr &ws)
const Geometry::IObject * updateWeightAndPosition(Geometry::Track &track, double &weight, const double k, Kernel::PseudoRandomNumberGenerator &rng, bool specialSingleScatterCalc, const ComponentWorkspaceMappings &componentWorkspaces)
update track start point and weight.
void integrateCumulative(const DiscusData1D &h, const double xmin, const double xmax, std::vector< double > &resultX, std::vector< double > &resultY, const bool returnCumulative)
Integrate a distribution between the supplied xmin and xmax values using trapezoid rule without any e...
void calculateQSQIntegralAsFunctionOfK(ComponentWorkspaceMappings &matWSs, const std::vector< double > &specialKs)
This is a generalised version of the normalisation done in the original Discus algorithm The original...
API::MatrixWorkspace_sptr createOutputWorkspace(const API::MatrixWorkspace &inputWS) const
virtual std::unique_ptr< InterpolationOption > createInterpolateOption()
Factory method to return an instance of the required InterpolationOption class.
double interpolateSquareRoot(const DiscusData1D &histToInterpolate, double x)
Interpolate function of the form y = a * sqrt(x - b) ie inverse of a quadratic Used to lookup value i...
bool q_dir(Geometry::Track &track, const Geometry::IObject *shapePtr, const ComponentWorkspaceMappings &invPOfQs, double &k, const double scatteringXSection, Kernel::PseudoRandomNumberGenerator &rng, double &weight)
Update track direction and weight as a result of a scatter.
void prepareSampleBeamGeometry(const API::MatrixWorkspace_sptr &inputWS)
virtual std::shared_ptr< SparseWorkspace > createSparseWorkspace(const API::MatrixWorkspace &modelWS, const size_t nXPoints, const size_t rows, const size_t columns)
Factory method to return an instance of the required SparseInstrument class.
void correctForWorkspaceNameClash(std::string &wsName)
Adjust workspace name in case of clash in the ADS.
const ComponentWorkspaceMapping * findMatchingComponent(const ComponentWorkspaceMappings &componentWorkspaces, const Geometry::IObject *shapeObjectWithScatter)
Lookup a sample or sample environment component in the supplied list.
double interpolateGaussian(const DiscusData1D &histToInterpolate, double x)
Interpolate a value from a spectrum containing Gaussian peaks.
double Interpolate2D(const ComponentWorkspaceMapping &SQWSMapping, double q, double w)
Interpolate value on S(Q,w) surface given a Q and w.
Geometry::Track start_point(Kernel::PseudoRandomNumberGenerator &rng)
Repeatedly attempt to generate an initial track starting at the source and entering the sample at a r...
void inc_xyz(Geometry::Track &track, double vl)
Update the x, y, z position of the neutron (or dV volume element to integrate over).
void prepareCumulativeProbForQ(double kinc, const ComponentWorkspaceMappings &PInvOfQs)
Calculate a cumulative probability distribution for use in importance sampling.
boost::container::small_vector< ComponentWorkspaceMapping, 5 > ComponentWorkspaceMappings
double getQSQIntegral(const DiscusData1D &QSQScaleFactor, double k)
This is a generalised version of the normalisation done in the original Discus algorithm The original...
void interpolateFromSparse(API::MatrixWorkspace &targetWS, const SparseWorkspace &sparseWS, const Mantid::Algorithms::InterpolationOption &interpOpt)
std::shared_ptr< const Geometry::ReferenceFrame > m_refframe
void prepareQSQ(double kinc)
Prepare a profile of Q*S(Q) that will later be used to calculate a cumulative probability distributio...
void getXMinMax(const Mantid::API::MatrixWorkspace &ws, double &xmin, double &xmax) const
This is a variation on the function MatrixWorkspace::getXMinMax with some additional logic eg if x va...
std::tuple< std::vector< double >, std::vector< double >, std::vector< double > > integrateQSQ(const std::shared_ptr< DiscusData2D > &QSQ, double kinc, const bool returnCumulative)
Integrate QSQ over Q and w over the kinematic range accessible for a given kinc.
double interpolateFlat(const DiscusData1D &histToInterpolate, double x)
Interpolate function using flat interpolation from previous point.
Class to provide a consistent interface to an interpolation option on algorithms.
std::string validateInputSize(const size_t size) const
Validate the size of input histogram.
void applyInPlace(const HistogramData::Histogram &in, HistogramData::Histogram &out) const
Apply the interpolation method to the output histogram.
void set(const Value &kind, const bool calculateErrors, const bool independentErrors)
Set the interpolation option.
void applyInplace(HistogramData::Histogram &inOut, size_t stepSize) const
Apply the interpolation method to the given histogram.
Defines functions and utilities to create and deal with sparse instruments.
virtual HistogramData::Histogram bilinearInterpolateFromDetectorGrid(const double lat, const double lon) const
Spatially interpolate a single histogram from nearby detectors using bilinear interpolation method.
Concrete workspace implementation.
Definition: Workspace2D.h:29
void grow(const BoundingBox &other)
Grow the bounding box so that it also encompasses the given box.
const IObject_sptr getShapePtr() const
Definition: Container.h:43
const Kernel::Material & material() const override
Definition: Container.h:93
IObject : Interface for geometry objects.
Definition: IObject.h:41
virtual const Kernel::Material & material() const =0
const Container & getContainer() const
const IObject & getComponent(const size_t index) const
Returns the requested IObject.
Geometry::BoundingBox boundingBox() const
int interceptSurfaces(Track &track) const
Update the given track with intersections within the environment.
const IObject_const_sptr getComponentPtr(const size_t index) const
Defines a track as a start point and a direction.
Definition: Track.h:165
LType::reference front()
Returns a reference to the first link.
Definition: Track.h:211
const Kernel::V3D & startPoint() const
Returns the starting point.
Definition: Track.h:191
void clearIntersectionResults()
Clear the current set of intersection results.
Definition: Track.cpp:55
int count() const
Returns the number of links.
Definition: Track.h:219
const Kernel::V3D & direction() const
Returns the direction as a unit vector.
Definition: Track.h:193
LType::const_iterator cbegin() const
Returns an interator to the start of the set of links (const version)
Definition: Track.h:206
LType::const_iterator cend() const
Returns an interator to one-past-the-end of the set of links (const version)
Definition: Track.h:209
void reset(const Kernel::V3D &startPoint, const Kernel::V3D &direction)
Set a starting point and direction.
Definition: Track.cpp:45
EqualBinsChecker : Checks for evenly spaced bins.
virtual std::string validate() const
Perform validation of the given X array.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
bool is(int level) const
Returns true if at least the given log level is set.
Definition: Logger.cpp:146
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
A material is defined as being composed of a given element, defined as a PhysicalConstants::NeutronAt...
Definition: Material.h:50
double absorbXSection(const double lambda=PhysicalConstants::NeutronAtom::ReferenceLambda) const
Get the absorption cross section at a given wavelength in barns.
Definition: Material.cpp:260
const std::string & name() const
Returns the name of the material.
Definition: Material.cpp:181
double totalScatterXSection() const
Return the total scattering cross section for a given wavelength in barns.
Definition: Material.cpp:252
This implements the the Mersenne Twister 19937 pseudo-random number generator algorithm as a specialz...
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
Definition: ProgressBase.h:51
void setNotifyStep(double notifyStepPct)
Override the frequency at which notifications are sent out.
Defines a 1D pseudo-random number generator, i.e.
virtual double nextValue()=0
Return the next double in the sequence.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Class for 3D vectors.
Definition: V3D.h:34
double normalize()
Make a normalized vector (return norm value)
Definition: V3D.cpp:130
double norm() const noexcept
Definition: V3D.h:263
std::unique_ptr< MatrixWorkspace > MatrixWorkspace_uptr
unique pointer to Mantid::API::MatrixWorkspace
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
Definition: Workspace_fwd.h:20
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< SparseWorkspace > SparseWorkspace_sptr
std::shared_ptr< const IComponent > IComponent_const_sptr
Typdef of a shared pointer to a const IComponent.
Definition: IComponent.h:161
std::shared_ptr< const IObject > IObject_const_sptr
Typdef for a shared pointer to a const object.
Definition: IObject.h:94
void MANTID_KERNEL_DLL convertToBinCentre(const std::vector< double > &bin_edges, std::vector< double > &bin_centres)
Convert an array of bin boundaries to bin center values.
void MANTID_KERNEL_DLL convertToBinBoundary(const std::vector< double > &bin_centers, std::vector< double > &bin_edges)
Convert an array of bin centers to bin boundary values.
int MANTID_KERNEL_DLL indexOfValueFromCentersNoThrow(const std::vector< double > &bin_centers, const double value)
Gets the bin of a value from a vector of bin centers and returns -1 if out of range.
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.
Definition: MultiThreaded.h:22
A namespace containing physical constants that are required by algorithms and unit routines.
Definition: Atom.h:14
static constexpr double E_mev_toNeutronWavenumberSq
Transformation coefficient to transform neutron energy into neutron wavevector: K-neutron[m^-10] = sq...
static constexpr double h
Planck constant in J*s.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition: EmptyValues.h:25
int32_t detid_t
Typedef for a detector ID.
Definition: SpectrumInfo.h:21
std::vector< double > MantidVec
typedef for the data storage used in Mantid matrix workspaces
Definition: cow_ptr.h:172
int32_t specnum_t
Typedef for a spectrum Number.
Definition: IDTypes.h:16
std::string to_string(const wide_integer< Bits, Signed > &n)
static std::string asString(const Type mode)
Return a string representation of the given mode.
Definition: DeltaEMode.cpp:52
Type
Define the available energy transfer modes It is important to assign enums proper numbers,...
Definition: DeltaEMode.h:29
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54