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](std::span<double const> 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 checkEqualQBins(SQWS->x(iHist));
305 }
306 } else if (SQWS->getAxis(1)->unit()->unitID() == "MomentumTransfer") {
307 auto qAxis = dynamic_cast<NumericAxis *>(SQWS->getAxis(1));
308 if (qAxis) {
309 auto qValues = qAxis->getValues();
310 checkEqualQBins(qValues);
311 }
312 }
313 }
314 }
315
316 for (auto &SQWS : SQWSs) {
317 for (size_t i = 0; i < SQWS->getNumberHistograms(); i++) {
318 auto &y = SQWS->y(i);
319 if (std::any_of(y.cbegin(), y.cend(), [](const auto yval) { return yval < 0 || std::isnan(yval); }))
320 issues["StructureFactorWorkspace"] += "S(Q) workspace must have all y >= 0";
321 }
322 }
323
324 const int nSimulationPoints = getProperty("NumberOfSimulationPoints");
325 if (!isEmpty(nSimulationPoints)) {
326 InterpolationOption interpOpt;
327 const std::string interpValue = getPropertyValue("Interpolation");
328 interpOpt.set(interpValue, false, false);
329 const auto nSimPointsIssue = interpOpt.validateInputSize(nSimulationPoints);
330 if (!nSimPointsIssue.empty())
331 issues["NumberOfSimulationPoints"] = nSimPointsIssue;
332 }
333
334 const bool simulateEnergiesIndependently = getProperty("SimulateEnergiesIndependently");
335 if (simulateEnergiesIndependently) {
336 if (inputWS->getEMode() == Kernel::DeltaEMode::Elastic)
337 issues["SimulateEnergiesIndependently"] =
338 "SimulateEnergiesIndependently is only applicable to inelastic direct geometry calculations";
339 if (inputWS->getEMode() == Kernel::DeltaEMode::Indirect)
340 issues["SimulateEnergiesIndependently"] =
341 "SimulateEnergiesIndependently is only applicable to inelastic direct geometry calculations. Different "
342 "energy transfer bins are always simulated separately for indirect geometry";
343 }
344
345 return issues;
346}
355 double &xmax) const {
356 // set to crazy values to start
357 xmin = std::numeric_limits<double>::max();
358 xmax = -1.0 * xmin;
359 size_t numberOfSpectra = ws.getNumberHistograms();
360 const auto &spectrumInfo = ws.spectrumInfo();
361
362 // determine the data range - only return min > 0. Bins with x=0 will be skipped later on
363 for (size_t wsIndex = 0; wsIndex < numberOfSpectra; wsIndex++) {
364 if (spectrumInfo.hasDetectors(wsIndex) && !spectrumInfo.isMonitor(wsIndex) && !spectrumInfo.isMasked(wsIndex)) {
365 const auto &dataX = ws.points(wsIndex);
366 const double xfront = dataX.front();
367 const double xback = dataX.back();
368 if (std::isnormal(xfront) && std::isnormal(xback)) {
369 if (xfront < xmin)
370 xmin = xfront;
371 if (xback > xmax)
372 xmax = xback;
373 }
374 }
375 }
376 if (xmin > xmax)
377 throw std::runtime_error("Unable to determine min and max x values for workspace");
378}
379
381 Workspace_sptr suppliedSQWS = getProperty("StructureFactorWorkspace");
382 auto SQWSGroup = std::dynamic_pointer_cast<WorkspaceGroup>(suppliedSQWS);
383 size_t nEnvComponents = 0;
384 if (m_env)
385 nEnvComponents = m_env->nelements();
386 m_SQWSs.clear();
387 if (SQWSGroup) {
388 std::string matName = m_sampleShape->material().name();
389 auto SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
390 addWorkspaceToDiscus2DData(m_sampleShape, matName, SQWSGroupMember);
391 if (nEnvComponents > 0) {
392 matName = m_env->getContainer().material().name();
393 SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
394 addWorkspaceToDiscus2DData(m_env->getContainer().getShapePtr(), matName, SQWSGroupMember);
395 }
396 for (size_t i = 1; i < nEnvComponents; i++) {
397 matName = m_env->getComponent(i).material().name();
398 SQWSGroupMember = std::static_pointer_cast<MatrixWorkspace>(SQWSGroup->getItem(matName));
399 addWorkspaceToDiscus2DData(m_env->getComponentPtr(i), matName, SQWSGroupMember);
400 }
401 } else {
403 std::dynamic_pointer_cast<MatrixWorkspace>(suppliedSQWS));
404 MatrixWorkspace_sptr isotropicSQ = DataObjects::create<Workspace2D>(
405 *std::dynamic_pointer_cast<MatrixWorkspace>(suppliedSQWS), static_cast<size_t>(1),
406 HistogramData::Histogram(HistogramData::Points{0.}, HistogramData::Frequencies{1.}));
407 if (nEnvComponents > 0) {
408 std::string_view matName = m_env->getContainer().material().name();
409 g_log.information() << "Creating isotropic structure factor for " << matName << std::endl;
410 addWorkspaceToDiscus2DData(m_env->getContainer().getShapePtr(), matName, isotropicSQ);
411 }
412 for (size_t i = 1; i < nEnvComponents; i++) {
413 std::string_view matName = m_env->getComponent(i).material().name();
414 g_log.information() << "Creating isotropic structure factor for " << matName << std::endl;
415 addWorkspaceToDiscus2DData(m_env->getComponentPtr(i), matName, isotropicSQ);
416 }
417 }
418}
419
425 const std::string_view &matName,
427 // avoid repeated conversion of bin edges to points inside loop by converting to point data
429 // if S(Q,w) has been supplied ensure Q is along the x axis of each spectrum (so same as S(Q))
430 if (SQWS->getAxis(1)->unit()->unitID() == "MomentumTransfer") {
431 auto transposeAlgorithm = this->createChildAlgorithm("Transpose");
432 transposeAlgorithm->initialize();
433 transposeAlgorithm->setProperty("InputWorkspace", SQWS);
434 transposeAlgorithm->setProperty("OutputWorkspace", "_");
435 transposeAlgorithm->execute();
436 SQWS = transposeAlgorithm->getProperty("OutputWorkspace");
437 } else if (SQWS->getAxis(1)->isSpectra()) {
438 // for elastic set w=0 on the spectrum axis to align code with inelastic
439 auto newAxis = std::make_unique<NumericAxis>(std::vector<double>{0.});
440 newAxis->setUnit("DeltaE");
441 SQWS->replaceAxis(1, std::move(newAxis));
442 }
443 auto specAxis = dynamic_cast<NumericAxis *>(SQWS->getAxis(1));
444 std::vector<DiscusData1D> data;
445 for (size_t i = 0; i < SQWS->getNumberHistograms(); i++) {
446 data.emplace_back(SQWS->x(i).rawData(), SQWS->y(i).rawData());
447 }
448 ComponentWorkspaceMapping SQWSMapping{
449 shape, matName,
450 std::make_shared<DiscusData2D>(data, std::make_shared<std::vector<double>>(specAxis->getValues()))};
451 SQWSMapping.logSQ = SQWSMapping.SQ->createCopy();
452 convertToLogWorkspace(SQWSMapping.logSQ);
453 m_SQWSs.push_back(SQWSMapping);
454}
455
462 if (ws->isHistogramData()) {
464 auto pointDataAlgorithm = this->createChildAlgorithm("ConvertToPointData");
465 pointDataAlgorithm->initialize();
466 pointDataAlgorithm->setProperty("InputWorkspace", ws);
467 pointDataAlgorithm->setProperty("OutputWorkspace", "_");
468 pointDataAlgorithm->execute();
469 ws = pointDataAlgorithm->getProperty("OutputWorkspace");
470 } else {
471 // flat interpolation is later used on S(Q) so convert to points by assigning Y value to LH bin edge
472 MatrixWorkspace_sptr SQWSPoints =
473 API::WorkspaceFactory::Instance().create(ws, ws->getNumberHistograms(), ws->blocksize(), ws->blocksize());
474 SQWSPoints->setSharedY(0, ws->sharedY(0));
475 SQWSPoints->setSharedE(0, ws->sharedE(0));
476 std::vector<double> newX = ws->x(0).rawData();
477 newX.pop_back();
478 SQWSPoints->setSharedX(0, HistogramData::Points(newX).cowData());
479 ws = SQWSPoints;
480 }
481 }
482 auto binAxis = dynamic_cast<BinEdgeAxis *>(ws->getAxis(1));
483 if (binAxis) {
484 auto edges = binAxis->getValues();
485 std::vector<double> centres;
486 VectorHelper::convertToBinCentre(edges, centres);
487 auto newAxis = std::make_unique<NumericAxis>(centres);
488 newAxis->setUnit(ws->getAxis(1)->unit()->unitID());
489 ws->replaceAxis(1, std::move(newAxis));
490 }
491}
492
497 if (!getAlwaysStoreInADS())
498 throw std::runtime_error("This algorithm explicitly stores named output workspaces in the ADS so must be run with "
499 "AlwaysStoreInADS set to true");
500 const MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
501
505
506 MatrixWorkspace_sptr sigmaSSWS = getProperty("ScatteringCrossSection");
507 if (sigmaSSWS)
508 m_sigmaSS = std::make_shared<DiscusData1D>(sigmaSSWS->x(0).rawData(), sigmaSSWS->y(0).rawData());
509
510 // for inelastic we could calculate the qmax based on the min\max w in the S(Q,w) but that
511 // would bake as assumption that S(Q,w)=0 beyond the limits of the supplied data
512 double qmax = std::numeric_limits<float>::max();
513 EFixedProvider efixed(*inputWS);
514 m_EMode = efixed.emode();
515 g_log.information("EMode=" + DeltaEMode::asString(m_EMode) + " detected");
517 double kmin, kmax;
518 getXMinMax(*inputWS, kmin, kmax);
519 qmax = 2 * kmax;
520 }
521 prepareQSQ(qmax);
522
523 m_simulateEnergiesIndependently = getProperty("SimulateEnergiesIndependently");
524 // call this function with dummy efixed to determine total possible simulation points.
525 // the Points is named and const, so that viewing it as a span does not trigger a copy-on-write detach
526 auto const inputPoints = inputWS->points(0);
527 auto const inputNbins = generateInputKOutputWList(-1.0, inputPoints).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 const auto xPoints = instrumentWS.points(i);
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 ? std::vector<double>(xPoints.begin(), xPoints.end())
633 : std::vector{std::get<2>(kInW[bin])};
634
636 prepareCumulativeProbForQ(kinc, componentWorkspaces);
637
638 auto [weights, weightsErrors] =
639 simulatePaths(nSingleScatterEvents, 1, rng, componentWorkspaces, kinc, wValues, true, detectorInfo, i);
640 if (std::get<1>(kInW[bin]) == -1) {
641 noAbsSimulationWS->getSpectrum(i).mutableY() += weights;
642 noAbsSimulationWS->getSpectrum(i).mutableE() += weightsErrors;
643 } else {
644 noAbsSimulationWS->getSpectrum(i).mutableY()[std::get<1>(kInW[bin])] = weights[0];
645 noAbsSimulationWS->getSpectrum(i).mutableE()[std::get<1>(kInW[bin])] = weightsErrors[0];
646 }
647
648 for (int ne = 0; ne < nScatters; ne++) {
649 int nEvents = ne == 0 ? nSingleScatterEvents : nMultiScatterEvents;
650
651 std::tie(weights, weightsErrors) =
652 simulatePaths(nEvents, ne + 1, rng, componentWorkspaces, kinc, wValues, false, detectorInfo, i);
653 if (std::get<1>(kInW[bin]) == -1.0) {
654 simulationWSs[ne]->getSpectrum(i).mutableY() += weights;
655 simulationWSs[ne]->getSpectrum(i).mutableE() += weightsErrors;
656 } else {
657 simulationWSs[ne]->getSpectrum(i).mutableY()[std::get<1>(kInW[bin])] = weights[0];
658 simulationWSs[ne]->getSpectrum(i).mutableE()[std::get<1>(kInW[bin])] = weightsErrors[0];
659 }
660 }
661
662 prog.report(reportMsg);
663
664 // Ensure we have the last point for the interpolation
665 if (xStepSize > 1 && bin + xStepSize >= nbins && bin + 1 != nbins) {
666 bin = nbins - xStepSize - 1;
667 }
668 } // bins
669
670 // interpolate through points not simulated. Simulation WS only has
671 // reduced X values if using sparse instrument so no interpolation
672 // required
673 if (!useSparseInstrument && xStepSize > 1) {
674 auto histNoAbs = noAbsSimulationWS->histogram(i);
675 if (xStepSize < nbins) {
676 interpolateOpt.applyInplace(histNoAbs, xStepSize);
677 } else {
678 std::fill(histNoAbs.mutableY().begin() + 1, histNoAbs.mutableY().end(), histNoAbs.y()[0]);
679 }
680 noAbsOutputWS->setHistogram(i, histNoAbs);
681
682 for (size_t ne = 0; ne < static_cast<size_t>(nScatters); ne++) {
683 auto histnew = simulationWSs[ne]->histogram(i);
684 if (xStepSize < nbins) {
685 interpolateOpt.applyInplace(histnew, xStepSize);
686 } else {
687 std::fill(histnew.mutableY().begin() + 1, histnew.mutableY().end(), histnew.y()[0]);
688 }
689 outputWSs[ne]->setHistogram(i, histnew);
690 }
691 }
692 prog.report(reportMsg);
693 }
694
696 }
698
699 if (useSparseInstrument) {
700 Poco::Thread::sleep(200); // to ensure prog message changes
701 const std::string reportMsgSpatialInterpolation = "Spatial Interpolation";
702 prog.report(reportMsgSpatialInterpolation);
703 interpolateFromSparse(*noAbsOutputWS, *std::dynamic_pointer_cast<SparseWorkspace>(noAbsSimulationWS),
704 interpolateOpt);
705 for (size_t ne = 0; ne < static_cast<size_t>(nScatters); ne++) {
706 interpolateFromSparse(*outputWSs[ne], *std::dynamic_pointer_cast<SparseWorkspace>(simulationWSs[ne]),
707 interpolateOpt);
708 }
709 }
710
711 // Create workspace group that holds output workspaces
712 auto wsgroup = std::make_shared<WorkspaceGroup>();
713 auto outputGroupWSName = getPropertyValue("OutputWorkspace");
714 if (AnalysisDataService::Instance().doesExist(outputGroupWSName))
715 API::AnalysisDataService::Instance().deepRemoveGroup(outputGroupWSName);
716
717 const std::string wsNamePrefix = outputGroupWSName + "_Scatter_";
718 std::string wsName = wsNamePrefix + "1_NoAbs";
719 setWorkspaceName(noAbsOutputWS, wsName);
720 wsgroup->addWorkspace(noAbsOutputWS);
721
722 for (size_t i = 0; i < outputWSs.size(); i++) {
723 wsName = wsNamePrefix + std::to_string(i + 1);
724 setWorkspaceName(outputWSs[i], wsName);
725 wsgroup->addWorkspace(outputWSs[i]);
726
727 auto integratedWorkspace = integrateWS(outputWSs[i]);
728 setWorkspaceName(integratedWorkspace, wsName + "_Integrated");
729 wsgroup->addWorkspace(integratedWorkspace);
730 }
731
732 if (outputWSs.size() > 1) {
733 // create sum of multiple scatter workspaces for use in subtraction method
734 auto summedMScatOutput = createOutputWorkspace(*inputWS);
735 summedMScatOutput = std::accumulate(outputWSs.cbegin() + 1, outputWSs.cend(), summedMScatOutput);
736 wsName = wsNamePrefix + "2_" + std::to_string(outputWSs.size()) + "_Summed";
737 setWorkspaceName(summedMScatOutput, wsName);
738 wsgroup->addWorkspace(summedMScatOutput);
739 // create sum of all scattering order workspaces for use in ratio method
740 auto summedAllScatOutput = createOutputWorkspace(*inputWS);
741 summedAllScatOutput = summedMScatOutput + outputWSs[0];
742 wsName = wsNamePrefix + "1_" + std::to_string(outputWSs.size()) + "_Summed";
743 setWorkspaceName(summedAllScatOutput, wsName);
744 wsgroup->addWorkspace(summedAllScatOutput);
745 // create ratio of single to all scatter
746 auto ratioOutput = createOutputWorkspace(*inputWS);
747 ratioOutput = outputWSs[0] / summedAllScatOutput;
748 wsName = outputGroupWSName + "_Ratio_Single_To_All";
749 setWorkspaceName(ratioOutput, wsName);
750 wsgroup->addWorkspace(ratioOutput);
751
752 // ConvFit method being investigated by Spencer for inelastic currently uses the opposite ratio
754 auto invRatioOutput = 1 / ratioOutput;
755 auto replaceNans = this->createChildAlgorithm("ReplaceSpecialValues");
756 replaceNans->setChild(true);
757 replaceNans->initialize();
758 replaceNans->setProperty("InputWorkspace", invRatioOutput);
759 replaceNans->setProperty("OutputWorkspace", invRatioOutput);
760 replaceNans->setProperty("NaNValue", 0.0);
761 replaceNans->setProperty("InfinityValue", 0.0);
762 replaceNans->execute();
763 wsName = outputGroupWSName + "_Ratio_All_To_Single";
764 setWorkspaceName(invRatioOutput, wsName);
765 wsgroup->addWorkspace(invRatioOutput);
766 }
767 }
768
769 // set the output property
770 setProperty("OutputWorkspace", wsgroup);
771
772 if (g_log.is(Kernel::Logger::Priority::PRIO_INFORMATION)) {
773 g_log.information() << "Total simulation points=" << nhists * nSimulationPoints << "\n";
774 for (const auto &kv : m_attemptsToGenerateInitialTrack)
775 g_log.information() << "Generating initial track required " << kv.first << " attempts on " << kv.second
776 << " occasions.\n";
777 g_log.information() << "Calls to interceptSurface=" << m_callsToInterceptSurface << "\n";
778 g_log.information() << "Total I(k) calculations=" << m_IkCalculations << ", average per simulation point="
779 << static_cast<double>(m_IkCalculations) / static_cast<double>(nhists * nSimulationPoints)
780 << "\n";
781 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG))
782 for (size_t i = 0; i < m_SQWSs.size(); i++)
783 g_log.information() << "Scatters in component " << i << ": " << *(m_SQWSs[i].scatterCount) << "\n";
784 }
785}
786
795std::vector<std::tuple<double, int, double>>
797 std::span<double const> const xPoints) {
798 std::vector<std::tuple<double, int, double>> kInW;
799 const double kFixed = toWaveVector(efixed);
801 int index = 0;
802 std::transform(xPoints.begin(), xPoints.end(), std::back_inserter(kInW), [&index](double d) {
803 auto t = std::make_tuple(d, index, 0.);
804 index++;
805 return t;
806 });
807 } else {
809 kInW.emplace_back(std::make_tuple(kFixed, -1, 0.));
810 else {
811 for (int i = 0; i < static_cast<int>(xPoints.size()); i++) {
813 kInW.emplace_back(std::make_tuple(kFixed, i, xPoints[i]));
814 else if (m_EMode == DeltaEMode::Indirect) {
815 const double initialE = efixed + xPoints[i];
816 if (initialE > 0) {
817 const double kin = toWaveVector(initialE);
818 kInW.emplace_back(std::make_tuple(kin, i, xPoints[i]));
819 } else
820 // negative kinc is filtered out later
821 kInW.emplace_back(std::make_tuple(-1.0, i, xPoints[i]));
822 }
823 }
824 }
825 }
826 return kInW;
827}
828
835 for (auto &SQWSMapping : m_SQWSs) {
836 auto &SQWS = SQWSMapping.SQ;
837 std::shared_ptr<DiscusData2D> outputWS = SQWS->createCopy(true);
838 std::vector<double> IOfQYFull;
839 // loop through the S(Q) spectra for the different energy transfer values
840 for (size_t iW = 0; iW < SQWS->getNumberHistograms(); iW++) {
841 std::vector<double> qValues = SQWS->histogram(iW).X;
842 std::vector<double> SQValues = SQWS->histogram(iW).Y;
843 // add terminating points at 0 and qmax before multiplying by Q so no extrapolation problems
844 if (qValues.front() > 0.) {
845 qValues.insert(qValues.begin(), 0.);
846 SQValues.insert(SQValues.begin(), SQValues.front());
847 }
848 if (qValues.back() < qmax) {
849 qValues.push_back(qmax);
850 SQValues.push_back(SQValues.back());
851 }
852 // add some extra points to help the Q.S(Q) integral get the right answer
853 for (size_t i = 1; i < qValues.size(); i++) {
854 if (std::abs(SQValues[i] - SQValues[i - 1]) >
855 std::numeric_limits<double>::epsilon() * std::min(SQValues[i - 1], SQValues[i])) {
856 qValues.insert(qValues.begin() + i, std::nextafter(qValues[i], -DBL_MAX));
857 SQValues.insert(SQValues.begin() + i, SQValues[i - 1]);
858 i++;
859 }
860 }
861
862 std::vector<double> QSQValues;
863 std::transform(SQValues.begin(), SQValues.end(), qValues.begin(), std::back_inserter(QSQValues),
864 std::multiplies<double>());
865
866 outputWS->histogram(iW).X.resize(qValues.size());
867 outputWS->histogram(iW).X = qValues;
868 outputWS->histogram(iW).Y.resize(QSQValues.size());
869 outputWS->histogram(iW).Y = QSQValues;
870 }
871 SQWSMapping.QSQ = outputWS;
872 }
873}
874
885std::tuple<std::vector<double>, std::vector<double>, std::vector<double>>
886DiscusMultipleScatteringCorrection::integrateQSQ(const std::shared_ptr<DiscusData2D> &QSQ, double kinc,
887 const bool returnCumulative) {
888 std::vector<double> IOfQYFull, qValuesFull, wIndices;
889 double IOfQMaxPreviousRow = 0.;
890
891 auto &wValues = QSQ->getSpecAxisValues();
892 std::vector<double> wWidths;
893 if (wValues.size() == 1) {
894 // convertToBinBoundary currently gives width of 1 for single point but because this is essential for the maths
895 // set the width to 1 explicitly
896 wWidths.push_back(1.);
897 } else {
898 std::vector<double> wBinEdges;
899 wBinEdges.reserve(wValues.size() + 1);
900 VectorHelper::convertToBinBoundary(wValues, wBinEdges);
901 std::adjacent_difference(wBinEdges.begin(), wBinEdges.end(), std::back_inserter(wWidths));
902 wWidths.erase(wWidths.begin()); // first element returned by adjacent_difference isn't a diff so delete it
903 }
904
905 double wMax = fromWaveVector(kinc);
906 auto it = std::lower_bound(wValues.begin(), wValues.end(), wMax);
907 size_t iFirstInaccessibleW = std::distance(wValues.begin(), it);
908 auto nAccessibleWPoints = iFirstInaccessibleW;
909
910 // loop through the S(Q) spectra for the different energy transfer values
911 std::vector<double> IOfQX, IOfQY;
912 // reserve minimum space required for performance
913 IOfQYFull.reserve(nAccessibleWPoints);
914 qValuesFull.reserve(nAccessibleWPoints);
915 wIndices.reserve(nAccessibleWPoints);
916 //}
917 for (size_t iW = 0; iW < nAccessibleWPoints; iW++) {
918 auto kf = getKf((wValues)[iW], kinc);
919 auto [qmin, qrange] = getKinematicRange(kf, kinc);
920 IOfQX.clear();
921 IOfQY.clear();
922 integrateCumulative(QSQ->histogram(iW), qmin, qmin + qrange, IOfQX, IOfQY, returnCumulative);
923 // w bin width for elastic will equal 1
924 double wBinWidth = wWidths[iW];
925 std::transform(IOfQY.begin(), IOfQY.end(), IOfQY.begin(),
926 [IOfQMaxPreviousRow, wBinWidth](double d) -> double { return d * wBinWidth + IOfQMaxPreviousRow; });
927 IOfQMaxPreviousRow = IOfQY.back();
928 IOfQYFull.insert(IOfQYFull.end(), IOfQY.begin(), IOfQY.end());
929 qValuesFull.insert(qValuesFull.end(), IOfQX.begin(), IOfQX.end());
930 wIndices.insert(wIndices.end(), IOfQX.size(), static_cast<double>(iW));
931 }
933 return {IOfQYFull, qValuesFull, wIndices};
934}
935
944 double kinc, const ComponentWorkspaceMappings &materialWorkspaces) {
945 for (size_t iMat = 0; iMat < materialWorkspaces.size(); iMat++) {
946 auto QSQ = materialWorkspaces[iMat].QSQ;
947 auto [IOfQYFull, qValuesFull, wIndices] = integrateQSQ(QSQ, kinc, true);
948 auto IOfQYAtQMax = IOfQYFull.empty() ? 0. : IOfQYFull.back();
949 if (IOfQYAtQMax == 0.)
950 throw std::runtime_error("Integral of Q * S(Q) is zero so can't generate probability distribution");
951 // normalise probability range to 0-1
952 std::vector<double> IOfQYNorm;
953 std::transform(IOfQYFull.begin(), IOfQYFull.end(), std::back_inserter(IOfQYNorm),
954 [IOfQYAtQMax](double d) -> double { return d / IOfQYAtQMax; });
955 // Store the normalized integral (= cumulative probability) on the x axis
956 // The y values in the two spectra store Q, w (or w index to be precise)
957 auto &InvPOfQ = materialWorkspaces[iMat].InvPOfQ;
958 for (size_t i = 0; i < InvPOfQ->getNumberHistograms(); i++) {
959 InvPOfQ->histogram(i).X.resize(IOfQYNorm.size());
960 InvPOfQ->histogram(i).X = IOfQYNorm;
961 }
962 InvPOfQ->histogram(0).Y.resize(qValuesFull.size());
963 InvPOfQ->histogram(0).Y = qValuesFull;
964 InvPOfQ->histogram(1).Y.resize(wIndices.size());
965 InvPOfQ->histogram(1).Y = wIndices;
966 }
967}
968
969void DiscusMultipleScatteringCorrection::convertToLogWorkspace(const std::shared_ptr<DiscusData2D> &SOfQ) {
970 // generate log of the structure factor to support gaussian interpolation
971
972 for (size_t i = 0; i < SOfQ->getNumberHistograms(); i++) {
973 auto &ySQ = SOfQ->histogram(i).Y;
974
975 std::transform(ySQ.begin(), ySQ.end(), ySQ.begin(), [](double d) -> double {
976 const double exp_that_gives_close_to_zero = -20.0;
977 if (d == 0.)
978 return exp_that_gives_close_to_zero;
979 else
980 return std::log(d);
981 });
982 }
983}
984
997 const std::vector<double> &specialKs) {
998 for (auto &SQWSMapping : matWSs) {
999 std::vector<double> finalkValues, QSQIntegrals;
1001 // Optimize performance by doing cumulative integral first at each q in S(Q) and then calculate integral for each
1002 // k by topping up those results
1003 double kMax = specialKs.back();
1004 std::vector<double> IOfQYFull, qValuesFull;
1005 std::tie(IOfQYFull, qValuesFull, std::ignore) = integrateQSQ(SQWSMapping.QSQ, kMax, true);
1006 for (auto k : specialKs) {
1007 auto qUpperLimit = 2 * k;
1008 auto iterPrevIntegral = std::upper_bound(qValuesFull.begin(), qValuesFull.end(), qUpperLimit) - 1;
1009 auto idxPrevIntegral = static_cast<size_t>(std::distance(qValuesFull.begin(), iterPrevIntegral));
1010 std::vector<double> ignoreVector, topUpIntegral;
1011 integrateCumulative(SQWSMapping.QSQ->histogram(0), *iterPrevIntegral, qUpperLimit, ignoreVector, topUpIntegral,
1012 false);
1013 double IOfQY = IOfQYFull[idxPrevIntegral] + topUpIntegral[0];
1014 if (IOfQY > 0) {
1015 double normalisedIntegral = IOfQY / (2 * k * k);
1016 finalkValues.push_back(k);
1017 QSQIntegrals.push_back(normalisedIntegral);
1018 }
1019 }
1020 } else {
1021 // Calculate the integral for a range of k values. Not massively important which k values but choose them here
1022 // based on the q points in the S(Q) profile and the initial k values incident on the sample
1023 std::set<double> kValues(specialKs.begin(), specialKs.end());
1024 const std::vector<double> &qValues = SQWSMapping.SQ->histogram(0).X;
1025 for (auto q : qValues) {
1026 if (q > 0)
1027 kValues.insert(q / 2);
1028 }
1029
1030 // add a few extra points beyond supplied q range to ensure capture asymptotic value of integral/2*k*k.
1031 // Useful when doing a flat interpolation on m_QSQIntegral during inelastic calculation where k not known up front
1032 double maxSuppliedQ = qValues.back();
1033 if (maxSuppliedQ > 0.) {
1034 kValues.insert(maxSuppliedQ);
1035 kValues.insert(2 * maxSuppliedQ);
1036 }
1037
1038 for (auto k : kValues) {
1039 std::vector<double> IOfQYFull;
1040 std::tie(IOfQYFull, std::ignore, std::ignore) = integrateQSQ(SQWSMapping.QSQ, k, false);
1041 auto IOfQYAtQMax = IOfQYFull.empty() ? 0. : IOfQYFull.back();
1042 // going to divide by this so storing zero results not useful - and don't want to interpolate a zero value
1043 // into a k region where the integral is actually non-zero
1044 if (IOfQYAtQMax > 0) {
1045 double normalisedIntegral = IOfQYAtQMax / (2 * k * k);
1046 finalkValues.push_back(k);
1047 QSQIntegrals.push_back(normalisedIntegral);
1048 }
1049 }
1050 }
1051 auto QSQScaleFactor = std::make_shared<DiscusData1D>(finalkValues, QSQIntegrals);
1052 SQWSMapping.QSQScaleFactor = QSQScaleFactor;
1053 }
1054}
1055
1071 const double xmax, std::vector<double> &resultX,
1072 std::vector<double> &resultY,
1073 const bool returnCumulative) {
1074 assert(h.X.size() == h.Y.size());
1075 const std::vector<double> &xValues = h.X;
1076 const std::vector<double> &yValues = h.Y;
1077
1078 // set the integral to zero at xmin
1079 if (returnCumulative) {
1080 resultX.emplace_back(xmin);
1081 resultY.emplace_back(0.);
1082 }
1083 double sum = 0;
1084
1085 // ensure there's a point at xmin
1086 if (xValues.front() > xmin)
1087 throw std::runtime_error("Distribution doesn't extend as far as lower integration limit, x=" +
1088 std::to_string(xmin));
1089 // ...and a terminating point. Q.S(Q) generally not flat so assuming flat extrapolation not v useful
1090 if (xValues.back() < xmax)
1091 throw std::runtime_error("Distribution doesn't extend as far as upper integration limit, x=" +
1092 std::to_string(xmax));
1093
1094 auto iter = std::upper_bound(xValues.cbegin(), xValues.cend(), xmin);
1095 auto iRight = static_cast<size_t>(std::distance(xValues.cbegin(), iter));
1096
1097 auto linearInterp = [&xValues, &yValues](const double x, const size_t lIndex, const size_t rIndex) -> double {
1098 return (yValues[lIndex] * (xValues[rIndex] - x) + yValues[rIndex] * (x - xValues[lIndex])) /
1099 (xValues[rIndex] - xValues[lIndex]);
1100 };
1101 double yToUse;
1102
1103 // deal with partial initial segments
1104 if (xmin > xValues[iRight - 1]) {
1105 if (xmax >= xValues[iRight]) {
1106 double interpY = linearInterp(xmin, iRight - 1, iRight);
1107 yToUse = 0.5 * (interpY + yValues[iRight]);
1108 sum += yToUse * (xValues[iRight] - xmin);
1109 if (returnCumulative) {
1110 resultX.push_back(xValues[iRight]);
1111 resultY.push_back(sum);
1112 }
1113 iRight++;
1114 } else {
1115 double interpY1 = linearInterp(xmin, iRight - 1, iRight);
1116 double interpY2 = linearInterp(xmax, iRight - 1, iRight);
1117 yToUse = 0.5 * (interpY1 + interpY2);
1118 sum += yToUse * (xmax - xmin);
1119 if (returnCumulative) {
1120 resultX.push_back(xmax);
1121 resultY.push_back(sum);
1122 }
1123 iRight++;
1124 }
1125 }
1126
1127 // integrate the intervals between each pair of points. Do this until right point is at end of vector or > xmax
1128 for (; iRight < xValues.size() && xValues[iRight] <= xmax; iRight++) {
1129 yToUse = 0.5 * (yValues[iRight - 1] + yValues[iRight]);
1130 double xLeft = xValues[iRight - 1];
1131 double xRight = xValues[iRight];
1132 sum += yToUse * (xRight - xLeft);
1133 if (returnCumulative) {
1134 if (xRight > std::nextafter(xLeft, DBL_MAX)) {
1135 resultX.emplace_back(xRight);
1136 resultY.emplace_back(sum);
1137 }
1138 }
1139 }
1140
1141 // integrate a partial final interval if xmax is between points
1142 if ((xmax > xValues[iRight - 1]) && (xmin <= xValues[iRight - 1])) {
1143 double interpY = linearInterp(xmax, iRight - 1, iRight);
1144 yToUse = 0.5 * (yValues[iRight - 1] + interpY);
1145 sum += yToUse * (xmax - xValues[iRight - 1]);
1146 if (returnCumulative) {
1147 resultX.emplace_back(xmax);
1148 resultY.emplace_back(sum);
1149 }
1150 }
1151 if (!returnCumulative) {
1152 resultX.emplace_back(xmax);
1153 resultY.emplace_back(sum);
1154 }
1155}
1156
1163 // don't call integrateCumulative function because want error calculation and support for bin edges
1164 auto integrateAlgorithm = this->createChildAlgorithm("Integration");
1165 integrateAlgorithm->initialize();
1166 integrateAlgorithm->setProperty("InputWorkspace", ws);
1167 integrateAlgorithm->setProperty("OutputWorkspace", "_");
1168 integrateAlgorithm->execute();
1169 MatrixWorkspace_sptr wsIntegrals = integrateAlgorithm->getProperty("OutputWorkspace");
1170 for (size_t i = 0; i < wsIntegrals->getNumberHistograms(); i++)
1171 wsIntegrals->setPoints(i, std::vector<double>{0.});
1172 return wsIntegrals;
1173}
1174
1185std::tuple<double, double> DiscusMultipleScatteringCorrection::new_vector(const Material &material, double k,
1186 bool specialSingleScatterCalc) {
1187 double scatteringXSection, absorbXsection;
1188 if (specialSingleScatterCalc) {
1189 absorbXsection = 0;
1190 } else {
1191 const double wavelength = 2 * M_PI / k;
1192 absorbXsection = material.absorbXSection(wavelength);
1193 }
1194 if (m_sigmaSS) {
1195 scatteringXSection = interpolateFlat(*m_sigmaSS, k);
1196 } else {
1197 scatteringXSection = material.totalScatterXSection();
1198 }
1199
1200 const auto sig_total = scatteringXSection + absorbXsection;
1201 return {sig_total, scatteringXSection};
1202}
1203
1211std::tuple<double, int>
1212DiscusMultipleScatteringCorrection::sampleQW(const std::shared_ptr<DiscusData2D> &CumulativeProb, double x) {
1213 return {interpolateSquareRoot(CumulativeProb->histogram(0), x),
1214 static_cast<int>(interpolateFlat(CumulativeProb->histogram(1), x))};
1215}
1216
1223 const auto &histx = histToInterpolate.X;
1224 const auto &histy = histToInterpolate.Y;
1225 assert(histToInterpolate.X.size() == histToInterpolate.Y.size());
1226 if (x > histx.back()) {
1227 return histy.back();
1228 }
1229 if (x < histx.front()) {
1230 return histy.front();
1231 }
1232 const auto iter = std::upper_bound(histx.cbegin(), histx.cend(), x);
1233 const auto idx = static_cast<size_t>(std::distance(histx.cbegin(), iter) - 1);
1234 const double x0 = histx[idx];
1235 const double x1 = histx[idx + 1];
1236 const double asq = (pow(histy[idx + 1], 2) - pow(histy[idx], 2)) / (x1 - x0);
1237 if (asq == 0.) {
1238 throw std::runtime_error("Cannot perform square root interpolation on supplied distribution");
1239 }
1240 const double b = x0 - pow(histy[idx], 2) / asq;
1241 return sqrt(asq * (x - b));
1242}
1243
1251 auto &xHisto = histToInterpolate.X;
1252 auto &yHisto = histToInterpolate.Y;
1253 if (x > xHisto.back()) {
1254 return yHisto.back();
1255 }
1256 if (x < xHisto.front()) {
1257 return yHisto.front();
1258 }
1259 // may be useful at some point to introduce a tolerance here in case x is just below a step change but seems to behave
1260 // OK for now
1261 auto iter = std::upper_bound(xHisto.cbegin(), xHisto.cend(), x);
1262 auto idx = static_cast<size_t>(std::distance(xHisto.cbegin(), iter) - 1);
1263 return yHisto[idx];
1264}
1265
1274 // could have written using points() method so it also worked on histogram data but found that the points
1275 // method was bottleneck on multithreaded code due to cow_ptr atomic_load
1276 assert(histToInterpolate.X.size() == histToInterpolate.Y.size());
1277 if (x > histToInterpolate.X.back()) {
1278 return exp(histToInterpolate.Y.back());
1279 }
1280 if (x < histToInterpolate.X.front()) {
1281 return exp(histToInterpolate.Y.front());
1282 }
1283 // assume log(cross section) is quadratic in k
1284 auto deltax = histToInterpolate.X[1] - histToInterpolate.X[0];
1285
1286 auto iter = std::upper_bound(histToInterpolate.X.cbegin(), histToInterpolate.X.cend(), x);
1287 auto idx = static_cast<size_t>(std::distance(histToInterpolate.X.cbegin(), iter) - 1);
1288
1289 // need at least two points to the right of the x value for the quadratic
1290 // interpolation to work
1291 auto ny = histToInterpolate.Y.size();
1292 if (ny < 3) {
1293 throw std::runtime_error("Need at least 3 y values to perform quadratic interpolation");
1294 }
1295 if (idx > ny - 3) {
1296 idx = ny - 3;
1297 }
1298 // this interpolation assumes the set of 3 bins\point have the same width
1299 // U=0 on point or bin edge to the left of where x lies
1300 const auto U = (x - histToInterpolate.X[idx]) / deltax;
1301 const auto &y = histToInterpolate.Y;
1302 const auto A = (y[idx] - 2 * y[idx + 1] + y[idx + 2]) / 2;
1303 const auto B = (-3 * y[idx] + 4 * y[idx + 1] - y[idx + 2]) / 2;
1304 const auto C = y[idx];
1305 return exp(A * U * U + B * U + C);
1306}
1307
1318 double w) {
1319 double SQ = 0.;
1320 int iW = -1;
1321 auto &wValues = SQWSMapping.SQ->getSpecAxisValues();
1322 if (wValues.size() == 1) {
1323 // don't use indexOfValue here because for single point it invents a bin width of +/-0.5
1324 if (w == (wValues)[0])
1325 iW = 0;
1326 } else
1327 try {
1328 // required w values will often equal the points in the S(Q,w) distribution so pick nearest value
1329 iW = static_cast<int>(Kernel::VectorHelper::indexOfValueFromCentersNoThrow(wValues, w));
1330 } catch (std::out_of_range &) {
1331 }
1332 if (iW >= 0) {
1334 // the square root interpolation used to look up Q, w in InvPOfQ is based on flat interpolation of S(Q) so use
1335 // same interpolation here for consistency
1336 SQ = interpolateFlat(SQWSMapping.SQ->histogram(iW), q);
1337 else
1338 SQ = interpolateGaussian(SQWSMapping.logSQ->histogram(iW), q);
1339 }
1340
1341 return SQ;
1342}
1343
1344GNU_DIAG_OFF("free-nonheap-object")
1345
1346
1364std::tuple<std::vector<double>, std::vector<double>> DiscusMultipleScatteringCorrection::simulatePaths(
1365 const int nPaths, const int nScatters, Kernel::PseudoRandomNumberGenerator &rng,
1366 const ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector<double> &wValues,
1367 bool specialSingleScatterCalc, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex) {
1368 // countZeroWeights for debugging and analysis of where importance sampling may help
1369 std::vector<int> countZeroWeights(wValues.size(), 0);
1370 std::vector<double> sumOfWeights(wValues.size(), 0.);
1371 std::vector<double> weightsMeans(wValues.size(), 0.), deltas(wValues.size(), 0.), weightsM2(wValues.size(), 0.),
1372 weightsErrors(wValues.size(), 0.);
1373
1374 for (int ie = 0; ie < nPaths; ie++) {
1375 auto [success, weights] = scatter(nScatters, rng, componentWorkspaces, kinc, wValues, specialSingleScatterCalc,
1376 detectorInfo, histogramIndex);
1377 if (success) {
1378 std::transform(weights.begin(), weights.end(), sumOfWeights.begin(), sumOfWeights.begin(), std::plus<double>());
1379 std::transform(weights.begin(), weights.end(), countZeroWeights.begin(), countZeroWeights.begin(),
1380 [](double d, int count) { return d > 0. ? count : count + 1; });
1381
1382 // increment standard deviation using Welford algorithm
1383 for (size_t i = 0; i < wValues.size(); i++) {
1384 deltas[i] = weights[i] - weightsMeans[i];
1385 weightsMeans[i] += deltas[i] / static_cast<double>(ie + 1);
1386 weightsM2[i] += deltas[i] * (weights[i] - weightsMeans[i]);
1387 // calculate sample SD (M2/n-1)
1388 // will give NaN for m_events=1, but that's correct
1389 weightsErrors[i] = sqrt(weightsM2[i] / static_cast<double>(ie));
1390 }
1391
1392 } else
1393 ie--;
1394 }
1395 for (size_t i = 0; i < wValues.size(); i++) {
1396 sumOfWeights[i] = sumOfWeights[i] / nPaths;
1397 weightsErrors[i] = weightsErrors[i] / sqrt(nPaths);
1398 }
1399
1400 return {sumOfWeights, weightsErrors};
1401}
1402
1403GNU_DIAG_ON("free-nonheap-object")
1404
1405
1423std::tuple<bool, std::vector<double>> DiscusMultipleScatteringCorrection::scatter(
1424 const int nScatters, Kernel::PseudoRandomNumberGenerator &rng,
1425 const ComponentWorkspaceMappings &componentWorkspaces, const double kinc, const std::vector<double> &wValues,
1426 bool specialSingleScatterCalc, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex) {
1427
1428 double weight = 1;
1429
1430 auto track = start_point(rng);
1431 auto shapeObjectWithScatter =
1432 updateWeightAndPosition(track, weight, kinc, rng, specialSingleScatterCalc, componentWorkspaces);
1433 double scatteringXSection;
1434 std::tie(std::ignore, scatteringXSection) =
1435 new_vector(shapeObjectWithScatter->material(), kinc, specialSingleScatterCalc);
1436
1437 auto currentComponentWorkspaces = componentWorkspaces;
1438 double k = kinc;
1439 for (int iScat = 0; iScat < nScatters - 1; iScat++) {
1440 if ((k != kinc)) {
1441 if (m_importanceSampling) {
1442 auto newComponentWorkspaces = componentWorkspaces;
1443 for (auto &SQWSMapping : currentComponentWorkspaces)
1444 SQWSMapping.InvPOfQ = SQWSMapping.InvPOfQ->createCopy();
1445 prepareCumulativeProbForQ(k, newComponentWorkspaces);
1446 currentComponentWorkspaces = std::move(newComponentWorkspaces);
1447 }
1448 }
1449 auto trackStillAlive =
1450 q_dir(track, shapeObjectWithScatter, currentComponentWorkspaces, k, scatteringXSection, rng, weight);
1451 if (!trackStillAlive)
1452 return {true, std::vector<double>(wValues.size(), 0.)};
1453 int nlinks = m_sampleShape->interceptSurface(track);
1454 if (m_env) {
1455 nlinks += m_env->interceptSurfaces(track);
1456 m_callsToInterceptSurface += m_env->nelements();
1457 }
1458 m_callsToInterceptSurface++;
1459 if (nlinks == 0) {
1460 return {false, {0.}};
1461 }
1462 shapeObjectWithScatter =
1463 updateWeightAndPosition(track, weight, k, rng, specialSingleScatterCalc, componentWorkspaces);
1464 std::tie(std::ignore, scatteringXSection) =
1465 new_vector(shapeObjectWithScatter->material(), k, specialSingleScatterCalc);
1466 }
1467
1468 bool considerCollimator = getProperty("RadialCollimator");
1469 if (considerCollimator) {
1470 const auto &samplePos = detectorInfo.samplePosition();
1471 auto hexahedron = createCollimatorHexahedronShape(samplePos, detectorInfo, histogramIndex);
1472 // zero the paths if the final scatter point is not inside the collimatorCorridor shape or the collimator shape is
1473 // not as expected
1474 if ((!hexahedron) || (!hexahedron->isValid(track.startPoint())))
1475 return {true, std::vector<double>(wValues.size(), 0.)};
1476 }
1477
1478 const auto &detPos = detectorInfo.position(histogramIndex);
1479 Kernel::V3D directionToDetector = detPos - track.startPoint();
1480 Kernel::V3D prevDirection = track.direction();
1481 directionToDetector.normalize();
1482 track.reset(track.startPoint(), directionToDetector);
1483 int nlinks = m_sampleShape->interceptSurface(track);
1484 m_callsToInterceptSurface++;
1485 if (m_env) {
1486 nlinks += m_env->interceptSurfaces(track);
1487 m_callsToInterceptSurface += m_env->nelements();
1488 }
1489 // due to VALID_INTERCEPT_POINT_SHIFT some tracks that skim the surface
1490 // of a CSGObject sample may not generate valid tracks. Start over again
1491 // for this event
1492 if (nlinks == 0) {
1493 return {false, {0.}};
1494 }
1495 std::vector<double> weights;
1496 auto scatteringXSectionFull = shapeObjectWithScatter->material().totalScatterXSection();
1497 // Step through required overall energy transfer (w) values and work out what
1498 // w that means for the final scatter. There will be a single w value for elastic
1499 // Slightly different approach to original DISCUS code. It stepped through the w values
1500 // in the supplied S(Q,w) distribution and applied each one to the final scatter. If
1501 // this resulted in an overall w that equalled one of the required w values it was output.
1502 // That approach implicitly assumed S(Q,w)=0 where not specified and that no interpolation
1503 // on w would be needed - this may be what's required but seems possible it might not always be
1504 for (auto &w : wValues) {
1505 const double finalE = fromWaveVector(kinc) - w;
1506 if (finalE > 0) {
1507 const double kout = toWaveVector(finalE);
1508 const auto qVector = directionToDetector * kout - prevDirection * k;
1509 const double q = qVector.norm();
1510 const double finalW = fromWaveVector(k) - finalE;
1511 auto componentWSIt = findMatchingComponent(componentWorkspaces, shapeObjectWithScatter);
1512 auto &componentWSMapping = *componentWSIt; // to help debugging
1513 double SQ = Interpolate2D(componentWSMapping, q, finalW);
1514 scatteringXSection = m_NormalizeSQ ? scatteringXSection / interpolateFlat(*(componentWSMapping.QSQScaleFactor), k)
1515 : scatteringXSectionFull;
1516
1517 double AT2 = 1;
1518 for (auto it = track.cbegin(); it != track.cend(); it++) {
1519 double sigma_total;
1520 auto &materialPassingThrough = it->object->material();
1521 std::tie(sigma_total, std::ignore) = new_vector(materialPassingThrough, kout, specialSingleScatterCalc);
1522 double numberDensity = materialPassingThrough.numberDensityEffective();
1523 double vmu = 100 * numberDensity * sigma_total;
1524 if (specialSingleScatterCalc)
1525 vmu = 0;
1526 const double dl = it->distInsideObject;
1527 AT2 *= exp(-dl * vmu);
1528 }
1529 weights.emplace_back(weight * AT2 * SQ * scatteringXSection / (4 * M_PI));
1530 } else {
1531 weights.emplace_back(0.);
1532 }
1533 }
1534 return {true, weights};
1535}
1536
1537/*
1538 * Construct a hexahedron shape extending from the detector's front face across the collimator openning window toward
1539 the sample with a legth as twice the distance from sample to detector.
1540 */
1542 const Kernel::V3D &samplePos, const Mantid::Geometry::DetectorInfo &detectorInfo, const size_t &histogramIndex) {
1543 const auto shape = detectorInfo.detector(histogramIndex).shape();
1544 if (!shape || (shape->shape() != Mantid::Geometry::detail::ShapeInfo::GeometryShape::CUBOID)) {
1545 return nullptr;
1546 }
1547
1548 try {
1549 shape->shapeInfo();
1550 } catch (std::exception &) {
1551 return nullptr;
1552 }
1553
1554 const auto colCorridorShape = readFromCollimatorCorridorCache(histogramIndex);
1555 if (colCorridorShape) {
1556 return colCorridorShape;
1557 }
1558
1559 const auto &detectorId = detectorInfo.detector(histogramIndex).getID();
1560 const auto &detectorAbsolPos = detectorInfo.position(detectorInfo.indexOf(detectorId));
1561 const auto cuboidGeometry = shape->shapeInfo().cuboidGeometry();
1562
1563 // Positions of the detector's front face
1564 const auto &detLeftFrontBottomPos = detectorAbsolPos + cuboidGeometry.leftFrontBottom;
1565 const auto &detLeftFrontTopPos = detectorAbsolPos + cuboidGeometry.leftFrontTop;
1566 const auto &detRightFrontBottomPos = detectorAbsolPos + cuboidGeometry.rightFrontBottom;
1567 const auto detRightFrontTopPos = detLeftFrontTopPos + detRightFrontBottomPos - detLeftFrontBottomPos;
1568
1569 const auto detCentrePos =
1570 (detLeftFrontBottomPos + detLeftFrontTopPos + detRightFrontBottomPos + detRightFrontTopPos) / 4.0;
1571 const auto detTopMiddlePos = (detLeftFrontTopPos + detRightFrontTopPos) / 2.0;
1572
1573 // Sanity check to avoid dividing by zero
1574 if ((detRightFrontTopPos == detLeftFrontTopPos) || (detTopMiddlePos == detCentrePos) || (detCentrePos == samplePos)) {
1575 return nullptr;
1576 }
1577
1578 // Define the unit vectors needed for calculations
1579 const auto unitVecSampleToDet = Kernel::normalize(detCentrePos - samplePos);
1580 const auto unitVecLeftToRight = Kernel::normalize(
1581 V3D(-1.0 * unitVecSampleToDet.Z(), 0,
1582 -1.0 * unitVecSampleToDet.X())); // this is a vector normal to unitVecSampleToDet on XZ plane
1583
1584 const auto colOpenningLeftTopPos =
1585 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos +
1586 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) -
1587 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1588 const auto colOpenningRightTopPos =
1589 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos +
1590 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) +
1591 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1592 const auto colOpenningLeftBottomPos =
1593 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos -
1594 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) -
1595 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1596 const auto colOpenningRightBottomPos =
1597 (unitVecSampleToDet * m_collimatorInfo->m_innerRadius * cos(m_collimatorInfo->m_halfAngularExtent)) + samplePos -
1598 (m_collimatorInfo->m_axisVec * (m_collimatorInfo->m_plateHeight / 2.0)) +
1599 (unitVecLeftToRight * m_collimatorInfo->m_innerRadius * sin(m_collimatorInfo->m_halfAngularExtent));
1600
1601 // Sanity check to avoid dividing by zero
1602 if ((colOpenningLeftTopPos == detLeftFrontTopPos) || (colOpenningLeftBottomPos == detLeftFrontBottomPos) ||
1603 (colOpenningRightTopPos == detRightFrontTopPos) || (colOpenningRightBottomPos == detRightFrontBottomPos)) {
1604 return nullptr;
1605 }
1606
1607 // Unit vectors along the sides of hexahedron shape
1608 const auto unitVecAlongLeftTopLeg = Kernel::normalize(colOpenningLeftTopPos - detLeftFrontTopPos);
1609 const auto unitVecAlongLeftBottomLeg = Kernel::normalize(colOpenningLeftBottomPos - detLeftFrontBottomPos);
1610 const auto unitVecAlongRightTopLeg = Kernel::normalize(colOpenningRightTopPos - detRightFrontTopPos);
1611 const auto unitVecAlongRightBottomLeg = Kernel::normalize(colOpenningRightBottomPos - detRightFrontBottomPos);
1612
1613 // Positions of the hexahedron extended towards the sample for each of its legs to have a length twice the lenght of
1614 // sample to detector
1615 double sampleCentreToDetDistance = (detCentrePos - samplePos).norm();
1616 const auto leftFrontBottomPoint = detRightFrontTopPos + unitVecAlongRightTopLeg * sampleCentreToDetDistance * 2.0;
1617 const auto hexaHedronLegsLenRatio =
1618 (leftFrontBottomPoint - detRightFrontTopPos).norm() / (colOpenningRightTopPos - detRightFrontTopPos).norm();
1619 const auto leftBackBottomPoint = detLeftFrontTopPos + unitVecAlongLeftTopLeg * hexaHedronLegsLenRatio *
1620 (colOpenningLeftTopPos - detLeftFrontTopPos).norm();
1621 const auto rightFrontBottomPoint =
1622 detRightFrontBottomPos +
1623 unitVecAlongRightBottomLeg * hexaHedronLegsLenRatio * (colOpenningRightBottomPos - detRightFrontBottomPos).norm();
1624 const auto rightBackBottomPoint =
1625 detLeftFrontBottomPos +
1626 unitVecAlongLeftBottomLeg * hexaHedronLegsLenRatio * (colOpenningLeftBottomPos - detLeftFrontBottomPos).norm();
1627 std::ostringstream xmlShapeStream;
1628 xmlShapeStream << "<hexahedron id=\"corridor-shape\" >"
1629 << "<left-back-bottom-point x=\"" << leftBackBottomPoint.X() << "\""
1630 << " y=\"" << leftBackBottomPoint.Y() << "\""
1631 << " z=\"" << leftBackBottomPoint.Z() << "\" />"
1632 << "<left-front-bottom-point x=\"" << leftFrontBottomPoint.X() << "\""
1633 << " y=\"" << leftFrontBottomPoint.Y() << "\""
1634 << " z=\"" << leftFrontBottomPoint.Z() << "\" />"
1635 << "<right-front-bottom-point x=\"" << rightFrontBottomPoint.X() << "\""
1636 << " y=\"" << rightFrontBottomPoint.Y() << "\""
1637 << " z=\"" << rightFrontBottomPoint.Z() << "\" />"
1638 << "<right-back-bottom-point x=\"" << rightBackBottomPoint.X() << "\""
1639 << " y=\"" << rightBackBottomPoint.Y() << "\""
1640 << " z=\"" << rightBackBottomPoint.Z() << "\" />"
1641 << "<left-back-top-point x=\"" << detLeftFrontTopPos.X() << "\""
1642 << " y=\"" << detLeftFrontTopPos.Y() << "\""
1643 << " z=\"" << detLeftFrontTopPos.Z() << "\" />"
1644 << "<left-front-top-point x=\"" << detRightFrontTopPos.X() << "\""
1645 << " y=\"" << detRightFrontTopPos.Y() << "\""
1646 << " z=\"" << detRightFrontTopPos.Z() << "\" />"
1647 << "<right-front-top-point x=\"" << detRightFrontBottomPos.X() << "\""
1648 << " y=\"" << detRightFrontBottomPos.Y() << "\""
1649 << " z=\"" << detRightFrontBottomPos.Z() << "\" />"
1650 << "<right-back-top-point x=\"" << detLeftFrontBottomPos.X() << "\""
1651 << " y=\"" << detLeftFrontBottomPos.Y() << "\""
1652 << " z=\"" << detLeftFrontBottomPos.Z() << "\" />"
1653 << "</hexahedron>";
1654 Geometry::ShapeFactory shapeMaker;
1655 const auto collimatorCorridorCsgObj = shapeMaker.createShape(xmlShapeStream.str());
1656 writeToCollimatorCorridorCache(histogramIndex, collimatorCorridorCsgObj);
1657 return collimatorCorridorCsgObj;
1658}
1659
1660const std::shared_ptr<Geometry::CSGObject>
1662 std::shared_lock<std::shared_mutex> guard(m_mutexCorridorCache);
1663 const auto itCollimatorCorridor = m_collimatorCorridorCache.find(histogramIndex);
1664 if (itCollimatorCorridor != m_collimatorCorridorCache.end()) {
1665 return itCollimatorCorridor->second;
1666 }
1667 return nullptr;
1668}
1669
1671 const std::size_t &histogramIndex, const std::shared_ptr<Geometry::CSGObject> &collimatorCorridorCsgObj) {
1672 std::unique_lock<std::shared_mutex> guard(m_mutexCorridorCache);
1673 m_collimatorCorridorCache[histogramIndex] = collimatorCorridorCsgObj;
1674}
1675
1677 m_collimatorCorridorCache.clear(); // Clear the cache for collimator corridor shapes
1678 const bool radialCollimator = getProperty("RadialCollimator");
1679 if (radialCollimator) {
1680 m_collimatorInfo = std::make_unique<CollimatorInfo>();
1681 // Collimator inner radius
1682 m_collimatorInfo->m_innerRadius = getDoubleParamFromIDF("col-radius");
1683 // Half of the angular extent of the collimator seen from the sample
1684 m_collimatorInfo->m_halfAngularExtent = 0.5 * getDoubleParamFromIDF("col-angular-extent");
1685 // Height of collimator plate
1686 m_collimatorInfo->m_plateHeight = getDoubleParamFromIDF("col-plate-height");
1687 m_collimatorInfo->m_axisVec = getV3DParamFromIDF("col-axis");
1688 }
1689}
1690
1692 if (!m_instrument->hasParameter(paramName)) {
1693 throw std::runtime_error("Cannot find parameter:" + paramName + " from instrument parameter file");
1694 }
1695 std::vector<double> val_vec = m_instrument->getNumberParameter(paramName, true);
1696 if (val_vec.empty()) {
1697 throw std::runtime_error("No value specified for:" + paramName + " in the instrument parameter file");
1698 }
1699
1700 return static_cast<double>(val_vec.front());
1701}
1702
1704 if (!m_instrument->hasParameter(paramName)) {
1705 throw std::runtime_error("Cannot find parameter:" + paramName + " from instrument parameter file");
1706 }
1707
1708 std::string paramValStr = m_instrument->getStringParameter(paramName)[0];
1709 std::vector<std::string> v3dStrComponent;
1710 boost::split(v3dStrComponent, paramValStr, boost::is_any_of(","));
1711 if (v3dStrComponent.size() != 3) {
1712 throw std::runtime_error("Invalid number of coordinates given for parameter:" + paramName +
1713 " in instrument parameter file");
1714 }
1715 std::vector<double> v3dComponents(3);
1716 std::transform(v3dStrComponent.begin(), v3dStrComponent.end(), v3dComponents.begin(),
1717 [](const std::string &str) -> double { return std::stod(str); });
1718
1719 return Kernel::V3D(v3dComponents[0], v3dComponents[1], v3dComponents[2]);
1720}
1721
1722double DiscusMultipleScatteringCorrection::getKf(const double deltaE, const double kinc) {
1723 double kf;
1724 if (deltaE == 0.) {
1725 kf = kinc; // avoid costly sqrt
1726 } else {
1727 // slightly concerned that rounding errors moving between k and E may mean we take the sqrt of
1728 // a negative number in here. deltaE was capped using a threshold calculated using fromWaveVector so
1729 // hopefully any rounding will affect fromWaveVector(kinc) in same direction
1730 kf = toWaveVector(fromWaveVector(kinc) - deltaE);
1731 assert(!std::isnan(kf));
1732 }
1733 return kf;
1734} // namespace Mantid::Algorithms
1735
1748std::tuple<double, double> DiscusMultipleScatteringCorrection::getKinematicRange(double kf, double ki) {
1749 const double qmin = abs(kf - ki);
1750 const double qrange = 2 * std::min(ki, kf);
1751 return {qmin, qrange};
1752}
1760std::tuple<double, double, int, double>
1762 Kernel::PseudoRandomNumberGenerator &rng, const double kinc) {
1763
1764 // in order to keep integration limits constant sample full range of w even if some not kinematically accessible
1765 // Note - Discus took different approach where it sampled q,w from kinematically accessible range only but it
1766 // only calculated for double scattering and easier to normalise in that case
1767 double wRange;
1768 /*
1769 // The rectangular integration region could be restricted further by limiting w range by calculating max possible w
1770 // TO DO: validate the results for this optimisation
1771 // the energy transfer must always be less than the positive value corresponding to energy going from ki to 0
1772 // Note - this is still the case for indirect because on a multiple scatter the kf isn't kfixed
1773 double wMax = fromWaveVector(kinc);
1774 // find largest w bin centre that is < wmax and then sample w up to the next bin edge
1775 auto it = std::lower_bound(wValues.begin(), wValues.end(), wMax);
1776 int iWMax = static_cast<int>(std::distance(wValues.begin(), it) - 1);*/
1777 int iW = 0;
1778 if (wValues.size() == 1) {
1779 iW = 0;
1780 wRange = 1;
1781 } else {
1782 std::vector<double> wBinEdges;
1783 wBinEdges.reserve(wValues.size() + 1);
1784 VectorHelper::convertToBinBoundary(wValues, wBinEdges);
1785 // w bins not necessarily equal so don't just sample w index
1786 wRange = /*std::min(wMax, wBinEdges[iWMax + 1])*/ wBinEdges.back() - wBinEdges.front();
1787 double w = wBinEdges.front() + rng.nextValue() * wRange;
1788 iW = static_cast<int>(Kernel::VectorHelper::indexOfValueFromCentersNoThrow(wValues, w));
1789 }
1790 double maxkf = toWaveVector(fromWaveVector(kinc) - wValues.front());
1791 double qRange = kinc + maxkf;
1792 double q = qRange * rng.nextValue();
1793 return {q, qRange, iW, wRange};
1794}
1795
1805 // the QSQIntegrals were divided by k^2 so in theory they should be ~flat
1806 return interpolateFlat(QSQScaleFactor, k) * 2 * k * k;
1807}
1808
1821 const ComponentWorkspaceMappings &componentWorkspaces, double &k,
1822 const double scatteringXSection,
1823 Kernel::PseudoRandomNumberGenerator &rng, double &weight) {
1824 const double kinc = k;
1825 double QQ;
1826 int iW;
1827 auto componentWSIt = findMatchingComponent(componentWorkspaces, shapePtr);
1829 std::tie(QQ, iW) = sampleQW(componentWSIt->InvPOfQ, rng.nextValue());
1830 k = getKf(componentWSIt->SQ->getSpecAxisValues()[iW], kinc);
1831 weight = weight * scatteringXSection;
1832 } else {
1833 double qrange, wRange;
1834 auto &wValues = componentWSIt->SQ->getSpecAxisValues();
1835 std::tie(QQ, qrange, iW, wRange) = sampleQWUniform(wValues, rng, kinc);
1836 // if w inaccessible return (ie treat as zero weight) rather than retry so that integration stays over full w
1837 // range
1838 if (fromWaveVector(kinc) - wValues[iW] <= 0)
1839 return false;
1840 k = getKf(wValues[iW], kinc);
1841 double SQ = interpolateGaussian(componentWSIt->logSQ->histogram(iW), QQ);
1842 // integrate over rectangular area of qw space
1843 weight = weight * scatteringXSection * SQ * QQ * qrange * wRange;
1844 if (SQ > 0) {
1845 double integralQSQ = getQSQIntegral(*componentWSIt->QSQScaleFactor, kinc);
1846 assert(integralQSQ != 0.);
1847 weight = weight / integralQSQ;
1848 } else
1849 return false;
1850 }
1851 // T = 2theta
1852 const double cosT = (kinc * kinc + k * k - QQ * QQ) / (2 * kinc * k);
1853 // if q not accessible return rather than retry so that integration stays over rectangular area
1854 if (std::abs(cosT) > 1.0)
1855 return false;
1856
1857 updateTrackDirection(track, cosT, rng.nextValue() * 2 * M_PI);
1858 return true;
1859}
1860
1868 const double phi) {
1869 const auto B3 = sqrt(1 - cosT * cosT);
1870 const auto B2 = cosT;
1871 // possible to do this using the Quat class instead??
1872 // Quat(const double _deg, const V3D &_axis);
1873 // Quat(acos(cosT)*180/M_PI,
1874 // Kernel::V3D(track.direction()[],track.direction()[],0))
1875
1876 // Rodrigues formula with final term equal to zero
1877 // v_rot = cosT * v + sinT(k x v)
1878 // with rotation axis k orthogonal to v
1879 // Define k by first creating two vectors orthogonal to v:
1880 // (vy, -vx, 0) by inspection
1881 // and then (-vz * vx, -vy * vz, vx * vx + vy * vy) as cross product
1882 // Then define k as combination of these:
1883 // sin(phi) * (vy, -vx, 0) + cos(phi) * (-vx * vz, -vy * vz, 1 - vz * vz)
1884 // ...with division by normalisation factor of sqrt(vx * vx + vy * vy)
1885 // Note: xyz convention here isn't the standard Mantid one. x=beam, z=up
1886 const auto vy = track.direction()[0];
1887 const auto vz = track.direction()[1];
1888 const auto vx = track.direction()[2];
1889 double UKX, UKY, UKZ;
1890 if (vz * vz < 1.0) {
1891 // calculate A2 from vx^2 + vy^2 rather than 1-vz^2 to reduce floating point rounding error when vz close to
1892 // 1
1893 auto A2 = sqrt(vx * vx + vy * vy);
1894 auto UQTZ = cos(phi) * A2;
1895 auto UQTX = -cos(phi) * vz * vx / A2 + sin(phi) * vy / A2;
1896 auto UQTY = -cos(phi) * vz * vy / A2 - sin(phi) * vx / A2;
1897 UKX = B2 * vx + B3 * UQTX;
1898 UKY = B2 * vy + B3 * UQTY;
1899 UKZ = B2 * vz + B3 * UQTZ;
1900 } else {
1901 // definition of phi in general formula is dependent on v. So may see phi "redefinition" as vx and vy tend
1902 // to zero and you move from general formula to this special case
1903 UKX = B3 * cos(phi);
1904 UKY = B3 * sin(phi);
1905 UKZ = B2 * vz;
1906 }
1907 track.reset(track.startPoint(), Kernel::V3D(UKY, UKZ, UKX));
1908}
1909
1918 for (int i = 0; i < m_maxScatterPtAttempts; i++) {
1919 auto t = generateInitialTrack(rng);
1920 int nlinks = m_sampleShape->interceptSurface(t);
1922 if (m_env) {
1923 nlinks += m_env->interceptSurfaces(t);
1925 }
1926 if (nlinks > 0) {
1927 if (i > 0) {
1928 if (g_log.is(Kernel::Logger::Priority::PRIO_WARNING)) {
1930 }
1931 }
1932 return t;
1933 }
1934 }
1935 throw std::runtime_error(
1936 "DiscusMultipleScatteringCorrection::start_point() - Unable to generate entry point into sample after " +
1937 std::to_string(m_maxScatterPtAttempts) + " attempts. Try increasing MaxScatterPtAttempts");
1938}
1939
1952 Geometry::Track &track, double &weight, const double k, Kernel::PseudoRandomNumberGenerator &rng,
1953 bool specialSingleScatterCalc, const ComponentWorkspaceMappings &componentWorkspaces) {
1954 double totalMuL = 0.;
1955 auto nlinks = track.count();
1956 // Set default size to 5 (same as in LineIntersectVisit.h)
1957 boost::container::small_vector<std::tuple<const Geometry::IObject *, double, double, double>, 5> geometryObjects;
1958 geometryObjects.reserve(nlinks);
1959 // loop through all the track segments calculating some useful quantities for later
1960 for (auto it = track.cbegin(); it != track.cend(); it++) {
1961 const double trackSegLength = it->distInsideObject;
1962 const auto geometryObj = it->object;
1963 double sigma_total;
1964 std::tie(sigma_total, std::ignore) = new_vector(geometryObj->material(), k, specialSingleScatterCalc);
1965 double vmu = 100 * geometryObj->material().numberDensityEffective() * sigma_total;
1966 double muL = trackSegLength * vmu;
1967 totalMuL += muL;
1968 // some overlap between the quantities stored here but since calculated them all may as well store them all
1969 geometryObjects.emplace_back(geometryObj, vmu, muL, sigma_total);
1970 }
1971
1972 // randomly sample distance travelled across a total muL and work out which component this sits in
1973 double b4Overall = (1.0 - exp(-totalMuL));
1974 double muL = -log(1 - rng.nextValue() * b4Overall);
1975 double vl = 0.;
1976 double newWeight = 0.;
1977 double prevExpTerms = 1.;
1978 std::tuple<const Geometry::IObject *, double, double, double> geometryObjectDetails;
1979 for (size_t i = 0; i < geometryObjects.size(); i++) {
1980 geometryObjectDetails = geometryObjects[i];
1981 auto muL_i = std::get<2>(geometryObjectDetails);
1982 auto vmu_i = std::get<1>(geometryObjectDetails);
1983 if (muL - muL_i > 0) {
1984 vl += muL_i / vmu_i;
1985 muL = muL - muL_i;
1986 prevExpTerms *= exp(-muL_i);
1987 } else {
1988 vl += muL / vmu_i;
1989 double b4 = (1.0 - exp(-muL_i)) * prevExpTerms;
1990 auto sigma_total = std::get<3>(geometryObjectDetails);
1991 newWeight = b4 / sigma_total;
1992 break;
1993 }
1994 }
1995 weight = weight * newWeight;
1996 // At the moment this doesn't cope if sample shape is concave eg if track has more than one segment inside the
1997 // sample with segment outside sample in between
1998 // Note - this clears the track intersections but the sample\environment shapes live on
1999 inc_xyz(track, vl);
2000 auto geometryObject = std::get<0>(geometryObjectDetails);
2001 if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG)) {
2002 auto componentIt = findMatchingComponent(componentWorkspaces, geometryObject);
2003 (*(componentIt->scatterCount))++;
2004 }
2005 return geometryObject;
2006}
2007
2015 // generate random point on front surface of sample bounding box
2016 // The change of variables from length to t1 means this still samples the points fairly in the integration
2017 // volume even in shapes like cylinders where the depth varies across xy
2018 auto neutron = m_beamProfile->generatePoint(rng, m_activeRegion);
2019 auto ptx = neutron.startPos.X();
2020 auto pty = neutron.startPos.Y();
2021
2022 auto ptOnBeamProfile = Kernel::V3D();
2023 ptOnBeamProfile[m_refframe->pointingHorizontal()] = ptx;
2024 ptOnBeamProfile[m_refframe->pointingUp()] = pty;
2025 ptOnBeamProfile[m_refframe->pointingAlongBeam()] = m_sourcePos[m_refframe->pointingAlongBeam()];
2026 auto toSample = Kernel::V3D();
2027 toSample[m_refframe->pointingAlongBeam()] = 1.;
2028 return Geometry::Track(ptOnBeamProfile, toSample);
2029}
2030
2039 Kernel::V3D position = track.front().entryPoint;
2040 Kernel::V3D direction = track.direction();
2041 const auto x = position[0] + vl * direction[0];
2042 const auto y = position[1] + vl * direction[1];
2043 const auto z = position[2] + vl * direction[2];
2044 const auto startPoint = V3D(x, y, z);
2046 track.reset(startPoint, track.direction());
2047}
2048
2058std::shared_ptr<SparseWorkspace>
2060 const size_t rows, const size_t columns) {
2061 auto sparseWS = std::make_shared<SparseWorkspace>(modelWS, nXPoints, rows, columns);
2062 return sparseWS;
2063}
2064
2066 for (auto &SQWSMapping : matWSs) {
2067 auto &QSQ = SQWSMapping.QSQ;
2068 size_t expectedMaxSize =
2069 std::accumulate(QSQ->histograms().cbegin(), QSQ->histograms().cend(), static_cast<size_t>(0),
2070 [](const size_t value, const DiscusData1D &histo) { return value + histo.Y.size(); });
2071 auto ws = std::make_shared<DiscusData2D>(std::vector<DiscusData1D>(nhists), nullptr);
2072 ws->histogram(0).X.reserve(expectedMaxSize);
2073 for (size_t i = 0; i < nhists; i++)
2074 ws->histogram(i).Y.reserve(expectedMaxSize);
2075 SQWSMapping.InvPOfQ = ws;
2076 }
2077}
2078
2080 MatrixWorkspace_uptr outputWS = DataObjects::create<Workspace2D>(inputWS);
2081 // The algorithm computes the signal values at bin centres so they should
2082 // be treated as a distribution
2083 outputWS->setDistribution(true);
2084 outputWS->setYUnit("");
2085 outputWS->setYUnitLabel("Scattered Weight");
2086 return outputWS;
2087}
2088
2095 auto interpolationOpt = std::make_unique<InterpolationOption>();
2096 return interpolationOpt;
2097}
2098
2100 MatrixWorkspace &targetWS, const SparseWorkspace &sparseWS,
2101 const Mantid::Algorithms::InterpolationOption &interpOpt) {
2102 const auto &spectrumInfo = targetWS.spectrumInfo();
2103 const auto refFrame = targetWS.getInstrument()->getReferenceFrame();
2104 PARALLEL_FOR_IF(Kernel::threadSafe(targetWS, sparseWS))
2105 for (int64_t i = 0; i < static_cast<decltype(i)>(spectrumInfo.size()); ++i) {
2107 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i)) {
2108 double lat, lon;
2109 std::tie(lat, lon) = spectrumInfo.geographicalAngles(i);
2110 const auto spatiallyInterpHisto = sparseWS.bilinearInterpolateFromDetectorGrid(lat, lon);
2111 if (spatiallyInterpHisto.size() > 1) {
2112 auto targetHisto = targetWS.histogram(i);
2113 interpOpt.applyInPlace(spatiallyInterpHisto, targetHisto);
2114 targetWS.setHistogram(i, targetHisto);
2115 } else {
2116 targetWS.mutableY(i) = spatiallyInterpHisto.y().front();
2117 }
2118 }
2120 }
2122}
2123
2131 bool noClash(false);
2132
2133 for (int i = 0; !noClash; ++i) {
2134 std::string wsIndex; // dont use an index if there is no other
2135 // workspace
2136 if (i > 0) {
2137 wsIndex = "_" + std::to_string(i);
2138 }
2139
2140 bool wsExists = AnalysisDataService::Instance().doesExist(wsName + wsIndex);
2141 if (!wsExists) {
2142 wsName += wsIndex;
2143 noClash = true;
2144 }
2145 }
2146}
2147
2156 API::AnalysisDataService::Instance().addOrReplace(wsName, ws);
2157}
2158
2167 const Geometry::IObject *shapeObjectWithScatter) {
2168 // Currently look up based on the raw pointer value. Did consider looking up based on something more human readable
2169 // such as the component id or name but this isn't guaranteed to be set and a string key may be longer than the
2170 // pointer which is probably 8 bytes
2171 auto componentWSIt = std::find_if(componentWorkspaces.begin(), componentWorkspaces.end(),
2172 [shapeObjectWithScatter](const ComponentWorkspaceMapping &SQWS) {
2173 return SQWS.ComponentPtr.get() == shapeObjectWithScatter;
2174 });
2175 assert(componentWSIt != componentWorkspaces.end());
2176 // can't return iterator because boost have moved vec_iterator into a different namespace post v1.65.1 so won't
2177 // build on all platforms
2178 return &(*componentWSIt);
2179}
2180
2182 m_sampleShape = inputWS->sample().getShapePtr();
2183 try {
2184 m_env = &inputWS->sample().getEnvironment();
2185 } catch (std::runtime_error &) {
2186 // swallow this as no defined environment from getEnvironment
2187 }
2188 // generate the bounding box before the multithreaded section
2189 m_activeRegion = m_sampleShape->getBoundingBox();
2190 if (m_env) {
2191 const auto &envBox = m_env->boundingBox();
2192 m_activeRegion.grow(envBox);
2193 }
2194 m_instrument = inputWS->getInstrument();
2196 m_refframe = m_instrument->getReferenceFrame();
2197 m_sourcePos = m_instrument->getSource()->getPos();
2198}
2199
2200} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:542
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:423
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:30
virtual const std::vector< double > & getValues() const
Return a const reference to the values.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
A property class for workspaces.
static std::unique_ptr< IBeamProfile > createBeamProfile(const Geometry::Instrument &instrument, const API::Sample &sample)
std::shared_ptr< std::vector< double > > m_specAxis
std::unique_ptr< DiscusData2D > createCopy(bool clearY=false)
Calculates a multiple scattering correction Based on Muscat Fortran code provided by Spencer Howells.
void setWorkspaceName(const API::MatrixWorkspace_sptr &ws, std::string wsName)
Set the name on a workspace, adjusting for potential clashes in the ADS.
void addWorkspaceToDiscus2DData(const Geometry::IObject_const_sptr &shape, const std::string_view &matName, API::MatrixWorkspace_sptr ws)
Function to convert between a Matrix workspace and the internal simplified 2D data structure.
void convertWsBothAxesToPoints(API::MatrixWorkspace_sptr &ws)
Convert x axis of a workspace to points if it's bin edges.
void convertToLogWorkspace(const std::shared_ptr< DiscusData2D > &SOfQ)
void createInvPOfQWorkspaces(ComponentWorkspaceMappings &matWSs, size_t nhists)
std::tuple< double, double > getKinematicRange(double kf, double ki)
Get the range of q values accessible for a particular kinc and kf.
std::tuple< double, double, int, double > sampleQWUniform(const std::vector< double > &wValues, Kernel::PseudoRandomNumberGenerator &rng, const double kinc)
Sample the q and w value for a scattering event without importance sampling.
std::vector< std::tuple< double, int, double > > generateInputKOutputWList(const double efixed, std::span< double const > xPoints)
Generate a list of the k and w points where calculation results are required.
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::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:165
std::shared_ptr< const IObject > IObject_const_sptr
Typdef for a shared pointer to a const object.
Definition IObject.h:95
int MANTID_KERNEL_DLL indexOfValueFromCentersNoThrow(std::span< double const > bin_centers, const double value)
Gets the bin of a value from a vector of bin centers and returns -1 if out of range.
void MANTID_KERNEL_DLL convertToBinBoundary(std::span< double const > bin_centers, std::vector< double > &bin_edges)
Convert an array of bin centers to bin boundary values.
void MANTID_KERNEL_DLL convertToBinCentre(std::span< double const > bin_edges, std::vector< double > &bin_centres)
Convert an array of bin boundaries to bin center values.
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.
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