Mantid
Loading...
Searching...
No Matches
CalculateTransmission.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 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 +
11#include "MantidAPI/IFunction.h"
21
22#include <algorithm>
23#include <cassert>
24#include <cmath>
25
26#include <boost/algorithm/string/join.hpp>
27#include <boost/lexical_cast.hpp>
28#include <utility>
29
30namespace Mantid::Algorithms {
31
32using namespace Kernel;
33using namespace API;
34
35namespace // anonymous
36{
37// For LOQ at least, the transmission monitor is 3. (The incident beam
38// monitor's UDET is 2.)
39const detid_t LOQ_TRANSMISSION_MONITOR_UDET = 3;
40
50size_t getIndexFromDetectorID(const MatrixWorkspace &ws, detid_t detid) {
51 const std::vector<detid_t> input = {detid};
52 std::vector<size_t> result = ws.getIndicesFromDetectorIDs(input);
53 if (result.empty())
54 throw std::invalid_argument("Could not find the spectra corresponding to detector ID " + std::to_string(detid));
55
56 return result[0];
57}
58} // namespace
59
60// Register the algorithm into the AlgorithmFactory
61DECLARE_ALGORITHM(CalculateTransmission)
62
64 auto wsValidator = std::make_shared<CompositeValidator>();
65 wsValidator->add<WorkspaceUnitValidator>("Wavelength");
66 wsValidator->add<CommonBinsValidator>();
67 wsValidator->add<HistogramValidator>();
68
69 declareProperty(std::make_unique<WorkspaceProperty<>>("SampleRunWorkspace", "", Direction::Input, wsValidator),
70 "The workspace containing the sample transmission run. Must "
71 "have common binning and be in units of wavelength.");
72 declareProperty(std::make_unique<WorkspaceProperty<>>("DirectRunWorkspace", "", Direction::Input, wsValidator),
73 "The workspace containing the direct beam (no sample) "
74 "transmission run. The units and binning must match those of "
75 "the SampleRunWorkspace.");
76 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
77 "The name of the workspace in which to store the fitted transmission "
78 "fractions.");
79
80 auto zeroOrMore = std::make_shared<BoundedValidator<int>>();
81 zeroOrMore->setLower(0);
82
83 declareProperty("IncidentBeamMonitor", EMPTY_INT(), zeroOrMore, "The UDET of the incident beam monitor");
84 declareProperty("TransmissionMonitor", EMPTY_INT(), zeroOrMore, "The UDET of the transmission monitor");
85
86 declareProperty(std::make_unique<ArrayProperty<double>>("RebinParams"),
87 "A comma separated list of first bin boundary, width, last "
88 "bin boundary. Optionally\n"
89 "this can be followed by a comma and more widths and last "
90 "boundary pairs.\n"
91 "Negative width values indicate logarithmic binning.");
92
93 std::vector<std::string> options(3);
94 options[0] = "Linear";
95 options[1] = "Log";
96 options[2] = "Polynomial";
97
98 declareProperty("FitMethod", "Log", std::make_shared<StringListValidator>(options),
99 "Whether to fit directly to the transmission curve using "
100 "Linear, Log or Polynomial.");
101 auto twoOrMore = std::make_shared<BoundedValidator<int>>();
102 twoOrMore->setLower(2);
103 declareProperty("PolynomialOrder", 2, twoOrMore,
104 "Order of the polynomial to "
105 "fit. It is considered only "
106 "for FitMethod=Polynomial");
107
108 declareProperty("OutputUnfittedData", false,
109 "If True, will output an additional workspace called "
110 "[OutputWorkspace]_unfitted containing the unfitted "
111 "transmission correction.");
112
113 declareProperty(std::make_unique<ArrayProperty<detid_t>>("TransmissionROI"),
114 "An optional ArrayProperty containing a list of detector "
115 "ID's. These specify a region of interest "
116 "which is to be summed and then used instead of a "
117 "transmission monitor. This allows for a \"beam stop "
118 "out\" method of transmission calculation.");
119}
120
122 m_done = 0.;
123 MatrixWorkspace_sptr sampleWS = getProperty("SampleRunWorkspace");
124 MatrixWorkspace_sptr directWS = getProperty("DirectRunWorkspace");
125
126 const detid_t beamMonitorID = getProperty("IncidentBeamMonitor");
127 detid_t transMonitorID = getProperty("TransmissionMonitor");
128 const std::vector<detid_t> transDetList = getProperty("TransmissionROI");
129
130 const bool usingSameInstrument = sampleWS->getInstrument()->getName() == directWS->getInstrument()->getName();
131 if (!usingSameInstrument)
132 throw std::invalid_argument("The input workspaces do not come from the same instrument.");
133 if (!WorkspaceHelpers::matchingBins(*sampleWS, *directWS))
134 throw std::invalid_argument("The input workspaces do not have matching bins.");
135
136 bool usingMonitor = !isEmpty(transMonitorID);
137 const bool usingROI = !transDetList.empty();
138 if (usingMonitor && usingROI)
139 throw std::invalid_argument("Unable to use both a monitor and a region of "
140 "interest in transmission calculation.");
141 if (!usingMonitor && !usingROI) {
142 transMonitorID = LOQ_TRANSMISSION_MONITOR_UDET;
143 usingMonitor = true;
144 }
145
146 // Populate transmissionIndices with the workspace indices to use for the
147 // transmission.
148 // In the case of TransmissionMonitor this will be a single index
149 // corresponding to a
150 // monitor, in the case of TransmissionROI it will be one or more indices
151 // corresponding
152 // to a region of interest on the detector bank(s).
153 std::vector<size_t> transmissionIndices;
154 if (usingMonitor) {
155 const size_t transmissionMonitorIndex = getIndexFromDetectorID(*sampleWS, transMonitorID);
156 transmissionIndices.emplace_back(transmissionMonitorIndex);
157 logIfNotMonitor(sampleWS, directWS, transmissionMonitorIndex);
158 } else if (usingROI) {
159 transmissionIndices = sampleWS->getIndicesFromDetectorIDs(transDetList);
160 } else
161 assert(false);
162
163 const std::string transPropName = usingMonitor ? "TransmissionMonitor" : "TransmissionROI";
164
165 if (transmissionIndices.empty())
166 throw std::invalid_argument("The UDET(s) passed to " + transPropName +
167 " do not correspond to spectra in the workspace.");
168
169 // Check if we're normalising to the incident beam monitor. If so, then it
170 // needs to be a monitor that is not also used for the transmission.
171 const bool normaliseToMonitor = !isEmpty(beamMonitorID);
172 size_t beamMonitorIndex = 0;
173 if (normaliseToMonitor) {
174 beamMonitorIndex = getIndexFromDetectorID(*sampleWS, beamMonitorID);
175 logIfNotMonitor(sampleWS, directWS, beamMonitorIndex);
176
177 const auto transmissionIndex = std::find(transmissionIndices.begin(), transmissionIndices.end(), beamMonitorIndex);
178 if (transmissionIndex != transmissionIndices.end())
179 throw std::invalid_argument("The IncidentBeamMonitor UDET (" + std::to_string(*transmissionIndex) +
180 ") matches a UDET given in " + transPropName + ".");
181 }
182
183 MatrixWorkspace_sptr sampleInc;
184 if (normaliseToMonitor)
185 sampleInc = this->extractSpectra(sampleWS, std::vector<size_t>(1, beamMonitorIndex));
186 MatrixWorkspace_sptr sampleTrans = this->extractSpectra(sampleWS, transmissionIndices);
187
188 MatrixWorkspace_sptr directInc;
189 if (normaliseToMonitor)
190 directInc = this->extractSpectra(directWS, std::vector<size_t>(1, beamMonitorIndex));
191 MatrixWorkspace_sptr directTrans = this->extractSpectra(directWS, transmissionIndices);
192
193 double start = m_done;
194 Progress progress(this, start, m_done += 0.2, 2);
195 progress.report("CalculateTransmission: Dividing transmission by incident");
196
197 // The main calculation
198 MatrixWorkspace_sptr transmission = sampleTrans / directTrans;
199 if (normaliseToMonitor)
200 transmission = transmission * (directInc / sampleInc);
201
202 // This workspace is now a distribution
203 progress.report("CalculateTransmission: Dividing transmission by incident");
204
205 // Output this data if requested
206 const bool outputRaw = getProperty("OutputUnfittedData");
207 if (outputRaw) {
208 auto childAlg = createChildAlgorithm("ReplaceSpecialValues");
209 childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", transmission);
210 childAlg->setProperty<double>("NaNValue", 0.0);
211 childAlg->setProperty<double>("NaNError", 0.0);
212 childAlg->setProperty<double>("InfinityValue", 0.0);
213 childAlg->setProperty<double>("InfinityError", 0.0);
214 childAlg->executeAsChildAlg();
215 transmission = childAlg->getProperty("OutputWorkspace");
216 std::string outputWSName = getPropertyValue("OutputWorkspace");
217 outputWSName += "_unfitted";
218 declareProperty(std::make_unique<WorkspaceProperty<>>("UnfittedData", outputWSName, Direction::Output));
219 setProperty("UnfittedData", transmission);
220 }
221
222 // Check that there are more than a single bin in the transmission
223 // workspace. Skip the fit if there isn't.
224 if (transmission->y(0).size() > 1) {
225 transmission = fit(transmission, getProperty("RebinParams"), getProperty("FitMethod"));
226 }
227 setProperty("OutputWorkspace", transmission);
228}
229
242 const std::vector<size_t> &indices) {
243 // Compile a comma separated list of indices that we can pass to SumSpectra.
244 std::vector<std::string> indexStrings(indices.size());
245 // A bug in boost 1.53: https://svn.boost.org/trac/boost/ticket/7421
246 // means that lexical_cast cannot be used directly as the call is ambiguous
247 // so we need to define a function pointer that can resolve the overloaded
248 // lexical_cast function
249 using from_size_t = std::string (*)(const size_t &);
250
251 std::transform(indices.begin(), indices.end(), indexStrings.begin(),
252 static_cast<from_size_t>(boost::lexical_cast<std::string, size_t>));
253 const std::string commaIndexList = boost::algorithm::join(indexStrings, ",");
254
255 double start = m_done;
256 auto childAlg = createChildAlgorithm("SumSpectra", start, m_done += 0.1);
257 childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", ws);
258 childAlg->setPropertyValue("ListOfWorkspaceIndices", commaIndexList);
259 childAlg->executeAsChildAlg();
260
261 // Only get to here if successful
262 return childAlg->getProperty("OutputWorkspace");
263}
264
276 const std::vector<double> &rebinParams,
277 const std::string &fitMethod) {
278 MatrixWorkspace_sptr output = this->extractSpectra(raw, std::vector<size_t>(1, 0));
279
280 Progress progress(this, m_done, 1.0, 4);
281 progress.report("CalculateTransmission: Performing fit");
282
283 // these are calculated by the call to fit below
284 double grad(0.0), offset(0.0);
285 std::vector<double> coeficients;
286 const bool logFit = (fitMethod == "Log");
287 if (logFit) {
288 g_log.debug("Fitting to the logarithm of the transmission");
289
290 auto &Y = output->mutableY(0);
291 auto &E = output->mutableE(0);
292 double start = m_done;
293 Progress prog2(this, start, m_done += 0.1, Y.size());
294 for (size_t i = 0; i < Y.size(); ++i) {
295 // Take the log of each datapoint for fitting. Recalculate errors
296 // remembering that d(log(a))/da = 1/a
297 E[i] = std::abs(E[i] / Y[i]);
298 Y[i] = std::log10(Y[i]);
299 progress.report("Fitting to the logarithm of the transmission");
300 }
301
302 // Now fit this to a straight line
303 output = fitData(output, grad, offset);
304 } // logFit true
305 else if (fitMethod == "Linear") { // Linear fit
306 g_log.debug("Fitting directly to the data (i.e. linearly)");
307 output = fitData(output, grad, offset);
308 } else { // fitMethod Polynomial
309 int order = getProperty("PolynomialOrder");
310 std::stringstream info;
311 info << "Fitting the transmission to polynomial order=" << order;
312 g_log.information(info.str());
313 output = fitPolynomial(output, order, coeficients);
314 }
315
316 progress.report("CalculateTransmission: Performing fit");
317
318 // if no rebin parameters were set the output workspace will have the same
319 // binning as the input ones, otherwise rebin
320 if (!rebinParams.empty()) {
321 output = rebin(rebinParams, output);
322 }
323 progress.report("CalculateTransmission: Performing fit");
324
325 // if there was rebinnning or log fitting we need to recalculate the Ys,
326 // otherwise we can just use the workspace kicked out by the fitData()'s call
327 // to Linear
328 if ((!rebinParams.empty()) || logFit) {
329 auto &X = output->x(0);
330 auto &Y = output->mutableY(0);
331 if (logFit) {
332 // Need to transform back to 'unlogged'
333 const double m(std::pow(10, grad));
334 const double factor(std::pow(10, offset));
335
336 auto &E = output->mutableE(0);
337 for (size_t i = 0; i < Y.size(); ++i) {
338 // the relationship between the grad and interspt of the log fit and the
339 // un-logged value of Y contain this dependence on the X (bin center
340 // values)
341 Y[i] = factor * (std::pow(m, 0.5 * (X[i] + X[i + 1])));
342 E[i] = std::abs(E[i] * Y[i]);
343 progress.report();
344 }
345 } // end logFit
346 else if (fitMethod == "Linear") {
347 // the simpler linear situation
348 for (size_t i = 0; i < Y.size(); ++i) {
349 Y[i] = (grad * 0.5 * (X[i] + X[i + 1])) + offset;
350 }
351 } else { // the polynomial fit
352 for (size_t i = 0; i < Y.size(); ++i) {
353 double aux = 0;
354 double x_v = 0.5 * (X[i] + X[i + 1]);
355
356 for (int j = 0; j < static_cast<int>(coeficients.size()); ++j) {
357 aux += coeficients[j] * std::pow(x_v, j);
358 }
359 Y[i] = aux;
360 }
361 }
362 }
363 progress.report("CalculateTransmission: Performing fit");
364
365 return output;
366}
376 double &offset) {
377 g_log.information("Fitting the experimental transmission curve");
378 double start = m_done;
379 auto childAlg = createChildAlgorithm("Fit", start, m_done + 0.9);
380 auto linearBack = API::FunctionFactory::Instance().createFunction("LinearBackground");
381 childAlg->setProperty("Function", linearBack);
382 childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", WS);
383 childAlg->setProperty("Minimizer", "Levenberg-MarquardtMD");
384 childAlg->setProperty("CreateOutput", true);
385 childAlg->setProperty("IgnoreInvalidData", true);
386 childAlg->executeAsChildAlg();
387
388 std::string fitStatus = childAlg->getProperty("OutputStatus");
389 if (fitStatus != "success") {
390 g_log.error("Unable to successfully fit the data: " + fitStatus);
391 throw std::runtime_error("Unable to successfully fit the data");
392 }
393
394 // Only get to here if successful
395 offset = linearBack->getParameter(0);
396 grad = linearBack->getParameter(1);
397 return this->extractSpectra(childAlg->getProperty("OutputWorkspace"), std::vector<size_t>(1, 1));
398}
406 std::vector<double> &coeficients) {
407 g_log.notice("Fitting the experimental transmission curve fitpolyno");
408 double start = m_done;
409 auto childAlg = createChildAlgorithm("Fit", start, m_done = 0.9);
410 auto polyfit = API::FunctionFactory::Instance().createFunction("Polynomial");
411 polyfit->setAttributeValue("n", order);
412 polyfit->initialize();
413 childAlg->setProperty("Function", polyfit);
414 childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", WS);
415 childAlg->setProperty("Minimizer", "Levenberg-MarquardtMD");
416 childAlg->setProperty("CreateOutput", true);
417 childAlg->setProperty("IgnoreInvalidData", true);
418 childAlg->executeAsChildAlg();
419 std::string fitStatus = childAlg->getProperty("OutputStatus");
420 if (fitStatus != "success") {
421 g_log.error("Unable to successfully fit the data: " + fitStatus);
422 throw std::runtime_error("Unable to successfully fit the data");
423 }
424
425 // Only get to here if successful
426 coeficients.resize(order + 1);
427 for (int i = 0; i <= order; i++) {
428 coeficients[i] = polyfit->getParameter(i);
429 }
430 return this->extractSpectra(childAlg->getProperty("OutputWorkspace"), std::vector<size_t>(1, 1));
431}
432
439API::MatrixWorkspace_sptr CalculateTransmission::rebin(const std::vector<double> &binParams,
440 const API::MatrixWorkspace_sptr &ws) {
441 double start = m_done;
442 auto childAlg = createChildAlgorithm("Rebin", start, m_done += 0.05);
443 childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", ws);
444 childAlg->setProperty<std::vector<double>>("Params", binParams);
445 childAlg->executeAsChildAlg();
446
447 // Only get to here if successful
448 return childAlg->getProperty("OutputWorkspace");
449}
450
460 const API::MatrixWorkspace_sptr &directWS, size_t index) {
461 const std::string message = "The detector at index " + std::to_string(index) + " is not a monitor in the ";
462 if (!sampleWS->spectrumInfo().isMonitor(index))
463 g_log.information(message + "sample workspace.");
464 if (!directWS->spectrumInfo().isMonitor(index))
465 g_log.information(message + "direct workspace.");
466}
467
468} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Definition: Algorithm.cpp:842
Kernel::Logger & g_log
Definition: Algorithm.h:451
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
A validator which provides a TENTATIVE check that a workspace contains common bins in each spectrum.
A validator which checks that a workspace contains histogram data (the default) or point data as requ...
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
A property class for workspaces.
A validator which checks that the unit of the workspace referred to by a WorkspaceProperty is the exp...
Calculates the transmission correction, as a function of wavelength, for a SANS instrument.
API::MatrixWorkspace_sptr extractSpectra(const API::MatrixWorkspace_sptr &ws, const std::vector< size_t > &indices)
Pull out a single spectrum from a 2D workspace.
API::MatrixWorkspace_sptr fit(const API::MatrixWorkspace_sptr &raw, const std::vector< double > &rebinParams, const std::string &fitMethod)
Returns a workspace with the evaulation of the fit to the calculated transmission fraction.
API::MatrixWorkspace_sptr fitData(const API::MatrixWorkspace_sptr &WS, double &grad, double &offset)
Call the Linear fitting algorithm as a child algorithm.
API::MatrixWorkspace_sptr rebin(const std::vector< double > &binParams, const API::MatrixWorkspace_sptr &ws)
Calls the rebin algorithm.
double m_done
stores an estimate of the progress so far as a proportion (starts at zero goes to 1....
API::MatrixWorkspace_sptr fitPolynomial(const API::MatrixWorkspace_sptr &WS, int order, std::vector< double > &coeficients)
Call the Polynomial fitting algorithm as a child algorithm.
void logIfNotMonitor(const API::MatrixWorkspace_sptr &sampleWS, const API::MatrixWorkspace_sptr &directWS, size_t index)
Outpus message to log if the detector at the given index is not a monitor in both input workspaces.
Support for a property that holds an array of values.
Definition: ArrayProperty.h:28
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void notice(const std::string &msg)
Logs at notice level.
Definition: Logger.cpp:95
void error(const std::string &msg)
Logs at error level.
Definition: Logger.cpp:77
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition: EmptyValues.h:25
int32_t detid_t
Typedef for a detector ID.
Definition: SpectrumInfo.h:21
std::string to_string(const wide_integer< Bits, Signed > &n)
static bool matchingBins(const MatrixWorkspace &ws1, const MatrixWorkspace &ws2, const bool firstOnly=false)
Checks whether the bins (X values) of two workspace are the same.
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54