Mantid
Loading...
Searching...
No Matches
FitPeaks.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 +
7//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
11#include "MantidAPI/Axis.h"
21#include "MantidAPI/TableRow.h"
29#include "MantidHistogramData/EstimatePolynomial.h"
30#include "MantidHistogramData/Histogram.h"
31#include "MantidHistogramData/HistogramBuilder.h"
32#include "MantidHistogramData/HistogramIterator.h"
39
40#include "MantidAPI/Column.h"
41#include "boost/algorithm/string.hpp"
42#include "boost/algorithm/string/trim.hpp"
43#include <cmath>
44#include <limits>
45#include <utility>
46
47using namespace Mantid;
48using namespace Algorithms::PeakParameterHelper;
49using namespace Mantid::API;
50using namespace Mantid::DataObjects;
51using namespace Mantid::HistogramData;
52using namespace Mantid::Kernel;
53using namespace Mantid::Geometry;
54using Mantid::HistogramData::Histogram;
55using namespace std;
56
57namespace Mantid::Algorithms {
58
59namespace {
60namespace PropertyNames {
61const std::string INPUT_WKSP("InputWorkspace");
62const std::string OUTPUT_WKSP("OutputWorkspace");
63const std::string START_WKSP_INDEX("StartWorkspaceIndex");
64const std::string STOP_WKSP_INDEX("StopWorkspaceIndex");
65const std::string PEAK_CENTERS("PeakCenters");
66const std::string PEAK_CENTERS_WKSP("PeakCentersWorkspace");
67const std::string PEAK_FUNC("PeakFunction");
68const std::string BACK_FUNC("BackgroundType");
69const std::string FIT_WINDOW_LIST("FitWindowBoundaryList");
70const std::string FIT_WINDOW_WKSP("FitPeakWindowWorkspace");
71const std::string PEAK_WIDTH_PERCENT("PeakWidthPercent");
72const std::string PEAK_PARAM_NAMES("PeakParameterNames");
73const std::string PEAK_PARAM_VALUES("PeakParameterValues");
74const std::string PEAK_PARAM_TABLE("PeakParameterValueTable");
75const std::string FIT_FROM_RIGHT("FitFromRight");
76const std::string MINIMIZER("Minimizer");
77const std::string COST_FUNC("CostFunction");
78const std::string STRICT_CONVERGENCE("StrictConvergence");
79const std::string MAX_FIT_ITER("MaxFitIterations");
80const std::string BACKGROUND_Z_SCORE("FindBackgroundSigma");
81const std::string HIGH_BACKGROUND("HighBackground");
82const std::string POSITION_TOL("PositionTolerance");
83const std::string POSITION_TOL_MODE("PositionToleranceMode");
84const std::string POSITION_TOL_FRACTIONAL("PositionToleranceFractional");
85const std::string PEAK_MIN_HEIGHT("MinimumPeakHeight");
86const std::string CONSTRAIN_PEAK_POS("ConstrainPeakPositions");
87const std::string CALC_UNCONSTRAINED_ERRORS("CalculateUnconstrainedErrors");
88const std::string COPY_LAST_GOOD_PEAK_PARAMS("CopyLastGoodPeakParameters");
89const std::string RESPECT_FIXED_PEAK_PARAMS("RespectFixedPeakParameters");
90const std::string OUTPUT_WKSP_MODEL("FittedPeaksWorkspace");
91const std::string OUTPUT_WKSP_PARAMS("OutputPeakParametersWorkspace");
92const std::string OUTPUT_WKSP_PARAM_ERRS("OutputParameterFitErrorsWorkspace");
93const std::string RAW_PARAMS("RawPeakParameters");
94const std::string PEAK_MIN_SIGNAL_TO_NOISE_RATIO("MinimumSignalToNoiseRatio");
95const std::string PEAK_MIN_TOTAL_COUNT("MinimumPeakTotalCount");
96const std::string PEAK_MIN_SIGNAL_TO_SIGMA_RATIO("MinimumSignalToSigmaRatio");
97} // namespace PropertyNames
98} // namespace
99
100namespace FitPeaksAlgorithm {
101
102//----------------------------------------------------------------------------------------------
104PeakFitResult::PeakFitResult(size_t num_peaks, size_t num_params) : m_function_parameters_number(num_params) {
105 // check input
106 if (num_peaks == 0 || num_params == 0)
107 throw std::runtime_error("No peak or no parameter error.");
108
109 //
110 m_fitted_peak_positions.resize(num_peaks, std::numeric_limits<double>::quiet_NaN());
111 m_costs.resize(num_peaks, DBL_MAX);
112 m_function_parameters_vector.resize(num_peaks);
113 m_function_errors_vector.resize(num_peaks);
114 for (size_t ipeak = 0; ipeak < num_peaks; ++ipeak) {
115 m_function_parameters_vector[ipeak].resize(num_params, std::numeric_limits<double>::quiet_NaN());
116 m_function_errors_vector[ipeak].resize(num_params, std::numeric_limits<double>::quiet_NaN());
117 }
118
119 return;
120}
121
122//----------------------------------------------------------------------------------------------
124
126
127//----------------------------------------------------------------------------------------------
134double PeakFitResult::getParameterError(size_t ipeak, size_t iparam) const {
135 return m_function_errors_vector[ipeak][iparam];
136}
137
138//----------------------------------------------------------------------------------------------
145double PeakFitResult::getParameterValue(size_t ipeak, size_t iparam) const {
146 return m_function_parameters_vector[ipeak][iparam];
147}
148
149//----------------------------------------------------------------------------------------------
150double PeakFitResult::getPeakPosition(size_t ipeak) const { return m_fitted_peak_positions[ipeak]; }
151
152//----------------------------------------------------------------------------------------------
153double PeakFitResult::getCost(size_t ipeak) const { return m_costs[ipeak]; }
154
155//----------------------------------------------------------------------------------------------
157void PeakFitResult::setRecord(size_t ipeak, const double cost, const double peak_position,
158 const FitFunction &fit_functions) {
159 // check input
160 if (ipeak >= m_costs.size())
161 throw std::runtime_error("Peak index is out of range.");
162
163 // set the values
164 m_costs[ipeak] = cost;
165
166 // set peak position
167 m_fitted_peak_positions[ipeak] = peak_position;
168
169 // transfer from peak function to vector
170 size_t peak_num_params = fit_functions.peakfunction->nParams();
171 for (size_t ipar = 0; ipar < peak_num_params; ++ipar) {
172 // peak function
173 m_function_parameters_vector[ipeak][ipar] = fit_functions.peakfunction->getParameter(ipar);
174 m_function_errors_vector[ipeak][ipar] = fit_functions.peakfunction->getError(ipar);
175 }
176 for (size_t ipar = 0; ipar < fit_functions.bkgdfunction->nParams(); ++ipar) {
177 // background function
178 m_function_parameters_vector[ipeak][ipar + peak_num_params] = fit_functions.bkgdfunction->getParameter(ipar);
179 m_function_errors_vector[ipeak][ipar + peak_num_params] = fit_functions.bkgdfunction->getError(ipar);
180 }
181}
182
183//----------------------------------------------------------------------------------------------
188void PeakFitResult::setBadRecord(size_t ipeak, const double peak_position) {
189 // check input
190 if (ipeak >= m_costs.size())
191 throw std::runtime_error("Peak index is out of range");
192 if (peak_position >= 0.)
193 throw std::runtime_error("Can only set negative postion for bad record");
194
195 // set the values
196 m_costs[ipeak] = DBL_MAX;
197
198 // set peak position
199 m_fitted_peak_positions[ipeak] = peak_position;
200
201 // transfer from peak function to vector
202 for (size_t ipar = 0; ipar < m_function_parameters_number; ++ipar) {
203 m_function_parameters_vector[ipeak][ipar] = 0.;
204 m_function_errors_vector[ipeak][ipar] = std::numeric_limits<double>::quiet_NaN();
205 }
206}
207
219
221
223
225
227
229
231
233
235 // the method should be used on an individual peak, not on a spectrum
236 assert(m_submitted_spectrum_peaks == 0);
237 assert(m_submitted_individual_peaks == 1);
238
239 // if a peak is rejected, it is rejected based on the very first check it fails
240 size_t individual_rejection_count = m_low_count_individual + m_not_enough_datapoints + m_low_snr;
241 assert(individual_rejection_count <= 1);
242
243 return individual_rejection_count == 1;
244}
245
248
249 // if no peaks were rejected by the pre-check, keep quiet
251 return "";
252
253 std::ostringstream os;
254 os << "Total number of peaks pre-checked before fitting: " << m_submitted_spectrum_peaks << "\n";
255 if (m_low_count_spectrum > 0)
256 os << m_low_count_spectrum << " peak(s) rejected: low signal count (whole spectrum).\n";
257 if (m_out_of_range > 0)
258 os << m_out_of_range << " peak(s) rejected: out of range.\n";
260 os << m_not_enough_datapoints << " peak(s) rejected: not enough X(Y) datapoints.\n";
262 os << m_low_count_individual << " peak(s) rejected: low signal count (individual peak).\n";
263 if (m_low_snr > 0)
264 os << m_low_snr << " peak(s) rejected: low signal-to-noise ratio.\n";
265
266 return os.str();
267}
268} // namespace FitPeaksAlgorithm
269
270//----------------------------------------------------------------------------------------------
272 : m_fitPeaksFromRight(true), m_fitIterations(50), m_numPeaksToFit(0), m_minPeakHeight(0.),
273 m_minSignalToNoiseRatio(0.), m_minPeakTotalCount(0.), m_peakPosTolCase234(false) {}
274
275//----------------------------------------------------------------------------------------------
280 "Name of the input workspace for peak fitting.");
283 "Name of the output workspace containing peak centers for "
284 "fitting offset."
285 "The output workspace is point data."
286 "Each workspace index corresponds to a spectrum. "
287 "Each X value ranges from 0 to N-1, where N is the number of "
288 "peaks to fit. "
289 "Each Y value is the peak position obtained by peak fitting. "
290 "Negative value is used for error signals. "
291 "-1 for data is zero; -2 for maximum value is smaller than "
292 "specified minimum value."
293 "and -3 for non-converged fitting.");
294
295 // properties about fitting range and criteria
296 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
297 mustBePositive->setLower(0);
298 declareProperty(PropertyNames::START_WKSP_INDEX, 0, mustBePositive, "Starting workspace index for fit");
300 PropertyNames::STOP_WKSP_INDEX, EMPTY_INT(),
301 "Last workspace index for fit is the smaller of this value and the workspace index of last spectrum.");
302 // properties about peak positions to fit
303 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::PEAK_CENTERS),
304 "List of peak centers to use as initial guess for fit.");
306 std::make_unique<WorkspaceProperty<MatrixWorkspace>>(PropertyNames::PEAK_CENTERS_WKSP, "", Direction::Input,
308 "MatrixWorkspace containing referent peak centers for each spectrum, defined at the same workspace indices.");
309
310 const std::string peakcentergrp("Peak Positions");
311 setPropertyGroup(PropertyNames::PEAK_CENTERS, peakcentergrp);
312 setPropertyGroup(PropertyNames::PEAK_CENTERS_WKSP, peakcentergrp);
313
314 // properties about peak profile
315 const std::vector<std::string> peakNames = FunctionFactory::Instance().getFunctionNames<API::IPeakFunction>();
316 declareProperty(PropertyNames::PEAK_FUNC, "Gaussian", std::make_shared<StringListValidator>(peakNames),
317 "Use of a BackToBackExponential profile is only reccomended if the "
318 "coeficients to calculate A and B are defined in the instrument "
319 "Parameters.xml file.");
320 const vector<string> bkgdtypes{"Flat", "Linear", "Quadratic"};
321 declareProperty(PropertyNames::BACK_FUNC, "Linear", std::make_shared<StringListValidator>(bkgdtypes),
322 "Type of Background.");
323
324 const std::string funcgroup("Function Types");
325 setPropertyGroup(PropertyNames::PEAK_FUNC, funcgroup);
326 setPropertyGroup(PropertyNames::BACK_FUNC, funcgroup);
327
328 // properties about peak range including fitting window and peak width
329 // (percentage)
330 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::FIT_WINDOW_LIST),
331 "List of boundaries of the peak fitting window corresponding to "
332 "PeakCenters.");
333
334 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>(PropertyNames::FIT_WINDOW_WKSP, "",
336 "MatrixWorkspace containing peak windows for each peak center in each spectrum, defined at the same "
337 "workspace indices.");
338
339 auto min = std::make_shared<BoundedValidator<double>>();
340 min->setLower(1e-3);
341 // min->setUpper(1.); TODO make this a limit
342 declareProperty(PropertyNames::PEAK_WIDTH_PERCENT, EMPTY_DBL(), min,
343 "The estimated peak width as a "
344 "percentage of the d-spacing "
345 "of the center of the peak. Value must be less than 1.");
346
347 const std::string fitrangeegrp("Peak Range Setup");
348 setPropertyGroup(PropertyNames::PEAK_WIDTH_PERCENT, fitrangeegrp);
349 setPropertyGroup(PropertyNames::FIT_WINDOW_LIST, fitrangeegrp);
350 setPropertyGroup(PropertyNames::FIT_WINDOW_WKSP, fitrangeegrp);
351
352 // properties about peak parameters' names and value
353 declareProperty(std::make_unique<ArrayProperty<std::string>>(PropertyNames::PEAK_PARAM_NAMES),
354 "List of peak parameters' names");
355 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::PEAK_PARAM_VALUES),
356 "List of peak parameters' value");
357 // declared against the ITableWorkspace interface (not the concrete TableWorkspace) so a table can
358 // be supplied from Python, where table workspaces are exposed as ITableWorkspace
359 declareProperty(std::make_unique<WorkspaceProperty<ITableWorkspace>>(PropertyNames::PEAK_PARAM_TABLE, "",
361 "Name of the an optional workspace, whose each column "
362 "corresponds to given peak parameter names, "
363 "and each row corresponds to a subset of spectra.");
364
365 const std::string startvaluegrp("Starting Parameters Setup");
366 setPropertyGroup(PropertyNames::PEAK_PARAM_NAMES, startvaluegrp);
367 setPropertyGroup(PropertyNames::PEAK_PARAM_VALUES, startvaluegrp);
368 setPropertyGroup(PropertyNames::PEAK_PARAM_TABLE, startvaluegrp);
369
370 // optimization setup
371 declareProperty(PropertyNames::FIT_FROM_RIGHT, true,
372 "Flag for the order to fit peaks. If true, peaks are fitted "
373 "from rightmost;"
374 "Otherwise peaks are fitted from leftmost.");
375
376 const std::vector<std::string> minimizerOptions = API::FuncMinimizerFactory::Instance().getKeys();
377 declareProperty(PropertyNames::MINIMIZER, "Levenberg-Marquardt",
379 "Minimizer to use for fitting.");
380
381 const std::array<string, 3> costFuncOptions = {{"Least squares", "Rwp", "Unweighted least squares"}};
382 declareProperty(PropertyNames::COST_FUNC, "Least squares",
383 Kernel::IValidator_sptr(new Kernel::ListValidator<std::string>(costFuncOptions)), "Cost functions");
384
385 declareProperty(PropertyNames::STRICT_CONVERGENCE, true,
386 "If true, a peak fit is only accepted when the minimizer reports the exact status "
387 "'success'. If false, fits that stop because the changes in function or parameter "
388 "value have become too small are also accepted as converged.");
389
390 auto min_max_iter = std::make_shared<BoundedValidator<int>>();
391 min_max_iter->setLower(49);
392 declareProperty(PropertyNames::MAX_FIT_ITER, 50, min_max_iter, "Maximum number of function fitting iterations.");
393
394 const std::string optimizergrp("Optimization Setup");
395 setPropertyGroup(PropertyNames::MINIMIZER, optimizergrp);
396 setPropertyGroup(PropertyNames::COST_FUNC, optimizergrp);
397
398 // other helping information
399 std::ostringstream os;
400 os << "Deprecated property. Use " << PropertyNames::PEAK_MIN_SIGNAL_TO_NOISE_RATIO << " instead.";
401 declareProperty(PropertyNames::BACKGROUND_Z_SCORE, EMPTY_DBL(), os.str());
402
403 declareProperty(PropertyNames::HIGH_BACKGROUND, true,
404 "Flag whether the input data has high background compared to peak heights.");
405
406 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::POSITION_TOL),
407 "List of tolerance on fitted peak positions against given peak positions."
408 "If there is only one value given, then ");
409
410 const std::vector<std::string> posTolModes{"Check", "Constrain"};
411 declareProperty(PropertyNames::POSITION_TOL_MODE, "Check", std::make_shared<StringListValidator>(posTolModes),
412 "How PositionTolerance is applied. 'Check' (default): the tolerance is only a "
413 "post-fit acceptance criterion - a fitted centre further than the tolerance from "
414 "its expected position is rejected. 'Constrain': the tolerance additionally bounds "
415 "the peak centre during the fit (expected position +/- tolerance). Unlike "
416 "ConstrainPeakPositions, the reported position error is recomputed free of the "
417 "constraint penalty so it remains a genuine covariance error. Requires "
418 "PositionTolerance to be specified.");
419
420 declareProperty(PropertyNames::POSITION_TOL_FRACTIONAL, false,
421 "If true, each PositionTolerance value is interpreted as a fraction of this peak's "
422 "fit window width rather than an absolute value: the effective tolerance becomes "
423 "tolerance*(window_max - window_min). Because the fit window can differ per spectrum "
424 "(e.g. via FitPeakWindowWorkspace), this gives a per-spectrum tolerance. Applies to "
425 "both the 'Check' and 'Constrain' modes.");
426
427 declareProperty(PropertyNames::PEAK_MIN_HEIGHT, 0.,
428 "Used for validating peaks before and after fitting. If a peak's observed/estimated or "
429 "fitted height is under this value, the peak will be marked as error.");
430
431 declareProperty(PropertyNames::CONSTRAIN_PEAK_POS, true,
432 "If true peak position will be constrained by estimated positions "
433 "(highest Y value position) and "
434 "the peak width either estimted by observation or calculate.");
435
436 declareProperty(PropertyNames::CALC_UNCONSTRAINED_ERRORS, false,
437 "If true, and a peak-position constraint is applied during fitting "
438 "(ConstrainPeakPositions or PositionToleranceMode='Constrain'), the reported "
439 "parameter errors are recomputed from the unconstrained cost function at the fitted "
440 "values. A position constraint contributes curvature to the Hessian that the error "
441 "calculation inverts, which reduces the reported position error; enabling this option "
442 "instead reports the covariance error from the data alone. Costs one extra "
443 "(zero-iteration) fit per constrained peak.");
444
445 declareProperty(PropertyNames::COPY_LAST_GOOD_PEAK_PARAMS, true,
446 "If true, initial peak parameters (with the exception of peak centre) "
447 "may be copied from the last successfully fit peak in the spectra.");
448
449 declareProperty(PropertyNames::RESPECT_FIXED_PEAK_PARAMS, false,
450 "If true, peak function parameters that are marked as fixed "
451 "(e.g. parameters calculated from the instrument geometry, such as A and B "
452 "of a BackToBackExponential) remain fixed during fitting. "
453 "If false (default), such parameters are unfixed so they can be refined.");
454
455 // additional output for reviewing
456 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>(PropertyNames::OUTPUT_WKSP_MODEL, "",
458 "Name of the output matrix workspace with fitted peak. "
459 "This output workspace has the same dimension as the input workspace."
460 "The Y values belonged to peaks to fit are replaced by fitted value. "
461 "Values of estimated background are used if peak fails to be fit.");
462
463 declareProperty(std::make_unique<WorkspaceProperty<API::ITableWorkspace>>(PropertyNames::OUTPUT_WKSP_PARAMS, "",
465 "Name of table workspace containing all fitted peak parameters.");
466
467 // Optional output table workspace for each individual parameter's fitting
468 // error
469 declareProperty(std::make_unique<WorkspaceProperty<API::ITableWorkspace>>(PropertyNames::OUTPUT_WKSP_PARAM_ERRS, "",
471 "Name of workspace containing all fitted peak parameters' fitting error."
472 "It must be used along with FittedPeaksWorkspace and RawPeakParameters "
473 "(True)");
474
475 declareProperty(PropertyNames::RAW_PARAMS, true,
476 "false generates table with effective centre/width/height "
477 "parameters. true generates a table with peak function "
478 "parameters");
479
481 PropertyNames::PEAK_MIN_SIGNAL_TO_NOISE_RATIO, 0.,
482 "Used for validating peaks before fitting. If the signal-to-noise ratio is under this value, "
483 "the peak will be marked as error. This does not apply to peaks for which the noise cannot be estimated.");
484
485 declareProperty(PropertyNames::PEAK_MIN_TOTAL_COUNT, EMPTY_DBL(),
486 "Used for validating peaks before fitting. If the total peak window Y-value count "
487 "is under this value, the peak will be excluded from fitting and calibration.");
488
489 declareProperty(PropertyNames::PEAK_MIN_SIGNAL_TO_SIGMA_RATIO, 0.,
490 "Used for validating peaks after fitting. If the signal-to-sigma ratio is under this value, "
491 "the peak will be excluded from fitting and calibration.");
492
493 const std::string addoutgrp("Analysis");
494 setPropertyGroup(PropertyNames::OUTPUT_WKSP_PARAMS, addoutgrp);
495 setPropertyGroup(PropertyNames::OUTPUT_WKSP_MODEL, addoutgrp);
496 setPropertyGroup(PropertyNames::OUTPUT_WKSP_PARAM_ERRS, addoutgrp);
497 setPropertyGroup(PropertyNames::RAW_PARAMS, addoutgrp);
498}
499
500//----------------------------------------------------------------------------------------------
503std::map<std::string, std::string> FitPeaks::validateInputs() {
504 map<std::string, std::string> issues;
505
506 // check that min/max spectra indices make sense - only matters if both are specified
507 if (!(isDefault(PropertyNames::START_WKSP_INDEX) && isDefault(PropertyNames::STOP_WKSP_INDEX))) {
508 const int startIndex = getProperty(PropertyNames::START_WKSP_INDEX);
509 const int stopIndex = getProperty(PropertyNames::STOP_WKSP_INDEX);
510 if (startIndex > stopIndex) {
511 const std::string msg =
512 PropertyNames::START_WKSP_INDEX + " must be less than or equal to " + PropertyNames::STOP_WKSP_INDEX;
513 issues[PropertyNames::START_WKSP_INDEX] = msg;
514 issues[PropertyNames::STOP_WKSP_INDEX] = msg;
515 }
516 }
517
518 // Constrain mode bounds the centre by expected position +/- tolerance, so an explicit
519 // tolerance must be supplied - there is nothing to constrain against otherwise.
520 const std::string posTolMode = getPropertyValue(PropertyNames::POSITION_TOL_MODE);
521 if (posTolMode == "Constrain") {
522 const std::vector<double> posTolerances = getProperty(PropertyNames::POSITION_TOL);
523 if (posTolerances.empty()) {
524 issues[PropertyNames::POSITION_TOL] =
525 "PositionTolerance must be specified when PositionToleranceMode is 'Constrain'.";
526 }
527 // ConstrainPeakPositions and Constrain mode both bound the peak centre during fitting, so they
528 // are mutually exclusive. ConstrainPeakPositions defaults to true, so 'Constrain' mode requires
529 // it to be explicitly set to false - avoiding two overlapping constraints on the same parameter.
530 const bool constrainPeakPositions = getProperty(PropertyNames::CONSTRAIN_PEAK_POS);
531 if (constrainPeakPositions) {
532 const std::string msg = "PositionToleranceMode='Constrain' and ConstrainPeakPositions both "
533 "constrain the peak centre during fitting and are mutually exclusive. "
534 "Set ConstrainPeakPositions to false to use 'Constrain' mode.";
535 issues[PropertyNames::CONSTRAIN_PEAK_POS] = msg;
536 issues[PropertyNames::POSITION_TOL_MODE] = msg;
537 }
538 }
539
540 // check that the peak parameters are in parallel properties
541 bool haveCommonPeakParameters(false);
542 std::vector<string> suppliedParameterNames = getProperty(PropertyNames::PEAK_PARAM_NAMES);
543 std::vector<double> peakParamValues = getProperty(PropertyNames::PEAK_PARAM_VALUES);
544 if ((!suppliedParameterNames.empty()) || (!peakParamValues.empty())) {
545 haveCommonPeakParameters = true;
546 if (suppliedParameterNames.size() != peakParamValues.size()) {
547 issues[PropertyNames::PEAK_PARAM_NAMES] = "must have same number of values as PeakParameterValues";
548 issues[PropertyNames::PEAK_PARAM_VALUES] = "must have same number of values as PeakParameterNames";
549 }
550 }
551
552 // get the information out of the table
553 std::string partablename = getPropertyValue(PropertyNames::PEAK_PARAM_TABLE);
554 if (!partablename.empty()) {
555 if (haveCommonPeakParameters) {
556 const std::string msg = "Parameter value table and initial parameter "
557 "name/value vectors cannot be given "
558 "simultanenously.";
559 issues[PropertyNames::PEAK_PARAM_TABLE] = msg;
560 issues[PropertyNames::PEAK_PARAM_NAMES] = msg;
561 issues[PropertyNames::PEAK_PARAM_VALUES] = msg;
562 } else {
563 m_profileStartingValueTable = getProperty(PropertyNames::PEAK_PARAM_TABLE);
564 suppliedParameterNames = m_profileStartingValueTable->getColumnNames();
565 }
566 }
567
568 // check that the suggested peak parameter names exist in the peak function
569 if (!suppliedParameterNames.empty()) {
570 std::string peakfunctiontype = getPropertyValue(PropertyNames::PEAK_FUNC);
572 std::dynamic_pointer_cast<IPeakFunction>(API::FunctionFactory::Instance().createFunction(peakfunctiontype));
573
574 // put the names in a vector
575 std::vector<string> functionParameterNames;
576 for (size_t i = 0; i < m_peakFunction->nParams(); ++i)
577 functionParameterNames.emplace_back(m_peakFunction->parameterName(i));
578 // check that the supplied names are in the function
579 // it is acceptable to be missing parameters
580 const bool failed = std::any_of(suppliedParameterNames.cbegin(), suppliedParameterNames.cend(),
581 [&functionParameterNames](const auto &parName) {
582 return std::find(functionParameterNames.begin(), functionParameterNames.end(),
583 parName) == functionParameterNames.end();
584 });
585 if (failed) {
586 std::string msg = "Specified invalid parameter for peak function";
587 if (haveCommonPeakParameters)
588 issues[PropertyNames::PEAK_PARAM_NAMES] = msg;
589 else
590 issues[PropertyNames::PEAK_PARAM_TABLE] = msg;
591 }
592 }
593
594 // check inputs for uncertainty (fitting error)
595 const std::string error_table_name = getPropertyValue(PropertyNames::OUTPUT_WKSP_PARAM_ERRS);
596 if (!error_table_name.empty()) {
597 const bool use_raw_params = getProperty(PropertyNames::RAW_PARAMS);
598 if (!use_raw_params) {
599 issues[PropertyNames::OUTPUT_WKSP_PARAM_ERRS] = "Cannot be used with " + PropertyNames::RAW_PARAMS + "=False";
600 issues[PropertyNames::RAW_PARAMS] =
601 "Cannot be False with " + PropertyNames::OUTPUT_WKSP_PARAM_ERRS + " specified";
602 }
603 }
604
605 return issues;
606}
607
608//----------------------------------------------------------------------------------------------
610 // process inputs
612
613 // create output workspace: fitted peak positions
615
616 // create output workspace: fitted peaks' parameters values
618
619 // create output workspace: calculated from fitted peak and background
621
622 // fit peaks
623 auto fit_results = fitPeaks();
624
625 // set the output workspaces to properites
626 processOutputs(fit_results);
627}
628
629//----------------------------------------------------------------------------------------------
631 // input workspaces
633
634 if (m_inputMatrixWS->getAxis(0)->unit()->unitID() == "dSpacing")
635 m_inputIsDSpace = true;
636 else
637 m_inputIsDSpace = false;
638
639 // spectra to fit
640 int start_wi = getProperty(PropertyNames::START_WKSP_INDEX);
641 m_startWorkspaceIndex = static_cast<size_t>(start_wi);
642
643 // last spectrum's workspace index, which is included
644 int stop_wi = getProperty(PropertyNames::STOP_WKSP_INDEX);
645 if (isEmpty(stop_wi))
646 m_stopWorkspaceIndex = m_inputMatrixWS->getNumberHistograms() - 1;
647 else {
648 m_stopWorkspaceIndex = static_cast<size_t>(stop_wi);
649 if (m_stopWorkspaceIndex > m_inputMatrixWS->getNumberHistograms() - 1)
650 m_stopWorkspaceIndex = m_inputMatrixWS->getNumberHistograms() - 1;
651 }
652
653 // total number of spectra to be fit
655
656 // optimizer, cost function and fitting scheme
657 m_minimizer = getPropertyValue(PropertyNames::MINIMIZER);
658 m_costFunction = getPropertyValue(PropertyNames::COST_FUNC);
659 m_strictConvergence = getProperty(PropertyNames::STRICT_CONVERGENCE);
660 m_fitPeaksFromRight = getProperty(PropertyNames::FIT_FROM_RIGHT);
661 m_constrainPeaksPosition = getProperty(PropertyNames::CONSTRAIN_PEAK_POS);
662 const std::string posTolMode = getProperty(PropertyNames::POSITION_TOL_MODE);
663 m_constrainByPositionTolerance = (posTolMode == "Constrain");
664 m_fractionalPositionTolerance = getProperty(PropertyNames::POSITION_TOL_FRACTIONAL);
665 m_calculateUnconstrainedErrors = getProperty(PropertyNames::CALC_UNCONSTRAINED_ERRORS);
666 m_fitIterations = getProperty(PropertyNames::MAX_FIT_ITER);
667 m_copyLastGoodPeakParameters = getProperty(PropertyNames::COPY_LAST_GOOD_PEAK_PARAMS);
668 m_respectFixedPeakParameters = getProperty(PropertyNames::RESPECT_FIXED_PEAK_PARAMS);
669
670 // Peak centers, tolerance and fitting range
672 // check
673 if (m_numPeaksToFit == 0)
674 throw std::runtime_error("number of peaks to fit is zero.");
675 // about how to estimate the peak width
676 m_peakWidthPercentage = getProperty(PropertyNames::PEAK_WIDTH_PERCENT);
679 if (m_peakWidthPercentage >= 1.) // TODO
680 throw std::runtime_error("PeakWidthPercent must be less than 1");
681 g_log.debug() << "peak width/value = " << m_peakWidthPercentage << "\n";
682
683 // set up background
684 m_highBackground = getProperty(PropertyNames::HIGH_BACKGROUND);
685 double temp = getProperty(PropertyNames::BACKGROUND_Z_SCORE);
686 if (!isEmpty(temp)) {
687 std::ostringstream os;
688 os << "FitPeaks property \"" << PropertyNames::BACKGROUND_Z_SCORE << "\" is deprecated and will be ignored."
689 << "\n";
690 logNoOffset(4 /*warning*/, os.str());
691 }
692
693 // Set up peak and background functions
695
696 // about peak width and other peak parameter estimating method
697 if (m_peakWidthPercentage > 0.)
698 m_peakWidthEstimateApproach = EstimatePeakWidth::InstrumentResolution;
699 else if (isObservablePeakProfile((m_peakFunction->name())))
700 m_peakWidthEstimateApproach = EstimatePeakWidth::Observation;
701 else
702 m_peakWidthEstimateApproach = EstimatePeakWidth::NoEstimation;
703 // m_peakWidthEstimateApproach = EstimatePeakWidth::NoEstimation;
704 g_log.debug() << "Process inputs [3] peak type: " << m_peakFunction->name()
705 << ", background type: " << m_bkgdFunction->name() << "\n";
706
709
710 return;
711}
712
713//----------------------------------------------------------------------------------------------
717 // peak functions
718 std::string peakfunctiontype = getPropertyValue(PropertyNames::PEAK_FUNC);
720 std::dynamic_pointer_cast<IPeakFunction>(API::FunctionFactory::Instance().createFunction(peakfunctiontype));
721
722 // background functions
723 std::string bkgdfunctiontype = getPropertyValue(PropertyNames::BACK_FUNC);
724 std::string bkgdname;
725 if (bkgdfunctiontype == "Linear")
726 bkgdname = "LinearBackground";
727 else if (bkgdfunctiontype == "Flat") {
728 g_log.warning("There may be problems with Flat background");
729 bkgdname = "FlatBackground";
730 } else
731 bkgdname = bkgdfunctiontype;
733 std::dynamic_pointer_cast<IBackgroundFunction>(API::FunctionFactory::Instance().createFunction(bkgdname));
735 m_linearBackgroundFunction = std::dynamic_pointer_cast<IBackgroundFunction>(
736 API::FunctionFactory::Instance().createFunction("LinearBackground"));
737 else
739
740 // TODO check that both parameter names and values exist
741 // input peak parameters
742 std::string partablename = getPropertyValue(PropertyNames::PEAK_PARAM_TABLE);
743 m_peakParamNames = getProperty(PropertyNames::PEAK_PARAM_NAMES);
744
746 if (partablename.empty() && (!m_peakParamNames.empty())) {
747 // use uniform starting value of peak parameters
748 m_initParamValues = getProperty(PropertyNames::PEAK_PARAM_VALUES);
749 // convert the parameter name in string to parameter name in integer index
751 // m_uniformProfileStartingValue = true;
752 } else if ((!partablename.empty()) && m_peakParamNames.empty()) {
753 // use non-uniform (per-spectrum) starting values of peak parameters. The table's columns are
754 // peak-function parameter names; convertParametersNameToIndex() picks the column names up (see
755 // its m_profileStartingValueTable branch) and maps them to parameter indexes, and the matching
756 // per-spectrum starting values are read from the table row in decideToEstimatePeakParams.
757 m_profileStartingValueTable = getProperty(PropertyNames::PEAK_PARAM_TABLE);
759 } else if (peakfunctiontype != "Gaussian") {
760 // user specifies nothing
761 g_log.warning("Neither parameter value table nor initial "
762 "parameter name/value vectors is specified. Fitting might "
763 "not be reliable for peak profile other than Gaussian");
764 }
765
766 return;
767}
768
769//----------------------------------------------------------------------------------------------
774 // get peak fit window
775 std::vector<double> peakwindow = getProperty(PropertyNames::FIT_WINDOW_LIST);
776 std::string peakwindowname = getPropertyValue(PropertyNames::FIT_WINDOW_WKSP);
777 API::MatrixWorkspace_const_sptr peakwindowws = getProperty(PropertyNames::FIT_WINDOW_WKSP);
778
779 // in most case, calculate window by instrument resolution is False
780
781 if ((!peakwindow.empty()) && peakwindowname.empty()) {
782 // Peak windows are uniform among spectra: use vector for peak windows
783
784 // check peak positions
786 throw std::invalid_argument(
787 "Specifying peak windows with a list requires also specifying peak positions with a list.");
788 // check size
789 if (peakwindow.size() != m_numPeaksToFit * 2)
790 throw std::invalid_argument("Peak window vector must be twice as large as number of peaks.");
791
792 // set up window to m_peakWindowVector
794 for (size_t i = 0; i < m_numPeaksToFit; ++i) {
795 std::vector<double> peakranges(2);
796 peakranges[0] = peakwindow[i * 2];
797 peakranges[1] = peakwindow[i * 2 + 1];
798 // check peak window (range) against peak centers
799 if ((peakranges[0] < m_peakCenters[i]) && (m_peakCenters[i] < peakranges[1])) {
800 // pass check: set
801 m_peakWindowVector[i] = peakranges;
802 } else {
803 // failed
804 std::stringstream errss;
805 errss << "Peak " << i << ": user specifies an invalid range and peak center against " << peakranges[0] << " < "
806 << m_peakCenters[i] << " < " << peakranges[1];
807 throw std::invalid_argument(errss.str());
808 }
809 } // END-FOR
810 m_getPeakFitWindow = [this](std::size_t wi, std::size_t ipeak) -> std::pair<double, double> {
811 this->checkWorkspaceIndices(wi);
812 this->checkPeakIndices(wi, ipeak);
813 double left = this->m_peakWindowVector[ipeak][0];
814 double right = this->m_peakWindowVector[ipeak][1];
815 this->checkPeakWindowEdgeOrder(left, right);
816 return std::make_pair(left, right);
817 };
818 // END if list peak windows
819 } else if (peakwindow.empty() && peakwindowws != nullptr) {
820 // use matrix workspace for non-uniform peak windows
821 m_peakWindowWorkspace = getProperty(PropertyNames::FIT_WINDOW_WKSP);
822
823 // check each spectrum whether the window is defined with the correct size
824 for (std::size_t wi = m_startWorkspaceIndex; wi <= m_stopWorkspaceIndex; wi++) {
825 const auto &peakWindowX = m_peakWindowWorkspace->x(wi);
826 const auto &peakCenterX = m_peakCenterWorkspace->x(wi);
827 if (peakWindowX.empty()) {
828 std::stringstream errss;
829 errss << "Peak window required at workspace index " << wi << " "
830 << "which is undefined in the peak window workspace. "
831 << "Ensure workspace indices correspond in peak window workspace and input workspace "
832 << "when using start and stop indices.";
833 throw std::invalid_argument(errss.str());
834 }
835 // check size
836 if (peakWindowX.size() % 2 != 0) {
837 throw std::invalid_argument("The peak window vector must be even, with two edges for each peak center.");
838 }
839 if (peakWindowX.size() != peakCenterX.size() * 2) {
840 std::stringstream errss;
841 errss << "Peak window workspace index " << wi << " has incompatible number of fit windows "
842 << peakWindowX.size() / 2 << " with the number of peaks " << peakCenterX.size() << " to fit.";
843 throw std::invalid_argument(errss.str());
844 }
845
846 for (size_t ipeak = 0; ipeak < peakCenterX.size(); ++ipeak) {
847 double left_w_bound = peakWindowX[ipeak * 2];
848 double right_w_bound = peakWindowX[ipeak * 2 + 1];
849 double center = peakCenterX[ipeak];
850
851 if (!(left_w_bound < center && center < right_w_bound)) {
852 std::stringstream errss;
853 errss << "Workspace index " << wi << " has incompatible peak window "
854 << "(" << left_w_bound << ", " << right_w_bound << ") "
855 << "with " << ipeak << "-th expected peak's center " << center;
856 throw std::runtime_error(errss.str());
857 }
858 }
859 }
860 m_getPeakFitWindow = [this](std::size_t wi, std::size_t ipeak) -> std::pair<double, double> {
861 this->checkWorkspaceIndices(wi);
862 this->checkPeakIndices(wi, ipeak);
863 double left = m_peakWindowWorkspace->x(wi)[ipeak * 2];
864 double right = m_peakWindowWorkspace->x(wi)[ipeak * 2 + 1];
865 this->checkPeakWindowEdgeOrder(left, right);
866 return std::make_pair(left, right);
867 };
868 // END if workspace peak windows
869 } else if (peakwindow.empty()) {
870 // no peak window is defined, then the peak window will be estimated by
871 // delta(D)/D
873 // m_peakWindowMethod = PeakWindowMethod::TOLERANCE;
874 // m_calculateWindowInstrument = true;
875 m_getPeakFitWindow = [this](std::size_t wi, std::size_t ipeak) -> std::pair<double, double> {
876 this->checkWorkspaceIndices(wi);
877 this->checkPeakIndices(wi, ipeak);
878 // calcualte peak window by delta(d)/d
879 double peak_pos = this->m_getExpectedPeakPositions(wi)[ipeak];
880 // calcalate expected peak width
881 double estimate_peak_width = peak_pos * m_peakWidthPercentage;
882 // using the NUMBER THREE to estimate the peak window
883 double THREE = 3.0;
884 double left = peak_pos - estimate_peak_width * THREE;
885 double right = peak_pos + estimate_peak_width * THREE;
886 this->checkPeakWindowEdgeOrder(left, right);
887 return std::make_pair(left, right);
888 };
889 } else {
890 throw std::invalid_argument("Without definition of peak window, the "
891 "input workspace must be in unit of dSpacing "
892 "and Delta(D)/D must be given!");
893 }
894 } else {
895 // non-supported situation
896 throw std::invalid_argument("One and only one of peak window array and "
897 "peak window workspace can be specified.");
898 }
899
900 return;
901}
902
903//----------------------------------------------------------------------------------------------
913 // peak centers
914 m_peakCenters = getProperty(PropertyNames::PEAK_CENTERS);
915 API::MatrixWorkspace_const_sptr peakcenterws = getProperty(PropertyNames::PEAK_CENTERS_WKSP);
916 if (!peakcenterws)
917 g_log.notice("Peak centers are not specified by peak center workspace");
918
919 std::string peakpswsname = getPropertyValue(PropertyNames::PEAK_CENTERS_WKSP);
920 if ((!m_peakCenters.empty()) && peakcenterws == nullptr) {
921 // peak positions are uniform among all spectra
923 // number of peaks to fit!
925 m_getExpectedPeakPositions = [this](std::size_t wi) -> std::vector<double> {
926 this->checkWorkspaceIndices(wi);
927 return this->m_peakCenters;
928 };
929 } else if (m_peakCenters.empty() && peakcenterws != nullptr) {
930 // peak positions can be different among spectra
932 m_peakCenterWorkspace = getProperty(PropertyNames::PEAK_CENTERS_WKSP);
933 // number of peaks to fit must correspond to largest number of reference peaks
934 m_numPeaksToFit = 0;
935 g_log.debug() << "Input peak center workspace: " << m_peakCenterWorkspace->x(0).size() << ", "
936 << m_peakCenterWorkspace->y(0).size() << "\n";
937 for (std::size_t wi = m_startWorkspaceIndex; wi <= m_stopWorkspaceIndex; wi++) {
938 if (m_peakCenterWorkspace->x(wi).empty()) {
939 std::stringstream errss;
940 errss << "Fit peaks was asked to fit from workspace index " << m_startWorkspaceIndex << " "
941 << "until workspace index " << m_stopWorkspaceIndex << ". "
942 << "However, the peak center workspace does not have values defined "
943 << "at workspace index " << wi << ". "
944 << "Make sure the workspace indices between input and peak center workspaces correspond.";
945 g_log.error() << errss.str();
946 throw std::invalid_argument(errss.str());
947 }
948 // the number of peaks to try to fit should be the max number of peaks across spectra
949 m_numPeaksToFit = std::max(m_numPeaksToFit, m_peakCenterWorkspace->x(wi).size());
950 }
951 m_getExpectedPeakPositions = [this](std::size_t wi) -> std::vector<double> {
952 this->checkWorkspaceIndices(wi);
953 return this->m_peakCenterWorkspace->x(wi).rawData();
954 };
955 } else {
956 std::stringstream errss;
957 errss << "One and only one in 'PeakCenters' (vector) and "
958 "'PeakCentersWorkspace' shall be given. "
959 << "'PeakCenters' has size " << m_peakCenters.size() << ", and name of peak center workspace "
960 << "is " << peakpswsname;
961 throw std::invalid_argument(errss.str());
962 }
963
964 return;
965}
966
967//----------------------------------------------------------------------------------------------
974 // check code integrity
975 if (m_numPeaksToFit == 0)
976 throw std::runtime_error("ProcessInputPeakTolerance() must be called after "
977 "ProcessInputPeakCenters()");
978
979 // peak tolerance
980 m_peakPosTolerances = getProperty(PropertyNames::POSITION_TOL);
981
982 if (m_peakPosTolerances.empty()) {
983 // case 2, 3, 4
984 m_peakPosTolerances.clear();
985 m_peakPosTolCase234 = true;
986 } else if (m_peakPosTolerances.size() == 1) {
987 // only 1 uniform peak position tolerance is defined: expand to all peaks
988 double peak_tol = m_peakPosTolerances[0];
989 m_peakPosTolerances.resize(m_numPeaksToFit, peak_tol);
990 } else if (m_peakPosTolerances.size() != m_numPeaksToFit) {
991 // not uniform but number of peaks does not match
992 g_log.error() << "number of peak position tolerance " << m_peakPosTolerances.size()
993 << " is not same as number of peaks " << m_numPeaksToFit << "\n";
994 throw std::runtime_error("Number of peak position tolerances and number of "
995 "peaks to fit are inconsistent.");
996 }
997
998 // set the minimum peak height to 0 (default value) if not specified or invalid
999 m_minPeakHeight = getProperty(PropertyNames::PEAK_MIN_HEIGHT);
1001 m_minPeakHeight = 0.;
1002
1003 // PEAK_MIN_HEIGHT used to function as both "peak height" and "total count" checker.
1004 // Now the "total count" is checked by PEAK_MIN_TOTAL_COUNT, so set it accordingly.
1005 m_minPeakTotalCount = getProperty(PropertyNames::PEAK_MIN_TOTAL_COUNT);
1008 else {
1009 // set the minimum peak total count to 0 if not specified or invalid
1012 }
1013
1014 // set the signal-to-noise threshold to zero (default value) if not specified or invalid
1015 m_minSignalToNoiseRatio = getProperty(PropertyNames::PEAK_MIN_SIGNAL_TO_NOISE_RATIO);
1018
1019 // set the signal-to-sigma threshold to zero (default value) if not specified or invalid
1020 m_minSignalToSigmaRatio = getProperty(PropertyNames::PEAK_MIN_SIGNAL_TO_SIGMA_RATIO);
1023}
1024
1025//----------------------------------------------------------------------------------------------
1032 // get a map for peak profile parameter name and parameter index
1033 std::map<std::string, size_t> parname_index_map;
1034 for (size_t iparam = 0; iparam < m_peakFunction->nParams(); ++iparam)
1035 parname_index_map.insert(std::make_pair(m_peakFunction->parameterName(iparam), iparam));
1036
1037 // define peak parameter names (class variable) if using table
1040
1041 // map the input parameter names to parameter indexes
1042 for (const auto &paramName : m_peakParamNames) {
1043 auto locator = parname_index_map.find(paramName);
1044 if (locator != parname_index_map.end()) {
1045 m_initParamIndexes.emplace_back(locator->second);
1046 } else {
1047 // a parameter name that is not defined in the peak profile function. An
1048 // out-of-range index is thus set to this
1049 g_log.warning() << "Given peak parameter " << paramName
1050 << " is not an allowed parameter of peak "
1051 "function "
1052 << m_peakFunction->name() << "\n";
1053 m_initParamIndexes.emplace_back(m_peakFunction->nParams() * 10);
1054 }
1055 }
1056
1057 return;
1058}
1059
1060//----------------------------------------------------------------------------------------------
1063std::vector<std::shared_ptr<FitPeaksAlgorithm::PeakFitResult>> FitPeaks::fitPeaks() {
1064 API::Progress prog(this, 0., 1., m_numPeaksToFit - 1);
1065
1068 std::vector<std::shared_ptr<FitPeaksAlgorithm::PeakFitResult>> fit_result_vector(m_numSpectraToFit);
1069
1070 const int nThreads = FrameworkManager::Instance().getNumOMPThreads();
1071 size_t chunkSize = m_numSpectraToFit / nThreads;
1072
1073 std::shared_ptr<FitPeaksAlgorithm::PeakFitPreCheckResult> pre_check_result =
1074 std::make_shared<FitPeaksAlgorithm::PeakFitPreCheckResult>();
1075
1076 PRAGMA_OMP(parallel for schedule(dynamic, 1) )
1077 for (int ithread = 0; ithread < nThreads; ithread++) {
1079 auto iws_begin = m_startWorkspaceIndex + chunkSize * static_cast<size_t>(ithread);
1080 auto iws_end = (ithread == nThreads - 1) ? m_stopWorkspaceIndex + 1 : iws_begin + chunkSize;
1081
1082 // vector to store fit params for last good fit to each peak
1083 std::vector<std::vector<double>> lastGoodPeakParameters(m_numPeaksToFit,
1084 std::vector<double>(m_peakFunction->nParams(), 0.0));
1085 // track which spectrum index last successfully fitted each peak
1086 std::vector<size_t> lastGoodPeakSpectra(m_numPeaksToFit, 0);
1087
1088 for (auto wi = iws_begin; wi < iws_end; ++wi) {
1089 // peaks to fit
1090 std::vector<double> expected_peak_centers = m_getExpectedPeakPositions(static_cast<size_t>(wi));
1091
1092 // initialize output for this
1093 size_t numfuncparams = m_peakFunction->nParams() + m_bkgdFunction->nParams();
1094 std::shared_ptr<FitPeaksAlgorithm::PeakFitResult> fit_result =
1095 std::make_shared<FitPeaksAlgorithm::PeakFitResult>(m_numPeaksToFit, numfuncparams);
1096
1097 std::shared_ptr<FitPeaksAlgorithm::PeakFitPreCheckResult> spectrum_pre_check_result =
1098 std::make_shared<FitPeaksAlgorithm::PeakFitPreCheckResult>();
1099
1100 fitSpectrumPeaks(static_cast<size_t>(wi), expected_peak_centers, fit_result, lastGoodPeakParameters,
1101 lastGoodPeakSpectra, spectrum_pre_check_result);
1102
1103 PARALLEL_CRITICAL(FindPeaks_WriteOutput) {
1104 writeFitResult(static_cast<size_t>(wi), expected_peak_centers, fit_result);
1105 fit_result_vector[wi - m_startWorkspaceIndex] = fit_result;
1106 *pre_check_result += *spectrum_pre_check_result;
1107 }
1108 prog.report();
1109 }
1111 }
1113 logNoOffset(5 /*notice*/, pre_check_result->getReport());
1114 return fit_result_vector;
1115}
1116
1117namespace {
1118// Forward declarations
1119bool estimateBackgroundParameters(const Histogram &histogram, const std::pair<size_t, size_t> &peak_window,
1120 const API::IBackgroundFunction_sptr &bkgd_function);
1121void reduceByBackground(const API::IBackgroundFunction_sptr &bkgd_func, const std::vector<double> &vec_x,
1122 std::vector<double> &vec_y);
1123template <typename vector_like>
1124void rangeToIndexBounds(const vector_like &vecx, const double range_left, const double range_right, size_t &left_index,
1125 size_t &right_index);
1126
1128std::vector<std::string> supported_peak_profiles{"Gaussian", "Lorentzian", "PseudoVoigt", "Voigt",
1129 "BackToBackExponential"};
1130
1131//----------------------------------------------------------------------------------------------
1136double estimateBackgroundNoise(const std::vector<double> &vec_y) {
1137 // peak window must have a certain minimum number of data points necessary to do the statistics
1138 size_t half_number_of_bkg_datapoints{5};
1139 if (vec_y.size() < 2 * half_number_of_bkg_datapoints + 3 /*a magic number*/)
1140 return DBL_MIN; // can't estimate the noise
1141
1142 // the specified number of left-most and right-most data points in the peak window are assumed to represent
1143 // background. Combine these data points into a single vector
1144 std::vector<double> vec_bkg;
1145 vec_bkg.resize(2 * half_number_of_bkg_datapoints);
1146 std::copy(vec_y.begin(), vec_y.begin() + half_number_of_bkg_datapoints, vec_bkg.begin());
1147 std::copy(vec_y.end() - half_number_of_bkg_datapoints, vec_y.end(), vec_bkg.begin() + half_number_of_bkg_datapoints);
1148
1149 // estimate the noise as the standard deviation of the combined background vector, but without outliers
1150 std::vector<double> zscore_vec = Kernel::getZscore(vec_bkg);
1151 std::vector<double> vec_bkg_no_outliers;
1152 vec_bkg_no_outliers.resize(vec_bkg.size());
1153 double zscore_crit = 3.; // using three-sigma rule
1154 for (size_t ii = 0; ii < vec_bkg.size(); ii++) {
1155 if (zscore_vec[ii] <= zscore_crit)
1156 vec_bkg_no_outliers.push_back(vec_bkg[ii]);
1157 }
1158
1159 if (vec_bkg_no_outliers.size() < half_number_of_bkg_datapoints)
1160 return DBL_MIN; // can't estimate the noise
1161
1162 auto intensityStatistics = Kernel::getStatistics(vec_bkg_no_outliers, StatOptions::CorrectedStdDev);
1163 return intensityStatistics.standard_deviation;
1164}
1165
1166//----------------------------------------------------------------------------------------------
1174template <typename vector_like>
1175void rangeToIndexBounds(const vector_like &elems, const double range_left, const double range_right, size_t &left_index,
1176 size_t &right_index) {
1177 const auto left_iter = std::lower_bound(elems.cbegin(), elems.cend(), range_left);
1178 const auto right_iter = std::upper_bound(elems.cbegin(), elems.cend(), range_right);
1179
1180 left_index = std::distance(elems.cbegin(), left_iter);
1181 right_index = std::distance(elems.cbegin(), right_iter);
1182 right_index = std::min(right_index, elems.size() - 1);
1183}
1184
1185//----------------------------------------------------------------------------------------------
1191void reduceByBackground(const API::IBackgroundFunction_sptr &bkgd_func, const std::vector<double> &vec_x,
1192 std::vector<double> &vec_y) {
1193 // calculate the background
1194 FunctionDomain1DVector vectorx(vec_x.begin(), vec_x.end());
1195 FunctionValues vector_bkgd(vectorx);
1196 bkgd_func->function(vectorx, vector_bkgd);
1197
1198 // subtract the background from the supplied data
1199 for (size_t i = 0; i < vec_y.size(); ++i) {
1200 (vec_y)[i] -= vector_bkgd[i];
1201 // Note, E is not changed here
1202 }
1203}
1204
1205//----------------------------------------------------------------------------------------------
1209class LoggingOffsetSentry {
1210public:
1211 LoggingOffsetSentry(Algorithm *const alg) : m_alg(alg) {
1214 }
1215 ~LoggingOffsetSentry() { m_alg->setLoggingOffset(m_loggingOffset); }
1216
1217private:
1220};
1221} // namespace
1222
1223//----------------------------------------------------------------------------------------------
1226void FitPeaks::fitSpectrumPeaks(size_t wi, const std::vector<double> &expected_peak_centers,
1227 const std::shared_ptr<FitPeaksAlgorithm::PeakFitResult> &fit_result,
1228 std::vector<std::vector<double>> &lastGoodPeakParameters,
1229 std::vector<size_t> &lastGoodPeakSpectra,
1230 const std::shared_ptr<FitPeaksAlgorithm::PeakFitPreCheckResult> &pre_check_result) {
1231 assert(fit_result->getNumberPeaks() == m_numPeaksToFit);
1232 pre_check_result->setNumberOfSubmittedSpectrumPeaks(m_numPeaksToFit);
1233 // if the whole spectrum has low count, do not fit any peaks for that spectrum
1235 for (size_t i = 0; i < m_numPeaksToFit; ++i)
1236 fit_result->setBadRecord(i, -1.);
1237 pre_check_result->setNumberOfSpectrumPeaksWithLowCount(m_numPeaksToFit);
1238 return;
1239 }
1240
1241 // Set up sub algorithm Fit for peak and background
1242 IAlgorithm_sptr peak_fitter = createChildFit(); // both peak and background (combo)
1243
1244 // Clone background function
1245 IBackgroundFunction_sptr bkgdfunction = std::dynamic_pointer_cast<API::IBackgroundFunction>(m_bkgdFunction->clone());
1246
1247 // set up properties of algorithm (reference) 'Fit'
1248 peak_fitter->setProperty("Minimizer", m_minimizer);
1249 peak_fitter->setProperty("CostFunction", m_costFunction);
1250 peak_fitter->setProperty("CalcErrors", true);
1251
1252 const double x0 = m_inputMatrixWS->histogram(wi).x().front();
1253 const double xf = m_inputMatrixWS->histogram(wi).x().back();
1254
1255 // index of previous peak in same spectrum (initially invalid)
1256 size_t prev_peak_index = m_numPeaksToFit;
1257 bool neighborPeakSameSpectrum = false;
1258 size_t number_of_out_of_range_peaks{0};
1259 for (size_t fit_index = 0; fit_index < m_numPeaksToFit; ++fit_index) {
1260 // convert fit index to peak index (in ascending order)
1261 size_t peak_index(fit_index);
1263 peak_index = m_numPeaksToFit - fit_index - 1;
1264
1265 // reset the background function
1266 for (size_t i = 0; i < bkgdfunction->nParams(); ++i)
1267 bkgdfunction->setParameter(i, 0.);
1268
1269 double expected_peak_pos = expected_peak_centers[peak_index];
1270
1271 // clone peak function for each peak (need to do this so can
1272 // set center and calc any parameters from xml)
1273 auto peakfunction = std::dynamic_pointer_cast<API::IPeakFunction>(m_peakFunction->clone());
1274 peakfunction->setCentre(expected_peak_pos);
1275
1276 std::pair<double, double> peak_window_i = m_getPeakFitWindow(wi, peak_index);
1277 peakfunction->setMatrixWorkspace(m_inputMatrixWS, wi, peak_window_i.first, peak_window_i.second);
1278
1279 std::map<size_t, double> keep_values;
1280 for (size_t ipar = 0; ipar < peakfunction->nParams(); ++ipar) {
1281 if (peakfunction->isFixed(ipar)) {
1282 // save value of these parameters which have just been calculated
1283 // if they were set to be fixed (e.g. for the B2Bexp this would
1284 // typically be A and B but not Sigma)
1285 keep_values[ipar] = peakfunction->getParameter(ipar);
1286 // by default let them be free to fit as these are typically refined
1287 // from a focussed bank; otherwise respect the fixed status and keep
1288 // the parameter locked at the calculated value.
1290 peakfunction->unfix(ipar);
1291 }
1292 }
1293 }
1294
1295 // Determine whether to set starting parameter from fitted value
1296 // of same peak but different spectrum
1297 bool samePeakCrossSpectrum = (lastGoodPeakParameters[peak_index].size() >
1298 static_cast<size_t>(std::count_if(lastGoodPeakParameters[peak_index].begin(),
1299 lastGoodPeakParameters[peak_index].end(),
1300 [&](auto const &val) { return val <= 1e-10; })));
1301
1302 // Check whether current spectrum's pixel (detector ID) is close to the
1303 // spectrum that last successfully fitted this peak.
1304 try {
1305 if (wi > 0 && samePeakCrossSpectrum) {
1306 size_t lastGoodWi = lastGoodPeakSpectra[peak_index];
1307 std::shared_ptr<const Geometry::Detector> pdetector =
1308 std::dynamic_pointer_cast<const Geometry::Detector>(m_inputMatrixWS->getDetector(lastGoodWi));
1309 std::shared_ptr<const Geometry::Detector> cdetector =
1310 std::dynamic_pointer_cast<const Geometry::Detector>(m_inputMatrixWS->getDetector(wi));
1311
1312 // If they do have detector ID
1313 if (pdetector && cdetector) {
1314 auto prev_id = pdetector->getID();
1315 auto curr_id = cdetector->getID();
1316 if (prev_id + 1 != curr_id)
1317 samePeakCrossSpectrum = false;
1318 } else {
1319 samePeakCrossSpectrum = false;
1320 }
1321
1322 } else {
1323 // first spectrum in the workspace: no peak's fitting result to copy
1324 // from
1325 samePeakCrossSpectrum = false;
1326 }
1327 } catch (const std::runtime_error &) {
1328 // workspace does not have detector ID set: there is no guarantee that the
1329 // adjacent spectra can have similar peak profiles
1330 samePeakCrossSpectrum = false;
1331 }
1332
1333 // Set starting values of the peak function.
1334 if (samePeakCrossSpectrum) { // somePeakFit
1335 // Get from local best result
1336 for (size_t i = 0; i < peakfunction->nParams(); ++i) {
1337 peakfunction->setParameter(i, lastGoodPeakParameters[peak_index][i]);
1338 }
1339 } else if (neighborPeakSameSpectrum && m_copyLastGoodPeakParameters) {
1340 // set the peak parameters from last good fit from ANY peak in the spectrum
1341 for (size_t i = 0; i < peakfunction->nParams(); ++i) {
1342 peakfunction->setParameter(i, lastGoodPeakParameters[prev_peak_index][i]);
1343 }
1344 }
1345
1346 // reset center though - don't know before hand which element this is
1347 peakfunction->setCentre(expected_peak_pos);
1348
1349 // reset value of parameters that were fixed (but are now free to vary)
1350 for (const auto &[ipar, value] : keep_values) {
1351 peakfunction->setParameter(ipar, value);
1352 }
1353
1354 double cost(DBL_MAX);
1355 if (expected_peak_pos <= x0 || expected_peak_pos >= xf) {
1356 // out of range and there won't be any fit
1357 peakfunction->setIntensity(0);
1358 number_of_out_of_range_peaks++;
1359 } else {
1360 // Decide whether to estimate peak width by observation
1361 // If no peaks fitted in the same or cross spectrum then the user supplied
1362 // parameters will be used if present and the width will not be estimated
1363 // (note this will overwrite parameter values caluclated from
1364 // Parameters.xml)
1365 // When CopyLastGoodPeakParameters is disabled, treat each peak as if it
1366 // has no fitted neighbour so that user-specified initial values are
1367 // re-applied as the baseline rather than falling back to function defaults.
1368 auto useUserSpecifedIfGiven =
1369 !(samePeakCrossSpectrum || (neighborPeakSameSpectrum && m_copyLastGoodPeakParameters));
1370 bool observe_peak_width = decideToEstimatePeakParams(useUserSpecifedIfGiven, wi, peakfunction);
1371
1372 if (observe_peak_width && m_peakWidthEstimateApproach == EstimatePeakWidth::NoEstimation) {
1373 g_log.warning("Peak width can be estimated as ZERO. The result can be wrong");
1374 }
1375
1376 // do fitting with peak and background function (no analysis at this point)
1377 std::shared_ptr<FitPeaksAlgorithm::PeakFitPreCheckResult> peak_pre_check_result =
1378 std::make_shared<FitPeaksAlgorithm::PeakFitPreCheckResult>();
1379 // in Constrain mode the fitted centre is bounded by expected_peak_pos +/- tolerance; a
1380 // non-positive tolerance (the default, or Check mode) leaves the centre unconstrained here.
1381 // When PositionToleranceFractional is set the tolerance is a fraction of this peak's fit
1382 // window width, so scale it here to an absolute bound.
1383 double peak_pos_tolerance = -1.0;
1385 peak_pos_tolerance = m_peakPosTolerances[peak_index];
1387 peak_pos_tolerance *= (peak_window_i.second - peak_window_i.first);
1388 }
1389 cost = fitIndividualPeak(wi, peak_fitter, expected_peak_pos, peak_pos_tolerance, peak_window_i,
1390 observe_peak_width, peakfunction, bkgdfunction, peak_pre_check_result);
1391 if (peak_pre_check_result->isIndividualPeakRejected())
1392 fit_result->setBadRecord(peak_index, -1.);
1393
1394 if (m_minSignalToSigmaRatio > 0) {
1395 if (calculateSignalToSigmaRatio(wi, peak_window_i, peakfunction) < m_minSignalToSigmaRatio) {
1396 fit_result->setBadRecord(peak_index, -1.);
1397 cost = DBL_MAX;
1398 }
1399 }
1400
1401 *pre_check_result += *peak_pre_check_result; // keep track of the rejection count within the spectrum
1402 }
1403 pre_check_result->setNumberOfOutOfRangePeaks(number_of_out_of_range_peaks);
1404
1405 // process fitting result
1406 FitPeaksAlgorithm::FitFunction fit_function;
1407 fit_function.peakfunction = peakfunction;
1408 fit_function.bkgdfunction = bkgdfunction;
1409
1410 auto good_fit = processSinglePeakFitResult(wi, peak_index, cost, expected_peak_centers, fit_function,
1411 fit_result); // sets the record
1412
1413 if (good_fit) {
1414 // reset the flag such that there is at a peak fit in this spectrum
1415 neighborPeakSameSpectrum = true;
1416 prev_peak_index = peak_index;
1417 // copy values and record which spectrum they came from
1418 for (size_t i = 0; i < lastGoodPeakParameters[peak_index].size(); ++i) {
1419 lastGoodPeakParameters[peak_index][i] = peakfunction->getParameter(i);
1420 }
1421 lastGoodPeakSpectra[peak_index] = wi;
1422 }
1423 }
1424
1425 return;
1426}
1427
1428//----------------------------------------------------------------------------------------------
1439bool FitPeaks::decideToEstimatePeakParams(const bool firstPeakInSpectrum, const size_t wsindex,
1440 const API::IPeakFunction_sptr &peak_function) {
1441 // should observe the peak width if the user didn't supply all of the peak
1442 // function parameters
1443 bool observe_peak_shape(m_initParamIndexes.size() != peak_function->nParams());
1444
1445 // when starting values come from a per-spectrum table, this spectrum must have a matching row;
1446 // if it does not, fall back to estimating the peak shape from the data by observation
1447 if (m_profileStartingValueTable && wsindex >= m_profileStartingValueTable->rowCount())
1448 return true;
1449
1450 if (!m_initParamIndexes.empty()) {
1451 // user specifies starting value of peak parameters
1452 if (firstPeakInSpectrum) {
1453 // set the parameter values in a vector and loop over it
1454 // first peak. using the user-specified value
1455 for (size_t i = 0; i < m_initParamIndexes.size(); ++i) {
1456 const size_t param_index = m_initParamIndexes[i];
1457 // a supplied name that is not a parameter of this peak function was flagged with an
1458 // out-of-range index by convertParametersNameToIndex; skip it rather than throw
1459 if (param_index >= peak_function->nParams())
1460 continue;
1461 // per-spectrum starting value from the table row for this spectrum, else the uniform value
1462 const double param_value = m_profileStartingValueTable
1463 ? m_profileStartingValueTable->getColumn(i)->toDouble(wsindex)
1464 : m_initParamValues[i];
1465 // a non-finite table cell means "no seed for this spectrum/parameter": leave the value
1466 // calculated from the instrument parameters (setMatrixWorkspace) in place for that one
1467 if (std::isfinite(param_value))
1468 peak_function->setParameter(param_index, param_value);
1469 }
1470 } else {
1471 // using the fitted paramters from the previous fitting result
1472 // do noting
1473 }
1474 } else {
1475 // no previously defined peak parameters: observation is thus required
1476 observe_peak_shape = true;
1477 }
1478
1479 return observe_peak_shape;
1480}
1481
1482//----------------------------------------------------------------------------------------------
1494bool FitPeaks::processSinglePeakFitResult(size_t wsindex, size_t peakindex, const double cost,
1495 const std::vector<double> &expected_peak_positions,
1496 const FitPeaksAlgorithm::FitFunction &fitfunction,
1497 const std::shared_ptr<FitPeaksAlgorithm::PeakFitResult> &fit_result) {
1498 // determine peak position tolerance
1499 double postol(DBL_MAX);
1500 bool case23(false);
1501 if (m_peakPosTolCase234) {
1502 // peak tolerance is not defined
1503 if (m_numPeaksToFit == 1) {
1504 // case (d) one peak only
1505 postol = m_inputMatrixWS->histogram(wsindex).x().back() - m_inputMatrixWS->histogram(wsindex).x().front();
1506 } else {
1507 // case b and c: more than 1 peaks without defined peak tolerance
1508 case23 = true;
1509 }
1510 } else {
1511 // user explicitly specified
1512 if (peakindex >= m_peakPosTolerances.size())
1513 throw std::runtime_error("Peak tolerance out of index");
1514 postol = m_peakPosTolerances[peakindex];
1515 // fractional tolerance: scale by this peak's fit window width to an absolute bound
1517 const std::pair<double, double> fitwindow = m_getPeakFitWindow(wsindex, peakindex);
1518 postol *= (fitwindow.second - fitwindow.first);
1519 }
1520 }
1521
1522 // get peak position and analyze the fitting is good or not by various
1523 // criteria
1524 auto peak_pos = fitfunction.peakfunction->centre();
1525 auto peak_fwhm = fitfunction.peakfunction->fwhm();
1526 bool good_fit(false);
1527 if ((cost < 0) || (cost >= DBL_MAX - 1.) || std::isnan(cost)) {
1528 // unphysical cost function value
1529 peak_pos = -4;
1530 } else if (fitfunction.peakfunction->height() < m_minPeakHeight) {
1531 // peak height is under minimum request
1532 peak_pos = -3;
1533 } else if (case23) {
1534 // case b and c to check peak position without defined peak tolerance
1535 std::pair<double, double> fitwindow = m_getPeakFitWindow(wsindex, peakindex);
1536 if (fitwindow.first < fitwindow.second) {
1537 // peak fit window is specified or calculated: use peak window as position
1538 // tolerance
1539 if (peak_pos < fitwindow.first || peak_pos > fitwindow.second) {
1540 // peak is out of fit window
1541 peak_pos = -2;
1542 g_log.debug() << "Peak position " << peak_pos << " is out of fit "
1543 << "window boundary " << fitwindow.first << ", " << fitwindow.second << "\n";
1544 } else if (peak_fwhm > (fitwindow.second - fitwindow.first)) {
1545 // peak is too wide or window is too small
1546 peak_pos = -2.25;
1547 g_log.debug() << "Peak position " << peak_pos << " has fwhm "
1548 << "wider than the fit window " << fitwindow.second - fitwindow.first << "\n";
1549 } else {
1550 good_fit = true;
1551 }
1552 } else {
1553 // use the 1/2 distance to neiboring peak without defined peak window
1554 double left_bound(-1);
1555 if (peakindex > 0)
1556 left_bound = 0.5 * (expected_peak_positions[peakindex] - expected_peak_positions[peakindex - 1]);
1557 double right_bound(-1);
1558 if (peakindex < m_numPeaksToFit - 1)
1559 right_bound = 0.5 * (expected_peak_positions[peakindex + 1] - expected_peak_positions[peakindex]);
1560 if (left_bound < 0)
1561 left_bound = right_bound;
1562 if (right_bound < left_bound)
1563 right_bound = left_bound;
1564 if (left_bound < 0 || right_bound < 0)
1565 throw std::runtime_error("Code logic error such that left or right "
1566 "boundary of peak position is negative.");
1567 if (peak_pos < left_bound || peak_pos > right_bound) {
1568 peak_pos = -2.5;
1569 } else if (peak_fwhm > (right_bound - left_bound)) {
1570 // peak is too wide or window is too small
1571 peak_pos = -2.75;
1572 g_log.debug() << "Peak position " << peak_pos << " has fwhm "
1573 << "wider than the fit window " << right_bound - left_bound << "\n";
1574 } else {
1575 good_fit = true;
1576 }
1577 }
1578 } else if (fabs(fitfunction.peakfunction->centre() - expected_peak_positions[peakindex]) > postol) {
1579 // peak center is not within tolerance
1580 peak_pos = -5;
1581 g_log.debug() << "Peak position difference "
1582 << fabs(fitfunction.peakfunction->centre() - expected_peak_positions[peakindex])
1583 << " is out of range of tolerance: " << postol << "\n";
1584 } else {
1585 // all criteria are passed
1586 good_fit = true;
1587 }
1588
1589 // set cost function to DBL_MAX if fitting is bad
1590 double adjust_cost(cost);
1591 if (!good_fit) {
1592 // set the cost function value to DBL_MAX
1593 adjust_cost = DBL_MAX;
1594 }
1595
1596 // reset cost
1597 if (adjust_cost > DBL_MAX - 1) {
1598 fitfunction.peakfunction->setIntensity(0);
1599 }
1600
1601 // chi2
1602 fit_result->setRecord(peakindex, adjust_cost, peak_pos, fitfunction);
1603
1604 return good_fit;
1605}
1606
1607//----------------------------------------------------------------------------------------------
1613void FitPeaks::calculateFittedPeaks(const std::vector<std::shared_ptr<FitPeaksAlgorithm::PeakFitResult>> &fit_results) {
1614 // check
1615 if (!m_fittedParamTable)
1616 throw std::runtime_error("No parameters");
1617
1618 const size_t num_peakfunc_params = m_peakFunction->nParams();
1619 const size_t num_bkgdfunc_params = m_bkgdFunction->nParams();
1620
1621 // Configure the peak functions before entering the parallel region. Some peak
1622 // functions use setMatrixWorkspace() to initialise detector/workspace-derived
1623 // state.
1624 std::vector<std::vector<IPeakFunction_sptr>> peak_functions(m_numSpectraToFit,
1625 std::vector<IPeakFunction_sptr>(m_numPeaksToFit));
1626 std::vector<std::vector<IBackgroundFunction_sptr>> bkgd_functions(
1627 m_numSpectraToFit, std::vector<IBackgroundFunction_sptr>(m_numPeaksToFit));
1628
1629 for (size_t iws = m_startWorkspaceIndex; iws <= m_stopWorkspaceIndex; ++iws) {
1630 const size_t output_iws = iws - m_startWorkspaceIndex;
1631 const std::shared_ptr<FitPeaksAlgorithm::PeakFitResult> &fit_result_i = fit_results[output_iws];
1632 // FIXME - This is a just a pure check
1633 if (!fit_result_i)
1634 throw std::runtime_error("There is something wroing with PeakFitResult vector!");
1635
1636 for (size_t ipeak = 0; ipeak < m_numPeaksToFit; ++ipeak) {
1637 const double chi2 = fit_result_i->getCost(ipeak);
1638 if (chi2 > 10.e10)
1639 continue;
1640
1641 IPeakFunction_sptr peak_function = std::dynamic_pointer_cast<IPeakFunction>(m_peakFunction->clone());
1642 IBackgroundFunction_sptr bkgd_function = std::dynamic_pointer_cast<IBackgroundFunction>(m_bkgdFunction->clone());
1643
1644 for (size_t iparam = 0; iparam < num_peakfunc_params; ++iparam)
1645 peak_function->setParameter(iparam, fit_result_i->getParameterValue(ipeak, iparam));
1646 for (size_t iparam = 0; iparam < num_bkgdfunc_params; ++iparam)
1647 bkgd_function->setParameter(iparam, fit_result_i->getParameterValue(ipeak, num_peakfunc_params + iparam));
1648
1649 const std::pair<double, double> peakwindow = m_getPeakFitWindow(iws, ipeak);
1650 peak_function->setMatrixWorkspace(m_inputMatrixWS, iws, peakwindow.first, peakwindow.second);
1651
1652 peak_functions[output_iws][ipeak] = std::move(peak_function);
1653 bkgd_functions[output_iws][ipeak] = std::move(bkgd_function);
1654 }
1655 }
1656
1658 for (int64_t iiws = m_startWorkspaceIndex; iiws <= static_cast<int64_t>(m_stopWorkspaceIndex); ++iiws) {
1660 const std::size_t iws = static_cast<std::size_t>(iiws);
1661 const size_t output_iws = iws - m_startWorkspaceIndex;
1662
1663 for (size_t ipeak = 0; ipeak < m_numPeaksToFit; ++ipeak) {
1664 if (!peak_functions[output_iws][ipeak] || !bkgd_functions[output_iws][ipeak])
1665 continue;
1666
1667 // use domain and function to calculate
1668 // get the range of start and stop to construct a function domain
1669 const auto vec_x = m_fittedPeakWS->points(iws);
1670 const std::pair<double, double> peakwindow = m_getPeakFitWindow(iws, ipeak);
1671 auto start_x_iter = std::lower_bound(vec_x.begin(), vec_x.end(), peakwindow.first);
1672 auto stop_x_iter = std::lower_bound(vec_x.begin(), vec_x.end(), peakwindow.second);
1673
1674 if (start_x_iter == stop_x_iter)
1675 throw std::runtime_error("Range size is zero in calculateFittedPeaks");
1676
1677 FunctionDomain1DVector domain(start_x_iter, stop_x_iter);
1678 FunctionValues values(domain);
1679 CompositeFunction_sptr comp_func = std::make_shared<API::CompositeFunction>();
1680 comp_func->addFunction(std::move(peak_functions[output_iws][ipeak]));
1681 comp_func->addFunction(std::move(bkgd_functions[output_iws][ipeak]));
1682 comp_func->function(domain, values);
1683
1684 // copy over the values
1685 std::size_t istart = static_cast<size_t>(start_x_iter - vec_x.begin());
1686 std::size_t istop = static_cast<size_t>(stop_x_iter - vec_x.begin());
1687 for (std::size_t yindex = istart; yindex < istop; ++yindex) {
1688 m_fittedPeakWS->mutableY(iws)[yindex] = values.getCalculated(yindex - istart);
1689 }
1690 } // END-FOR (ipeak)
1692 } // END-FOR (iws)
1694
1695 return;
1696}
1697
1698double FitPeaks::calculateSignalToSigmaRatio(const size_t &iws, const std::pair<double, double> &peakWindow,
1699 const API::IPeakFunction_sptr &peakFunction) {
1700 const auto vecX = m_inputMatrixWS->points(iws);
1701 auto startX = std::lower_bound(vecX.begin(), vecX.end(), peakWindow.first);
1702 auto stopX = std::lower_bound(vecX.begin(), vecX.end(), peakWindow.second);
1703
1704 FunctionDomain1DVector domain(startX, stopX);
1705 FunctionValues values(domain);
1706
1707 peakFunction->function(domain, values);
1708 auto peakValues = values.toVector();
1709
1710 const auto &errors = m_inputMatrixWS->e(iws);
1711 auto startE = errors.begin() + (startX - vecX.begin());
1712 auto stopE = errors.begin() + (stopX - vecX.begin());
1713 std::vector<double> peakErrors(startE, stopE);
1714
1715 double peakSum = std::accumulate(peakValues.cbegin(), peakValues.cend(), 0.0);
1716 double sigma = sqrt(std::accumulate(peakErrors.cbegin(), peakErrors.cend(), 0.0, VectorHelper::SumSquares<double>()));
1717
1718 return peakSum / ((sigma == 0) ? 1 : sigma);
1719}
1720
1721namespace {
1722bool estimateBackgroundParameters(const Histogram &histogram, const std::pair<size_t, size_t> &peak_window,
1723 const API::IBackgroundFunction_sptr &bkgd_function) {
1724 // for estimating background parameters
1725 // 0 = constant, 1 = linear
1726 const auto POLYNOMIAL_ORDER = std::min<size_t>(1, bkgd_function->nParams());
1727
1728 if (peak_window.first >= peak_window.second)
1729 throw std::runtime_error("Invalid peak window");
1730
1731 // reset the background function
1732 const auto nParams = bkgd_function->nParams();
1733 for (size_t i = 0; i < nParams; ++i)
1734 bkgd_function->setParameter(i, 0.);
1735
1736 // 10 is a magic number that worked in a variety of situations
1737 const size_t iback_start = peak_window.first + 10;
1738 const size_t iback_stop = peak_window.second - 10;
1739
1740 // use the simple way to find linear background
1741 // there aren't enough bins in the window to try to estimate so just leave the
1742 // estimate at zero
1743 if (iback_start < iback_stop) {
1744 double bkgd_a0{0.}; // will be fit
1745 double bkgd_a1{0.}; // may be fit
1746 double bkgd_a2{0.}; // will be ignored
1747 double chisq{DBL_MAX}; // how well the fit worked
1748 HistogramData::estimateBackground(POLYNOMIAL_ORDER, histogram, peak_window.first, peak_window.second, iback_start,
1749 iback_stop, bkgd_a0, bkgd_a1, bkgd_a2, chisq);
1750 // update the background function with the result
1751 bkgd_function->setParameter(0, bkgd_a0);
1752 if (nParams > 1)
1753 bkgd_function->setParameter(1, bkgd_a1);
1754 // quadratic term is always estimated to be zero
1755
1756 // TODO: return false if chisq is too large
1757 return true;
1758 }
1759
1760 return false; // too few data points for the fit
1761}
1762} // anonymous namespace
1763
1764//----------------------------------------------------------------------------------------------
1770bool FitPeaks::isObservablePeakProfile(const std::string &peakprofile) {
1771 return (std::find(supported_peak_profiles.begin(), supported_peak_profiles.end(), peakprofile) !=
1772 supported_peak_profiles.end());
1773}
1774
1775//----------------------------------------------------------------------------------------------
1778bool FitPeaks::fitBackground(const size_t &ws_index, const std::pair<double, double> &fit_window,
1779 const double &expected_peak_pos, const API::IBackgroundFunction_sptr &bkgd_func) {
1780 constexpr size_t MIN_POINTS{10}; // TODO explain why 10
1781
1782 // find out how to fit background
1783 const auto histogram = m_inputMatrixWS->histogram(ws_index);
1784 const auto &points = histogram.points();
1785 size_t start_index = findXIndex(points.rawData(), fit_window.first);
1786 size_t expected_peak_index = findXIndex(points.rawData(), expected_peak_pos, start_index);
1787 size_t stop_index = findXIndex(points.rawData(), fit_window.second, expected_peak_index);
1788
1789 // treat 5 as a magic number - TODO explain why
1790 bool good_fit(false);
1791 if (expected_peak_index - start_index > MIN_POINTS && stop_index - expected_peak_index > MIN_POINTS) {
1792 // enough data points left for multi-domain fitting
1793 // set a smaller fit window
1794 const std::pair<double, double> vec_min{fit_window.first, points[expected_peak_index + 5]};
1795 const std::pair<double, double> vec_max{points[expected_peak_index - 5], fit_window.second};
1796
1797 // reset background function value
1798 for (size_t n = 0; n < bkgd_func->nParams(); ++n)
1799 bkgd_func->setParameter(n, 0);
1800
1801 double chi2 = fitFunctionMD(bkgd_func, m_inputMatrixWS, ws_index, vec_min, vec_max);
1802
1803 // process
1804 if (chi2 < DBL_MAX - 1) {
1805 good_fit = true;
1806 }
1807
1808 } else {
1809 // fit as a single domain function. check whether the result is good or bad
1810
1811 // TODO FROM HERE!
1812 g_log.debug() << "Don't know what to do with background fitting with single "
1813 << "domain function! " << (expected_peak_index - start_index) << " points to the left "
1814 << (stop_index - expected_peak_index) << " points to the right\n";
1815 }
1816
1817 return good_fit;
1818}
1819
1820//----------------------------------------------------------------------------------------------
1823double FitPeaks::fitIndividualPeak(size_t wi, const API::IAlgorithm_sptr &fitter, const double expected_peak_center,
1824 const double peak_pos_tolerance, const std::pair<double, double> &fitwindow,
1825 const bool estimate_peak_width, const API::IPeakFunction_sptr &peakfunction,
1826 const API::IBackgroundFunction_sptr &bkgdfunc,
1827 const std::shared_ptr<FitPeaksAlgorithm::PeakFitPreCheckResult> &pre_check_result) {
1828 pre_check_result->setNumberOfSubmittedIndividualPeaks(1);
1829 double cost(DBL_MAX);
1830
1831 // make sure the number of data points satisfies the number of fitting parameters plus a magic cushion of 2.
1832 size_t min_required_datapoints{peakfunction->nParams() + bkgdfunc->nParams() + 2};
1833 size_t number_of_datapoints = histRangeToDataPointCount(wi, fitwindow);
1834 if (number_of_datapoints < min_required_datapoints) {
1835 pre_check_result->setNumberOfPeaksWithNotEnoughDataPoints(1);
1836 return cost;
1837 }
1838
1839 // check the number of counts in the peak window
1840 if (m_minPeakTotalCount >= 0.0 && numberCounts(wi, fitwindow) <= m_minPeakTotalCount) {
1841 pre_check_result->setNumberOfIndividualPeaksWithLowCount(1);
1842 return cost;
1843 }
1844
1845 // exclude a peak with a low signal-to-noise ratio
1846 if (m_minSignalToNoiseRatio > 0.0 && calculateSignalToNoiseRatio(wi, fitwindow, bkgdfunc) < m_minSignalToNoiseRatio) {
1847 pre_check_result->setNumberOfPeaksWithLowSignalToNoise(1);
1848 return cost;
1849 }
1850
1851 if (m_highBackground) {
1852 // fit peak with high background!
1853 cost = fitFunctionHighBackground(fitter, fitwindow, wi, expected_peak_center, peak_pos_tolerance,
1854 estimate_peak_width, peakfunction, bkgdfunc);
1855 } else {
1856 // fit peak and background
1857 cost = fitFunctionSD(fitter, peakfunction, bkgdfunc, m_inputMatrixWS, wi, fitwindow, expected_peak_center,
1858 peak_pos_tolerance, estimate_peak_width, true);
1859 }
1860
1861 return cost;
1862}
1863
1864//----------------------------------------------------------------------------------------------
1869bool FitPeaks::fitStatusIsConverged(const std::string &fitStatus, const bool strict) {
1870 if (fitStatus == API::MinimizerStatus::SUCCESS)
1871 return true;
1872 if (strict)
1873 return false;
1876}
1877
1878//----------------------------------------------------------------------------------------------
1885 const API::IBackgroundFunction_sptr &bkgd_function,
1886 const API::MatrixWorkspace_sptr &dataws, size_t wsindex,
1887 const std::pair<double, double> &peak_range, const double &expected_peak_center,
1888 const double peak_pos_tolerance, bool estimate_peak_width, bool estimate_background) {
1889 std::stringstream errorid;
1890 errorid << "(WorkspaceIndex=" << wsindex << " PeakCentre=" << expected_peak_center << ")";
1891
1892 // validate peak window
1893 if (peak_range.first >= peak_range.second) {
1894 std::stringstream msg;
1895 msg << "Invalid peak window: xmin>xmax (" << peak_range.first << ", " << peak_range.second << ")" << errorid.str();
1896 throw std::runtime_error(msg.str());
1897 }
1898
1899 // determine the peak window in terms of vector indexes
1900 const auto &histogram = dataws->histogram(wsindex);
1901 const auto &vector_x = histogram.points();
1902 const auto start_index = findXIndex(vector_x, peak_range.first);
1903 const auto stop_index = findXIndex(vector_x, peak_range.second, start_index);
1904 if (start_index == stop_index)
1905 throw std::runtime_error("Range size is zero in fitFunctionSD");
1906 std::pair<size_t, size_t> peak_index_window = std::make_pair(start_index, stop_index);
1907
1908 // Estimate background
1909 if (estimate_background) {
1910 if (!estimateBackgroundParameters(histogram, peak_index_window, bkgd_function)) {
1911 return DBL_MAX;
1912 }
1913 }
1914
1915 // Estimate peak profile parameter
1916 peak_function->setCentre(expected_peak_center); // set expected position first
1917 int result = estimatePeakParameters(histogram, peak_index_window, peak_function, bkgd_function, estimate_peak_width,
1919
1920 if (result != GOOD) {
1921 peak_function->setCentre(expected_peak_center);
1922 if (result == NOSIGNAL || result == LOWPEAK) {
1923 return DBL_MAX; // exit early - don't fit
1924 }
1925 }
1926
1927 // Create the composition function
1928 CompositeFunction_sptr comp_func = std::make_shared<API::CompositeFunction>();
1929 comp_func->addFunction(peak_function);
1930 comp_func->addFunction(bkgd_function);
1931 IFunction_sptr fitfunc = std::dynamic_pointer_cast<IFunction>(comp_func);
1932
1933 // Set the properties
1934 fit->setProperty("Function", fitfunc);
1935 fit->setProperty("InputWorkspace", dataws);
1936 fit->setProperty("WorkspaceIndex", static_cast<int>(wsindex));
1937 fit->setProperty("MaxIterations", m_fitIterations); // magic number
1938 fit->setProperty("StartX", peak_range.first);
1939 fit->setProperty("EndX", peak_range.second);
1940 fit->setProperty("IgnoreInvalidData", true);
1941
1942 // Constrain mode (tolerance-based centre bound) and ConstrainPeakPositions (width-based bound)
1943 // are mutually exclusive - validateInputs rejects enabling both - so at most one branch runs.
1944 const bool constrainByTolerance = m_constrainByPositionTolerance && peak_pos_tolerance > 0.;
1945 bool positionConstrained = false;
1946 if (constrainByTolerance) {
1947 // bound the fitted centre to expected_peak_center +/- tolerance during the fit
1948 std::stringstream peak_center_constraint;
1949 peak_center_constraint << std::setprecision(std::numeric_limits<double>::max_digits10);
1950 peak_center_constraint << (expected_peak_center - peak_pos_tolerance) << " < f0."
1951 << peak_function->getCentreParameterName() << " < "
1952 << (expected_peak_center + peak_pos_tolerance);
1953 fit->setProperty("Constraints", peak_center_constraint.str());
1954 positionConstrained = true;
1955 } else if (m_constrainPeaksPosition) {
1956 // set up a constraint on peak position
1957 double peak_center = peak_function->centre();
1958 double peak_width = peak_function->fwhm();
1959 std::stringstream peak_center_constraint;
1960 peak_center_constraint << std::setprecision(std::numeric_limits<double>::max_digits10);
1961 peak_center_constraint << (peak_center - 0.5 * peak_width) << " < f0." << peak_function->getCentreParameterName()
1962 << " < " << (peak_center + 0.5 * peak_width);
1963 fit->setProperty("Constraints", peak_center_constraint.str());
1964 positionConstrained = true;
1965 }
1966
1967 // Execute fit and get result of fitting background
1968 g_log.debug() << "[E1201] FitSingleDomain Before fitting, Fit function: " << fit->asString() << "\n";
1969 errorid << " starting function [" << comp_func->asString() << "]";
1970 try {
1971 fit->execute();
1972 g_log.debug() << "[E1202] FitSingleDomain After fitting, Fit function: " << fit->asString() << "\n";
1973
1974 if (!fit->isExecuted()) {
1975 g_log.warning() << "Fitting peak SD (single domain) failed to execute. " + errorid.str();
1976 return DBL_MAX;
1977 }
1978 } catch (std::invalid_argument &e) {
1979 errorid << ": " << e.what();
1980 g_log.warning() << "\nWhile fitting " + errorid.str();
1981 return DBL_MAX; // probably the wrong thing to do
1982 }
1983
1984 // Retrieve result
1985 std::string fitStatus = fit->getProperty("OutputStatus");
1986 double chi2{std::numeric_limits<double>::max()};
1987 if (fitStatusIsConverged(fitStatus, m_strictConvergence)) {
1988 chi2 = fit->getProperty("OutputChi2overDoF");
1989 if (m_calculateUnconstrainedErrors && positionConstrained) {
1990 // re-report the parameter errors from the unconstrained cost function so the position
1991 // constraint's contribution to the Hessian does not reduce them (see the method comment)
1992 recalculateErrorsWithoutConstraint(peak_function, bkgd_function, dataws, wsindex, peak_range);
1993 }
1994 }
1995
1996 return chi2;
1997}
1998
1999//----------------------------------------------------------------------------------------------
2010 const API::IBackgroundFunction_sptr &bkgd_function,
2011 const API::MatrixWorkspace_sptr &dataws, size_t wsindex,
2012 const std::pair<double, double> &peak_range) {
2013 // fit on clones so the caller's functions (still attached to the constrained fit's composite) are
2014 // untouched, then copy the recomputed errors back. The clones carry the fitted parameter values,
2015 // and with zero iterations and no constraint the fit only evaluates the covariance at those values.
2016 IPeakFunction_sptr peak_clone = std::dynamic_pointer_cast<IPeakFunction>(peak_function->clone());
2017 IBackgroundFunction_sptr bkgd_clone = std::dynamic_pointer_cast<IBackgroundFunction>(bkgd_function->clone());
2018 CompositeFunction_sptr comp_func = std::make_shared<API::CompositeFunction>();
2019 comp_func->addFunction(peak_clone);
2020 comp_func->addFunction(bkgd_clone);
2021
2023
2024 fit->setProperty("Function", std::dynamic_pointer_cast<IFunction>(comp_func));
2025 fit->setProperty("InputWorkspace", dataws);
2026 fit->setProperty("WorkspaceIndex", static_cast<int>(wsindex));
2027 fit->setProperty("MaxIterations", 0); // evaluate errors at the fitted values; do not re-fit
2028 fit->setProperty("StartX", peak_range.first);
2029 fit->setProperty("EndX", peak_range.second);
2030 fit->setProperty("IgnoreInvalidData", true);
2031 fit->setProperty("CalcErrors", true);
2032 fit->setProperty("Minimizer", m_minimizer);
2033 fit->setProperty("CostFunction", m_costFunction);
2034
2035 try {
2036 fit->execute();
2037 } catch (const std::exception &e) {
2038 // the error re-evaluation is non-essential: keep the constrained fit's errors if it fails
2039 // rather than discarding an otherwise good fit
2040 g_log.debug() << "Unconstrained error re-evaluation failed: " << e.what() << "\n";
2041 return;
2042 }
2043 if (!fit->isExecuted())
2044 return;
2045
2046 // overwrite only the errors; the parameter values remain those of the constrained fit
2047 for (size_t i = 0; i < peak_function->nParams(); ++i)
2048 peak_function->setError(i, peak_clone->getError(i));
2049 for (size_t i = 0; i < bkgd_function->nParams(); ++i)
2050 bkgd_function->setError(i, bkgd_clone->getError(i));
2051}
2052
2053//----------------------------------------------------------------------------------------------
2055 const size_t wsindex, const std::pair<double, double> &vec_xmin,
2056 const std::pair<double, double> &vec_xmax) {
2057 // Note: after testing it is found that multi-domain Fit cannot be reused
2059 // set up background fit instance
2060 fit->setProperty("Minimizer", m_minimizer);
2061 fit->setProperty("CostFunction", m_costFunction);
2062 fit->setProperty("CalcErrors", true);
2063
2064 // This use multi-domain; but does not know how to set up IFunction_sptr
2065 // fitfunc,
2066 std::shared_ptr<MultiDomainFunction> md_function = std::make_shared<MultiDomainFunction>();
2067
2068 // Set function first
2069 md_function->addFunction(std::move(fit_function));
2070
2071 // set domain for function with index 0 covering both sides
2072 md_function->clearDomainIndices();
2073 md_function->setDomainIndices(0, {0, 1});
2074
2075 // Set the properties
2076 fit->setProperty("Function", std::dynamic_pointer_cast<IFunction>(md_function));
2077 fit->setProperty("InputWorkspace", dataws);
2078 fit->setProperty("WorkspaceIndex", static_cast<int>(wsindex));
2079 fit->setProperty("StartX", vec_xmin.first);
2080 fit->setProperty("EndX", vec_xmax.first);
2081 fit->setProperty("InputWorkspace_1", dataws);
2082 fit->setProperty("WorkspaceIndex_1", static_cast<int>(wsindex));
2083 fit->setProperty("StartX_1", vec_xmin.second);
2084 fit->setProperty("EndX_1", vec_xmax.second);
2085 fit->setProperty("MaxIterations", m_fitIterations);
2086 fit->setProperty("IgnoreInvalidData", true);
2087
2088 // Execute
2089 fit->execute();
2090 if (!fit->isExecuted()) {
2091 throw runtime_error("Fit is not executed on multi-domain function/data. ");
2092 }
2093
2094 // Retrieve result
2095 std::string fitStatus = fit->getProperty("OutputStatus");
2096
2097 double chi2 = DBL_MAX;
2098 if (fitStatusIsConverged(fitStatus, m_strictConvergence)) {
2099 chi2 = fit->getProperty("OutputChi2overDoF");
2100 }
2101
2102 return chi2;
2103}
2104
2105//----------------------------------------------------------------------------------------------
2107double FitPeaks::fitFunctionHighBackground(const IAlgorithm_sptr &fit, const std::pair<double, double> &fit_window,
2108 const size_t &ws_index, const double &expected_peak_center,
2109 const double peak_pos_tolerance, bool observe_peak_shape,
2110 const API::IPeakFunction_sptr &peakfunction,
2111 const API::IBackgroundFunction_sptr &bkgdfunc) {
2113
2114 // high background to reduce
2115 API::IBackgroundFunction_sptr high_bkgd_function =
2116 std::dynamic_pointer_cast<API::IBackgroundFunction>(m_linearBackgroundFunction->clone());
2117
2118 // Fit the background first if there is enough data points
2119 fitBackground(ws_index, fit_window, expected_peak_center, high_bkgd_function);
2120
2121 // Get partial of the data
2122 std::vector<double> vec_x, vec_y, vec_e;
2123 getRangeData(ws_index, fit_window, vec_x, vec_y, vec_e);
2124
2125 // Reduce the background
2126 reduceByBackground(high_bkgd_function, vec_x, vec_y);
2127 for (std::size_t n = 0; n < bkgdfunc->nParams(); ++n)
2128 bkgdfunc->setParameter(n, 0);
2129
2130 // Create a new workspace
2131 API::MatrixWorkspace_sptr reduced_bkgd_ws = createMatrixWorkspace(vec_x, vec_y, vec_e);
2132
2133 // Fit peak with background. This intermediate fit on background-reduced data only refines the
2134 // peak shape to seed the final fit, so it is left unconstrained (-1.0); the tolerance constraint
2135 // is anchored to expected_peak_center and applied on the authoritative final fit below.
2136 fitFunctionSD(fit, peakfunction, bkgdfunc, reduced_bkgd_ws, 0, {vec_x.front(), vec_x.back()}, expected_peak_center,
2137 -1.0, observe_peak_shape, false);
2138
2139 // add the reduced background back
2140 bkgdfunc->setParameter(0, bkgdfunc->getParameter(0) + high_bkgd_function->getParameter(0));
2141 bkgdfunc->setParameter(1, bkgdfunc->getParameter(1) + // TODO doesn't work for flat background
2142 high_bkgd_function->getParameter(1));
2143
2144 double cost = fitFunctionSD(fit, peakfunction, bkgdfunc, m_inputMatrixWS, ws_index, {vec_x.front(), vec_x.back()},
2145 expected_peak_center, peak_pos_tolerance, false, false);
2146
2147 return cost;
2148}
2149
2150//----------------------------------------------------------------------------------------------
2153 const std::vector<double> &vec_y,
2154 const std::vector<double> &vec_e) {
2155 std::size_t size = vec_x.size();
2156 std::size_t ysize = vec_y.size();
2157
2158 HistogramBuilder builder;
2159 builder.setX(size);
2160 builder.setY(ysize);
2161 MatrixWorkspace_sptr matrix_ws = create<Workspace2D>(1, builder.build());
2162
2163 auto &dataX = matrix_ws->mutableX(0);
2164 auto &dataY = matrix_ws->mutableY(0);
2165 auto &dataE = matrix_ws->mutableE(0);
2166
2167 dataX.assign(vec_x.cbegin(), vec_x.cend());
2168 dataY.assign(vec_y.cbegin(), vec_y.cend());
2169 dataE.assign(vec_e.cbegin(), vec_e.cend());
2170 return matrix_ws;
2171}
2172
2173//----------------------------------------------------------------------------------------------
2177 // create output workspace for peak positions: can be partial spectra to input
2178 // workspace
2179 m_outputPeakPositionWorkspace = create<Workspace2D>(m_numSpectraToFit, Points(m_numPeaksToFit));
2180 // set default
2181 for (std::size_t wi = 0; wi < m_numSpectraToFit; ++wi) {
2182 // convert to workspace index of input data workspace
2183 std::size_t inp_wi = wi + m_startWorkspaceIndex;
2184 std::vector<double> expected_position = m_getExpectedPeakPositions(inp_wi);
2185 for (std::size_t ipeak = 0; ipeak < expected_position.size(); ++ipeak) {
2186 m_outputPeakPositionWorkspace->mutableX(wi)[ipeak] = expected_position[ipeak];
2187 }
2188 }
2189
2190 return;
2191}
2192
2193//----------------------------------------------------------------------------------------------
2201 const std::vector<std::string> &param_names, bool with_chi2) {
2202 // add columns
2203 table_ws->addColumn("int", "wsindex");
2204 table_ws->addColumn("int", "peakindex");
2205 for (const auto &param_name : param_names)
2206 table_ws->addColumn("double", param_name);
2207 if (with_chi2)
2208 table_ws->addColumn("double", "chi2");
2209
2210 // add rows
2211 const size_t numParam = m_fittedParamTable->columnCount() - 3;
2212 for (size_t iws = m_startWorkspaceIndex; iws <= m_stopWorkspaceIndex; ++iws) {
2213 for (size_t ipeak = 0; ipeak < m_numPeaksToFit; ++ipeak) {
2214 API::TableRow newRow = table_ws->appendRow();
2215 newRow << static_cast<int>(iws); // workspace index
2216 newRow << static_cast<int>(ipeak); // peak number
2217 for (size_t iparam = 0; iparam < numParam; ++iparam)
2218 newRow << 0.; // parameters for each peak
2219 if (with_chi2)
2220 newRow << DBL_MAX; // chisq
2221 }
2222 }
2223
2224 return;
2225}
2226
2227//----------------------------------------------------------------------------------------------
2233 // peak parameter workspace
2234 m_rawPeaksTable = getProperty(PropertyNames::RAW_PARAMS);
2235
2236 // create parameters
2237 // peak
2238 std::vector<std::string> param_vec;
2239 if (m_rawPeaksTable) {
2240 param_vec = m_peakFunction->getParameterNames();
2241 } else {
2242 param_vec.emplace_back("centre");
2243 param_vec.emplace_back("width");
2244 param_vec.emplace_back("height");
2245 param_vec.emplace_back("intensity");
2246 }
2247 // background
2248 for (size_t iparam = 0; iparam < m_bkgdFunction->nParams(); ++iparam)
2249 param_vec.emplace_back(m_bkgdFunction->parameterName(iparam));
2250
2251 // parameter value table
2252 m_fittedParamTable = std::make_shared<TableWorkspace>();
2254
2255 // for error workspace
2256 std::string fiterror_table_name = getPropertyValue(PropertyNames::OUTPUT_WKSP_PARAM_ERRS);
2257 // do nothing if user does not specifiy
2258 if (fiterror_table_name.empty()) {
2259 // not specified
2260 m_fitErrorTable = nullptr;
2261 } else {
2262 // create table and set up parameter table
2263 m_fitErrorTable = std::make_shared<TableWorkspace>();
2265 }
2266
2267 return;
2268}
2269
2270//----------------------------------------------------------------------------------------------
2275 // matrix workspace contained calculated peaks from fitting
2276 std::string fit_ws_name = getPropertyValue(PropertyNames::OUTPUT_WKSP_MODEL);
2277 if (fit_ws_name.size() == 0) {
2278 // skip if user does not specify
2279 m_fittedPeakWS = nullptr;
2280 return;
2281 }
2282
2283 // create a wokspace with same size as in the input matrix workspace
2284 m_fittedPeakWS = create<Workspace2D>(*m_inputMatrixWS);
2285}
2286
2287//----------------------------------------------------------------------------------------------
2289void FitPeaks::processOutputs(std::vector<std::shared_ptr<FitPeaksAlgorithm::PeakFitResult>> fit_result_vec) {
2291 setProperty(PropertyNames::OUTPUT_WKSP_PARAMS, m_fittedParamTable);
2292
2293 if (m_fitErrorTable) {
2294 g_log.warning("Output error table workspace");
2295 setProperty(PropertyNames::OUTPUT_WKSP_PARAM_ERRS, m_fitErrorTable);
2296 } else {
2297 g_log.warning("No error table output");
2298 }
2299
2300 // optional
2302 g_log.debug("about to calcualte fitted peaks");
2303 calculateFittedPeaks(std::move(fit_result_vec));
2304 setProperty(PropertyNames::OUTPUT_WKSP_MODEL, m_fittedPeakWS);
2305 }
2306}
2307
2308//----------------------------------------------------------------------------------------------
2313double FitPeaks::numberCounts(size_t iws) {
2314 const Histogram histogram = m_inputMatrixWS->histogram(iws);
2315 const auto &vec_y = histogram.y().rawData();
2316 double total = std::accumulate(vec_y.begin(), vec_y.end(), 0.);
2317 return total;
2318}
2319
2320//----------------------------------------------------------------------------------------------
2326double FitPeaks::numberCounts(size_t iws, const std::pair<double, double> &range) {
2327 // get data range
2328 std::vector<double> vec_x, vec_y, vec_e;
2329 getRangeData(iws, range, vec_x, vec_y, vec_e);
2330 // sum up all counts
2331 double total = std::accumulate(vec_y.begin(), vec_y.end(), 0.);
2332 return total;
2333}
2334
2335//----------------------------------------------------------------------------------------------
2341size_t FitPeaks::histRangeToDataPointCount(size_t iws, const std::pair<double, double> &range) {
2342 size_t left_index, right_index;
2343 histRangeToIndexBounds(iws, range, left_index, right_index);
2344 size_t number_dp = right_index - left_index + 1;
2345 if (m_inputMatrixWS->isHistogramData())
2346 number_dp -= 1;
2347 assert(number_dp > 0);
2348 return number_dp;
2349}
2350
2351//----------------------------------------------------------------------------------------------
2358void FitPeaks::histRangeToIndexBounds(size_t iws, const std::pair<double, double> &range, size_t &left_index,
2359 size_t &right_index) {
2360 const Histogram histogram = m_inputMatrixWS->histogram(iws);
2361 const auto &orig_x = histogram.x();
2362 rangeToIndexBounds(orig_x, range.first, range.second, left_index, right_index);
2363
2364 // handle an invalid range case. For the histogram point data, make sure the number of data points is non-zero as
2365 // well.
2366 if (left_index >= right_index || (m_inputMatrixWS->isHistogramData() && left_index == right_index - 1)) {
2367 std::stringstream err_ss;
2368 err_ss << "Unable to get a valid subset of histogram from given fit window. "
2369 << "Histogram X: " << orig_x.front() << "," << orig_x.back() << "; Range: " << range.first << ","
2370 << range.second;
2371 throw std::runtime_error(err_ss.str());
2372 }
2373}
2374
2375//----------------------------------------------------------------------------------------------
2383void FitPeaks::getRangeData(size_t iws, const std::pair<double, double> &range, std::vector<double> &vec_x,
2384 std::vector<double> &vec_y, std::vector<double> &vec_e) {
2385 // convert range to index boundaries
2386 size_t left_index, right_index;
2387 histRangeToIndexBounds(iws, range, left_index, right_index);
2388
2389 // copy X, Y and E
2390 size_t num_elements_x = right_index - left_index;
2391
2392 vec_x.resize(num_elements_x);
2393 const Histogram histogram = m_inputMatrixWS->histogram(iws);
2394 const auto &orig_x = histogram.x();
2395 std::copy(orig_x.begin() + left_index, orig_x.begin() + right_index, vec_x.begin());
2396
2397 size_t num_datapoints = m_inputMatrixWS->isHistogramData() ? num_elements_x - 1 : num_elements_x;
2398
2399 const auto &orig_y = histogram.y().rawData();
2400 const auto &orig_e = histogram.e().rawData();
2401 vec_y.resize(num_datapoints);
2402 vec_e.resize(num_datapoints);
2403 std::copy(orig_y.begin() + left_index, orig_y.begin() + left_index + num_datapoints, vec_y.begin());
2404 std::copy(orig_e.begin() + left_index, orig_e.begin() + left_index + num_datapoints, vec_e.begin());
2405}
2406
2407//----------------------------------------------------------------------------------------------
2414double FitPeaks::calculateSignalToNoiseRatio(size_t iws, const std::pair<double, double> &range,
2415 const API::IBackgroundFunction_sptr &bkgd_function) {
2416 // convert range to index boundaries
2417 size_t left_index, right_index;
2418 histRangeToIndexBounds(iws, range, left_index, right_index);
2419
2420 // estimate background level by Y(X) fitting
2421 if (!estimateBackgroundParameters(m_inputMatrixWS->histogram(iws), std::pair<size_t, size_t>(left_index, right_index),
2422 bkgd_function))
2423 return 0.0; // failed to estimate background parameters
2424
2425 // get X,Y,and E for the data range
2426 std::vector<double> vec_x, vec_y, vec_e;
2427 getRangeData(iws, range, vec_x, vec_y, vec_e);
2428 if (vec_x.empty())
2429 return 0.0;
2430
2431 // subtract background from Y-values
2432 reduceByBackground(bkgd_function, vec_x, vec_y);
2433
2434 // estimate the signal as the highest Y-value in the data range
2435 auto it_max = std::max_element(vec_y.begin(), vec_y.end());
2436 double signal = vec_y[it_max - vec_y.begin()];
2437 if (signal <= DBL_MIN)
2438 return 0.0;
2439
2440 // estimate noise from background. If noise is zero, or impossible to estimate, return DBL_MAX so that the peak
2441 // won't be rejected.
2442 double noise = estimateBackgroundNoise(vec_y);
2443 if (noise <= DBL_MIN)
2444 return DBL_MAX;
2445
2446 // finally, calculate the signal-to-noise ratio
2447 return signal / noise;
2448}
2449
2450//----------------------------------------------------------------------------------------------
2452
2453void FitPeaks::checkWorkspaceIndices(std::size_t const &wi) {
2454 if (wi < m_startWorkspaceIndex || wi > m_stopWorkspaceIndex) {
2455 std::stringstream errss;
2456 errss << "Workspace index " << wi << " is out of range "
2457 << "[" << m_startWorkspaceIndex << ", " << m_stopWorkspaceIndex << "]";
2458 throw std::runtime_error(errss.str());
2459 }
2460}
2461
2462void FitPeaks::checkPeakIndices(std::size_t const &wi, std::size_t const &ipeak) {
2463 // check peak index
2464 if (ipeak >= m_getExpectedPeakPositions(wi).size()) {
2465 std::stringstream errss;
2466 errss << "Peak index " << ipeak << " is out of range (" << m_numPeaksToFit << ")";
2467 throw std::runtime_error(errss.str());
2468 }
2469}
2470
2471void FitPeaks::checkPeakWindowEdgeOrder(double const &left, double const &right) {
2472 if (left >= right) {
2473 std::stringstream errss;
2474 errss << "Peak window is inappropriate for workspace index: " << left << " >= " << right;
2475 throw std::runtime_error(errss.str());
2476 }
2477}
2478
2479//---------------------------------------------------------------------------------------------
2485 try {
2486 return createChildAlgorithm("Fit", -1, -1, false);
2487 } catch (Exception::NotFoundError &) {
2488 std::stringstream errss;
2489 errss << "The FitPeaks algorithm requires the CurveFitting library";
2490 g_log.error(errss.str());
2491 throw std::runtime_error(errss.str());
2492 }
2493}
2494
2495//----------------------------------------------------------------------------------------------
2504void FitPeaks::writeFitResult(size_t wi, const std::vector<double> &expected_positions,
2505 const std::shared_ptr<FitPeaksAlgorithm::PeakFitResult> &fit_result) {
2506 // convert to
2507 size_t out_wi = wi - m_startWorkspaceIndex;
2508 if (out_wi >= m_outputPeakPositionWorkspace->getNumberHistograms()) {
2509 g_log.error() << "workspace index " << wi << " is out of output peak position workspace "
2510 << "range of spectra, which contains " << m_outputPeakPositionWorkspace->getNumberHistograms()
2511 << " spectra"
2512 << "\n";
2513 throw std::runtime_error("Out of boundary to set output peak position workspace");
2514 }
2515
2516 // Fill the output peak position workspace
2517 for (size_t ipeak = 0; ipeak < m_numPeaksToFit; ++ipeak) {
2518 double exp_peak_pos(expected_positions[ipeak]);
2519 double fitted_peak_pos = fit_result->getPeakPosition(ipeak);
2520 double peak_chi2 = fit_result->getCost(ipeak);
2521
2522 m_outputPeakPositionWorkspace->mutableX(out_wi)[ipeak] = exp_peak_pos;
2523 m_outputPeakPositionWorkspace->mutableY(out_wi)[ipeak] = fitted_peak_pos;
2524 m_outputPeakPositionWorkspace->mutableE(out_wi)[ipeak] = peak_chi2;
2525 }
2526
2527 // Output the peak parameters to the table workspace
2528 // check vector size
2529
2530 // last column of the table is for chi2
2531 size_t chi2_index = m_fittedParamTable->columnCount() - 1;
2532
2533 // check TableWorkspace and given FitResult
2534 if (m_rawPeaksTable) {
2535 // duplicate from FitPeakResult to table workspace
2536 // check again with the column size versus peak parameter values
2537 if (fit_result->getNumberParameters() != m_fittedParamTable->columnCount() - 3) {
2538 g_log.error() << "Peak of type (" << m_peakFunction->name() << ") has " << fit_result->getNumberParameters()
2539 << " parameters. Parameter table shall have 3 more "
2540 "columns. But not it has "
2541 << m_fittedParamTable->columnCount() << " columns\n";
2542 throw std::runtime_error("Peak parameter vector for one peak has different sizes to output "
2543 "table workspace");
2544 }
2545 } else {
2546 // effective peak profile parameters: need to re-construct the peak function
2547 if (4 + m_bkgdFunction->nParams() != m_fittedParamTable->columnCount() - 3) {
2548
2549 std::stringstream err_ss;
2550 err_ss << "Peak has 4 effective peak parameters and " << m_bkgdFunction->nParams() << " background parameters "
2551 << ". Parameter table shall have 3 more columns. But not it has " << m_fittedParamTable->columnCount()
2552 << " columns";
2553 throw std::runtime_error(err_ss.str());
2554 }
2555 }
2556
2557 // go through each peak
2558 // get a copy of peak function and background function
2559 IPeakFunction_sptr peak_function = std::dynamic_pointer_cast<IPeakFunction>(m_peakFunction->clone());
2560 size_t num_peakfunc_params = peak_function->nParams();
2561 size_t num_bkgd_params = m_bkgdFunction->nParams();
2562
2563 for (size_t ipeak = 0; ipeak < m_numPeaksToFit; ++ipeak) {
2564 // get row number
2565 size_t row_index = out_wi * m_numPeaksToFit + ipeak;
2566
2567 // treat as different cases for writing out raw or effective parametr
2568 if (m_rawPeaksTable) {
2569 // duplicate from FitPeakResult to table workspace
2570 for (size_t iparam = 0; iparam < num_peakfunc_params + num_bkgd_params; ++iparam) {
2571 size_t col_index = iparam + 2;
2572 // fitted parameter's value
2573 m_fittedParamTable->cell<double>(row_index, col_index) = fit_result->getParameterValue(ipeak, iparam);
2574 // fitted parameter's fitting error
2575 if (m_fitErrorTable) {
2576 m_fitErrorTable->cell<double>(row_index, col_index) = fit_result->getParameterError(ipeak, iparam);
2577 }
2578
2579 } // end for (iparam)
2580 } else {
2581 // effective peak profile parameter
2582 // construct the peak function
2583 for (size_t iparam = 0; iparam < num_peakfunc_params; ++iparam)
2584 peak_function->setParameter(iparam, fit_result->getParameterValue(ipeak, iparam));
2585
2586 const std::pair<double, double> peak_window = m_getPeakFitWindow(wi, ipeak);
2587 peak_function->setMatrixWorkspace(m_inputMatrixWS, wi, peak_window.first, peak_window.second);
2588
2589 // set the effective peak parameters
2590 m_fittedParamTable->cell<double>(row_index, 2) = peak_function->centre();
2591 m_fittedParamTable->cell<double>(row_index, 3) = peak_function->fwhm();
2592 m_fittedParamTable->cell<double>(row_index, 4) = peak_function->height();
2593 m_fittedParamTable->cell<double>(row_index, 5) = peak_function->intensity();
2594
2595 // background
2596 for (size_t iparam = 0; iparam < num_bkgd_params; ++iparam)
2597 m_fittedParamTable->cell<double>(row_index, 6 + iparam) =
2598 fit_result->getParameterValue(ipeak, num_peakfunc_params + iparam);
2599 }
2600
2601 // set chi2
2602 m_fittedParamTable->cell<double>(row_index, chi2_index) = fit_result->getCost(ipeak);
2603 }
2604
2605 return;
2606}
2607
2608//----------------------------------------------------------------------------------------------
2610 std::string height_name("");
2611
2612 std::vector<std::string> peak_parameters = peak_function->getParameterNames();
2613 for (const auto &parName : peak_parameters) {
2614 if (parName == "Height") {
2615 height_name = "Height";
2616 break;
2617 } else if (parName == "I") {
2618 height_name = "I";
2619 break;
2620 } else if (parName == "Intensity") {
2621 height_name = "Intensity";
2622 break;
2623 }
2624 }
2625
2626 if (height_name.empty())
2627 throw std::runtime_error("Peak height parameter name cannot be found.");
2628
2629 return height_name;
2630}
2631
2632// A client, like PDCalibration, may set a logging offset to make FitPeaks less "chatty".
2633// This method temporarily removes the logging offset and logs the message at its priority level.
2634void FitPeaks::logNoOffset(const size_t &priority, const std::string &msg) {
2635 LoggingOffsetSentry sentry(this);
2636
2637 switch (priority) {
2638 case 4: // warning
2639 g_log.warning() << msg;
2640 break;
2641 case 5: // notice
2642 g_log.notice() << msg;
2643 break;
2644 default:
2645 assert(false); // not implemented yet
2646 }
2647}
2648
2650
2651} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:542
size_t istart
double value
The value of the point.
Definition FitMW.cpp:51
Algorithm *const m_alg
int m_loggingOffset
double left
double right
#define fabs(x)
Definition Matrix.cpp:22
#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_CRITICAL(name)
#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 PRAGMA_OMP(expression)
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
Base class from which all concrete algorithm classes should be derived.
Definition Algorithm.h:76
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.
void setLoggingOffset(const int value) override
gets the logging priority offset
Kernel::Logger & g_log
Definition Algorithm.h:423
int getLoggingOffset() const override
returns the logging priority offset
bool isDefault(const std::string &name) const
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
Implements FunctionDomain1D with its own storage in form of a std::vector.
A class to store values calculated by a function.
const std::vector< double > & toVector() const
Return the calculated values as a vector.
double getCalculated(size_t i) const
Get i-th calculated value.
An interface to a peak function, which extend the interface of IFunctionWithLocation by adding method...
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.
PeakFitPreCheckResult & operator+=(const PeakFitPreCheckResult &another)
Definition FitPeaks.cpp:208
size_t m_function_parameters_number
number of function parameters
Definition FitPeaks.h:53
std::vector< std::vector< double > > m_function_parameters_vector
Definition FitPeaks.h:59
PeakFitResult(size_t num_peaks, size_t num_params)
Holds all of the fitting information for a single spectrum.
Definition FitPeaks.cpp:104
double getParameterValue(size_t ipeak, size_t iparam) const
get the fitted value of a particular parameter
Definition FitPeaks.cpp:145
std::vector< std::vector< double > > m_function_errors_vector
fitted peak and background parameters' fitting error
Definition FitPeaks.h:61
void setRecord(size_t ipeak, const double cost, const double peak_position, const FitFunction &fit_functions)
set the peak fitting record/parameter for one peak
Definition FitPeaks.cpp:157
double getParameterError(size_t ipeak, size_t iparam) const
get the fitting error of a particular parameter
Definition FitPeaks.cpp:134
void setBadRecord(size_t ipeak, const double peak_position)
The peak postition should be negative and indicates what went wrong.
Definition FitPeaks.cpp:188
Algorithms::PeakParameterHelper::EstimatePeakWidth m_peakWidthEstimateApproach
Flag for observing peak width: there are 3 states (1) no estimation (2) from 'observation' (3) calcul...
Definition FitPeaks.h:339
void calculateFittedPeaks(const std::vector< std::shared_ptr< FitPeaksAlgorithm::PeakFitResult > > &fit_results)
calculate peak+background for fitted
double calculateSignalToNoiseRatio(size_t iws, const std::pair< double, double > &range, const API::IBackgroundFunction_sptr &bkgd_function)
calculate signal-to-noise ratio in histogram range
API::MatrixWorkspace_const_sptr m_peakCenterWorkspace
Definition FitPeaks.h:313
void generateFittedParametersValueWorkspaces()
Generate output workspaces.
void setupParameterTableWorkspace(const API::ITableWorkspace_sptr &table_ws, const std::vector< std::string > &param_names, bool with_chi2)
Set up parameter table (parameter value or error)
std::vector< double > m_peakPosTolerances
tolerances for fitting peak positions
Definition FitPeaks.h:335
API::IPeakFunction_sptr m_peakFunction
Peak profile name.
Definition FitPeaks.h:283
bool fitBackground(const size_t &ws_index, const std::pair< double, double > &fit_window, const double &expected_peak_pos, const API::IBackgroundFunction_sptr &bkgd_func)
fit background
API::MatrixWorkspace_sptr m_outputPeakPositionWorkspace
output workspace for peak positions
Definition FitPeaks.h:267
API::MatrixWorkspace_sptr m_fittedPeakWS
matrix workspace contained calcalated peaks+background from fitted result it has same number of spect...
Definition FitPeaks.h:279
bool m_strictConvergence
Require an exact 'success' status to accept a fit, rather than also accepting the "changes too small"...
Definition FitPeaks.h:295
API::ITableWorkspace_const_sptr m_profileStartingValueTable
table workspace for profile parameters' starting value
Definition FitPeaks.h:366
std::string m_minimizer
Minimzer.
Definition FitPeaks.h:290
API::IBackgroundFunction_sptr m_linearBackgroundFunction
Linear background function for high background fitting.
Definition FitPeaks.h:287
double fitFunctionHighBackground(const API::IAlgorithm_sptr &fit, const std::pair< double, double > &fit_window, const size_t &ws_index, const double &expected_peak_center, const double peak_pos_tolerance, bool observe_peak_shape, const API::IPeakFunction_sptr &peakfunction, const API::IBackgroundFunction_sptr &bkgdfunc)
fit a single peak with high background
bool m_peakPosTolCase234
peak positon tolerance case b, c and d
Definition FitPeaks.h:386
void fitSpectrumPeaks(size_t wi, const std::vector< double > &expected_peak_centers, const std::shared_ptr< FitPeaksAlgorithm::PeakFitResult > &fit_result, std::vector< std::vector< double > > &lastGoodPeakParameters, std::vector< size_t > &lastGoodPeakSpectra, const std::shared_ptr< FitPeaksAlgorithm::PeakFitPreCheckResult > &pre_check_result)
fit peaks in a same spectrum
std::map< std::string, std::string > validateInputs() override
Validate inputs.
Definition FitPeaks.cpp:503
double m_peakWidthPercentage
flag to estimate peak width from
Definition FitPeaks.h:325
API::IBackgroundFunction_sptr m_bkgdFunction
Background function.
Definition FitPeaks.h:285
size_t histRangeToDataPointCount(size_t iws, const std::pair< double, double > &range)
convert a histogram range to index boundaries
void checkPeakIndices(std::size_t const &, std::size_t const &)
void processInputPeakCenters()
peak centers
Definition FitPeaks.cpp:912
std::vector< std::shared_ptr< FitPeaksAlgorithm::PeakFitResult > > fitPeaks()
suites of method to fit peaks
double m_minPeakHeight
minimum peak height without background and it also serves as the criteria for observed peak parameter
Definition FitPeaks.h:373
std::string m_costFunction
Cost function.
Definition FitPeaks.h:292
void processInputFunctions()
process inputs for peak and background functions
Definition FitPeaks.cpp:716
void processInputPeakTolerance()
process inputs about fitted peak positions' tolerance
Definition FitPeaks.cpp:973
void writeFitResult(size_t wi, const std::vector< double > &expected_positions, const std::shared_ptr< FitPeaksAlgorithm::PeakFitResult > &fit_result)
Write result of peak fit per spectrum to output analysis workspaces.
void histRangeToIndexBounds(size_t iws, const std::pair< double, double > &range, size_t &left_index, size_t &right_index)
convert a histogram range to index boundaries
void exec() override
Main exec method.
Definition FitPeaks.cpp:609
void init() override
Init.
Definition FitPeaks.cpp:278
void logNoOffset(const size_t &priority, const std::string &msg)
bool m_calculateUnconstrainedErrors
when true, and a peak-position constraint was applied (ConstrainPeakPositions or PositionToleranceMod...
Definition FitPeaks.h:354
std::size_t m_numSpectraToFit
total number of spectra to be fit
Definition FitPeaks.h:333
std::vector< std::string > m_peakParamNames
input peak parameters' names
Definition FitPeaks.h:361
double calculateSignalToSigmaRatio(const size_t &iws, const std::pair< double, double > &peakWindow, const API::IPeakFunction_sptr &peakFunction)
bool isObservablePeakProfile(const std::string &peakprofile)
check whether FitPeaks supports observation on a certain peak profile's parameters (width!...
void processInputFitRanges()
process inputs for peak fitting range
Definition FitPeaks.cpp:773
std::size_t m_numPeaksToFit
the number of peaks to fit in all spectra
Definition FitPeaks.h:315
double fitIndividualPeak(size_t wi, const API::IAlgorithm_sptr &fitter, const double expected_peak_center, const double peak_pos_tolerance, const std::pair< double, double > &fitwindow, const bool estimate_peak_width, const API::IPeakFunction_sptr &peakfunction, const API::IBackgroundFunction_sptr &bkgdfunc, const std::shared_ptr< FitPeaksAlgorithm::PeakFitPreCheckResult > &pre_check_result)
Fit an individual peak.
std::size_t m_startWorkspaceIndex
start index
Definition FitPeaks.h:329
API::MatrixWorkspace_sptr createMatrixWorkspace(const std::vector< double > &vec_x, const std::vector< double > &vec_y, const std::vector< double > &vec_e)
Create a single spectrum workspace for fitting.
void generateOutputPeakPositionWS()
main method to create output workspaces
void generateCalculatedPeaksWS()
Generate workspace for calculated values.
static bool fitStatusIsConverged(const std::string &fitStatus, const bool strict)
Decide whether a Fit "OutputStatus" string should be treated as a converged fit.
double fitFunctionMD(API::IFunction_sptr fit_function, const API::MatrixWorkspace_sptr &dataws, const size_t wsindex, const std::pair< double, double > &vec_xmin, const std::pair< double, double > &vec_xmax)
std::string getPeakHeightParameterName(const API::IPeakFunction_const_sptr &peak_function)
Get the parameter name for peak height (I or height or etc)
API::IAlgorithm_sptr createChildFit()
Create a Fit child algorithm, with a check that the CurveFitting library is available.
API::ITableWorkspace_sptr m_fittedParamTable
output analysis workspaces table workspace for fitted parameters
Definition FitPeaks.h:270
void convertParametersNameToIndex()
Convert peak function's parameter names to parameter index for fast access.
void processOutputs(std::vector< std::shared_ptr< FitPeaksAlgorithm::PeakFitResult > > fit_result_vec)
Set the workspaces and etc to output properties.
bool m_highBackground
flag for high background
Definition FitPeaks.h:382
void recalculateErrorsWithoutConstraint(const API::IPeakFunction_sptr &peak_function, const API::IBackgroundFunction_sptr &bkgd_function, const API::MatrixWorkspace_sptr &dataws, size_t wsindex, const std::pair< double, double > &peak_range)
Re-evaluate parameter fitting errors free of any peak-position boundary constraint penalty,...
std::vector< double > m_initParamValues
input peak parameters' starting values corresponding to above peak parameter names
Definition FitPeaks.h:364
std::vector< double > m_peakCenters
Designed peak positions and tolerance.
Definition FitPeaks.h:312
std::vector< std::vector< double > > m_peakWindowVector
peak windows
Definition FitPeaks.h:357
double fitFunctionSD(const API::IAlgorithm_sptr &fit, const API::IPeakFunction_sptr &peak_function, const API::IBackgroundFunction_sptr &bkgd_function, const API::MatrixWorkspace_sptr &dataws, size_t wsindex, const std::pair< double, double > &peak_range, const double &expected_peak_center, const double peak_pos_tolerance, bool estimate_peak_width, bool estimate_background)
Methods to fit functions (general)
std::function< std::pair< double, double >(std::size_t const &, std::size_t const &)> m_getPeakFitWindow
Definition FitPeaks.h:319
API::MatrixWorkspace_sptr m_inputMatrixWS
mandatory input and output workspaces
Definition FitPeaks.h:262
std::vector< size_t > m_initParamIndexes
input starting parameters' indexes in peak function
Definition FitPeaks.h:309
bool m_fitPeaksFromRight
Fit from right or left.
Definition FitPeaks.h:297
std::function< std::vector< double >(std::size_t const &)> m_getExpectedPeakPositions
Definition FitPeaks.h:318
bool m_rawPeaksTable
flag to show that the pamarameters in table are raw parameters or effective parameters
Definition FitPeaks.h:275
void processInputs()
process inputs (main and child algorithms)
Definition FitPeaks.cpp:630
void checkWorkspaceIndices(std::size_t const &)
Get the expected peak's position.
void checkPeakWindowEdgeOrder(double const &, double const &)
bool decideToEstimatePeakParams(const bool firstPeakInSpectrum, const size_t wsindex, const API::IPeakFunction_sptr &peak_function)
Decide whether to estimate peak parameters.
int m_fitIterations
Fit iterations.
Definition FitPeaks.h:299
double numberCounts(size_t iws)
sum up all counts in histogram
bool m_fractionalPositionTolerance
when true, each PositionTolerance value is interpreted as a fraction of this peak's (per-spectrum) fi...
Definition FitPeaks.h:349
API::MatrixWorkspace_const_sptr m_peakWindowWorkspace
Definition FitPeaks.h:358
bool m_uniformProfileStartingValue
flag for profile startng value being uniform or not
Definition FitPeaks.h:368
API::ITableWorkspace_sptr m_fitErrorTable
table workspace for fitted parameters' fitting error. This is optional
Definition FitPeaks.h:272
void getRangeData(size_t iws, const std::pair< double, double > &range, std::vector< double > &vec_x, std::vector< double > &vec_y, std::vector< double > &vec_e)
get vector X, Y and E in a given range
bool m_constrainByPositionTolerance
when true, PositionTolerance is applied as an active constraint on the peak centre during fitting (bo...
Definition FitPeaks.h:344
std::size_t m_stopWorkspaceIndex
stop index (workspace index of the last spectrum included)
Definition FitPeaks.h:331
bool processSinglePeakFitResult(size_t wsindex, size_t peakindex, const double cost, const std::vector< double > &expected_peak_positions, const FitPeaksAlgorithm::FitFunction &fitfunction, const std::shared_ptr< FitPeaksAlgorithm::PeakFitResult > &fit_result)
Process the result from fitting a single peak.
Support for a property that holds an array of values.
Exception for when an item is not found in a collection.
Definition Exception.h:145
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...
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
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
StartsWithValidator is a validator that requires the value of a property to start with one of the str...
const std::string CHANGES_IN_FUNCTION_TOO_SMALL
Reported by Levenberg-Marquardt when the change in the cost function between iterations has fallen be...
const std::string CHANGES_IN_PARAMETER_TOO_SMALL
Reported by Levenberg-Marquardt when the change in the parameter values between iterations has fallen...
const std::string SUCCESS
Reported when a minimizer has fully converged.
std::shared_ptr< IAlgorithm > IAlgorithm_sptr
shared pointer to Mantid::API::IAlgorithm
std::shared_ptr< IBackgroundFunction > IBackgroundFunction_sptr
std::shared_ptr< IPeakFunction > IPeakFunction_sptr
std::shared_ptr< ITableWorkspace > ITableWorkspace_sptr
shared pointer to Mantid::API::ITableWorkspace
std::shared_ptr< const IPeakFunction > IPeakFunction_const_sptr
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< IFunction > IFunction_sptr
shared pointer to the function base class
Definition IFunction.h:748
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< CompositeFunction > CompositeFunction_sptr
shared pointer to the composite function base class
std::string const OUTPUT_WKSP("OutputWorkspace")
std::string const INPUT_WKSP("InputWorkspace")
MANTID_ALGORITHMS_DLL size_t findXIndex(const vector_like &vecx, const double x, const size_t startindex=0)
Get an index of a value in a sorted vector.
MANTID_ALGORITHMS_DLL int estimatePeakParameters(const HistogramData::Histogram &histogram, const std::pair< size_t, size_t > &peak_window, const API::IPeakFunction_sptr &peakfunction, const API::IBackgroundFunction_sptr &bkgdfunction, bool observe_peak_width, const EstimatePeakWidth peakWidthEstimateApproach, const double peakWidthPercentage, const double minPeakHeight)
Estimate peak parameters by 'observation'.
Statistics getStatistics(const std::vector< TYPE > &data, const unsigned int flags=StatOptions::AllStats)
Return a statistics object for the given data set.
std::vector< double > getZscore(const std::vector< TYPE > &data)
Return the Z score values for a dataset.
std::shared_ptr< IValidator > IValidator_sptr
A shared_ptr to an IValidator.
Definition IValidator.h:26
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.
const std::string OUTPUT_WKSP("OutputWorkspace")
const std::string INPUT_WKSP("InputWorkspace")
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
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
STL namespace.
API::IBackgroundFunction_sptr bkgdfunction
Definition FitPeaks.h:35
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54
Functor to accumulate a sum of squares.