Mantid
Loading...
Searching...
No Matches
PDCalibration.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 +
14#include "MantidAPI/Run.h"
16#include "MantidAPI/TableRow.h"
27#include "MantidHistogramData/Histogram.h"
36#include "MantidKernel/Unit.h"
37
38#include <algorithm>
39#include <gsl/gsl_multifit_nlin.h>
40#include <gsl/gsl_multimin.h>
41#include <limits>
42#include <numeric>
43
44namespace Mantid::Algorithms {
45
46using namespace Mantid::DataObjects;
47using namespace Mantid::HistogramData;
69using std::vector;
70
71// Register the algorithm into the AlgorithmFactory
72DECLARE_ALGORITHM(PDCalibration)
73
74namespace { // anonymous
75const auto isNonZero = [](const double value) { return value != 0.; };
76} // namespace
77
80public:
90 FittedPeaks(const API::MatrixWorkspace_const_sptr &wksp, const std::size_t wkspIndex) {
91 this->wkspIndex = wkspIndex;
92
93 // convert workspace index into detector id
94 const auto &spectrum = wksp->getSpectrum(wkspIndex);
95 this->detid = spectrum.getDetectorIDs();
96
97 const auto &X = spectrum.x();
98 const auto &Y = spectrum.y();
99 tofMin = X.front();
100 tofMax = X.back();
101
102 // determine tof min supported by the workspace (non-zero counts)
103 size_t minIndex = 0; // want to store value
104 for (; minIndex < Y.size(); ++minIndex) {
105 if (isNonZero(Y[minIndex])) {
106 tofMin = X[minIndex];
107 break; // we found the first bin with non-zero counts
108 }
109 }
110
111 // determine tof max supported by the workspace (non-zero counts)
112 size_t maxIndex = Y.size() - 1;
113 for (; maxIndex > minIndex; --maxIndex) {
114 if (isNonZero(Y[maxIndex])) {
115 tofMax = X[maxIndex];
116 break; // we found the last bin with non-zero counts
117 }
118 }
119 }
120
131 void setPositions(const std::vector<double> &peaksInD, const std::vector<double> &peaksInDWindows, const double difa,
132 const double difc, const double tzero) {
133 // clear out old values
134 inDPos.clear();
135 inTofPos.clear();
136 inTofWindows.clear();
137
138 // assign things
139 inDPos.assign(peaksInD.begin(), peaksInD.end());
140 inTofPos.assign(peaksInD.begin(), peaksInD.end());
141 inTofWindows.assign(peaksInDWindows.begin(), peaksInDWindows.end());
142
143 // convert the bits that matter to TOF
144 Kernel::Units::dSpacing dSpacingUnit;
145 std::vector<double> yunused;
146 dSpacingUnit.toTOF(
147 inTofPos, yunused, -1, 0,
149 dSpacingUnit.toTOF(
150 inTofWindows, yunused, -1, 0,
152 }
153
154 std::size_t wkspIndex;
155 std::set<detid_t> detid;
156 double tofMin; // TOF of bin with minimum TOF and non-zero counts
157 double tofMax; // TOF of bin with maximum TOF and non-zero counts
158 std::vector<double> inTofPos; // peak centers, in TOF
159 // left and right fit ranges for each peak center, in TOF
160 std::vector<double> inTofWindows;
161 std::vector<double> inDPos; // peak centers, in d-spacing
162};
163
164//----------------------------------------------------------------------------------------------
168
169//----------------------------------------------------------------------------------------------
173
174//----------------------------------------------------------------------------------------------
175
177const std::string PDCalibration::name() const { return "PDCalibration"; }
178
180int PDCalibration::version() const { return 1; }
181
183const std::string PDCalibration::category() const { return "Diffraction\\Calibration"; }
184
186const std::string PDCalibration::summary() const {
187 return "This algorithm determines the diffractometer constants that convert diffraction spectra "
188 "from time-of-flight (TOF) to d-spacing. The results are output to a calibration table.";
189}
190
191//----------------------------------------------------------------------------------------------
195 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>("InputWorkspace", "", Direction::Input),
196 "Input workspace containing spectra as a function of TOF measured on a standard sample.");
197
198 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
199 mustBePositive->setLower(0);
200 declareProperty("StartWorkspaceIndex", 0, mustBePositive, "Starting workspace index for fit");
202 "StopWorkspaceIndex", EMPTY_INT(),
203 "Last workspace index for fit is the smaller of this value and the workspace index of last spectrum.");
204
206 std::make_unique<ArrayProperty<double>>("TofBinning", std::make_shared<RebinParamsValidator>()),
207 "Min, Step, and Max of TOF bins. "
208 "Logarithmic binning is used if Step is negative. Chosen binning should ensure sufficient datapoints across "
209 "the peaks to be fitted, considering the number of parameters required by PeakFunction and BackgroundType.");
210
211 const std::vector<std::string> exts2{".h5", ".cal"};
212 declareProperty(std::make_unique<FileProperty>("PreviousCalibrationFile", "", FileProperty::OptionalLoad, exts2),
213 "An existent file to be loaded into a calibration table, which is used for converting PeakPositions "
214 "from d-spacing to TOF.");
216 "PreviousCalibrationTable", "", Direction::Input, API::PropertyMode::Optional),
217 "An existent calibration table used for converting PeakPositions from d-spacing to TOF. "
218 "This property has precedence over PreviousCalibrationFile.");
219
220 // properties about peak positions to fit
221 std::vector<std::string> peaktypes{"BackToBackExponential", "Gaussian", "Lorentzian", "PseudoVoigt",
222 "IkedaCarpenterPV"};
223 declareProperty("PeakFunction", "Gaussian", std::make_shared<StringListValidator>(peaktypes),
224 "Function to fit input peaks.");
225 vector<std::string> bkgdtypes{"Flat", "Linear", "Quadratic"};
226 declareProperty("BackgroundType", "Linear", std::make_shared<StringListValidator>(bkgdtypes),
227 "Function to fit input peaks background.");
228
229 auto peaksValidator = std::make_shared<CompositeValidator>();
230 auto mustBePosArr = std::make_shared<Kernel::ArrayBoundedValidator<double>>();
231 mustBePosArr->setLower(0.0);
232 peaksValidator->add(mustBePosArr);
233 peaksValidator->add(std::make_shared<MandatoryValidator<std::vector<double>>>());
234 declareProperty(std::make_unique<ArrayProperty<double>>("PeakPositions", peaksValidator),
235 "Comma-delimited positions (d-spacing) of reference peaks. Care should be taken to avoid using peaks "
236 "whose predicted positions are too close considering their peak windows.");
237
238 auto windowValidator = std::make_shared<CompositeValidator>();
239 windowValidator->add(mustBePosArr);
240 auto lengthValidator = std::make_shared<Kernel::ArrayLengthValidator<double>>();
241 lengthValidator->setLengthMin(1);
242 windowValidator->add(lengthValidator);
243 windowValidator->add(std::make_shared<MandatoryValidator<std::vector<double>>>());
244 declareProperty(std::make_unique<ArrayProperty<double>>("PeakWindow", "0.1", windowValidator),
245 "Width of the window (d-spacing) over which to fit a peak. If a single value is supplied, it will be "
246 "used as half "
247 "the window width for all peaks. Otherwise, the expected input is a comma-delimited list of 2*N "
248 "window boundaries, "
249 "where N is the number of values in PeakPositions. The order of the window boundaries should match "
250 "the order in PeakPositions.");
251
252 auto min = std::make_shared<BoundedValidator<double>>();
253 min->setLower(1e-3);
254 declareProperty("PeakWidthPercent", EMPTY_DBL(), min,
255 "Used for estimating peak width (an initial parameter for peak fitting) by multiplying peak's value "
256 "in PeakPositions by this factor.");
257
258 declareProperty("MinimumPeakHeight", 2.,
259 "Used for validating peaks before and after fitting. If a peak's observed/estimated or "
260 "fitted height is under this value, the peak will be marked as an error.");
261
262 declareProperty("MaxChiSq", 100.,
263 "Used for validating peaks after fitting. If the chi-squared value is higher than this value, "
264 "the peak will be excluded from calibration. The recommended value is between 2 and 10.");
265
266 declareProperty("ConstrainPeakPositions", false,
267 "If true, a peak center being fit will be constrained by the estimated peak position "
268 "+/- 0.5 * \"estimated peak width\", where the estimated peak position is the highest Y-value "
269 "position in the window, "
270 " and the estimated peak width is FWHM calculated over the window data.");
271
272 declareProperty("HighBackground", false,
273 "Flag whether the input data has high background compared to peaks' heights. "
274 "This option is recommended for data with peak-to-background ratios under ~5.");
275
277 "CopyLastGoodPeakParameters", true,
278 "If true, for a given peak in a spectrum the initial peak parameters (with the exception of peak centre) "
279 "may be copied from the last successfully fit peak in that spectrum.");
280
281 declareProperty("MinimumSignalToNoiseRatio", 0.,
282 "Used for validating peaks before fitting. If the signal-to-noise ratio is under this value, "
283 "the peak will be excluded from fitting and calibration. This check does not apply to peaks for "
284 "which the noise cannot be estimated. "
285 "The minimum recommended value is 12.");
286
287 std::vector<std::string> modes{"DIFC", "DIFC+TZERO", "DIFC+TZERO+DIFA"};
288 declareProperty("CalibrationParameters", "DIFC", std::make_shared<StringListValidator>(modes),
289 "Select which diffractometer constants (GSAS convention) to determine.");
290
291 declareProperty(std::make_unique<ArrayProperty<double>>("TZEROrange"),
292 "Range for allowable calibrated TZERO value. Default: no restriction.");
293
294 declareProperty(std::make_unique<ArrayProperty<double>>("DIFArange"),
295 "Range for allowable calibrated DIFA value. Default: no restriction.");
296
297 declareProperty("UseChiSq", false,
298 "Defines the weighting scheme used in the least-squares fit of the extracted peak centers "
299 "that determines the diffractometer constants. If true, the peak weight will be "
300 "the inverse square of the error on the fitted peak center. If false, the peak weight will be "
301 "the square of the fitted peak height.");
302
304 std::make_unique<WorkspaceProperty<API::ITableWorkspace>>("OutputCalibrationTable", "", Direction::Output),
305 "Output table workspace containing the calibration.");
306
307 // Mantid's python API _requires_ a non empty-string name for any Output workspace, even when 'PropertyMode::Optional'
308 // is specified.
309 declareProperty(std::make_unique<WorkspaceProperty<MaskWorkspace>>("MaskWorkspace", "_empty_", Direction::Output,
311 "Mask workspace (optional input / output workspace):"
312 " when specified, if the workspace already exists, any incoming masked detectors will be combined"
313 " with any additional outgoing masked detectors detected by the algorithm");
314
316 std::make_unique<WorkspaceProperty<API::WorkspaceGroup>>("DiagnosticWorkspaces", "", Direction::Output),
317 "Auxiliary workspaces containing extended information on the calibration results.");
318
319 declareProperty("MinimumPeakTotalCount", EMPTY_DBL(),
320 "Used for validating peaks before fitting. If the total peak window Y-value count "
321 "is under this value, the peak will be excluded from fitting and calibration.");
322
323 declareProperty("MinimumSignalToSigmaRatio", 0.,
324 "Used for validating peaks after fitting. If the signal-to-sigma ratio is under this value, "
325 "the peak will be excluded from fitting and calibration.");
326
327 // make group for Input properties
328 std::string inputGroup("Input Options");
329 setPropertyGroup("InputWorkspace", inputGroup);
330 setPropertyGroup("StartWorkspaceIndex", inputGroup);
331 setPropertyGroup("StopWorkspaceIndex", inputGroup);
332 setPropertyGroup("TofBinning", inputGroup);
333 setPropertyGroup("PreviousCalibrationFile", inputGroup);
334 setPropertyGroup("PreviousCalibrationTable", inputGroup);
335
336 std::string funcgroup("Function Types");
337 setPropertyGroup("PeakFunction", funcgroup);
338 setPropertyGroup("BackgroundType", funcgroup);
339
340 // make group for FitPeaks properties
341 std::string fitPeaksGroup("Peak Fitting");
342 setPropertyGroup("PeakPositions", fitPeaksGroup);
343 setPropertyGroup("PeakWindow", fitPeaksGroup);
344 setPropertyGroup("PeakWidthPercent", fitPeaksGroup);
345 setPropertyGroup("MinimumPeakHeight", fitPeaksGroup);
346 setPropertyGroup("MinimumSignalToNoiseRatio", fitPeaksGroup);
347 setPropertyGroup("MinimumPeakTotalCount", fitPeaksGroup);
348 setPropertyGroup("MinimumSignalToSigmaRatio", fitPeaksGroup);
349 setPropertyGroup("HighBackground", fitPeaksGroup);
350 setPropertyGroup("MaxChiSq", fitPeaksGroup);
351 setPropertyGroup("ConstrainPeakPositions", fitPeaksGroup);
352 setPropertyGroup("CopyLastGoodPeakParameters", fitPeaksGroup);
353
354 // make group for type of calibration
355 std::string calGroup("Calibration Type");
356 setPropertyGroup("CalibrationParameters", calGroup);
357 setPropertyGroup("TZEROrange", calGroup);
358 setPropertyGroup("DIFArange", calGroup);
359 setPropertyGroup("UseChiSq", calGroup);
360}
361
362std::map<std::string, std::string> PDCalibration::validateInputs() {
363 std::map<std::string, std::string> messages;
364
365 if (MaskWorkspace_const_sptr maskWS = getProperty("MaskWorkspace")) {
366 MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
367
368 // detectors which are monitors are not included in the mask
369 if (maskWS->getInstrument()->getNumberDetectors(true) != inputWS->getInstrument()->getNumberDetectors(true)) {
370 messages["MaskWorkspace"] = "incoming mask workspace must have the same instrument as the input workspace";
371 } else if (maskWS->getNumberHistograms() != inputWS->getInstrument()->getNumberDetectors(true)) {
372 messages["MaskWorkspace"] = "incoming mask workspace must have one spectrum per detector";
373 }
374 }
375
376 vector<double> tzeroRange = getProperty("TZEROrange");
377 if (!tzeroRange.empty()) {
378 if (tzeroRange.size() != 2) {
379 messages["TZEROrange"] = "Require two values [min,max]";
380 } else if (tzeroRange[0] >= tzeroRange[1]) {
381 messages["TZEROrange"] = "min must be less than max";
382 }
383 }
384
385 vector<double> difaRange = getProperty("DIFArange");
386 if (!difaRange.empty()) {
387 if (difaRange.size() != 2) {
388 messages["DIFArange"] = "Require two values [min,max]";
389 } else if (difaRange[0] >= difaRange[1]) {
390 messages["DIFArange"] = "min must be less than max";
391 }
392 }
393
394 vector<double> peakWindow = getProperty("PeakWindow");
395 vector<double> peakCentres = getProperty("PeakPositions");
396 if (peakWindow.size() > 1 && peakWindow.size() != 2 * peakCentres.size()) {
397 messages["PeakWindow"] = "PeakWindow must be a vector with either 1 element (interpreted as half the width of "
398 "the window) or twice the number of peak centres provided.";
399 }
400
401 return messages;
402}
403
404namespace {
405
407bool hasDasIDs(const API::ITableWorkspace_const_sptr &table) {
408 const auto columnNames = table->getColumnNames();
409 return (std::find(columnNames.begin(), columnNames.end(), std::string("dasid")) != columnNames.end());
410}
411
419double getWidthToFWHM(const std::string &peakshape) {
420 // could we use actual peak function here?
421 if (peakshape == "Gaussian") {
422 return 2 * std::sqrt(2. * std::log(2.));
423 } else if (peakshape == "Lorentzian") {
424 return 2.;
425 } else if (peakshape == "BackToBackExponential") {
426 return 1.; // TODO the conversion isn't document in the function
427 } else {
428 return 1.;
429 }
430}
431
432} // end of anonymous namespace
433
434//----------------------------------------------------------------------------------------------
438 vector<double> tofBinningParams = getProperty("TofBinning");
439 m_tofMin = tofBinningParams.front();
440 m_tofMax = tofBinningParams.back();
441
442 vector<double> tzeroRange = getProperty("TZEROrange");
443 if (tzeroRange.size() == 2) {
444 m_tzeroMin = tzeroRange[0];
445 m_tzeroMax = tzeroRange[1];
446
447 std::stringstream msg;
448 msg << "Using tzero range of " << m_tzeroMin << " <= "
449 << "TZERO <= " << m_tzeroMax;
450 g_log.information(msg.str());
451 } else {
452 g_log.information("Using all TZERO values");
453
454 m_tzeroMin = std::numeric_limits<double>::lowest();
455 m_tzeroMax = std::numeric_limits<double>::max();
456 }
457
458 vector<double> difaRange = getProperty("DIFArange");
459 if (difaRange.size() == 2) {
460 m_difaMin = difaRange[0];
461 m_difaMax = difaRange[1];
462
463 std::stringstream msg;
464 msg << "Using difa range of " << m_difaMin << " <= "
465 << "DIFA <= " << m_difaMax;
466 g_log.information(msg.str());
467 } else {
468 g_log.information("Using all DIFA values");
469
470 m_difaMin = std::numeric_limits<double>::lowest();
471 m_difaMax = std::numeric_limits<double>::max();
472 }
473
474 m_peaksInDspacing = getProperty("PeakPositions");
475 std::vector<double> peakWindow = getProperty("PeakWindow");
476 const std::size_t NUMPEAKS = m_peaksInDspacing.size();
477 // if peak windows given for each peak center, sort all by peak center
478 if (peakWindow.size() > 1) {
479 // load windows into ordered map keyed by peak center
480 // this preserves association of centers and window edges
481 std::map<double, std::pair<double, double>> peakEdgeAndCenter;
482 for (std::size_t i = 0; i < m_peaksInDspacing.size(); i++) {
483 peakEdgeAndCenter[m_peaksInDspacing[i]] = {peakWindow[2 * i], peakWindow[2 * i + 1]};
484 }
485 m_peaksInDspacing.clear();
486 m_peaksInDspacing.reserve(NUMPEAKS);
487 peakWindow.clear();
488 peakWindow.reserve(2 * NUMPEAKS);
489 // retrieve ordered center, windows
490 for (auto it = peakEdgeAndCenter.begin(); it != peakEdgeAndCenter.end(); it++) {
491 m_peaksInDspacing.push_back(it->first);
492 peakWindow.push_back(it->second.first);
493 peakWindow.push_back(it->second.second);
494 }
495 } else {
496 // Sort peak positions, required for correct peak window calculations
497 std::sort(m_peaksInDspacing.begin(), m_peaksInDspacing.end());
498 }
499
500 const double minPeakHeight = getProperty("MinimumPeakHeight");
501 const double minPeakTotalCount = getProperty("MinimumPeakTotalCount");
502 const double minSignalToNoiseRatio = getProperty("MinimumSignalToNoiseRatio");
503 const double minSignalToSigmaRatio = getProperty("MinimumSignalToSigmaRatio");
504 const double maxChiSquared = getProperty("MaxChiSq");
505 const bool copyLastGoodPeakParameters = getProperty("CopyLastGoodPeakParameters");
506
507 const std::string calParams = getPropertyValue("CalibrationParameters");
508 if (calParams == std::string("DIFC"))
510 else if (calParams == std::string("DIFC+TZERO"))
512 else if (calParams == std::string("DIFC+TZERO+DIFA"))
514 else
515 throw std::runtime_error("Encountered impossible CalibrationParameters value");
516
518 setProperty("InputWorkspace", m_uncalibratedWS);
519
520 m_startWorkspaceIndex = getProperty("StartWorkspaceIndex");
521 m_stopWorkspaceIndex = isDefault("StopWorkspaceIndex") ? static_cast<int>(m_uncalibratedWS->getNumberHistograms() - 1)
522 : getProperty("StopWorkspaceIndex");
523
524 auto uncalibratedEWS = std::dynamic_pointer_cast<EventWorkspace>(m_uncalibratedWS);
525 auto isEvent = bool(uncalibratedEWS);
526
527 // Load Previous Calibration or create calibration table from signal file
528 if ((!static_cast<std::string>(getProperty("PreviousCalibrationFile")).empty()) ||
529 (!getPropertyValue("PreviousCalibrationTable").empty())) { //"PreviousCalibrationTable"
531 } else {
532 createCalTableNew(); // calculates "difc" values from instrument geometry
533 }
535
536 // Use the incoming mask workspace, or start a new one if the workspace does not exist.
537 MaskWorkspace_sptr maskWS;
538 if (!isDefault("MaskWorkspace")) {
539 maskWS = getProperty("MaskWorkspace");
540 }
541 if (!maskWS) {
542 g_log.debug() << "[PDCalibration]: CREATING new MaskWorkspace.\n";
543 // A new mask is completely cleared at creation.
544 maskWS = std::make_shared<MaskWorkspace>(m_uncalibratedWS->getInstrument());
545 } else {
546 g_log.debug() << "[PDCalibration]: Using EXISTING MaskWorkspace.\n";
547 }
548 // Include any incoming masked detector flags in the mask-workspace values.
549 maskWS->combineFromDetectorMasks(m_uncalibratedWS->detectorInfo());
550
551 const std::string peakFunction = getProperty("PeakFunction");
552 const double WIDTH_TO_FWHM = getWidthToFWHM(peakFunction);
553 if (WIDTH_TO_FWHM == 1.) {
554 g_log.notice() << "Unknown conversion for \"" << peakFunction
555 << "\", found peak widths and resolution should not be "
556 "directly compared to delta-d/d";
557 }
558 auto NUMHIST = m_stopWorkspaceIndex - m_startWorkspaceIndex + 1;
559
560 // Create a pair of workspaces, one containing the nominal peak centers in TOF units,
561 // the other containing the left and right fitting ranges around each nominal
562 // peak center, also in TOF units. This for each pixel of the instrument
563 auto matrix_pair = createTOFPeakCenterFitWindowWorkspaces(m_uncalibratedWS, peakWindow);
564 API::MatrixWorkspace_sptr tof_peak_center_ws = matrix_pair.first;
565 API::MatrixWorkspace_sptr tof_peak_window_ws = matrix_pair.second;
566
567 double peak_width_percent = getProperty("PeakWidthPercent");
568
569 const std::string diagnostic_prefix = getPropertyValue("DiagnosticWorkspaces");
570
571 // Refine the position of the peak centers starting from the nominal peak
572 // centers and fitting them against a peak fit function (e.g. a Gaussian)
573 auto algFitPeaks = createChildAlgorithm("FitPeaks", .2, .7);
574 algFitPeaks->setLoggingOffset(3);
575
576 algFitPeaks->setProperty("InputWorkspace", m_uncalibratedWS);
577
578 // limit the spectra to fit
579 algFitPeaks->setProperty("StartWorkspaceIndex", static_cast<int>(m_startWorkspaceIndex));
580 algFitPeaks->setProperty("StopWorkspaceIndex", static_cast<int>(m_stopWorkspaceIndex));
581
582 // theoretical peak center
583 algFitPeaks->setProperty("PeakCentersWorkspace", tof_peak_center_ws);
584
585 // peak and background functions
586 algFitPeaks->setProperty<std::string>("PeakFunction", peakFunction);
587 algFitPeaks->setProperty<std::string>("BackgroundType", getProperty("BackgroundType"));
588 // peak range setup
589 algFitPeaks->setProperty("FitPeakWindowWorkspace", tof_peak_window_ws);
590 algFitPeaks->setProperty("PeakWidthPercent", peak_width_percent);
591 algFitPeaks->setProperty("MinimumPeakHeight", minPeakHeight);
592 algFitPeaks->setProperty("MinimumPeakTotalCount", minPeakTotalCount);
593 algFitPeaks->setProperty("MinimumSignalToNoiseRatio", minSignalToNoiseRatio);
594 algFitPeaks->setProperty("MinimumSignalToSigmaRatio", minSignalToSigmaRatio);
595 // some fitting strategy
596 algFitPeaks->setProperty("FitFromRight", true);
597 const bool highBackground = getProperty("HighBackground");
598 algFitPeaks->setProperty("HighBackground", highBackground);
599 bool constrainPeakPosition = getProperty("ConstrainPeakPositions");
600 algFitPeaks->setProperty("ConstrainPeakPositions",
601 constrainPeakPosition); // TODO Pete: need to test this option
602 // optimization setup // TODO : need to test LM or LM-MD
603 algFitPeaks->setProperty("Minimizer", "Levenberg-Marquardt");
604 algFitPeaks->setProperty("CostFunction", "Least squares");
605
606 // FitPeaks will abstract the peak parameters if you ask (if using chisq then
607 // need FitPeaks to output fitted params rather than height, width)
608 const bool useChiSq = getProperty("UseChiSq");
609 algFitPeaks->setProperty("RawPeakParameters", useChiSq);
610
611 // Analysis output
612 // If using a Gaussian peak shape plus a constant background, then
613 // OutputPeakParametersWorkspace is a table with columns:
614 // wsindex_0 peakindex_0 centre width height intensity A0 chi2
615 // ...
616 // wsindex_0 peakindex_N centre width height intensity A0 chi2
617 // ...
618 // ...
619 // wsindex_M peakindex_N centre width height intensity
620 algFitPeaks->setPropertyValue("OutputPeakParametersWorkspace", diagnostic_prefix + "_fitparam");
621 // contains the same intensities as input m_uncalibratedWS except within
622 // the fitting range of each successfully fitted peak. Within this range,
623 // the actual intensities are replaced with the values resulting from
624 // evaluating the peak function (e.g. a Gaussian peak function)
625 algFitPeaks->setPropertyValue("FittedPeaksWorkspace", diagnostic_prefix + "_fitted");
626 if (useChiSq) {
627 algFitPeaks->setPropertyValue("OutputParameterFitErrorsWorkspace", diagnostic_prefix + "_fiterrors");
628 }
629
630 // whether, for a given peak in each spectrum, the initial peak parameters (with the exception of peak centre)
631 // may be copied from the last successfully fit peak in that spectrum.
632 algFitPeaks->setProperty("CopyLastGoodPeakParameters", copyLastGoodPeakParameters);
633
634 // run and get the result
635 algFitPeaks->executeAsChildAlg();
636 g_log.information("finished FitPeaks");
637
638 // get the fit result
639 API::ITableWorkspace_sptr fittedTable = algFitPeaks->getProperty("OutputPeakParametersWorkspace");
640 API::MatrixWorkspace_sptr calculatedWS = algFitPeaks->getProperty("FittedPeaksWorkspace");
641 API::ITableWorkspace_sptr errorTable; // or nullptr as in FitPeaks L1997
642 if (useChiSq) {
643 errorTable = algFitPeaks->getProperty("OutputParameterFitErrorsWorkspace");
644 }
645
646 // check : for Pete
647 if (!fittedTable)
648 throw std::runtime_error("FitPeaks does not have output OutputPeakParametersWorkspace.");
649 if (fittedTable->rowCount() != NUMHIST * m_peaksInDspacing.size())
650 throw std::runtime_error("The number of rows in OutputPeakParametersWorkspace is not correct!");
651
652 // END-OF (FitPeaks)
653 const std::string backgroundType = getPropertyValue("BackgroundType");
654
655 API::Progress prog(this, 0.7, 1.0, NUMHIST);
656
657 // calculate fitting ranges to the left and right of each nominal peak
658 // center, in d-spacing units
659 const auto windowsInDSpacing = dSpacingWindows(m_peaksInDspacing, peakWindow);
660
661 // get spectrum info to check workspace index correpsonds to a valid spectrum
662 const auto &spectrumInfo = m_uncalibratedWS->spectrumInfo();
663
664 // Scan the table containing the fit parameters for every peak, retrieve the
665 // parameters for peaks that were successfully fitting, then use this info
666 // to obtain difc, difa, and tzero for each pixel
667
668 PRAGMA_OMP(parallel for schedule(dynamic, 1))
669 for (int wkspIndex = m_startWorkspaceIndex; wkspIndex <= m_stopWorkspaceIndex; ++wkspIndex) {
671 if ((isEvent && uncalibratedEWS->getSpectrum(wkspIndex).empty()) || !spectrumInfo.hasDetectors(wkspIndex) ||
672 spectrumInfo.isMonitor(wkspIndex) ||
673 maskWS->isMasked(m_uncalibratedWS->getSpectrum(wkspIndex).getDetectorIDs())) {
674 prog.report();
675
676 if (spectrumInfo.hasDetectors(wkspIndex) && !spectrumInfo.isMonitor(wkspIndex)) {
677 if (isEvent && uncalibratedEWS->getSpectrum(wkspIndex).empty()) {
678 maskWS->setMasked(m_uncalibratedWS->getSpectrum(wkspIndex).getDetectorIDs(), true);
679 g_log.debug() << "FULLY masked spectrum, index: " << wkspIndex << "\n";
680 }
681 }
682
683 continue;
684 }
685
686 // object to hold information about the peak positions, detid, and workspace index
688 const auto [dif_c, dif_a, tzero] =
689 getDSpacingToTof(peaks.detid); // doesn't matter which one - all have same difc etc.
690 peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, dif_a, dif_c, tzero);
691
692 // includes peaks that aren't used in the fit
693 // The following data structures will hold information for the peaks
694 // found in the current spectrum
695 const size_t numPeaks = m_peaksInDspacing.size();
696 // TOF of fitted peak centers, default `nan` for failed fitted peaks
697 std::vector<double> tof_vec_full(numPeaks, std::nan(""));
698 std::vector<double> d_vec; // nominal peak centers of fitted peaks
699 std::vector<double> tof_vec; // TOF of fitted peak centers only
700 // width of fitted peak centers, default `nan` for failed fitted peaks
701 std::vector<double> width_vec_full(numPeaks, std::nan(""));
702 // height of fitted peak centers, default `nan` for failed fitted peaks
703 std::vector<double> height_vec_full(numPeaks, std::nan(""));
704 std::vector<double> weights; // weights for diff const fits
705 // row where first peak occurs
706 const size_t rowNumInFitTableOffset = (wkspIndex - m_startWorkspaceIndex) * numPeaks;
707 // We assumed that the current spectrum contains peaks near the nominal
708 // peak centers. Now we check how many peaks we actually found
709 for (size_t peakIndex = 0; peakIndex < numPeaks; ++peakIndex) {
710 size_t rowIndexInFitTable = rowNumInFitTableOffset + peakIndex;
711
712 // check indices in PeaksTable
713 if (fittedTable->getRef<int>("wsindex", rowIndexInFitTable) != wkspIndex)
714 throw std::runtime_error("workspace index mismatch!");
715 if (fittedTable->getRef<int>("peakindex", rowIndexInFitTable) != static_cast<int>(peakIndex))
716 throw std::runtime_error("peak index mismatch but workspace index matched");
717
718 const double chi2 = fittedTable->getRef<double>("chi2", rowIndexInFitTable);
719 double centre = 0.0;
720 double centre_error = 0.0; // only used if useChiSq true
721 double width = 0.0;
722 double height = 0.0;
723 if (!useChiSq) {
724 // get the effective peak parameters from FitPeaks output
725 centre = fittedTable->getRef<double>("centre", rowIndexInFitTable);
726 width = fittedTable->getRef<double>("width", rowIndexInFitTable);
727 height = fittedTable->getRef<double>("height", rowIndexInFitTable);
728 } else {
729 // FitPeaks outputs actual fitting parameters
730 // extract these from the peak function (not efficient)
731 auto peakfunc = std::dynamic_pointer_cast<API::IPeakFunction>(
732 API::FunctionFactory::Instance().createFunction(peakFunction));
733 // set peak functio nparameters from fit
734 for (size_t ipar = 0; ipar < peakfunc->nParams(); ipar++) {
735 peakfunc->setParameter(ipar, fittedTable->getRef<double>(peakfunc->parameterName(ipar), rowIndexInFitTable));
736 }
737 centre = peakfunc->centre();
738 width = peakfunc->fwhm();
739 height = peakfunc->height();
740 centre_error = errorTable->getRef<double>(peakfunc->getCentreParameterName(), rowIndexInFitTable);
741 }
742
743 // check chi-square
744 if (chi2 > maxChiSquared || chi2 < 0.) {
745 g_log.debug("failure to fit: chi2 > maximum");
746 continue; // peak fit deemed as failure
747 }
748
749 // rule out of peak with wrong position. `centre` should be within its
750 // left and right window ranges
751 if (peaks.inTofWindows[2 * peakIndex] >= centre || peaks.inTofWindows[2 * peakIndex + 1] <= centre) {
752 g_log.debug("failure to fit: peak center is out-of-range");
753 continue; // peak fit deemed as failure
754 }
755
756 // check height: make sure 0 is smaller than 0
757 if (height < minPeakHeight + 1.E-15) {
758 g_log.debug("failure to fit: peak height is less than minimum");
759 continue; // peak fit deemed as failure
760 }
761
762 // the peak fit was a success. Collect info
763 g_log.getLogStream(Logger::Priority::PRIO_TRACE) << "successful fit: peak centered at " << centre << "\n";
764
765 d_vec.emplace_back(m_peaksInDspacing[peakIndex]);
766 tof_vec.emplace_back(centre);
767 if (!useChiSq) {
768 weights.emplace_back(height * height);
769 } else {
770 weights.emplace_back(1 / (centre_error * centre_error));
771 }
772 tof_vec_full[peakIndex] = centre;
773 width_vec_full[peakIndex] = width;
774 height_vec_full[peakIndex] = height;
775 }
776
777 if (d_vec.size() < 2) {
778 // If less than two peaks were fitted successfully, indicate failure by
779 // masking all of the detectors contributing to the spectrum.
780 maskWS->setMasked(peaks.detid, true);
781
782 g_log.debug() << "MASKING:\n";
783 for (const auto &det : peaks.detid) {
784 g_log.debug() << " " << det << "\n";
785 }
786 g_log.debug() << "\n";
787
788 continue;
789 } else {
790 // obtain difc, difa, and t0 by fitting the nominal peak center
791 // positions, in d-spacing against the fitted peak center positions, in
792 // TOF units.
793 double difc = 0., t0 = 0., difa = 0.;
794 fitDIFCtZeroDIFA_LM(d_vec, tof_vec, weights, difc, t0, difa);
795 for (auto iter = peaks.detid.begin(); iter != peaks.detid.end(); ++iter) {
796 auto det = *iter;
797 const auto rowIndexOutputPeaks = m_detidToRow[det];
798 // chisq represent the deviations between the nominal peak positions
799 // and the peak positions using the GSAS formula with optimized difc,
800 // difa, and tzero
801 double chisq = 0.;
803 dSpacingUnit.initialize(-1., 0,
807 for (std::size_t i = 0; i < numPeaks; ++i) {
808 if (std::isnan(tof_vec_full[i]))
809 continue;
810 // Find d-spacing using the GSAS formula with optimized difc, difa,
811 // t0 for the TOF of the current peak's center.
812 const double dspacing = dSpacingUnit.singleFromTOF(tof_vec_full[i]);
813 // `temp` is residual between the nominal position in d-spacing for
814 // the current peak, and the fitted position in d-spacing
815 const double temp = m_peaksInDspacing[i] - dspacing;
816 chisq += (temp * temp);
817 m_peakPositionTable->cell<double>(rowIndexOutputPeaks, i + 1) = dspacing;
818 m_peakWidthTable->cell<double>(rowIndexOutputPeaks, i + 1) =
819 WIDTH_TO_FWHM * (width_vec_full[i] / (2 * difa * dspacing + difc));
820 m_peakHeightTable->cell<double>(rowIndexOutputPeaks, i + 1) = height_vec_full[i];
821 }
822 m_peakPositionTable->cell<double>(rowIndexOutputPeaks, m_peaksInDspacing.size() + 1) = chisq;
823 m_peakPositionTable->cell<double>(rowIndexOutputPeaks, m_peaksInDspacing.size() + 2) =
824 chisq / static_cast<double>(numPeaks - 1);
825
826 setCalibrationValues(det, difc, difa, t0);
827 }
828 }
829 prog.report();
830
832 }
834
835 // sort the calibration tables by increasing detector ID
837 setProperty("OutputCalibrationTable", m_calibrationTable);
838
839 // Return the mask workspace only if it was specified as a parameter.
840 if (!isDefault("MaskWorkspace")) {
841 // Align the detector mask flags of the mask workspace with the workspace values:
842 maskWS->combineToDetectorMasks();
843 setProperty("MaskWorkspace", maskWS);
844 }
845
846 // fix-up the diagnostic workspaces
850
851 // a derived table from the position and width
852 auto resolutionWksp = calculateResolutionTable();
853
854 // set the diagnostic workspaces out
855 auto diagnosticGroup = std::make_shared<API::WorkspaceGroup>();
856 // add workspaces calculated by FitPeaks
857 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_fitparam", fittedTable);
858 diagnosticGroup->addWorkspace(fittedTable);
859 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_fitted", calculatedWS);
860 diagnosticGroup->addWorkspace(calculatedWS);
861 if (useChiSq) {
862 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_fiterror", errorTable);
863 diagnosticGroup->addWorkspace(errorTable);
864 }
865
866 // add workspaces calculated by PDCalibration
867 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_dspacing", m_peakPositionTable);
868 diagnosticGroup->addWorkspace(m_peakPositionTable);
869 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_width", m_peakWidthTable);
870 diagnosticGroup->addWorkspace(m_peakWidthTable);
871 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_height", m_peakHeightTable);
872 diagnosticGroup->addWorkspace(m_peakHeightTable);
873 API::AnalysisDataService::Instance().addOrReplace(diagnostic_prefix + "_resolution", resolutionWksp);
874 diagnosticGroup->addWorkspace(resolutionWksp);
875 setProperty("DiagnosticWorkspaces", diagnosticGroup);
876}
877
878namespace { // anonymous namespace
886double gsl_costFunction(const gsl_vector *v, void *peaks) {
887 // this array is [numPeaks, numParams, vector<tof>, vector<dspace>,
888 // vector<weights>]
889 // index as [0, 1, 2, , 2+n , 2+2n]
890 const std::vector<double> *peakVec = reinterpret_cast<std::vector<double> *>(peaks);
891 // number of peaks being fit
892 const auto numPeaks = static_cast<size_t>(peakVec->at(0));
893 // number of parameters
894 const auto numParams = static_cast<size_t>(peakVec->at(1));
895
896 // isn't strictly necessary, but makes reading the code much easier
897 const std::vector<double> tofObs(peakVec->begin() + 2, peakVec->begin() + 2 + numPeaks);
898 const std::vector<double> dspace(peakVec->begin() + (2 + numPeaks), peakVec->begin() + (2 + 2 * numPeaks));
899 const std::vector<double> weights(peakVec->begin() + (2 + 2 * numPeaks), peakVec->begin() + (2 + 3 * numPeaks));
900
901 // create the function to convert tof to dspacing
902 double difc = gsl_vector_get(v, 0);
903 double tzero = 0.;
904 double difa = 0.;
905 if (numParams > 1) {
906 tzero = gsl_vector_get(v, 1);
907 if (numParams > 2)
908 difa = gsl_vector_get(v, 2);
909 }
911 dSpacingUnit.initialize(-1., 0,
915
916 // calculate the sum of the residuals from observed peaks
917 double errsum = 0.0;
918 for (size_t i = 0; i < numPeaks; ++i) {
919 const double tofCalib = dSpacingUnit.singleToTOF(dspace[i]);
920 const double errsum_i = std::pow(tofObs[i] - tofCalib, 2) * weights[i];
921 errsum += errsum_i;
922 }
923
924 return errsum;
925}
926
945double fitDIFCtZeroDIFA(std::vector<double> &peaks, double &difc, double &t0, double &difa) {
946 const auto numParams = static_cast<size_t>(peaks[1]);
947
948 // initial starting point as [DIFC, 0, 0]
949 gsl_vector *fitParams = gsl_vector_alloc(numParams);
950 gsl_vector_set_all(fitParams, 0.0); // set all parameters to zero
951 gsl_vector_set(fitParams, 0, difc);
952 if (numParams > 1) {
953 gsl_vector_set(fitParams, 1, t0);
954 if (numParams > 2) {
955 gsl_vector_set(fitParams, 2, difa);
956 }
957 }
958
959 // Set initial step sizes
960 gsl_vector *stepSizes = gsl_vector_alloc(numParams);
961 gsl_vector_set_all(stepSizes, 0.1);
962 gsl_vector_set(stepSizes, 0, 0.01);
963
964 // Initialize method and iterate
965 gsl_multimin_function minex_func;
966 minex_func.n = numParams;
967 minex_func.f = &gsl_costFunction;
968 minex_func.params = &peaks;
969
970 // Set up GSL minimzer - simplex is overkill
971 const gsl_multimin_fminimizer_type *minimizerType = gsl_multimin_fminimizer_nmsimplex2;
972 gsl_multimin_fminimizer *minimizer = gsl_multimin_fminimizer_alloc(minimizerType, numParams);
973 gsl_multimin_fminimizer_set(minimizer, &minex_func, fitParams, stepSizes);
974
975 // Finally do the fitting
976 size_t iter = 0; // number of iterations
977 const size_t MAX_ITER = 75 * numParams;
978 int status = 0;
979 do {
980 iter++;
981 status = gsl_multimin_fminimizer_iterate(minimizer);
982 if (status)
983 break;
984
985 double size = gsl_multimin_fminimizer_size(minimizer);
986 status = gsl_multimin_test_size(size, 1e-4);
987
988 } while (status == GSL_CONTINUE && iter < MAX_ITER);
989
990 // only update calibration values on successful fit
991 double errsum = 0.; // return 0. if fit didn't work
992 std::string status_msg = gsl_strerror(status);
993 if (status_msg == "success") {
994 difc = gsl_vector_get(minimizer->x, 0);
995 if (numParams > 1) {
996 t0 = gsl_vector_get(minimizer->x, 1);
997 if (numParams > 2) {
998 difa = gsl_vector_get(minimizer->x, 2);
999 }
1000 }
1001 // return from gsl_costFunction can be accessed as fval
1002 errsum = minimizer->fval;
1003 }
1004
1005 // free memory
1006 gsl_vector_free(fitParams);
1007 gsl_vector_free(stepSizes);
1008 gsl_multimin_fminimizer_free(minimizer);
1009 return errsum;
1010}
1011
1012} // end of anonymous namespace
1013
1028void PDCalibration::fitDIFCtZeroDIFA_LM(const std::vector<double> &d, const std::vector<double> &tof,
1029 const std::vector<double> &weights, double &difc, double &t0, double &difa) {
1030 const size_t numPeaks = d.size();
1031 if (numPeaks <= 1) {
1032 return; // don't do anything
1033 }
1034 // number of fit parameters 1=[DIFC], 2=[DIFC,TZERO], 3=[DIFC,TZERO,DIFA]
1035 // set the maximum number of parameters that will be used
1036 // statistics doesn't support having too few peaks
1037 size_t maxParams = std::min<size_t>(numPeaks - 1, m_numberMaxParams);
1038
1039 // this must have the same layout as the unpacking in gsl_costFunction above
1040 // `peaks` has the following structure for a fit session with three peaks
1041 // and two fit parameters:
1042 // 3, 2, tof1, tof2, tof3, d1, d2, d3, h21, h22, h23
1043 std::vector<double> peaks(numPeaks * 3 + 2, 0.);
1044 peaks[0] = static_cast<double>(d.size());
1045 peaks[1] = 1.; // number of parameters to fit. Initialize to just one
1046 for (size_t i = 0; i < numPeaks; ++i) {
1047 peaks[i + 2] = tof[i];
1048 peaks[i + 2 + numPeaks] = d[i];
1049 peaks[i + 2 + 2 * numPeaks] = weights[i];
1050 }
1051
1052 // calculate a starting DIFC
1053 double difc_start = difc;
1054 if (difc_start == 0.) {
1055 const double d_sum = std::accumulate(d.begin(), d.end(), 0.);
1056 const double tof_sum = std::accumulate(tof.begin(), tof.end(), 0.);
1057 difc_start = tof_sum / d_sum; // number of peaks falls out of division
1058 }
1059
1060 // save the best values so far
1061 double best_errsum = std::numeric_limits<double>::max();
1062 double best_difc = difc_start;
1063 double best_t0 = 0.;
1064 double best_difa = 0.;
1065
1066 // loop over possible number of parameters, doing up to three sequential fits.
1067 // We first start with equation TOF = DIFC * d and obtain
1068 // optimized DIFC which serves as initial guess for next fit with equation
1069 // TOF = DIFC * d + TZERO, obtaining optimized DIFC and TZERO which serve as
1070 // initial guess for final fit TOF = DIFC * d + DIFA * d^2 + TZERO.
1071 for (size_t numParams = 1; numParams <= maxParams; ++numParams) {
1072 peaks[1] = static_cast<double>(numParams);
1073 double difc_local = best_difc;
1074 double t0_local = best_t0;
1075 double difa_local = best_difa;
1076 double errsum = fitDIFCtZeroDIFA(peaks, difc_local, t0_local, difa_local);
1077 if (errsum > 0.) {
1078 // normalize by degrees of freedom
1079 errsum = errsum / static_cast<double>(numPeaks - numParams);
1080 // save the best and forget the rest
1081 // the selected (DIFC, DIFA, TZERO) correspond to those of the fit that
1082 // minimizes errsum. It doesn't have to be the last fit.
1083 if (errsum < best_errsum) {
1084 if (difa_local > m_difaMax || difa_local < m_difaMin)
1085 continue; // unphysical fit
1086 if (t0_local > m_tzeroMax || t0_local < m_tzeroMin)
1087 continue; // unphysical fit
1088 best_errsum = errsum;
1089 best_difc = difc_local;
1090 best_t0 = t0_local;
1091 best_difa = difa_local;
1092 }
1093 }
1094 }
1095
1096 difc = best_difc;
1097 // check that something actually fit and set to the best result
1098 if (best_difc > 0. && best_errsum < std::numeric_limits<double>::max()) {
1099 t0 = best_t0;
1100 difa = best_difa;
1101 }
1102}
1103
1113vector<double> PDCalibration::dSpacingWindows(const std::vector<double> &centres,
1114 const std::vector<double> &windows_in) {
1115
1116 if (!(windows_in.size() == 1 || windows_in.size() / 2 == centres.size()))
1117 throw std::logic_error("the peak-window vector must contain either a single peak-width value, or a pair of values "
1118 "for each peak center specified");
1119
1120 const std::size_t numPeaks = centres.size();
1121
1122 // assumes distance between peaks can be used for window sizes
1123 if (!(numPeaks >= 2))
1124 throw std::logic_error("at least two peak centres must be specified: the distance between these centres will be "
1125 "used to estimate the peak widths");
1126
1127 vector<double> windows_out(2 * numPeaks);
1128 double left;
1129 double right;
1130 for (std::size_t i = 0; i < numPeaks; ++i) {
1131 if (windows_in.size() == 1) {
1132 left = centres[i] - windows_in[0];
1133 right = centres[i] + windows_in[0];
1134 } else {
1135 left = windows_in[2 * i];
1136 right = windows_in[2 * i + 1];
1137 }
1138 // check boundaries don't exceed half dist to adjacent peaks
1139 if (i > 0) {
1140 left = std::max(left, centres[i] - 0.5 * (centres[i] - centres[i - 1]));
1141 }
1142 if (i < numPeaks - 1) {
1143 right = std::min(right, centres[i] + 0.5 * (centres[i + 1] - centres[i]));
1144 }
1145 // set the windows
1146 windows_out[2 * i] = left;
1147 windows_out[2 * i + 1] = right;
1148 }
1149 return windows_out;
1150}
1151
1159std::tuple<double, double, double> PDCalibration::getDSpacingToTof(const std::set<detid_t> &detIds) {
1160 // to start this is the old calibration values
1161 double difc = 0.;
1162 double difa = 0.;
1163 double tzero = 0.;
1164 for (auto detId : detIds) {
1165 auto rowNum = m_detidToRow[detId];
1166 difc += m_calibrationTable->getRef<double>("difc", rowNum);
1167 difa += m_calibrationTable->getRef<double>("difa", rowNum);
1168 tzero += m_calibrationTable->getRef<double>("tzero", rowNum);
1169 }
1170 if (detIds.size() > 1) {
1171 double norm = 1. / static_cast<double>(detIds.size());
1172 difc = norm * difc;
1173 difa = norm * difa;
1174 tzero = norm * tzero;
1175 }
1176
1177 return {difc, difa, tzero};
1178}
1179
1180void PDCalibration::setCalibrationValues(const detid_t detid, const double difc, const double difa,
1181 const double tzero) {
1182 // don't set values that aren't in the table
1183 const auto rowIter = m_detidToRow.find(detid);
1184 if (rowIter == m_detidToRow.end())
1185 return;
1186
1187 // get the row number
1188 auto rowNum = rowIter->second;
1189
1190 // detid is already there
1191 m_calibrationTable->cell<double>(rowNum, 1) = difc;
1192 m_calibrationTable->cell<double>(rowNum, 2) = difa;
1193 m_calibrationTable->cell<double>(rowNum, 3) = tzero;
1194
1195 size_t hasDasIdsOffset = 0; // because it adds a column
1196 if (m_hasDasIds)
1197 hasDasIdsOffset++;
1198
1199 const auto tofMinMax = getTOFminmax(difc, difa, tzero);
1200 m_calibrationTable->cell<double>(rowNum, 4 + hasDasIdsOffset) = tofMinMax[0];
1201 m_calibrationTable->cell<double>(rowNum, 5 + hasDasIdsOffset) = tofMinMax[1];
1202}
1203
1212vector<double> PDCalibration::getTOFminmax(const double difc, const double difa, const double tzero) {
1213 vector<double> tofminmax(2);
1214
1215 Kernel::Units::dSpacing dSpacingUnit;
1216 tofminmax[0] = dSpacingUnit.calcTofMin(difc, difa, tzero, m_tofMin);
1217 tofminmax[1] = dSpacingUnit.calcTofMax(difc, difa, tzero, m_tofMax);
1218
1219 return tofminmax;
1220}
1221MatrixWorkspace_sptr PDCalibration::load(const std::string &filename) {
1222 // TODO this assumes that all files are event-based
1223 const double maxChunkSize = getProperty("MaxChunkSize");
1224 const double filterBadPulses = getProperty("FilterBadPulses");
1225
1226 auto alg = createChildAlgorithm("LoadEventAndCompress");
1227 alg->setLoggingOffset(1);
1228 alg->setProperty("Filename", filename);
1229 alg->setProperty("MaxChunkSize", maxChunkSize);
1230 alg->setProperty("FilterByTofMin", m_tofMin);
1231 alg->setProperty("FilterByTofMax", m_tofMax);
1232 alg->setProperty("FilterBadPulses", filterBadPulses);
1233 alg->setProperty("LoadMonitors", false);
1234 alg->executeAsChildAlg();
1235 API::Workspace_sptr workspace = alg->getProperty("OutputWorkspace");
1236
1237 return std::dynamic_pointer_cast<MatrixWorkspace>(workspace);
1238}
1239
1245
1247 g_log.information("Binning data in time-of-flight");
1248 auto rebin = createChildAlgorithm("Rebin");
1249 rebin->setLoggingOffset(1);
1250 rebin->setProperty("InputWorkspace", wksp);
1251 rebin->setProperty("OutputWorkspace", wksp);
1252 rebin->setProperty("Params", getPropertyValue("TofBinning"));
1253 rebin->setProperty("PreserveEvents", true);
1254 rebin->executeAsChildAlg();
1255 wksp = rebin->getProperty("OutputWorkspace");
1256
1257 return wksp;
1258}
1259
1261 std::set<detid_t> detids;
1262
1263 // return early since everything is being used
1264 if (isDefault("StartWorkspaceIndex") && isDefault("StopWorkspaceIndex"))
1265 return detids;
1266
1267 // get the indices to loop over
1268 std::size_t startIndex = static_cast<std::size_t>(m_startWorkspaceIndex);
1269 std::size_t stopIndex = static_cast<std::size_t>(m_stopWorkspaceIndex);
1270
1271 for (std::size_t i = startIndex; i <= stopIndex; ++i) {
1272 const auto detidsForSpectrum = m_uncalibratedWS->getSpectrum(i).getDetectorIDs();
1273 for (const auto &detid : detidsForSpectrum) {
1274 detids.emplace(detid);
1275 }
1276 }
1277 return detids;
1278}
1279
1281 // create a new workspace
1282 m_calibrationTable = std::make_shared<DataObjects::TableWorkspace>();
1283 // TODO m_calibrationTable->setTitle("");
1284 m_calibrationTable->addColumn("int", "detid");
1285 m_calibrationTable->addColumn("double", "difc");
1286 m_calibrationTable->addColumn("double", "difa");
1287 m_calibrationTable->addColumn("double", "tzero");
1288 if (m_hasDasIds)
1289 m_calibrationTable->addColumn("int", "dasid");
1290 m_calibrationTable->addColumn("double", "tofmin");
1291 m_calibrationTable->addColumn("double", "tofmax");
1292}
1293
1308 API::ITableWorkspace_sptr calibrationTableOld = getProperty("PreviousCalibrationTable");
1309 if (calibrationTableOld == nullptr) {
1310 // load from file
1311 std::string filename = getProperty("PreviousCalibrationFile");
1312 auto alg = createChildAlgorithm("LoadDiffCal");
1313 alg->setLoggingOffset(1);
1314 alg->setProperty("Filename", filename);
1315 alg->setProperty("WorkspaceName", "NOMold"); // TODO
1316 alg->setProperty("MakeGroupingWorkspace", false);
1317 alg->setProperty("MakeMaskWorkspace", false);
1318 alg->setProperty("TofMin", m_tofMin);
1319 alg->setProperty("TofMax", m_tofMax);
1320 alg->executeAsChildAlg();
1321 calibrationTableOld = alg->getProperty("OutputCalWorkspace");
1322 }
1323
1324 m_hasDasIds = hasDasIDs(calibrationTableOld);
1325
1326 // create a new workspace
1327 this->createCalTableHeader();
1328
1329 const auto includedDetids = detIdsForTable();
1330 const bool useAllDetids = includedDetids.empty();
1331
1332 // generate the map of detid -> row
1333 API::ColumnVector<int> detIDs = calibrationTableOld->getVector("detid");
1334 std::size_t rowNum = 0;
1335 for (std::size_t i = 0; i < detIDs.size(); ++i) {
1336 // only add rows for detids that exist in input workspace
1337 if ((useAllDetids) || (includedDetids.count(detIDs[i]) > 0)) {
1338 m_detidToRow[static_cast<detid_t>(detIDs[i])] = rowNum++;
1339 API::TableRow newRow = m_calibrationTable->appendRow();
1340 int detid = calibrationTableOld->getRef<int>("detid", i);
1341 double difc = calibrationTableOld->getRef<double>("difc", i);
1342 double difa = calibrationTableOld->getRef<double>("difa", i);
1343 double tzero = calibrationTableOld->getRef<double>("tzero", i);
1344
1345 newRow << detid << difc << difa << tzero;
1346 if (m_hasDasIds)
1347 newRow << calibrationTableOld->getRef<int>("dasid", i);
1348
1349 // adjust tofmin and tofmax for this pixel
1350 const auto tofMinMax = getTOFminmax(difc, difa, tzero);
1351 newRow << tofMinMax[0] << tofMinMax[1]; // tofmin/tofmax
1352 }
1353 }
1354}
1355
1363 // create new calibraion table for when an old one isn't loaded
1364 // using the signal workspace and CalculateDIFC
1365 auto alg = createChildAlgorithm("CalculateDIFC");
1366 alg->setLoggingOffset(1);
1367 alg->setProperty("InputWorkspace", m_uncalibratedWS);
1368 alg->executeAsChildAlg();
1369 API::MatrixWorkspace_const_sptr difcWS = alg->getProperty("OutputWorkspace");
1370
1371 // create a new workspace
1372 this->createCalTableHeader();
1373
1374 const detid2index_map allDetectors = difcWS->getDetectorIDToWorkspaceIndexMap(false);
1375
1376 const auto includedDetids = detIdsForTable();
1377 const bool useAllDetids = includedDetids.empty();
1378
1379 // copy over the values
1380 size_t i = 0;
1381 for (auto it = allDetectors.begin(); it != allDetectors.end(); ++it) {
1382 const detid_t detID = it->first;
1383 // only add rows for detids that exist in input workspace
1384 if (useAllDetids || (includedDetids.count(detID) > 0)) {
1385 m_detidToRow[detID] = i++;
1386 const size_t wi = it->second;
1387 API::TableRow newRow = m_calibrationTable->appendRow();
1388 newRow << detID;
1389 newRow << difcWS->y(wi)[0];
1390 newRow << 0.; // difa
1391 newRow << 0.; // tzero
1392 newRow << 0.; // tofmin
1393 newRow << DBL_MAX; // tofmax
1394 }
1395 }
1396}
1397
1404 // table for the fitted location of the various peaks, in d-spacing units
1405 m_peakPositionTable = std::make_shared<DataObjects::TableWorkspace>();
1406 m_peakWidthTable = std::make_shared<DataObjects::TableWorkspace>();
1407 m_peakHeightTable = std::make_shared<DataObjects::TableWorkspace>();
1408
1409 m_peakPositionTable->addColumn("int", "detid");
1410 m_peakWidthTable->addColumn("int", "detid");
1411 m_peakHeightTable->addColumn("int", "detid");
1412
1413 for (double dSpacing : m_peaksInDspacing) {
1414 std::stringstream namess;
1415 namess << "@" << std::setprecision(5) << dSpacing;
1416 m_peakPositionTable->addColumn("double", namess.str());
1417 m_peakWidthTable->addColumn("double", namess.str());
1418 m_peakHeightTable->addColumn("double", namess.str());
1419 }
1420 m_peakPositionTable->addColumn("double", "chisq");
1421 m_peakPositionTable->addColumn("double", "normchisq");
1422 // residuals aren't needed for FWHM or height
1423
1424 // convert the map of m_detidToRow to be a vector of detector ids
1425 std::vector<detid_t> detIds(m_detidToRow.size());
1426 for (const auto &it : m_detidToRow) {
1427 detIds[it.second] = it.first;
1428 }
1429
1430 // copy the detector ids from the main table and add lots of NaNs
1431 for (const auto &detId : detIds) {
1432 API::TableRow newPosRow = m_peakPositionTable->appendRow();
1433 API::TableRow newWidthRow = m_peakWidthTable->appendRow();
1434 API::TableRow newHeightRow = m_peakHeightTable->appendRow();
1435
1436 newPosRow << detId;
1437 newWidthRow << detId;
1438 newHeightRow << detId;
1439
1440 for (double dSpacing : m_peaksInDspacing) {
1441 UNUSED_ARG(dSpacing);
1442 newPosRow << std::nan("");
1443 newWidthRow << std::nan("");
1444 newHeightRow << std::nan("");
1445 }
1446 }
1447}
1448
1451 std::make_shared<DataObjects::SpecialWorkspace2D>(m_uncalibratedWS->getInstrument());
1452 resolutionWksp->setTitle("average width/height");
1453
1454 // assume both tables have the same number of rows b/c the algorithm created
1455 // both
1456 // they are also in the same order
1457 // accessing cells is done by (row, col)
1458 const size_t numRows = m_peakPositionTable->rowCount();
1459 const size_t numPeaks = m_peaksInDspacing.size();
1460 std::vector<double> resolution; // vector of non-nan resolutions
1461 for (size_t rowIndex = 0; rowIndex < numRows; ++rowIndex) {
1462 resolution.clear();
1463 // first column is detid
1464 const auto detId = static_cast<detid_t>(m_peakPositionTable->Int(rowIndex, 0));
1465 for (size_t peakIndex = 1; peakIndex < numPeaks + 1; ++peakIndex) {
1466 const double pos = m_peakPositionTable->Double(rowIndex, peakIndex);
1467 if (std::isnormal(pos)) {
1468 resolution.emplace_back(m_peakWidthTable->Double(rowIndex, peakIndex) / pos);
1469 }
1470 }
1471 if (resolution.empty()) {
1472 resolutionWksp->setValue(detId, 0., 0.); // instview doesn't like nan
1473 } else {
1474 // calculate the mean
1475 const double mean =
1476 std::accumulate(resolution.begin(), resolution.end(), 0.) / static_cast<double>(resolution.size());
1477 double stddev = 0.;
1478 std::for_each(resolution.cbegin(), resolution.cend(),
1479 [&stddev, mean](const auto value) { stddev += (value - mean) * (value * mean); });
1480 stddev = std::sqrt(stddev / static_cast<double>(resolution.size() - 1));
1481 resolutionWksp->setValue(detId, mean, stddev);
1482 }
1483 }
1484
1485 return resolutionWksp;
1486}
1487
1490 auto alg = createChildAlgorithm("SortTableWorkspace");
1491 alg->setLoggingOffset(1);
1492 alg->setProperty("InputWorkspace", table);
1493 alg->setProperty("OutputWorkspace", table);
1494 alg->setProperty("Columns", "detid");
1495 alg->executeAsChildAlg();
1496 table = alg->getProperty("OutputWorkspace");
1497
1498 return table;
1499}
1500
1515std::pair<API::MatrixWorkspace_sptr, API::MatrixWorkspace_sptr>
1517 const std::vector<double> &peakWindow) {
1518
1519 // calculate fitting ranges to the left and right of each nominal peak
1520 // center, in d-spacing units
1521 const auto windowsInDSpacing = dSpacingWindows(m_peaksInDspacing, peakWindow);
1522
1523 g_log.information() << "DSPACING WINDOWS\n";
1524 for (std::size_t i = 0; i < m_peaksInDspacing.size(); ++i) {
1525 g_log.information() << "[" << i << "] " << windowsInDSpacing[2 * i] << " < " << m_peaksInDspacing[i] << " < "
1526 << windowsInDSpacing[2 * i + 1] << "\n";
1527 }
1528
1529 // create workspaces for nominal peak centers and fit ranges
1530 size_t numspec = dataws->getNumberHistograms();
1531 size_t numpeaks = m_peaksInDspacing.size();
1532 MatrixWorkspace_sptr peak_pos_ws = create<Workspace2D>(numspec, Points(numpeaks));
1533 MatrixWorkspace_sptr peak_window_ws = create<Workspace2D>(numspec, Points(numpeaks * 2));
1534
1535 const auto NUM_HIST = m_stopWorkspaceIndex - m_startWorkspaceIndex + 1;
1536 API::Progress prog(this, 0., .2, NUM_HIST);
1537
1538 g_log.information() << "TOF WINDOWS\n";
1539 PRAGMA_OMP(parallel for schedule(dynamic, 1) )
1540 for (int64_t iiws = m_startWorkspaceIndex; iiws <= static_cast<int64_t>(m_stopWorkspaceIndex); iiws++) {
1542 std::size_t iws = static_cast<std::size_t>(iiws);
1543 // calculatePositionWindowInTOF
1544 PDCalibration::FittedPeaks peaks(dataws, iws);
1545 // toTof is a function that converts from d-spacing to TOF for a particular
1546 // pixel
1547 auto [difc, difa, tzero] = getDSpacingToTof(peaks.detid);
1548 // setpositions initializes peaks.inTofPos and peaks.inTofWindows
1549 peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, difa, difc, tzero);
1550 peak_pos_ws->setPoints(iws, peaks.inTofPos);
1551 peak_window_ws->setPoints(iws, peaks.inTofWindows);
1552 prog.report();
1553
1554 for (std::size_t i = 0; i < peaks.inTofPos.size(); i++) {
1555 g_log.information() << "[" << iws << "," << i << "] " << peaks.inTofWindows[2 * i] << " < " << peaks.inTofPos[i]
1556 << " < " << peaks.inTofWindows[2 * i + 1] << "\n";
1557 }
1558
1560 }
1562
1563 return std::make_pair(peak_pos_ws, peak_window_ws);
1564}
1565
1566} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
double value
The value of the point.
Definition FitMW.cpp:51
double height
Definition GetAllEi.cpp:155
IPeaksWorkspace_sptr workspace
double left
double right
#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 PRAGMA_OMP(expression)
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:44
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Kernel::Logger & g_log
Definition Algorithm.h:422
bool isDefault(const std::string &name) const
ColumnVector gives access to the column elements without alowing its resizing.
size_t size()
Size of the vector.
A specialized class for dealing with file properties.
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
Base MatrixWorkspace Abstract Class.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
TableRow represents a row in a TableWorkspace.
Definition TableRow.h:39
A property class for workspaces.
void setPositions(const std::vector< double > &peaksInD, const std::vector< double > &peaksInDWindows, const double difa, const double difc, const double tzero)
Store the positions of the peak centers, as well as the left and right fit ranges for each peak,...
FittedPeaks(const API::MatrixWorkspace_const_sptr &wksp, const std::size_t wkspIndex)
Find the bins with non-zero counts and with minimum and maximum TOF.
API::ITableWorkspace_sptr m_peakWidthTable
std::map< detid_t, size_t > m_detidToRow
API::ITableWorkspace_sptr m_peakHeightTable
void setCalibrationValues(const detid_t detid, const double difc, const double difa, const double tzero)
API::MatrixWorkspace_sptr m_uncalibratedWS
API::MatrixWorkspace_sptr rebin(API::MatrixWorkspace_sptr wksp)
int m_stopWorkspaceIndex
stop index (workspace index of the last spectrum included)
API::ITableWorkspace_sptr sortTableWorkspace(API::ITableWorkspace_sptr &table)
sort the calibration table according increasing values in column "detid"
std::vector< double > dSpacingWindows(const std::vector< double > &centres, const std::vector< double > &widthMax)
Fitting ranges to the left and right of peak center (the window cannot exceed half the distance to th...
const std::string category() const override
Algorithm's category for identification.
std::pair< API::MatrixWorkspace_sptr, API::MatrixWorkspace_sptr > createTOFPeakCenterFitWindowWorkspaces(const API::MatrixWorkspace_sptr &dataws, const std::vector< double > &peakWindowMaxInDSpacing)
NEW: convert peak positions in dSpacing to peak centers workspace.
API::ITableWorkspace_sptr m_calibrationTable
void init() override
Initialize the algorithm's properties.
API::MatrixWorkspace_sptr loadAndBin()
load input workspace and rebin with parameters "TofBinning" provided by User
std::vector< double > getTOFminmax(const double difc, const double difa, const double tzero)
Adjustment of TofMin and TofMax values, to ensure positive values of d-spacing when converting from T...
void createInformationWorkspaces()
Table workspaces where the first column is the detector ID and subsequent columns are termed "@x....
~PDCalibration() override
Destructor.
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
const std::string name() const override
Algorithms name for identification.
void fitDIFCtZeroDIFA_LM(const std::vector< double > &d, const std::vector< double > &tof, const std::vector< double > &height2, double &difc, double &t0, double &difa)
Fit the nominal peak center positions, in d-spacing against the fitted peak center positions,...
double m_tofMin
first bin boundary when rebinning in TOF (user input)
std::tuple< double, double, double > getDSpacingToTof(const std::set< detid_t > &detIds)
Return a function that converts from d-spacing to TOF units for a particular pixel,...
API::MatrixWorkspace_sptr load(const std::string &filename)
void createCalTableNew()
Initialize the calibration table workspace.
API::MatrixWorkspace_sptr calculateResolutionTable()
int version() const override
Algorithm's version for identification.
const std::string summary() const override
Algorithm's summary for use in the GUI and help.
API::ITableWorkspace_sptr m_peakPositionTable
std::vector< double > m_peaksInDspacing
void exec() override
Execute the algorithm.
void createCalTableFromExisting()
Read a calibration table workspace provided by user, or load from a file provided by User.
double m_tofMax
last bin boundary when rebinning in TOF (user input)
This class is intended to fulfill the design specified in <https://github.com/mantidproject/documents...
Kernel/ArrayBoundedValidator.h.
Support for a property that holds an array of values.
BoundedValidator is a validator that requires the values to be between upper or lower bounds,...
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void setPropertyGroup(const std::string &name, const std::string &group)
Set the group for a given property.
ListValidator is a validator that requires the value of a property to be one of a defined list of pos...
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
std::ostream & getLogStream(const Priority &priority)
gets the correct log stream for a priority
Definition Logger.cpp:404
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
Validator to check that a property is not left empty.
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
Validator to check the format of a vector providing the rebin parameters to an algorithm.
void initialize(const double &_l1, const int &_emode, const UnitParametersMap &params)
Initialize the unit to perform conversion using singleToTof() and singleFromTof()
Definition Unit.cpp:133
void toTOF(std::vector< double > &xdata, std::vector< double > const &ydata, const double &_l1, const int &_emode, std::initializer_list< std::pair< const UnitParams, double > > params)
Convert from the concrete unit to time-of-flight.
Definition Unit.cpp:148
d-Spacing in Angstrom
Definition Unit.h:351
double calcTofMax(const double difc, const double difa, const double tzero, const double tofmax=0.)
Definition Unit.cpp:782
double calcTofMin(const double difc, const double difa, const double tzero, const double tofmin=0.)
Definition Unit.cpp:775
double singleFromTOF(const double tof) const override
DIFA * d^2 + DIFC * d + T0 - TOF = 0.
Definition Unit.cpp:696
double singleToTOF(const double x) const override
Convert a single X value to TOF.
Definition Unit.cpp:670
std::shared_ptr< ITableWorkspace > ITableWorkspace_sptr
shared pointer to Mantid::API::ITableWorkspace
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
Mantid::Kernel::SingletonHolder< AnalysisDataServiceImpl > AnalysisDataService
std::shared_ptr< const ITableWorkspace > ITableWorkspace_const_sptr
shared pointer to Mantid::API::ITableWorkspace (const version)
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
static double gsl_costFunction(const gsl_vector *v, void *params)
The gsl_costFunction is optimized by GSL simplex.
std::shared_ptr< const MaskWorkspace > MaskWorkspace_const_sptr
shared pointer to a const MaskWorkspace
std::shared_ptr< MaskWorkspace > MaskWorkspace_sptr
shared pointer to the MaskWorkspace class
std::shared_ptr< SpecialWorkspace2D > SpecialWorkspace2D_sptr
shared pointer to the SpecialWorkspace2D class
std::shared_ptr< const Instrument > Instrument_const_sptr
Shared pointer to an const instrument object.
std::unordered_map< UnitParams, double > UnitParametersMap
Definition Unit.h:30
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
int32_t detid_t
Typedef for a detector ID.
std::unordered_map< detid_t, size_t > detid2index_map
Map with key = detector ID, value = workspace index.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
Enumeration for a mandatory/optional property.
Describes the direction (within an algorithm) of a Property.
Definition Property.h:50
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54