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"
36
37#include <boost/algorithm/string.hpp>
38
39using namespace Mantid::API;
40using namespace Mantid::Kernel;
43
44namespace {
45constexpr int DEFAULT_NPATHS = 1000;
46constexpr int DEFAULT_SEED = 123456789;
47constexpr int DEFAULT_NSCATTERINGS = 2;
48constexpr int DEFAULT_LATITUDINAL_DETS = 5;
49constexpr int DEFAULT_LONGITUDINAL_DETS = 10;
50
54inline double toWaveVector(double energy) { return sqrt(energy / PhysicalConstants::E_mev_toNeutronWavenumberSq); }
55
57inline double fromWaveVector(double wavevector) {
58 return PhysicalConstants::E_mev_toNeutronWavenumberSq * wavevector * wavevector;
59}
60
61struct EFixedProvider {
62 explicit EFixedProvider(const ExperimentInfo &expt) : m_expt(expt), m_emode(expt.getEMode()), m_EFixed(0.0) {
63 if (m_emode == DeltaEMode::Direct) {
64 m_EFixed = m_expt.getEFixed();
65 }
66 }
67 inline DeltaEMode::Type emode() const { return m_emode; }
68 inline double value(const Mantid::detid_t detID) const {
69 if (m_emode != DeltaEMode::Indirect)
70 return m_EFixed;
71 else
72 return m_expt.getEFixed(detID);
73 }
74
75private:
76 const ExperimentInfo &m_expt;
77 const DeltaEMode::Type m_emode;
78 double m_EFixed;
79};
80} // namespace
81
82namespace Mantid::Algorithms {
83
84std::unique_ptr<DiscusData2D> DiscusData2D::createCopy(bool clearY) {
85 auto data2DNew = std::make_unique<DiscusData2D>();
86 data2DNew->m_data.resize(m_data.size());
87 for (size_t i = 0; i < m_data.size(); i++) {
88 data2DNew->m_data[i].X = m_data[i].X;
89 data2DNew->m_data[i].Y = clearY ? std::vector<double>(m_data[i].Y.size(), 0.) : m_data[i].Y;
90 }
91 data2DNew->m_specAxis = m_specAxis;
92 return data2DNew;
93}
94
95const std::vector<double> &DiscusData2D::getSpecAxisValues() {
96 if (!m_specAxis)
97 throw std::runtime_error("DiscusData2D::getSpecAxisValues - No spec axis has been defined.");
98 return *m_specAxis;
99}
100
101// Register the algorithm into the AlgorithmFactory
103
104
108 // The input workspace must have an instrument
109 auto wsValidator = std::make_shared<InstrumentValidator>();
110
111 declareProperty(
112 std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input, wsValidator),
113 "The name of the input workspace. The input workspace must have X units of Momentum (k) for elastic "
114 "calculations and units of energy transfer (DeltaE) for inelastic calculations. This is used to "
115 "supply the sample details, the detector positions and the x axis range to calculate corrections for");
116
117 declareProperty(std::make_unique<WorkspaceProperty<Workspace>>("StructureFactorWorkspace", "", Direction::Input),
118 "The name of the workspace containing S'(q) or S'(q, w). For elastic calculations, the input "
119 "workspace must contain a single spectrum and have X units of momentum transfer. A workspace group "
120 "containing one workspace per component can also be supplied if a calculation is being run on a "
121 "workspace with a sample environment specified");
122 declareProperty(std::make_unique<WorkspaceProperty<WorkspaceGroup>>("OutputWorkspace", "", Direction::Output),
123 "Name for the WorkspaceGroup that will be created. Each workspace in the "
124 "group contains a calculated weight for a particular number of "
125 "scattering events. The number of scattering events varies from 1 up to "
126 "the number supplied in the NumberOfScatterings parameter. The group "
127 "will also include an additional workspace for a calculation with a "
128 "single scattering event where the absorption post scattering has been "
129 "set to zero");
130 auto wsKValidator = std::make_shared<WorkspaceUnitValidator>("Momentum");
131 declareProperty(std::make_unique<WorkspaceProperty<>>("ScatteringCrossSection", "", Direction::Input,
132 PropertyMode::Optional, wsKValidator),
133 "A workspace containing the scattering cross section as a function of k, :math:`\\sigma_s(k)`. Note "
134 "- this parameter would normally be left empty which results in the tabulated cross section data "
135 "being used instead which implies no wavelength dependence");
136
137 auto positiveInt = std::make_shared<Kernel::BoundedValidator<int>>();
138 positiveInt->setLower(1);
139 declareProperty("NumberOfSimulationPoints", EMPTY_INT(), positiveInt,
140 "The number of points on the input workspace x axis for which a simulation is attempted");
141
142 declareProperty("NeutronPathsSingle", DEFAULT_NPATHS, positiveInt,
143 "The number of \"neutron\" paths to generate for single scattering");
144 declareProperty("NeutronPathsMultiple", DEFAULT_NPATHS, positiveInt,
145 "The number of \"neutron\" paths to generate for multiple scattering");
146 declareProperty("SeedValue", DEFAULT_SEED, positiveInt, "Seed the random number generator with this value");
147 auto nScatteringsValidator = std::make_shared<Kernel::BoundedValidator<int>>();
148 nScatteringsValidator->setLower(1);
149 nScatteringsValidator->setUpper(5);
150 declareProperty("NumberScatterings", DEFAULT_NSCATTERINGS, nScatteringsValidator, "Number of scatterings");
151
152 auto interpolateOpt = createInterpolateOption();
153 declareProperty(interpolateOpt->property(), interpolateOpt->propertyDoc());
154 declareProperty("SparseInstrument", false,
155 "Enable simulation on special "
156 "instrument with a sparse grid of "
157 "detectors interpolating the "
158 "results to the real instrument.");
159 auto threeOrMore = std::make_shared<Kernel::BoundedValidator<int>>();
160 threeOrMore->setLower(3);
161 declareProperty("NumberOfDetectorRows", DEFAULT_LATITUDINAL_DETS, threeOrMore,
162 "Number of detector rows in the detector grid of the sparse instrument.");
163 setPropertySettings("NumberOfDetectorRows",
164 std::make_unique<EnabledWhenProperty>("SparseInstrument", ePropertyCriterion::IS_NOT_DEFAULT));
165 auto twoOrMore = std::make_shared<Kernel::BoundedValidator<int>>();
166 twoOrMore->setLower(2);
167 declareProperty("NumberOfDetectorColumns", DEFAULT_LONGITUDINAL_DETS, twoOrMore,
168 "Number of detector columns in the detector grid "
169 "of the sparse instrument.");
170 setPropertySettings("NumberOfDetectorColumns",
171 std::make_unique<EnabledWhenProperty>("SparseInstrument", ePropertyCriterion::IS_NOT_DEFAULT));
172 declareProperty("ImportanceSampling", false,
173 "Enable importance sampling on the Q value chosen on multiple scatters based on Q.S(Q)");
174 // Control the number of attempts made to generate a random point in the object
175 declareProperty("MaxScatterPtAttempts", 5000, positiveInt,
176 "Maximum number of tries made to generate a scattering point "
177 "within the sample. Objects with holes in them, e.g. a thin "
178 "annulus can cause problems if this number is too low.\n"
179 "If a scattering point cannot be generated by increasing "
180 "this value then there is most likely a problem with "
181 "the sample geometry.");
182 declareProperty("SimulateEnergiesIndependently", false,
183 "For inelastic calculation, whether the results for adjacent energy transfer bins are simulated "
184 "separately. Currently applies to Direct geometry only");
185 declareProperty("NormalizeStructureFactors", false,
186 "Enable normalization of supplied structure factor(s). May be required when running a calculation "
187 "involving more than one material where the normalization of the default S(Q)=1 structure factor "
188 "doesn't match the normalization of a supplied non-isotropic structure factor");
189 declareProperty("RadialCollimator", false,
190 "Enable use of a radial collimator that assign zero weights to tracks where the final scatter "
191 "is not in a position that allows the final track segment to pass through the collimator corridor "
192 "which spans from the guage volume toward the each detector");
193}
194
199std::map<std::string, std::string> DiscusMultipleScatteringCorrection::validateInputs() {
200 std::map<std::string, std::string> issues;
201 MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
202 if (inputWS == nullptr) {
203 // Mainly aimed at groups. Group ws pass the property validation on MatrixWorkspace type if all members are
204 // MatrixWorkspaces. We output a WorkspaceGroup for a single input workspace so can't manage input groups
205 issues["InputWorkspace"] = "Input workspace must be a matrix workspace";
206 return issues;
207 }
208 Geometry::IComponent_const_sptr sample = inputWS->getInstrument()->getSample();
209 if (!sample)
210 issues["InputWorkspace"] = "Input workspace does not have a Sample";
211
212 bool atLeastOneValidShape = inputWS->sample().getShape().hasValidShape();
213 if (!atLeastOneValidShape) {
214 if (inputWS->sample().hasEnvironment()) {
215 auto env = &inputWS->sample().getEnvironment();
216 for (size_t i = 0; i < env->nelements(); i++) {
217 if (env->getComponent(i).hasValidShape()) {
218 atLeastOneValidShape = true;
219 break;
220 }
221 }
222 }
223 }
224 if (!atLeastOneValidShape) {
225 issues["InputWorkspace"] = "Either the Sample or one of the environment parts must have a valid shape.";
226 }
227
228 if (inputWS->sample().getShape().hasValidShape())
229 if (inputWS->sample().getMaterial().numberDensity() == 0)
230 issues["InputWorkspace"] = "Sample must have a material set up with a non-zero number density\n";
231 if (inputWS->sample().hasEnvironment()) {
232 auto env = &inputWS->sample().getEnvironment();
233 for (size_t i = 0; i < env->nelements(); i++)
234 if (env->getComponent(i).hasValidShape())
235 if (env->getComponent(i).material().numberDensity() == 0)
236 issues["InputWorkspace"] = "Sample environment component " + std::to_string(i) +
237 " must have a material set up with a non-zero number density\n";
238 }
239
240 std::vector<MatrixWorkspace_sptr> SQWSs;
241 Workspace_sptr SQWSBase = getProperty("StructureFactorWorkspace");
242 auto SQWSGroup = std::dynamic_pointer_cast<WorkspaceGroup>(SQWSBase);
243 if (SQWSGroup) {
244 auto groupMembers = SQWSGroup->getAllItems();
245 std::set<std::string> materialNames;
246 materialNames.insert(inputWS->sample().getMaterial().name());
247 if (inputWS->sample().hasEnvironment()) {
248 auto nEnvComponents = inputWS->sample().getEnvironment().nelements();
249 for (size_t i = 0; i < nEnvComponents; i++)
250 materialNames.insert(inputWS->sample().getEnvironment().getComponent(i).material().name());
251 }
252
253 for (auto &materialName : materialNames) {
254 auto wsIt = std::find_if(groupMembers.begin(), groupMembers.end(),
255 [materialName](Workspace_sptr &ws) { return ws->getName() == materialName; });
256 if (wsIt == groupMembers.end()) {
257 issues["StructureFactorWorkspace"] =
258 "No workspace for material " + materialName + " found in S(Q,w) workspace group";
259 } else
260 SQWSs.push_back(std::dynamic_pointer_cast<MatrixWorkspace>(*wsIt));
261 }
262 } else
263 SQWSs.push_back(std::dynamic_pointer_cast<MatrixWorkspace>(SQWSBase));
264
265 if (inputWS->getEMode() == Kernel::DeltaEMode::Elastic) {
266 if (inputWS->getAxis(0)->unit()->unitID() != "Momentum")
267 issues["InputWorkspace"] += "Input workspace must have units of Momentum (k) for elastic instrument\n";
268 for (auto &SQWS : SQWSs) {
269 if (SQWS->getNumberHistograms() != 1)
270 issues["StructureFactorWorkspace"] += "S(Q) workspace must contain a single spectrum for elastic mode\n";
271
272 if (SQWS->getAxis(0)->unit()->unitID() != "MomentumTransfer")
273 issues["StructureFactorWorkspace"] += "S(Q) workspace must have units of MomentumTransfer\n";
274 }
275 } else {
276 for (auto &SQWS : SQWSs) {
277 if (inputWS->getAxis(0)->unit()->unitID() != "DeltaE")
278 issues["InputWorkspace"] = "Input workspace must have units of DeltaE for inelastic instrument\n";
279 std::set<std::string> axisUnits;
280 axisUnits.insert(SQWS->getAxis(0)->unit()->unitID());
281 axisUnits.insert(SQWS->getAxis(1)->unit()->unitID());
282 if (axisUnits != std::set<std::string>{"DeltaE", "MomentumTransfer"})
283 issues["StructureFactorWorkspace"] +=
284 "S(Q, w) workspace must have units of Energy Transfer and MomentumTransfer\n";
285
286 if (SQWS->getAxis(1)->isSpectra())
287 issues["StructureFactorWorkspace"] += "S(Q, w) must have a numeric spectrum axis\n";
288 std::vector<double> wValues;
289 if (SQWS->getAxis(0)->unit()->unitID() == "DeltaE") {
290 if (!SQWS->isCommonBins())
291 issues["StructureFactorWorkspace"] += "S(Q,w) must have common w values at all Q";
292 }
293
294 auto checkEqualQBins = [&issues](const MantidVec &qValues) {
295 Kernel::EqualBinsChecker checker(qValues, 1.0E-07, -1);
296 if (!checker.validate().empty())
297 issues["StructureFactorWorkspace"] +=
298 "S(Q,w) must have equal size bins in Q in order to support gaussian interpolation";
299 ;
300 };
301
302 if (SQWS->getAxis(0)->unit()->unitID() == "MomentumTransfer") {
303 for (size_t iHist = 0; iHist < SQWS->getNumberHistograms(); iHist++) {
304 auto qValues = SQWS->dataX(iHist);
305 checkEqualQBins(qValues);
306 }
307 } else if (SQWS->getAxis(1)->unit()->unitID() == "MomentumTransfer") {
308 auto qAxis = dynamic_cast<NumericAxis *>(SQWS->getAxis(1));
309 if (qAxis) {
310 auto qValues = qAxis->getValues();
311 checkEqualQBins(qValues);
312 }
313 }
314 }
315 }
316
317 for (auto &SQWS : SQWSs) {
318 for (size_t i = 0; i < SQWS->getNumberHistograms(); i++) {
319 auto &y = SQWS->y(i);
320 if (std::any_of(y.cbegin(), y.cend(), [](const auto yval) { return yval < 0 || std::isnan(yval); }))
321 issues["StructureFactorWorkspace"] += "S(Q) workspace must have all y >= 0";
322 }
323 }
324
325 const int nSimulationPoints = getProperty("NumberOfSimulationPoints");
326 if (!isEmpty(nSimulationPoints)) {
327 InterpolationOption interpOpt;
328 const std::string interpValue = getPropertyValue("Interpolation");
329 interpOpt.set(interpValue, false, false);
330 const auto nSimPointsIssue = interpOpt.validateInputSize(nSimulationPoints);
331 if (!nSimPointsIssue.empty())
332 issues["NumberOfSimulationPoints"] = nSimPointsIssue;
333 }
334
335 const bool simulateEnergiesIndependently = getProperty("SimulateEnergiesIndependently");
336 if (simulateEnergiesIndependently) {
337 if (inputWS->getEMode() == Kernel::DeltaEMode::Elastic)
338 issues["SimulateEnergiesIndependently"] =
339 "SimulateEnergiesIndependently is only applicable to inelastic direct geometry calculations";
340 if (inputWS->getEMode() == Kernel::DeltaEMode::Indirect)
341 issues["SimulateEnergiesIndependently"] =
342 "SimulateEnergiesIndependently is only applicable to inelastic direct geometry calculations. Different "
343 "energy transfer bins are always simulated separately for indirect geometry";
344 }
345
346 return issues;
347}
356 double &xmax) const {
357 // set to crazy values to start
358 xmin = std::numeric_limits<double>::max();
359 xmax = -1.0 * xmin;
360 size_t numberOfSpectra = ws.getNumberHistograms();
361 const auto &spectrumInfo = ws.spectrumInfo();
362
363 // determine the data range - only return min > 0. Bins with x=0 will be skipped later on
364 for (size_t wsIndex = 0; wsIndex < numberOfSpectra; wsIndex++) {
365 if (spectrumInfo.hasDetectors(wsIndex) && !spectrumInfo.isMonitor(wsIndex) && !spectrumInfo.isMasked(wsIndex)) {
366 const auto &dataX = ws.points(wsIndex);
367 const double xfront = dataX.front();
368 const double xback = dataX.back();
369 if (std::isnormal(xfront) && std::isnormal(xback)) {
370 if (xfront < xmin)
371 xmin = xfront;
372 if (xback > xmax)
373 xmax = xback;
374 }
375 }
376 }
377 if (xmin > xmax)
378 throw std::runtime_error("Unable to determine min and max x values for workspace");
379}
380
382 Workspace_sptr suppliedSQWS = getProperty("StructureFactorWorkspace");
383 auto SQWSGroup = std::dynamic_pointer_cast<WorkspaceGroup>(suppliedSQWS);
384 size_t nEnvComponents = 0;
385 if (m_env)
386 nEnvComponents = m_env->nelements();
387 m_SQWSs.clear();
388 if (SQWSGroup) {
389 std::string matName = m_sampleShape->material().name();
390 auto SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
391 addWorkspaceToDiscus2DData(m_sampleShape, matName, SQWSGroupMember);
392 if (nEnvComponents > 0) {
393 matName = m_env->getContainer().material().name();
394 SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
395 addWorkspaceToDiscus2DData(m_env->getContainer().getShapePtr(), matName, SQWSGroupMember);
396 }
397 for (size_t i = 1; i < nEnvComponents; i++) {
398 matName = m_env->getComponent(i).material().name();
399 SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
400 addWorkspaceToDiscus2DData(m_env->getComponentPtr(i), matName, SQWSGroupMember);
401 }
402 } else {
404 std::dynamic_pointer_cast<MatrixWorkspace>(suppliedSQWS));
405 MatrixWorkspace_sptr isotropicSQ = DataObjects::create<Workspace2D>(
406 *std::dynamic_pointer_cast<MatrixWorkspace>(suppliedSQWS), static_cast<size_t>(1),
407 HistogramData::Histogram(HistogramData::Points{0.}, HistogramData::Frequencies{1.}));
408 if (nEnvComponents > 0) {
409 std::string_view matName = m_env->getContainer().material().name();
410 g_log.information() << "Creating isotropic structure factor for " << matName << std::endl;
411 addWorkspaceToDiscus2DData(m_env->getContainer().getShapePtr(), matName, isotropicSQ);
412 }
413 for (size_t i = 1; i < nEnvComponents; i++) {
414 std::string_view matName = m_env->getComponent(i).material().name();
415 g_log.information() << "Creating isotropic structure factor for " << matName << std::endl;
416 addWorkspaceToDiscus2DData(m_env->getComponentPtr(i), matName, isotropicSQ);
417 }
418 }
419}
420
426 const std::string_view &matName,
428 // avoid repeated conversion of bin edges to points inside loop by converting to point data
430 // if S(Q,w) has been supplied ensure Q is along the x axis of each spectrum (so same as S(Q))
431 if (SQWS->getAxis(1)->unit()->unitID() == "MomentumTransfer") {
432 auto transposeAlgorithm = this->createChildAlgorithm("Transpose");
433 transposeAlgorithm->initialize();
434 transposeAlgorithm->setProperty("InputWorkspace", SQWS);
435 transposeAlgorithm->setProperty("OutputWorkspace", "_");
436 transposeAlgorithm->execute();
437 SQWS = transposeAlgorithm->getProperty("OutputWorkspace");
438 } else if (SQWS->getAxis(1)->isSpectra()) {
439 // for elastic set w=0 on the spectrum axis to align code with inelastic
440 auto newAxis = std::make_unique<NumericAxis>(std::vector<double>{0.});
441 newAxis->setUnit("DeltaE");
442 SQWS->replaceAxis(1, std::move(newAxis));
443 }
444 auto specAxis = dynamic_cast<NumericAxis *>(SQWS->getAxis(1));
445 std::vector<DiscusData1D> data;
446 for (size_t i = 0; i < SQWS->getNumberHistograms(); i++) {
447 data.emplace_back(SQWS->histogram(i).dataX(), SQWS->histogram(i).dataY());
448 }
449 ComponentWorkspaceMapping SQWSMapping{
450 shape, matName,
451 std::make_shared<DiscusData2D>(data, std::make_shared<std::vector<double>>(specAxis->getValues()))};
452 SQWSMapping.logSQ = SQWSMapping.SQ->createCopy();
453 convertToLogWorkspace(SQWSMapping.logSQ);
454 m_SQWSs.push_back(SQWSMapping);
455}
456
463 if (ws->isHistogramData()) {
465 auto pointDataAlgorithm = this->createChildAlgorithm("ConvertToPointData");
466 pointDataAlgorithm->initialize();
467 pointDataAlgorithm->setProperty("InputWorkspace", ws);
468 pointDataAlgorithm->setProperty("OutputWorkspace", "_");
469 pointDataAlgorithm->execute();
470 ws = pointDataAlgorithm->getProperty("OutputWorkspace");
471 } else {
472 // flat interpolation is later used on S(Q) so convert to points by assigning Y value to LH bin edge
473 MatrixWorkspace_sptr SQWSPoints =
474 API::WorkspaceFactory::Instance().create(ws, ws->getNumberHistograms(), ws->blocksize(), ws->blocksize());
475 SQWSPoints->setSharedY(0, ws->sharedY(0));
476 SQWSPoints->setSharedE(0, ws->sharedE(0));
477 std::vector<double> newX = ws->histogram(0).dataX();
478 newX.pop_back();
479 SQWSPoints->setSharedX(0, HistogramData::Points(newX).cowData());
480 ws = SQWSPoints;
481 }
482 }
483 auto binAxis = dynamic_cast<BinEdgeAxis *>(ws->getAxis(1));
484 if (binAxis) {
485 auto edges = binAxis->getValues();
486 std::vector<double> centres;
487 VectorHelper::convertToBinCentre(edges, centres);
488 auto newAxis = std::make_unique<NumericAxis>(centres);
489 newAxis->setUnit(ws->getAxis(1)->unit()->unitID());
490 ws->replaceAxis(1, std::move(newAxis));
491 }
492}
493
498 if (!getAlwaysStoreInADS())
499 throw std::runtime_error("This algorithm explicitly stores named output workspaces in the ADS so must be run with "
500 "AlwaysStoreInADS set to true");
501 const MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
502
506
507 MatrixWorkspace_sptr sigmaSSWS = getProperty("ScatteringCrossSection");
508 if (sigmaSSWS)
509 m_sigmaSS = std::make_shared<DiscusData1D>(
510 DiscusData1D{sigmaSSWS->getSpectrum(0).readX(), sigmaSSWS->getSpectrum(0).readY()});
511
512 // for inelastic we could calculate the qmax based on the min\max w in the S(Q,w) but that
513 // would bake as assumption that S(Q,w)=0 beyond the limits of the supplied data
514 double qmax = std::numeric_limits<float>::max();
515 EFixedProvider efixed(*inputWS);
516 m_EMode = efixed.emode();
517 g_log.information("EMode=" + DeltaEMode::asString(m_EMode) + " detected");
519 double kmin, kmax;
520 getXMinMax(*inputWS, kmin, kmax);
521 qmax = 2 * kmax;
522 }
523 prepareQSQ(qmax);
524
525 m_simulateEnergiesIndependently = getProperty("SimulateEnergiesIndependently");
526 // call this function with dummy efixed to determine total possible simulation points
527 const auto inputNbins = generateInputKOutputWList(-1.0, inputWS->points(0).rawData()).size();
528
529 int nSimulationPointsInt = getProperty("NumberOfSimulationPoints");
530 size_t nSimulationPoints = static_cast<size_t>(nSimulationPointsInt);
531
532 if (isEmpty(nSimulationPoints)) {
533 nSimulationPoints = inputNbins;
534 } else if (nSimulationPoints > inputNbins) {
535 g_log.warning() << "The requested number of simulation points is larger "
536 "than the maximum number of simulations per spectra. "
537 "Defaulting to "
538 << inputNbins << ".\n ";
539 nSimulationPoints = inputNbins;
540 }
541
542 m_NormalizeSQ = getProperty("NormalizeStructureFactors");
543
544 const bool useSparseInstrument = getProperty("SparseInstrument");
545 SparseWorkspace_sptr sparseWS;
546 if (useSparseInstrument) {
547 const int latitudinalDets = getProperty("NumberOfDetectorRows");
548 const int longitudinalDets = getProperty("NumberOfDetectorColumns");
549 sparseWS = createSparseWorkspace(*inputWS, nSimulationPoints, latitudinalDets, longitudinalDets);
550 }
551 const int nScatters = getProperty("NumberScatterings");
552 m_maxScatterPtAttempts = getProperty("MaxScatterPtAttempts");
553 std::vector<MatrixWorkspace_sptr> simulationWSs;
554 std::vector<MatrixWorkspace_sptr> outputWSs;
555
556 auto noAbsOutputWS = createOutputWorkspace(*inputWS);
557 auto noAbsSimulationWS = useSparseInstrument ? sparseWS->clone() : noAbsOutputWS;
558 for (int i = 0; i < nScatters; i++) {
559 auto outputWS = createOutputWorkspace(*inputWS);
560 MatrixWorkspace_sptr simulationWS = useSparseInstrument ? sparseWS->clone() : outputWS;
561 simulationWSs.emplace_back(simulationWS);
562 outputWSs.emplace_back(outputWS);
563 }
564 const MatrixWorkspace &instrumentWS = useSparseInstrument ? *sparseWS : *inputWS;
565 const auto nhists = useSparseInstrument ? sparseWS->getNumberHistograms() : inputWS->getNumberHistograms();
566
567 const int nSingleScatterEvents = getProperty("NeutronPathsSingle");
568 const int nMultiScatterEvents = getProperty("NeutronPathsMultiple");
569
570 const int seed = getProperty("SeedValue");
571
572 InterpolationOption interpolateOpt;
573 bool independentErrors = (m_EMode == DeltaEMode::Direct) ? m_simulateEnergiesIndependently : true;
574 interpolateOpt.set(getPropertyValue("Interpolation"), true, independentErrors);
575
576 m_importanceSampling = getProperty("ImportanceSampling");
577
578 // add one extra progress step per hist for the wavelength interpolation
579 Progress prog(this, 0.0, 1.0, nhists * (nSimulationPoints + 1));
580 prog.setNotifyStep(0.1);
581 const std::string reportMsg = "Computing corrections";
582
583 bool enableParallelFor = true;
584 enableParallelFor = std::all_of(simulationWSs.cbegin(), simulationWSs.cend(),
585 [](const MatrixWorkspace_sptr &ws) { return Kernel::threadSafe(*ws); });
586
587 enableParallelFor = enableParallelFor && Kernel::threadSafe(*noAbsOutputWS);
588
589 const auto &spectrumInfo = instrumentWS.spectrumInfo();
590 const auto &detectorInfo = instrumentWS.detectorInfo();
591
592 PARALLEL_FOR_IF(enableParallelFor)
593 for (int64_t i = 0; i < static_cast<int64_t>(nhists); ++i) { // signed int for openMP loop
595
596 auto &spectrum = instrumentWS.getSpectrum(i);
597 Mantid::specnum_t specNo = spectrum.getSpectrumNo();
598 MersenneTwister rng(seed + specNo);
599 // no two theta for monitors
600
601 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i) && !spectrumInfo.isMasked(i)) {
602
603 const double eFixedValue = efixed.value(spectrumInfo.detector(i).getID());
604 auto xPoints = instrumentWS.points(i).rawData();
605
606 auto kInW = generateInputKOutputWList(eFixedValue, xPoints);
607
608 const auto nbins = kInW.size();
609 // step size = index range / number of steps requested
610 const size_t nsteps = std::max(static_cast<size_t>(1), nSimulationPoints - 1);
611 const size_t xStepSize = nbins == 1 ? 1 : (nbins - 1) / nsteps;
612
613 // create copy of the SQ workspaces vector and fully copy any members that will be modified
614 auto componentWorkspaces = m_SQWSs;
615
617 // prep invPOfQ outside the bin loop to avoid costly construction\destruction
618 createInvPOfQWorkspaces(componentWorkspaces, 2);
619
620 std::vector<double> kValues;
621 std::transform(kInW.begin(), kInW.end(), std::back_inserter(kValues),
622 [](std::tuple<double, int, double> t) { return std::get<0>(t); });
623 calculateQSQIntegralAsFunctionOfK(componentWorkspaces, kValues);
624
625 for (size_t bin = 0; bin < nbins; bin += xStepSize) {
626 const double kinc = std::get<0>(kInW[bin]);
627 if ((kinc <= 0) || std::isnan(kinc)) {
628 g_log.warning("Skipping calculation for bin with invalid x, workspace index=" + std::to_string(i) +
629 " bin index=" + std::to_string(std::get<1>(kInW[bin])));
630 continue;
631 }
632 std::vector<double> wValues = std::get<1>(kInW[bin]) == -1 ? xPoints : std::vector{std::get<2>(kInW[bin])};
633
635 prepareCumulativeProbForQ(kinc, componentWorkspaces);
636
637 auto [weights, weightsErrors] =
638 simulatePaths(nSingleScatterEvents, 1, rng, componentWorkspaces, kinc, wValues, true, detectorInfo, i);
639 if (std::get<1>(kInW[bin]) == -1) {
640 noAbsSimulationWS->getSpectrum(i).mutableY() += weights;
641 noAbsSimulationWS->getSpectrum(i).mutableE() += weightsErrors;
642 } else {
643 noAbsSimulationWS->getSpectrum(i).dataY()[std::get<1>(kInW[bin])] = weights[0];
644 noAbsSimulationWS->getSpectrum(i).dataE()[std::get<1>(kInW[bin])] = weightsErrors[0];
645 }
646
647 for (int ne = 0; ne < nScatters; ne++) {
648 int nEvents = ne == 0 ? nSingleScatterEvents : nMultiScatterEvents;
649
650 std::tie(weights, weightsErrors) =
651 simulatePaths(nEvents, ne + 1, rng, componentWorkspaces, kinc, wValues, false, detectorInfo, i);
652 if (std::get<1>(kInW[bin]) == -1.0) {
653 simulationWSs[ne]->getSpectrum(i).mutableY() += weights;
654 simulationWSs[ne]->getSpectrum(i).mutableE() += weightsErrors;
655 } else {
656 simulationWSs[ne]->getSpectrum(i).dataY()[std::get<1>(kInW[bin])] = weights[0];
657 simulationWSs[ne]->getSpectrum(i).dataE()[std::get<1>(kInW[bin])] = weightsErrors[0];
658 }
659 }
660
661 prog.report(reportMsg);
662
663 // Ensure we have the last point for the interpolation
664 if (xStepSize > 1 && bin + xStepSize >= nbins && bin + 1 != nbins) {
665 bin = nbins - xStepSize - 1;
666 }
667 } // bins
668
669 // interpolate through points not simulated. Simulation WS only has
670 // reduced X values if using sparse instrument so no interpolation
671 // required
672 if (!useSparseInstrument && xStepSize > 1) {
673 auto histNoAbs = noAbsSimulationWS->histogram(i);
674 if (xStepSize < nbins) {
675 interpolateOpt.applyInplace(histNoAbs, xStepSize);
676 } else {
677 std::fill(histNoAbs.mutableY().begin() + 1, histNoAbs.mutableY().end(), histNoAbs.y()[0]);
678 }
679 noAbsOutputWS->setHistogram(i, histNoAbs);
680
681 for (size_t ne = 0; ne < static_cast<size_t>(nScatters); ne++) {
682 auto histnew = simulationWSs[ne]->histogram(i);
683 if (xStepSize < nbins) {
684 interpolateOpt.applyInplace(histnew, xStepSize);
685 } else {
686 std::fill(histnew.mutableY().begin() + 1, histnew.mutableY().end(), histnew.y()[0]);
687 }
688 outputWSs[ne]->setHistogram(i, histnew);
689 }
690 }
691 prog.report(reportMsg);
692 }
693
695 }
697
698 if (useSparseInstrument) {
699 Poco::Thread::sleep(200); // to ensure prog message changes
700 const std::string reportMsgSpatialInterpolation = "Spatial Interpolation";
701 prog.report(reportMsgSpatialInterpolation);
702 interpolateFromSparse(*noAbsOutputWS, *std::dynamic_pointer_cast<SparseWorkspace>(noAbsSimulationWS),
703 interpolateOpt);
704 for (size_t ne = 0; ne < static_cast<size_t>(nScatters); ne++) {
705 interpolateFromSparse(*outputWSs[ne], *std::dynamic_pointer_cast<SparseWorkspace>(simulationWSs[ne]),
706 interpolateOpt);
707 }
708 }
709
710 // Create workspace group that holds output workspaces
711 auto wsgroup = std::make_shared<WorkspaceGroup>();
712 auto outputGroupWSName = getPropertyValue("OutputWorkspace");
713 if (AnalysisDataService::Instance().doesExist(outputGroupWSName))
714 API::AnalysisDataService::Instance().deepRemoveGroup(outputGroupWSName);
715
716 const std::string wsNamePrefix = outputGroupWSName + "_Scatter_";
717 std::string wsName = wsNamePrefix + "1_NoAbs";
718 setWorkspaceName(noAbsOutputWS, wsName);
719 wsgroup->addWorkspace(noAbsOutputWS);
720
721 for (size_t i = 0; i < outputWSs.size(); i++) {
722 wsName = wsNamePrefix + std::to_string(i + 1);
723 setWorkspaceName(outputWSs[i], wsName);
724 wsgroup->addWorkspace(outputWSs[i]);
725
726 auto integratedWorkspace = integrateWS(outputWSs[i]);
727 setWorkspaceName(integratedWorkspace, wsName + "_Integrated");
728 wsgroup->addWorkspace(integratedWorkspace);
729 }
730
731 if (outputWSs.size() > 1) {
732 // create sum of multiple scatter workspaces for use in subtraction method
733 auto summedMScatOutput = createOutputWorkspace(*inputWS);
734 summedMScatOutput = std::accumulate(outputWSs.cbegin() + 1, outputWSs.cend(), summedMScatOutput);
735 wsName = wsNamePrefix + "2_" + std::to_string(outputWSs.size()) + "_Summed";
736 setWorkspaceName(summedMScatOutput, wsName);
737 wsgroup->addWorkspace(summedMScatOutput);
738 // create sum of all scattering order workspaces for use in ratio method
739 auto summedAllScatOutput = createOutputWorkspace(*inputWS);
740 summedAllScatOutput = summedMScatOutput + outputWSs[0];
741 wsName = wsNamePrefix + "1_" + std::to_string(outputWSs.size()) + "_Summed";
742 setWorkspaceName(summedAllScatOutput, wsName);
743 wsgroup->addWorkspace(summedAllScatOutput);
744 // create ratio of single to all scatter
745 auto ratioOutput = createOutputWorkspace(*inputWS);
746 ratioOutput = outputWSs[0] / summedAllScatOutput;
747 wsName = outputGroupWSName + "_Ratio_Single_To_All";
748 setWorkspaceName(ratioOutput, wsName);
749 wsgroup->addWorkspace(ratioOutput);
750
751 // ConvFit method being investigated by Spencer for inelastic currently uses the opposite ratio
753 auto invRatioOutput = 1 / ratioOutput;
754 auto replaceNans = this->createChildAlgorithm("ReplaceSpecialValues");
755 replaceNans->setChild(true);
756 replaceNans->initialize();
757 replaceNans->setProperty("InputWorkspace", invRatioOutput);
758 replaceNans->setProperty("OutputWorkspace", invRatioOutput);
759 replaceNans->setProperty("NaNValue", 0.0);
760 replaceNans->setProperty("InfinityValue", 0.0);
761 replaceNans->execute();
762 wsName = outputGroupWSName + "_Ratio_All_To_Single";
763 setWorkspaceName(invRatioOutput, wsName);
764 wsgroup->addWorkspace(invRatioOutput);
765 }
766 }
767
768 // set the output property
769 setProperty("OutputWorkspace", wsgroup);
770
771 if (g_log.is(Kernel::Logger::Priority::PRIO_INFORMATION)) {
772 g_log.information() << "Total simulation points=" << nhists * nSimulationPoints << "\n";
773 for (const auto &kv : m_attemptsToGenerateInitialTrack)
774 g_log.information() << "Generating initial track required " << kv.first << " attempts on " << kv.second
775 << " occasions.\n";
776 g_log.information() << "Calls to interceptSurface=" << m_callsToInterceptSurface << "\n";
777 g_log.information() << "Total I(k) calculations=" << m_IkCalculations << ", average per simulation point="
778 << static_cast<double>(m_IkCalculations) / static_cast<double>(nhists * nSimulationPoints)
779 << "\n";
780 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG))
781 for (size_t i = 0; i < m_SQWSs.size(); i++)
782 g_log.information() << "Scatters in component " << i << ": " << *(m_SQWSs[i].scatterCount) << "\n";
783 }
784}
785
794std::vector<std::tuple<double, int, double>>
795DiscusMultipleScatteringCorrection::generateInputKOutputWList(const double efixed, const std::vector<double> &xPoints) {
796 std::vector<std::tuple<double, int, double>> kInW;
797 const double kFixed = toWaveVector(efixed);
799 int index = 0;
800 std::transform(xPoints.begin(), xPoints.end(), std::back_inserter(kInW), [&index](double d) {
801 auto t = std::make_tuple(d, index, 0.);
802 index++;
803 return t;
804 });
805 } else {
807 kInW.emplace_back(std::make_tuple(kFixed, -1, 0.));
808 else {
809 for (int i = 0; i < static_cast<int>(xPoints.size()); i++) {
811 kInW.emplace_back(std::make_tuple(kFixed, i, xPoints[i]));
812 else if (m_EMode == DeltaEMode::Indirect) {
813 const double initialE = efixed + xPoints[i];
814 if (initialE > 0) {
815 const double kin = toWaveVector(initialE);
816 kInW.emplace_back(std::make_tuple(kin, i, xPoints[i]));
817 } else
818 // negative kinc is filtered out later
819 kInW.emplace_back(std::make_tuple(-1.0, i, xPoints[i]));
820 }
821 }
822 }
823 }
824 return kInW;
825}
826
833 for (auto &SQWSMapping : m_SQWSs) {
834 auto &SQWS = SQWSMapping.SQ;
835 std::shared_ptr<DiscusData2D> outputWS = SQWS->createCopy(true);
836 std::vector<double> IOfQYFull;
837 // loop through the S(Q) spectra for the different energy transfer values
838 for (size_t iW = 0; iW < SQWS->getNumberHistograms(); iW++) {
839 std::vector<double> qValues = SQWS->histogram(iW).X;
840 std::vector<double> SQValues = SQWS->histogram(iW).Y;
841 // add terminating points at 0 and qmax before multiplying by Q so no extrapolation problems
842 if (qValues.front() > 0.) {
843 qValues.insert(qValues.begin(), 0.);
844 SQValues.insert(SQValues.begin(), SQValues.front());
845 }
846 if (qValues.back() < qmax) {
847 qValues.push_back(qmax);
848 SQValues.push_back(SQValues.back());
849 }
850 // add some extra points to help the Q.S(Q) integral get the right answer
851 for (size_t i = 1; i < qValues.size(); i++) {
852 if (std::abs(SQValues[i] - SQValues[i - 1]) >
853 std::numeric_limits<double>::epsilon() * std::min(SQValues[i - 1], SQValues[i])) {
854 qValues.insert(qValues.begin() + i, std::nextafter(qValues[i], -DBL_MAX));
855 SQValues.insert(SQValues.begin() + i, SQValues[i - 1]);
856 i++;
857 }
858 }
859
860 std::vector<double> QSQValues;
861 std::transform(SQValues.begin(), SQValues.end(), qValues.begin(), std::back_inserter(QSQValues),
862 std::multiplies<double>());
863
864 outputWS->histogram(iW).X.resize(qValues.size());
865 outputWS->histogram(iW).X = qValues;
866 outputWS->histogram(iW).Y.resize(QSQValues.size());
867 outputWS->histogram(iW).Y = QSQValues;
868 }
869 SQWSMapping.QSQ = outputWS;
870 }
871}
872
883std::tuple<std::vector<double>, std::vector<double>, std::vector<double>>
884DiscusMultipleScatteringCorrection::integrateQSQ(const std::shared_ptr<DiscusData2D> &QSQ, double kinc,
885 const bool returnCumulative) {
886 std::vector<double> IOfQYFull, qValuesFull, wIndices;
887 double IOfQMaxPreviousRow = 0.;
888
889 auto &wValues = QSQ->getSpecAxisValues();
890 std::vector<double> wWidths;
891 if (wValues.size() == 1) {
892 // convertToBinBoundary currently gives width of 1 for single point but because this is essential for the maths
893 // set the width to 1 explicitly
894 wWidths.push_back(1.);
895 } else {
896 std::vector<double> wBinEdges;
897 wBinEdges.reserve(wValues.size() + 1);
898 VectorHelper::convertToBinBoundary(wValues, wBinEdges);
899 std::adjacent_difference(wBinEdges.begin(), wBinEdges.end(), std::back_inserter(wWidths));
900 wWidths.erase(wWidths.begin()); // first element returned by adjacent_difference isn't a diff so delete it
901 }
902
903 double wMax = fromWaveVector(kinc);
904 auto it = std::lower_bound(wValues.begin(), wValues.end(), wMax);
905 size_t iFirstInaccessibleW = std::distance(wValues.begin(), it);
906 auto nAccessibleWPoints = iFirstInaccessibleW;
907
908 // loop through the S(Q) spectra for the different energy transfer values
909 std::vector<double> IOfQX, IOfQY;
910 // reserve minimum space required for performance
911 IOfQYFull.reserve(nAccessibleWPoints);
912 qValuesFull.reserve(nAccessibleWPoints);
913 wIndices.reserve(nAccessibleWPoints);
914 //}
915 for (size_t iW = 0; iW < nAccessibleWPoints; iW++) {
916 auto kf = getKf((wValues)[iW], kinc);
917 auto [qmin, qrange] = getKinematicRange(kf, kinc);
918 IOfQX.clear();
919 IOfQY.clear();
920 integrateCumulative(QSQ->histogram(iW), qmin, qmin + qrange, IOfQX, IOfQY, returnCumulative);
921 // w bin width for elastic will equal 1
922 double wBinWidth = wWidths[iW];
923 std::transform(IOfQY.begin(), IOfQY.end(), IOfQY.begin(),
924 [IOfQMaxPreviousRow, wBinWidth](double d) -> double { return d * wBinWidth + IOfQMaxPreviousRow; });
925 IOfQMaxPreviousRow = IOfQY.back();
926 IOfQYFull.insert(IOfQYFull.end(), IOfQY.begin(), IOfQY.end());
927 qValuesFull.insert(qValuesFull.end(), IOfQX.begin(), IOfQX.end());
928 wIndices.insert(wIndices.end(), IOfQX.size(), static_cast<double>(iW));
929 }
931 return {IOfQYFull, qValuesFull, wIndices};
932}
933
942 double kinc, const ComponentWorkspaceMappings &materialWorkspaces) {
943 for (size_t iMat = 0; iMat < materialWorkspaces.size(); iMat++) {
944 auto QSQ = materialWorkspaces[iMat].QSQ;
945 auto [IOfQYFull, qValuesFull, wIndices] = integrateQSQ(QSQ, kinc, true);
946 auto IOfQYAtQMax = IOfQYFull.empty() ? 0. : IOfQYFull.back();
947 if (IOfQYAtQMax == 0.)
948 throw std::runtime_error("Integral of Q * S(Q) is zero so can't generate probability distribution");
949 // normalise probability range to 0-1
950 std::vector<double> IOfQYNorm;
951 std::transform(IOfQYFull.begin(), IOfQYFull.end(), std::back_inserter(IOfQYNorm),
952 [IOfQYAtQMax](double d) -> double { return d / IOfQYAtQMax; });
953 // Store the normalized integral (= cumulative probability) on the x axis
954 // The y values in the two spectra store Q, w (or w index to be precise)
955 auto &InvPOfQ = materialWorkspaces[iMat].InvPOfQ;
956 for (size_t i = 0; i < InvPOfQ->getNumberHistograms(); i++) {
957 InvPOfQ->histogram(i).X.resize(IOfQYNorm.size());
958 InvPOfQ->histogram(i).X = IOfQYNorm;
959 }
960 InvPOfQ->histogram(0).Y.resize(qValuesFull.size());
961 InvPOfQ->histogram(0).Y = qValuesFull;
962 InvPOfQ->histogram(1).Y.resize(wIndices.size());
963 InvPOfQ->histogram(1).Y = wIndices;
964 }
965}
966
967void DiscusMultipleScatteringCorrection::convertToLogWorkspace(const std::shared_ptr<DiscusData2D> &SOfQ) {
968 // generate log of the structure factor to support gaussian interpolation
969
970 for (size_t i = 0; i < SOfQ->getNumberHistograms(); i++) {
971 auto &ySQ = SOfQ->histogram(i).Y;
972
973 std::transform(ySQ.begin(), ySQ.end(), ySQ.begin(), [](double d) -> double {
974 const double exp_that_gives_close_to_zero = -20.0;
975 if (d == 0.)
976 return exp_that_gives_close_to_zero;
977 else
978 return std::log(d);
979 });
980 }
981}
982
995 const std::vector<double> &specialKs) {
996 for (auto &SQWSMapping : matWSs) {
997 std::vector<double> finalkValues, QSQIntegrals;
999 // Optimize performance by doing cumulative integral first at each q in S(Q) and then calculate integral for each
1000 // k by topping up those results
1001 double kMax = specialKs.back();
1002 std::vector<double> IOfQYFull, qValuesFull;
1003 std::tie(IOfQYFull, qValuesFull, std::ignore) = integrateQSQ(SQWSMapping.QSQ, kMax, true);
1004 for (auto k : specialKs) {
1005 auto qUpperLimit = 2 * k;
1006 auto iterPrevIntegral = std::upper_bound(qValuesFull.begin(), qValuesFull.end(), qUpperLimit) - 1;
1007 auto idxPrevIntegral = static_cast<size_t>(std::distance(qValuesFull.begin(), iterPrevIntegral));
1008 std::vector<double> ignoreVector, topUpIntegral;
1009 integrateCumulative(SQWSMapping.QSQ->histogram(0), *iterPrevIntegral, qUpperLimit, ignoreVector, topUpIntegral,
1010 false);
1011 double IOfQY = IOfQYFull[idxPrevIntegral] + topUpIntegral[0];
1012 if (IOfQY > 0) {
1013 double normalisedIntegral = IOfQY / (2 * k * k);
1014 finalkValues.push_back(k);
1015 QSQIntegrals.push_back(normalisedIntegral);
1016 }
1017 }
1018 } else {
1019 // Calculate the integral for a range of k values. Not massively important which k values but choose them here
1020 // based on the q points in the S(Q) profile and the initial k values incident on the sample
1021 std::set<double> kValues(specialKs.begin(), specialKs.end());
1022 const std::vector<double> qValues = SQWSMapping.SQ->histogram(0).X;
1023 for (auto q : qValues) {
1024 if (q > 0)
1025 kValues.insert(q / 2);
1026 }
1027
1028 // add a few extra points beyond supplied q range to ensure capture asymptotic value of integral/2*k*k.
1029 // Useful when doing a flat interpolation on m_QSQIntegral during inelastic calculation where k not known up front
1030 double maxSuppliedQ = qValues.back();
1031 if (maxSuppliedQ > 0.) {
1032 kValues.insert(maxSuppliedQ);
1033 kValues.insert(2 * maxSuppliedQ);
1034 }
1035
1036 for (auto k : kValues) {
1037 std::vector<double> IOfQYFull;
1038 std::tie(IOfQYFull, std::ignore, std::ignore) = integrateQSQ(SQWSMapping.QSQ, k, false);
1039 auto IOfQYAtQMax = IOfQYFull.empty() ? 0. : IOfQYFull.back();
1040 // going to divide by this so storing zero results not useful - and don't want to interpolate a zero value
1041 // into a k region where the integral is actually non-zero
1042 if (IOfQYAtQMax > 0) {
1043 double normalisedIntegral = IOfQYAtQMax / (2 * k * k);
1044 finalkValues.push_back(k);
1045 QSQIntegrals.push_back(normalisedIntegral);
1046 }
1047 }
1048 }
1049 auto QSQScaleFactor = std::make_shared<DiscusData1D>(DiscusData1D{finalkValues, QSQIntegrals});
1050 SQWSMapping.QSQScaleFactor = QSQScaleFactor;
1051 }
1052}
1053
1069 const double xmax, std::vector<double> &resultX,
1070 std::vector<double> &resultY,
1071 const bool returnCumulative) {
1072 assert(h.X.size() == h.Y.size());
1073 const std::vector<double> &xValues = h.X;
1074 const std::vector<double> &yValues = h.Y;
1075
1076 // set the integral to zero at xmin
1077 if (returnCumulative) {
1078 resultX.emplace_back(xmin);
1079 resultY.emplace_back(0.);
1080 }
1081 double sum = 0;
1082
1083 // ensure there's a point at xmin
1084 if (xValues.front() > xmin)
1085 throw std::runtime_error("Distribution doesn't extend as far as lower integration limit, x=" +
1086 std::to_string(xmin));
1087 // ...and a terminating point. Q.S(Q) generally not flat so assuming flat extrapolation not v useful
1088 if (xValues.back() < xmax)
1089 throw std::runtime_error("Distribution doesn't extend as far as upper integration limit, x=" +
1090 std::to_string(xmax));
1091
1092 auto iter = std::upper_bound(xValues.cbegin(), xValues.cend(), xmin);
1093 auto iRight = static_cast<size_t>(std::distance(xValues.cbegin(), iter));
1094
1095 auto linearInterp = [&xValues, &yValues](const double x, const size_t lIndex, const size_t rIndex) -> double {
1096 return (yValues[lIndex] * (xValues[rIndex] - x) + yValues[rIndex] * (x - xValues[lIndex])) /
1097 (xValues[rIndex] - xValues[lIndex]);
1098 };
1099 double yToUse;
1100
1101 // deal with partial initial segments
1102 if (xmin > xValues[iRight - 1]) {
1103 if (xmax >= xValues[iRight]) {
1104 double interpY = linearInterp(xmin, iRight - 1, iRight);
1105 yToUse = 0.5 * (interpY + yValues[iRight]);
1106 sum += yToUse * (xValues[iRight] - xmin);
1107 if (returnCumulative) {
1108 resultX.push_back(xValues[iRight]);
1109 resultY.push_back(sum);
1110 }
1111 iRight++;
1112 } else {
1113 double interpY1 = linearInterp(xmin, iRight - 1, iRight);
1114 double interpY2 = linearInterp(xmax, iRight - 1, iRight);
1115 yToUse = 0.5 * (interpY1 + interpY2);
1116 sum += yToUse * (xmax - xmin);
1117 if (returnCumulative) {
1118 resultX.push_back(xmax);
1119 resultY.push_back(sum);
1120 }
1121 iRight++;
1122 }
1123 }
1124
1125 // integrate the intervals between each pair of points. Do this until right point is at end of vector or > xmax
1126 for (; iRight < xValues.size() && xValues[iRight] <= xmax; iRight++) {
1127 yToUse = 0.5 * (yValues[iRight - 1] + yValues[iRight]);
1128 double xLeft = xValues[iRight - 1];
1129 double xRight = xValues[iRight];
1130 sum += yToUse * (xRight - xLeft);
1131 if (returnCumulative) {
1132 if (xRight > std::nextafter(xLeft, DBL_MAX)) {
1133 resultX.emplace_back(xRight);
1134 resultY.emplace_back(sum);
1135 }
1136 }
1137 }
1138
1139 // integrate a partial final interval if xmax is between points
1140 if ((xmax > xValues[iRight - 1]) && (xmin <= xValues[iRight - 1])) {
1141 double interpY = linearInterp(xmax, iRight - 1, iRight);
1142 yToUse = 0.5 * (yValues[iRight - 1] + interpY);
1143 sum += yToUse * (xmax - xValues[iRight - 1]);
1144 if (returnCumulative) {
1145 resultX.emplace_back(xmax);
1146 resultY.emplace_back(sum);
1147 }
1148 }
1149 if (!returnCumulative) {
1150 resultX.emplace_back(xmax);
1151 resultY.emplace_back(sum);
1152 }
1153}
1154
1161 // don't call integrateCumulative function because want error calculation and support for bin edges
1162 auto integrateAlgorithm = this->createChildAlgorithm("Integration");
1163 integrateAlgorithm->initialize();
1164 integrateAlgorithm->setProperty("InputWorkspace", ws);
1165 integrateAlgorithm->setProperty("OutputWorkspace", "_");
1166 integrateAlgorithm->execute();
1167 MatrixWorkspace_sptr wsIntegrals = integrateAlgorithm->getProperty("OutputWorkspace");
1168 for (size_t i = 0; i < wsIntegrals->getNumberHistograms(); i++)
1169 wsIntegrals->setPoints(i, std::vector<double>{0.});
1170 return wsIntegrals;
1171}
1172
1183std::tuple<double, double> DiscusMultipleScatteringCorrection::new_vector(const Material &material, double k,
1184 bool specialSingleScatterCalc) {
1185 double scatteringXSection, absorbXsection;
1186 if (specialSingleScatterCalc) {
1187 absorbXsection = 0;
1188 } else {
1189 const double wavelength = 2 * M_PI / k;
1190 absorbXsection = material.absorbXSection(wavelength);
1191 }
1192 if (m_sigmaSS) {
1193 scatteringXSection = interpolateFlat(*m_sigmaSS, k);
1194 } else {
1195 scatteringXSection = material.totalScatterXSection();
1196 }
1197
1198 const auto sig_total = scatteringXSection + absorbXsection;
1199 return {sig_total, scatteringXSection};
1200}
1201
1209std::tuple<double, int>
1210DiscusMultipleScatteringCorrection::sampleQW(const std::shared_ptr<DiscusData2D> &CumulativeProb, double x) {
1211 return {interpolateSquareRoot(CumulativeProb->histogram(0), x),
1212 static_cast<int>(interpolateFlat(CumulativeProb->histogram(1), x))};
1213}
1214
1221 const auto &histx = histToInterpolate.X;
1222 const auto &histy = histToInterpolate.Y;
1223 assert(histToInterpolate.X.size() == histToInterpolate.Y.size());
1224 if (x > histx.back()) {
1225 return histy.back();
1226 }
1227 if (x < histx.front()) {
1228 return histy.front();
1229 }
1230 const auto iter = std::upper_bound(histx.cbegin(), histx.cend(), x);
1231 const auto idx = static_cast<size_t>(std::distance(histx.cbegin(), iter) - 1);
1232 const double x0 = histx[idx];
1233 const double x1 = histx[idx + 1];
1234 const double asq = (pow(histy[idx + 1], 2) - pow(histy[idx], 2)) / (x1 - x0);
1235 if (asq == 0.) {
1236 throw std::runtime_error("Cannot perform square root interpolation on supplied distribution");
1237 }
1238 const double b = x0 - pow(histy[idx], 2) / asq;
1239 return sqrt(asq * (x - b));
1240}
1241
1249 auto &xHisto = histToInterpolate.X;
1250 auto &yHisto = histToInterpolate.Y;
1251 if (x > xHisto.back()) {
1252 return yHisto.back();
1253 }
1254 if (x < xHisto.front()) {
1255 return yHisto.front();
1256 }
1257 // may be useful at some point to introduce a tolerance here in case x is just below a step change but seems to behave
1258 // OK for now
1259 auto iter = std::upper_bound(xHisto.cbegin(), xHisto.cend(), x);
1260 auto idx = static_cast<size_t>(std::distance(xHisto.cbegin(), iter) - 1);
1261 return yHisto[idx];
1262}
1263
1272 // could have written using points() method so it also worked on histogram data but found that the points
1273 // method was bottleneck on multithreaded code due to cow_ptr atomic_load
1274 assert(histToInterpolate.X.size() == histToInterpolate.Y.size());
1275 if (x > histToInterpolate.X.back()) {
1276 return exp(histToInterpolate.Y.back());
1277 }
1278 if (x < histToInterpolate.X.front()) {
1279 return exp(histToInterpolate.Y.front());
1280 }
1281 // assume log(cross section) is quadratic in k
1282 auto deltax = histToInterpolate.X[1] - histToInterpolate.X[0];
1283
1284 auto iter = std::upper_bound(histToInterpolate.X.cbegin(), histToInterpolate.X.cend(), x);
1285 auto idx = static_cast<size_t>(std::distance(histToInterpolate.X.cbegin(), iter) - 1);
1286
1287 // need at least two points to the right of the x value for the quadratic
1288 // interpolation to work
1289 auto ny = histToInterpolate.Y.size();
1290 if (ny < 3) {
1291 throw std::runtime_error("Need at least 3 y values to perform quadratic interpolation");
1292 }
1293 if (idx > ny - 3) {
1294 idx = ny - 3;
1295 }
1296 // this interpolation assumes the set of 3 bins\point have the same width
1297 // U=0 on point or bin edge to the left of where x lies
1298 const auto U = (x - histToInterpolate.X[idx]) / deltax;
1299 const auto &y = histToInterpolate.Y;
1300 const auto A = (y[idx] - 2 * y[idx + 1] + y[idx + 2]) / 2;
1301 const auto B = (-3 * y[idx] + 4 * y[idx + 1] - y[idx + 2]) / 2;
1302 const auto C = y[idx];
1303 return exp(A * U * U + B * U + C);
1304}
1305
1316 double w) {
1317 double SQ = 0.;
1318 int iW = -1;
1319 auto &wValues = SQWSMapping.SQ->getSpecAxisValues();
1320 if (wValues.size() == 1) {
1321 // don't use indexOfValue here because for single point it invents a bin width of +/-0.5
1322 if (w == (wValues)[0])
1323 iW = 0;
1324 } else
1325 try {
1326 // required w values will often equal the points in the S(Q,w) distribution so pick nearest value
1327 iW = static_cast<int>(Kernel::VectorHelper::indexOfValueFromCentersNoThrow(wValues, w));
1328 } catch (std::out_of_range &) {
1329 }
1330 if (iW >= 0) {
1332 // the square root interpolation used to look up Q, w in InvPOfQ is based on flat interpolation of S(Q) so use
1333 // same interpolation here for consistency
1334 SQ = interpolateFlat(SQWSMapping.SQ->histogram(iW), q);
1335 else
1336 SQ = interpolateGaussian(SQWSMapping.logSQ->histogram(iW), q);
1337 }
1338
1339 return SQ;
1340}
1341
1342GNU_DIAG_OFF("free-nonheap-object")
1343
1344
1362std::tuple<std::vector<double>, std::vector<double>> DiscusMultipleScatteringCorrection::simulatePaths(
1363 const int nPaths, const int nScatters, Kernel::PseudoRandomNumberGenerator &rng,
1364 const ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector<double> &wValues,
1365 bool specialSingleScatterCalc, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex) {
1366 // countZeroWeights for debugging and analysis of where importance sampling may help
1367 std::vector<int> countZeroWeights(wValues.size(), 0);
1368 std::vector<double> sumOfWeights(wValues.size(), 0.);
1369 std::vector<double> weightsMeans(wValues.size(), 0.), deltas(wValues.size(), 0.), weightsM2(wValues.size(), 0.),
1370 weightsErrors(wValues.size(), 0.);
1371
1372 for (int ie = 0; ie < nPaths; ie++) {
1373 auto [success, weights] = scatter(nScatters, rng, componentWorkspaces, kinc, wValues, specialSingleScatterCalc,
1374 detectorInfo, histogramIndex);
1375 if (success) {
1376 std::transform(weights.begin(), weights.end(), sumOfWeights.begin(), sumOfWeights.begin(), std::plus<double>());
1377 std::transform(weights.begin(), weights.end(), countZeroWeights.begin(), countZeroWeights.begin(),
1378 [](double d, int count) { return d > 0. ? count : count + 1; });
1379
1380 // increment standard deviation using Welford algorithm
1381 for (size_t i = 0; i < wValues.size(); i++) {
1382 deltas[i] = weights[i] - weightsMeans[i];
1383 weightsMeans[i] += deltas[i] / static_cast<double>(ie + 1);
1384 weightsM2[i] += deltas[i] * (weights[i] - weightsMeans[i]);
1385 // calculate sample SD (M2/n-1)
1386 // will give NaN for m_events=1, but that's correct
1387 weightsErrors[i] = sqrt(weightsM2[i] / static_cast<double>(ie));
1388 }
1389
1390 } else
1391 ie--;
1392 }
1393 for (size_t i = 0; i < wValues.size(); i++) {
1394 sumOfWeights[i] = sumOfWeights[i] / nPaths;
1395 weightsErrors[i] = weightsErrors[i] / sqrt(nPaths);
1396 }
1397
1398 return {sumOfWeights, weightsErrors};
1399}
1400
1401GNU_DIAG_ON("free-nonheap-object")
1402
1403
1421std::tuple<bool, std::vector<double>> DiscusMultipleScatteringCorrection::scatter(
1422 const int nScatters, Kernel::PseudoRandomNumberGenerator &rng,
1423 const ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector<double> &wValues,
1424 bool specialSingleScatterCalc, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex) {
1425
1426 double weight = 1;
1427
1428 auto track = start_point(rng);
1429 auto shapeObjectWithScatter =
1430 updateWeightAndPosition(track, weight, kinc, rng, specialSingleScatterCalc, componentWorkspaces);
1431 double scatteringXSection;
1432 std::tie(std::ignore, scatteringXSection) =
1433 new_vector(shapeObjectWithScatter->material(), kinc, specialSingleScatterCalc);
1434
1435 auto currentComponentWorkspaces = componentWorkspaces;
1436 double k = kinc;
1437 for (int iScat = 0; iScat < nScatters - 1; iScat++) {
1438 if ((k != kinc)) {
1439 if (m_importanceSampling) {
1440 auto newComponentWorkspaces = componentWorkspaces;
1441 for (auto &SQWSMapping : currentComponentWorkspaces)
1442 SQWSMapping.InvPOfQ = SQWSMapping.InvPOfQ->createCopy();
1443 prepareCumulativeProbForQ(k, newComponentWorkspaces);
1444 currentComponentWorkspaces = std::move(newComponentWorkspaces);
1445 }
1446 }
1447 auto trackStillAlive =
1448 q_dir(track, shapeObjectWithScatter, currentComponentWorkspaces, k, scatteringXSection, rng, weight);
1449 if (!trackStillAlive)
1450 return {true, std::vector<double>(wValues.size(), 0.)};
1451 int nlinks = m_sampleShape->interceptSurface(track);
1452 if (m_env) {
1453 nlinks += m_env->interceptSurfaces(track);
1454 m_callsToInterceptSurface += m_env->nelements();
1455 }
1456 m_callsToInterceptSurface++;
1457 if (nlinks == 0) {
1458 return {false, {0.}};
1459 }
1460 shapeObjectWithScatter =
1461 updateWeightAndPosition(track, weight, k, rng, specialSingleScatterCalc, componentWorkspaces);
1462 std::tie(std::ignore, scatteringXSection) =
1463 new_vector(shapeObjectWithScatter->material(), k, specialSingleScatterCalc);
1464 }
1465
1466 bool considerCollimator = getProperty("RadialCollimator");
1467 if (considerCollimator) {
1468 const auto &samplePos = detectorInfo.samplePosition();
1469 auto hexahedron = createCollimatorHexahedronShape(samplePos, detectorInfo, histogramIndex);
1470 // zero the paths if the final scatter point is not inside the collimatorCorridor shape or the collimator shape is
1471 // not as expected
1472 if ((!hexahedron) || (!hexahedron->isValid(track.startPoint())))
1473 return {true, std::vector<double>(wValues.size(), 0.)};
1474 }
1475
1476 const auto &detPos = detectorInfo.position(histogramIndex);
1477 Kernel::V3D directionToDetector = detPos - track.startPoint();
1478 Kernel::V3D prevDirection = track.direction();
1479 directionToDetector.normalize();
1480 track.reset(track.startPoint(), directionToDetector);
1481 int nlinks = m_sampleShape->interceptSurface(track);
1482 m_callsToInterceptSurface++;
1483 if (m_env) {
1484 nlinks += m_env->interceptSurfaces(track);
1485 m_callsToInterceptSurface += m_env->nelements();
1486 }
1487 // due to VALID_INTERCEPT_POINT_SHIFT some tracks that skim the surface
1488 // of a CSGObject sample may not generate valid tracks. Start over again
1489 // for this event
1490 if (nlinks == 0) {
1491 return {false, {0.}};
1492 }
1493 std::vector<double> weights;
1494 auto scatteringXSectionFull = shapeObjectWithScatter->material().totalScatterXSection();
1495 // Step through required overall energy transfer (w) values and work out what
1496 // w that means for the final scatter. There will be a single w value for elastic
1497 // Slightly different approach to original DISCUS code. It stepped through the w values
1498 // in the supplied S(Q,w) distribution and applied each one to the final scatter. If
1499 // this resulted in an overall w that equalled one of the required w values it was output.
1500 // That approach implicitly assumed S(Q,w)=0 where not specified and that no interpolation
1501 // on w would be needed - this may be what's required but seems possible it might not always be
1502 for (auto &w : wValues) {
1503 const double finalE = fromWaveVector(kinc) - w;
1504 if (finalE > 0) {
1505 const double kout = toWaveVector(finalE);
1506 const auto qVector = directionToDetector * kout - prevDirection * k;
1507 const double q = qVector.norm();
1508 const double finalW = fromWaveVector(k) - finalE;
1509 auto componentWSIt = findMatchingComponent(componentWorkspaces, shapeObjectWithScatter);
1510 auto &componentWSMapping = *componentWSIt; // to help debugging
1511 double SQ = Interpolate2D(componentWSMapping, q, finalW);
1512 scatteringXSection = m_NormalizeSQ ? scatteringXSection / interpolateFlat(*(componentWSMapping.QSQScaleFactor), k)
1513 : scatteringXSectionFull;
1514
1515 double AT2 = 1;
1516 for (auto it = track.cbegin(); it != track.cend(); it++) {
1517 double sigma_total;
1518 auto &materialPassingThrough = it->object->material();
1519 std::tie(sigma_total, std::ignore) = new_vector(materialPassingThrough, kout, specialSingleScatterCalc);
1520 double numberDensity = materialPassingThrough.numberDensityEffective();
1521 double vmu = 100 * numberDensity * sigma_total;
1522 if (specialSingleScatterCalc)
1523 vmu = 0;
1524 const double dl = it->distInsideObject;
1525 AT2 *= exp(-dl * vmu);
1526 }
1527 weights.emplace_back(weight * AT2 * SQ * scatteringXSection / (4 * M_PI));
1528 } else {
1529 weights.emplace_back(0.);
1530 }
1531 }
1532 return {true, weights};
1533}
1534
1535/*
1536 * Construct a hexahedron shape extending from the detector's front face across the collimator openning window toward
1537 the sample with a legth as twice the distance from sample to detector.
1538 */
1540 const Kernel::V3D &samplePos, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex) {
1541 const auto shape = detectorInfo.detector(histogramIndex).shape();
1542 if (!shape || (shape->shape() != Mantid::Geometry::detail::ShapeInfo::GeometryShape::CUBOID)) {
1543 return nullptr;
1544 }
1545
1546 try {
1547 shape->shapeInfo();
1548 } catch (std::exception &) {
1549 return nullptr;
1550 }
1551
1552 const auto colCorridorShape = readFromCollimatorCorridorCache(histogramIndex);
1553 if (colCorridorShape) {
1554 return colCorridorShape;
1555 }
1556
1557 const auto &detectorId = detectorInfo.detector(histogramIndex).getID();
1558 const auto &detectorAbsolPos = detectorInfo.position(detectorInfo.indexOf(detectorId));
1559 const auto cuboidGeometry = shape->shapeInfo().cuboidGeometry();
1560
1561 // Positions of the detector's front face
1562 const auto &detLeftFrontBottomPos = detectorAbsolPos + cuboidGeometry.leftFrontBottom;
1563 const auto &detLeftFrontTopPos = detectorAbsolPos + cuboidGeometry.leftFrontTop;
1564 const auto &detRightFrontBottomPos = detectorAbsolPos + cuboidGeometry.rightFrontBottom;
1565 const auto detRightFrontTopPos = detLeftFrontTopPos + detRightFrontBottomPos - detLeftFrontBottomPos;
1566
1567 const auto detCentrePos =
1568 (detLeftFrontBottomPos + detLeftFrontTopPos + detRightFrontBottomPos + detRightFrontTopPos) / 4.0;
1569 const auto detTopMiddlePos = (detLeftFrontTopPos + detRightFrontTopPos) / 2.0;
1570
1571 // Sanity check to avoid dividing by zero
1572 if ((detRightFrontTopPos == detLeftFrontTopPos) || (detTopMiddlePos == detCentrePos) || (detCentrePos == samplePos)) {
1573 return nullptr;
1574 }
1575
1576 // Define the unit vectors needed for calculations
1577 const auto unitVecSampleToDet = Kernel::normalize(detCentrePos - samplePos);
1578 const auto unitVecLeftToRight = Kernel::normalize(
1579 V3D(-1.0 * unitVecSampleToDet.Z(), 0,
1580 -1.0 * unitVecSampleToDet.X())); // this is a vector normal to unitVecSampleToDet on XZ plane
1581
1582 const auto colOpenningLeftTopPos =
1583 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos +
1584 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) -
1585 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1586 const auto colOpenningRightTopPos =
1587 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos +
1588 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) +
1589 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1590 const auto colOpenningLeftBottomPos =
1591 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos -
1592 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) -
1593 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1594 const auto colOpenningRightBottomPos =
1595 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos -
1596 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) +
1597 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1598
1599 // Sanity check to avoid dividing by zero
1600 if ((colOpenningLeftTopPos == detLeftFrontTopPos) || (colOpenningLeftBottomPos == detLeftFrontBottomPos) ||
1601 (colOpenningRightTopPos == detRightFrontTopPos) || (colOpenningRightBottomPos == detRightFrontBottomPos)) {
1602 return nullptr;
1603 }
1604
1605 // Unit vectors along the sides of hexahedron shape
1606 const auto unitVecAlongLeftTopLeg = Kernel::normalize(colOpenningLeftTopPos - detLeftFrontTopPos);
1607 const auto unitVecAlongLeftBottomLeg = Kernel::normalize(colOpenningLeftBottomPos - detLeftFrontBottomPos);
1608 const auto unitVecAlongRightTopLeg = Kernel::normalize(colOpenningRightTopPos - detRightFrontTopPos);
1609 const auto unitVecAlongRightBottomLeg = Kernel::normalize(colOpenningRightBottomPos - detRightFrontBottomPos);
1610
1611 // Positions of the hexahedron extended towards the sample for each of its legs to have a length twice the lenght of
1612 // sample to detector
1613 double sampleCentreToDetDistance = (detCentrePos - samplePos).norm();
1614 const auto leftFrontBottomPoint = detRightFrontTopPos + unitVecAlongRightTopLeg * sampleCentreToDetDistance * 2.0;
1615 const auto hexaHedronLegsLenRatio =
1616 (leftFrontBottomPoint - detRightFrontTopPos).norm() / (colOpenningRightTopPos - detRightFrontTopPos).norm();
1617 const auto leftBackBottomPoint = detLeftFrontTopPos + unitVecAlongLeftTopLeg * hexaHedronLegsLenRatio *
1618 (colOpenningLeftTopPos - detLeftFrontTopPos).norm();
1619 const auto rightFrontBottomPoint =
1620 detRightFrontBottomPos +
1621 unitVecAlongRightBottomLeg * hexaHedronLegsLenRatio * (colOpenningRightBottomPos - detRightFrontBottomPos).norm();
1622 const auto rightBackBottomPoint =
1623 detLeftFrontBottomPos +
1624 unitVecAlongLeftBottomLeg * hexaHedronLegsLenRatio * (colOpenningLeftBottomPos - detLeftFrontBottomPos).norm();
1625 std::ostringstream xmlShapeStream;
1626 xmlShapeStream << "<hexahedron id=\"corridor-shape\" >"
1627 << "<left-back-bottom-point x=\"" << leftBackBottomPoint.X() << "\""
1628 << " y=\"" << leftBackBottomPoint.Y() << "\""
1629 << " z=\"" << leftBackBottomPoint.Z() << "\" />"
1630 << "<left-front-bottom-point x=\"" << leftFrontBottomPoint.X() << "\""
1631 << " y=\"" << leftFrontBottomPoint.Y() << "\""
1632 << " z=\"" << leftFrontBottomPoint.Z() << "\" />"
1633 << "<right-front-bottom-point x=\"" << rightFrontBottomPoint.X() << "\""
1634 << " y=\"" << rightFrontBottomPoint.Y() << "\""
1635 << " z=\"" << rightFrontBottomPoint.Z() << "\" />"
1636 << "<right-back-bottom-point x=\"" << rightBackBottomPoint.X() << "\""
1637 << " y=\"" << rightBackBottomPoint.Y() << "\""
1638 << " z=\"" << rightBackBottomPoint.Z() << "\" />"
1639 << "<left-back-top-point x=\"" << detLeftFrontTopPos.X() << "\""
1640 << " y=\"" << detLeftFrontTopPos.Y() << "\""
1641 << " z=\"" << detLeftFrontTopPos.Z() << "\" />"
1642 << "<left-front-top-point x=\"" << detRightFrontTopPos.X() << "\""
1643 << " y=\"" << detRightFrontTopPos.Y() << "\""
1644 << " z=\"" << detRightFrontTopPos.Z() << "\" />"
1645 << "<right-front-top-point x=\"" << detRightFrontBottomPos.X() << "\""
1646 << " y=\"" << detRightFrontBottomPos.Y() << "\""
1647 << " z=\"" << detRightFrontBottomPos.Z() << "\" />"
1648 << "<right-back-top-point x=\"" << detLeftFrontBottomPos.X() << "\""
1649 << " y=\"" << detLeftFrontBottomPos.Y() << "\""
1650 << " z=\"" << detLeftFrontBottomPos.Z() << "\" />"
1651 << "</hexahedron>";
1652 Geometry::ShapeFactory shapeMaker;
1653 const auto collimatorCorridorCsgObj = shapeMaker.createShape(xmlShapeStream.str());
1654 writeToCollimatorCorridorCache(histogramIndex, collimatorCorridorCsgObj);
1655 return collimatorCorridorCsgObj;
1656}
1657
1658const std::shared_ptr<Geometry::CSGObject>
1660 std::shared_lock<std::shared_mutex> guard(m_mutexCorridorCache);
1661 const auto itCollimatorCorridor = m_collimatorCorridorCache.find(histogramIndex);
1662 if (itCollimatorCorridor != m_collimatorCorridorCache.end()) {
1663 return itCollimatorCorridor->second;
1664 }
1665 return nullptr;
1666}
1667
1669 const std::size_t &histogramIndex, const std::shared_ptr<Geometry::CSGObject> &collimatorCorridorCsgObj) {
1670 std::unique_lock<std::shared_mutex> guard(m_mutexCorridorCache);
1671 m_collimatorCorridorCache[histogramIndex] = collimatorCorridorCsgObj;
1672}
1673
1675 m_collimatorCorridorCache.clear(); // Clear the cache for collimator corridor shapes
1676 const bool radialCollimator = getProperty("RadialCollimator");
1677 if (radialCollimator) {
1678 m_collimatorInfo = std::make_unique<CollimatorInfo>();
1679 // Collimator inner radius
1680 m_collimatorInfo->m_innerRadius = getDoubleParamFromIDF("col-radius");
1681 // Half of the angular extent of the collimator seen from the sample
1682 m_collimatorInfo->m_halfAngularExtent = 0.5 * getDoubleParamFromIDF("col-angular-extent");
1683 // Height of collimator plate
1684 m_collimatorInfo->m_plateHeight = getDoubleParamFromIDF("col-plate-height");
1685 m_collimatorInfo->m_axisVec = getV3DParamFromIDF("col-axis");
1686 }
1687}
1688
1690 if (!m_instrument->hasParameter(paramName)) {
1691 throw std::runtime_error("Cannot find parameter:" + paramName + " from instrument parameter file");
1692 }
1693 std::vector<double> val_vec = m_instrument->getNumberParameter(paramName, true);
1694 if (val_vec.empty()) {
1695 throw std::runtime_error("No value specified for:" + paramName + " in the instrument parameter file");
1696 }
1697
1698 return static_cast<double>(val_vec.front());
1699}
1700
1702 if (!m_instrument->hasParameter(paramName)) {
1703 throw std::runtime_error("Cannot find parameter:" + paramName + " from instrument parameter file");
1704 }
1705
1706 std::string paramValStr = m_instrument->getStringParameter(paramName)[0];
1707 std::vector<std::string> v3dStrComponent;
1708 boost::split(v3dStrComponent, paramValStr, boost::is_any_of(","));
1709 if (v3dStrComponent.size() != 3) {
1710 throw std::runtime_error("Invalid number of coordinates given for parameter:" + paramName +
1711 " in instrument parameter file");
1712 }
1713 std::vector<double> v3dComponents(3);
1714 std::transform(v3dStrComponent.begin(), v3dStrComponent.end(), v3dComponents.begin(),
1715 [](const std::string &str) -> double { return std::stod(str); });
1716
1717 return Kernel::V3D(v3dComponents[0], v3dComponents[1], v3dComponents[2]);
1718}
1719
1720double DiscusMultipleScatteringCorrection::getKf(const double deltaE, const double kinc) {
1721 double kf;
1722 if (deltaE == 0.) {
1723 kf = kinc; // avoid costly sqrt
1724 } else {
1725 // slightly concerned that rounding errors moving between k and E may mean we take the sqrt of
1726 // a negative number in here. deltaE was capped using a threshold calculated using fromWaveVector so
1727 // hopefully any rounding will affect fromWaveVector(kinc) in same direction
1728 kf = toWaveVector(fromWaveVector(kinc) - deltaE);
1729 assert(!std::isnan(kf));
1730 }
1731 return kf;
1732} // namespace Mantid::Algorithms
1733
1746std::tuple<double, double> DiscusMultipleScatteringCorrection::getKinematicRange(double kf, double ki) {
1747 const double qmin = abs(kf - ki);
1748 const double qrange = 2 * std::min(ki, kf);
1749 return {qmin, qrange};
1750}
1758std::tuple<double, double, int, double>
1760 Kernel::PseudoRandomNumberGenerator &rng, const double kinc) {
1761
1762 // in order to keep integration limits constant sample full range of w even if some not kinematically accessible
1763 // Note - Discus took different approach where it sampled q,w from kinematically accessible range only but it
1764 // only calculated for double scattering and easier to normalise in that case
1765 double wRange;
1766 /*
1767 // The rectangular integration region could be restricted further by limiting w range by calculating max possible w
1768 // TO DO: validate the results for this optimisation
1769 // the energy transfer must always be less than the positive value corresponding to energy going from ki to 0
1770 // Note - this is still the case for indirect because on a multiple scatter the kf isn't kfixed
1771 double wMax = fromWaveVector(kinc);
1772 // find largest w bin centre that is < wmax and then sample w up to the next bin edge
1773 auto it = std::lower_bound(wValues.begin(), wValues.end(), wMax);
1774 int iWMax = static_cast<int>(std::distance(wValues.begin(), it) - 1);*/
1775 int iW = 0;
1776 if (wValues.size() == 1) {
1777 iW = 0;
1778 wRange = 1;
1779 } else {
1780 std::vector<double> wBinEdges;
1781 wBinEdges.reserve(wValues.size() + 1);
1782 VectorHelper::convertToBinBoundary(wValues, wBinEdges);
1783 // w bins not necessarily equal so don't just sample w index
1784 wRange = /*std::min(wMax, wBinEdges[iWMax + 1])*/ wBinEdges.back() - wBinEdges.front();
1785 double w = wBinEdges.front() + rng.nextValue() * wRange;
1786 iW = static_cast<int>(Kernel::VectorHelper::indexOfValueFromCentersNoThrow(wValues, w));
1787 }
1788 double maxkf = toWaveVector(fromWaveVector(kinc) - wValues.front());
1789 double qRange = kinc + maxkf;
1790 double q = qRange * rng.nextValue();
1791 return {q, qRange, iW, wRange};
1792}
1793
1803 // the QSQIntegrals were divided by k^2 so in theory they should be ~flat
1804 return interpolateFlat(QSQScaleFactor, k) * 2 * k * k;
1805}
1806
1819 const ComponentWorkspaceMappings &componentWorkspaces, double &k,
1820 const double scatteringXSection,
1821 Kernel::PseudoRandomNumberGenerator &rng, double &weight) {
1822 const double kinc = k;
1823 double QQ;
1824 int iW;
1825 auto componentWSIt = findMatchingComponent(componentWorkspaces, shapePtr);
1827 std::tie(QQ, iW) = sampleQW(componentWSIt->InvPOfQ, rng.nextValue());
1828 k = getKf(componentWSIt->SQ->getSpecAxisValues()[iW], kinc);
1829 weight = weight * scatteringXSection;
1830 } else {
1831 double qrange, wRange;
1832 auto &wValues = componentWSIt->SQ->getSpecAxisValues();
1833 std::tie(QQ, qrange, iW, wRange) = sampleQWUniform(wValues, rng, kinc);
1834 // if w inaccessible return (ie treat as zero weight) rather than retry so that integration stays over full w
1835 // range
1836 if (fromWaveVector(kinc) - wValues[iW] <= 0)
1837 return false;
1838 k = getKf(wValues[iW], kinc);
1839 double SQ = interpolateGaussian(componentWSIt->logSQ->histogram(iW), QQ);
1840 // integrate over rectangular area of qw space
1841 weight = weight * scatteringXSection * SQ * QQ * qrange * wRange;
1842 if (SQ > 0) {
1843 double integralQSQ = getQSQIntegral(*componentWSIt->QSQScaleFactor, kinc);
1844 assert(integralQSQ != 0.);
1845 weight = weight / integralQSQ;
1846 } else
1847 return false;
1848 }
1849 // T = 2theta
1850 const double cosT = (kinc * kinc + k * k - QQ * QQ) / (2 * kinc * k);
1851 // if q not accessible return rather than retry so that integration stays over rectangular area
1852 if (std::abs(cosT) > 1.0)
1853 return false;
1854
1855 updateTrackDirection(track, cosT, rng.nextValue() * 2 * M_PI);
1856 return true;
1857}
1858
1866 const double phi) {
1867 const auto B3 = sqrt(1 - cosT * cosT);
1868 const auto B2 = cosT;
1869 // possible to do this using the Quat class instead??
1870 // Quat(const double _deg, const V3D &_axis);
1871 // Quat(acos(cosT)*180/M_PI,
1872 // Kernel::V3D(track.direction()[],track.direction()[],0))
1873
1874 // Rodrigues formula with final term equal to zero
1875 // v_rot = cosT * v + sinT(k x v)
1876 // with rotation axis k orthogonal to v
1877 // Define k by first creating two vectors orthogonal to v:
1878 // (vy, -vx, 0) by inspection
1879 // and then (-vz * vx, -vy * vz, vx * vx + vy * vy) as cross product
1880 // Then define k as combination of these:
1881 // sin(phi) * (vy, -vx, 0) + cos(phi) * (-vx * vz, -vy * vz, 1 - vz * vz)
1882 // ...with division by normalisation factor of sqrt(vx * vx + vy * vy)
1883 // Note: xyz convention here isn't the standard Mantid one. x=beam, z=up
1884 const auto vy = track.direction()[0];
1885 const auto vz = track.direction()[1];
1886 const auto vx = track.direction()[2];
1887 double UKX, UKY, UKZ;
1888 if (vz * vz < 1.0) {
1889 // calculate A2 from vx^2 + vy^2 rather than 1-vz^2 to reduce floating point rounding error when vz close to
1890 // 1
1891 auto A2 = sqrt(vx * vx + vy * vy);
1892 auto UQTZ = cos(phi) * A2;
1893 auto UQTX = -cos(phi) * vz * vx / A2 + sin(phi) * vy / A2;
1894 auto UQTY = -cos(phi) * vz * vy / A2 - sin(phi) * vx / A2;
1895 UKX = B2 * vx + B3 * UQTX;
1896 UKY = B2 * vy + B3 * UQTY;
1897 UKZ = B2 * vz + B3 * UQTZ;
1898 } else {
1899 // definition of phi in general formula is dependent on v. So may see phi "redefinition" as vx and vy tend
1900 // to zero and you move from general formula to this special case
1901 UKX = B3 * cos(phi);
1902 UKY = B3 * sin(phi);
1903 UKZ = B2 * vz;
1904 }
1905 track.reset(track.startPoint(), Kernel::V3D(UKY, UKZ, UKX));
1906}
1907
1916 for (int i = 0; i < m_maxScatterPtAttempts; i++) {
1917 auto t = generateInitialTrack(rng);
1918 int nlinks = m_sampleShape->interceptSurface(t);
1920 if (m_env) {
1921 nlinks += m_env->interceptSurfaces(t);
1923 }
1924 if (nlinks > 0) {
1925 if (i > 0) {
1926 if (g_log.is(Kernel::Logger::Priority::PRIO_WARNING)) {
1928 }
1929 }
1930 return t;
1931 }
1932 }
1933 throw std::runtime_error(
1934 "DiscusMultipleScatteringCorrection::start_point() - Unable to generate entry point into sample after " +
1935 std::to_string(m_maxScatterPtAttempts) + " attempts. Try increasing MaxScatterPtAttempts");
1936}
1937
1950 Geometry::Track &track, double &weight, const double k, Kernel::PseudoRandomNumberGenerator &rng,
1951 bool specialSingleScatterCalc, const ComponentWorkspaceMappings &componentWorkspaces) {
1952 double totalMuL = 0.;
1953 auto nlinks = track.count();
1954 // Set default size to 5 (same as in LineIntersectVisit.h)
1955 boost::container::small_vector<std::tuple<const Geometry::IObject *, double, double, double>, 5> geometryObjects;
1956 geometryObjects.reserve(nlinks);
1957 // loop through all the track segments calculating some useful quantities for later
1958 for (auto it = track.cbegin(); it != track.cend(); it++) {
1959 const double trackSegLength = it->distInsideObject;
1960 const auto geometryObj = it->object;
1961 double sigma_total;
1962 std::tie(sigma_total, std::ignore) = new_vector(geometryObj->material(), k, specialSingleScatterCalc);
1963 double vmu = 100 * geometryObj->material().numberDensityEffective() * sigma_total;
1964 double muL = trackSegLength * vmu;
1965 totalMuL += muL;
1966 // some overlap between the quantities stored here but since calculated them all may as well store them all
1967 geometryObjects.emplace_back(geometryObj, vmu, muL, sigma_total);
1968 }
1969
1970 // randomly sample distance travelled across a total muL and work out which component this sits in
1971 double b4Overall = (1.0 - exp(-totalMuL));
1972 double muL = -log(1 - rng.nextValue() * b4Overall);
1973 double vl = 0.;
1974 double newWeight = 0.;
1975 double prevExpTerms = 1.;
1976 std::tuple<const Geometry::IObject *, double, double, double> geometryObjectDetails;
1977 for (size_t i = 0; i < geometryObjects.size(); i++) {
1978 geometryObjectDetails = geometryObjects[i];
1979 auto muL_i = std::get<2>(geometryObjectDetails);
1980 auto vmu_i = std::get<1>(geometryObjectDetails);
1981 if (muL - muL_i > 0) {
1982 vl += muL_i / vmu_i;
1983 muL = muL - muL_i;
1984 prevExpTerms *= exp(-muL_i);
1985 } else {
1986 vl += muL / vmu_i;
1987 double b4 = (1.0 - exp(-muL_i)) * prevExpTerms;
1988 auto sigma_total = std::get<3>(geometryObjectDetails);
1989 newWeight = b4 / sigma_total;
1990 break;
1991 }
1992 }
1993 weight = weight * newWeight;
1994 // At the moment this doesn't cope if sample shape is concave eg if track has more than one segment inside the
1995 // sample with segment outside sample in between
1996 // Note - this clears the track intersections but the sample\environment shapes live on
1997 inc_xyz(track, vl);
1998 auto geometryObject = std::get<0>(geometryObjectDetails);
1999 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG)) {
2000 auto componentIt = findMatchingComponent(componentWorkspaces, geometryObject);
2001 (*(componentIt->scatterCount))++;
2002 }
2003 return geometryObject;
2004}
2005
2013 // generate random point on front surface of sample bounding box
2014 // The change of variables from length to t1 means this still samples the points fairly in the integration
2015 // volume even in shapes like cylinders where the depth varies across xy
2016 auto neutron = m_beamProfile->generatePoint(rng, m_activeRegion);
2017 auto ptx = neutron.startPos.X();
2018 auto pty = neutron.startPos.Y();
2019
2020 auto ptOnBeamProfile = Kernel::V3D();
2021 ptOnBeamProfile[m_refframe->pointingHorizontal()] = ptx;
2022 ptOnBeamProfile[m_refframe->pointingUp()] = pty;
2023 ptOnBeamProfile[m_refframe->pointingAlongBeam()] = m_sourcePos[m_refframe->pointingAlongBeam()];
2024 auto toSample = Kernel::V3D();
2025 toSample[m_refframe->pointingAlongBeam()] = 1.;
2026 return Geometry::Track(ptOnBeamProfile, toSample);
2027}
2028
2037 Kernel::V3D position = track.front().entryPoint;
2038 Kernel::V3D direction = track.direction();
2039 const auto x = position[0] + vl * direction[0];
2040 const auto y = position[1] + vl * direction[1];
2041 const auto z = position[2] + vl * direction[2];
2042 const auto startPoint = V3D(x, y, z);
2044 track.reset(startPoint, track.direction());
2045}
2046
2056std::shared_ptr<SparseWorkspace>
2058 const size_t rows, const size_t columns) {
2059 auto sparseWS = std::make_shared<SparseWorkspace>(modelWS, nXPoints, rows, columns);
2060 return sparseWS;
2061}
2062
2064 for (auto &SQWSMapping : matWSs) {
2065 auto &QSQ = SQWSMapping.QSQ;
2066 size_t expectedMaxSize =
2067 std::accumulate(QSQ->histograms().cbegin(), QSQ->histograms().cend(), static_cast<size_t>(0),
2068 [](const size_t value, const DiscusData1D &histo) { return value + histo.Y.size(); });
2069 auto ws = std::make_shared<DiscusData2D>(std::vector<DiscusData1D>(nhists), nullptr);
2070 ws->histogram(0).X.reserve(expectedMaxSize);
2071 for (size_t i = 0; i < nhists; i++)
2072 ws->histogram(i).Y.reserve(expectedMaxSize);
2073 SQWSMapping.InvPOfQ = ws;
2074 }
2075}
2076
2078 MatrixWorkspace_uptr outputWS = DataObjects::create<Workspace2D>(inputWS);
2079 // The algorithm computes the signal values at bin centres so they should
2080 // be treated as a distribution
2081 outputWS->setDistribution(true);
2082 outputWS->setYUnit("");
2083 outputWS->setYUnitLabel("Scattered Weight");
2084 return outputWS;
2085}
2086
2093 auto interpolationOpt = std::make_unique<InterpolationOption>();
2094 return interpolationOpt;
2095}
2096
2098 MatrixWorkspace &targetWS, const SparseWorkspace &sparseWS,
2099 const Mantid::Algorithms::InterpolationOption &interpOpt) {
2100 const auto &spectrumInfo = targetWS.spectrumInfo();
2101 const auto refFrame = targetWS.getInstrument()->getReferenceFrame();
2102 PARALLEL_FOR_IF(Kernel::threadSafe(targetWS, sparseWS))
2103 for (int64_t i = 0; i < static_cast<decltype(i)>(spectrumInfo.size()); ++i) {
2105 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i)) {
2106 double lat, lon;
2107 std::tie(lat, lon) = spectrumInfo.geographicalAngles(i);
2108 const auto spatiallyInterpHisto = sparseWS.bilinearInterpolateFromDetectorGrid(lat, lon);
2109 if (spatiallyInterpHisto.size() > 1) {
2110 auto targetHisto = targetWS.histogram(i);
2111 interpOpt.applyInPlace(spatiallyInterpHisto, targetHisto);
2112 targetWS.setHistogram(i, targetHisto);
2113 } else {
2114 targetWS.mutableY(i) = spatiallyInterpHisto.y().front();
2115 }
2116 }
2118 }
2120}
2121
2129 bool noClash(false);
2130
2131 for (int i = 0; !noClash; ++i) {
2132 std::string wsIndex; // dont use an index if there is no other
2133 // workspace
2134 if (i > 0) {
2135 wsIndex = "_" + std::to_string(i);
2136 }
2137
2138 bool wsExists = AnalysisDataService::Instance().doesExist(wsName + wsIndex);
2139 if (!wsExists) {
2140 wsName += wsIndex;
2141 noClash = true;
2142 }
2143 }
2144}
2145
2154 API::AnalysisDataService::Instance().addOrReplace(wsName, ws);
2155}
2156
2165 const Geometry::IObject *shapeObjectWithScatter) {
2166 // Currently look up based on the raw pointer value. Did consider looking up based on something more human readable
2167 // such as the component id or name but this isn't guaranteed to be set and a string key may be longer than the
2168 // pointer which is probably 8 bytes
2169 auto componentWSIt = std::find_if(componentWorkspaces.begin(), componentWorkspaces.end(),
2170 [shapeObjectWithScatter](const ComponentWorkspaceMapping &SQWS) {
2171 return SQWS.ComponentPtr.get() == shapeObjectWithScatter;
2172 });
2173 assert(componentWSIt != componentWorkspaces.end());
2174 // can't return iterator because boost have moved vec_iterator into a different namespace post v1.65.1 so won't
2175 // build on all platforms
2176 return &(*componentWSIt);
2177}
2178
2180 m_sampleShape = inputWS->sample().getShapePtr();
2181 try {
2182 m_env = &inputWS->sample().getEnvironment();
2183 } catch (std::runtime_error &) {
2184 // swallow this as no defined environment from getEnvironment
2185 }
2186 // generate the bounding box before the multithreaded section
2187 m_activeRegion = m_sampleShape->getBoundingBox();
2188 if (m_env) {
2189 const auto &envBox = m_env->boundingBox();
2190 m_activeRegion.grow(envBox);
2191 }
2192 m_instrument = inputWS->getInstrument();
2194 m_refframe = m_instrument->getReferenceFrame();
2195 m_sourcePos = m_instrument->getSource()->getPos();
2196}
2197
2198} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
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
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...
#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.
#define GNU_DIAG_ON(x)
#define GNU_DIAG_OFF(x)
This is a collection of macros for turning compiler warnings off in a controlled manner.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
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 getAlwaysStoreInADS() const override
Returns true if we always store in the AnalysisDataService.
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.
const Geometry::DetectorInfo & detectorInfo() const
Return a const reference to the DetectorInfo object.
Geometry::Instrument_const_sptr getInstrument() const
Returns the parameterized instrument.
specnum_t getSpectrumNo() const
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.
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::map< std::size_t, std::shared_ptr< Geometry::CSGObject > > m_collimatorCorridorCache
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)
Create new workspace with y equal to integral across the bins.
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 writeToCollimatorCorridorCache(const std::size_t &histogramIndex, const std::shared_ptr< Geometry::CSGObject > &collimatorCorridorCsgObj)
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.
const std::shared_ptr< Geometry::CSGObject > readFromCollimatorCorridorCache(const std::size_t &histogramIndex)
void correctForWorkspaceNameClash(std::string &wsName)
Adjust workspace name in case of clash in the ADS.
const std::shared_ptr< Geometry::CSGObject > createCollimatorHexahedronShape(const Kernel::V3D &samplePos, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex)
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).
std::tuple< std::vector< double >, std::vector< double > > simulatePaths(const int nEvents, const int nScatters, Kernel::PseudoRandomNumberGenerator &rng, const ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector< double > &wValues, bool specialSingleScatterCalc, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex)
Simulates a set of neutron paths through the sample to a specific detector position with each path co...
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
Geometry::DetectorInfo is an intermediate step towards a DetectorInfo that is part of Instrument-2....
Kernel::V3D position(const size_t index) const
Returns the position of the detector with given index.
const Geometry::IDetector & detector(const size_t index) const
Return a const reference to the detector with given index.
size_t indexOf(const detid_t id) const
Returns the index of the detector with the given detector ID.
virtual detid_t getID() const =0
Get the detector ID.
virtual const std::shared_ptr< const IObject > shape() const =0
Returns the shape of the Object.
IObject : Interface for geometry objects.
Definition IObject.h:42
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
Class originally intended to be used with the DataHandling 'LoadInstrument' algorithm.
std::shared_ptr< CSGObject > createShape(Poco::XML::Element *pElem)
Creates a geometric object from a DOM-element-node pointing to an element whose child nodes contain t...
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:117
bool is(int level) const
Returns true if at least the given log level is set.
Definition Logger.cpp:177
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
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 Mersenne Twister 19937 pseudo-random number generator algorithm as a specialzatio...
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
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:129
double norm() const noexcept
Definition V3D.h:269
EXPORT_OPT_MANTIDQT_COMMON std::string getEMode(const Mantid::API::MatrixWorkspace_sptr &ws)
Gets the energy mode from a workspace based on the X unit.
std::unique_ptr< MatrixWorkspace > MatrixWorkspace_uptr
unique pointer to Mantid::API::MatrixWorkspace
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
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:167
std::shared_ptr< const IObject > IObject_const_sptr
Typdef for a shared pointer to a const object.
Definition IObject.h:95
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.
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition V3D.h:352
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.
Helper class which provides the Collimation Length for SANS instruments.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
int32_t detid_t
Typedef for a detector ID.
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:14
STL namespace.
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.
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