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